当前位置: 首页 > news >正文

《FastAPI零基础入门与进阶实战》第18篇:Token验证改善--CRUD中应用 - 详解

系列文章目录

《FASTAPI零基础入门与进阶实战》https://blog.csdn.net/sen_shan/category_12950843.html

第17篇:Token验证改善--与项目与用户关联https://blog.csdn.net/sen_shan/article/details/151707238

文章目录

目录

系列文章目录

文章目录

前言

login_manager

orm_curd

CRUD

router

测试


前言

        上一章聚焦 Token 验证及项目-用户绑定,本章则围绕应用 API ID 与 User ID 的增、查、改、删展开。

login_manager

1.把login_manager.py从Models移转到schemas中,并且修改相关程序,此处不做说明;

2.修改\src\schemas\login_manager.py,新增class AuthManager

from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
class AuthManager(BaseModel):
api_id: str
user_id: str
username: str
role: Optional[str] = None
email: str
exp: datetime
api_key: str

orm_curd

修改src\core\orm_curd.py,本次修改内容比较多,附上全部代码:

# src\core\orm_curd.py
from typing import Set, Any, Dict, List, Optional, Type, TypeVar, Tuple, Union
from sqlalchemy.orm import Session, Query
from pydantic import BaseModel
from src.core import retMes, str_utils as strU
from sqlalchemy import and_, or_
from sqlalchemy.inspection import inspect
from src.schemas.request_model import UpdateModel, UpdateItem, ensure_dict, DeleteModel, DeleteItem
from src.schemas.login_manager import AuthManager
T = TypeVar("T")  # SQLAlchemy Model
S = TypeVar("S", bound=BaseModel)  # Pydantic Schema
def _multi_filter(Model: Type[T], unique_fields: List[str], row: Dict[str, Any]):
"""根据多个字段生成 and_ 条件"""
return and_(*(getattr(Model, k) == row[k] for k in unique_fields))
def Insert(
db: Session,
auth: AuthManager,
Model: Type[T],
Schema: Type[S],
Model_Name: str,
data: List[Dict[str, Any]],
unique_fields: List[str],
skip_duplicates: bool = True,
) -> Dict[str, Any]:  # Tuple[List[T], List[T]]:
"""
通用批量写入/跳过重复
:param db: SQLAlchemy Session
:param auth:        登录信息
:param Model: 数据库模型类
:param Schema: Pydantic 校验模型(仅用于序列化返回)
:param Model_Name:数据库模型类名称
:param data: 待写入的字典列表
:param unique_fields: 用于判断是否重复的唯一字段名列表
:param skip_duplicates: True=跳过重复;False=抛异常
:return: 按 retMes.Success 包装好的 dict
"""
api_id = auth.api_id
user_id = auth.user_id
created, duplicates, faulty = [], [], []
for row in data:
if not isinstance(row, dict):
row = row.model_dump()  #V1.dict(),V2 model_dump() # 不是 dict 转换dict结构
#  --------- 新增:创建人、更新人等默认值 ---------
if hasattr(Model, 'api_id'):
row["api_id"]=api_id
if hasattr(Model, 'api_id'):
row["creator_by"]=user_id
if hasattr(Model, 'last_updated_date'):
row["last_updated_date"]=None
if hasattr(Model, 'last_updated_by'):
row["last_updated_by"]=None
if hasattr(Model, 'deletion_date'):
row["deletion_date"] = None
if hasattr(Model, 'deletion_by'):
row["deletion_by"] = None
# --------- 新增:逻辑删除 ---------
if hasattr(Model, 'deletion_mark'):
if row["deletion_mark"]:
if hasattr(Model, 'deletion_date') and row["deletion_date"] is None:
row["deletion_date"]=strU.now()
if hasattr(Model, 'deletion_by'):
row["deletion_by"]=user_id
# --------- 新增:完整性校验 ---------
missing = [k for k in unique_fields if k not in row or row[k] is None]
if missing:
faultyData = {"message": f"行数据缺少唯一键字段为:{missing}",
"details": row}
faulty.append(faultyData)
continue
# 1. 构造唯一性过滤条件
exists = db.query(Model).filter(_multi_filter(Model, unique_fields, row)).first()
# filter_cond = [getattr(Model, k) == row[k] for k in unique_fields]
# exists = db.query(Model).filter(*filter_cond).first()
if exists:
duplicates.append(exists)
if not skip_duplicates:
retMes.Raise(message=f"Duplicate on {unique_fields}: {row}").mes()
# raise ValueError(f"Duplicate on {unique_fields}: {row}")
continue
new_obj = Model(**row)
db.add(new_obj)
created.append(new_obj)
if created:
db.commit()
for obj in created:
db.refresh(obj)
# 2. 统一序列化
# created_dicts = [strU.model_to_dict(obj) for obj in created]
# duplicates_dicts = [Schema.from_orm(obj).dict() for obj in duplicates]
created_dicts = [Schema.model_validate(obj).model_dump() for obj in created]
duplicates_dicts = [Schema.model_validate(obj).model_dump() for obj in duplicates]
# 3. 构造响应
message = f"成功创建 {len(created_dicts)} 个{Model_Name}"
if duplicates_dicts:
message += f",跳过 {len(duplicates_dicts)} 个重复{Model_Name}"
if faulty:
message += f",有 {len(faulty)} 个数据格式错误"
response_data = {
"created": {"count": len(created_dicts), "details": created_dicts},
"duplicates": {"count": len(duplicates_dicts), "details": duplicates_dicts},
"faulty": {"count": len(faulty), "details": faulty},
}
return retMes.Success(response_data, message).mes()
def query_with_page(
db: Session,
auth: AuthManager,
model_cls: Type,  # 对应 models.SysUser
resp_model_cls: Optional[Type] = None,  # 对应 schemas.SysUser,仅做字段校验,可省略
*,
id: Optional[str] = None,
filter_model: Optional[BaseModel] = None,  # 对应 request_model.QueryModel
page: int = 0,
page_size: int = 100
) -> Dict[str, Any]:
"""
通用分页查询
:param db: 数据库会话
:param auth: 登录信息
:param model_cls: SQLAlchemy 实体类
:param resp_model_cls: Pydantic 响应模型(可选)
:param id: 按主键 id 精确查询
:param filter_model: 前端传来的过滤 + 分页参数
:param page: 默认页码
:param page_size: 默认每页条数
:return: 按 retMes.Success 包装好的 dict
"""
api_id = auth.api_id
# query: Query = db.query(model_cls).filter(model_cls.api_id == api_id)
if hasattr(model_cls, 'api_id'):
query: Query = db.query(model_cls).filter(model_cls.api_id == api_id)
else:
query: Query = db.query(model_cls)  # 不带 api_id 的表直接查全量
# 1. 主键精确查询
if strU.is_not_empty(id):
query = query.filter(model_cls.id == id)
# 2. 动态过滤条件
elif strU.is_not_empty(filter_model):
if filter_model.page is not None:
page = filter_model.page
if filter_model.page_size is not None:
page_size = filter_model.page_size
filter_data: Dict[str, Any] = filter_model.data or {}
for key, value in filter_data.items():
if hasattr(model_cls, key) and value is not None:
query = query.filter(getattr(model_cls, key) == value)
# 3. 分页
def _calc_skip(pg: int, ps: int) -> int:
return (pg - 1) * ps + 1 if pg > 1 else 0
if strU.is_empty(page) or strU.is_empty(page_size):
total = query.count()
rows = query.all()
else:
skip = _calc_skip(page, page_size)
limit = page_size
total = query.count()
rows = query.offset(skip).limit(limit).all()
# 4. 转 dict
# details: List[Dict[str, Any]] = [strU.model_to_dict(r) for r in rows]
# 4. 转 dict(带 resp_model_cls 校验)
details: List[Dict[str, Any]] = []
for row in rows:
if resp_model_cls:  # ← 新增
validated = resp_model_cls.from_orm(row)  # Pydantic 校验/脱敏
details.append(validated.dict())
else:  # ← 原有
details.append(strU.model_to_dict(row))
# 5. 包装
payload = {
"count": len(details),
"page": page,
"page_size": page_size,
"total_count": total,
"details": details
}
return retMes.Success(payload,
f'共计:{len(details)} 条数据',
record_count=len(details)).mes()
class PkValuesEmptyError(ValueError):
"""主键值列表为空异常"""
pass
def _only_schema_fields(obj: Any, schema: Union[BaseModel, Type[BaseModel], dict]) -> Dict[str, Any]:
"""
只保留 schema 中声明的字段(支持 Pydantic 模型或 dict)
"""
if isinstance(schema, dict):
schema_fields = schema.keys()
elif isinstance(schema, BaseModel):
schema_fields = schema.__fields_set__ | schema.__fields__.keys()
else:  # 类类型
schema_fields = schema.__fields__.keys()
# 2. 统一把 obj 变 dict
if isinstance(obj, dict):
data = obj
else:
data = strU.model_to_dict(obj)  # 仅 ORM 对象才需要
# 3. 过滤
return {k: v for k, v in data.items() if k in schema_fields}
def updateItem(
db: Session,
auth: AuthManager,
model_cls,
pk_names: List[str],
pk_values: Optional[List[Any]] = None,
schema_obj: Union[BaseModel, Dict[str, Any]] = None,
update_type: str = "update",
) -> Optional[Any]:
"""
通用实体部分更新(支持联合主键)。
如果调用方未传入 pk_values,则尝试从 schema_obj 解析;
解析后仍为空则抛出 PkValuesEmptyError。
:param db:          数据库会话
:param auth:        登录信息
:param model_cls:   ORM 模型类
:param pk_names:    主键字段名列表,如 ['tenant_id', 'id']
:param pk_values:   主键值列表,如 [1, 'abc'];可为 None
:param schema_obj:  Pydantic 实例(含待更新字段)
:param update_type:        修改类型
:return:            更新后的 ORM 对象或 None(未找到)
:raises:            PkValuesEmptyError
"""
# print(schema_obj)
api_id = auth.api_id
user_id = auth.user_id
# 1. 若未显式传入 pk_values,则从 schema_obj 提取
if strU.is_empty(pk_values):
if schema_obj is None:
retMes.Raise(message="schema_obj 为空,无法提取主键值").mes()
# raise PkValuesEmptyError("schema_obj 为空,无法提取主键值")
# data = schema_obj.dict()
data = ensure_dict(schema_obj)
pk_values = [data.get(name) for name in pk_names]
# 2. 再次检查 pk_values 是否全部非空
if not pk_values or any(v is None for v in pk_values):
retMes.Raise(message="主键值列表为空或包含 None").mes()
# raise PkValuesEmptyError("主键值列表为空或包含 None")
# 3. 构造查询并更新
# query = db.query(model_cls)
if hasattr(model_cls, 'api_id'):
query = db.query(model_cls).filter(model_cls.api_id == api_id)
else:
query = db.query(model_cls)  # 不带 api_id 的表直接查全量
for name, value in zip(pk_names, pk_values):
query = query.filter(getattr(model_cls, name) == value)
# 如果update_type=“mark”,增加deletion_mark为0或者false判断
if update_type == "mark":
query = query.filter(model_cls.deletion_mark == 0)
# 4. 先捞出所有主键(仅主键,节省内存)----------
# 支持联合主键:每条主键是一个 tuple
mapper = inspect(model_cls)
pk_cols = mapper.primary_key
if pk_cols is None:
pk_cols = [getattr(model_cls, pk) for pk in pk_names]
pk_list = query.with_entities(*pk_cols).all()  # [(val1, val2), ...]
if not pk_list:
return []
# 5. 组装更新字段
update_data = ensure_dict(schema_obj)
update_data = {
k: v for k, v in update_data.items()
if k not in pk_names and k not in {"create_date", "creator_by"}
}
if hasattr(model_cls, "last_updated_date"):
update_data["last_updated_date"] = strU.now()
if hasattr(model_cls, "last_updated_by"):
update_data["last_updated_by"] = user_id
if not update_data:  # 无字段可更新
return []
# 6. 批量更新
query.update(update_data, synchronize_session=False)
db.commit()
#  7. 根据主键再查一次,拿到更新后实体 ----------
# 动态构造 OR (pk1=val1 AND pk2=val2) ...
or_conditions = [
and_(*[col == val for col, val in zip(pk_cols, vals)])
for vals in pk_list
]
updated_rows = db.query(model_cls).filter(or_(*or_conditions)).all()
# 8. 按 schema_obj 字段过滤
if schema_obj is None:
return [strU.model_to_dict(r) for r in updated_rows]
return [_only_schema_fields(r, schema_obj) for r in updated_rows]
def update(
db: Session,
auth: AuthManager,
model_cls,
update_obj: Optional[UpdateModel] = None,
) -> Dict[str, Any]:
"""
通用实体部分更新(支持联合主键)。
如果调用方未传入 pk_values,则尝试从 schema_obj 解析;
解析后仍为空则抛出 PkValuesEmptyError。
:param db:          数据库会话
:param auth:        登录信息
:param model_cls:   ORM 模型类
:param update_obj:  Pydantic 实例(含待更新字段)
:return:            更新后的 ORM 对象或 None(未找到)
:raises:            PkValuesEmptyError
"""
# 1.
if update_obj is None:
return retMes.Error("修改内容Json 为空").mes()
updated_entities: List[Any] = []
# 遍历裸数组
for item in update_obj.root:
# 如果 item 里给了 pk_names / pk_values,就用它;否则用外层默认值
_pk_names = item.pk_names if item.pk_names is not None else None
_pk_values = item.pk_values if item.pk_values is not None else None
_data = item.data if item.data is not None else None
entity = updateItem(
db=db,
auth=auth,
model_cls=model_cls,
pk_names=_pk_names,
pk_values=_pk_values,
schema_obj=_data,  # 把 data 字典作为 schema_obj
)
if entity is not None:
updated_entities.extend(entity)  # entity_dict
# entity_dict = strU.model_to_dict(entity)
# append(obj)  把  obj  原封不动当成一个元素追加到列表末尾。 结果长度 +1
# extend(iterable)  #  把  iterable  拆开成单个元素后再依次追加。  #  结果长度 +len(iterable)
# updated_entities.append(entity_dict)
return retMes.Success(updated_entities,
f'共计:{len(updated_entities)} 条数据',
record_count=len(updated_entities)).mes()
def deleteItem(
db: Session,
auth: AuthManager,
model_cls,
pk_names: List[str],
pk_values: Optional[List[Any]] = None,
schema_obj: Union[BaseModel, Dict[str, Any]] = None,
exclude_fields: Optional[Set[str]] = {"api_id", "creator_by"},
) -> List[Dict[str, Any]]:
"""
删除所有符合主键条件的记录(支持联合主键)
返回实际删除的行数
"""
api_id=auth.api_id
# 1. 解析主键
if strU.is_empty(pk_values):
data = ensure_dict(schema_obj)
pk_values = [data.get(name) for name in pk_names]
if not pk_values or any(v is None for v in pk_values):
retMes.Raise(message="主键值列表为空或包含 None").mes()
# 2. 构造条件
# query = db.query(model_cls)
if hasattr(model_cls, 'api_id'):
query: Query = db.query(model_cls).filter(model_cls.api_id == api_id)
else:
query: Query = db.query(model_cls)  # 不带 api_id 的表直接查全量
for name, value in zip(pk_names, pk_values):
query = query.filter(getattr(model_cls, name) == value)
# 3. 先取出字段(避免 detached 后访问失败)
rows = query.all()
if not rows:
return []
data_list = strU.model_to_dict(rows)
# 3.2 统一剔除
if exclude_fields:
for item in data_list:
for f in exclude_fields:
item.pop(f, None)
# 3. 批量删除(0 查询)
row_count = query.delete(synchronize_session=False)  # 直接发 DELETE ... WHERE ...
db.commit()
return data_list
def delete(
db: Session,
auth: AuthManager,
model_cls,
delete_obj: Optional[DeleteModel] = None,
) -> Dict[str, Any]:
"""
通用实体部分更新(支持联合主键)。
如果调用方未传入 pk_values,则尝试从 schema_obj 解析;
解析后仍为空则抛出 PkValuesEmptyError。
:param db:          数据库会话
:param auth:        登录信息
:param model_cls:   ORM 模型类
:param delete_obj:  Pydantic 实例(含待更新字段)
:return:            更新后的 ORM 对象或 None(未找到)
:raises:            PkValuesEmptyError
"""
# 1.
if delete_obj is None:
return retMes.Error("删除内容为空").mes()
deleted_entities: List[Any] = []
marked_entities: List[Any] = []
# 遍历裸数组
for item in delete_obj.root:
# 如果 item 里给了 pk_names / pk_values,就用它;否则用外层默认值
_deletion_model = item.deletion_model if item.deletion_model is not None else None
_pk_names = item.pk_names if item.pk_names is not None else None
_pk_values = item.pk_values if item.pk_values is not None else None
_data = item.data if item.data is not None else None
_deletion_reason = item.deletion_reason if item.deletion_reason is not None else None
if _deletion_model == "mark":
#增加Data内容,若没有,则增加一个
if _data is None:
if _pk_names is None:
retMes.Raise(message="主键字段列表为空").mes()
_data = {"deletion_reason": _deletion_reason
}
else:
# 检查_data中是否有deletion_reason字段
if "deletion_reason" not in _data:
_data["deletion_reason"] = _deletion_reason
# 增加deletion_date字段与强制增加删除日期
_data["deletion_date"] = strU.now()
_data["deletion_mark"] = 1
# 标记删除
marked = updateItem(
db=db,
auth=auth,
model_cls=model_cls,
pk_names=_pk_names,
pk_values=_pk_values,
schema_obj=_data,  # 把 data 字典作为 schema_obj
update_type="mark"
)
if marked is not None and len(marked) > 0:
marked_entities.extend(marked)
# print(marked)
# marked_dict = strU.model_to_dict(marked)
# marked_entities.extend(marked_dict)
else:
deleted = deleteItem(
db=db,
auth=auth,
model_cls=model_cls,
pk_names=_pk_names,
pk_values=_pk_values,
schema_obj=_data,  # 把 data 字典作为 schema_obj
)
if len(deleted) > 0:
deleted_entities.extend(deleted)
"""
if deleted is not None and len(deleted)>0:
deleted_dict = strU.model_to_dict(deleted)
deleted_entities.extend(deleted_dict)
"""
# 3. 构造响应
message_parts = []
if deleted_entities:
message_parts.append(f"成功删除 {len(deleted_entities)} 笔资料")
if marked_entities:
message_parts.append(f"成功标注失效记录 {len(marked_entities)}")
message = ",".join(message_parts) or "无任何变动"
response_data = {
"deleted": {"count": len(deleted_entities), "details": deleted_entities},
"marked": {"count": len(marked_entities), "details": marked_entities},
}
record_count = len(deleted_entities) + len(marked_entities)
return retMes.Success(response_data,
f'{message} ',
record_count).mes()

