feat: add permission server

This commit is contained in:
王性驊 2025-02-24 09:04:06 +08:00
parent e8c5616206
commit 547b3b06dd
24 changed files with 1212 additions and 39 deletions

33
etc/rbac.conf Normal file
View File

@ -0,0 +1,33 @@
# 表示請求參數
# role: 角色
# path: api path
# method: http method
[request_definition]
r = role, path, method
# 策略的基本參數
# role: 角色
# path: api path
# method: http method可以多個
# name: 名稱
[policy_definition]
p = role, path, methods, name
# 策略配對結果後的應對,只要配對到一個即可成功
[policy_effect]
e = some(where (p.eft == allow))
# 規範角色對應是"用戶"與"角色"
# g=A用戶,管理員
[role_definition]
g = _, _
# 策略配對規則
# 三個條件必須完全匹配
# g(r.role, p.role),只要判斷用戶角色(r.role)是否屬於策略角色(p.role)為什麼不寫r.role == p.role因為role可以有繼承關係所以不能這樣寫
# keyMatch(r.path, p.path)使用正則表達式分析api path參考 https://casbin.org/docs/function
# regexMatch(r.method, p.methods)使用正則表達式分析http method參考 https://casbin.org/docs/function
# 或達到一個條件
# r.role == admin用戶 UID 是 GodDog 全開放
[matchers]
m = g(r.role, p.role) && keyMatch2(r.path, p.path) && regexMatch(r.method, p.methods) || r.role == "GodDog"

40
go.mod
View File

