Refactor password handling and model typing

Replaced custom password generation and verification logic with a new pkg/password.py module using Argon2 for secure hashing. Updated model field types to use PEP 604 union syntax (e.g., str | None) and improved type annotations. Refactored admin and session routes to use new password utilities and direct model methods for CRUD operations. Removed legacy tool-based password functions and cleaned up .idea project files.
This commit is contained in:
2025-10-03 12:01:01 +08:00
parent 1491fc0fbd
commit 815e709339
23 changed files with 191 additions and 293 deletions

View File

@@ -1,6 +1,6 @@
# model/base.py
from datetime import datetime, timezone
from typing import Optional, Type, TypeVar, Union, Literal, List
from typing import Type, TypeVar, Union, Literal, List
from sqlalchemy import DateTime, BinaryExpression, ClauseElement
from sqlalchemy.orm import selectinload
@@ -27,7 +27,7 @@ class TableBase(AsyncAttrs, SQLModel):
sa_column_kwargs={"default": utcnow, "onupdate": utcnow},
default_factory=utcnow
)
deleted_at: Optional[datetime] = Field(
deleted_at: datetime | None = Field(
default=None,
description="删除时间",
sa_column={"nullable": True}
@@ -148,4 +148,4 @@ class TableBase(AsyncAttrs, SQLModel):
# 需要“自增 id 主键”的模型才混入它Setting 不混入
class IdMixin(SQLModel):
id: Optional[int] = Field(default=None, primary_key=True, description="主键ID")
id: int | None = Field(default=None, primary_key=True, description="主键ID")

View File

@@ -42,12 +42,25 @@ class Database:
async with _async_session_factory() as session:
yield session
@staticmethod
@asynccontextmanager
async def session_context() -> AsyncGenerator[AsyncSession, None]:
"""
提供异步上下文管理器用于直接获取数据库会话
使用示例:
async with Database.session_context() as session:
# 执行数据库操作
pass
"""
async with _async_session_factory() as session:
yield session
async def init_db(self, url: str = ASYNC_DATABASE_URL):
"""创建数据库结构"""
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
# For internal use, create a temporary context manager
get_session_cm = asynccontextmanager(self.get_session)
async with get_session_cm() as session:
async with self.session_context() as session:
await migration(session) # 执行迁移脚本

View File

@@ -1,5 +1,4 @@
from pydantic import BaseModel
from typing import Optional
class Item(BaseModel):
id: int
@@ -9,8 +8,7 @@ class Item(BaseModel):
icon: str
status: str
phone: int
lost_description: Optional[str]
find_ip: Optional[str]
lost_description: str | None
find_ip: str | None
create_time: str
lost_time: Optional[str]
lost_time: str | None

View File

@@ -1,7 +1,7 @@
from loguru import logger
from sqlmodel import select
from .setting import Setting
import tool
from pkg.password import Password
default_settings: list[Setting] = [
Setting(type='string', name='version', value='1.0.0'),
@@ -18,11 +18,11 @@ async def migration(session):
return
# 生成初始密码与密钥
admin_password = tool.generate_password()
admin_password = Password.generate()
logger.warning(f"密码(请牢记,后续不再显示): {admin_password}")
settings.append(Setting(type='string', name='password', value=tool.hash_password(admin_password)))
settings.append(Setting(type='string', name='SECRET_KEY', value=tool.generate_password(64)))
settings.append(Setting(type='string', name='password', value=Password.hash(admin_password)))
settings.append(Setting(type='string', name='SECRET_KEY', value=Password.generate(64)))
# 读取库里已存在的 name避免主键冲突
names = [s.name for s in settings]

View File

@@ -1,45 +1,29 @@
# my_project/models/download.py
from typing import Literal, Optional, TYPE_CHECKING
from sqlmodel import Field, Column, SQLModel, String, DateTime
from typing import Literal
from sqlmodel import Field, Column, String, DateTime
from .base import TableBase, IdMixin
from datetime import datetime
"""
原建表语句:
CREATE TABLE IF NOT EXISTS fr_objects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL,
name TEXT NOT NULL,
icon TEXT,
status TEXT,
phone TEXT,
context TEXT,
find_ip TEXT,
create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
lost_at TIMESTAMP
"""
if TYPE_CHECKING:
pass
class Object(IdMixin, TableBase, table=True):
__tablename__ = 'fr_objects'
key: str = Field(index=True, nullable=False, description="物品外部ID")
type: Literal['object', 'box'] = Field(
default='object',
type: Literal['normal', 'car'] = Field(
default='normal',
description="物品类型",
sa_column=Column(String, default='object', nullable=False)
sa_column=Column(String, default='normal', nullable=False)
)
name: str = Field(nullable=False, description="物品名称")
icon: Optional[str] = Field(default=None, description="物品图标")
status: Optional[str] = Field(default=None, description="物品状态")
phone: Optional[str] = Field(default=None, description="联系电话")
context: Optional[str] = Field(default=None, description="物品描述")
find_ip: Optional[str] = Field(default=None, description="最后一次发现的IP地址")
lost_at: Optional[datetime] = Field(
icon: str | None = Field(default=None, description="物品图标")
status: Literal['ok', 'lost'] = Field(
default='ok',
description="物品状态",
sa_column=Column(String, default='ok', nullable=False)
)
phone: str | None = Field(default=None, description="联系电话")
context: str | None = Field(default=None, description="物品描述")
find_ip: str | None = Field(default=None, description="最后一次发现的IP地址")
lost_at: datetime | None = Field(
default=None,
description="物品标记为丢失的时间",
sa_column=Column(DateTime, nullable=True)

View File

@@ -1,5 +1,5 @@
from pydantic import BaseModel
from typing import Literal, Optional
from typing import Literal
class DefaultResponse(BaseModel):
code: int = 0

View File

@@ -1,5 +1,4 @@
# model/setting.py
from typing import TYPE_CHECKING, Optional
from sqlmodel import Field
from .base import TableBase
@@ -12,12 +11,8 @@ CREATE TABLE IF NOT EXISTS fr_settings (
)
"""
if TYPE_CHECKING:
pass
class Setting(TableBase, table=True):
__tablename__ = 'fr_settings'
type: str = Field(index=True, nullable=False, description="设置类型")
name: str = Field(primary_key=True, nullable=False, description="设置名称") # name 为唯一主键
value: Optional[str] = Field(description="设置值")
value: str | None = Field(description="设置值")