Compare commits
92
Commits
b2c643cf73
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0602f1d21d | ||
|
|
54a05c27ab | ||
|
|
bce8d30108 | ||
|
|
8dae755df2 | ||
|
|
9105cba067 | ||
|
|
fe16e8b2f9 | ||
|
|
bc15b19474 | ||
|
|
ac72b80505 | ||
|
|
080d62621e | ||
|
|
7c9bb58806 | ||
|
|
d21a1adfb2 | ||
|
|
ac082785f4 | ||
|
|
fc640407d9 | ||
|
|
4100a51eb8 | ||
|
|
d5a159be05 | ||
|
|
4e0b283fa9 | ||
|
|
ae0dddd255 | ||
|
|
9e8db7754f | ||
|
|
201ee8c0f9 | ||
|
|
58a29f2b9d | ||
|
|
d6d2a4ad96 | ||
|
|
a5dbbc4163 | ||
|
|
026ba10bb7 | ||
|
|
a725b56ab2 | ||
|
|
f8b062711a | ||
|
|
1505226628 | ||
|
|
f9e099bc24 | ||
|
|
d7b15c99cf | ||
|
|
1aef5775a6 | ||
|
|
cd061668f9 | ||
|
|
cee1687373 | ||
|
|
28aec98414 | ||
|
|
6d379714cd | ||
|
|
8c615d01b1 | ||
|
|
d4eb597575 | ||
|
|
f8022662aa | ||
|
|
1c7d71e688 | ||
|
|
462c476141 | ||
|
|
409aac8486 | ||
|
|
4ca3356214 | ||
|
|
37fd2c2bac | ||
|
|
c7cf47c8d2 | ||
|
|
f774419140 | ||
|
|
2786b011a8 | ||
|
|
650c480416 | ||
|
|
819a2eb8ec | ||
|
|
ea9ecb770d | ||
|
|
3476d66c0d | ||
|
|
1f0158dd76 | ||
|
|
f35fad442a | ||
|
|
a505f2ddc9 | ||
|
|
42cfe0dc0f | ||
|
|
2c8e25bdf8 | ||
|
|
0992b02880 | ||
|
|
1f8a5b8c37 | ||
|
|
795a9186a9 | ||
|
|
69d4c5d038 | ||
|
|
78d496ae72 | ||
|
|
4c19f4b16c | ||
|
|
1c56423ea4 | ||
|
|
5d304b6334 | ||
|
|
5e3c0762ab | ||
|
|
f883b0a7d8 | ||
|
|
861ac5d365 | ||
|
|
1e4c2d540e | ||
|
|
af3a297bb4 | ||
|
|
eb88f4b231 | ||
|
|
cf4287ddd9 | ||
|
|
88aee26105 | ||
|
|
b6ce840400 | ||
|
|
333882c9e0 | ||
|
|
640b1aa99e | ||
|
|
509b274c5c | ||
|
|
4c1e0d417d | ||
|
|
deb958b71b | ||
|
|
ad1f450b19 | ||
|
|
c675324d6d | ||
|
|
9a31fa96b5 | ||
|
|
fb4709e59a | ||
|
|
7e6f5e3a19 | ||
|
|
f2e7d7a5c4 | ||
|
|
7adcebee75 | ||
|
|
94e472a6c4 | ||
|
|
da7f40642e | ||
|
|
2d80cac3fd | ||
|
|
e20ff1a3b7 | ||
|
|
f2e9afc8aa | ||
|
|
315d43a16e | ||
|
|
5ee4291356 | ||
|
|
b47e1fa881 | ||
|
|
c938e5b770 | ||
|
|
652b913930 |
@@ -0,0 +1,43 @@
|
|||||||
|
name: 部署开发环境
|
||||||
|
on: [ push ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-dev:
|
||||||
|
env:
|
||||||
|
RUNNER_TOOL_CACHE: /toolcache
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 检出代码
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 构建Docker镜像
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
git_hash=$(git rev-parse --short "$GITHUB_SHA")
|
||||||
|
build_date=$(TZ=Asia/Shanghai date +"%Y%m%d%H%M")
|
||||||
|
docker build --build-arg VERSION="dev - $build_date - $git_hash" -t dypid:latest .
|
||||||
|
|
||||||
|
- name: 导出镜像
|
||||||
|
run: mkdir release && docker save -o release/dypid.tar dypid:latest && docker rmi dypid:latest
|
||||||
|
|
||||||
|
- name: 安装sshpass工具
|
||||||
|
run: |
|
||||||
|
apt update
|
||||||
|
apt install -y sshpass
|
||||||
|
|
||||||
|
- name: 部署程序到测试服务器
|
||||||
|
env:
|
||||||
|
SSH_HOST: ${{ secrets.TEST_SSH_HOST }}
|
||||||
|
SSH_USER: ${{ secrets.TEST_SSH_USER }}
|
||||||
|
SSH_PASSWORD: ${{ secrets.TEST_SSH_PASSWORD }}
|
||||||
|
DEPLOY_DIR: "/data/dypid"
|
||||||
|
run: |
|
||||||
|
set -ex
|
||||||
|
# 上传新镜像
|
||||||
|
sshpass -p "$SSH_PASSWORD" scp -o StrictHostKeyChecking=no release/dypid.tar $SSH_USER@$SSH_HOST:/tmp/dypid.tar
|
||||||
|
# 重启程序
|
||||||
|
sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST \
|
||||||
|
"docker load -i /tmp/dypid.tar \
|
||||||
|
&& cd $DEPLOY_DIR \
|
||||||
|
&& docker compose up -d \
|
||||||
|
&& docker image prune -f"
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
name: 部署生产环境
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-production:
|
||||||
|
env:
|
||||||
|
RUNNER_TOOL_CACHE: /toolcache
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 检出代码
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 构建Docker镜像
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
git_hash=$(git rev-parse --short "$GITHUB_SHA")
|
||||||
|
build_date=$(TZ=Asia/Shanghai date +"%Y%m%d%H%M")
|
||||||
|
docker build --build-arg VERSION="release - $build_date - $git_hash" -t dypid:latest .
|
||||||
|
|
||||||
|
- name: 导出镜像
|
||||||
|
run: mkdir release && docker save -o release/dypid.tar dypid:latest && docker rmi dypid:latest
|
||||||
|
|
||||||
|
- name: 安装sshpass工具
|
||||||
|
run: |
|
||||||
|
apt update
|
||||||
|
apt install -y sshpass
|
||||||
|
|
||||||
|
- name: 部署程序到生产服务器
|
||||||
|
env:
|
||||||
|
SSH_HOST: ${{ secrets.SSH_HOST }}
|
||||||
|
SSH_USER: ${{ secrets.SSH_USER }}
|
||||||
|
SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }}
|
||||||
|
DEPLOY_DIR: "/data/dypid"
|
||||||
|
run: |
|
||||||
|
set -ex
|
||||||
|
# 上传新镜像
|
||||||
|
sshpass -p "$SSH_PASSWORD" scp -o StrictHostKeyChecking=no release/dypid.tar $SSH_USER@$SSH_HOST:/tmp/dypid.tar
|
||||||
|
# 重启程序
|
||||||
|
sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST \
|
||||||
|
"docker load -i /tmp/dypid.tar \
|
||||||
|
&& cd $DEPLOY_DIR \
|
||||||
|
&& docker compose up -d \
|
||||||
|
&& docker image prune -f"
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
FROM node:lts AS webBuilder
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
COPY web .
|
||||||
|
|
||||||
|
RUN npm install -g pnpm \
|
||||||
|
&& pnpm install \
|
||||||
|
&& pnpm run build
|
||||||
|
|
||||||
|
FROM golang AS goBuilder
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
COPY --from=webBuilder /build/dist ./web/dist
|
||||||
|
|
||||||
|
ARG VERSION
|
||||||
|
|
||||||
|
RUN go env -w CGO_ENABLED=0 \
|
||||||
|
&& go mod tidy \
|
||||||
|
&& go build \
|
||||||
|
-ldflags="-s -w -X 'dypid/global.Version=$VERSION'" \
|
||||||
|
-o dypid
|
||||||
|
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
|
WORKDIR /data
|
||||||
|
|
||||||
|
COPY --from=goBuilder /build/dypid /dypid
|
||||||
|
|
||||||
|
RUN apk update \
|
||||||
|
&& apk add --no-cache tzdata \
|
||||||
|
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
|
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||||
|
&& apk del tzdata \
|
||||||
|
&& chmod +x /dypid
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/dypid" ]
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"dypid/db"
|
|
||||||
"dypid/global"
|
|
||||||
"dypid/model"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ReadDataHandler(c *gin.Context) {
|
|
||||||
lLen := global.RDB.LLen(global.RCtx, fmt.Sprintf("list:%s", c.Query("token")))
|
|
||||||
if lLen.Val() == 0 {
|
|
||||||
c.JSON(200, gin.H{"result": "数据库没有数据"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
retData := global.RDB.BLPop(global.RCtx, 0, fmt.Sprintf("list:%s", c.Query("token")))
|
|
||||||
newData := model.Data{}
|
|
||||||
err := json.Unmarshal([]byte(retData.Val()[1]), &newData)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(200, gin.H{"result": newData})
|
|
||||||
}
|
|
||||||
func WriteDataHandler(c *gin.Context) {
|
|
||||||
data := model.Data{}
|
|
||||||
|
|
||||||
if err := c.BindQuery(&data); err != nil {
|
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dedupObject, err := db.GetDedupObject(data.Token)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = createBF(fmt.Sprintf("dedup:%s:%s", data.Token, dedupObject), 0.01, 100000000)
|
|
||||||
if err != nil && err.Error() != "ERR item exists" {
|
|
||||||
c.JSON(400, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
luaScript := `
|
|
||||||
local dedupKey = KEYS[1] -- KEYS[1]: 去重键 (dedup:token:object)
|
|
||||||
local listKey = KEYS[2] -- KEYS[2]: 列表键 (list:token)
|
|
||||||
local dedupValue = ARGV[1] -- ARGV[1]: 去重值
|
|
||||||
local jsonData = ARGV[2] -- ARGV[2]: JSON序列化的数据
|
|
||||||
|
|
||||||
-- 检查布隆过滤器中是否已存在该值
|
|
||||||
local exists = redis.call('BF.EXISTS', dedupKey, dedupValue)
|
|
||||||
|
|
||||||
-- 如果已存在,返回已去重标记
|
|
||||||
if exists == 1 then
|
|
||||||
return "已去重"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 添加到布隆过滤器
|
|
||||||
redis.call('BF.ADD', dedupKey, dedupValue)
|
|
||||||
-- 添加到列表
|
|
||||||
redis.call('LPUSH', listKey, jsonData)
|
|
||||||
|
|
||||||
-- 返回成功结果
|
|
||||||
return "ok"
|
|
||||||
`
|
|
||||||
k1 := fmt.Sprintf("dedup:%s:%s", data.Token, dedupObject)
|
|
||||||
k2 := fmt.Sprintf("list:%s", data.Token)
|
|
||||||
v1 := c.Query(dedupObject)
|
|
||||||
v2, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(500, gin.H{"error": "JSON序列化失败 " + err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := redis.NewScript(luaScript).Run(
|
|
||||||
global.RCtx,
|
|
||||||
global.RDB,
|
|
||||||
[]string{k1, k2},
|
|
||||||
v1,
|
|
||||||
string(v2),
|
|
||||||
).Result()
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(500, gin.H{"error": "Redis操作失败 " + err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if resultMap, ok := result.(string); ok {
|
|
||||||
c.JSON(200, gin.H{"result": resultMap})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(500, gin.H{"error": "WriteDataHandler 错误"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func createBF(bloomFilter string, errorRate float64, capacity int64) error {
|
|
||||||
_, err := global.RDB.BFReserve(global.RCtx, bloomFilter, errorRate, capacity).Result()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"dypid/db"
|
|
||||||
"dypid/global"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ListTokenHandler(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{"result": db.ListToken()})
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTokenHandler(c *gin.Context) {
|
|
||||||
if c.Query("token") == "" {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "token不能为空"})
|
|
||||||
return
|
|
||||||
} else if c.Query("dedup_object") == "" {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "dedup_object不能为空"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := db.CreateToken(c.Query("token"), c.Query("dedup_object"))
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, gin.H{"result": "ok"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateTokenHandler(c *gin.Context) {
|
|
||||||
if c.Query("token") == "" {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "token不能为空"})
|
|
||||||
return
|
|
||||||
} else if c.Query("dedup_object") == "" {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "dedup_object不能为空"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := db.UpdateToken(c.Query("token"), c.Query("dedup_object"))
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, gin.H{"result": "ok"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTokenHandler(c *gin.Context) {
|
|
||||||
if c.Query("token") == "" {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "token不能为空"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := db.DeleteToken(c.Query("token"))
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, gin.H{"result": "ok"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTokenInfoHandler(c *gin.Context) {
|
|
||||||
input := struct {
|
|
||||||
Token string `form:"token" binding:"required"`
|
|
||||||
}{}
|
|
||||||
if err := c.ShouldBindQuery(&input); err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Token不能为空"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dedupObject, err := db.GetDedupObject(input.Token)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取去重对象失败 " + err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output := struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
DedupObject string `json:"dedup_object"`
|
|
||||||
DedupItemsNumber int64 `json:"dedup_items_number"`
|
|
||||||
CacheListNumber int64 `json:"cache_list_number"`
|
|
||||||
}{}
|
|
||||||
output.Token = input.Token
|
|
||||||
output.DedupObject = dedupObject
|
|
||||||
output.DedupItemsNumber = global.RDB.BFCard(global.RCtx, "dedup:"+input.Token+":"+dedupObject).Val()
|
|
||||||
output.CacheListNumber = global.RDB.LLen(global.RCtx, "list:"+input.Token).Val()
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"result": output})
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTokenInfoHandler(c *gin.Context) {
|
|
||||||
//解析输入数据
|
|
||||||
input := struct {
|
|
||||||
Token string `form:"token" binding:"required"`
|
|
||||||
DedupBF bool `form:"dedup_bf"`
|
|
||||||
CacheList bool `form:"cache_list"`
|
|
||||||
}{}
|
|
||||||
if err := c.ShouldBindQuery(&input); err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Token不能为空"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//检查token是否存在
|
|
||||||
_, err := db.GetDedupObject(input.Token)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Token不存在" + err.Error()})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//删除去重对象
|
|
||||||
if input.DedupBF {
|
|
||||||
keys := global.RDB.Keys(global.RCtx, "dedup:"+input.Token+":*").Val()
|
|
||||||
global.RDB.Del(global.RCtx, keys...)
|
|
||||||
}
|
|
||||||
if input.CacheList {
|
|
||||||
global.RDB.Del(global.RCtx, "list:"+input.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
//输出信息
|
|
||||||
c.JSON(http.StatusOK, gin.H{"result": "ok"})
|
|
||||||
}
|
|
||||||
@@ -1,57 +1,52 @@
|
|||||||
module dypid
|
module dypid
|
||||||
|
|
||||||
go 1.25
|
go 1.27
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/labstack/echo/v4 v4.13.4
|
github.com/gin-contrib/cors v1.7.7
|
||||||
github.com/redis/go-redis/v9 v9.12.1
|
github.com/gin-gonic/gin v1.12.0
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/redis/go-redis/v9 v9.22.0
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.13.3 // indirect
|
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/sonic v1.15.2 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.5.2 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.7 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
|
||||||
github.com/gin-contrib/cors v1.7.6 // indirect
|
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
|
||||||
github.com/gin-contrib/static v1.1.5 // indirect
|
|
||||||
github.com/gin-gonic/gin v1.10.1 // 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.26.0 // indirect
|
github.com/go-playground/validator/v10 v10.30.3 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.6 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.19.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.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
||||||
github.com/labstack/gommon v0.4.2 // indirect
|
github.com/leodido/go-urn v1.5.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // 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.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/quic-go/quic-go v0.61.0 // indirect
|
||||||
github.com/spf13/afero v1.12.0 // indirect
|
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||||
github.com/spf13/cast v1.7.1 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.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.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.2 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
golang.org/x/arch v0.30.0 // indirect
|
||||||
golang.org/x/arch v0.18.0 // indirect
|
golang.org/x/crypto v0.55.0 // indirect
|
||||||
golang.org/x/crypto v0.39.0 // indirect
|
golang.org/x/net v0.58.0 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
golang.org/x/text v0.41.0 // indirect
|
||||||
golang.org/x/text v0.26.0 // indirect
|
google.golang.org/protobuf v1.36.12 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,179 +2,129 @@ 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/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 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
||||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
|
||||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
|
||||||
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
github.com/bytedance/sonic/loader v0.5.2 h1:0QtP1gevc1OZ6/H8Lb9BRZiCXd1Ftjd3OKuj1T1lBIo=
|
||||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
github.com/bytedance/sonic/loader v0.5.2/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
|
||||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
|
||||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
|
||||||
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 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
|
||||||
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
||||||
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
|
||||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
|
||||||
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-contrib/static v1.1.5 h1:bAPqT4KTZN+4uDY1b90eSrD1t8iNzod7Jj8njwmnzz4=
|
|
||||||
github.com/gin-contrib/static v1.1.5/go.mod h1:8JSEXwZHcQ0uCrLPcsvnAJ4g+ODxeupP8Zetl9fd8wM=
|
|
||||||
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-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.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
|
||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
|
||||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
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/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.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
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/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
|
github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
|
||||||
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
|
github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
|
||||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||||
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/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
|
||||||
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/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=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
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/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.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
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/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.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
|
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
|
||||||
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA=
|
||||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4=
|
||||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
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/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.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.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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
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.3.2 h1:zkEASHHyEClGeURfgNT9PJZVfAbs9oEX9QXggwWNJbc=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.3.2/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU=
|
||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||||
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
|
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||||
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||||
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
|
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||||
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
|
||||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
|
||||||
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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
|
||||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
|
||||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
|
||||||
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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
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=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dypid/internal/db"
|
||||||
|
"dypid/internal/global"
|
||||||
|
"dypid/internal/model"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReadDataHandler(c *gin.Context) {
|
||||||
|
//解析输入数据
|
||||||
|
input := struct {
|
||||||
|
Token string `form:"token" binding:"required"`
|
||||||
|
}{}
|
||||||
|
if err := c.ShouldBindQuery(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "参数不能为空 " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
retData := global.RDB.LPop(global.RCtx, fmt.Sprintf("list:%s", input.Token)).Val()
|
||||||
|
if retData == "null" {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"result": "数据库没有数据"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.String(http.StatusOK, retData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteDataHandler(c *gin.Context) {
|
||||||
|
//解析输入数据
|
||||||
|
input := struct {
|
||||||
|
Token string `form:"token" binding:"required"`
|
||||||
|
Data string `form:"data" binding:"required"`
|
||||||
|
}{}
|
||||||
|
if err := c.ShouldBindQuery(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "参数不能为空 " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取去重对象
|
||||||
|
dedupObject, err := db.GetDedupObject(input.Token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断数据是否需要去重
|
||||||
|
//如果数据不需要去重则直接写入数据库
|
||||||
|
if dedupObject == "false" {
|
||||||
|
global.RDB.LPush(global.RCtx, fmt.Sprintf("list:%s", input.Token), input.Data)
|
||||||
|
c.JSON(http.StatusOK, model.APIResponse{
|
||||||
|
Code: 200,
|
||||||
|
Message: "数据写入成功",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//数据要去重,下面函数会执行
|
||||||
|
//获取数据索引
|
||||||
|
dataIndex, err := getDataIndex(input.Token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dedupValue := strings.Split(input.Data, "----")[dataIndex[dedupObject]]
|
||||||
|
|
||||||
|
err = createCF(fmt.Sprintf("dedup:%s:%s", input.Token, dedupObject), 100_000_000)
|
||||||
|
if err != nil && err.Error() != "ERR item exists" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
luaScript := `
|
||||||
|
local dedupKey = KEYS[1] -- KEYS[1]: 去重键 (dedup:token:object)
|
||||||
|
local listKey = KEYS[2] -- KEYS[2]: 列表键 (list:token)
|
||||||
|
local deleteListKey = KEYS[3] -- KEYS[3]: 删除列表键 (delete-list:token)
|
||||||
|
local dedupValue = ARGV[1] -- ARGV[1]: 去重值
|
||||||
|
local rawData = ARGV[2] -- ARGV[2]: 原始数据
|
||||||
|
|
||||||
|
-- 检查布隆过滤器中是否已存在该值
|
||||||
|
local exists = redis.call('CF.EXISTS', dedupKey, dedupValue)
|
||||||
|
|
||||||
|
-- 如果已存在,返回已去重标记
|
||||||
|
if exists == 1 then
|
||||||
|
return "已去重"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 添加到布隆过滤器
|
||||||
|
redis.call('CF.ADD', dedupKey, dedupValue)
|
||||||
|
-- 添加到列表
|
||||||
|
redis.call('LPUSH', listKey, rawData)
|
||||||
|
|
||||||
|
redis.call('LPUSH', deleteListKey, dedupValue)
|
||||||
|
|
||||||
|
-- 返回成功结果
|
||||||
|
return "ok"
|
||||||
|
`
|
||||||
|
result, err := redis.NewScript(luaScript).Run(
|
||||||
|
global.RCtx,
|
||||||
|
global.RDB,
|
||||||
|
[]string{
|
||||||
|
fmt.Sprintf("dedup:%s:%s", input.Token, dedupObject),
|
||||||
|
fmt.Sprintf("list:%s", input.Token),
|
||||||
|
fmt.Sprintf("delete-list:%s", input.Token),
|
||||||
|
},
|
||||||
|
dedupValue,
|
||||||
|
input.Data,
|
||||||
|
).Result()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Redis操作失败 " + err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultMap, ok := result.(string); ok {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"result": resultMap})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "WriteDataHandler 错误"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCF(bloomFilter string, capacity int64) error {
|
||||||
|
_, err := global.RDB.CFReserveBucketSize(global.RCtx, bloomFilter, capacity, 6).Result()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDataIndex(token string) (index map[string]int, err error) {
|
||||||
|
dataFormat, err := db.GetDataFormat(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f := strings.Split(dataFormat, "----")
|
||||||
|
index = make(map[string]int)
|
||||||
|
for i, s := range f {
|
||||||
|
index[s] = i
|
||||||
|
}
|
||||||
|
return index, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dypid/internal/db"
|
||||||
|
"dypid/internal/global"
|
||||||
|
"dypid/internal/model"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListTokenHandler(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, model.APIResponse{
|
||||||
|
Code: 200,
|
||||||
|
Message: "获取所有token成功",
|
||||||
|
Data: db.ListToken(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTokenHandler(c *gin.Context) {
|
||||||
|
//解析输入数据
|
||||||
|
input := struct {
|
||||||
|
Token string `form:"token" binding:"required"`
|
||||||
|
DedupObject string `form:"dedup_object" binding:"required"`
|
||||||
|
DataFormat string `form:"data_format" binding:"required"`
|
||||||
|
Notes string `form:"notes"`
|
||||||
|
}{}
|
||||||
|
if err := c.ShouldBindQuery(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "参数不能为空 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查Token是否存在
|
||||||
|
if db.CheckToken(input.Token) {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "创建Token失败,Token已经存在,请勿重复创建",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//创建Token
|
||||||
|
err := db.CreateToken(input.Token, input.DedupObject, input.DataFormat, input.Notes)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.APIResponse{
|
||||||
|
Code: 500,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//返回
|
||||||
|
c.JSON(http.StatusOK, model.APIResponse{
|
||||||
|
Code: 200,
|
||||||
|
Message: "创建Token成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateTokenHandler(c *gin.Context) {
|
||||||
|
input := struct {
|
||||||
|
Token string `form:"token" binding:"required"`
|
||||||
|
DedupObject string `form:"dedup_object" binding:"required"`
|
||||||
|
DataFormat string `form:"data_format" binding:"required"`
|
||||||
|
Notes string `form:"notes"`
|
||||||
|
}{}
|
||||||
|
if err := c.ShouldBindQuery(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "参数不能为空 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查Token是否存在
|
||||||
|
if !db.CheckToken(input.Token) {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "更改失败,Token不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.UpdateToken(input.Token, input.DedupObject, input.DataFormat, input.Notes)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.APIResponse{
|
||||||
|
Code: 500,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, model.APIResponse{
|
||||||
|
Code: 200,
|
||||||
|
Message: "更改Token成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTokenHandler(c *gin.Context) {
|
||||||
|
input := struct {
|
||||||
|
Token string `form:"token" binding:"required"`
|
||||||
|
}{}
|
||||||
|
if err := c.ShouldBindQuery(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "参数不能为空 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查Token是否存在
|
||||||
|
if !db.CheckToken(input.Token) {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "删除Token失败,Token不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.DeleteToken(input.Token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.APIResponse{
|
||||||
|
Code: 500,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, model.APIResponse{
|
||||||
|
Code: 200,
|
||||||
|
Message: "删除Token成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTokenInfoHandler(c *gin.Context) {
|
||||||
|
input := struct {
|
||||||
|
Token string `form:"token" binding:"required"`
|
||||||
|
}{}
|
||||||
|
if err := c.ShouldBindQuery(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "参数不能为空 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查Token是否存在
|
||||||
|
if !db.CheckToken(input.Token) {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "获取信息失败,Token不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dedupObject, err := db.GetDedupObject(input.Token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.APIResponse{
|
||||||
|
Code: 500,
|
||||||
|
Message: "获取去重对象失败 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dataFormat, err := db.GetDataFormat(input.Token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.APIResponse{
|
||||||
|
Code: 500,
|
||||||
|
Message: "获取数据格式失败 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output := struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
DedupObject string `json:"dedup_object"`
|
||||||
|
DataFormat string `json:"data_format"`
|
||||||
|
DedupItemsNumber int64 `json:"dedup_items_number"`
|
||||||
|
CacheListNumber int64 `json:"cache_list_number"`
|
||||||
|
}{
|
||||||
|
Token: input.Token,
|
||||||
|
DedupObject: dedupObject,
|
||||||
|
DataFormat: dataFormat,
|
||||||
|
DedupItemsNumber: global.RDB.CFInfo(global.RCtx, "dedup:"+input.Token+":"+dedupObject).Val().NumItemsInserted,
|
||||||
|
CacheListNumber: global.RDB.LLen(global.RCtx, "list:"+input.Token).Val(),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, model.APIResponse{
|
||||||
|
Code: 200,
|
||||||
|
Message: "获取Token信息成功",
|
||||||
|
Data: output,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTokenInfoHandler(c *gin.Context) {
|
||||||
|
//解析输入数据
|
||||||
|
input := struct {
|
||||||
|
Token string `form:"token" binding:"required"`
|
||||||
|
DedupBF string `form:"dedup_bf"`
|
||||||
|
CacheList string `form:"cache_list"`
|
||||||
|
BothNumber string `form:"both_number"`
|
||||||
|
}{}
|
||||||
|
if err := c.ShouldBindQuery(&input); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "参数不能为空 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//检查Token是否存在
|
||||||
|
if !db.CheckToken(input.Token) {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "删除Token信息失败,Token不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取去重对象
|
||||||
|
dedupObject, err := db.GetDedupObject(input.Token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "获取去重对象失败 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除去重对象
|
||||||
|
switch input.DedupBF {
|
||||||
|
case "":
|
||||||
|
case "all":
|
||||||
|
keys := global.RDB.Keys(global.RCtx, "dedup:"+input.Token+":*").Val()
|
||||||
|
global.RDB.Del(global.RCtx, keys...)
|
||||||
|
global.RDB.Del(global.RCtx, "delete-list:"+input.Token)
|
||||||
|
default:
|
||||||
|
num, err := strconv.Atoi(input.DedupBF)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "dedup_bf数量设置错误 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := num; i > 0; i -= 20_0000 {
|
||||||
|
a := 0
|
||||||
|
a = min(i, 20_0000)
|
||||||
|
|
||||||
|
//获得要删的值(列表)
|
||||||
|
result := global.RDB.LRange(global.RCtx, "delete-list:"+input.Token, int64(-a), -1).Val()
|
||||||
|
|
||||||
|
//用事件批量删除布隆过滤器中值
|
||||||
|
_, err = global.RDB.TxPipelined(global.RCtx, func(pipe redis.Pipeliner) error {
|
||||||
|
for _, s := range result {
|
||||||
|
pipe.CFDel(global.RCtx, "dedup:"+input.Token+":"+dedupObject, s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.APIResponse{
|
||||||
|
Code: 500,
|
||||||
|
Message: "删除去重对象失败 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//清除列表中要删的值
|
||||||
|
global.RDB.LTrim(global.RCtx, "delete-list:"+input.Token, 0, int64(-a-1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除原始数据
|
||||||
|
switch input.CacheList {
|
||||||
|
case "":
|
||||||
|
case "all":
|
||||||
|
global.RDB.Del(global.RCtx, "list:"+input.Token)
|
||||||
|
default:
|
||||||
|
num, err := strconv.Atoi(input.CacheList)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 400,
|
||||||
|
Message: "cache_list数量设置错误 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
global.RDB.LTrim(global.RCtx, "list:"+input.Token, 0, int64(-num-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除去重参考值和原始数据
|
||||||
|
switch input.BothNumber {
|
||||||
|
case "":
|
||||||
|
default:
|
||||||
|
num, err := strconv.Atoi(input.BothNumber)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, model.APIResponse{
|
||||||
|
Code: 500,
|
||||||
|
Message: "both_number数量设置错误 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := num; i > 0; i -= 20_0000 {
|
||||||
|
a := 0
|
||||||
|
a = min(i, 20_0000)
|
||||||
|
|
||||||
|
result := global.RDB.LRange(global.RCtx, "delete-list:"+input.Token, int64(-a), -1).Val()
|
||||||
|
_, err = global.RDB.TxPipelined(global.RCtx, func(pipe redis.Pipeliner) error {
|
||||||
|
for _, s := range result {
|
||||||
|
pipe.CFDel(global.RCtx, "dedup:"+input.Token+":"+dedupObject, s)
|
||||||
|
}
|
||||||
|
pipe.LTrim(global.RCtx, "delete-list:"+input.Token, 0, int64(-a-1))
|
||||||
|
pipe.LTrim(global.RCtx, "list:"+input.Token, 0, int64(-a-1))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, model.APIResponse{
|
||||||
|
Code: 500,
|
||||||
|
Message: "删除Token信息失败 " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//输出信息
|
||||||
|
c.JSON(http.StatusOK, model.APIResponse{
|
||||||
|
Code: 200,
|
||||||
|
Message: "删除Token信息成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ var localDB []Token
|
|||||||
type Token struct {
|
type Token struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
DedupObject string `json:"dedup_object"`
|
DedupObject string `json:"dedup_object"`
|
||||||
|
DataFormat string `json:"data_format"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLocalDB() {
|
func InitLocalDB() {
|
||||||
@@ -30,12 +32,22 @@ start:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckToken(token string) bool {
|
||||||
|
InitLocalDB()
|
||||||
|
for _, t := range localDB {
|
||||||
|
if t.Token == token {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func ListToken() []Token {
|
func ListToken() []Token {
|
||||||
InitLocalDB()
|
InitLocalDB()
|
||||||
return localDB
|
return localDB
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateToken(token string, dedupObject string) error {
|
func CreateToken(token string, dedupObject string, dataFormat string, Notes string) error {
|
||||||
InitLocalDB()
|
InitLocalDB()
|
||||||
for _, t := range localDB {
|
for _, t := range localDB {
|
||||||
if t.Token == token {
|
if t.Token == token {
|
||||||
@@ -43,7 +55,7 @@ func CreateToken(token string, dedupObject string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a := append(localDB, Token{token, dedupObject})
|
a := append(localDB, Token{token, dedupObject, dataFormat, Notes})
|
||||||
marshal, err := json.Marshal(a)
|
marshal, err := json.Marshal(a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -55,12 +67,14 @@ func CreateToken(token string, dedupObject string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateToken(token string, dedupObject string) error {
|
func UpdateToken(token string, dedupObject string, dataFormat string, Notes string) error {
|
||||||
InitLocalDB()
|
InitLocalDB()
|
||||||
|
|
||||||
for i, t := range localDB {
|
for i, t := range localDB {
|
||||||
if t.Token == token {
|
if t.Token == token {
|
||||||
localDB[i].DedupObject = dedupObject
|
localDB[i].DedupObject = dedupObject
|
||||||
|
localDB[i].DataFormat = dataFormat
|
||||||
|
localDB[i].Notes = Notes
|
||||||
|
|
||||||
marshal, err := json.Marshal(localDB)
|
marshal, err := json.Marshal(localDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -72,6 +86,7 @@ func UpdateToken(token string, dedupObject string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InitLocalDB()
|
InitLocalDB()
|
||||||
return errors.New("token not found")
|
return errors.New("token not found")
|
||||||
}
|
}
|
||||||
@@ -93,6 +108,7 @@ func DeleteToken(token string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InitLocalDB()
|
InitLocalDB()
|
||||||
return errors.New("token not found")
|
return errors.New("token not found")
|
||||||
}
|
}
|
||||||
@@ -105,3 +121,12 @@ func GetDedupObject(token string) (dedupObject string, err error) {
|
|||||||
}
|
}
|
||||||
return "", errors.New("未找到Token")
|
return "", errors.New("未找到Token")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDataFormat(token string) (dataFormat string, err error) {
|
||||||
|
for i, t := range localDB {
|
||||||
|
if t.Token == token {
|
||||||
|
return localDB[i].DataFormat, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("未找到Token")
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dypid/config"
|
"dypid/internal/config"
|
||||||
"dypid/global"
|
"dypid/internal/global"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
@@ -8,3 +8,5 @@ import (
|
|||||||
|
|
||||||
var RDB *redis.Client
|
var RDB *redis.Client
|
||||||
var RCtx = context.Background()
|
var RCtx = context.Background()
|
||||||
|
|
||||||
|
var Version = "dev"
|
||||||
@@ -4,7 +4,7 @@ type Data struct {
|
|||||||
Token string `form:"token" json:"token"`
|
Token string `form:"token" json:"token"`
|
||||||
Uid string `form:"uid" json:"uid"`
|
Uid string `form:"uid" json:"uid"`
|
||||||
Pid string `form:"pid" json:"pid"`
|
Pid string `form:"pid" json:"pid"`
|
||||||
SecId string `form:"secid" json:"secId"`
|
SecId string `form:"secid" json:"secid"`
|
||||||
Dyid string `form:"dyid" json:"dyid"`
|
Dyid string `form:"dyid" json:"dyid"`
|
||||||
Key string `form:"key" json:"key"`
|
Key string `form:"key" json:"key"`
|
||||||
CommentId string `form:"comment_id" json:"comment_id"`
|
CommentId string `form:"comment_id" json:"comment_id"`
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type APIResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data any `json:"data"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dypid/internal/controller"
|
||||||
|
"dypid/internal/global"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegAPIService(r *gin.Engine) {
|
||||||
|
g := r.Group("/api") //初始化路由组 /api/xxxx
|
||||||
|
{
|
||||||
|
g.GET("/test", func(context *gin.Context) {
|
||||||
|
context.String(http.StatusOK, "ok")
|
||||||
|
})
|
||||||
|
g.GET("/version", func(context *gin.Context) {
|
||||||
|
context.String(http.StatusOK, fmt.Sprintf("程序版本: %s\nGin: %s", global.Version, gin.Version))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
g.GET("/token", controller.ListTokenHandler) //获取token列表
|
||||||
|
g.POST("/token", controller.CreateTokenHandler) //创建token
|
||||||
|
g.PUT("/token", controller.UpdateTokenHandler) //更新token
|
||||||
|
g.DELETE("/token", controller.DeleteTokenHandler) //删除token
|
||||||
|
g.GET("/token/info", controller.GetTokenInfoHandler) //获取token信息
|
||||||
|
g.DELETE("/token/info", controller.DeleteTokenInfoHandler) //删除token数据库
|
||||||
|
}
|
||||||
|
{
|
||||||
|
g.GET("/data", controller.ReadDataHandler) //获取数据
|
||||||
|
g.POST("/data", controller.WriteDataHandler) //写入数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegWebService(r *gin.Engine, webFiles embed.FS) {
|
||||||
|
assets, _ := fs.Sub(webFiles, "web/dist/assets")
|
||||||
|
r.StaticFS("/assets", http.FS(assets))
|
||||||
|
|
||||||
|
icon, _ := fs.ReadFile(webFiles, "web/dist/favicon.ico")
|
||||||
|
r.GET("/favicon.ico", func(c *gin.Context) {
|
||||||
|
c.Data(200, "image/x-icon", icon)
|
||||||
|
})
|
||||||
|
|
||||||
|
indexHtml, _ := fs.ReadFile(webFiles, "web/dist/index.html")
|
||||||
|
r.NoRoute(func(c *gin.Context) {
|
||||||
|
c.Data(200, "text/html; charset=utf-8", indexHtml)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,59 +1,43 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"dypid/config"
|
"dypid/internal/config"
|
||||||
"dypid/controller"
|
"dypid/internal/db"
|
||||||
"dypid/db"
|
"dypid/internal/service"
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-contrib/static"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed web/dist/*
|
//go:embed web/dist/*
|
||||||
var webDir embed.FS
|
var webFiles embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config.InitConfig()
|
config.InitConfig()
|
||||||
db.InitRedis()
|
db.InitRedis()
|
||||||
db.InitLocalDB()
|
db.InitLocalDB()
|
||||||
|
|
||||||
//初始化一个http服务对象
|
//初始化一个http服务对象
|
||||||
gin.SetMode(config.APPConfig.RunMode)
|
gin.SetMode(config.APPConfig.RunMode)
|
||||||
r := gin.Default()
|
r := gin.New()
|
||||||
//跨域设置
|
if config.APPConfig.RunMode == "debug" {
|
||||||
r.Use(cors.Default())
|
r.Use(gin.Logger()) //日志处理
|
||||||
|
}
|
||||||
|
r.Use(gin.Recovery()) //错误处理
|
||||||
|
r.Use(cors.Default()) //跨域设置
|
||||||
|
|
||||||
//静态文件目录
|
//注册网页服务(Vue)
|
||||||
web, err := static.EmbedFolder(webDir, "web/dist")
|
service.RegWebService(r, webFiles)
|
||||||
if err != nil {
|
//注册API接口
|
||||||
panic(err)
|
service.RegAPIService(r)
|
||||||
}
|
|
||||||
r.Use(static.Serve("/", web))
|
|
||||||
|
|
||||||
//API接口
|
|
||||||
g := r.Group("/api") //初始化路由组 /api/xxxx
|
|
||||||
{
|
|
||||||
g.GET("/token", controller.ListTokenHandler) //获取token列表
|
|
||||||
g.POST("/token", controller.CreateTokenHandler) //创建token
|
|
||||||
g.PUT("/token", controller.UpdateTokenHandler) //更新token
|
|
||||||
g.DELETE("/token", controller.DeleteTokenHandler) //删除token
|
|
||||||
g.GET("/token/info", controller.GetTokenInfoHandler) //获取token信息
|
|
||||||
g.DELETE("/token/info", controller.DeleteTokenInfoHandler) //删除token数据库
|
|
||||||
}
|
|
||||||
{
|
|
||||||
g.GET("/data", controller.ReadDataHandler) //获取数据
|
|
||||||
g.POST("/data", controller.WriteDataHandler) //写入数据
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听并在 0.0.0.0:8080 上启动服务
|
// 监听并在 0.0.0.0:8080 上启动服务
|
||||||
fmt.Printf("服务器正在%s运行\n", config.APPConfig.Host)
|
fmt.Printf("服务器正在运行:http://%s\n", config.APPConfig.Host)
|
||||||
err = r.Run(config.APPConfig.Host)
|
err := r.Run(config.APPConfig.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("服务启动失败:", err)
|
fmt.Println("服务启动失败:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//atomic.AddInt64(&Same, 1)
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
./upload
|
|
||||||
config.toml
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UploadData struct {
|
|
||||||
Dyid string // dyid
|
|
||||||
Uid string // uid
|
|
||||||
Secuid string // secuid
|
|
||||||
Pid string // pid
|
|
||||||
CommentId string // comment_id
|
|
||||||
Id1 string // id1
|
|
||||||
Id2 string // id2
|
|
||||||
Id3 string // id3
|
|
||||||
Id4 string // id4
|
|
||||||
Id5 string // id5
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpClient = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
MaxIdleConnsPerHost: 10,
|
|
||||||
IdleConnTimeout: 30 * time.Second,
|
|
||||||
},
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
//程序配置
|
|
||||||
//设置默认值
|
|
||||||
viper.SetDefault("url", "http://localhost:8080")
|
|
||||||
viper.SetDefault("token", "")
|
|
||||||
viper.SetDefault("thread-count", 10)
|
|
||||||
//设置配置文件名和路径 ./config.toml
|
|
||||||
viper.AddConfigPath(".")
|
|
||||||
viper.SetConfigName("config")
|
|
||||||
viper.SetConfigType("toml")
|
|
||||||
viper.SafeWriteConfig() //安全写入默认配置
|
|
||||||
//读取配置文件
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
|
||||||
fmt.Errorf("无法读取配置文件: %w", err)
|
|
||||||
}
|
|
||||||
viper.WatchConfig()
|
|
||||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
|
||||||
fmt.Errorf("无法读取配置文件: %w", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
//检测./upload
|
|
||||||
fmt.Println("程序启动成功,正在检测upload")
|
|
||||||
os.Mkdir("./upload", os.ModePerm)
|
|
||||||
for {
|
|
||||||
files, err := getTxtFiles("./upload")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if files != nil {
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
for _, filePath := range files {
|
|
||||||
fmt.Println("正在上传文件:", filePath)
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
processFile(filePath)
|
|
||||||
os.Remove(filePath)
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
}
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func uploadDataToServer(data UploadData) error {
|
|
||||||
params := url.Values{}
|
|
||||||
params.Add("token", viper.GetString("token"))
|
|
||||||
params.Add("dyid", data.Dyid)
|
|
||||||
params.Add("uid", data.Uid)
|
|
||||||
params.Add("secuid", data.Secuid)
|
|
||||||
params.Add("pid", data.Pid)
|
|
||||||
params.Add("comment_id", data.CommentId)
|
|
||||||
params.Add("id1", data.Id1)
|
|
||||||
params.Add("id2", data.Id2)
|
|
||||||
params.Add("id3", data.Id3)
|
|
||||||
params.Add("id4", data.Id4)
|
|
||||||
params.Add("id5", data.Id5)
|
|
||||||
|
|
||||||
resp, err := httpClient.Post(viper.GetString("url")+"/api/data?"+params.Encode(), "application/x-www-form-urlencoded", strings.NewReader(""))
|
|
||||||
_, _ = io.Copy(io.Discard, resp.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取目录中的所有txt文件
|
|
||||||
func getTxtFiles(dir string) ([]string, error) {
|
|
||||||
var txtFiles []string
|
|
||||||
|
|
||||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只处理普通文件,跳过目录
|
|
||||||
if !info.Mode().IsRegular() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查文件扩展名是否为.txt
|
|
||||||
if strings.ToLower(filepath.Ext(path)) == ".txt" {
|
|
||||||
txtFiles = append(txtFiles, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return txtFiles, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func processFile(filePath string) {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
// 打开文件
|
|
||||||
file, err := os.Open(filePath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("无法打开文件 %s: %v\n", filePath, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// 创建行通道
|
|
||||||
lines := make(chan string, 100)
|
|
||||||
|
|
||||||
// 创建10个worker处理文件上传
|
|
||||||
for i := 0; i < viper.GetInt("thread-count"); i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(workerID int) {
|
|
||||||
defer wg.Done()
|
|
||||||
processLines(lines, workerID, filePath)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取文件并发送到通道
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
lineCount := 0
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
lines <- line
|
|
||||||
lineCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
close(lines)
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
fmt.Printf("读取文件 %s 错误: %v\n", filePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("文件 %s 处理完成,共处理 %d 行数据\n", filePath, lineCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func processLines(lines <-chan string, workerID int, filePath string) {
|
|
||||||
for line := range lines {
|
|
||||||
// 跳过空行
|
|
||||||
if strings.TrimSpace(line) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用----分割行数据
|
|
||||||
parts := strings.Split(line, "----")
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
parts = append(parts, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保有足够的字段
|
|
||||||
if len(parts) < 3 {
|
|
||||||
fmt.Printf("Worker %d (文件 %s): 行数据字段不足,跳过: %s\n", workerID, filePath, line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建上传数据结构
|
|
||||||
uploadData := UploadData{
|
|
||||||
Dyid: parts[0],
|
|
||||||
Uid: parts[1],
|
|
||||||
Secuid: parts[2],
|
|
||||||
Pid: parts[3],
|
|
||||||
CommentId: parts[4],
|
|
||||||
Id1: parts[5],
|
|
||||||
Id2: parts[6],
|
|
||||||
Id3: parts[7],
|
|
||||||
Id4: parts[8],
|
|
||||||
Id5: parts[9],
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传数据
|
|
||||||
if err := uploadDataToServer(uploadData); err != nil {
|
|
||||||
fmt.Printf("Worker %d (文件 %s): 上传失败: %v\n", workerID, filePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加短暂延迟避免服务器过载
|
|
||||||
//time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+12
-3
@@ -14,9 +14,6 @@ dist-ssr
|
|||||||
coverage
|
coverage
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
/cypress/videos/
|
|
||||||
/cypress/screenshots/
|
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
@@ -28,3 +25,15 @@ coverage
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Cypress
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Vitest
|
||||||
|
__screenshots__/
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
*.timestamp-*-*.mjs
|
||||||
+17
-5
@@ -4,11 +4,23 @@ This template should help get you started developing with Vue 3 in Vite.
|
|||||||
|
|
||||||
## Recommended IDE Setup
|
## Recommended IDE Setup
|
||||||
|
|
||||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (
|
||||||
|
and disable Vetur).
|
||||||
|
|
||||||
|
## Recommended Browser Setup
|
||||||
|
|
||||||
|
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||||
|
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||||
|
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||||
|
- Firefox:
|
||||||
|
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||||
|
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||||
|
|
||||||
## Type Support for `.vue` Imports in TS
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for
|
||||||
|
type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the
|
||||||
|
TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
## Customize configuration
|
## Customize configuration
|
||||||
|
|
||||||
@@ -17,17 +29,17 @@ See [Vite Configuration Reference](https://vite.dev/config/).
|
|||||||
## Project Setup
|
## Project Setup
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compile and Hot-Reload for Development
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Type-Check, Compile and Minify for Production
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Vite App</title>
|
<title>抖音数据去重</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
Generated
-3758
File diff suppressed because it is too large
Load Diff
+23
-17
@@ -3,9 +3,6 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
@@ -14,21 +11,30 @@
|
|||||||
"type-check": "vue-tsc --build"
|
"type-check": "vue-tsc --build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.11.0",
|
"@element-plus/icons-vue": "^2.3.2",
|
||||||
"element-plus": "^2.11.1",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"pinia": "^3.0.3",
|
"axios": "^1.19.0",
|
||||||
"vue": "^3.5.18",
|
"element-plus": "^2.14.5",
|
||||||
"vue-router": "^4.5.1"
|
"pinia": "^4.0.3",
|
||||||
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
|
"tailwindcss": "^4.3.3",
|
||||||
|
"vue": "^3.5.41",
|
||||||
|
"vue-router": "^5.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node22": "^22.0.2",
|
"@tsconfig/node24": "^24.0.5",
|
||||||
"@types/node": "^22.16.5",
|
"@types/node": "^24.13.3",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.8",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.9.1",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^9.0.3",
|
||||||
"typescript": "~5.8.0",
|
"typescript": "~6.0.3",
|
||||||
"vite": "^7.0.6",
|
"unplugin-auto-import": "^21.1.0",
|
||||||
"vite-plugin-vue-devtools": "^8.0.0",
|
"unplugin-vue-components": "^32.1.0",
|
||||||
"vue-tsc": "^3.0.4"
|
"vite": "^8.2.2",
|
||||||
|
"vite-plugin-vue-devtools": "^8.2.1",
|
||||||
|
"vue-tsc": "^3.3.11"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2968
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 9.8 KiB |
+3
-53
@@ -1,59 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, watch} from 'vue'
|
const themeMode = ref('light')
|
||||||
import {useRoute, useRouter} from "vue-router";
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const activeIndex = ref(route.name?.toString() || "TokenList")
|
|
||||||
|
|
||||||
watch(route, (newRoute) => {
|
|
||||||
activeIndex.value = newRoute.name?.toString() || "TokenList"
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSelect = (key: string) => {
|
|
||||||
router.push({
|
|
||||||
name: key
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-container>
|
<div :class="themeMode" class="min-h-screen bg-(--bg-page) max-w-full">
|
||||||
<el-header>
|
|
||||||
<el-menu
|
|
||||||
:default-active="activeIndex"
|
|
||||||
class="el-menu-demo"
|
|
||||||
mode="horizontal"
|
|
||||||
@select="handleSelect"
|
|
||||||
>
|
|
||||||
<el-menu-item index="TokenList">Token列表</el-menu-item>
|
|
||||||
<el-menu-item index="TokenDetail">Token信息</el-menu-item>
|
|
||||||
</el-menu>
|
|
||||||
</el-header>
|
|
||||||
|
|
||||||
<el-container>
|
|
||||||
<el-main>
|
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</el-main>
|
</div>
|
||||||
</el-container>
|
|
||||||
</el-container>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
html, body, #app {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-container {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu-demo {
|
|
||||||
line-height: 60px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const axiosInstance = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getVersion:() =>
|
||||||
|
axiosInstance.get('/api/version'),
|
||||||
|
|
||||||
|
getTokenList: () =>
|
||||||
|
axiosInstance.get('/api/token'),
|
||||||
|
|
||||||
|
createToken: (params: {
|
||||||
|
token: string
|
||||||
|
dedup_object: string
|
||||||
|
data_format: string
|
||||||
|
notes: string
|
||||||
|
}) =>
|
||||||
|
axiosInstance.post('/api/token', {}, {params}),
|
||||||
|
|
||||||
|
updateToken: (params: {
|
||||||
|
token: string
|
||||||
|
dedup_object?: string
|
||||||
|
data_format?: string
|
||||||
|
notes?: string
|
||||||
|
}) =>
|
||||||
|
axiosInstance.put('/api/token', {}, {params}),
|
||||||
|
|
||||||
|
deleteToken: (params: {
|
||||||
|
token: string
|
||||||
|
}) =>
|
||||||
|
axiosInstance.delete('/api/token', {params}),
|
||||||
|
|
||||||
|
getTokenInfo: (params: {
|
||||||
|
token: string
|
||||||
|
}) =>
|
||||||
|
axiosInstance.get('/api/token/info', {params}),
|
||||||
|
|
||||||
|
deleteTokenInfo: (params: {
|
||||||
|
token: string
|
||||||
|
dedup_bf?: string
|
||||||
|
cache_list?: string
|
||||||
|
both_number?: string
|
||||||
|
}) =>
|
||||||
|
axiosInstance.delete('/api/token/info', {params}),
|
||||||
|
|
||||||
|
uploadData: (params: {
|
||||||
|
data: string
|
||||||
|
token: string
|
||||||
|
}) =>
|
||||||
|
axiosInstance.post('/api/data', {}, {params}),
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
@import "tailwindcss" important;
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--animate-fade-in: fade-in 0.3s ease;
|
||||||
|
--bg-page: #f5f7fa;
|
||||||
|
--bg-card: #ffffff;
|
||||||
|
--bg-header: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
const instance = axios.create({
|
|
||||||
baseURL: import.meta.env.VITE_API_BASE_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default instance;
|
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import api from "@/api"
|
||||||
|
import {Upload} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: boolean
|
||||||
|
token?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const inputData = ref('')
|
||||||
|
const uploading = ref(false)
|
||||||
|
const uploadedCount = ref(0)
|
||||||
|
const totalCount = computed(() => {
|
||||||
|
const lines = inputData.value.split('\n').filter(line => line.trim())
|
||||||
|
return lines.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
inputData.value = ''
|
||||||
|
uploadedCount.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpload = async () => {
|
||||||
|
if (!inputData.value.trim()) {
|
||||||
|
ElMessage.warning('请输入数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploading.value = true
|
||||||
|
uploadedCount.value = 0
|
||||||
|
|
||||||
|
const lines = inputData.value.split('\n').filter(line => line.trim())
|
||||||
|
const total = lines.length
|
||||||
|
uploadedCount.value = 0
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
try {
|
||||||
|
await api.uploadData({data: line, token: props.token || ''})
|
||||||
|
uploadedCount.value++
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error.response?.data?.error || '上传失败')
|
||||||
|
uploading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uploading.value = false
|
||||||
|
ElMessage({message: '已上传成功 ' + uploadedCount.value + ' 条数据', type: 'success'})
|
||||||
|
setTimeout(() => {
|
||||||
|
close()
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:model-value="modelValue"
|
||||||
|
@update:model-value="emit('update:modelValue', $event)"
|
||||||
|
width="700px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
添加数据
|
||||||
|
<el-tag type="primary" effect="plain">{{ props.token }}</el-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<el-input
|
||||||
|
v-model="inputData"
|
||||||
|
type="textarea"
|
||||||
|
:rows="18"
|
||||||
|
placeholder="请输入数据,每行一条"
|
||||||
|
:disabled="uploading"
|
||||||
|
class="textarea-input"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center gap-4">
|
||||||
|
<div class="flex-1 flex items-center gap-3">
|
||||||
|
<el-progress
|
||||||
|
:percentage="totalCount > 0 ? Math.round((uploadedCount / totalCount) * 100) : 0"
|
||||||
|
:show-text="false"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-[var(--el-text-color-secondary)] whitespace-nowrap">已上传 {{ uploadedCount }}/{{ totalCount }} 行</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="uploading"
|
||||||
|
@click="handleUpload"
|
||||||
|
>
|
||||||
|
<el-icon v-if="!uploading">
|
||||||
|
<Upload/>
|
||||||
|
</el-icon>
|
||||||
|
上传
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.textarea-input :deep(.el-textarea__inner) {
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre;
|
||||||
|
word-break: keep-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+21
-7
@@ -1,15 +1,29 @@
|
|||||||
import {createApp} from 'vue'
|
import {createApp} from 'vue'
|
||||||
import {createPinia} from 'pinia'
|
import {createPinia} from 'pinia'
|
||||||
|
import persistedState from 'pinia-plugin-persistedstate';
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import ElementPlus from 'element-plus'
|
// import ElementPlus from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
// import 'element-plus/dist/index.css'
|
||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
pinia.use(persistedState)
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
app.use(pinia)
|
||||||
app.use(createPinia())
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(ElementPlus)
|
// app.use(ElementPlus)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
|
|
||||||
|
router.beforeEach((to, from) => {
|
||||||
|
var postfix: string = ''
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
postfix = '-dev'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.meta.title) {
|
||||||
|
document.title = to.meta.title + postfix
|
||||||
|
}
|
||||||
|
})
|
||||||
+35
-4
@@ -1,11 +1,42 @@
|
|||||||
import {createRouter, createWebHistory} from 'vue-router'
|
import {createRouter, createWebHistory} from 'vue-router'
|
||||||
|
|
||||||
import TokenListView from '@/views/TokenListView.vue'
|
import TokenManageView from '@/views/admin/TokenManageView.vue'
|
||||||
import TokenDetailView from '@/views/TokenDetailView.vue'
|
import TokenDetailView from '@/views/admin/TokenDetailView.vue'
|
||||||
|
import AdminView from '@/views/AdminView.vue'
|
||||||
|
import HomeView from '@/views/HomeView.vue'
|
||||||
|
import SettingsView from '@/views/admin/SettingsView.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{path: '/', name: "TokenList", component: TokenListView},
|
{
|
||||||
{path: '/', name: "TokenDetail", component: TokenDetailView},
|
path: '/',
|
||||||
|
name: "Home",
|
||||||
|
component: HomeView,
|
||||||
|
meta: {title: "抖音数据去重",}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/haha',
|
||||||
|
name: "Admin",
|
||||||
|
component: AdminView,
|
||||||
|
meta: {title: "抖音数据去重 | 管理后台",},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: "TokenManage",
|
||||||
|
component: TokenManageView
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'token',
|
||||||
|
name: "TokenDetail",
|
||||||
|
component: TokenDetailView
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "settings",
|
||||||
|
name: "AdminSettings",
|
||||||
|
component: SettingsView,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import {ref, computed} from 'vue'
|
import {ref} from 'vue'
|
||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
|
|
||||||
export const useCounterStore = defineStore('counter', () => {
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
const token = ref("")
|
const token = ref("")
|
||||||
|
const isAdmin = ref(false)
|
||||||
|
|
||||||
return {token}
|
return {token, isAdmin}
|
||||||
|
}, {
|
||||||
|
persist: true
|
||||||
})
|
})
|
||||||
|
|||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
/* oxlint-disable */
|
||||||
|
/* oxfmt-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const EffectScope: typeof import('vue').EffectScope
|
||||||
|
const ElMessage: typeof import('element-plus/es').ElMessage
|
||||||
|
const ElMessageBox: typeof import('element-plus/es').ElMessageBox
|
||||||
|
const computed: typeof import('vue').computed
|
||||||
|
const createApp: typeof import('vue').createApp
|
||||||
|
const customRef: typeof import('vue').customRef
|
||||||
|
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
|
||||||
|
const defineComponent: typeof import('vue').defineComponent
|
||||||
|
const effectScope: typeof import('vue').effectScope
|
||||||
|
const getCurrentInstance: typeof import('vue').getCurrentInstance
|
||||||
|
const getCurrentScope: typeof import('vue').getCurrentScope
|
||||||
|
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
|
||||||
|
const h: typeof import('vue').h
|
||||||
|
const inject: typeof import('vue').inject
|
||||||
|
const isProxy: typeof import('vue').isProxy
|
||||||
|
const isReactive: typeof import('vue').isReactive
|
||||||
|
const isReadonly: typeof import('vue').isReadonly
|
||||||
|
const isRef: typeof import('vue').isRef
|
||||||
|
const isShallow: typeof import('vue').isShallow
|
||||||
|
const markRaw: typeof import('vue').markRaw
|
||||||
|
const nextTick: typeof import('vue').nextTick
|
||||||
|
const onActivated: typeof import('vue').onActivated
|
||||||
|
const onBeforeMount: typeof import('vue').onBeforeMount
|
||||||
|
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
|
||||||
|
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
|
||||||
|
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
|
||||||
|
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
|
||||||
|
const onDeactivated: typeof import('vue').onDeactivated
|
||||||
|
const onErrorCaptured: typeof import('vue').onErrorCaptured
|
||||||
|
const onMounted: typeof import('vue').onMounted
|
||||||
|
const onRenderTracked: typeof import('vue').onRenderTracked
|
||||||
|
const onRenderTriggered: typeof import('vue').onRenderTriggered
|
||||||
|
const onScopeDispose: typeof import('vue').onScopeDispose
|
||||||
|
const onServerPrefetch: typeof import('vue').onServerPrefetch
|
||||||
|
const onUnmounted: typeof import('vue').onUnmounted
|
||||||
|
const onUpdated: typeof import('vue').onUpdated
|
||||||
|
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
|
||||||
|
const provide: typeof import('vue').provide
|
||||||
|
const reactive: typeof import('vue').reactive
|
||||||
|
const readonly: typeof import('vue').readonly
|
||||||
|
const ref: typeof import('vue').ref
|
||||||
|
const resolveComponent: typeof import('vue').resolveComponent
|
||||||
|
const shallowReactive: typeof import('vue').shallowReactive
|
||||||
|
const shallowReadonly: typeof import('vue').shallowReadonly
|
||||||
|
const shallowRef: typeof import('vue').shallowRef
|
||||||
|
const toRaw: typeof import('vue').toRaw
|
||||||
|
const toRef: typeof import('vue').toRef
|
||||||
|
const toRefs: typeof import('vue').toRefs
|
||||||
|
const toValue: typeof import('vue').toValue
|
||||||
|
const triggerRef: typeof import('vue').triggerRef
|
||||||
|
const unref: typeof import('vue').unref
|
||||||
|
const useAttrs: typeof import('vue').useAttrs
|
||||||
|
const useCssModule: typeof import('vue').useCssModule
|
||||||
|
const useCssVars: typeof import('vue').useCssVars
|
||||||
|
const useId: typeof import('vue').useId
|
||||||
|
const useLink: typeof import('vue-router').useLink
|
||||||
|
const useModel: typeof import('vue').useModel
|
||||||
|
const useRoute: typeof import('vue-router').useRoute
|
||||||
|
const useRouter: typeof import('vue-router').useRouter
|
||||||
|
const useSlots: typeof import('vue').useSlots
|
||||||
|
const useTemplateRef: typeof import('vue').useTemplateRef
|
||||||
|
const watch: typeof import('vue').watch
|
||||||
|
const watchEffect: typeof import('vue').watchEffect
|
||||||
|
const watchPostEffect: typeof import('vue').watchPostEffect
|
||||||
|
const watchSyncEffect: typeof import('vue').watchSyncEffect
|
||||||
|
}
|
||||||
|
// for type re-export
|
||||||
|
declare global {
|
||||||
|
// @ts-ignore
|
||||||
|
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
|
import('vue')
|
||||||
|
}
|
||||||
Vendored
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
// oxlint-disable
|
||||||
|
// ------
|
||||||
|
// Generated by unplugin-vue-components
|
||||||
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
|
||||||
|
export {}
|
||||||
|
|
||||||
|
/* prettier-ignore */
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
AddDataDialog: typeof import('./../../components/AddDataDialog.vue')['default']
|
||||||
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
|
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||||
|
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||||
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
|
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||||
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
|
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
||||||
|
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||||
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
|
ElSpace: typeof import('element-plus/es')['ElSpace']
|
||||||
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
|
ElText: typeof import('element-plus/es')['ElText']
|
||||||
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
}
|
||||||
|
export interface GlobalDirectives {
|
||||||
|
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {useCounterStore} from "@/stores/counter.ts"
|
||||||
|
import {CloseBold, Document, Edit, Lock, Setting, User} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const store = useCounterStore()
|
||||||
|
|
||||||
|
const activeIndex = ref(route.name?.toString() || "TokenManage")
|
||||||
|
const _userName = ref("未登录")
|
||||||
|
const _isAdmin = ref(store.isAdmin)
|
||||||
|
const inputPassWord = ref('')
|
||||||
|
|
||||||
|
if (_isAdmin.value) {
|
||||||
|
_userName.value = "管理员"
|
||||||
|
} else {
|
||||||
|
_userName.value = "未登录"
|
||||||
|
}
|
||||||
|
watch(_isAdmin, (newValue) => {
|
||||||
|
store.isAdmin = newValue
|
||||||
|
if (newValue) {
|
||||||
|
_userName.value = "管理员"
|
||||||
|
} else {
|
||||||
|
_userName.value = "未登录"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(route, (newRoute) => {
|
||||||
|
activeIndex.value = newRoute.name?.toString() || "TokenManage"
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSelect = (name: string) => {
|
||||||
|
router.push({name: name})
|
||||||
|
}
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
_isAdmin.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkPassword = () => {
|
||||||
|
if (inputPassWord.value === "haha") {
|
||||||
|
ElMessage({message: '登录成功', type: 'success'})
|
||||||
|
_isAdmin.value = true
|
||||||
|
} else {
|
||||||
|
ElMessage.error('密码错误')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const navItemClass = (name: string) => {
|
||||||
|
return activeIndex.value === name
|
||||||
|
? 'bg-[var(--el-color-primary)] text-white'
|
||||||
|
: 'text-[var(--el-text-color-regular)] hover:bg-[var(--bg-page)] hover:text-[var(--el-text-color-primary)]'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="min-h-screen flex flex-col bg-(--bg-page)">
|
||||||
|
<el-header
|
||||||
|
class="flex items-center justify-between bg-(--bg-header) border-b border-(--el-border-color) sticky top-0 z-50 max-md:flex-wrap max-md:h-auto max-md:px-4 max-md:py-3">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex items-center gap-2.5 text-(--el-color-primary) font-semibold text-lg">DYPID 管理后台</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="flex gap-2 max-md:order-3 max-md:w-full max-md:mt-3 max-md:overflow-x-auto max-md:pb-1">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-1.5 px-4 py-2 rounded-lg cursor-pointer text-sm transition-all duration-200 max-md:px-3 max-md:py-2 max-md:text-xs"
|
||||||
|
:class="navItemClass('TokenManage')"
|
||||||
|
@click="handleSelect('TokenManage')"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<Edit/>
|
||||||
|
</el-icon>
|
||||||
|
<span>管理 Token</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-1.5 px-4 py-2 rounded-lg cursor-pointer text-sm transition-all duration-200 max-md:px-3 max-md:py-2 max-md:text-xs"
|
||||||
|
:class="navItemClass('TokenDetail')"
|
||||||
|
@click="handleSelect('TokenDetail')"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<Document/>
|
||||||
|
</el-icon>
|
||||||
|
<span>Token 详情</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<el-button v-if="_isAdmin" circle link
|
||||||
|
@click="handleSelect('AdminSettings')"
|
||||||
|
>
|
||||||
|
<el-icon size="16">
|
||||||
|
<Setting/>
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
<el-divider direction="vertical"/>
|
||||||
|
<el-dropdown :disabled="!_isAdmin">
|
||||||
|
<el-button :icon="User" link>{{ _userName }}</el-button>
|
||||||
|
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item @click="logout()">
|
||||||
|
<el-text type="danger">
|
||||||
|
<el-icon>
|
||||||
|
<CloseBold/>
|
||||||
|
</el-icon>
|
||||||
|
退出登录
|
||||||
|
</el-text>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
</el-header>
|
||||||
|
|
||||||
|
<el-main>
|
||||||
|
<!-- 登录页面 -->
|
||||||
|
<div v-if="!_isAdmin" class="flex justify-center items-center min-h-[60vh]">
|
||||||
|
<div
|
||||||
|
class="w-full max-w-100 text-center py-12 px-10 bg-(--bg-card) rounded-2xl shadow-[0_4px_24px_rgba(0,0,0,0.08)]">
|
||||||
|
<div class="mb-6">
|
||||||
|
<el-icon size="48" color="#409eff">
|
||||||
|
<Lock/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-semibold text-(--el-text-color-primary) mb-2">管理员登录</div>
|
||||||
|
<div class="text-sm text-(--el-text-color-secondary) mb-8">请输入管理员密码访问管理后台</div>
|
||||||
|
|
||||||
|
<el-input
|
||||||
|
v-model="inputPassWord"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入管理员密码"
|
||||||
|
size="large"
|
||||||
|
show-password
|
||||||
|
class="mb-4"
|
||||||
|
@keyup.enter="checkPassword"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Lock/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-button type="primary" size="large" class="w-full mt-2" @click="checkPassword">
|
||||||
|
登录
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<router-view v-else v-slot="{ Component }">
|
||||||
|
<component :is="Component"/>
|
||||||
|
</router-view>
|
||||||
|
</el-main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import api from "@/api"
|
||||||
|
import {DataAnalysis, Document, Key, Plus, Search, Warning} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const inputToken = ref(route.query.token as string || '')
|
||||||
|
const result = ref<any>(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const lastUpdate = ref('')
|
||||||
|
const showAddDataDialog = ref(false)
|
||||||
|
|
||||||
|
const updateTime = () => {
|
||||||
|
const now = new Date()
|
||||||
|
lastUpdate.value = now.toLocaleTimeString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchInfo = async () => {
|
||||||
|
if (!inputToken.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await api.getTokenInfo({token: inputToken.value})
|
||||||
|
result.value = res.data.data
|
||||||
|
updateTime()
|
||||||
|
} catch {
|
||||||
|
result.value = null
|
||||||
|
ElMessage({message: 'Token输入错误', type: 'error', duration: 2000})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await api.getTokenInfo({token: inputToken.value})
|
||||||
|
result.value = res.data.data
|
||||||
|
updateTime()
|
||||||
|
ElMessage({message: '查询成功', type: 'success', duration: 1500})
|
||||||
|
} catch {
|
||||||
|
result.value = null
|
||||||
|
ElMessage({message: 'Token输入错误', type: 'error', duration: 2000})
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputChange = () => {
|
||||||
|
if (inputToken.value) fetchInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
let timer: number
|
||||||
|
onMounted(() => {
|
||||||
|
fetchInfo()
|
||||||
|
timer = window.setInterval(fetchInfo, 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(timer)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="p-6 h-dvh">
|
||||||
|
<div class="bg-white rounded-lg p-6 shadow-[0_2px_12px_rgba(0,0,0,0.08)] h-full">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<div class="text-xl font-semibold text-(--el-text-color-primary) mb-5 pb-3 border-b border-(--el-border-color)">
|
||||||
|
Token 信息查询
|
||||||
|
</div>
|
||||||
|
<div class="text-(--el-text-color-secondary) mt-2">输入 Token 以查看去重和缓存信息</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center mb-8">
|
||||||
|
<el-input placeholder="请输入要查询数据的Token..." size="large" clearable
|
||||||
|
class="max-w-120"
|
||||||
|
v-model="inputToken" @change="inputChange"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
Token
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-button type="primary" size="large"
|
||||||
|
class="ml-3"
|
||||||
|
@click="refresh" :loading="loading" :icon="Search"
|
||||||
|
>
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-space direction="vertical"
|
||||||
|
class="w-full animate-fade-in"
|
||||||
|
v-if="result"
|
||||||
|
>
|
||||||
|
<el-card class="p-6">
|
||||||
|
<template #header>
|
||||||
|
<el-text>Token 信息</el-text>
|
||||||
|
<el-divider direction="vertical"/>
|
||||||
|
<el-tag type="primary" effect="plain">{{ inputToken }}</el-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-space direction="vertical">
|
||||||
|
<el-space>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200
|
||||||
|
hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)]
|
||||||
|
min-w-100"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0"
|
||||||
|
style="background: #409eff20; color: #409eff"
|
||||||
|
>
|
||||||
|
<el-icon size="24">
|
||||||
|
<DataAnalysis/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary) mb-1">
|
||||||
|
去重对象
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-lg font-semibold text-(--el-text-color-primary) overflow-hidden text-ellipsis whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{{ result['dedup_object'] || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200
|
||||||
|
hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)]
|
||||||
|
min-w-100"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0"
|
||||||
|
style="background: #67c23a20; color: #67c23a"
|
||||||
|
>
|
||||||
|
<el-icon size="24">
|
||||||
|
<Document/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary) mb-1">
|
||||||
|
上传数据格式
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-lg font-semibold text-(--el-text-color-primary) overflow-hidden text-ellipsis whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{{ result['data_format'] || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-space>
|
||||||
|
<el-space>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200
|
||||||
|
hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)]
|
||||||
|
min-w-100"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0"
|
||||||
|
style="background: #e6a23c20; color: #e6a23c"
|
||||||
|
>
|
||||||
|
<el-icon size="24">
|
||||||
|
<Key/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary) mb-1">
|
||||||
|
去重记录值
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-lg font-semibold text-(--el-text-color-primary) overflow-hidden text-ellipsis whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{{ result['dedup_items_number'] || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200
|
||||||
|
hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)]
|
||||||
|
min-w-100"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0"
|
||||||
|
style="background: #90939920; color: #909399"
|
||||||
|
>
|
||||||
|
<el-icon size="24">
|
||||||
|
<Warning/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary) mb-1">
|
||||||
|
Redis中数据条数
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-lg font-semibold text-(--el-text-color-primary) overflow-hidden text-ellipsis whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{{ result['cache_list_number'] || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-space>
|
||||||
|
</el-space>
|
||||||
|
|
||||||
|
<el-text v-if="lastUpdate" type="info"
|
||||||
|
class="block w-full mt-5 text-center"
|
||||||
|
>
|
||||||
|
每5秒更新一次数据 最后更新: {{ lastUpdate }}
|
||||||
|
</el-text>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-text>操作</el-text>
|
||||||
|
<el-divider direction="vertical"/>
|
||||||
|
<el-button type="primary" size=""
|
||||||
|
class=""
|
||||||
|
@click="showAddDataDialog = true" :icon="Plus"
|
||||||
|
>
|
||||||
|
添加数据
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-card>
|
||||||
|
</el-space>
|
||||||
|
|
||||||
|
<el-space v-else direction="vertical"
|
||||||
|
class="flex p-16"
|
||||||
|
>
|
||||||
|
<el-icon size="64" color="#dcdfe6">
|
||||||
|
<Search/>
|
||||||
|
</el-icon>
|
||||||
|
<el-text type="info" size="large" class="">请输入 Token 进行查询</el-text>
|
||||||
|
</el-space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AddDataDialog v-model="showAddDataDialog" :token="inputToken"/>
|
||||||
|
</template>
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {useCounterStore} from "@/stores/counter.ts";
|
|
||||||
import {ref} from 'vue'
|
|
||||||
import axios from "@/axios.ts";
|
|
||||||
|
|
||||||
const result = ref()
|
|
||||||
|
|
||||||
const getInfo = () => {
|
|
||||||
axios.get('/api/token/info', {
|
|
||||||
params: {
|
|
||||||
token: useCounterStore().token
|
|
||||||
}
|
|
||||||
}).then(res => {
|
|
||||||
if (res.status == 200) {
|
|
||||||
result.value = res.data.result
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteDedup = () => {
|
|
||||||
axios.delete('/api/token/info', {
|
|
||||||
params: {
|
|
||||||
token: useCounterStore().token,
|
|
||||||
dedup_bf: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
getInfo()
|
|
||||||
}
|
|
||||||
const deleteRedis = () => {
|
|
||||||
axios.delete('/api/token/info', {
|
|
||||||
params: {
|
|
||||||
token: useCounterStore().token,
|
|
||||||
cache_list: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
getInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
getInfo()
|
|
||||||
setInterval(getInfo, 5000)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<p>当前Token:{{ useCounterStore().token }}</p>
|
|
||||||
<el-button type="danger" @click="deleteDedup">删除去重记录值</el-button>
|
|
||||||
<el-button type="danger" @click="deleteRedis">删除Redis数据</el-button>
|
|
||||||
<el-divider/>
|
|
||||||
<el-button type="primary" @click="getInfo">手动刷新</el-button>
|
|
||||||
<el-descriptions
|
|
||||||
:title="'Token信息 - ' + useCounterStore().token+'(每5秒刷新)'"
|
|
||||||
direction="vertical"
|
|
||||||
:column="4"
|
|
||||||
border
|
|
||||||
>
|
|
||||||
<el-descriptions-item label="去重对象">{{ result?.dedup_object }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="去重记录值">{{ result?.dedup_items_number }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="Redis中数据条数">{{ result?.cache_list_number }}</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import {ref} from "vue";
|
|
||||||
import axios from "@/axios.ts";
|
|
||||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
|
||||||
|
|
||||||
const tableData = ref([])
|
|
||||||
|
|
||||||
axios.get("/api/token").then(res => {
|
|
||||||
tableData.value = res.data.result
|
|
||||||
})
|
|
||||||
|
|
||||||
const input = ref('')
|
|
||||||
const value = ref('')
|
|
||||||
const options = [
|
|
||||||
{
|
|
||||||
value: 'uid',
|
|
||||||
label: 'uid',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'pid',
|
|
||||||
label: 'pid',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'secid',
|
|
||||||
label: 'secid',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'dyid',
|
|
||||||
label: 'dyid',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'comment_id',
|
|
||||||
label: 'comment_id',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const addToken = () => {
|
|
||||||
axios.post('/api/token', {}, {
|
|
||||||
params: {
|
|
||||||
token: input.value,
|
|
||||||
dedup_object: value.value
|
|
||||||
}
|
|
||||||
}).then(response => {
|
|
||||||
if (response.data.result == "ok") {
|
|
||||||
ElMessage({
|
|
||||||
message: '添加成功',
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
axios.get('/api/token').then(res => {
|
|
||||||
tableData.value = res.data.result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
ElMessage.error(error.response?.data?.error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
import {useCounterStore} from "@/stores/counter.ts";
|
|
||||||
import {useRouter} from "vue-router";
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const viewDetails = (row: any) => {
|
|
||||||
useCounterStore().token = row.token
|
|
||||||
router.push({
|
|
||||||
name: "TokenDetail"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var rowOut: any
|
|
||||||
const dedupObjectVisible = ref(false)
|
|
||||||
const dialogDedupObjectVisible = (row: any) => {
|
|
||||||
rowOut = row
|
|
||||||
dedupObjectVisible.value = true
|
|
||||||
}
|
|
||||||
const updateDedupObject = () => {
|
|
||||||
dedupObjectVisible.value = false
|
|
||||||
axios.put('/api/token', {}, {
|
|
||||||
params: {
|
|
||||||
token: rowOut.token,
|
|
||||||
dedup_object: value.value
|
|
||||||
}
|
|
||||||
}).then(res => {
|
|
||||||
if (res.data.result == "ok") {
|
|
||||||
ElMessage({
|
|
||||||
message: '更改成功',
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
axios.get('/api/token').then(res => {
|
|
||||||
tableData.value = res.data.result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
ElMessage.error(error.response?.data?.error)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteToken = (row: any) => {
|
|
||||||
axios.delete('/api/token', {
|
|
||||||
params: {
|
|
||||||
token: row.token
|
|
||||||
}
|
|
||||||
}).then(res => {
|
|
||||||
if (res.data.result == "ok") {
|
|
||||||
ElMessage({
|
|
||||||
message: '删除成功',
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
axios.get('/api/token').then(res => {
|
|
||||||
tableData.value = res.data.result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
ElMessage.error(error.response?.data?.error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!--添加Token-->
|
|
||||||
<el-input v-model="input" style="width: 200px" placeholder="请输入Token名称"/>
|
|
||||||
<el-select v-model="value" placeholder="选择去重对象" style="width: 200px">
|
|
||||||
<el-option
|
|
||||||
v-for="item in options"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<el-button type="primary" @click="addToken">添加Token</el-button>
|
|
||||||
|
|
||||||
<!--Token列表-->
|
|
||||||
<el-table :data="tableData" style="width: 100%">
|
|
||||||
<el-table-column prop="token" label="Token" width="180"/>
|
|
||||||
<el-table-column prop="dedup_object" label="去重对象" width="180"/>
|
|
||||||
<el-table-column label="操作">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button @click="viewDetails(scope.row)">查看详细</el-button>
|
|
||||||
<el-button @click="dialogDedupObjectVisible(scope.row)" type="primary">更改去重对象</el-button>
|
|
||||||
<el-popconfirm
|
|
||||||
width="180"
|
|
||||||
title="确认删除此Token吗"
|
|
||||||
confirm-button-text="确认"
|
|
||||||
cancel-button-text="取消"
|
|
||||||
@confirm="deleteToken(scope.row)"
|
|
||||||
>
|
|
||||||
<template #reference>
|
|
||||||
<el-button type="danger">删除此Token</el-button>
|
|
||||||
</template>
|
|
||||||
</el-popconfirm>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<el-dialog v-model="dedupObjectVisible" title="更改去重对象" width="400">
|
|
||||||
<el-select v-model="value" placeholder="选择去重对象" style="width: 200px">
|
|
||||||
<el-option
|
|
||||||
v-for="item in options"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<template #footer>
|
|
||||||
<el-button type="primary" @click="updateDedupObject">
|
|
||||||
确定
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import api from "@/api.ts";
|
||||||
|
|
||||||
|
const version = ref('')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
api.getVersion().then((response) => {
|
||||||
|
version.value = response.data
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{ version }}
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {useCounterStore} from "@/stores/counter.ts"
|
||||||
|
import api from "@/api"
|
||||||
|
import {DataAnalysis, Delete, Document, InfoFilled, Key, Refresh, Search, Warning} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const store = useCounterStore()
|
||||||
|
const result = ref<any>(null)
|
||||||
|
const value = ref('')
|
||||||
|
const options = ref<string[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const lastUpdate = ref('')
|
||||||
|
|
||||||
|
const deleteSpecifyDataVisible = ref(false)
|
||||||
|
const inputSpecifyData = ref('')
|
||||||
|
const deleteSpecifyDedupVisible = ref(false)
|
||||||
|
const inputSpecifyDedup = ref('')
|
||||||
|
const deleteSpecifyRawVisible = ref(false)
|
||||||
|
const inputSpecifyRaw = ref('')
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const fetchInfo = async () => {
|
||||||
|
if (!value.value) return
|
||||||
|
|
||||||
|
const res = await api.getTokenInfo({token: value.value})
|
||||||
|
if (res.status === 200) {
|
||||||
|
result.value = res.data.data
|
||||||
|
lastUpdate.value = new Date().toLocaleTimeString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchTokens = async () => {
|
||||||
|
try {
|
||||||
|
const res = await api.getTokenList()
|
||||||
|
if (res.status === 200) {
|
||||||
|
options.value = res.data.data?.map((item: any) => item.token) || []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch tokens:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
loading.value = true
|
||||||
|
await fetchInfo()
|
||||||
|
loading.value = false
|
||||||
|
ElMessage({message: '刷新成功', type: 'success', duration: 1500})
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteDedup = () => {
|
||||||
|
ElMessageBox.confirm('确定要删除全部去重参考值吗?', '警告', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).then(async () => {
|
||||||
|
await api.deleteTokenInfo({token: value.value, dedup_bf: "all"})
|
||||||
|
ElMessage({message: '删除成功', type: 'success'})
|
||||||
|
await fetchInfo()
|
||||||
|
}).catch(() => {
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRedis = () => {
|
||||||
|
ElMessageBox.confirm('确定要删除全部原始数据吗?', '警告', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}).then(async () => {
|
||||||
|
await api.deleteTokenInfo({token: value.value, cache_list: "all"})
|
||||||
|
ElMessage({message: '删除成功', type: 'success'})
|
||||||
|
await fetchInfo()
|
||||||
|
}).catch(() => {
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSpecifyDedup = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
await api.deleteTokenInfo({token: value.value, dedup_bf: inputSpecifyDedup.value})
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
ElMessage({message: '删除成功', type: 'success'})
|
||||||
|
deleteSpecifyDedupVisible.value = false
|
||||||
|
inputSpecifyDedup.value = ''
|
||||||
|
|
||||||
|
await fetchInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSpecifyRaw = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
await api.deleteTokenInfo({token: value.value, cache_list: inputSpecifyRaw.value})
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
ElMessage({message: '删除成功', type: 'success'})
|
||||||
|
deleteSpecifyRawVisible.value = false
|
||||||
|
inputSpecifyRaw.value = ''
|
||||||
|
|
||||||
|
await fetchInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSpecifyData = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
await api.deleteTokenInfo({token: value.value, both_number: inputSpecifyData.value})
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
ElMessage({message: '删除成功', type: 'success'})
|
||||||
|
deleteSpecifyDataVisible.value = false
|
||||||
|
inputSpecifyData.value = ''
|
||||||
|
|
||||||
|
await fetchInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
let timer: number
|
||||||
|
onMounted(() => {
|
||||||
|
fetchTokens()
|
||||||
|
if (store.token) {
|
||||||
|
value.value = store.token
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(value, (newValue) => {
|
||||||
|
store.token = newValue
|
||||||
|
if (newValue) fetchInfo()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (value.value) fetchInfo()
|
||||||
|
timer = window.setInterval(fetchInfo, 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(timer)
|
||||||
|
})
|
||||||
|
|
||||||
|
const statCards = [
|
||||||
|
{label: '去重对象', key: 'dedup_object', icon: DataAnalysis, color: '#409eff'},
|
||||||
|
{label: '上传数据格式', key: 'data_format', icon: Document, color: '#67c23a'},
|
||||||
|
{label: '去重参考值数量', key: 'dedup_items_number', icon: Key, color: '#e6a23c'},
|
||||||
|
{label: '原始数据数量', key: 'cache_list_number', icon: Warning, color: '#909399'},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mx-auto w-full">
|
||||||
|
<!-- 管理页面 -->
|
||||||
|
<div class="bg-white rounded-lg p-6 shadow-[0_2px_12px_rgba(0,0,0,0.08)]">
|
||||||
|
<div class="text-xl font-semibold text-(--el-text-color-primary) mb-5 pb-3 border-b border-(--el-border-color)">
|
||||||
|
Token 详情
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 选择 Token -->
|
||||||
|
<div class="flex items-center gap-3 mb-6 flex-wrap max-md:flex-col max-md:items-stretch">
|
||||||
|
<el-select v-model="value" placeholder="选择 Token" clearable style="width: 100%; max-width: 300px">
|
||||||
|
<el-option v-for="item in options" :key="item" :value="item" :label="item"/>
|
||||||
|
</el-select>
|
||||||
|
<el-button type="primary" @click="refresh" :loading="loading">
|
||||||
|
<el-icon>
|
||||||
|
<Refresh/>
|
||||||
|
</el-icon>
|
||||||
|
刷新
|
||||||
|
</el-button>
|
||||||
|
<span v-if="lastUpdate" class="text-xs text-(--el-text-color-secondary)">上次更新: {{ lastUpdate }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Token 信息 -->
|
||||||
|
<div v-if="result && value" class="animate-fade-in">
|
||||||
|
<div class="text-base font-semibold text-(--el-text-color-primary) mt-5 mb-4">Token 信息</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-4 gap-4 mb-8 max-xl:grid-cols-2 max-md:grid-cols-1">
|
||||||
|
<div v-for="card in statCards" :key="card.key"
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)]">
|
||||||
|
<div class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0"
|
||||||
|
:style="{ background: card.color + '20', color: card.color }">
|
||||||
|
<el-icon size="24">
|
||||||
|
<component :is="card.icon"/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary) mb-1">{{ card.label }}</div>
|
||||||
|
<div class="text-lg font-semibold text-(--el-text-color-primary)">{{ result[card.key] || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 管理操作 -->
|
||||||
|
<div class="mt-8">
|
||||||
|
<div class="text-base font-semibold text-(--el-text-color-primary) mt-5 mb-4">数据管理</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-3 gap-4 max-xl:grid-cols-2 max-md:grid-cols-1">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)] max-md:flex-col max-md:text-center">
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0 bg-(--el-color-primary) text-white">
|
||||||
|
<el-icon size="28">
|
||||||
|
<Delete/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="text-base font-semibold text-(--el-text-color-primary) mb-1">删除全部去重参考值</div>
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary)">清除该 Token 的所有去重布隆过滤器</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="danger" @click="deleteDedup">执行</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)] max-md:flex-col max-md:text-center">
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0 bg-(--el-color-primary) text-white">
|
||||||
|
<el-icon size="28">
|
||||||
|
<Delete/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="text-base font-semibold text-(--el-text-color-primary) mb-1">删除全部原始数据</div>
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary)">清除该 Token 的所有原始缓存数据</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="danger" @click="deleteRedis">执行</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-sm font-semibold text-(--el-text-color-secondary) mt-6 mb-4">按数量删除</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-3 gap-4 max-xl:grid-cols-2 max-md:grid-cols-1">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)] max-md:flex-col max-md:text-center">
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0 bg-(--el-color-warning) text-white">
|
||||||
|
<el-icon size="28">
|
||||||
|
<Search/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="text-base font-semibold text-(--el-text-color-primary) mb-1">删除指定数量去重参考值</div>
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary)">指定要删除的去重参考值数量</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="warning" @click="deleteSpecifyDedupVisible = true">指定</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)] max-md:flex-col max-md:text-center">
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0 bg-(--el-color-warning) text-white">
|
||||||
|
<el-icon size="28">
|
||||||
|
<Search/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="text-base font-semibold text-(--el-text-color-primary) mb-1">删除指定数量原始数据</div>
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary)">指定要删除的原始数据数量</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="warning" @click="deleteSpecifyRawVisible = true">指定</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 p-5 bg-(--bg-page) rounded-xl transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_4px_16px_rgba(0,0,0,0.1)] max-md:flex-col max-md:text-center">
|
||||||
|
<div
|
||||||
|
class="w-14 h-14 rounded-xl flex items-center justify-center shrink-0 bg-(--el-color-warning) text-white">
|
||||||
|
<el-icon size="28">
|
||||||
|
<Search/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="text-base font-semibold text-(--el-text-color-primary) mb-1">删除指定数量数据</div>
|
||||||
|
<div class="text-xs text-(--el-text-color-secondary)">同时删除去重参考值和原始数据</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="warning" @click="deleteSpecifyDataVisible = true">指定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-else-if="value" class="flex flex-col items-center p-16 text-(--el-text-color-secondary)">
|
||||||
|
<el-icon size="64" color="#dcdfe6">
|
||||||
|
<InfoFilled/>
|
||||||
|
</el-icon>
|
||||||
|
<div class="mt-4 text-[15px]">暂无数据</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 未选择 -->
|
||||||
|
<div v-else class="flex flex-col items-center p-16 text-(--el-text-color-secondary)">
|
||||||
|
<el-icon size="64" color="#dcdfe6">
|
||||||
|
<Key/>
|
||||||
|
</el-icon>
|
||||||
|
<div class="mt-4 text-[15px]">请选择 Token 查看详情</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 删除确认对话框 -->
|
||||||
|
<el-dialog v-model="deleteSpecifyDedupVisible" title="删除指定数量去重参考值" width="400">
|
||||||
|
<el-input v-model="inputSpecifyDedup" placeholder="请输入删除数量" type="number"/>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="deleteSpecifyDedupVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="deleteSpecifyDedup" :loading="isLoading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog v-model="deleteSpecifyRawVisible" title="删除指定数量原始数据" width="400">
|
||||||
|
<el-input v-model="inputSpecifyRaw" placeholder="请输入删除数量" type="number"/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="deleteSpecifyRawVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="deleteSpecifyRaw" :loading="isLoading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog v-model="deleteSpecifyDataVisible" title="删除指定数量数据" width="400">
|
||||||
|
<el-input v-model="inputSpecifyData" placeholder="请输入删除数量" type="number"/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="deleteSpecifyDataVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="deleteSpecifyData" :loading="isLoading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import api from "@/api"
|
||||||
|
import {useCounterStore} from "@/stores/counter.ts"
|
||||||
|
import {Delete, Edit, Key, Memo, Plus, View} from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const store = useCounterStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const tableData = ref<any[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const input = ref('')
|
||||||
|
|
||||||
|
const inputDedupObject = ref('')
|
||||||
|
const inputDataFormat = ref('')
|
||||||
|
const inputNotes = ref('')
|
||||||
|
|
||||||
|
const activeNames = ref(['1'])
|
||||||
|
|
||||||
|
const rowOut = ref<any>(null)
|
||||||
|
const addDataDialogVisible = ref(false)
|
||||||
|
const dataDialogVisible = ref(false)
|
||||||
|
|
||||||
|
const isAdding = ref(false)
|
||||||
|
const optionName = ref('')
|
||||||
|
|
||||||
|
const onAddOption = () => {
|
||||||
|
isAdding.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const onConfirm = () => {
|
||||||
|
if (optionName.value) {
|
||||||
|
dataFormatOptions.push({
|
||||||
|
label: optionName.value,
|
||||||
|
value: optionName.value,
|
||||||
|
})
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
optionName.value = ''
|
||||||
|
isAdding.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{value: 'false', label: '不去重'},
|
||||||
|
{value: 'uid', label: 'uid'},
|
||||||
|
{value: 'secid', label: 'secid'},
|
||||||
|
{value: 'pid', label: 'pid'},
|
||||||
|
{value: 'comment_id', label: 'comment_id'},
|
||||||
|
{value: 'dyid', label: 'dyid'},
|
||||||
|
{value: "弹幕ID", label: "弹幕ID"}
|
||||||
|
]
|
||||||
|
|
||||||
|
const dataFormatOptions = [
|
||||||
|
{value: 'uid----secid----pid----comment_id', label: 'uid----secid----pid----comment_id'},
|
||||||
|
{value: "uid----pid----弹幕ID", label: "uid----pid----弹幕ID"},
|
||||||
|
{value: 'uid----secid', label: 'uid----secid'},
|
||||||
|
{value: 'dyid', label: 'dyid'}
|
||||||
|
]
|
||||||
|
|
||||||
|
const fetchTokens = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await api.getTokenList()
|
||||||
|
tableData.value = res.data.data || []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addToken = async () => {
|
||||||
|
if (!input.value || !inputDedupObject.value || !inputDataFormat.value) {
|
||||||
|
ElMessage.warning('请填写完整信息')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await api.createToken({
|
||||||
|
token: input.value,
|
||||||
|
dedup_object: inputDedupObject.value,
|
||||||
|
data_format: inputDataFormat.value,
|
||||||
|
notes: inputNotes.value,
|
||||||
|
})
|
||||||
|
ElMessage({message: '添加成功', type: 'success'})
|
||||||
|
input.value = ''
|
||||||
|
inputDedupObject.value = ''
|
||||||
|
inputDataFormat.value = ''
|
||||||
|
inputNotes.value = ''
|
||||||
|
fetchTokens()
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error('添加失败' + error.response?.data?.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewDetails = (row: any) => {
|
||||||
|
store.token = row.token
|
||||||
|
router.push({name: "TokenDetail"})
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogDataADDVisible = (row: any) => {
|
||||||
|
rowOut.value = row
|
||||||
|
|
||||||
|
addDataDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayDataVisible = (row: any) => {
|
||||||
|
rowOut.value = row
|
||||||
|
|
||||||
|
inputDedupObject.value = row.dedup_object
|
||||||
|
inputDataFormat.value = row.data_format
|
||||||
|
inputNotes.value = row.notes
|
||||||
|
|
||||||
|
dataDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateData = async () => {
|
||||||
|
try {
|
||||||
|
await api.updateToken({
|
||||||
|
token: rowOut.value.token,
|
||||||
|
dedup_object: inputDedupObject.value,
|
||||||
|
data_format: inputDataFormat.value,
|
||||||
|
notes: inputNotes.value,
|
||||||
|
})
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error('更新失败' + error.response?.data?.error)
|
||||||
|
dataDialogVisible.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ElMessage({message: '更新成功', type: 'success'})
|
||||||
|
fetchTokens()
|
||||||
|
dataDialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteToken = async (row: any) => {
|
||||||
|
try {
|
||||||
|
await api.deleteToken({token: row.token})
|
||||||
|
ElMessage({message: '删除成功', type: 'success'})
|
||||||
|
fetchTokens()
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error('删除失败' + error.response?.data?.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (store.isAdmin) {
|
||||||
|
fetchTokens()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mx-auto w-full bg-white">
|
||||||
|
<!-- 管理页面 -->
|
||||||
|
<div class="rounded-lg p-6 shadow-[0_2px_12px_rgba(0,0,0,0.08)]">
|
||||||
|
<div class="text-xl font-semibold text-(--el-text-color-primary) pb-3 border-b border-(--el-border-color)">
|
||||||
|
Token 管理
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加表单 -->
|
||||||
|
<el-collapse
|
||||||
|
class="rounded-xl"
|
||||||
|
v-model="activeNames"
|
||||||
|
>
|
||||||
|
<el-collapse-item title="添加新 Token" name="1"
|
||||||
|
class="flex flex-wrap gap-3 items-center max-md:flex-col max-md:items-stretch"
|
||||||
|
>
|
||||||
|
<el-input v-model="input" placeholder="Token 名称" clearable class="w-55 max-md:w-full">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Key/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-select v-model="inputDedupObject" placeholder="去重对象" clearable class="w-70 max-md:w-full">
|
||||||
|
<el-option v-for="item in options" :key="item.value" :value="item.value" :label="item.label"/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-select v-model="inputDataFormat" placeholder="数据格式" clearable class="w-70 max-md:w-full">
|
||||||
|
<template #footer>
|
||||||
|
<el-button v-if="!isAdding" text bg size="small" @click="onAddOption">
|
||||||
|
添加数据格式
|
||||||
|
</el-button>
|
||||||
|
<template v-else>
|
||||||
|
<el-input
|
||||||
|
v-model="optionName"
|
||||||
|
placeholder="输入数据格式(以----分割)"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<el-button type="primary" size="small" @click="onConfirm">添加</el-button>
|
||||||
|
<el-button size="small" @click="clear">取消</el-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<el-option v-for="item in dataFormatOptions" :key="item.value" :value="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-input v-model="inputNotes" placeholder="备注(可选)" clearable class="w-55 max-md:w-full">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Memo/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-button type="primary" @click="addToken" class="max-md:w-full">
|
||||||
|
<el-icon>
|
||||||
|
<Plus/>
|
||||||
|
</el-icon>
|
||||||
|
添加
|
||||||
|
</el-button>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
|
||||||
|
<!-- Token 列表 -->
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-base font-semibold text-(--el-text-color-primary) mt-5 mb-4">Token 列表</span>
|
||||||
|
<span class="text-xs text-(--el-text-color-secondary)">共 {{ tableData.length }} 条</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table empty-text="没有数据" stripe style="width: 100%"
|
||||||
|
:data="tableData" v-loading="loading">
|
||||||
|
<el-table-column prop="token" label="Token" min-width="100" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<el-icon>
|
||||||
|
<Key/>
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ row.token }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="dedup_object" label="去重对象" width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag type="primary" effect="plain">
|
||||||
|
{{ row.dedup_object == "false" ? "不去重" : row.dedup_object }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="data_format" label="数据格式" min-width="200" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="text-xs break-all">{{ row.data_format }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="notes" label="备注" min-width="150" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="text-(--el-text-color-secondary) overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
{{ row.notes || '-' }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="380" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex gap-1 flex-wrap max-md:flex-col">
|
||||||
|
<el-button size="small" text @click="viewDetails(row)">
|
||||||
|
<el-icon>
|
||||||
|
<View/>
|
||||||
|
</el-icon>
|
||||||
|
详情
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" text @click="dialogDataADDVisible(row)">
|
||||||
|
<el-icon>
|
||||||
|
<Plus/>
|
||||||
|
</el-icon>
|
||||||
|
数据
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" text @click="displayDataVisible(row)">
|
||||||
|
<el-icon>
|
||||||
|
<Edit/>
|
||||||
|
</el-icon>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-popconfirm
|
||||||
|
title="确认删除此 Token 吗?"
|
||||||
|
confirm-button-text="确认"
|
||||||
|
cancel-button-text="取消"
|
||||||
|
@confirm="deleteToken(row)"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button type="danger" size="small" text>
|
||||||
|
<el-icon>
|
||||||
|
<Delete/>
|
||||||
|
</el-icon>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 编辑对话框 -->
|
||||||
|
<el-dialog width="400"
|
||||||
|
v-model="dataDialogVisible"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
编辑Token
|
||||||
|
<el-tag type="primary" effect="plain">{{ rowOut?.token }}</el-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<el-text class="mr-auto">去重对象</el-text>
|
||||||
|
<el-select v-model="inputDedupObject" placeholder="选择去重对象">
|
||||||
|
<el-option v-for="item in options" :key="item.value" :value="item.value" :label="item.label"/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-text class="mr-auto">数据格式</el-text>
|
||||||
|
<el-select v-model="inputDataFormat" placeholder="选择数据格式">
|
||||||
|
<el-option v-for="item in dataFormatOptions" :key="item.value" :value="item.value" :label="item.label"/>
|
||||||
|
</el-select>
|
||||||
|
<el-text class="mr-auto">备注</el-text>
|
||||||
|
<el-input v-model="inputNotes" placeholder="请输入备注"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dataDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="updateData">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<AddDataDialog
|
||||||
|
v-model="addDataDialogVisible"
|
||||||
|
:token="rowOut?.token"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
+18
-6
@@ -1,12 +1,24 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": [
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"env.d.ts",
|
||||||
|
"src/**/*",
|
||||||
|
"src/**/*.vue",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"src/**/__tests__/*"
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
// Extra safety for array and object lookups, but may have false positives.
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
// Path mapping for cleaner imports.
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": [
|
||||||
}
|
"./src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||||
|
// Specified here to keep it out of the root directory.
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-7
@@ -1,19 +1,26 @@
|
|||||||
|
// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping.
|
||||||
{
|
{
|
||||||
"extends": "@tsconfig/node22/tsconfig.json",
|
"extends": "@tsconfig/node24/tsconfig.json",
|
||||||
"include": [
|
"include": [
|
||||||
"vite.config.*",
|
"vite.config.*",
|
||||||
"vitest.config.*",
|
"vitest.config.*",
|
||||||
"cypress.config.*",
|
"cypress.config.*",
|
||||||
"nightwatch.conf.*",
|
|
||||||
"playwright.config.*",
|
"playwright.config.*",
|
||||||
"eslint.config.*"
|
"eslint.config.*"
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
// Most tools use transpilation instead of Node.js's native type-stripping.
|
||||||
|
// Bundler mode provides a smoother developer experience.
|
||||||
|
"module": "preserve",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
// Include Node.js types and avoid accidentally including other `@types/*` packages.
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
// Disable emitting output during `vue-tsc --build`, which is used for type-checking only.
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||||
|
// Specified here to keep it out of the root directory.
|
||||||
"module": "ESNext",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
|
||||||
"moduleResolution": "Bundler",
|
|
||||||
"types": ["node"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,29 @@ import { fileURLToPath, URL } from 'node:url'
|
|||||||
import {defineConfig} from 'vite'
|
import {defineConfig} from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import {ElementPlusResolver} from 'unplugin-vue-components/resolvers'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
vueDevTools(),
|
vueDevTools(),
|
||||||
|
tailwindcss(),
|
||||||
|
AutoImport({
|
||||||
|
imports: [
|
||||||
|
'vue',
|
||||||
|
'vue-router',
|
||||||
|
],
|
||||||
|
resolvers: [ElementPlusResolver()],
|
||||||
|
dts: 'src/types/import/auto-imports.d.ts',
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [ElementPlusResolver()],
|
||||||
|
dts: 'src/types/import/components.d.ts',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user