feat(phase4): 全部业务模块迁移到 modules/(结构迁移)

- 21 个模块目录(auth/system/dictionary/configuration/cms/member/region/link/notice/job/log/upload/index)
- 每个模块 router.py(合并模块导出多个 router)/schemas.py/service.py/repository.py/models.py
- routers/v1.py 与 routers/__init__.py 改为直接引用 modules,前缀与权限串原样保留
- core/middleware.py 懒加载指向 modules.system.menu / modules.log;core/redis_client.py 指向 modules.job.scheduler
- apps/views|services|forms 全部改为 re-export 垫片
- 迁移后 grep 守门通过:modules/core 无 apps/extensions 残留引用
- 实测登录、27+ 模块接口、写操作 commit、操作日志落库均正常

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-07 10:13:30 +08:00
co-authored by Claude
parent b2c7b93786
commit 64a5504396
156 changed files with 7977 additions and 7533 deletions
+2 -117
View File
@@ -1,117 +1,2 @@
import logging
from sqlalchemy import and_
from apps.constants.constants import OPERATION_SOURCE_LIST, OPERATION_STATUS_LIST
from apps.constants.message import PAGE_SIZE
from apps.models.operation_log import OperationLog
from extensions.sqlalchemy import commit_independently
from extensions import get_db_session
from utils import R
from utils.ip import get_client_ip
from utils.ip2region import get_ip_location
from utils.ua import parse_user_agent
from utils.request import like_contains, parse_id_list
logger = logging.getLogger(__name__)
def record_operation_log(request, title, **kwargs):
"""
记录操作日志
使用独立session写入,不受请求事务commit/rollback影响。
日志是审计数据,无论业务成功或失败都应持久化。
Args:
request: FastAPI Request 对象
title: 操作标题
**kwargs: 其他可选参数
- type: 请求类型
- source: 请求来源(默认0-后台)
- method: 请求方法
- request_method: 请求方式
- url: 请求URL
- param: 请求参数
- result: 请求结果
- consume_time: 请求耗时(毫秒)
- status: 操作状态(0-正常,1-异常)
- error: 错误描述
"""
try:
# 从 request 提取信息
ip = get_client_ip(request)
location = get_ip_location(ip)
ua = request.headers.get('User-Agent', '')
os_name, browser_name = parse_user_agent(ua)
log = OperationLog(
title=title,
type=kwargs.get('type', 0),
source=kwargs.get('source', 0),
method=kwargs.get('method', ''),
request_method=kwargs.get('request_method', request.method),
url=kwargs.get('url', str(request.url)),
param=kwargs.get('param', ''),
result=kwargs.get('result', ''),
ip=ip,
location=location,
os=os_name,
browser=browser_name,
consume_time=kwargs.get('consume_time', 0),
status=kwargs.get('status', 0),
error=kwargs.get('error', ''),
)
commit_independently(log)
return log
except Exception as e:
logger.error(f"记录操作日志异常: {str(e)}")
return None
def get_operation_log_page(request):
db = get_db_session()
# 页码
page = int(request.query_params.get("pageNo", 1))
# 每页数
limit = int(request.query_params.get("pageSize", PAGE_SIZE))
# 实例化查询对象
query = db.query(OperationLog).filter(OperationLog.is_delete == 0)
# 请求标题
title = request.query_params.get('title')
if title:
query = query.filter(OperationLog.title.like(like_contains(title), escape='\\'))
# 请求方式
request_method = request.query_params.get('request_method')
if request_method:
query = query.filter(OperationLog.request_method == request_method)
# 操作状态
status = request.query_params.get('status')
if status:
query = query.filter(OperationLog.status == status)
# 排序
query = query.order_by(OperationLog.id.desc())
# 记录总数
count = query.count()
# 分页查询
log_list = query.limit(limit).offset((page - 1) * limit).all()
# 若当前页超出末页(数据被并发删除),自动回退到第一页
if page > 1 and not log_list:
page = 1
count = query.count()
log_list = query.limit(limit).offset(0).all()
# 实例化结果
result = []
if len(log_list) > 0:
for item in log_list:
data = item.to_dict()
data['source_name'] = OPERATION_SOURCE_LIST.get(item.source)
data['status_name'] = OPERATION_STATUS_LIST.get(item.status)
result.append(data)
return R.ok(data=result, count=count)
def delete_operation_log(ids_str):
return OperationLog.batch_delete_r(ids_str)
# 兼容垫片:由 modules.log.service 迁移
from modules.log.service import * # noqa: F401,F403