# -*- coding:utf-8 -*- """ @Author : xuxingchen @Contact : xuxingchen@sinochem.com @Desc : 登陆界面控制逻辑 """ import hashlib import os.path import random import io import time from typing import Optional from starlette.responses import StreamingResponse from pydantic import BaseModel from fastapi import APIRouter, Query, Request from PIL import Image, ImageDraw, ImageFont from utils.database import get_table_handler from utils import logger from utils.misc import generate_captcha_text from models.sessions import SessionsTable router = APIRouter() class LoginItem(BaseModel): username: str password: str session_id: str captcha: str class LoginResp(BaseModel): status: bool message: str token: Optional[str] = None def generate_captcha_image(text, size=(120, 40), font_path='./data/mvboli.ttf', font_size=24): """生成验证码图片""" image = Image.new('RGB', size, (73, 109, 137)) # 设置背景色 d = ImageDraw.Draw(image) assert os.path.exists(font_path), "字体文件不存在" font = ImageFont.truetype(font_path, font_size) # d.text((5, 5), text, font=font, fill=(255, 255, 0)) # 设置字体颜色和位置 font_box = font.getbbox(text) text_width, text_height = font_box[2] - font_box[0], font_box[3] - font_box[1] text_x = (size[0] - text_width) // 2 text_y = (size[1] - text_height) // 2 d.text((text_x, text_y - 8), text, font=font, fill=(255, 255, 0)) # 添加噪点 for _ in range(100): # 可以根据需要调整噪点数量 x = random.randint(0, size[0] - 1) y = random.randint(0, size[1] - 1) image.putpixel((x, y), (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))) # 添加线条 for _ in range(3): # 可以根据需要调整线条数量 x1, y1 = random.randint(0, size[0] - 1), random.randint(0, size[1] - 1) x2, y2 = random.randint(0, size[0] - 1), random.randint(0, size[1] - 1) d.line([(x1, y1), (x2, y2)], fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)), width=2) # 将图片保存为字节流 bytes_io = io.BytesIO() image.save(bytes_io, format='PNG') # image.save("./data/tmp.png", format='PNG') bytes_io.seek(0) return bytes_io def hash_password(password): """对密码进行 SHA-256 哈希加密""" password_bytes = password.encode('utf-8') sha256_hash = hashlib.sha256() sha256_hash.update(password_bytes) return sha256_hash.hexdigest() def verify_password(password, hashed_password): """比对密码和哈希值""" password_hash = hash_password(password) return password_hash.lower() == hashed_password.lower() def authenticate_token(token: str): th = get_table_handler() timeout_timestamp = str(int(time.time() * 1000 - 4 * 60 * 60 * 1000)) return SessionsTable.check_token(th, timeout_timestamp, token) @router.get("/generateCaptcha", summary="生成验证码") async def generate_captcha_endpoint(request: Request, session_id: str = Query(None, description="Session ID")): """生成验证码图片并返回""" if session_id: captcha_text = generate_captcha_text() logger.Logger.debug(f"{request.url.path} 验证码生成:{captcha_text}") # session_id 入库 th = get_table_handler() SessionsTable.insert(th, session_id, captcha_text) captcha_image = generate_captcha_image(captcha_text) # 设置HTTP响应的头部,指定内容类型为PNG图片 headers = {"Content-Type": "image/png"} return StreamingResponse(captcha_image, headers=headers) else: logger.Logger.error(f"{request.url.path} 请求参数缺少 session_id - {request.client.host}") return {"status": False, "message": "请求参数缺少 session_id"} @router.post("/login", response_model=LoginResp, response_model_exclude_none=True, summary="登录请求") async def login(item: LoginItem): """简单的账户登录逻辑""" status = False token = None th = get_table_handler() # 验证码校验 right_captcha = SessionsTable.get_captcha(th, item.session_id) if right_captcha is not None: if item.captcha.lower() == right_captcha.lower(): # 账户密码校验 if item.username == "admin": if verify_password(item.password, "B12AD23C7E230E2CA365508DD16635DD3D7214BCD9BEA27457A356FD5C15F8BF"): status = True message = "校验通过" token = generate_captcha_text(16) SessionsTable.update(th, item.session_id, item.username, token) else: message = "密码错误" else: message = "账户不存在" else: message = "验证码错误" else: message = "无效 session_id" resp = LoginResp(status=status, message=message, token=token) logger.Logger.debug("/login " + str(resp.__dict__)) return resp