package service import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "encoding/hex" "errors" "fmt" "io" "strings" "time" "seeyon-filesystem/config" "seeyon-filesystem/dto" "seeyon-filesystem/model" "seeyon-filesystem/utils" ) // OpenAPIService 开放平台服务 type OpenAPIService struct{} func NewOpenAPIService() *OpenAPIService { return &OpenAPIService{} } // ========== 应用管理 ========== // CreateApp 创建应用 func (s *OpenAPIService) CreateApp(appName string) (*dto.AppVO, error) { appID := generateRandomHex(16) appSecret := generateRandomHex(32) app := &model.App{ AppID: appID, AppSecret: appSecret, AppName: appName, Status: 1, } if err := config.DB.Create(app).Error; err != nil { return nil, fmt.Errorf("创建应用失败: %w", err) } return &dto.AppVO{ ID: app.ID, AppID: appID, AppSecret: appSecret, AppName: appName, Status: 1, CreatedAt: app.CreatedAt.Format("2006-01-02 15:04:05"), }, nil } // ListApps 获取应用列表(含绑定用户) func (s *OpenAPIService) ListApps() ([]dto.AppVO, error) { var apps []model.App if err := config.DB.Order("created_at DESC").Find(&apps).Error; err != nil { return nil, err } vos := make([]dto.AppVO, 0, len(apps)) for _, a := range apps { vo := dto.AppVO{ ID: a.ID, AppID: a.AppID, AppName: a.AppName, Status: a.Status, CreatedAt: a.CreatedAt.Format("2006-01-02 15:04:05"), } // 查询绑定用户 vo.Users = s.getAppUsers(a.ID) vos = append(vos, vo) } return vos, nil } // DeleteApp 删除应用 func (s *OpenAPIService) DeleteApp(id uint64) error { config.DB.Where("app_id = ?", id).Delete(&model.AppUser{}) return config.DB.Delete(&model.App{}, id).Error } // ToggleApp 启用/禁用应用 func (s *OpenAPIService) ToggleApp(id uint64, status int8) error { return config.DB.Model(&model.App{}).Where("id = ?", id).Update("status", status).Error } // ========== 应用用户绑定 ========== // BindUsers 绑定用户到应用 func (s *OpenAPIService) BindUsers(appID uint64, userIDs []uint64) error { // 验证应用存在 var app model.App if err := config.DB.First(&app, appID).Error; err != nil { return errors.New("应用不存在") } // 清除旧绑定 config.DB.Where("app_id = ?", appID).Delete(&model.AppUser{}) // 创建新绑定 for _, uid := range userIDs { appUser := &model.AppUser{ AppID: appID, UserID: uid, } config.DB.Create(appUser) } return nil } // UnbindUser 解绑用户 func (s *OpenAPIService) UnbindUser(appID, userID uint64) error { return config.DB.Where("app_id = ? AND user_id = ?", appID, userID).Delete(&model.AppUser{}).Error } // getAppUsers 获取应用绑定的用户列表 func (s *OpenAPIService) getAppUsers(appID uint64) []dto.AppUserVO { var appUsers []model.AppUser config.DB.Where("app_id = ?", appID).Find(&appUsers) if len(appUsers) == 0 { return nil } userIDs := make([]uint64, len(appUsers)) for i, au := range appUsers { userIDs[i] = au.UserID } var users []model.User config.DB.Where("id IN ?", userIDs).Find(&users) vos := make([]dto.AppUserVO, 0, len(users)) for _, u := range users { vos = append(vos, dto.AppUserVO{ UserID: u.ID, Username: u.Username, Nickname: u.Nickname, }) } return vos } // ========== Token 生成与验证 ========== // GenerateToken 生成SSO Token func (s *OpenAPIService) GenerateToken(appID, username string) (string, error) { app, err := s.findApp(appID) if err != nil { return "", err } plaintext := fmt.Sprintf("%s:%d", username, time.Now().Unix()) token, err := aesEncrypt(plaintext, app.AppSecret) if err != nil { return "", fmt.Errorf("生成token失败: %w", err) } return token, nil } // ValidateToken 验证SSO Token // 流程: 解密token → 获取username → 验证用户存在 → 验证用户是否被授权给该应用 func (s *OpenAPIService) ValidateToken(appID, token string) (string, error) { // 1. 查找应用 app, err := s.findApp(appID) if err != nil { return "", err } // 2. 解密token plaintext, err := aesDecrypt(token, app.AppSecret) if err != nil { return "", errors.New("token无效或已过期") } // 3. 解析 username:timestamp parts := strings.SplitN(plaintext, ":", 2) if len(parts) != 2 { return "", errors.New("token格式错误") } username := parts[0] // 4. 验证用户是否存在且启用 var user model.User if err := config.DB.Where("username = ? AND status = 1", username).First(&user).Error; err != nil { return "", errors.New("用户不存在或已禁用") } // 5. 验证用户是否被授权给该应用 var count int64 config.DB.Model(&model.AppUser{}).Where("app_id = ? AND user_id = ?", app.ID, user.ID).Count(&count) if count == 0 { return "", fmt.Errorf("用户 %s 未被授权访问此应用", username) } return username, nil } // GenerateJWTForUser 为验证通过的用户生成JWT func (s *OpenAPIService) GenerateJWTForUser(username string) (map[string]interface{}, error) { var user model.User if err := config.DB.Where("username = ?", username).First(&user).Error; err != nil { return nil, errors.New("用户不存在") } token, err := utils.GenerateToken(user.ID, user.Username, user.Role, config.AppConfig.JWT.Secret, config.AppConfig.JWT.Expiration) if err != nil { return nil, fmt.Errorf("生成JWT失败: %w", err) } return map[string]interface{}{ "token": token, "username": user.Username, "nickname": user.Nickname, "role": user.Role, }, nil } // ========== 内部方法 ========== func (s *OpenAPIService) findApp(appID string) (*model.App, error) { var app model.App if err := config.DB.Where("app_id = ? AND status = 1", appID).First(&app).Error; err != nil { return nil, errors.New("应用不存在或已禁用") } return &app, nil } // AES 加解密 func aesEncrypt(plaintext, key string) (string, error) { keyBytes := padKey(key) block, err := aes.NewCipher(keyBytes) if err != nil { return "", err } paddedText := pkcs7Pad([]byte(plaintext), aes.BlockSize) iv := make([]byte, aes.BlockSize) if _, err := io.ReadFull(rand.Reader, iv); err != nil { return "", err } mode := cipher.NewCBCEncrypter(block, iv) ciphertext := make([]byte, len(paddedText)) mode.CryptBlocks(ciphertext, paddedText) result := append(iv, ciphertext...) return base64.StdEncoding.EncodeToString(result), nil } func aesDecrypt(ciphertext, key string) (string, error) { keyBytes := padKey(key) data, err := base64.StdEncoding.DecodeString(ciphertext) if err != nil { return "", err } if len(data) < aes.BlockSize { return "", errors.New("ciphertext too short") } block, err := aes.NewCipher(keyBytes) if err != nil { return "", err } iv := data[:aes.BlockSize] encrypted := data[aes.BlockSize:] if len(encrypted)%aes.BlockSize != 0 { return "", errors.New("ciphertext is not a multiple of block size") } mode := cipher.NewCBCDecrypter(block, iv) plaintext := make([]byte, len(encrypted)) mode.CryptBlocks(plaintext, encrypted) plaintext, err = pkcs7Unpad(plaintext) if err != nil { return "", err } return string(plaintext), nil } func padKey(key string) []byte { keyBytes := []byte(key) if len(keyBytes) >= 32 { return keyBytes[:32] } padded := make([]byte, 32) copy(padded, keyBytes) return padded } func pkcs7Pad(data []byte, blockSize int) []byte { padding := blockSize - len(data)%blockSize padText := make([]byte, padding) for i := range padText { padText[i] = byte(padding) } return append(data, padText...) } func pkcs7Unpad(data []byte) ([]byte, error) { if len(data) == 0 { return nil, errors.New("empty data") } padding := int(data[len(data)-1]) if padding > aes.BlockSize || padding == 0 { return nil, errors.New("invalid padding") } return data[:len(data)-padding], nil } func generateRandomHex(length int) string { bytes := make([]byte, length/2) rand.Read(bytes) return hex.EncodeToString(bytes) }