diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..35410ca
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/Findreve.iml b/.idea/Findreve.iml
new file mode 100644
index 0000000..3708bfa
--- /dev/null
+++ b/.idea/Findreve.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..bae54b5
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ sqlite.xerial
+ true
+ Findreve Data
+ org.sqlite.JDBC
+ jdbc:sqlite:$PROJECT_DIR$/data.db
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..ef59b15
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml
new file mode 100644
index 0000000..844ac1b
--- /dev/null
+++ b/.idea/material_theme_project_new.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..82554e2
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..cd62433
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Findreve.xml b/.idea/runConfigurations/Findreve.xml
new file mode 100644
index 0000000..f4ac18e
--- /dev/null
+++ b/.idea/runConfigurations/Findreve.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Findreve.code-workspace b/Findreve.code-workspace
index 6470250..1abc245 100644
--- a/Findreve.code-workspace
+++ b/Findreve.code-workspace
@@ -8,6 +8,9 @@
},
{
"path": "../Findreve-NiceGUI"
+ },
+ {
+ "path": "../vein-based-iov-simulator-main"
}
],
"settings": {}
diff --git a/README.md b/README.md
index 04836d4..1fba819 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,9 @@ Upon launch, Findreve will create a SQLite database in the project's root direct
display the administrator's account and password in the console.
## 构建
+
+> 当前版本的 Findreve Core 无法正常工作,因为我们正在尝试[重构数据库组件以使用ORM](https://github.com/Findreve/Findreve/issues/8)
+
你需要安装Python 3.8 以上的版本。然后,clone 本仓库到您的服务器并解压,然后安装下面的依赖:
You need to have Python 3.8 or higher installed on your server. Then, clone this repository
diff --git a/app.py b/app.py
index e89af34..b5ebc5e 100644
--- a/app.py
+++ b/app.py
@@ -5,21 +5,11 @@ from contextlib import asynccontextmanager
from routes import (session, admin, object)
import model.database
import os, asyncio
+import pkg.conf
# 初始化数据库
asyncio.run(model.database.Database().init_db())
-# 定义程序参数
-APP_NAME: str = 'Findreve'
-VERSION: str = '2.0.0-ootc'
-summary='标记、追踪与找回 —— 就这么简单。'
-description='Findreve 是一款强大且直观的解决方案,旨在帮助您管理个人物品,'\
- '并确保丢失后能够安全找回。每个物品都会被分配一个 唯一 ID ,'\
- '并生成一个 安全链接 ,可轻松嵌入到 二维码 或 NFC 标签 中。'\
- '当扫描该代码时,会将拾得者引导至一个专门的网页,上面显示物品详情和您的联系信息,'\
- '既保障隐私又便于沟通。无论您是在管理个人物品还是专业资产,'\
- 'Findreve 都能以高效、简便的方式弥合丢失与找回之间的距离。'
-
# Findreve 的生命周期
@asynccontextmanager
async def lifespan(app: FastAPI):
@@ -28,10 +18,10 @@ async def lifespan(app: FastAPI):
# 定义 Findreve 服务器
app = FastAPI(
- title=APP_NAME,
- version=VERSION,
- summary=summary,
- description=description,
+ title=pkg.conf.APP_NAME,
+ version=pkg.conf.VERSION,
+ summary=pkg.conf.summary,
+ description=pkg.conf.description,
lifespan=lifespan
)
diff --git a/model/__init__.py b/model/__init__.py
index d0d2fd8..61d1c24 100644
--- a/model/__init__.py
+++ b/model/__init__.py
@@ -1 +1,3 @@
-from . import token
\ No newline at end of file
+from . import token
+from .setting import Setting
+from .object import Object
\ No newline at end of file
diff --git a/model/base.py b/model/base.py
new file mode 100644
index 0000000..a0abcf0
--- /dev/null
+++ b/model/base.py
@@ -0,0 +1,151 @@
+# model/base.py
+from datetime import datetime, timezone
+from typing import Optional, Type, TypeVar, Union, Literal, List
+
+from sqlalchemy import DateTime, BinaryExpression, ClauseElement
+from sqlalchemy.orm import selectinload
+from sqlalchemy.ext.asyncio.session import AsyncSession
+from sqlalchemy.ext.asyncio import AsyncAttrs
+from sqlmodel import SQLModel, Field, select, Relationship
+from sqlalchemy.sql._typing import _OnClauseArgument
+
+B = TypeVar('B', bound='TableBase')
+M = TypeVar('M', bound='SQLModel')
+
+utcnow = lambda: datetime.now(tz=timezone.utc)
+
+class TableBase(AsyncAttrs, SQLModel):
+ __abstract__ = True
+
+ created_at: datetime = Field(
+ default_factory=utcnow,
+ description="创建时间",
+ )
+ updated_at: datetime = Field(
+ sa_type=DateTime,
+ description="更新时间",
+ sa_column_kwargs={"default": utcnow, "onupdate": utcnow},
+ default_factory=utcnow
+ )
+ deleted_at: Optional[datetime] = Field(
+ default=None,
+ description="删除时间",
+ sa_column={"nullable": True}
+ )
+
+ @classmethod
+ async def add(
+ cls: Type[B],
+ session: AsyncSession,
+ instances: B | List[B],
+ refresh: bool = True
+ ) -> B | List[B]:
+ is_list = isinstance(instances, list)
+ if is_list:
+ session.add_all(instances)
+ else:
+ session.add(instances)
+ await session.commit()
+ if refresh:
+ if is_list:
+ for i in instances:
+ await session.refresh(i)
+ else:
+ await session.refresh(instances)
+ return instances
+
+ async def save(
+ self: B,
+ session: AsyncSession,
+ load: Union[Relationship, None] = None, # 设默认值,避免必须传
+ ):
+ session.add(self)
+ await session.commit()
+ if load is not None:
+ cls = type(self)
+ return await cls.get(session, cls.id == self.id, load=load) # 若该模型没有 id,请别用 load 模式
+ else:
+ await session.refresh(self)
+ return self
+
+ async def update(
+ self: B,
+ session: AsyncSession,
+ other: M,
+ extra_data: dict = None,
+ exclude_unset: bool = True,
+ ) -> B:
+ self.sqlmodel_update(
+ other.model_dump(exclude_unset=exclude_unset),
+ update=extra_data
+ )
+ session.add(self)
+ await session.commit()
+ await session.refresh(self)
+ return self
+
+ @classmethod
+ async def delete(
+ cls: Type[B],
+ session: AsyncSession,
+ instance: B | list[B],
+ ) -> None:
+ if isinstance(instance, list):
+ for inst in instance:
+ await session.delete(inst)
+ else:
+ await session.delete(instance)
+ await session.commit()
+
+ @classmethod
+ async def get(
+ cls: Type[B],
+ session: AsyncSession,
+ condition: BinaryExpression | ClauseElement | None,
+ *,
+ offset: int | None = None,
+ limit: int | None = None,
+ fetch_mode: Literal["one", "first", "all"] = "first",
+ join: Type[B] | tuple[Type[B], _OnClauseArgument] | None = None,
+ options: list | None = None,
+ load: Union[Relationship, None] = None,
+ order_by: list[ClauseElement] | None = None
+ ) -> B | List[B] | None:
+ statement = select(cls)
+ if condition is not None:
+ statement = statement.where(condition)
+ if join is not None:
+ statement = statement.join(*join)
+ if options:
+ statement = statement.options(*options)
+ if load:
+ statement = statement.options(selectinload(load))
+ if order_by is not None:
+ statement = statement.order_by(*order_by)
+ if offset:
+ statement = statement.offset(offset)
+ if limit:
+ statement = statement.limit(limit)
+
+ result = await session.exec(statement)
+ if fetch_mode == "one":
+ return result.one()
+ elif fetch_mode == "first":
+ return result.first()
+ elif fetch_mode == "all":
+ return list(result.all())
+ else:
+ raise ValueError(f"无效的 fetch_mode: {fetch_mode}")
+
+ @classmethod
+ async def get_exist_one(cls: Type[B], session: AsyncSession, id: int, load: Union[Relationship, None] = None) -> B:
+ instance = await cls.get(session, cls.id == id, load=load)
+ if not instance:
+ from fastapi import HTTPException
+ raise HTTPException(status_code=404, detail="Not found")
+ return instance
+
+
+# 需要“自增 id 主键”的模型才混入它;Setting 不混入
+class IdMixin(SQLModel):
+ id: Optional[int] = Field(default=None, primary_key=True, description="主键ID")
\ No newline at end of file
diff --git a/model/database.py b/model/database.py
index 0e8ab0c..244a872 100644
--- a/model/database.py
+++ b/model/database.py
@@ -1,19 +1,31 @@
-'''
-Author: 于小丘 海枫
-Date: 2024-10-02 15:23:34
-LastEditors: Yuerchu admin@yuxiaoqiu.cn
-LastEditTime: 2024-11-29 20:05:03
-FilePath: /Findreve/model.py
-Description: Findreve 数据库组件 model
-
-Copyright (c) 2018-2024 by 于小丘Yuerchu, All Rights Reserved.
-'''
-
+from contextlib import asynccontextmanager
import aiosqlite
from datetime import datetime
-import tool
-import logging
-from typing import Literal, Optional
+from typing import Optional
+
+from sqlmodel import SQLModel
+from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
+from sqlmodel.ext.asyncio.session import AsyncSession
+from sqlalchemy.orm import sessionmaker
+from typing import AsyncGenerator
+
+import warnings
+from .migration import migration
+
+ASYNC_DATABASE_URL = "sqlite+aiosqlite:///data.db"
+
+engine: AsyncEngine = create_async_engine(
+ ASYNC_DATABASE_URL,
+ echo=True,
+ connect_args={
+ "check_same_thread": False
+ } if ASYNC_DATABASE_URL.startswith("sqlite") else None,
+ future=True,
+ # pool_size=POOL_SIZE,
+ # max_overflow=64,
+)
+
+_async_session_factory = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
# 数据库类
class Database:
@@ -24,104 +36,28 @@ class Database:
db_path: str = "data.db" # db_path 数据库文件路径,默认为 data.db
):
self.db_path = db_path
-
- async def init_db(self):
- """初始化数据库和表"""
- logging.info("开始初始化数据库和表")
-
- create_objects_table = """
- CREATE TABLE IF NOT EXISTS fr_objects (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- type TEXT NOT NULL,
- 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
- )
- """
-
- create_settings_table = """
- CREATE TABLE IF NOT EXISTS fr_settings (
- type TEXT,
- name TEXT PRIMARY KEY,
- value TEXT
- )
- """
-
- async with aiosqlite.connect(self.db_path) as db:
- logging.info("连接到数据库")
- await db.execute(create_objects_table)
- logging.info("创建或验证fr_objects表")
- await db.execute(create_settings_table)
- logging.info("创建或验证fr_settings表")
-
- # 初始化设置表数据
- async with db.execute("SELECT name FROM fr_settings WHERE name = 'version'") as cursor:
- if not await cursor.fetchone():
- await db.execute(
- "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
- ('string', 'version', '1.0.0')
- )
- logging.info("插入初始版本信息: version 1.0.0")
-
- async with db.execute("SELECT name FROM fr_settings WHERE name = 'ver'") as cursor:
- if not await cursor.fetchone():
- await db.execute(
- "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
- ('int', 'ver', '1')
- )
- logging.info("插入初始版本号: ver 1")
-
- async with db.execute("SELECT name FROM fr_settings WHERE name = 'account'") as cursor:
- if not await cursor.fetchone():
- account = 'admin@yuxiaoqiu.cn'
- await db.execute(
- "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
- ('string', 'account', account)
- )
- logging.info(f"插入初始账号信息: {account}")
- print(f"账号: {account}")
-
- async with db.execute("SELECT name FROM fr_settings WHERE name = 'password'") as cursor:
- if not await cursor.fetchone():
- password = tool.generate_password()
- hashed_password = tool.hash_password(password)
- await db.execute(
- "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
- ('string', 'password', hashed_password)
- )
- logging.info("插入初始密码信息")
- print(f"密码(请牢记,后续不再显示): {password}")
-
- async with db.execute("SELECT name FROM fr_settings WHERE name = 'SECRET_KEY'") as cursor:
- if not await cursor.fetchone():
- secret_key = tool.generate_password(64)
- await db.execute(
- "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
- ('string', 'SECRET_KEY', secret_key)
- )
- logging.info("插入初始密钥信息")
-
- await db.commit()
- logging.info("数据库初始化完成并提交更改")
- async def add_object(
- self,
- key: str,
- type: Literal['normal', 'car'],
- name: str,
- icon: str = None,
- phone: str = None,
- ):
+ @staticmethod
+ @asynccontextmanager
+ async def get_session() -> AsyncGenerator[AsyncSession, None]:
+ 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)
+
+ async with self.get_session() as session:
+ await migration(session) # 执行迁移脚本
+
+ async def add_object(self, key: str, name: str, icon: str = None, phone: str = None):
"""
添加新对象
- :param type: 对象类型
:param key: 序列号
:param name: 名称
:param icon: 图标
@@ -135,15 +71,14 @@ class Database:
now = datetime.now()
now = now.strftime("%Y-%m-%d %H:%M:%S")
await db.execute(
- "INSERT INTO fr_objects (key, name, icon, phone, create_at, status, type) VALUES (?, ?, ?, ?, ?, 'ok', ?)",
- (key, name, icon, phone, now, type)
+ "INSERT INTO fr_objects (key, name, icon, phone, create_at, status) VALUES (?, ?, ?, ?, ?, 'ok')",
+ (key, name, icon, phone, now)
)
await db.commit()
async def update_object(
self,
id: int,
- type: Literal['normal', 'car'] = None,
key: str = None,
name: str = None,
icon: str = None,
@@ -151,13 +86,11 @@ class Database:
phone: int = None,
lost_description: Optional[str] = None,
find_ip: Optional[str] = None,
- lost_time: Optional[str] = None
- ):
+ lost_time: Optional[str] = None):
"""
更新对象信息
:param id: 对象ID
- :param type: 对象类型
:param key: 序列号
:param name: 名称
:param icon: 图标
@@ -185,18 +118,13 @@ class Database:
f"phone = COALESCE(?, phone), "
f"context = COALESCE(?, context), "
f"find_ip = COALESCE(?, find_ip), "
- f"lost_at = COALESCE(?, lost_at), "
- f"type = COALESCE(?, type) "
+ f"lost_at = COALESCE(?, lost_at) "
f"WHERE id = ?",
- (key, name, icon, status, phone, lost_description, find_ip, lost_time, type, id)
+ (key, name, icon, status, phone, lost_description, find_ip, lost_time, id)
)
await db.commit()
- async def get_object(
- self,
- id: int = None,
- key: str = None
- ):
+ async def get_object(self, id: int = None, key: str = None):
"""
获取对象
diff --git a/model/migration.py b/model/migration.py
new file mode 100644
index 0000000..c829735
--- /dev/null
+++ b/model/migration.py
@@ -0,0 +1,84 @@
+from typing import Sequence
+from sqlmodel import select
+from .setting import Setting
+import tool
+
+default_settings: list[Setting] = [
+ Setting(type='string', name='version', value='1.0.0'),
+ Setting(type='int', name='ver', value='1'),
+ Setting(type='string', name='account', value='admin@yuxiaoqiu.cn'),
+]
+
+async def migration(session):
+ # 先准备基础配置
+ settings: list[Setting] = default_settings.copy()
+
+ # 生成初始密码与密钥
+ admin_password = tool.generate_password()
+ print(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)))
+
+ # 读取库里已存在的 name,避免主键冲突
+ names = [s.name for s in settings]
+ exist_stmt = select(Setting.name).where(Setting.name.in_(names))
+ exist_rs = await session.exec(exist_stmt)
+ existed: set[str] = set(exist_rs.all())
+
+ to_insert = [s for s in settings if s.name not in existed]
+ if to_insert:
+ # 使用你写好的通用新增方法(是类方法),并传入会话
+ await Setting.add(session, to_insert, refresh=False)
+
+"""
+# 初始化设置表数据
+ async with db.execute("SELECT name FROM fr_settings WHERE name = 'version'") as cursor:
+ if not await cursor.fetchone():
+ await db.execute(
+ "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
+ ('string', 'version', '1.0.0')
+ )
+ logging.info("插入初始版本信息: version 1.0.0")
+
+ async with db.execute("SELECT name FROM fr_settings WHERE name = 'ver'") as cursor:
+ if not await cursor.fetchone():
+ await db.execute(
+ "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
+ ('int', 'ver', '1')
+ )
+ logging.info("插入初始版本号: ver 1")
+
+ async with db.execute("SELECT name FROM fr_settings WHERE name = 'account'") as cursor:
+ if not await cursor.fetchone():
+ account = 'admin@yuxiaoqiu.cn'
+ await db.execute(
+ "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
+ ('string', 'account', account)
+ )
+ logging.info(f"插入初始账号信息: {account}")
+ print(f"账号: {account}")
+
+ async with db.execute("SELECT name FROM fr_settings WHERE name = 'password'") as cursor:
+ if not await cursor.fetchone():
+ password = tool.generate_password()
+ hashed_password = tool.hash_password(password)
+ await db.execute(
+ "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
+ ('string', 'password', hashed_password)
+ )
+ logging.info("插入初始密码信息")
+ print(f"密码(请牢记,后续不再显示): {password}")
+
+ async with db.execute("SELECT name FROM fr_settings WHERE name = 'SECRET_KEY'") as cursor:
+ if not await cursor.fetchone():
+ secret_key = tool.generate_password(64)
+ await db.execute(
+ "INSERT INTO fr_settings (type, name, value) VALUES (?, ?, ?)",
+ ('string', 'SECRET_KEY', secret_key)
+ )
+ logging.info("插入初始密钥信息")
+
+ await db.commit()
+ logging.info("数据库初始化完成并提交更改")
+"""
\ No newline at end of file
diff --git a/model/object.py b/model/object.py
new file mode 100644
index 0000000..43c80a9
--- /dev/null
+++ b/model/object.py
@@ -0,0 +1,46 @@
+# my_project/models/download.py
+
+from typing import Literal, Optional, TYPE_CHECKING
+from sqlmodel import Field, Column, SQLModel, 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',
+ description="物品类型",
+ sa_column=Column(String, default='object', 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(
+ default=None,
+ description="物品标记为丢失的时间",
+ sa_column=Column(DateTime, nullable=True)
+ )
\ No newline at end of file
diff --git a/model/setting.py b/model/setting.py
new file mode 100644
index 0000000..bf56756
--- /dev/null
+++ b/model/setting.py
@@ -0,0 +1,23 @@
+# model/setting.py
+from typing import TYPE_CHECKING, Optional
+from sqlmodel import Field
+from .base import TableBase
+
+"""
+原表:
+CREATE TABLE IF NOT EXISTS fr_settings (
+ type TEXT,
+ name TEXT PRIMARY KEY,
+ value TEXT
+)
+"""
+
+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="设置值")
diff --git a/pkg/conf.py b/pkg/conf.py
new file mode 100644
index 0000000..89260e4
--- /dev/null
+++ b/pkg/conf.py
@@ -0,0 +1,9 @@
+APP_NAME: str = 'Findreve'
+VERSION: str = '2.0.0'
+summary='标记、追踪与找回 —— 就这么简单。'
+description='Findreve 是一款强大且直观的解决方案,旨在帮助您管理个人物品,'\
+ '并确保丢失后能够安全找回。每个物品都会被分配一个 唯一 ID ,'\
+ '并生成一个 安全链接 ,可轻松嵌入到 二维码 或 NFC 标签 中。'\
+ '当扫描该代码时,会将拾得者引导至一个专门的网页,上面显示物品详情和您的联系信息,'\
+ '既保障隐私又便于沟通。无论您是在管理个人物品还是专业资产,'\
+ 'Findreve 都能以高效、简便的方式弥合丢失与找回之间的距离。'
\ No newline at end of file
diff --git a/routes/session.py b/routes/session.py
index 4c5c5ce..6e006f9 100644
--- a/routes/session.py
+++ b/routes/session.py
@@ -7,7 +7,7 @@ from fastapi import APIRouter
import jwt, JWT
from model.token import Token
-from model import database
+from model import Setting, database
from tool import verify_password
Router = APIRouter(tags=["令牌 session"])
@@ -26,8 +26,8 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None):
# 验证账号密码
async def authenticate_user(username: str, password: str):
# 验证账号和密码
- account = await database.Database().get_setting('account')
- stored_password = await database.Database().get_setting('password')
+ account = await Setting.get('setting', 'account')
+ stored_password = await Setting.get('setting', 'password')
if account != username or not verify_password(stored_password, password):
return False