1
0

2 Коммитууд a097d89c02 ... 8b74a552ba

Эзэн SHA1 Мессеж Огноо
  liuchengsen 8b74a552ba 搜索频率降低,一个是用户手动点击搜索,一个是每两秒触发一次,同样字符不重复触发 3 долоо хоног өмнө
  liuchengsen e091f7dadc 修复代码bug 3 долоо хоног өмнө

+ 1 - 1
backend-java/src/main/java/com/pharmacopoeia/repository/UserEventRepository.java

@@ -109,7 +109,7 @@ public interface UserEventRepository extends JpaRepository<UserEvent, Long>,
     Page<Object[]> countByUserIpPaged(@Param("startTime") Instant startTime,
                                        @Param("endTime") Instant endTime, Pageable pageable);
 
-    /** ??????????? */
+    /** 获取所有去重事件类型,供前端筛选 */
     @Query("SELECT DISTINCT e.eventType FROM UserEvent e ORDER BY e.eventType")
     List<String> findDistinctEventTypes();
 }

+ 4 - 0
backend-java/src/main/java/com/pharmacopoeia/security/JwtAuthFilter.java

@@ -75,6 +75,10 @@ public class JwtAuthFilter extends OncePerRequestFilter {
                     log.debug("Token parse failed: {}", e.getMessage());
                 }
             }