@ -4,12 +4,14 @@ go 1.23.6
require (
code.30cm.net/digimon/library-go/errs v1.2.14
code.30cm.net/digimon/library-go/mongo v0.0.9
github.com/alicebob/miniredis/v2 v2.34.0
github.com/casbin/casbin/v2 v2.103.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.mongodb.org/mongo-driver v1.17.2
go.uber.org/mock v0.5.0
google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.5
@ -18,6 +20,8 @@ require (
require (
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
@ -34,19 +38,21 @@ require (
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/golang/snappy v0.0.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/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/compress v1.17.11 // 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
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
@ -56,34 +62,42 @@ require (
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/shopspring/decimal v1.4.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
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.32.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.34.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 v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.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.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.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/atomic v1.10.0 // 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.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sync v0.10.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
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // 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
@ -95,5 +109,5 @@ require (
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

98
go.sum
View File

@ -1,5 +1,7 @@
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=
code.30cm.net/digimon/library-go/mongo v0.0.9 h1:fPciIE5B85tXpLg8aeVQqKVbLnfpVAk9xbMu7pE2tVw=
code.30cm.net/digimon/library-go/mongo v0.0.9/go.mod h1:KBVKz/Ci5IheI77BgZxPUeKkaGvDy8fV8EDHSCOLIO4=
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=
@ -8,10 +10,16 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
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/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
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/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic=
github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco=
github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
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=
@ -50,10 +58,13 @@ 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.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
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/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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=
@ -66,8 +77,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY
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/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
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=
@ -76,8 +87,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
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/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
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=
@ -99,6 +110,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/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=
@ -125,10 +138,12 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
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=
@ -146,9 +161,18 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
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/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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=
@ -159,30 +183,34 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5
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.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
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 v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
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/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
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.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
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=
@ -198,14 +226,21 @@ 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/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
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=
@ -214,40 +249,51 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
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/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
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/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA=
google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
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=
@ -281,5 +327,5 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm
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=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@ -0,0 +1,22 @@
package entity
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Permission struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Parent string `bson:"parent"` // 父權限的 ID (用字串儲存 ObjectID 的 Hex)
Name string `bson:"name"`
HTTPMethod string `bson:"http_method"` // 視作操作Action
HTTPPath string `bson:"http_path"` // 視作資源Object
Status permission.Status `bson:"status"` // 例如 1: 啟用, 0: 停用
Type permission.Type `bson:"type"`
UpdateAt int64 `bson:"update_at"`
CreateAt int64 `bson:"create_at"`
}
func (c *Permission) Collection() string {
return "permission"
}

View File

@ -0,0 +1,28 @@
package entity
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
//TODO 未來才實作要做IAM 時)
// Policy 表示一個存取控制策略文件
type Policy struct {
ID primitive.ObjectID `bson:"_id,omitempty"` // MongoDB 的主鍵
Name string `bson:"name"` // 策略名稱
Description string `bson:"description,omitempty"` // 策略描述
Effect string `bson:"effect"` // "allow" 或 "deny"
Condition interface{} `bson:"condition,omitempty"` // 存放 JSON 條件,可以是 map[string]interface{} 或其他符合 BSON 格式的資料
CreatedAt int64 `bson:"created_at"` // 建立時間
UpdatedAt int64 `bson:"updated_at"` // 更新時間
}
// Resource 表示需要保護的資源
type Resource struct {
ID primitive.ObjectID `bson:"_id,omitempty"` // MongoDB 的主鍵
Name string `bson:"name"` // 資源名稱
Type string `bson:"type"` // 資源類型
Description string `bson:"description,omitempty"` // 資源描述
CreatedAt int64 `bson:"created_at"` // 建立時間
UpdatedAt int64 `bson:"updated_at"` // 更新時間
}

18
pkg/domain/entity/role.go Normal file
View File

@ -0,0 +1,18 @@
package entity
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Role struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"`
Status permission.Status `bson:"status"` // 例如 1: 啟用, 0: 停用
CreateAt int64 `bson:"create_at"`
UpdateAt int64 `bson:"update_at"`
}
func (c *Role) Collection() string {
return "role"
}

View File

@ -0,0 +1,15 @@
package entity
import "go.mongodb.org/mongo-driver/bson/primitive"
type RolePermission struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
RoleID string `bson:"role_id"` // 對應 Role 的 _id
PermissionID string `bson:"permission_id"` // 對應 Permission 的 _id
CreateAt int64 `bson:"create_at"`
UpdateAt int64 `bson:"update_at"`
}
func (c *RolePermission) Collection() string {
return "role_permission"
}

View File

@ -0,0 +1,15 @@
package entity
import "go.mongodb.org/mongo-driver/bson/primitive"
type UserRole struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
UID string `bson:"uid"` // 對應 User 的 _id
RoleID string `bson:"role_id"` // 對應 Role 的 _id
CreateAt int64 `bson:"create_at"`
UpdateAt int64 `bson:"update_at"`
}
func (c *UserRole) Collection() string {
return "user_role"
}

View File

@ -28,6 +28,8 @@ const (
GenerateVerifyCodeRedisErrorCode
FailedToCheckVerifyCode
AccountPlatformNotCorrectErrorCode
PermissionDeleteErrorCode
)
func TokenError(ec ers.ErrorCode, s ...string) *ers.LibError {

View File

@ -0,0 +1,27 @@
package permission
type Status int8
const (
Open Status = iota + 1
Close
)
const (
ClosePermission string = "close"
OpenPermission string = "open"
)
func (s Status) String() string {
status, ok := statusMap[s]
if ok {
return status
}
return ClosePermission
}
var statusMap = map[Status]string{
Open: OpenPermission,
Close: ClosePermission,
}

View File

@ -0,0 +1,8 @@
package permission
type Type int8
const (
BackendUser Type = iota + 1
FrontendUser
)

61
pkg/domain/rbac/rule.go Normal file
View File

@ -0,0 +1,61 @@
package rbac
// Rule 最多六個參數,例如
//
// p, admin, data1, read
// PolicyType:p
// Field0:admin
// Field01:data1
// Field02:read
type Rule struct {
PolicyType string // p or g
Field0 string
Field1 string
Field2 string
Field3 string
Field4 string
Field5 string
}
// ToString 轉換成這樣 []string{"p", "admin", "data1", "read"} 讓 casbin 看得懂
func (rule Rule) ToString() []string {
fields := []string{
rule.PolicyType, rule.Field0, rule.Field1,
rule.Field2, rule.Field3, rule.Field4, rule.Field5,
}
// 移除空字串,提高效能
var result []string
for _, field := range fields {
if field != "" {
result = append(result, field)
}
}
return result
}
func StringToPolicy(policyType string, rule []string) Rule {
line := Rule{}
line.PolicyType = policyType
if len(rule) > 0 {
line.Field0 = rule[0]
}
if len(rule) > 1 {
line.Field1 = rule[1]
}
if len(rule) > 2 {
line.Field2 = rule[2]
}
if len(rule) > 3 {
line.Field3 = rule[3]
}
if len(rule) > 4 {
line.Field4 = rule[4]
}
if len(rule) > 5 {
line.Field5 = rule[5]
}
return line
}

View File

@ -41,3 +41,13 @@ func GetDeviceTokenRedisKey(device string) string {
func GetTicketRedisKey(ticket string) string {
return TicketRedisKey.With(ticket).ToString()
}
// ===================
const (
CasbinRuleRedisKey RedisKey = "casbin_rule"
)
func GetCasbinRuleRedisKey() string {
return CasbinRuleRedisKey.ToString()
}

View File

@ -0,0 +1,20 @@
package repository
import (
"github.com/casbin/casbin/v2/persist"
)
// 角色權限策略 (policy.csv) 以 const 定義
/*
p, admin, data1, read
p, admin, data1, write
p, user, data1, read
g, alice, admin
g, bob, user
*/
// RBACAdapter 資料接收器,讓 Casbin 讀取規則的,類似上面這種規則
type RBACAdapter interface {
persist.Adapter
}

View File

@ -0,0 +1,45 @@
package repository
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"context"
"go.mongodb.org/mongo-driver/mongo"
)
type PermissionRepository interface {
Insert(ctx context.Context, permission entity.Permission) error
Update(ctx context.Context, id string, req UpdatePermission) error
Delete(ctx context.Context, id string) error
GetPermission
Index
}
type GetPermission interface {
// GetAll 取得所有權限列表
GetAll(ctx context.Context, status *permission.Status) ([]entity.Permission, error)
// GetAllByID 以權限 ID 作為鍵取得所有權限的映射
GetAllByID(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error)
// FindOne 根據查詢條件取得單筆權限資料
FindOne(ctx context.Context, query PermissionQuery) (entity.Permission, error)
// FindByNames 根據權限名稱列表查詢權限資料
FindByNames(ctx context.Context, names []string) ([]entity.Permission, error)
}
type Index interface {
Index20250214UP(ctx context.Context) (*mongo.Cursor, error)
}
type PermissionQuery struct {
HTTPMethod string
HTTPPath string
}
type UpdatePermission struct {
ParentID *string
Name *string
HTTPMethod *string
HTTPPath *string
Status *permission.Status
Type *permission.Type
}

View File

@ -0,0 +1,42 @@
package repository
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"context"
)
type RoleRepository interface {
Insert(ctx context.Context, role *entity.Role) error
Update(ctx context.Context, role *entity.Role) error
Delete(ctx context.Context, role *entity.Role) error
List()
ListAll()
Get()
GetByUID()
}
type RoleRepository interface {
Page(ctx context.Context, filter PageRoleFilter, page, size int) ([]entity.Role, int64, error)
Get(ctx context.Context, id int64) (entity.Role, error)
GetByUID(ctx context.Context, uid string) (entity.Role, error)
All(ctx context.Context, clientID int) ([]entity.Role, error)
IncrementID(ctx context.Context) (int, error)
Create(ctx context.Context, role *entity.Role) (int64, error)
Update(ctx context.Context, role *entity.Role) error
Delete(ctx context.Context, uid string) error
}
type PageRoleFilter struct {
ClientID int
UID string
Name string
Permissions []string
Status int
}
type RolePermissionRepository interface {
BindRolePermission()
UnBindRolePermission()
GetRolePermission()
GetByPermissionID()
}

View File

@ -0,0 +1,20 @@
package usecase
import (
"context"
"time"
)
type RBACUseCase interface {
Check(ctx context.Context, role, path, method string) (CheckRolePermissionStatus, error)
LoadPolicy(ctx context.Context) error
SyncPolicy(ctx context.Context, cron time.Duration)
}
type CheckRolePermissionStatus struct {
Allow bool `json:"allow"`
Select struct {
PermissionName string `json:"permission_name"`
PlainCode bool `json:"plain_code"`
} `json:"select"`
}

View File

@ -0,0 +1,42 @@
package usecase
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"context"
)
type PermissionUseCase interface {
Insert(ctx context.Context, req CreatePermissionReq) error
Del(ctx context.Context, id string) error
Update(ctx context.Context, req UpdatePermissionReq) error
Get(ctx context.Context, id string) (entity.Permission, error)
List(ctx context.Context, param ListParam) ([]entity.Permission, int64, error)
All(ctx context.Context, status *permission.Status)
// FilterAll 用樹的結構,使得付節點若關閉,子節點也不會顯示
FilterAll(ctx context.Context) ([]entity.Permission, error)
}
type CreatePermissionReq struct {
Parent *string // 父權限的 ID (用字串儲存 ObjectID 的 Hex)
Name string // 權限名字
HTTPMethod string // 視作操作Action
HTTPPath string // 視作資源Object
Status permission.Status // 例如 1: 啟用, 0: 停用
Type permission.Type
}
type UpdatePermissionReq struct {
Parent *string // 父權限的 ID (用字串儲存 ObjectID 的 Hex)
Name *string // 權限名字
HTTPMethod *string // 視作操作Action
HTTPPath *string // 視作資源Object
Status *permission.Status // 例如 1: 啟用, 0: 停用
Type *permission.Type
}
type ListParam struct {
PageIndex int64
PageSize int64
Parent *string
}

View File

@ -0,0 +1,149 @@
package repository
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/rbac"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
"context"
"encoding/json"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist"
"github.com/zeromicro/go-zero/core/stores/redis"
"time"
)
type RBACAdapterParam struct {
Redis *redis.Redis
}
type rbacAdapter struct {
redis *redis.Redis
}
func NewRBACAdapter(param RBACAdapterParam) (repository.RBACAdapter, error) {
return &rbacAdapter{
redis: param.Redis,
}, nil
}
// loadPolicyByString 真正將 Rule 存到 casbin 讓他可以使用的地方
func loadPolicyByString(line rbac.Rule, model model.Model) error {
text := line.ToString()
// 雖然是從Redis 來,不過最終還是有一份會存在記憶體當中,這樣做的原因只是不要存第二次
err := persist.LoadPolicyArray(text, model)
if err != nil {
return err
}
return nil
}
// LoadPolicy 將檔案從記憶體當中載入本地 Casbin
func (adapter *rbacAdapter) LoadPolicy(model model.Model) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
rk := domain.GetCasbinRuleRedisKey()
stop, err := adapter.redis.LlenCtx(ctx, rk)
if err != nil {
return err
}
rules, err := adapter.redis.LrangeCtx(ctx, rk, 0, stop)
if err != nil {
return err
}
for _, ruleStr := range rules {
var rule rbac.Rule
err := json.Unmarshal([]byte(ruleStr), &rule)
if err != nil {
return err
}
err = loadPolicyByString(rule, model)
if err != nil {
return err
}
}
return nil
}
// SavePolicy saves policy to database. 將 Rule 一次就從資料庫中存到 redis 裡面 -> 打API 初始化 Redis 同步的資料
func (adapter *rbacAdapter) SavePolicy(model model.Model) error {
rk := domain.GetCasbinRuleRedisKey()
_, err := adapter.redis.Del(rk)
if err != nil {
return err
}
var texts []string
for policy, ast := range model["p"] {
for _, rule := range ast.Policy {
line := rbac.StringToPolicy(policy, rule)
text, err := json.Marshal(line)
if err != nil {
return err
}
texts = append(texts, string(text))
}
}
for policy, ast := range model["g"] {
for _, rule := range ast.Policy {
line := rbac.StringToPolicy(policy, rule)
text, err := json.Marshal(line)
if err != nil {
return err
}
texts = append(texts, string(text))
}
}
_, err = adapter.redis.Rpush(rk, texts)
if err != nil {
return err
}
return nil
}
// AddPolicy adds a policy rule to the storage.
func (adapter *rbacAdapter) AddPolicy(_ string, policyType string, rule []string) error {
policy := rbac.StringToPolicy(policyType, rule)
text, err := json.Marshal(policy)
if err != nil {
return err
}
rk := domain.GetCasbinRuleRedisKey()
_, err = adapter.redis.Rpush(rk, text)
if err != nil {
return err
}
return nil
}
func (adapter *rbacAdapter) RemovePolicy(_ string, policyType string, rule []string) error {
policy := rbac.StringToPolicy(policyType, rule)
text, err := json.Marshal(policy)
if err != nil {
return err
}
rk := domain.GetCasbinRuleRedisKey()
_, err = adapter.redis.Lrem(rk, 1, string(text))
if err != nil {
return err
}
return nil
}
// RemoveFilteredPolicy 沒用到先不實作
func (adapter *rbacAdapter) RemoveFilteredPolicy(_ string, policyType string, fieldIndex int, fieldValues ...string) error {
return nil
}

