51 lines
2.1 KiB
Plaintext
51 lines
2.1 KiB
Plaintext
# ticket_attachment.dspy - 工单附件打开/下载(I5 可见性服务端校验)
|
||
# 入参: msg_id=<消息id>, att_id=<附件id>, download=1 时以附件名下载
|
||
# 客户只能取 visibility='customer' 消息附件;staff/admin 全量。
|
||
|
||
from urllib.parse import quote as _quote
|
||
from aiohttp.web_fileresponse import FileResponse
|
||
|
||
user_id = await get_user()
|
||
if not user_id:
|
||
return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录"}}
|
||
|
||
msg_id = ((params_kw or {}).get('msg_id') or '').strip()
|
||
att_id = ((params_kw or {}).get('att_id') or '').strip()
|
||
download = ((params_kw or {}).get('download') or '').strip()
|
||
|
||
ok, res = await ticket_get_attachment_file(msg_id, att_id, user_id, await get_userorgid())
|
||
if not ok:
|
||
code, msg = res
|
||
return {"widgettype": "Message", "options": {"title": "打开失败", "message": msg}}
|
||
|
||
full_path, fname = res
|
||
fname = str(fname)
|
||
safe_name = _quote(fname)
|
||
headers = {}
|
||
if download:
|
||
headers['Content-Disposition'] = (
|
||
'attachment; filename="%s"; filename*=UTF-8\'\'%s' % (fname, safe_name))
|
||
else:
|
||
# 图片/PDF 等浏览器可直接渲染的类型内联打开
|
||
headers['Content-Disposition'] = (
|
||
'inline; filename="%s"; filename*=UTF-8\'\'%s' % (fname, safe_name))
|
||
|
||
# 文本类显式带 charset=utf-8:aiohttp FileResponse 按扩展名猜 Content-Type 不附
|
||
# charset,text/plain 无 charset 时浏览器按 ISO-8859-1 解 UTF-8 → 中文乱码
|
||
# (2026-09-11 用户报障)。未知扩展名的纯文本(log/conf 等)也归入 text/plain 内联展示。
|
||
import mimetypes as _mt
|
||
_ctype = _mt.guess_type(fname)[0] or ''
|
||
if not _ctype:
|
||
_ext = fname.rsplit('.', 1)[-1].lower() if '.' in fname else ''
|
||
if _ext in ('txt', 'log', 'md', 'csv', 'conf', 'ini', 'yml', 'yaml',
|
||
'py', 'js', 'sh', 'sql', 'json'):
|
||
_ctype = 'text/plain'
|
||
if _ctype.startswith('text/') or _ctype in (
|
||
'application/json', 'application/javascript',
|
||
'application/xml', 'image/svg+xml'):
|
||
_ctype += '; charset=utf-8'
|
||
if _ctype:
|
||
headers['Content-Type'] = _ctype
|
||
|
||
return FileResponse(full_path, headers=headers)
|