liuchengsen пре 1 месец
родитељ
комит
a1d3eba586

+ 0 - 11
backend-java/src/main/java/com/pharmacopoeia/controller/AuthController.java

@@ -31,15 +31,4 @@ public class AuthController {
                 "user_id", openid
         ));
     }
-
-    @PostMapping("/guest")
-    public ResponseEntity<Map<String, Object>> guestLogin() {
-        String guestId = "guest-" + System.currentTimeMillis();
-        String token = jwtUtil.createToken(guestId);
-        return ResponseEntity.ok(Map.of(
-                "access_token", token,
-                "token_type", "bearer",
-                "user_id", guestId
-        ));
-    }
 }

+ 0 - 9
backend-python/app/api/auth.py

@@ -1,8 +1,6 @@
 from fastapi import APIRouter, HTTPException
 from pydantic import BaseModel
 import httpx
-import uuid
-
 from app.core.security import create_access_token
 from app.core.config import get_settings
 
@@ -39,10 +37,3 @@ async def wechat_login(req: WechatLoginRequest):
     openid = data["openid"]
     access_token = create_access_token(data={"sub": openid})
     return LoginResponse(access_token=access_token, user_id=openid)
-
-
-@router.post("/guest", response_model=LoginResponse)
-async def guest_login():
-    guest_id = f"guest-{uuid.uuid4().hex[:8]}"
-    access_token = create_access_token(data={"sub": guest_id})
-    return LoginResponse(access_token=access_token, user_id=guest_id)

+ 29 - 25
diagnose_online.py

@@ -324,27 +324,20 @@ def extract_drug_name(content: str) -> str:
 # ============================================================
 # API Caller
 # ============================================================
-async def call_production_api(query: str, api_base: str = "https://pharmacopoeia.kailin.com.cn"):
+async def call_production_api(query: str, token: str, api_base: str = "https://pharmacopoeia.kailin.com.cn"):
     """Call the production chat API and capture full SSE response."""
     import httpx
 
-    # Step 1: Get guest token
-    print(f"\n[API] Getting guest token from {api_base}/api/v1/auth/guest ...")
-    async with httpx.AsyncClient(timeout=30) as client:
-        resp = await client.post(f"{api_base}/api/v1/auth/guest")
-        if resp.status_code != 200:
-            print(f"  FAILED: {resp.status_code} {resp.text}")
-            return None
-        token_data = resp.json()
-        token = token_data.get("access_token", "")
-        print(f"  OK: token={token[:20]}...")
+    if not token:
+        print("\n[API] ERROR: No token provided (use --token).")
+        return None
 
-        # Step 2: Call chat stream
-        print(f"\n[API] Calling {api_base}/api/v1/chat/stream ...")
-        print(f"  Query: {query}")
+    print(f"\n[API] Calling {api_base}/api/v1/chat/stream ...")
+    print(f"  Query: {query}")
 
-        events = {"intent": None, "status": [], "content": [], "meta": None}
+    events = {"intent": None, "status": [], "content": [], "meta": None}
 