View File

@ -0,0 +1,195 @@
package repository
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/rbac"
"context"
"encoding/json"
"github.com/casbin/casbin/v2/model"
"github.com/stretchr/testify/assert"
"testing"
)
func Test_rbacAdapter_AddPolicy(t *testing.T) {
mr, r := setupMiniRedis()
defer mr.Close()
adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: r})
assert.NoError(t, err)
// 新增策略
err = adapter.AddPolicy("", "p", []string{"user", "data2", "read"})
assert.NoError(t, err)
// 驗證 Redis 內的數據
rk := domain.GetCasbinRuleRedisKey()
data, err := r.LrangeCtx(context.Background(), rk, 0, -1)
assert.NoError(t, err)
assert.Len(t, data, 1)
// 解析 JSON 確認內容
var policy rbac.Rule
err = json.Unmarshal([]byte(data[0]), &policy)
assert.NoError(t, err)
assert.Equal(t, "p", policy.PolicyType)
assert.Equal(t, "user", policy.Field0)
assert.Equal(t, "data2", policy.Field1)
assert.Equal(t, "read", policy.Field2)
}
// 測試 `RemovePolicy`
func TestRBACAdapter_RemovePolicy(t *testing.T) {
mr, rdb := setupMiniRedis()
defer mr.Close()
adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: rdb})
assert.NoError(t, err)
// 新增策略
err = adapter.AddPolicy("", "p", []string{"user", "data2", "read"})
assert.NoError(t, err)
// 確保策略存在
rk := domain.GetCasbinRuleRedisKey()
data, err := rdb.LrangeCtx(context.Background(), rk, 0, -1)
assert.NoError(t, err)
assert.Len(t, data, 1)
// 刪除策略
err = adapter.RemovePolicy("", "p", []string{"user", "data2", "read"})
assert.NoError(t, err)
// 確保 Redis 已經刪除該策略
data, err = rdb.LrangeCtx(context.Background(), rk, 0, -1)
assert.NoError(t, err)
assert.Len(t, data, 0)
}
// 測試 SavePolicy: 確保能夠存入 Redis
func TestRBACAdapter_SavePolicy(t *testing.T) {
mr, rdb := setupMiniRedis()
defer mr.Close()
adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: rdb})
assert.NoError(t, err)
// 準備 Casbin Model
m, _ := model.NewModelFromString(`
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`)
// 加入測試策略
m.AddPolicy("p", "p", []string{"admin", "data1", "read"})
m.AddPolicy("p", "p", []string{"admin", "data1", "write"})
m.AddPolicy("p", "g", []string{"alice", "admin"})
// 確保 Redis 內最開始是空的
rk := domain.GetCasbinRuleRedisKey()
rulesBefore, _ := rdb.LrangeCtx(context.Background(), rk, 0, -1)
assert.Len(t, rulesBefore, 0)
// 執行 SavePolicy
err = adapter.SavePolicy(m)
assert.NoError(t, err)
// 確保 Redis 內的數據與 `policy` 一致
rulesAfter, err := rdb.LrangeCtx(context.Background(), rk, 0, -1)
assert.NoError(t, err)
// 解析 JSON 並確認內容
expectedPolicies := [][]string{
{"p", "admin", "data1", "read"},
{"p", "admin", "data1", "write"},
{"g", "alice", "admin"},
}
for i, ruleStr := range rulesAfter {
var rule rbac.Rule
err := json.Unmarshal([]byte(ruleStr), &rule)
assert.NoError(t, err)
assert.Equal(t, expectedPolicies[i][0], rule.PolicyType)
assert.Equal(t, expectedPolicies[i][1], rule.Field0)
assert.Equal(t, expectedPolicies[i][2], rule.Field1)
if len(expectedPolicies[i]) > 3 {
assert.Equal(t, expectedPolicies[i][3], rule.Field2)
}
}
}
// 測試 SavePolicy: Redis 寫入失敗
func TestRBACAdapter_SavePolicy_RedisError(t *testing.T) {
mr, rdb := setupMiniRedis()
defer mr.Close()
adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: rdb})
assert.NoError(t, err)
m, _ := model.NewModelFromString(`
[policy_definition]
p = sub, obj, act
`)
m.AddPolicy("p", "p", []string{"admin", "data1", "read"})
err = adapter.SavePolicy(m)
assert.Error(t, err) // 應該要回傳錯誤
}
// 測試 `LoadPolicy` 是否能從 Redis 讀取 `p` 和 `g` 規則
func TestRBACAdapter_LoadPolicy(t *testing.T) {
mr, rdb := setupMiniRedis()
defer mr.Close()
adapter, err := NewRBACAdapter(RBACAdapterParam{Redis: rdb})
assert.NoError(t, err)
// 預先寫入 Redis 測試數據
rk := domain.GetCasbinRuleRedisKey()
testRules := []rbac.Rule{
{PolicyType: "p", Field0: "admin", Field1: "data1", Field2: "read"},
{PolicyType: "p", Field0: "admin", Field1: "data1", Field2: "write"},
{PolicyType: "g", Field0: "alice", Field1: "admin"},
{PolicyType: "g", Field0: "bob", Field1: "user"},
}
for _, rule := range testRules {
data, _ := json.Marshal(rule)
_, err := rdb.Rpush(rk, string(data))
assert.NoError(t, err)
}
// 創建 Casbin Model
m, _ := model.NewModelFromString(`
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`)
// 測試 `LoadPolicy`
err = adapter.LoadPolicy(m)
assert.NoError(t, err)
}

