281 lines
7.7 KiB
Python
281 lines
7.7 KiB
Python
"""
|
||
二维码生成路由
|
||
生成项目绑定二维码,供手机APP扫描绑定
|
||
"""
|
||
|
||
from fastapi import APIRouter, Response
|
||
from pydantic import BaseModel
|
||
from typing import Optional
|
||
import json
|
||
import base64
|
||
import io
|
||
|
||
# 尝试导入qrcode库
|
||
try:
|
||
import qrcode
|
||
from qrcode.constants import ERROR_CORRECT_H
|
||
QRCODE_AVAILABLE = True
|
||
except ImportError:
|
||
QRCODE_AVAILABLE = False
|
||
|
||
router = APIRouter(prefix="/qrcode", tags=["二维码"])
|
||
|
||
|
||
class ProjectBindConfig(BaseModel):
|
||
"""项目绑定配置"""
|
||
project_id: str
|
||
project_name: Optional[str] = ""
|
||
server: Optional[str] = "ws://sdk.quwanzhi.com:8899/ws/device"
|
||
pwa: Optional[str] = ""
|
||
|
||
|
||
@router.post("/generate")
|
||
async def generate_qrcode(config: ProjectBindConfig):
|
||
"""
|
||
生成项目绑定二维码
|
||
|
||
请求体:
|
||
{
|
||
"project_id": "project_001",
|
||
"project_name": "测试项目",
|
||
"server": "ws://sdk.quwanzhi.com:8899/ws/device",
|
||
"pwa": ""
|
||
}
|
||
|
||
返回: Base64编码的PNG图片
|
||
"""
|
||
if not QRCODE_AVAILABLE:
|
||
return {
|
||
"success": False,
|
||
"message": "服务器未安装qrcode库,请运行: pip install qrcode[pil]"
|
||
}
|
||
|
||
qr_data = {
|
||
"server": config.server,
|
||
"project": config.project_id,
|
||
"project_name": config.project_name,
|
||
}
|
||
if config.pwa:
|
||
qr_data["pwa"] = config.pwa
|
||
qr_content = json.dumps(qr_data, ensure_ascii=False)
|
||
|
||
# 生成二维码
|
||
qr = qrcode.QRCode(
|
||
version=1,
|
||
error_correction=ERROR_CORRECT_H,
|
||
box_size=10,
|
||
border=4,
|
||
)
|
||
qr.add_data(qr_content)
|
||
qr.make(fit=True)
|
||
|
||
img = qr.make_image(fill_color="black", back_color="white")
|
||
|
||
# 转换为Base64
|
||
buffer = io.BytesIO()
|
||
img.save(buffer, format='PNG')
|
||
img_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||
|
||
return {
|
||
"success": True,
|
||
"project_id": config.project_id,
|
||
"project_name": config.project_name,
|
||
"qr_content": qr_content,
|
||
"image_base64": img_base64,
|
||
"image_data_url": f"data:image/png;base64,{img_base64}"
|
||
}
|
||
|
||
|
||
@router.get("/image/{project_id}")
|
||
async def get_qrcode_image(
|
||
project_id: str,
|
||
project_name: str = "",
|
||
server: str = "ws://sdk.quwanzhi.com:8899/ws/device",
|
||
pwa: str = ""
|
||
):
|
||
"""
|
||
直接获取二维码图片
|
||
|
||
示例: GET /api/v3/qrcode/image/project_001?project_name=测试项目
|
||
|
||
返回: PNG图片
|
||
"""
|
||
if not QRCODE_AVAILABLE:
|
||
return Response(
|
||
content="QRCode library not installed",
|
||
media_type="text/plain",
|
||
status_code=500
|
||
)
|
||
|
||
qr_data = {"server": server, "project": project_id, "project_name": project_name}
|
||
if pwa:
|
||
qr_data["pwa"] = pwa
|
||
qr_content = json.dumps(qr_data, ensure_ascii=False)
|
||
|
||
# 生成二维码
|
||
qr = qrcode.QRCode(
|
||
version=1,
|
||
error_correction=ERROR_CORRECT_H,
|
||
box_size=10,
|
||
border=4,
|
||
)
|
||
qr.add_data(qr_content)
|
||
qr.make(fit=True)
|
||
|
||
img = qr.make_image(fill_color="black", back_color="white")
|
||
|
||
# 返回PNG图片
|
||
buffer = io.BytesIO()
|
||
img.save(buffer, format='PNG')
|
||
buffer.seek(0)
|
||
|
||
return Response(
|
||
content=buffer.getvalue(),
|
||
media_type="image/png"
|
||
)
|
||
|
||
|
||
@router.get("/html/{project_id}")
|
||
async def get_qrcode_html(
|
||
project_id: str,
|
||
project_name: str = "",
|
||
server: str = "ws://sdk.quwanzhi.com:8899/ws/device",
|
||
pwa: str = ""
|
||
):
|
||
"""
|
||
获取带样式的二维码HTML页面
|
||
|
||
示例: GET /api/v3/qrcode/html/project_001?project_name=测试项目
|
||
|
||
返回: HTML页面,可直接打印或分享
|
||
"""
|
||
if not QRCODE_AVAILABLE:
|
||
return Response(
|
||
content="<h1>QRCode library not installed</h1>",
|
||
media_type="text/html",
|
||
status_code=500
|
||
)
|
||
|
||
qr_data = {"server": server, "project": project_id, "project_name": project_name}
|
||
if pwa:
|
||
qr_data["pwa"] = pwa
|
||
qr_content = json.dumps(qr_data, ensure_ascii=False)
|
||
|
||
# 生成二维码
|
||
qr = qrcode.QRCode(
|
||
version=1,
|
||
error_correction=ERROR_CORRECT_H,
|
||
box_size=10,
|
||
border=4,
|
||
)
|
||
qr.add_data(qr_content)
|
||
qr.make(fit=True)
|
||
|
||
img = qr.make_image(fill_color="black", back_color="white")
|
||
|
||
# 转换为Base64
|
||
buffer = io.BytesIO()
|
||
img.save(buffer, format='PNG')
|
||
img_base64 = base64.b64encode(buffer.getvalue()).decode()
|
||
|
||
# 生成HTML页面
|
||
html = f"""
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>项目绑定二维码 - {project_name or project_id}</title>
|
||
<style>
|
||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||
body {{
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 20px;
|
||
}}
|
||
.card {{
|
||
background: white;
|
||
border-radius: 20px;
|
||
padding: 40px;
|
||
text-align: center;
|
||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||
max-width: 400px;
|
||
}}
|
||
.logo {{
|
||
font-size: 48px;
|
||
margin-bottom: 20px;
|
||
}}
|
||
h1 {{
|
||
color: #333;
|
||
font-size: 24px;
|
||
margin-bottom: 10px;
|
||
}}
|
||
.project-name {{
|
||
color: #4CAF50;
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
margin-bottom: 30px;
|
||
}}
|
||
.qr-container {{
|
||
background: #f5f5f5;
|
||
padding: 20px;
|
||
border-radius: 16px;
|
||
margin-bottom: 30px;
|
||
}}
|
||
.qr-container img {{
|
||
max-width: 100%;
|
||
height: auto;
|
||
}}
|
||
.instructions {{
|
||
color: #666;
|
||
font-size: 14px;
|
||
line-height: 1.8;
|
||
text-align: left;
|
||
}}
|
||
.instructions li {{
|
||
margin-bottom: 8px;
|
||
}}
|
||
.project-id {{
|
||
background: #e8f5e9;
|
||
color: #2e7d32;
|
||
padding: 8px 16px;
|
||
border-radius: 20px;
|
||
display: inline-block;
|
||
font-size: 12px;
|
||
margin-top: 20px;
|
||
}}
|
||
@media print {{
|
||
body {{ background: white; }}
|
||
.card {{ box-shadow: none; }}
|
||
}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="card">
|
||
<div class="logo">📱</div>
|
||
<h1>工作手机Agent</h1>
|
||
<div class="project-name">{project_name or project_id}</div>
|
||
|
||
<div class="qr-container">
|
||
<img src="data:image/png;base64,{img_base64}" alt="绑定二维码">
|
||
</div>
|
||
|
||
<ul class="instructions">
|
||
<li>打开手机上的「工作手机Agent」APP</li>
|
||
<li>点击「📷 扫码绑定项目」按钮</li>
|
||
<li>扫描上方二维码完成绑定</li>
|
||
<li>点击「连接服务器」开始使用</li>
|
||
</ul>
|
||
|
||
<div class="project-id">项目ID: {project_id}</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
"""
|
||
|
||
return Response(content=html, media_type="text/html")
|