Compare commits

...

2 Commits

Author SHA1 Message Date
王性驊 e8c5616206 feat: add token func 2025-02-13 19:06:51 +08:00
王性驊 0e7f0a2b68 feat: add token repository 2025-02-12 09:51:46 +08:00
74 changed files with 4676 additions and 3503 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
.idea/
go.sum
old/go.sum
account/
gen_result/
etc/permission.yaml

View File

@ -6,7 +6,7 @@ GOFMT ?= gofmt "-s"
GOFILES := $(shell find . -name "*.go")
LDFLAGS := -s -w
VERSION="v1.0.1"
DOCKER_REPO="igs170911/permission"
DOCKER_REPO="code.30cm.net/permission"
.PHONY: test
test: # 進行測試
@ -46,24 +46,10 @@ build-docker:
rm -rf Dockerfile
@echo "Generate core-api files successfully"
.PHONY: gen-my-sql-model-up
gen-my-sql-model: # 建立 rpc 資料庫
goctl model mysql ddl -c no -s ./generate/database/mysql/20240816014305_create_permission_table.up.sql --style $(GO_ZERO_STYLE) -d ./internal/model -i ''
goctl model mysql ddl -c no -s ./generate/database/mysql/20240819013052_create_roles_table.up.sql --style $(GO_ZERO_STYLE) -d ./internal/model -i ''
goctl model mysql ddl -c no -s ./generate/database/mysql/20240819022436_create_user_role_table.up.sql --style $(GO_ZERO_STYLE) -d ./internal/model -i ''
goctl model mysql ddl -c no -s ./generate/database/mysql/20240819090248_create_role_permission_table.up.sql --style $(GO_ZERO_STYLE) -d ./internal/model -i ''
@echo "Generate mysql model files successfully"
.PHONY: mock-gen
mock-gen: # 建立 mock 資料
mockgen -source=./internal/model/permission_model.go -destination=./internal/mock/model/permission_model.go -package=mock
mockgen -source=./internal/model/permission_model_gen.go -destination=./internal/mock/model/permission_model_gen.go -package=mock
mockgen -source=./internal/model/role_model.go -destination=./internal/mock/model/role_model.go -package=mock
mockgen -source=./internal/model/role_model_gen.go -destination=./internal/mock/model/role_model_gen.go -package=mock
mockgen -source=./internal/model/role_permission_model.go -destination=./internal/mock/model/role_permission_model.go -package=mock
mockgen -source=./internal/model/role_permission_model_gen.go -destination=./internal/mock/model/role_permission_model_gen.go -package=mock
mockgen -source=./internal/model/user_role_model.go -destination=./internal/mock/model/user_role_model.go -package=mock
mockgen -source=./internal/model/user_role_model_gen.go -destination=./internal/mock/model/user_role_model_gen.go -package=mock
mockgen -source=./pkg/domain/repository/token.go -destination=./pkg/mock/repository/token.go -package=mock
@echo "Generate mock files successfully"

View File

@ -2,12 +2,11 @@
# BUILDER #
###########
FROM golang:1.22.3 as builder
FROM golang:1.24.0 AS builder
ARG VERSION
ARG BUILT
ARG GIT_COMMIT
ARG SSH_PRV_KEY
# private go packages
ENV GOPRIVATE=code.30cm.net
@ -17,22 +16,19 @@ COPY . .
RUN apt-get update && \
apt-get install git
apt-get install -y git && \
mkdir /root/.ssh
# Make the root foler for our ssh
RUN mkdir -p /root/.ssh && \
chmod 0700 /root/.ssh && \
ssh-keyscan git.30cm.net > /root/.ssh/known_hosts && \
echo "$SSH_PRV_KEY" > /root/.ssh/id_rsa && \
chmod 600 /root/.ssh/id_rsa
RUN --mount=type=secret,id=ssh_key,dst=/root/.ssh/id_rsa \
ssh-keyscan git.30cm.net >> /root/.ssh/known_hosts
RUN --mount=type=ssh go mod download
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags "$FLAG" \
-o service
-o permission
##########
## FINAL #
@ -41,7 +37,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
FROM gcr.io/distroless/static-debian11
WORKDIR /app
COPY --from=builder /app/service /app/service
COPY --from=builder /app/etc/service.yaml /app/etc/service.yaml
COPY --from=builder /app/permission /app/permission
COPY --from=builder /app/etc/permission.yaml /app/etc/permission.yaml
EXPOSE 8080
CMD ["/app/service"]
CMD ["/app/permission"]

View File

@ -1 +0,0 @@
// TODO 未來要放 helm 的地方

74
go.mod
View File

@ -1,22 +1,22 @@
module app-cloudep-permission-server
module code.30cm.net/digimon/app-cloudep-permission-server
go 1.22.3
go 1.23.6
require (
code.30cm.net/digimon/library-go/errors v1.0.1
code.30cm.net/digimon/library-go/utils/invited_code v1.0.2
code.30cm.net/digimon/library-go/validator v1.0.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.9.0
github.com/zeromicro/go-zero v1.7.0
go.uber.org/mock v0.4.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
code.30cm.net/digimon/library-go/errs v1.2.14
github.com/alicebob/miniredis/v2 v2.34.0
github.com/golang-jwt/jwt/v4 v4.5.1
github.com/pkg/errors v0.9.1
github.com/segmentio/ksuid v1.0.4
github.com/stretchr/testify v1.10.0
github.com/zeromicro/go-zero v1.8.0
go.uber.org/mock v0.5.0
google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.5
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@ -25,27 +25,23 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@ -55,39 +51,39 @@ require (
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/redis/go-redis/v9 v9.6.1 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/redis/go-redis/v9 v9.7.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel v1.32.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.9.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

285
go.sum Normal file
View File

@ -0,0 +1,285 @@
code.30cm.net/digimon/library-go/errs v1.2.14 h1:Un9wcIIjjJW8D2i0ISf8ibzp9oNT4OqLsaSKW0T4RJU=
code.30cm.net/digimon/library-go/errs v1.2.14/go.mod h1:Hs4v7SbXNggDVBGXSYsFMjkii1qLF+rugrIpWePN4/o=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
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/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/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/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/zeromicro/go-zero v1.8.0 h1:4g/8VW+fOyM51HZYPeI3mXIZdEX+Fl6SsdYX2H5PYw4=
github.com/zeromicro/go-zero v1.8.0/go.mod h1:xDBF+/iDzj30zPvu6HNUIbpz1J6+/g3Sx9D/DytJfss=
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=
go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E=
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y=
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

View File

@ -1,22 +1,7 @@
package config
import (
"time"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/zrpc"
)
import "github.com/zeromicro/go-zero/zrpc"
type Config struct {
zrpc.RpcServerConf
RedisCluster redis.RedisConf
Token struct {
RefreshExpires time.Duration
Expired time.Duration
Secret string
}
// 加上DB結構體
DB struct {
DsnString string
}
}

View File

@ -1,18 +0,0 @@
package domain
type GrantType string
const (
PasswordCredentials GrantType = "password"
ClientCredentials GrantType = "client_credentials"
Refreshing GrantType = "refresh_token"
)
const (
// DefaultRole 預設role
DefaultRole = "user"
)
const (
TokenTypeBearer = "Bearer"
)

View File

@ -1,89 +0,0 @@
package domain
import (
mts "app-cloudep-permission-server/internal/lib/metric"
ers "code.30cm.net/digimon/library-go/errors"
"code.30cm.net/digimon/library-go/errors/code"
)
// 12 represents Scope
// 100 represents Category
// 9 represents Detail error code
// full code 12009 只會有 系統以及錯誤碼category 是給系統判定用的
// 目前 Scope 以及分類要系統共用,係向的錯誤各自服務實作就好
// token error 方面
const (
TokenUnexpectedSigningErrorCode = iota + 1
TokenValidateErrorCode
TokenClaimErrorCode
)
const (
RedisDelErrorCode = iota + 20
RedisPipLineErrorCode
RedisErrorCode
)
const (
PermissionNotFoundCode = iota + 30
PermissionGetDataErrorCode
)
// TokenUnexpectedSigningErr 30001 Token 簽名錯誤
func TokenUnexpectedSigningErr(msg string) *ers.LibError {
mts.AppErrorMetrics.AddFailure("token", "token_unexpected_sign")
return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenUnexpectedSigningErrorCode, msg)
}
// TokenTokenValidateErr 30002 Token 驗證錯誤
func TokenTokenValidateErr(msg string) *ers.LibError {
mts.AppErrorMetrics.AddFailure("token", "token_validate_ilegal")
return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenValidateErrorCode, msg)
}
// TokenClaimError 30003 Token 驗證錯誤
func TokenClaimError(msg string) *ers.LibError {
mts.AppErrorMetrics.AddFailure("token", "token_claim_error")
return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenClaimErrorCode, msg)
}
// RedisDelError 30020 Redis 刪除錯誤
func RedisDelError(msg string) *ers.LibError {
// 看需要建立哪些 Metrics
mts.AppErrorMetrics.AddFailure("redis", "del_error")
return ers.NewErr(code.CloudEPPermission, code.CatDB, RedisDelErrorCode, msg)
}
// RedisPipLineError 30021 Redis PipLine 錯誤
func RedisPipLineError(msg string) *ers.LibError {
// 看需要建立哪些 Metrics
mts.AppErrorMetrics.AddFailure("redis", "pip_line_error")
return ers.NewErr(code.CloudEPPermission, code.CatInput, RedisPipLineErrorCode, msg)
}
// RedisError 30022 Redis 錯誤
func RedisError(msg string) *ers.LibError {
// 看需要建立哪些 Metrics
mts.AppErrorMetrics.AddFailure("redis", "error")
return ers.NewErr(code.CloudEPPermission, code.CatInput, RedisErrorCode, msg)
}
// PermissionNotFoundError 30030 權限錯誤
func PermissionNotFoundError(msg string) *ers.LibError {
// 看需要建立哪些 Metrics
return ers.NewErr(code.CloudEPPermission, code.Forbidden, PermissionNotFoundCode, msg)
}
// PermissionGetDataError 30031 解析權限時錯誤
func PermissionGetDataError(msg string) *ers.LibError {
// 看需要建立哪些 Metrics
return ers.NewErr(code.CloudEPPermission, code.InvalidFormat, PermissionGetDataErrorCode, msg)
}

View File

@ -1,45 +0,0 @@
package domain
type PermissionType int8
const (
PermissionTypeBackendUser PermissionType = iota + 1
PermissionTypeFrontendUser
)
type PermissionTypeCode string
const (
PermissionTypeBackCode PermissionTypeCode = "back"
PermissionTypeFrontCode PermissionTypeCode = "front"
)
var permissionMap = map[int64]PermissionTypeCode{
1: PermissionTypeFrontCode,
2: PermissionTypeBackCode,
}
func ToPermissionTypeCode(code int64) (PermissionTypeCode, bool) {
result, ok := permissionMap[code]
if !ok {
return "", false
}
return result, true
}
func (t *PermissionTypeCode) ToString() string {
return string(*t)
}
type PermissionStatus string
type Permissions map[string]PermissionStatus
const (
PermissionStatusOpenCode PermissionStatus = "open"
PermissionStatusCloseCode PermissionStatus = "close"
)
const (
AdminRoleID = "GodDog!@#"
)

View File

@ -1,66 +0,0 @@
package repository
import (
mts "app-cloudep-permission-server/internal/lib/metric"
ers "code.30cm.net/digimon/library-go/errors"
"code.30cm.net/digimon/library-go/errors/code"
)
// token error 方面
const (
TokenUnexpectedSigningErrorCode = iota + 1
TokenValidateErrorCode
TokenClaimErrorCode
)
const (
RedisDelErrorCode = iota + 20
RedisPipLineErrorCode
RedisErrorCode
)
// TokenUnexpectedSigningErr 30001 Token 簽名錯誤
func TokenUnexpectedSigningErr(msg string) *ers.LibError {
mts.AppErrorMetrics.AddFailure("token", "token_unexpected_sign")
return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenUnexpectedSigningErrorCode, msg)
}
// TokenTokenValidateErr 30002 Token 驗證錯誤
func TokenTokenValidateErr(msg string) *ers.LibError {
mts.AppErrorMetrics.AddFailure("token", "token_validate_ilegal")
return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenValidateErrorCode, msg)
}
// TokenClaimError 30003 Token 驗證錯誤
func TokenClaimError(msg string) *ers.LibError {
mts.AppErrorMetrics.AddFailure("token", "token_claim_error")
return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenClaimErrorCode, msg)
}
// RedisDelError 30020 Redis 刪除錯誤
func RedisDelError(msg string) *ers.LibError {
// 看需要建立哪些 Metrics
mts.AppErrorMetrics.AddFailure("redis", "del_error")
return ers.NewErr(code.CloudEPPermission, code.CatDB, RedisDelErrorCode, msg)
}
// RedisPipLineError 30021 Redis PipLine 錯誤
func RedisPipLineError(msg string) *ers.LibError {
// 看需要建立哪些 Metrics
mts.AppErrorMetrics.AddFailure("redis", "pip_line_error")
return ers.NewErr(code.CloudEPPermission, code.CatInput, RedisPipLineErrorCode, msg)
}
// RedisError 30022 Redis 錯誤
func RedisError(msg string) *ers.LibError {
// 看需要建立哪些 Metrics
mts.AppErrorMetrics.AddFailure("redis", "error")
return ers.NewErr(code.CloudEPPermission, code.CatInput, RedisErrorCode, msg)
}

View File

@ -1,16 +0,0 @@
package repository
import "context"
// MemberOnlineStatusRepository 會員上限狀態使用Bitmap
type MemberOnlineStatusRepository interface {
SetMemberOnline(ctx context.Context, uid string) (bool, error)
SetMemberOffline(ctx context.Context, uid string) (bool, error)
IsMemberOnline(ctx context.Context, uid string) (bool, error)
QueryMemberOnlineList(ctx context.Context, uids []string) ([]MemberOnlineStatusResp, error)
}
type MemberOnlineStatusResp struct {
UID string
Status bool
}

View File

@ -1,31 +0,0 @@
package repository
import (
"app-cloudep-permission-server/internal/entity"
"context"
"time"
)
// TokenRepository token 的 redis 操作
type TokenRepository interface {
// Create 建立Token
Create(ctx context.Context, token entity.Token) error
// CreateOneTimeToken 建立臨時 Token
CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, dt time.Duration) error
GetAccessTokenByByOneTimeToken(ctx context.Context, oneTimeToken string) (entity.Token, error)
GetAccessTokenByID(ctx context.Context, id string) (entity.Token, error)
GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error)
GetAccessTokenCountByUID(uid string) (int, error)
GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error)
GetAccessTokenCountByDeviceID(deviceID string) (int, error)
Delete(ctx context.Context, token entity.Token) error
DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error
DeleteAccessTokenByID(ctx context.Context, ids []string) error
DeleteAccessTokensByUID(ctx context.Context, uid string) error
DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error
}
type DeviceToken struct {
DeviceID string
TokenID string
}

View File

@ -1,16 +0,0 @@
package usecase
type BitMapUseCase interface {
// SetTrue 設定該 Bit 狀態為 true
SetTrue(bitPos uint32)
// SetFalse 設定該Bit 狀態為 false
SetFalse(bitPos uint32)
// IsTrue 確認是否為真
IsTrue(bitPos uint32) bool
// Reset 重設 BitMap
Reset()
// ByteSize 最大 Byte 數
ByteSize() int
// BitSize 最大 Byte * 8
BitSize() int
}

View File

@ -1,47 +0,0 @@
package usecase
import (
"context"
)
type OpaUseCase interface {
// CheckRBACPermission 確認有無權限
CheckRBACPermission(ctx context.Context, req CheckReq) (CheckOPAResp, error)
// LoadPolicy 將 Policy 從其他地方加載到 opa 的 policy 當中
LoadPolicy(ctx context.Context, input []Policy) error
GetPolicy(ctx context.Context) []map[string]any
}
type CheckReq struct {
ID string
Roles []string
Path string
Method string
}
type Grant struct {
ID string
Path string
Method string
}
type Policy struct {
Methods []string `json:"methods"`
Name string `json:"name"`
Path string `json:"path"`
Role string `json:"role"`
}
type RuleRequest struct {
Method string `json:"method"`
Path string `json:"path"`
Policies []Policy `json:"policies"`
Roles []string `json:"roles"`
}
type CheckOPAResp struct {
Allow bool `json:"allow"`
PolicyName string `json:"policy_name"`
PlainCode bool `json:"plain_code"` // 是否為明碼顯示
Request RuleRequest `json:"request"`
}

View File

@ -1,33 +0,0 @@
package usecase
// // PermissionTreeManager 定義一組操作權限樹的接口
// // 這個名稱說明它是專門負責管理和操作權限樹的管理器
// type PermissionTreeManager interface {
// // AddPermission 將一個新的權限節點插入到樹中
// // key 是父節點的IDvalue 是要插入的 Permission 資料
// // 此方法應該能處理節點是否存在於父節點下的情況
// AddPermission(parentID int64, permission entity.Permission) error
// // FindPermissionByID 根據權限 ID 查詢樹中的某個節點
// // 如果節點存在,返回對應的 Permission 資料,否則返回 nil
// FindPermissionByID(permissionID int64) (*Permission, error)
// // GetAllParentPermissionIDs 根據傳入的 permissions 列表
// // 找出每個權限的完整父節點權限 ID 路徑
// // 例如,如果 B 的父權限是 A並且給了 B 權限,則返回 A 和 B 的權限 ID
// GetAllParentPermissionIDs(permissions domain.Permissions) ([]int64, error)
// // GetAllParentPermissionStatuses 返回給定權限下的所有完整父節點權限狀態
// // 例如,若給 B 權限,該方法將返回所有與 B 相關的父權限的狀態
// GetAllParentPermissionStatuses(permissions domain.Permissions) (domain.Permissions, error)
// // GetRolePermissionTree 根據角色權限找出所有父節點和子節點權限狀態
// // 角色權限是傳入的一個列表,該方法會根據每個角色的權限,返回所有相關的權限狀態
// GetRolePermissionTree(rolePermissions []entity.RolePermission) domain.Permissions
// }
//
// type Permission struct {
// ID int64 `json:"-"`
// Name string `json:"name"`
// HTTPMethod string `json:"http_method"`
// HTTPPath string `json:"http_path"`
// Parent *Permission `json:"-"`
// Children []*Permission `json:"children"`
// PathIDs []int64 `json:"-"` // full path id
// }

View File

