Files
SeeyonFileSystem/server/service/openapi_service.go
2026-07-18 10:52:32 +08:00

327 lines
7.9 KiB
Go

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{
ID: utils.GenID(),
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)
}