11
pkg/repository/error.go Normal file
View File

@ -0,0 +1,11 @@
package repository
import (
"errors"
"github.com/zeromicro/go-zero/core/stores/mon"
)
var (
ErrNotFound = mon.ErrNotFound
ErrInvalidObjectID = errors.New("invalid objectId")
)

View File

@ -0,0 +1,175 @@
package repository
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
mgo "code.30cm.net/digimon/library-go/mongo"
"context"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)
type PermissionRepositoryParam struct {
Conf *mgo.Conf
DBOpts []mon.Option
}
type PermissionRepository struct {
DB mgo.DocumentDBUseCase
}
// TODO 量小不需要 Redis 以後有需要再增加
func NewPermissionRepository(param PermissionRepositoryParam) repository.PermissionRepository {
e := entity.Permission{}
db, err := mgo.NewDocumentDB(param.Conf, e.Collection(), param.DBOpts...)
if err != nil {
panic(err)
}
return &PermissionRepository{
DB: db,
}
}
func (repo *PermissionRepository) Insert(ctx context.Context, data entity.Permission) error {
if data.ID.IsZero() {
now := time.Now().UTC().UnixNano()
data.ID = primitive.NewObjectID()
data.CreateAt = now
data.UpdateAt = now
}
_, err := repo.DB.GetClient().InsertOne(ctx, data)
return err
}
func (repo *PermissionRepository) Update(ctx context.Context, id string, req repository.UpdatePermission) error {
now := time.Now().UTC().UnixNano()
// 動態構建更新內容
updateFields := bson.M{
"update_at": now, // 確保 `updateAt` 總是更新
}
if req.Name != nil {
updateFields["name"] = *req.Name
}
if req.HTTPMethod != nil {
updateFields["http_method"] = *req.HTTPMethod
}
if req.HTTPPath != nil {
updateFields["http_path"] = *req.HTTPPath
}
if req.Status != nil {
updateFields["status"] = *req.Status
}
if req.Type != nil {
updateFields["type"] = *req.Type
}
if req.ParentID != nil {
updateFields["parent"] = *req.ParentID
}
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
_, err = repo.DB.GetClient().UpdateOne(ctx, bson.M{"_id": oid}, bson.M{"$set": updateFields})
if err != nil {
return err
}
return nil
}
func (repo *PermissionRepository) Delete(ctx context.Context, id string) error {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return ErrInvalidObjectID
}
_, err = repo.DB.GetClient().DeleteOne(ctx, bson.M{"_id": oid})
if err != nil {
return err
}
return nil
}
func (repo *PermissionRepository) GetAll(ctx context.Context, status *permission.Status) ([]entity.Permission, error) {
filter := bson.M{}
if status != nil {
filter["status"] = status
}
opt := options.Find().SetSort(bson.M{"id": 1})
result := make([]entity.Permission, 0)
err := repo.DB.GetClient().Find(ctx, &result, filter, opt)
if err != nil {
return nil, err
}
return result, nil
}
func (repo *PermissionRepository) GetAllByID(ctx context.Context, status *permission.Status) (map[string]entity.Permission, error) {
permissions, err := repo.GetAll(ctx, status)
if err != nil {
return nil, err
}
permissionMap := make(map[string]entity.Permission)
for _, v := range permissions {
permissionMap[v.Name] = v
}
return permissionMap, nil
}
func (repo *PermissionRepository) FindOne(ctx context.Context, query repository.PermissionQuery) (entity.Permission, error) {
filter := bson.M{"http_method": query.HTTPMethod, "http_path": query.HTTPPath}
var result entity.Permission
err := repo.DB.GetClient().FindOne(ctx, &result, filter)
if err != nil {
return entity.Permission{}, err
}
return result, nil
}
func (repo *PermissionRepository) FindByNames(ctx context.Context, names []string) ([]entity.Permission, error) {
result := make([]entity.Permission, 0)
// 使用 $in 操作符查詢 name 在 names 切片中的文件
filter := bson.M{"name": bson.M{"$in": names}}
err := repo.DB.GetClient().FindOne(ctx, &result, filter)
if err != nil {
return []entity.Permission{}, err
}
return result, nil
}
func (repo *PermissionRepository) Index20250214UP(ctx context.Context) (*mongo.Cursor, error) {
// 等價於 db.account.createIndex({ "http_method": 1, "http_path": 1}, {unique: false})
repo.DB.PopulateMultiIndex(ctx, []string{
"http_method",
"http_path",
}, []int32{1, 1}, false)
// 等價於 db.account.createIndex({"create_at": 1})
repo.DB.PopulateIndex(ctx, "name", 1, true)
return repo.DB.GetClient().Indexes().List(ctx)
}

