diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index 4dba044..c962983 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -123,3 +123,45 @@ def change_password( db.commit() return {"message": "密码修改成功"} + + +# ============ 短信验证码接口 ============ + +@router.post("/send-verification-code") +def send_verification_code( + phone: str = Body(..., min_length=11, max_length=11), + purpose: str = Body("register") # register | login | reset_password +): + """发送短信验证码""" + from app.services.sms import send_verification_code as send_sms + + # 验证手机号格式 + if not phone.startswith("1") or len(phone) != 11: + return {"success": False, "message": "手机号格式不正确"} + + result = send_sms(phone) + + if result["success"]: + return { + "success": True, + "message": f"验证码已发送到 {phone[:3]}****{phone[7:]}", + "expire": result.get("expire", 300) + } + else: + return result + + +@router.post("/verify-code") +def verify_code( + phone: str = Body(...), + code: str = Body(..., min_length=6, max_length=6) +): + """验证短信验证码(仅验证,不执行后续操作)""" + from app.services.sms import verify_code as check_code + + is_valid = check_code(phone, code) + + if is_valid: + return {"success": True, "message": "验证成功"} + else: + return {"success": False, "message": "验证码错误或已过期"} diff --git a/backend/app/services/sms.py b/backend/app/services/sms.py new file mode 100644 index 0000000..e8a0a7b --- /dev/null +++ b/backend/app/services/sms.py @@ -0,0 +1,106 @@ +# 阿里云短信服务 +import os +import random +import string +import time +from datetime import datetime, timedelta +from typing import Optional + +# 阿里云短信配置 +SMS_CONFIG = { + "access_key_id": os.getenv("SMS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"), + "access_key_secret": os.getenv("SMS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"), + "sign_name": "阿里云", + "template_code": "100001", +} + +# 验证码缓存(生产环境建议用Redis) +# 格式: { phone: { code: "123456", expire: 1234567890 } } +VERIFICATION_CODES = {} + + +def generate_code(length: int = 6) -> str: + """生成6位数字验证码""" + return ''.join(random.choices(string.digits, k=length)) + + +def send_verification_code(phone: str) -> dict: + """发送短信验证码""" + from alibabacloud_dysmsapi20170525 import models + from alibabacloud_dysmsapi20170525.client import Client + from alibabacloud_tea_openapi import models as open_models + + try: + # 生成验证码 + code = generate_code(6) + + # 配置客户端 + config = open_models.Config( + access_key_id=SMS_CONFIG["access_key_id"], + access_key_secret=SMS_CONFIG["access_key_secret"], + ) + config.endpoint = "dysmsapi.aliyuncs.com" + config.region_id = "cn-hangzhou" + + client = Client(config) + + # 构造请求 + request = models.SendSmsRequest( + phone_numbers=phone, + sign_name=SMS_CONFIG["sign_name"], + template_code=SMS_CONFIG["template_code"], + template_param=f'{{"code":"{code}"}}' + ) + + # 发送 + response = client.send_sms(request) + + # 检查结果 + if response.body.code == "OK": + # 保存验证码 + VERIFICATION_CODES[phone] = { + "code": code, + "expire": int(time.time()) + 300 # 5分钟有效 + } + return { + "success": True, + "message": "验证码已发送", + "expire": 300 + } + else: + return { + "success": False, + "message": f"发送失败: {response.body.message}" + } + + except Exception as e: + return { + "success": False, + "message": f"发送失败: {str(e)}" + } + + +def verify_code(phone: str, code: str) -> bool: + """验证验证码""" + if phone not in VERIFICATION_CODES: + return False + + stored = VERIFICATION_CODES[phone] + + # 检查是否过期 + if int(time.time()) > stored["expire"]: + del VERIFICATION_CODES[phone] + return False + + # 验证码匹配 + if stored["code"] == code: + # 验证成功,删除验证码 + del VERIFICATION_CODES[phone] + return True + + return False + + +def check_code_exists(phone: str) -> bool: + """检查是否已发送过验证码""" + return phone in VERIFICATION_CODES diff --git a/config/VERSION b/config/VERSION index 1495ebe..dec8cfe 100644 --- a/config/VERSION +++ b/config/VERSION @@ -2,7 +2,7 @@ # Version Configuration for Zodiac Collection Management System # 当前版本号 (语义化版本:主版本。次版本.修订版) -VERSION=1.1.18 +VERSION=1.1.19 # 版本代号 (可选) VERSION_CODENAME="新生" diff --git a/frontend/index.html b/frontend/index.html index 9218288..edb3da0 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.1.17 + 甲辰收藏 v1.1.18 diff --git a/frontend/src/pages/Detail.jsx b/frontend/src/pages/Detail.jsx index 828c026..31c1a8d 100644 --- a/frontend/src/pages/Detail.jsx +++ b/frontend/src/pages/Detail.jsx @@ -65,8 +65,14 @@ export default function Detail() { } const goBack = () => { - if (window.refreshList) window.refreshList() - window.location.hash = '#/list' + // 尝试返回到上次筛选的列表页面 + const lastUrl = sessionStorage.getItem('lastListUrl') + if (lastUrl) { + sessionStorage.removeItem('lastListUrl') + window.location.hash = '#' + lastUrl + } else { + window.location.hash = '#/list' + } } const doDelete = async () => { diff --git a/frontend/src/pages/List.jsx b/frontend/src/pages/List.jsx index 936f0b4..e6a2bff 100644 --- a/frontend/src/pages/List.jsx +++ b/frontend/src/pages/List.jsx @@ -132,6 +132,8 @@ export default function List() { }, []) const goDetail = (id) => { + // 保存当前列表的URL(包含筛选条件),用于返回时恢复 + sessionStorage.setItem('lastListUrl', window.location.hash.substring(1)) window.location.hash = '#/detail?id=' + id }