+            if (authProperties.isEnabled() && !isPublicPath(request.getRequestURI())) {
+                send401(response, "未提供认证 token");
+                return;
+            }
             filterChain.doFilter(request, response);
         } finally {
             SecurityContextHolder.clearContext();

+ 14 - 10
backend-java/src/main/java/com/pharmacopoeia/security/SecurityConfig.java

@@ -36,18 +36,22 @@ public class SecurityConfig {
                 .csrf(AbstractHttpConfigurer::disable)
                 .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
 
-//        if (authProperties.isEnabled()) {
-//            // 生产模式:所有 /api/** 需要鉴权,公开路径仅限登录和静态资源
-//            http.authorizeHttpRequests(auth -> auth
-//                    .requestMatchers("/health", "/api/v1/auth/**").permitAll()
-//                    .requestMatchers("/static/**", "/").permitAll()
-//                    .requestMatchers("/api/**").authenticated()
-//                    .anyRequest().permitAll()
-//            );
-//        } else {
+        if (authProperties.isEnabled()) {
+            // 生产模式:所有 /api/** 需要鉴权,公开路径仅限登录和静态资源
+            http.authorizeHttpRequests(auth -> auth
+                    .requestMatchers("/health",
+                            "/api/v1/auth/**",
+                            "/api/v1/yaodian/**",
+                            "/api/v1/analytics/**")
+                    .permitAll()
+                    .requestMatchers("/static/**", "/", "/favicon.ico").permitAll()
+                    .requestMatchers("/api/**").authenticated()
+                    .anyRequest().permitAll()
+            );
+        } else {
             // 开发模式:全部放行
             http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
-      //  }
+        }
 
         http.addFilterBefore(rateLimitFilter, UsernamePasswordAuthenticationFilter.class);
         http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

+ 1 - 0
backend-java/src/main/resources/application.yml

@@ -96,5 +96,6 @@ logging:
   level:
     com.pharmacopoeia: INFO
     org.springframework.web: WARN
+    org.springframework.security: INFO
   file:
     path: ${LOG_DIR:/var/log/pharmacopoeia}

+ 38 - 24
static/index.html

@@ -104,9 +104,11 @@
     .drug-detail h3{font-size:15px;color:var(--bk);margin:16px 0 8px}
     .drug-detail .info-row{font-size:13px;color:var(--sub);margin:4px 0}
     .drug-detail .section{background:var(--card);border-radius:8px;padding:12px;margin:8px 0;font-size:14px;line-height:1.8}
-    .search-box{padding:10px 14px;background:#fff;border-bottom:1px solid #e0e0e0;flex-shrink:0}
-    .search-box input{width:100%;padding:10px 14px;border:1px solid #ddd;border-radius:24px;font-size:14px;outline:none;min-height:44px}
+    .search-box{padding:10px 14px;background:#fff;border-bottom:1px solid #e0e0e0;flex-shrink:0;display:flex;gap:8px}
+    .search-box input{flex:1;padding:10px 14px;border:1px solid #ddd;border-radius:24px;font-size:14px;outline:none;min-height:44px}
     .search-box input:focus{border-color:var(--bk);box-shadow:0 0 0 2px rgba(0,47,167,.12)}
+    .search-box button{padding:8px 16px;border:none;border-radius:24px;background:var(--bk);color:#fff;font-size:14px;font-weight:600;cursor:pointer;white-space:nowrap;min-height:44px}
+    .search-box button:hover{background:#1a3fbf}
     .status-bar{font-size:11px;padding:4px 14px;text-align:center;flex-shrink:0}
     .hidden{display:none!important}
     .pager{display:flex;justify-content:center;align-items:center;gap:4px;flex-wrap:wrap}
@@ -155,7 +157,7 @@
   </div>
 </div>
 <div id="tabDrugs" class="hidden">
-  <div class="search-box"><input id="drugSearchInput" placeholder="搜索药品名称..." oninput="searchDrugs()"></div>
+  <div class="search-box"><input id="drugSearchInput" placeholder="搜索药品名称..." oninput="onSearchInput()"><button onclick="searchDrugs()">搜索</button></div>
   <div class="drug-list" id="drugList"></div>
   <div class="drug-pager" id="drugPager"></div>
 </div>
@@ -218,9 +220,8 @@
     try {
       fetch(API_BASE + '/api/v1/analytics/events', {
         method: 'POST', headers: h, body: JSON.stringify(payload), keepalive: true
-      }).then(function(r){
-      }).catch(function(e){ console.error('[track] error', e); });
-    } catch(e) { console.error('[track] exception', e); }
+      }).catch(function(){});
+    } catch(e) {}
   }
 
   // 自动埋点所有 fetch 调用
@@ -404,28 +405,31 @@
     if(!msgs.length)return;
     renderMessages(msgs);
   }
+  /** 统一渲染 AI 消息的内部 HTML(标签+内容+来源+品牌推荐) */
+  function buildAiInnerHtml(m) {
+    var tag=m.intent||'';
+    var tagHtml=tag?'<div class="tag '+tagClass(tag)+'">'+intentLabel(tag)+'</div>':'<div class="tag tag-fallback">AI 药典助手</div>';
+    var h = tagHtml+'<div class="content">'+md2html(m.content||'')+'</div>';
+    if (m.sources && m.sources.length) {
+      sourcesData = m.sources;
+      h += buildSourceTags();
+      sourcesData = [];
+    }
+    if (m.brand_recommendations && m.brand_recommendations.length) {
+      brandRecsData = m.brand_recommendations;
+      h += buildBrandRecs();
+      brandRecsData = [];
+    }
+    return h;
+  }
+
   function renderMessages(msgs){
     var w=document.getElementById('chat').querySelector('.welcome');if(w)w.remove();
     for(var i=0;i<msgs.length;i++){
       var m=msgs[i],div=document.createElement('div');
       div.className='msg '+m.role;
       if(m.role==='user'){div.textContent=m.content}
-      else{
-        var tag=m.intent||'';
-        var tagHtml=tag?'<div class="tag '+tagClass(tag)+'">'+intentLabel(tag)+'</div>':'<div class="tag tag-fallback">AI 药典助手</div>';
-        div.innerHTML=tagHtml+'<div class="content">'+md2html(m.content)+'</div>';
-        // 恢复历史记录中的来源引用和品牌推荐
-        if (m.sources && m.sources.length) {
-          sourcesData = m.sources;
-          div.innerHTML += buildSourceTags();
-          sourcesData = [];
-        }
-        if (m.brand_recommendations && m.brand_recommendations.length) {
-          brandRecsData = m.brand_recommendations;
-          div.innerHTML += buildBrandRecs();
-          brandRecsData = [];
-        }
-      }
+      else{ div.innerHTML = buildAiInnerHtml(m); }
       document.getElementById('chat').appendChild(div);
     }
     document.getElementById('chat').scrollTop=document.getElementById('chat').scrollHeight;
@@ -779,10 +783,20 @@
       renderDrugCards(ALL_DRUGS);
     }catch(e){ALL_DRUGS=[];renderDrugCards([])}
   }
+  var searchTimer=null;
+  var lastSearched='';
+  window.onSearchInput=function(){
+    if(searchTimer)clearTimeout(searchTimer);
+    searchTimer=setTimeout(function(){searchDrugs();},2000);
+  };
   window.searchDrugs=function(){
-    drugKeyword=(document.getElementById('drugSearchInput').value||'').trim();
+    if(searchTimer){clearTimeout(searchTimer);searchTimer=null}
+    var kw=(document.getElementById('drugSearchInput').value||'').trim();
+    if(kw===lastSearched)return;
+    drugKeyword=kw;
     if (drugKeyword) track('search', {keyword: drugKeyword});
     loadDrugList(1);
+    lastSearched=kw;
     return false;
   };
   function renderDrugCards(items){
@@ -844,7 +858,7 @@
     }catch(e){document.getElementById('tabDrugs').innerHTML='<div class="welcome"><p>加载失败</p></div>'}
   };
   window.showDrugList=function(){
-    document.getElementById('tabDrugs').innerHTML='<div class="search-box"><input id="drugSearchInput" placeholder="搜索药品名称..." oninput="searchDrugs()" value="'+escAttr(drugKeyword)+'"></div><div class="drug-list" id="drugList"></div><div class="drug-pager" id="drugPager"></div>';
+    document.getElementById('tabDrugs').innerHTML='<div class="search-box"><input id="drugSearchInput" placeholder="搜索药品名称..." oninput="onSearchInput()" value="'+escAttr(drugKeyword)+'"><button onclick="searchDrugs()">搜索</button></div><div class="drug-list" id="drugList"></div><div class="drug-pager" id="drugPager"></div>';
     renderDrugCards(ALL_DRUGS);
   };
   window.handleJumpPage=function(){