View File

@ -0,0 +1,174 @@
package usecase
import (
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
"code.30cm.net/digimon/library-go/errs"
"context"
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/zeromicro/go-zero/core/logx"
"log"
"time"
)
type RBACUseCaseParam struct {
ModulePath string
permissionRepo repository.PermissionRepository
RBACRedisAdapter repository.RBACAdapter
// role permission 之類的
}
type RBACUseCase struct {
permissionRepo repository.PermissionRepository
adapter repository.RBACAdapter
instance *casbin.Enforcer
}
func NewUseCase(param RBACUseCaseParam) usecase.RBACUseCase {
result := &RBACUseCase{
adapter: param.RBACRedisAdapter,
permissionRepo: param.permissionRepo,
}
// 1. 讀取 RBAC 模型 ->
m, err := model.NewModelFromFile(param.ModulePath)
if err != nil {
log.Fatalf("failed to load model: %v", err)
}
// 3. 創建 Casbin Enforcer
enforcer, err := casbin.NewEnforcer(m, result.adapter)
if err != nil {
log.Fatalf("failed to init Enforcer: %v", err)
}
result.instance = enforcer
return result
}
func (use *RBACUseCase) Check(ctx context.Context, role, path, method string) (usecase.CheckRolePermissionStatus, error) {
ok, p, err := use.instance.EnforceEx(role, path, method)
if err != nil {
e := errs.ForbiddenL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "req", Value: fmt.Sprintf("role: %s, path: %s, method: %s", role, path, method)},
{Key: "func", Value: "casbin.EnforceEx"},
{Key: "err", Value: err.Error()},
},
"failed to get permission")
return usecase.CheckRolePermissionStatus{}, e
}
status := usecase.CheckRolePermissionStatus{
Allow: ok,
}
fmt.Println(p)
//// 檢查是否有明碼查詢權限
//if role == domain.AdminRoleUID {
// status.Select.PlainCode = true
//} else if ok && method == http.MethodGet {
// status.Select.PlainCode = use.rbac.GetModel().HasPolicy("p", "p", []string{
// role, path, method, p[3] + ".plain_code",
// })
//}
//
//limit := 4
//if len(p) >= limit {
// status.Select.PermissionName = p[3]
//}
return status, nil
}
func (use *RBACUseCase) LoadPolicy(ctx context.Context) error {
status := permission.Open
// 取得所有permission -> permission tree 拿到有開啟的節點,如果付節點關閉,子節點也就不顯示了
permissions, err := use.permissionRepo.GetAll(ctx, &status)
if err != nil {
return fmt.Errorf("permissionRepo.AllStatus error: %w", err)
}
fmt.Println(permissions)
// 全部permission
//permissionMap := make(map[int64]entity.Permission, len(permissions))
//for _, v := range permissions {
// permissionMap[v.ID] = v
//}
// 全部角色
//roles, err := r.roleRepo.All(ctx, 1)
//if err != nil {
// return fmt.Errorf("roleRepo.AllStatus error: %w", err)
//}
//
//roleMap := make(map[int64]entity.Role, len(roles))
//for _, v := range roles {
// roleMap[v.ID] = v
//}
// 根據角色組合權限表
//for _, v := range roles {
// rolePermissions, err := r.rolePermissionRepo.Get(ctx, v.ID)
// if err != nil {
// return fmt.Errorf("rolePermissionRepo.Get ID: %d error: %w", v.ID, err)
// }
//
// for _, rp := range rolePermissions {
// role, ok := roleMap[rp.RoleID]
// if !ok {
// logrus.WithFields(logrus.Fields{
// "role_id": rp.RoleID,
// }).Error("role not found")
//
// continue
// }
//
// permission, ok := permissionMap[rp.PermissionID]
// if !ok {
// logrus.WithFields(logrus.Fields{
// "permission_id": rp.PermissionID,
// }).Error("permission not found")
//
// continue
// }
//
// if permission.HTTPPath == "" || permission.HTTPMethod == "" {
// continue
// }
//
// // 根據策略model configs/rbac_model.conf填入policy_definition對應參數
// err := persist.LoadPolicyArray([]string{"p", role.UID, permission.HTTPPath, permission.HTTPMethod, permission.Name}, model)
// if err != nil {
// return fmt.Errorf("persist.LoadPolicyArray error: %w", err)
// }
// }
//}
return nil
}
func (use *RBACUseCase) SyncPolicy(ctx context.Context, cron time.Duration) {
t := time.NewTicker(cron)
for {
select {
case <-t.C:
if err := use.LoadPolicy(ctx); err == nil {
logx.Info("LoadPolicy success")
}
case <-ctx.Done():
t.Stop()
logx.Info("exit Policy success")
return
}
}
}

View File

@ -0,0 +1 @@
package usecase