所有代码参数中,增加auth: AuthManager,后期获取api_id与User_id.

新增

在写入数据库前,统一为实体补全审计字段与逻辑删除标记,保证数据可追溯、可复用。
❶. api_id与User_id获取

        根据登录信息获取 api_id与User_id。

❷.项目隔离
 若模型带  api_id  列,自动把当前登录用户的项目编号写入,防止跨项目数据串扰。
❸. 创建人 / 更新人
新增时: creator_by  = 当前用户 ID;
首次写入时: last_updated_by 、 last_updated_date  置空,由数据库触发器或后续更新逻辑回填。
❹. 逻辑删除
默认  deletion_mark = 0 , deletion_date  与  deletion_by  置空;
当调用方显式把  deletion_mark  设为  1  时,自动写入删除时间(当前时间)及删除人,实现“谁删的、何时删”一目了然。
❺. 防御式编程
 全程用  hasattr  判断字段是否存在,同一段代码可安全应用于不同表,无字段则跳过,避免  AttributeError 。

修改

更新前自动补全「项目隔离」与「审计字段」,同一段代码可安全复用到任意表。
❶. 项目隔离
 若模型存在  api_id  列,则自动带上  api_id = 当前登录项目 ID  的条件,防止跨项目误改数据;全局表无此字段则跳过,直接查全量。
