# 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)