From ba94ceae1315174798ae1967ef62268c6d16cd5b Mon Sep 17 00:00:00 2001
From: Codex Assistant <codex@example.com>
Date: 星期一, 06 十月 2025 22:07:06 +0800
Subject: [PATCH] feat: 评审与活动相关改动 - backend(GraphQL): Activity schema 增加 updateActivityState(id, state);实现 resolver/service 仅更新 state=2 作为逻辑删除 - backend(GraphQL): region.graphqls 新增 Query leafRegions - backend(GraphQL): player.graphqls 的 projectReviewApplications 增加可选参数 regionId - backend(Service): listProjectReviewApplications 绑定 regionId 参数,修复 QueryParameterException - frontend(web): 新增 api/activity.js 的 updateActivityState 并接入 activity-list 删除逻辑 - frontend(web): review-list.vue 权限仅校验登录,移除角色限制;查询参数修正为 name/regionId - frontend(web): 删除未引用的 ActivityList.vue - frontend(web): projectReviewNew.js GraphQL 查询增加 name 参数

---
 backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java |  197 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 195 insertions(+), 2 deletions(-)

diff --git a/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java b/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java
index 96e6413..c0f9452 100644
--- a/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java
+++ b/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java
@@ -10,7 +10,9 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -18,9 +20,12 @@
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 import org.springframework.stereotype.Component;
 import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.web.util.ContentCachingRequestWrapper;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Optional;
 
 /**
@@ -37,6 +42,13 @@
     @Autowired
     private UserRepository userRepository;
     
+    // 鍏佽鍖垮悕璁块棶鐨凣raphQL鏌ヨ鍒楄〃
+    private static final List<String> PUBLIC_GRAPHQL_QUERIES = Arrays.asList(
+        "carouselPlayList",
+        "activities",
+        "hello"
+    );
+    
     /**
      * 鍒ゆ柇鏄惁搴旇璺宠繃JWT璁よ瘉
      */
@@ -48,8 +60,8 @@
             "/test/",
             "/cleanup/",
             "/upload/",
-            "/graphql",
-            "/graphiql"
+            "/graphiql"   // GraphiQL寮�鍙戝伐鍏�
+            // 娉ㄦ剰锛�/graphql 涓嶅湪璺宠繃鍒楄〃涓紝闇�瑕佺敱JWT杩囨护鍣ㄥ鐞嗕互鍖哄垎鍏紑鍜岀鏈夋煡璇�
         };
         
         for (String path : skipPaths) {
@@ -59,6 +71,107 @@
         }
         
         return false;