@ -1,50 +0,0 @@
package entity
import "time"
type Token struct {
ID string `json:"id"`
UID string `json:"uid"`
DeviceID string `json:"device_id"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
AccessCreateAt time.Time `json:"access_create_at"`
RefreshToken string `json:"refresh_token"`
RefreshExpiresIn int `json:"refresh_expires_in"`
RefreshCreateAt time.Time `json:"refresh_create_at"`
}
func (t *Token) AccessTokenExpires() time.Duration {
return time.Duration(t.ExpiresIn) * time.Second
}
func (t *Token) RefreshTokenExpires() time.Duration {
return time.Duration(t.RefreshExpiresIn) * time.Second
}
func (t *Token) RefreshTokenExpiresUnix() int64 {
return time.Now().Add(t.RefreshTokenExpires()).Unix()
}
func (t *Token) IsExpires() bool {
return t.AccessCreateAt.Add(t.AccessTokenExpires()).Before(time.Now())
}
func (t *Token) RedisExpiredSec() int64 {
sec := time.Unix(int64(t.ExpiresIn), 0).Sub(time.Now().UTC())
return int64(sec.Seconds())
}
func (t *Token) RedisRefreshExpiredSec() int64 {
sec := time.Unix(int64(t.RefreshExpiresIn), 0).Sub(time.Now().UTC())
return int64(sec.Seconds())
}
type UIDToken map[string]int64
type Ticket struct {
Data any `json:"data"`
Token Token `json:"token"`
}

View File

@ -1,30 +0,0 @@
package metric
import (
"github.com/zeromicro/go-zero/core/metric"
)
var AppErrorMetrics = NewAppErrMetrics()
type appErrMetrics struct {
metric.CounterVec
}
type Metrics interface {
AddFailure(source, reason string)
}
// NewAppErrMetrics initiate metrics and register to prometheus
func NewAppErrMetrics() Metrics {
return &appErrMetrics{metric.NewCounterVec(&metric.CounterVecOpts{
Namespace: "ark",
Subsystem: "permission",
Name: "permission_app_error_total",
Help: "App defined failure total.",
Labels: []string{"source", "reason"},
})}
}
func (m *appErrMetrics) AddFailure(source, reason string) {
m.Inc(source, reason)
}

View File

@ -3,10 +3,8 @@ package tokenservicelogic
import (
"context"
ers "code.30cm.net/digimon/library-go/errors"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -25,23 +23,9 @@ func NewCancelOneTimeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext)
}
}
type cancelOneTimeTokenReq struct {
Token []string `json:"token" validate:"required"`
}
// CancelOneTimeToken 取消一次性使用
func (l *CancelOneTimeTokenLogic) CancelOneTimeToken(in *permission.CancelOneTimeTokenReq) (*permission.OKResp, error) {
// 驗證所需
if err := l.svcCtx.Validate.ValidateAll(&cancelOneTimeTokenReq{
Token: in.GetToken(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
err := l.svcCtx.TokenRedisRepo.DeleteOneTimeToken(l.ctx, in.GetToken(), nil)
if err != nil {
return nil, err
}
// todo: add your logic here and delete this line
return &permission.OKResp{}, nil
}

View File

@ -3,10 +3,8 @@ package tokenservicelogic
import (
"context"
ers "code.30cm.net/digimon/library-go/errors"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -27,19 +25,7 @@ func NewCancelTokenByDeviceIdLogic(ctx context.Context, svcCtx *svc.ServiceConte
// CancelTokenByDeviceId 取消 Token 從 Device 視角出發可以選登出這個Device 下所有 token 登出這個Device 下指定token
func (l *CancelTokenByDeviceIdLogic) CancelTokenByDeviceId(in *permission.DoTokenByDeviceIDReq) (*permission.OKResp, error) {
if err := l.svcCtx.Validate.ValidateAll(&getUserTokensByDeviceIdReq{
DeviceID: in.GetDeviceId(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
// todo: add your logic here and delete this line
err := l.svcCtx.TokenRedisRepo.DeleteAccessTokensByDeviceID(l.ctx, in.GetDeviceId())
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.DeleteAccessTokensByDeviceID"),
logx.Field("DeviceID", in.GetDeviceId()),
).Error(err.Error())
return nil, err
}
return &permission.OKResp{}, nil
}

View File

@ -3,10 +3,8 @@ package tokenservicelogic
import (
"context"
ers "code.30cm.net/digimon/library-go/errors"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -25,44 +23,9 @@ func NewCancelTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Cance
}
}
type cancelTokenReq struct {
Token string `json:"token" validate:"required"`
}
// CancelToken 取消 Token也包含他裡面的 One Time Toke
func (l *CancelTokenLogic) CancelToken(in *permission.CancelTokenReq) (*permission.OKResp, error) {
// 驗證所需
if err := l.svcCtx.Validate.ValidateAll(&cancelTokenReq{
Token: in.GetToken(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
claims, err := parseClaims(in.GetToken(), l.svcCtx.Config.Token.Secret, false)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "parseClaims"),
).Error(err.Error())
return nil, err
}
token, err := l.svcCtx.TokenRedisRepo.GetAccessTokenByID(l.ctx, claims.ID())
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.GetByAccess"),
logx.Field("claims", claims),
).Error(err.Error())
return nil, err
}
err = l.svcCtx.TokenRedisRepo.Delete(l.ctx, token)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.Delete"),
logx.Field("req", token),
).Error(err.Error())
return nil, err
}
// todo: add your logic here and delete this line
return &permission.OKResp{}, nil
}

View File

@ -3,10 +3,8 @@ package tokenservicelogic
import (
"context"
ers "code.30cm.net/digimon/library-go/errors"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -27,27 +25,7 @@ func NewCancelTokensLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Canc
// CancelTokens 取消 Token 從UID 視角,以及 token id 視角出發, UID 登出,底下所有 Device ID 也要登出, Token ID 登出, 所有 UID + Device 都要登出
func (l *CancelTokensLogic) CancelTokens(in *permission.DoTokenByUIDReq) (*permission.OKResp, error) {
if in.GetUid() != "" {
err := l.svcCtx.TokenRedisRepo.DeleteAccessTokensByUID(l.ctx, in.GetUid())
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.DeleteAccessTokensByUID"),
logx.Field("uid", in.GetUid()),
).Error(err.Error())
return nil, ers.ResourceInsufficient(err.Error())
}
}
if len(in.GetIds()) > 0 {
err := l.svcCtx.TokenRedisRepo.DeleteAccessTokenByID(l.ctx, in.GetIds())
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.DeleteAccessTokenByID"),
logx.Field("ids", in.GetIds()),
).Error(err.Error())
return nil, ers.ResourceInsufficient(err.Error())
}
}
// todo: add your logic here and delete this line
return &permission.OKResp{}, nil
}

View File

@ -1,13 +1,10 @@
package tokenservicelogic
import (
"app-cloudep-permission-server/internal/domain"
"context"
ers "code.30cm.net/digimon/library-go/errors"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -26,34 +23,9 @@ func NewGetUserTokensByDeviceIdLogic(ctx context.Context, svcCtx *svc.ServiceCon
}
}
type getUserTokensByDeviceIdReq struct {
DeviceID string `json:"device_id" validate:"required"`
}
// GetUserTokensByDeviceId 取得目前所對應的 DeviceID 所存在的 Tokens
func (l *GetUserTokensByDeviceIdLogic) GetUserTokensByDeviceId(in *permission.DoTokenByDeviceIDReq) (*permission.Tokens, error) {
if err := l.svcCtx.Validate.ValidateAll(&getUserTokensByDeviceIdReq{
DeviceID: in.GetDeviceId(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
// todo: add your logic here and delete this line
uidTokens, err := l.svcCtx.TokenRedisRepo.GetAccessTokensByDeviceID(l.ctx, in.GetDeviceId())
if err != nil {
return nil, err
}
tokens := make([]*permission.TokenResp, 0, len(uidTokens))
for _, v := range uidTokens {
tokens = append(tokens, &permission.TokenResp{
AccessToken: v.AccessToken,
TokenType: domain.TokenTypeBearer,
ExpiresIn: int32(v.ExpiresIn),
RefreshToken: v.RefreshToken,
})
}
return &permission.Tokens{
Token: tokens,
}, nil
return &permission.Tokens{}, nil
}

View File

@ -1,13 +1,10 @@
package tokenservicelogic
import (
"app-cloudep-permission-server/internal/domain"
"context"
ers "code.30cm.net/digimon/library-go/errors"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -26,34 +23,9 @@ func NewGetUserTokensByUidLogic(ctx context.Context, svcCtx *svc.ServiceContext)
}
}
type getUserTokensByUidReq struct {
UID string `json:"uid" validate:"required"`
}
// GetUserTokensByUid 取得目前所對應的 UID 所存在的 Tokens
func (l *GetUserTokensByUidLogic) GetUserTokensByUid(in *permission.QueryTokenByUIDReq) (*permission.Tokens, error) {
if err := l.svcCtx.Validate.ValidateAll(&getUserTokensByUidReq{
UID: in.GetUid(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
// todo: add your logic here and delete this line
uidTokens, err := l.svcCtx.TokenRedisRepo.GetAccessTokensByUID(l.ctx, in.GetUid())
if err != nil {
return nil, err
}
tokens := make([]*permission.TokenResp, 0, len(uidTokens))
for _, v := range uidTokens {
tokens = append(tokens, &permission.TokenResp{
AccessToken: v.AccessToken,
TokenType: domain.TokenTypeBearer,
ExpiresIn: int32(v.ExpiresIn),
RefreshToken: v.RefreshToken,
})
}
return &permission.Tokens{
Token: tokens,
}, nil
return &permission.Tokens{}, nil
}

View File

@ -1,16 +1,10 @@
package tokenservicelogic
import (
"app-cloudep-permission-server/internal/domain"
"app-cloudep-permission-server/internal/entity"
"context"
"time"
ers "code.30cm.net/digimon/library-go/errors"
"github.com/google/uuid"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -31,41 +25,7 @@ func NewNewOneTimeTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *N
// NewOneTimeToken 建立一次性使用例如RefreshToken
func (l *NewOneTimeTokenLogic) NewOneTimeToken(in *permission.CreateOneTimeTokenReq) (*permission.CreateOneTimeTokenResp, error) {
// 驗證所需
if err := l.svcCtx.Validate.ValidateAll(&refreshTokenReq{
Token: in.GetToken(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
// todo: add your logic here and delete this line
// 驗證Token
claims, err := parseClaims(in.GetToken(), l.svcCtx.Config.Token.Secret, false)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "parseClaims"),
).Error(err.Error())
return nil, err
}
token, err := l.svcCtx.TokenRedisRepo.GetAccessTokenByID(l.ctx, claims.ID())
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.GetByAccess"),
logx.Field("claims", claims),
).Error(err.Error())
return nil, err
}
oneTimeToken := generateRefreshToken(uuid.Must(uuid.NewRandom()).String())
key := domain.TicketKeyPrefix + oneTimeToken
if err = l.svcCtx.TokenRedisRepo.CreateOneTimeToken(l.ctx, key, entity.Ticket{
Data: claims,
Token: token,
}, time.Minute); err != nil {
return &permission.CreateOneTimeTokenResp{}, err
}
return &permission.CreateOneTimeTokenResp{
OneTimeToken: oneTimeToken,
}, nil
return &permission.CreateOneTimeTokenResp{}, nil
}

View File

@ -1,17 +1,10 @@
package tokenservicelogic
import (
"app-cloudep-permission-server/internal/config"
"app-cloudep-permission-server/internal/domain"
"app-cloudep-permission-server/internal/entity"
"context"
"time"
ers "code.30cm.net/digimon/library-go/errors"
"github.com/google/uuid"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -30,110 +23,9 @@ func NewNewTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NewToken
}
}
// https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
type authorizationReq struct {
GrantType domain.GrantType `json:"grant_type" validate:"required,oneof=password client_credentials refresh_token"`
DeviceID string `json:"device_id"`
Scope string `json:"scope" validate:"required"`
Data map[string]string `json:"data"`
Expires int `json:"expires"`
IsRefreshToken bool `json:"is_refresh_token"`
}
// NewToken 建立一個新的 Token例如AccessToken
func (l *NewTokenLogic) NewToken(in *permission.AuthorizationReq) (*permission.TokenResp, error) {
data := authorizationReq{
GrantType: domain.GrantType(in.GetGrantType()),
Scope: in.GetScope(),
DeviceID: in.GetDeviceId(),
Data: in.GetData(),
Expires: int(in.GetExpires()),
IsRefreshToken: in.GetIsRefreshToken(),
}
// 驗證所需
if err := l.svcCtx.Validate.ValidateAll(&data); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
token, err := newToken(data, l.svcCtx.Config)
if err != nil {
return nil, err
}
// todo: add your logic here and delete this line
err = l.svcCtx.TokenRedisRepo.Create(l.ctx, *token)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.Create"),
logx.Field("token", token),
).Error(err.Error())
return nil, err
}
return &permission.TokenResp{
AccessToken: token.AccessToken,
TokenType: domain.TokenTypeBearer,
ExpiresIn: int32(token.ExpiresIn),
RefreshToken: token.RefreshToken,
}, nil
}
func newToken(authReq authorizationReq, cfg config.Config) (*entity.Token, error) {
// 準備建立 Token 所需
now := time.Now().UTC()
expires := authReq.Expires
refreshExpires := authReq.Expires
if expires <= 0 {
// 將時間加上 300 秒
sec := time.Duration(cfg.Token.Expired.Seconds()) * time.Second
newTime := now.Add(sec)
// 獲取 Unix 時間戳
timestamp := newTime.Unix()
expires = int(timestamp)
refreshExpires = expires
}
// 如果這是一個 Refresh Token 過期時間要比普通的Token 長
if authReq.IsRefreshToken {
// 將時間加上 300 秒
sec := time.Duration(cfg.Token.RefreshExpires.Seconds()) * time.Second
newTime := now.Add(sec)
// 獲取 Unix 時間戳
timestamp := newTime.Unix()
refreshExpires = int(timestamp)
}
token := entity.Token{
ID: uuid.Must(uuid.NewRandom()).String(),
DeviceID: authReq.DeviceID,
ExpiresIn: expires,
RefreshExpiresIn: refreshExpires,
AccessCreateAt: now,
RefreshCreateAt: now,
}
claims := claims(authReq.Data)
claims.SetRole(domain.DefaultRole)
claims.SetID(token.ID)
claims.SetScope(authReq.Scope)
token.UID = claims.UID()
if authReq.DeviceID != "" {
claims.SetDeviceID(authReq.DeviceID)
}
var err error
token.AccessToken, err = generateAccessTokenFunc(token, claims, cfg.Token.Secret)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "generateAccessTokenFunc"),
logx.Field("claims", claims),
).Error(err.Error())
return nil, err
}
if authReq.IsRefreshToken {
token.RefreshToken = generateRefreshTokenFunc(token.AccessToken)
}
return &token, nil
return &permission.TokenResp{}, nil
}

View File

@ -1,13 +1,10 @@
package tokenservicelogic
import (
"app-cloudep-permission-server/internal/domain"
"context"
ers "code.30cm.net/digimon/library-go/errors"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -26,83 +23,9 @@ func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Refr
}
}
type refreshReq struct {
RefreshToken string `json:"grant_type" validate:"required"`
DeviceID string `json:"device_id" validate:"required"`
Scope string `json:"scope" validate:"required"`
}
// RefreshToken 更新目前的token 以及裡面包含的一次性 Token
func (l *RefreshTokenLogic) RefreshToken(in *permission.RefreshTokenReq) (*permission.RefreshTokenResp, error) {
// 驗證所需
if err := l.svcCtx.Validate.ValidateAll(&refreshReq{
RefreshToken: in.GetToken(),
Scope: in.GetScope(),
DeviceID: in.GetDeviceId(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
// todo: add your logic here and delete this line
// step 1 拿看看有沒有這個 refresh token
token, err := l.svcCtx.TokenRedisRepo.GetAccessTokenByByOneTimeToken(l.ctx, in.Token)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.GetByRefresh"),
logx.Field("req", in),
).Error(err.Error())
return nil, err
}
// 取得 Data
c, err := parseClaims(token.AccessToken, l.svcCtx.Config.Token.Secret, false)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "parseClaims"),
logx.Field("token", token),
).Error(err.Error())
return nil, err
}
// step 2 建立新 token
nt, err := newToken(authorizationReq{
GrantType: domain.ClientCredentials,
Scope: in.GetScope(),
DeviceID: in.GetDeviceId(),
Data: c,
Expires: int(in.GetExpires()),
IsRefreshToken: true,
}, l.svcCtx.Config)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "newToken"),
logx.Field("req", in),
).Error(err.Error())
return nil, err
}
// 刪除掉舊的 token
err = l.svcCtx.TokenRedisRepo.Delete(l.ctx, token)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.Delete"),
logx.Field("req", token),
).Error(err.Error())
return nil, err
}
err = l.svcCtx.TokenRedisRepo.Create(l.ctx, *nt)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.Create"),
logx.Field("token", token),
).Error(err.Error())
return nil, err
}
return &permission.RefreshTokenResp{
Token: nt.AccessToken,
OneTimeToken: nt.RefreshToken,
ExpiresIn: int64(nt.ExpiresIn),
TokenType: domain.TokenTypeBearer,
}, nil
return &permission.RefreshTokenResp{}, nil
}

View File

@ -1,55 +0,0 @@
package tokenservicelogic
type claims map[string]string
func (c claims) SetID(id string) {
c["id"] = id
}
func (c claims) SetRole(role string) {
c["role"] = role
}
func (c claims) SetDeviceID(deviceID string) {
c["device_id"] = deviceID
}
func (c claims) SetScope(scope string) {
c["scope"] = scope
}
func (c claims) Role() string {
role, ok := c["role"]
if !ok {
return ""
}
return role
}
func (c claims) ID() string {
id, ok := c["id"]
if !ok {
return ""
}
return id
}
func (c claims) DeviceID() string {
deviceID, ok := c["device_id"]
if !ok {
return ""
}
return deviceID
}
func (c claims) UID() string {
uid, ok := c["uid"]
if !ok {
return ""
}
return uid
}

View File

@ -1,105 +0,0 @@
package tokenservicelogic
import (
"app-cloudep-permission-server/internal/domain"
"app-cloudep-permission-server/internal/entity"
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
"github.com/golang-jwt/jwt/v4"
)
var generateAccessTokenFunc = generateAccessToken
var generateRefreshTokenFunc = generateRefreshToken
func generateAccessToken(token entity.Token, data any, sign string) (string, error) {
claim := entity.Claims{
Data: data,
RegisteredClaims: jwt.RegisteredClaims{
ID: token.ID,
ExpiresAt: jwt.NewNumericDate(time.Unix(int64(token.ExpiresIn), 0)),
Issuer: "permission",
},
}
accessToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claim).
SignedString([]byte(sign))
if err != nil {
return "", domain.TokenClaimError(err.Error())
}
return accessToken, nil
}
func generateRefreshToken(accessToken string) string {
buf := bytes.NewBufferString(accessToken)
h := sha256.New()
_, _ = h.Write(buf.Bytes())
return hex.EncodeToString(h.Sum(nil))
}
func parseToken(accessToken string, secret string, validate bool) (jwt.MapClaims, error) {
// 跳過驗證的解析
var token *jwt.Token
var err error
if validate {
token, err = jwt.Parse(accessToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, domain.TokenUnexpectedSigningErr(fmt.Sprintf("token unexpected signing method: %v", token.Header["alg"]))
}
return []byte(secret), nil
})
if err != nil {
return jwt.MapClaims{}, err
}
} else {
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
token, err = parser.Parse(accessToken, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil {
return jwt.MapClaims{}, err
}
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok && token.Valid {
return jwt.MapClaims{}, domain.TokenTokenValidateErr("token valid error")
}
return claims, nil
}
func parseClaims(accessToken string, secret string, validate bool) (claims, error) {
claimMap, err := parseToken(accessToken, secret, validate)
if err != nil {
return claims{}, err
}
claimsData, ok := claimMap["data"].(map[string]any)
if ok {
return convertMap(claimsData), nil
}
return claims{}, domain.TokenClaimError("get data from claim map error")
}
func convertMap(input map[string]interface{}) map[string]string {
output := make(map[string]string)
for key, value := range input {
switch v := value.(type) {
case string:
output[key] = v
case fmt.Stringer:
output[key] = v.String()
default:
output[key] = fmt.Sprintf("%v", value)
}
}
return output
}

View File

@ -3,10 +3,8 @@ package tokenservicelogic
import (
"context"
ers "code.30cm.net/digimon/library-go/errors"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
@ -25,46 +23,9 @@ func NewValidationTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *V
}
}
type refreshTokenReq struct {
Token string `json:"token" validate:"required"`
}
// ValidationToken 驗證這個 Token 有沒有效
func (l *ValidationTokenLogic) ValidationToken(in *permission.ValidationTokenReq) (*permission.ValidationTokenResp, error) {
// 驗證所需
if err := l.svcCtx.Validate.ValidateAll(&refreshTokenReq{
Token: in.GetToken(),
}); err != nil {
return nil, ers.InvalidFormat(err.Error())
}
claims, err := parseClaims(in.GetToken(), l.svcCtx.Config.Token.Secret, true)
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "parseClaims"),
).Info(err.Error())
return nil, err
}
token, err := l.svcCtx.TokenRedisRepo.GetAccessTokenByID(l.ctx, claims.ID())
if err != nil {
logx.WithCallerSkip(1).WithFields(
logx.Field("func", "TokenRedisRepo.GetByAccess"),
logx.Field("claims", claims),
).Error(err.Error())
return nil, err
}
// todo: add your logic here and delete this line
return &permission.ValidationTokenResp{
Token: &permission.Token{
Id: token.ID,
Uid: token.UID,
DeviceId: token.DeviceID,
AccessCreateAt: token.AccessCreateAt.Unix(),
AccessToken: token.AccessToken,
ExpiresIn: int32(token.ExpiresIn),
RefreshToken: token.RefreshToken,
RefreshExpiresIn: int32(token.RefreshExpiresIn),
RefreshCreateAt: token.RefreshCreateAt.Unix(),
},
Data: claims,
}, nil
return &permission.ValidationTokenResp{}, nil
}

View File

@ -1,115 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./internal/model/permission_model.go
//
// Generated by this command:
//
// mockgen -source=./internal/model/permission_model.go -destination=./internal/mock/model/permission_model.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
model "app-cloudep-permission-server/internal/model"
context "context"
sql "database/sql"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockPermissionModel is a mock of PermissionModel interface.
type MockPermissionModel struct {
ctrl *gomock.Controller
recorder *MockPermissionModelMockRecorder
}
// MockPermissionModelMockRecorder is the mock recorder for MockPermissionModel.
type MockPermissionModelMockRecorder struct {
mock *MockPermissionModel
}
// NewMockPermissionModel creates a new mock instance.
func NewMockPermissionModel(ctrl *gomock.Controller) *MockPermissionModel {
mock := &MockPermissionModel{ctrl: ctrl}
mock.recorder = &MockPermissionModelMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPermissionModel) EXPECT() *MockPermissionModelMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockPermissionModel) Delete(ctx context.Context, id int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockPermissionModelMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockPermissionModel)(nil).Delete), ctx, id)
}
// FindOne mocks base method.
func (m *MockPermissionModel) FindOne(ctx context.Context, id int64) (*model.Permission, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*model.Permission)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockPermissionModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockPermissionModel)(nil).FindOne), ctx, id)
}
// FindOneByName mocks base method.
func (m *MockPermissionModel) FindOneByName(ctx context.Context, name string) (*model.Permission, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByName", ctx, name)
ret0, _ := ret[0].(*model.Permission)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByName indicates an expected call of FindOneByName.
func (mr *MockPermissionModelMockRecorder) FindOneByName(ctx, name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByName", reflect.TypeOf((*MockPermissionModel)(nil).FindOneByName), ctx, name)
}
// Insert mocks base method.
func (m *MockPermissionModel) Insert(ctx context.Context, data *model.Permission) (sql.Result, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(sql.Result)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Insert indicates an expected call of Insert.
func (mr *MockPermissionModelMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockPermissionModel)(nil).Insert), ctx, data)
}
// Update mocks base method.
func (m *MockPermissionModel) Update(ctx context.Context, data *model.Permission) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockPermissionModelMockRecorder) Update(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockPermissionModel)(nil).Update), ctx, data)
}

View File

@ -1,115 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./internal/model/permission_model_gen.go
//
// Generated by this command:
//
// mockgen -source=./internal/model/permission_model_gen.go -destination=./internal/mock/model/permission_model_gen.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
model "app-cloudep-permission-server/internal/model"
context "context"
sql "database/sql"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockpermissionModel is a mock of permissionModel interface.
type MockpermissionModel struct {
ctrl *gomock.Controller
recorder *MockpermissionModelMockRecorder
}
// MockpermissionModelMockRecorder is the mock recorder for MockpermissionModel.
type MockpermissionModelMockRecorder struct {
mock *MockpermissionModel
}
// NewMockpermissionModel creates a new mock instance.
func NewMockpermissionModel(ctrl *gomock.Controller) *MockpermissionModel {
mock := &MockpermissionModel{ctrl: ctrl}
mock.recorder = &MockpermissionModelMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockpermissionModel) EXPECT() *MockpermissionModelMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockpermissionModel) Delete(ctx context.Context, id int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockpermissionModelMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockpermissionModel)(nil).Delete), ctx, id)
}
// FindOne mocks base method.
func (m *MockpermissionModel) FindOne(ctx context.Context, id int64) (*model.Permission, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*model.Permission)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockpermissionModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockpermissionModel)(nil).FindOne), ctx, id)
}
// FindOneByName mocks base method.
func (m *MockpermissionModel) FindOneByName(ctx context.Context, name string) (*model.Permission, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByName", ctx, name)
ret0, _ := ret[0].(*model.Permission)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByName indicates an expected call of FindOneByName.
func (mr *MockpermissionModelMockRecorder) FindOneByName(ctx, name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByName", reflect.TypeOf((*MockpermissionModel)(nil).FindOneByName), ctx, name)
}
// Insert mocks base method.
func (m *MockpermissionModel) Insert(ctx context.Context, data *model.Permission) (sql.Result, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(sql.Result)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Insert indicates an expected call of Insert.
func (mr *MockpermissionModelMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockpermissionModel)(nil).Insert), ctx, data)
}
// Update mocks base method.
func (m *MockpermissionModel) Update(ctx context.Context, data *model.Permission) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockpermissionModelMockRecorder) Update(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockpermissionModel)(nil).Update), ctx, data)
}

View File

@ -1,130 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./internal/model/role_model.go
//
// Generated by this command:
//
// mockgen -source=./internal/model/role_model.go -destination=./internal/mock/model/role_model.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
model "app-cloudep-permission-server/internal/model"
context "context"
sql "database/sql"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockRoleModel is a mock of RoleModel interface.
type MockRoleModel struct {
ctrl *gomock.Controller
recorder *MockRoleModelMockRecorder
}
// MockRoleModelMockRecorder is the mock recorder for MockRoleModel.
type MockRoleModelMockRecorder struct {
mock *MockRoleModel
}
// NewMockRoleModel creates a new mock instance.
func NewMockRoleModel(ctrl *gomock.Controller) *MockRoleModel {
mock := &MockRoleModel{ctrl: ctrl}
mock.recorder = &MockRoleModelMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRoleModel) EXPECT() *MockRoleModelMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockRoleModel) Delete(ctx context.Context, id int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockRoleModelMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRoleModel)(nil).Delete), ctx, id)
}
// FindOne mocks base method.
func (m *MockRoleModel) FindOne(ctx context.Context, id int64) (*model.Role, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*model.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockRoleModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockRoleModel)(nil).FindOne), ctx, id)
}
// FindOneByDisplayName mocks base method.
func (m *MockRoleModel) FindOneByDisplayName(ctx context.Context, displayName string) (*model.Role, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByDisplayName", ctx, displayName)
ret0, _ := ret[0].(*model.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByDisplayName indicates an expected call of FindOneByDisplayName.
func (mr *MockRoleModelMockRecorder) FindOneByDisplayName(ctx, displayName any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByDisplayName", reflect.TypeOf((*MockRoleModel)(nil).FindOneByDisplayName), ctx, displayName)
}
// FindOneByRoleId mocks base method.
func (m *MockRoleModel) FindOneByRoleId(ctx context.Context, roleId string) (*model.Role, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByRoleId", ctx, roleId)
ret0, _ := ret[0].(*model.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByRoleId indicates an expected call of FindOneByRoleId.
func (mr *MockRoleModelMockRecorder) FindOneByRoleId(ctx, roleId any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByRoleId", reflect.TypeOf((*MockRoleModel)(nil).FindOneByRoleId), ctx, roleId)
}
// Insert mocks base method.
func (m *MockRoleModel) Insert(ctx context.Context, data *model.Role) (sql.Result, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(sql.Result)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Insert indicates an expected call of Insert.
func (mr *MockRoleModelMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockRoleModel)(nil).Insert), ctx, data)
}
// Update mocks base method.
func (m *MockRoleModel) Update(ctx context.Context, data *model.Role) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockRoleModelMockRecorder) Update(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRoleModel)(nil).Update), ctx, data)
}

View File

@ -1,130 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./internal/model/role_model_gen.go
//
// Generated by this command:
//
// mockgen -source=./internal/model/role_model_gen.go -destination=./internal/mock/model/role_model_gen.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
model "app-cloudep-permission-server/internal/model"
context "context"
sql "database/sql"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockroleModel is a mock of roleModel interface.
type MockroleModel struct {
ctrl *gomock.Controller
recorder *MockroleModelMockRecorder
}
// MockroleModelMockRecorder is the mock recorder for MockroleModel.
type MockroleModelMockRecorder struct {
mock *MockroleModel
}
// NewMockroleModel creates a new mock instance.
func NewMockroleModel(ctrl *gomock.Controller) *MockroleModel {
mock := &MockroleModel{ctrl: ctrl}
mock.recorder = &MockroleModelMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockroleModel) EXPECT() *MockroleModelMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockroleModel) Delete(ctx context.Context, id int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockroleModelMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockroleModel)(nil).Delete), ctx, id)
}
// FindOne mocks base method.
func (m *MockroleModel) FindOne(ctx context.Context, id int64) (*model.Role, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*model.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockroleModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockroleModel)(nil).FindOne), ctx, id)
}
// FindOneByDisplayName mocks base method.
func (m *MockroleModel) FindOneByDisplayName(ctx context.Context, displayName string) (*model.Role, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByDisplayName", ctx, displayName)
ret0, _ := ret[0].(*model.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByDisplayName indicates an expected call of FindOneByDisplayName.
func (mr *MockroleModelMockRecorder) FindOneByDisplayName(ctx, displayName any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByDisplayName", reflect.TypeOf((*MockroleModel)(nil).FindOneByDisplayName), ctx, displayName)
}
// FindOneByRoleId mocks base method.
func (m *MockroleModel) FindOneByRoleId(ctx context.Context, roleId string) (*model.Role, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByRoleId", ctx, roleId)
ret0, _ := ret[0].(*model.Role)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByRoleId indicates an expected call of FindOneByRoleId.
func (mr *MockroleModelMockRecorder) FindOneByRoleId(ctx, roleId any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByRoleId", reflect.TypeOf((*MockroleModel)(nil).FindOneByRoleId), ctx, roleId)
}
// Insert mocks base method.
func (m *MockroleModel) Insert(ctx context.Context, data *model.Role) (sql.Result, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(sql.Result)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Insert indicates an expected call of Insert.
func (mr *MockroleModelMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockroleModel)(nil).Insert), ctx, data)
}
// Update mocks base method.
func (m *MockroleModel) Update(ctx context.Context, data *model.Role) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockroleModelMockRecorder) Update(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockroleModel)(nil).Update), ctx, data)
}

View File

@ -1,100 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./internal/model/role_permission_model.go
//
// Generated by this command:
//
// mockgen -source=./internal/model/role_permission_model.go -destination=./internal/mock/model/role_permission_model.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
model "app-cloudep-permission-server/internal/model"
context "context"
sql "database/sql"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockRolePermissionModel is a mock of RolePermissionModel interface.
type MockRolePermissionModel struct {
ctrl *gomock.Controller
recorder *MockRolePermissionModelMockRecorder
}
// MockRolePermissionModelMockRecorder is the mock recorder for MockRolePermissionModel.
type MockRolePermissionModelMockRecorder struct {
mock *MockRolePermissionModel
}
// NewMockRolePermissionModel creates a new mock instance.
func NewMockRolePermissionModel(ctrl *gomock.Controller) *MockRolePermissionModel {
mock := &MockRolePermissionModel{ctrl: ctrl}
mock.recorder = &MockRolePermissionModelMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRolePermissionModel) EXPECT() *MockRolePermissionModelMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockRolePermissionModel) Delete(ctx context.Context, id int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockRolePermissionModelMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRolePermissionModel)(nil).Delete), ctx, id)
}
// FindOne mocks base method.
func (m *MockRolePermissionModel) FindOne(ctx context.Context, id int64) (*model.RolePermission, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*model.RolePermission)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockRolePermissionModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockRolePermissionModel)(nil).FindOne), ctx, id)
}
// Insert mocks base method.
func (m *MockRolePermissionModel) Insert(ctx context.Context, data *model.RolePermission) (sql.Result, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(sql.Result)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Insert indicates an expected call of Insert.
func (mr *MockRolePermissionModelMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockRolePermissionModel)(nil).Insert), ctx, data)
}
// Update mocks base method.
func (m *MockRolePermissionModel) Update(ctx context.Context, data *model.RolePermission) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockRolePermissionModelMockRecorder) Update(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockRolePermissionModel)(nil).Update), ctx, data)
}

View File

@ -1,100 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./internal/model/role_permission_model_gen.go
//
// Generated by this command:
//
// mockgen -source=./internal/model/role_permission_model_gen.go -destination=./internal/mock/model/role_permission_model_gen.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
model "app-cloudep-permission-server/internal/model"
context "context"
sql "database/sql"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockrolePermissionModel is a mock of rolePermissionModel interface.
type MockrolePermissionModel struct {
ctrl *gomock.Controller
recorder *MockrolePermissionModelMockRecorder
}
// MockrolePermissionModelMockRecorder is the mock recorder for MockrolePermissionModel.
type MockrolePermissionModelMockRecorder struct {
mock *MockrolePermissionModel
}
// NewMockrolePermissionModel creates a new mock instance.
func NewMockrolePermissionModel(ctrl *gomock.Controller) *MockrolePermissionModel {
mock := &MockrolePermissionModel{ctrl: ctrl}
mock.recorder = &MockrolePermissionModelMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockrolePermissionModel) EXPECT() *MockrolePermissionModelMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockrolePermissionModel) Delete(ctx context.Context, id int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockrolePermissionModelMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockrolePermissionModel)(nil).Delete), ctx, id)
}
// FindOne mocks base method.
func (m *MockrolePermissionModel) FindOne(ctx context.Context, id int64) (*model.RolePermission, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*model.RolePermission)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockrolePermissionModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockrolePermissionModel)(nil).FindOne), ctx, id)
}
// Insert mocks base method.
func (m *MockrolePermissionModel) Insert(ctx context.Context, data *model.RolePermission) (sql.Result, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(sql.Result)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Insert indicates an expected call of Insert.
func (mr *MockrolePermissionModelMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockrolePermissionModel)(nil).Insert), ctx, data)
}
// Update mocks base method.
func (m *MockrolePermissionModel) Update(ctx context.Context, data *model.RolePermission) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockrolePermissionModelMockRecorder) Update(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockrolePermissionModel)(nil).Update), ctx, data)
}

View File

@ -1,115 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./internal/model/user_role_model.go
//
// Generated by this command:
//
// mockgen -source=./internal/model/user_role_model.go -destination=./internal/mock/model/user_role_model.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
model "app-cloudep-permission-server/internal/model"
context "context"
sql "database/sql"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockUserRoleModel is a mock of UserRoleModel interface.
type MockUserRoleModel struct {
ctrl *gomock.Controller
recorder *MockUserRoleModelMockRecorder
}
// MockUserRoleModelMockRecorder is the mock recorder for MockUserRoleModel.
type MockUserRoleModelMockRecorder struct {
mock *MockUserRoleModel
}
// NewMockUserRoleModel creates a new mock instance.
func NewMockUserRoleModel(ctrl *gomock.Controller) *MockUserRoleModel {
mock := &MockUserRoleModel{ctrl: ctrl}
mock.recorder = &MockUserRoleModelMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockUserRoleModel) EXPECT() *MockUserRoleModelMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockUserRoleModel) Delete(ctx context.Context, id int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockUserRoleModelMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockUserRoleModel)(nil).Delete), ctx, id)
}
// FindOne mocks base method.
func (m *MockUserRoleModel) FindOne(ctx context.Context, id int64) (*model.UserRole, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*model.UserRole)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockUserRoleModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockUserRoleModel)(nil).FindOne), ctx, id)
}
// FindOneByUid mocks base method.
func (m *MockUserRoleModel) FindOneByUid(ctx context.Context, uid string) (*model.UserRole, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByUid", ctx, uid)
ret0, _ := ret[0].(*model.UserRole)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByUid indicates an expected call of FindOneByUid.
func (mr *MockUserRoleModelMockRecorder) FindOneByUid(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByUid", reflect.TypeOf((*MockUserRoleModel)(nil).FindOneByUid), ctx, uid)
}
// Insert mocks base method.
func (m *MockUserRoleModel) Insert(ctx context.Context, data *model.UserRole) (sql.Result, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(sql.Result)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Insert indicates an expected call of Insert.
func (mr *MockUserRoleModelMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockUserRoleModel)(nil).Insert), ctx, data)
}
// Update mocks base method.
func (m *MockUserRoleModel) Update(ctx context.Context, data *model.UserRole) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockUserRoleModelMockRecorder) Update(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUserRoleModel)(nil).Update), ctx, data)
}

View File

@ -1,115 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./internal/model/user_role_model_gen.go
//
// Generated by this command:
//
// mockgen -source=./internal/model/user_role_model_gen.go -destination=./internal/mock/model/user_role_model_gen.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
model "app-cloudep-permission-server/internal/model"
context "context"
sql "database/sql"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockuserRoleModel is a mock of userRoleModel interface.
type MockuserRoleModel struct {
ctrl *gomock.Controller
recorder *MockuserRoleModelMockRecorder
}
// MockuserRoleModelMockRecorder is the mock recorder for MockuserRoleModel.
type MockuserRoleModelMockRecorder struct {
mock *MockuserRoleModel
}
// NewMockuserRoleModel creates a new mock instance.
func NewMockuserRoleModel(ctrl *gomock.Controller) *MockuserRoleModel {
mock := &MockuserRoleModel{ctrl: ctrl}
mock.recorder = &MockuserRoleModelMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockuserRoleModel) EXPECT() *MockuserRoleModelMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockuserRoleModel) Delete(ctx context.Context, id int64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockuserRoleModelMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockuserRoleModel)(nil).Delete), ctx, id)
}
// FindOne mocks base method.
func (m *MockuserRoleModel) FindOne(ctx context.Context, id int64) (*model.UserRole, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", ctx, id)
ret0, _ := ret[0].(*model.UserRole)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockuserRoleModelMockRecorder) FindOne(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockuserRoleModel)(nil).FindOne), ctx, id)
}
// FindOneByUid mocks base method.
func (m *MockuserRoleModel) FindOneByUid(ctx context.Context, uid string) (*model.UserRole, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByUid", ctx, uid)
ret0, _ := ret[0].(*model.UserRole)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByUid indicates an expected call of FindOneByUid.
func (mr *MockuserRoleModelMockRecorder) FindOneByUid(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByUid", reflect.TypeOf((*MockuserRoleModel)(nil).FindOneByUid), ctx, uid)
}
// Insert mocks base method.
func (m *MockuserRoleModel) Insert(ctx context.Context, data *model.UserRole) (sql.Result, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(sql.Result)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Insert indicates an expected call of Insert.
func (mr *MockuserRoleModelMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockuserRoleModel)(nil).Insert), ctx, data)
}
// Update mocks base method.
func (m *MockuserRoleModel) Update(ctx context.Context, data *model.UserRole) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockuserRoleModelMockRecorder) Update(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockuserRoleModel)(nil).Update), ctx, data)
}

View File

@ -1,27 +0,0 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ PermissionModel = (*customPermissionModel)(nil)
type (
// PermissionModel is an interface to be customized, add more methods here,
// and implement the added methods in customPermissionModel.
PermissionModel interface {
permissionModel
}
customPermissionModel struct {
*defaultPermissionModel
}
)
// NewPermissionModel returns a model for the database table.
func NewPermissionModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) PermissionModel {
return &customPermissionModel{
defaultPermissionModel: newPermissionModel(conn, c, opts...),
}
}

View File

@ -1,157 +0,0 @@
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
permissionFieldNames = builder.RawFieldNames(&Permission{})
permissionRows = strings.Join(permissionFieldNames, ",")
permissionRowsExpectAutoSet = strings.Join(stringx.Remove(permissionFieldNames, "`id`"), ",")
permissionRowsWithPlaceHolder = strings.Join(stringx.Remove(permissionFieldNames, "`id`"), "=?,") + "=?"
cachePermissionIdPrefix = "cache:permission:id:"
cachePermissionNamePrefix = "cache:permission:name:"
)
type (
permissionModel interface {
Insert(ctx context.Context, data *Permission) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*Permission, error)
FindOneByName(ctx context.Context, name string) (*Permission, error)
Update(ctx context.Context, data *Permission) error
Delete(ctx context.Context, id int64) error
}
defaultPermissionModel struct {
sqlc.CachedConn
table string
}
Permission struct {
Id int64 `db:"id"` // PK
Parent sql.NullInt64 `db:"parent"`
Name string `db:"name"`
HttpMethod string `db:"http_method"`
HttpPath string `db:"http_path"`
Status int64 `db:"status"` // 狀態 1: 啟用, 2: 關閉
Type int64 `db:"type"` // 狀態 1: 後台, 2: 前台
CreateTime int64 `db:"create_time"` // 創建時間
UpdateTime int64 `db:"update_time"` // 更新時間
}
)
func newPermissionModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultPermissionModel {
return &defaultPermissionModel{
CachedConn: sqlc.NewConn(conn, c, opts...),
table: "`permission`",
}
}
func (m *defaultPermissionModel) withSession(session sqlx.Session) *defaultPermissionModel {
return &defaultPermissionModel{
CachedConn: m.CachedConn.WithSession(session),
table: "`permission`",
}
}
func (m *defaultPermissionModel) Delete(ctx context.Context, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
permissionIdKey := fmt.Sprintf("%s%v", cachePermissionIdPrefix, id)
permissionNameKey := fmt.Sprintf("%s%v", cachePermissionNamePrefix, data.Name)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, permissionIdKey, permissionNameKey)
return err
}
func (m *defaultPermissionModel) FindOne(ctx context.Context, id int64) (*Permission, error) {
permissionIdKey := fmt.Sprintf("%s%v", cachePermissionIdPrefix, id)
var resp Permission
err := m.QueryRowCtx(ctx, &resp, permissionIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", permissionRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultPermissionModel) FindOneByName(ctx context.Context, name string) (*Permission, error) {
permissionNameKey := fmt.Sprintf("%s%v", cachePermissionNamePrefix, name)
var resp Permission
err := m.QueryRowIndexCtx(ctx, &resp, permissionNameKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
query := fmt.Sprintf("select %s from %s where `name` = ? limit 1", permissionRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, name); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultPermissionModel) Insert(ctx context.Context, data *Permission) (sql.Result, error) {
permissionIdKey := fmt.Sprintf("%s%v", cachePermissionIdPrefix, data.Id)
permissionNameKey := fmt.Sprintf("%s%v", cachePermissionNamePrefix, data.Name)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?)", m.table, permissionRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.Parent, data.Name, data.HttpMethod, data.HttpPath, data.Status, data.Type, data.CreateTime, data.UpdateTime)
}, permissionIdKey, permissionNameKey)
return ret, err
}
func (m *defaultPermissionModel) Update(ctx context.Context, newData *Permission) error {
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return err
}
permissionIdKey := fmt.Sprintf("%s%v", cachePermissionIdPrefix, data.Id)
permissionNameKey := fmt.Sprintf("%s%v", cachePermissionNamePrefix, data.Name)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, permissionRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, newData.Parent, newData.Name, newData.HttpMethod, newData.HttpPath, newData.Status, newData.Type, newData.CreateTime, newData.UpdateTime, newData.Id)
}, permissionIdKey, permissionNameKey)
return err
}
func (m *defaultPermissionModel) formatPrimary(primary any) string {
return fmt.Sprintf("%s%v", cachePermissionIdPrefix, primary)
}
func (m *defaultPermissionModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", permissionRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary)
}
func (m *defaultPermissionModel) tableName() string {
return m.table
}

View File

@ -1,27 +0,0 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ RoleModel = (*customRoleModel)(nil)
type (
// RoleModel is an interface to be customized, add more methods here,
// and implement the added methods in customRoleModel.
RoleModel interface {
roleModel
}
customRoleModel struct {
*defaultRoleModel
}
)
// NewRoleModel returns a model for the database table.
func NewRoleModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) RoleModel {
return &customRoleModel{
defaultRoleModel: newRoleModel(conn, c, opts...),
}
}

View File

@ -1,179 +0,0 @@
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
roleFieldNames = builder.RawFieldNames(&Role{})
roleRows = strings.Join(roleFieldNames, ",")
roleRowsExpectAutoSet = strings.Join(stringx.Remove(roleFieldNames, "`id`"), ",")
roleRowsWithPlaceHolder = strings.Join(stringx.Remove(roleFieldNames, "`id`"), "=?,") + "=?"
cacheRoleIdPrefix = "cache:role:id:"
cacheRoleDisplayNamePrefix = "cache:role:displayName:"
cacheRoleRoleIdPrefix = "cache:role:roleId:"
)
type (
roleModel interface {
Insert(ctx context.Context, data *Role) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*Role, error)
FindOneByDisplayName(ctx context.Context, displayName string) (*Role, error)
FindOneByRoleId(ctx context.Context, roleId string) (*Role, error)
Update(ctx context.Context, data *Role) error
Delete(ctx context.Context, id int64) error
}
defaultRoleModel struct {
sqlc.CachedConn
table string
}
Role struct {
Id int64 `db:"id"` // PK
RoleId string `db:"role_id"`
DisplayName string `db:"display_name"` // 名稱
Status int64 `db:"status"` // 狀態 1: 啟用, 2: 禁用
CreateTime int64 `db:"create_time"` // 創建時間
UpdateTime int64 `db:"update_time"` // 更新時間
}
)
func newRoleModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultRoleModel {
return &defaultRoleModel{
CachedConn: sqlc.NewConn(conn, c, opts...),
table: "`role`",
}
}
func (m *defaultRoleModel) withSession(session sqlx.Session) *defaultRoleModel {
return &defaultRoleModel{
CachedConn: m.CachedConn.WithSession(session),
table: "`role`",
}
}
func (m *defaultRoleModel) Delete(ctx context.Context, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
roleDisplayNameKey := fmt.Sprintf("%s%v", cacheRoleDisplayNamePrefix, data.DisplayName)
roleIdKey := fmt.Sprintf("%s%v", cacheRoleIdPrefix, id)
roleRoleIdKey := fmt.Sprintf("%s%v", cacheRoleRoleIdPrefix, data.RoleId)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, roleDisplayNameKey, roleIdKey, roleRoleIdKey)
return err
}
func (m *defaultRoleModel) FindOne(ctx context.Context, id int64) (*Role, error) {
roleIdKey := fmt.Sprintf("%s%v", cacheRoleIdPrefix, id)
var resp Role
err := m.QueryRowCtx(ctx, &resp, roleIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", roleRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultRoleModel) FindOneByDisplayName(ctx context.Context, displayName string) (*Role, error) {
roleDisplayNameKey := fmt.Sprintf("%s%v", cacheRoleDisplayNamePrefix, displayName)
var resp Role
err := m.QueryRowIndexCtx(ctx, &resp, roleDisplayNameKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
query := fmt.Sprintf("select %s from %s where `display_name` = ? limit 1", roleRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, displayName); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultRoleModel) FindOneByRoleId(ctx context.Context, roleId string) (*Role, error) {
roleRoleIdKey := fmt.Sprintf("%s%v", cacheRoleRoleIdPrefix, roleId)
var resp Role
err := m.QueryRowIndexCtx(ctx, &resp, roleRoleIdKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
query := fmt.Sprintf("select %s from %s where `role_id` = ? limit 1", roleRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, roleId); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultRoleModel) Insert(ctx context.Context, data *Role) (sql.Result, error) {
roleDisplayNameKey := fmt.Sprintf("%s%v", cacheRoleDisplayNamePrefix, data.DisplayName)
roleIdKey := fmt.Sprintf("%s%v", cacheRoleIdPrefix, data.Id)
roleRoleIdKey := fmt.Sprintf("%s%v", cacheRoleRoleIdPrefix, data.RoleId)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?)", m.table, roleRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.RoleId, data.DisplayName, data.Status, data.CreateTime, data.UpdateTime)
}, roleDisplayNameKey, roleIdKey, roleRoleIdKey)
return ret, err
}
func (m *defaultRoleModel) Update(ctx context.Context, newData *Role) error {
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return err
}
roleDisplayNameKey := fmt.Sprintf("%s%v", cacheRoleDisplayNamePrefix, data.DisplayName)
roleIdKey := fmt.Sprintf("%s%v", cacheRoleIdPrefix, data.Id)
roleRoleIdKey := fmt.Sprintf("%s%v", cacheRoleRoleIdPrefix, data.RoleId)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, roleRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, newData.RoleId, newData.DisplayName, newData.Status, newData.CreateTime, newData.UpdateTime, newData.Id)
}, roleDisplayNameKey, roleIdKey, roleRoleIdKey)
return err
}
func (m *defaultRoleModel) formatPrimary(primary any) string {
return fmt.Sprintf("%s%v", cacheRoleIdPrefix, primary)
}
func (m *defaultRoleModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", roleRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary)
}
func (m *defaultRoleModel) tableName() string {
return m.table
}

View File

@ -1,27 +0,0 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ RolePermissionModel = (*customRolePermissionModel)(nil)
type (
// RolePermissionModel is an interface to be customized, add more methods here,
// and implement the added methods in customRolePermissionModel.
RolePermissionModel interface {
rolePermissionModel
}
customRolePermissionModel struct {
*defaultRolePermissionModel
}
)
// NewRolePermissionModel returns a model for the database table.
func NewRolePermissionModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) RolePermissionModel {
return &customRolePermissionModel{
defaultRolePermissionModel: newRolePermissionModel(conn, c, opts...),
}
}

View File

@ -1,118 +0,0 @@
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
rolePermissionFieldNames = builder.RawFieldNames(&RolePermission{})
rolePermissionRows = strings.Join(rolePermissionFieldNames, ",")
rolePermissionRowsExpectAutoSet = strings.Join(stringx.Remove(rolePermissionFieldNames, "`id`"), ",")
rolePermissionRowsWithPlaceHolder = strings.Join(stringx.Remove(rolePermissionFieldNames, "`id`"), "=?,") + "=?"
cacheRolePermissionIdPrefix = "cache:rolePermission:id:"
)
type (
rolePermissionModel interface {
Insert(ctx context.Context, data *RolePermission) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*RolePermission, error)
Update(ctx context.Context, data *RolePermission) error
Delete(ctx context.Context, id int64) error
}
defaultRolePermissionModel struct {
sqlc.CachedConn
table string
}
RolePermission struct {
Id int64 `db:"id"` // PK
RoleId sql.NullInt64 `db:"role_id"` // role.id
PermissionId sql.NullInt64 `db:"permission_id"` // permission.id
CreateTime int64 `db:"create_time"` // 創建時間
UpdateTime int64 `db:"update_time"` // 更新時間
}
)
func newRolePermissionModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultRolePermissionModel {
return &defaultRolePermissionModel{
CachedConn: sqlc.NewConn(conn, c, opts...),
table: "`role_permission`",
}
}
func (m *defaultRolePermissionModel) withSession(session sqlx.Session) *defaultRolePermissionModel {
return &defaultRolePermissionModel{
CachedConn: m.CachedConn.WithSession(session),
table: "`role_permission`",
}
}
func (m *defaultRolePermissionModel) Delete(ctx context.Context, id int64) error {
rolePermissionIdKey := fmt.Sprintf("%s%v", cacheRolePermissionIdPrefix, id)
_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, rolePermissionIdKey)
return err
}
func (m *defaultRolePermissionModel) FindOne(ctx context.Context, id int64) (*RolePermission, error) {
rolePermissionIdKey := fmt.Sprintf("%s%v", cacheRolePermissionIdPrefix, id)
var resp RolePermission
err := m.QueryRowCtx(ctx, &resp, rolePermissionIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", rolePermissionRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultRolePermissionModel) Insert(ctx context.Context, data *RolePermission) (sql.Result, error) {
rolePermissionIdKey := fmt.Sprintf("%s%v", cacheRolePermissionIdPrefix, data.Id)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?)", m.table, rolePermissionRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.RoleId, data.PermissionId, data.CreateTime, data.UpdateTime)
}, rolePermissionIdKey)
return ret, err
}
func (m *defaultRolePermissionModel) Update(ctx context.Context, data *RolePermission) error {
rolePermissionIdKey := fmt.Sprintf("%s%v", cacheRolePermissionIdPrefix, data.Id)
_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, rolePermissionRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, data.RoleId, data.PermissionId, data.CreateTime, data.UpdateTime, data.Id)
}, rolePermissionIdKey)
return err
}
func (m *defaultRolePermissionModel) formatPrimary(primary any) string {
return fmt.Sprintf("%s%v", cacheRolePermissionIdPrefix, primary)
}
func (m *defaultRolePermissionModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", rolePermissionRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary)
}
func (m *defaultRolePermissionModel) tableName() string {
return m.table
}

View File

@ -1,27 +0,0 @@
package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ UserRoleModel = (*customUserRoleModel)(nil)
type (
// UserRoleModel is an interface to be customized, add more methods here,
// and implement the added methods in customUserRoleModel.
UserRoleModel interface {
userRoleModel
}
customUserRoleModel struct {
*defaultUserRoleModel
}
)
// NewUserRoleModel returns a model for the database table.
func NewUserRoleModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) UserRoleModel {
return &customUserRoleModel{
defaultUserRoleModel: newUserRoleModel(conn, c, opts...),
}
}

View File

@ -1,155 +0,0 @@
// Code generated by goctl. DO NOT EDIT.
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
userRoleFieldNames = builder.RawFieldNames(&UserRole{})
userRoleRows = strings.Join(userRoleFieldNames, ",")
userRoleRowsExpectAutoSet = strings.Join(stringx.Remove(userRoleFieldNames, "`id`"), ",")
userRoleRowsWithPlaceHolder = strings.Join(stringx.Remove(userRoleFieldNames, "`id`"), "=?,") + "=?"
cacheUserRoleIdPrefix = "cache:userRole:id:"
cacheUserRoleUidPrefix = "cache:userRole:uid:"
)
type (
userRoleModel interface {
Insert(ctx context.Context, data *UserRole) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*UserRole, error)
FindOneByUid(ctx context.Context, uid string) (*UserRole, error)
Update(ctx context.Context, data *UserRole) error
Delete(ctx context.Context, id int64) error
}
defaultUserRoleModel struct {
sqlc.CachedConn
table string
}
UserRole struct {
Id int64 `db:"id"` // PK
Brand string `db:"brand"`
Uid string `db:"uid"`
RoleId string `db:"role_id"`
Status int64 `db:"status"` // 狀態 1: 啟用, 2: 禁用
CreateTime int64 `db:"create_time"` // 創建時間
UpdateTime int64 `db:"update_time"` // 更新時間
}
)
func newUserRoleModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultUserRoleModel {
return &defaultUserRoleModel{
CachedConn: sqlc.NewConn(conn, c, opts...),
table: "`user_role`",
}
}
func (m *defaultUserRoleModel) withSession(session sqlx.Session) *defaultUserRoleModel {
return &defaultUserRoleModel{
CachedConn: m.CachedConn.WithSession(session),
table: "`user_role`",
}
}
func (m *defaultUserRoleModel) Delete(ctx context.Context, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
userRoleIdKey := fmt.Sprintf("%s%v", cacheUserRoleIdPrefix, id)
userRoleUidKey := fmt.Sprintf("%s%v", cacheUserRoleUidPrefix, data.Uid)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, userRoleIdKey, userRoleUidKey)
return err
}
func (m *defaultUserRoleModel) FindOne(ctx context.Context, id int64) (*UserRole, error) {
userRoleIdKey := fmt.Sprintf("%s%v", cacheUserRoleIdPrefix, id)
var resp UserRole
err := m.QueryRowCtx(ctx, &resp, userRoleIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userRoleRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserRoleModel) FindOneByUid(ctx context.Context, uid string) (*UserRole, error) {
userRoleUidKey := fmt.Sprintf("%s%v", cacheUserRoleUidPrefix, uid)
var resp UserRole
err := m.QueryRowIndexCtx(ctx, &resp, userRoleUidKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
query := fmt.Sprintf("select %s from %s where `uid` = ? limit 1", userRoleRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, uid); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserRoleModel) Insert(ctx context.Context, data *UserRole) (sql.Result, error) {
userRoleIdKey := fmt.Sprintf("%s%v", cacheUserRoleIdPrefix, data.Id)
userRoleUidKey := fmt.Sprintf("%s%v", cacheUserRoleUidPrefix, data.Uid)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?)", m.table, userRoleRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.Brand, data.Uid, data.RoleId, data.Status, data.CreateTime, data.UpdateTime)
}, userRoleIdKey, userRoleUidKey)
return ret, err
}
func (m *defaultUserRoleModel) Update(ctx context.Context, newData *UserRole) error {
data, err := m.FindOne(ctx, newData.Id)
if err != nil {
return err
}
userRoleIdKey := fmt.Sprintf("%s%v", cacheUserRoleIdPrefix, data.Id)
userRoleUidKey := fmt.Sprintf("%s%v", cacheUserRoleUidPrefix, data.Uid)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRoleRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, newData.Brand, newData.Uid, newData.RoleId, newData.Status, newData.CreateTime, newData.UpdateTime, newData.Id)
}, userRoleIdKey, userRoleUidKey)
return err
}
func (m *defaultUserRoleModel) formatPrimary(primary any) string {
return fmt.Sprintf("%s%v", cacheUserRoleIdPrefix, primary)
}
func (m *defaultUserRoleModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userRoleRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary)
}
func (m *defaultUserRoleModel) tableName() string {
return m.table
}

View File

@ -1,5 +0,0 @@
package model
import "github.com/zeromicro/go-zero/core/stores/sqlx"
var ErrNotFound = sqlx.ErrNotFound

View File

@ -1,104 +0,0 @@
package repository
import (
"app-cloudep-permission-server/internal/domain/repository"
"code.30cm.net/digimon/library-go/utils/invited_code"
"context"
"fmt"
"github.com/zeromicro/go-zero/core/stores/redis"
)
type MemberOnlineStatusRepositoryParam struct {
Store *redis.Redis `name:"redis"`
}
type memberOnlineStatusRepository struct {
store *redis.Redis
}
// 使用 UID 計算 Bitmap 的 offset
func (t *memberOnlineStatusRepository) uidToOffset(uid string) (int64, error) {
converter := invited_code.MustConverter(10, invited_code.DefaultCodeLen, invited_code.ConvertTable)
// 將 UID 轉換為整數型,並減去基礎 UID (1000000)
uidInt, err := converter.DecodeFromCode(uid)
if err != nil {
return 0, fmt.Errorf("invalid UID: %w", err)
}
// 以 1000000 作為基準
baseUID := invited_code.InitAutoId
if uidInt < int64(baseUID) {
return 0, fmt.Errorf("UID smaller than base: %d", baseUID)
}
return uidInt - int64(baseUID), nil
}
func (t *memberOnlineStatusRepository) SetMemberOnline(ctx context.Context, uid string) (bool, error) {
offset, err := t.uidToOffset(uid)
if err != nil {
return false, err
}
// 使用 SET BIT 設置對應的位為 1
_, err = t.store.SetBit("member_status", offset, 1)
if err != nil {
return false, fmt.Errorf("failed to set member online: %w", err)
}
return true, nil
}
func (t *memberOnlineStatusRepository) SetMemberOffline(ctx context.Context, uid string) (bool, error) {
offset, err := t.uidToOffset(uid)
if err != nil {
return false, err
}
// 使用 SET BIT 設置對應的位為 1
_, err = t.store.SetBit("member_status", offset, 0)
if err != nil {
return false, fmt.Errorf("failed to set member offline: %w", err)
}
return true, nil
}
func (t *memberOnlineStatusRepository) IsMemberOnline(ctx context.Context, uid string) (bool, error) {
offset, err := t.uidToOffset(uid)
if err != nil {
return false, err
}
// 使用 GET BIT 獲取對應的位1 表示在線0 表示離線
status, err := t.store.GetBit("member_status", offset)
if err != nil {
return false, fmt.Errorf("failed to get member status: %w", err)
}
return status == 1, nil
}
func (t *memberOnlineStatusRepository) QueryMemberOnlineList(ctx context.Context, uids []string) ([]repository.MemberOnlineStatusResp, error) {
statusMap := make([]repository.MemberOnlineStatusResp, 0, len(uids))
// 遍歷所有用戶的 UID查詢他們的在線狀態
for _, uid := range uids {
isOnline, err := t.IsMemberOnline(ctx, uid)
if err != nil {
return nil, fmt.Errorf("failed to query member status for UID %s: %w", uid, err)
}
statusMap = append(statusMap, repository.MemberOnlineStatusResp{
UID: uid,
Status: isOnline,
})
}
return statusMap, nil
}
func NewMemberOnlineStatusRepository(param MemberOnlineStatusRepositoryParam) repository.MemberOnlineStatusRepository {
return &memberOnlineStatusRepository{
store: param.Store,
}
}

View File

@ -1,312 +0,0 @@
package repository
import (
"app-cloudep-permission-server/internal/domain"
"app-cloudep-permission-server/internal/domain/repository"
"app-cloudep-permission-server/internal/entity"
"context"
"encoding/json"
"errors"
"fmt"
"time"
ers "code.30cm.net/digimon/library-go/errors"
"github.com/zeromicro/go-zero/core/stores/redis"
)
type TokenRepositoryParam struct {
Store *redis.Redis `name:"redis"`
}
type tokenRepository struct {
store *redis.Redis
}
func NewTokenRepository(param TokenRepositoryParam) repository.TokenRepository {
return &tokenRepository{
store: param.Store,
}
}
func (t *tokenRepository) Create(ctx context.Context, token entity.Token) error {
body, err := json.Marshal(token)
if err != nil {
return ers.ArkInternal("json.Marshal token error", err.Error())
}
if err := t.store.Pipelined(func(tx redis.Pipeliner) error {
refreshTTL := time.Duration(token.RedisRefreshExpiredSec()) * time.Second
if err := t.setToken(ctx, tx, token, body, refreshTTL); err != nil {
return err
}
if err := t.setRefreshToken(ctx, tx, token, refreshTTL); err != nil {
return err
}
return t.setRelation(ctx, tx, token.UID, token.DeviceID, token.ID, refreshTTL)
}); err != nil {
return repository.RedisPipLineError(err.Error())
}
return nil
}
func (t *tokenRepository) Delete(ctx context.Context, token entity.Token) error {
keys := []string{
domain.GetAccessTokenRedisKey(token.ID),
domain.RefreshTokenRedisKey.With(token.RefreshToken).ToString(),
}
if err := t.deleteKeys(ctx, keys...); err != nil {
return repository.RedisPipLineError(err.Error())
}
_, _ = t.store.Srem(domain.DeviceTokenRedisKey.With(token.DeviceID).ToString(), token.ID)
_, _ = t.store.Srem(domain.UIDTokenRedisKey.With(token.UID).ToString(), token.ID)
return nil
}
func (t *tokenRepository) GetAccessTokenByID(ctx context.Context, id string) (entity.Token, error) {
token, err := t.get(ctx, domain.GetAccessTokenRedisKey(id))
if err != nil {
return entity.Token{}, err
}
return token, nil
}
func (t *tokenRepository) DeleteAccessTokensByUID(ctx context.Context, uid string) error {
tokens, err := t.GetAccessTokensByUID(ctx, uid)
if err != nil {
return err
}
for _, token := range tokens {
if err := t.Delete(ctx, token); err != nil {
return err
}
}
return nil
}
func (t *tokenRepository) DeleteAccessTokenByID(ctx context.Context, ids []string) error {
for _, tokenID := range ids {
token, err := t.GetAccessTokenByID(ctx, tokenID)
if err != nil {
continue
}
keys := []string{
domain.GetAccessTokenRedisKey(token.ID),
domain.RefreshTokenRedisKey.With(token.RefreshToken).ToString(),
}
if err := t.deleteKeys(ctx, keys...); err != nil {
continue
}
_, _ = t.store.Srem(domain.DeviceTokenRedisKey.With(token.DeviceID).ToString(), token.ID)
_, _ = t.store.Srem(domain.UIDTokenRedisKey.With(token.UID).ToString(), token.ID)
}
return nil
}
func (t *tokenRepository) GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error) {
return t.getTokensBySet(ctx, domain.GetUIDTokenRedisKey(uid))
}
func (t *tokenRepository) GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error) {
return t.getTokensBySet(ctx, domain.DeviceTokenRedisKey.With(deviceID).ToString())
}
func (t *tokenRepository) DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error {
tokens, err := t.GetAccessTokensByDeviceID(ctx, deviceID)
if err != nil {
return repository.RedisDelError(fmt.Sprintf("GetAccessTokensByDeviceID error: %v", err))
}
var keys []string
for _, token := range tokens {
keys = append(keys, domain.GetAccessTokenRedisKey(token.ID))
keys = append(keys, domain.RefreshTokenRedisKey.With(token.RefreshToken).ToString())
}
err = t.store.Pipelined(func(tx redis.Pipeliner) error {
for _, token := range tokens {
_, _ = t.store.Srem(domain.UIDTokenRedisKey.With(token.UID).ToString(), token.ID)
}
return nil
})
if err != nil {
return err
}
if err := t.deleteKeys(ctx, keys...); err != nil {
return err
}
_, err = t.store.Del(domain.DeviceTokenRedisKey.With(deviceID).ToString())
return err
}
func (t *tokenRepository) GetAccessTokenCountByDeviceID(deviceID string) (int, error) {
return t.getCountBySet(domain.DeviceTokenRedisKey.With(deviceID).ToString())
}
func (t *tokenRepository) GetAccessTokenCountByUID(uid string) (int, error) {
return t.getCountBySet(domain.UIDTokenRedisKey.With(uid).ToString())
}
func (t *tokenRepository) GetAccessTokenByByOneTimeToken(ctx context.Context, oneTimeToken string) (entity.Token, error) {
id, err := t.store.Get(domain.RefreshTokenRedisKey.With(oneTimeToken).ToString())
if err != nil {
return entity.Token{}, repository.RedisError(fmt.Sprintf("GetAccessTokenByByOneTimeToken store.Get error: %s", err.Error()))
}
if id == "" {
return entity.Token{}, ers.ResourceNotFound("token key not found in redis", domain.RefreshTokenRedisKey.With(oneTimeToken).ToString())
}
return t.GetAccessTokenByID(ctx, id)
}
func (t *tokenRepository) DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error {
var keys []string
for _, id := range ids {
keys = append(keys, domain.RefreshTokenRedisKey.With(id).ToString())
}
for _, token := range tokens {
keys = append(keys, domain.RefreshTokenRedisKey.With(token.RefreshToken).ToString())
}
return t.deleteKeys(ctx, keys...)
}
func (t *tokenRepository) CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, expires time.Duration) error {
body, err := json.Marshal(ticket)
if err != nil {
return ers.InvalidFormat("CreateOneTimeToken json.Marshal error", err.Error())
}
_, err = t.store.SetnxEx(domain.RefreshTokenRedisKey.With(key).ToString(), string(body), int(expires.Seconds()))
if err != nil {
return repository.RedisError(fmt.Sprintf("CreateOneTimeToken store.SetnxEx error: %s", err.Error()))
}
return nil
}
// -------------------- Private area --------------------
func (t *tokenRepository) get(ctx context.Context, key string) (entity.Token, error) {
body, err := t.store.GetCtx(ctx, key)
if err != nil {
return entity.Token{}, repository.RedisError(fmt.Sprintf("token %s not found in redis: %s", key, err.Error()))
}
if body == "" {
return entity.Token{}, ers.ResourceNotFound("this token not found")
}
var token entity.Token
if err := json.Unmarshal([]byte(body), &token); err != nil {
return entity.Token{}, ers.ArkInternal("json.Unmarshal token error", err.Error())
}
return token, nil
}
func (t *tokenRepository) setToken(ctx context.Context, tx redis.Pipeliner, token entity.Token, body []byte, ttl time.Duration) error {
return tx.Set(ctx, domain.GetAccessTokenRedisKey(token.ID), body, ttl).Err()
}
func (t *tokenRepository) setRefreshToken(ctx context.Context, tx redis.Pipeliner, token entity.Token, ttl time.Duration) error {
if token.RefreshToken != "" {
return tx.Set(ctx, domain.RefreshTokenRedisKey.With(token.RefreshToken).ToString(), token.ID, ttl).Err()
}
return nil
}
func (t *tokenRepository) setRelation(ctx context.Context, tx redis.Pipeliner, uid, deviceID, tokenID string, ttl time.Duration) error {
if err := tx.SAdd(ctx, domain.UIDTokenRedisKey.With(uid).ToString(), tokenID).Err(); err != nil {
return err
}
// 設置 UID 鍵的過期時間
if err := tx.Expire(ctx, domain.UIDTokenRedisKey.With(uid).ToString(), ttl).Err(); err != nil {
return err
}
if err := tx.SAdd(ctx, domain.DeviceTokenRedisKey.With(deviceID).ToString(), tokenID).Err(); err != nil {
return err
}
// 設置 deviceID 鍵的過期時間
if err := tx.Expire(ctx, domain.DeviceTokenRedisKey.With(deviceID).ToString(), ttl).Err(); err != nil {
return err
}
return nil
}
func (t *tokenRepository) deleteKeys(ctx context.Context, keys ...string) error {
return t.store.Pipelined(func(tx redis.Pipeliner) error {
for _, key := range keys {
if err := tx.Del(ctx, key).Err(); err != nil {
return repository.RedisDelError(fmt.Sprintf("store.Del key error: %v", err))
}
}
return nil
})
}
func (t *tokenRepository) getTokensBySet(ctx context.Context, setKey string) ([]entity.Token, error) {
ids, err := t.store.Smembers(setKey)
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, nil
}
return nil, repository.RedisError(fmt.Sprintf("getTokensBySet store.Get %s error: %v", setKey, err.Error()))
}
var tokens []entity.Token
var deleteTokens []string
now := time.Now().Unix()
for _, id := range ids {
token, err := t.get(ctx, domain.GetAccessTokenRedisKey(id))
if err != nil {
deleteTokens = append(deleteTokens, id)
continue
}
if int64(token.ExpiresIn) < now {
deleteTokens = append(deleteTokens, id)
continue
}
tokens = append(tokens, token)
}
if len(deleteTokens) > 0 {
_ = t.DeleteAccessTokenByID(ctx, deleteTokens)
}
return tokens, nil
}
func (t *tokenRepository) getCountBySet(setKey string) (int, error) {
count, err := t.store.Scard(setKey)
if err != nil {
return 0, err
}
return int(count), nil
}

View File

@ -1,4 +1,5 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.7.3
// Source: permission.proto
package server
@ -6,9 +7,10 @@ package server
import (
"context"
"app-cloudep-permission-server/gen_result/pb/permission"
tokenservicelogic "app-cloudep-permission-server/internal/logic/tokenservice"
"app-cloudep-permission-server/internal/svc"
tokenservicelogic "code.30cm.net/digimon/app-cloudep-permission-server/internal/logic/tokenservice"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
)
type TokenServiceServer struct {

View File

@ -1,42 +1,13 @@
package svc
import (
"app-cloudep-permission-server/internal/config"
"app-cloudep-permission-server/internal/domain/repository"
repo "app-cloudep-permission-server/internal/repository"
ers "code.30cm.net/digimon/library-go/errors"
"code.30cm.net/digimon/library-go/errors/code"
vi "code.30cm.net/digimon/library-go/validator"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
import "code.30cm.net/digimon/app-cloudep-permission-server/internal/config"
type ServiceContext struct {
Config config.Config
Conn sqlx.SqlConn
Validate vi.Validate
Redis redis.Redis
TokenRedisRepo repository.TokenRepository
}
func NewServiceContext(c config.Config) *ServiceContext {
ers.Scope = code.CloudEPPermission
sqlConn := sqlx.NewMysql(c.DB.DsnString)
newRedis, err := redis.NewRedis(c.RedisCluster, redis.Cluster())
if err != nil {
panic(err)
}
return &ServiceContext{
Conn: sqlConn,
Config: c,
Validate: vi.MustValidator(),
Redis: *newRedis,
TokenRedisRepo: repo.NewTokenRepository(repo.TokenRepositoryParam{
Store: newRedis,
}),
}
}

View File

@ -1,117 +0,0 @@
package usecase
// Bitmap 基礎結構
// Bitmap 是一個位圖結構,使用 byte slice 來表示大量的位bit
// 每個 byte 由 8 個位組成因此可以高效地管理大量的開關狀態true/false
// Bitmap 的優點在於它能節省空間,尤其是在需要大量布爾值的場合。
// 缺點是,如果需要動態擴充 Bitmap 的大小,會導致效率下降,因為需要重新分配和移動內存。
// 因此,最好在初始化時就規劃好所需的位數大小,避免在之後頻繁擴充。
type Bitmap []byte
// MakeBitmapWithBitSize 通過指定的 bit 數創建一個新的 Bitmap。
// 參數 nBits 表示所需的位bit數。
// 如果指定的位數少於 64則默認將 Bitmap 初始化為 64 位(這是最低的限制)。
// 此外位數nBits會被自動調整為 8 的倍數,以適配 byte 的長度(每 8 位為一個 byte
// 返回值是一個 Bitmapbyte slice其大小根據位數確定。
func MakeBitmapWithBitSize(nBits int) Bitmap {
// 如果指定的位數少於 64則設置為 64 位8 個 byte
if nBits < 64 {
nBits = 64
}
// 計算需要的 byte 數,確保每 8 位為一個 byte並調整 nBits 以達到 8 的倍數
return MustBitMap((nBits + 7) / 8)
}
// MustBitMap 根據指定的 byte 數創建一個 Bitmapbyte slice
// 參數 nBytes 表示所需的 byte 數。
// 返回值是一個長度為 nBytes 的 Bitmap。
func MustBitMap(nBytes int) Bitmap {
// 使用 make 函數創建一個 byte slice大小為 nBytes。
return make([]byte, nBytes)
}
// SetTrue 設置指定位置的 bit 為 true1
// 參數 bitPos 是需要設置的位的位置(以 0 為基準的位索引)。
// 這個操作會找到該 bit 所在的 byte然後通過位運算將該位置的 bit 設置為 1。
func (b Bitmap) SetTrue(bitPos uint32) {
// |= 是一種位運算的複合賦值運算符,表示將左邊的變數與右邊的值進行 位或運算bitwise OR並將結果賦值
b[bitPos/8] |= 1 << (bitPos % 8)
}
// SetFalse 設置指定位置的 bit 為 false0
// 參數 bitPos 是需要設置的位的位置(以 0 為基準的位索引)。
// 這個操作會找到該 bit 所在的 byte然後通過位運算將該位置的 bit 設置為 0。
func (b Bitmap) SetFalse(bitPos uint32) {
// 取出對應 byte使用位與和取反運算將對應的 bit 設置為 0
// 假設我們有以下情況:
// • b[1](即 b[bitPos/8])是 10101111十進制 175
// • bitPos = 10也就是我們想清除第 10 位的值。
//
// 操作步驟:
//
// 1. bitPos/8 = 1所以我們要修改 b[1] 這個 byte。
// 2. bitPos % 8 = 2表示我們要清除的位是這個 byte 中的第 3 位(從右數起第 3 位)。
// 3. 1 << (bitPos % 8) = 1 << 2 = 00000100生成位掩碼 00000100。
// 4. 取反:^(1 << 2) = ^00000100 = 11111011這樣的掩碼表示除了第 3 位,其他位都是 1。
// 5. 位與運算10101111 & 11111011 = 10101011結果將第 3 位清除其餘位保持不變。即b[1] 變成了 10101011十進制 171
// &= 是一種 位與運算的複合賦值運算符,表示將左邊的變數與右邊的值進行 位與運算bitwise AND然後將結果賦值給左邊的變數。
b[bitPos/8] &= ^(1 << (bitPos % 8))
}
// IsTrue 檢查指定位置的 bit 是否為 true1
// 參數 bitPos 是要檢查的位的位置(以 0 為基準的位索引)。
// 如果該 bit 是 1則返回 true否則返回 false。
func (b Bitmap) IsTrue(bitPos uint32) bool {
/*
這一行程式碼 b[bitPos/8]&(1<<(bitPos%8)) != 0 是用來檢查 指定位bit 是否為 true1
它的核心是位運算讓我們逐步拆解並解釋這一行程式碼
1. 背景知識
位運算 是在二進制層面操作數字每個 byte 8 個位bit所以位圖結構是以 byte 來表示位的集合
b 是一個 Bitmap 結構也就是 []byte byte 的切片
bitPos 是一個 uint32 類型的變數表示我們想要檢查的位bit在整個位圖中的索引位置
3. 完整流程舉例
假設
b = []byte{0b10101010, 0b01010101} 即二進制表示的兩個 byte分別是 10101010 01010101
bitPos = 10我們要檢查第 10 位是否為 1
操作順序
1. 計算 bitPos/8 = 10/8 = 1所以我們要檢查的是第二個 byte0b01010101
2. 計算 bitPos%8 = 10%8 = 2所以我們要檢查的是該 byte 中的第 3 從右數起第 3
3. 位移1 << 2 = 00000100
4. 位與0b01010101 & 0b00000100 = 0b00000100因為該 byte 的第 3 位是 1結果不等於 0
5. 判斷結果結果不等於 0因此第 10 位是 1true
4. 總結
b[bitPos/8]&(1<<(bitPos%8)) != 0 是一個經典的位操作用來檢查位圖中某一個位是否為 1
bitPos/8 找到對應的 bytebitPos % 8 找到該位在這個 byte 中的具體位置
最後的位與運算和比較用來確定該位的狀態是 true1還是 false0
*/
return b[bitPos/8]&(1<<(bitPos%8)) != 0
}
// Reset 重置 Bitmap使所有的 bit 都設置為 false0
// 這個操作會將整個 Bitmap 的所有 byte 都設置為 0從而達到重置的效果。
func (b Bitmap) Reset() {
// 迭代 Bitmap 中的每個 byte並將其設置為 0
for i := range b {
b[i] = 0
}
}
// ByteSize 返回 Bitmap 的 byte 長度。
// 這個函數返回 Bitmap 目前占用的 byte 數量,該值等於 Bitmap 的長度slice 的長度)。
func (b Bitmap) ByteSize() int {
return len(b)
}
// BitSize 返回 Bitmap 的位bit長度。
// 這個函數通過將 byte 長度乘以 8 來計算 Bitmap 中的總位數。
func (b Bitmap) BitSize() int {
// 每個 byte 包含 8 個 bit因此將 byte 長度乘以 8
return len(b) * 8
}

View File

@ -1,69 +0,0 @@
package usecase
import "testing"
// 基準測試 SetTrue 函數,測試在不同大小的 Bitmap 上設置位元為 true 的效能
func BenchmarkBitmapSetTrue(b *testing.B) {
// 以 10^6 位元作為基準測試的 Bitmap 大小
bitmap := MakeBitmapWithBitSize(1000000)
b.ResetTimer() // 重設計時器,排除初始化的時間
for i := 0; i < b.N; i++ {
bitmap.SetTrue(uint32(i % 1000000)) // 設置位元為 true
}
}
// 基準測試 SetFalse 函數,測試在不同大小的 Bitmap 上清除位元為 false 的效能
func BenchmarkBitmapSetFalse(b *testing.B) {
// 以 10^6 位元作為基準測試的 Bitmap 大小
bitmap := MakeBitmapWithBitSize(1000000)
b.ResetTimer() // 重設計時器,排除初始化的時間
for i := 0; i < b.N; i++ {
bitmap.SetFalse(uint32(i % 1000000)) // 清除位元為 false
}
}
// 基準測試 IsTrue 函數,測試在不同大小的 Bitmap 上檢查位元狀態的效能
func BenchmarkBitmapIsTrue(b *testing.B) {
// 以 10^6 位元作為基準測試的 Bitmap 大小
bitmap := MakeBitmapWithBitSize(1000000)
b.ResetTimer() // 重設計時器,排除初始化的時間
for i := 0; i < b.N; i++ {
_ = bitmap.IsTrue(uint32(i % 1000000)) // 檢查位元是否為 true
}
}
// 基準測試 Reset 函數,測試重置不同大小的 Bitmap 的效能
func BenchmarkBitmapReset(b *testing.B) {
// 以 10^6 位元作為基準測試的 Bitmap 大小
bitmap := MakeBitmapWithBitSize(1000000)
b.ResetTimer() // 重設計時器,排除初始化的時間
for i := 0; i < b.N; i++ {
bitmap.Reset() // 重置 Bitmap
}
}
// 基準測試 BitSize 函數,測試返回位圖的 bit 長度的效能
func BenchmarkBitmapBitSize(b *testing.B) {
// 以 10^6 位元作為基準測試的 Bitmap 大小
bitmap := MakeBitmapWithBitSize(1000000)
b.ResetTimer() // 重設計時器,排除初始化的時間
for i := 0; i < b.N; i++ {
_ = bitmap.BitSize(0) // 測試返回位圖的 bit 長度
}
}
// 基準測試 ByteSize 函數,測試返回位圖的 byte 長度的效能
func BenchmarkBitmapByteSize(b *testing.B) {
// 以 10^6 位元作為基準測試的 Bitmap 大小
bitmap := MakeBitmapWithBitSize(1000000)
b.ResetTimer() // 重設計時器,排除初始化的時間
for i := 0; i < b.N; i++ {
_ = bitmap.ByteSize(0) // 測試返回位圖的 byte 長度
}
}

View File

@ -1,78 +0,0 @@
package usecase
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBitmap_SetTrueAndIsTrue(t *testing.T) {
tests := []struct {
name string
bitPos uint32
expected bool
}{
{"Set bit 0 to true", 0, true},
{"Set bit 1 to true", 1, true},
{"Set bit 63 to true", 63, true},
{"Set bit 64 to true", 64, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bitmap := MakeBitmapWithBitSize(128) // 初始化一個 128 位的 Bitmap
bitmap.SetTrue(tt.bitPos)
result := bitmap.IsTrue(tt.bitPos)
assert.Equal(t, tt.expected, result)
})
}
}
func TestBitmap_SetFalse(t *testing.T) {
tests := []struct {
name string
bitPos uint32
expected bool
}{
{"Set bit 0 to false", 0, false},
{"Set bit 1 to false", 1, false},
{"Set bit 63 to false", 63, false},
{"Set bit 64 to false", 64, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bitmap := MakeBitmapWithBitSize(128) // 初始化一個 128 位的 Bitmap
bitmap.SetTrue(tt.bitPos) // 先設置該 bit 為 true
bitmap.SetFalse(tt.bitPos) // 然後設置該 bit 為 false
result := bitmap.IsTrue(tt.bitPos)
assert.Equal(t, tt.expected, result)
})
}
}
func TestBitmap_Reset(t *testing.T) {
bitmap := MakeBitmapWithBitSize(128) // 初始化一個 128 位的 Bitmap
bitmap.SetTrue(0)
bitmap.SetTrue(64)
// 確認 bit 0 和 bit 64 是 true
assert.True(t, bitmap.IsTrue(0))
assert.True(t, bitmap.IsTrue(64))
bitmap.Reset() // 重置位圖
// 確認所有的位都已經重置為 false
assert.False(t, bitmap.IsTrue(0))
assert.False(t, bitmap.IsTrue(64))
}
func TestBitmap_ByteSize(t *testing.T) {
bitmap := MakeBitmapWithBitSize(64) // 初始化一個 64 位的 Bitmap
assert.Equal(t, 8, bitmap.ByteSize(0)) // 64 位應該佔用 8 個 byte
}
func TestBitmap_BitSize(t *testing.T) {
bitmap := MakeBitmapWithBitSize(128) // 初始化一個 128 位的 Bitmap
assert.Equal(t, 128, bitmap.BitSize(0)) // 128 位應該有 128 個 bit
}

View File

@ -4,10 +4,10 @@ import (
"flag"
"fmt"
"app-cloudep-permission-server/gen_result/pb/permission"
"app-cloudep-permission-server/internal/config"
tokenserviceServer "app-cloudep-permission-server/internal/server/tokenservice"
"app-cloudep-permission-server/internal/svc"
"code.30cm.net/digimon/app-cloudep-permission-server/gen_result/pb/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/config"
tokenserviceServer "code.30cm.net/digimon/app-cloudep-permission-server/internal/server/tokenservice"
"code.30cm.net/digimon/app-cloudep-permission-server/internal/svc"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/service"

View File

@ -0,0 +1,6 @@
package entity
type Ticket struct {
Data interface{} `json:"data"`
Token Token `json:"token"`
}

View File

@ -0,0 +1,56 @@
package entity
import "time"
// 時間都以 UnixNano 來做
// Token 定義
type Token struct {
ID string `json:"id"` // Token 的唯一標識符
UID string `json:"uid"` // UID
DeviceID string `json:"device_id"` // Device
AccessToken string `json:"access_token"` // Access Token
ExpiresIn int64 `json:"expires_in"` // 存取 Token 的有效時間 UnixNano e.g 過期時間的Expire unix nano
AccessCreateAt int64 `json:"access_create_at"` // 存取 Token 的創建時間 timestamp
RefreshToken string `json:"refresh_token"` // 刷新 Token one time token
RefreshExpiresIn int64 `json:"refresh_expires_in"` // 刷新 Token one time token 的有效時間 UnixNano e.g 過期時間的Expire unix nano
RefreshCreateAt int64 `json:"refresh_create_at"` // 刷新 Token one time token 的創建時間
}
// AccessTokenExpires 返回存取 Token 的有效期(以秒為單位)。
func (t *Token) AccessTokenExpires() time.Duration {
return time.Duration(t.ExpiresIn) * time.Second
}
// RefreshTokenExpires 返回刷新 Token 的有效期(以秒為單位)。
func (t *Token) RefreshTokenExpires() time.Duration {
return time.Duration(t.RefreshExpiresIn) * time.Second
}
// RefreshTokenExpiresUnix 返回刷新 Token 的到期時間UnixNano 時間戳)。
func (t *Token) RefreshTokenExpiresUnix() int64 {
return time.Now().Add(t.RefreshTokenExpires()).UnixNano()
}
// IsExpires 檢查存取 Token 是否已過期。如果存取 Token 的創建時間加上其有效期早於當前時間,則返回 true。
func (t *Token) IsExpires() bool {
accessCreateAt := time.Unix(0, t.AccessCreateAt)
return accessCreateAt.Add(t.AccessTokenExpires()).Before(time.Now())
}
// RedisExpiredSec 返回存取 Token 在 Redis 中的剩餘有效時間(秒)。
// 計算方法為:從到期時間的 Unix 時間戳減去當前時間。
func (t *Token) RedisExpiredSec() int64 {
sec := time.Unix(0, t.ExpiresIn).Sub(time.Now().UTC())
return int64(sec.Seconds())
}
// RedisRefreshExpiredSec 返回刷新 Token 在 Redis 中的剩餘有效時間(秒)。
// 計算方法為:從刷新到期時間的 Unix 時間戳減去當前時間。
func (t *Token) RedisRefreshExpiredSec() int64 {
sec := time.Unix(0, t.RefreshExpiresIn).Sub(time.Now().UTC())
return int64(sec.Seconds())
}

View File

@ -0,0 +1,163 @@
package entity
import (
"testing"
"time"
)
// TestAccessTokenExpires 測試 AccessTokenExpires 方法是否能正確將 ExpiresIn 轉換成 time.Duration
func TestAccessTokenExpires(t *testing.T) {
tests := []struct {
name string // 測試案例名稱
expiresIn int64 // 輸入的 ExpiresIn 值,單位為秒
want time.Duration // 預期返回的時間間隔
}{
{"zero expiration", 0, 0},
{"1 second expiration", 1, 1 * time.Second},
{"60 seconds expiration", 60, 60 * time.Second},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 建立一個 Token 實例,僅設置 ExpiresIn 欄位
token := Token{
ExpiresIn: tt.expiresIn,
}
// 呼叫 AccessTokenExpires 方法
got := token.AccessTokenExpires() // ex 1m0s, 1s, 0s etc ....
// 檢查返回值是否與預期相符
if got != tt.want {
t.Errorf("AccessTokenExpires() = %v, want %v", got, tt.want)
}
})
}
}
// TestRefreshTokenExpires 測試 RefreshTokenExpires 方法是否能正確將 RefreshExpiresIn轉換成 time.Duration
func TestRefreshTokenExpires(t *testing.T) {
tests := []struct {
name string
refreshExpiresIn int64
want time.Duration
}{
{"zero", 0, 0},
{"1 second", 1, 1 * time.Second},
{"60 seconds", 60, 60 * time.Second},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
token := Token{
RefreshExpiresIn: tt.refreshExpiresIn,
}
got := token.RefreshTokenExpires()
if got != tt.want {
t.Errorf("RefreshTokenExpires() = %v, want %v", got, tt.want)
}
})
}
}
// TestRefreshTokenExpiresUnix 測試 RefreshTokenExpiresUnix 方法返回的 UnixNano 時間戳是否大致符合預期
func TestRefreshTokenExpiresUnix(t *testing.T) {
// 設定 refreshExpiresIn 為 60 秒
token := Token{
RefreshExpiresIn: 60,
}
// 取得測試開始時的時間
now := time.Now()
// 預期值now + 60 秒
expected := now.Add(60 * time.Second).UnixNano()
got := token.RefreshTokenExpiresUnix()
// 設定允許誤差 50 毫秒
tolerance := int64(50 * time.Millisecond)
if got < expected-tolerance || got > expected+tolerance {
t.Errorf("RefreshTokenExpiresUnix() = %v, want approx %v", got, expected)
}
}
// TestIsExpires 測試 IsExpires 方法判斷 token 是否過期
func TestIsExpires(t *testing.T) {
now := time.Now()
// 測試未過期token 創建時間為現在,過期時長為 10 秒
tokenValid := Token{
AccessCreateAt: now.UnixNano(),
ExpiresIn: 10, // 此處 ExpiresIn 為有效時長(秒)
}
if tokenValid.IsExpires() {
t.Errorf("IsExpires() 返回 true但 token 尚未過期")
}
// 測試已過期token 創建時間為 20 秒前,過期時長為 10 秒
tokenExpired := Token{
AccessCreateAt: now.Add(-20 * time.Second).UnixNano(),
ExpiresIn: 10,
}
if !tokenExpired.IsExpires() {
t.Errorf("IsExpires() 返回 false但 token 已過期")
}
}
// TestRedisExpiredSec 測試 RedisExpiredSec 方法
// 此方法根據 t.ExpiresIn (UnixNano 表示的到期時間) 與當前 UTC 時間計算剩餘秒數
func TestRedisExpiredSec(t *testing.T) {
// 定義測試案例:
// - expireOffset 為從當前時間的偏移量(正數表示未過期,負數表示已過期)
// - minExpected 與 maxExpected 為允許的返回值範圍(因為執行期間會有少量延遲)
tests := []struct {
name string
expireOffset time.Duration // token 過期時間相對於當前的偏移量
minExpected int64 // 預期返回值下限(秒)
maxExpected int64 // 預期返回值上限(秒)
}{
{"Not expired +10s", 10 * time.Second, 9, 10},
{"Expired -5s", -5 * time.Second, -6, -5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
now := time.Now().UTC()
// token 的到期時間設定為當前時間加上偏移量
expTime := now.Add(tt.expireOffset)
token := Token{
ExpiresIn: expTime.UnixNano(),
}
got := token.RedisExpiredSec()
if got < tt.minExpected || got > tt.maxExpected {
t.Errorf("RedisExpiredSec() = %d, want between %d and %d", got, tt.minExpected, tt.maxExpected)
}
})
}
}
// TestRedisRefreshExpiredSec 測試 RedisRefreshExpiredSec 方法
// 此方法根據 t.RefreshExpiresIn (UnixNano 表示的刷新到期時間) 與當前 UTC 時間計算剩餘秒數
func TestRedisRefreshExpiredSec(t *testing.T) {
tests := []struct {
name string
expireOffset time.Duration // 刷新 token 到期時間相對於當前的偏移量
minExpected int64 // 預期返回值下限(秒)
maxExpected int64 // 預期返回值上限(秒)
}{
{"Not expired +20s", 20 * time.Second, 19, 20},
{"Expired -5s", -5 * time.Second, -6, -5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
now := time.Now().UTC()
expTime := now.Add(tt.expireOffset)
token := Token{
RefreshExpiresIn: expTime.UnixNano(),
}
got := token.RedisRefreshExpiredSec()
if got < tt.minExpected || got > tt.maxExpected {
t.Errorf("RedisRefreshExpiredSec() = %d, want between %d and %d", got, tt.minExpected, tt.maxExpected)
}
})
}
}

43
pkg/domain/error.go Normal file
View File

@ -0,0 +1,43 @@
package domain
import (
"fmt"
"strings"
ers "code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/library-go/errs/code"
"github.com/zeromicro/go-zero/core/logx"
)
const (
TokenServerErrorCode = 1 + iota
TokenServerRedisErrorCode
TokenValidateErrorCode
TokenClaimErrorCode
TokenCreateErrorCode
TokenRefreshErrorCode
TokenCancelErrorCode
TokensCancelErrorCode
TokenGetErrorCode
NewOneTokenErrorCode
DelOneTokenErrorCode
SendTooShortErrorCode
SetForgetPasswordRedisErrorCode
FailedToGetCorrectVerifyCode
SendVerifyCodeRedisErrorCode
GenerateVerifyCodeRedisErrorCode
FailedToCheckVerifyCode
AccountPlatformNotCorrectErrorCode
)
func TokenError(ec ers.ErrorCode, s ...string) *ers.LibError {
return ers.NewError(code.CloudEPPermission, code.SigAndPayloadNotMatched, ec.ToUint32(), fmt.Sprintf("token create error: %s", strings.Join(s, " ")))
}
func TokenErrorL(ec ers.ErrorCode,
l logx.Logger, filed []logx.LogField, s ...string) *ers.LibError {
e := TokenError(ec, s...)
l.WithCallerSkip(1).WithFields(filed...).Error(e.Error())
return e
}

View File

@ -2,23 +2,14 @@ package domain
import "strings"
const (
TicketKeyPrefix = "tic/"
)
const (
ClientDataKey = "permission:clients"
)
type RedisKey string
const (
AccessTokenRedisKey RedisKey = "access_token"
RefreshTokenRedisKey RedisKey = "refresh_token"
DeviceTokenRedisKey RedisKey = "device_token"
UIDTokenRedisKey RedisKey = "uid_token"
TicketRedisKey RedisKey = "ticket"
DeviceUIDRedisKey RedisKey = "device_uid"
DeviceTokenRedisKey RedisKey = "device_token"
)
func (key RedisKey) ToString() string {
@ -35,10 +26,18 @@ func GetAccessTokenRedisKey(id string) string {
return AccessTokenRedisKey.With(id).ToString()
}
func GetRefreshTokenRedisKey(id string) string {
return RefreshTokenRedisKey.With(id).ToString()
}
func GetUIDTokenRedisKey(uid string) string {
return UIDTokenRedisKey.With(uid).ToString()
}
func GetDeviceTokenRedisKey(device string) string {
return DeviceTokenRedisKey.With(device).ToString()
}
func GetTicketRedisKey(ticket string) string {
return TicketRedisKey.With(ticket).ToString()
}

View File

@ -0,0 +1,50 @@
package repository
import (
"context"
"time"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
)
// TokenRepo 管理Token
type TokenRepo interface {
Create
Get
Delete
}
type Create interface {
// Create 建立新的 Token
Create(ctx context.Context, token entity.Token) error
// CreateOneTimeToken 建立臨時一次性Token並指定有效期限
CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, et time.Duration) error
}
type Get interface {
// GetAccessTokenByOneTimeToken 根據一次性 Token 獲取對應的存取 Token
GetAccessTokenByOneTimeToken(ctx context.Context, oneTimeToken string) (entity.Token, error)
// GetAccessTokenByID 根據 Token ID 獲取對應的存取 Token
GetAccessTokenByID(ctx context.Context, id string) (entity.Token, error)
// GetAccessTokensByUID 根據用戶 ID 獲取該用戶的所有存取 Token
GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error)
// GetAccessTokenCountByUID 根據用戶 ID 獲取該用戶的存取 Token 數量
GetAccessTokenCountByUID(ctx context.Context, uid string) (int, error)
// GetAccessTokensByDeviceID 根據裝置 ID 獲取該裝置的所有存取 Token
GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error)
// GetAccessTokenCountByDeviceID 根據裝置 ID 獲取該裝置的存取 Token 數量
GetAccessTokenCountByDeviceID(ctx context.Context, deviceID string) (int, error)
}
type Delete interface {
// Delete 刪除指定的 Token
Delete(ctx context.Context, token entity.Token) error
// DeleteAccessTokenByID 根據 Token ID 批量刪除存取 Token
DeleteAccessTokenByID(ctx context.Context, ids []string) error
// DeleteAccessTokensByUID 根據用戶 ID 刪除該用戶的所有存取 Token
DeleteAccessTokensByUID(ctx context.Context, uid string) error
// DeleteAccessTokensByDeviceID 根據裝置 ID 刪除該裝置的所有存取 Token
DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error
// DeleteOneTimeToken 批量刪除一次性 Token
DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error
}

View File

@ -0,0 +1,38 @@
package token
type Additional string
func (a Additional) String() string {
return string(a)
}
const (
ID Additional = "id"
Role Additional = "role"
Device Additional = "device"
UID Additional = "uid"
Account Additional = "account"
Scope Additional = "scope"
Type Additional = "token_type"
)
// 定義一個集合存放所有合法的 Additional Keys
var validAdditionalKeys = map[Additional]struct{}{
ID: {},
Role: {},
Device: {},
UID: {},
Account: {},
Scope: {},
Type: {},
}
// IsValidAdditional 檢查是否是有效的 Additional Key
func IsValidAdditional(key Additional) bool {
_, exists := validAdditionalKeys[key]
return exists
}
const (
Issuer = "permission"
)

View File

@ -0,0 +1,7 @@
package token
type TScope string
func (s *TScope) ToString() string {
return string(*s)
}

11
pkg/domain/token/type.go Normal file
View File

@ -0,0 +1,11 @@
package token
type VerifyType string
func (t *VerifyType) ToString() string {
return string(*t)
}
const (
Bearer VerifyType = "Bearer"
)

View File

@ -0,0 +1,10 @@
package usecase
import "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
// Additional 系統在 Token 當中的附加資訊
type Additional interface {
Set(key token.Additional, val string)
Get(key token.Additional) string
GetAll() map[string]string
}

View File

@ -0,0 +1,92 @@
package usecase
import (
"context"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"github.com/golang-jwt/jwt/v4"
)
type TokenUseCase interface {
ParseClaims
// GenerateAccessToken 產生新的 Access Token
GenerateAccessToken(ctx context.Context, req GenerateTokenRequest) (AccessTokenResponse, error)
// RefreshAccessToken 使用 Refresh Token 更新 Access Token刷新令牌
RefreshAccessToken(ctx context.Context, req RefreshTokenRequest) (RefreshTokenResponse, error)
// RevokeToken 撤銷單個 Token
RevokeToken(ctx context.Context, req TokenRequest) error
// VerifyToken 驗證 Token 是否有效
VerifyToken(ctx context.Context, req TokenRequest) (VerifyTokenResponse, error)
// RevokeTokensByUID 根據 UID 撤銷所有 Token
RevokeTokensByUID(ctx context.Context, req RevokeTokensByUIDRequest) error
// RevokeTokensByDeviceID 根據 Device ID 取消所有相關的 Token
RevokeTokensByDeviceID(ctx context.Context, deviceID string) error
// GetUserTokensByDeviceID 根據 Device ID 獲取所有 AccessToken
GetUserTokensByDeviceID(ctx context.Context, deviceID string) ([]*AccessTokenResponse, error)
// GetUserTokensByUID 根據 UID 獲取所有 AccessToken
GetUserTokensByUID(ctx context.Context, uid string) ([]*AccessTokenResponse, error)
// ReadTokenBasicData 檢查Token 帶的資料
ReadTokenBasicData(ctx context.Context, token string) (Additional, error)
}
type ParseClaims interface {
// CreateAccessToken 建立 access token
CreateAccessToken(token entity.Token, data any, secretKey string) (string, error)
// CreateRefreshToken 建立 RefreshToken
CreateRefreshToken(accessToken string) string
// ParseJWTClaimsByAccessToken 使用Access Token 解析出 JWT 資訊
ParseJWTClaimsByAccessToken(accessToken string, secret string, validate bool) (jwt.MapClaims, error)
// ParseSystemClaimsByAccessToken 使用Access Token 解析出 系統資訊
ParseSystemClaimsByAccessToken(accessToken string, secret string, validate bool) (map[string]string, error)
}
// GenerateTokenRequest 定義授權請求的結構
type GenerateTokenRequest struct {
TokenType string `json:"token_type"` // 告訴前端Token 類型
DeviceID string `json:"device_id"` // 設備 ID
Scope string `json:"scope"` // 授權範圍
Expires int64 `json:"expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
RefreshExpires int64 `json:"refresh_expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
Role string `json:"role"` // 是否為刷新令牌
Account string `json:"account"` // 登入時用的帳號
UID string `json:"uid"` // 使用者在系統中的帳號
Data map[string]string `json:"data"` // 附加數據 -> 不在上面的以後要額外放進來的
}
// AccessTokenResponse 定義訪問令牌響應的結構
type AccessTokenResponse struct {
AccessToken string `json:"access_token"` // 訪問令牌
ExpiresIn int64 `json:"expires_in"` // 過期時間 UnixNano UTC 時間
RefreshToken string `json:"refresh_token"` // 刷新令牌
}
// RefreshTokenRequest 更新 Token 的請求
type RefreshTokenRequest struct {
Token string `json:"token"` // 令牌
Scope string `json:"scope"` // 授權範圍
Expires int64 `json:"expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
RefreshExpires int64 `json:"refresh_expires"` // 指定過期時間 UnixNano UTC 時間(沒給 = now 加設定秒數)
DeviceID string `json:"device_id"` // 設備 ID
}
// RefreshTokenResponse 更新令牌的響應
type RefreshTokenResponse struct {
AccessToken string `json:"token"` // 新的訪問令牌
RefreshToken string `json:"refresh_token"` // 更新令牌
ExpiresIn int64 `json:"expires_in"` // 過期時間(秒)
TokenType string `json:"token_type"` // 令牌類型
}
type TokenRequest struct {
Token string `json:"token"` // 需要註銷的令牌
}
type VerifyTokenResponse struct {
Token entity.Token `json:"token"` // Token 詳情
Data map[string]string `json:"data"` // 附加資料
}
type RevokeTokensByUIDRequest struct {
IDs []string `json:"ids"` // Token ID 列表
UID string `json:"uid"` // 用戶 ID
}

