fix(ad): 文字广告 cover 列 NOT NULL 冲突;删除 11.txt 杂项

- modules/cms/ad/service.py add_ad/update_ad:非图片广告(type!=1)cover 从 None 改为空字符串 '',避免插入 NOT NULL 列报 'cover cannot be null';详情展示已兼容空串
- 删除 11.txt(既有审查遗留的 bug 清单文件)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-07 11:07:03 +08:00
co-authored by Claude
parent 969fdbbe7f
commit 1c645cd8d3
2 changed files with 4 additions and 107 deletions
-99
View File
@@ -1,99 +0,0 @@
已按企业级标准完成全量审查(约 1.4 万行 Python,覆盖 25 个业务模块 + 核心框架)。以下按严重程度分类汇总。
---
一、严重 BUG(功能已损坏 / 需立即修复)
1. 上传接口全部 500 —— 缺 request 参数
apps/views/upload.py:15/23/31 三个端点签名是 async def upload_image(file: UploadFile = File(...)),没有 Request 参数。而 permission_required 装饰器(apps/middleware/permission_required.py:11-19)靠 _extract_request()
从参数里找 Request,找不到就传 None 进 _check(None) → get_user_id(None) → None.state 抛 AttributeError → 所有用户(含 admin)上传图片/文件都报 500。
▎ 修复:三个函数都补上 request: Request 参数。
2. Excel 导入功能必崩
apps/services/level.py:190:result = await upload.upload_file(...) 返回的是 JSONResponse(R.ok/R.failed),不是 dict,result.get('code') 抛 AttributeError → 500。职级导入完全不可用(全库仅此一处)。
▎ 修复:改为 result.status_code == 200 后 json.loads(result.body),或让 upload.upload_file 返回 dict。
3. 权限字符串系统性不匹配 → 非 admin 用户全部被拒(RBAC 失效)
- 视图层要求:sys:{module}:page(分页)、sys:{module}:batch_delete(批量删除)——见 apps/views/level.py:19/96 及全部模块。
- 权限节点生成器 apps/services/menu.py:244/274 生成的是 sys:{module}:list 和 sys:{module}:dall。
- 种子数据 document/djangoadmin.fastapi.elevue.sql 存的也是 sys:level:list / sys:level:dall。
- 只有 dept 和 menu 视图用 list(与种子一致),其余 20+ 模块全不一致。
结果:非 admin 用户即使在菜单里勾选了"查询",拿到的是 sys:level:list,但 /level/page 端点校验 sys:level:page → 永远"权限不足"。批量删除节点(dall)更是完全无效。
▎ 修复:统一三处(视图、set_permission、种子 SQL)的权限字符串,建议全部收敛为 page / batch_delete。
---
二、高危(安全隐患 / 影响稳定性)
4. 操作日志中间件同步 HTTP 调用阻塞事件循环
apps/middleware/operation_log.py:74-91 在 finally 里直接(未走 to_thread)调用 record_operation_log → utils/ip.py:59 的 get_ip_location 用同步 urllib.request.urlopen(..., timeout=3) 访问
ipapi.co。每次写操作都可能把整个事件循环卡住最长 3 秒,并发下是稳定性隐患。(login_log 在登录服务里已用 asyncio.to_thread 包装,唯独 operation_log 中间件漏了。)
▎ 修复:把 record_operation_log 包进 asyncio.to_thread;或 IP 定位改为异步/本地库。
5. 分页参数非数字 → 500(全模块)
所有 services 都是 int(request.query_params.get("pageNo", 1)),传 ?pageNo=abc 即抛 ValueError。apps/__init__.py 全局异常只处理了 Authorization/Business/RequestValidation 三类,未捕获 ValueError → 裸 500。22
个模块的列表页全中招。
6. 省市区字段 NOT NULL 冲突 → IntegrityError
apps/forms/member.py:16-18、apps/forms/user.py:20-22 的 province_code/city_code/district_code 是 Optional(None),但模型 apps/models/member.py:66-70、user.py:74-78 是 nullable=False。当 city 数组不足 3 个元素时,字段保持 None →
插入/更新抛 IntegrityError → 500。add_user/add_member/update_* 均受影响。
7. 上传大小校验在整文件读入内存之后
apps/services/upload.py:204-208 先 await file.read() 全量读入内存,再比对 UPLOAD_MAX_SIZE_MB。攻击者传超大文件即可耗尽内存(DoS)。应先用 Content-Length 头预检,或流式分段读取。
8. 存储型 XSS 风险
- .svg 在允许列表中,_sanitize_svg 的 apps/services/upload.py:101-128 正则可被绕过(HTML 实体、foreignObject、命名空间等),且无 X-Content-Type-Options: nosniff 保障(若 Nginx 直接服务 uploads/ 目录则风险实锤)。
- apps/services/notice.py:91/122 富文本 content 只转义 title,正文 HTML 原样存储并原样返回(get_notice_detail 第 76 行),无服务端清洗。公告若展示给前台用户即为存储型 XSS。
9. 密钥/配置管理
- .env 未列入 .gitignore(当前项目是 SVN 且 .env 在根目录),内含 DB 密码 root、Redis 密码 123456、JWT 密钥。若入库即泄露。
- .env 中 FASTAPI_UPLOAD_DIR=E:\DjangoAdmin\... 是写死的异机绝对路径(与当前目录 D:\Temp\... 不符);FASTAPI_IMAGE_URL='http://images.fastapi.elevue' 是假域名。FASTAPI_DEBUG=True、DB_DEBUG=True 默认开。
- 本应用没有挂载静态目录(全库无 StaticFiles/mount),上传文件必须靠外部 Nginx 服务,否则返回的 fileUrl 不可访问——部署时需确认。
---
三、中低危 / 优化点
事务与数据一致性
- apps/services/job.py:216/239 在请求内直接 db.commit(),与 db_session_middleware 统一提交重复(冗余);且 pause_job_by_id 里 job.update_user = job.create_user(应为当前操作人 get_user_id(request))。
- update_user_info(apps/services/user.py:312-329)bulk update 不触发 onupdate,update_time/update_user 不会被更新。
性能
- 权限 JOIN(menu.get_permissions_list_sync,apps/services/menu.py:332-345)对 user_role.user_id/role_id、role_menu.menu_id/role_id、menu.parent_id、user.dept_id 等列全无索引,非 admin 每次请求全表扫描。建议加索引。
- perm_cache.invalidate_role_perms(utils/perm_cache.py:117-118)在异步上下文里直接同步 db.query,未走 to_thread。
- 22 个模块的 get_*_page 分页/回退逻辑高度重复,可抽取公共分页组件。
健壮性
- get_access_token(utils/jwt.py:422)replace('Bearer ', '') 大小写敏感,客户端发 bearer 小写会解析失败。
- captcha 防刷 redis.get 后 incr 存在 TOCTOU 竞态(utils/captcha.py:106-130),并发可绕过上限。
- detail 路由 id 为裸字符串(apps/views/level.py:28),未做 int 校验,非法值直接进 SQL。
- base_model.batch_delete_r(apps/models/base_model.py:176)只要有一个 id 不存在就整体报"记录不存在"(all-or-nothing),行为需与前端确认。
- rich_text.save_file(utils/rich_text.py:58-69)对 URL 中的 .. 未规范化,存在目录穿越到上传目录外的可能(需源文件已存在,低危)。
一致性/代码质量
- JobLog 用 del_flag(apps/models/job_log.py:46),其余模型统一 is_delete;create_user/update_user 用 String(50) 而非 Integer。
- forms/dept.py 校验 type 仅 1-2,而模型注释是 1-4。
- config/sms.py 读的 SMS_URL/SMS_API_KEY... 与 .env 中实际变量(SMS_ALIYUN_*)不一致(当前无用,属预留模块)。
- R.ok 允许 kwargs 覆盖 code,可能产生 {"ok": True, "code": 1} 的矛盾响应;R.failed 默认 code=1 与中间件"code==0 即成功"判断需保持口径一致。
- 无测试套件、无 lint/format 配置(CLAUDE.md 已注明),建议至少补 pytest 覆盖鉴权中间件与 CRUD 主路径。
---
四、建议修复优先级
1. 立即:#1 上传 500、#2 Excel 导入 500、#3 权限字符串统一(功能不可用 / RBAC 失效)
2. 尽快:#4 事件循环阻塞、#5 分页 500、#6 NOT NULL 冲突、#7 上传内存 DoS
3. 安全:#8 富文本/SVG XSS、#9 密钥与 DEBUG 配置
4. 后续:#11 索引、#12 公共分页组件、其余一致性项
需要我直接开始修复吗?可以按优先级逐个处理,或先修最严重的 #1–#3(三个都是几行的改动,我可以一次性完成并自测)。
+4 -8
View File
@@ -92,12 +92,10 @@ def get_ad_detail(ad_id):
# 添加广告 # 添加广告
def add_ad(request, data: AdForm): def add_ad(request, data: AdForm):
# 广告封面 # 广告封面(cover 列 NOT NULL,非图片广告用空字符串占位)
cover = data.cover cover = data.cover or ""
if data.type == 1 and cover: if data.type == 1 and cover:
cover = save_file(cover, "ad") cover = save_file(cover, "ad")
else:
cover = None
# # 广告标题 # # 广告标题
# title = data.title # title = data.title
@@ -124,12 +122,10 @@ def update_ad(request, data: AdForm):
if not ad: if not ad:
return R.failed("记录不存在") return R.failed("记录不存在")
# 广告封面 # 广告封面(cover 列 NOT NULL,非图片广告用空字符串占位)
cover = data.cover cover = data.cover or ""
if data.type == 1 and cover: if data.type == 1 and cover:
cover = save_file(cover, "ad") cover = save_file(cover, "ad")
else:
cover = None
# # 广告标题 # # 广告标题
# title = data.title # title = data.title