Implement Dify account creation with tenant association: 1. Create tenant named 'username's Workspace' 2. Set owner role for new account 3. Add normal role in ucas's Workspace 4. Fix UUID handling in tenant info retrieval

This commit is contained in:
xin 2025-06-01 17:52:27 +08:00
parent 97ff5ffd24
commit 671886d57b
21 changed files with 976 additions and 24 deletions

View File

@ -7,6 +7,7 @@ import logging
from datetime import datetime, timezone
import psycopg2.extras
from database import get_db_cursor, execute_query, execute_update
from tenant_manager import TenantManager # Import TenantManager for tenant operations
# 配置日志
logger = logging.getLogger(__name__)
@ -65,6 +66,26 @@ class AccountManager:
cursor.connection.commit()
logger.info(f"用户 {username} 邮箱 {email} 注册成功!")
# 创建租户并关联角色
try:
# 创建租户(租户名为"账号名's Workspace"
tenant_name = f"{username}'s Workspace"
tenant_id = TenantManager.create_tenant(tenant_name)
# 关联新账号为自建租户的owner
AccountManager.associate_with_tenant(result[0], tenant_id, "owner")
# 关联新账号为ucas's Workspace的normal角色
ucas_tenant = TenantManager.get_tenant_by_name("ucas's Workspace")
if ucas_tenant:
AccountManager.associate_with_tenant(result[0], ucas_tenant['id'], "normal")
else:
logger.warning("ucas's Workspace not found - skipping normal role association")
except Exception as e:
logger.error(f"租户创建或关联失败: {e}")
# Continue even if tenant operations fail since account creation succeeded
return {
"id": result[0],
"username": result[1],
@ -289,11 +310,14 @@ class AccountManager:
def get_account_tenants(account_id):
"""获取账户关联的所有租户"""
try:
# 处理account_id可能是UUID对象或字符串的情况
account_id_str = str(account_id) if hasattr(account_id, 'hex') else account_id
# 验证account_id是否为有效UUID
try:
uuid.UUID(account_id)
uuid.UUID(account_id_str)
except ValueError:
logger.error(f"无效的account_id格式: {account_id}")
logger.error(f"无效的account_id格式: {account_id_str}")
return []
query = """
@ -302,7 +326,7 @@ class AccountManager:
JOIN tenant_account_joins j ON t.id = j.tenant_id
WHERE j.account_id = %s::uuid;
"""
tenants = execute_query(query, (account_id,))
tenants = execute_query(query, (account_id_str,))
if not tenants:
logger.info(f"账号 {account_id} 未关联任何租户")
return []

View File

@ -256,6 +256,21 @@ async def refresh_token(current_user: dict = Depends(get_current_user)):
return {"access_token": access_token, "token_type": "bearer"}
# 账户管理路由组
@app.post("/api/accounts/")
async def create_account(account: AccountCreate):
"""创建后台管理账户"""
try:
user = backend_account_manager.create_account(account.username, account.email, account.password)
return {
"user_id": str(user["id"]),
"username": user["username"],
"email": user["email"],
"created_at": user["created_at"]
}
except Exception as e:
logger.error(f"创建后台账户失败: {e}")
raise HTTPException(status_code=400, detail="创建后台账户失败")
@app.post("/api/dify_accounts/")
async def create_dify_account(account: AccountCreate):
"""创建Dify账户"""

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAvRzEAE/Cbm+QKTqGQ5Xry+W60kTJeIeoQMb8G374PcbTxdPe
0hURUaIH/h7jQ1rhDkNvkaBXC26v0q6ZkCBt89i4TopWCxewVzXb18DAcUswW2z2
qQs70ssZqpSIn8sQbKfdZn20CD8C1Exkt18uI01VquWqMB30iwZvK8Wly94jm3WC
6xiqnItOz64Y+prOQFhLr0trvoYEf0aKOAnrJr5qeOP6/XnxdF/bHJwOXCU0uFHA
8RXHp58O60fDEZctdSTp9iUQeZidSXS0p3B85NRj/Y2rd/UOEiQ9Sj+DSXg0knXk
CnYiLCeMBpV88bWyCwopRn3Im0haxzVrp9UYVQIDAQABAoIBAAuiuHXjHRhvFCn6
ktuXtDFlyiU5MZg76ItLyE6SH7NVXMmq6+Z11gygG+kefLyJMUMPvSPy/0gRyw77
H9xb/R09dAxJELA6szvlHB4Imw6dd1/jXW3bO2wQg7IY1NXYHa5BydU43hnfyUxy
Atm8KSTnk4zMorhhL3RaX1QMeNNelXRzddzm82u1A/mItIMwyu3b3RXrDjIB/VsS
VtY9C1hiXhcWvZdVcRMTRSQt4nbh7ap1QQG1oCHQKBDwBH4rryZHXioKBdKdJOvJ
/1rNtvwWmUtsUwxVNDQ0GnQQlRhg4QoWoEvL9PzMqEEx/Oix2OPEZMa9ePJjUWoj
j0udMkUCgYEA7YrwB/7ASDrNNC2x6dZvNHFAz5Ng9IDqjdBu+u6V61Aq5whoJFDh
ysmywbSaz5NiVtTltr1wggkmWZJKvCLq3CJ+eRm0UfSAsWzeyeOii1+7eazW5zzp
0PpIGdhNBoQf9jo6hn84iyCnxaehyTPDH+0HecFmYu2xyqGwPvFCqfMCgYEAy856
tbXM/TSeO5GVX/giJ6OzebXcyannbyqeTOgSZ3wlm9pTtsyRhcIfjV4/VjnDkpby
WTPsQqDjCQnWD6kPJXSwRjxQlhoT84FnDf3TtogxOkPUQgp7TRZRFPAy9WtisTyY
EvZefWllw1U5G5WQclBLk4jtWLF8gPG3cya+PpcCgYAGkkJjpjuBhAbrO5xhYKpA
5CUr2OLkwLA8bhiz3mfNU7FsJMJkxswIRR4p16o0tdoRluAn9wOmxUQHVFG29KxU
nKutA/YGEmKBBgb/xMlZW7OfARYM3ZWGT3YxKNewp3UO5bt6vwg8aRHbGPET1NfA
CVmcl6oScIrrlR+ADkKO2wKBgFE7HeuY53gwZbTmZLwYgTx9YcZr9vEl2AmV2fxb
LyKx96dz0JP2WzYN1leZLfNnGI0vvq5RJKS1zWfH1NrvDgtmw+9qkNAS2sreU73e
4mhJRh2r9UjRNq3V7EFFL3pj77UmqpI8QhMIVDgTmqxk42q8mDLYi4kjTLV0IiMy
WPdHAoGBAJkJAhQ1JGSUAY+3QAKBWre8QwabKZdZIzDFq9DdQfnwiHkluueVUI63
YY5U+wbff0dIAPBpeFGap56XYN8qy3/i6UkRNTTYji5kj6XqVWbyn2xU1IX8/KcC
7AqeEsg04ArqGP0eDAw3gfSe4fgJ5Zoc5ZOkARcb/ajMJi+zA2+W
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA6GsVZmXAF+XkytRj9c+5tJP2YlWdYLQMo0HXfprVKobLUI5N
+VMrCSreTCARTpsr2KtPV21zSgwVsRzA3zx9RgnAvHzDkJZtkSS1OHGKkA2CAU2T
bJmpZWDEw5L/wOxx0QKA0tWBZzlWvRtXQjGqte/yNYF18TuxCOty6cIPUEQlVcYq
QCwIeDfxGS94GI5ibwynvp7A8QfocR7DxYUZQdo8RSOlBz7d5ra/KVpIRb7I6fsV
4U4qEYsx2GFFXZeZ6HWH7a+deC7Selv9Eycw9cvERWLQVE+FtGyDygbTHpwG7lPl
8D9Yaf2NkXgibsxqHroK6OKyHIr0CkjlxfX1bQIDAQABAoIBACBSVAWLfO1V4T17
z/LtUmwUb505Psul/mPt0iQfMCnb2QZ448saj8+UkJmRlBaUacKWAFCuPew8Rf/9
ea0R4nINBSqxRxkiclZO/oy87YIe/sdjnG7r56zbikGQfdS8jQ9+A6BOZNKQHMnr
tXTVJ2Jp3gsm19oWbQZKiA6OGEcXDyikRVipDoavDPBGoTBniJzUNwqD+JyasqdX
QoUIqyJPLLTvXNN6pij8MnK+N86RccEVHdd/NdCDUGyh9ej4vs0dkg8BLTT+3fuw
GR6WSwxZ7qFWl7PD0NhcrJfVQo2X1JNHm4BZFo5kNTJiWkKX+uqEp5QNAZmcXXXK
N6owt9kCgYEA9vBZAE6JKvKBj+wNWeAvUGI/W5KTbYuk8NbPFoYzsqFaseewr0ei
Iq4yzp31fmbIBbyx3iYEIuW2sdxMTEg9ZLi1yqh4qxn3aOhN0/dLkAFZlnBNsJVA
izNsdFDtwy9EIT8vg4Tl1byC+ZzvT2cpP5PYVF4auD1DVUP3Zxet6wMCgYEA8PJV
y18O6smWJPkgDfS2rbUHnP2dBJ6JznrauDGTzDlnd9LNL/sQBG4ZN/nDbo+M2++w
84PeaWB+IQvpweQfZYC/CEA8gJqpQZzlL0TiRJTOAFlm45IEkY9IY9ZM2M0QfbmW
Alt9uCxOQ/+O0TSg7s/hJgjFO7HVU1L+0TGc+s8CgYEA1vm6obe9VnQ0MvVPUjUH
O7Wsbq4WRsHRYBHBy1wd2Z768/I+MPntvr9kX9fIBri33ba+KiVyaGcPO5wLmEUz
nWHocXtlQZ+jceL7DlM1pzJlqcAvAlGNFSI5grGJq+iUKVV2qcqixAYPB/uFotyu
Xjj5lcku/cHnwm0fDtLgh70CgYBeIg8quSSWzdL+9uVzDlwgWU10JBEoMNgd9xlk
RNo9O2AVDCJl0GxdjrYDjGyx0RtzAZ3cRXSrLquYNZNJ0NoDSd1YV+BLTiE6JRXU
9dCAgrJIqKTt26o1RAlAGAVF3jfHJq5YlW30ejrJjho52ZnWMxj/RwXnYH67Yv1y
tTNjOQKBgH57/cRMpvuQ/kuLNYu1IS+EEZJ5WlDjEC0lmM2E54+Lb4D1UBMj+Y7V
EZa9r+UQYm5DnsD4BQjiI1PWY/Iu39Cq3hlkxitOcTOds307xHKbivXancdwlwwx
G+K1nGpNnAHSYCf+qaGdQgfG1LU9REKeLiPkIyt2zOtk6tZ4hmNq
-----END RSA PRIVATE KEY-----

54
docs/api/README.md Normal file
View File

@ -0,0 +1,54 @@
# Dify Admin API Documentation
## Overview
This documentation covers the core API modules of the Dify Admin system. Each module provides specific functionality for managing the administration platform.
## Module Documentation
### [Account Management](account_manager.md)
- User account creation and management
- Password hashing and verification
- Tenant association
### [Tenant Management](tenant_manager.md)
- Workspace/tenant creation
- RSA key pair generation
- Tenant search and retrieval
### [Model Management](model_manager.md)
- AI model configuration
- Model encryption and storage
- Bulk operations
### [Provider Management](provider_manager.md)
- AI service provider management
- API key encryption
- Provider configuration
### [Database Operations](database.md)
- Connection pooling
- SQL execution helpers
- Database initialization
### [Operation Logging](operation_logger.md)
- Audit trail recording
- Log querying
- Log maintenance
### [Encryption Services](encryption.md)
- Hybrid encryption (RSA + AES)
- Key management
- API key protection
## Getting Started
To use these APIs, import the required module and instantiate the appropriate class:
```python
from api.account_manager import AccountManager
account_mgr = AccountManager()
```
## Security Notes
- All sensitive operations use encryption
- API keys are never stored in plaintext
- Detailed logging for audit purposes

View File

@ -0,0 +1,97 @@
# Account Manager API Documentation
## Overview
The `AccountManager` class provides functionality for managing user accounts, including:
- Account creation and authentication
- Password management (hashing, verification, reset)
- Account search and retrieval
- Tenant association management
## Class Methods
### `hash_password(password, salt=None)`
Generates a hashed password and salt for secure storage.
**Parameters:**
- `password` (str): Plain text password to hash
- `salt` (bytes, optional): Optional salt value. If None, generates new salt.
**Returns:**
- Tuple of (base64_password_hashed, base64_salt)
**Example:**
```python
hashed_pw, salt = AccountManager.hash_password("mysecurepassword")
```
### `create_account(username, email, password)`
Creates a new user account.
**Parameters:**
- `username` (str): Unique username
- `email` (str): User email address
- `password` (str): Plain text password
**Returns:**
- Dictionary with created account details:
```python
{
"id": UUID,
"username": str,
"email": str,
"created_at": datetime
}
```
### `get_user_by_username(username)`
Retrieves user information by username.
**Parameters:**
- `username` (str): Username to search for
**Returns:**
- User dictionary or None if not found
### `search_accounts(search=None, page=1, page_size=10)`
Searches accounts with pagination.
**Parameters:**
- `search` (str): Optional search term
- `page` (int): Page number (1-based)
- `page_size` (int): Items per page
**Returns:**
```python
{
"data": [user_dicts],
"total": int
}
```
### `verify_password(plain_password, hashed_password, salt)`
Verifies a password against stored hash.
**Parameters:**
- `plain_password` (str): Password to verify
- `hashed_password` (str): Stored password hash
- `salt` (str): Password salt
**Returns:**
- bool: True if password matches
### Password Management
- `update_password(username, email, new_password)`
- `reset_password(account_id)`
### Tenant Association
- `associate_with_tenant(account_id, tenant_id, role, invited_by, current)`
- `get_tenant_accounts(tenant_id)`
- `get_account_tenants(account_id)`
## Error Handling
All methods raise exceptions on failure and log errors using the module logger.
## Security Notes
- Uses PBKDF2 with SHA-256 for password hashing
- Generates random salts for each password
- All sensitive operations are logged

81
docs/api/database.md Normal file
View File

@ -0,0 +1,81 @@
# Database API Documentation
## Overview
The database module provides database connection management and basic operations for both PostgreSQL and SQLite databases. Key features include:
- Connection pooling for PostgreSQL
- Context managers for safe connection handling
- Unified interface for both database types
- SQL execution helpers
## Connection Management
### `get_db_connection(db_type='postgres')`
Context manager for database connections.
**Parameters:**
- `db_type`: 'postgres' or 'sqlite' (default: 'postgres')
**Usage:**
```python
with get_db_connection() as conn:
# Use connection
```
### `get_db_cursor(cursor_factory=None, db_type='postgres')`
Context manager for database cursors with automatic commit/rollback.
**Parameters:**
- `cursor_factory`: Optional cursor factory (e.g., psycopg2.extras.DictCursor)
- `db_type`: 'postgres' or 'sqlite' (default: 'postgres')
**Usage:**
```python
with get_db_cursor() as cursor:
cursor.execute("SELECT * FROM table")
```
## SQL Execution Helpers
### `execute_query(query, params=None, cursor_factory=None, fetch_one=False, db_type='postgres')`
Executes a query and returns results.
**Parameters:**
- `query`: SQL query string
- `params`: Query parameters (tuple/dict)
- `cursor_factory`: Optional cursor factory
- `fetch_one`: Return single row if True
- `db_type`: Database type
**Returns:**
- Query results (list or single row)
### `execute_update(query, params=None, db_type='postgres')`
Executes an update/insert/delete query.
**Parameters:**
- `query`: SQL query string
- `params`: Query parameters (tuple/dict)
- `db_type`: Database type
**Returns:**
- Number of affected rows
## Initialization
### `init_sqlite_db()`
Initializes SQLite database tables (api_endpoints, api_logs, api_users, api_operations).
## Configuration
Module uses configuration from:
- `DB_CONFIG` for PostgreSQL
- `SQLITE_CONFIG` for SQLite
## Error Handling
- All database operations are wrapped in try/except blocks
- Errors are logged with detailed context
- Connections are automatically returned to pool (PostgreSQL) or closed (SQLite)
## Best Practices
- Always use context managers (`with` statements) for connections/cursors
- For PostgreSQL, prefer connection pooling for better performance
- For complex transactions, use explicit commit/rollback

51
docs/api/encryption.md Normal file
View File

@ -0,0 +1,51 @@
# Encryption API Documentation
## Overview
The `Encryption` class provides cryptographic functions including:
- RSA key pair generation and management
- Hybrid encryption (RSA + AES)
- API key encryption
- Secure key storage
## Class Methods
### Key Management
- `load_public_key(public_key_path_or_content)`
- Loads public key from file or content
- `load_private_key(private_key_path)`
- Loads private key from file
### Encryption/Decryption
- `encrypt(text, public_key)`
- Encrypts text using hybrid RSA+AES approach
- Returns: Encrypted data with "HYBRID:" prefix
- `decrypt(encrypted_text, private_key)`
- Decrypts hybrid encrypted data
- Returns: Original plaintext
### Specialized Methods
- `encrypt_api_key(public_key_pem, api_key)`
- Encrypts API keys with base64 encoding
- Returns: base64 encoded encrypted key
## Security Features
- Uses 2048-bit RSA keys
- AES-256 for symmetric encryption
- Random key generation for each operation
- Secure key storage practices
- Detailed error logging
## Error Handling
- Validates all inputs
- Raises exceptions for invalid operations
- Logs all errors with context
## Example Usage
```python
# Encrypt data
public_key = Encryption.load_public_key("public.pem")
encrypted = Encryption.encrypt("secret data", public_key)
# Decrypt data
private_key = Encryption.load_private_key("private.pem")
decrypted = Encryption.decrypt(encrypted, private_key)

75
docs/api/model_manager.md Normal file
View File

@ -0,0 +1,75 @@
# Model Manager API Documentation
## Overview
The `ModelManager` class provides functionality for managing AI models, including:
- Adding models to tenants (both standard and Volc models)
- Removing models
- Querying model information
- Bulk operations for all tenants
## Class Methods
### Model Addition
- `add_model_for_tenant(tenant_id, public_key_pem, model_config)`
- Adds a standard model to a tenant
- Encrypts API keys using tenant's public key
- Supports different model types (text-generation, embeddings, etc.)
- `add_volc_model_for_tenant(tenant_id, public_key_pem, model_config)`
- Adds a Volc engine model to a tenant
- Special handling for Volc-specific parameters
**Common Parameters:**
- `tenant_id`: Target tenant UUID
- `public_key_pem`: Tenant's public key for encryption
- `model_config`: Dictionary containing model configuration
**Required Fields in model_config:**
```python
{
"model_name": str, # Unique model name
"provider_name": str, # Provider identifier
"model_type": str, # Type of model
"api_key": str # API key to encrypt (or volc_api_key for Volc models)
}
```
### Bulk Operations
- `add_models_for_all_tenants(config_path)`
- `add_volc_models_for_all_tenants(config_path)`
- `add_models_for_tenant(tenant_name, config_path)`
- `add_volc_models_for_tenant(tenant_name, config_path)`
### Model Removal
- `delete_model_for_tenant(tenant_id, model_name)`
- `delete_models_for_tenant(tenant_id)`
- `delete_specific_model_for_all_tenants(model_name)`
### Query Methods
- `get_tenant_models(tenant_id)`
- Returns all models for a tenant
- Format:
```python
{
"tenant_id": str,
"models": [{
"id": str,
"provider_name": str,
"model_name": str,
"model_type": str,
"encrypted_config": dict,
"is_valid": bool,
"created_at": datetime,
"updated_at": datetime
}]
}
```
## Security Considerations
- All API keys are encrypted using tenant's public key
- Supports different encryption configurations per model type
- Detailed logging of all operations
## Error Handling
- Raises exceptions for invalid configurations
- Logs all errors with detailed context

View File

@ -0,0 +1,65 @@
# Operation Logger API Documentation
## Overview
The `OperationLogger` class provides functionality for recording and managing operation logs, including:
- Logging API operations
- Querying operation logs with filters
- Cleaning up old logs
## Class Methods
### `log_operation(user_id, operation_type, endpoint, parameters=None, status="SUCCESS")`
Records an API operation in the database.
**Parameters:**
- `user_id` (int): ID of the user performing the operation
- `operation_type` (str): Type of operation (e.g., "CREATE", "UPDATE")
- `endpoint` (str): API endpoint accessed
- `parameters` (str, optional): Operation parameters in string format
- `status` (str): Operation status (default: "SUCCESS")
**Returns:**
- int: ID of the created log record
**Example:**
```python
logger.log_operation(123, "LOGIN", "/api/auth/login", "username=test", "SUCCESS")
```
### `get_operations(user_id=None, start_time=None, end_time=None, limit=100)`
Queries operation logs with optional filters.
**Parameters:**
- `user_id` (int, optional): Filter by user ID
- `start_time` (datetime, optional): Earliest log time
- `end_time` (datetime, optional): Latest log time
- `limit` (int): Maximum number of logs to return (default: 100)
**Returns:**
- List of log records with fields:
- id
- user_id
- operation_type
- endpoint
- parameters
- status
- created_at
### `clean_old_logs(days=30)`
Deletes logs older than specified number of days.
**Parameters:**
- `days` (int): Delete logs older than this many days (default: 30)
**Returns:**
- int: Number of logs deleted
## Error Handling
- Raises `OperationLogError` for all failures
- Detailed error logging through module logger
## Best Practices
- Log all critical operations for audit trail
- Regularly clean old logs to maintain performance
- Use appropriate status values ("SUCCESS", "FAILED", etc.)
- Include relevant parameters for debugging

View File

@ -0,0 +1,61 @@
# Provider Manager API Documentation
## Overview
The `ProviderManager` class provides functionality for managing AI service providers, including:
- Adding providers to tenants
- Removing providers
- Querying provider information
- Bulk operations for all tenants
## Class Methods
### Provider Addition
- `add_provider_for_tenant(tenant_id, public_key_pem, provider_config)`
- Adds a provider to a tenant
- Encrypts API keys using tenant's public key
- Supports different provider types
**Parameters:**
- `tenant_id`: Target tenant UUID
- `public_key_pem`: Tenant's public key for encryption
- `provider_config`: Dictionary containing provider configuration
**Required Fields in provider_config:**
```python
{
"provider_name": str, # Unique provider name
"config": dict # Provider configuration including API keys
}
```
### Bulk Operations
- `add_providers_for_all_tenants(config_path)`
- `add_providers_for_tenant(tenant_name, config_path)`
### Provider Removal
- `delete_provider_for_tenant(tenant_id, provider_name)`
- `delete_providers_for_tenant(tenant_id)`
### Query Methods
- `get_providers_for_tenant(tenant_id)`
- Returns all providers for a tenant
- Format:
```python
[
{
"id": str,
"provider_name": str,
"provider_type": str,
"is_valid": bool
}
]
```
## Security Considerations
- API keys are encrypted using tenant's public key
- Detailed logging of all operations
- Validation of required fields
## Error Handling
- Raises exceptions for invalid configurations
- Logs all errors with detailed context

View File

@ -0,0 +1,70 @@
# Tenant Manager API Documentation
## Overview
The `TenantManager` class provides functionality for managing tenants (workspaces), including:
- Tenant creation with RSA key pair generation
- Key management (generation, storage)
- Tenant search and retrieval
## Class Methods
### `generate_rsa_key_pair()`
Generates a new RSA key pair for tenant encryption.
**Returns:**
- Tuple of (public_key_pem, private_key)
- public_key_pem: PEM formatted public key string
- private_key: RSA private key object
**Example:**
```python
public_key, private_key = TenantManager.generate_rsa_key_pair()
```
### `save_private_key(tenant_id, private_key)`
Securely stores a private key for a tenant.
**Parameters:**
- `tenant_id` (UUID): Tenant identifier
- `private_key`: RSA private key object
**Returns:**
- Path to stored private key file
### `create_tenant(workspace_name)`
Creates a new tenant with cryptographic keys.
**Parameters:**
- `workspace_name` (str): Name for the new tenant/workspace
**Returns:**
- UUID of created tenant
**Process:**
1. Generates RSA key pair
2. Stores private key securely
3. Creates tenant record with public key
### Query Methods
- `get_tenant_by_name(workspace_name)`: Retrieves tenant by name
- `get_all_tenants()`: Lists all tenants
- `search_tenants(search_term)`: Searches tenants by name
**Return Format:**
```python
{
"id": str/UUID,
"name": str,
"encrypt_public_key": str,
"created_at": datetime
}
```
## Security Considerations
- Uses 2048-bit RSA keys for encryption
- Private keys stored in secure directory structure
- Public keys stored in database for encryption
- All operations are logged
## Error Handling
Methods raise exceptions on failure and log errors using the module logger.

View File

@ -0,0 +1,48 @@
# Dify Admin System Architecture
## Overall Architecture
```mermaid
graph TD
A[Frontend] -->|API Calls| B[Backend]
B --> C[Database]
B --> D[External Services]
C --> E[(PostgreSQL)]
C --> F[(SQLite)]
```
## Core Components
### Frontend
- **Framework**: Vue 3 + TypeScript
- **State Management**: Pinia
- **UI Library**: Element Plus
- **Routing**: Vue Router
### Backend
- **API Services**:
- Account Management
- Tenant Management
- Model Management
- Provider Management
- **Core Modules**:
- Database Access
- Encryption
- Operation Logging
## Data Flow
1. User actions trigger API calls
2. Backend processes requests with business logic
3. Data persisted to database
4. Responses returned to frontend
5. Frontend updates state and UI
## Security Architecture
- JWT Authentication
- RSA Encryption for sensitive data
- Role-based Access Control
- Audit logging
## Deployment
- Frontend: Static hosting (Vite)
- Backend: Containerized microservices
- Database: PostgreSQL (primary), SQLite (logging)

31
docs/web/README.md Normal file
View File

@ -0,0 +1,31 @@
# Web Frontend Documentation
## Technology Stack
- **Framework**: Vue 3 (Composition API)
- **UI Library**: Element Plus
- **State Management**: Pinia
- **Routing**: Vue Router
- **HTTP Client**: Axios
## Core Architecture
### Application Initialization
1. Creates Vue app instance
2. Configures router and store
3. Initializes Element Plus components
4. Checks for existing auth token
5. Mounts to DOM element (#app)
### Key Files
- `main.ts`: Application entry point
- `App.vue`: Root component
- `router/index.ts`: Route configuration
- `store/index.ts`: State management setup
## Module Structure
- `api/`: API service layer
- `axios/`: HTTP client configuration
- `router/`: Navigation logic
- `store/`: Global state management
- `utils/`: Helper functions
- `views/`: Page components

48
docs/web/api.md Normal file
View File

@ -0,0 +1,48 @@
# API Service Documentation
## Architecture
- **HTTP Client**: Axios with custom wrapper
- **Modular Structure**: Feature-based organization
- **Type Safety**: TypeScript interfaces
## Auth Module
### Methods
- `login(data)`: User authentication
- Parameters: `{username, password}`
- Returns: `{access_token}`
- Content-Type: `application/json`
- `register(formData)`: User registration
- Parameters: FormData
- Returns: `{user_id}`
- Content-Type: `multipart/form-data`
- `getPublicKey()`: Gets RSA public key
- Returns: Public key string
- Error Handling: Custom error messages
- `refreshToken()`: Refreshes access token
- Returns: New `{access_token}`
## Request Wrapper
Located in `axios/service.ts`:
- Base URL configuration
- Request/response interceptors
- Error handling
- Type definitions
## Type Definitions
Each module has corresponding type definitions:
```typescript
// Example from auth/types.ts
interface LoginParams {
username: string
password: string
}
```
## Error Handling
- Custom error messages
- Type-safe error responses
- Console logging for debugging

37
docs/web/router.md Normal file
View File

@ -0,0 +1,37 @@
# Router Documentation
## Route Configuration
### Public Routes
- `/login`: Login page
- `/register`: Registration page
### Authenticated Routes (Nested under Layout)
- `/dashboard`: Main dashboard
- `/user`: User management
- `/account`: Account settings
- `/model`: Model management
## Route Meta Fields
- `requiresAuth`: Boolean indicating if authentication is required
## Route Guards
### Authentication Check
1. Checks for `access_token` in localStorage
2. Redirects to `/login` if:
- Route requires auth (`meta.requiresAuth`)
- No valid token found
## Technical Details
- **Mode**: HTML5 History Mode
- **Component Loading**: Dynamic imports (code splitting)
- **Base Path**: Project root (`/`)
## Example Usage
```javascript
// Adding a new route
{
path: 'new-route',
component: () => import('../views/NewRoute.vue'),
meta: { requiresAuth: true }
}

36
docs/web/store.md Normal file
View File

@ -0,0 +1,36 @@
# State Management Documentation
## Architecture
- **Library**: Pinia (Vue 3 recommended state management)
- **Structure**: Modular stores
- **Reactivity**: Composition API style
## User Store
### State Properties
- `token`: Authentication token
- `userInfo`: User profile data
### Methods
- `setToken(token)`: Updates token in store and localStorage
- `setUserInfo(info)`: Updates user profile
- `login(params)`: Handles login flow
- Parameters: `{username, password}`
- Returns: `{access_token}`
- `getUserInfo()`: Returns current user info
### Persistence
- Token is automatically synced with localStorage
- Uses `utils/storage` helper methods
## Usage Example
```javascript
import { useUserStore } from '@/store'
const userStore = useUserStore()
// Login
await userStore.login({username: 'test', password: '123'})
// Get user info
const user = userStore.getUserInfo()

View File

@ -46,14 +46,19 @@ export const toggleAccountStatus = (id: string) =>
url: `/api/accounts/${id}/toggle-status`
})
export const createAccount = (data: { username: string }) =>
export const createAccount = (data: { username: string; email: string; password?: string }) =>
request<{
message: string
account: AccountItem
user_id: string
username: string
email: string
created_at: string
}>({
method: 'POST',
url: '/api/accounts',
data
url: '/api/accounts/',
data: {
...data,
password: data.password || 'TempPassword123!' // Default password
}
})
// Dify账号相关API

View File

@ -0,0 +1,58 @@
<template>
<el-dialog v-model="visible" title="创建账号" width="500px">
<el-form ref="form" label-width="100px">
<el-form-item label="用户名" prop="username" required>
<el-input
:model-value="formData.username"
@update:model-value="val => formData.username = val"
placeholder="4-20位字母数字或下划线"
/>
</el-form-item>
<el-form-item label="邮箱" prop="email" required>
<el-input
:model-value="formData.email"
@update:model-value="val => formData.email = val"
placeholder="请输入有效邮箱地址"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
const visible = ref(false)
const formData = reactive({
username: '',
email: ''
})
const emit = defineEmits(['success'])
const show = () => {
visible.value = true
formData.username = ''
formData.email = ''
}
const handleSubmit = async () => {
if (!/^[a-zA-Z0-9_]{4,20}$/.test(formData.username)) {
ElMessage.error('用户名必须为4-20位字母数字或下划线')
return
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
ElMessage.error('请输入有效的邮箱地址')
return
}
emit('success', { ...formData })
visible.value = false
}
defineExpose({ show })
</script>

View File

@ -0,0 +1,29 @@
<template>
<div>
<el-form ref="form" label-width="80px">
<el-form-item label="用户名" prop="username" required>
<el-input
v-model="form.username"
placeholder="4-20位字母数字或下划线"
/>
</el-form-item>
<el-form-item label="邮箱" prop="email" required>
<el-input
v-model="form.email"
placeholder="请输入有效邮箱地址"
/>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
defineProps({
form: {
type: Object,
required: true
}
})
</script>

View File

@ -91,17 +91,20 @@
</el-table-column>
</el-table>
</el-dialog>
<CreateAccountDialog ref="createDialog" @success="handleCreateSuccess" />
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import CreateAccountDialog from './components/CreateAccountDialog.vue'
import {
fetchAccounts,
toggleAccountStatus,
createAccount,
resetPassword,
getDifyAccount
getDifyAccount,
createDifyAccount
} from '@/api/account'
import { ElMessage, ElMessageBox } from 'element-plus'
@ -117,6 +120,10 @@ interface Account {
const accountList = ref<Account[]>([])
const filteredAccountList = ref<Account[]>([])
const tenantList = ref<any[]>([])
const createForm = ref({
username: '',
email: ''
})
const searchQuery = ref('')
const loading = ref(false)
const tenantDialogVisible = ref(false)
@ -155,21 +162,27 @@ const handleToggleStatus = async (account: Account) => {
}
}
const createDialog = ref()
const handleCreate = () => {
ElMessageBox.prompt('请输入新账号用户名', '创建账号', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^[a-zA-Z0-9_]{4,20}$/,
inputErrorMessage: '用户名必须为4-20位字母数字或下划线'
}).then(async ({ value }) => {
try {
await createAccount({ username: value })
ElMessage.success('账号创建成功')
await fetchAccountData()
} catch (error) {
ElMessage.error('账号创建失败')
}
})
createDialog.value?.show()
}
const handleCreateSuccess = async (formData) => {
try {
// Create Dify account only
await createDifyAccount({
...formData,
password: 'Welcome123!'
})
ElMessage.success('Dify账号创建成功')
await fetchAccountData()
} catch (error) {
console.error('创建Dify账号失败:', error)
const errorMsg = error.response?.data?.message || error.message
ElMessage.error(`创建Dify账号失败: ${errorMsg}`)
}
}
const handleResetPassword = async (account: Account) => {