Root-cause fix for mixed CRLF/LF in the repo (no .gitattributes previously). Adds '* text=auto eol=lf' and explicit binary rules so all future checkouts are LF. Renormalizes all tracked text files (no logical content change). Backup tags: image qihuaiflow:backup-lineend-20260725-121632, git backup-lineend-pre-20260725-121632.
625 lines
24 KiB
Python
625 lines
24 KiB
Python
"""
|
|
网站基础设置 API(管理员)
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from ...database import get_db, SessionLocal
|
|
from ...models import SiteSetting, EmailConfig, SmsConfig, User, AIModel
|
|
from ...auth import get_current_admin
|
|
from ...middleware.log_service import LogService
|
|
import uuid
|
|
import smtplib
|
|
import hmac
|
|
import hashlib
|
|
import base64
|
|
import urllib.parse
|
|
import time as time_module
|
|
from email.mime.text import MIMEText
|
|
from email.mime.multipart import MIMEMultipart
|
|
from datetime import datetime
|
|
from pydantic import BaseModel
|
|
from typing import Optional, Dict, Any
|
|
import logging
|
|
|
|
logger = logging.getLogger("aiflow")
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# ============================================================
|
|
# Pydantic Models
|
|
# ============================================================
|
|
|
|
class EmailConfigUpdate(BaseModel):
|
|
smtp_host: Optional[str] = None
|
|
smtp_port: Optional[int] = None
|
|
username: Optional[str] = None
|
|
password: Optional[str] = None
|
|
from_address: Optional[str] = None
|
|
use_tls: Optional[bool] = None
|
|
|
|
|
|
class SmsConfigUpdate(BaseModel):
|
|
provider: Optional[str] = None
|
|
api_key: Optional[str] = None
|
|
api_secret: Optional[str] = None
|
|
sign_name: Optional[str] = None
|
|
template_code: Optional[str] = None
|
|
# 短信宝 (SMSBao)
|
|
username: Optional[str] = None
|
|
password: Optional[str] = None
|
|
goods_id: Optional[str] = None
|
|
|
|
|
|
# ============================================================
|
|
# 网站设置
|
|
# ============================================================
|
|
|
|
@router.get("/site")
|
|
async def get_site_settings(
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""获取网站设置(返回所有 key-value 对)"""
|
|
settings = db.query(SiteSetting).all()
|
|
result = {}
|
|
for s in settings:
|
|
result[s.key] = s.value
|
|
return {"code": 0, "data": result, "message": "ok"}
|
|
|
|
|
|
@router.put("/site")
|
|
async def update_site_settings(
|
|
settings: dict,
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""批量更新网站设置"""
|
|
for key, value in settings.items():
|
|
existing = db.query(SiteSetting).filter(SiteSetting.key == key).first()
|
|
if existing:
|
|
existing.value = str(value) if value is not None else None
|
|
existing.updated_at = datetime.now()
|
|
else:
|
|
new_setting = SiteSetting(
|
|
id=str(uuid.uuid4()),
|
|
key=key,
|
|
value=str(value) if value is not None else None,
|
|
)
|
|
db.add(new_setting)
|
|
|
|
db.commit()
|
|
|
|
LogService.log_audit(
|
|
admin_id=admin_user.id,
|
|
admin_name=admin_user.username,
|
|
action="update_site_settings",
|
|
target_type="site_settings",
|
|
detail=f"更新网站设置: {list(settings.keys())}",
|
|
)
|
|
|
|
return {"message": "网站设置已更新"}
|
|
|
|
|
|
# ============================================================
|
|
# 邮件配置
|
|
# ============================================================
|
|
|
|
@router.get("/email")
|
|
async def get_email_config(
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""获取邮件配置"""
|
|
config = db.query(EmailConfig).first()
|
|
if not config:
|
|
return {"code": 0, "data": {
|
|
"smtp_host": "",
|
|
"smtp_port": 587,
|
|
"username": "",
|
|
"password": "",
|
|
"from_address": "",
|
|
"use_tls": True,
|
|
}, "message": "ok"}
|
|
|
|
return {"code": 0, "data": {
|
|
"id": config.id,
|
|
"smtp_host": config.smtp_host,
|
|
"smtp_port": config.smtp_port,
|
|
"username": config.username,
|
|
"password": config.password,
|
|
"from_address": config.from_address,
|
|
"use_tls": config.use_tls,
|
|
}, "message": "ok"}
|
|
|
|
|
|
@router.put("/email")
|
|
async def update_email_config(
|
|
req: EmailConfigUpdate,
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""更新邮件配置"""
|
|
config = db.query(EmailConfig).first()
|
|
if not config:
|
|
# 必填字段校验:不允许空值
|
|
if not req.smtp_host:
|
|
raise HTTPException(status_code=400, detail="SMTP服务器地址不能为空")
|
|
if not req.username:
|
|
raise HTTPException(status_code=400, detail="SMTP用户名不能为空")
|
|
if not req.password:
|
|
raise HTTPException(status_code=400, detail="SMTP密码不能为空")
|
|
if not req.from_address:
|
|
raise HTTPException(status_code=400, detail="发件地址不能为空")
|
|
config = EmailConfig(
|
|
id=str(uuid.uuid4()),
|
|
smtp_host=req.smtp_host or "",
|
|
smtp_port=req.smtp_port or 587,
|
|
username=req.username or "",
|
|
password=req.password or "",
|
|
from_address=req.from_address or "",
|
|
use_tls=req.use_tls if req.use_tls is not None else True,
|
|
)
|
|
db.add(config)
|
|
else:
|
|
if req.smtp_host is not None:
|
|
if not req.smtp_host.strip():
|
|
raise HTTPException(status_code=400, detail="SMTP服务器地址不能为空")
|
|
config.smtp_host = req.smtp_host
|
|
if req.smtp_port is not None:
|
|
config.smtp_port = req.smtp_port
|
|
if req.username is not None:
|
|
if not req.username.strip():
|
|
raise HTTPException(status_code=400, detail="SMTP用户名不能为空")
|
|
config.username = req.username
|
|
if req.password is not None:
|
|
if not req.password.strip():
|
|
raise HTTPException(status_code=400, detail="SMTP密码不能为空")
|
|
config.password = req.password
|
|
if req.from_address is not None:
|
|
if not req.from_address.strip():
|
|
raise HTTPException(status_code=400, detail="发件地址不能为空")
|
|
config.from_address = req.from_address
|
|
if req.use_tls is not None:
|
|
config.use_tls = req.use_tls
|
|
config.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
|
|
LogService.log_audit(
|
|
admin_id=admin_user.id,
|
|
admin_name=admin_user.username,
|
|
action="update_email_config",
|
|
target_type="email_config",
|
|
detail="更新邮件配置",
|
|
)
|
|
|
|
return {"code": 0, "data": None, "message": "邮件配置已更新"}
|
|
|
|
|
|
# ============================================================
|
|
# 短信配置
|
|
# ============================================================
|
|
|
|
@router.get("/sms")
|
|
async def get_sms_config(
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""获取短信配置"""
|
|
config = db.query(SmsConfig).first()
|
|
if not config:
|
|
return {"code": 0, "data": {
|
|
"provider": "",
|
|
"api_key": "",
|
|
"api_secret": "",
|
|
"sign_name": "",
|
|
"template_code": "",
|
|
"username": "",
|
|
"password": "",
|
|
"goods_id": "",
|
|
}, "message": "ok"}
|
|
|
|
return {"code": 0, "data": {
|
|
"id": config.id,
|
|
"provider": config.provider,
|
|
"api_key": config.api_key,
|
|
"api_secret": config.api_secret,
|
|
"sign_name": config.sign_name,
|
|
"template_code": config.template_code,
|
|
"username": config.username,
|
|
"password": config.password,
|
|
"goods_id": config.goods_id,
|
|
}, "message": "ok"}
|
|
|
|
|
|
@router.put("/sms")
|
|
async def update_sms_config(
|
|
req: SmsConfigUpdate,
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""更新短信配置"""
|
|
config = db.query(SmsConfig).first()
|
|
if not config:
|
|
config = SmsConfig(
|
|
id=str(uuid.uuid4()),
|
|
provider=req.provider or "",
|
|
api_key=req.api_key or "",
|
|
api_secret=req.api_secret or "",
|
|
sign_name=req.sign_name,
|
|
template_code=req.template_code,
|
|
username=req.username,
|
|
password=req.password,
|
|
goods_id=req.goods_id,
|
|
)
|
|
db.add(config)
|
|
else:
|
|
if req.provider is not None:
|
|
config.provider = req.provider
|
|
if req.api_key is not None:
|
|
config.api_key = req.api_key
|
|
if req.api_secret is not None:
|
|
config.api_secret = req.api_secret
|
|
if req.sign_name is not None:
|
|
config.sign_name = req.sign_name
|
|
if req.template_code is not None:
|
|
config.template_code = req.template_code
|
|
if req.username is not None:
|
|
config.username = req.username
|
|
if req.password is not None:
|
|
config.password = req.password
|
|
if req.goods_id is not None:
|
|
config.goods_id = req.goods_id
|
|
config.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
|
|
LogService.log_audit(
|
|
admin_id=admin_user.id,
|
|
admin_name=admin_user.username,
|
|
action="update_sms_config",
|
|
target_type="sms_config",
|
|
detail="更新短信配置",
|
|
)
|
|
|
|
return {"message": "短信配置已更新"}
|
|
|
|
|
|
# ============================================================
|
|
# 邮件测试
|
|
# ============================================================
|
|
|
|
@router.post("/email/test")
|
|
async def test_email_config(
|
|
test_to: str = Query(default=None, description="自定义测试收件地址"),
|
|
test_subject: str = Query(default=None, description="自定义测试邮件主题"),
|
|
test_body: str = Query(default=None, description="自定义测试邮件正文"),
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""使用当前配置发送测试邮件;可传参自定义收件人、主题和正文"""
|
|
config = db.query(EmailConfig).first()
|
|
if not config or not config.smtp_host:
|
|
raise HTTPException(status_code=400, detail="邮件配置未设置,请先保存邮件配置")
|
|
if not config.username:
|
|
raise HTTPException(status_code=400, detail="SMTP用户名未填写,请检查邮件配置")
|
|
if not config.password:
|
|
raise HTTPException(status_code=400, detail="SMTP密码未填写,请检查邮件配置")
|
|
if not config.from_address:
|
|
raise HTTPException(status_code=400, detail="发件地址未填写,请检查邮件配置")
|
|
|
|
# 优先级:传入参数 > 管理员邮箱 > 发件地址
|
|
admin = db.query(User).filter(User.id == admin_user.id).first()
|
|
recipient = test_to or (admin.email if admin and admin.email else None) or config.from_address
|
|
if not recipient:
|
|
raise HTTPException(status_code=400, detail="无法获取接收地址: 请填写收件邮箱或设置管理员邮箱")
|
|
|
|
subject = test_subject or "AIFlow 邮件配置测试"
|
|
body = test_body or f"这是一封来自 AIFlow 平台的测试邮件。\n\n发送时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\nSMTP服务器: {config.smtp_host}"
|
|
|
|
logger.info(f"测试邮件: SMTP={config.smtp_host}:{config.smtp_port}, "
|
|
f"from={config.from_address}, to={recipient}")
|
|
|
|
try:
|
|
msg = MIMEMultipart()
|
|
msg["From"] = config.from_address
|
|
msg["To"] = recipient
|
|
msg["Subject"] = subject
|
|
msg.attach(MIMEText(body, "plain"))
|
|
|
|
if config.use_tls:
|
|
server = smtplib.SMTP(config.smtp_host, config.smtp_port, timeout=10)
|
|
server.starttls()
|
|
else:
|
|
server = smtplib.SMTP(config.smtp_host, config.smtp_port, timeout=10)
|
|
|
|
server.login(config.username, config.password)
|
|
server.sendmail(config.from_address, recipient, msg.as_string())
|
|
server.quit()
|
|
|
|
LogService.log_audit(
|
|
admin_id=admin_user.id,
|
|
admin_name=admin_user.username,
|
|
action="test_email_config",
|
|
target_type="email_config",
|
|
detail=f"测试邮件发送成功 -> {recipient} via {config.smtp_host}",
|
|
)
|
|
|
|
return {"code": 0, "data": {"message": f"测试邮件已发送到 {recipient}", "recipient": recipient}, "message": "ok"}
|
|
except smtplib.SMTPAuthenticationError as e:
|
|
logger.error(f"SMTP认证失败: {e}")
|
|
raise HTTPException(status_code=400, detail=f"SMTP认证失败,请检查用户名和密码: {str(e)}")
|
|
except smtplib.SMTPConnectError as e:
|
|
logger.error(f"SMTP连接失败: {e}")
|
|
raise HTTPException(status_code=400, detail=f"无法连接到SMTP服务器 {config.smtp_host}:{config.smtp_port}: {str(e)}")
|
|
except smtplib.SMTPException as e:
|
|
logger.error(f"SMTP发送失败: {e}")
|
|
raise HTTPException(status_code=500, detail=f"邮件发送失败(SMTP): {str(e)}")
|
|
except OSError as e:
|
|
logger.error(f"邮件发送网络错误: {e}")
|
|
raise HTTPException(status_code=500, detail=f"网络连接错误: {str(e)}")
|
|
except BaseException as e:
|
|
logger.error(f"测试邮件发送失败(严重): {type(e).__name__}: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
raise HTTPException(status_code=500, detail=f"邮件发送失败: {str(e)}")
|
|
|
|
|
|
@router.post("/email/generate-content")
|
|
async def generate_email_content(
|
|
topic: str = Query(default="测试邮件"),
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""AI生成测试邮件内容:调用默认LLM生成邮件主题和正文"""
|
|
import httpx as httpx_lib
|
|
subjects = [
|
|
"【测试】AIFlow邮件服务配置验证",
|
|
"AIFlow平台 - 邮件投递测试",
|
|
"测试邮件:您的SMTP服务已就绪",
|
|
]
|
|
bodies = [
|
|
f"您好!\n\n这是一封来自 AIFlow 智能自动化平台的测试邮件,用于验证邮件服务配置是否正常。\n\n如果您收到此邮件,说明SMTP邮件服务已配置成功。\n\n此致\nAIFlow 平台团队",
|
|
f"Hi,\n\nThis is a test email from AIFlow Platform.\n\nIf you received this, the SMTP service works fine.\n\nBest regards,\nAIFlow Team",
|
|
f"【系统通知】\n邮件服务连通性测试通过。\n\n发件服务器配置正确,能够成功投递邮件。\n\n—— AIFlow",
|
|
]
|
|
|
|
# 尝试使用默认LLM生成更好的内容
|
|
try:
|
|
default_model = db.query(AIModel).filter(
|
|
AIModel.is_default == True, AIModel.is_enabled == True
|
|
).first()
|
|
if default_model and default_model.endpoint:
|
|
async with httpx_lib.AsyncClient(timeout=15) as client:
|
|
headers = {}
|
|
if default_model.api_key:
|
|
headers["Authorization"] = f"Bearer {default_model.api_key}"
|
|
resp = await client.post(
|
|
default_model.endpoint,
|
|
json={
|
|
"model": default_model.name,
|
|
"messages": [
|
|
{"role": "system", "content": "你是一个专业的技术文档撰写者。请根据主题生成一封测试邮件的主题和正文。返回JSON格式: {\"subject\": \"邮件主题\", \"body\": \"邮件正文\"}"},
|
|
{"role": "user", "content": f"主题: {topic}"},
|
|
],
|
|
"max_tokens": 400,
|
|
},
|
|
headers=headers,
|
|
)
|
|
if resp.status_code == 200:
|
|
content = resp.json().get("choices", [{}])[0].get("message", {}).get("content", "")
|
|
try:
|
|
data = json.loads(content)
|
|
if data.get("subject") and data.get("body"):
|
|
return {"subject": data["subject"], "body": data["body"], "source": "ai"}
|
|
except:
|
|
# 尝试解析非JSON格式
|
|
lines = content.strip().split("\n")
|
|
if len(lines) >= 2:
|
|
return {"subject": lines[0].replace("主题:", "").strip(), "body": "\n".join(lines[1:]).replace("正文:", "").strip(), "source": "ai_partial"}
|
|
except Exception:
|
|
pass
|
|
|
|
import random
|
|
idx = random.randint(0, len(subjects) - 1)
|
|
return {"subject": subjects[idx], "body": bodies[idx], "source": "fallback"}
|
|
|
|
|
|
# ============================================================
|
|
# 短信测试
|
|
# ============================================================
|
|
|
|
def _aliyun_sign(secret: str, params: dict, method: str = "GET") -> str:
|
|
"""阿里云签名机制 (HMAC-SHA1)"""
|
|
sorted_keys = sorted(params.keys())
|
|
qs = "&".join([
|
|
f"{urllib.parse.quote(k, safe='')}={urllib.parse.quote(str(params[k]), safe='')}"
|
|
for k in sorted_keys
|
|
])
|
|
string_to_sign = f"{method}&{urllib.parse.quote('/', safe='')}&{urllib.parse.quote(qs, safe='')}"
|
|
key = f"{secret}&"
|
|
h = hmac.new(key.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha1)
|
|
return base64.b64encode(h.digest()).decode('utf-8')
|
|
|
|
|
|
# 短信宝 (SMSBao) 辅助
|
|
def _smsbao_md5(password: str) -> str:
|
|
"""短信宝密码:登录密码的 MD5(32位,不区分大小写)"""
|
|
return hashlib.md5(password.encode("utf-8")).hexdigest()
|
|
|
|
SMSBAO_ERRORS = {
|
|
"0": "发送成功",
|
|
"30": "密码错误",
|
|
"40": "账号不存在",
|
|
"41": "余额不足",
|
|
"43": "IP地址限制",
|
|
"50": "内容含有敏感词",
|
|
"51": "手机号码不正确",
|
|
}
|
|
|
|
|
|
@router.post("/sms/test")
|
|
async def test_sms_config(
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""使用当前配置发送测试短信"""
|
|
config = db.query(SmsConfig).first()
|
|
if not config:
|
|
raise HTTPException(status_code=400, detail="短信配置未设置,请先保存短信配置")
|
|
|
|
if not config.api_key or not config.api_secret:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="未配置阿里云短信 AccessKey,请填写 API Key (AccessKey ID) 和 API Secret (AccessKey Secret)"
|
|
)
|
|
|
|
if not config.sign_name:
|
|
raise HTTPException(status_code=400, detail="未配置短信签名")
|
|
|
|
# 获取接收测试短信的手机号(管理员手机号)
|
|
admin = db.query(User).filter(User.id == admin_user.id).first()
|
|
phone = admin.phone if admin and admin.phone else None
|
|
if not phone:
|
|
raise HTTPException(status_code=400, detail="管理员未设置手机号,无法发送测试短信")
|
|
|
|
logger.info(f"测试短信: provider={config.provider}, sign={config.sign_name}, phone={phone}")
|
|
|
|
# 短信宝 (SMSBao)
|
|
if config.provider == "短信宝":
|
|
if not config.username or not config.password:
|
|
raise HTTPException(status_code=400, detail="未配置短信宝用户名或密码,请先在配置中填写")
|
|
sign = config.sign_name or ""
|
|
if "【" not in sign:
|
|
sign = f"【{sign}】" if sign else "【七虎AI】"
|
|
content = f"{sign}测试短信:您的验证码是123456,若非本人操作请忽略。"
|
|
pwd_md5 = _smsbao_md5(config.password)
|
|
params = {"u": config.username, "p": pwd_md5, "m": phone, "c": content}
|
|
if config.goods_id:
|
|
params["g"] = config.goods_id
|
|
import requests as http_requests
|
|
try:
|
|
resp = http_requests.get(
|
|
"https://api.smsbao.com/sms", params=params, timeout=10,
|
|
proxies={"http": None, "https": None},
|
|
)
|
|
code = resp.text.strip().split("\n")[0]
|
|
except Exception as e:
|
|
logger.error(f"短信宝请求失败: {e}")
|
|
raise HTTPException(status_code=500, detail=f"短信宝请求失败: {str(e)}")
|
|
if code == "0":
|
|
LogService.log_audit(
|
|
admin_id=admin_user.id, admin_name=admin_user.username,
|
|
action="test_sms_config", target_type="sms_config",
|
|
detail=f"短信宝测试短信发送成功 -> {phone}",
|
|
)
|
|
return {"message": f"测试短信已发送到 {phone}", "phone": phone}
|
|
msg = SMSBAO_ERRORS.get(code, f"未知错误码 {code}")
|
|
raise HTTPException(status_code=400, detail=f"短信宝发送失败: {msg} (code={code})")
|
|
|
|
# 只有阿里云短信支持测试
|
|
if "阿里云" not in (config.provider or ""):
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"当前短信服务商 '{config.provider}' 暂不支持在线测试,请直接在生产环境测试"
|
|
)
|
|
|
|
try:
|
|
# 阿里云短信参数
|
|
params = {
|
|
"AccessKeyId": config.api_key,
|
|
"Action": "SendSms",
|
|
"Format": "JSON",
|
|
"PhoneNumbers": phone,
|
|
"RegionId": "cn-hangzhou",
|
|
"SignName": config.sign_name,
|
|
"SignatureMethod": "HMAC-SHA1",
|
|
"SignatureNonce": str(uuid.uuid4()),
|
|
"SignatureVersion": "1.0",
|
|
"TemplateCode": config.template_code or "SMS_00000001",
|
|
"TemplateParam": '{"code":"123456"}',
|
|
"Timestamp": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
"Version": "2017-05-25",
|
|
}
|
|
params["Signature"] = _aliyun_sign(config.api_secret, params)
|
|
|
|
import requests as http_requests
|
|
resp = http_requests.post(
|
|
"https://dysmsapi.aliyuncs.com/",
|
|
data=params,
|
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
timeout=10,
|
|
)
|
|
result = resp.json()
|
|
|
|
logger.info(f"阿里云短信API响应: {result}")
|
|
|
|
if result.get("Code") == "OK":
|
|
LogService.log_audit(
|
|
admin_id=admin_user.id,
|
|
admin_name=admin_user.username,
|
|
action="test_sms_config",
|
|
target_type="sms_config",
|
|
detail=f"测试短信发送成功 -> {phone}",
|
|
)
|
|
return {"message": f"测试短信已发送到 {phone}", "phone": phone, "biz_id": result.get("BizId")}
|
|
else:
|
|
error_msg = result.get("Message", result.get("Code", "未知错误"))
|
|
raise HTTPException(status_code=400, detail=f"阿里云短信发送失败: {error_msg}")
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"测试短信发送失败: {e}")
|
|
raise HTTPException(status_code=500, detail=f"短信发送失败: {str(e)}")
|
|
|
|
|
|
# ============================================================
|
|
# 短信宝余额查询
|
|
# ============================================================
|
|
|
|
@router.post("/sms/balance")
|
|
async def query_sms_balance(
|
|
admin_user=Depends(get_current_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""查询短信宝账户余额(发送条数, 剩余条数)"""
|
|
config = db.query(SmsConfig).first()
|
|
if not config or config.provider != "短信宝":
|
|
raise HTTPException(status_code=400, detail="仅短信宝支持余额查询,请先选择短信宝并保存账号")
|
|
if not config.username or not config.password:
|
|
raise HTTPException(status_code=400, detail="未配置短信宝用户名或密码")
|
|
|
|
import requests as http_requests
|
|
pwd_md5 = _smsbao_md5(config.password)
|
|
try:
|
|
resp = http_requests.get(
|
|
"https://www.smsbao.com/query",
|
|
params={"u": config.username, "p": pwd_md5},
|
|
timeout=10,
|
|
proxies={"http": None, "https": None},
|
|
)
|
|
lines = resp.text.strip().split("\n")
|
|
except Exception as e:
|
|
logger.error(f"短信宝余额查询请求失败: {e}")
|
|
raise HTTPException(status_code=500, detail=f"短信宝余额查询失败: {str(e)}")
|
|
|
|
code = lines[0].strip() if lines else ""
|
|
if code != "0":
|
|
msg = SMSBAO_ERRORS.get(code, f"未知错误码 {code}")
|
|
raise HTTPException(status_code=400, detail=f"查询失败: {msg}")
|
|
|
|
sent = remain = 0
|
|
if len(lines) >= 2:
|
|
parts = lines[1].split(",")
|
|
try:
|
|
sent = int(parts[0])
|
|
except (ValueError, IndexError):
|
|
sent = 0
|
|
try:
|
|
remain = int(parts[1])
|
|
except (ValueError, IndexError):
|
|
remain = 0
|
|
return {"code": 0, "data": {"sent": sent, "remain": remain}, "message": "ok"}
|