View File

@ -0,0 +1,491 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/domain/repository/token.go
//
// Generated by this command:
//
// mockgen -source=./pkg/domain/repository/token.go -destination=./pkg/mock/repository/token.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
time "time"
entity "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
gomock "go.uber.org/mock/gomock"
)
// MockTokenRepo is a mock of TokenRepo interface.
type MockTokenRepo struct {
ctrl *gomock.Controller
recorder *MockTokenRepoMockRecorder
isgomock struct{}
}
// MockTokenRepoMockRecorder is the mock recorder for MockTokenRepo.
type MockTokenRepoMockRecorder struct {
mock *MockTokenRepo
}
// NewMockTokenRepo creates a new mock instance.
func NewMockTokenRepo(ctrl *gomock.Controller) *MockTokenRepo {
mock := &MockTokenRepo{ctrl: ctrl}
mock.recorder = &MockTokenRepoMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTokenRepo) EXPECT() *MockTokenRepoMockRecorder {
return m.recorder
}
// Create mocks base method.
func (m *MockTokenRepo) Create(ctx context.Context, token entity.Token) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, token)
ret0, _ := ret[0].(error)
return ret0
}
// Create indicates an expected call of Create.
func (mr *MockTokenRepoMockRecorder) Create(ctx, token any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTokenRepo)(nil).Create), ctx, token)
}
// CreateOneTimeToken mocks base method.
func (m *MockTokenRepo) CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, et time.Duration) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOneTimeToken", ctx, key, ticket, et)
ret0, _ := ret[0].(error)
return ret0
}
// CreateOneTimeToken indicates an expected call of CreateOneTimeToken.
func (mr *MockTokenRepoMockRecorder) CreateOneTimeToken(ctx, key, ticket, et any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOneTimeToken", reflect.TypeOf((*MockTokenRepo)(nil).CreateOneTimeToken), ctx, key, ticket, et)
}
// Delete mocks base method.
func (m *MockTokenRepo) Delete(ctx context.Context, token entity.Token) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, token)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockTokenRepoMockRecorder) Delete(ctx, token any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockTokenRepo)(nil).Delete), ctx, token)
}
// DeleteAccessTokenByID mocks base method.
func (m *MockTokenRepo) DeleteAccessTokenByID(ctx context.Context, ids []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAccessTokenByID", ctx, ids)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteAccessTokenByID indicates an expected call of DeleteAccessTokenByID.
func (mr *MockTokenRepoMockRecorder) DeleteAccessTokenByID(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokenByID", reflect.TypeOf((*MockTokenRepo)(nil).DeleteAccessTokenByID), ctx, ids)
}
// DeleteAccessTokensByDeviceID mocks base method.
func (m *MockTokenRepo) DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAccessTokensByDeviceID", ctx, deviceID)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteAccessTokensByDeviceID indicates an expected call of DeleteAccessTokensByDeviceID.
func (mr *MockTokenRepoMockRecorder) DeleteAccessTokensByDeviceID(ctx, deviceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokensByDeviceID", reflect.TypeOf((*MockTokenRepo)(nil).DeleteAccessTokensByDeviceID), ctx, deviceID)
}
// DeleteAccessTokensByUID mocks base method.
func (m *MockTokenRepo) DeleteAccessTokensByUID(ctx context.Context, uid string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAccessTokensByUID", ctx, uid)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteAccessTokensByUID indicates an expected call of DeleteAccessTokensByUID.
func (mr *MockTokenRepoMockRecorder) DeleteAccessTokensByUID(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokensByUID", reflect.TypeOf((*MockTokenRepo)(nil).DeleteAccessTokensByUID), ctx, uid)
}
// DeleteOneTimeToken mocks base method.
func (m *MockTokenRepo) DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteOneTimeToken", ctx, ids, tokens)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteOneTimeToken indicates an expected call of DeleteOneTimeToken.
func (mr *MockTokenRepoMockRecorder) DeleteOneTimeToken(ctx, ids, tokens any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOneTimeToken", reflect.TypeOf((*MockTokenRepo)(nil).DeleteOneTimeToken), ctx, ids, tokens)
}
// GetAccessTokenByID mocks base method.
func (m *MockTokenRepo) GetAccessTokenByID(ctx context.Context, id string) (entity.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokenByID", ctx, id)
ret0, _ := ret[0].(entity.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokenByID indicates an expected call of GetAccessTokenByID.
func (mr *MockTokenRepoMockRecorder) GetAccessTokenByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenByID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokenByID), ctx, id)
}
// GetAccessTokenByOneTimeToken mocks base method.
func (m *MockTokenRepo) GetAccessTokenByOneTimeToken(ctx context.Context, oneTimeToken string) (entity.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokenByOneTimeToken", ctx, oneTimeToken)
ret0, _ := ret[0].(entity.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokenByOneTimeToken indicates an expected call of GetAccessTokenByOneTimeToken.
func (mr *MockTokenRepoMockRecorder) GetAccessTokenByOneTimeToken(ctx, oneTimeToken any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenByOneTimeToken", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokenByOneTimeToken), ctx, oneTimeToken)
}
// GetAccessTokenCountByDeviceID mocks base method.
func (m *MockTokenRepo) GetAccessTokenCountByDeviceID(ctx context.Context, deviceID string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokenCountByDeviceID", ctx, deviceID)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokenCountByDeviceID indicates an expected call of GetAccessTokenCountByDeviceID.
func (mr *MockTokenRepoMockRecorder) GetAccessTokenCountByDeviceID(ctx, deviceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenCountByDeviceID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokenCountByDeviceID), ctx, deviceID)
}
// GetAccessTokenCountByUID mocks base method.
func (m *MockTokenRepo) GetAccessTokenCountByUID(ctx context.Context, uid string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokenCountByUID", ctx, uid)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokenCountByUID indicates an expected call of GetAccessTokenCountByUID.
func (mr *MockTokenRepoMockRecorder) GetAccessTokenCountByUID(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenCountByUID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokenCountByUID), ctx, uid)
}
// GetAccessTokensByDeviceID mocks base method.
func (m *MockTokenRepo) GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokensByDeviceID", ctx, deviceID)
ret0, _ := ret[0].([]entity.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokensByDeviceID indicates an expected call of GetAccessTokensByDeviceID.
func (mr *MockTokenRepoMockRecorder) GetAccessTokensByDeviceID(ctx, deviceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokensByDeviceID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokensByDeviceID), ctx, deviceID)
}
// GetAccessTokensByUID mocks base method.
func (m *MockTokenRepo) GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokensByUID", ctx, uid)
ret0, _ := ret[0].([]entity.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokensByUID indicates an expected call of GetAccessTokensByUID.
func (mr *MockTokenRepoMockRecorder) GetAccessTokensByUID(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokensByUID", reflect.TypeOf((*MockTokenRepo)(nil).GetAccessTokensByUID), ctx, uid)
}
// MockCreate is a mock of Create interface.
type MockCreate struct {
ctrl *gomock.Controller
recorder *MockCreateMockRecorder
isgomock struct{}
}
// MockCreateMockRecorder is the mock recorder for MockCreate.
type MockCreateMockRecorder struct {
mock *MockCreate
}
// NewMockCreate creates a new mock instance.
func NewMockCreate(ctrl *gomock.Controller) *MockCreate {
mock := &MockCreate{ctrl: ctrl}
mock.recorder = &MockCreateMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCreate) EXPECT() *MockCreateMockRecorder {
return m.recorder
}
// Create mocks base method.
func (m *MockCreate) Create(ctx context.Context, token entity.Token) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, token)
ret0, _ := ret[0].(error)
return ret0
}
// Create indicates an expected call of Create.
func (mr *MockCreateMockRecorder) Create(ctx, token any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockCreate)(nil).Create), ctx, token)
}
// CreateOneTimeToken mocks base method.
func (m *MockCreate) CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, et time.Duration) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOneTimeToken", ctx, key, ticket, et)
ret0, _ := ret[0].(error)
return ret0
}
// CreateOneTimeToken indicates an expected call of CreateOneTimeToken.
func (mr *MockCreateMockRecorder) CreateOneTimeToken(ctx, key, ticket, et any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOneTimeToken", reflect.TypeOf((*MockCreate)(nil).CreateOneTimeToken), ctx, key, ticket, et)
}
// MockGet is a mock of Get interface.
type MockGet struct {
ctrl *gomock.Controller
recorder *MockGetMockRecorder
isgomock struct{}
}
// MockGetMockRecorder is the mock recorder for MockGet.
type MockGetMockRecorder struct {
mock *MockGet
}
// NewMockGet creates a new mock instance.
func NewMockGet(ctrl *gomock.Controller) *MockGet {
mock := &MockGet{ctrl: ctrl}
mock.recorder = &MockGetMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGet) EXPECT() *MockGetMockRecorder {
return m.recorder
}
// GetAccessTokenByID mocks base method.
func (m *MockGet) GetAccessTokenByID(ctx context.Context, id string) (entity.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokenByID", ctx, id)
ret0, _ := ret[0].(entity.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokenByID indicates an expected call of GetAccessTokenByID.
func (mr *MockGetMockRecorder) GetAccessTokenByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenByID", reflect.TypeOf((*MockGet)(nil).GetAccessTokenByID), ctx, id)
}
// GetAccessTokenByOneTimeToken mocks base method.
func (m *MockGet) GetAccessTokenByOneTimeToken(ctx context.Context, oneTimeToken string) (entity.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokenByOneTimeToken", ctx, oneTimeToken)
ret0, _ := ret[0].(entity.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokenByOneTimeToken indicates an expected call of GetAccessTokenByOneTimeToken.
func (mr *MockGetMockRecorder) GetAccessTokenByOneTimeToken(ctx, oneTimeToken any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenByOneTimeToken", reflect.TypeOf((*MockGet)(nil).GetAccessTokenByOneTimeToken), ctx, oneTimeToken)
}
// GetAccessTokenCountByDeviceID mocks base method.
func (m *MockGet) GetAccessTokenCountByDeviceID(ctx context.Context, deviceID string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokenCountByDeviceID", ctx, deviceID)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokenCountByDeviceID indicates an expected call of GetAccessTokenCountByDeviceID.
func (mr *MockGetMockRecorder) GetAccessTokenCountByDeviceID(ctx, deviceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenCountByDeviceID", reflect.TypeOf((*MockGet)(nil).GetAccessTokenCountByDeviceID), ctx, deviceID)
}
// GetAccessTokenCountByUID mocks base method.
func (m *MockGet) GetAccessTokenCountByUID(ctx context.Context, uid string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokenCountByUID", ctx, uid)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokenCountByUID indicates an expected call of GetAccessTokenCountByUID.
func (mr *MockGetMockRecorder) GetAccessTokenCountByUID(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokenCountByUID", reflect.TypeOf((*MockGet)(nil).GetAccessTokenCountByUID), ctx, uid)
}
// GetAccessTokensByDeviceID mocks base method.
func (m *MockGet) GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokensByDeviceID", ctx, deviceID)
ret0, _ := ret[0].([]entity.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokensByDeviceID indicates an expected call of GetAccessTokensByDeviceID.
func (mr *MockGetMockRecorder) GetAccessTokensByDeviceID(ctx, deviceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokensByDeviceID", reflect.TypeOf((*MockGet)(nil).GetAccessTokensByDeviceID), ctx, deviceID)
}
// GetAccessTokensByUID mocks base method.
func (m *MockGet) GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccessTokensByUID", ctx, uid)
ret0, _ := ret[0].([]entity.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccessTokensByUID indicates an expected call of GetAccessTokensByUID.
func (mr *MockGetMockRecorder) GetAccessTokensByUID(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccessTokensByUID", reflect.TypeOf((*MockGet)(nil).GetAccessTokensByUID), ctx, uid)
}
// MockDelete is a mock of Delete interface.
type MockDelete struct {
ctrl *gomock.Controller
recorder *MockDeleteMockRecorder
isgomock struct{}
}
// MockDeleteMockRecorder is the mock recorder for MockDelete.
type MockDeleteMockRecorder struct {
mock *MockDelete
}
// NewMockDelete creates a new mock instance.
func NewMockDelete(ctrl *gomock.Controller) *MockDelete {
mock := &MockDelete{ctrl: ctrl}
mock.recorder = &MockDeleteMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDelete) EXPECT() *MockDeleteMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockDelete) Delete(ctx context.Context, token entity.Token) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, token)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockDeleteMockRecorder) Delete(ctx, token any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDelete)(nil).Delete), ctx, token)
}
// DeleteAccessTokenByID mocks base method.
func (m *MockDelete) DeleteAccessTokenByID(ctx context.Context, ids []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAccessTokenByID", ctx, ids)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteAccessTokenByID indicates an expected call of DeleteAccessTokenByID.
func (mr *MockDeleteMockRecorder) DeleteAccessTokenByID(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokenByID", reflect.TypeOf((*MockDelete)(nil).DeleteAccessTokenByID), ctx, ids)
}
// DeleteAccessTokensByDeviceID mocks base method.
func (m *MockDelete) DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAccessTokensByDeviceID", ctx, deviceID)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteAccessTokensByDeviceID indicates an expected call of DeleteAccessTokensByDeviceID.
func (mr *MockDeleteMockRecorder) DeleteAccessTokensByDeviceID(ctx, deviceID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokensByDeviceID", reflect.TypeOf((*MockDelete)(nil).DeleteAccessTokensByDeviceID), ctx, deviceID)
}
// DeleteAccessTokensByUID mocks base method.
func (m *MockDelete) DeleteAccessTokensByUID(ctx context.Context, uid string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteAccessTokensByUID", ctx, uid)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteAccessTokensByUID indicates an expected call of DeleteAccessTokensByUID.
func (mr *MockDeleteMockRecorder) DeleteAccessTokensByUID(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAccessTokensByUID", reflect.TypeOf((*MockDelete)(nil).DeleteAccessTokensByUID), ctx, uid)
}
// DeleteOneTimeToken mocks base method.
func (m *MockDelete) DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteOneTimeToken", ctx, ids, tokens)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteOneTimeToken indicates an expected call of DeleteOneTimeToken.
func (mr *MockDeleteMockRecorder) DeleteOneTimeToken(ctx, ids, tokens any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOneTimeToken", reflect.TypeOf((*MockDelete)(nil).DeleteOneTimeToken), ctx, ids, tokens)
}

332
pkg/repository/token.go Normal file
View File

@ -0,0 +1,332 @@
package repository
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
"github.com/zeromicro/go-zero/core/stores/redis"
)
// TokenRepositoryParam token 需要的參數
type TokenRepositoryParam struct {
Redis *redis.Redis
}
// TokenRepository 用於操作 token 的存儲庫
type TokenRepository struct {
TokenRepositoryParam
}
// NewTokenRepository 初始化並返回一個 TokenRepository 實例
func NewTokenRepository(param TokenRepositoryParam) repository.TokenRepo {
return &TokenRepository{
TokenRepositoryParam: param,
}
}
// ====================== 私有工具函數 =======================
// runPipeline 執行 Redis pipeline 操作
func (repo *TokenRepository) runPipeline(ctx context.Context, pipelineFunc func(tx redis.Pipeliner) error) error {
return repo.Redis.PipelinedCtx(ctx, pipelineFunc)
}
// setToken 使用 Redis pipeline 存儲 token 並設定 TTL
func (repo *TokenRepository) setToken(ctx context.Context, tx redis.Pipeliner, id string, body []byte, ttl time.Duration) error {
return tx.Set(ctx, domain.GetAccessTokenRedisKey(id), body, ttl).Err()
}
// setRefreshToken 若 token 中有 refresh token則存儲之使用 pipeline
func (repo *TokenRepository) setRefreshToken(ctx context.Context, tx redis.Pipeliner, token entity.Token, ttl time.Duration) error {
if token.RefreshToken == "" {
return nil
}
return tx.Set(ctx, domain.GetRefreshTokenRedisKey(token.RefreshToken), token.ID, ttl).Err()
}
// setTokenRelation 在 Redis 中設定 token 與 UID/Device 之間的關聯,並設定過期時間
func (repo *TokenRepository) setTokenRelation(ctx context.Context, tx redis.Pipeliner, uid, deviceID, tokenID string, ttl time.Duration) error {
// 定義需要執行的操作列表
operations := []struct {
key string
op func() error
}{
{
key: domain.GetUIDTokenRedisKey(uid),
op: func() error {
return tx.SAdd(ctx, domain.GetUIDTokenRedisKey(uid), tokenID).Err()
},
},
{
key: domain.GetDeviceTokenRedisKey(deviceID),
op: func() error {
return tx.SAdd(ctx, domain.GetDeviceTokenRedisKey(deviceID), tokenID).Err()
},
},
}
// 執行每個操作,並為對應 key 設置過期時間
for _, operation := range operations {
if err := operation.op(); err != nil {
return fmt.Errorf("failed to create token relaction: %w", err)
}
if err := tx.Expire(ctx, operation.key, ttl).Err(); err != nil {
return fmt.Errorf("failed to set expire: %w", err)
}
}
return nil
}
// retrieveToken 根據指定 key 從 Redis 中獲取 token
func (repo *TokenRepository) retrieveToken(ctx context.Context, key string) (entity.Token, error) {
body, err := repo.Redis.GetCtx(ctx, key)
if err != nil {
return entity.Token{}, err
}
if body == "" {
return entity.Token{}, fmt.Errorf("failed to found token")
}
var token entity.Token
if err := json.Unmarshal([]byte(body), &token); err != nil {
return entity.Token{}, fmt.Errorf("failed to unmarshal token JSON: %w", err)
}
return token, nil
}
// getTokensBySet 根據集合 key 獲取所有 token
func (repo *TokenRepository) getTokensBySet(ctx context.Context, setKey string) ([]entity.Token, error) {
ids, err := repo.Redis.Smembers(setKey)
if err != nil {
if errors.Is(err, redis.Nil) {
return nil, nil
}
return nil, err
}
tokens := make([]entity.Token, 0, len(ids))
var tokensToDelete []string
now := time.Now().UnixNano()
for _, id := range ids {
token, err := repo.retrieveToken(ctx, domain.GetAccessTokenRedisKey(id))
if err != nil {
tokensToDelete = append(tokensToDelete, id)
continue
}
if token.ExpiresIn < now {
tokensToDelete = append(tokensToDelete, id)
continue
}
tokens = append(tokens, token)
}
// 清除過期或錯誤的 token
if len(tokensToDelete) > 0 {
_ = repo.DeleteAccessTokenByID(ctx, tokensToDelete)
}
return tokens, nil
}
// getCountBySet 獲取集合中元素的數量
func (repo *TokenRepository) getCountBySet(ctx context.Context, setKey string) (int, error) {
count, err := repo.Redis.ScardCtx(ctx, setKey)
if err != nil {
return 0, err
}
return int(count), nil
}
// deleteKeysAndRelations 刪除指定的 Redis key 並移除相關關聯UID 與 DeviceID
func (repo *TokenRepository) deleteKeysAndRelations(ctx context.Context, keys []string, uid, deviceID, tokenID string) error {
err := repo.Redis.Pipelined(func(tx redis.Pipeliner) error {
// 移除 UID 與 DeviceID 關聯中的 tokenID
_ = tx.SRem(ctx, domain.GetUIDTokenRedisKey(uid), tokenID)
_ = tx.SRem(ctx, domain.GetDeviceTokenRedisKey(deviceID), tokenID)
// 刪除所有指定的 keys
for _, key := range keys {
_ = tx.Del(ctx, key)
}
return nil
})
return err
}
// batchDeleteKeys 批量刪除 Redis keys
func (repo *TokenRepository) batchDeleteKeys(ctx context.Context, keys ...string) error {
return repo.Redis.Pipelined(func(tx redis.Pipeliner) error {
for _, key := range keys {
if err := tx.Del(ctx, key).Err(); err != nil {
return err
}
}
return nil
})
}
// ====================== 公開方法 =======================
// Create 創建新的 token
func (repo *TokenRepository) Create(ctx context.Context, token entity.Token) error {
binToken, err := json.Marshal(token)
if err != nil {
return err
}
// 根據 token 設定 refresh 過期秒數計算 TTL
refreshTTL := time.Duration(token.RedisRefreshExpiredSec()) * time.Second
return repo.runPipeline(ctx, func(tx redis.Pipeliner) error {
if err := repo.setToken(ctx, tx, token.ID, binToken, refreshTTL); err != nil {
return err
}
if err := repo.setRefreshToken(ctx, tx, token, refreshTTL); err != nil {
return err
}
if err := repo.setTokenRelation(ctx, tx, token.UID, token.DeviceID, token.ID, refreshTTL); err != nil {
return err
}
return nil
})
}
// CreateOneTimeToken 創建一次性 token
func (repo *TokenRepository) CreateOneTimeToken(ctx context.Context, key string, ticket entity.Ticket, dt time.Duration) error {
body, err := json.Marshal(ticket)
if err != nil {
return err
}
_, err = repo.Redis.SetnxExCtx(ctx, domain.GetRefreshTokenRedisKey(key), string(body), int(dt.Seconds()))
return err
}
// GetAccessTokenByOneTimeToken 根據一次性 token 獲取 access token
func (repo *TokenRepository) GetAccessTokenByOneTimeToken(ctx context.Context, oneTimeToken string) (entity.Token, error) {
return repo.retrieveToken(ctx, domain.GetRefreshTokenRedisKey(oneTimeToken))
}
// GetAccessTokenByID 根據 token ID 獲取 access token
func (repo *TokenRepository) GetAccessTokenByID(ctx context.Context, id string) (entity.Token, error) {
return repo.retrieveToken(ctx, domain.GetAccessTokenRedisKey(id))
}
// GetAccessTokensByUID 根據 UID 獲取所有 access tokens
func (repo *TokenRepository) GetAccessTokensByUID(ctx context.Context, uid string) ([]entity.Token, error) {
return repo.getTokensBySet(ctx, domain.GetUIDTokenRedisKey(uid))
}
// GetAccessTokenCountByUID 根據 UID 獲取 access token 的數量
func (repo *TokenRepository) GetAccessTokenCountByUID(ctx context.Context, uid string) (int, error) {
return repo.getCountBySet(ctx, domain.GetUIDTokenRedisKey(uid))
}
// GetAccessTokensByDeviceID 根據 DeviceID 獲取所有 access tokens
func (repo *TokenRepository) GetAccessTokensByDeviceID(ctx context.Context, deviceID string) ([]entity.Token, error) {
return repo.getTokensBySet(ctx, domain.GetDeviceTokenRedisKey(deviceID))
}
// GetAccessTokenCountByDeviceID 根據 DeviceID 獲取 access token 的數量
func (repo *TokenRepository) GetAccessTokenCountByDeviceID(ctx context.Context, deviceID string) (int, error) {
return repo.getCountBySet(ctx, domain.GetDeviceTokenRedisKey(deviceID))
}
// Delete 刪除指定的 token
func (repo *TokenRepository) Delete(ctx context.Context, token entity.Token) error {
keys := []string{
domain.GetAccessTokenRedisKey(token.ID),
domain.GetRefreshTokenRedisKey(token.RefreshToken),
}
return repo.deleteKeysAndRelations(ctx, keys, token.UID, token.DeviceID, token.ID)
}
// DeleteAccessTokenByID 根據 token ID 刪除 access token
func (repo *TokenRepository) DeleteAccessTokenByID(ctx context.Context, ids []string) error {
for _, tokenID := range ids {
token, err := repo.GetAccessTokenByID(ctx, tokenID)
if err != nil {
continue
}
keys := []string{
domain.GetAccessTokenRedisKey(token.ID),
domain.GetRefreshTokenRedisKey(token.RefreshToken),
}
_ = repo.deleteKeysAndRelations(ctx, keys, token.UID, token.DeviceID, token.ID)
}
return nil
}
// DeleteAccessTokensByUID 根據 UID 刪除所有 access tokens
func (repo *TokenRepository) DeleteAccessTokensByUID(ctx context.Context, uid string) error {
tokens, err := repo.GetAccessTokensByUID(ctx, uid)
if err != nil {
return err
}
for _, token := range tokens {
if err := repo.Delete(ctx, token); err != nil {
return err
}
}
return nil
}
// DeleteAccessTokensByDeviceID 根據 DeviceID 刪除所有 access tokens
func (repo *TokenRepository) DeleteAccessTokensByDeviceID(ctx context.Context, deviceID string) error {
tokens, err := repo.GetAccessTokensByDeviceID(ctx, deviceID)
if err != nil {
return err
}
// 預分配 keys每個 token 包含兩個 key
keys := make([]string, 0, len(tokens)*2)
for _, token := range tokens {
keys = append(keys, domain.GetAccessTokenRedisKey(token.ID))
keys = append(keys, domain.GetRefreshTokenRedisKey(token.RefreshToken))
}
// 移除 UID 關聯中的 tokenID
if err := repo.runPipeline(ctx, func(tx redis.Pipeliner) error {
for _, token := range tokens {
_ = tx.SRem(ctx, domain.GetUIDTokenRedisKey(token.UID), token.ID)
}
return nil
}); err != nil {
return err
}
if err := repo.batchDeleteKeys(ctx, keys...); err != nil {
return err
}
_, err = repo.Redis.Del(domain.GetDeviceTokenRedisKey(deviceID))
return err
}
// DeleteOneTimeToken 刪除一次性 token支持多個 key 一併刪除)
func (repo *TokenRepository) DeleteOneTimeToken(ctx context.Context, ids []string, tokens []entity.Token) error {
totalKeys := len(ids) + len(tokens)
keys := make([]string, 0, totalKeys)
for _, id := range ids {
keys = append(keys, domain.GetRefreshTokenRedisKey(id))
}
for _, token := range tokens {
keys = append(keys, domain.GetRefreshTokenRedisKey(token.RefreshToken))
}
return repo.batchDeleteKeys(ctx, keys...)
}

1750
pkg/repository/token_test.go Normal file

File diff suppressed because it is too large Load Diff

35
pkg/usecase/additional.go Normal file
View File

@ -0,0 +1,35 @@
package usecase
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
)
// additional 實作 TokenClaims 介面
type additional struct {
additional map[string]string
}
func (use *additional) GetAll() map[string]string {
return use.additional
}
func (use *additional) Set(key token.Additional, val string) {
use.additional[key.String()] = val
}
func (use *additional) Get(additional token.Additional) string {
value, ok := use.additional[additional.String()]
if !ok {
return ""
}
return value
}
// NewAdditional 創建一個新的 tokenClaims 實例
func NewAdditional(data map[string]string) usecase.Additional {
return &additional{
additional: data,
}
}

View File

@ -0,0 +1,64 @@
package usecase
import (
"testing"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
"github.com/stretchr/testify/assert"
)
func TestAdditional_SetAndGet(t *testing.T) {
// 初始化 additional
additional := NewAdditional(map[string]string{})
// 測試 Set() 只允許有效 Key
validCases := map[token.Additional]string{
token.ID: "12345",
token.Role: "admin",
token.Device: "device-001",
token.UID: "user-999",
token.Account: "test@example.com",
token.Scope: "read:write",
}
// 測試有效 Key
for key, val := range validCases {
additional.Set(key, val)
assert.Equal(t, val, additional.Get(key), "Set/Get for key: "+key.String())
}
// 測試 key 未設定時應回傳空字串
assert.Equal(t, "", additional.Get("non-existent-key"))
}
func TestIsValidAdditional(t *testing.T) {
// 測試合法的 keys
assert.True(t, token.IsValidAdditional(token.ID))
assert.True(t, token.IsValidAdditional(token.Role))
assert.True(t, token.IsValidAdditional(token.Device))
assert.True(t, token.IsValidAdditional(token.UID))
assert.True(t, token.IsValidAdditional(token.Account))
assert.True(t, token.IsValidAdditional(token.Scope))
// 測試不合法的 keys
assert.False(t, token.IsValidAdditional(token.Additional("unknown")))
assert.False(t, token.IsValidAdditional(token.Additional("random")))
assert.False(t, token.IsValidAdditional(token.Additional("invalid-key")))
}
func TestIGetAll(t *testing.T) {
validCases := map[string]string{
token.ID.String(): "12345",
token.Role.String(): "admin",
token.Device.String(): "device-001",
token.UID.String(): "user-999",
token.Account.String(): "test@example.com",
token.Scope.String(): "read:write",
}
a := NewAdditional(validCases)
result := a.GetAll()
assert.Equal(t, validCases, result)
}

527
pkg/usecase/token.go Normal file
View File

@ -0,0 +1,527 @@
package usecase
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
dt "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
ers "code.30cm.net/digimon/library-go/errs"
"github.com/golang-jwt/jwt/v4"
"github.com/segmentio/ksuid"
"github.com/zeromicro/go-zero/core/logx"
)
type TokenUseCaseParam struct {
TokenRepo repository.TokenRepo
RefreshExpires time.Duration
Expired time.Duration
Secret string
}
type TokenUseCase struct {
TokenUseCaseParam
Token struct {
RefreshExpires time.Duration
Expired time.Duration
Secret string
}
}
func NewTokenUseCase(param TokenUseCaseParam) usecase.TokenUseCase {
return &TokenUseCase{
TokenUseCaseParam: param,
Token: struct {
RefreshExpires time.Duration
Expired time.Duration
Secret string
}{
RefreshExpires: param.RefreshExpires,
Expired: param.Expired,
Secret: param.Secret,
},
}
}
func (use *TokenUseCase) GenerateAccessToken(ctx context.Context, req usecase.GenerateTokenRequest) (usecase.AccessTokenResponse, error) {
token, err := use.newToken(ctx, &req)
if err != nil {
return usecase.AccessTokenResponse{}, err
}
err = use.TokenRepo.Create(ctx, *token)
if err != nil {
// 錯誤代碼
e := domain.TokenErrorL(
domain.TokenCreateErrorCode,
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: req},
{Key: "func", Value: "TokenRepo.Create"},
{Key: "err", Value: err.Error()},
},
"failed to create token").Wrap(err)
return usecase.AccessTokenResponse{}, e
}
return usecase.AccessTokenResponse{
AccessToken: token.AccessToken,
ExpiresIn: token.ExpiresIn,
RefreshToken: token.RefreshToken,
}, nil
}
func (use *TokenUseCase) RefreshAccessToken(ctx context.Context, req usecase.RefreshTokenRequest) (usecase.RefreshTokenResponse, error) {
// Step 1: 檢查 refresh token
token, err := use.TokenRepo.GetAccessTokenByOneTimeToken(ctx, req.Token)
if err != nil {
return usecase.RefreshTokenResponse{},
use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo.GetAccessTokenByOneTimeToken",
req: req,
err: err,
message: "failed to get access token",
errorCode: domain.TokenRefreshErrorCode,
})
}
// Step 2: 提取 Claims Data
claimsData, err := use.ParseSystemClaimsByAccessToken(token.AccessToken, use.Token.Secret, false)
if err != nil {
return usecase.RefreshTokenResponse{},
use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "extractClaims",
req: req,
err: err,
message: "failed to extract claims",
errorCode: domain.TokenRefreshErrorCode,
})
}
data := NewAdditional(claimsData)
data.Set(dt.Scope, req.Scope)
data.Set(dt.Device, req.DeviceID)
// Step 3: 創建新 token
newToken, err := use.newToken(ctx, &usecase.GenerateTokenRequest{
Scope: req.Scope,
DeviceID: req.DeviceID,
Expires: req.Expires,
RefreshExpires: req.RefreshExpires,
Data: data.GetAll(),
Role: data.Get(dt.Role),
UID: data.Get(dt.UID),
Account: data.Get(dt.Account),
})
if err != nil {
return usecase.RefreshTokenResponse{},
use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "use.newToken",
req: req,
err: err,
message: "failed to create new token",
errorCode: domain.TokenRefreshErrorCode,
})
}
if err := use.TokenRepo.Create(ctx, *newToken); err != nil {
return usecase.RefreshTokenResponse{},
use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo.Create",
req: req,
err: err,
message: "failed to create new token",
errorCode: domain.TokenRefreshErrorCode,
})
}
// Step 4: 刪除舊 token 並創建新 token
if err := use.TokenRepo.Delete(ctx, token); err != nil {
return usecase.RefreshTokenResponse{},
use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo.Delete",
req: req,
err: err,
message: "failed to delete old token",
errorCode: domain.TokenRefreshErrorCode,
})
}
// 返回新的 Token 響應
return usecase.RefreshTokenResponse{
AccessToken: newToken.AccessToken,
RefreshToken: newToken.RefreshToken,
ExpiresIn: newToken.ExpiresIn,
TokenType: data.Get(dt.Type),
}, nil
}
func (use *TokenUseCase) RevokeToken(ctx context.Context, req usecase.TokenRequest) error {
claims, err := use.ParseSystemClaimsByAccessToken(req.Token, use.Token.Secret, false)
if err != nil {
return use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "CancelToken extractClaims",
req: req,
err: err,
message: "failed to get token claims",
errorCode: domain.TokenCancelErrorCode,
})
}
data := NewAdditional(claims)
token, err := use.TokenRepo.GetAccessTokenByID(ctx, data.Get(dt.ID))
if err != nil {
return use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo GetAccessTokenByID",
req: req,
err: err,
message: fmt.Sprintf("failed to get token claims :%s", data.Get(dt.ID)),
errorCode: domain.TokenCancelErrorCode,
})
}
err = use.TokenRepo.Delete(ctx, token)
if err != nil {
return use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo Delete",
req: req,
err: err,
message: fmt.Sprintf("failed to delete token :%s", token.ID),
errorCode: domain.TokenCancelErrorCode,
})
}
return nil
}
func (use *TokenUseCase) VerifyToken(ctx context.Context, req usecase.TokenRequest) (usecase.VerifyTokenResponse, error) {
claims, err := use.ParseSystemClaimsByAccessToken(req.Token, use.Token.Secret, true)
if err != nil {
return usecase.VerifyTokenResponse{},
use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "parseClaims",
req: req,
err: err,
message: "validate token claims error",
errorCode: domain.TokenValidateErrorCode,
})
}
data := NewAdditional(claims)
token, err := use.TokenRepo.GetAccessTokenByID(ctx, data.Get(dt.ID))
if err != nil {
return usecase.VerifyTokenResponse{},
use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo.GetAccessTokenByID",
req: req,
err: err,
message: fmt.Sprintf("failed to get token :%s", data.Get(dt.ID)),
errorCode: domain.TokenValidateErrorCode,
})
}
return usecase.VerifyTokenResponse{
Token: token,
Data: data.GetAll(),
}, nil
}
func (use *TokenUseCase) RevokeTokensByUID(ctx context.Context, req usecase.RevokeTokensByUIDRequest) error {
if req.UID != "" {
err := use.TokenRepo.DeleteAccessTokensByUID(ctx, req.UID)
if err != nil {
return use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo.DeleteAccessTokensByUID",
req: req,
err: err,
message: "failed to cancel tokens by uid",
errorCode: domain.TokensCancelErrorCode,
})
}
}
if len(req.IDs) > 0 {
err := use.TokenRepo.DeleteAccessTokenByID(ctx, req.IDs)
if err != nil {
return use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo.DeleteAccessTokenByID",
req: req,
err: err,
message: "failed to cancel tokens by token ids",
errorCode: domain.TokensCancelErrorCode,
})
}
}
return nil
}
func (use *TokenUseCase) RevokeTokensByDeviceID(ctx context.Context, deviceID string) error {
err := use.TokenRepo.DeleteAccessTokensByDeviceID(ctx, deviceID)
if err != nil {
return use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo.DeleteAccessTokensByDeviceID",
req: deviceID,
err: err,
message: "failed to cancel token by device id",
errorCode: domain.TokensCancelErrorCode,
})
}
return nil
}
func (use *TokenUseCase) GetUserTokensByDeviceID(ctx context.Context, deviceID string) ([]*usecase.AccessTokenResponse, error) {
tokens, err := use.TokenRepo.GetAccessTokensByDeviceID(ctx, deviceID)
if err != nil {
return nil, use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo.GetAccessTokensByDeviceID",
req: deviceID,
err: err,
message: "failed to get token by device id",
errorCode: domain.TokenGetErrorCode,
})
}
result := make([]*usecase.AccessTokenResponse, 0, len(tokens))
for _, v := range tokens {
result = append(result, &usecase.AccessTokenResponse{
AccessToken: v.AccessToken,
ExpiresIn: v.ExpiresIn,
RefreshToken: v.RefreshToken,
})
}
return result, nil
}
func (use *TokenUseCase) GetUserTokensByUID(ctx context.Context, uid string) ([]*usecase.AccessTokenResponse, error) {
tokens, err := use.TokenRepo.GetAccessTokensByUID(ctx, uid)
if err != nil {
return nil, use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "TokenRepo.GetAccessTokensByUID",
req: uid,
err: err,
message: "failed to get token by uid",
errorCode: domain.TokenGetErrorCode,
})
}
result := make([]*usecase.AccessTokenResponse, 0, len(tokens))
for _, v := range tokens {
result = append(result, &usecase.AccessTokenResponse{
AccessToken: v.AccessToken,
ExpiresIn: v.ExpiresIn,
RefreshToken: v.RefreshToken,
})
}
return result, nil
}
func (use *TokenUseCase) ReadTokenBasicData(ctx context.Context, token string) (usecase.Additional, error) {
claims, err := use.ParseSystemClaimsByAccessToken(token, use.Token.Secret, false)
if err != nil {
return nil,
use.wrapTokenError(ctx, wrapTokenErrorReq{
funcName: "parseClaims",
req: token,
err: err,
message: "validate token claims error",
errorCode: domain.TokenValidateErrorCode,
})
}
return NewAdditional(claims), nil
}
// ======== JWT Token ========
// CreateAccessToken 會將基本 token 以及想要加入Token Claims 的Data 依照 secret key 加密之後變成 jwt access token
func (use *TokenUseCase) CreateAccessToken(token entity.Token, data any, secretKey string) (string, error) {
claims := entity.Claims{
Data: data,
RegisteredClaims: jwt.RegisteredClaims{
ID: token.ID,
ExpiresAt: jwt.NewNumericDate(time.Unix(0, token.ExpiresIn)),
Issuer: dt.Issuer,
},
}
accessToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).
SignedString([]byte(secretKey))
if err != nil {
return "", err
}
return accessToken, nil
}
func (use *TokenUseCase) CreateRefreshToken(accessToken string) string {
hash := sha256.New()
_, _ = hash.Write([]byte(accessToken))
return hex.EncodeToString(hash.Sum(nil))
}
func (use *TokenUseCase) ParseJWTClaimsByAccessToken(accessToken string, secret string, validate bool) (jwt.MapClaims, error) {
// 跳過驗證的解析
var token *jwt.Token
var err error
if validate {
token, err = jwt.Parse(accessToken, func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("token unexpected signing method: %v", token.Header["alg"])
}
return []byte(secret), nil
})
if err != nil {
return jwt.MapClaims{}, err
}
} else {
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
token, err = parser.Parse(accessToken, func(_ *jwt.Token) (any, error) {
return []byte(secret), nil
})
if err != nil {
return jwt.MapClaims{}, err
}
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok && token.Valid {
return jwt.MapClaims{}, fmt.Errorf("token valid error")
}
return claims, nil
}
func (use *TokenUseCase) ParseSystemClaimsByAccessToken(accessToken string, secret string, validate bool) (map[string]string, error) {
claimMap, err := use.ParseJWTClaimsByAccessToken(accessToken, secret, validate)
if err != nil {
return map[string]string{}, err
}
claimsData, ok := claimMap["data"].(map[string]any)
if ok {
return convertMap(claimsData), nil
}
return map[string]string{}, fmt.Errorf("get data from claim map error")
}
// ======== 工具 ========
func (use *TokenUseCase) newToken(ctx context.Context, req *usecase.GenerateTokenRequest) (*entity.Token, error) {
// 準備建立 Token 所需
now := time.Now().UTC()
expires := req.Expires
refreshExpires := req.RefreshExpires
if expires <= 0 {
// 將時間加上 n 秒 -> 系統內預設
sec := time.Duration(use.Token.Expired.Seconds()) * time.Second
// 獲取 Unix 時間戳
expires = now.Add(sec).UnixNano()
}
// Refresh Token 過期時間要比普通的Token 長
if req.RefreshExpires <= 0 {
// 獲取 Unix 時間戳
refresh := time.Duration(use.Token.RefreshExpires.Seconds()) * time.Second
refreshExpires = now.Add(refresh).UnixNano()
}
token := entity.Token{
ID: ksuid.New().String(),
DeviceID: req.DeviceID,
ExpiresIn: expires,
RefreshExpiresIn: refreshExpires,
AccessCreateAt: now.UnixNano(),
RefreshCreateAt: now.UnixNano(),
UID: req.UID,
}
// 故意 data 裡面不會有那些已經有的欄位資訊
data := NewAdditional(req.Data)
data.Set(dt.ID, token.ID)
data.Set(dt.Role, req.Role)
data.Set(dt.Scope, req.Scope)
data.Set(dt.Account, req.Account)
data.Set(dt.UID, req.UID)
data.Set(dt.Type, req.TokenType)
if req.DeviceID != "" {
data.Set(dt.Device, req.DeviceID)
}
var err error
token.AccessToken, err = use.CreateAccessToken(token, data.GetAll(), use.Token.Secret)
token.RefreshToken = use.CreateRefreshToken(token.AccessToken)
if err != nil {
// 錯誤代碼 20-201-02
e := domain.TokenErrorL(
domain.TokenClaimErrorCode,
logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: req},
{Key: "func", Value: "accessTokenGenerator"},
{Key: "err", Value: err.Error()},
},
"failed to generator access token").Wrap(err)
return nil, e
}
return &token, nil
}
func convertMap(input map[string]any) map[string]string {
output := make(map[string]string)
for key, value := range input {
switch v := value.(type) {
case string:
output[key] = v
case fmt.Stringer:
output[key] = v.String()
default:
output[key] = fmt.Sprintf("%v", value)
}
}
return output
}
type wrapTokenErrorReq struct {
funcName string
req any
err error
message string
errorCode ers.ErrorCode
}
// wrapTokenError 將錯誤訊息封裝到 domain.TokenErrorL 中
func (use *TokenUseCase) wrapTokenError(ctx context.Context, param wrapTokenErrorReq) error {
logFields := []logx.LogField{
{Key: "req", Value: param.req},
{Key: "func", Value: param.funcName},
{Key: "err", Value: param.err.Error()},
}
wrappedErr := domain.TokenErrorL(
param.errorCode,
logx.WithContext(ctx),
logFields,
param.message,
).Wrap(param.err)
return wrappedErr
}

