fix: 生产安全修复 + Go SDK + CAS SSO框架

安全修复:
- CRITICAL: SSO重定向URL注入漏洞 - 修复redirect_uri白名单验证
- HIGH: SSO ClientSecret未验证 - 使用crypto/subtle.ConstantTimeCompare验证
- HIGH: 邮件验证码熵值过低(3字节) - 提升到6字节(48位熵)
- HIGH: 短信验证码熵值过低(4字节) - 提升到6字节
- HIGH: Goroutine使用已取消上下文 - auth_email.go使用独立context+超时
- HIGH: SQL LIKE查询注入风险 - permission/role仓库使用escapeLikePattern

新功能:
- Go SDK: sdk/go/user-management/ 完整SDK实现
- CAS SSO框架: internal/auth/cas.go CAS协议支持

其他:
- L1Cache实例问题修复 - AuthMiddleware共享l1Cache
- 设备指纹XSS防护 - 内存存储替代localStorage
- 响应格式协议中间件
- 导出无界查询修复
This commit is contained in:
2026-04-03 17:38:31 +08:00
parent 44e60be918
commit 765a50b7d4
22 changed files with 2318 additions and 71 deletions

View File

@@ -0,0 +1,246 @@
package userManagement
import (
"context"
"fmt"
)
// LoginRequest 登录请求
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
DeviceID string `json:"device_id,omitempty"`
DeviceName string `json:"device_name,omitempty"`
RememberMe bool `json:"remember_me"`
}
// LoginResponse 登录响应
type LoginResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresIn int64 `json:"expires_in"`
TokenType string `json:"token_type"`
User *User `json:"user,omitempty"`
}
// RegisterRequest 注册请求
type RegisterRequest struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
Phone string `json:"phone,omitempty"`
Nickname string `json:"nickname,omitempty"`
}
// RefreshTokenRequest 刷新令牌请求
type RefreshTokenRequest struct {
RefreshToken string `json:"refresh_token"`
}
// CapabilitiesResponse 能力响应
type CapabilitiesResponse struct {
LoginMethods []string `json:"login_methods"`
SocialProviders []string `json:"social_providers,omitempty"`
CaptchaRequired bool `json:"captcha_required"`
SocialBindRequired bool `json:"social_bind_required,omitempty"`
}
// TwoFactorVerifyRequest 两因素验证请求
type TwoFactorVerifyRequest struct {
Code string `json:"code"`
DeviceID string `json:"device_id,omitempty"`
TrustDevice bool `json:"trust_device,omitempty"`
}
// PasswordResetRequest 密码重置请求
type PasswordResetRequest struct {
Token string `json:"token"`
NewPassword string `json:"new_password"`
}
// Login 执行登录
func (c *Client) Login(ctx context.Context, req *LoginRequest) (*LoginResponse, error) {
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/login", req)
if err != nil {
return nil, err
}
var result LoginResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
// 自动设置 access token
if result.AccessToken != "" {
c.SetAccessToken(result.AccessToken)
}
return &result, nil
}
// Register 注册用户
func (c *Client) Register(ctx context.Context, req *RegisterRequest) (*User, error) {
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/register", req)
if err != nil {
return nil, err
}
var result User
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetCapabilities 获取登录能力
func (c *Client) GetCapabilities(ctx context.Context) (*CapabilitiesResponse, error) {
resp, err := c.doRequest(ctx, "GET", "/api/v1/auth/capabilities", nil)
if err != nil {
return nil, err
}
var result CapabilitiesResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// RefreshToken 刷新令牌
func (c *Client) RefreshToken(ctx context.Context, req *RefreshTokenRequest) (*LoginResponse, error) {
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/refresh", req)
if err != nil {
return nil, err
}
var result LoginResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
if result.AccessToken != "" {
c.SetAccessToken(result.AccessToken)
}
return &result, nil
}
// VerifyTwoFactor 验证两因素验证码
func (c *Client) VerifyTwoFactor(ctx context.Context, req *TwoFactorVerifyRequest) error {
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/2fa/verify", req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// Logout 登出
func (c *Client) Logout(ctx context.Context) error {
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/logout", nil)
if err != nil {
return err
}
c.accessToken = ""
return c.parseResponse(resp, nil)
}
// RequestPasswordReset 请求密码重置
func (c *Client) RequestPasswordReset(ctx context.Context, email string) error {
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/password/reset", map[string]string{"email": email})
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// ResetPassword 重置密码
func (c *Client) ResetPassword(ctx context.Context, req *PasswordResetRequest) error {
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/password/reset/confirm", req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// SendVerifyCode 发送验证码
func (c *Client) SendVerifyCode(ctx context.Context, phone string) error {
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/phone/send-code", map[string]string{"phone": phone})
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// LoginWithPhone 手机号登录
func (c *Client) LoginWithPhone(ctx context.Context, phone, code string) (*LoginResponse, error) {
req := map[string]string{
"phone": phone,
"code": code,
}
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/login/phone", req)
if err != nil {
return nil, err
}
var result LoginResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
if result.AccessToken != "" {
c.SetAccessToken(result.AccessToken)
}
return &result, nil
}
// OAuthURL 获取 OAuth 授权 URL
func (c *Client) OAuthURL(provider string, redirectURI, state string) (string, error) {
params := map[string]string{
"provider": provider,
"redirect_uri": redirectURI,
}
if state != "" {
params["state"] = state
}
query := ""
for k, v := range params {
if query != "" {
query += "&"
}
query += k + "=" + v
}
return fmt.Sprintf("%s/api/v1/auth/oauth/authorize?%s", c.baseURL, query), nil
}
// HandleOAuthCallback 处理 OAuth 回调
func (c *Client) HandleOAuthCallback(ctx context.Context, provider, code string) (*LoginResponse, error) {
req := map[string]string{
"provider": provider,
"code": code,
}
resp, err := c.doRequest(ctx, "POST", "/api/v1/auth/oauth/callback", req)
if err != nil {
return nil, err
}
var result LoginResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
if result.AccessToken != "" {
c.SetAccessToken(result.AccessToken)
}
return &result, nil
}

View File

@@ -0,0 +1,144 @@
package userManagement
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
// Client API 客户端
type Client struct {
baseURL string
httpClient *http.Client
accessToken string
apiKey string
}
// ClientOption 配置选项
type ClientOption func(*Client)
// WithAPIToken 设置 API Token用于简单认证
func WithAPIToken(token string) ClientOption {
return func(c *Client) {
c.apiKey = token
}
}
// WithAccessToken 设置 Access Token用于已认证请求
func WithAccessToken(token string) ClientOption {
return func(c *Client) {
c.accessToken = token
}
}
// WithHTTPClient 设置自定义 HTTP 客户端
func WithHTTPClient(httpClient *http.Client) ClientOption {
return func(c *Client) {
c.httpClient = httpClient
}
}
// NewClient 创建新的 API 客户端
func NewClient(baseURL string, opts ...ClientOption) *Client {
c := &Client{
baseURL: baseURL,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
for _, opt := range opts {
opt(c)
}
return c
}
// APIResponse 标准 API 响应
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data,omitempty"`
}
// ErrorResponse 错误响应
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
u, err := url.JoinPath(c.baseURL, path)
if err != nil {
return nil, fmt.Errorf("failed to join URL: %w", err)
}
var reqBody io.Reader
if body != nil {
jsonData, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
reqBody = bytes.NewReader(jsonData)
}
req, err := http.NewRequestWithContext(ctx, method, u, reqBody)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if c.accessToken != "" {
req.Header.Set("Authorization", "Bearer "+c.accessToken)
} else if c.apiKey != "" {
req.Header.Set("X-API-Key", c.apiKey)
}
return c.httpClient.Do(req)
}
func (c *Client) parseResponse(resp *http.Response, result interface{}) error {
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode >= 400 {
var errResp ErrorResponse
if err := json.Unmarshal(body, &errResp); err == nil {
return fmt.Errorf("API error %d: %s", resp.StatusCode, errResp.Message)
}
return fmt.Errorf("API error %d: %s", resp.StatusCode, string(body))
}
if result == nil {
return nil
}
var apiResp APIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}
if apiResp.Data != nil {
if err := json.Unmarshal(apiResp.Data, result); err != nil {
return fmt.Errorf("failed to unmarshal data: %w", err)
}
}
return nil
}
// SetAccessToken 设置访问令牌
func (c *Client) SetAccessToken(token string) {
c.accessToken = token
}

View File

@@ -0,0 +1,138 @@
package userManagement
import (
"context"
"fmt"
)
// ListDevicesParams 设备列表查询参数
type ListDevicesParams struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID int64 `json:"user_id,omitempty"`
IsActive *bool `json:"is_active,omitempty"`
IsTrusted *bool `json:"is_trusted,omitempty"`
}
// GetMyDevices 获取当前用户的设备列表
func (c *Client) GetMyDevices(ctx context.Context) ([]*Device, error) {
resp, err := c.doRequest(ctx, "GET", "/api/v1/devices/me", nil)
if err != nil {
return nil, err
}
var result []*Device
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return result, nil
}
// GetTrustedDevices 获取信任设备列表
func (c *Client) GetTrustedDevices(ctx context.Context) ([]*Device, error) {
resp, err := c.doRequest(ctx, "GET", "/api/v1/devices/me/trusted", nil)
if err != nil {
return nil, err
}
var result []*Device
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return result, nil
}
// GetDevice 获取设备详情
func (c *Client) GetDevice(ctx context.Context, id int64) (*Device, error) {
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/devices/%d", id), nil)
if err != nil {
return nil, err
}
var result Device
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// ListDevices 获取设备列表(管理员用)
func (c *Client) ListDevices(ctx context.Context, params *ListDevicesParams) (*PaginatedResponse, error) {
if params.Page <= 0 {
params.Page = 1
}
if params.PageSize <= 0 {
params.PageSize = 20
}
path := fmt.Sprintf("/api/v1/admin/devices?page=%d&page_size=%d", params.Page, params.PageSize)
if params.UserID > 0 {
path += fmt.Sprintf("&user_id=%d", params.UserID)
}
resp, err := c.doRequest(ctx, "GET", path, nil)
if err != nil {
return nil, err
}
var result PaginatedResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// TrustDevice 信任设备
func (c *Client) TrustDevice(ctx context.Context, deviceID int64) error {
resp, err := c.doRequest(ctx, "POST", fmt.Sprintf("/api/v1/devices/%d/trust", deviceID), nil)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// UntrustDevice 取消设备信任
func (c *Client) UntrustDevice(ctx context.Context, deviceID int64) error {
resp, err := c.doRequest(ctx, "DELETE", fmt.Sprintf("/api/v1/devices/%d/trust", deviceID), nil)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// TrustDeviceByDeviceID 通过 device_id 信任设备
func (c *Client) TrustDeviceByDeviceID(ctx context.Context, deviceID string) error {
resp, err := c.doRequest(ctx, "POST", fmt.Sprintf("/api/v1/devices/by-device-id/%s/trust", deviceID), nil)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// RevokeDevice 撤销设备
func (c *Client) RevokeDevice(ctx context.Context, deviceID int64) error {
resp, err := c.doRequest(ctx, "DELETE", fmt.Sprintf("/api/v1/devices/%d", deviceID), nil)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// LogoutOtherDevices 登出其他设备
func (c *Client) LogoutOtherDevices(ctx context.Context, currentDeviceID string) error {
req := map[string]string{"current_device_id": currentDeviceID}
resp, err := c.doRequest(ctx, "POST", "/api/v1/devices/me/logout-others", req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}

View File

@@ -0,0 +1,135 @@
package userManagement
import (
"context"
"fmt"
"log"
)
// Example_basic_usage 基础使用示例
func Example_basic_usage() {
// 创建客户端
client := NewClient("https://api.example.com")
// 登录
loginResp, err := client.Login(context.Background(), &LoginRequest{
Username: "admin",
Password: "password123",
DeviceName: "Go SDK Test",
})
if err != nil {
log.Fatalf("Login failed: %v", err)
}
fmt.Printf("Logged in as %s, token: %s...\n", loginResp.User.Username, loginResp.AccessToken[:20])
}
// Example_user_management 用户管理示例
func Example_user_management() {
client := NewClient("https://api.example.com", WithAPIToken("your-api-token"))
// 获取当前用户
user, err := client.GetCurrentUser(context.Background())
if err != nil {
log.Fatalf("GetCurrentUser failed: %v", err)
}
fmt.Printf("Current user: %s (%s)\n", user.Username, user.Email)
// 创建新用户
newUser, err := client.CreateUser(context.Background(), &CreateUserRequest{
Username: "newuser",
Email: "newuser@example.com",
Password: "SecurePass123!",
Status: UserStatusActive,
})
if err != nil {
log.Fatalf("CreateUser failed: %v", err)
}
fmt.Printf("Created user: %s (ID: %d)\n", newUser.Username, newUser.ID)
// 更新用户
updatedUser, err := client.UpdateUser(context.Background(), newUser.ID, &UpdateUserRequest{
Nickname: "New Nickname",
})
if err != nil {
log.Fatalf("UpdateUser failed: %v", err)
}
fmt.Printf("Updated nickname: %s\n", updatedUser.Nickname)
// 删除用户
if err := client.DeleteUser(context.Background(), newUser.ID); err != nil {
log.Fatalf("DeleteUser failed: %v", err)
}
fmt.Printf("User %d deleted\n", newUser.ID)
}
// Example_device_management 设备管理示例
func Example_device_management() {
client := NewClient("https://api.example.com", WithAccessToken("access-token"))
// 获取我的设备
devices, err := client.GetMyDevices(context.Background())
if err != nil {
log.Fatalf("GetMyDevices failed: %v", err)
}
fmt.Printf("My devices (%d):\n", len(devices))
for _, d := range devices {
trustStatus := "untrusted"
if d.IsTrusted {
trustStatus = "trusted"
}
fmt.Printf(" - %s (%s) [%s]\n", d.DeviceName, d.DeviceType, trustStatus)
}
// 获取信任设备
trusted, err := client.GetTrustedDevices(context.Background())
if err != nil {
log.Fatalf("GetTrustedDevices failed: %v", err)
}
fmt.Printf("Trusted devices: %d\n", len(trusted))
}
// Example_role_management 角色管理示例
func Example_role_management() {
client := NewClient("https://api.example.com", WithAccessToken("access-token"))
// 获取角色列表
roles, err := client.ListRoles(context.Background(), &ListRolesParams{
Page: 1,
PageSize: 20,
})
if err != nil {
log.Fatalf("ListRoles failed: %v", err)
}
fmt.Printf("Total roles: %d\n", roles.Total)
// 获取权限树
permissions, err := client.ListPermissions(context.Background())
if err != nil {
log.Fatalf("ListPermissions failed: %v", err)
}
fmt.Printf("Total permissions: %d\n", len(permissions))
}
// Example_totp TOTP 两因素认证示例
func Example_totp() {
client := NewClient("https://api.example.com", WithAccessToken("access-token"))
// 启用 TOTP
setup, err := client.EnableTOTP(context.Background())
if err != nil {
log.Fatalf("EnableTOTP failed: %v", err)
}
fmt.Printf("TOTP Secret: %s\n", setup.Secret)
fmt.Printf("QR Code URL: %s\n", setup.QRCodeURL)
fmt.Printf("Recovery Codes: %v\n", setup.RecoveryCodes)
// 用户手动验证 TOTP 后才能正式启用
// 这里用示例 code 验证
if err := client.VerifyTOTP(context.Background(), "123456"); err != nil {
fmt.Printf("TOTP verification: %v\n", err)
} else {
fmt.Println("TOTP verified successfully")
}
}

View File

@@ -0,0 +1,3 @@
module github.com/user-management-system/sdk/go
go 1.21

View File

@@ -0,0 +1,135 @@
package userManagement
import (
"context"
"fmt"
"time"
)
// ListLoginLogsParams 登录日志查询参数
type ListLoginLogsParams struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID int64 `json:"user_id,omitempty"`
Status int `json:"status,omitempty"`
StartAt *time.Time `json:"start_at,omitempty"`
EndAt *time.Time `json:"end_at,omitempty"`
}
// ListOperationLogsParams 操作日志查询参数
type ListOperationLogsParams struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID int64 `json:"user_id,omitempty"`
Action string `json:"action,omitempty"`
Resource string `json:"resource,omitempty"`
StartAt *time.Time `json:"start_at,omitempty"`
EndAt *time.Time `json:"end_at,omitempty"`
}
// GetLoginLogs 获取登录日志列表
func (c *Client) GetLoginLogs(ctx context.Context, params *ListLoginLogsParams) (*PaginatedResponse, error) {
if params.Page <= 0 {
params.Page = 1
}
if params.PageSize <= 0 {
params.PageSize = 20
}
path := fmt.Sprintf("/api/v1/logs/login?page=%d&page_size=%d", params.Page, params.PageSize)
if params.UserID > 0 {
path += fmt.Sprintf("&user_id=%d", params.UserID)
}
if params.Status > 0 {
path += fmt.Sprintf("&status=%d", params.Status)
}
resp, err := c.doRequest(ctx, "GET", path, nil)
if err != nil {
return nil, err
}
var result PaginatedResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetOperationLogs 获取操作日志列表
func (c *Client) GetOperationLogs(ctx context.Context, params *ListOperationLogsParams) (*PaginatedResponse, error) {
if params.Page <= 0 {
params.Page = 1
}
if params.PageSize <= 0 {
params.PageSize = 20
}
path := fmt.Sprintf("/api/v1/logs/operation?page=%d&page_size=%d", params.Page, params.PageSize)
if params.UserID > 0 {
path += fmt.Sprintf("&user_id=%d", params.UserID)
}
if params.Action != "" {
path += "&action=" + params.Action
}
if params.Resource != "" {
path += "&resource=" + params.Resource
}
resp, err := c.doRequest(ctx, "GET", path, nil)
if err != nil {
return nil, err
}
var result PaginatedResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// ExportLoginLogsRequest 导出登录日志请求
type ExportLoginLogsRequest struct {
Format string `json:"format"` // "xlsx" or "csv"
UserID int64 `json:"user_id,omitempty"`
Status int `json:"status,omitempty"`
StartAt *time.Time `json:"start_at,omitempty"`
EndAt *time.Time `json:"end_at,omitempty"`
Fields string `json:"fields,omitempty"`
}
// ExportLoginLogs 导出登录日志(返回下载 URL
func (c *Client) ExportLoginLogs(ctx context.Context, req *ExportLoginLogsRequest) (string, error) {
resp, err := c.doRequest(ctx, "GET", "/api/v1/logs/login/export", req)
if err != nil {
return "", err
}
var result map[string]string
if err := c.parseResponse(resp, &result); err != nil {
return "", err
}
if url, ok := result["download_url"]; ok {
return url, nil
}
return "", nil
}
// GetStats 获取统计信息
func (c *Client) GetStats(ctx context.Context) (*Stats, error) {
resp, err := c.doRequest(ctx, "GET", "/api/v1/stats/dashboard", nil)
if err != nil {
return nil, err
}
var result Stats
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}

View File

@@ -0,0 +1,157 @@
package userManagement
import (
"context"
"fmt"
)
// CreateRoleRequest 创建角色请求
type CreateRoleRequest struct {
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description,omitempty"`
PermissionIDs []int64 `json:"permission_ids,omitempty"`
Status RoleStatus `json:"status,omitempty"`
}
// UpdateRoleRequest 更新角色请求
type UpdateRoleRequest struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
PermissionIDs []int64 `json:"permission_ids,omitempty"`
Status RoleStatus `json:"status,omitempty"`
}
// ListRolesParams 角色列表查询参数
type ListRolesParams struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Keyword string `json:"keyword,omitempty"`
Status string `json:"status,omitempty"`
}
// GetRole 获取角色详情
func (c *Client) GetRole(ctx context.Context, id int64) (*Role, error) {
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/roles/%d", id), nil)
if err != nil {
return nil, err
}
var result Role
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// ListRoles 获取角色列表
func (c *Client) ListRoles(ctx context.Context, params *ListRolesParams) (*PaginatedResponse, error) {
if params.Page <= 0 {
params.Page = 1
}
if params.PageSize <= 0 {
params.PageSize = 20
}
path := fmt.Sprintf("/api/v1/roles?page=%d&page_size=%d", params.Page, params.PageSize)
if params.Keyword != "" {
path += "&keyword=" + params.Keyword
}
if params.Status != "" {
path += "&status=" + params.Status
}
resp, err := c.doRequest(ctx, "GET", path, nil)
if err != nil {
return nil, err
}
var result PaginatedResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// CreateRole 创建角色
func (c *Client) CreateRole(ctx context.Context, req *CreateRoleRequest) (*Role, error) {
resp, err := c.doRequest(ctx, "POST", "/api/v1/roles", req)
if err != nil {
return nil, err
}
var result Role
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// UpdateRole 更新角色
func (c *Client) UpdateRole(ctx context.Context, id int64, req *UpdateRoleRequest) (*Role, error) {
resp, err := c.doRequest(ctx, "PUT", fmt.Sprintf("/api/v1/roles/%d", id), req)
if err != nil {
return nil, err
}
var result Role
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// DeleteRole 删除角色
func (c *Client) DeleteRole(ctx context.Context, id int64) error {
resp, err := c.doRequest(ctx, "DELETE", fmt.Sprintf("/api/v1/roles/%d", id), nil)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// AssignPermissions 分配权限给角色
func (c *Client) AssignPermissions(ctx context.Context, roleID int64, permissionIDs []int64) error {
req := map[string][]int64{"permission_ids": permissionIDs}
resp, err := c.doRequest(ctx, "POST", fmt.Sprintf("/api/v1/roles/%d/permissions", roleID), req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// GetRolePermissions 获取角色权限
func (c *Client) GetRolePermissions(ctx context.Context, roleID int64) ([]*Permission, error) {
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/roles/%d/permissions", roleID), nil)
if err != nil {
return nil, err
}
var result []*Permission
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return result, nil
}
// ListPermissions 获取权限列表(树形)
func (c *Client) ListPermissions(ctx context.Context) ([]*Permission, error) {
resp, err := c.doRequest(ctx, "GET", "/api/v1/permissions", nil)
if err != nil {
return nil, err
}
var result []*Permission
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return result, nil
}

View File

@@ -0,0 +1,171 @@
package userManagement
import "time"
// User 用户
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Phone string `json:"phone,omitempty"`
Nickname string `json:"nickname,omitempty"`
Avatar string `json:"avatar,omitempty"`
Status UserStatus `json:"status"`
RoleIDs []int64 `json:"role_ids,omitempty"`
Roles []*Role `json:"roles,omitempty"`
IsSuperAdmin bool `json:"is_super_admin"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
}
// UserStatus 用户状态
type UserStatus string
const (
UserStatusActive UserStatus = "active"
UserStatusInactive UserStatus = "inactive"
UserStatusBanned UserStatus = "banned"
)
// Role 角色
type Role struct {
ID int64 `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description,omitempty"`
Status RoleStatus `json:"status"`
Permissions []*Permission `json:"permissions,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// RoleStatus 角色状态
type RoleStatus string
const (
RoleStatusActive RoleStatus = "active"
RoleStatusInactive RoleStatus = "inactive"
)
// Permission 权限
type Permission struct {
ID int64 `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description,omitempty"`
Type PermissionType `json:"type"`
ParentID *int64 `json:"parent_id,omitempty"`
Children []*Permission `json:"children,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
// PermissionType 权限类型
type PermissionType string
const (
PermissionTypeMenu PermissionType = "menu"
PermissionTypeAction PermissionType = "action"
PermissionTypeAPI PermissionType = "api"
)
// Device 设备
type Device struct {
ID int64 `json:"id"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
DeviceType DeviceType `json:"device_type"`
DeviceOS string `json:"device_os"`
DeviceBrowser string `json:"device_browser"`
IP string `json:"ip"`
Location string `json:"location,omitempty"`
IsTrusted bool `json:"is_trusted"`
IsActive bool `json:"is_active"`
LastActiveAt time.Time `json:"last_active_at"`
CreatedAt time.Time `json:"created_at"`
UserID int64 `json:"user_id"`
}
// DeviceType 设备类型
type DeviceType string
const (
DeviceTypeDesktop DeviceType = "desktop"
DeviceTypeMobile DeviceType = "mobile"
DeviceTypeTablet DeviceType = "tablet"
)
// LoginLog 登录日志
type LoginLog struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Username string `json:"username"`
IP string `json:"ip"`
Location string `json:"location,omitempty"`
DeviceID string `json:"device_id"`
DeviceName string `json:"device_name"`
Status LoginStatus `json:"status"`
FailReason string `json:"fail_reason,omitempty"`
LoginMethod string `json:"login_method"`
CreatedAt time.Time `json:"created_at"`
}
// LoginStatus 登录状态
type LoginStatus int
const (
LoginStatusFailed LoginStatus = 0
LoginStatusSuccess LoginStatus = 1
)
// OperationLog 操作日志
type OperationLog struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Username string `json:"username"`
Action string `json:"action"`
Resource string `json:"resource"`
ResourceID *int64 `json:"resource_id,omitempty"`
Details string `json:"details,omitempty"`
IP string `json:"ip"`
Status int `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
// Webhook Webhook 配置
type Webhook struct {
ID int64 `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Events []string `json:"events"`
Secret string `json:"secret,omitempty"`
IsActive bool `json:"is_active"`
RetryCount int `json:"retry_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Stats 统计信息
type Stats struct {
TotalUsers int64 `json:"total_users"`
ActiveUsers int64 `json:"active_users"`
TotalDevices int64 `json:"total_devices"`
ActiveDevices int64 `json:"active_devices"`
TodayLogins int64 `json:"today_logins"`
TodayFailLogins int64 `json:"today_fail_logins"`
}
// Pagination 分页参数
type Pagination struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// PaginatedResponse 分页响应
type PaginatedResponse struct {
Items interface{} `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Pages int `json:"pages"`
}

View File

@@ -0,0 +1,247 @@
package userManagement
import (
"context"
"fmt"
)
// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
Phone string `json:"phone,omitempty"`
Nickname string `json:"nickname,omitempty"`
RoleIDs []int64 `json:"role_ids,omitempty"`
Status UserStatus `json:"status,omitempty"`
}
// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
Nickname string `json:"nickname,omitempty"`
Avatar string `json:"avatar,omitempty"`
Status UserStatus `json:"status,omitempty"`
}
// AssignRolesRequest 分配角色请求
type AssignRolesRequest struct {
RoleIDs []int64 `json:"role_ids"`
}
// ListUsersParams 用户列表查询参数
type ListUsersParams struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Keyword string `json:"keyword,omitempty"`
Status string `json:"status,omitempty"`
}
// GetCurrentUser 获取当前登录用户
func (c *Client) GetCurrentUser(ctx context.Context) (*User, error) {
resp, err := c.doRequest(ctx, "GET", "/api/v1/users/me", nil)
if err != nil {
return nil, err
}
var result User
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// GetUser 获取用户详情
func (c *Client) GetUser(ctx context.Context, id int64) (*User, error) {
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/users/%d", id), nil)
if err != nil {
return nil, err
}
var result User
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// ListUsers 获取用户列表
func (c *Client) ListUsers(ctx context.Context, params *ListUsersParams) (*PaginatedResponse, error) {
if params.Page <= 0 {
params.Page = 1
}
if params.PageSize <= 0 {
params.PageSize = 20
}
path := fmt.Sprintf("/api/v1/users?page=%d&page_size=%d", params.Page, params.PageSize)
if params.Keyword != "" {
path += "&keyword=" + params.Keyword
}
if params.Status != "" {
path += "&status=" + params.Status
}
resp, err := c.doRequest(ctx, "GET", path, nil)
if err != nil {
return nil, err
}
var result PaginatedResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// CreateUser 创建用户
func (c *Client) CreateUser(ctx context.Context, req *CreateUserRequest) (*User, error) {
resp, err := c.doRequest(ctx, "POST", "/api/v1/users", req)
if err != nil {
return nil, err
}
var result User
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// UpdateUser 更新用户
func (c *Client) UpdateUser(ctx context.Context, id int64, req *UpdateUserRequest) (*User, error) {
resp, err := c.doRequest(ctx, "PUT", fmt.Sprintf("/api/v1/users/%d", id), req)
if err != nil {
return nil, err
}
var result User
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// DeleteUser 删除用户
func (c *Client) DeleteUser(ctx context.Context, id int64) error {
resp, err := c.doRequest(ctx, "DELETE", fmt.Sprintf("/api/v1/users/%d", id), nil)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// AssignRoles 分配角色
func (c *Client) AssignRoles(ctx context.Context, userID int64, req *AssignRolesRequest) error {
resp, err := c.doRequest(ctx, "POST", fmt.Sprintf("/api/v1/users/%d/roles", userID), req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// GetUserRoles 获取用户角色
func (c *Client) GetUserRoles(ctx context.Context, userID int64) ([]*Role, error) {
resp, err := c.doRequest(ctx, "GET", fmt.Sprintf("/api/v1/users/%d/roles", userID), nil)
if err != nil {
return nil, err
}
var result []*Role
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return result, nil
}
// UpdatePassword 更新密码
func (c *Client) UpdatePassword(ctx context.Context, oldPassword, newPassword string) error {
req := map[string]string{
"old_password": oldPassword,
"new_password": newPassword,
}
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/password", req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// BindEmail 绑定邮箱
func (c *Client) BindEmail(ctx context.Context, email string) error {
req := map[string]string{"email": email}
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/email", req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// BindPhone 绑定手机
func (c *Client) BindPhone(ctx context.Context, phone, code string) error {
req := map[string]string{
"phone": phone,
"code": code,
}
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/phone", req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// EnableTOTP 启用 TOTP
func (c *Client) EnableTOTP(ctx context.Context) (*TOTPSetupResponse, error) {
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/2fa/totp/setup", nil)
if err != nil {
return nil, err
}
var result TOTPSetupResponse
if err := c.parseResponse(resp, &result); err != nil {
return nil, err
}
return &result, nil
}
// TOTPSetupResponse TOTP 设置响应
type TOTPSetupResponse struct {
Secret string `json:"secret"`
QRCodeURL string `json:"qr_code_url"`
RecoveryCodes []string `json:"recovery_codes,omitempty"`
}
// VerifyTOTP 验证 TOTP
func (c *Client) VerifyTOTP(ctx context.Context, code string) error {
req := map[string]string{"code": code}
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/2fa/totp/verify", req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}
// DisableTOTP 禁用 TOTP
func (c *Client) DisableTOTP(ctx context.Context, code string) error {
req := map[string]string{"code": code}
resp, err := c.doRequest(ctx, "POST", "/api/v1/users/me/2fa/totp/disable", req)
if err != nil {
return err
}
return c.parseResponse(resp, nil)
}