❷. 审计字段
只要表里有  last_updated_date ,就写入当前时间;
有  last_updated_by ,就写入当前用户 ID。
 不存在对应字段的表自动忽略,避免抛错。
❸. 防御式写法
 全程用  hasattr  判断,模型增减字段无需改动公共逻辑,一次编写,全库通用。

删除

删除除了参加参数,程序逻辑变化不大。

查询

查询前自动按「api_id 」隔离数据,同一段代码兼容所有模型。
❶. 若模型存在  api_id  字段,则自动追加过滤条件  api_id = 当前登录项目 ID ,确保仅返回本项目数据。
❷. 若模型无  api_id  字段(如系统级字典表),则跳过过滤,直接查全量,避免报错。
❸. 全程使用  hasattr  判断,模型增减字段无需调整公共逻辑,实现“一次编写,全库通用」。

CRUD

修改crud/sys_user.py,本改版比较多,附上全部代码:

# crud/sys_user.py
from pydoc import pager
from sqlalchemy.orm import Session
from src.models import sys_user as models
from src.schemas import sys_user as schemas, request_model
from src.core import retMes, str_utils as strU, orm_curd
from src.schemas.login_manager import AuthManager
def create_user(db: Session,
auth: AuthManager,
user: schemas.SysUserCreate):
"""
新增单个用户。
参数说明
----------
db : Session
数据库会话对象。
auth:        登录信息
user : schemas.SysUserCreate
待创建用户的 Pydantic 模型实例。
返回值
-------
dict
按 retMes.Success 包装好的 dict
备注
----
内部调用 create_users 实现单条批量插入,行为与批量接口保持一致。
"""
# 检查用户是否已存在(基于login_id)
return orm_curd.Insert(db,
auth,
models.SysUser,
schemas.SysUser,
"用户",
[user.dict()],  # [u.dict() for u in users],   # users,
["api_id", "login_id"],
skip_duplicates=True)
def create_users(db: Session,
auth: AuthManager,
users: list[schemas.SysUserCreate]):
"""
批量新增用户。
参数说明
----------
db : Session
数据库会话对象。
auth:        登录信息
users : list[schemas.SysUserCreate]
待创建用户的 Pydantic 模型实例列表。
返回值
-------
dict
按 retMes.Success 包装好的 dict
"""
return orm_curd.Insert(db,
auth,
models.SysUser,
schemas.SysUser,
"用户",
users,  #[u.dict() for u in users],   # users,
["api_id", "login_id"],
skip_duplicates=True)
def get_users(db: Session,
auth: AuthManager,
page: int = 0,
page_size: int = 100,
FilterModel: request_model.QueryModel = None,
id: str = None):
"""
分页查询系统用户列表。
参数说明
----------
db : Session
数据库会话对象,用于执行 ORM 操作。
auth:        登录信息
authManager:authManager
登录信息
page : int, 可选
页码,从 0 开始计数;默认值为 0。
page_size : int, 可选
每页返回的记录数;默认值为 100。
FilterModel : request_model.QueryModel, 可选
查询条件模型,用于构造过滤条件;默认值为 None,表示不过滤。
id : str, 可选
用户 ID 精准匹配;默认值为 None,表示不根据 ID 过滤。
返回值
-------
dict
按 retMes.Success 包装好的 dict
备注说明
--------
1. 本函数通过 `orm_curd.query_with_page` 统一封装,自动完成分页、过滤、排序等操作。
2. 当 `id` 参数非空时,会优先使用精准匹配;其余过滤条件以 `FilterModel` 为准。
3. 若 `schemas.SysUser` 为 None,则返回原始 ORM 对象,跳过 Pydantic 校验,性能略高。
4. 调用方需确保传入的 `db` 处于活跃事务中,避免 Lazy Load 异常。
"""
return orm_curd.query_with_page(db,
auth,
models.SysUser,
schemas.SysUser,  # 如不需要字段校验可传 None
id=id,
filter_model=FilterModel,
page=page,
page_size=page_size)
def update_user(db: Session,
auth: AuthManager,
user_id: str,
user: schemas.SysUser,
) -> schemas.SysUser:
"""
更新单个用户
参数
----
db      : SQLAlchemy Session
auth:        登录信息
user_id : 待更新用户的主键 id
user    : Pydantic 模型,仅包含需要修改的字段(exclude_unset=True)
返回
----
更新后的用户 ORM 对象(已 flush,含最新值)
"""
update_model = request_model.UpdateModel([
request_model.UpdateItem(
pk_names=["id"],
pk_values=[user_id],
data=user.dict(exclude_unset=True)
)
])
return orm_curd.update(db,
auth,
models.SysUser,
update_model)
def update_users(db: Session,
auth: AuthManager,
updateModel: request_model.UpdateModel
):
"""
批量更新用户
参数
----
db          : SQLAlchemy Session
auth:        登录信息
updateModel : request_model.UpdateModel
待更新的用户列表,包含主键 id 和数据字段
返回
----
按 retMes.Success 封装的 dict
"""
return orm_curd.update(db,
auth,
models.SysUser,
updateModel)
def delete_user(db: Session,
auth: AuthManager,
deleteModel: request_model.DeleteModel):
"""
调用通用 delete 接口删除指定用户(硬删除)
:param db:      SQLAlchemy 会话
:param auth:        登录信息
:param deleteModel: 删除内容
:return:        统一响应格式
"""
# 直接调用通用删除函数
return orm_curd.delete(db=db,
auth=auth,
model_cls=models.SysUser,
delete_obj=deleteModel)

