feat: add document and redis into project
This commit is contained in:
parent
4828386370
commit
d435713e3b
|
@ -0,0 +1,236 @@
|
||||||
|
# Go Linting 配置說明
|
||||||
|
|
||||||
|
本項目使用現代化的 Go linting 工具來確保代碼質量和風格一致性。
|
||||||
|
|
||||||
|
## 工具介紹
|
||||||
|
|
||||||
|
### golangci-lint
|
||||||
|
- **現代化的 Go linter 聚合工具**,整合了多個 linter
|
||||||
|
- 比傳統的 `golint` 更快、更全面
|
||||||
|
- 支持並行執行和緩存
|
||||||
|
- 配置文件:`.golangci.yml`
|
||||||
|
|
||||||
|
## 安裝
|
||||||
|
|
||||||
|
### 安裝 golangci-lint
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
brew install golangci-lint
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安裝其他工具
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 格式化工具
|
||||||
|
go install mvdan.cc/gofumpt@latest
|
||||||
|
go install golang.org/x/tools/cmd/goimports@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### Makefile 命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 基本代碼檢查
|
||||||
|
make lint
|
||||||
|
|
||||||
|
# 自動修復可修復的問題
|
||||||
|
make lint-fix
|
||||||
|
|
||||||
|
# 詳細輸出
|
||||||
|
make lint-verbose
|
||||||
|
|
||||||
|
# 只檢查新問題(與 main 分支比較)
|
||||||
|
make lint-new
|
||||||
|
|
||||||
|
# 格式化代碼
|
||||||
|
make fmt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 直接使用 golangci-lint
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 基本檢查
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
# 自動修復
|
||||||
|
golangci-lint run --fix
|
||||||
|
|
||||||
|
# 檢查特定目錄
|
||||||
|
golangci-lint run ./pkg/...
|
||||||
|
|
||||||
|
# 詳細輸出
|
||||||
|
golangci-lint run -v
|
||||||
|
|
||||||
|
# 只顯示新問題
|
||||||
|
golangci-lint run --new-from-rev=main
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置說明
|
||||||
|
|
||||||
|
### 啟用的 Linters
|
||||||
|
|
||||||
|
我們的配置啟用了以下 linter 類別:
|
||||||
|
|
||||||
|
#### 核心檢查
|
||||||
|
- `errcheck`: 檢查未處理的錯誤
|
||||||
|
- `gosimple`: 簡化代碼建議
|
||||||
|
- `govet`: 檢查常見錯誤
|
||||||
|
- `staticcheck`: 靜態分析
|
||||||
|
- `typecheck`: 類型檢查
|
||||||
|
- `unused`: 檢查未使用的變量和函數
|
||||||
|
|
||||||
|
#### 代碼質量
|
||||||
|
- `cyclop`: 循環複雜度檢查
|
||||||
|
- `dupl`: 代碼重複檢測
|
||||||
|
- `funlen`: 函數長度檢查
|
||||||
|
- `gocognit`: 認知複雜度檢查
|
||||||
|
- `gocyclo`: 循環複雜度檢查
|
||||||
|
- `nestif`: 嵌套深度檢查
|
||||||
|
|
||||||
|
#### 格式化
|
||||||
|
- `gofmt`: 格式化檢查
|
||||||
|
- `gofumpt`: 更嚴格的格式化
|
||||||
|
- `goimports`: 導入排序
|
||||||
|
|
||||||
|
#### 命名和風格
|
||||||
|
- `goconst`: 常量檢查
|
||||||
|
- `gocritic`: 代碼評論
|
||||||
|
- `gomnd`: 魔術數字檢查
|
||||||
|
- `stylecheck`: 風格檢查
|
||||||
|
- `varnamelen`: 變量名長度檢查
|
||||||
|
|
||||||
|
#### 安全
|
||||||
|
- `gosec`: 安全檢查
|
||||||
|
|
||||||
|
#### 錯誤處理
|
||||||
|
- `errorlint`: 錯誤處理檢查
|
||||||
|
- `nilerr`: nil 錯誤檢查
|
||||||
|
- `wrapcheck`: 錯誤包裝檢查
|
||||||
|
|
||||||
|
### 配置文件結構
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .golangci.yml
|
||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
skip-dirs: [vendor, .git, bin, build, dist, tmp]
|
||||||
|
skip-files: [".*\\.pb\\.go$", ".*\\.gen\\.go$"]
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable: [errcheck, gosimple, govet, ...]
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
# 各個 linter 的詳細配置
|
||||||
|
|
||||||
|
issues:
|
||||||
|
# 問題排除規則
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters: [gomnd, funlen, dupl]
|
||||||
|
```
|
||||||
|
|
||||||
|
## IDE 整合
|
||||||
|
|
||||||
|
### VS Code
|
||||||
|
項目包含 `.vscode/settings.json` 配置:
|
||||||
|
- 自動使用 golangci-lint 進行檢查
|
||||||
|
- 保存時自動格式化
|
||||||
|
- 使用 gofumpt 作為格式化工具
|
||||||
|
|
||||||
|
### GoLand/IntelliJ
|
||||||
|
1. 安裝 golangci-lint 插件
|
||||||
|
2. 在設置中指向項目的 `.golangci.yml` 文件
|
||||||
|
|
||||||
|
## CI/CD 整合
|
||||||
|
|
||||||
|
### GitHub Actions
|
||||||
|
項目包含 `.github/workflows/ci.yml`:
|
||||||
|
- 自動運行測試
|
||||||
|
- 執行 golangci-lint 檢查
|
||||||
|
- 安全掃描
|
||||||
|
- 依賴檢查
|
||||||
|
|
||||||
|
### 本地 Git Hooks
|
||||||
|
可以設置 pre-commit hook:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/sh
|
||||||
|
# .git/hooks/pre-commit
|
||||||
|
make lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常見問題
|
||||||
|
|
||||||
|
### 1. 如何忽略特定的檢查?
|
||||||
|
|
||||||
|
在代碼中使用註釋:
|
||||||
|
```go
|
||||||
|
//nolint:gosec // 忽略安全檢查
|
||||||
|
password := "hardcoded"
|
||||||
|
|
||||||
|
//nolint:lll // 忽略行長度檢查
|
||||||
|
url := "https://very-long-url-that-exceeds-line-length-limit.com/api/v1/endpoint"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 如何為測試文件設置不同的規則?
|
||||||
|
|
||||||
|
配置文件中已經為測試文件設置了特殊規則:
|
||||||
|
```yaml
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters: [gomnd, funlen, dupl, lll, goconst]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 如何調整複雜度閾值?
|
||||||
|
|
||||||
|
在 `.golangci.yml` 中調整:
|
||||||
|
```yaml
|
||||||
|
linters-settings:
|
||||||
|
cyclop:
|
||||||
|
max-complexity: 15 # 調整循環複雜度
|
||||||
|
funlen:
|
||||||
|
lines: 100 # 調整函數行數限制
|
||||||
|
statements: 50 # 調整語句數限制
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 性能優化
|
||||||
|
|
||||||
|
- 使用緩存:`golangci-lint cache clean` 清理緩存
|
||||||
|
- 只檢查修改的文件:`--new-from-rev=main`
|
||||||
|
- 並行執行:默認已啟用
|
||||||
|
|
||||||
|
## 升級和維護
|
||||||
|
|
||||||
|
定期更新 golangci-lint:
|
||||||
|
```bash
|
||||||
|
# 檢查版本
|
||||||
|
golangci-lint version
|
||||||
|
|
||||||
|
# 升級到最新版本
|
||||||
|
brew upgrade golangci-lint # macOS
|
||||||
|
# 或重新下載安裝腳本
|
||||||
|
```
|
||||||
|
|
||||||
|
定期檢查配置文件的新選項和 linter:
|
||||||
|
```bash
|
||||||
|
# 查看所有可用的 linter
|
||||||
|
golangci-lint linters
|
||||||
|
|
||||||
|
# 查看配置幫助
|
||||||
|
golangci-lint config -h
|
||||||
|
```
|
||||||
|
|
||||||
|
## 參考資源
|
||||||
|
|
||||||
|
- [golangci-lint 官方文檔](https://golangci-lint.run/)
|
||||||
|
- [Go 代碼風格指南](https://github.com/golang/go/wiki/CodeReviewComments)
|
||||||
|
- [Effective Go](https://golang.org/doc/effective_go.html)
|
10
Makefile
10
Makefile
|
@ -36,7 +36,6 @@ mock-gen: # 建立 mock 資料
|
||||||
fmt: # 格式優化
|
fmt: # 格式優化
|
||||||
$(GOFMT) -w $(GOFILES)
|
$(GOFMT) -w $(GOFILES)
|
||||||
goimports -w ./
|
goimports -w ./
|
||||||
golangci-lint run
|
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: # 編譯專案
|
build: # 編譯專案
|
||||||
|
@ -63,17 +62,13 @@ install: # 安裝依賴
|
||||||
go mod tidy
|
go mod tidy
|
||||||
go mod download
|
go mod download
|
||||||
|
|
||||||
.PHONY: lint
|
|
||||||
lint: # 代碼檢查
|
|
||||||
golangci-lint run
|
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help: # 顯示幫助信息
|
help: # 顯示幫助信息
|
||||||
@echo "Available commands:"
|
@echo "Available commands:"
|
||||||
@echo " test - 運行測試"
|
@echo " test - 運行測試"
|
||||||
@echo " gen-api - 產生 api"
|
@echo " gen-api - 產生 api"
|
||||||
@echo " gen-swagger - 生成 JSON 格式 Swagger 文檔"
|
@echo " gen-doc - 生成 Swagger 文檔"
|
||||||
@echo " gen-swagger-yaml - 生成 YAML 格式 Swagger 文檔"
|
@echo " mock-gen - 建立 mock 資料"
|
||||||
@echo " fmt - 格式化代碼"
|
@echo " fmt - 格式化代碼"
|
||||||
@echo " build - 編譯專案"
|
@echo " build - 編譯專案"
|
||||||
@echo " run - 運行專案"
|
@echo " run - 運行專案"
|
||||||
|
@ -81,6 +76,5 @@ help: # 顯示幫助信息
|
||||||
@echo " docker-build - 構建 Docker 映像"
|
@echo " docker-build - 構建 Docker 映像"
|
||||||
@echo " docker-run - 運行 Docker 容器"
|
@echo " docker-run - 運行 Docker 容器"
|
||||||
@echo " install - 安裝依賴"
|
@echo " install - 安裝依賴"
|
||||||
@echo " lint - 代碼檢查"
|
|
||||||
@echo " help - 顯示幫助信息"
|
@echo " help - 顯示幫助信息"
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,31 @@
|
||||||
|
services:
|
||||||
|
mongo:
|
||||||
|
image: mongo:8.0
|
||||||
|
container_name: mongo
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: root
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: example
|
||||||
|
|
||||||
|
etcd:
|
||||||
|
image: quay.io/coreos/etcd:v3.5.5
|
||||||
|
container_name: etcd
|
||||||
|
restart: always
|
||||||
|
command: >
|
||||||
|
/usr/local/bin/etcd
|
||||||
|
--data-dir=/etcd-data
|
||||||
|
--name=etcd
|
||||||
|
--listen-client-urls=http://0.0.0.0:2379
|
||||||
|
--advertise-client-urls=http://etcd:2379
|
||||||
|
ports:
|
||||||
|
- "2379:2379"
|
||||||
|
- "2380:2380"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7.0
|
||||||
|
container_name: redis
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
|
@ -1,3 +1,44 @@
|
||||||
Name: gateway
|
Name: gateway
|
||||||
Host: 0.0.0.0
|
Host: 0.0.0.0
|
||||||
Port: 8888
|
Port: 8888
|
||||||
|
|
||||||
|
Cache:
|
||||||
|
- Host: 127.0.0.1:6379
|
||||||
|
type: node
|
||||||
|
CacheExpireTime: 1s
|
||||||
|
CacheWithNotFoundExpiry: 1s
|
||||||
|
|
||||||
|
RedisConf:
|
||||||
|
Host: 127.0.0.1:6379
|
||||||
|
Type: node
|
||||||
|
Pass: ""
|
||||||
|
Tls: false
|
||||||
|
|
||||||
|
Mongo:
|
||||||
|
Schema: mongodb
|
||||||
|
Host: "127.0.0.1:27017"
|
||||||
|
User: "root"
|
||||||
|
Password: "example"
|
||||||
|
Port: ""
|
||||||
|
Database: digimon_member
|
||||||
|
ReplicaName: "rs0"
|
||||||
|
MaxStaleness: 30m
|
||||||
|
MaxPoolSize: 30
|
||||||
|
MinPoolSize: 10
|
||||||
|
MaxConnIdleTime: 30m
|
||||||
|
Compressors:
|
||||||
|
- f
|
||||||
|
EnableStandardReadWriteSplitMode: true
|
||||||
|
ConnectTimeoutMs : 300
|
||||||
|
|
||||||
|
Bcrypt:
|
||||||
|
Cost: 10
|
||||||
|
|
||||||
|
GoogleAuth:
|
||||||
|
ClientID: xxx.apps.googleusercontent.com
|
||||||
|
AuthURL: x
|
||||||
|
|
||||||
|
LineAuth:
|
||||||
|
ClientID : "200000000"
|
||||||
|
ClientSecret : xxxxx
|
||||||
|
RedirectURI : http://localhost:8080/line.html
|
2931
gateway.json
2931
gateway.json
File diff suppressed because it is too large
Load Diff
|
@ -3,7 +3,7 @@ syntax = "v1"
|
||||||
// ================ 通用響應 ================
|
// ================ 通用響應 ================
|
||||||
type (
|
type (
|
||||||
// 成功響應
|
// 成功響應
|
||||||
OKResp {
|
RespOK {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Data interface{} `json:"data,omitempty"`
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
@ -26,8 +26,7 @@ type (
|
||||||
|
|
||||||
BaseReq {}
|
BaseReq {}
|
||||||
|
|
||||||
VerifyHeader {
|
Authorization {
|
||||||
Token string `header:"token" validate:"required"`
|
Authorization string `header:"Authorization" validate:"required"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,554 +1,307 @@
|
||||||
syntax = "v1"
|
syntax = "v1"
|
||||||
|
|
||||||
// ================ 請求/響應結構 ================
|
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Type: 授權與驗證 (Auth)
|
||||||
|
// =================================================================
|
||||||
type (
|
type (
|
||||||
// 創建帳號請求
|
// CredentialsPayload 傳統帳號密碼註冊的資料
|
||||||
CreateUserAccountReq {
|
CredentialsPayload {
|
||||||
LoginID string `json:"login_id" validate:"required,min=3,max=50"`
|
Password string `json:"password" validate:"required,min=8,max=128"` // 密碼 (後端應使用 bcrypt 進行雜湊)
|
||||||
Platform string `json:"platform" validate:"required,oneof=platform google line apple"`
|
PasswordConfirm string `json:"password_confirm" validate:"eqfield=Password"` // 確認密碼
|
||||||
Token string `json:"token" validate:"required,min=8,max=128"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 綁定用戶請求
|
// PlatformPayload 第三方平台註冊的資料
|
||||||
BindingUserReq {
|
PlatformPayload {
|
||||||
UID string `json:"uid" validate:"required,min=1,max=50"`
|
Provider string `json:"provider" validate:"required,oneof=google line apple"` // 平台名稱
|
||||||
LoginID string `json:"login_id" validate:"required,min=3,max=50"`
|
Token string `json:"token" validate:"required"` // 平台提供的 Access Token 或 ID Token
|
||||||
Type int `json:"type" validate:"required,oneof=1 2 3"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 綁定用戶響應
|
// RegisterReq 註冊請求 (整合了兩種方式)
|
||||||
BindingUserResp {
|
LoginReq {
|
||||||
UID string `json:"uid"`
|
AuthMethod string `json:"auth_method" validate:"required,oneof=credentials platform"`
|
||||||
LoginID string `json:"login_id"`
|
LoginID string `json:"login_id" validate:"required,min=3,max=50"` // 信箱或手機號碼
|
||||||
Type int `json:"type"`
|
Credentials *CredentialsPayload `json:"credentials,optional"` // AuthMethod 為 'credentials' 時使用
|
||||||
|
Platform *PlatformPayload `json:"platform,optional"` // AuthMethod 為 'platform' 時使用
|
||||||
}
|
}
|
||||||
|
|
||||||
// 創建用戶資料請求
|
// LoginResp 登入/註冊成功後的響應
|
||||||
CreateUserInfoReq {
|
LoginResp {
|
||||||
UID string `json:"uid" validate:"required,min=1,max=50"`
|
UID string `json:"uid"`
|
||||||
AlarmType int `json:"alarm_type" validate:"required,oneof=0 1 2"`
|
AccessToken string `json:"access_token"`
|
||||||
Status int `json:"status" validate:"required,oneof=0 1 2 3"`
|
RefreshToken string `json:"refresh_token"`
|
||||||
Language string `json:"language" validate:"required,min=2,max=10"`
|
TokenType string `json:"token_type"` // 通常固定為 "Bearer"
|
||||||
Currency string `json:"currency" validate:"required,min=3,max=3"`
|
}
|
||||||
Avatar *string `json:"avatar,omitempty"`
|
// --- 密碼重設流程 ---
|
||||||
NickName *string `json:"nick_name,omitempty" validate:"omitempty,min=1,max=50"`
|
|
||||||
FullName *string `json:"full_name,omitempty" validate:"omitempty,min=1,max=100"`
|
// RequestPasswordResetReq 請求發送「忘記密碼」的驗證碼
|
||||||
Gender *int64 `json:"gender,omitempty" validate:"omitempty,oneof=0 1 2"`
|
RequestPasswordResetReq {
|
||||||
Birthdate *int64 `json:"birthdate,omitempty" validate:"omitempty,min=19000101,max=21001231"`
|
Identifier string `json:"identifier" validate:"required,email|phone"` // 使用者帳號 (信箱或手機)
|
||||||
PhoneNumber *string `json:"phone_number,omitempty" validate:"omitempty,min=10,max=20"`
|
AccountType string `json:"account_type" validate:"required,oneof=email phone"`
|
||||||
Email *string `json:"email,omitempty" validate:"omitempty,email"`
|
|
||||||
Address *string `json:"address,omitempty" validate:"omitempty,max=200"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取帳號資訊響應
|
// VerifyCodeReq 驗證碼校驗 (通用)
|
||||||
GetAccountInfoResp {
|
VerifyCodeReq {
|
||||||
Data CreateUserAccountReq `json:"data"`
|
Identifier string `json:"identifier" validate:"required"`
|
||||||
|
VerifyCode string `json:"verify_code" validate:"required,len=6"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新用戶資料請求
|
// ResetPasswordReq 使用已驗證的 Code 來重設密碼 只有用帳號密碼的才能發送重設密碼
|
||||||
UpdateUserInfoReq {
|
ResetPasswordReq {
|
||||||
UID string `json:"uid" validate:"required,min=1,max=50"`
|
Identifier string `json:"identifier" validate:"required"`
|
||||||
Language *string `json:"language,omitempty" validate:"omitempty,min=2,max=10"`
|
VerifyCode string `json:"verify_code" validate:"required"` // 來自上一步驗證通過的 Code,作為一種「票證」
|
||||||
Currency *string `json:"currency,omitempty" validate:"omitempty,min=3,max=3"`
|
Password string `json:"password" validate:"required,min=8,max=128"` // 新密碼
|
||||||
NickName *string `json:"nick_name,omitempty" validate:"omitempty,min=1,max=50"`
|
PasswordConfirm string `json:"password_confirm" validate:"eqfield=Password"` // 確認新密碼
|
||||||
Avatar *string `json:"avatar,omitempty"`
|
|
||||||
AlarmType *int `json:"alarm_type,omitempty" validate:"omitempty,oneof=0 1 2"`
|
|
||||||
Status *int `json:"status,omitempty" validate:"omitempty,oneof=0 1 2 3"`
|
|
||||||
FullName *string `json:"full_name,omitempty" validate:"omitempty,min=1,max=100"`
|
|
||||||
Gender *int64 `json:"gender,omitempty" validate:"omitempty,oneof=0 1 2"`
|
|
||||||
Birthdate *int64 `json:"birthdate,omitempty" validate:"omitempty,min=19000101,max=21001231"`
|
|
||||||
Address *string `json:"address,omitempty" validate:"omitempty,max=200"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取 UID 請求
|
// --- 4. 權杖刷新 ---
|
||||||
GetUIDByAccountReq {
|
// RefreshTokenReq 更新 AccessToken
|
||||||
Account string `json:"account" validate:"required,min=3,max=50"`
|
RefreshTokenReq {
|
||||||
|
RefreshToken string `json:"refresh_token" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 獲取 UID 響應
|
// RefreshTokenResp 刷新權杖後的響應
|
||||||
GetUIDByAccountResp {
|
RefreshTokenResp {
|
||||||
UID string `json:"uid"`
|
AccessToken string `json:"access_token"`
|
||||||
Account string `json:"account"`
|
RefreshToken string `json:"refresh_token"` // 可選:某些策略下刷新後也會換發新的 Refresh Token
|
||||||
}
|
TokenType string `json:"token_type"`
|
||||||
|
|
||||||
// 更新密碼請求
|
|
||||||
UpdateTokenReq {
|
|
||||||
Account string `json:"account" validate:"required,min=3,max=50"`
|
|
||||||
Token string `json:"token" validate:"required,min=8,max=128"`
|
|
||||||
Platform int `json:"platform" validate:"required,oneof=1 2 3 4"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成驗證碼請求
|
|
||||||
GenerateRefreshCodeReq {
|
|
||||||
Account string `json:"account" validate:"required,min=3,max=50"`
|
|
||||||
CodeType int `json:"code_type" validate:"required,oneof=1 2 3"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 驗證碼響應
|
|
||||||
VerifyCodeResp {
|
|
||||||
VerifyCode string `json:"verify_code"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成驗證碼響應
|
|
||||||
GenerateRefreshCodeResp {
|
|
||||||
Data VerifyCodeResp `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 驗證碼請求
|
|
||||||
VerifyRefreshCodeReq {
|
|
||||||
Account string `json:"account" validate:"required,min=3,max=50"`
|
|
||||||
CodeType int `json:"code_type" validate:"required,oneof=1 2 3"`
|
|
||||||
VerifyCode string `json:"verify_code" validate:"required,min=4,max=10"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新狀態請求
|
|
||||||
UpdateStatusReq {
|
|
||||||
UID string `json:"uid" validate:"required,min=1,max=50"`
|
|
||||||
Status int `json:"status" validate:"required,oneof=0 1 2 3"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 獲取用戶資訊請求
|
|
||||||
GetUserInfoReq {
|
|
||||||
UID string `json:"uid,omitempty" validate:"omitempty,min=1,max=50"`
|
|
||||||
NickName *string `json:"nick_name,omitempty" validate:"omitempty,min=1,max=50"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用戶資訊
|
|
||||||
UserInfo {
|
|
||||||
UID string `json:"uid"`
|
|
||||||
AvatarURL *string `json:"avatar_url,omitempty"`
|
|
||||||
FullName *string `json:"full_name,omitempty"`
|
|
||||||
NickName *string `json:"nick_name,omitempty"`
|
|
||||||
GenderCode *int64 `json:"gender_code,omitempty"`
|
|
||||||
Birthday *int64 `json:"birthday,omitempty"`
|
|
||||||
Phone *string `json:"phone,omitempty"`
|
|
||||||
Email *string `json:"email,omitempty"`
|
|
||||||
Address *string `json:"address,omitempty"`
|
|
||||||
AlarmType int `json:"alarm_type"`
|
|
||||||
Status int `json:"status"`
|
|
||||||
Language string `json:"language"`
|
|
||||||
Currency string `json:"currency"`
|
|
||||||
CreateTime int64 `json:"create_time"`
|
|
||||||
UpdateTime int64 `json:"update_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 獲取用戶資訊響應
|
|
||||||
GetUserInfoResp {
|
|
||||||
Data UserInfo `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用戶列表請求
|
|
||||||
ListUserInfoReq {
|
|
||||||
AlarmType *int `json:"alarm_type,omitempty" validate:"omitempty,oneof=0 1 2"`
|
|
||||||
Status *int `json:"status,omitempty" validate:"omitempty,oneof=0 1 2 3"`
|
|
||||||
CreateStartTime *int64 `json:"create_start_time,omitempty"`
|
|
||||||
CreateEndTime *int64 `json:"create_end_time,omitempty"`
|
|
||||||
PageSize int `json:"page_size" validate:"required,min=1,max=100"`
|
|
||||||
PageIndex int `json:"page_index" validate:"required,min=1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用戶列表響應
|
|
||||||
ListUserInfoResp {
|
|
||||||
Data []UserInfo `json:"data"`
|
|
||||||
Pager PagerResp `json:"pager"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 驗證認證結果請求
|
|
||||||
VerifyAuthResultReq {
|
|
||||||
Token string `json:"token" validate:"required,min=1"`
|
|
||||||
Account *string `json:"account,omitempty" validate:"omitempty,min=3,max=50"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 驗證認證結果響應
|
|
||||||
VerifyAuthResultResp {
|
|
||||||
Status bool `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Google 認證結果響應
|
|
||||||
VerifyGoogleAuthResultResp {
|
|
||||||
Status bool `json:"status"`
|
|
||||||
Iss *string `json:"iss,omitempty"`
|
|
||||||
Sub *string `json:"sub,omitempty"`
|
|
||||||
Aud *string `json:"aud,omitempty"`
|
|
||||||
Exp *string `json:"exp,omitempty"`
|
|
||||||
Iat *string `json:"iat,omitempty"`
|
|
||||||
Email *string `json:"email,omitempty"`
|
|
||||||
EmailVerified *string `json:"email_verified,omitempty"`
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
Picture *string `json:"picture,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 綁定驗證 Email 請求
|
|
||||||
BindVerifyEmailReq {
|
|
||||||
UID string `json:"uid" validate:"required,min=1,max=50"`
|
|
||||||
Email string `json:"email" validate:"required,email"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 綁定驗證 Phone 請求
|
|
||||||
BindVerifyPhoneReq {
|
|
||||||
UID string `json:"uid" validate:"required,min=1,max=50"`
|
|
||||||
Phone string `json:"phone" validate:"required,min=10,max=20"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINE 獲取 Token 請求
|
|
||||||
LineGetTokenReq {
|
|
||||||
Code string `json:"code" validate:"required,min=1"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINE Access Token 響應
|
|
||||||
LineAccessTokenResp {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINE 用戶資料
|
|
||||||
LineUserProfile {
|
|
||||||
DisplayName string `json:"display_name"`
|
|
||||||
UserID string `json:"user_id"`
|
|
||||||
PictureURL string `json:"picture_url"`
|
|
||||||
StatusMessage string `json:"status_message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LINE 獲取用戶資訊請求
|
|
||||||
LineGetUserInfoReq {
|
|
||||||
Token string `json:"token" validate:"required,min=1"`
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ================ API 路由 ================
|
// =================================================================
|
||||||
|
// Type: 使用者資訊與管理 (User)
|
||||||
|
// =================================================================
|
||||||
|
type (
|
||||||
|
// --- 1. 會員資訊 ---
|
||||||
|
|
||||||
|
// UserInfoResp 用於獲取會員資訊的標準響應結構
|
||||||
|
UserInfoResp {
|
||||||
|
Platform string `json:"platform"` // 註冊平台
|
||||||
|
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"` // 性別代碼
|
||||||
|
Birthdate string `json:"birthdate"` // 生日 (格式: 1993-04-17)
|
||||||
|
PhoneNumber string `json:"phone_number"` // 電話
|
||||||
|
IsPhoneVerified bool `json:"is_phone_verified"` // 手機是否已驗證
|
||||||
|
Email string `json:"email"` // 信箱
|
||||||
|
IsEmailVerified bool `json:"is_email_verified"` // 信箱是否已驗證
|
||||||
|
Address string `json:"address"` // 地址
|
||||||
|
UserStatus string `json:"user_status"` // 用戶狀態
|
||||||
|
PreferredLanguage string `json:"preferred_language"` // 偏好語言
|
||||||
|
Currency string `json:"currency"` // 偏好幣種
|
||||||
|
National string `json:"national"` // 國家
|
||||||
|
PostCode string `json:"post_code"` // 郵遞區號
|
||||||
|
Carrier string `json:"carrier"` // 載具
|
||||||
|
Role string `json:"role"` // 角色
|
||||||
|
UpdateAt string `json:"update_at"`
|
||||||
|
CreateAt string `json:"create_at"`
|
||||||
|
Authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserInfoReq 更新會員資訊的請求結構
|
||||||
|
UpdateUserInfoReq {
|
||||||
|
AvatarURL *string `json:"avatar_url,optional"` // 頭像 URL
|
||||||
|
FullName *string `json:"full_name,optional"` // 用戶全名
|
||||||
|
Nickname *string `json:"nickname,optional"` // 暱稱
|
||||||
|
GenderCode *string `json:"gender_code,optional" validate:"omitempty,oneof=secret male female"` // 性別
|
||||||
|
Birthdate *string `json:"birthdate,optional"` // 生日 (格式: 1993-04-17)
|
||||||
|
Address *string `json:"address,optional"` // 地址
|
||||||
|
PreferredLanguage *string `json:"preferred_language,optional" validate:"omitempty,oneof=zh-tw en-us"` // 語言
|
||||||
|
Currency *string `json:"currency,optional" validate:"omitempty,oneof=TWD USD"` // 貨幣代號
|
||||||
|
National *string `json:"national,optional"` // 國家
|
||||||
|
PostCode *string `json:"post_code,optional"` // 郵遞區號
|
||||||
|
Carrier *string `json:"carrier,optional"` // 載具
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. 修改密碼 (已登入狀態) ---
|
||||||
|
// UpdatePasswordReq 修改密碼的請求
|
||||||
|
UpdatePasswordReq {
|
||||||
|
CurrentPassword string `json:"current_password" validate:"required"`
|
||||||
|
NewPassword string `json:"new_password" validate:"required,min=8,max=128"`
|
||||||
|
NewPasswordConfirm string `json:"new_password_confirm" validate:"eqfield=NewPassword"`
|
||||||
|
Authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. 通用驗證碼 (已登入狀態) ---
|
||||||
|
// RequestVerificationCodeReq 請求發送驗證碼
|
||||||
|
RequestVerificationCodeReq {
|
||||||
|
// 驗證目的:'email_verification' 或 'phone_verification'
|
||||||
|
Purpose string `json:"purpose" validate:"required,oneof=email_verification phone_verification"`
|
||||||
|
Authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitVerificationCodeReq 提交驗證碼以完成驗證
|
||||||
|
SubmitVerificationCodeReq {
|
||||||
|
Purpose string `json:"purpose" validate:"required,oneof=email_verification phone_verification"`
|
||||||
|
VerifyCode string `json:"verify_code" validate:"required,len=6"`
|
||||||
|
Authorization
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// Service: 公開 API - 無需登入 (Auth Service)
|
||||||
|
// =================================================================
|
||||||
@server(
|
@server(
|
||||||
group: account
|
group: auth
|
||||||
prefix: /api/v1/account
|
prefix: /api/v1/auth
|
||||||
|
schemes: https
|
||||||
|
timeout: 10s
|
||||||
)
|
)
|
||||||
service gateway {
|
service gateway {
|
||||||
@doc(
|
@doc(
|
||||||
summary: "創建帳號"
|
summary: "註冊新帳號"
|
||||||
description: "創建新的帳號,支援多平台登入"
|
description: "使用傳統帳號密碼或第三方平台進行註冊。成功後直接返回登入後的 Token 資訊。"
|
||||||
)
|
)
|
||||||
/*
|
/*
|
||||||
@respdoc-201 () // 創建成功
|
@respdoc-200 (LoginResp) // 註冊成功,並返回 Token
|
||||||
@respdoc-400 (
|
@respdoc-400 (ErrorResp) "請求參數格式錯誤"
|
||||||
40001: (ErrorResp) 帳號格式錯誤
|
@respdoc-409 (ErrorResp) "帳號已被註冊" // 409 Conflict: 資源衝突
|
||||||
40002: (ErrorResp) 密碼強度不足
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
40003: (ErrorResp) 平台類型無效
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-409 (ErrorResp) // 帳號已存在
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
*/
|
||||||
@handler CreateUserAccount
|
@handler register
|
||||||
post /create (CreateUserAccountReq) returns ()
|
post /register (LoginReq) returns (LoginResp)
|
||||||
|
|
||||||
@doc(
|
@doc(
|
||||||
summary: "獲取帳號資訊"
|
summary: "使用者登入"
|
||||||
description: "根據帳號獲取用戶的帳號資訊"
|
description: "使用傳統帳號密碼或第三方平台 Token 進行登入,以創建一個新的會話(Session)。"
|
||||||
)
|
)
|
||||||
/*
|
/*
|
||||||
@respdoc-200 (GetAccountInfoResp) // 獲取成功
|
@respdoc-200 (LoginResp) // 登入成功
|
||||||
@respdoc-400 (
|
@respdoc-400 (ErrorResp) "請求參數格式錯誤"
|
||||||
40001: (ErrorResp) 帳號格式錯誤
|
@respdoc-401 (ErrorResp) "帳號或密碼錯誤 / 無效的平台 Token" // 401 Unauthorized
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
) // 客戶端錯誤
|
*/
|
||||||
@respdoc-404 (ErrorResp) // 帳號不存在
|
@handler login
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
post /sessions (LoginReq) returns (LoginResp)
|
||||||
*/
|
|
||||||
@handler GetUserAccountInfo
|
|
||||||
post /info (GetUIDByAccountReq) returns (GetAccountInfoResp)
|
|
||||||
|
|
||||||
@doc(
|
@doc(
|
||||||
summary: "更新用戶密碼"
|
summary: "刷新 Access Token"
|
||||||
description: "更新指定帳號的密碼"
|
description: "使用有效的 Refresh Token 來獲取一組新的 Access Token 和 Refresh Token。"
|
||||||
)
|
)
|
||||||
/*
|
/*
|
||||||
@respdoc-200 (OKResp) // 更新成功
|
@respdoc-200 (RefreshTokenResp) // 刷新成功
|
||||||
@respdoc-400 (
|
@respdoc-400 (ErrorResp) "請求參數格式錯誤"
|
||||||
40001: (ErrorResp) 帳號格式錯誤
|
@respdoc-401 (ErrorResp) "無效或已過期的 Refresh Token" // 401 Unauthorized
|
||||||
40002: (ErrorResp) 密碼強度不足
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
40003: (ErrorResp) 平台類型無效
|
*/
|
||||||
) // 客戶端錯誤
|
@handler refreshToken
|
||||||
@respdoc-404 (ErrorResp) // 帳號不存在
|
post /sessions/refresh (RefreshTokenReq) returns (RefreshTokenResp)
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler UpdateUserToken
|
|
||||||
post /update-token (UpdateTokenReq) returns (OKResp)
|
|
||||||
|
|
||||||
@doc(
|
@doc(
|
||||||
summary: "獲取用戶 UID"
|
summary: "請求發送密碼重設驗證碼"
|
||||||
description: "根據帳號獲取對應的用戶 UID"
|
description: "為指定的 email 或 phone 發送一個一次性的密碼重設驗證碼。"
|
||||||
)
|
)
|
||||||
/*
|
/*
|
||||||
@respdoc-200 (GetUIDByAccountResp) // 獲取成功
|
@respdoc-200 (RespOK) // 請求成功 (為安全起見,即使帳號不存在也應返回成功)
|
||||||
@respdoc-400 (
|
@respdoc-400 (ErrorResp) "請求參數格式錯誤"
|
||||||
40001: (ErrorResp) 帳號格式錯誤
|
@respdoc-429 (ErrorResp) "請求過於頻繁" // 429 Too Many Requests
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
) // 客戶端錯誤
|
*/
|
||||||
@respdoc-404 (ErrorResp) // 帳號不存在
|
@handler requestPasswordReset
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
post /password-resets/request (RequestPasswordResetReq) returns (RespOK)
|
||||||
*/
|
|
||||||
@handler GetUIDByAccount
|
|
||||||
post /uid (GetUIDByAccountReq) returns (GetUIDByAccountResp)
|
|
||||||
|
|
||||||
@doc(
|
@doc(
|
||||||
summary: "綁定帳號"
|
summary: "校驗密碼重設驗證碼"
|
||||||
description: "將帳號綁定到指定的用戶 UID"
|
description: "在實際重設密碼前,先驗證使用者輸入的驗證碼是否正確。"
|
||||||
)
|
)
|
||||||
/*
|
/*
|
||||||
@respdoc-200 (BindingUserResp) // 綁定成功
|
@respdoc-200 (RespOK) // 驗證碼正確
|
||||||
@respdoc-400 (
|
@respdoc-400 (ErrorResp) "驗證碼無效或已過期"
|
||||||
40001: (ErrorResp) 帳號格式錯誤
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
*/
|
||||||
40005: (ErrorResp) UID 格式錯誤
|
@handler verifyPasswordResetCode
|
||||||
) // 客戶端錯誤
|
post /password-resets/verify (VerifyCodeReq) returns (RespOK)
|
||||||
@respdoc-404 (ErrorResp) // 帳號不存在
|
|
||||||
@respdoc-409 (ErrorResp) // 帳號已綁定
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler BindAccount
|
|
||||||
post /bind (BindingUserReq) returns (BindingUserResp)
|
|
||||||
|
|
||||||
@doc(
|
@doc(
|
||||||
summary: "綁定用戶資料"
|
summary: "執行密碼重設"
|
||||||
description: "初次綁定用戶的詳細資料"
|
description: "使用有效的驗證碼來設定新的密碼。"
|
||||||
)
|
)
|
||||||
/*
|
/*
|
||||||
@respdoc-200 (OKResp) // 綁定成功
|
@respdoc-200 (RespOK) // 密碼重設成功
|
||||||
@respdoc-400 (
|
@respdoc-400 (ErrorResp) "驗證碼無效或請求參數錯誤"
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
40005: (ErrorResp) UID 格式錯誤
|
*/
|
||||||
40006: (ErrorResp) 郵箱格式錯誤
|
@handler resetPassword
|
||||||
40007: (ErrorResp) 電話格式錯誤
|
put /password-resets (ResetPasswordReq) returns (RespOK)
|
||||||
) // 客戶端錯誤
|
}
|
||||||
@respdoc-404 (ErrorResp) // 用戶不存在
|
|
||||||
@respdoc-409 (ErrorResp) // 用戶資料已存在
|
// =================================================================
|
||||||
@respdoc-422 (ErrorResp) // 用戶資料不完整
|
// Service: 授權 API - 需要登入 (User Service)
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
// =================================================================
|
||||||
*/
|
@server(
|
||||||
@handler BindUserInfo
|
group: user
|
||||||
post /bind-info (CreateUserInfoReq) returns (OKResp)
|
prefix: /api/v1/user
|
||||||
|
schemes: https
|
||||||
@doc(
|
timeout: 10s
|
||||||
summary: "綁定驗證 Email"
|
middleware: AuthMiddleware // 所有此 group 的路由都需要經過 JWT 驗證
|
||||||
description: "綁定並驗證用戶的 Email 地址"
|
)
|
||||||
)
|
service gateway {
|
||||||
/*
|
@doc(
|
||||||
@respdoc-200 (OKResp) // 綁定成功
|
summary: "取得當前登入的會員資訊(自己)"
|
||||||
@respdoc-400 (
|
)
|
||||||
40005: (ErrorResp) UID 格式錯誤
|
/*
|
||||||
40006: (ErrorResp) 郵箱格式錯誤
|
@respdoc-200 (UserInfoResp) // 成功獲取
|
||||||
) // 客戶端錯誤
|
@respdoc-401 (ErrorResp) "未授權或 Token 無效"
|
||||||
@respdoc-404 (ErrorResp) // 用戶不存在
|
@respdoc-404 (ErrorResp) "找不到使用者"
|
||||||
@respdoc-409 (ErrorResp) // 郵箱已綁定
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
@respdoc-422 (ErrorResp) // 郵箱未驗證
|
*/
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
@handler getUserInfo
|
||||||
*/
|
get /me (Authorization) returns (UserInfoResp)
|
||||||
@handler BindVerifyEmail
|
|
||||||
post /bind-email (BindVerifyEmailReq) returns (OKResp)
|
@doc(
|
||||||
|
summary: "更新當前登入的會員資訊"
|
||||||
@doc(
|
description: "只更新傳入的欄位,未傳入的欄位將保持不變。"
|
||||||
summary: "綁定驗證 Phone"
|
)
|
||||||
description: "綁定並驗證用戶的電話號碼"
|
/*
|
||||||
)
|
@respdoc-200 (UserInfoResp) // 更新成功,並返回更新後的使用者資訊
|
||||||
/*
|
@respdoc-400 (ErrorResp) "請求參數格式錯誤"
|
||||||
@respdoc-200 (OKResp) // 綁定成功
|
@respdoc-401 (ErrorResp) "未授權或 Token 無效"
|
||||||
@respdoc-400 (
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
40005: (ErrorResp) UID 格式錯誤
|
*/
|
||||||
40007: (ErrorResp) 電話格式錯誤
|
@handler updateUserInfo
|
||||||
) // 客戶端錯誤
|
put /me (UpdateUserInfoReq) returns (UserInfoResp)
|
||||||
@respdoc-404 (ErrorResp) // 用戶不存在
|
|
||||||
@respdoc-409 (ErrorResp) // 電話已綁定
|
@doc(
|
||||||
@respdoc-422 (ErrorResp) // 電話未驗證
|
summary: "修改當前登入使用者的密碼"
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
description: "必須提供當前密碼以進行驗證。"
|
||||||
*/
|
)
|
||||||
@handler BindVerifyPhone
|
/*
|
||||||
post /bind-phone (BindVerifyPhoneReq) returns (OKResp)
|
@respdoc-200 (RespOK) // 密碼修改成功
|
||||||
|
@respdoc-400 (ErrorResp) "請求參數格式錯誤或新舊密碼不符"
|
||||||
@doc(
|
@respdoc-401 (ErrorResp) "未授權或 Token 無效"
|
||||||
summary: "更新用戶資料"
|
@respdoc-403 (ErrorResp) "當前密碼不正確" // 403 Forbidden
|
||||||
description: "更新用戶的個人資料資訊"
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
)
|
*/
|
||||||
/*
|
@handler updatePassword
|
||||||
@respdoc-200 (OKResp) // 更新成功
|
put /me/password (UpdatePasswordReq) returns (RespOK)
|
||||||
@respdoc-400 (
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
@doc(
|
||||||
40005: (ErrorResp) UID 格式錯誤
|
summary: "請求發送驗證碼 (用於驗證信箱/手機)"
|
||||||
40006: (ErrorResp) 郵箱格式錯誤
|
description: "根據傳入的 `purpose` 發送對應的驗證碼。"
|
||||||
40007: (ErrorResp) 電話格式錯誤
|
)
|
||||||
) // 客戶端錯誤
|
/*
|
||||||
@respdoc-404 (ErrorResp) // 用戶不存在
|
@respdoc-200 (RespOK) // 請求已受理
|
||||||
@respdoc-422 (ErrorResp) // 用戶資料無效
|
@respdoc-400 (ErrorResp) "請求參數格式錯誤"
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
@respdoc-401 (ErrorResp) "未授權或 Token 無效"
|
||||||
*/
|
@respdoc-429 (ErrorResp) "請求過於頻繁" // 429 Too Many Requests
|
||||||
@handler UpdateUserInfo
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
post /update-info (UpdateUserInfoReq) returns (OKResp)
|
*/
|
||||||
|
@handler requestVerificationCode
|
||||||
@doc(
|
post /me/verifications (RequestVerificationCodeReq) returns (RespOK)
|
||||||
summary: "更新用戶狀態"
|
|
||||||
description: "更新用戶的帳號狀態"
|
@doc(
|
||||||
)
|
summary: "提交驗證碼以完成驗證"
|
||||||
/*
|
description: "提交收到的驗證碼,以完成特定目的的驗證,例如綁定手機或 Email。"
|
||||||
@respdoc-200 (OKResp) // 更新成功
|
)
|
||||||
@respdoc-400 (
|
/*
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
@respdoc-200 (RespOK) // 驗證成功
|
||||||
40005: (ErrorResp) UID 格式錯誤
|
@respdoc-400 (ErrorResp) "驗證碼無效或已過期"
|
||||||
40008: (ErrorResp) 狀態值無效
|
@respdoc-401 (ErrorResp) "未授權或 Token 無效"
|
||||||
) // 客戶端錯誤
|
@respdoc-500 (ErrorResp) // 伺服器內部錯誤
|
||||||
@respdoc-404 (ErrorResp) // 用戶不存在
|
*/
|
||||||
@respdoc-422 (ErrorResp) // 狀態更新無效
|
@handler submitVerificationCode
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
put /me/verifications (SubmitVerificationCodeReq) returns (RespOK)
|
||||||
*/
|
|
||||||
@handler UpdateStatus
|
|
||||||
post /update-status (UpdateStatusReq) returns (OKResp)
|
|
||||||
|
|
||||||
@doc(
|
|
||||||
summary: "獲取用戶資訊"
|
|
||||||
description: "根據 UID 或暱稱獲取用戶詳細資訊"
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
@respdoc-200 (GetUserInfoResp) // 獲取成功
|
|
||||||
@respdoc-400 (
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
|
||||||
40005: (ErrorResp) UID 格式錯誤
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-404 (ErrorResp) // 用戶不存在
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler GetUserInfo
|
|
||||||
post /user-info (GetUserInfoReq) returns (GetUserInfoResp)
|
|
||||||
|
|
||||||
@doc(
|
|
||||||
summary: "獲取用戶列表"
|
|
||||||
description: "分頁獲取用戶列表,支援篩選條件"
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
@respdoc-200 (ListUserInfoResp) // 獲取成功
|
|
||||||
@respdoc-400 (
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
|
||||||
40009: (ErrorResp) 分頁參數無效
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler ListMember
|
|
||||||
post /list (ListUserInfoReq) returns (ListUserInfoResp)
|
|
||||||
|
|
||||||
@doc(
|
|
||||||
summary: "生成驗證碼"
|
|
||||||
description: "為指定帳號生成驗證碼,用於忘記密碼等功能"
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
@respdoc-200 (GenerateRefreshCodeResp) // 生成成功
|
|
||||||
@respdoc-400 (
|
|
||||||
40001: (ErrorResp) 帳號格式錯誤
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
|
||||||
40010: (ErrorResp) 驗證碼類型無效
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-404 (ErrorResp) // 帳號不存在
|
|
||||||
@respdoc-429 (ErrorResp) // 請求過於頻繁
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler GenerateRefreshCode
|
|
||||||
post /generate-code (GenerateRefreshCodeReq) returns (GenerateRefreshCodeResp)
|
|
||||||
|
|
||||||
@doc(
|
|
||||||
summary: "驗證驗證碼"
|
|
||||||
description: "驗證並使用驗證碼,驗證後會刪除驗證碼"
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
@respdoc-200 (OKResp) // 驗證成功
|
|
||||||
@respdoc-400 (
|
|
||||||
40001: (ErrorResp) 帳號格式錯誤
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
|
||||||
40010: (ErrorResp) 驗證碼類型無效
|
|
||||||
40011: (ErrorResp) 驗證碼格式錯誤
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-404 (ErrorResp) // 驗證碼不存在
|
|
||||||
@respdoc-422 (ErrorResp) // 驗證碼無效或已過期
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler VerifyRefreshCode
|
|
||||||
post /verify-code (VerifyRefreshCodeReq) returns (OKResp)
|
|
||||||
|
|
||||||
@doc(
|
|
||||||
summary: "檢查驗證碼"
|
|
||||||
description: "檢查驗證碼是否正確,但不刪除驗證碼"
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
@respdoc-200 (OKResp) // 檢查成功
|
|
||||||
@respdoc-400 (
|
|
||||||
40001: (ErrorResp) 帳號格式錯誤
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
|
||||||
40010: (ErrorResp) 驗證碼類型無效
|
|
||||||
40011: (ErrorResp) 驗證碼格式錯誤
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-404 (ErrorResp) // 驗證碼不存在
|
|
||||||
@respdoc-422 (ErrorResp) // 驗證碼無效或已過期
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler CheckRefreshCode
|
|
||||||
post /check-code (VerifyRefreshCodeReq) returns (OKResp)
|
|
||||||
|
|
||||||
@doc(
|
|
||||||
summary: "驗證 Google 認證"
|
|
||||||
description: "驗證 Google OAuth 登入是否有效"
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
@respdoc-200 (VerifyGoogleAuthResultResp) // 驗證成功
|
|
||||||
@respdoc-400 (
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
|
||||||
40012: (ErrorResp) Token 格式錯誤
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-401 (ErrorResp) // Token 無效或過期
|
|
||||||
@respdoc-422 (ErrorResp) // Google 認證失敗
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler VerifyGoogleAuthResult
|
|
||||||
post /verify-google (VerifyAuthResultReq) returns (VerifyGoogleAuthResultResp)
|
|
||||||
|
|
||||||
@doc(
|
|
||||||
summary: "驗證平台認證"
|
|
||||||
description: "驗證平台登入認證是否有效"
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
@respdoc-200 (VerifyAuthResultResp) // 驗證成功
|
|
||||||
@respdoc-400 (
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
|
||||||
40012: (ErrorResp) Token 格式錯誤
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-401 (ErrorResp) // Token 無效或過期
|
|
||||||
@respdoc-422 (ErrorResp) // 平台認證失敗
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler VerifyPlatformAuthResult
|
|
||||||
post /verify-platform (VerifyAuthResultReq) returns (VerifyAuthResultResp)
|
|
||||||
|
|
||||||
@doc(
|
|
||||||
summary: "LINE 獲取 Access Token"
|
|
||||||
description: "使用 LINE 授權碼獲取 Access Token"
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
@respdoc-200 (LineAccessTokenResp) // 獲取成功
|
|
||||||
@respdoc-400 (
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
|
||||||
40013: (ErrorResp) 授權碼格式錯誤
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-401 (ErrorResp) // 授權碼無效或過期
|
|
||||||
@respdoc-422 (ErrorResp) // LINE 認證失敗
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler LineCodeToAccessToken
|
|
||||||
post /line/token (LineGetTokenReq) returns (LineAccessTokenResp)
|
|
||||||
|
|
||||||
@doc(
|
|
||||||
summary: "LINE 獲取用戶資料"
|
|
||||||
description: "使用 LINE Access Token 獲取用戶資料"
|
|
||||||
)
|
|
||||||
/*
|
|
||||||
@respdoc-200 (LineUserProfile) // 獲取成功
|
|
||||||
@respdoc-400 (
|
|
||||||
40004: (ErrorResp) 參數驗證失敗
|
|
||||||
40012: (ErrorResp) Token 格式錯誤
|
|
||||||
) // 客戶端錯誤
|
|
||||||
@respdoc-401 (ErrorResp) // Token 無效或過期
|
|
||||||
@respdoc-422 (ErrorResp) // LINE 用戶資料獲取失敗
|
|
||||||
@respdoc-500 (ErrorResp) // 服務器錯誤
|
|
||||||
*/
|
|
||||||
@handler LineGetProfileByAccessToken
|
|
||||||
post /line/profile (LineGetUserInfoReq) returns (LineUserProfile)
|
|
||||||
}
|
}
|
2
go.mod
2
go.mod
|
@ -4,7 +4,6 @@ go 1.25.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.30cm.net/digimon/library-go/errs v1.2.14
|
code.30cm.net/digimon/library-go/errs v1.2.14
|
||||||
code.30cm.net/digimon/library-go/mongo v0.0.9
|
|
||||||
code.30cm.net/digimon/library-go/utils/invited_code v1.2.5
|
code.30cm.net/digimon/library-go/utils/invited_code v1.2.5
|
||||||
github.com/alicebob/miniredis/v2 v2.35.0
|
github.com/alicebob/miniredis/v2 v2.35.0
|
||||||
github.com/shopspring/decimal v1.4.0
|
github.com/shopspring/decimal v1.4.0
|
||||||
|
@ -61,7 +60,6 @@ require (
|
||||||
github.com/moby/sys/user v0.4.0 // indirect
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
github.com/moby/sys/userns v0.1.0 // indirect
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
github.com/moby/term v0.5.0 // indirect
|
github.com/moby/term v0.5.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1,7 +1,5 @@
|
||||||
code.30cm.net/digimon/library-go/errs v1.2.14 h1:Un9wcIIjjJW8D2i0ISf8ibzp9oNT4OqLsaSKW0T4RJU=
|
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/errs v1.2.14/go.mod h1:Hs4v7SbXNggDVBGXSYsFMjkii1qLF+rugrIpWePN4/o=
|
||||||
code.30cm.net/digimon/library-go/mongo v0.0.9 h1:fPciIE5B85tXpLg8aeVQqKVbLnfpVAk9xbMu7pE2tVw=
|
|
||||||
code.30cm.net/digimon/library-go/mongo v0.0.9/go.mod h1:KBVKz/Ci5IheI77BgZxPUeKkaGvDy8fV8EDHSCOLIO4=
|
|
||||||
code.30cm.net/digimon/library-go/utils/invited_code v1.2.5 h1:szWsI0K+1iEHmc/AtKx+5c7tDIc1AZdStvT0tVza1pg=
|
code.30cm.net/digimon/library-go/utils/invited_code v1.2.5 h1:szWsI0K+1iEHmc/AtKx+5c7tDIc1AZdStvT0tVza1pg=
|
||||||
code.30cm.net/digimon/library-go/utils/invited_code v1.2.5/go.mod h1:eHmWpbX6N6KXQ2xaY71uj5bwfzTaNL8pQc2njYo5Gj0=
|
code.30cm.net/digimon/library-go/utils/invited_code v1.2.5/go.mod h1:eHmWpbX6N6KXQ2xaY71uj5bwfzTaNL8pQc2njYo5Gj0=
|
||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
|
@ -116,8 +114,6 @@ github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g
|
||||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
|
||||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
|
|
@ -1,7 +1,52 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "github.com/zeromicro/go-zero/rest"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
rest.RestConf
|
rest.RestConf
|
||||||
|
// Redis 配置
|
||||||
|
RedisConf redis.RedisConf
|
||||||
|
// Redis Cluster (Cache)
|
||||||
|
Cache cache.CacheConf
|
||||||
|
CacheExpireTime time.Duration
|
||||||
|
CacheWithNotFoundExpiry time.Duration
|
||||||
|
|
||||||
|
Mongo struct {
|
||||||
|
Schema string
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Database string
|
||||||
|
ReplicaName string
|
||||||
|
MaxStaleness time.Duration
|
||||||
|
MaxPoolSize uint64
|
||||||
|
MinPoolSize uint64
|
||||||
|
MaxConnIdleTime time.Duration
|
||||||
|
Compressors []string
|
||||||
|
EnableStandardReadWriteSplitMode bool
|
||||||
|
ConnectTimeoutMs int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// 密碼加密層數
|
||||||
|
Bcrypt struct {
|
||||||
|
Cost int
|
||||||
|
}
|
||||||
|
|
||||||
|
GoogleAuth struct {
|
||||||
|
ClientID string
|
||||||
|
AuthURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
LineAuth struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
RedirectURI string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/auth"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/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 {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewLoginLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.Login(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/auth"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 刷新 Access Token
|
||||||
|
func RefreshTokenHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.RefreshTokenReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewRefreshTokenLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.RefreshToken(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/auth"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 註冊新帳號
|
||||||
|
func RegisterHandler(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 {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewRegisterLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.Register(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/auth"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 請求發送密碼重設驗證碼
|
||||||
|
func RequestPasswordResetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.RequestPasswordResetReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewRequestPasswordResetLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.RequestPasswordReset(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/auth"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 執行密碼重設
|
||||||
|
func ResetPasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.ResetPasswordReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewResetPasswordLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.ResetPassword(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/auth"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 校驗密碼重設驗證碼
|
||||||
|
func VerifyPasswordResetCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.VerifyCodeReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewVerifyPasswordResetCodeLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.VerifyPasswordResetCode(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"backend/internal/logic/ping"
|
"backend/internal/logic/ping"
|
||||||
"backend/internal/svc"
|
"backend/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest/httpx"
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by goctl. DO NOT EDIT.
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
// goctl 1.8.1
|
// goctl 1.8.5
|
||||||
|
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
|
@ -7,13 +7,58 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
auth "backend/internal/handler/auth"
|
||||||
ping "backend/internal/handler/ping"
|
ping "backend/internal/handler/ping"
|
||||||
|
user "backend/internal/handler/user"
|
||||||
"backend/internal/svc"
|
"backend/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/rest"
|
"github.com/zeromicro/go-zero/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||||
|
server.AddRoutes(
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
// 執行密碼重設
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Path: "/password-resets",
|
||||||
|
Handler: auth.ResetPasswordHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 請求發送密碼重設驗證碼
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/password-resets/request",
|
||||||
|
Handler: auth.RequestPasswordResetHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 校驗密碼重設驗證碼
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/password-resets/verify",
|
||||||
|
Handler: auth.VerifyPasswordResetCodeHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 註冊新帳號
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/register",
|
||||||
|
Handler: auth.RegisterHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 使用者登入
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/sessions",
|
||||||
|
Handler: auth.LoginHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 刷新 Access Token
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/sessions/refresh",
|
||||||
|
Handler: auth.RefreshTokenHandler(serverCtx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rest.WithPrefix("/api/v1/auth"),
|
||||||
|
rest.WithTimeout(10000*time.Millisecond),
|
||||||
|
)
|
||||||
|
|
||||||
server.AddRoutes(
|
server.AddRoutes(
|
||||||
[]rest.Route{
|
[]rest.Route{
|
||||||
{
|
{
|
||||||
|
@ -26,4 +71,44 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||||
rest.WithPrefix("/api/v1"),
|
rest.WithPrefix("/api/v1"),
|
||||||
rest.WithTimeout(10000*time.Millisecond),
|
rest.WithTimeout(10000*time.Millisecond),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
server.AddRoutes(
|
||||||
|
rest.WithMiddlewares(
|
||||||
|
[]rest.Middleware{serverCtx.AuthMiddleware},
|
||||||
|
[]rest.Route{
|
||||||
|
{
|
||||||
|
// 取得當前登入的會員資訊(自己)
|
||||||
|
Method: http.MethodGet,
|
||||||
|
Path: "/me",
|
||||||
|
Handler: user.GetUserInfoHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 更新當前登入的會員資訊
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Path: "/me",
|
||||||
|
Handler: user.UpdateUserInfoHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 修改當前登入使用者的密碼
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Path: "/me/password",
|
||||||
|
Handler: user.UpdatePasswordHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 請求發送驗證碼 (用於驗證信箱/手機)
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/me/verifications",
|
||||||
|
Handler: user.RequestVerificationCodeHandler(serverCtx),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 提交驗證碼以完成驗證
|
||||||
|
Method: http.MethodPut,
|
||||||
|
Path: "/me/verifications",
|
||||||
|
Handler: user.SubmitVerificationCodeHandler(serverCtx),
|
||||||
|
},
|
||||||
|
}...,
|
||||||
|
),
|
||||||
|
rest.WithPrefix("/api/v1/user"),
|
||||||
|
rest.WithTimeout(10000*time.Millisecond),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/user"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 取得當前登入的會員資訊(自己)
|
||||||
|
func GetUserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.Authorization
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewGetUserInfoLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.GetUserInfo(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/user"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 請求發送驗證碼 (用於驗證信箱/手機)
|
||||||
|
func RequestVerificationCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.RequestVerificationCodeReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewRequestVerificationCodeLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.RequestVerificationCode(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/user"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 提交驗證碼以完成驗證
|
||||||
|
func SubmitVerificationCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.SubmitVerificationCodeReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewSubmitVerificationCodeLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.SubmitVerificationCode(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/user"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/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 {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewUpdatePasswordLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.UpdatePassword(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"backend/internal/logic/user"
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/rest/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 更新當前登入的會員資訊
|
||||||
|
func UpdateUserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req types.UpdateUserInfoReq
|
||||||
|
if err := httpx.Parse(r, &req); err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := user.NewUpdateUserInfoLogic(r.Context(), svcCtx)
|
||||||
|
resp, err := l.UpdateUserInfo(&req)
|
||||||
|
if err != nil {
|
||||||
|
httpx.ErrorCtx(r.Context(), w, err)
|
||||||
|
} else {
|
||||||
|
httpx.OkJsonCtx(r.Context(), w, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用者登入
|
||||||
|
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.LoginResp, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RefreshTokenLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新 Access Token
|
||||||
|
func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RefreshTokenLogic {
|
||||||
|
return &RefreshTokenLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *RefreshTokenLogic) RefreshToken(req *types.RefreshTokenReq) (resp *types.RefreshTokenResp, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegisterLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 註冊新帳號
|
||||||
|
func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
|
||||||
|
return &RegisterLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *RegisterLogic) Register(req *types.LoginReq) (resp *types.LoginResp, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestPasswordResetLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 請求發送密碼重設驗證碼
|
||||||
|
func NewRequestPasswordResetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RequestPasswordResetLogic {
|
||||||
|
return &RequestPasswordResetLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *RequestPasswordResetLogic) RequestPasswordReset(req *types.RequestPasswordResetReq) (resp *types.RespOK, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResetPasswordLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 執行密碼重設
|
||||||
|
func NewResetPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetPasswordLogic {
|
||||||
|
return &ResetPasswordLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordReq) (resp *types.RespOK, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VerifyPasswordResetCodeLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校驗密碼重設驗證碼
|
||||||
|
func NewVerifyPasswordResetCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VerifyPasswordResetCodeLogic {
|
||||||
|
return &VerifyPasswordResetCodeLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *VerifyPasswordResetCodeLogic) VerifyPasswordResetCode(req *types.VerifyCodeReq) (resp *types.RespOK, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"backend/internal/svc"
|
"backend/internal/svc"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetUserInfoLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取得當前登入的會員資訊(自己)
|
||||||
|
func NewGetUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserInfoLogic {
|
||||||
|
return &GetUserInfoLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *GetUserInfoLogic) GetUserInfo(req *types.Authorization) (resp *types.UserInfoResp, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestVerificationCodeLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 請求發送驗證碼 (用於驗證信箱/手機)
|
||||||
|
func NewRequestVerificationCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RequestVerificationCodeLogic {
|
||||||
|
return &RequestVerificationCodeLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *RequestVerificationCodeLogic) RequestVerificationCode(req *types.RequestVerificationCodeReq) (resp *types.RespOK, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubmitVerificationCodeLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交驗證碼以完成驗證
|
||||||
|
func NewSubmitVerificationCodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SubmitVerificationCodeLogic {
|
||||||
|
return &SubmitVerificationCodeLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SubmitVerificationCodeLogic) SubmitVerificationCode(req *types.SubmitVerificationCodeReq) (resp *types.RespOK, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdatePasswordLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改當前登入使用者的密碼
|
||||||
|
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) (resp *types.RespOK, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"backend/internal/svc"
|
||||||
|
"backend/internal/types"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateUserInfoLogic struct {
|
||||||
|
logx.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新當前登入的會員資訊
|
||||||
|
func NewUpdateUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserInfoLogic {
|
||||||
|
return &UpdateUserInfoLogic{
|
||||||
|
Logger: logx.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *UpdateUserInfoLogic) UpdateUserInfo(req *types.UpdateUserInfoReq) (resp *types.UserInfoResp, err error) {
|
||||||
|
// todo: add your logic here and delete this line
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type AuthMiddleware struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthMiddleware() *AuthMiddleware {
|
||||||
|
return &AuthMiddleware{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// TODO generate middleware implement function, delete after code implementation
|
||||||
|
|
||||||
|
// Passthrough to next handler if need
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package svc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"backend/internal/config"
|
||||||
|
mgo "backend/pkg/library/mongo"
|
||||||
|
cfg "backend/pkg/member/domain/config"
|
||||||
|
"backend/pkg/member/domain/usecase"
|
||||||
|
"backend/pkg/member/repository"
|
||||||
|
uc "backend/pkg/member/usecase"
|
||||||
|
"context"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAccountUC(c *config.Config, rds *redis.Redis) usecase.AccountUseCase {
|
||||||
|
// 準備Mongo Config
|
||||||
|
conf := &mgo.Conf{
|
||||||
|
Schema: c.Mongo.Schema,
|
||||||
|
Host: c.Mongo.Host,
|
||||||
|
Database: c.Mongo.Database,
|
||||||
|
MaxStaleness: c.Mongo.MaxStaleness,
|
||||||
|
MaxPoolSize: c.Mongo.MaxPoolSize,
|
||||||
|
MinPoolSize: c.Mongo.MinPoolSize,
|
||||||
|
MaxConnIdleTime: c.Mongo.MaxConnIdleTime,
|
||||||
|
Compressors: c.Mongo.Compressors,
|
||||||
|
EnableStandardReadWriteSplitMode: c.Mongo.EnableStandardReadWriteSplitMode,
|
||||||
|
ConnectTimeoutMs: c.Mongo.ConnectTimeoutMs,
|
||||||
|
}
|
||||||
|
if c.Mongo.User != "" {
|
||||||
|
conf.User = c.Mongo.User
|
||||||
|
conf.Password = c.Mongo.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快取選項
|
||||||
|
cacheOpts := []cache.Option{
|
||||||
|
cache.WithExpiry(c.CacheExpireTime),
|
||||||
|
cache.WithNotFoundExpiry(c.CacheWithNotFoundExpiry),
|
||||||
|
}
|
||||||
|
dbOpts := []mon.Option{
|
||||||
|
mgo.SetCustomDecimalType(),
|
||||||
|
mgo.InitMongoOptions(*conf),
|
||||||
|
}
|
||||||
|
|
||||||
|
ac := repository.NewAccountRepository(repository.AccountRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
CacheConf: c.Cache,
|
||||||
|
CacheOpts: cacheOpts,
|
||||||
|
DBOpts: dbOpts,
|
||||||
|
})
|
||||||
|
|
||||||
|
u := repository.NewUserRepository(repository.UserRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
CacheConf: c.Cache,
|
||||||
|
CacheOpts: cacheOpts,
|
||||||
|
DBOpts: dbOpts,
|
||||||
|
})
|
||||||
|
guid := repository.NewAutoIDRepository(repository.AutoIDRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
DBOpts: dbOpts,
|
||||||
|
})
|
||||||
|
auid := repository.NewAccountUIDRepository(repository.AccountUIDRepositoryParam{
|
||||||
|
Conf: conf,
|
||||||
|
CacheConf: c.Cache,
|
||||||
|
CacheOpts: cacheOpts,
|
||||||
|
DBOpts: dbOpts,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _ = ac.Index20241226001UP(context.Background())
|
||||||
|
_, _ = u.Index20241226001UP(context.Background())
|
||||||
|
_, _ = guid.Index20241226001UP(context.Background())
|
||||||
|
_, _ = auid.Index20241226001UP(context.Background())
|
||||||
|
|
||||||
|
return uc.MustMemberUseCase(uc.MemberUseCaseParam{
|
||||||
|
Account: ac,
|
||||||
|
User: u,
|
||||||
|
AccountUID: auid,
|
||||||
|
VerifyCodeModel: repository.NewVerifyCodeRepository(rds),
|
||||||
|
GenerateUID: guid,
|
||||||
|
Config: prepareCfg(c),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareCfg(c *config.Config) cfg.Config {
|
||||||
|
return cfg.Config{
|
||||||
|
Bcrypt: struct{ Cost int }{Cost: c.Bcrypt.Cost},
|
||||||
|
GoogleAuth: struct {
|
||||||
|
ClientID string
|
||||||
|
AuthURL string
|
||||||
|
}{
|
||||||
|
ClientID: c.GoogleAuth.ClientID,
|
||||||
|
AuthURL: c.GoogleAuth.AuthURL,
|
||||||
|
},
|
||||||
|
|
||||||
|
LineAuth: struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
RedirectURI string
|
||||||
|
}{
|
||||||
|
ClientID: c.LineAuth.ClientID,
|
||||||
|
ClientSecret: c.LineAuth.ClientSecret,
|
||||||
|
RedirectURI: c.LineAuth.RedirectURI,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,14 +2,28 @@ package svc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"backend/internal/config"
|
"backend/internal/config"
|
||||||
|
"backend/internal/middleware"
|
||||||
|
"backend/pkg/member/domain/usecase"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
|
"github.com/zeromicro/go-zero/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceContext struct {
|
type ServiceContext struct {
|
||||||
Config config.Config
|
Config config.Config
|
||||||
|
AuthMiddleware rest.Middleware
|
||||||
|
AccountUC usecase.AccountUseCase
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServiceContext(c config.Config) *ServiceContext {
|
func NewServiceContext(c config.Config) *ServiceContext {
|
||||||
|
rds, err := redis.NewRedis(c.RedisConf)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
return &ServiceContext{
|
return &ServiceContext{
|
||||||
Config: c,
|
Config: c,
|
||||||
|
AuthMiddleware: middleware.NewAuthMiddleware().Handle,
|
||||||
|
AccountUC: NewAccountUC(&c, rds),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,138 @@
|
||||||
// Code generated by goctl. DO NOT EDIT.
|
// Code generated by goctl. DO NOT EDIT.
|
||||||
// goctl 1.8.1
|
// goctl 1.8.5
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
type Authorization struct {
|
||||||
|
Authorization string `header:"Authorization" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
type BaseReq struct {
|
type BaseReq struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseResponse struct {
|
type CredentialsPayload struct {
|
||||||
Status Status `json:"status"` // 狀態
|
Password string `json:"password" validate:"required,min=8,max=128"` // 密碼 (後端應使用 bcrypt 進行雜湊)
|
||||||
Data interface{} `json:"data"` // 資料
|
PasswordConfirm string `json:"password_confirm" validate:"eqfield=Password"` // 確認密碼
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pager struct {
|
type ErrorResp struct {
|
||||||
Total int64 `json:"total"`
|
Code int `json:"code"`
|
||||||
PageSize int64 `json:"page_size"`
|
Msg string `json:"msg"`
|
||||||
PageIndex int64 `json:"page_index"`
|
Details string `json:"details,omitempty"`
|
||||||
}
|
|
||||||
|
|
||||||
type RespOK struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
type Status struct {
|
|
||||||
Code int64 `json:"code"` // 狀態碼
|
|
||||||
Message string `json:"message"` // 訊息
|
|
||||||
Data interface{} `json:"data,omitempty"` // 可選的資料,當有返回時才出現
|
|
||||||
Error interface{} `json:"error,omitempty"` // 可選的錯誤信息
|
Error interface{} `json:"error,omitempty"` // 可選的錯誤信息
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerifyHeader struct {
|
type LoginReq struct {
|
||||||
Token string `header:"token" validate:"required"`
|
AuthMethod string `json:"auth_method" validate:"required,oneof=credentials platform"`
|
||||||
|
LoginID string `json:"login_id" validate:"required,min=3,max=50"` // 信箱或手機號碼
|
||||||
|
Credentials *CredentialsPayload `json:"credentials,optional"` // AuthMethod 為 'credentials' 時使用
|
||||||
|
Platform *PlatformPayload `json:"platform,optional"` // AuthMethod 為 'platform' 時使用
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginResp struct {
|
||||||
|
UID string `json:"uid"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
TokenType string `json:"token_type"` // 通常固定為 "Bearer"
|
||||||
|
}
|
||||||
|
|
||||||
|
type PagerResp struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Index int64 `json:"index"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlatformPayload struct {
|
||||||
|
Provider string `json:"provider" validate:"required,oneof=google line apple"` // 平台名稱
|
||||||
|
Token string `json:"token" validate:"required"` // 平台提供的 Access Token 或 ID Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshTokenReq struct {
|
||||||
|
RefreshToken string `json:"refresh_token" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RefreshTokenResp struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"` // 可選:某些策略下刷新後也會換發新的 Refresh Token
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestPasswordResetReq struct {
|
||||||
|
Identifier string `json:"identifier" validate:"required,email|phone"` // 使用者帳號 (信箱或手機)
|
||||||
|
AccountType string `json:"account_type" validate:"required,oneof=email phone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestVerificationCodeReq struct {
|
||||||
|
Purpose string `json:"purpose" validate:"required,oneof=email_verification phone_verification"`
|
||||||
|
Authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResetPasswordReq struct {
|
||||||
|
Identifier string `json:"identifier" validate:"required"`
|
||||||
|
VerifyCode string `json:"verify_code" validate:"required"` // 來自上一步驗證通過的 Code,作為一種「票證」
|
||||||
|
Password string `json:"password" validate:"required,min=8,max=128"` // 新密碼
|
||||||
|
PasswordConfirm string `json:"password_confirm" validate:"eqfield=Password"` // 確認新密碼
|
||||||
|
}
|
||||||
|
|
||||||
|
type RespOK struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubmitVerificationCodeReq struct {
|
||||||
|
Purpose string `json:"purpose" validate:"required,oneof=email_verification phone_verification"`
|
||||||
|
VerifyCode string `json:"verify_code" validate:"required,len=6"`
|
||||||
|
Authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePasswordReq struct {
|
||||||
|
CurrentPassword string `json:"current_password" validate:"required"`
|
||||||
|
NewPassword string `json:"new_password" validate:"required,min=8,max=128"`
|
||||||
|
NewPasswordConfirm string `json:"new_password_confirm" validate:"eqfield=NewPassword"`
|
||||||
|
Authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserInfoReq struct {
|
||||||
|
AvatarURL *string `json:"avatar_url,optional"` // 頭像 URL
|
||||||
|
FullName *string `json:"full_name,optional"` // 用戶全名
|
||||||
|
Nickname *string `json:"nickname,optional"` // 暱稱
|
||||||
|
GenderCode *string `json:"gender_code,optional" validate:"omitempty,oneof=secret male female"` // 性別
|
||||||
|
Birthdate *string `json:"birthdate,optional"` // 生日 (格式: 1993-04-17)
|
||||||
|
Address *string `json:"address,optional"` // 地址
|
||||||
|
PreferredLanguage *string `json:"preferred_language,optional" validate:"omitempty,oneof=zh-tw en-us"` // 語言
|
||||||
|
Currency *string `json:"currency,optional" validate:"omitempty,oneof=TWD USD"` // 貨幣代號
|
||||||
|
National *string `json:"national,optional"` // 國家
|
||||||
|
PostCode *string `json:"post_code,optional"` // 郵遞區號
|
||||||
|
Carrier *string `json:"carrier,optional"` // 載具
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoResp struct {
|
||||||
|
Platform string `json:"platform"` // 註冊平台
|
||||||
|
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"` // 性別代碼
|
||||||
|
Birthdate string `json:"birthdate"` // 生日 (格式: 1993-04-17)
|
||||||
|
PhoneNumber string `json:"phone_number"` // 電話
|
||||||
|
IsPhoneVerified bool `json:"is_phone_verified"` // 手機是否已驗證
|
||||||
|
Email string `json:"email"` // 信箱
|
||||||
|
IsEmailVerified bool `json:"is_email_verified"` // 信箱是否已驗證
|
||||||
|
Address string `json:"address"` // 地址
|
||||||
|
UserStatus string `json:"user_status"` // 用戶狀態
|
||||||
|
PreferredLanguage string `json:"preferred_language"` // 偏好語言
|
||||||
|
Currency string `json:"currency"` // 偏好幣種
|
||||||
|
National string `json:"national"` // 國家
|
||||||
|
PostCode string `json:"post_code"` // 郵遞區號
|
||||||
|
Carrier string `json:"carrier"` // 載具
|
||||||
|
Role string `json:"role"` // 角色
|
||||||
|
UpdateAt string `json:"update_at"`
|
||||||
|
CreateAt string `json:"create_at"`
|
||||||
|
Authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyCodeReq struct {
|
||||||
|
Identifier string `json:"identifier" validate:"required"`
|
||||||
|
VerifyCode string `json:"verify_code" validate:"required,len=6"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ func (document *DocumentDB) GetClient() *mon.Model {
|
||||||
|
|
||||||
func (document *DocumentDB) yieldIndexModel(keys []string, sorts []int32, unique bool, indexOpt *options.IndexOptionsBuilder) mongo.IndexModel {
|
func (document *DocumentDB) yieldIndexModel(keys []string, sorts []int32, unique bool, indexOpt *options.IndexOptionsBuilder) mongo.IndexModel {
|
||||||
SetKeysDoc := bson.D{}
|
SetKeysDoc := bson.D{}
|
||||||
for index, _ := range keys {
|
for index := range keys {
|
||||||
key := keys[index]
|
key := keys[index]
|
||||||
sort := sorts[index]
|
sort := sorts[index]
|
||||||
SetKeysDoc = append(SetKeysDoc, bson.E{Key: key, Value: sort})
|
SetKeysDoc = append(SetKeysDoc, bson.E{Key: key, Value: sort})
|
||||||
|
|
|
@ -228,4 +228,3 @@ func TestInitMongoOptions_ReturnType(t *testing.T) {
|
||||||
// Test that we can use the options (basic type check)
|
// Test that we can use the options (basic type check)
|
||||||
var _ mon.Option = opts
|
var _ mon.Option = opts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,18 +5,19 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"backend/pkg/member/domain/member"
|
"backend/pkg/member/domain/member"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Account represents a user account with authentication credentials.
|
// Account represents a user account with authentication credentials.
|
||||||
// It stores login information, hashed passwords, and platform-specific data.
|
// It stores login information, hashed passwords, and platform-specific data.
|
||||||
type Account struct {
|
type Account struct {
|
||||||
ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
LoginID string `bson:"login_id"` // Unique login identifier (email, phone, username)
|
LoginID string `bson:"login_id"` // Unique login identifier (email, phone, username)
|
||||||
Token string `bson:"token"` // Hashed password or platform-specific token
|
Token string `bson:"token"` // Hashed password or platform-specific token
|
||||||
Platform member.Platform `bson:"platform"` // Platform type: 1. platform 2. google 3. line 4. apple
|
Platform member.Platform `bson:"platform"` // Platform type: 1. platform 2. google 3. line 4. apple
|
||||||
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
||||||
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CollectionName returns the MongoDB collection name for Account entities.
|
// CollectionName returns the MongoDB collection name for Account entities.
|
||||||
|
|
|
@ -5,16 +5,17 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"backend/pkg/member/domain/member"
|
"backend/pkg/member/domain/member"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccountUID struct {
|
type AccountUID struct {
|
||||||
ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
LoginID string `bson:"login_id"`
|
LoginID string `bson:"login_id"`
|
||||||
UID string `bson:"uid"`
|
UID string `bson:"uid"`
|
||||||
Type member.AccountType `bson:"type"`
|
Type member.AccountType `bson:"type"`
|
||||||
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
||||||
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AccountUID) CollectionName() string {
|
func (a *AccountUID) CollectionName() string {
|
||||||
|
|
|
@ -5,28 +5,29 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"backend/pkg/member/domain/member"
|
"backend/pkg/member/domain/member"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User represents a user profile with personal information and preferences.
|
// User represents a user profile with personal information and preferences.
|
||||||
// It contains detailed user data that is separate from authentication credentials.
|
// It contains detailed user data that is separate from authentication credentials.
|
||||||
type User struct {
|
type User struct {
|
||||||
ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"`
|
||||||
UID string `bson:"uid"` // Unique user identifier
|
UID string `bson:"uid"` // Unique user identifier
|
||||||
AvatarURL *string `bson:"avatar_url,omitempty"` // User avatar URL (optional)
|
AvatarURL *string `bson:"avatar_url,omitempty"` // User avatar URL (optional)
|
||||||
FullName *string `bson:"full_name,omitempty"` // User's full name
|
FullName *string `bson:"full_name,omitempty"` // User's full name
|
||||||
Nickname *string `bson:"nickname,omitempty"` // User's nickname (optional)
|
Nickname *string `bson:"nickname,omitempty"` // User's nickname (optional)
|
||||||
GenderCode *int64 `bson:"gender_code,omitempty"` // Gender code
|
GenderCode *int64 `bson:"gender_code,omitempty"` // Gender code
|
||||||
Birthdate *int64 `bson:"birthdate,omitempty"` // Birth date (format: 19930417)
|
Birthdate *int64 `bson:"birthdate,omitempty"` // Birth date (format: 19930417)
|
||||||
Address *string `bson:"address,omitempty"` // User's address
|
Address *string `bson:"address,omitempty"` // User's address
|
||||||
AlarmCategory member.AlarmType `bson:"alarm_category"` // Alert notification settings
|
AlarmCategory member.AlarmType `bson:"alarm_category"` // Alert notification settings
|
||||||
UserStatus member.Status `bson:"user_status"` // User account status
|
UserStatus member.Status `bson:"user_status"` // User account status
|
||||||
PreferredLanguage string `bson:"preferred_language"` // User's preferred language
|
PreferredLanguage string `bson:"preferred_language"` // User's preferred language
|
||||||
Currency string `bson:"currency"` // User's preferred currency
|
Currency string `bson:"currency"` // User's preferred currency
|
||||||
PhoneNumber *string `bson:"phone_number,omitempty"` // Phone number (appears after verification)
|
PhoneNumber *string `bson:"phone_number,omitempty"` // Phone number (appears after verification)
|
||||||
Email *string `bson:"email,omitempty"` // Email address (appears after verification)
|
Email *string `bson:"email,omitempty"` // Email address (appears after verification)
|
||||||
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
UpdateAt *int64 `bson:"update_at,omitempty" json:"update_at,omitempty"`
|
||||||
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
CreateAt *int64 `bson:"create_at,omitempty" json:"create_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CollectionName returns the MongoDB collection name for User entities.
|
// CollectionName returns the MongoDB collection name for User entities.
|
||||||
|
|
|
@ -108,34 +108,34 @@ func TestValidatePhone(t *testing.T) {
|
||||||
|
|
||||||
func TestValidatePassword(t *testing.T) {
|
func TestValidatePassword(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
password string
|
password string
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid password",
|
name: "valid password",
|
||||||
password: "password123",
|
password: "password123",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "password too short",
|
name: "password too short",
|
||||||
password: "123",
|
password: "123",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "password too long",
|
name: "password too long",
|
||||||
password: string(make([]byte, MaxPasswordLength+1)),
|
password: string(make([]byte, MaxPasswordLength+1)),
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty password",
|
name: "empty password",
|
||||||
password: "",
|
password: "",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "minimum length password",
|
name: "minimum length password",
|
||||||
password: "12345678",
|
password: "12345678",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"backend/pkg/member/domain/repository"
|
"backend/pkg/member/domain/repository"
|
||||||
|
|
||||||
"backend/pkg/library/mongo"
|
"backend/pkg/library/mongo"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
"github.com/zeromicro/go-zero/core/stores/mon"
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"backend/pkg/member/domain/repository"
|
"backend/pkg/member/domain/repository"
|
||||||
|
|
||||||
mgo "backend/pkg/library/mongo"
|
mgo "backend/pkg/library/mongo"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis/v2"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"backend/pkg/member/domain/repository"
|
"backend/pkg/member/domain/repository"
|
||||||
|
|
||||||
"backend/pkg/library/mongo"
|
"backend/pkg/library/mongo"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
"github.com/zeromicro/go-zero/core/stores/mon"
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"backend/pkg/member/domain/repository"
|
"backend/pkg/member/domain/repository"
|
||||||
|
|
||||||
mgo "backend/pkg/library/mongo"
|
mgo "backend/pkg/library/mongo"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis/v2"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
GIDLib "code.30cm.net/digimon/library-go/utils/invited_code"
|
GIDLib "code.30cm.net/digimon/library-go/utils/invited_code"
|
||||||
|
|
||||||
"backend/pkg/library/mongo"
|
"backend/pkg/library/mongo"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/mon"
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
|
mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"backend/pkg/member/domain/repository"
|
"backend/pkg/member/domain/repository"
|
||||||
|
|
||||||
mgo "backend/pkg/library/mongo"
|
mgo "backend/pkg/library/mongo"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"backend/pkg/member/domain/repository"
|
"backend/pkg/member/domain/repository"
|
||||||
|
|
||||||
mgo "backend/pkg/library/mongo"
|
mgo "backend/pkg/library/mongo"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis/v2"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"backend/pkg/member/domain"
|
"backend/pkg/member/domain"
|
||||||
"backend/pkg/member/domain/repository"
|
"backend/pkg/member/domain/repository"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package usecase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue