mirror of
https://github.com/wangdage12/Snap.Server.git
synced 2026-02-18 02:42:12 +08:00
Compare commits
2 Commits
cd31c409d7
...
199308fab1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
199308fab1 | ||
|
|
c1339a9858 |
112
README.md
112
README.md
@@ -1,2 +1,114 @@
|
|||||||
# Snap.Server
|
# Snap.Server
|
||||||
Snap.Hutao新后端API
|
Snap.Hutao新后端API
|
||||||
|
|
||||||
|
## 部署方法
|
||||||
|
|
||||||
|
### 在服务器生成RSA密钥
|
||||||
|
|
||||||
|
执行以下代码在根目录生成密钥:
|
||||||
|
```python
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
|
||||||
|
# 生成 2048 位 RSA 密钥对
|
||||||
|
key = RSA.generate(2048)
|
||||||
|
|
||||||
|
private_key = key.export_key()
|
||||||
|
public_key = key.publickey().export_key()
|
||||||
|
|
||||||
|
with open("private.pem", "wb") as f:
|
||||||
|
f.write(private_key)
|
||||||
|
|
||||||
|
with open("public.pem", "wb") as f:
|
||||||
|
f.write(public_key)
|
||||||
|
|
||||||
|
print("Keys generated.")
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**确保客户端的公钥和生成的相同,否则将无法使用账户功能**
|
||||||
|
|
||||||
|
### 创建配置文件
|
||||||
|
|
||||||
|
创建`config.json`文件,示例内容如下:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"SECRET_KEY": "jwt_secret_key",
|
||||||
|
"MONGO_URI": "mongodb+srv://wdgwdg889_db_user:xxxxxx@cluster0.eplrcvl.mongodb.net/?appName=Cluster0",
|
||||||
|
"TIMEZONE": "Asia/Shanghai",
|
||||||
|
"ISTEST_MODE": false,
|
||||||
|
"SERVER": {
|
||||||
|
"HOST": "0.0.0.0",
|
||||||
|
"PORT": 5222,
|
||||||
|
"DEBUG": false
|
||||||
|
},
|
||||||
|
"JWT": {
|
||||||
|
"ALGORITHM": "HS256",
|
||||||
|
"EXPIRATION_HOURS": 24
|
||||||
|
},
|
||||||
|
"EMAIL": {
|
||||||
|
"GMAIL_USER": "wdgwdg889@gmail.com",
|
||||||
|
"APP_PASSWORD": ""
|
||||||
|
},
|
||||||
|
"RSA": {
|
||||||
|
"PRIVATE_KEY_FILE": "private.pem",
|
||||||
|
"PUBLIC_KEY_FILE": "public.pem"
|
||||||
|
},
|
||||||
|
"LOGGING": {
|
||||||
|
"LEVEL": "DEBUG",
|
||||||
|
"FORMAT": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| SECRET_KEY | 用于JWT签名的密钥,请设置为复杂字符串 |
|
||||||
|
| MONGO_URI | MongoDB连接字符串 |
|
||||||
|
| TIMEZONE | 服务器时区 |
|
||||||
|
| ISTEST_MODE | 是否启用测试模式,测试模式下部分功能将返回默认值,不连接数据库 |
|
||||||
|
| SERVER.HOST | 服务器监听地址 |
|
||||||
|
| SERVER.PORT | 服务器监听端口 |
|
||||||
|
| SERVER.DEBUG | 是否启用Flask的调试模式 |
|
||||||
|
| JWT.ALGORITHM | JWT签名算法 |
|
||||||
|
| JWT.EXPIRATION_HOURS | JWT过期时间(小时) |
|
||||||
|
| EMAIL.GMAIL_USER | 用于发送验证邮件的Gmail账号 |
|
||||||
|
| EMAIL.APP_PASSWORD | Gmail应用专用密码 |
|
||||||
|
| RSA.PRIVATE_KEY_FILE | RSA私钥文件路径 |
|
||||||
|
| RSA.PUBLIC_KEY_FILE | RSA公钥文件路径 |
|
||||||
|
| LOGGING.LEVEL | 日志记录级别,生产环境建议设置为INFO |
|
||||||
|
| LOGGING.FORMAT | 日志记录格式 |
|
||||||
|
|
||||||
|
### 开发环境启动方法
|
||||||
|
|
||||||
|
确保已安装依赖:
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
运行Flask应用:
|
||||||
|
```
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生产环境启动方法
|
||||||
|
|
||||||
|
建议使用Gunicorn部署:
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt && python -m gunicorn run:app --bind 0.0.0.0:5222 --workers 4 --threads 2 --access-logfile - --error-logfile -
|
||||||
|
```
|
||||||
|
|
||||||
|
请根据服务器性能调整`--workers`和`--threads`参数。
|
||||||
|
|
||||||
|
### API文档
|
||||||
|
|
||||||
|
API文档可以在该地址访问:
|
||||||
|
|
||||||
|
https://rdgm3wrj7r.apifox.cn/
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
|
||||||
|
在轻量使用的场景下,可以直接使用MongoDB Atlas的免费套餐,但在高并发场景下,建议使用自建MongoDB服务器以获得更好的性能和稳定性。
|
||||||
|
|
||||||
|
新MongoDB数据库会在写入数据时自动创建,无需手动创建数据库和集合。
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ PyJWT==2.10.1
|
|||||||
pymongo==4.15.5
|
pymongo==4.15.5
|
||||||
Werkzeug==3.1.4
|
Werkzeug==3.1.4
|
||||||
sentry-sdk[flask]
|
sentry-sdk[flask]
|
||||||
|
gunicorn
|
||||||
@@ -4,7 +4,7 @@ from services.auth_service import (
|
|||||||
decrypt_data, send_verification_email, verify_user_credentials,
|
decrypt_data, send_verification_email, verify_user_credentials,
|
||||||
create_user_account, get_user_by_id
|
create_user_account, get_user_by_id
|
||||||
)
|
)
|
||||||
from app.extensions import generate_code, logger
|
from app.extensions import generate_code, logger , config_loader
|
||||||
|
|
||||||
auth_bp = Blueprint("auth", __name__)
|
auth_bp = Blueprint("auth", __name__)
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ def passport_register():
|
|||||||
"data": {
|
"data": {
|
||||||
"AccessToken": access_token,
|
"AccessToken": access_token,
|
||||||
"RefreshToken": access_token,
|
"RefreshToken": access_token,
|
||||||
"ExpiresIn": 3600
|
"ExpiresIn": config_loader.JWT_EXPIRATION_HOURS * 3600
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ def passport_login():
|
|||||||
"data": {
|
"data": {
|
||||||
"AccessToken": access_token,
|
"AccessToken": access_token,
|
||||||
"RefreshToken": access_token,
|
"RefreshToken": access_token,
|
||||||
"ExpiresIn": 3600
|
"ExpiresIn": config_loader.JWT_EXPIRATION_HOURS * 3600
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ def passport_refresh_token():
|
|||||||
"data": {
|
"data": {
|
||||||
"AccessToken": access_token,
|
"AccessToken": access_token,
|
||||||
"RefreshToken": access_token,
|
"RefreshToken": access_token,
|
||||||
"ExpiresIn": 3600
|
"ExpiresIn": config_loader.JWT_EXPIRATION_HOURS * 3600
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from flask import Blueprint, request, jsonify
|
|||||||
from app.utils.jwt_utils import verify_token, create_token
|
from app.utils.jwt_utils import verify_token, create_token
|
||||||
from services.auth_service import verify_user_credentials, get_users_with_search
|
from services.auth_service import verify_user_credentials, get_users_with_search
|
||||||
from app.decorators import require_maintainer_permission
|
from app.decorators import require_maintainer_permission
|
||||||
from app.extensions import generate_numeric_id, client, logger
|
from app.extensions import generate_numeric_id, client, logger, config_loader
|
||||||
|
|
||||||
web_api_bp = Blueprint("web_api", __name__)
|
web_api_bp = Blueprint("web_api", __name__)
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ def web_api_login():
|
|||||||
"message": "success",
|
"message": "success",
|
||||||
"data": {
|
"data": {
|
||||||
"access_token": access_token,
|
"access_token": access_token,
|
||||||
"expires_in": 3600
|
"expires_in": config_loader.JWT_EXPIRATION_HOURS * 3600
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
21
run.py
21
run.py
@@ -1,6 +1,21 @@
|
|||||||
from app import create_app
|
from app.init import create_app
|
||||||
|
from app.config_loader import config_loader
|
||||||
|
import sentry_sdk
|
||||||
|
|
||||||
|
sentry_sdk.init(
|
||||||
|
dsn="https://d1cad1d2b442cf8431df3ee4bab925e0@o4507525750521856.ingest.us.sentry.io/4510623668830208",
|
||||||
|
# Add data like request headers and IP for users,
|
||||||
|
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
|
||||||
|
send_default_pii=True,
|
||||||
|
traces_sample_rate=1.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建应用实例
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run(
|
||||||
|
host=config_loader.SERVER_HOST,
|
||||||
|
port=config_loader.SERVER_PORT,
|
||||||
|
debug=config_loader.SERVER_DEBUG
|
||||||
|
)
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
import datetime
|
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
from app.extensions import client, logger
|
from app.extensions import client, logger
|
||||||
from app.config import Config
|
from app.config import Config
|
||||||
|
from Crypto.Cipher import PKCS1_OAEP
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from app.config_loader import config_loader
|
||||||
|
from datetime import timezone
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
import datetime
|
||||||
|
import SendEmailTool
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
|
||||||
def decrypt_data(encrypted_data):
|
def decrypt_data(encrypted_data):
|
||||||
"""使用RSA私钥解密数据"""
|
"""使用RSA私钥解密数据"""
|
||||||
try:
|
try:
|
||||||
from Crypto.Cipher import PKCS1_OAEP
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
import base64
|
|
||||||
from app.config_loader import config_loader
|
|
||||||
|
|
||||||
private_key_file = config_loader.RSA_PRIVATE_KEY_FILE
|
private_key_file = config_loader.RSA_PRIVATE_KEY_FILE
|
||||||
private_key = RSA.import_key(open(private_key_file).read())
|
with open(private_key_file, 'r') as f:
|
||||||
|
private_key = RSA.import_key(f.read())
|
||||||
cipher = PKCS1_OAEP.new(private_key)
|
cipher = PKCS1_OAEP.new(private_key)
|
||||||
decrypted_data = cipher.decrypt(base64.b64decode(encrypted_data))
|
decrypted_data = cipher.decrypt(base64.b64decode(encrypted_data))
|
||||||
return decrypted_data.decode()
|
return decrypted_data.decode()
|
||||||
@@ -26,8 +29,6 @@ def decrypt_data(encrypted_data):
|
|||||||
def send_verification_email(email, code):
|
def send_verification_email(email, code):
|
||||||
"""发送验证码邮件"""
|
"""发送验证码邮件"""
|
||||||
try:
|
try:
|
||||||
import SendEmailTool
|
|
||||||
|
|
||||||
subject = "Snap Hutao 验证码"
|
subject = "Snap Hutao 验证码"
|
||||||
body = f"您的验证码是: {code}"
|
body = f"您的验证码是: {code}"
|
||||||
|
|
||||||
@@ -74,8 +75,9 @@ def create_user_account(email, password):
|
|||||||
"CreatedAt": datetime.datetime.utcnow(),
|
"CreatedAt": datetime.datetime.utcnow(),
|
||||||
"IsLicensedDeveloper": False,
|
"IsLicensedDeveloper": False,
|
||||||
"IsMaintainer": False,
|
"IsMaintainer": False,
|
||||||
"GachaLogExpireAt": "2026-01-01T00:00:00Z",
|
# 现在默认用户的上传权限不过期
|
||||||
"CdnExpireAt": "2026-01-01T00:00:00Z"
|
"GachaLogExpireAt": "2099-01-01T00:00:00Z",
|
||||||
|
"CdnExpireAt": "2099-01-01T00:00:00Z"
|
||||||
}
|
}
|
||||||
|
|
||||||
result = client.ht_server.users.insert_one(new_user)
|
result = client.ht_server.users.insert_one(new_user)
|
||||||
@@ -91,7 +93,8 @@ def get_user_by_id(user_id):
|
|||||||
if user:
|
if user:
|
||||||
user['_id'] = str(user['_id'])
|
user['_id'] = str(user['_id'])
|
||||||
return user
|
return user
|
||||||
except:
|
except Exception as e:
|
||||||
|
logger.error(f"Error retrieving user by ID: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -119,18 +122,6 @@ def get_users_with_search(query_text=""):
|
|||||||
or_conditions.append({
|
or_conditions.append({
|
||||||
"_id": ObjectId(query_text)
|
"_id": ObjectId(query_text)
|
||||||
})
|
})
|
||||||
else:
|
|
||||||
# 允许部分 ObjectId 搜索(转字符串后匹配)
|
|
||||||
or_conditions.append({
|
|
||||||
"_id": {
|
|
||||||
"$in": [
|
|
||||||
u["_id"] for u in client.ht_server.users.find(
|
|
||||||
{},
|
|
||||||
{"_id": 1}
|
|
||||||
) if query_text.lower() in str(u["_id"]).lower()
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
query = {"$or": or_conditions}
|
query = {"$or": or_conditions}
|
||||||
|
|
||||||
@@ -145,9 +136,6 @@ def get_users_with_search(query_text=""):
|
|||||||
users = list(users_map.values())
|
users = list(users_map.values())
|
||||||
|
|
||||||
# 数据格式化
|
# 数据格式化
|
||||||
from datetime import timezone
|
|
||||||
from zoneinfo import ZoneInfo
|
|
||||||
|
|
||||||
CST = ZoneInfo("Asia/Shanghai")
|
CST = ZoneInfo("Asia/Shanghai")
|
||||||
|
|
||||||
for u in users:
|
for u in users:
|
||||||
|
|||||||
Reference in New Issue
Block a user