init project
This commit is contained in:
		
						commit
						79b21c7264
					
				| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
.idea/
 | 
			
		||||
etc/gateway.yaml
 | 
			
		||||
.DS_Store
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,135 @@
 | 
			
		|||
run:
 | 
			
		||||
  timeout: 3m
 | 
			
		||||
  # Exit code when at least one issue was found.
 | 
			
		||||
  # Default: 1
 | 
			
		||||
  issues-exit-code: 2
 | 
			
		||||
  # Include test files or not.
 | 
			
		||||
  # Default: true
 | 
			
		||||
  tests: false
 | 
			
		||||
 | 
			
		||||
# Reference URL: https://golangci-lint.run/usage/linters/
 | 
			
		||||
linters:
 | 
			
		||||
  # Disable everything by default so upgrades to not include new - default
 | 
			
		||||
  # enabled-  linters.
 | 
			
		||||
  disable-all: true
 | 
			
		||||
  # Specifically enable linters we want to use.
 | 
			
		||||
  enable:
 | 
			
		||||
    #    - depguard
 | 
			
		||||
    - errcheck
 | 
			
		||||
    #    - godot
 | 
			
		||||
    - gofmt
 | 
			
		||||
    - goimports
 | 
			
		||||
    - gosimple
 | 
			
		||||
    - govet
 | 
			
		||||
    - ineffassign
 | 
			
		||||
    - misspell
 | 
			
		||||
    - revive
 | 
			
		||||
    - typecheck
 | 
			
		||||
    - unused
 | 
			
		||||
    - asasalint
 | 
			
		||||
    - asciicheck
 | 
			
		||||
    - bidichk
 | 
			
		||||
    - bodyclose
 | 
			
		||||
    - contextcheck
 | 
			
		||||
    - wastedassign
 | 
			
		||||
    - whitespace
 | 
			
		||||
    - thelper
 | 
			
		||||
    - tparallel
 | 
			
		||||
    - unconvert
 | 
			
		||||
    - unparam
 | 
			
		||||
    - usestdlibvars
 | 
			
		||||
    - tenv
 | 
			
		||||
    - testableexamples
 | 
			
		||||
    - stylecheck
 | 
			
		||||
    - sqlclosecheck
 | 
			
		||||
    - nosprintfhostport
 | 
			
		||||
    - paralleltest
 | 
			
		||||
    - prealloc
 | 
			
		||||
    - predeclared
 | 
			
		||||
    - promlinter
 | 
			
		||||
    - reassign
 | 
			
		||||
    - rowserrcheck
 | 
			
		||||
    - nakedret
 | 
			
		||||
    - nestif
 | 
			
		||||
    - nilerr
 | 
			
		||||
    - nilnil
 | 
			
		||||
    - nlreturn
 | 
			
		||||
    - noctx
 | 
			
		||||
    - nolintlint
 | 
			
		||||
    - nonamedreturns
 | 
			
		||||
    - decorder
 | 
			
		||||
    - dogsled
 | 
			
		||||
    - dupword
 | 
			
		||||
    - durationcheck
 | 
			
		||||
    - errchkjson
 | 
			
		||||
    - errname
 | 
			
		||||
    - errorlint
 | 
			
		||||
    # - execinquery
 | 
			
		||||
    - exhaustive
 | 
			
		||||
    - exportloopref
 | 
			
		||||
    - forbidigo
 | 
			
		||||
    - forcetypeassert
 | 
			
		||||
    - gochecknoinits
 | 
			
		||||
    - gocognit
 | 
			
		||||
    - goconst
 | 
			
		||||
    - gocritic
 | 
			
		||||
    - gocyclo
 | 
			
		||||
    - goheader
 | 
			
		||||
    - gomoddirectives
 | 
			
		||||
    - goprintffuncname
 | 
			
		||||
    - gosec
 | 
			
		||||
    - grouper
 | 
			
		||||
    - importas
 | 
			
		||||
    - interfacebloat
 | 
			
		||||
    - lll
 | 
			
		||||
    - loggercheck
 | 
			
		||||
    - maintidx
 | 
			
		||||
    - makezero
 | 
			
		||||
 | 
			
		||||
issues:
 | 
			
		||||
  exclude-rules:
 | 
			
		||||
    - path: _test\.go
 | 
			
		||||
      linters:
 | 
			
		||||
        - funlen
 | 
			
		||||
        - goconst
 | 
			
		||||
        - interfacer
 | 
			
		||||
        - dupl
 | 
			
		||||
        - lll
 | 
			
		||||
        - goerr113
 | 
			
		||||
        - errcheck
 | 
			
		||||
        - gocritic
 | 
			
		||||
        - cyclop
 | 
			
		||||
        - wrapcheck
 | 
			
		||||
        - gocognit
 | 
			
		||||
        - contextcheck
 | 
			
		||||
 | 
			
		||||
  exclude-dirs:
 | 
			
		||||
    - internal/module/seckill/usecase
 | 
			
		||||
#    - internal/logic
 | 
			
		||||
 | 
			
		||||
  exclude-files:
 | 
			
		||||
    - .*_test.go
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
linters-settings:
 | 
			
		||||
  gci:
 | 
			
		||||
    sections:
 | 
			
		||||
      - standard # Standard section: captures all standard packages.
 | 
			
		||||
      - default # Default section: contains all imports that could not be matched to another section type.
 | 
			
		||||
  gocognit:
 | 
			
		||||
    # Minimal code complexity to report.
 | 
			
		||||
    # Default: 30 (but we recommend 10-20)
 | 
			
		||||
    min-complexity: 40
 | 
			
		||||
  nestif:
 | 
			
		||||
    # Minimal complexity of if statements to report.
 | 
			
		||||
    # Default: 5
 | 
			
		||||
    min-complexity: 10
 | 
			
		||||
  lll:
 | 
			
		||||
    # Max line length, lines longer will be reported.
 | 
			
		||||
    # '\t' is counted as 1 character by default, and can be changed with the tab-width option.
 | 
			
		||||
    # Default: 120.
 | 
			
		||||
    line-length: 200
 | 
			
		||||
    # Tab width in spaces.
 | 
			
		||||
    # Default: 1
 | 
			
		||||
    tab-width: 1
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
#goctl api plugin -plugin goctl-swagger="swagger -filename gateway.json -host dev-api.30cm.net" -api ./generate/api/gateway.api -dir .
 | 
			
		||||
#goctl api go -api ./generate/api/gateway.api -dir . -style go_zero
 | 
			
		||||
 | 
			
		||||
GOFMT ?= gofmt
 | 
			
		||||
GOFILES := $(shell find . -name "*.go")
 | 
			
		||||
 | 
			
		||||
.PHONY: fmt
 | 
			
		||||
fmt: # 格式優化
 | 
			
		||||
	$(GOFMT) -s -w $(GOFILES)
 | 
			
		||||
	goimports -w  ./
 | 
			
		||||
	golangci-lint run
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.PHONY: gen-doc
 | 
			
		||||
gen-doc: # 格式優化
 | 
			
		||||
	goctl api plugin -plugin goctl-swagger="swagger -filename gateway.json -host dev-api.30cm.net" -api ./generate/api/gateway.api -dir .
 | 
			
		||||
 | 
			
		||||
.PHONY: gen-api
 | 
			
		||||
gen-api: # 格式優化
 | 
			
		||||
	goctl api go -api ./generate/api/gateway.api -dir . -style go_zero
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
Name: gateway
 | 
			
		||||
Host: 0.0.0.0
 | 
			
		||||
Port: 8888
 | 
			
		||||
 | 
			
		||||
DevServer:
 | 
			
		||||
  Enabled: false
 | 
			
		||||
  Port: 6470
 | 
			
		||||
  MetricsPath: /metrics
 | 
			
		||||
  EnableMetrics: true
 | 
			
		||||
 | 
			
		||||
PyroScope:
 | 
			
		||||
  Enable: false
 | 
			
		||||
  URL: http://10.0.0.1:4040
 | 
			
		||||
  Token: "xxx"
 | 
			
		||||
 | 
			
		||||
RedisCfg:
 | 
			
		||||
  Host: 127.0.0.1:6379
 | 
			
		||||
  Type: node
 | 
			
		||||
 | 
			
		||||
MemberRPC:
 | 
			
		||||
  Etcd:
 | 
			
		||||
    Hosts:
 | 
			
		||||
      - 10.0.0.13:2379
 | 
			
		||||
    Key: member.rpc
 | 
			
		||||
NotificationRPC:
 | 
			
		||||
  Etcd:
 | 
			
		||||
    Hosts:
 | 
			
		||||
      - 10.0.0.13:2379
 | 
			
		||||
    Key: notification.rpc
 | 
			
		||||
PermissionRPC:
 | 
			
		||||
  Etcd:
 | 
			
		||||
    Hosts:
 | 
			
		||||
      - 10.0.0.13:2379
 | 
			
		||||
    Key: permission.rpc
 | 
			
		||||
 | 
			
		||||
MailSender: service@30cm.net
 | 
			
		||||
 | 
			
		||||
Token:
 | 
			
		||||
  Expired: 86500s
 | 
			
		||||
  RefreshExpired: 86500s
 | 
			
		||||
 | 
			
		||||