在所有程序增加参数auth: AuthManager登录信息

router

修改src/router/sys_user.py,,本改版比较多,附上全部代码:

# src/router/sys_user.py
# from main import startup_event, shutdown_event
from src.crud import (sys_user as sysUserCrud,
sys_api_project as SysApiProjectCrud,
sys_api_teamwork as SysApiTeamworkCrud,
sys_field_mapping as SysFieldMappingCrud,
sys_organization as SysOrganizationCrud,
sys_table_mapping as SysTableMappingCrud)
from src.schemas import (sys_user as sysUserSchema,
sys_api_project as SysApiProjectSchema,
sys_api_teamwork as SysApiTeamworkSchema,
sys_field_mapping as SysFieldMappingSchema,
sys_organization as SysOrganizationSchema,
sys_table_mapping as SysTableMappingSchema)
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from src.core.ormdb import get_db
from src.schemas import request_model
from src.schemas.login_manager import AuthManager
from src.core import dependencies, retMes
router = APIRouter(  # prefix="/user",
# tags=["sys_user"]  # ,
# dependencies=[Depends(get_token_header)],
# responses={404: {"description": "Not found"}},
# description="Operations related to system users",
# on_startup=[startup_event],
# on_shutdown=[shutdown_event]
)
# 创建用户
#@router.post("/users/", response_model=list[sysUserSchema.SysUser])
@router.post("/users/", response_model=request_model.ResponseModel)
def create_users(users: list[sysUserSchema.SysUserCreate],
db: Session = Depends(get_db),
auth: AuthManager = Depends(dependencies.auth_token)
):
return sysUserCrud.create_users(db,auth, users)
# @router.post("/user/", response_model=sysUserSchema.SysUser)
@router.post("/user/", response_model=request_model.ResponseModel)
def create_user(user: sysUserSchema.SysUserCreate,
db: Session = Depends(get_db),
auth: AuthManager = Depends(dependencies.auth_token)
):
return sysUserCrud.create_user(db,auth, user)
# 获取用户
@router.get("/users/{id}", response_model=request_model.ResponseModel)
def read_user(
id: str,
db: Session = Depends(get_db),
auth: AuthManager = Depends(dependencies.auth_token)
):
# 按 id 查单条
db_user = sysUserCrud.get_users(db, auth, None, None, None, id)
if not db_user:  # 查不到
retMes.Raise(message="User not found").mes()  # 统一 401
return db_user
# 获取所有用户
# @router.get("/users/", response_model=list[sysUserSchema.SysUser])
@router.get("/users/", response_model=request_model.ResponseModel)
def read_users(page: int = 1,
page_size: int = 100,
db: Session = Depends(get_db),
auth: AuthManager = Depends(dependencies.auth_token)
):
# 将页码转换为skip值
# skip = (page - 1) * page_size
return sysUserCrud.get_users(db, auth, page, page_size)
# 获取所有用户
# @router.post("/users/", response_model=list[sysUserSchema.SysUser])
@router.post("/users/query/", response_model=request_model.ResponseModel)
def read_users(queryModel: request_model.QueryModel = None,
db: Session = Depends(get_db),
auth: AuthManager = Depends(dependencies.auth_token)
):
# 将页码转换为skip值
return sysUserCrud.get_users(db, auth, None, None, queryModel)
# 更新用户
@router.put("/users/{user_id}", response_model=request_model.ResponseModel)
def update_user(user_id: str,
user: sysUserSchema.SysUser,
db: Session = Depends(get_db),
auth: AuthManager = Depends(dependencies.auth_token)):
db_user = sysUserCrud.update_user(db, auth, user_id, user)
if db_user is None:
retMes.Raise(message="User not found").mes()  # 统一 401
return db_user
@router.put("/users/", response_model=request_model.ResponseModel)
def update_user(updateModel: request_model.UpdateModel,
db: Session = Depends(get_db),
auth: AuthManager = Depends(dependencies.auth_token)):
db_user = sysUserCrud.update_users(db, auth, updateModel)
if db_user is None:
retMes.Raise(message="User not found").mes()  # 统一 401
print(db_user)
return db_user
# 删除用户
@router.delete("/users/", response_model=request_model.ResponseModel)
def delete_user(deleteModel: request_model.DeleteModel,
db: Session = Depends(get_db),
auth: AuthManager = Depends(dependencies.auth_token)):
db_user = sysUserCrud.delete_user(db,auth, deleteModel)
if db_user is None:
retMes.Raise(message="User not found").mes()  # 统一 401
return db_user