+    async with httpx.AsyncClient(timeout=120) as client:
         async with client.stream(
             "POST",
             f"{api_base}/api/v1/chat/stream",
@@ -376,22 +369,23 @@ async def call_production_api(query: str, api_base: str = "https://pharmacopoeia
                         except json.JSONDecodeError:
                             pass
 
-        answer = "".join(events["content"])
-        print(f"\n  Answer length: {len(answer)} chars")
+    answer = "".join(events["content"])
+    print(f"\n  Answer length: {len(answer)} chars")
 
-        return {
-            "intent": events["intent"],
-            "sources": events["meta"].get("sources", []) if events["meta"] else [],
-            "answer_preview": answer[:500] + ("..." if len(answer) > 500 else ""),
-            "status": events["status"],
-        }
+    return {
+        "intent": events["intent"],
+        "sources": events["meta"].get("sources", []) if events["meta"] else [],
+        "answer_preview": answer[:500] + ("..." if len(answer) > 500 else ""),
+        "status": events["status"],
+    }
 
 
 # ============================================================
 # Main Diagnostic
 # ============================================================
 async def run_diagnosis(query: str, top_k: int = 20, call_api: bool = False,
-                        api_base: str = "https://pharmacopoeia.kailin.com.cn"):
+                        api_base: str = "https://pharmacopoeia.kailin.com.cn",
+                        token: str = ""):
     print("=" * 80)
     print(f"  RAG Pipeline Diagnosis: '{query}'")
     print("=" * 80)
@@ -542,7 +536,7 @@ async def run_diagnosis(query: str, top_k: int = 20, call_api: bool = False,
         print(f"  PHASE 5: Production API Call")
         print(f"{'='*80}")
         try:
-            api_result = await call_production_api(query, api_base)
+            api_result = await call_production_api(query, token, api_base)
             if api_result:
                 print(f"\n  API Intent: {api_result['intent']}")
                 print(f"  API Sources ({len(api_result['sources'])}):")
@@ -654,11 +648,21 @@ if __name__ == "__main__":
         "--api-base", default="https://pharmacopoeia.kailin.com.cn",
         help="API base URL (default: https://pharmacopoeia.kailin.com.cn)"
     )
+    parser.add_argument(
+        "--token", default="",
+        help="JWT token for API authentication (required when --call-api)"
+    )
     args = parser.parse_args()
 
+    if args.call_api and not args.token:
+        print("ERROR: --token is required when using --call-api")
+        import sys
+        sys.exit(1)
+
     asyncio.run(run_diagnosis(
         query=args.query,
         top_k=args.top_k,
         call_api=args.call_api,
         api_base=args.api_base,
+        token=args.token,
     ))

+ 2 - 3
docs/HANDOVER.md

@@ -132,7 +132,6 @@ DASHSCOPE_RATE_LIMIT_PER_DAY=50000
 ### 认证
 | 方法 | 路径 | 说明 |
 |------|------|------|
-| POST | `/api/v1/auth/guest` | 获取 guest token |
 | POST | `/api/v1/auth/login/wechat` | 微信登录 |
 
 ### 聊天
@@ -165,8 +164,8 @@ DASHSCOPE_RATE_LIMIT_PER_DAY=50000
 | `auth.enabled=true` | 生产模式 | `/api/**` 需要 Bearer token |
 
 **Token 流程:**
-1. 前端页面加载 → 自动调 `/api/v1/auth/guest` 获取 guest token
-2. 发送消息时无 token → 弹登录框(微信小程序内跳转登录页)
+1. 外部系统通过 URL 参数 `?token=xxx` 传入 JWT,前端写入 localStorage
+2. 发送消息时无 token → 弹登录框(微信小程序内跳转登录页,或跳转外部 `login_url`
 3. API 返回 401 → 前端自动弹登录框
 
 **公共路径(无需 token):** `/health`, `/api/v1/auth/**`, `/static/**`, `/`

+ 40 - 41
frontend-web/test.html

@@ -17,52 +17,50 @@
   <input id="q" placeholder="输入问题" style="width:40%">
   <button onclick="go()">发送</button>
 </div>
-<div class="status" id="status">点击发送将自动获取 Token 并发起 SSE 流式对话</div>
+<div class="status" id="status">需在外部系统登录后,通过 URL ?token=xxx 传入或手动粘贴 Token</div>
 <div id="log">等待输入...</div>
+<div style="margin-top:8px">
+  <input id="tokenInput" placeholder="手动粘贴 JWT Token(或从 URL ?token= 读取)" style="width:80%;font-size:12px">
+</div>
 <script>
-let TOKEN = '';
-
-async function fetchToken(apiBase) {
-  // 通过 /api/v1/auth/guest 获取游客 Token
-  const r = await fetch(apiBase + '/api/v1/auth/guest', {
-    method: 'POST',
-    headers: {'Content-Type': 'application/json'},
-    body: JSON.stringify({})
-  });
-  if (!r.ok) throw new Error('获取 Token 失败: HTTP ' + r.status);
-  const d = await r.json();
-  if (!d.access_token && !d.token) throw new Error('返回中无 token 字段');
-  return d.access_token || d.token;
-}
+// 优先从 URL 参数读取 token,其次取输入框
+var TOKEN = (function(){
+  var p = new URLSearchParams(window.location.search);
+  var t = p.get('token') || '';
+  if (t) {
+    setTimeout(function(){
+      document.getElementById('tokenInput').value = t;
+      document.getElementById('tokenInput').placeholder = '已从 URL 加载 Token';
+    }, 0);
+  }
+  return t;
+})();
 
 function log(m) {
-  const el = document.getElementById('log');
+  var el = document.getElementById('log');
   el.textContent += m + '\n';
   el.scrollTop = el.scrollHeight;
 }
 
 async function go() {
-  const apiBase = document.getElementById('apiBase').value.trim().replace(/\/$/, '');
-  const q = document.getElementById('q').value.trim();
+  var apiBase = document.getElementById('apiBase').value.trim().replace(/\/$/, '');
+  var q = document.getElementById('q').value.trim();
   if (!q) return;
 
   document.getElementById('log').textContent = '';
 
-  // 1. 获取 Token(首次或过期时)
+  // 如果没有 URL 传入的 token,尝试读取输入框
   if (!TOKEN) {
-    log('获取 Token 中...');
-    try {
-      TOKEN = await fetchToken(apiBase);
-      log('Token 获取成功');
-    } catch (e) {
-      log('Token 获取失败: ' + e.message);
-      return;
-    }
+    TOKEN = document.getElementById('tokenInput').value.trim();
+  }
+  if (!TOKEN) {
+    log('错误: 请先在外部系统登录获取 Token,粘贴到上方输入框');
+    return;
   }
 
   log('发送: ' + q);
   try {
-    const r = await fetch(apiBase + '/api/v1/chat/stream', {
+    var r = await fetch(apiBase + '/api/v1/chat/stream', {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
@@ -73,29 +71,30 @@ async function go() {
     });
 
     if (r.status === 401) {
-      log('Token 已过期,重新获取...');
+      log('Token 无效或已过期,请重新获取 Token');
       TOKEN = '';
-      return go();
+      return;
     }
     if (!r.ok) throw new Error('HTTP ' + r.status);
 
     log('连接成功,开始接收...');
-    const reader = r.body.getReader();
-    const d = new TextDecoder();
-    let buf = '', txt = '';
-    let curEvent = 'content';
+    var reader = r.body.getReader();
+    var decoder = new TextDecoder();
+    var buf = '', txt = '';
+    var curEvent = 'content';
 
     while (true) {
-      const {value, done} = await reader.read();
-      if (done) break;
-      buf += d.decode(value, {stream: true});
-      const lines = buf.split('\n');
+      var result = await reader.read();
+      if (result.done) break;
+      buf += decoder.decode(result.value, {stream: true});
+      var lines = buf.split('\n');
       buf = lines.pop() || '';
-      for (const l of lines) {
+      for (var i = 0; i < lines.length; i++) {
+        var l = lines[i];
         if (l.indexOf('event:') === 0) { curEvent = l.substring(6).trim(); continue; }
         if (l.indexOf('data:') !== 0) continue;
-        let v = l.slice(5);
-        if (v.startsWith(' ')) v = v.substring(1);
+        var v = l.slice(5);
+        if (v[0] === ' ') v = v.substring(1);
         if (v === '[DONE]') { log('\n[DONE]'); continue; }
         if (curEvent === 'intent') { log('[intent] ' + v); }
         else if (curEvent === 'status') { log('[status] ' + v); }

+ 5 - 28
static/index.html

@@ -156,14 +156,11 @@
   // ============================================================
   // 走 nginx 反向代理,无需暴露 9000 端口
   var API_BASE = '';
-  // 从外部系统读取 token(优先 URL 参数 → localStorage → cookie)
+  // Token 仅从外部系统通过 URL 参数 ?token=xxx 传入,不持久化存储
+  // 退出页面即失效,重新进入必须再次通过 URL 传入
   var TOKEN = (function(){
     var p = new URLSearchParams(window.location.search);
-    var hasTokenParam = p.has('token');
-    var t = hasTokenParam ? (p.get('token') || '') : (localStorage.getItem('pharma_token') || '');
-    if (t) localStorage.setItem('pharma_token', t);
-    else if (hasTokenParam) localStorage.removeItem('pharma_token');
-    return t;
+    return p.get('token') || '';
   })();
 
   function syncAppHeight(){
@@ -191,22 +188,6 @@
     return h;
   }
 
-  // 自动获取 guest token(后端鉴权未就绪时静默降级)
-  async function ensureToken(){
-    if (TOKEN) return true;
-    try {
-      var r = await fetch(API_BASE + '/api/v1/auth/guest', {
-        method: 'POST', headers: {'Content-Type':'application/json'}
-      });
-      if (r.ok) {
-        var d = await r.json();
-        TOKEN = d.access_token || d.token || '';
-        if (TOKEN) { localStorage.setItem('pharma_token', TOKEN); return true; }
-      }
-    } catch(e) {}
-    return false;
-  }
-
   function classifyIntent(q){
     var t = q.trim();
     if (/怎么吃|吃多少|怎么用|孕妇|儿童用量|副作用|不良反应|禁忌|过敏|能不能|可以吗|安全吗|剂量/.test(t)) return 'usage_guide';
@@ -511,11 +492,8 @@
     var input=document.getElementById('userInput'),text=input.value.trim();
     var hasMedia=!!pendingMedia;
     if(!text&&!hasMedia)return;input.value='';if(STREAMING)return;
-    // 强鉴权:无 token 时自动获取,失败则弹登录框
-    if(!TOKEN){
-      var ok = await ensureToken();
-      if(!ok){ showLoginModal(); return; }
-    }
+    // 强鉴权:无 token 直接弹登录框
+    if(!TOKEN){ showLoginModal(); return; }
     doSend(text,hasMedia?pendingMedia:null);
   };
   function doSend(text,media){
@@ -681,7 +659,6 @@
   };
   // 初始化:打开本地数据库 → 恢复对话 → 预加载药品列表
       (async function init(){
-        await ensureToken();
         await openChatDB();
         await restoreChat();
         loadDrugList();