Member:
 | 
			
		||||
  ForgetTimeOutInSec: 180
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/config"
 | 
			
		||||
	"biz-member-gateway/internal/handler"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/conf"
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var configFile = flag.String("f", "etc/gateway.yaml", "the config file")
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	var c config.Config
 | 
			
		||||
	conf.MustLoad(*configFile, &c)
 | 
			
		||||
 | 
			
		||||
	server := rest.MustNewServer(c.RestConf)
 | 
			
		||||
	defer server.Stop()
 | 
			
		||||
 | 
			
		||||
	ctx := svc.NewServiceContext(c)
 | 
			
		||||
	handler.RegisterHandlers(server, ctx)
 | 
			
		||||
 | 
			
		||||
	logx.Infof("Starting server at %s:%d...\n", c.Host, c.Port)
 | 
			
		||||
	server.Start()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,785 @@
 | 
			
		|||
{
 | 
			
		||||
  "swagger": "2.0",
 | 
			
		||||
  "info": {
 | 
			
		||||
    "title": "",
 | 
			
		||||
    "version": ""
 | 
			
		||||
  },
 | 
			
		||||
  "host": "dev-api.30cm.net",
 | 
			
		||||
  "schemes": [
 | 
			
		||||
    "http",
 | 
			
		||||
    "https"
 | 
			
		||||
  ],
 | 
			
		||||
  "consumes": [
 | 
			
		||||
    "application/json"
 | 
			
		||||
  ],
 | 
			
		||||
  "produces": [
 | 
			
		||||
    "application/json"
 | 
			
		||||
  ],
 | 
			
		||||
  "paths": {
 | 
			
		||||
    "/api/v1/member": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "summary": "創建新會員",
 | 
			
		||||
        "description": "創建一個全新的帳號,創完成之後會自動登入",
 | 
			
		||||
        "operationId": "AccountCreate",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/LoginTokenResp"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "description": " -------------------------------------------",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/CreateAccountRequest"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ],
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "multipart/form-data"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/check-verify-code": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "summary": "確認邀請 - 綁定會員",
 | 
			
		||||
        "description": "確認驗證碼是否有效",
 | 
			
		||||
        "operationId": "CheckVerifyCode",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/RespOK"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/CheckoutVerifyReq"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/forget-password-code": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "summary": "發送忘記密碼驗證",
 | 
			
		||||
        "description": "發送忘記密碼驗證(三分鐘內只能發一次信)",
 | 
			
		||||
        "operationId": "ForgetPasswordCode",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/RespOK"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/ForgetPasswordCodeReq"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/info": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "summary": "取得會員資訊",
 | 
			
		||||
        "operationId": "Info",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/UserInfo"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "required",
 | 
			
		||||
            "in": "header",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "put": {
 | 
			
		||||
        "summary": "更新會員詳細資訊",
 | 
			
		||||
        "operationId": "ModifyMemberInfo",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/UserInfo"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/BindingUserInfoReq"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/login": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "summary": "登入",
 | 
			
		||||
        "description": "會員登入",
 | 
			
		||||
        "operationId": "Login",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/LoginTokenResp"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/LoginReq"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ],
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "multipart/form-data"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/logout": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "summary": "會員登出",
 | 
			
		||||
        "operationId": "Logout",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/RespOK"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "required",
 | 
			
		||||
            "in": "header",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "type": "string"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/modify-passwd": {
 | 
			
		||||
      "put": {
 | 
			
		||||
        "summary": "修改密碼",
 | 
			
		||||
        "description": "修改密碼",
 | 
			
		||||
        "operationId": "ModifyPasswdHandler",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/RespOK"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/ModifyPasswdReq"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/pre-verify": {
 | 
			
		||||
      "put": {
 | 
			
		||||
        "summary": "預先驗證驗證碼",
 | 
			
		||||
        "description": "忘記密碼的時候看 ui. 流程要預先驗證一次才給送,",
 | 
			
		||||
        "operationId": "PreVerifyUpdatePasswordCode",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/RespOK"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/PreVerifyForgetPasswdReq"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/refresh_access_token": {
 | 
			
		||||
      "put": {
 | 
			
		||||
        "summary": "更新 Access Token",
 | 
			
		||||
        "description": "用 RefreshToken 換取 AccessToken",
 | 
			
		||||
        "operationId": "RefreshAccessToken",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/LoginTokenResp"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/UpdateTokenReq"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/update-password": {
 | 
			
		||||
      "put": {
 | 
			
		||||
        "summary": "更新密碼(要發送驗證碼才可以的流程)",
 | 
			
		||||
        "description": "更新密碼(要發送驗證碼才可以的流程)",
 | 
			
		||||
        "operationId": "UpdatePassword",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/RespOK"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/UpdatePasswordReq"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/api/v1/member/verify": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "summary": "發送邀請 - 綁定會員",
 | 
			
		||||
        "description": "可以依照類別(手機驗證,email驗證),同一個類型十分鐘內只能發送一次",
 | 
			
		||||
        "operationId": "SendVerifyCode",
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "description": "A successful response.",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/RespOK"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "required": true,
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/VerificationCodeRequest"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "member"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "definitions": {
 | 
			
		||||
    "BaseResponse": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "status": {
 | 
			
		||||
          "$ref": "#/definitions/Status",
 | 
			
		||||
          "description": " 狀態"
 | 
			
		||||
        },
 | 
			
		||||
        "data": {
 | 
			
		||||
          "type": "object",
 | 
			
		||||
          "description": " 資料"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "BaseResponse",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "status",
 | 
			
		||||
        "data"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "BindingUserInfoReq": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "preferred_language": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 使用語言"
 | 
			
		||||
        },
 | 
			
		||||
        "currency": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "avatar_url": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 頭像 URL(可選)"
 | 
			
		||||
        },
 | 
			
		||||
        "nickname": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "full_name": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 用戶全名"
 | 
			
		||||
        },
 | 
			
		||||
        "gender_code": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 性別代碼"
 | 
			
		||||
        },
 | 
			
		||||
        "birthdate": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 生日 (格式: unix)"
 | 
			
		||||
        },
 | 
			
		||||
        "address": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 地址"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "BindingUserInfoReq",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "gender_code"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "CheckoutVerifyReq": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "account": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 帳號名稱"
 | 
			
		||||
        },
 | 
			
		||||
        "code_type": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 驗證碼類型 1 信箱 2 手機"
 | 
			
		||||
        },
 | 
			
		||||
        "verify_code": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 驗證碼,長度為6"
 | 
			
		||||
        },
 | 
			
		||||
        "uid": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "CheckoutVerifyReq",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "account",
 | 
			
		||||
        "code_type",
 | 
			
		||||
        "verify_code",
 | 
			
		||||
        "uid"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "CreateAccountRequest": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "account": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 帳號名稱(line code 輸入在這邊)"
 | 
			
		||||
        },
 | 
			
		||||
        "token": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 密碼或平台token,密碼請 sha256 轉碼,如果三方token 請隨便給一個 sha256 字串"
 | 
			
		||||
        },
 | 
			
		||||
        "token_check": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 密碼或平台token,token 請保持原樣,填在這邊,不用管 token"
 | 
			
		||||
        },
 | 
			
		||||
        "platform": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 平台名稱 (platform) 平台、google、line"
 | 
			
		||||
        },
 | 
			
		||||
        "account_type": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 帳號類型 phone(手機)、email(信箱)、platform(自定義帳號) -\u003e (如果為第三方都寫 platform)"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "CreateAccountRequest",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "account",
 | 
			
		||||
        "token",
 | 
			
		||||
        "token_check",
 | 
			
		||||
        "platform",
 | 
			
		||||
        "account_type"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "ForgetPasswordCodeReq": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "account": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 帳號名稱"
 | 
			
		||||
        },
 | 
			
		||||
        "account_type": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 帳號類型 (phone) 手機 (email) 信箱"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "ForgetPasswordCodeReq",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "account",
 | 
			
		||||
        "account_type"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "LoginReq": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "account": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 帳號名稱"
 | 
			
		||||
        },
 | 
			
		||||
        "token": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 密碼或平台token,密碼請 sha256 轉碼"
 | 
			
		||||
        },
 | 
			
		||||
        "platform": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 平台名稱 platform, google"
 | 
			
		||||
        },
 | 
			
		||||
        "account_type": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 帳號類型 1 手機 2 信箱 3 自定義帳號"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "LoginReq",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "account",
 | 
			
		||||
        "token",
 | 
			
		||||
        "platform",
 | 
			
		||||
        "account_type"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "LoginTokenResp": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "uid": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": "  Account"
 | 
			
		||||
        },
 | 
			
		||||
        "access_token": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 訪問令牌 預設 5 分鐘過期"
 | 
			
		||||
        },
 | 
			
		||||
        "refresh_token": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 刷新令牌 (預設一天過期,只能用一次),當呼叫更新token api 時,會自動把舊的失效,變成新的 refresh_token ,前端要記得過其實協助刷新,刷新不過表示全失效了(重新登入)"
 | 
			
		||||
        },
 | 
			
		||||
        "token_type": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " Bearer"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "LoginTokenResp",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "uid",
 | 
			
		||||
        "access_token",
 | 
			
		||||
        "refresh_token",
 | 
			
		||||
        "token_type"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "MemberLoginHeader": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "title": "MemberLoginHeader"
 | 
			
		||||
    },
 | 
			
		||||
    "ModifyPasswdReq": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "token": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 密碼或平台token,密碼請 sha256 轉碼"
 | 
			
		||||
        },
 | 
			
		||||
        "token_check": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 密碼或平台token,密碼請 sha256 轉碼"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "ModifyPasswdReq",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "token",
 | 
			
		||||
        "token_check"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "PreVerifyForgetPasswdReq": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "identifier": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 聯繫方式,可以是 email 或 phone"
 | 
			
		||||
        },
 | 
			
		||||
        "verify_code": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 驗證碼,長度為6"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "PreVerifyForgetPasswdReq",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "identifier",
 | 
			
		||||
        "verify_code"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "RespOK": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "title": "RespOK"
 | 
			
		||||
    },
 | 
			
		||||
    "Status": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "code": {
 | 
			
		||||
          "type": "integer",
 | 
			
		||||
          "format": "int64",
 | 
			
		||||
          "description": " 狀態碼"
 | 
			
		||||
        },
 | 
			
		||||
        "message": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 訊息"
 | 
			
		||||
        },
 | 
			
		||||
        "data": {
 | 
			
		||||
          "type": "object",
 | 
			
		||||
          "description": " 可選的數據,當有返回時才出現"
 | 
			
		||||
        },
 | 
			
		||||
        "error": {
 | 
			
		||||
          "type": "object",
 | 
			
		||||
          "description": " 可選的錯誤信息"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "Status",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "code",
 | 
			
		||||
        "message"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "UpdatePasswordReq": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "account": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 帳號名稱"
 | 
			
		||||
        },
 | 
			
		||||
        "verify_code": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 驗證碼,長度為6"
 | 
			
		||||
        },
 | 
			
		||||
        "token": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 密碼或平台token,密碼請 sha256 轉碼"
 | 
			
		||||
        },
 | 
			
		||||
        "token_check": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 密碼或平台token,密碼請 sha256 轉碼"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "UpdatePasswordReq",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "account",
 | 
			
		||||
        "verify_code",
 | 
			
		||||
        "token",
 | 
			
		||||
        "token_check"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "UpdateTokenReq": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "uid": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 誰要更新"
 | 
			
		||||
        },
 | 
			
		||||
        "token": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " access token -\u003e 已過期要被更新的"
 | 
			
		||||
        },
 | 
			
		||||
        "refresh_token": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " refresh token -\u003e  重點,要驗證他的"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "UpdateTokenReq",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "uid",
 | 
			
		||||
        "token",
 | 
			
		||||
        "refresh_token"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "UserInfo": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "platform": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 用戶平台 platform, google, line"
 | 
			
		||||
        },
 | 
			
		||||
        "uid": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 用戶 UID"
 | 
			
		||||
        },
 | 
			
		||||
        "avatar_url": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 頭像 URL(可選)"
 | 
			
		||||
        },
 | 
			
		||||
        "full_name": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 用戶全名"
 | 
			
		||||
        },
 | 
			
		||||
        "nickname": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 暱稱(可選)"
 | 
			
		||||
        },
 | 
			
		||||
        "gender_code": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 性別代碼 mail, femail ,sec"
 | 
			
		||||
        },
 | 
			
		||||
        "birthdate": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 生日 (格式: 19930417)"
 | 
			
		||||
        },
 | 
			
		||||
        "phone_number": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 電話"
 | 
			
		||||
        },
 | 
			
		||||
        "address": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 地址"
 | 
			
		||||
        },
 | 
			
		||||
        "email": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 驗證後的信箱"
 | 
			
		||||
        },
 | 
			
		||||
        "alarm_category": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 告警狀態"
 | 
			
		||||
        },
 | 
			
		||||
        "user_status": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 用戶狀態"
 | 
			
		||||
        },
 | 
			
		||||
        "preferred_language": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 使用語言"
 | 
			
		||||
        },
 | 
			
		||||
        "currency": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 使用幣種"
 | 
			
		||||
        },
 | 
			
		||||
        "update_at": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "create_at": {
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "UserInfo",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "platform",
 | 
			
		||||
        "uid",
 | 
			
		||||
        "avatar_url",
 | 
			
		||||
        "full_name",
 | 
			
		||||
        "nickname",
 | 
			
		||||
        "gender_code",
 | 
			
		||||
        "birthdate",
 | 
			
		||||
        "phone_number",
 | 
			
		||||
        "address",
 | 
			
		||||
        "email",
 | 
			
		||||
        "alarm_category",
 | 
			
		||||
        "user_status",
 | 
			
		||||
        "preferred_language",
 | 
			
		||||
        "currency",
 | 
			
		||||
        "update_at",
 | 
			
		||||
        "create_at"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "VerificationCodeRequest": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "identifier": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 聯繫方式,可以是 email 或 phone"
 | 
			
		||||
        },
 | 
			
		||||
        "code_type": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "description": " 驗證碼類型"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "title": "VerificationCodeRequest",
 | 
			
		||||
      "required": [
 | 
			
		||||
        "identifier",
 | 
			
		||||
        "code_type"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "VerifyHeader": {
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "title": "VerifyHeader"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "securityDefinitions": {
 | 
			
		||||
    "apiKey": {
 | 
			
		||||
      "type": "apiKey",
 | 
			
		||||
      "description": "Enter JWT Bearer token **_only_**",
 | 
			
		||||
      "name": "Authorization",
 | 
			
		||||
      "in": "header"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
syntax = "v1"
 | 
			
		||||
 | 
			
		||||
type Status {
 | 
			
		||||
	Code    int64       `json:"code"` // 狀態碼
 | 
			
		||||
	Message string      `json:"message"` // 訊息
 | 
			
		||||
	Data    interface{} `json:"data,omitempty"` // 可選的數據,當有返回時才出現
 | 
			
		||||
	Error   interface{} `json:"error,omitempty"` // 可選的錯誤信息
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BaseResponse {
 | 
			
		||||
	Status Status      `json:"status"` // 狀態
 | 
			
		||||
	Data   interface{} `json:"data"` // 資料
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type VerifyHeader {
 | 
			
		||||
	Token string `header:"token" validate:"required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RespOK {}
 | 
			
		||||
 | 
			
		||||
import "member.api"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,252 @@
 | 
			
		|||
syntax = "v1"
 | 
			
		||||
 | 
			
		||||
info(
 | 
			
		||||
	title: "Portal-Api-Gateway (PGW)"
 | 
			
		||||
	desc: "netpute web portal api gateway"
 | 
			
		||||
	author: "daniel wang"
 | 
			
		||||
	email: "daniel.wang@30cm.net"
 | 
			
		||||
	version: "0.0.1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MemberLoginHeader {
 | 
			
		||||
	DeviceID    string `header:"device_id"`
 | 
			
		||||
	IpAddress	string `header:"ip_address"`
 | 
			
		||||
	Brewser     string `header:"brewser"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// -------------------------------------------
 | 
			
		||||
 | 
			
		||||
type CreateAccountRequest {
 | 
			
		||||
	Account     string `json:"account" validate:"required"`                            // 帳號名稱(line code 輸入在這邊)
 | 
			
		||||
	Token       string `json:"token" validate:"required"`                              // 密碼或平台token,密碼請 sha256 轉碼,如果三方token 請隨便給一個 sha256 字串
 | 
			
		||||
	TokenCheck  string `json:"token_check" validate:"required"` 		               // 密碼或平台token,token 請保持原樣,填在這邊,不用管 token
 | 
			
		||||
	Platform    string `json:"platform" validate:"oneof=platform google line"`         // 平台名稱 (platform) 平台、google、line
 | 
			
		||||
	AccountType string `json:"account_type" validate:"oneof=phone email platform"`     // 帳號類型 phone(手機)、email(信箱)、platform(自定義帳號) -> (如果為第三方都寫 platform)
 | 
			
		||||
	MemberLoginHeader
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoginReq {
 | 
			
		||||
	Account     string `json:"account" validate:"required"`                         // 帳號名稱
 | 
			
		||||
	Token       string `json:"token"`                                              // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
	Platform    string `json:"platform" validate:"oneof=platform google line"`     // 平台名稱 platform, google
 | 
			
		||||
	AccountType string `json:"account_type" validate:"oneof=phone email platform"` // 帳號類型 1 手機 2 信箱 3 自定義帳號
 | 
			
		||||
	MemberLoginHeader
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoginTokenResp {
 | 
			
		||||
	UID          string `json:"uid"`           //  Account
 | 
			
		||||
	AccessToken  string `json:"access_token"`  // 訪問令牌 預設 5 分鐘過期
 | 
			
		||||
	RefreshToken string `json:"refresh_token"` // 刷新令牌 (預設一天過期,只能用一次),當呼叫更新token api 時,會自動把舊的失效,變成新的 refresh_token ,前端要記得過其實協助刷新,刷新不過表示全失效了(重新登入)
 | 
			
		||||
	TokenType    string `json:"token_type"`    // Bearer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type ForgetPasswordCodeReq {
 | 
			
		||||
	Account     string `json:"account" validate:"required"`                     // 帳號名稱
 | 
			
		||||
	AccountType string `json:"account_type" validate:"oneof=phone email"` 		// 帳號類型 (phone) 手機 (email) 信箱
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PreVerifyForgetPasswdReq {
 | 
			
		||||
	Identifier string `json:"identifier" validate:"required"`         // 聯繫方式,可以是 email 或 phone
 | 
			
		||||
	VerifyCode string `json:"verify_code" validate:"required,len=6"`  // 驗證碼,長度為6
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateTokenReq {
 | 
			
		||||
	UID          string `json:"uid" validate:"required"`           // 誰要更新
 | 
			
		||||
	Token        string `json:"token" validate:"required"`         // access token -> 已過期要被更新的
 | 
			
		||||
	RefreshToken string `json:"refresh_token" validate:"required"` // refresh token ->  重點,要驗證他的
 | 
			
		||||
																   //	MemberLoginHeader
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdatePasswordReq {
 | 
			
		||||
	Account    string `json:"account" validate:"required"`           		// 帳號名稱
 | 
			
		||||
	VerifyCode string `json:"verify_code" validate:"required,len=6"` 		// 驗證碼,長度為6
 | 
			
		||||
	Token      string `json:"token" validate:"required,len=64"`             // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
	TokenCheck string `json:"token_check" validate:"required,len=64"`       // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@server(
 | 
			
		||||
	group:  member
 | 
			
		||||
	prefix: /api/v1
 | 
			
		||||
	schemes: https
 | 
			
		||||
	timeout: 10s
 | 
			
		||||
)
 | 
			
		||||
service gateway {
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary:"創建新會員"
 | 
			
		||||
		description: "創建一個全新的帳號,創完成之後會自動登入"
 | 
			
		||||
	)
 | 
			
		||||
	@handler AccountCreate
 | 
			
		||||
	post /member (CreateAccountRequest) returns (LoginTokenResp)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-401 (BaseResponse) // 輸入的帳號密碼未經驗證-> 帳號密碼錯誤的意思 */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary:"登入"
 | 
			
		||||
		description: "會員登入"
 | 
			
		||||
	)
 | 
			
		||||
	@handler Login
 | 
			
		||||
	post /member/login (LoginReq) returns (LoginTokenResp)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary:"發送忘記密碼驗證"
 | 
			
		||||
		description: "發送忘記密碼驗證(三分鐘內只能發一次信)"
 | 
			
		||||
	)
 | 
			
		||||
	@handler ForgetPasswordCode
 | 
			
		||||
	post /member/forget-password-code (ForgetPasswordCodeReq) returns (RespOK)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-401 (BaseResponse) // 無效的驗證碼 */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary:"更新密碼(要發送驗證碼才可以的流程)"
 | 
			
		||||
		description: "更新密碼(要發送驗證碼才可以的流程)"
 | 
			
		||||
	)
 | 
			
		||||
	@handler UpdatePassword
 | 
			
		||||
	put /member/update-password (UpdatePasswordReq) returns (RespOK)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-401 (BaseResponse) // 無效的驗證碼 */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary:"預先驗證驗證碼"
 | 
			
		||||
		description: "忘記密碼的時候看 ui. 流程要預先驗證一次才給送,"
 | 
			
		||||
	)
 | 
			
		||||
	@handler PreVerifyUpdatePasswordCode
 | 
			
		||||
	put /member/pre-verify (PreVerifyForgetPasswdReq) returns (RespOK)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-401 (BaseResponse) // 無效的 Refresh Token */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary:"更新 Access Token"
 | 
			
		||||
		description: "用 RefreshToken 換取 AccessToken"
 | 
			
		||||
	)
 | 
			
		||||
	@handler RefreshAccessToken
 | 
			
		||||
	put /member/refresh_access_token (UpdateTokenReq) returns (LoginTokenResp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ------------------- 要登入之後才可以做的事情 ------------------------
 | 
			
		||||
 | 
			
		||||
type UserInfo {
 | 
			
		||||
	Platform 		  string  `json:"platform"`               // 用戶平台 platform, google, line
 | 
			
		||||
	UID               string  `json:"uid"`                    // 用戶 UID
 | 
			
		||||
	AvatarURL         string  `json:"avatar_url"`             // 頭像 URL(可選)
 | 
			
		||||
	FullName          string  `json:"full_name"`              // 用戶全名
 | 
			
		||||
	Nickname          string  `json:"nickname"`               // 暱稱(可選)
 | 
			
		||||
	GenderCode        string  `json:"gender_code"`            // 性別代碼 mail, femail ,sec
 | 
			
		||||
	Birthdate         string  `json:"birthdate"`              // 生日 (格式: 19930417)
 | 
			
		||||
	PhoneNumber       string  `json:"phone_number"`           // 電話
 | 
			
		||||
	Address           string  `json:"address"`                // 地址
 | 
			
		||||
	Email             string  `json:"email"`                  // 驗證後的信箱
 | 
			
		||||
	AlarmCategory     string  `json:"alarm_category"`         // 告警狀態
 | 
			
		||||
	UserStatus        string  `json:"user_status"`            // 用戶狀態
 | 
			
		||||
	PreferredLanguage string  `json:"preferred_language"`     // 使用語言
 | 
			
		||||
	Currency          string  `json:"currency"`               // 使用幣種
 | 
			
		||||
	UpdateAt          string  `json:"update_at"`
 | 
			
		||||
	CreateAt          string  `json:"create_at"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BindingUserInfoReq {
 | 
			
		||||
	VerifyHeader
 | 
			
		||||
	PreferredLanguage string  `json:"preferred_language,optional" validate:"oneof=zh-tw en-us"` // 使用語言
 | 
			
		||||
	Currency          string  `json:"currency,optional" validate:"oneof=TWD USD"`
 | 
			
		||||
	AvatarURL         string  `json:"avatar_url,optional"`                                      // 頭像 URL(可選)
 | 
			
		||||
	Nickname          string  `json:"nickname,optional"`
 | 
			
		||||
	FullName          string  `json:"full_name,optional"`                                       // 用戶全名
 | 
			
		||||
	GenderCode        string  `json:"gender_code" validate:"oneof=secret male female"`          // 性別代碼
 | 
			
		||||
	Birthday         string  `json:"birthday,optional" validate:"rfc3339"`                    // 生日 (格式: unix)
 | 
			
		||||
	Address           string  `json:"address,optional"`                                         // 地址
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type VerificationCodeRequest {
 | 
			
		||||
	VerifyHeader
 | 
			
		||||
	Identifier 		string 	  `json:"identifier" validate:"required"`                         // 聯繫方式,可以是 email 或 phone
 | 
			
		||||
	CodeType   		string    `json:"code_type" validate:"oneof=email phone forget_password"` // 驗證碼類型
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CheckoutVerifyReq {
 | 
			
		||||
	VerifyHeader
 | 
			
		||||
	Account    string `json:"account" validate:"required"`            // 帳號名稱
 | 
			
		||||
	CodeType   string `json:"code_type" validate:"oneof=email phone"` // 驗證碼類型 1 信箱 2 手機
 | 
			
		||||
	VerifyCode string `json:"verify_code" validate:"required,len=6"`  // 驗證碼,長度為6
 | 
			
		||||
	UID string `json:"uid" validate:"required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ModifyPasswdReq {
 | 
			
		||||
	VerifyHeader
 | 
			
		||||
	NewToken      string `json:"token" validate:"required,len=64"`             // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
	NewTokenCheck string `json:"token_check" validate:"required,len=64"`       // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@server(
 | 
			
		||||
	group:  member
 | 
			
		||||
	prefix: /api/v1
 | 
			
		||||
	schemes: https
 | 
			
		||||
	timeout: 10s
 | 
			
		||||
	middleware: AuthMiddleware
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
service gateway {
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-401 (BaseResponse) // 無效的Token */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary: "會員登出"
 | 
			
		||||
	)
 | 
			
		||||
	@handler Logout
 | 
			
		||||
	get /member/logout (VerifyHeader) returns (RespOK)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-401 (BaseResponse) // 無效的Token */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary: "取得會員資訊"
 | 
			
		||||
	)
 | 
			
		||||
	@handler Info
 | 
			
		||||
	get /member/info (VerifyHeader) returns (UserInfo)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-403 (BaseResponse) // 無效的Token */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary: "更新會員詳細資訊"
 | 
			
		||||
	)
 | 
			
		||||
	@handler ModifyMemberInfo
 | 
			
		||||
	put /member/info (BindingUserInfoReq) returns (UserInfo)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-401 (BaseResponse) // 無效的Token */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary: "發送邀請 - 綁定會員"
 | 
			
		||||
		description: "可以依照類別(手機驗證,email驗證),同一個類型十分鐘內只能發送一次"
 | 
			
		||||
	)
 | 
			
		||||
	@handler SendVerifyCode
 | 
			
		||||
	post /member/verify (VerificationCodeRequest) returns (RespOK)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-401 (BaseResponse) // 無效的驗證碼 */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary: "確認邀請 - 綁定會員"
 | 
			
		||||
		description: "確認驗證碼是否有效"
 | 
			
		||||
	)
 | 
			
		||||
	@handler CheckVerifyCode
 | 
			
		||||
	post /member/check-verify-code (CheckoutVerifyReq) returns (RespOK)
 | 
			
		||||
 | 
			
		||||
/*	@respdoc-400 (BaseResponse) // 輸入的參數錯誤 */
 | 
			
		||||
/*	@respdoc-401 (BaseResponse) // 無效的驗證碼 */
 | 
			
		||||
/*  @respdoc-500 (BaseResponse) // 伺服器出錯 */
 | 
			
		||||
	@doc(
 | 
			
		||||
		summary: "修改密碼"
 | 
			
		||||
		description: "修改密碼"
 | 
			
		||||
	)
 | 
			
		||||
	@handler ModifyPasswdHandler
 | 
			
		||||
	put /member/modify-passwd (ModifyPasswdReq) returns (RespOK)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,126 @@
 | 
			
		|||
module biz-member-gateway
 | 
			
		||||
 | 
			
		||||
go 1.24.0
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	code.30cm.net/digimon/app-cloudep-member-server v0.0.0-20250208060713-714163bbe6c6
 | 
			
		||||
	code.30cm.net/digimon/app-cloudep-notification-service v0.0.2
 | 
			
		||||
	code.30cm.net/digimon/app-cloudep-permission-server v0.0.2
 | 
			
		||||
	code.30cm.net/digimon/library-go/errors v1.2.2
 | 
			
		||||
	code.30cm.net/digimon/library-go/errs v1.2.14
 | 
			
		||||
	code.30cm.net/digimon/library-go/validator v1.0.0
 | 
			
		||||
	code.30cm.net/digimon/proto-all v0.0.0-20250309093454-2a2927ced9e2
 | 
			
		||||
	github.com/go-playground/validator/v10 v10.25.0
 | 
			
		||||
	github.com/golang/protobuf v1.5.4
 | 
			
		||||
	github.com/grafana/pyroscope-go v1.2.0
 | 
			
		||||
	github.com/shopspring/decimal v1.4.0
 | 
			
		||||
	github.com/zeromicro/go-zero v1.8.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Masterminds/semver v1.4.2 // indirect
 | 
			
		||||
	github.com/Masterminds/sprig v2.16.0+incompatible // indirect
 | 
			
		||||
	github.com/PuerkitoBio/goquery v1.5.0 // indirect
 | 
			
		||||
	github.com/andybalholm/cascadia v1.0.0 // indirect
 | 
			
		||||
	github.com/aokoli/goutils v1.0.1 // 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
 | 
			
		||||
	github.com/coreos/go-semver v0.3.1 // indirect
 | 
			
		||||
	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
 | 
			
		||||
	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.18.0 // indirect
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.4.8 // 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/gogo/protobuf v1.3.2 // indirect
 | 
			
		||||
	github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
 | 
			
		||||
	github.com/golang/mock v1.6.0 // 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/gorilla/css v1.0.0 // indirect
 | 
			
		||||
	github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
 | 
			
		||||
	github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
 | 
			
		||||
	github.com/huandu/xstrings v1.2.0 // indirect
 | 
			
		||||
	github.com/imdario/mergo v0.3.6 // indirect
 | 
			
		||||
	github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 // indirect
 | 
			
		||||
	github.com/josharian/intern v1.0.0 // indirect
 | 
			
		||||
	github.com/json-iterator/go v1.1.12 // indirect
 | 
			
		||||
	github.com/klauspost/compress v1.17.11 // indirect
 | 
			
		||||
	github.com/leodido/go-urn v1.4.0 // indirect
 | 
			
		||||
	github.com/mailru/easyjson v0.7.7 // indirect
 | 
			
		||||
	github.com/matcornic/hermes/v2 v2.1.0 // indirect
 | 
			
		||||
	github.com/mattn/go-colorable v0.1.13 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.20 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.15 // indirect
 | 
			
		||||
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
			
		||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
			
		||||
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.5 // indirect
 | 
			
		||||
	github.com/openzipkin/zipkin-go v0.4.3 // indirect
 | 
			
		||||
	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
 | 
			
		||||
	github.com/prometheus/client_golang v1.21.0 // indirect
 | 
			
		||||
	github.com/prometheus/client_model v0.6.1 // indirect
 | 
			
		||||
	github.com/prometheus/common v0.62.0 // indirect
 | 
			
		||||
	github.com/prometheus/procfs v0.15.1 // indirect
 | 
			
		||||
	github.com/redis/go-redis/v9 v9.7.1 // indirect
 | 
			
		||||
	github.com/rivo/uniseg v0.2.0 // indirect
 | 
			
		||||
	github.com/russross/blackfriday/v2 v2.0.1 // indirect
 | 
			
		||||
	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
 | 
			
		||||
	github.com/spaolacci/murmur3 v1.1.0 // indirect
 | 
			
		||||
	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
 | 
			
		||||
	github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04 // indirect
 | 
			
		||||
	github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe // 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.mongodb.org/mongo-driver v1.17.3 // 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.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.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.33.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.35.0 // indirect
 | 
			
		||||
	golang.org/x/oauth2 v0.24.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.30.0 // indirect
 | 
			
		||||
	golang.org/x/term v0.29.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.22.0 // indirect
 | 
			
		||||
	golang.org/x/time v0.10.0 // 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
 | 
			
		||||
	google.golang.org/grpc v1.70.0 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.36.5 // 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
 | 
			
		||||
	k8s.io/api v0.29.3 // indirect
 | 
			
		||||
	k8s.io/apimachinery v0.29.4 // indirect
 | 
			
		||||
	k8s.io/client-go v0.29.3 // indirect
 | 
			
		||||
	k8s.io/klog/v2 v2.110.1 // indirect
 | 
			
		||||
	k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
 | 
			
		||||
	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.4.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,366 @@
 | 
			
		|||
code.30cm.net/digimon/app-cloudep-member-server v0.0.0-20250208060713-714163bbe6c6 h1:yDeI9SQyPd2XDHoxpaLavw5AU0s4NPNa4is9G/ZbxNM=
 | 
			
		||||
code.30cm.net/digimon/app-cloudep-member-server v0.0.0-20250208060713-714163bbe6c6/go.mod h1:4r33vmU2j7PZWzWFIn+7NAE05fuF/6/kdVdfnyHmf6Y=
 | 
			
		||||
code.30cm.net/digimon/app-cloudep-notification-service v0.0.2 h1:6GkX4oPXbftD4DV1wBM0e0SD9KdfdcNTxLSEASzEaWo=
 | 
			
		||||
code.30cm.net/digimon/app-cloudep-notification-service v0.0.2/go.mod h1:TdUdjsHafpkrQl6AKPDv3VdH1tSX4f+UWqnW5TaPvsM=
 | 
			
		||||
code.30cm.net/digimon/app-cloudep-permission-server v0.0.2 h1:1n8RVs1/R3z1WA7uPPUC+EuQIrIqHA9Uj8Cy2eFP2rg=
 | 
			
		||||
code.30cm.net/digimon/app-cloudep-permission-server v0.0.2/go.mod h1:ga0L3jN4FspRHfifGpT2nIzW0APPgzQqv5tBLf2JM2Q=
 | 
			
		||||
code.30cm.net/digimon/library-go/errors v1.2.2 h1:InY2PB+6eSZi91n6dcnAwsihOQuqe+GrrDMKlHJPxXA=
 | 
			
		||||
code.30cm.net/digimon/library-go/errors v1.2.2/go.mod h1:4yVE3lyHi/cLB3W59Tw70FBqO10ZhXo470NWp7aQ0tc=
 | 
			
		||||
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/validator v1.0.0 h1:F48CU0+Z4ZZFhKHvjgm4u0M7v5wanFfVLKz+d92o+ks=
 | 
			
		||||
code.30cm.net/digimon/library-go/validator v1.0.0/go.mod h1:haNzZMm0PhdvhuX8R6N/ZMp4CjOhs7/dHgTHzeOTNrY=
 | 
			
		||||
code.30cm.net/digimon/proto-all v0.0.0-20250309093454-2a2927ced9e2 h1:eF4/vGVf9Q+EW1wVMSeklXbHm4Hvm38Iwlz+VaDh9YM=
 | 
			
		||||
code.30cm.net/digimon/proto-all v0.0.0-20250309093454-2a2927ced9e2/go.mod h1:jyO0y8lBk81kiBd5BP5bDo1rRDkKBN1983FnAANocR8=
 | 
			
		||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
 | 
			
		||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 | 
			
		||||
github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY=
 | 
			
		||||
github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
 | 
			
		||||
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/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
 | 
			
		||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 | 
			
		||||
github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
 | 
			
		||||
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
 | 
			
		||||
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/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 | 
			
		||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
 | 
			
		||||
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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 | 
			
		||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 | 
			
		||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 | 
			
		||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
 | 
			
		||||
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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
			
		||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 | 
			
		||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 | 
			
		||||
github.com/grafana/pyroscope-go v1.2.0 h1:aILLKjTj8CS8f/24OPMGPewQSYlhmdQMBmol1d3KGj8=
 | 
			
		||||
github.com/grafana/pyroscope-go v1.2.0/go.mod h1:2GHr28Nr05bg2pElS+dDsc98f3JTUh2f6Fz1hWXrqwk=
 | 
			
		||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
 | 
			
		||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
 | 
			
		||||
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/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
 | 
			
		||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
 | 
			
		||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
 | 
			
		||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 | 
			
		||||
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 h1:xqgexXAGQgY3HAjNPSaCqn5Aahbo5TKsmhp8VRfr1iQ=
 | 
			
		||||
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
 | 
			
		||||
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.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
 | 
			
		||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 | 
			
		||||
github.com/kr/pretty v0.3.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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 | 
			
		||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 | 
			
		||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 | 
			
		||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
			
		||||
github.com/matcornic/hermes/v2 v2.1.0 h1:9TDYFBPFv6mcXanaDmRDEp/RTWj0dTTi+LpFnnnfNWc=
 | 
			
		||||
github.com/matcornic/hermes/v2 v2.1.0/go.mod h1:2+ziJeoyRfaLiATIL8VZ7f9hpzH4oDHqTmn0bhrsgVI=
 | 
			
		||||
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/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
 | 
			
		||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 | 
			
		||||
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/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 | 
			
		||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 | 
			
		||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 | 
			
		||||
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.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA=
 | 
			
		||||
github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
 | 
			
		||||
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.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
 | 
			
		||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
 | 
			
		||||
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.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
 | 
			
		||||
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
 | 
			
		||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 | 
			
		||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 | 
			
		||||
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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
 | 
			
		||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
			
		||||
github.com/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/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
 | 
			
		||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
 | 
			
		||||
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
			
		||||
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/vanng822/css v0.0.0-20190504095207-a21e860bcd04 h1:L0rPdfzq43+NV8rfIx2kA4iSSLRj2jN5ijYHoeXRwvQ=
 | 
			
		||||
github.com/vanng822/css v0.0.0-20190504095207-a21e860bcd04/go.mod h1:tcnB1voG49QhCrwq1W0w5hhGasvOg+VQp9i9H1rCM1w=
 | 
			
		||||
github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe h1:9YnI5plmy+ad6BM+JCLJb2ZV7/TNiE5l7SNKfumYKgc=
 | 
			
		||||
github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe/go.mod h1:JTFJA/t820uFDoyPpErFQ3rb3amdZoPtxcKervG0OE4=
 | 
			
		||||
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.1 h1:iUYQEMQzS9Pb8ebzJtV3FGtv/YTjZxAh/NvLW/316wo=
 | 
			
		||||
github.com/zeromicro/go-zero v1.8.1/go.mod h1:gc54Ad4qt7OJ0PbKajnYsSKsZBYN4JLRIXKlqDX2A2I=
 | 
			
		||||
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.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
 | 
			
		||||
go.opentelemetry.io/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.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.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.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=
 | 
			
		||||
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/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-20181029175232-7e6ffbd03851/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
 | 
			
		||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
 | 
			
		||||
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-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
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.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
 | 
			
		||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
 | 
			
		||||
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-20190225065934-cc5685c2db12/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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
 | 
			
		||||
golang.org/x/sys v0.30.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.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
 | 
			
		||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
 | 
			
		||||
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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
 | 
			
		||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
 | 
			
		||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
 | 
			
		||||
golang.org/x/time v0.10.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.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
 | 
			
		||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 | 
			
		||||
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-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=
 | 
			
		||||
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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 | 
			
		||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 | 
			
		||||
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.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
 | 
			
		||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/stores/redis"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest"
 | 
			
		||||
	"github.com/zeromicro/go-zero/zrpc"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	rest.RestConf
 | 
			
		||||
	MemberRPC       zrpc.RpcClientConf
 | 
			
		||||
	PermissionRPC   zrpc.RpcClientConf
 | 
			
		||||
	NotificationRPC zrpc.RpcClientConf
 | 
			
		||||
	// PyroScope 監控
 | 
			
		||||
	PyroScope struct {
 | 
			
		||||
		Enable bool
 | 
			
		||||
		URL    string
 | 
			
		||||
		Token  string
 | 
			
		||||
	}
 | 
			
		||||
	Token struct {
 | 
			
		||||
		Expired        time.Duration
 | 
			
		||||
		RefreshExpired time.Duration
 | 
			
		||||
	}
 | 
			
		||||
	// Redis Cluster
 | 
			
		||||
	RedisCfg redis.RedisConf
 | 
			
		||||
	Member   struct {
 | 
			
		||||
		ForgetTimeOutInSec int64
 | 
			
		||||
	}
 | 
			
		||||
	MailSender string
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
package domain
 | 
			
		||||
 | 
			
		||||
const SuccessCode = 10200
 | 
			
		||||
const SuccessMessage = "success"
 | 
			
		||||
 | 
			
		||||
const DefaultLang = "zh-tw"
 | 
			
		||||
const DefaultCurrency = "USDT"
 | 
			
		||||
const DefaultRole = "user"
 | 
			
		||||
const TokenTypeBearer = "Bearer"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
package domain
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	APIErrorCode = 1 + iota
 | 
			
		||||
	TokenNotTheSameAPIErrorCode
 | 
			
		||||
	FailedToCheckVerifyCodeErrorCode
 | 
			
		||||
	FailedToSetVerifyCodeErrorCode
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
package domain
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 字串轉數字的對應表
 | 
			
		||||
var genderStringToCode = map[string]int64{
 | 
			
		||||
	"secret": 0,
 | 
			
		||||
	"male":   1,
 | 
			
		||||
	"female": 2,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 數字轉字串的對應表
 | 
			
		||||
var genderCodeToString = map[int64]string{
 | 
			
		||||
	0: "secret",
 | 
			
		||||
	1: "male",
 | 
			
		||||
	2: "female",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StringToGenderCode 將 "male"/"female"/"secret" 轉為對應數字
 | 
			
		||||
func StringToGenderCode(gender string) (int64, error) {
 | 
			
		||||
	code, ok := genderStringToCode[gender]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return -1, fmt.Errorf("invalid gender string")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return code, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GenderCodeToString 將 0/1/2 轉為對應字串 "male"/"female"/"secret"
 | 
			
		||||
func GenderCodeToString(code int64) (string, error) {
 | 
			
		||||
	gender, ok := genderCodeToString[code]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", fmt.Errorf("invalid gender code")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return gender, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
package domain
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func UID(ctx context.Context) string {
 | 
			
		||||
	if uid, ok := ctx.Value(token.UID.String()).(string); ok {
 | 
			
		||||
		return uid
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Scope(ctx context.Context) string {
 | 
			
		||||
	if scope, ok := ctx.Value(token.Scope.String()).(string); ok {
 | 
			
		||||
		return scope
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Role(ctx context.Context) string {
 | 
			
		||||
	if role, ok := ctx.Value(token.Role.String()).(string); ok {
 | 
			
		||||
		return role
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DeviceID(ctx context.Context) string {
 | 
			
		||||
	if deviceID, ok := ctx.Value(token.Device.String()).(string); ok {
 | 
			
		||||
		return deviceID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Account(ctx context.Context) string {
 | 
			
		||||
	if account, ok := ctx.Value(token.Account.String()).(string); ok {
 | 
			
		||||
		return account
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
package domain
 | 
			
		||||
 | 
			
		||||
import "strings"
 | 
			
		||||
 | 
			
		||||
type RedisKey string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	SendCodeRedisKey RedisKey = "vc"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (key RedisKey) ToString() string {
 | 
			
		||||
	return "biz-member-gw:" + string(key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (key RedisKey) With(s ...string) RedisKey {
 | 
			
		||||
	parts := append([]string{string(key)}, s...)
 | 
			
		||||
 | 
			
		||||
	return RedisKey(strings.Join(parts, ":"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetSendCodeRedisKey(id string) string {
 | 
			
		||||
	return SendCodeRedisKey.With(id).ToString()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,68 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AccountCreateHandler 創建新會員
 | 
			
		||||
func AccountCreateHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.CreateAccountRequest
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewAccountCreateLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.AccountCreate(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,79 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CheckVerifyCodeHandler 確認邀請 - 綁定會員
 | 
			
		||||
func CheckVerifyCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.CheckoutVerifyReq
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewCheckVerifyCodeLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.CheckVerifyCode(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 發送忘記密碼驗證
 | 
			
		||||
func ForgetPasswordCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.ForgetPasswordCodeReq
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewForgetPasswordCodeLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.ForgetPasswordCode(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 取得會員資訊
 | 
			
		||||
func InfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.VerifyHeader
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewInfoLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.Info(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 登入
 | 
			
		||||
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.LoginReq
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewLoginLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.Login(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 會員登出
 | 
			
		||||
func LogoutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.VerifyHeader
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewLogoutLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.Logout(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 更新會員詳細資訊
 | 
			
		||||
func ModifyMemberInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.BindingUserInfoReq
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewModifyMemberInfoLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.ModifyMemberInfo(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 修改密碼
 | 
			
		||||
func ModifyPasswdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.ModifyPasswdReq
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewModifyPasswdLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.ModifyPasswd(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 預先驗證驗證碼
 | 
			
		||||
func PreVerifyUpdatePasswordCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.PreVerifyForgetPasswdReq
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewPreVerifyUpdatePasswordCodeLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.PreVerifyUpdatePasswordCode(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 更新 Access Token
 | 
			
		||||
func RefreshAccessTokenHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.UpdateTokenReq
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewRefreshAccessTokenLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.RefreshAccessToken(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SendVerifyCodeHandler 發送邀請 - 綁定會員
 | 
			
		||||
func SendVerifyCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.VerificationCodeRequest
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewSendVerifyCodeLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.SendVerifyCode(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/logic/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 更新密碼(要發送驗證碼才可以的流程)
 | 
			
		||||
func UpdatePasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var req types.UpdatePasswordReq
 | 
			
		||||
		if err := httpx.Parse(r, &req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := svcCtx.Validate.ValidateAll(req); err != nil {
 | 
			
		||||
			e := ers.InvalidFormat(err.Error())
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: err.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		l := member.NewUpdatePasswordLogic(r.Context(), svcCtx)
 | 
			
		||||
		resp, err := l.UpdatePassword(&req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			e := ers.FromError(err)
 | 
			
		||||
			if e.FullCode() == 0 {
 | 
			
		||||
				e = ers.FromGRPCError(err)
 | 
			
		||||
			}
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, e.HTTPStatus(), types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    int64(e.FullCode()),
 | 
			
		||||
					Message: e.Error(),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			httpx.WriteJsonCtx(r.Context(), w, http.StatusOK, types.BaseResponse{
 | 
			
		||||
				Status: types.Status{
 | 
			
		||||
					Code:    domain.SuccessCode,
 | 
			
		||||
					Message: domain.SuccessMessage,
 | 
			
		||||
				},
 | 
			
		||||
				Data: resp,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,105 @@
 | 
			
		|||
// Code generated by goctl. DO NOT EDIT.
 | 
			
		||||
// goctl 1.8.1
 | 
			
		||||
 | 
			
		||||
package handler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	member "biz-member-gateway/internal/handler/member"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 | 
			
		||||
	server.AddRoutes(
 | 
			
		||||
		[]rest.Route{
 | 
			
		||||
			{
 | 
			
		||||
				// 創建新會員
 | 
			
		||||
				Method:  http.MethodPost,
 | 
			
		||||
				Path:    "/member",
 | 
			
		||||
				Handler: member.AccountCreateHandler(serverCtx),
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				// 發送忘記密碼驗證
 | 
			
		||||
				Method:  http.MethodPost,
 | 
			
		||||
				Path:    "/member/forget-password-code",
 | 
			
		||||
				Handler: member.ForgetPasswordCodeHandler(serverCtx),
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				// 登入
 | 
			
		||||
				Method:  http.MethodPost,
 | 
			
		||||
				Path:    "/member/login",
 | 
			
		||||
				Handler: member.LoginHandler(serverCtx),
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				// 預先驗證驗證碼
 | 
			
		||||
				Method:  http.MethodPut,
 | 
			
		||||
				Path:    "/member/pre-verify",
 | 
			
		||||
				Handler: member.PreVerifyUpdatePasswordCodeHandler(serverCtx),
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				// 更新 Access Token
 | 
			
		||||
				Method:  http.MethodPut,
 | 
			
		||||
				Path:    "/member/refresh_access_token",
 | 
			
		||||
				Handler: member.RefreshAccessTokenHandler(serverCtx),
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				// 更新密碼(要發送驗證碼才可以的流程)
 | 
			
		||||
				Method:  http.MethodPut,
 | 
			
		||||
				Path:    "/member/update-password",
 | 
			
		||||
				Handler: member.UpdatePasswordHandler(serverCtx),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		rest.WithPrefix("/api/v1"),
 | 
			
		||||
		rest.WithTimeout(10000*time.Millisecond),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	server.AddRoutes(
 | 
			
		||||
		rest.WithMiddlewares(
 | 
			
		||||
			[]rest.Middleware{serverCtx.AuthMiddleware},
 | 
			
		||||
			[]rest.Route{
 | 
			
		||||
				{
 | 
			
		||||
					// 確認邀請 - 綁定會員
 | 
			
		||||
					Method:  http.MethodPost,
 | 
			
		||||
					Path:    "/member/check-verify-code",
 | 
			
		||||
					Handler: member.CheckVerifyCodeHandler(serverCtx),
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					// 取得會員資訊
 | 
			
		||||
					Method:  http.MethodGet,
 | 
			
		||||
					Path:    "/member/info",
 | 
			
		||||
					Handler: member.InfoHandler(serverCtx),
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					// 更新會員詳細資訊
 | 
			
		||||
					Method:  http.MethodPut,
 | 
			
		||||
					Path:    "/member/info",
 | 
			
		||||
					Handler: member.ModifyMemberInfoHandler(serverCtx),
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					// 會員登出
 | 
			
		||||
					Method:  http.MethodGet,
 | 
			
		||||
					Path:    "/member/logout",
 | 
			
		||||
					Handler: member.LogoutHandler(serverCtx),
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					// 修改密碼
 | 
			
		||||
					Method:  http.MethodPut,
 | 
			
		||||
					Path:    "/member/modify-passwd",
 | 
			
		||||
					Handler: member.ModifyPasswdHandler(serverCtx),
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					// 發送邀請 - 綁定會員
 | 
			
		||||
					Method:  http.MethodPost,
 | 
			
		||||
					Path:    "/member/verify",
 | 
			
		||||
					Handler: member.SendVerifyCodeHandler(serverCtx),
 | 
			
		||||
				},
 | 
			
		||||
			}...,
 | 
			
		||||
		),
 | 
			
		||||
		rest.WithPrefix("/api/v1"),
 | 
			
		||||
		rest.WithTimeout(10000*time.Millisecond),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,242 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	bizDomain "biz-member-gateway/internal/domain"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
 | 
			
		||||
	memberUC "code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/usecase"
 | 
			
		||||
	tokenUC "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
 | 
			
		||||
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs/code"
 | 
			
		||||
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/protobuf/proto"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	memberProto "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
	permissionProto "code.30cm.net/digimon/proto-all/pkg/permission"
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AccountCreateLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAccountCreateLogic 創建新會員
 | 
			
		||||
func NewAccountCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AccountCreateLogic {
 | 
			
		||||
	return &AccountCreateLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AccountCreate 建立新帳號 -> 業務邏輯
 | 
			
		||||
func (l *AccountCreateLogic) AccountCreate(req *types.CreateAccountRequest) (resp *types.LoginTokenResp, err error) {
 | 
			
		||||
	platform := member.GetPlatformByPlatformCode(req.Platform)
 | 
			
		||||
	if platform == member.PlatformNone {
 | 
			
		||||
		// http 400
 | 
			
		||||
		return nil, errs.InvalidFormat("platform not support")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Step 1: 根據平台構建相關數據
 | 
			
		||||
	createAccountReq, bindingUserReq, createUserInfoRequest, err := l.buildAccountRequests(req, platform)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// 裡面有錯從 Grpc 已經轉換完成
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Step 2: 建立帳號
 | 
			
		||||
	_, err = l.svcCtx.MemberRPC.CreateUserAccount(l.ctx, &memberProto.CreateLoginUserReq{
 | 
			
		||||
		LoginId:  createAccountReq.LoginID,
 | 
			
		||||
		Token:    createAccountReq.Token,
 | 
			
		||||
		Platform: createAccountReq.Platform.ToInt64(),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Step 3: 綁定帳號並獲取 UID
 | 
			
		||||
	account, err := l.svcCtx.MemberRPC.BindAccount(l.ctx, &memberProto.BindingUserReq{
 | 
			
		||||
		LoginId: bindingUserReq.LoginID,
 | 
			
		||||
		Type:    int64(bindingUserReq.Type),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Step 4: 更新使用者資訊
 | 
			
		||||
	_, err = l.svcCtx.MemberRPC.BindUserInfo(l.ctx, &memberProto.CreateUserInfoReq{
 | 
			
		||||
		Uid:         account.Uid,
 | 
			
		||||
		AlarmType:   memberProto.AlarmType(createUserInfoRequest.AlarmCategory),
 | 
			
		||||
		Status:      memberProto.MemberStatus(createUserInfoRequest.UserStatus),
 | 
			
		||||
		Language:    createUserInfoRequest.PreferredLanguage,
 | 
			
		||||
		Currency:    createUserInfoRequest.Currency,
 | 
			
		||||
		Avatar:      createUserInfoRequest.AvatarURL,
 | 
			
		||||
		NickName:    createUserInfoRequest.Nickname,
 | 
			
		||||
		FullName:    createUserInfoRequest.FullName,
 | 
			
		||||
		Gender:      createUserInfoRequest.GenderCode,
 | 
			
		||||
		Birthdate:   createUserInfoRequest.Birthdate,
 | 
			
		||||
		PhoneNumber: createUserInfoRequest.PhoneNumber,
 | 
			
		||||
		Email:       createUserInfoRequest.Email,
 | 
			
		||||
		Address:     createUserInfoRequest.Address,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	// TODO 綁定角色
 | 
			
		||||
 | 
			
		||||
	// Step 5: 生成 Token
 | 
			
		||||
	t, err := l.generateToken(req, account.GetUid())
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &types.LoginTokenResp{
 | 
			
		||||
		UID:          account.GetUid(),
 | 
			
		||||
		AccessToken:  t.AccessToken,
 | 
			
		||||
		RefreshToken: t.RefreshToken,
 | 
			
		||||
		TokenType:    bizDomain.TokenTypeBearer,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 構建不同平台的請求數據
 | 
			
		||||
func (l *AccountCreateLogic) buildAccountRequests(req *types.CreateAccountRequest, platform member.Platform) (
 | 
			
		||||
	memberUC.CreateLoginUserRequest, memberUC.BindingUser, memberUC.CreateUserInfoRequest, error) {
 | 
			
		||||
	var createAccountReq memberUC.CreateLoginUserRequest
 | 
			
		||||
	var bindingUserReq memberUC.BindingUser
 | 
			
		||||
	var createUserInfoRequest memberUC.CreateUserInfoRequest
 | 
			
		||||
	switch platform {
 | 
			
		||||
	case member.Digimon:
 | 
			
		||||
		// 驗證 Token 是否一致
 | 
			
		||||
		if req.Token != req.TokenCheck {
 | 
			
		||||
			return createAccountReq, bindingUserReq, createUserInfoRequest, errs.NewError(
 | 
			
		||||
				code.CloudEPMember, code.InvalidRange, bizDomain.TokenNotTheSameAPIErrorCode,
 | 
			
		||||
				"failed to verify check token",
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
		createAccountReq = memberUC.CreateLoginUserRequest{
 | 
			
		||||
			LoginID:  req.Account,
 | 
			
		||||
			Token:    req.Token,
 | 
			
		||||
			Platform: platform,
 | 
			
		||||
		}
 | 
			
		||||
		bindingUserReq = memberUC.BindingUser{
 | 
			
		||||
			LoginID: req.Account,
 | 
			
		||||
			Type:    member.GetAccountTypeByCode(req.AccountType),
 | 
			
		||||
		}
 | 
			
		||||
		createUserInfoRequest = memberUC.CreateUserInfoRequest{
 | 
			
		||||
			AlarmCategory:     member.AlarmNoAlert,
 | 
			
		||||
			UserStatus:        member.AccountStatusUnverified,
 | 
			
		||||
			PreferredLanguage: bizDomain.DefaultLang,
 | 
			
		||||
			Currency:          bizDomain.DefaultCurrency,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case member.Google:
 | 
			
		||||
		googleToken, err := l.svcCtx.MemberRPC.VerifyGoogleAuthResult(l.ctx, &memberProto.VerifyAuthResultReq{
 | 
			
		||||
			Account: proto.String(req.Account),
 | 
			
		||||
			Token:   req.Token,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return createAccountReq, bindingUserReq, createUserInfoRequest, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		createAccountReq = memberUC.CreateLoginUserRequest{
 | 
			
		||||
			LoginID:  *googleToken.Email,
 | 
			
		||||
			Token:    "",
 | 
			
		||||
			Platform: platform,
 | 
			
		||||
		}
 | 
			
		||||
		bindingUserReq = memberUC.BindingUser{
 | 
			
		||||
			LoginID: *googleToken.Email,
 | 
			
		||||
			Type:    member.GetAccountTypeByCode(req.AccountType),
 | 
			
		||||
		}
 | 
			
		||||
		createUserInfoRequest = memberUC.CreateUserInfoRequest{
 | 
			
		||||
			AvatarURL:         googleToken.Picture,
 | 
			
		||||
			FullName:          googleToken.Name,
 | 
			
		||||
			Nickname:          googleToken.Name,
 | 
			
		||||
			Email:             googleToken.Email,
 | 
			
		||||
			AlarmCategory:     member.AlarmNoAlert,
 | 
			
		||||
			UserStatus:        member.AccountStatusActive,
 | 
			
		||||
			PreferredLanguage: bizDomain.DefaultLang,
 | 
			
		||||
			Currency:          bizDomain.DefaultCurrency,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case member.Line:
 | 
			
		||||
		// 用 code 換取 line access token
 | 
			
		||||
		lineAccessToken, err := l.svcCtx.MemberRPC.LineCodeToAccessToken(l.ctx, &memberProto.LineGetTokenReq{
 | 
			
		||||
			Code: req.Account,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return createAccountReq, bindingUserReq, createUserInfoRequest, err
 | 
			
		||||
		}
 | 
			
		||||
		// 用 access token 換取 line 使用者資料
 | 
			
		||||
		userInfo, err := l.svcCtx.MemberRPC.LineGetProfileByAccessToken(l.ctx, &memberProto.LineGetUserInfoReq{
 | 
			
		||||
			Token: lineAccessToken.Token,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return createAccountReq, bindingUserReq, createUserInfoRequest, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		createAccountReq = memberUC.CreateLoginUserRequest{
 | 
			
		||||
			LoginID:  userInfo.UserId,
 | 
			
		||||
			Token:    "",
 | 
			
		||||
			Platform: platform,
 | 
			
		||||
		}
 | 
			
		||||
		bindingUserReq = memberUC.BindingUser{
 | 
			
		||||
			LoginID: userInfo.UserId,
 | 
			
		||||
			Type:    member.GetAccountTypeByCode(req.AccountType),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		createUserInfoRequest = memberUC.CreateUserInfoRequest{
 | 
			
		||||
			AvatarURL:         proto.String(userInfo.PictureUrl),
 | 
			
		||||
			FullName:          proto.String(userInfo.DisplayName),
 | 
			
		||||
			Nickname:          proto.String(userInfo.DisplayName),
 | 
			
		||||
			AlarmCategory:     member.AlarmNoAlert,
 | 
			
		||||
			UserStatus:        member.AccountStatusActive,
 | 
			
		||||
			PreferredLanguage: bizDomain.DefaultLang,
 | 
			
		||||
			Currency:          bizDomain.DefaultCurrency,
 | 
			
		||||
		}
 | 
			
		||||
	case member.PlatformNone:
 | 
			
		||||
	default:
 | 
			
		||||
		return createAccountReq, bindingUserReq, createUserInfoRequest, errs.InvalidFormat("invalid platform")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return createAccountReq, bindingUserReq, createUserInfoRequest, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 生成 Token 這裡是註冊的,所以角色是固定的,不能讓平常註冊的使用者選擇角色
 | 
			
		||||
func (l *AccountCreateLogic) generateToken(req *types.CreateAccountRequest, uid string) (tokenUC.AccessTokenResponse, error) {
 | 
			
		||||
	credentials := "client_credentials"
 | 
			
		||||
	// 生成預設的Role -> 所有邏輯上的其他角色,應該都要從 user 開始,後續再通過admin 做更改
 | 
			
		||||
	// 第一個 admin 應該是直接插入資料庫的
 | 
			
		||||
	t, err := l.svcCtx.TokenRPC.NewToken(l.ctx, &permissionProto.AuthorizationReq{
 | 
			
		||||
		GrantType:      credentials,
 | 
			
		||||
		DeviceId:       req.DeviceID,
 | 
			
		||||
		Scope:          bizDomain.DefaultRole,
 | 
			
		||||
		IsRefreshToken: true,
 | 
			
		||||
		Account:        req.Account,
 | 
			
		||||
		Uid:            uid,
 | 
			
		||||
		Expires:        proto.Int64(time.Now().UTC().Add(l.svcCtx.Config.Token.Expired).UnixNano()),        // 指定到期的時間
 | 
			
		||||
		RefreshExpire:  proto.Int64(time.Now().UTC().Add(l.svcCtx.Config.Token.RefreshExpired).UnixNano()), // 指定到期的時間
 | 
			
		||||
		Data:           map[string]string{},
 | 
			
		||||
		Role:           bizDomain.DefaultRole,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return tokenUC.AccessTokenResponse{}, errs.FromGRPCError(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tokenUC.AccessTokenResponse{
 | 
			
		||||
		AccessToken:  t.AccessToken,
 | 
			
		||||
		ExpiresIn:    t.ExpiresIn,
 | 
			
		||||
		RefreshToken: t.RefreshToken,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,104 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs/code"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
 | 
			
		||||
	memberProto "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CheckVerifyCodeLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCheckVerifyCodeLogic 確認邀請 - 綁定會員
 | 
			
		||||
func NewCheckVerifyCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CheckVerifyCodeLogic {
 | 
			
		||||
	return &CheckVerifyCodeLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *CheckVerifyCodeLogic) CheckVerifyCode(req *types.CheckoutVerifyReq) (resp *types.RespOK, err error) {
 | 
			
		||||
	var account string
 | 
			
		||||
	accountType := member.GetAccountTypeByCode(req.CodeType)
 | 
			
		||||
 | 
			
		||||
	switch accountType {
 | 
			
		||||
	case member.AccountTypePhone:
 | 
			
		||||
		phone, isPhone := normalizeTaiwanMobile(req.Account)
 | 
			
		||||
		if !isPhone {
 | 
			
		||||
			return nil, errs.InvalidFormat("phone number is invalid")
 | 
			
		||||
		}
 | 
			
		||||
		account = phone
 | 
			
		||||
	case member.AccountTypeMail:
 | 
			
		||||
		if !isValidEmail(req.Account) {
 | 
			
		||||
			return nil, errs.InvalidFormat("email is invalid")
 | 
			
		||||
		}
 | 
			
		||||
		account = req.Account
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, errs.InvalidFormat("invalid account type")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ct := member.GetGetCodeNameByCode(req.CodeType)
 | 
			
		||||
	if _, err := l.svcCtx.MemberRPC.CheckRefreshCode(l.ctx, &memberProto.VerifyRefreshCodeReq{
 | 
			
		||||
		VerifyCode: req.VerifyCode,
 | 
			
		||||
		Account:    account,
 | 
			
		||||
		CodeType:   int32(ct),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, errs.Forbidden("failed to get verify code").Wrap(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 處理綁定 Email 或 Phone
 | 
			
		||||
	switch ct {
 | 
			
		||||
	case member.GenerateCodeTypeEmail:
 | 
			
		||||
		if _, err := l.svcCtx.MemberRPC.BindVerifyEmail(l.ctx, &memberProto.BindVerifyEmailReq{
 | 
			
		||||
			Uid:   req.UID,
 | 
			
		||||
			Email: account,
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return nil, errs.DatabaseErrorWithScope(
 | 
			
		||||
				code.CloudEPMember, domain.FailedToCheckVerifyCodeErrorCode,
 | 
			
		||||
				"failed to bind email", err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	case member.GenerateCodeTypePhone:
 | 
			
		||||
		if _, err := l.svcCtx.MemberRPC.BindVerifyPhone(l.ctx, &memberProto.BindVerifyPhoneReq{
 | 
			
		||||
			Uid:   req.UID,
 | 
			
		||||
			Phone: account,
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			return nil, errs.DatabaseErrorWithScope(
 | 
			
		||||
				code.CloudEPMember, domain.FailedToCheckVerifyCodeErrorCode, "failed to bind phone", err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 更新帳戶狀態
 | 
			
		||||
	_, err = l.svcCtx.MemberRPC.UpdateStatus(l.ctx, &memberProto.UpdateStatusReq{
 | 
			
		||||
		Uid:    req.UID,
 | 
			
		||||
		Status: memberProto.MemberStatus(member.AccountStatusActive),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errs.DatabaseErrorWithScope(
 | 
			
		||||
			code.CloudEPMember, domain.FailedToCheckVerifyCodeErrorCode, "failed to update status", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 最後刪除驗證碼
 | 
			
		||||
	if _, err := l.svcCtx.MemberRPC.VerifyRefreshCode(l.ctx, &memberProto.VerifyRefreshCodeReq{
 | 
			
		||||
		VerifyCode: req.VerifyCode,
 | 
			
		||||
		Account:    account,
 | 
			
		||||
		CodeType:   int32(ct),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, errs.Forbidden("failed to get verify code").Wrap(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &types.RespOK{}, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,192 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
	"bytes"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs/code"
 | 
			
		||||
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs"
 | 
			
		||||
 | 
			
		||||
	ntpl "code.30cm.net/digimon/app-cloudep-notification-service/pkg/domain/template"
 | 
			
		||||
	memberProto "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
	notificationProto "code.30cm.net/digimon/proto-all/pkg/notification"
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ForgetPasswordCodeLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewForgetPasswordCodeLogic 發送忘記密碼驗證
 | 
			
		||||
func NewForgetPasswordCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ForgetPasswordCodeLogic {
 | 
			
		||||
	return &ForgetPasswordCodeLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *ForgetPasswordCodeLogic) ForgetPasswordCode(req *types.ForgetPasswordCodeReq) (resp *types.RespOK, err error) {
 | 
			
		||||
	// 1. 驗證並標準化帳號
 | 
			
		||||
	acc, err := normalizeAccount(req.AccountType, req.Account)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 2. 統一檢查冷卻時間與平台正確性
 | 
			
		||||
	if err = l.checkAccountValidity(acc); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 3. 生成驗證碼
 | 
			
		||||
	vcode, err := l.svcCtx.MemberRPC.GenerateRefreshCode(l.ctx, &memberProto.GenerateRefreshCodeReq{
 | 
			
		||||
		Account:  acc,
 | 
			
		||||
		CodeType: int32(member.GenerateCodeTypeForgetPassword),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 4. 取得用戶資訊
 | 
			
		||||
	uidInfo, err := l.svcCtx.MemberRPC.GetUIDByAccount(l.ctx, &memberProto.GetUIDByAccountReq{Account: acc})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	userInfo, err := l.svcCtx.MemberRPC.GetUserInfo(l.ctx, &memberProto.GetUserInfoReq{Uid: uidInfo.GetUid()})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 5. 發送驗證碼
 | 
			
		||||
	nickname := getEmailShowName(userInfo)
 | 
			
		||||
	if err = l.sendVerificationCode(req.AccountType, acc, userInfo.Data, vcode.Data.VerifyCode, nickname); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 6. 設置 Redis 驗證碼鍵值
 | 
			
		||||
	rk := domain.GetSendCodeRedisKey(fmt.Sprintf("%s:%d", acc, member.GenerateCodeTypeForgetPassword))
 | 
			
		||||
	l.setRedisKeyWithExpiry(rk, vcode.Data.VerifyCode, int(l.svcCtx.Config.Member.ForgetTimeOutInSec))
 | 
			
		||||
 | 
			
		||||
	return &types.RespOK{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// normalizeAccount 驗證並標準化帳號
 | 
			
		||||
func normalizeAccount(accountType, account string) (string, error) {
 | 
			
		||||
	at := member.GetAccountTypeByCode(accountType)
 | 
			
		||||
	switch at {
 | 
			
		||||
	case member.AccountTypePhone:
 | 
			
		||||
		phone, ok := normalizeTaiwanMobile(account)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return "", errs.InvalidFormat("phone number is invalid")
 | 
			
		||||
		}
 | 
			
		||||
		return phone, nil
 | 
			
		||||
	case member.AccountTypeMail:
 | 
			
		||||
		if !isValidEmail(account) {
 | 
			
		||||
			return "", errs.InvalidFormat("email is invalid")
 | 
			
		||||
		}
 | 
			
		||||
		return account, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return "", errs.InvalidFormat("unsupported account type")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkAccountValidity 同時檢查發送冷卻與平台正確性
 | 
			
		||||
func (l *ForgetPasswordCodeLogic) checkAccountValidity(acc string) error {
 | 
			
		||||
	// 檢查發送冷卻
 | 
			
		||||
	rk := domain.GetSendCodeRedisKey(fmt.Sprintf("%s:%d", acc, member.GenerateCodeTypeForgetPassword))
 | 
			
		||||
	if cached, err := l.svcCtx.Redis.GetCtx(l.ctx, rk); err != nil || cached != "" {
 | 
			
		||||
		return errs.InvalidRange("verification code already sent, please wait for system to send again")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 檢查平台是否正確(只允許平台帳號進行忘記密碼操作)
 | 
			
		||||
	accountInfo, err := l.svcCtx.MemberRPC.GetUserAccountInfo(l.ctx, &memberProto.GetUIDByAccountReq{Account: acc})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if accountInfo.Data.Platform != member.Digimon.ToInt64() {
 | 
			
		||||
		return errs.InvalidResourceState("failed to send verify code since platform not correct")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sendVerificationCode 根據帳號類型發送驗證碼
 | 
			
		||||
func (l *ForgetPasswordCodeLogic) sendVerificationCode(accountType, acc string, info *memberProto.UserInfo, verifyCode, nickname string) error {
 | 
			
		||||
	switch member.GetAccountTypeByCode(accountType) {
 | 
			
		||||
	case member.AccountTypePhone:
 | 
			
		||||
		// TODO: 傳送簡訊
 | 
			
		||||
		fmt.Printf("SMS Template: %s\n", verifyCode)
 | 
			
		||||
	case member.AccountTypeMail:
 | 
			
		||||
		return l.sendVerificationEmail(acc, info, verifyCode, nickname)
 | 
			
		||||
	default:
 | 
			
		||||
		return errs.InvalidResourceState("unsupported account type")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EmailTmpInfo struct {
 | 
			
		||||
	Username   string
 | 
			
		||||
	VerifyCode string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sendVerificationEmail 發送驗證郵件
 | 
			
		||||
func (l *ForgetPasswordCodeLogic) sendVerificationEmail(recipientEmail string, info *memberProto.UserInfo, verifyCode, nickname string) error {
 | 
			
		||||
	tpl, err := l.svcCtx.NotificationRPC.GetStaticTemplate(l.ctx, ¬ificationProto.TemplateReq{
 | 
			
		||||
		Language:   info.Language,
 | 
			
		||||
		TemplateId: string(ntpl.ForgetPasswordVerify),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tmpl, err := template.New("ForgetPasswordEmail").Parse(tpl.GetBody())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emailParams := EmailTmpInfo{Username: nickname, VerifyCode: verifyCode}
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	if err := tmpl.Execute(&buf, emailParams); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = l.svcCtx.NotificationRPC.SendMail(l.ctx, ¬ificationProto.SendMailReq{
 | 
			
		||||
		To:      recipientEmail,
 | 
			
		||||
		Subject: tpl.Title,
 | 
			
		||||
		Body:    buf.String(),
 | 
			
		||||
		From:    l.svcCtx.Config.MailSender,
 | 
			
		||||
	})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getEmailShowName 取得寄信用的顯示名稱
 | 
			
		||||
func getEmailShowName(info *memberProto.GetUserInfoResp) string {
 | 
			
		||||
	if info.Data.FullName != nil {
 | 
			
		||||
		return *info.Data.FullName
 | 
			
		||||
	}
 | 
			
		||||
	if info.Data.NickName != nil {
 | 
			
		||||
		return *info.Data.NickName
 | 
			
		||||
	}
 | 
			
		||||
	return info.Data.Uid
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// setRedisKeyWithExpiry 設置 Redis 鍵與過期時間
 | 
			
		||||
func (l *ForgetPasswordCodeLogic) setRedisKeyWithExpiry(rk, verifyCode string, expiry int) {
 | 
			
		||||
	if status, err := l.svcCtx.Redis.SetnxExCtx(l.ctx, rk, verifyCode, expiry); err != nil || !status {
 | 
			
		||||
		_ = errs.DatabaseErrorWithScopeL(code.CloudEPMember, domain.FailedToSetVerifyCodeErrorCode,
 | 
			
		||||
			logx.WithContext(l.ctx),
 | 
			
		||||
			[]logx.LogField{
 | 
			
		||||
				{Key: "redisKey", Value: rk},
 | 
			
		||||
				{Key: "error", Value: err.Error()},
 | 
			
		||||
			}, "failed to set redis expire").Wrap(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,95 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
	"biz-member-gateway/internal/utils"
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
	memberProto "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type InfoLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewInfoLogic 取得會員資訊
 | 
			
		||||
func NewInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *InfoLogic {
 | 
			
		||||
	return &InfoLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *InfoLogic) Info(req *types.VerifyHeader) (resp *types.UserInfo, err error) {
 | 
			
		||||
	info, err := l.svcCtx.MemberRPC.GetUserInfo(l.ctx, &memberProto.GetUserInfoReq{
 | 
			
		||||
		Uid: domain.UID(l.ctx),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	accountInfo, err := l.svcCtx.MemberRPC.GetUserAccountInfo(l.ctx, &memberProto.GetUIDByAccountReq{
 | 
			
		||||
		Account: domain.Account(l.ctx),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	alarmType := member.AlarmType(info.Data.AlarmType)
 | 
			
		||||
	status := member.Status(info.Data.Status)
 | 
			
		||||
	res := &types.UserInfo{
 | 
			
		||||
		Platform:          member.Platform(accountInfo.Data.Platform).ToString(),
 | 
			
		||||
		UID:               domain.UID(l.ctx),
 | 
			
		||||
		UpdateAt:          utils.UnixToRfc3339(info.Data.UpdateTime),
 | 
			
		||||
		CreateAt:          utils.UnixToRfc3339(info.Data.CreateTime),
 | 
			
		||||
		AlarmCategory:     alarmType.CodeToString(),
 | 
			
		||||
		UserStatus:        status.CodeToString(),
 | 
			
		||||
		PreferredLanguage: info.Data.Language,
 | 
			
		||||
		Currency:          info.Data.Currency,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.AvatarUrl != nil {
 | 
			
		||||
		res.AvatarURL = *info.Data.AvatarUrl
 | 
			
		||||
	}
 | 
			
		||||
	if info.Data.FullName != nil {
 | 
			
		||||
		res.FullName = *info.Data.FullName
 | 
			
		||||
	}
 | 
			
		||||
	if info.Data.NickName != nil {
 | 
			
		||||
		res.Nickname = *info.Data.NickName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.GenderCode != nil {
 | 
			
		||||
		gc, err := domain.GenderCodeToString(*info.Data.GenderCode)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			gc = "secret"
 | 
			
		||||
		}
 | 
			
		||||
		res.GenderCode = gc
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.Birthday != nil {
 | 
			
		||||
		// RFC 3339
 | 
			
		||||
		res.Birthdate = utils.UnixToRfc3339(*info.Data.Birthday)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.Phone != nil {
 | 
			
		||||
		res.PhoneNumber = *info.Data.Phone
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.Address != nil {
 | 
			
		||||
		res.Address = *info.Data.Address
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.Email != nil {
 | 
			
		||||
		res.Email = *info.Data.Email
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,168 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	bizDomain "biz-member-gateway/internal/domain"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
	"context"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
	memberUC "code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/usecase"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs"
 | 
			
		||||
	memberProto "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
	permissionProto "code.30cm.net/digimon/proto-all/pkg/permission"
 | 
			
		||||
	"github.com/golang/protobuf/proto"
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type LoginLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLoginLogic 登入
 | 
			
		||||
func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {
 | 
			
		||||
	return &LoginLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginTokenResp, err error) {
 | 
			
		||||
	var result memberUC.VerifyAuthResultResponse
 | 
			
		||||
	// Step 1 驗證進來的 Token
 | 
			
		||||
	platform := member.GetPlatformByPlatformCode(req.Platform)
 | 
			
		||||
 | 
			
		||||
	switch platform {
 | 
			
		||||
	case member.Digimon:
 | 
			
		||||
		switch member.GetAccountTypeByCode(req.AccountType) {
 | 
			
		||||
		case member.AccountTypePhone:
 | 
			
		||||
			phone, isPhone := normalizeTaiwanMobile(req.Account)
 | 
			
		||||
			if !isPhone {
 | 
			
		||||
				return nil, errs.InvalidFormat("phone number is invalid")
 | 
			
		||||
			}
 | 
			
		||||
			req.Account = phone
 | 
			
		||||
		case member.AccountTypeMail:
 | 
			
		||||
			if !isValidEmail(req.Account) {
 | 
			
		||||
				return nil, errs.InvalidFormat("email is invalid")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 原始平台驗證
 | 
			
		||||
		res, err := l.svcCtx.MemberRPC.VerifyPlatformAuthResult(l.ctx, &memberProto.VerifyAuthResultReq{
 | 
			
		||||
			Account: proto.String(req.Account),
 | 
			
		||||
			Token:   req.Token,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		result.Status = res.Status
 | 
			
		||||
	case member.Google:
 | 
			
		||||
		// 原始平台驗證
 | 
			
		||||
		_, err := l.svcCtx.MemberRPC.VerifyGoogleAuthResult(l.ctx, &memberProto.VerifyAuthResultReq{
 | 
			
		||||
			Account: proto.String(req.Account),
 | 
			
		||||
			Token:   req.Token,
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		result.Status = true
 | 
			
		||||
	case member.Line:
 | 
			
		||||
		// 原始平台驗證
 | 
			
		||||
		accessToken, err := l.svcCtx.MemberRPC.LineCodeToAccessToken(l.ctx, &memberProto.LineGetTokenReq{
 | 
			
		||||
			Code: req.Account,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 換資料
 | 
			
		||||
		userInfo, err := l.svcCtx.MemberRPC.LineGetProfileByAccessToken(l.ctx, &memberProto.LineGetUserInfoReq{
 | 
			
		||||
			Token: accessToken.Token,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		result.Status = true
 | 
			
		||||
		req.Account = userInfo.UserId
 | 
			
		||||
	case member.PlatformNone:
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, errs.InvalidFormat("invalid platform")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !result.Status {
 | 
			
		||||
		return nil, errs.Unauthorized("failed to validate password ")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	account, err := l.svcCtx.MemberRPC.GetUIDByAccount(l.ctx, &memberProto.GetUIDByAccountReq{
 | 
			
		||||
		Account: req.Account,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	credentials := "client_credentials"
 | 
			
		||||
	// TODO 去拿這個使用者Role
 | 
			
		||||
	role := bizDomain.DefaultRole
 | 
			
		||||
 | 
			
		||||
	t, err := l.svcCtx.TokenRPC.NewToken(l.ctx, &permissionProto.AuthorizationReq{
 | 
			
		||||
		GrantType:      credentials,
 | 
			
		||||
		DeviceId:       req.DeviceID,
 | 
			
		||||
		Scope:          role,
 | 
			
		||||
		IsRefreshToken: true,
 | 
			
		||||
		Account:        req.Account,
 | 
			
		||||
		Uid:            account.GetUid(),
 | 
			
		||||
		Expires:        proto.Int64(time.Now().UTC().Add(l.svcCtx.Config.Token.Expired).UnixNano()),        // 指定到期的時間
 | 
			
		||||
		RefreshExpire:  proto.Int64(time.Now().UTC().Add(l.svcCtx.Config.Token.RefreshExpired).UnixNano()), // 指定到期的時間
 | 
			
		||||
		Data:           map[string]string{},
 | 
			
		||||
		Role:           role,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &types.LoginTokenResp{
 | 
			
		||||
		UID:          account.GetUid(),
 | 
			
		||||
		AccessToken:  t.AccessToken,
 | 
			
		||||
		RefreshToken: t.RefreshToken,
 | 
			
		||||
		TokenType:    domain.TokenTypeBearer,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 標準化號碼並驗證是否為合法台灣手機號碼
 | 
			
		||||
func normalizeTaiwanMobile(phone string) (string, bool) {
 | 
			
		||||
	// 移除空格
 | 
			
		||||
	phone = strings.ReplaceAll(phone, " ", "")
 | 
			
		||||
 | 
			
		||||
	// 移除 "+886" 並將剩餘部分標準化
 | 
			
		||||
	if strings.HasPrefix(phone, "+886") {
 | 
			
		||||
		phone = strings.TrimPrefix(phone, "+886")
 | 
			
		||||
		if !strings.HasPrefix(phone, "0") {
 | 
			
		||||
			phone = "0" + phone
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 正則表達式驗證標準化後的號碼
 | 
			
		||||
	regex := regexp.MustCompile(`^(09\d{8})$`)
 | 
			
		||||
	if regex.MatchString(phone) {
 | 
			
		||||
		return phone, true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 驗證 Email 格式的函數
 | 
			
		||||
func isValidEmail(email string) bool {
 | 
			
		||||
	// 定義正則表達式
 | 
			
		||||
	regex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
 | 
			
		||||
 | 
			
		||||
	return regex.MatchString(email)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	permissionProto "code.30cm.net/digimon/proto-all/pkg/permission"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type LogoutLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLogoutLogic 會員登出
 | 
			
		||||
func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogic {
 | 
			
		||||
	return &LogoutLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *LogoutLogic) Logout(req *types.VerifyHeader) (*types.RespOK, error) {
 | 
			
		||||
	_, err := l.svcCtx.TokenRPC.CancelToken(l.ctx, &permissionProto.CancelTokenReq{
 | 
			
		||||
		Token: req.Token,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &types.RespOK{}, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,144 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
	"biz-member-gateway/internal/utils"
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
	memberProto "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
	"github.com/golang/protobuf/proto"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ModifyMemberInfoLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 更新會員詳細資訊
 | 
			
		||||
func NewModifyMemberInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModifyMemberInfoLogic {
 | 
			
		||||
	return &ModifyMemberInfoLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *ModifyMemberInfoLogic) ModifyMemberInfo(req *types.BindingUserInfoReq) (*types.UserInfo, error) {
 | 
			
		||||
	update := &memberProto.UpdateUserInfoReq{
 | 
			
		||||
		Uid: domain.UID(l.ctx),
 | 
			
		||||
	}
 | 
			
		||||
	if req.Nickname != "" {
 | 
			
		||||
		update.NickName = proto.String(req.Nickname)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.PreferredLanguage != "" {
 | 
			
		||||
		update.Language = proto.String(req.PreferredLanguage)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.Currency != "" {
 | 
			
		||||
		update.Currency = proto.String(req.Currency)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.AvatarURL != "" {
 | 
			
		||||
		update.Avatar = proto.String(req.AvatarURL)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.FullName != "" {
 | 
			
		||||
		update.FullName = proto.String(req.FullName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.GenderCode != "" {
 | 
			
		||||
		gc, err := domain.StringToGenderCode(req.GenderCode)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			gc = 0
 | 
			
		||||
		}
 | 
			
		||||
		update.Gender = proto.Int64(gc)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.Birthday != "" {
 | 
			
		||||
		unix, err := utils.Rfc3339ToUnix(req.Birthday)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			update.Birthdate = proto.Int64(unix)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.Address != "" {
 | 
			
		||||
		update.Address = proto.String(req.Address)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := l.svcCtx.MemberRPC.UpdateUserInfo(l.ctx, update)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 再次取得資訊
 | 
			
		||||
	info, err := l.svcCtx.MemberRPC.GetUserInfo(l.ctx, &memberProto.GetUserInfoReq{
 | 
			
		||||
		Uid: domain.UID(l.ctx),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	accountInfo, err := l.svcCtx.MemberRPC.GetUserAccountInfo(l.ctx, &memberProto.GetUIDByAccountReq{
 | 
			
		||||
		Account: domain.Account(l.ctx),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	alarmType := member.AlarmType(info.Data.AlarmType)
 | 
			
		||||
	status := member.Status(info.Data.Status)
 | 
			
		||||
	res := &types.UserInfo{
 | 
			
		||||
		Platform:          member.Platform(accountInfo.Data.Platform).ToString(),
 | 
			
		||||
		UID:               domain.UID(l.ctx),
 | 
			
		||||
		UpdateAt:          utils.UnixToRfc3339(info.Data.UpdateTime),
 | 
			
		||||
		CreateAt:          utils.UnixToRfc3339(info.Data.CreateTime),
 | 
			
		||||
		AlarmCategory:     alarmType.CodeToString(),
 | 
			
		||||
		UserStatus:        status.CodeToString(),
 | 
			
		||||
		PreferredLanguage: info.Data.Language,
 | 
			
		||||
		Currency:          info.Data.Currency,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.AvatarUrl != nil {
 | 
			
		||||
		res.AvatarURL = *info.Data.AvatarUrl
 | 
			
		||||
	}
 | 
			
		||||
	if info.Data.FullName != nil {
 | 
			
		||||
		res.FullName = *info.Data.FullName
 | 
			
		||||
	}
 | 
			
		||||
	if info.Data.NickName != nil {
 | 
			
		||||
		res.Nickname = *info.Data.NickName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.GenderCode != nil {
 | 
			
		||||
		gc, err := domain.GenderCodeToString(*info.Data.GenderCode)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			gc = "secret"
 | 
			
		||||
		}
 | 
			
		||||
		res.GenderCode = gc
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.Birthday != nil {
 | 
			
		||||
		// RFC 3339
 | 
			
		||||
		res.Birthdate = utils.UnixToRfc3339(*info.Data.Birthday)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.Phone != nil {
 | 
			
		||||
		res.PhoneNumber = *info.Data.Phone
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.Address != nil {
 | 
			
		||||
		res.Address = *info.Data.Address
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.Email != nil {
 | 
			
		||||
		res.Email = *info.Data.Email
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs"
 | 
			
		||||
	memberRPC "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
	permissionProto "code.30cm.net/digimon/proto-all/pkg/permission"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ModifyPasswdLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewModifyPasswdLogic 修改密碼
 | 
			
		||||
func NewModifyPasswdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModifyPasswdLogic {
 | 
			
		||||
	return &ModifyPasswdLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *ModifyPasswdLogic) ModifyPasswd(req *types.ModifyPasswdReq) (*types.RespOK, error) {
 | 
			
		||||
	account := domain.Account(l.ctx)
 | 
			
		||||
 | 
			
		||||
	// 檢查是否有此帳號
 | 
			
		||||
	_, err := l.svcCtx.MemberRPC.GetUIDByAccount(l.ctx, &memberRPC.GetUIDByAccountReq{
 | 
			
		||||
		Account: account,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.NewToken != req.NewTokenCheck {
 | 
			
		||||
		return nil, errs.InvalidFormat("failed to check token")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 更新
 | 
			
		||||
	_, err = l.svcCtx.MemberRPC.UpdateUserToken(l.ctx, &memberRPC.UpdateTokenReq{
 | 
			
		||||
		Account:  account,
 | 
			
		||||
		Token:    req.NewToken,
 | 
			
		||||
		Platform: member.Digimon.ToInt64(),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 登出所有,請使用者中新登入 -> 射後不理,因為已經更新成功了
 | 
			
		||||
	_, _ = l.svcCtx.TokenRPC.CancelToken(l.ctx, &permissionProto.CancelTokenReq{
 | 
			
		||||
		Token: req.Token,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return &types.RespOK{}, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs"
 | 
			
		||||
	memberProto "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PreVerifyUpdatePasswordCodeLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewPreVerifyUpdatePasswordCodeLogic 預先驗證驗證碼
 | 
			
		||||
func NewPreVerifyUpdatePasswordCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PreVerifyUpdatePasswordCodeLogic {
 | 
			
		||||
	return &PreVerifyUpdatePasswordCodeLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *PreVerifyUpdatePasswordCodeLogic) PreVerifyUpdatePasswordCode(req *types.PreVerifyForgetPasswdReq) (resp *types.RespOK, err error) {
 | 
			
		||||
	// 先驗證,不刪除
 | 
			
		||||
	if _, err := l.svcCtx.MemberRPC.CheckRefreshCode(l.ctx, &memberProto.VerifyRefreshCodeReq{
 | 
			
		||||
		Account:    req.Identifier,
 | 
			
		||||
		CodeType:   int32(member.GenerateCodeTypeForgetPassword),
 | 
			
		||||
		VerifyCode: req.VerifyCode,
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return nil, errs.Forbidden("failed to get verify code")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs"
 | 
			
		||||
	"code.30cm.net/digimon/proto-all/pkg/permission"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type RefreshAccessTokenLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRefreshAccessTokenLogic 更新 Access Token
 | 
			
		||||
func NewRefreshAccessTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RefreshAccessTokenLogic {
 | 
			
		||||
	return &RefreshAccessTokenLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *RefreshAccessTokenLogic) RefreshAccessToken(req *types.UpdateTokenReq) (*types.LoginTokenResp, error) {
 | 
			
		||||
	data, err := l.svcCtx.TokenRPC.GetSystemClaimByAccessToken(l.ctx, &permission.GetSystemClaimReq{
 | 
			
		||||
		AccessToken: req.Token,
 | 
			
		||||
		IsExpired:   false,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uid := data.Data[token.UID.String()]
 | 
			
		||||
	if uid != req.UID {
 | 
			
		||||
		return nil, errs.Forbidden("failed to verify token user")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t, err := l.svcCtx.TokenRPC.RefreshToken(l.ctx, &permission.RefreshTokenReq{
 | 
			
		||||
		Token:    req.RefreshToken,
 | 
			
		||||
		Scope:    data.Data[token.Scope.String()],
 | 
			
		||||
		Expires:  0, // 指定到期的時間,不給會交由底層給(token repo)
 | 
			
		||||
		DeviceId: data.Data[token.Device.String()],
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &types.LoginTokenResp{
 | 
			
		||||
		UID:          req.UID,
 | 
			
		||||
		AccessToken:  t.Token,
 | 
			
		||||
		RefreshToken: t.OneTimeToken,
 | 
			
		||||
		TokenType:    domain.TokenTypeBearer,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,157 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
	ntpl "code.30cm.net/digimon/app-cloudep-notification-service/pkg/domain/template"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs/code"
 | 
			
		||||
	memberProto "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
	notificationProto "code.30cm.net/digimon/proto-all/pkg/notification"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SendVerifyCodeLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSendVerifyCodeLogic 發送邀請 - 綁定會員
 | 
			
		||||
func NewSendVerifyCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendVerifyCodeLogic {
 | 
			
		||||
	return &SendVerifyCodeLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *SendVerifyCodeLogic) SendVerifyCode(req *types.VerificationCodeRequest) (resp *types.RespOK, err error) {
 | 
			
		||||
	at := member.GetGetCodeNameByCode(req.CodeType)
 | 
			
		||||
	// 1. 驗證並標準化帳號
 | 
			
		||||
	acc, err := normalizeAccount(req.CodeType, req.Identifier)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 2. 統一檢查冷卻時間與平台正確性
 | 
			
		||||
	if err = l.checkAccountValidity(acc, at); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 3. 生成驗證碼
 | 
			
		||||
	vcode, err := l.svcCtx.MemberRPC.GenerateRefreshCode(l.ctx, &memberProto.GenerateRefreshCodeReq{
 | 
			
		||||
		Account:  acc,
 | 
			
		||||
		CodeType: int32(at),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 4. 取得用戶資訊
 | 
			
		||||
	uidInfo, err := l.svcCtx.MemberRPC.GetUIDByAccount(l.ctx, &memberProto.GetUIDByAccountReq{Account: acc})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	userInfo, err := l.svcCtx.MemberRPC.GetUserInfo(l.ctx, &memberProto.GetUserInfoReq{Uid: uidInfo.GetUid()})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 5. 發送驗證碼
 | 
			
		||||
	nickname := getEmailShowName(userInfo)
 | 
			
		||||
	if err = l.sendVerificationCode(req.CodeType, acc, userInfo.Data, vcode.Data.VerifyCode, nickname); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 6. 設置 Redis 驗證碼鍵值
 | 
			
		||||
	rk := domain.GetSendCodeRedisKey(fmt.Sprintf("%s:%d", acc, at))
 | 
			
		||||
	l.setRedisKeyWithExpiry(rk, vcode.Data.VerifyCode, int(l.svcCtx.Config.Member.ForgetTimeOutInSec))
 | 
			
		||||
 | 
			
		||||
	return &types.RespOK{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// checkAccountValidity 同時檢查發送冷卻與平台正確性
 | 
			
		||||
func (l *SendVerifyCodeLogic) checkAccountValidity(acc string, gc member.GenerateCodeType) error {
 | 
			
		||||
	// 檢查發送冷卻
 | 
			
		||||
	rk := domain.GetSendCodeRedisKey(fmt.Sprintf("%s:%d", acc, gc))
 | 
			
		||||
	if cached, err := l.svcCtx.Redis.GetCtx(l.ctx, rk); err != nil || cached != "" {
 | 
			
		||||
		return errs.InvalidRange("verification code already sent, please wait for system to send again")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 檢查平台是否正確(只允許平台帳號進行綁定)
 | 
			
		||||
	accountInfo, err := l.svcCtx.MemberRPC.GetUserAccountInfo(l.ctx, &memberProto.GetUIDByAccountReq{Account: acc})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if accountInfo.Data.Platform != member.Digimon.ToInt64() {
 | 
			
		||||
		return errs.InvalidResourceState("failed to send verify code since platform not correct")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sendVerificationEmail 發送驗證郵件
 | 
			
		||||
func (l *SendVerifyCodeLogic) sendVerificationEmail(recipientEmail string, info *memberProto.UserInfo, verifyCode, nickname string) error {
 | 
			
		||||
	tpl, err := l.svcCtx.NotificationRPC.GetStaticTemplate(l.ctx, ¬ificationProto.TemplateReq{
 | 
			
		||||
		Language:   info.Language,
 | 
			
		||||
		TemplateId: string(ntpl.BindingEmail),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tmpl, err := template.New("SendVerificationEmail").Parse(tpl.GetBody())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emailParams := EmailTmpInfo{Username: nickname, VerifyCode: verifyCode}
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	if err := tmpl.Execute(&buf, emailParams); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = l.svcCtx.NotificationRPC.SendMail(l.ctx, ¬ificationProto.SendMailReq{
 | 
			
		||||
		To:      recipientEmail,
 | 
			
		||||
		Subject: tpl.Title,
 | 
			
		||||
		Body:    buf.String(),
 | 
			
		||||
		From:    l.svcCtx.Config.MailSender,
 | 
			
		||||
	})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sendVerificationCode 根據帳號類型發送驗證碼
 | 
			
		||||
func (l *SendVerifyCodeLogic) sendVerificationCode(accountType, acc string, info *memberProto.UserInfo, verifyCode, nickname string) error {
 | 
			
		||||
	switch member.GetAccountTypeByCode(accountType) {
 | 
			
		||||
	case member.AccountTypePhone:
 | 
			
		||||
		// TODO: 傳送簡訊
 | 
			
		||||
		fmt.Printf("SMS Template: %s\n", verifyCode)
 | 
			
		||||
	case member.AccountTypeMail:
 | 
			
		||||
		return l.sendVerificationEmail(acc, info, verifyCode, nickname)
 | 
			
		||||
	default:
 | 
			
		||||
		return errs.InvalidResourceState("unsupported account type")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// setRedisKeyWithExpiry 設置 Redis 鍵與過期時間
 | 
			
		||||
func (l *SendVerifyCodeLogic) setRedisKeyWithExpiry(rk, verifyCode string, expiry int) {
 | 
			
		||||
	if status, err := l.svcCtx.Redis.SetnxExCtx(l.ctx, rk, verifyCode, expiry); err != nil || !status {
 | 
			
		||||
		_ = errs.DatabaseErrorWithScopeL(code.CloudEPMember, domain.FailedToSetVerifyCodeErrorCode,
 | 
			
		||||
			logx.WithContext(l.ctx),
 | 
			
		||||
			[]logx.LogField{
 | 
			
		||||
				{Key: "func", Value: "SendVerifyCodeLogic.setRedisKeyWithExpiry"},
 | 
			
		||||
				{Key: "redisKey", Value: rk},
 | 
			
		||||
				{Key: "error", Value: err.Error()},
 | 
			
		||||
			}, "failed to set redis expire").Wrap(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
package member
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/domain"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member"
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errs"
 | 
			
		||||
	memberProto "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
	permissionProto "code.30cm.net/digimon/proto-all/pkg/permission"
 | 
			
		||||
 | 
			
		||||
	"biz-member-gateway/internal/svc"
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type UpdatePasswordLogic struct {
 | 
			
		||||
	logx.Logger
 | 
			
		||||
	ctx    context.Context
 | 
			
		||||
	svcCtx *svc.ServiceContext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUpdatePasswordLogic 更新密碼(要發送驗證碼才可以的流程)
 | 
			
		||||
func NewUpdatePasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePasswordLogic {
 | 
			
		||||
	return &UpdatePasswordLogic{
 | 
			
		||||
		Logger: logx.WithContext(ctx),
 | 
			
		||||
		ctx:    ctx,
 | 
			
		||||
		svcCtx: svcCtx,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *UpdatePasswordLogic) UpdatePassword(req *types.UpdatePasswordReq) (*types.RespOK, error) {
 | 
			
		||||
	// 驗證密碼,兩次密碼要一致
 | 
			
		||||
	if req.Token != req.TokenCheck {
 | 
			
		||||
		return nil, errs.InvalidFormat("password confirmation does not match")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 驗證碼
 | 
			
		||||
	_, err := l.svcCtx.MemberRPC.VerifyRefreshCode(l.ctx, &memberProto.VerifyRefreshCodeReq{
 | 
			
		||||
		Account:    req.Account,
 | 
			
		||||
		CodeType:   int32(member.GenerateCodeTypeForgetPassword),
 | 
			
		||||
		VerifyCode: req.VerifyCode,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// 表使沒有這驗證碼 -> 未授權
 | 
			
		||||
		return nil, errs.Unauthorized("failed to get verify code")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	info, err := l.svcCtx.MemberRPC.GetUserAccountInfo(l.ctx, &memberProto.GetUIDByAccountReq{Account: req.Account})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if info.Data.Platform != member.Digimon.ToInt64() {
 | 
			
		||||
		return nil, ers.InvalidRange("invalid platform")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 更新
 | 
			
		||||
	_, err = l.svcCtx.MemberRPC.UpdateUserToken(l.ctx, &memberProto.UpdateTokenReq{
 | 
			
		||||
		Account:  req.Account,
 | 
			
		||||
		Token:    req.Token,
 | 
			
		||||
		Platform: member.Digimon.ToInt64(),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 刪除驗證碼
 | 
			
		||||
	rk := domain.GetSendCodeRedisKey(fmt.Sprintf("%s:%s", req.Account, member.GenerateCodeTypeForgetPassword))
 | 
			
		||||
	_, err = l.svcCtx.Redis.Del(rk)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, ers.DBError("failed to get account data")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 登出舊 Token 如果有的話
 | 
			
		||||
	ac, err := l.svcCtx.MemberRPC.GetUIDByAccount(l.ctx, &memberProto.GetUIDByAccountReq{Account: req.Account})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = l.svcCtx.TokenRPC.CancelTokens(l.ctx, &permissionProto.DoTokenByUIDReq{Uid: ac.GetUid()})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 返回成功響應
 | 
			
		||||
	return &types.RespOK{}, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,84 @@
 | 
			
		|||
package middleware
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/types"
 | 
			
		||||
	"context"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/token"
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
	permissionRPC "code.30cm.net/digimon/proto-all/pkg/permission"
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest/httpx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AuthMiddlewareParam struct {
 | 
			
		||||
	TokenRPC permissionRPC.TokenServiceClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AuthMiddleware struct {
 | 
			
		||||
	TokenRPC permissionRPC.TokenServiceClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAuthMiddleware(param AuthMiddlewareParam) *AuthMiddleware {
 | 
			
		||||
	return &AuthMiddleware{TokenRPC: param.TokenRPC}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		// 解析 Header
 | 
			
		||||
		header := types.VerifyHeader{}
 | 
			
		||||
		if err := httpx.ParseHeaders(r, &header); err != nil {
 | 
			
		||||
			m.writeErrorResponse(w, r, http.StatusBadRequest, "Failed to parse headers", int64(ers.InvalidFormat().FullCode()))
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 驗證 Token
 | 
			
		||||
		claim, err := m.TokenRPC.GetSystemClaimByAccessToken(r.Context(), &permissionRPC.GetSystemClaimReq{
 | 
			
		||||
			AccessToken: header.Token,
 | 
			
		||||
			IsExpired:   true,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// 是否需要紀錄錯誤,是不是只要紀錄除了驗證失敗或過期之外的真錯誤
 | 
			
		||||
			m.writeErrorResponse(w, r,
 | 
			
		||||
				http.StatusUnauthorized, "failed to verify toke",
 | 
			
		||||
				int64(100400))
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 驗證 Token 是否在黑名單中
 | 
			
		||||
		if _, err := m.TokenRPC.ValidationToken(r.Context(), &permissionRPC.ValidationTokenReq{Token: header.Token}); err != nil {
 | 
			
		||||
			m.writeErrorResponse(w, r, http.StatusForbidden,
 | 
			
		||||
				"failed to get toke",
 | 
			
		||||
				int64(100400))
 | 
			
		||||
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 設置 context 並傳遞給下一個處理器
 | 
			
		||||
		ctx := SetContext(r, claim.Data)
 | 
			
		||||
		//nolint:contextcheck
 | 
			
		||||
		next(w, r.WithContext(ctx))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SetContext(r *http.Request, claim map[string]string) context.Context {
 | 
			
		||||
	ctx := context.WithValue(r.Context(), token.Role.String(), claim[token.Role.String()])
 | 
			
		||||
	ctx = context.WithValue(ctx, token.UID.String(), claim[token.UID.String()])
 | 
			
		||||
	ctx = context.WithValue(ctx, token.Account.String(), claim[token.Account.String()])
 | 
			
		||||
	ctx = context.WithValue(ctx, token.Scope.String(), claim[token.Scope.String()])
 | 
			
		||||
	ctx = context.WithValue(ctx, token.Device.String(), claim[token.Device.String()])
 | 
			
		||||
 | 
			
		||||
	return ctx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// writeErrorResponse 用於處理錯誤回應
 | 
			
		||||
func (m *AuthMiddleware) writeErrorResponse(w http.ResponseWriter, r *http.Request, statusCode int, message string, code int64) {
 | 
			
		||||
	httpx.WriteJsonCtx(r.Context(), w, statusCode, types.BaseResponse{
 | 
			
		||||
		Status: types.Status{
 | 
			
		||||
			Code:    code,
 | 
			
		||||
			Message: message,
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
package svc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/config"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/grafana/pyroscope-go"
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/logx"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InitPyroScope(c config.Config) {
 | 
			
		||||
	if c.PyroScope.Enable {
 | 
			
		||||
		podName := os.Getenv("POD_NAME")
 | 
			
		||||
 | 
			
		||||
		_, err := pyroscope.Start(pyroscope.Config{
 | 
			
		||||
			ApplicationName: fmt.Sprintf("biz-%s-%s", c.Name, podName),
 | 
			
		||||
			ServerAddress:   c.PyroScope.URL,
 | 
			
		||||
			Logger:          logx.WithContext(context.Background()),
 | 
			
		||||
			ProfileTypes: []pyroscope.ProfileType{
 | 
			
		||||
				pyroscope.ProfileCPU,
 | 
			
		||||
 | 
			
		||||
				pyroscope.ProfileInuseObjects,
 | 
			
		||||
				pyroscope.ProfileAllocObjects,
 | 
			
		||||
				pyroscope.ProfileInuseSpace,
 | 
			
		||||
				pyroscope.ProfileAllocSpace,
 | 
			
		||||
 | 
			
		||||
				pyroscope.ProfileGoroutines,
 | 
			
		||||
 | 
			
		||||
				pyroscope.ProfileMutexCount,
 | 
			
		||||
				pyroscope.ProfileMutexDuration,
 | 
			
		||||
 | 
			
		||||
				pyroscope.ProfileBlockCount,
 | 
			
		||||
				pyroscope.ProfileBlockDuration,
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logx.WithCallerSkip(1).WithFields(logx.LogField{
 | 
			
		||||
				Key: "error", Value: err.Error(),
 | 
			
		||||
			}).Error("failed to init pyroscope")
 | 
			
		||||
 | 
			
		||||
			panic(fmt.Sprintf("Pyroscope start err: %s", err.Error()))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
package svc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"biz-member-gateway/internal/config"
 | 
			
		||||
	"biz-member-gateway/internal/middleware"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/limit"
 | 
			
		||||
	"github.com/zeromicro/go-zero/core/stores/redis"
 | 
			
		||||
 | 
			
		||||
	"github.com/zeromicro/go-zero/rest"
 | 
			
		||||
	"github.com/zeromicro/go-zero/zrpc"
 | 
			
		||||
 | 
			
		||||
	ers "code.30cm.net/digimon/library-go/errors"
 | 
			
		||||
	"code.30cm.net/digimon/library-go/errors/code"
 | 
			
		||||
	vi "code.30cm.net/digimon/library-go/validator"
 | 
			
		||||
	memberRPC "code.30cm.net/digimon/proto-all/pkg/member"
 | 
			
		||||
	notificationRPC "code.30cm.net/digimon/proto-all/pkg/notification"
 | 
			
		||||
	permissionRPC "code.30cm.net/digimon/proto-all/pkg/permission"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ServiceContext struct {
 | 
			
		||||
	Config         config.Config
 | 
			
		||||
	AuthMiddleware rest.Middleware
 | 
			
		||||
 | 
			
		||||
	MemberRPC       memberRPC.AccountClient
 | 
			
		||||
	TokenRPC        permissionRPC.TokenServiceClient
 | 
			
		||||
	NotificationRPC notificationRPC.SenderServiceClient
 | 
			
		||||
 | 
			
		||||
	// 但是底線就是 redis 其他db 絕對不行
 | 
			
		||||
	Redis *redis.Redis
 | 
			
		||||
 | 
			
		||||
	VerifyLimiter *limit.PeriodLimit
 | 
			
		||||
	Validate      vi.Validate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewServiceContext(c config.Config) *ServiceContext {
 | 
			
		||||
	ers.Scope = code.CloudEPPortalGW
 | 
			
		||||
	tokenService := permissionRPC.NewTokenServiceClient(zrpc.MustNewClient(c.PermissionRPC).Conn())
 | 
			
		||||
 | 
			
		||||
	InitPyroScope(c)
 | 
			
		||||
 | 
			
		||||
	// 啟動Redis
 | 
			
		||||
	newRedis, err := redis.NewRedis(c.RedisCfg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &ServiceContext{
 | 
			
		||||
		Config: c,
 | 
			
		||||
		AuthMiddleware: middleware.NewAuthMiddleware(middleware.AuthMiddlewareParam{
 | 
			
		||||
			TokenRPC: tokenService,
 | 
			
		||||
		}).Handle,
 | 
			
		||||
		MemberRPC:       memberRPC.NewAccountClient(zrpc.MustNewClient(c.MemberRPC).Conn()),
 | 
			
		||||
		TokenRPC:        tokenService,
 | 
			
		||||
		NotificationRPC: notificationRPC.NewSenderServiceClient(zrpc.MustNewClient(c.NotificationRPC).Conn()),
 | 
			
		||||
		Redis:           newRedis,
 | 
			
		||||
		Validate: vi.MustValidator(
 | 
			
		||||
			WithDecimalGt(),
 | 
			
		||||
			WithDecimalGte(),
 | 
			
		||||
			WithHTTPURL(),
 | 
			
		||||
			WithRfc3339Format(),
 | 
			
		||||
		),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,83 @@
 | 
			
		|||
package svc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	vi "code.30cm.net/digimon/library-go/validator"
 | 
			
		||||
	"github.com/go-playground/validator/v10"
 | 
			
		||||
	"github.com/shopspring/decimal"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// WithDecimalGt 是否大於等於
 | 
			
		||||
func WithDecimalGt() vi.Option {
 | 
			
		||||
	return vi.Option{
 | 
			
		||||
		ValidatorName: "decimalGt",
 | 
			
		||||
		ValidatorFunc: func(fl validator.FieldLevel) bool {
 | 
			
		||||
			if val, ok := fl.Field().Interface().(string); ok {
 | 
			
		||||
				value, err := decimal.NewFromString(val)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return false
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				conditionValue, err := decimal.NewFromString(fl.Param())
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return false
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return value.GreaterThan(conditionValue)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithDecimalGte 是否大於等於
 | 
			
		||||
func WithDecimalGte() vi.Option {
 | 
			
		||||
	return vi.Option{
 | 
			
		||||
		ValidatorName: "decimalGte",
 | 
			
		||||
		ValidatorFunc: func(fl validator.FieldLevel) bool {
 | 
			
		||||
			if val, ok := fl.Field().Interface().(string); ok {
 | 
			
		||||
				value, err := decimal.NewFromString(val)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return false
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				conditionValue, err := decimal.NewFromString(fl.Param())
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return false
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return value.GreaterThanOrEqual(conditionValue)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WithHTTPURL() vi.Option {
 | 
			
		||||
	return vi.Option{
 | 
			
		||||
		ValidatorName: "httpurl",
 | 
			
		||||
		ValidatorFunc: func(fl validator.FieldLevel) bool {
 | 
			
		||||
			value := fl.Field().String()
 | 
			
		||||
			// 使用正則檢查是否以 http:// 或 https:// 開頭
 | 
			
		||||
			re := regexp.MustCompile(`^https?://`)
 | 
			
		||||
 | 
			
		||||
			return re.MatchString(value)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WithRfc3339Format() vi.Option {
 | 
			
		||||
	return vi.Option{
 | 
			
		||||
		ValidatorName: "rfc3339",
 | 
			
		||||
		ValidatorFunc: func(fl validator.FieldLevel) bool {
 | 
			
		||||
			// 使用正則檢查是否以 http:// 或 https:// 開頭
 | 
			
		||||
			_, err := time.Parse(time.RFC3339, fl.Field().String())
 | 
			
		||||
 | 
			
		||||
			return err == nil
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
// Code generated by goctl. DO NOT EDIT.
 | 
			
		||||
// goctl 1.8.1
 | 
			
		||||
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
type BaseResponse struct {
 | 
			
		||||
	Status Status      `json:"status"` // 狀態
 | 
			
		||||
	Data   interface{} `json:"data"`   // 資料
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BindingUserInfoReq struct {
 | 
			
		||||
	VerifyHeader
 | 
			
		||||
	PreferredLanguage string `json:"preferred_language,optional" validate:"oneof=zh-tw en-us"` // 使用語言
 | 
			
		||||
	Currency          string `json:"currency,optional" validate:"oneof=TWD USD"`
 | 
			
		||||
	AvatarURL         string `json:"avatar_url,optional"` // 頭像 URL(可選)
 | 
			
		||||
	Nickname          string `json:"nickname,optional"`
 | 
			
		||||
	FullName          string `json:"full_name,optional"`                              // 用戶全名
 | 
			
		||||
	GenderCode        string `json:"gender_code" validate:"oneof=secret male female"` // 性別代碼
 | 
			
		||||
	Birthday          string `json:"birthday,optional" validate:"rfc3339"`            // 生日 (格式: unix)
 | 
			
		||||
	Address           string `json:"address,optional"`                                // 地址
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CheckoutVerifyReq struct {
 | 
			
		||||
	VerifyHeader
 | 
			
		||||
	Account    string `json:"account" validate:"required"`            // 帳號名稱
 | 
			
		||||
	CodeType   string `json:"code_type" validate:"oneof=email phone"` // 驗證碼類型 1 信箱 2 手機
 | 
			
		||||
	VerifyCode string `json:"verify_code" validate:"required,len=6"`  // 驗證碼,長度為6
 | 
			
		||||
	UID        string `json:"uid" validate:"required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CreateAccountRequest struct {
 | 
			
		||||
	Account     string `json:"account" validate:"required"`                        // 帳號名稱(line code 輸入在這邊)
 | 
			
		||||
	Token       string `json:"token" validate:"required"`                          // 密碼或平台token,密碼請 sha256 轉碼,如果三方token 請隨便給一個 sha256 字串
 | 
			
		||||
	TokenCheck  string `json:"token_check" validate:"required"`                    // 密碼或平台token,token 請保持原樣,填在這邊,不用管 token
 | 
			
		||||
	Platform    string `json:"platform" validate:"oneof=platform google line"`     // 平台名稱 (platform) 平台、google、line
 | 
			
		||||
	AccountType string `json:"account_type" validate:"oneof=phone email platform"` // 帳號類型 phone(手機)、email(信箱)、platform(自定義帳號) -> (如果為第三方都寫 platform)
 | 
			
		||||
	MemberLoginHeader
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ForgetPasswordCodeReq struct {
 | 
			
		||||
	Account     string `json:"account" validate:"required"`               // 帳號名稱
 | 
			
		||||
	AccountType string `json:"account_type" validate:"oneof=phone email"` // 帳號類型 (phone) 手機 (email) 信箱
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoginReq struct {
 | 
			
		||||
	Account     string `json:"account" validate:"required"`                        // 帳號名稱
 | 
			
		||||
	Token       string `json:"token"`                                              // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
	Platform    string `json:"platform" validate:"oneof=platform google line"`     // 平台名稱 platform, google
 | 
			
		||||
	AccountType string `json:"account_type" validate:"oneof=phone email platform"` // 帳號類型 1 手機 2 信箱 3 自定義帳號
 | 
			
		||||
	MemberLoginHeader
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LoginTokenResp struct {
 | 
			
		||||
	UID          string `json:"uid"`           //  Account
 | 
			
		||||
	AccessToken  string `json:"access_token"`  // 訪問令牌 預設 5 分鐘過期
 | 
			
		||||
	RefreshToken string `json:"refresh_token"` // 刷新令牌 (預設一天過期,只能用一次),當呼叫更新token api 時,會自動把舊的失效,變成新的 refresh_token ,前端要記得過其實協助刷新,刷新不過表示全失效了(重新登入)
 | 
			
		||||
	TokenType    string `json:"token_type"`    // Bearer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MemberLoginHeader struct {
 | 
			
		||||
	DeviceID  string `header:"device_id"`
 | 
			
		||||
	IpAddress string `header:"ip_address"`
 | 
			
		||||
	Brewser   string `header:"brewser"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ModifyPasswdReq struct {
 | 
			
		||||
	VerifyHeader
 | 
			
		||||
	NewToken      string `json:"token" validate:"required,len=64"`       // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
	NewTokenCheck string `json:"token_check" validate:"required,len=64"` // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PreVerifyForgetPasswdReq struct {
 | 
			
		||||
	Identifier string `json:"identifier" validate:"required"`        // 聯繫方式,可以是 email 或 phone
 | 
			
		||||
	VerifyCode string `json:"verify_code" validate:"required,len=6"` // 驗證碼,長度為6
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RespOK struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Status struct {
 | 
			
		||||
	Code    int64       `json:"code"`            // 狀態碼
 | 
			
		||||
	Message string      `json:"message"`         // 訊息
 | 
			
		||||
	Data    interface{} `json:"data,omitempty"`  // 可選的數據,當有返回時才出現
 | 
			
		||||
	Error   interface{} `json:"error,omitempty"` // 可選的錯誤信息
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdatePasswordReq struct {
 | 
			
		||||
	Account    string `json:"account" validate:"required"`            // 帳號名稱
 | 
			
		||||
	VerifyCode string `json:"verify_code" validate:"required,len=6"`  // 驗證碼,長度為6
 | 
			
		||||
	Token      string `json:"token" validate:"required,len=64"`       // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
	TokenCheck string `json:"token_check" validate:"required,len=64"` // 密碼或平台token,密碼請 sha256 轉碼
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateTokenReq struct {
 | 
			
		||||
	UID          string `json:"uid" validate:"required"`           // 誰要更新
 | 
			
		||||
	Token        string `json:"token" validate:"required"`         // access token -> 已過期要被更新的
 | 
			
		||||
	RefreshToken string `json:"refresh_token" validate:"required"` // refresh token ->  重點,要驗證他的
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UserInfo struct {
 | 
			
		||||
	Platform          string `json:"platform"`           // 用戶平台 platform, google, line
 | 
			
		||||
	UID               string `json:"uid"`                // 用戶 UID
 | 
			
		||||
	AvatarURL         string `json:"avatar_url"`         // 頭像 URL(可選)
 | 
			
		||||
	FullName          string `json:"full_name"`          // 用戶全名
 | 
			
		||||
	Nickname          string `json:"nickname"`           // 暱稱(可選)
 | 
			
		||||
	GenderCode        string `json:"gender_code"`        // 性別代碼 mail, femail ,sec
 | 
			
		||||
	Birthdate         string `json:"birthdate"`          // 生日 (格式: 19930417)
 | 
			
		||||
	PhoneNumber       string `json:"phone_number"`       // 電話
 | 
			
		||||
	Address           string `json:"address"`            // 地址
 | 
			
		||||
	Email             string `json:"email"`              // 驗證後的信箱
 | 
			
		||||
	AlarmCategory     string `json:"alarm_category"`     // 告警狀態
 | 
			
		||||
	UserStatus        string `json:"user_status"`        // 用戶狀態
 | 
			
		||||
	PreferredLanguage string `json:"preferred_language"` // 使用語言
 | 
			
		||||
	Currency          string `json:"currency"`           // 使用幣種
 | 
			
		||||
	UpdateAt          string `json:"update_at"`
 | 
			
		||||
	CreateAt          string `json:"create_at"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type VerificationCodeRequest struct {
 | 
			
		||||
	VerifyHeader
 | 
			
		||||
	Identifier string `json:"identifier" validate:"required"`                         // 聯繫方式,可以是 email 或 phone
 | 
			
		||||
	CodeType   string `json:"code_type" validate:"oneof=email phone forget_password"` // 驗證碼類型
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type VerifyHeader struct {
 | 
			
		||||
	Token string `header:"token" validate:"required"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
func UnixToRfc3339(t int64) string {
 | 
			
		||||
	res := time.Unix(0, t).UTC()
 | 
			
		||||
 | 
			
		||||
	return res.Format(time.RFC3339)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Rfc3339ToUnix(rfc string) (int64, error) {
 | 
			
		||||
	t, err := time.Parse(time.RFC3339, rfc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return t.UnixNano(), nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
func ToPointer[T any](v T) *T { return &v }
 | 
			
		||||
		Loading…
	
		Reference in New Issue