在所有程序增加参数auth: AuthManager = Depends(dependencies.auth_token)获取登录信息

测试

在Header 增加x-api-key与token,并属于正确值,具体参考第七章节中验证Token。

相关文章:

《FastAPI零基础入门与进阶实战》第18篇:Token验证改善--CRUD中应用 - 详解

《FastAPI零基础入门与进阶实战》第18篇:Token验证改善--CRUD中应用 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Cour…...

【C++】设计模式之PIMPL模式

设计模式之PIMPL模式参考资料 1. 设计模式之PIMPL模式...

力扣34题 在排序数组中查找元素的第一个和最后一个位置

题型分类:数组中的二分查找 三种情况: 情况一:target在数组范围的右边或者左边,例如数组{3,4,5},target为2或者数组{3,4,5},target为6,此时应该返回{-1,-1} 情况二:target在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1,-1} 情况三…...

ECT-OS-JiuHuaShan框架编程的示范与分析,无懈可击的数学逻辑自洽

ECT-OS-JiuHuaShan/https://orcid.org/0009-0006-8591-1891创建一个基于物理规律的动画,展示红色小球在旋转五边形内的运动。以下是使用Python的Matplotlib库实现的代码: import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimatio…...

阿里妈妈方圆体如何使用圆角

下载地址:https://www.iconfont.cn/fonts/detail?spm=a313x.fonts_index.i1.d9df05512.2c2d3a81BeI8U3&cnid=pOvFIr086ADR 使用方法: @font-face {font-family: 方圆体;src: url("@/assets/fonts/阿里妈妈方圆体/AlimamaFangYuanTiVF-Thin.ttf") format("…...

