refactor: P4c 批次1 服务层改造(notice/param/link/level)

【服务层新范式批量铺开】4 个 uniform 模块:
- service:get_x_page(params)->dict、add/update/status(data)->None、
  delete(ids)->int、get_info 委托 Repository(新建 repository.py)
- 业务失败 raise BizError;router 取参包 R
- level 的 Excel import/export 暂保留 R 返回(后续统一改造)

【工具】codemod 函数级转换(try/except 退格移除、R.failed→BizError、
R.ok→return data)+ black 格式化 + 未用导入清理

【教训】清理脚本 startswith 前缀误删含多名字的 import 行(models_to_list,
model_to_dict 同行)导致 F821,已按 F821 精确补回

【验收】应用启动;4 服务分页查询运行时正常;12 passed;flake8 零告警

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
DjangoAdmin Dev
2026-08-07 15:15:22 +08:00
co-authored by Claude
parent 63afa4e629
commit 3f55f62f0d
17 changed files with 570 additions and 656 deletions
-1
View File
@@ -50,7 +50,6 @@ from src.common.base_db import BaseDB
from src.common.base_model import BaseModel from src.common.base_model import BaseModel
from config import DB_PREFIX from config import DB_PREFIX
from src.core.database import db from src.core.database import db
from src.common.utils.case_converter import convert_camel_to_snake
# ============================================================ # ============================================================
+15
View File
@@ -0,0 +1,15 @@
"""
Link 仓储模块
=============================================================
数据访问层,继承 BaseRepository 获得通用 CRUD 能力。
"""
from src.common.base_repository import BaseRepository
from src.modules.cms.link.models import Link
class LinkRepository(BaseRepository):
"""Link 仓储"""
model = Link
+36 -20
View File
@@ -55,22 +55,24 @@
application.middleware.check_permission - 权限校验中间件 application.middleware.check_permission - 权限校验中间件
""" """
from flask import Blueprint from flask import Blueprint, request
from application.middleware.check_demo import check_demo from application.middleware.check_demo import check_demo
from application.middleware.check_permission import check_permission from application.middleware.check_permission import check_permission
from src.modules.cms.link import service as link from src.modules.cms.link import service as link
from src.common.utils.request import parse_batch_ids from src.common.utils.request import parse_batch_ids, parse_request_body
link_blue = Blueprint('link', __name__, url_prefix='/link')
from src.core import response as R from src.core import response as R
link_blue = Blueprint("link", __name__, url_prefix="/link")
# ============================================================ # ============================================================
# 友链分页查询 # 友链分页查询
# ============================================================ # ============================================================
@link_blue.route('/page', methods=["GET"])
@link_blue.route("/page", methods=["GET"])
@check_permission("sys:link:page") @check_permission("sys:link:page")
def page(): def page():
""" """
@@ -82,15 +84,15 @@ def page():
Returns: Returns:
JSON: 友链分页数据,包含 records(数据列表)、total(总记录数)等 JSON: 友链分页数据,包含 records(数据列表)、total(总记录数)等
""" """
result = link.get_link_page() return R.ok(data=link.get_link_page(request.args))
return result
# ============================================================ # ============================================================
# 友链详情查询 # 友链详情查询
# ============================================================ # ============================================================
@link_blue.route('/detail/<int:id>', methods=["GET"])
@link_blue.route("/detail/<int:id>", methods=["GET"])
@check_permission("sys:link:detail") @check_permission("sys:link:detail")
def detail(id): def detail(id):
""" """
@@ -110,7 +112,8 @@ def detail(id):
# 友链添加 # 友链添加
# ============================================================ # ============================================================
@link_blue.route('/add', methods=["POST"])
@link_blue.route("/add", methods=["POST"])
@check_permission("sys:link:add") @check_permission("sys:link:add")
@check_demo @check_demo
def add(): def add():
@@ -123,15 +126,19 @@ def add():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = link.add_link() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
link.add_link(json_data)
return R.ok(msg="添加成功")
# ============================================================ # ============================================================
# 友链更新 # 友链更新
# ============================================================ # ============================================================
@link_blue.route('/update', methods=["PUT"])
@link_blue.route("/update", methods=["PUT"])
@check_permission("sys:link:update") @check_permission("sys:link:update")
@check_demo @check_demo
def update(): def update():
@@ -144,15 +151,19 @@ def update():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = link.update_link() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
link.update_link(json_data)
return R.ok(msg="更新成功")
# ============================================================ # ============================================================
# 友链删除 # 友链删除
# ============================================================ # ============================================================
@link_blue.route('/delete/<string:id>', methods=["DELETE"])
@link_blue.route("/delete/<string:id>", methods=["DELETE"])
@check_permission("sys:link:delete") @check_permission("sys:link:delete")
@check_demo @check_demo
def delete(id): def delete(id):
@@ -165,15 +176,16 @@ def delete(id):
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = link.delete_links(id) count = link.delete_links(id)
return result return R.ok(msg=f"本次共删除{count}条数据")
# ============================================================ # ============================================================
# 友链批量删除 # 友链批量删除
# ============================================================ # ============================================================
@link_blue.route('/batchDelete', methods=["DELETE"])
@link_blue.route("/batchDelete", methods=["DELETE"])
@check_permission("sys:link:batch_delete") @check_permission("sys:link:batch_delete")
@check_demo @check_demo
def batch_delete(): def batch_delete():
@@ -199,7 +211,8 @@ def batch_delete():
# 友链状态设置 # 友链状态设置
# ============================================================ # ============================================================
@link_blue.route('/status', methods=["PUT"])
@link_blue.route("/status", methods=["PUT"])
@check_permission("sys:link:status") @check_permission("sys:link:status")
@check_demo @check_demo
def status(): def status():
@@ -212,5 +225,8 @@ def status():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = link.update_link_status() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
link.update_link_status(json_data)
return R.ok(msg="更新成功")
+97 -152
View File
@@ -65,19 +65,17 @@
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from flask import request
from sqlalchemy import and_
from src.common.constants.common import PAGE_SIZE from src.common.constants.common import PAGE_SIZE
from src.common.constants.dictionary_constants import LINK_TYPE_LIST, LINK_FORM_LIST from src.common.constants.dictionary_constants import LINK_TYPE_LIST, LINK_FORM_LIST
from src.modules.cms.link.schema import LinkForm, LinkStatusForm from src.modules.cms.link.schema import LinkForm, LinkStatusForm
from src.modules.cms.link.models import Link from src.modules.cms.link.models import Link
from src.core.database import db from src.modules.cms.link.repository import LinkRepository
from src.core import response as R from src.core.exceptions import BizError
from src.common.utils import regular from src.common.utils import regular
from src.common.utils.case_converter import convert_camel_to_snake from src.common.utils.case_converter import convert_camel_to_snake
from src.common.utils.model_utils import models_to_list, model_to_dict from src.common.utils.model_utils import model_to_dict
from src.common.utils.request import parse_request_body, parse_id_list, like_contains from src.common.utils.request import parse_id_list, like_contains
from src.common.utils.rich_text import get_file_url, save_file from src.common.utils.rich_text import get_file_url, save_file
from src.core.security import get_username from src.core.security import get_username
@@ -89,7 +87,8 @@ logger = logging.getLogger(__name__)
# 友链分页查询 # 友链分页查询
# ============================================================ # ============================================================
def get_link_page():
def get_link_page(params: dict) -> dict:
""" """
查询友链分页数据 查询友链分页数据
@@ -112,31 +111,26 @@ def get_link_page():
"pages": 总页数 "pages": 总页数
} }
""" """
try: # 获取分页参数
# 获取分页参数 page_no = int(params.get("pageNo", 1))
page_no = int(request.args.get('pageNo', 1)) page_size = int(params.get("pageSize", PAGE_SIZE))
page_size = int(request.args.get('pageSize', PAGE_SIZE))
# 应用筛选条件 # 应用筛选条件
query = _apply_filters() query = _apply_filters(params)
# 记录总数 # 记录总数
total = query.count() total = query.count()
# 计算总页数 # 计算总页数
pages = (total + page_size - 1) // page_size if total > 0 else 0 pages = (total + page_size - 1) // page_size if total > 0 else 0
# 分页查询 # 分页查询
link_list = query.limit(page_size).offset((page_no - 1) * page_size).all() link_list = query.limit(page_size).offset((page_no - 1) * page_size).all()
# 构建返回数据 # 构建返回数据
page_data = _build_page_data(link_list, total, page_size, page_no, pages) page_data = _build_page_data(link_list, total, page_size, page_no, pages)
return R.ok(data=page_data) return page_data
except Exception as e:
logger.error(f"查询友链分页数据异常: {str(e)}")
return R.failed(msg="查询失败,请稍后重试")
def _apply_filters(): def _apply_filters(params: dict):
""" """
应用查询筛选条件 应用查询筛选条件
@@ -156,22 +150,22 @@ def _apply_filters():
query = Link.query.filter(Link.is_delete == 0) query = Link.query.filter(Link.is_delete == 0)
# 友链名称模糊筛选 # 友链名称模糊筛选
name = request.args.get('name') name = params.get("name")
if name: if name:
query = query.filter(Link.name.like(like_contains(name), escape='\\')) query = query.filter(Link.name.like(like_contains(name), escape="\\"))
# 友链类型精确筛选 # 友链类型精确筛选
link_type = request.args.get('type') link_type = params.get("type")
if link_type: if link_type:
query = query.filter(Link.type == link_type) query = query.filter(Link.type == link_type)
# 友链形式精确筛选 # 友链形式精确筛选
form = request.args.get('form') form = params.get("form")
if form: if form:
query = query.filter(Link.form == form) query = query.filter(Link.form == form)
# 友链状态精确筛选 # 友链状态精确筛选
status = request.args.get('status') status = params.get("status")
if status: if status:
query = query.filter(Link.status == status) query = query.filter(Link.status == status)
@@ -198,27 +192,22 @@ def _build_page_data(link_list, total, page_size, page_no, pages):
for item in link_list: for item in link_list:
data = model_to_dict(item) data = model_to_dict(item)
# 添加友链类型描述 # 添加友链类型描述
data['typeName'] = LINK_TYPE_LIST.get(item.type) data["typeName"] = LINK_TYPE_LIST.get(item.type)
# 添加友链形式描述 # 添加友链形式描述
data['formName'] = LINK_FORM_LIST.get(item.form) data["formName"] = LINK_FORM_LIST.get(item.form)
# 友链图片URL # 友链图片URL
if item.image: if item.image:
data['image'] = get_file_url(item.image) data["image"] = get_file_url(item.image)
records.append(data) records.append(data)
return { return {"records": records, "total": total, "size": page_size, "current": page_no, "pages": pages}
'records': records,
'total': total,
'size': page_size,
'current': page_no,
'pages': pages
}
# ============================================================ # ============================================================
# 友链详情查询 # 友链详情查询
# ============================================================ # ============================================================
def get_link_detail(link_id): def get_link_detail(link_id):
""" """
根据ID查询友链详情(返回字典格式) 根据ID查询友链详情(返回字典格式)
@@ -241,7 +230,7 @@ def get_link_detail(link_id):
data = model_to_dict(link) data = model_to_dict(link)
# 友链图片URL # 友链图片URL
if link.image: if link.image:
data['image'] = get_file_url(link.image) data["image"] = get_file_url(link.image)
return data return data
except Exception as e: except Exception as e:
@@ -253,6 +242,7 @@ def get_link_detail(link_id):
# 友链信息查询(根据ID)- 核心查询方法 # 友链信息查询(根据ID)- 核心查询方法
# ============================================================ # ============================================================
def get_info(link_id: int): def get_info(link_id: int):
""" """
根据ID查询友链信息(返回模型对象) 根据ID查询友链信息(返回模型对象)
@@ -278,26 +268,10 @@ def get_info(link_id: int):
if link: if link:
print(link.name) print(link.name)
""" """
# 参数有效性验证 return LinkRepository.get_by_id(link_id)
if not link_id or link_id <= 0:
return None
try:
# 查询未删除的友链
link = Link.query.filter(
and_(Link.id == link_id, Link.is_delete == 0)
).first()
return link
except Exception as e:
logger.error(f"查询友链信息异常,ID: {link_id}, 错误: {str(e)}")
return None
# ============================================================ def add_link(data: dict) -> None:
# 友链添加
# ============================================================
def add_link():
""" """
添加友链 添加友链
@@ -310,40 +284,29 @@ def add_link():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = LinkForm(data=json_data) form = LinkForm(data=data)
if not form.validate(): if not form.validate():
err_msg = regular.get_err(form) raise BizError(regular.get_err(form))
return R.failed(msg=err_msg)
# 处理友链图片 # 处理友链图片
image = form.data.get('image') image = form.data.get("image")
if image: if image:
form.image.data = save_file(image, "link") form.image.data = save_file(image, "link")
# 创建友链 # 创建友链
link = Link(**form.data) link = Link(**form.data)
link.create_user = get_username() link.create_user = get_username()
link.save() link.save()
return R.ok(msg="添加成功")
except Exception as e:
logger.error(f"添加友链异常: {str(e)}")
return R.failed(msg="添加失败,请稍后重试")
# ============================================================ # ============================================================
# 友链更新 # 友链更新
# ============================================================ # ============================================================
def update_link():
def update_link(data: dict) -> None:
""" """
更新友链 更新友链
@@ -357,57 +320,47 @@ def update_link():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = LinkForm(data=json_data) form = LinkForm(data=data)
if not form.validate(): if not form.validate():
return R.failed(msg=regular.get_err(form)) raise BizError(regular.get_err(form))
# 获取友链ID # 获取友链ID
link_id = form.data.get('id') link_id = form.data.get("id")
if not link_id or int(link_id) <= 0: if not link_id or int(link_id) <= 0:
return R.failed("友链ID无效") raise BizError("友链ID无效")
link_id = int(link_id) link_id = int(link_id)
# 复用 get_info 方法验证友链是否存在 # 复用 get_info 方法验证友链是否存在
link = get_info(link_id) link = get_info(link_id)
if not link: if not link:
return R.failed("友链不存在") raise BizError("友链不存在")
# 处理友链图片 # 处理友链图片
image = form.data.get('image') image = form.data.get("image")
if image: if image:
form.image.data = save_file(image, "link") form.image.data = save_file(image, "link")
# 更新友链信息(移除ID,避免更新主键) # 更新友链信息(移除ID,避免更新主键)
form_data = convert_camel_to_snake(form.data.copy()) form_data = convert_camel_to_snake(form.data.copy())
form_data.pop('id', None) form_data.pop("id", None)
form_data['update_time'] = datetime.now(tz=timezone.utc) form_data["update_time"] = datetime.now(tz=timezone.utc)
form_data['update_user'] = get_username() form_data["update_user"] = get_username()
result = Link.query.filter_by(id=link_id).update(form_data) result = Link.query.filter_by(id=link_id).update(form_data)
if not result: if not result:
return R.failed("更新失败") raise BizError("更新失败")
return R.ok(msg="更新成功")
except Exception as e:
logger.error(f"更新友链异常: {str(e)}")
return R.failed(msg="更新友链失败,请稍后重试")
# ============================================================ # ============================================================
# 友链删除 # 友链删除
# ============================================================ # ============================================================
def delete_links(link_ids):
def delete_links(link_ids: str) -> int:
""" """
删除友链(批量删除) 删除友链(批量删除)
@@ -420,14 +373,18 @@ def delete_links(link_ids):
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
return Link.batch_delete_r(link_ids) link_ids_list = parse_id_list(link_ids)
if not link_ids_list:
raise BizError("无效的记录ID")
return Link.batch_delete(link_ids_list)
# ============================================================ # ============================================================
# 友链状态设置 # 友链状态设置
# ============================================================ # ============================================================
def update_link_status():
def update_link_status(data: dict) -> None:
""" """
设置友链状态 设置友链状态
@@ -436,35 +393,23 @@ def update_link_status():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = LinkStatusForm(data=json_data) form = LinkStatusForm(data=data)
if not form.validate(): if not form.validate():
err_msg = regular.get_err(form) raise BizError(regular.get_err(form))
return R.failed(msg=err_msg)
link_id = int(form.data['id']) link_id = int(form.data["id"])
status = int(form.data['status']) status = int(form.data["status"])
# 复用 get_info 方法验证友链是否存在 # 复用 get_info 方法验证友链是否存在
link = get_info(link_id) link = get_info(link_id)
if not link: if not link:
return R.failed("友链不存在") raise BizError("友链不存在")
# 更新状态 # 更新状态
update_data = {"status": status, "update_time": datetime.now(tz=timezone.utc), "update_user": get_username()} update_data = {"status": status, "update_time": datetime.now(tz=timezone.utc), "update_user": get_username()}
result = Link.query.filter_by(id=link_id).update(update_data) result = Link.query.filter_by(id=link_id).update(update_data)
if not result: if not result:
return R.failed("设置失败") raise BizError("设置失败")
return R.ok(msg="设置成功")
except Exception as e:
logger.error(f"设置友链状态异常: {str(e)}")
return R.failed(msg="设置友链状态失败,请稍后重试")
-1
View File
@@ -50,7 +50,6 @@ from src.common.base_db import BaseDB
from src.common.base_model import BaseModel from src.common.base_model import BaseModel
from config import DB_PREFIX from config import DB_PREFIX
from src.core.database import db from src.core.database import db
from src.common.utils.case_converter import convert_camel_to_snake
# ============================================================ # ============================================================
+15
View File
@@ -0,0 +1,15 @@
"""
Notice 仓储模块
=============================================================
数据访问层,继承 BaseRepository 获得通用 CRUD 能力。
"""
from src.common.base_repository import BaseRepository
from src.modules.notice.models import Notice
class NoticeRepository(BaseRepository):
"""Notice 仓储"""
model = Notice
+29 -17
View File
@@ -53,22 +53,24 @@
依赖模块: application.services.notice - 通知业务逻辑层 依赖模块: application.services.notice - 通知业务逻辑层
application.middleware.check_permission - 权限校验中间件 application.middleware.check_permission - 权限校验中间件
""" """
from flask import Blueprint from flask import Blueprint, request
from application.middleware.check_demo import check_demo from application.middleware.check_demo import check_demo
from application.middleware.check_permission import check_permission from application.middleware.check_permission import check_permission
from src.modules.notice import service as notice from src.modules.notice import service as notice
from src.common.utils.request import parse_batch_ids from src.common.utils.request import parse_batch_ids, parse_request_body
notice_blue = Blueprint('notice', __name__, url_prefix='/notice')
from src.core import response as R from src.core import response as R
notice_blue = Blueprint("notice", __name__, url_prefix="/notice")
# ============================================================ # ============================================================
# 通知分页查询 # 通知分页查询
# ============================================================ # ============================================================
@notice_blue.route('/page', methods=["GET"])
@notice_blue.route("/page", methods=["GET"])
@check_permission("sys:notice:page") @check_permission("sys:notice:page")
def page(): def page():
""" """
@@ -80,15 +82,15 @@ def page():
Returns: Returns:
JSON: 通知分页数据,包含 records(数据列表)、total(总记录数)等 JSON: 通知分页数据,包含 records(数据列表)、total(总记录数)等
""" """
result = notice.get_notice_page() return R.ok(data=notice.get_notice_page(request.args))
return result
# ============================================================ # ============================================================
# 通知详情查询 # 通知详情查询
# ============================================================ # ============================================================
@notice_blue.route('/detail/<int:id>', methods=["GET"])
@notice_blue.route("/detail/<int:id>", methods=["GET"])
@check_permission("sys:notice:detail") @check_permission("sys:notice:detail")
def detail(id): def detail(id):
""" """
@@ -108,7 +110,8 @@ def detail(id):
# 通知添加 # 通知添加
# ============================================================ # ============================================================
@notice_blue.route('/add', methods=["POST"])
@notice_blue.route("/add", methods=["POST"])
@check_permission("sys:notice:add") @check_permission("sys:notice:add")
@check_demo @check_demo
def add(): def add():
@@ -121,15 +124,19 @@ def add():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = notice.add_notice() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
notice.add_notice(json_data)
return R.ok(msg="添加成功")
# ============================================================ # ============================================================
# 通知更新 # 通知更新
# ============================================================ # ============================================================
@notice_blue.route('/update', methods=["PUT"])
@notice_blue.route("/update", methods=["PUT"])
@check_permission("sys:notice:update") @check_permission("sys:notice:update")
@check_demo @check_demo
def update(): def update():
@@ -142,15 +149,19 @@ def update():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = notice.update_notice() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
notice.update_notice(json_data)
return R.ok(msg="更新成功")
# ============================================================ # ============================================================
# 通知删除 # 通知删除
# ============================================================ # ============================================================
@notice_blue.route('/delete/<string:id>', methods=["DELETE"])
@notice_blue.route("/delete/<string:id>", methods=["DELETE"])
@check_permission("sys:notice:delete") @check_permission("sys:notice:delete")
@check_demo @check_demo
def delete(id): def delete(id):
@@ -163,15 +174,16 @@ def delete(id):
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = notice.delete_notices(id) count = notice.delete_notices(id)
return result return R.ok(msg=f"本次共删除{count}条数据")
# ============================================================ # ============================================================
# 通知公告批量删除 # 通知公告批量删除
# ============================================================ # ============================================================
@notice_blue.route('/batchDelete', methods=["DELETE"])
@notice_blue.route("/batchDelete", methods=["DELETE"])
@check_permission("sys:notice:batch_delete") @check_permission("sys:notice:batch_delete")
@check_demo @check_demo
def batch_delete(): def batch_delete():
+89 -133
View File
@@ -63,20 +63,18 @@
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from flask import request
from sqlalchemy import and_
from src.common.constants.common import PAGE_SIZE from src.common.constants.common import PAGE_SIZE
from src.common.constants.dictionary_constants import NOTICE_TYPE_LIST from src.common.constants.dictionary_constants import NOTICE_TYPE_LIST
from src.modules.notice.schema import NoticeForm from src.modules.notice.schema import NoticeForm
from src.modules.notice.models import Notice from src.modules.notice.models import Notice
from src.modules.notice.repository import NoticeRepository
from src.core.exceptions import BizError
from config import FLASK_FILE_URL from config import FLASK_FILE_URL
from src.core.database import db
from src.core import response as R
from src.common.utils import regular from src.common.utils import regular
from src.common.utils.case_converter import convert_camel_to_snake from src.common.utils.case_converter import convert_camel_to_snake
from src.common.utils.model_utils import model_to_dict from src.common.utils.model_utils import model_to_dict
from src.common.utils.request import parse_request_body, parse_id_list, like_contains from src.common.utils.request import parse_id_list, like_contains
from src.common.utils.rich_text import save_content, save_file from src.common.utils.rich_text import save_content, save_file
from src.core.security import get_username from src.core.security import get_username
@@ -88,7 +86,8 @@ logger = logging.getLogger(__name__)
# 通知公告分页查询 # 通知公告分页查询
# ============================================================ # ============================================================
def get_notice_page():
def get_notice_page(params: dict) -> dict:
""" """
查询通知公告分页数据 查询通知公告分页数据
@@ -110,31 +109,26 @@ def get_notice_page():
"pages": 总页数 "pages": 总页数
} }
""" """
try: # 获取分页参数
# 获取分页参数 page_no = int(params.get("pageNo", 1))
page_no = int(request.args.get('pageNo', 1)) page_size = int(params.get("pageSize", PAGE_SIZE))
page_size = int(request.args.get('pageSize', PAGE_SIZE))
# 应用筛选条件 # 应用筛选条件
query = _apply_filters() query = _apply_filters(params)
# 记录总数 # 记录总数
total = query.count() total = query.count()
# 计算总页数 # 计算总页数
pages = (total + page_size - 1) // page_size if total > 0 else 0 pages = (total + page_size - 1) // page_size if total > 0 else 0
# 分页查询 # 分页查询
notice_list = query.limit(page_size).offset((page_no - 1) * page_size).all() notice_list = query.limit(page_size).offset((page_no - 1) * page_size).all()
# 构建返回数据 # 构建返回数据
page_data = _build_page_data(notice_list, total, page_size, page_no, pages) page_data = _build_page_data(notice_list, total, page_size, page_no, pages)
return R.ok(data=page_data) return page_data
except Exception as e:
logger.error(f"查询通知公告分页数据异常: {str(e)}")
return R.failed(msg="查询失败,请稍后重试")
def _apply_filters(): def _apply_filters(params: dict):
""" """
应用查询筛选条件 应用查询筛选条件
@@ -152,17 +146,17 @@ def _apply_filters():
query = Notice.query.filter(Notice.is_delete == 0) query = Notice.query.filter(Notice.is_delete == 0)
# 通知公告标题模糊筛选 # 通知公告标题模糊筛选
title = request.args.get('title') title = params.get("title")
if title: if title:
query = query.filter(Notice.title.like(like_contains(title), escape='\\')) query = query.filter(Notice.title.like(like_contains(title), escape="\\"))
# 通知类型精确筛选 # 通知类型精确筛选
type = request.args.get('type') type = params.get("type")
if type: if type:
query = query.filter(Notice.type == type) query = query.filter(Notice.type == type)
# 通知状态精确筛选 # 通知状态精确筛选
status = request.args.get('status') status = params.get("status")
if status: if status:
query = query.filter(Notice.status == status) query = query.filter(Notice.status == status)
@@ -189,22 +183,17 @@ def _build_page_data(notice_list, total, page_size, page_no, pages):
for notice in notice_list: for notice in notice_list:
data = model_to_dict(notice) data = model_to_dict(notice)
# 添加通知来源描述 # 添加通知来源描述
data['typeText'] = NOTICE_TYPE_LIST.get(notice.type) data["typeText"] = NOTICE_TYPE_LIST.get(notice.type)
records.append(data) records.append(data)
return { return {"records": records, "total": total, "size": page_size, "current": page_no, "pages": pages}
'records': records,
'total': total,
'size': page_size,
'current': page_no,
'pages': pages
}
# ============================================================ # ============================================================
# 通知公告详情查询 # 通知公告详情查询
# ============================================================ # ============================================================
def get_notice_detail(notice_id): def get_notice_detail(notice_id):
""" """
根据ID查询通知公告详情(返回字典格式) 根据ID查询通知公告详情(返回字典格式)
@@ -227,9 +216,9 @@ def get_notice_detail(notice_id):
data = model_to_dict(notice) data = model_to_dict(notice)
# 富文本内容处理:替换图片URL占位符 # 富文本内容处理:替换图片URL占位符
content = notice.content.replace("[IMG_URL]", FLASK_FILE_URL) if notice.content else "" content = notice.content.replace("[IMG_URL]", FLASK_FILE_URL) if notice.content else ""
data['content'] = content data["content"] = content
# 通知来源描述 # 通知来源描述
data['typeText'] = NOTICE_TYPE_LIST.get(notice.type) data["typeText"] = NOTICE_TYPE_LIST.get(notice.type)
return data return data
except Exception as e: except Exception as e:
@@ -241,6 +230,7 @@ def get_notice_detail(notice_id):
# 通知公告信息查询(根据ID)- 核心查询方法 # 通知公告信息查询(根据ID)- 核心查询方法
# ============================================================ # ============================================================
def get_info(notice_id: int): def get_info(notice_id: int):
""" """
根据ID查询通知公告信息(返回模型对象) 根据ID查询通知公告信息(返回模型对象)
@@ -265,26 +255,10 @@ def get_info(notice_id: int):
if notice: if notice:
print(notice.title) print(notice.title)
""" """
# 参数有效性验证 return NoticeRepository.get_by_id(notice_id)
if not notice_id or notice_id <= 0:
return None
try:
# 查询未删除的通知公告
notice = Notice.query.filter(
and_(Notice.id == notice_id, Notice.is_delete == 0)
).first()
return notice
except Exception as e:
logger.error(f"查询通知公告信息异常,ID: {notice_id}, 错误: {str(e)}")
return None
# ============================================================ def add_notice(data: dict) -> None:
# 通知公告添加
# ============================================================
def add_notice():
""" """
添加通知公告 添加通知公告
@@ -297,46 +271,35 @@ def add_notice():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = NoticeForm(data=json_data) form = NoticeForm(data=data)
if not form.validate(): if not form.validate():
err_msg = regular.get_err(form) raise BizError(regular.get_err(form))
return R.failed(msg=err_msg)
# 通知封面处理 # 通知封面处理
cover = form.data.get('cover') cover = form.data.get("cover")
if cover: if cover:
cover = save_file(cover, "notice") cover = save_file(cover, "notice")
form.cover.data = cover form.cover.data = cover
# 处理富文本内容 # 处理富文本内容
title = form.data.get('title', '') title = form.data.get("title", "")
content = form.data.get('content', '') content = form.data.get("content", "")
if content: if content:
form.content.data = save_content(content, title, "notice") form.content.data = save_content(content, title, "notice")
# 创建通知公告 # 创建通知公告
notice = Notice(**form.data) notice = Notice(**form.data)
notice.create_user = get_username() notice.create_user = get_username()
notice.save() notice.save()
return R.ok(msg="添加成功")
except Exception as e:
logger.error(f"添加通知公告异常: {str(e)}")
return R.failed(msg="添加失败,请稍后重试")
# ============================================================ # ============================================================
# 通知公告更新 # 通知公告更新
# ============================================================ # ============================================================
def update_notice():
def update_notice(data: dict) -> None:
""" """
更新通知公告 更新通知公告
@@ -350,63 +313,53 @@ def update_notice():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = NoticeForm(data=json_data) form = NoticeForm(data=data)
if not form.validate(): if not form.validate():
return R.failed(msg=regular.get_err(form)) raise BizError(regular.get_err(form))
# 获取通知公告ID # 获取通知公告ID
notice_id = form.data.get('id') notice_id = form.data.get("id")
if not notice_id or int(notice_id) <= 0: if not notice_id or int(notice_id) <= 0:
return R.failed("通知公告ID无效") raise BizError("通知公告ID无效")
notice_id = int(notice_id) notice_id = int(notice_id)
# 复用 get_info 方法验证通知公告是否存在 # 复用 get_info 方法验证通知公告是否存在
notice = get_info(notice_id) notice = get_info(notice_id)
if not notice: if not notice:
return R.failed("通知公告不存在") raise BizError("通知公告不存在")
# 通知封面处理 # 通知封面处理
cover = form.data.get('cover') cover = form.data.get("cover")
if cover: if cover:
cover = save_file(cover, "notice") cover = save_file(cover, "notice")
form.cover.data = cover form.cover.data = cover
# 处理富文本内容 # 处理富文本内容
title = form.data.get('title', '') title = form.data.get("title", "")
content = form.data.get('content', '') content = form.data.get("content", "")
if content: if content:
form.content.data = save_content(content, title, "notice") form.content.data = save_content(content, title, "notice")
# 更新通知公告信息(移除ID,避免更新主键) # 更新通知公告信息(移除ID,避免更新主键)
form_data = convert_camel_to_snake(form.data.copy()) form_data = convert_camel_to_snake(form.data.copy())
form_data.pop('id', None) form_data.pop("id", None)
form_data['update_time'] = datetime.now(tz=timezone.utc) form_data["update_time"] = datetime.now(tz=timezone.utc)
form_data['update_user'] = get_username() form_data["update_user"] = get_username()
result = Notice.query.filter_by(id=notice_id).update(form_data) result = Notice.query.filter_by(id=notice_id).update(form_data)
if not result: if not result:
return R.failed("更新失败") raise BizError("更新失败")
return R.ok(msg="更新成功")
except Exception as e:
logger.error(f"更新通知公告异常: {str(e)}")
return R.failed(msg="更新通知公告失败,请稍后重试")
# ============================================================ # ============================================================
# 通知公告删除 # 通知公告删除
# ============================================================ # ============================================================
def delete_notices(notice_ids):
def delete_notices(notice_ids: str) -> int:
""" """
删除通知公告(批量删除) 删除通知公告(批量删除)
@@ -419,4 +372,7 @@ def delete_notices(notice_ids):
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
return Notice.batch_delete_r(notice_ids) notice_ids_list = parse_id_list(notice_ids)
if not notice_ids_list:
raise BizError("无效的记录ID")
return Notice.batch_delete(notice_ids_list)
-1
View File
@@ -9,7 +9,6 @@ from src.common.base_db import BaseDB
from src.common.base_model import BaseModel from src.common.base_model import BaseModel
from config import DB_PREFIX from config import DB_PREFIX
from src.core.database import db from src.core.database import db
from src.common.utils.case_converter import convert_camel_to_snake
# ============================================================ # ============================================================
+15
View File
@@ -0,0 +1,15 @@
"""
Param 仓储模块
=============================================================
数据访问层,继承 BaseRepository 获得通用 CRUD 能力。
"""
from src.common.base_repository import BaseRepository
from src.modules.param.models import Param
class ParamRepository(BaseRepository):
"""Param 仓储"""
model = Param
+36 -20
View File
@@ -57,22 +57,24 @@
依赖模块: application.services.param - 参数业务逻辑层 依赖模块: application.services.param - 参数业务逻辑层
application.middleware.check_permission - 权限校验中间件 application.middleware.check_permission - 权限校验中间件
""" """
from flask import Blueprint from flask import Blueprint, request
from application.middleware.check_demo import check_demo from application.middleware.check_demo import check_demo
from application.middleware.check_permission import check_permission from application.middleware.check_permission import check_permission
from src.modules.param import service as param from src.modules.param import service as param
from src.common.utils.request import parse_batch_ids from src.common.utils.request import parse_batch_ids, parse_request_body
param_blue = Blueprint('param', __name__, url_prefix='/param')
from src.core import response as R from src.core import response as R
param_blue = Blueprint("param", __name__, url_prefix="/param")
# ============================================================ # ============================================================
# 参数分页查询 # 参数分页查询
# ============================================================ # ============================================================
@param_blue.route('/page', methods=["GET"])
@param_blue.route("/page", methods=["GET"])
@check_permission("sys:param:page") @check_permission("sys:param:page")
def page(): def page():
""" """
@@ -84,15 +86,15 @@ def page():
Returns: Returns:
JSON: 参数分页数据,包含 records(数据列表)、total(总记录数)等 JSON: 参数分页数据,包含 records(数据列表)、total(总记录数)等
""" """
result = param.get_param_page() return R.ok(data=param.get_param_page(request.args))
return result
# ============================================================ # ============================================================
# 参数详情查询 # 参数详情查询
# ============================================================ # ============================================================
@param_blue.route('/detail/<int:id>', methods=["GET"])
@param_blue.route("/detail/<int:id>", methods=["GET"])
@check_permission("sys:param:detail") @check_permission("sys:param:detail")
def detail(id): def detail(id):
""" """
@@ -112,7 +114,8 @@ def detail(id):
# 参数添加 # 参数添加
# ============================================================ # ============================================================
@param_blue.route('/add', methods=["POST"])
@param_blue.route("/add", methods=["POST"])
@check_permission("sys:param:add") @check_permission("sys:param:add")
@check_demo @check_demo
def add(): def add():
@@ -125,15 +128,19 @@ def add():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = param.add_param() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
param.add_param(json_data)
return R.ok(msg="添加成功")
# ============================================================ # ============================================================
# 参数更新 # 参数更新
# ============================================================ # ============================================================
@param_blue.route('/update', methods=["PUT"])
@param_blue.route("/update", methods=["PUT"])
@check_permission("sys:param:update") @check_permission("sys:param:update")
@check_demo @check_demo
def update(): def update():
@@ -146,15 +153,19 @@ def update():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = param.update_param() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
param.update_param(json_data)
return R.ok(msg="更新成功")
# ============================================================ # ============================================================
# 参数删除 # 参数删除
# ============================================================ # ============================================================
@param_blue.route('/delete/<string:id>', methods=["DELETE"])
@param_blue.route("/delete/<string:id>", methods=["DELETE"])
@check_permission("sys:param:delete") @check_permission("sys:param:delete")
@check_demo @check_demo
def delete(id): def delete(id):
@@ -167,15 +178,16 @@ def delete(id):
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = param.delete_params(id) count = param.delete_params(id)
return result return R.ok(msg=f"本次共删除{count}条数据")
# ============================================================ # ============================================================
# 参数批量删除 # 参数批量删除
# ============================================================ # ============================================================
@param_blue.route('/batchDelete', methods=["DELETE"])
@param_blue.route("/batchDelete", methods=["DELETE"])
@check_permission("sys:param:batch_delete") @check_permission("sys:param:batch_delete")
@check_demo @check_demo
def batch_delete(): def batch_delete():
@@ -201,7 +213,8 @@ def batch_delete():
# 参数状态设置 # 参数状态设置
# ============================================================ # ============================================================
@param_blue.route('/status', methods=["PUT"])
@param_blue.route("/status", methods=["PUT"])
@check_permission("sys:param:status") @check_permission("sys:param:status")
@check_demo @check_demo
def status(): def status():
@@ -214,5 +227,8 @@ def status():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = param.update_param_status() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
param.update_param_status(json_data)
return R.ok(msg="更新成功")
+85 -140
View File
@@ -26,17 +26,15 @@
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from flask import request
from sqlalchemy import and_
from src.common.constants.common import PAGE_SIZE from src.common.constants.common import PAGE_SIZE
from src.modules.param.schema import ParamForm, ParamStatusForm from src.modules.param.schema import ParamForm, ParamStatusForm
from src.modules.param.models import Param from src.modules.param.models import Param
from src.core.database import db from src.modules.param.repository import ParamRepository
from src.core import response as R from src.core.exceptions import BizError
from src.common.utils import regular from src.common.utils import regular
from src.common.utils.model_utils import models_to_list, model_to_dict from src.common.utils.model_utils import models_to_list, model_to_dict
from src.common.utils.request import parse_request_body, parse_id_list, like_contains from src.common.utils.request import parse_id_list, like_contains
from src.core.security import get_username from src.core.security import get_username
# 获取日志记录器 # 获取日志记录器
@@ -47,7 +45,8 @@ logger = logging.getLogger(__name__)
# 参数分页查询 # 参数分页查询
# ============================================================ # ============================================================
def get_param_page():
def get_param_page(params: dict) -> dict:
""" """
查询参数分页数据 查询参数分页数据
@@ -70,31 +69,26 @@ def get_param_page():
"pages": 总页数 "pages": 总页数
} }
""" """
try: # 获取分页参数
# 获取分页参数 page_no = int(params.get("pageNo", 1))
page_no = int(request.args.get('pageNo', 1)) page_size = int(params.get("pageSize", PAGE_SIZE))
page_size = int(request.args.get('pageSize', PAGE_SIZE))
# 应用筛选条件 # 应用筛选条件
query = _apply_filters() query = _apply_filters(params)
# 记录总数 # 记录总数
total = query.count() total = query.count()
# 计算总页数 # 计算总页数
pages = (total + page_size - 1) // page_size if total > 0 else 0 pages = (total + page_size - 1) // page_size if total > 0 else 0
# 分页查询 # 分页查询
param_list = query.limit(page_size).offset((page_no - 1) * page_size).all() param_list = query.limit(page_size).offset((page_no - 1) * page_size).all()
# 构建返回数据 # 构建返回数据
page_data = _build_page_data(param_list, total, page_size, page_no, pages) page_data = _build_page_data(param_list, total, page_size, page_no, pages)
return R.ok(data=page_data) return page_data
except Exception as e:
logger.error(f"查询参数分页数据异常: {str(e)}")
return R.failed(msg="查询失败,请稍后重试")
def _apply_filters(): def _apply_filters(params: dict):
""" """
应用查询筛选条件 应用查询筛选条件
@@ -113,22 +107,22 @@ def _apply_filters():
query = Param.query.filter(Param.is_delete == 0) query = Param.query.filter(Param.is_delete == 0)
# 参数名称模糊筛选 # 参数名称模糊筛选
name = request.args.get('name') name = params.get("name")
if name: if name:
query = query.filter(Param.name.like(like_contains(name), escape='\\')) query = query.filter(Param.name.like(like_contains(name), escape="\\"))
# 参数编码模糊筛选 # 参数编码模糊筛选
code = request.args.get('code') code = params.get("code")
if code: if code:
query = query.filter(Param.code.like(like_contains(code), escape='\\')) query = query.filter(Param.code.like(like_contains(code), escape="\\"))
# 参数类型精确筛选 # 参数类型精确筛选
param_type = request.args.get('type') param_type = params.get("type")
if param_type: if param_type:
query = query.filter(Param.type == param_type) query = query.filter(Param.type == param_type)
# 参数状态精确筛选 # 参数状态精确筛选
status = request.args.get('status') status = params.get("status")
if status: if status:
query = query.filter(Param.status == status) query = query.filter(Param.status == status)
@@ -153,19 +147,14 @@ def _build_page_data(param_list, total, page_size, page_no, pages):
# 将模型对象列表转换为字典列表 # 将模型对象列表转换为字典列表
records = models_to_list(param_list) records = models_to_list(param_list)
return { return {"records": records, "total": total, "size": page_size, "current": page_no, "pages": pages}
'records': records,
'total': total,
'size': page_size,
'current': page_no,
'pages': pages
}
# ============================================================ # ============================================================
# 参数详情查询 # 参数详情查询
# ============================================================ # ============================================================
def get_param_detail(param_id): def get_param_detail(param_id):
""" """
根据ID查询参数详情(返回字典格式) 根据ID查询参数详情(返回字典格式)
@@ -196,6 +185,7 @@ def get_param_detail(param_id):
# 参数信息查询(根据ID)- 核心查询方法 # 参数信息查询(根据ID)- 核心查询方法
# ============================================================ # ============================================================
def get_info(param_id: int): def get_info(param_id: int):
""" """
根据ID查询参数信息(返回模型对象) 根据ID查询参数信息(返回模型对象)
@@ -221,26 +211,10 @@ def get_info(param_id: int):
if param: if param:
print(param.name) print(param.name)
""" """
# 参数有效性验证 return ParamRepository.get_by_id(param_id)
if not param_id or param_id <= 0:
return None
try:
# 查询未删除的参数
param = Param.query.filter(
and_(Param.id == param_id, Param.is_delete == 0)
).first()
return param
except Exception as e:
logger.error(f"查询参数信息异常,ID: {param_id}, 错误: {str(e)}")
return None
# ============================================================ def add_param(data: dict) -> None:
# 参数添加
# ============================================================
def add_param():
""" """
添加参数 添加参数
@@ -252,35 +226,24 @@ def add_param():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = ParamForm(data=json_data) form = ParamForm(data=data)
if not form.validate(): if not form.validate():
err_msg = regular.get_err(form) raise BizError(regular.get_err(form))
return R.failed(msg=err_msg)
# 创建参数 # 创建参数
param = Param(**form.data) param = Param(**form.data)
param.create_user = get_username() param.create_user = get_username()
param.save() param.save()
return R.ok(msg="添加成功")
except Exception as e:
logger.error(f"添加参数异常: {str(e)}")
return R.failed(msg="添加参数失败,请稍后重试")
# ============================================================ # ============================================================
# 参数更新 # 参数更新
# ============================================================ # ============================================================
def update_param():
def update_param(data: dict) -> None:
""" """
更新参数 更新参数
@@ -293,52 +256,42 @@ def update_param():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = ParamForm(data=json_data) form = ParamForm(data=data)
if not form.validate(): if not form.validate():
return R.failed(msg=regular.get_err(form)) raise BizError(regular.get_err(form))
# 获取参数ID # 获取参数ID
param_id = form.data.get('id') param_id = form.data.get("id")
if not param_id or int(param_id) <= 0: if not param_id or int(param_id) <= 0:
return R.failed("参数ID无效") raise BizError("参数ID无效")
param_id = int(param_id) param_id = int(param_id)
# 复用 get_info 方法验证参数是否存在 # 复用 get_info 方法验证参数是否存在
param = get_info(param_id) param = get_info(param_id)
if not param: if not param:
return R.failed("参数不存在") raise BizError("参数不存在")
# 更新参数信息(移除ID,避免更新主键) # 更新参数信息(移除ID,避免更新主键)
form_data = form.data.copy() form_data = form.data.copy()
form_data.pop('id', None) form_data.pop("id", None)
form_data['update_time'] = datetime.now(tz=timezone.utc) form_data["update_time"] = datetime.now(tz=timezone.utc)
form_data['update_user'] = get_username() form_data["update_user"] = get_username()
result = Param.query.filter_by(id=param_id).update(form_data) result = Param.query.filter_by(id=param_id).update(form_data)
if not result: if not result:
return R.failed("更新失败") raise BizError("更新失败")
return R.ok(msg="更新成功")
except Exception as e:
logger.error(f"更新参数异常: {str(e)}")
return R.failed(msg="更新参数失败,请稍后重试")
# ============================================================ # ============================================================
# 参数删除 # 参数删除
# ============================================================ # ============================================================
def delete_params(param_ids):
def delete_params(param_ids: str) -> int:
""" """
删除参数(批量删除) 删除参数(批量删除)
@@ -351,14 +304,18 @@ def delete_params(param_ids):
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
return Param.batch_delete_r(param_ids) param_ids_list = parse_id_list(param_ids)
if not param_ids_list:
raise BizError("无效的记录ID")
return Param.batch_delete(param_ids_list)
# ============================================================ # ============================================================
# 参数状态设置 # 参数状态设置
# ============================================================ # ============================================================
def update_param_status():
def update_param_status(data: dict) -> None:
""" """
设置参数状态 设置参数状态
@@ -367,35 +324,23 @@ def update_param_status():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = ParamStatusForm(data=json_data) form = ParamStatusForm(data=data)
if not form.validate(): if not form.validate():
err_msg = regular.get_err(form) raise BizError(regular.get_err(form))
return R.failed(msg=err_msg)
param_id = int(form.data['id']) param_id = int(form.data["id"])
status = int(form.data['status']) status = int(form.data["status"])
# 复用 get_info 方法验证参数是否存在 # 复用 get_info 方法验证参数是否存在
param = get_info(param_id) param = get_info(param_id)
if not param: if not param:
return R.failed("参数不存在") raise BizError("参数不存在")
# 更新状态 # 更新状态
update_data = {"status": status, "update_time": datetime.now(tz=timezone.utc), "update_user": get_username()} update_data = {"status": status, "update_time": datetime.now(tz=timezone.utc), "update_user": get_username()}
result = Param.query.filter_by(id=param_id).update(update_data) result = Param.query.filter_by(id=param_id).update(update_data)
if not result: if not result:
return R.failed("设置失败") raise BizError("设置失败")
return R.ok(msg="设置成功")
except Exception as e:
logger.error(f"设置参数状态异常: {str(e)}")
return R.failed(msg="设置参数状态失败,请稍后重试")
-1
View File
@@ -50,7 +50,6 @@ from src.common.base_db import BaseDB
from src.common.base_model import BaseModel from src.common.base_model import BaseModel
from config import DB_PREFIX from config import DB_PREFIX
from src.core.database import db from src.core.database import db
from src.common.utils.case_converter import convert_camel_to_snake
# ============================================================ # ============================================================
+15
View File
@@ -0,0 +1,15 @@
"""
Level 仓储模块
=============================================================
数据访问层,继承 BaseRepository 获得通用 CRUD 能力。
"""
from src.common.base_repository import BaseRepository
from src.modules.system.level.models import Level
class LevelRepository(BaseRepository):
"""Level 仓储"""
model = Level
+42 -23
View File
@@ -56,22 +56,24 @@
依赖模块: application.services.level - 职级业务逻辑层 依赖模块: application.services.level - 职级业务逻辑层
application.middleware.check_permission - 权限校验中间件 application.middleware.check_permission - 权限校验中间件
""" """
from flask import Blueprint from flask import Blueprint, request
from application.middleware.check_demo import check_demo from application.middleware.check_demo import check_demo
from application.middleware.check_permission import check_permission from application.middleware.check_permission import check_permission
from src.modules.system.level import service as level from src.modules.system.level import service as level
from src.common.utils.request import parse_batch_ids from src.common.utils.request import parse_batch_ids, parse_request_body
level_blue = Blueprint('level', __name__, url_prefix='/level')
from src.core import response as R from src.core import response as R
level_blue = Blueprint("level", __name__, url_prefix="/level")
# ============================================================ # ============================================================
# 职级分页查询 # 职级分页查询
# ============================================================ # ============================================================
@level_blue.route('/page', methods=["GET"])
@level_blue.route("/page", methods=["GET"])
@check_permission("sys:level:page") @check_permission("sys:level:page")
def page(): def page():
""" """
@@ -83,15 +85,15 @@ def page():
Returns: Returns:
JSON: 职级分页数据,包含 records(数据列表)、total(总记录数)等 JSON: 职级分页数据,包含 records(数据列表)、total(总记录数)等
""" """
result = level.get_level_page() return R.ok(data=level.get_level_page(request.args))
return result
# ============================================================ # ============================================================
# 职级详情查询 # 职级详情查询
# ============================================================ # ============================================================
@level_blue.route('/detail/<int:id>', methods=["GET"])
@level_blue.route("/detail/<int:id>", methods=["GET"])
@check_permission("sys:level:detail") @check_permission("sys:level:detail")
def detail(id): def detail(id):
""" """
@@ -111,7 +113,8 @@ def detail(id):
# 职级添加 # 职级添加
# ============================================================ # ============================================================
@level_blue.route('/add', methods=["POST"])
@level_blue.route("/add", methods=["POST"])
@check_permission("sys:level:add") @check_permission("sys:level:add")
@check_demo @check_demo
def add(): def add():
@@ -124,15 +127,19 @@ def add():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = level.add_level() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
level.add_level(json_data)
return R.ok(msg="添加成功")
# ============================================================ # ============================================================
# 职级更新 # 职级更新
# ============================================================ # ============================================================
@level_blue.route('/update', methods=["PUT"])
@level_blue.route("/update", methods=["PUT"])
@check_permission("sys:level:update") @check_permission("sys:level:update")
@check_demo @check_demo
def update(): def update():
@@ -145,15 +152,19 @@ def update():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = level.update_level() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
level.update_level(json_data)
return R.ok(msg="更新成功")
# ============================================================ # ============================================================
# 职级删除 # 职级删除
# ============================================================ # ============================================================
@level_blue.route('/delete/<string:id>', methods=["DELETE"])
@level_blue.route("/delete/<string:id>", methods=["DELETE"])
@check_permission("sys:level:delete") @check_permission("sys:level:delete")
@check_demo @check_demo
def delete(id): def delete(id):
@@ -166,15 +177,16 @@ def delete(id):
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = level.delete_levels(id) count = level.delete_levels(id)
return result return R.ok(msg=f"本次共删除{count}条数据")
# ============================================================ # ============================================================
# 职级批量删除 # 职级批量删除
# ============================================================ # ============================================================
@level_blue.route('/batchDelete', methods=["DELETE"])
@level_blue.route("/batchDelete", methods=["DELETE"])
@check_permission("sys:level:batch_delete") @check_permission("sys:level:batch_delete")
@check_demo @check_demo
def batch_delete(): def batch_delete():
@@ -200,7 +212,8 @@ def batch_delete():
# 职级状态设置 # 职级状态设置
# ============================================================ # ============================================================
@level_blue.route('/status', methods=["PUT"])
@level_blue.route("/status", methods=["PUT"])
@check_permission("sys:level:status") @check_permission("sys:level:status")
@check_demo @check_demo
def status(): def status():
@@ -213,15 +226,19 @@ def status():
Returns: Returns:
JSON: 操作结果 JSON: 操作结果
""" """
result = level.update_level_status() json_data, err = parse_request_body()
return result if err:
return R.failed(msg=err)
level.update_level_status(json_data)
return R.ok(msg="更新成功")
# ============================================================ # ============================================================
# Excel导入 # Excel导入
# ============================================================ # ============================================================
@level_blue.route('/import', methods=["POST"])
@level_blue.route("/import", methods=["POST"])
@check_permission("sys:level:import") @check_permission("sys:level:import")
@check_demo @check_demo
def imports(): def imports():
@@ -242,7 +259,8 @@ def imports():
# Excel导出 # Excel导出
# ============================================================ # ============================================================
@level_blue.route('/export', methods=["POST"])
@level_blue.route("/export", methods=["POST"])
@check_permission("sys:level:export") @check_permission("sys:level:export")
@check_demo @check_demo
def export(): def export():
@@ -263,7 +281,8 @@ def export():
# 职级列表获取(无分页) # 职级列表获取(无分页)
# ============================================================ # ============================================================
@level_blue.route('/list', methods=["GET"])
@level_blue.route("/list", methods=["GET"])
def get_level_list(): def get_level_list():
""" """
获取职级数据列表(无分页) 获取职级数据列表(无分页)
+95 -146
View File
@@ -71,24 +71,25 @@ import time
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from flask import request from sqlalchemy import and_
from openpyxl.reader.excel import load_workbook from openpyxl.reader.excel import load_workbook
from openpyxl.workbook import Workbook from openpyxl.workbook import Workbook
from sqlalchemy import and_
import utils import utils
from src.common.constants.common import PAGE_SIZE from src.common.constants.common import PAGE_SIZE
from src.modules.system.level.schema import LevelForm, LevelStatusForm from src.modules.system.level.schema import LevelForm, LevelStatusForm
from src.modules.system.level.models import Level from src.modules.system.level.models import Level
from src.modules.system.level.repository import LevelRepository
from src.core import response as R
from src.core.database import db
from src.core.exceptions import BizError
from src.modules.upload import service as upload from src.modules.upload import service as upload
from config import FLASK_TEMP_PATH, FLASK_UPLOAD_DIR from config import FLASK_TEMP_PATH, FLASK_UPLOAD_DIR
from src.core.database import db
from src.core import response as R
from src.common.utils import regular from src.common.utils import regular
from src.common.utils.case_converter import convert_camel_to_snake from src.common.utils.case_converter import convert_camel_to_snake
from src.common.utils.model_utils import models_to_list, model_to_dict from src.common.utils.model_utils import models_to_list, model_to_dict
from src.common.utils.request import parse_request_body, parse_id_list, like_contains from src.common.utils.request import parse_id_list, like_contains
from src.common.utils.rich_text import get_file_url from src.common.utils.rich_text import get_file_url
from src.common.utils.file import mkdir from src.common.utils.file import mkdir
from src.core.security import get_username from src.core.security import get_username
@@ -101,7 +102,8 @@ logger = logging.getLogger(__name__)
# 职级分页查询 # 职级分页查询
# ============================================================ # ============================================================
def get_level_page():
def get_level_page(params: dict) -> dict:
""" """
查询职级分页数据 查询职级分页数据
@@ -122,31 +124,26 @@ def get_level_page():
"pages": 总页数 "pages": 总页数
} }
""" """
try: # 获取分页参数
# 获取分页参数 page_no = int(params.get("pageNo", 1))
page_no = int(request.args.get('pageNo', 1)) page_size = int(params.get("pageSize", PAGE_SIZE))
page_size = int(request.args.get('pageSize', PAGE_SIZE))
# 应用筛选条件 # 应用筛选条件
query = _apply_filters() query = _apply_filters(params)
# 记录总数 # 记录总数
total = query.count() total = query.count()
# 计算总页数 # 计算总页数
pages = (total + page_size - 1) // page_size if total > 0 else 0 pages = (total + page_size - 1) // page_size if total > 0 else 0
# 分页查询 # 分页查询
level_list = query.limit(page_size).offset((page_no - 1) * page_size).all() level_list = query.limit(page_size).offset((page_no - 1) * page_size).all()
# 构建返回数据 # 构建返回数据
page_data = _build_page_data(level_list, total, page_size, page_no, pages) page_data = _build_page_data(level_list, total, page_size, page_no, pages)
return R.ok(data=page_data) return page_data
except Exception as e:
logger.error(f"查询职级分页数据异常: {str(e)}")
return R.failed(msg="查询失败,请稍后重试")
def _apply_filters(): def _apply_filters(params: dict):
""" """
应用查询筛选条件 应用查询筛选条件
@@ -163,12 +160,12 @@ def _apply_filters():
query = Level.query.filter(Level.is_delete == 0) query = Level.query.filter(Level.is_delete == 0)
# 职级名称模糊筛选 # 职级名称模糊筛选
name = request.args.get('name') name = params.get("name")
if name: if name:
query = query.filter(Level.name.like(like_contains(name), escape='\\')) query = query.filter(Level.name.like(like_contains(name), escape="\\"))
# 职级状态精确筛选 # 职级状态精确筛选
status = request.args.get('status') status = params.get("status")
if status: if status:
query = query.filter(Level.status == status) query = query.filter(Level.status == status)
@@ -193,19 +190,14 @@ def _build_page_data(level_list, total, page_size, page_no, pages):
# 将模型对象列表转换为字典列表 # 将模型对象列表转换为字典列表
records = models_to_list(level_list) records = models_to_list(level_list)
return { return {"records": records, "total": total, "size": page_size, "current": page_no, "pages": pages}
'records': records,
'total': total,
'size': page_size,
'current': page_no,
'pages': pages
}
# ============================================================ # ============================================================
# 职级详情查询 # 职级详情查询
# ============================================================ # ============================================================
def get_level_detail(level_id): def get_level_detail(level_id):
""" """
根据ID查询职级详情(返回字典格式) 根据ID查询职级详情(返回字典格式)
@@ -236,6 +228,7 @@ def get_level_detail(level_id):
# 职级信息查询(根据ID)- 核心查询方法 # 职级信息查询(根据ID)- 核心查询方法
# ============================================================ # ============================================================
def get_info(level_id: int): def get_info(level_id: int):
""" """
根据ID查询职级信息(返回模型对象) 根据ID查询职级信息(返回模型对象)
@@ -261,26 +254,10 @@ def get_info(level_id: int):
if level: if level:
print(level.name) print(level.name)
""" """
# 参数有效性验证 return LevelRepository.get_by_id(level_id)
if not level_id or level_id <= 0:
return None
try:
# 查询未删除的职级
level = Level.query.filter(
and_(Level.id == level_id, Level.is_delete == 0)
).first()
return level
except Exception as e:
logger.error(f"查询职级信息异常,ID: {level_id}, 错误: {str(e)}")
return None
# ============================================================ def add_level(data: dict) -> None:
# 职级添加
# ============================================================
def add_level():
""" """
添加职级 添加职级
@@ -292,35 +269,24 @@ def add_level():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = LevelForm(data=json_data) form = LevelForm(data=data)
if not form.validate(): if not form.validate():
err_msg = regular.get_err(form) raise BizError(regular.get_err(form))
return R.failed(msg=err_msg)
# 创建职级 # 创建职级
level = Level(**form.data) level = Level(**form.data)
level.create_user = get_username() level.create_user = get_username()
level.save() level.save()
return R.ok(msg="添加成功")
except Exception as e:
logger.error(f"添加职级异常: {str(e)}")
return R.failed(msg="添加失败,请稍后重试")
# ============================================================ # ============================================================
# 职级更新 # 职级更新
# ============================================================ # ============================================================
def update_level():
def update_level(data: dict) -> None:
""" """
更新职级 更新职级
@@ -333,52 +299,42 @@ def update_level():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = LevelForm(data=json_data) form = LevelForm(data=data)
if not form.validate(): if not form.validate():
return R.failed(msg=regular.get_err(form)) raise BizError(regular.get_err(form))
# 获取职级ID # 获取职级ID
level_id = form.data.get('id') level_id = form.data.get("id")
if not level_id or int(level_id) <= 0: if not level_id or int(level_id) <= 0:
return R.failed("职级ID无效") raise BizError("职级ID无效")
level_id = int(level_id) level_id = int(level_id)
# 复用 get_info 方法验证职级是否存在 # 复用 get_info 方法验证职级是否存在
level = get_info(level_id) level = get_info(level_id)
if not level: if not level:
return R.failed("职级不存在") raise BizError("职级不存在")
# 更新职级信息(移除ID,避免更新主键) # 更新职级信息(移除ID,避免更新主键)
form_data = convert_camel_to_snake(form.data.copy()) form_data = convert_camel_to_snake(form.data.copy())
form_data.pop('id', None) form_data.pop("id", None)
form_data['update_time'] = datetime.now(tz=timezone.utc) form_data["update_time"] = datetime.now(tz=timezone.utc)
form_data['update_user'] = get_username() form_data["update_user"] = get_username()
result = Level.query.filter_by(id=level_id).update(form_data) result = Level.query.filter_by(id=level_id).update(form_data)
if not result: if not result:
return R.failed("更新失败") raise BizError("更新失败")
return R.ok(msg="更新成功")
except Exception as e:
logger.error(f"更新职级异常: {str(e)}")
return R.failed(msg="更新职级失败,请稍后重试")
# ============================================================ # ============================================================
# 职级删除 # 职级删除
# ============================================================ # ============================================================
def delete_levels(level_ids):
def delete_levels(level_ids: str) -> int:
""" """
删除职级(批量删除) 删除职级(批量删除)
@@ -391,14 +347,18 @@ def delete_levels(level_ids):
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
return Level.batch_delete_r(level_ids) level_ids_list = parse_id_list(level_ids)
if not level_ids_list:
raise BizError("无效的记录ID")
return Level.batch_delete(level_ids_list)
# ============================================================ # ============================================================
# 职级状态设置 # 职级状态设置
# ============================================================ # ============================================================
def update_level_status():
def update_level_status(data: dict) -> None:
""" """
设置职级状态 设置职级状态
@@ -407,44 +367,33 @@ def update_level_status():
Returns: Returns:
R: 操作结果 R: 操作结果
""" """
try:
# 获取参数
json_data, error = parse_request_body()
if error:
return R.failed(msg=error)
# 表单验证 # 表单验证
form = LevelStatusForm(data=json_data) form = LevelStatusForm(data=data)
if not form.validate(): if not form.validate():
err_msg = regular.get_err(form) raise BizError(regular.get_err(form))
return R.failed(msg=err_msg)
level_id = int(form.data['id']) level_id = int(form.data["id"])
status = int(form.data['status']) status = int(form.data["status"])
# 复用 get_info 方法验证职级是否存在 # 复用 get_info 方法验证职级是否存在
level = get_info(level_id) level = get_info(level_id)
if not level: if not level:
return R.failed("职级不存在") raise BizError("职级不存在")
# 更新状态 # 更新状态
update_data = {"status": status, "update_time": datetime.now(tz=timezone.utc), "update_user": get_username()} update_data = {"status": status, "update_time": datetime.now(tz=timezone.utc), "update_user": get_username()}
result = Level.query.filter_by(id=level_id).update(update_data) result = Level.query.filter_by(id=level_id).update(update_data)
if not result: if not result:
return R.failed("设置失败") raise BizError("设置失败")
return R.ok(msg="设置成功")
except Exception as e:
logger.error(f"设置职级状态异常: {str(e)}")
return R.failed(msg="设置职级状态失败,请稍后重试")
# ============================================================ # ============================================================
# Excel导入 # Excel导入
# ============================================================ # ============================================================
def import_levels(): def import_levels():
""" """
导入Excel文件批量导入职级 导入Excel文件批量导入职级
@@ -462,7 +411,7 @@ def import_levels():
try: try:
# 上传并读取Excel文件 # 上传并读取Excel文件
file = upload.upload_file(".xls|.xlsx") file = upload.upload_file(".xls|.xlsx")
path = file['filePath'] path = file["filePath"]
wb = load_workbook(path) wb = load_workbook(path)
ws = wb.worksheets[0] ws = wb.worksheets[0]
@@ -502,8 +451,8 @@ def import_levels():
finally: finally:
# 清理导入产生的临时Excel文件 # 清理导入产生的临时Excel文件
try: try:
if 'path' in dir() and path: if "path" in dir() and path:
abs_path = os.path.join(FLASK_UPLOAD_DIR, path.lstrip('/')) abs_path = os.path.join(FLASK_UPLOAD_DIR, path.lstrip("/"))
if os.path.isfile(abs_path): if os.path.isfile(abs_path):
os.remove(abs_path) os.remove(abs_path)
except Exception: except Exception:
@@ -514,6 +463,7 @@ def import_levels():
# Excel导出 # Excel导出
# ============================================================ # ============================================================
def export_levels(): def export_levels():
""" """
导出职级数据到Excel文件 导出职级数据到Excel文件
@@ -533,17 +483,17 @@ def export_levels():
wb = Workbook() wb = Workbook()
ws = wb.worksheets[0] ws = wb.worksheets[0]
# 写入表头 # 写入表头
ws.append(['职级ID', '职级名称', '职级排序', '职级状态', '创建时间', '更新时间']) ws.append(["职级ID", "职级名称", "职级排序", "职级状态", "创建时间", "更新时间"])
# 写入数据 # 写入数据
for data in level_list: for data in level_list:
ws.append(list(data.values())) ws.append(list(data.values()))
# 保存文件 # 保存文件
save_path = FLASK_TEMP_PATH + "/" + time.strftime('%Y%m%d') save_path = FLASK_TEMP_PATH + "/" + time.strftime("%Y%m%d")
mkdir(save_path) mkdir(save_path)
file_name = time.strftime('%Y%m%d%H%M%S') + '%05d' % random.randint(0, 100) + ".xlsx" file_name = time.strftime("%Y%m%d%H%M%S") + "%05d" % random.randint(0, 100) + ".xlsx"
file_path = save_path + "/" + file_name file_path = save_path + "/" + file_name
wb.save(file_path) wb.save(file_path)
@@ -562,6 +512,7 @@ def export_levels():
# 职级列表获取(无分页) # 职级列表获取(无分页)
# ============================================================ # ============================================================
def get_level_list(): def get_level_list():
""" """
获取所有启用的职级列表(无分页) 获取所有启用的职级列表(无分页)
@@ -574,9 +525,7 @@ def get_level_list():
""" """
try: try:
# 查询所有启用的职级 # 查询所有启用的职级
level_list = Level.query.filter( level_list = Level.query.filter(and_(Level.is_delete == 0, Level.status == 1)).order_by(Level.sort.asc()).all()
and_(Level.is_delete == 0, Level.status == 1)
).order_by(Level.sort.asc()).all()
# 转换为字典列表 # 转换为字典列表
result = [] result = []
+1 -1
View File
@@ -354,7 +354,7 @@ from src.core.database import db # 统一 db 单例
| **P1 工具链** | pyproject.toml / requirements-dev.txt / .flake8 / .pre-commit / Makefile / pytest 骨架 | 现有代码 `flake8` 通过(或登记存量告警);`pytest` 空跑通过。**已登记存量告警:154 条(F401×111 / E402×36 / 其余 7);pytest 5 passed** | 低 | | **P1 工具链** | pyproject.toml / requirements-dev.txt / .flake8 / .pre-commit / Makefile / pytest 骨架 | 现有代码 `flake8` 通过(或登记存量告警);`pytest` 空跑通过。**已登记存量告警:154 条(F401×111 / E402×36 / 其余 7);pytest 5 passed** | 低 |
| **P2 core+common** | 建 `src/core/` `src/common/`;extensions/utils 打 shim;模型聚合器建 `src/models_registry.py` | 已验收:22 模型表全量识别且 DB 全部存在;src/ flake8 零告警;pytest 5 passed;应用启动;/login 表单校验与 /user/page 鉴权拦截冒烟正常。**基线修复**:application/models + middleware 原被嵌套 `.gitignore`(`*`)误排除,已补入。**登记既有缺陷**:① `flask db` CLI 因 .env 的 FLASK_VERSION 与 --version 冲突报错(Makefile 已加 FLASK_SKIP_DOTENV 规避);② 本机 Redis 未设密码但 REDIS_AUTH=True,/captcha 报 AUTH 错误(环境问题,非重构) | 高(依赖面最大,已完成) | | **P2 core+common** | 建 `src/core/` `src/common/`;extensions/utils 打 shim;模型聚合器建 `src/models_registry.py` | 已验收:22 模型表全量识别且 DB 全部存在;src/ flake8 零告警;pytest 5 passed;应用启动;/login 表单校验与 /user/page 鉴权拦截冒烟正常。**基线修复**:application/models + middleware 原被嵌套 `.gitignore`(`*`)误排除,已补入。**登记既有缺陷**:① `flask db` CLI 因 .env 的 FLASK_VERSION 与 --version 冲突报错(Makefile 已加 FLASK_SKIP_DOTENV 规避);② 本机 Redis 未设密码但 REDIS_AUTH=True,/captcha 报 AUTH 错误(环境问题,非重构) | 高(依赖面最大,已完成) |
| **P3 模块搬迁** | 按 §2.5 顺序逐组迁移 23 个模块(models→forms→services→views),每批打 shim + 验证 | 已验收:22 模型→19 模块 models.py;23 表单→schema.py;29 服务→service.py(含 executor/scheduler_service/mail_service/sms_service);26 视图→router.py(login+index 合并入 auth)。应用启动、25 蓝图无重复注册(views/__init__.py 已加去重)、pytest 5 passed、接口冒烟(登录校验/鉴权 401)正常 | 中(已完成) | | **P3 模块搬迁** | 按 §2.5 顺序逐组迁移 23 个模块(models→forms→services→views),每批打 shim + 验证 | 已验收:22 模型→19 模块 models.py;23 表单→schema.py;29 服务→service.py(含 executor/scheduler_service/mail_service/sms_service);26 视图→router.py(login+index 合并入 auth)。应用启动、25 蓝图无重复注册(views/__init__.py 已加去重)、pytest 5 passed、接口冒烟(登录校验/鉴权 401)正常 | 中(已完成) |
| **P4 服务层改造** | 26 个服务文件改「入参数据/出参数据」;router 取参包 R;引入 base_service/base_repository + BizError | **P4a 地基已完成**:BizError/base_repository/base_service + 全局处理器接入。**P4b 标杆(position)已完成**:服务函数入参数据/出参数据、raise BizError、repository 层、7 个单测(无需 DB 可测)。**待铺开**:其余 ~25 个服务按 position 范式批量改造 + 单测 | 高(P4a/P4b 已完成,铺开中) | | **P4 服务层改造** | 26 个服务文件改「入参数据/出参数据」;router 取参包 R;引入 base_service/base_repository + BizError | **P4a 地基 ✓**(BizError/base_repository/base_service)。**P4b 标杆 position ✓**(含单测)。**P4c 批次1 ✓**(notice/param/link/level 服务+router 改造,flake8 零告警,服务运行时验证通过)。**待铺开**:其余 ~20 个服务。注:level 的 Excel import/export 仍返回 R(暂保留 R 导入,后续统一改造) | 高(地基+标杆+批次1 完成,铺开中) |
| **P5 DI 注入** | core/permission.py + op_log 接口化;app 工厂注入 menu 服务 | `grep -r "from application" src/core/` 为空;权限/日志行为与现状一致 | 中 | | **P5 DI 注入** | core/permission.py + op_log 接口化;app 工厂注入 menu 服务 | `grep -r "from application" src/core/` 为空;权限/日志行为与现状一致 | 中 |
| **P6 API v1** | routes/v1.py + `/api/v1`;白名单/内部调用/前端前缀同步 | 全部接口路径 `curl /api/v1/...` 通过;`/login` 白名单放行 | 中 | | **P6 API v1** | routes/v1.py + `/api/v1`;白名单/内部调用/前端前缀同步 | 全部接口路径 `curl /api/v1/...` 通过;`/login` 白名单放行 | 中 |
| **P7 切尾** | 删除 application/extensions/utils;`src/wsgi.py`;gunicorn.conf 指向 src.wsgi;更新 CLAUDE.md | `gunicorn -c gunicorn.conf.py src.wsgi:app` 启动;全量回归 | 高 | | **P7 切尾** | 删除 application/extensions/utils;`src/wsgi.py`;gunicorn.conf 指向 src.wsgi;更新 CLAUDE.md | `gunicorn -c gunicorn.conf.py src.wsgi:app` 启动;全量回归 | 高 |