611
pkg/usecase/token_test.go Normal file
View File

@ -0,0 +1,611 @@
package usecase
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
mock "code.30cm.net/digimon/app-cloudep-permission-server/pkg/mock/repository"
"context"
"fmt"
"github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
"time"
)
// TestTokenUseCase_CreateAccessToken_TableDriven 透過 table-driven 方式測試 CreateAccessToken
func TestTokenUseCase_CreateAccessToken_TableDriven(t *testing.T) {
// 固定發行者設定,測試中會用來驗證 claims.Issuer
now := time.Now()
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
uc := NewTokenUseCase(TokenUseCaseParam{
TokenRepo: mockAutoIDModel,
RefreshExpires: 2 * time.Minute,
Expired: 2 * time.Minute,
Secret: "gg88g88",
})
tests := []struct {
name string
token entity.Token
data any
secretKey string
wantErr bool
verifyClaims func(t *testing.T, claims *entity.Claims, expectedExpiry time.Time)
}{
{
name: "ok",
token: entity.Token{
ID: "token1",
ExpiresIn: now.Add(1 * time.Hour).UnixNano(),
},
data: map[string]interface{}{"foo": "bar"},
secretKey: "secret123",
wantErr: false,
verifyClaims: func(t *testing.T, claims *entity.Claims, expectedExpiry time.Time) {
assert.Equal(t, "token1", claims.ID)
assert.True(t, claims.ExpiresAt.Time.Before(expectedExpiry),
"expected expiry %v, got %v", expectedExpiry, claims.ExpiresAt.Time)
dataMap, ok := claims.Data.(map[string]interface{})
assert.True(t, ok, "claims.Data 應為 map[string]interface{}")
assert.Equal(t, "bar", dataMap["foo"])
},
},
{
name: "valid token with string data",
token: entity.Token{
ID: "token2",
ExpiresIn: now.Add(2 * time.Hour).UnixNano(),
},
data: map[string]interface{}{"foo": "bar"},
secretKey: "anotherSecret",
wantErr: false,
verifyClaims: func(t *testing.T, claims *entity.Claims, expectedExpiry time.Time) {
assert.Equal(t, "token2", claims.ID)
assert.True(t, claims.ExpiresAt.Time.Before(expectedExpiry))
assert.Equal(t, map[string]interface{}{"foo": "bar"}, claims.Data)
},
},
{
name: "empty secret key",
token: entity.Token{
ID: "token3",
ExpiresIn: now.Add(30 * time.Minute).UnixNano(),
},
data: map[string]interface{}{"key": "value"},
secretKey: "",
wantErr: false,
verifyClaims: func(t *testing.T, claims *entity.Claims, expectedExpiry time.Time) {
assert.Equal(t, "token3", claims.ID)
assert.True(t, claims.ExpiresAt.Time.Before(expectedExpiry))
dataMap, ok := claims.Data.(map[string]interface{})
assert.True(t, ok, "claims.Data 應為 map[string]interface{}")
assert.Equal(t, "value", dataMap["key"])
},
},
// 如有需要,可加入更多測試案例,例如模擬簽名錯誤等情境
}
for _, tt := range tests {
tt := tt // 捕捉範圍變數
t.Run(tt.name, func(t *testing.T) {
jwtStr, err := uc.CreateAccessToken(tt.token, tt.data, tt.secretKey)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
// 解析 JWT
parsedToken, err := jwt.ParseWithClaims(jwtStr, &entity.Claims{}, func(token *jwt.Token) (interface{}, error) {
// 驗證簽名方法是否為 HMAC
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(tt.secretKey), nil
})
assert.NoError(t, err)
assert.True(t, parsedToken.Valid, "解析後的 JWT 應該有效")
claims, ok := parsedToken.Claims.(*entity.Claims)
assert.True(t, ok, "claims 型別錯誤,預期 *entity.Claims, got %T", parsedToken.Claims)
// 根據 token.ExpiresIn 計算預期的過期時間
expectedExpiry := time.Unix(0, tt.token.ExpiresIn)
// 呼叫 verifyClaims 驗證其它 Claim 資料
tt.verifyClaims(t, claims, expectedExpiry)
})
}
}
func TestTokenUseCase_CreateRefreshToken(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
uc := NewTokenUseCase(TokenUseCaseParam{
TokenRepo: mockAutoIDModel,
RefreshExpires: 2 * time.Minute,
Expired: 2 * time.Minute,
Secret: "gg88g88",
})
tests := []struct {
name string
accessToken string
expected string
}{
{
name: "empty access token",
accessToken: "",
// SHA256("") 的 hex 編碼結果
expected: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
{
name: "normal access token",
accessToken: "access-token",
expected: "3f16bed7089f4653e5ef21bfd2824d7f3aaaecc7a598e7e89c580e1606a9cc52",
},
}
for _, tt := range tests {
tt := tt // 捕捉變數
t.Run(tt.name, func(t *testing.T) {
result := uc.CreateRefreshToken(tt.accessToken)
assert.Equal(t, tt.expected, result)
})
}
}
// TestTokenUseCase_ParseJWTClaimsByAccessToken 使用 table-driven 方式測試 ParseJWTClaimsByAccessToken
func TestTokenUseCase_ParseJWTClaimsByAccessToken(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
uc := NewTokenUseCase(TokenUseCaseParam{
TokenRepo: mockAutoIDModel,
RefreshExpires: 2 * time.Minute,
Expired: 2 * time.Minute,
Secret: "gg88g88",
})
// 定義測試案例的結構
tests := []struct {
name string
// tokenGen 用來動態產生要解析的 access token
tokenGen func(t *testing.T) string
secret string
validate bool
wantClaims jwt.MapClaims
wantErr bool
errContains string
}{
{
name: "valid token with validation",
tokenGen: func(t *testing.T) string {
claims := jwt.MapClaims{
"sub": "123",
"role": "admin",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte("testsecret"))
if err != nil {
t.Fatalf("failed to sign token: %v", err)
}
return tokenString
},
secret: "testsecret",
validate: true,
wantClaims: jwt.MapClaims{
"sub": "123",
"role": "admin",
},
wantErr: false,
},
{
name: "valid token without validation",
tokenGen: func(t *testing.T) string {
claims := jwt.MapClaims{
"sub": "123",
"role": "admin",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte("testsecret"))
if err != nil {
t.Fatalf("failed to sign token: %v", err)
}
return tokenString
},
secret: "testsecret",
validate: false,
wantClaims: jwt.MapClaims{
"sub": "123",
"role": "admin",
},
wantErr: false,
},
{
name: "invalid secret",
tokenGen: func(t *testing.T) string {
claims := jwt.MapClaims{
"sub": "123",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte("testsecret"))
if err != nil {
t.Fatalf("failed to sign token: %v", err)
}
return tokenString
},
secret: "wrongsecret",
validate: true,
wantErr: true,
errContains: "signature", // 預期錯誤訊息中包含 "signature"
},
{
name: "unexpected signing method",
tokenGen: func(t *testing.T) string {
claims := jwt.MapClaims{
"sub": "456",
}
// 使用 SigningMethodNone 產生 token
token := jwt.NewWithClaims(jwt.SigningMethodNone, claims)
// 針對 None 演算法SignedString 需要使用 jwt.UnsafeAllowNoneSignatureType
tokenString, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
if err != nil {
t.Fatalf("failed to sign token: %v", err)
}
return tokenString
},
secret: "testsecret",
validate: true,
wantErr: true,
errContains: "unexpected signing method",
},
{
name: "malformed token",
tokenGen: func(t *testing.T) string {
return "not-a-token"
},
secret: "testsecret",
validate: true,
wantErr: true,
errContains: "token contains an invalid number of segments",
},
}
// 針對每個測試案例執行測試
for _, tt := range tests {
tt := tt // 捕捉迴圈變數
t.Run(tt.name, func(t *testing.T) {
// 產生 access token
accessToken := tt.tokenGen(t)
claims, err := uc.ParseJWTClaimsByAccessToken(accessToken, tt.secret, tt.validate)
if tt.wantErr {
assert.Error(t, err)
if err != nil && tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}
assert.NoError(t, err)
// 驗證解析出來的 claims 是否符合預期
assert.Equal(t, tt.wantClaims, claims)
})
}
}
func TestTokenUseCase_ParseSystemClaimsByAccessToken(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
uc := NewTokenUseCase(TokenUseCaseParam{
TokenRepo: mockAutoIDModel,
RefreshExpires: 2 * time.Minute,
Expired: 2 * time.Minute,
Secret: "gg88g88",
})
//table-driven 測試案例
tests := []struct {
name string
tokenGen func(t *testing.T) string // 用來產生 access token
secret string
validate bool
want map[string]string // 預期轉換後的資料
wantErr bool
errContains string
}{
{
name: "valid token with correct data map",
tokenGen: func(t *testing.T) string {
// 建立 claims其中 "data" 欄位為 map[string]any
claims := jwt.MapClaims{
"data": map[string]any{
"key1": "value1",
"key2": "value2",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString([]byte("secret"))
if err != nil {
t.Fatalf("failed to sign token: %v", err)
}
return tokenStr
},
secret: "secret",
validate: true,
want: map[string]string{
"key1": "value1",
"key2": "value2",
},
wantErr: false,
},
{
name: "token missing data field",
tokenGen: func(t *testing.T) string {
// claims 中不包含 "data"
claims := jwt.MapClaims{
"other": "something",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString([]byte("secret"))
if err != nil {
t.Fatalf("failed to sign token: %v", err)
}
return tokenStr
},
secret: "secret",
validate: true,
want: map[string]string{},
wantErr: true,
errContains: "get data from claim map error",
},
{
name: "malformed token",
tokenGen: func(t *testing.T) string {
return "not-a-token"
},
secret: "secret",
validate: true,
want: map[string]string{},
wantErr: true,
errContains: "token contains an invalid number of segments",
},
{
name: "data field not a map",
tokenGen: func(t *testing.T) string {
// 將 "data" 設為一個字串,而非 map
claims := jwt.MapClaims{
"data": "not-a-map",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString([]byte("secret"))
if err != nil {
t.Fatalf("failed to sign token: %v", err)
}
return tokenStr
},
secret: "secret",
validate: true,
want: map[string]string{},
wantErr: true,
errContains: "get data from claim map error",
},
}
for _, tt := range tests {
tt := tt // 捕捉區域變數
t.Run(tt.name, func(t *testing.T) {
accessToken := tt.tokenGen(t)
result, err := uc.ParseSystemClaimsByAccessToken(accessToken, tt.secret, tt.validate)
if tt.wantErr {
assert.Error(t, err)
if err != nil && tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, result)
})
}
}
func TestTokenUseCase_newToken(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAutoIDModel := mock.NewMockTokenRepo(mockCtrl)
uc := TokenUseCase{
TokenUseCaseParam: TokenUseCaseParam{
TokenRepo: mockAutoIDModel,
RefreshExpires: 2 * time.Minute,
Expired: 2 * time.Minute,
Secret: "gg88g88",
},
}
// 取得一個參考時間,用來檢查 default expiration 的結果
nowRef := time.Now().UTC()
tests := []struct {
name string
req *usecase.GenerateTokenRequest
// 模擬產生 AccessToken 與 RefreshToken 的函式
stubAccessToken func(token entity.Token, data map[string]interface{}, secret string) (string, error)
stubRefreshToken func(accessToken string) string
wantErr bool
// 當使用者提供明確的 expires 與 refreshExpires 時,期望的值(否則使用預設)
expectExpiresProvided bool
expectedExpires int64
expectRefreshExpiresProvided bool
expectedRefreshExpires int64
}{
{
name: "default expiration used when req.Expires/RefreshExpires are zero",
req: &usecase.GenerateTokenRequest{
DeviceID: "device1",
UID: "user1",
Expires: 0,
RefreshExpires: 0,
Data: map[string]string{"foo": "bar"},
Role: "admin",
Scope: "read",
Account: "account1",
TokenType: "access",
},
wantErr: false,
expectExpiresProvided: false,
expectRefreshExpiresProvided: false,
},
{
name: "explicit expiration provided",
req: func() *usecase.GenerateTokenRequest {
// 提供明確的 expires 與 refreshExpires
exp := nowRef.Add(5 * time.Minute).UnixNano()
refExp := nowRef.Add(10 * time.Minute).UnixNano()
return &usecase.GenerateTokenRequest{
DeviceID: "device2",
UID: "user2",
Expires: exp,
RefreshExpires: refExp,
Data: map[string]string{},
Role: "user",
Scope: "write",
Account: "account2",
TokenType: "access",
}
}(),
stubAccessToken: func(token entity.Token, data map[string]interface{}, secret string) (string, error) {
return "access-token", nil
},
stubRefreshToken: func(accessToken string) string {
return "refresh-token"
},
wantErr: false,
expectExpiresProvided: true,
// 預期值就與 req.Expires 相同
expectedExpires: func() int64 { return nowRef.Add(5 * time.Minute).UnixNano() }(),
expectRefreshExpiresProvided: true,
expectedRefreshExpires: func() int64 { return nowRef.Add(10 * time.Minute).UnixNano() }(),
},
}
for _, tt := range tests {
tt := tt // 捕捉範圍變數
t.Run(tt.name, func(t *testing.T) {
// 呼叫 newToken 方法
token, err := uc.newToken(context.Background(), tt.req)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
// 檢查基本欄位
assert.NotEmpty(t, token.ID, "token.ID should not be empty")
assert.Equal(t, tt.req.DeviceID, token.DeviceID)
assert.Equal(t, tt.req.UID, token.UID)
// 驗證建立時間欄位有被設置
assert.NotZero(t, token.AccessCreateAt)
assert.NotZero(t, token.RefreshCreateAt)
})
}
}
func TestTokenUseCase_GenerateAccessToken(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockNewMockTokenRepo := mock.NewMockTokenRepo(mockCtrl)
uc := TokenUseCase{
TokenUseCaseParam: TokenUseCaseParam{
TokenRepo: mockNewMockTokenRepo,
RefreshExpires: 2 * time.Minute,
Expired: 2 * time.Minute,
Secret: "gg88g88",
},
}
// 定義 table-driven 測試案例
tests := []struct {
name string
repoErr error
req usecase.GenerateTokenRequest
wantErr bool
errContains string
setup func()
// 若成功,預期回傳的 access token 與 refresh token
expectedAccessToken string
expectedRefreshToken string
}{
{
name: "newToken error from CreateAccessToken",
repoErr: nil,
setup: func() {
mockNewMockTokenRepo.EXPECT().Create(gomock.Any(), gomock.Any()).Return(fmt.Errorf("token create error: failed to create token"))
},
req: usecase.GenerateTokenRequest{
DeviceID: "device1",
UID: "user1",
Expires: 0, // 使用預設過期時間
RefreshExpires: 0,
Data: map[string]string{"foo": "bar"},
Role: "admin",
Scope: "read",
Account: "account1",
TokenType: "access",
},
wantErr: true,
errContains: "token create error: failed to create token",
},
{
name: "successful generation",
repoErr: nil,
setup: func() {
mockNewMockTokenRepo.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil)
},
req: usecase.GenerateTokenRequest{
DeviceID: "device3",
UID: "user3",
Expires: 0,
RefreshExpires: 0,
Data: map[string]string{"foo": "bar"},
Role: "member",
Scope: "read",
Account: "account3",
TokenType: "access",
},
wantErr: false,
},
}
// 針對每個測試案例執行測試
for _, tt := range tests {
tt := tt // 捕捉區域變數
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
tt.setup()
resp, err := uc.GenerateAccessToken(ctx, tt.req)
if tt.wantErr {
assert.Error(t, err)
if err != nil && tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}
assert.NoError(t, err)
// 驗證 ExpiresIn 非零newToken 會根據當前時間與設定產生過期時間)
assert.NotZero(t, resp.ExpiresIn)
})
}
}