+    }
+    
+    /**
+     * 妫�鏌ユ槸鍚︽槸GraphQL璇锋眰
+     */
+    private boolean isGraphQLRequest(String requestURI) {
+        return "/graphql".equals(requestURI);
+    }
+    
+    /**
+     * 妫�鏌raphQL璇锋眰鏄惁涓哄叕寮�鏌ヨ锛堜笉闇�瑕佽璇侊級
+     * 鍙湁鏄庣‘鏍囪涓哄叕寮�鐨勬煡璇㈡墠鍏佽鍖垮悕璁块棶
+     */
+    private boolean isPublicGraphQLQuery(HttpServletRequest request) {
+        try {
+            // 妫�鏌ET鍙傛暟涓殑query
+            String query = request.getParameter("query");
+            if (query != null && !query.trim().isEmpty()) {
+                logger.debug("浠庡弬鏁拌幏鍙朑raphQL鏌ヨ: {}", query);
+                return containsPublicQuery(query);
+            }
+            
+            // 瀵逛簬POST璇锋眰锛屽皾璇曡鍙栬姹備綋
+            if ("POST".equalsIgnoreCase(request.getMethod())) {
+                try {
+                    // 浣跨敤CachedBodyHttpServletRequest鏉ヨ鍙栬姹備綋
+                    String body = getRequestBody(request);
+                    if (body != null && !body.trim().isEmpty()) {
+                        logger.debug("浠庤姹備綋鑾峰彇GraphQL鏌ヨ: {}", body);
+                        
+                        // 妫�鏌ontent-Type
+                        String contentType = request.getContentType();
+                        if (contentType != null && contentType.contains("application/graphql")) {
+                            // 瀵逛簬application/graphql锛岃姹備綋鐩存帴鏄疓raphQL鏌ヨ
+                            return containsPublicQuery(body);
+                        } else {
+                            // 瀵逛簬application/json锛岀畝鍗曡В鏋怞SON锛屾煡鎵緌uery瀛楁
+                            if (body.contains("\"query\"")) {
+                                return containsPublicQuery(body);
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    logger.warn("璇诲彇POST璇锋眰浣撳け璐�", e);
+                }
+            }
+            
+            return false;
+        } catch (Exception e) {
+            logger.error("瑙f瀽GraphQL璇锋眰澶辫触", e);
+            return false;
+        }
+    }
+    
+    /**
+     * 璇诲彇璇锋眰浣撳唴瀹�
+     */
+    private String getRequestBody(HttpServletRequest request) {
+        try {
+            if (request instanceof ContentCachingRequestWrapper) {
+                ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
+                byte[] content = wrapper.getContentAsByteArray();
+                if (content.length > 0) {
+                    String encoding = wrapper.getCharacterEncoding() != null ? wrapper.getCharacterEncoding() : "UTF-8";
+                    return new String(content, encoding);
+                }
+            }
+            // 涓嶄粠鍘熷璇锋眰娴佽鍙栵紝閬垮厤涓嬫父缁勪欢鎷夸笉鍒拌姹備綋瀵艰嚧 400
+            return null;
+        } catch (Exception e) {
+            logger.warn("璇诲彇璇锋眰浣撳け璐�", e);
+            return null;
+        }
+    }
+    
+    /**
+     * 妫�鏌ユ煡璇㈠瓧绗︿覆鏄惁鍖呭惈鍏紑鏌ヨ
+     */
+    private boolean containsPublicQuery(String queryString) {
+        if (queryString == null || queryString.trim().isEmpty()) {
+            return false;
+        }
+        
+        // 妫�鏌ユ槸鍚﹀寘鍚叕寮�鏌ヨ
+        for (String publicQuery : PUBLIC_GRAPHQL_QUERIES) {
+            if (queryString.contains(publicQuery)) {
+                logger.debug("妫�娴嬪埌鍏紑GraphQL鏌ヨ: {}", publicQuery);
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    /**
+     * 杩斿洖鏉冮檺閿欒鍝嶅簲
+     */
+    private void sendUnauthorizedResponse(HttpServletResponse response) throws IOException {
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        response.setContentType("application/json;charset=UTF-8");
+        response.getWriter().write("{\"errors\":[{\"message\":\"娌℃湁鏉冮檺璁块棶锛岃鍏堢櫥褰昞",\"extensions\":{\"code\":\"UNAUTHORIZED\"}}]}");
     }
 
     @Override
@@ -83,6 +196,86 @@
             return;
         }
         
+        // 瀵笹raphQL璇锋眰杩涜鐗规畩澶勭悊
+        if (isGraphQLRequest(pathWithoutContext)) {
+            logger.debug("妫�娴嬪埌GraphQL璇锋眰");
+            
+            // 涓篜OST璇锋眰鍖呰璇锋眰浠ユ敮鎸侀噸澶嶈鍙栬姹備綋
+            HttpServletRequest wrappedRequest = request;
+            if ("POST".equalsIgnoreCase(request.getMethod())) {
+                wrappedRequest = new ContentCachingRequestWrapper(request);
+            }
+            
+            // 鍏堟鏌uthorization澶达紝濡傛灉娌℃湁token锛屽啀妫�鏌ユ槸鍚︿负鍏紑鏌ヨ
+            String authHeader = request.getHeader("Authorization");
+            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+                logger.debug("GraphQL璇锋眰娌℃湁Authorization澶达紝灏濊瘯鍒ゅ畾鏄惁涓哄叕寮�鏌ヨ");
+                
+                // 灏濊瘯鍒ゅ畾鍏紑鏌ヨ锛涘鏋滆兘纭畾鏄叕寮�鏌ヨ鍒欐斁琛�
+                if (isPublicGraphQLQuery(wrappedRequest)) {
+                    logger.debug("妫�娴嬪埌鍏紑GraphQL鏌ヨ锛屽厑璁稿尶鍚嶈闂�");
+                    AnonymousAuthenticationToken anonymousAuth = new AnonymousAuthenticationToken(
+                        "anonymous",
+                        "anonymous",
+                        Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))
+                    );
+                    SecurityContextHolder.getContext().setAuthentication(anonymousAuth);
+                    filterChain.doFilter(wrappedRequest, response);
+                    return;
+                }
+                
+                // 鏃犳硶鍙潬璇诲彇/鍒ゅ畾璇锋眰浣撴椂锛岄粯璁や互鍖垮悕韬唤鏀捐鍒癎raphQL灞傦紝鐢卞悇Resolver鑷杩涜鏉冮檺鏍¢獙
+                logger.debug("鏃犳硶鍙潬鍒ゅ畾鏄惁涓哄叕寮�鏌ヨ锛岃缃尶鍚嶈璇佸苟浜ょ敱GraphQL灞傚鐞�");
+                AnonymousAuthenticationToken anonymousAuth = new AnonymousAuthenticationToken(
+                    "anonymous",
+                    "anonymous",
+                    Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS"))
+                );
+                SecurityContextHolder.getContext().setAuthentication(anonymousAuth);
+                filterChain.doFilter(wrappedRequest, response);
+                return;
+            }
+            
+            logger.debug("妫�娴嬪埌闇�瑕佽璇佺殑GraphQL璇锋眰锛屽紑濮嬮獙璇丣WT");
+            
+            String token = authHeader.substring(7);
+            try {
+                Long userId = jwtUtil.getUserIdFromToken(token);
+                if (userId == null || !jwtUtil.validateToken(token)) {
+                    logger.warn("GraphQL璇锋眰鐨凧WT token鏃犳晥");
+                    sendUnauthorizedResponse(response);
+                    return;
+                }
+                
+                // 鏌ユ壘鐢ㄦ埛淇℃伅骞惰缃璇�
+                Optional<User> userOpt = userRepository.findById(userId);
+                if (userOpt.isPresent()) {
+                    User user = userOpt.get();
+                    UsernamePasswordAuthenticationToken authToken = 
+                        new UsernamePasswordAuthenticationToken(
+                            user.getId().toString(), 
+                            null, 
+                            new ArrayList<>()
+                        );
+                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+                    SecurityContextHolder.getContext().setAuthentication(authToken);
+                    logger.debug("GraphQL璇锋眰璁よ瘉鎴愬姛: userId={}", user.getId());
+                } else {
+                    logger.warn("GraphQL璇锋眰鐨勭敤鎴蜂笉瀛樺湪: userId={}", userId);
+                    sendUnauthorizedResponse(response);
+                    return;
+                }
+            } catch (Exception e) {
+                logger.error("GraphQL璇锋眰JWT楠岃瘉澶辫触: {}", e.getMessage());
+                sendUnauthorizedResponse(response);
+                return;
+            }
+            
+            // 缁х画澶勭悊璇锋眰
+            filterChain.doFilter(wrappedRequest, response);
+            return;
+        }
+        
         String authHeader = request.getHeader("Authorization");
         String token = null;
         Long userId = null;

--
Gitblit v1.8.0