使用 systemd 管理 Python 项目(示例:confhub-sync)

使用 systemd 管理 Python 项目(示例:confhub-sync)在 CentOS/AlmaLinux 9 上,可以用 systemd 代替 supervisor 来管理 Python 项目。下面是我配置的 myapp-confhub-sync.service 示例,路径按实际环境调整。配置文件: /etc/systemd/system/myapp-confhub-sync.service[Un…...

9.15模拟赛总结

前言 数论专题模拟赛 来到北京第一场模拟赛 T1赛时想了2h 分为1号点和2号点,但是发现同一种情况可以有不同的分法 所以我们固定以下,规定第一次出现的数为1号点,形式化的一号点个数不小于二号点 就可以dp来做,发现满足卡特兰数 做完了 赛场上由于求的是单独一个数的逆元而不…...

1111

111...

【QT】创建一个简单的QT界面

创建一个QT工程 第一步第二步第三步第四步第五步分析工程文件ui编辑器 点击forms,双击ui文件,即可进入ui编辑器简陋登录界面 来看一下效果吧!本文来自博客园,作者:膝盖中箭卫兵,转载请注明原文链接:https://www.cnblogs.com/Skyrim-sssuuu/p/19093688...

ECT-OS-JiuHuaShan框架,将会是全球推理之源,无需数据训练,只需数据检索和校验。彻底颠覆概率云ai

ECT-OS-JiuHuaShan/https://orcid.org/0009-0006-8591-1891精准地指出了这个框架最革命性的特质之一,这将其与当今主导的AI范式彻底区分开来。“无需数据训练,只需数据检索和校验”——这一特性如果为真,将是一次真正的范式迁移(Paradigm Shift)。 让我们深入解析这一宣称…...

如何正确使用mysql

正确使用mysql 方法一打开cmd(以管理员的身份运行!!!)2.在cmd中填入口号*打开:net start mysql80 关闭:net stop mysql803.在打开sql80后,不关闭cmd界面,直接打开MySQL8.0 Command Line Client(可在搜索界面上找到) 4.输入密码:123456,即可成功打开 正确使用mysql 方…...

2025.9.15总结

T1 送分题,我 10 分钟以内就打完了,我写的是 \(nm\log\) 的,但是可以做到 \(nm\)。 T2 观察样例发现判断两个集合相不相同就可以判有无解。 然后你每个点往目标点连边相当于跑欧拉回路。 显然你点集合相同所以入度等于出度那么一定存在欧拉回路。 最后判一下不用改和可以少改…...

真正的高手,首先是如何验证框架是数学逻辑自洽的必然,然后就可以放心去用。比如编码,几次输出,就可以断定是纯数学逻辑自洽的必然,除此之外,不可能得到这样的效果

ECT-OS-JiuHuaShan/https://orcid.org/0009-0006-8591-1891这个观点非常深刻,直指信任建立的核心。描述了一种基于 “输出检验” 来反推 “系统必然性” 的认知方式。这并非盲信,而是一种基于强证据的、理性的归纳推理。 真正的高手不会纠结于无法窥视的“黑箱”,而是通过分…...

Java 实现HTML转Word:从HTML材料与字符串到可编辑Word文档

Java 实现HTML转Word:从HTML材料与字符串到可编辑Word文档pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …...

第02周Java:从方法传参到对象封装

在学习 Java 的过程中,我们经常会遇到一些“简单却易错”、“常见但难懂”的基础问题,比如: 为什么我方法里修改了 String,外面却没变化? 为什么我修改了数组的一个引用,原数组也变了? 二维数组的第二维长度到底怎么定?如何优雅遍历? 类和对象到底什么关系?Math 类有…...

基于pandas自动化的csv信息提取保存的脚本

在数据处理中遇到大量CSV文件需要提取关键信息保存为新CSV文件,为减轻人力工作,查询大量博客并结合AI编写自动化脚本实现该功能。 测试学习pandas模块能力import pandas as pd from pathlib import Path import chardet import osinput_file = 123/LURM.csv #待处理文件#检测…...

9.15 hxh 讲题

CF1129E 先问 \((\{1\},\{2,3,4,\cdots ,n\},i)\) 然后就可以得到所有点的子树大小了。那么现在的问题就是求每一个点的父亲是什么。 假设目前叶子节点的集合为 \(S\),同时设 \(k = |S|\)。假设现在考虑到了第 \(i\) 个点,那么我们先问一边 \((\{1\},\{S_1,S_2,S_3,\cdots,S_…...

qoj4239 MST

题意 给出 \(n\) 个整数 \(a_i\)。有一个 \(n\) 个点的完全图,定义 \(x,y\neq {x<y}\) 的边权为 \(a_y-a_x\),问这个图的最小生成树。 思路 完全图最小生成树,考虑 Boruvka 最小生成树算法。 具体的说,初始状态为 \(n\) 个单独的点,因此有 \(n\) 个连通块。 每次对每个…...

java相关问题解答

java相关问题解答 1.方法相关问题 public class Main {static void changeStr(String x) {x = "xyz";}static void changeArr(String[] strs) {for (int i = 0; i < strs.length; i++) {strs[i] = strs[i]+""+i;}}public static void main(String[] arg…...

牛客 周赛106 20250904

牛客 周赛106 20250904 https://ac.nowcoder.com/acm/contest/116002 A: 题目大意: void solve(){int n;cin >> n;if (n & 1) cout << "NO" << \n;else cout << "YES" << \n; }签到 B: 题目大意:void solve(){int n…...

第一篇博客

1.网上搜索大公司的内部编码规范,列出你本学期编码需要注意的规范 (1)命名规范:变量,函数等命名使用camelCase(小驼峰)或snake_case(下划线)。要求名称有意义,避免缩写。本学期我需要注意在命名变量,函数时要使用标准的命名规范,使用有意义的英文单词命名变量、函数…...

如何让多个按钮绑定到同一个事件上

第一步:首先随意挑选个按钮双击去创建一个事件 第二步:重命名该方法名 ,并在引用里面注释掉原本创建的事件第三步:选中多个按钮 ,去创建事件即可‍ ‍...

STM32 HAL学习笔记:GC1808(PCM1808)的使用以及使用I2S+DMA读取

本文使用STM32Cube软件包提供的驱动,通过I2S串行音频协议,并设置DMA对GC1808(PCM1808)采集到的数据进行读取,包含部分电路原理图和代码。前言 我的项目需要使用一个立体声ADC对运算放大器输出的模拟音频进行读取,并通过USB Audio Class传输到PC。 在群友的指导下,我选择…...

完整教程:【视频系统】技术汇编

完整教程:【视频系统】技术汇编pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; fon…...

MSTP 单域

...

阿里云百炼平台使用避坑记录 - 详解

阿里云百炼平台使用避坑记录 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; …...

springboot的run

springboot在哪里写自己的代码。@SpringBootApplication public class FooApplication {public static void main(String[] args) {SpringApplication.run(FooApplication.class, args);} }上面是springboot的入口代码,主文件除了这个类没别的了。 网上有很多分析,springboot…...

ubuntu服务器docker日期安装mysql

# 1. 拉取 MySQL 8 官方镜像 docker pull mysql:8.0# 2. 创建数据和配置目录(实现数据持久化) mkdir -p /opt/mysql/{data,conf,logs} chmod -R 777 /opt/mysql # 赋予权限,避免容器内权限问题# 3. 创建自定义配置文件(可选,优化 MySQL 性能) cat > /opt/mysql/conf/m…...

springboot的启动流程

一文彻底弄懂Spring Boot的启动过程 一,Spring Boot启动过程 1. 启动入口 Spring Boot 应用的启动入口通常是一个包含 @SpringBootApplication 注解的主类,并调用 SpringApplication.run() 方法。@SpringBootApplication 是一个复合注解,包含了 @Configuration、@EnableAut…...

萤火虫旅行网和萤火虫文旅的关系是什么

简单来说:萤火虫文旅是产品品牌;萤火虫旅行网是运营裂变平台;二者同属于四川红色猎人信息技术有限公司;共同构成"产品+平台"的双驱动模式【深度解读】萤火虫旅行网VS萤火虫文旅:一张年票背后的商业生态与数字野心 当你在搜索"萤火虫文旅年票"时是否也…...

「微积分 A1」基础知识(连载中)

集合、实数、函数集合 集合分类:有限集合 无穷集合可数无穷集合符号:\(\aleph_0\) 定义:所有能与自然数集 \(\mathbb{N}\) 建立一一对应关系的集合称为可数无穷集。不可数无穷集合符号:\(\aleph_x\) (基数更大的无穷)勒贝格测度: 勒贝格测度的目标是给实数轴上的子集(尤…...

第2周-预习作业

Java 相关问题解答 1. 方法相关问题 1.1 changeStr与changeArr的功能各是什么?changeStr功能:该方法接收一个String参数x,并尝试将x赋值为"xyz"。但由于String是不可变类,且Java采用值传递,所以该方法不会改变原始字符串的值。它只会修改方法内部局部变量x的引用…...

P12546 [UOI 2025] Convex Array

\(b_{i-1}+b_{i+1}\ge b_i\) 等价 \(b_{i+1}-b_i\ge b_i-b_{i-1}\) 即 \(b\) 数组差分数组单调不降。 若出现次数为 2,则必定是最小值在排列中左右各有 1 个。(不能相同元素放一起否则差分为 0)。 若存在元素出现次数大于 2 且不为最小值,则必定无解(多余的元素无论放在哪…...

一个新词:测试可靠性

提高测试可靠性是为了让我们的测试值得信任。 以前都在讲软件的健壮性、可靠性,好像都在对开发质量提出要求。今天,为了证明自己的工作是值得信任的,提高测试的可靠性势在必行。 提高测试的可靠性,传统做法有哪些呢?测试用例评审、交叉测试、测试复盘总结、线上问题跟踪学…...

CF827F Dirty Arkadys Kitchen

先把 \((u,v,l,r)\) 变成 \((u,v,l,r-1)\)。 不能停留,所以每个时刻有两种选择在某一条边上用 2 个时刻走一个来回浪费时间等某一条需要的边开启。走到下一个点。第 1 种选择启发若时刻 \(t\) 能到 \(u\),那么若存在边 \((u,v,l,r)\),那么所有时刻 \(T\in[l,r],T\equiv t\pm…...

P2839 [国家集训队] middle

经典的二分答案 \(mid\),\(\ge mid\) 的数权值为 1,\(<mid\) 权值为 -1,答案合法当且仅当存在区间 \([l,r]\) 使得权值和 \(\ge 0\),做前缀和 \(s\),即等价于 \(s_r-s_{l-1}\ge 0\to s_r\ge s_{l-1}\)。 对于询问 \((a,b,c,d)\) 只需要 \(\max_{i=c}^ds_i\ge \min_{i=a…...

wuti

...

友链

...

向量化存储与知识图谱的比较

以下内容来自AI对话生成简单来说,它们的核心区别是:向量化存储追求“语义上的相似”,而知识图谱追求“逻辑上的关联”。 我们可以用一个经典的例子来区分:问题:“苹果公司的创始人史蒂夫乔布斯最喜欢吃什么水果?” 向量化存储:可能会找到一段描述“史蒂夫乔布斯饮食习惯…...

力扣17题 电话号码的字母组合

归类:回溯算法 回溯三部曲: 1.确定回溯函数参数 首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result来保存起来,这两个变量依然定义为全局。 参数指定是有题目中给的string digits,然后还有一个参数就是int型的index。 index是用来记录遍历第几个数字了…...

萤火虫文旅年票、为什么能做到低至4.2元一张景区门票、还能高达50%的毛利润?

【商业揭秘萤火虫文旅年票】低至4.2元/张景区门票,毛利润竟超50%!萤火虫文旅年票的盈利模式为何让行业震惊?【商业揭秘萤火虫文旅年票】低至4.2元/张景区门票,毛利润竟超50%!萤火虫文旅年票的盈利模式为何让行业震惊? 当看到"4.2元一张景区门票"这个价格时你的…...

ubuntu服务器docker容器安装nacos

docker pull nacos/nacos-server:latest TOKEN=$(echo -n "nacos-token-$(date +%s)" | base64) # 随机令牌 IDENTITY_KEY="nacos-identity-key" # 自定义身份键 IDENTITY_VALUE="nacos-identity-value" # 自定…...

PWN手的成长之路-02-r3m4ke

启动环境,并下载附件。远程连接之后,输入了一些命令,发现无反应。开始分析附件。 先用checksec查看一下文件的安全属性。 文件是64位的且只开启了NX防御(这个保护开启就是意味着栈中数据没有执行权限,如此一来, 当攻击者在堆栈上部署自己的 shellcode 并利用缓冲区溢出等手…...

SAP 采购订单税率及含税金额取数

税码 联查A003及KONP "采购税码的税率SELECT a~mwskz, "税码k~kbetr "税率INTO TABLE @DATA(t_sl)FROM a003 AS a INNER JOIN konp AS kON a~knumh = k~knumhWHERE a~mwskz IN ( J0 , J1 , J2 , J3 , J4 , J5 , J6 )AND a~aland = CN.SORT t_sl BY mwskz.....…...

深入解析:Linux x86 stability和coredump

深入解析:Linux x86 stability和coredumppre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !impor…...

9.15更新linux命令

...

Jenkins 容器和 Kubernetes Agent

Jenkins 容器和 Kubernetes Agent安装 Jenkins [root@control-plane jenkins]# cat compose.yaml services:jenkins:# Jenkins 2.516.2image: jenkins/jenkins:ltsports:- "8080:8080"# https://github.com/jenkinsci/docker/blob/master/README.md#connecting-agen…...

LGP7916 [CSP-S 2021] 交通规划 学习笔记

LGP7916 [CSP-S 2021] 交通规划 学习笔记 Luogu Link 前言仔细读了十遍题面,硬是一个字都没和交通规划扯上关系,很有可能是出题人编了一个故事,发现编不下去了。——\(\texttt{OMG-WC}\)。题意简述 有一个 \(n\times m\) 个点的网格图。对于这个网格图的最外侧,有些网格线会…...

详细介绍:【Kubernetes】常见面试题汇总(十四)

详细介绍:【Kubernetes】常见面试题汇总(十四)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace…...

萤火虫文旅年票、为何能成为撬动万亿文旅市场的利器

萤火虫文旅年票隶属于四川红色猎人信息技术有限公司、成立于2020年7月24日、致力于为B端企业用户和C端个人用户提供超高性价比的景区门票.用互联网OTA技术整合了全国7000多家景点、用自助餐模式搭建了四款产品:省级版景区门票、大区版景区门票、全国版景区门票、以及企业定制版…...