From afeeed281e60466b576fbe74d339634cc5d07b82 Mon Sep 17 00:00:00 2001 From: Codex Assistant <codex@example.com> Date: 星期三, 08 十月 2025 08:56:42 +0800 Subject: [PATCH] 修复评审功能和用户认证问题 --- wx/pages/judge/review.js | 520 +++++++------- backend/src/main/java/com/rongyichuang/employee/entity/Employee.java | 12 web/src/views/review-detail.vue | 114 ++ backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java | 32 backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java | 9 backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java | 73 ++ backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java | 76 + backend/src/main/java/com/rongyichuang/auth/service/AuthService.java | 44 + wx/pages/review/index.js | 122 +- backend/src/main/resources/graphql/user.graphqls | 1 wx/pages/registration/registration.js | 15 backend/src/main/resources/graphql/auth.graphqls | 18 backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java | 47 + backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java | 224 +++++ wx/app.js | 196 +---- backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java | 18 backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java | 31 backend/src/main/java/com/rongyichuang/user/service/UserService.java | 16 web/src/views/check-detail.vue | 154 ++++ backend/src/main/resources/application.yml | 2 wx/pages/profile/personal-info.js | 94 ++ backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java | 36 wx/pages/project/detail.js | 106 +- wx/pages/message/message.js | 18 backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java | 103 ++ 25 files changed, 1,373 insertions(+), 708 deletions(-) diff --git a/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java b/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java new file mode 100644 index 0000000..4738fae --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java @@ -0,0 +1,73 @@ +package com.rongyichuang.auth.api; + +import com.rongyichuang.auth.dto.PhoneDecryptResponse; +import com.rongyichuang.auth.dto.WxLoginRequest; +import com.rongyichuang.auth.dto.WxLoginResponse; +import com.rongyichuang.auth.dto.LoginRequest; +import com.rongyichuang.auth.dto.LoginResponse; +import com.rongyichuang.auth.service.AuthService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.MutationMapping; +import org.springframework.stereotype.Controller; + +/** + * 璁よ瘉GraphQL API鎺у埗鍣� + */ +@Controller +public class AuthGraphqlApi { + + @Autowired + private AuthService authService; + + /** + * 寰俊鐧诲綍 + */ + @MutationMapping + public WxLoginResponse wxLogin(@Argument WxLoginRequest input) { + try { + return authService.wxLogin(input); + } catch (Exception e) { + throw new RuntimeException("寰俊鐧诲綍澶辫触: " + e.getMessage(), e); + } + } + + /** + * Web绔櫥褰� + */ + @MutationMapping + public LoginResponse webLogin(@Argument LoginRequest input) { + try { + return authService.login(input); + } catch (Exception e) { + throw new RuntimeException("鐧诲綍澶辫触: " + e.getMessage(), e); + } + } + + /** + * 瑙e瘑寰俊鎵嬫満鍙凤紙鏃х増API锛� + */ + @MutationMapping + public PhoneDecryptResponse decryptPhoneNumber( + @Argument String encryptedData, + @Argument String iv, + @Argument String sessionKey) { + try { + return authService.decryptPhoneNumber(encryptedData, iv, sessionKey); + } catch (Exception e) { + throw new RuntimeException("鎵嬫満鍙疯В瀵嗗け璐�: " + e.getMessage(), e); + } + } + + /** + * 鑾峰彇寰俊鎵嬫満鍙凤紙鏂扮増API锛� + */ + @MutationMapping + public PhoneDecryptResponse getPhoneNumberByCode(@Argument String code) { + try { + return authService.getPhoneNumberByCode(code); + } catch (Exception e) { + throw new RuntimeException("鑾峰彇鎵嬫満鍙峰け璐�: " + e.getMessage(), e); + } + } +} \ No newline at end of file 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 c0f9452..328fe4f 100644 --- a/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java @@ -169,7 +169,7 @@ * 杩斿洖鏉冮檺閿欒鍝嶅簲 */ private void sendUnauthorizedResponse(HttpServletResponse response) throws IOException { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"errors\":[{\"message\":\"娌℃湁鏉冮檺璁块棶锛岃鍏堢櫥褰昞",\"extensions\":{\"code\":\"UNAUTHORIZED\"}}]}"); } @@ -247,23 +247,37 @@ return; } - // 鏌ユ壘鐢ㄦ埛淇℃伅骞惰缃璇� - Optional<User> userOpt = userRepository.findById(userId); - if (userOpt.isPresent()) { - User user = userOpt.get(); + // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛锛堣礋鏁扮敤鎴稩D锛� + if (userId < 0) { + // 鍖垮悕鐢ㄦ埛锛岃缃壒娈婄殑璁よ瘉淇℃伅 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - user.getId().toString(), + "anonymous_" + userId, null, - new ArrayList<>() + Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS")) ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); - logger.debug("GraphQL璇锋眰璁よ瘉鎴愬姛: userId={}", user.getId()); + logger.debug("GraphQL璇锋眰鍖垮悕鐢ㄦ埛璁よ瘉鎴愬姛: userId={}", userId); } else { - logger.warn("GraphQL璇锋眰鐨勭敤鎴蜂笉瀛樺湪: userId={}", userId); - sendUnauthorizedResponse(response); - return; + // 姝e父鐢ㄦ埛锛屾煡鎵剧敤鎴蜂俊鎭苟璁剧疆璁よ瘉 + 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()); @@ -306,26 +320,40 @@ if (jwtUtil.validateToken(token)) { logger.debug("Token楠岃瘉鎴愬姛锛屾煡鎵剧敤鎴蜂俊鎭�"); - // 鏌ユ壘鐢ㄦ埛淇℃伅 - Optional<User> userOpt = userRepository.findById(userId); - if (userOpt.isPresent()) { - User user = userOpt.get(); - logger.debug("鎵惧埌鐢ㄦ埛: userId={}, phone={}", user.getId(), user.getPhone()); - - // 鍒涘缓璁よ瘉瀵硅薄 + // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛锛堣礋鏁扮敤鎴稩D锛� + if (userId < 0) { + // 鍖垮悕鐢ㄦ埛锛岃缃壒娈婄殑璁よ瘉淇℃伅 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - user.getId().toString(), + "anonymous_" + userId, null, - new ArrayList<>() // 鏆傛椂涓嶈缃潈闄愶紝鍚庣画鍙互鏍规嵁瑙掕壊璁剧疆 + Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS")) ); - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); - - logger.info("鐢ㄦ埛璁よ瘉鎴愬姛: userId={}, phone={}", user.getId(), user.getPhone()); + logger.info("鍖垮悕鐢ㄦ埛璁よ瘉鎴愬姛: userId={}", userId); } else { - logger.warn("鐢ㄦ埛涓嶅瓨鍦�: userId={}", userId); + // 姝e父鐢ㄦ埛锛屾煡鎵剧敤鎴蜂俊鎭� + Optional<User> userOpt = userRepository.findById(userId); + if (userOpt.isPresent()) { + User user = userOpt.get(); + logger.debug("鎵惧埌鐢ㄦ埛: userId={}, phone={}", user.getId(), user.getPhone()); + + // 鍒涘缓璁よ瘉瀵硅薄 + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken( + user.getId().toString(), + null, + new ArrayList<>() // 鏆傛椂涓嶈缃潈闄愶紝鍚庣画鍙互鏍规嵁瑙掕壊璁剧疆 + ); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + + logger.info("鐢ㄦ埛璁よ瘉鎴愬姛: userId={}, phone={}", user.getId(), user.getPhone()); + } else { + logger.warn("鐢ㄦ埛涓嶅瓨鍦�: userId={}", userId); + } } } else { logger.warn("Token楠岃瘉澶辫触"); diff --git a/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java b/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java index e9deed6..756a3a4 100644 --- a/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java +++ b/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java @@ -293,9 +293,9 @@ logger.warn("鈿狅笍 unionid涓虹┖鎴栫敤鎴峰凡鎵惧埌锛岃烦杩噓nionid鏌ユ壘"); } - // 4. 濡傛灉閮芥病鎵惧埌鐢ㄦ埛锛屼笉鍒涘缓鏂扮敤鎴凤紝鍙褰曠櫥褰曚俊鎭� + // 4. 濡傛灉閮芥病鎵惧埌鐢ㄦ埛锛屼负灏忕▼搴忚闂垱寤哄尶鍚嶇敤鎴峰璞★紙涓嶄繚瀛樺埌鏁版嵁搴擄級 if (user == null) { - logger.info("鏈壘鍒扮幇鏈夌敤鎴凤紝鏅�氳闂敤鎴蜂笉鍒涘缓user璁板綍锛屽彧璁板綍鐧诲綍淇℃伅"); + logger.info("鏈壘鍒扮幇鏈夌敤鎴凤紝涓哄皬绋嬪簭璁块棶鍒涘缓鍖垮悕鐢ㄦ埛瀵硅薄"); logger.info("璁块棶鐢ㄦ埛淇℃伅:"); logger.info("- openid: {}", wxLoginRequest.getWxOpenid()); logger.info("- unionid: {}", wxLoginRequest.getWxUnionid()); @@ -321,17 +321,44 @@ throw new RuntimeException("鍒涘缓鐧诲綍璁板綍澶辫触: " + e.getMessage(), e); } - // 杩斿洖璁块棶鐢ㄦ埛鐨勫搷搴旓紙鏃犵敤鎴蜂俊鎭紝鏃爐oken锛� + // 6. 涓哄尶鍚嶇敤鎴风敓鎴愮壒娈婄殑JWT token锛堜娇鐢ㄤ笓闂ㄧ殑wxopenid瀛楁锛� + logger.info("姝ラ4: 涓哄尶鍚嶇敤鎴风敓鎴怞WT token"); + String token = null; + try { + // 浣跨敤鐗规畩鐨勭敤鎴稩D锛堣礋鏁帮級鏉ユ爣璇嗗尶鍚嶇敤鎴凤紝閬垮厤涓庣湡瀹炵敤鎴稩D鍐茬獊 + Long anonymousUserId = -Math.abs(wxLoginRequest.getWxOpenid().hashCode()) % 1000000L; + // 浣跨敤鏂扮殑涓夊弬鏁版柟娉曪細userId, phone(null), wxopenid + token = jwtUtil.generateToken(anonymousUserId, null, wxLoginRequest.getWxOpenid()); + logger.info("鉁� 鎴愬姛鐢熸垚鍖垮悕鐢ㄦ埛JWT token锛寃xopenid: {}", wxLoginRequest.getWxOpenid()); + } catch (Exception e) { + logger.error("鉂� 鐢熸垚鍖垮悕鐢ㄦ埛JWT token澶辫触: {}", e.getMessage()); + logger.error("寮傚父鍫嗘爤:", e); + throw new RuntimeException("鐢熸垚JWT token澶辫触: " + e.getMessage(), e); + } + + // 7. 鏋勫缓鍖垮悕鐢ㄦ埛淇℃伅 + logger.info("姝ラ5: 鏋勫缓鍖垮悕鐢ㄦ埛淇℃伅鍝嶅簲"); + LoginResponse.UserInfo userInfo = new LoginResponse.UserInfo( + null, // userId涓簄ull锛岃〃绀哄尶鍚嶇敤鎴� + "寰俊鐢ㄦ埛", // 榛樿鍚嶇О + null, // 娌℃湁鐢佃瘽鍙风爜 + "anonymous" // 鐢ㄦ埛绫诲瀷涓哄尶鍚� + ); + + // 杩斿洖鍖垮悕鐢ㄦ埛鐨勫搷搴旓紙鍖呭惈鐢ㄦ埛淇℃伅鍜宼oken锛� WxLoginResponse response = new WxLoginResponse(); response.setSuccess(true); - response.setMessage("璁块棶鎴愬姛"); + response.setMessage("鍖垮悕璁块棶鎴愬姛"); + response.setToken(token); + response.setUserInfo(userInfo); response.setSessionKey(wxLoginRequest.getSessionKey()); response.setIsNewUser(false); response.setHasEmployee(false); response.setHasJudge(false); response.setHasPlayer(false); + response.setLoginRecordId(loginRecord.getId()); - logger.info("=== 寰俊璁块棶娴佺▼瀹屾垚锛堟棤鐢ㄦ埛鍒涘缓锛� ==="); + logger.info("=== 寰俊鍖垮悕璁块棶娴佺▼瀹屾垚 ==="); return response; } @@ -408,12 +435,13 @@ // 6. 鐢熸垚JWT token logger.info("姝ラ5: 鐢熸垚JWT token"); - String tokenIdentifier = user.getPhone() != null ? user.getPhone() : user.getWxOpenid(); - logger.info("Token鏍囪瘑绗�: {}", tokenIdentifier); + logger.info("鐢ㄦ埛鎵嬫満鍙�: {}", user.getPhone()); + logger.info("鐢ㄦ埛wxopenid: {}", user.getWxOpenid()); String token = null; try { - token = jwtUtil.generateToken(user.getId(), tokenIdentifier); + // 浣跨敤鏂扮殑涓夊弬鏁版柟娉曪細userId, phone, wxopenid + token = jwtUtil.generateToken(user.getId(), user.getPhone(), user.getWxOpenid()); logger.info("鉁� 鎴愬姛鐢熸垚JWT token锛岄暱搴�: {}", token != null ? token.length() : 0); } catch (Exception e) { logger.error("鉂� 鐢熸垚JWT token澶辫触: {}", e.getMessage()); diff --git a/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java b/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java index 91b828e..489d0f8 100644 --- a/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java +++ b/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java @@ -25,21 +25,37 @@ private long jwtExpiration; /** - * 鐢熸垚JWT token + * 鐢熸垚JWT token锛堟棫鐗堟湰锛屼繚鎸佸吋瀹规�э級 */ public String generateToken(Long userId, String phone) { + return generateToken(userId, phone, null); + } + + /** + * 鐢熸垚JWT token锛堟柊鐗堟湰锛屾敮鎸亀xopenid锛� + */ + public String generateToken(Long userId, String phone, String wxopenid) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + jwtExpiration); SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes()); - return Jwts.builder() + JwtBuilder builder = Jwts.builder() .setSubject(userId.toString()) - .claim("phone", phone) .setIssuedAt(now) - .setExpiration(expiryDate) - .signWith(key, SignatureAlgorithm.HS256) - .compact(); + .setExpiration(expiryDate); + + // 鍙湁褰損hone涓嶄负null鏃舵墠娣诲姞phone claim + if (phone != null) { + builder.claim("phone", phone); + } + + // 鍙湁褰搘xopenid涓嶄负null鏃舵墠娣诲姞wxopenid claim + if (wxopenid != null) { + builder.claim("wxopenid", wxopenid); + } + + return builder.signWith(key, SignatureAlgorithm.HS256).compact(); } /** @@ -59,6 +75,14 @@ } /** + * 浠巘oken涓幏鍙栧井淇penid + */ + public String getWxOpenidFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return claims.get("wxopenid", String.class); + } + + /** * 楠岃瘉token鏄惁鏈夋晥 */ public boolean validateToken(String token) { diff --git a/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java b/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java index 2337aab..0ce0566 100644 --- a/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java +++ b/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java @@ -36,19 +36,18 @@ private JwtUtil jwtUtil; /** - * 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛ID - * 浠嶫WT token涓В鏋愮敤鎴稩D + * 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛ID锛堝寘鎷尶鍚嶇敤鎴凤級 + * 浠嶫WT token涓В鏋愮敤鎴稩D锛屽寘鎷礋鏁扮殑鍖垮悕鐢ㄦ埛ID * - * @return 鐢ㄦ埛ID - * @throws SecurityException 褰撴病鏈夋湁鏁堣璇佹椂鎶涘嚭 + * @return 鐢ㄦ埛ID锛屽寘鎷尶鍚嶇敤鎴风殑璐熸暟ID */ - public Long getCurrentUserId() { + public Long getCurrentUserIdIncludingAnonymous() { try { // 棣栧厛灏濊瘯浠嶩TTP璇锋眰澶翠腑鑾峰彇JWT token String token = getTokenFromRequest(); if (token != null && jwtUtil.validateToken(token)) { Long userId = jwtUtil.getUserIdFromToken(token); - logger.debug("浠嶫WT token涓幏鍙栧埌鐢ㄦ埛ID: {}", userId); + logger.debug("浠嶫WT token涓幏鍙栧埌鐢ㄦ埛ID锛堝寘鎷尶鍚嶇敤鎴凤級: {}", userId); return userId; } @@ -60,29 +59,103 @@ // 濡傛灉娌℃湁鏈夋晥鐨凧WT token锛屽皾璇曚粠Spring Security涓婁笅鏂囪幏鍙� Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && authentication.isAuthenticated() && - !"anonymousUser".equals(authentication.getPrincipal())) { - logger.debug("鑾峰彇鍒拌璇佺敤鎴�: {}", authentication.getName()); + if (authentication != null && authentication.isAuthenticated()) { + String principal = authentication.getName(); + logger.debug("鑾峰彇鍒拌璇佺敤鎴�: {}", principal); + + // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛 + if ("anonymousUser".equals(principal)) { + logger.debug("妫�娴嬪埌Spring榛樿鍖垮悕鐢ㄦ埛锛岃繑鍥瀗ull"); + return null; + } else if (principal.startsWith("anonymous_")) { + // 浠� "anonymous_-833488" 涓彁鍙栫敤鎴稩D + try { + String userIdStr = principal.substring("anonymous_".length()); + Long userId = Long.parseLong(userIdStr); + logger.debug("浠庡尶鍚嶈璇佷腑瑙f瀽鍒扮敤鎴稩D: {}", userId); + return userId; + } catch (NumberFormatException e) { + logger.warn("鏃犳硶浠庡尶鍚嶈璇佷俊鎭腑瑙f瀽鐢ㄦ埛ID: {}", principal); + } + } + // 浠嶴pring Security涓婁笅鏂囦腑鑾峰彇鐢ㄦ埛ID try { - return Long.parseLong(authentication.getName()); + return Long.parseLong(principal); } catch (NumberFormatException e) { - logger.warn("鏃犳硶浠庤璇佷俊鎭腑瑙f瀽鐢ㄦ埛ID: {}", authentication.getName()); + logger.warn("鏃犳硶浠庤璇佷俊鎭腑瑙f瀽鐢ㄦ埛ID: {}", principal); } } } catch (Exception e) { logger.warn("鑾峰彇褰撳墠鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage()); } - // 濡傛灉娌℃湁鏈夋晥鐨勮璇佷俊鎭紝鎶涘嚭鏉冮檺寮傚父 - logger.warn("娌℃湁鏈夋晥鐨勮璇佷俊鎭紝鎷掔粷璁块棶"); - throw new SecurityException("娌℃湁鏉冮檺"); + // 濡傛灉娌℃湁鏈夋晥鐨勮璇佷俊鎭紝杩斿洖null + logger.debug("娌℃湁鏈夋晥鐨勮璇佷俊鎭紝杩斿洖null"); + return null; + } + + /** + * 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛ID + * 浠嶫WT token涓В鏋愮敤鎴稩D + * + * @return 鐢ㄦ埛ID锛屽鏋滄槸鍖垮悕鐢ㄦ埛鍒欒繑鍥瀗ull + */ + public Long getCurrentUserId() { + try { + // 棣栧厛灏濊瘯浠嶩TTP璇锋眰澶翠腑鑾峰彇JWT token + String token = getTokenFromRequest(); + if (token != null && jwtUtil.validateToken(token)) { + Long userId = jwtUtil.getUserIdFromToken(token); + logger.debug("浠嶫WT token涓幏鍙栧埌鐢ㄦ埛ID: {}", userId); + + // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛锛堣礋鏁扮敤鎴稩D锛� + if (userId != null && userId < 0) { + logger.debug("妫�娴嬪埌鍖垮悕鐢ㄦ埛锛岃繑鍥瀗ull"); + return null; + } + + return userId; + } + + if (token == null) { + logger.debug("鏈兘浠庤姹傚ご鑾峰彇鍒癑WT token"); + } else { + logger.debug("浠庤姹傚ご鑾峰彇鍒皌oken浣嗘牎楠屽け璐�"); + } + + // 濡傛灉娌℃湁鏈夋晥鐨凧WT token锛屽皾璇曚粠Spring Security涓婁笅鏂囪幏鍙� + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + String principal = authentication.getName(); + logger.debug("鑾峰彇鍒拌璇佺敤鎴�: {}", principal); + + // 妫�鏌ユ槸鍚︿负鍖垮悕鐢ㄦ埛 + if ("anonymousUser".equals(principal) || principal.startsWith("anonymous_")) { + logger.debug("妫�娴嬪埌鍖垮悕鐢ㄦ埛璁よ瘉锛岃繑鍥瀗ull"); + return null; + } + + // 浠嶴pring Security涓婁笅鏂囦腑鑾峰彇鐢ㄦ埛ID + try { + return Long.parseLong(principal); + } catch (NumberFormatException e) { + logger.warn("鏃犳硶浠庤璇佷俊鎭腑瑙f瀽鐢ㄦ埛ID: {}", principal); + } + } + } catch (Exception e) { + logger.warn("鑾峰彇褰撳墠鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage()); + } + + // 濡傛灉娌℃湁鏈夋晥鐨勮璇佷俊鎭紝杩斿洖null锛堟敮鎸佸尶鍚嶈闂級 + logger.debug("娌℃湁鏈夋晥鐨勮璇佷俊鎭紝杩斿洖null锛堝尶鍚嶇敤鎴凤級"); + return null; } /** * 浠嶩TTP璇锋眰涓幏鍙朖WT token */ - private String getTokenFromRequest() { + public String getTokenFromRequest() { try { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes == null) { diff --git a/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java index 27a88e5..e79257c 100644 --- a/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java +++ b/backend/src/main/java/com/rongyichuang/employee/dto/response/EmployeeResponse.java @@ -1,6 +1,7 @@ package com.rongyichuang.employee.dto.response; import com.rongyichuang.employee.entity.Employee; +import com.rongyichuang.user.entity.User; /** * 鍛樺伐鍝嶅簲DTO @@ -11,18 +12,38 @@ private String phone; private String roleId; private String description; + private Integer state; private String createTime; private String updateTime; // 鏋勯�犲嚱鏁� public EmployeeResponse() {} + /** + * @deprecated 姝ゆ瀯閫犲嚱鏁板凡搴熷純锛岃浣跨敤 EmployeeResponse(Employee, User) 鏋勯�犲嚱鏁� + */ + @Deprecated public EmployeeResponse(Employee employee) { this.id = employee.getId(); this.name = employee.getName(); - this.phone = employee.getPhone(); + this.phone = employee.getPhone(); // 杩欓噷浼氳繑鍥瀗ull锛屽洜涓簆hone瀛楁宸插簾寮� this.roleId = employee.getRoleId(); this.description = employee.getDescription(); + this.state = employee.getState(); + this.createTime = employee.getCreateTime() != null ? employee.getCreateTime().toString() : null; + this.updateTime = employee.getUpdateTime() != null ? employee.getUpdateTime().toString() : null; + } + + /** + * 鎺ㄨ崘浣跨敤鐨勬瀯閫犲嚱鏁帮紝浠嶶ser瀵硅薄鑾峰彇phone淇℃伅 + */ + public EmployeeResponse(Employee employee, User user) { + this.id = employee.getId(); + this.name = employee.getName(); + this.phone = user != null ? user.getPhone() : null; // 浠嶶ser瀵硅薄鑾峰彇phone + this.roleId = employee.getRoleId(); + this.description = employee.getDescription(); + this.state = employee.getState(); this.createTime = employee.getCreateTime() != null ? employee.getCreateTime().toString() : null; this.updateTime = employee.getUpdateTime() != null ? employee.getUpdateTime().toString() : null; } @@ -68,6 +89,14 @@ this.description = description; } + public Integer getState() { + return state; + } + + public void setState(Integer state) { + this.state = state; + } + public String getCreateTime() { return createTime; } diff --git a/backend/src/main/java/com/rongyichuang/employee/entity/Employee.java b/backend/src/main/java/com/rongyichuang/employee/entity/Employee.java index aa680e8..0076ce1 100644 --- a/backend/src/main/java/com/rongyichuang/employee/entity/Employee.java +++ b/backend/src/main/java/com/rongyichuang/employee/entity/Employee.java @@ -20,8 +20,10 @@ /** * 鎵嬫満鍙风爜 + * @deprecated 姝ゅ瓧娈靛凡搴熷純锛岀數璇濆彿鐮佺粺涓�淇濆瓨鍦╰_user琛ㄤ腑锛屾瀛楁灏嗚缃负null */ - @Column(name = "phone", length = 32, nullable = false) + @Deprecated + @Column(name = "phone", length = 32, nullable = true) private String phone; @@ -68,10 +70,18 @@ this.name = name; } + /** + * @deprecated 姝ゆ柟娉曞凡搴熷純锛岃閫氳繃鍏宠仈鐨刄ser瀵硅薄鑾峰彇鐢佃瘽鍙风爜 + */ + @Deprecated public String getPhone() { return phone; } + /** + * @deprecated 姝ゆ柟娉曞凡搴熷純锛岀數璇濆彿鐮佺粺涓�淇濆瓨鍦║ser琛ㄤ腑 + */ + @Deprecated public void setPhone(String phone) { this.phone = phone; } diff --git a/backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java b/backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java index 417cbce..5b7be2e 100644 --- a/backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java +++ b/backend/src/main/java/com/rongyichuang/employee/service/EmployeeService.java @@ -43,12 +43,25 @@ private static final Pattern PASSWORD_PATTERN = Pattern.compile("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*?&]{6,}$"); /** + * 杈呭姪鏂规硶锛氬皢Employee杞崲涓篍mployeeResponse + */ + private EmployeeResponse convertToResponse(Employee employee) { + Optional<User> userOpt = userService.findById(employee.getUserId()); + if (userOpt.isPresent()) { + User user = userOpt.get(); + return new EmployeeResponse(employee, user); + } else { + throw new BusinessException("USER_NOT_FOUND", "鍛樺伐瀵瑰簲鐨勭敤鎴蜂笉瀛樺湪锛屽憳宸D: " + employee.getId()); + } + } + + /** * 鑾峰彇鎵�鏈夊憳宸ュ垪琛� */ public List<EmployeeResponse> findAllEmployees() { List<Employee> employees = employeeRepository.findAll(); return employees.stream() - .map(EmployeeResponse::new) + .map(this::convertToResponse) .collect(Collectors.toList()); } @@ -58,7 +71,7 @@ public Page<EmployeeResponse> findEmployees(String name, int page, int size) { Pageable pageable = PageRequest.of(page, size); Page<Employee> employeePage = employeeRepository.findByNameContainingOrderByCreateTimeDesc(name, pageable); - return employeePage.map(EmployeeResponse::new); + return employeePage.map(this::convertToResponse); } /** @@ -70,7 +83,7 @@ } List<Employee> employees = employeeRepository.findByNameContaining(name.trim()); return employees.stream() - .map(EmployeeResponse::new) + .map(this::convertToResponse) .collect(Collectors.toList()); } @@ -80,7 +93,14 @@ public EmployeeResponse findById(Long id) { Optional<Employee> employee = employeeRepository.findById(id); if (employee.isPresent()) { - return new EmployeeResponse(employee.get()); + Employee emp = employee.get(); + Optional<User> userOpt = userService.findById(emp.getUserId()); + if (userOpt.isPresent()) { + User user = userOpt.get(); + return new EmployeeResponse(emp, user); + } else { + throw new BusinessException("USER_NOT_FOUND", "鍛樺伐瀵瑰簲鐨勭敤鎴蜂笉瀛樺湪锛屽憳宸D: " + id); + } } throw new BusinessException("EMPLOYEE_NOT_FOUND", "鍛樺伐涓嶅瓨鍦�"); } @@ -116,21 +136,24 @@ employee = employeeRepository.findById(input.getId()) .orElseThrow(() -> new BusinessException("EMPLOYEE_NOT_FOUND", "鍛樺伐涓嶅瓨鍦�")); - // 妫�鏌ユ墜鏈哄彿鏄惁琚叾浠栧憳宸ヤ娇鐢� - if (employeeRepository.existsByPhoneAndIdNot(input.getPhone(), input.getId())) { - throw new BusinessException("PHONE_ALREADY_EXISTS", "鎵嬫満鍙峰凡琚叾浠栧憳宸ヤ娇鐢�"); + // 妫�鏌ョ敤鎴稩D鏄惁琚叾浠栧憳宸ヤ娇鐢紙鎺掗櫎褰撳墠鍛樺伐锛� + Optional<Employee> existingEmployee = employeeRepository.findByUserId(user.getId()); + if (existingEmployee.isPresent() && !existingEmployee.get().getId().equals(input.getId())) { + throw new BusinessException("PHONE_ALREADY_EXISTS", "璇ユ墜鏈哄彿宸茶鍏朵粬鍛樺伐浣跨敤"); } } else { - // 鏂板鍛樺伐 - if (employeeRepository.existsByPhone(input.getPhone())) { - throw new BusinessException("PHONE_ALREADY_EXISTS", "鎵嬫満鍙峰凡瀛樺湪"); + // 鏂板鍛樺伐 - 妫�鏌ヨ鐢ㄦ埛鏄惁宸茬粡鏄憳宸� + Optional<Employee> existingEmployee = employeeRepository.findByUserId(user.getId()); + if (existingEmployee.isPresent()) { + throw new BusinessException("PHONE_ALREADY_EXISTS", "璇ユ墜鏈哄彿宸茶鍏朵粬鍛樺伐浣跨敤"); } employee = new Employee(); } // 璁剧疆鍩烘湰淇℃伅 employee.setName(input.getName()); - employee.setPhone(input.getPhone()); + // 涓嶅啀璁剧疆phone瀛楁锛屼繚鎸佷负null + employee.setPhone(null); employee.setRoleId(input.getRoleId()); employee.setDescription(input.getDescription()); employee.setUserId(user.getId()); // 璁剧疆鍏宠仈鐨勭敤鎴稩D @@ -138,7 +161,7 @@ Employee savedEmployee = employeeRepository.save(employee); logger.info("鍛樺伐淇濆瓨鎴愬姛: {}", savedEmployee.getName()); - return new EmployeeResponse(savedEmployee); + return new EmployeeResponse(savedEmployee, user); } /** diff --git a/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java b/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java index c4d82ec..dfc89f0 100644 --- a/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java +++ b/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java @@ -159,18 +159,28 @@ } if (input.getId() != null) { + // 鏇存柊鐜版湁璇勫 judge = judgeRepository.findById(input.getId()) .orElseThrow(() -> new BusinessException("璇勫涓嶅瓨鍦�")); + + // 妫�鏌ョ敤鎴稩D鏄惁琚叾浠栬瘎濮斾娇鐢� + Optional<Judge> existingJudgeByUserId = judgeRepository.findByUserId(user.getId()); + if (existingJudgeByUserId.isPresent() && !existingJudgeByUserId.get().getId().equals(input.getId())) { + throw new BusinessException("璇ユ墜鏈哄彿宸茶鍏朵粬璇勫浣跨敤"); + } } else { + // 鏂板璇勫 judge = new Judge(); - // 鏂板璇勫鏃舵鏌ユ墜鏈哄彿鏄惁宸插瓨鍦� - if (judgeRepository.existsByPhone(input.getPhone())) { - throw new BusinessException("PHONE_EXISTS", "鎵嬫満鍙风爜宸插瓨鍦紝璇蜂娇鐢ㄥ叾浠栨墜鏈哄彿鐮�"); + + // 妫�鏌ヨ鐢ㄦ埛鏄惁宸叉槸璇勫 + Optional<Judge> existingJudge = judgeRepository.findByUserId(user.getId()); + if (existingJudge.isPresent()) { + throw new BusinessException("璇ユ墜鏈哄彿宸茶鍏朵粬璇勫浣跨敤"); } } judge.setName(input.getName()); - judge.setPhone(input.getPhone()); + judge.setPhone(null); // 搴熷純judge.phone瀛楁锛岃缃负null judge.setGender(input.getGender()); judge.setDescription(input.getDescription()); judge.setTitle(input.getTitle()); @@ -204,7 +214,19 @@ JudgeResponse response = new JudgeResponse(); response.setId(judge.getId()); response.setName(judge.getName()); - response.setPhone(judge.getPhone()); + + // 閫氳繃鍏宠仈鐨剈ser鑾峰彇phone + if (judge.getUserId() != null) { + Optional<User> userOpt = userService.findById(judge.getUserId()); + if (userOpt.isPresent()) { + response.setPhone(userOpt.get().getPhone()); + } else { + response.setPhone(null); + } + } else { + response.setPhone(null); + } + response.setGender(judge.getGender()); response.setDescription(judge.getDescription()); response.setTitle(judge.getTitle()); diff --git a/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java b/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java index 0f513a3..e8c8503 100644 --- a/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java +++ b/backend/src/main/java/com/rongyichuang/review/resolver/ReviewResolver.java @@ -31,10 +31,10 @@ */ @QueryMapping public ReviewProjectPageResponse unReviewedProjects( + @Argument String searchKeyword, @Argument int page, - @Argument int pageSize, - @Argument String searchKeyword) { - log.info("鏌ヨ鎴戞湭璇勫鐨勯」鐩垪琛紝page: {}, pageSize: {}, searchKeyword: {}", page, pageSize, searchKeyword); + @Argument int pageSize) { + log.info("鏌ヨ鎴戞湭璇勫鐨勯」鐩垪琛紝searchKeyword: {}, page: {}, pageSize: {}", searchKeyword, page, pageSize); Long currentJudgeId = userContextUtil.getCurrentJudgeId(); if (currentJudgeId == null) { @@ -49,10 +49,10 @@ */ @QueryMapping public ReviewProjectPageResponse reviewedProjects( + @Argument String searchKeyword, @Argument int page, - @Argument int pageSize, - @Argument String searchKeyword) { - log.info("鏌ヨ鎴戝凡璇勫鐨勯」鐩垪琛紝page: {}, pageSize: {}, searchKeyword: {}", page, pageSize, searchKeyword); + @Argument int pageSize) { + log.info("鏌ヨ鎴戝凡璇勫鐨勯」鐩垪琛紝searchKeyword: {}, page: {}, pageSize: {}", searchKeyword, page, pageSize); Long currentJudgeId = userContextUtil.getCurrentJudgeId(); if (currentJudgeId == null) { @@ -67,10 +67,10 @@ */ @QueryMapping public ReviewProjectPageResponse studentUnReviewedProjects( + @Argument String searchKeyword, @Argument int page, - @Argument int pageSize, - @Argument String searchKeyword) { - log.info("鏌ヨ瀛﹀憳鏈瘎瀹$殑椤圭洰鍒楄〃锛宲age: {}, pageSize: {}, searchKeyword: {}", page, pageSize, searchKeyword); + @Argument int pageSize) { + log.info("鏌ヨ瀛﹀憳鏈瘎瀹$殑椤圭洰鍒楄〃锛宻earchKeyword: {}, page: {}, pageSize: {}", searchKeyword, page, pageSize); Long currentJudgeId = userContextUtil.getCurrentJudgeId(); if (currentJudgeId == null) { diff --git a/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java b/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java index e62996a..0011f3d 100644 --- a/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java +++ b/backend/src/main/java/com/rongyichuang/user/dto/response/UserProfile.java @@ -21,6 +21,7 @@ private String gender; private String birthday; private List<String> roles; + private String userType; private String createdAt; private EmployeeInfo employee; private JudgeInfo judge; @@ -115,6 +116,14 @@ this.roles = roles; } + public String getUserType() { + return userType; + } + + public void setUserType(String userType) { + this.userType = userType; + } + public String getCreatedAt() { return createdAt; } diff --git a/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java b/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java index dff7fd5..7381c53 100644 --- a/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java +++ b/backend/src/main/java/com/rongyichuang/user/resolver/UserResolver.java @@ -4,6 +4,7 @@ import com.rongyichuang.auth.dto.LoginResponse.JudgeInfo; import com.rongyichuang.auth.dto.LoginResponse.PlayerInfo; import com.rongyichuang.common.util.UserContextUtil; +import com.rongyichuang.auth.util.JwtUtil; import com.rongyichuang.employee.entity.Employee; import com.rongyichuang.employee.service.EmployeeService; import com.rongyichuang.judge.entity.Judge; @@ -14,6 +15,9 @@ import com.rongyichuang.common.repository.MediaRepository; import com.rongyichuang.common.entity.Media; import com.rongyichuang.common.enums.MediaTargetType; +import com.rongyichuang.media.service.MediaV2Service; +import com.rongyichuang.media.dto.MediaSaveInput; +import com.rongyichuang.media.dto.MediaSaveResponse; import com.rongyichuang.user.dto.response.UserProfile; import com.rongyichuang.user.dto.response.UserStats; import com.rongyichuang.user.dto.response.UserRegistration; @@ -73,6 +77,12 @@ @Autowired private UserContextUtil userContextUtil; + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private MediaV2Service mediaV2Service; + @Value("${app.media-url}") private String mediaBaseUrl; @@ -84,8 +94,17 @@ try { Long userId = userContextUtil.getCurrentUserId(); logger.debug("杩涘叆userProfile锛岃В鏋愬埌鐢ㄦ埛ID: {}", userId); + + // 濡傛灉鏄尶鍚嶇敤鎴凤紝杩斿洖鍩烘湰鐨勭敤鎴锋。妗� if (userId == null) { - throw new RuntimeException("鐢ㄦ埛鏈櫥褰�"); + logger.debug("鍖垮悕鐢ㄦ埛璁块棶userProfile锛岃繑鍥炲熀鏈。妗�"); + UserProfile profile = new UserProfile(); + profile.setId("0"); // 鍖垮悕鐢ㄦ埛ID璁句负0 + profile.setName("鍖垮悕鐢ㄦ埛"); + profile.setRoles(new ArrayList<>()); + profile.setUserType("user"); // 鍖垮悕鐢ㄦ埛绫诲瀷涓烘櫘閫氱敤鎴� + profile.setCreatedAt(java.time.LocalDateTime.now().toString()); + return profile; } UserProfile profile = new UserProfile(); @@ -96,6 +115,9 @@ User user = null; if (userOpt.isPresent()) { user = userOpt.get(); + // 璁剧疆鍩烘湰淇℃伅锛堝鍚嶅拰鐢佃瘽鍙风爜锛� + profile.setName(user.getName()); + profile.setPhone(user.getPhone()); // 璁剧疆鎬у埆 if (user.getGender() != null) { profile.setGender(user.getGender() == 1 ? "MALE" : "FEMALE"); @@ -113,8 +135,13 @@ Employee employee = employeeService.findByUserId(userId); logger.debug("鍛樺伐鏌ヨ缁撴灉: {}", employee != null ? ("id=" + employee.getId() + ", name=" + employee.getName()) : "鏃�"); if (employee != null) { - profile.setName(employee.getName()); - profile.setPhone(employee.getPhone()); + // 濡傛灉鍛樺伐淇℃伅瀛樺湪锛屽垯瑕嗙洊鍩烘湰淇℃伅 + if (employee.getName() != null) { + profile.setName(employee.getName()); + } + if (employee.getPhone() != null) { + profile.setPhone(employee.getPhone()); + } roles.add("EMPLOYEE"); EmployeeInfo employeeInfo = new EmployeeInfo( @@ -131,8 +158,11 @@ Judge judge = judgeService.findByUserId(userId); logger.debug("璇勫鏌ヨ缁撴灉: {}", judge != null ? ("id=" + judge.getId() + ", name=" + judge.getName()) : "鏃�"); if (judge != null) { - if (profile.getName() == null) { + // 濡傛灉璇勫淇℃伅瀛樺湪涓斿熀鏈俊鎭负绌猴紝鍒欒缃瘎濮斾俊鎭� + if (profile.getName() == null && judge.getName() != null) { profile.setName(judge.getName()); + } + if (profile.getPhone() == null && judge.getPhone() != null) { profile.setPhone(judge.getPhone()); } roles.add("JUDGE"); @@ -152,8 +182,11 @@ Player player = playerService.findByUserId(userId); logger.debug("瀛﹀憳鏌ヨ缁撴灉: {}", player != null ? ("id=" + player.getId() + ", name=" + player.getName()) : "鏃�"); if (player != null) { - if (profile.getName() == null) { + // 濡傛灉瀛﹀憳淇℃伅瀛樺湪涓斿熀鏈俊鎭负绌猴紝鍒欒缃鍛樹俊鎭� + if (profile.getName() == null && player.getName() != null) { profile.setName(player.getName()); + } + if (profile.getPhone() == null && player.getPhone() != null) { profile.setPhone(player.getPhone()); } roles.add("PLAYER"); @@ -168,6 +201,20 @@ } profile.setRoles(roles); + + // 纭畾涓昏瑙掕壊绫诲瀷锛堜紭鍏堢骇锛歟mployee > judge > player锛� + String userType; + if (employee != null) { + userType = "employee"; + } else if (judge != null) { + userType = "judge"; + } else if (player != null) { + userType = "player"; + } else { + userType = "user"; // 鏅�氱敤鎴� + } + profile.setUserType(userType); + logger.debug("璁剧疆鐢ㄦ埛绫诲瀷: {}", userType); // 鑾峰彇鐢ㄦ埛澶村儚 try { @@ -429,30 +476,99 @@ @MutationMapping public UserProfileInfo saveUserInfo(@Argument UserInput input) { try { - Long userId = userContextUtil.getCurrentUserId(); - logger.debug("杩涘叆saveUserInfo锛岃В鏋愬埌鐢ㄦ埛ID: {}", userId); + Long userId = userContextUtil.getCurrentUserIdIncludingAnonymous(); + logger.debug("杩涘叆saveUserInfo锛岃В鏋愬埌鐢ㄦ埛ID锛堝寘鎷尶鍚嶇敤鎴凤級: {}", userId); if (userId == null) { throw new RuntimeException("鐢ㄦ埛鏈櫥褰�"); } - // 鏌ユ壘鐜版湁鐢ㄦ埛 - Optional<User> userOpt = userRepository.findById(userId); User user; + boolean isNewUser = false; - if (userOpt.isPresent()) { - // 鐢ㄦ埛瀛樺湪锛屾洿鏂颁俊鎭� - user = userOpt.get(); - logger.debug("鏇存柊鐜版湁鐢ㄦ埛淇℃伅锛岀敤鎴稩D: {}", userId); + // 妫�鏌ユ墜鏈哄彿鏄惁瀛樺湪 + if (StringUtils.hasText(input.getPhone())) { + Optional<User> existingUserByPhone = userRepository.findByPhone(input.getPhone()); + if (existingUserByPhone.isPresent()) { + // 鎵嬫満鍙峰凡瀛樺湪锛屾洿鏂扮幇鏈夌敤鎴� + user = existingUserByPhone.get(); + logger.debug("鎵嬫満鍙穥}宸插瓨鍦紝鏇存柊鐜版湁鐢ㄦ埛锛岀敤鎴稩D: {}", input.getPhone(), user.getId()); + + // 濡傛灉褰撳墠鐢ㄦ埛ID涓庢墜鏈哄彿瀵瑰簲鐨勭敤鎴稩D涓嶅悓锛岄渶瑕佸鐞嗗尶鍚嶇敤鎴疯浆姝g殑鎯呭喌 + if (!user.getId().equals(userId)) { + logger.debug("鍖垮悕鐢ㄦ埛{}杞涓烘寮忕敤鎴穥}", userId, user.getId()); + + // 妫�鏌ュ尶鍚嶇敤鎴锋槸鍚︽湁闇�瑕佸悎骞剁殑淇℃伅 + if (userId < 0) { + // 杩欐槸鍖垮悕鐢ㄦ埛杞锛屾鏌ユ槸鍚︽湁涓存椂淇℃伅闇�瑕佸悎骞� + Optional<User> anonymousUserOpt = userRepository.findById(userId); + if (anonymousUserOpt.isPresent()) { + User anonymousUser = anonymousUserOpt.get(); + logger.debug("鍙戠幇鍖垮悕鐢ㄦ埛璁板綍锛屽噯澶囧悎骞朵俊鎭�"); + + // 鍚堝苟鍖垮悕鐢ㄦ埛鐨勪俊鎭埌姝e紡鐢ㄦ埛锛堝鏋滄寮忕敤鎴锋病鏈夎繖浜涗俊鎭級 + if (!StringUtils.hasText(user.getName()) && StringUtils.hasText(anonymousUser.getName())) { + user.setName(anonymousUser.getName()); + logger.debug("鍚堝苟鍖垮悕鐢ㄦ埛鐨勫鍚�: {}", anonymousUser.getName()); + } + + if (user.getGender() == null && anonymousUser.getGender() != null) { + user.setGender(anonymousUser.getGender()); + logger.debug("鍚堝苟鍖垮悕鐢ㄦ埛鐨勬�у埆: {}", anonymousUser.getGender()); + } + + if (user.getBirthday() == null && anonymousUser.getBirthday() != null) { + user.setBirthday(anonymousUser.getBirthday()); + logger.debug("鍚堝苟鍖垮悕鐢ㄦ埛鐨勭敓鏃�: {}", anonymousUser.getBirthday()); + } + + // 鍒犻櫎鍖垮悕鐢ㄦ埛璁板綍锛堝彲閫夛紝閬垮厤鏁版嵁鍐椾綑锛� + try { + userRepository.delete(anonymousUser); + logger.debug("鍒犻櫎鍖垮悕鐢ㄦ埛璁板綍: {}", userId); + } catch (Exception e) { + logger.warn("鍒犻櫎鍖垮悕鐢ㄦ埛璁板綍澶辫触: {}", e.getMessage()); + } + } + } + + userId = user.getId(); // 浣跨敤姝e紡鐢ㄦ埛ID + } + } else { + // 鎵嬫満鍙蜂笉瀛樺湪锛屾鏌ュ綋鍓嶇敤鎴稩D鏄惁瀛樺湪 + Optional<User> userOpt = userRepository.findById(userId); + if (userOpt.isPresent()) { + // 鐢ㄦ埛ID瀛樺湪锛屾洿鏂颁俊鎭� + user = userOpt.get(); + logger.debug("鏇存柊鐜版湁鐢ㄦ埛淇℃伅锛岀敤鎴稩D: {}", userId); + } else { + // 鐢ㄦ埛ID涓嶅瓨鍦紝鍒涘缓鏂扮敤鎴� + user = new User(); + user.setId(userId); + isNewUser = true; + logger.debug("鍒涘缓鏂扮敤鎴凤紝鐢ㄦ埛ID: {}", userId); + } + } } else { - // 鐢ㄦ埛涓嶅瓨鍦紝鍒涘缓鏂扮敤鎴� - user = new User(); - user.setId(userId); - logger.debug("鍒涘缓鏂扮敤鎴凤紝鐢ㄦ埛ID: {}", userId); + // 娌℃湁鎻愪緵鎵嬫満鍙凤紝鐩存帴鏍规嵁鐢ㄦ埛ID鏌ユ壘鎴栧垱寤� + Optional<User> userOpt = userRepository.findById(userId); + if (userOpt.isPresent()) { + user = userOpt.get(); + logger.debug("鏇存柊鐜版湁鐢ㄦ埛淇℃伅锛岀敤鎴稩D: {}", userId); + } else { + user = new User(); + user.setId(userId); + isNewUser = true; + logger.debug("鍒涘缓鏂扮敤鎴凤紝鐢ㄦ埛ID: {}", userId); + } } // 鏇存柊鐢ㄦ埛鍩烘湰淇℃伅锛堜笉鍖呭惈澶村儚锛� - user.setName(input.getName()); - user.setPhone(input.getPhone()); + if (StringUtils.hasText(input.getName())) { + user.setName(input.getName()); + } + if (StringUtils.hasText(input.getPhone())) { + user.setPhone(input.getPhone()); + } // 澶勭悊鎬у埆杞崲锛歁ALE -> 1, FEMALE -> 0 if ("MALE".equals(input.getGender())) { @@ -470,10 +586,75 @@ logger.warn("鐢熸棩鏍煎紡瑙f瀽澶辫触: {}", input.getBirthday(), e); } } + + // 澶勭悊wxopenid鏇存柊锛氫粠JWT token涓幏鍙栧綋鍓嶇敤鎴风殑wxopenid + try { + String token = userContextUtil.getTokenFromRequest(); + if (token != null && jwtUtil.validateToken(token)) { + Long currentUserId = jwtUtil.getUserIdFromToken(token); + String wxopenidFromToken = jwtUtil.getWxOpenidFromToken(token); + + // 濡傛灉token涓寘鍚玾xopenid锛屽垯鏇存柊鐢ㄦ埛鐨剋xopenid + if (StringUtils.hasText(wxopenidFromToken)) { + logger.debug("浠巘oken涓幏鍙栧埌wxopenid: {}", wxopenidFromToken); + + // 妫�鏌ヨ繖涓猳penid鏄惁宸茬粡琚叾浠栫敤鎴蜂娇鐢� + Optional<User> existingUserWithOpenid = userRepository.findByWxOpenid(wxopenidFromToken); + if (existingUserWithOpenid.isEmpty() || existingUserWithOpenid.get().getId().equals(user.getId())) { + user.setWxOpenid(wxopenidFromToken); + logger.debug("璁剧疆鐢ㄦ埛wxopenid: {}", wxopenidFromToken); + } else { + logger.warn("wxopenid {} 宸茶鍏朵粬鐢ㄦ埛浣跨敤锛岀敤鎴稩D: {}", wxopenidFromToken, existingUserWithOpenid.get().getId()); + } + } else { + logger.debug("token涓湭鍖呭惈wxopenid淇℃伅"); + } + } + } catch (Exception e) { + logger.warn("澶勭悊wxopenid鏇存柊鏃跺彂鐢熷紓甯�: {}", e.getMessage(), e); + } // 淇濆瓨鐢ㄦ埛鍩烘湰淇℃伅 user = userRepository.save(user); - logger.debug("鐢ㄦ埛淇℃伅淇濆瓨鎴愬姛锛岀敤鎴稩D: {}", user.getId()); + logger.debug("鐢ㄦ埛淇℃伅淇濆瓨鎴愬姛锛岀敤鎴稩D: {}, 鏄惁鏂扮敤鎴�: {}", user.getId(), isNewUser); + + // 澶勭悊澶村儚淇濆瓨 + if (StringUtils.hasText(input.getAvatar())) { + try { + logger.debug("寮�濮嬩繚瀛樼敤鎴峰ご鍍忥紝璺緞: {}", input.getAvatar()); + + // 鏋勫缓MediaSaveInput + MediaSaveInput mediaSaveInput = new MediaSaveInput(); + mediaSaveInput.setTargetType("player"); // 浣跨敤"player"浣滀负鐩爣绫诲瀷 + mediaSaveInput.setTargetId(user.getId()); + mediaSaveInput.setPath(input.getAvatar()); + mediaSaveInput.setMediaType(1); // 1琛ㄧず鍥剧墖 + mediaSaveInput.setFileSize(0L); // 璁剧疆榛樿鏂囦欢澶у皬涓�0锛岄伩鍏嶆暟鎹簱绾︽潫閿欒 + + // 浠庤矾寰勪腑鎻愬彇鏂囦欢鍚嶅拰鎵╁睍鍚� + String fileName = input.getAvatar(); + if (fileName.contains("/")) { + fileName = fileName.substring(fileName.lastIndexOf("/") + 1); + } + mediaSaveInput.setFileName(fileName); + + if (fileName.contains(".")) { + String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1); + mediaSaveInput.setFileExt(fileExt); + } + + // 淇濆瓨澶村儚濯掍綋璁板綍 + MediaSaveResponse saveResponse = mediaV2Service.saveMedia(mediaSaveInput); + if (saveResponse.getSuccess()) { + logger.debug("澶村儚淇濆瓨鎴愬姛锛屽獟浣揑D: {}", saveResponse.getMediaId()); + } else { + logger.warn("澶村儚淇濆瓨澶辫触: {}", saveResponse.getMessage()); + } + } catch (Exception e) { + logger.error("淇濆瓨澶村儚鏃跺彂鐢熼敊璇�", e); + // 澶村儚淇濆瓨澶辫触涓嶅奖鍝嶇敤鎴蜂俊鎭繚瀛� + } + } // 鏋勫缓杩斿洖缁撴灉 UserProfileInfo result = new UserProfileInfo(); @@ -494,12 +675,13 @@ try { List<Media> avatarMedias = mediaRepository.findByTargetTypeAndTargetIdAndState( MediaTargetType.USER_AVATAR.getValue(), - userId, + user.getId(), 1 ); if (!avatarMedias.isEmpty()) { Media avatarMedia = avatarMedias.get(0); result.setAvatar(buildFullMediaUrl(avatarMedia.getPath())); + logger.debug("璁剧疆澶村儚URL: {}", result.getAvatar()); } } catch (Exception e) { logger.warn("鑾峰彇澶村儚澶辫触: {}", e.getMessage(), e); diff --git a/backend/src/main/java/com/rongyichuang/user/service/UserService.java b/backend/src/main/java/com/rongyichuang/user/service/UserService.java index a4c4913..0e8f24a 100644 --- a/backend/src/main/java/com/rongyichuang/user/service/UserService.java +++ b/backend/src/main/java/com/rongyichuang/user/service/UserService.java @@ -75,14 +75,22 @@ boolean needUpdateWx = false; if (currentWxOpenid != null && !currentWxOpenid.trim().isEmpty()) { if (user.getWxOpenid() == null || !currentWxOpenid.equals(user.getWxOpenid())) { - user.setWxOpenid(currentWxOpenid); - needUpdateWx = true; + // 妫�鏌ヨ繖涓猳penid鏄惁宸茬粡琚叾浠栫敤鎴蜂娇鐢� + Optional<User> existingUserWithOpenid = userRepository.findByWxOpenid(currentWxOpenid); + if (existingUserWithOpenid.isEmpty() || existingUserWithOpenid.get().getId().equals(user.getId())) { + user.setWxOpenid(currentWxOpenid); + needUpdateWx = true; + } } } if (currentWxUnionid != null && !currentWxUnionid.trim().isEmpty()) { if (user.getWxUnionid() == null || !currentWxUnionid.equals(user.getWxUnionid())) { - user.setWxUnionid(currentWxUnionid); - needUpdateWx = true; + // 妫�鏌ヨ繖涓猽nionid鏄惁宸茬粡琚叾浠栫敤鎴蜂娇鐢� + Optional<User> existingUserWithUnionid = userRepository.findByWxUnionid(currentWxUnionid); + if (existingUserWithUnionid.isEmpty() || existingUserWithUnionid.get().getId().equals(user.getId())) { + user.setWxUnionid(currentWxUnionid); + needUpdateWx = true; + } } } if (needUpdateWx) { diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 9f5dff8..f4b9dda 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -86,7 +86,7 @@ base-url: https://rych.9village.cn jwt: secret: ryc-jwt-secret-key-2024-secure-256bit-hmac-sha-algorithm-compatible - expiration: 7200000 # 2灏忔椂 + expiration: 86400000 # 24灏忔椂 media-url: https://ryc-1379367838.cos.ap-chengdu.myqcloud.com # 寰俊灏忕▼搴忛厤缃� diff --git a/backend/src/main/resources/graphql/auth.graphqls b/backend/src/main/resources/graphql/auth.graphqls index aa790e5..7addfa7 100644 --- a/backend/src/main/resources/graphql/auth.graphqls +++ b/backend/src/main/resources/graphql/auth.graphqls @@ -4,8 +4,17 @@ } extend type Mutation { - # 璁よ瘉鐩稿叧鎺ュ彛宸茶縼绉诲埌RESTful API (/auth/*) - _: Boolean + # 寰俊鐧诲綍 + wxLogin(input: WxLoginRequest!): WxLoginResponse + + # Web绔櫥褰� + webLogin(input: LoginRequest!): LoginResponse + + # 瑙e瘑寰俊鎵嬫満鍙凤紙鏃х増API锛� + decryptPhoneNumber(encryptedData: String!, iv: String!, sessionKey: String!): PhoneDecryptResponse + + # 鑾峰彇寰俊鎵嬫満鍙凤紙鏂扮増API锛� + getPhoneNumberByCode(code: String!): PhoneDecryptResponse } input LoginRequest { @@ -35,6 +44,11 @@ isNewUser: Boolean loginRecordId: Long sessionKey: String + success: Boolean + message: String + hasEmployee: Boolean + hasJudge: Boolean + hasPlayer: Boolean } type UserInfo { diff --git a/backend/src/main/resources/graphql/user.graphqls b/backend/src/main/resources/graphql/user.graphqls index 58628d0..36d96e3 100644 --- a/backend/src/main/resources/graphql/user.graphqls +++ b/backend/src/main/resources/graphql/user.graphqls @@ -13,6 +13,7 @@ gender: String birthday: String roles: [String!]! + userType: String createdAt: String # 瑙掕壊鐩稿叧淇℃伅 employee: EmployeeInfo diff --git a/web/src/views/check-detail.vue b/web/src/views/check-detail.vue index 139047a..d60d400 100644 --- a/web/src/views/check-detail.vue +++ b/web/src/views/check-detail.vue @@ -115,6 +115,13 @@ <el-button type="primary" size="small" + @click="previewAttachment(attachment)" + > + 棰勮 + </el-button> + <el-button + type="primary" + size="small" @click="downloadAttachment(attachment)" > 涓嬭浇 @@ -130,7 +137,24 @@ <div class="card-header"> <span>瀹℃牳绠$悊</span> </div> - </template> + <!-- 闄勪欢棰勮瀵硅瘽妗� --> + <el-dialog v-model="previewVisible" title="鏂囦欢棰勮" width="80%" center> + <div class="preview-content"> + <!-- 鍥剧墖棰勮 --> + <img v-if="previewType === 'image' && previewUrl" :src="previewUrl" style="max-width: 100%; max-height: 70vh; object-fit: contain;" /> + <!-- 瑙嗛棰勮 --> + <video v-else-if="previewType === 'video' && previewUrl" :src="previewUrl" controls style="width: 100%; max-height: 70vh;"></video> + <!-- PDF 棰勮 --> + <iframe v-else-if="previewType === 'pdf' && previewUrl" :src="previewUrl" style="width: 100%; height: 70vh; border: none;"></iframe> + <!-- DOCX 棰勮 --> + <div v-else-if="previewType === 'docx'" ref="docxContainer" class="docx-preview"></div> + <!-- 鍏跺畠涓嶆敮鎸� --> + <div v-else class="preview-error"> + <el-empty description="鏃犳硶棰勮姝ゆ枃浠剁被鍨嬶紝璇蜂笅杞芥煡鐪�" /> + </div> + </div> + </el-dialog> +</template> <div class="review-section"> <div class="review-status"> @@ -223,6 +247,12 @@ const playerData = ref<any>(null) const activityPlayerData = ref<any>(null) const attachments = ref<any[]>([]) + +// 棰勮鐩稿叧 +const previewVisible = ref(false) +const previewUrl = ref('') +const previewType = ref<'' | 'image' | 'video' | 'pdf' | 'docx' | 'unknown'>('') +const docxContainer = ref<HTMLElement | null>(null) // 瀹℃牳鐩稿叧鏁版嵁 const feedbackText = ref('') @@ -422,6 +452,113 @@ const downloadAttachment = (attachment: any) => { // TODO: 瀹炵幇闄勪欢涓嬭浇鍔熻兘 window.open(attachment.url, '_blank') +} + +/** + * 棰勮闄勪欢锛氭寜鎵╁睍鍚嶅垎鍙� 鍥剧墖/瑙嗛/PDF/DOCX + */ +const previewAttachment = async (attachment: any) => { + const name: string = attachment.originalName || attachment.name || '' + const url: string = attachment.url + const ext = (name.split('.').pop() || '').toLowerCase() + + previewVisible.value = true + previewUrl.value = '' + previewType.value = '' + + const imageExts = ['jpg','jpeg','png','gif','bmp','webp'] + const videoExts = ['mp4','webm','ogg','avi','mov','wmv','flv','mkv'] + + if (imageExts.includes(ext)) { + previewType.value = 'image' + previewUrl.value = url + return + } + if (videoExts.includes(ext)) { + previewType.value = 'video' + previewUrl.value = url + return + } + if (ext === 'pdf') { + previewType.value = 'pdf' + previewUrl.value = url + return + } + if (ext === 'docx') { + previewType.value = 'docx' + try { + await renderDocx(url) + } catch (e: any) { + console.error('DOCX 棰勮澶辫触:', e) + ElMessage.warning('DOCX 棰勮澶辫触锛屽缓璁笅杞芥煡鐪�') + previewType.value = 'unknown' + } + return + } + if (ext === 'doc') { + ElMessage.info('鏆備笉鏀寔 .doc 棰勮锛岃涓嬭浇鏌ョ湅') + previewType.value = 'unknown' + return + } + + ElMessage.warning('姝ゆ枃浠剁被鍨嬩笉鏀寔棰勮锛岃涓嬭浇鏌ョ湅') + previewType.value = 'unknown' +} + +/** + * 鍔ㄦ�佸姞杞� docx-preview 骞舵覆鏌� DOCX + */ +const renderDocx = async (url: string) => { + // 鍔ㄦ�佸姞杞� docx-preview + const ensureScript = () => new Promise((resolve, reject) => { + if ((window as any).docx && (window as any).docx.renderAsync) return resolve(true) + const existed = document.querySelector('script[data-docx-preview]') + if (existed) { + existed.addEventListener('load', () => resolve(true)) + existed.addEventListener('error', reject) + return + } + const s = document.createElement('script') + s.src = 'https://unpkg.com/docx-preview/dist/docx-preview.min.js' + s.async = true + s.setAttribute('data-docx-preview', '1') + s.onload = () => resolve(true) + s.onerror = reject + document.head.appendChild(s) + }) + + // 瑙勮寖鍖� URL锛堟敮鎸佺浉瀵硅矾寰勶級 + const buildUrl = (u: string) => { + if (!u) return u + if (u.startsWith('http://') || u.startsWith('https://')) return u + if (u.startsWith('/')) return location.origin + u + return u + } + + // 鎼哄甫閴存潈澶磋闂檮浠讹紙寰堝鏂囦欢鏈嶅姟涓嶈蛋 cookie锛岃�屾槸 JWT Header锛� + const { getToken } = await import('@/utils/auth') + const token = getToken ? getToken() : '' + const requestUrl = buildUrl(url) + + let res: Response + try { + res = await fetch(requestUrl, { + credentials: 'include', + headers: token ? { Authorization: `Bearer ${token}` } : undefined, + mode: 'cors' + } as RequestInit) + } catch (e) { + throw new Error('鑾峰彇 DOCX 澶辫触: 缃戠粶涓嶅彲杈炬垨琚嫤鎴�') + } + if (!res.ok) throw new Error('鑾峰彇 DOCX 澶辫触: ' + res.status) + + const blob = await res.blob() + + await ensureScript() + if (docxContainer.value) { + docxContainer.value.innerHTML = '' + await (window as any).docx.renderAsync(blob, docxContainer.value, null, { inWrapper: true }) + } } // 瀹℃牳閫氳繃 @@ -770,4 +907,19 @@ grid-template-columns: 1fr; } } + .preview-content { + text-align: center; + } + + .preview-error { + padding: 40px 0; + } + + .docx-preview { + text-align: left; + max-height: 70vh; + overflow: auto; + background: #fff; + padding: 12px; + } </style> \ No newline at end of file diff --git a/web/src/views/review-detail.vue b/web/src/views/review-detail.vue index 848948b..fd0a3be 100644 --- a/web/src/views/review-detail.vue +++ b/web/src/views/review-detail.vue @@ -168,13 +168,17 @@ <!-- 鏂囦欢棰勮瀵硅瘽妗� --> <el-dialog v-model="previewVisible" title="鏂囦欢棰勮" width="80%" center> <div class="preview-content"> - <iframe - v-if="previewUrl" - :src="previewUrl" - style="width: 100%; height: 500px; border: none;" - ></iframe> + <!-- 鍥剧墖棰勮 --> + <img v-if="previewType === 'image' && previewUrl" :src="previewUrl" style="max-width: 100%; max-height: 70vh; object-fit: contain;" /> + <!-- 瑙嗛棰勮 --> + <video v-else-if="previewType === 'video' && previewUrl" :src="previewUrl" controls style="width: 100%; max-height: 70vh;"></video> + <!-- PDF 棰勮 --> + <iframe v-else-if="previewType === 'pdf' && previewUrl" :src="previewUrl" style="width: 100%; height: 70vh; border: none;"></iframe> + <!-- DOCX 棰勮 --> + <div v-else-if="previewType === 'docx'" ref="docxContainer" class="docx-preview"></div> + <!-- 鍏跺畠涓嶆敮鎸� --> <div v-else class="preview-error"> - <el-empty description="鏃犳硶棰勮姝ゆ枃浠剁被鍨�" /> + <el-empty description="鏃犳硶棰勮姝ゆ枃浠剁被鍨嬶紝璇蜂笅杞芥煡鐪�" /> </div> </div> </el-dialog> @@ -204,6 +208,8 @@ const ratingComment = ref('') const previewVisible = ref(false) const previewUrl = ref('') +const previewType = ref('') // image | video | pdf | docx | unknown +const docxContainer = ref(null) // 鏉冮檺楠岃瘉鐩稿叧 const currentJudge = ref(null) @@ -446,17 +452,84 @@ } } -// 鏂囦欢棰勮 -const previewFile = (file) => { - // 鏍规嵁鏂囦欢绫诲瀷鍐冲畾棰勮鏂瑰紡 - const fileExtension = file.name.split('.').pop().toLowerCase() - const previewableTypes = ['pdf', 'txt', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm'] - - if (previewableTypes.includes(fileExtension)) { - // 鍦ㄦ柊绐楀彛涓墦寮�棰勮 - window.open(file.url, '_blank') - } else { - ElMessage.warning('姝ゆ枃浠剁被鍨嬩笉鏀寔棰勮锛岃涓嬭浇鏌ョ湅') +/** + * 鏂囦欢棰勮锛氭寜鎵╁睍鍚嶅垎鍙戝埌鍥剧墖/瑙嗛/PDF/DOCX + */ +const previewFile = async (file) => { + const ext = (file.name?.split('.').pop() || '').toLowerCase() + previewVisible.value = true + previewUrl.value = '' + previewType.value = '' + // 缁熶竴鍙栧彲鐢ㄧ殑瀹屾暣 URL + const url = file.url || file.fullUrl || file.full_path || file.path + + const imageExts = ['jpg','jpeg','png','gif','bmp','webp'] + const videoExts = ['mp4','webm','ogg','avi','mov','wmv','flv','mkv'] + + if (imageExts.includes(ext)) { + previewType.value = 'image' + previewUrl.value = url + return + } + if (videoExts.includes(ext)) { + previewType.value = 'video' + previewUrl.value = url + return + } + if (ext === 'pdf') { + previewType.value = 'pdf' + previewUrl.value = url + return + } + if (ext === 'docx') { + previewType.value = 'docx' + try { + await renderDocx(url) + } catch (e) { + console.error('DOCX 棰勮澶辫触:', e) + ElMessage.warning('DOCX 棰勮澶辫触锛屽缓璁笅杞芥煡鐪�') + previewType.value = 'unknown' + } + return + } + if (ext === 'doc') { + ElMessage.info('鏆備笉鏀寔 .doc 棰勮锛岃涓嬭浇鏌ョ湅') + previewType.value = 'unknown' + return + } + + ElMessage.warning('姝ゆ枃浠剁被鍨嬩笉鏀寔棰勮锛岃涓嬭浇鏌ョ湅') + previewType.value = 'unknown' +} + +/** + * 鍔ㄦ�佸姞杞� docx-preview 骞舵覆鏌� DOCX + */ +const renderDocx = async (url) => { + const ensureScript = () => new Promise((resolve, reject) => { + if (window.docx && window.docx.renderAsync) return resolve(true) + const existed = document.querySelector('script[data-docx-preview]') + if (existed) { + existed.addEventListener('load', () => resolve(true)) + existed.addEventListener('error', reject) + return + } + const s = document.createElement('script') + s.src = 'https://unpkg.com/docx-preview/dist/docx-preview.min.js' + s.async = true + s.setAttribute('data-docx-preview', '1') + s.onload = () => resolve(true) + s.onerror = reject + document.head.appendChild(s) + }) + + await ensureScript() + const res = await fetch(url, { credentials: 'include' }) + if (!res.ok) throw new Error('鑾峰彇 DOCX 澶辫触: ' + res.status) + const blob = await res.blob() + if (docxContainer.value) { + docxContainer.value.innerHTML = '' + await window.docx.renderAsync(blob, docxContainer.value, null, { inWrapper: true }) } } @@ -699,6 +772,13 @@ .preview-content { text-align: center; } +.docx-preview { + text-align: left; + max-height: 70vh; + overflow: auto; + background: #fff; + padding: 12px; +} .preview-error { padding: 40px 0; diff --git a/wx/app.js b/wx/app.js index 57d5a4b..4145d1c 100644 --- a/wx/app.js +++ b/wx/app.js @@ -163,7 +163,7 @@ } // 妫�鏌ユ槸鍚︽湁閿欒淇℃伅锛堥�傞厤涓嶅悓鐨勯敊璇搷搴旀牸寮忥級 - if (res.data.error || res.data.message || res.data.success === false) { + if (res.data.error || res.data.success === false) { const errorMsg = res.data.error || res.data.message || '鐧诲綍澶辫触' console.error('鉂� 鐧诲綍澶辫触:', errorMsg) wx.showToast({ @@ -276,174 +276,56 @@ // GraphQL璇锋眰灏佽 graphqlRequest(query, variables = {}) { return new Promise((resolve, reject) => { - this._makeGraphQLRequest(query, variables, resolve, reject, false) - }) - }, - - // 鍐呴儴GraphQL璇锋眰鏂规硶锛屾敮鎸侀噸璇曟満鍒� - _makeGraphQLRequest(query, variables, resolve, reject, isRetry = false) { - // 纭繚token鐨勪竴鑷存�э細浼樺厛浣跨敤globalData涓殑token锛屽鏋滄病鏈夊垯浠巗torage鑾峰彇 - let token = this.globalData.token - if (!token) { - token = wx.getStorageSync('token') - if (token) { - this.globalData.token = token // 鍚屾鍒癵lobalData + // 纭繚token鐨勪竴鑷存�э細浼樺厛浣跨敤globalData涓殑token锛屽鏋滄病鏈夊垯浠巗torage鑾峰彇 + let token = this.globalData.token + if (!token) { + token = wx.getStorageSync('token') + if (token) { + this.globalData.token = token // 鍚屾鍒癵lobalData + } } - } - wx.request({ - url: this.globalData.baseUrl, - method: 'POST', - header: { - 'Content-Type': 'application/json', - 'Authorization': token ? `Bearer ${token}` : '' - }, - data: { - query: query, - variables: variables - }, - success: (res) => { - console.log('GraphQL鍝嶅簲:', res.data) - - // 妫�鏌TTP鐘舵�佺爜 - if (res.statusCode !== 200) { - // 瀵逛簬401鐘舵�佺爜锛屽彲鑳芥槸璁よ瘉閿欒锛岄渶瑕佹鏌ュ搷搴斿唴瀹� - if (res.statusCode === 401 && res.data && res.data.errors) { - console.log('鏀跺埌401鐘舵�佺爜锛屾鏌ユ槸鍚︿负璁よ瘉閿欒') - // 缁х画澶勭悊锛岃涓嬮潰鐨凣raphQL閿欒妫�鏌ラ�昏緫澶勭悊璁よ瘉閿欒 - } else { + wx.request({ + url: this.globalData.baseUrl, + method: 'POST', + header: { + 'Content-Type': 'application/json', + 'Authorization': token ? `Bearer ${token}` : '' + }, + data: { + query: query, + variables: variables + }, + success: (res) => { + console.log('GraphQL鍝嶅簲:', res.data) + + // 妫�鏌TTP鐘舵�佺爜 + if (res.statusCode !== 200) { console.error('GraphQL HTTP閿欒:', res.statusCode) reject(new Error(`HTTP閿欒: ${res.statusCode}`)) return } - } - // 妫�鏌raphQL閿欒 - if (res.data && res.data.errors) { - console.error('GraphQL閿欒:', res.data.errors) - - // 妫�鏌ユ槸鍚︽槸璁よ瘉閿欒锛坱oken杩囨湡鎴栨棤鏁堬級 - const authErrors = res.data.errors.filter(error => - error.message && ( - error.message.includes('娌℃湁鏉冮檺璁块棶') || - error.message.includes('璇峰厛鐧诲綍') || - error.message.includes('UNAUTHORIZED') || - error.extensions?.code === 'UNAUTHORIZED' - ) - ) - - if (authErrors.length > 0 && !isRetry) { - console.log('馃攧 妫�娴嬪埌璁よ瘉閿欒锛屽皾璇曢噸鏂扮櫥褰�...') - // 娓呴櫎杩囨湡鐨勮璇佷俊鎭� - this.globalData.token = null - this.globalData.userInfo = null - this.globalData.sessionKey = null - wx.removeStorageSync('token') - wx.removeStorageSync('userInfo') - wx.removeStorageSync('sessionKey') - - // 閲嶆柊鐧诲綍 - wx.login({ - success: (loginRes) => { - if (loginRes.code) { - console.log('馃攧 閲嶆柊鑾峰彇寰俊code鎴愬姛锛岃皟鐢ㄥ悗绔櫥褰�...') - this._retryAfterLogin(loginRes.code, query, variables, resolve, reject) - } else { - console.error('鉂� 閲嶆柊鑾峰彇寰俊code澶辫触') - reject(new Error('閲嶆柊鐧诲綍澶辫触')) - } - }, - fail: (err) => { - console.error('鉂� 閲嶆柊鐧诲綍澶辫触:', err) - reject(new Error('閲嶆柊鐧诲綍澶辫触')) - } - }) + // 妫�鏌raphQL閿欒 + if (res.data.errors) { + console.error('GraphQL閿欒:', res.data.errors) + reject(new Error(res.data.errors[0]?.message || 'GraphQL璇锋眰閿欒')) return } - - reject(new Error(res.data.errors[0]?.message || 'GraphQL璇锋眰閿欒')) - return - } - // 妫�鏌ユ暟鎹� - if (res.data.data !== undefined) { - resolve(res.data.data) - } else { - console.error('GraphQL鍝嶅簲寮傚父:', res.data) - reject(new Error('GraphQL鍝嶅簲鏁版嵁寮傚父')) - } - }, - fail: (err) => { - console.error('GraphQL缃戠粶璇锋眰澶辫触:', err) - reject(new Error('缃戠粶璇锋眰澶辫触')) - } - }) - }, - - // 閲嶆柊鐧诲綍鍚庨噸璇旼raphQL璇锋眰 - _retryAfterLogin(code, query, variables, resolve, reject) { - const that = this - const deviceInfo = this.getDeviceInfo() - const requestData = { - code: code, - loginIp: '127.0.0.1', // 灏忕▼搴忔棤娉曡幏鍙栫湡瀹濱P锛屼娇鐢ㄩ粯璁ゅ�� - deviceInfo: deviceInfo - } - - wx.request({ - url: 'http://localhost:8080/api/auth/wx-login', - method: 'POST', - header: { - 'Content-Type': 'application/json' - }, - data: requestData, - success: (res) => { - console.log('馃攧 閲嶆柊鐧诲綍鍝嶅簲:', res.data) - - if (res.statusCode !== 200 || res.data.error) { - console.error('鉂� 閲嶆柊鐧诲綍澶辫触:', res.data.error || res.data.message) - reject(new Error('閲嶆柊鐧诲綍澶辫触')) - return - } - - // 妫�鏌ュ搷搴旀暟鎹牸寮� - let loginResult = null - if (res.data.token && res.data.userInfo) { - loginResult = res.data - } else if (res.data.success && res.data.data) { - loginResult = res.data.data - } - - if (loginResult && loginResult.token) { - console.log('鉁� 閲嶆柊鐧诲綍鎴愬姛锛屾洿鏂皌oken') - - // 淇濆瓨鏂扮殑鐧诲綍淇℃伅 - try { - wx.setStorageSync('token', loginResult.token) - wx.setStorageSync('userInfo', loginResult.userInfo) - if (loginResult.sessionKey) { - wx.setStorageSync('sessionKey', loginResult.sessionKey) - } - } catch (storageErr) { - console.error('鉂� 淇濆瓨閲嶆柊鐧诲綍淇℃伅澶辫触:', storageErr) + // 妫�鏌ユ暟鎹� + if (res.data.data !== undefined) { + resolve(res.data.data) + } else { + console.error('GraphQL鍝嶅簲寮傚父:', res.data) + reject(new Error('GraphQL鍝嶅簲鏁版嵁寮傚父')) } - - that.globalData.token = loginResult.token - that.globalData.userInfo = loginResult.userInfo - that.globalData.sessionKey = loginResult.sessionKey - - // 浣跨敤鏂皌oken閲嶈瘯鍘熷璇锋眰 - console.log('馃攧 浣跨敤鏂皌oken閲嶈瘯GraphQL璇锋眰...') - that._makeGraphQLRequest(query, variables, resolve, reject, true) - } else { - console.error('鉂� 閲嶆柊鐧诲綍鍝嶅簲鏍煎紡閿欒') - reject(new Error('閲嶆柊鐧诲綍鍝嶅簲鏍煎紡閿欒')) + }, + fail: (err) => { + console.error('GraphQL缃戠粶璇锋眰澶辫触:', err) + reject(new Error('缃戠粶璇锋眰澶辫触')) } - }, - fail: (err) => { - console.error('鉂� 閲嶆柊鐧诲綍缃戠粶璇锋眰澶辫触:', err) - reject(new Error('閲嶆柊鐧诲綍缃戠粶璇锋眰澶辫触')) - } + }) }) } }) \ No newline at end of file diff --git a/wx/pages/judge/review.js b/wx/pages/judge/review.js index 1fd19b0..27afd37 100644 --- a/wx/pages/judge/review.js +++ b/wx/pages/judge/review.js @@ -1,6 +1,6 @@ // pages/judge/review.js const app = getApp() -const { graphqlRequest, formatDate: formatDateUtil } = require('../../lib/utils') +const { graphqlRequest, formatDate } = require('../../lib/utils') Page({ data: { @@ -10,15 +10,13 @@ // 鎻愪氦浣滃搧淇℃伅 submission: null, activityPlayerId: '', - stageId: null, - submissionId: null, - + // 娲诲姩淇℃伅 activity: null, - + // 璇勫鏍囧噯 criteria: [], - + // 璇勫垎鏁版嵁 scores: {}, @@ -33,7 +31,24 @@ reviewStatus: 'PENDING', // PENDING, COMPLETED // 宸叉湁璇勫璁板綍 - existingReview: null + existingReview: null, + + // 濯掍綋棰勮 + showMediaPreview: false, + currentMedia: null, + mediaType: 'image', + + // 鏂囦欢涓嬭浇 + downloadingFiles: [], + + // 璇勫垎绛夌骇 + scoreOptions: [ + { value: 1, label: '1鍒� - 寰堝樊' }, + { value: 2, label: '2鍒� - 杈冨樊' }, + { value: 3, label: '3鍒� - 涓�鑸�' }, + { value: 4, label: '4鍒� - 鑹ソ' }, + { value: 5, label: '5鍒� - 浼樼' } + ] }, onLoad(options) { @@ -47,32 +62,6 @@ // 椤甸潰鏄剧ず鏃舵鏌ヨ瘎瀹$姸鎬� if (this.data.submissionId) { this.checkReviewStatus() - } - }, - - transformMediaFile(file) { - const url = file.fullUrl || file.url - const thumbUrl = file.fullThumbUrl || url - const ext = (file.fileExt || '').toLowerCase() - let mediaType = 'file' - - if (file.mediaType === 1 || ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'heic'].includes(ext)) { - mediaType = 'image' - } else if (file.mediaType === 2 || ['mp4', 'mov', 'avi', 'wmv', 'mkv', 'webm', 'flv'].includes(ext)) { - mediaType = 'video' - } else if (ext === 'pdf') { - mediaType = 'pdf' - } else if (['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'wps', 'txt', 'rtf'].includes(ext)) { - mediaType = 'word' - } - - return { - id: file.id, - name: file.name, - url, - thumbUrl, - mediaType, - size: file.fileSize || 0 } }, @@ -113,11 +102,13 @@ submissionFiles { id name + url fullUrl - fullThumbUrl fileExt fileSize mediaType + thumbUrl + fullThumbUrl } ratingForm { schemeId @@ -126,8 +117,10 @@ items { id name + description maxScore - orderNo + weight + sortOrder } } } @@ -144,64 +137,62 @@ id: detail.id, title: detail.projectName, description: detail.description, - submittedAt: detail.submitTime || null, - team: detail.team || null, + files: detail.submissionFiles ? detail.submissionFiles.map(file => ({ + id: file.id, + name: file.name, + url: file.fullUrl || file.url, + type: file.fileExt, + size: file.fileSize, + isDownloading: this.data.downloadingFiles.indexOf(file.id) > -1 + })) : [], + images: detail.submissionFiles ? detail.submissionFiles + .filter(file => file.mediaType === 1) + .map(file => file.fullUrl || file.url) : [], + videos: detail.submissionFiles ? detail.submissionFiles + .filter(file => file.mediaType === 2) + .map(file => file.fullUrl || file.url) : [], participant: { - id: detail.playerInfo?.id, - name: detail.playerInfo?.name, - phone: detail.playerInfo?.phone || detail.playerInfo?.userInfo?.phone || '', - avatar: detail.playerInfo?.userInfo?.avatarUrl || '/images/default-avatar.svg', - gender: this.getGenderLabel(detail.playerInfo?.gender), - birthday: this.getBirthdayText(detail.playerInfo?.birthday), - region: detail.regionInfo?.fullPath || detail.regionInfo?.name || '', - education: detail.playerInfo?.education || '', + id: detail.playerInfo.id, + name: detail.playerInfo.name, school: detail.regionInfo ? detail.regionInfo.name : '', - major: detail.playerInfo?.education || '' + major: detail.playerInfo.education || '', + avatar: detail.playerInfo.userInfo?.avatarUrl || '/images/default-avatar.svg' }, - status: detail.state === 1 ? 'APPROVED' : detail.state === 2 ? 'REJECTED' : 'PENDING', - mediaList: (detail.submissionFiles || []).map(file => this.transformMediaFile(file)) + status: detail.state === 1 ? 'APPROVED' : detail.state === 2 ? 'REJECTED' : 'PENDING' } - - const criteria = (detail.ratingForm?.items || []).map(item => { - const maxScore = item.maxScore || 0 - return { - id: item.id, - name: item.name, - maxScore, - description: item.description || '鏆傛棤鎻忚堪', - step: maxScore > 20 ? 1 : 0.5, - currentScore: 0 - } - }) - + + // 鏋勫缓activity瀵硅薄 + const activity = { + id: detail.stageId, + title: detail.activityName, + description: detail.description, + judgeCriteria: detail.ratingForm ? detail.ratingForm.items || [] : [] + } + + // 鍒濆鍖栬瘎鍒嗘暟鎹� const scores = {} - criteria.forEach(criterion => { - scores[criterion.id] = 0 - }) - - const maxScore = detail.ratingForm?.totalMaxScore || criteria.reduce((sum, item) => sum + (item.maxScore || 0), 0) - + let maxScore = 0 + + if (activity.judgeCriteria) { + activity.judgeCriteria.forEach(criterion => { + scores[criterion.id] = 0 // 鏆傛椂璁句负0锛屽悗缁渶瑕佹煡璇㈠凡鏈夎瘎鍒� + maxScore += criterion.maxScore + }) + } + this.setData({ submission, - activity: { - id: detail.id, - stageId: detail.stageId, - ratingSchemeId: detail.ratingForm?.schemeId || null, - totalMaxScore: maxScore - }, - stageId: detail.stageId || null, - submissionId: detail.id, - criteria, + activity, + criteria: activity.judgeCriteria || [], scores, maxScore, - totalScore: 0, - existingReview: null, + existingReview: null, // 鏆傛椂璁句负null锛屽悗缁渶瑕佹煡璇㈠凡鏈夎瘎鍒� reviewStatus: 'PENDING', comment: '' }) - + this.calculateTotalScore() - + // 妫�鏌ユ槸鍚﹀凡鏈夎瘎鍒� this.checkReviewStatus() } @@ -224,7 +215,7 @@ currentJudgeRating(activityPlayerId: $activityPlayerId) { id totalScore - remark + comment status ratedAt items { @@ -248,98 +239,38 @@ if (rating.items) { rating.items.forEach(item => { - const numericScore = item.score !== undefined && item.score !== null ? Number(item.score) : 0 - scores[item.ratingItemId] = numericScore - totalScore += numericScore + scores[item.ratingItemId] = item.score + totalScore += item.score }) } - - const updatedCriteria = this.data.criteria.map(criterion => { - const value = scores[criterion.id] !== undefined ? scores[criterion.id] : 0 - return { - ...criterion, - currentScore: value - } - }) - - const normalizedTotal = Number(totalScore.toFixed(2)) - + this.setData({ scores, - criteria: updatedCriteria, - totalScore: normalizedTotal, - comment: rating.remark || rating.comment || '', - existingReview: { - ...rating, - totalScore: rating.totalScore ? Number(rating.totalScore) : normalizedTotal, - reviewedAt: rating.ratedAt || rating.reviewedAt || rating.updateTime || null - }, - reviewStatus: 'COMPLETED' + totalScore, + comment: rating.comment || '', + existingReview: rating, + reviewStatus: rating.status || 'COMPLETED' }) console.log('宸插姞杞界幇鏈夎瘎鍒�:', rating) } else { console.log('褰撳墠璇勫灏氭湭璇勫垎') } - - this.calculateTotalScore() } catch (error) { console.error('妫�鏌ヨ瘎瀹$姸鎬佸け璐�:', error) } }, - normalizeScore(value, criterion) { - const maxScore = Number(criterion.maxScore || 0) - const step = Number(criterion.step || (maxScore > 20 ? 1 : 0.5)) - if (Number.isNaN(value)) { - value = 0 - } - let normalized = Math.round(value / step) * step - if (normalized < 0) normalized = 0 - if (normalized > maxScore) normalized = maxScore - return Number(normalized.toFixed(2)) - }, - - updateCriterionScore(criterionId, index, value) { - const criterion = this.data.criteria[index] - if (!criterion) return - const normalized = this.normalizeScore(value, criterion) - - this.setData({ - [`scores.${criterionId}`]: normalized, - [`criteria[${index}].currentScore`]: normalized - }) - - this.calculateTotalScore() - }, - // 璇勫垎鏀瑰彉 onScoreChange(e) { - const { criterionId, index } = e.currentTarget.dataset - const criterion = this.data.criteria[index] - if (!criterion) return - - const inputValue = Number(e.detail.value) - const newScore = this.normalizeScore(inputValue, criterion) - this.updateCriterionScore(criterionId, index, newScore) - }, - - increaseScore(e) { - const { criterionId, index } = e.currentTarget.dataset - const criterion = this.data.criteria[index] - if (!criterion) return - const current = Number(this.data.scores[criterionId] || criterion.currentScore || 0) - const step = criterion.step || (criterion.maxScore > 20 ? 1 : 0.5) - this.updateCriterionScore(criterionId, index, current + step) - }, - - decreaseScore(e) { - const { criterionId, index } = e.currentTarget.dataset - const criterion = this.data.criteria[index] - if (!criterion) return - const current = Number(this.data.scores[criterionId] || criterion.currentScore || 0) - const step = criterion.step || (criterion.maxScore > 20 ? 1 : 0.5) - this.updateCriterionScore(criterionId, index, current - step) + const { criterionId } = e.currentTarget.dataset + const { value } = e.detail + + this.setData({ + [`scores.${criterionId}`]: parseInt(value) + }) + + this.calculateTotalScore() }, // 璁$畻鎬诲垎 @@ -348,11 +279,11 @@ let totalScore = 0 criteria.forEach(criterion => { - const score = Number(scores[criterion.id] || 0) - totalScore += score + const score = scores[criterion.id] || 0 + totalScore += score * (criterion.weight || 1) }) - this.setData({ totalScore: Number(totalScore.toFixed(2)) }) + this.setData({ totalScore }) }, // 璇勫鎰忚杈撳叆 @@ -364,58 +295,106 @@ // 濯掍綋鐐瑰嚮 onMediaTap(e) { - const index = Number(e.currentTarget.dataset.index) - const mediaList = this.data.submission?.mediaList || [] - const media = mediaList[index] - if (!media) return - - if (media.mediaType === 'image') { - const imageUrls = mediaList - .filter(item => item.mediaType === 'image') - .map(item => item.url) + const { url, type } = e.currentTarget.dataset + + if (type === 'image') { wx.previewImage({ - current: media.url, - urls: imageUrls + current: url, + urls: this.data.submission.images || [] }) - } else if (media.mediaType === 'video') { - wx.navigateTo({ - url: `/pages/video/video?url=${encodeURIComponent(media.url)}&title=${encodeURIComponent(media.name)}` + } else if (type === 'video') { + this.setData({ + showMediaPreview: true, + currentMedia: url, + mediaType: 'video' }) - } else { - this.openDocumentMedia(media) } }, - async openDocumentMedia(media) { + // 鍏抽棴濯掍綋棰勮 + onCloseMediaPreview() { + this.setData({ + showMediaPreview: false, + currentMedia: null + }) + }, + + // 涓嬭浇鏂囦欢 + async onDownloadFile(e) { + const { fileId, fileName, fileUrl } = e.currentTarget.dataset + try { - wx.showLoading({ title: '鎵撳紑涓�...' }) - const downloadRes = await new Promise((resolve, reject) => { - wx.downloadFile({ - url: media.url, - success: resolve, - fail: reject - }) - }) - - if (downloadRes.statusCode !== 200) { - throw new Error('鏂囦欢涓嬭浇澶辫触') + // 娣诲姞鍒颁笅杞戒腑鍒楄〃 + const downloadingFiles = [...this.data.downloadingFiles, fileId] + + // 鍚屾椂鏇存柊鏂囦欢鐨刬sDownloading瀛楁 + const submission = { ...this.data.submission } + if (submission.files) { + submission.files = submission.files.map(file => ({ + ...file, + isDownloading: file.id === fileId ? true : file.isDownloading + })) } - - await new Promise((resolve, reject) => { - wx.openDocument({ - filePath: downloadRes.tempFilePath, - showMenu: true, - success: resolve, - fail: reject - }) + + this.setData({ + downloadingFiles, + submission + }) + + wx.showLoading({ title: '涓嬭浇涓�...' }) + + const result = await wx.downloadFile({ + url: fileUrl, + success: (res) => { + if (res.statusCode === 200) { + // 淇濆瓨鍒扮浉鍐屾垨鏂囦欢 + wx.saveFile({ + tempFilePath: res.tempFilePath, + success: () => { + wx.showToast({ + title: '涓嬭浇鎴愬姛', + icon: 'success' + }) + }, + fail: () => { + wx.showToast({ + title: '淇濆瓨澶辫触', + icon: 'error' + }) + } + }) + } + }, + fail: () => { + wx.showToast({ + title: '涓嬭浇澶辫触', + icon: 'error' + }) + } }) } catch (error) { - console.error('鎵撳紑鏂囦欢澶辫触:', error) + console.error('涓嬭浇鏂囦欢澶辫触:', error) wx.showToast({ - title: '鏃犳硶鎵撳紑鏂囦欢', + title: '涓嬭浇澶辫触', icon: 'error' }) } finally { + // 浠庝笅杞戒腑鍒楄〃绉婚櫎 + const downloadingFiles = this.data.downloadingFiles.filter(id => id !== fileId) + + // 鍚屾椂鏇存柊鏂囦欢鐨刬sDownloading瀛楁 + const submission = { ...this.data.submission } + if (submission.files) { + submission.files = submission.files.map(file => ({ + ...file, + isDownloading: file.id === fileId ? false : file.isDownloading + })) + } + + this.setData({ + downloadingFiles, + submission + }) wx.hideLoading() } }, @@ -423,7 +402,6 @@ // 楠岃瘉璇勫鏁版嵁 validateReview() { const { scores, criteria, comment } = this.data - const commentText = (comment || '').trim() // 妫�鏌ユ槸鍚︽墍鏈夋爣鍑嗛兘宸茶瘎鍒� for (let criterion of criteria) { @@ -437,7 +415,7 @@ } // 妫�鏌ヨ瘎瀹℃剰瑙� - if (!commentText) { + if (!comment.trim()) { wx.showToast({ title: '璇峰~鍐欒瘎瀹℃剰瑙�', icon: 'error' @@ -445,7 +423,7 @@ return false } - if (commentText.length < 10) { + if (comment.trim().length < 10) { wx.showToast({ title: '璇勫鎰忚鑷冲皯10涓瓧绗�', icon: 'error' @@ -454,6 +432,54 @@ } return true + }, + + // 淇濆瓨鑽夌 + async onSaveDraft() { + try { + wx.showLoading({ title: '淇濆瓨涓�...' }) + + const { activityPlayerId, scores, comment, criteria, activity } = this.data + + // 鏋勫缓璇勫垎椤规暟缁� + const ratings = criteria.map(criterion => ({ + itemId: criterion.id, + score: scores[criterion.id] || 0 + })) + + const mutation = ` + mutation SaveActivityPlayerRating($input: ActivityPlayerRatingInput!) { + saveActivityPlayerRating(input: $input) + } + ` + + const input = { + activityPlayerId, + stageId: activity.stageId, + ratings, + comment: comment.trim() + } + + const result = await graphqlRequest(mutation, { input }) + + if (result && result.saveActivityPlayerRating) { + wx.showToast({ + title: '鑽夌宸蹭繚瀛�', + icon: 'success' + }) + + // 閲嶆柊鍔犺浇璇勫垎鐘舵�� + await this.checkReviewStatus() + } + } catch (error) { + console.error('淇濆瓨鑽夌澶辫触:', error) + wx.showToast({ + title: '淇濆瓨澶辫触', + icon: 'error' + }) + } finally { + wx.hideLoading() + } }, // 鎻愪氦璇勫 @@ -479,23 +505,12 @@ this.setData({ submitting: true }) wx.showLoading({ title: '鎻愪氦涓�...' }) - const { activityPlayerId, scores, comment, criteria, stageId } = this.data - const commentText = (comment || '').trim() - - if (!stageId) { - wx.showToast({ - title: '缂哄皯闃舵淇℃伅锛屾棤娉曟彁浜�', - icon: 'none' - }) - this.setData({ submitting: false }) - wx.hideLoading() - return - } + const { activityPlayerId, scores, comment, criteria, activity } = this.data // 鏋勫缓璇勫垎椤规暟缁� const ratings = criteria.map(criterion => ({ itemId: criterion.id, - score: Number(scores[criterion.id] || 0) + score: scores[criterion.id] || 0 })) const mutation = ` @@ -506,9 +521,9 @@ const input = { activityPlayerId, - stageId, + stageId: activity.stageId, ratings, - comment: commentText + comment: comment.trim() } const result = await graphqlRequest(mutation, { input }) @@ -554,17 +569,34 @@ // 鑱旂郴鍙傝禌鑰� onContactParticipant() { const { submission } = this.data - const phone = submission?.participant?.phone - if (phone) { - wx.makePhoneCall({ - phoneNumber: phone - }) - } else { - wx.showToast({ - title: '鏆傛棤鑱旂郴鏂瑰紡', - icon: 'none' + + if (submission.participant) { + wx.showActionSheet({ + itemList: ['鍙戦�佹秷鎭�', '鏌ョ湅璇︽儏'], + success: (res) => { + switch (res.tapIndex) { + case 0: + // 鍙戦�佹秷鎭姛鑳� + wx.navigateTo({ + url: `/pages/chat/chat?userId=${submission.participant.id}` + }) + break + case 1: + // 鏌ョ湅鐢ㄦ埛璇︽儏 + wx.navigateTo({ + url: `/pages/user/profile?userId=${submission.participant.id}` + }) + break + } + } }) } + }, + + // 鑾峰彇璇勫垎绛夌骇鏂囨湰 + getScoreLabel(score) { + const option = this.data.scoreOptions.find(opt => opt.value === score) + return option ? option.label : `${score}鍒哷 }, // 鑾峰彇鏂囦欢澶у皬鏂囨湰 @@ -578,47 +610,9 @@ } }, - // 缁熶竴澶勭悊鎬у埆鏄剧ず鏂囨湰 - getGenderLabel(gender) { - if (gender === null || gender === undefined || gender === '') { - return '鏈~鍐�' - } - - const normalized = String(gender).trim().toLowerCase() - - if (normalized === '') { - return '鏈~鍐�' - } - - if (normalized === 'male' || normalized === 'm') { - return '鐢�' - } - if (normalized === 'female' || normalized === 'f') { - return '濂�' - } - - if (/^-?\d+$/.test(normalized)) { - const numeric = Number(normalized) - if (numeric === 1) return '鐢�' - if (numeric === 0) return '濂�' - if (numeric === 2) return '濂�' - } - - return gender === undefined || gender === null ? '鏈~鍐�' : String(gender) - }, - - // 缁熶竴澶勭悊鍑虹敓鏃ユ湡鏄剧ず鏂囨湰 - getBirthdayText(dateString) { - if (!dateString) { - return '鏈~鍐�' - } - const formatted = formatDateUtil(dateString, 'YYYY-MM-DD') - return formatted || '鏈~鍐�' - }, - // 鏍煎紡鍖栨棩鏈� formatDate(dateString) { - return formatDateUtil(dateString, 'YYYY-MM-DD HH:mm') + return formatDate(dateString, 'YYYY-MM-DD HH:mm') }, // 鍒嗕韩椤甸潰 diff --git a/wx/pages/message/message.js b/wx/pages/message/message.js index e390706..4fa1f29 100644 --- a/wx/pages/message/message.js +++ b/wx/pages/message/message.js @@ -21,22 +21,8 @@ // 鍔犺浇娑堟伅鍒楄〃 loadMessages() { - // 妫�鏌ョ敤鎴锋槸鍚﹀凡鐧诲綍锛屽鏋済lobalData涓病鏈夛紝灏濊瘯浠庢湰鍦板瓨鍌ㄦ仮澶� - let userInfo = app.globalData.userInfo - if (!userInfo || !userInfo.userId) { - console.log('globalData涓病鏈塽serInfo锛屽皾璇曚粠鏈湴瀛樺偍鎭㈠') - try { - const storedUserInfo = wx.getStorageSync('userInfo') - if (storedUserInfo && storedUserInfo.userId) { - console.log('浠庢湰鍦板瓨鍌ㄦ仮澶島serInfo鎴愬姛') - app.globalData.userInfo = storedUserInfo - userInfo = storedUserInfo - } - } catch (error) { - console.error('浠庢湰鍦板瓨鍌ㄦ仮澶島serInfo澶辫触:', error) - } - } - + // 妫�鏌ョ敤鎴锋槸鍚﹀凡鐧诲綍 + const userInfo = app.globalData.userInfo if (!userInfo || !userInfo.userId) { console.error('鐢ㄦ埛鏈櫥褰曟垨userId涓嶅瓨鍦�') wx.showToast({ diff --git a/wx/pages/profile/personal-info.js b/wx/pages/profile/personal-info.js index 1fbdb5f..b9f5f07 100644 --- a/wx/pages/profile/personal-info.js +++ b/wx/pages/profile/personal-info.js @@ -157,10 +157,17 @@ title: '鑾峰彇涓�...' }) - // TODO: 璋冪敤鍚庣API瑙e瘑鎵嬫満鍙� + // 璋冪敤鍚庣API瑙e瘑鎵嬫満鍙� + const app = getApp() + const sessionKey = app.globalData.sessionKey + + if (!sessionKey) { + throw new Error('SessionKey涓嶅瓨鍦紝璇烽噸鏂扮櫥褰�') + } + const mutation = ` - mutation DecryptPhoneNumber($encryptedData: String!, $iv: String!) { - decryptPhoneNumber(encryptedData: $encryptedData, iv: $iv) { + mutation DecryptPhoneNumber($encryptedData: String!, $iv: String!, $sessionKey: String!) { + decryptPhoneNumber(encryptedData: $encryptedData, iv: $iv, sessionKey: $sessionKey) { phoneNumber } } @@ -168,7 +175,8 @@ const variables = { encryptedData: e.detail.encryptedData, - iv: e.detail.iv + iv: e.detail.iv, + sessionKey: sessionKey } const result = await graphqlRequest(mutation, variables) @@ -229,10 +237,13 @@ return } - if (!userInfo.phone) { - wx.showToast({ - title: '璇疯幏鍙栨墜鏈哄彿', - icon: 'none' + // 寮哄埗瑕佹眰鎺堟潈鐢佃瘽鍙风爜 + if (!userInfo.phone || userInfo.phone.trim() === '') { + wx.showModal({ + title: '闇�瑕佹巿鏉冩墜鏈哄彿', + content: '鏍规嵁骞冲彴瑙勫畾锛屽繀椤绘巿鏉冩墜鏈哄彿鐮佹墠鑳戒繚瀛樼敤鎴蜂俊鎭�傝鍏堣幏鍙栨墜鏈哄彿鐮佹巿鏉冦��', + showCancel: false, + confirmText: '鎴戠煡閬撲簡' }) return } @@ -284,15 +295,17 @@ // 淇濆瓨鍒版湰鍦板瓨鍌� wx.setStorageSync('userInfo', app.globalData.userInfo) - wx.showToast({ + // 鏄剧ず淇濆瓨鎴愬姛鎻愮ず锛屽苟鎻愰啋闇�瑕侀噸鏂扮櫥褰� + wx.showModal({ title: '淇濆瓨鎴愬姛', - icon: 'success' + content: '鐢ㄦ埛淇℃伅宸蹭繚瀛樻垚鍔熴�備负浜嗚幏鍙栨渶鏂扮殑鍏宠仈淇℃伅锛堝鍙傝禌鑰呫�佽瘎濮斻�佸憳宸ヨ韩浠斤級锛岀郴缁熷皢閲嶆柊鐧诲綍銆�', + showCancel: false, + confirmText: '纭畾', + success: () => { + // 寮哄埗閲嶆柊鐧诲綍浠ヨ幏鍙栨渶鏂扮殑鐢ㄦ埛鍏宠仈淇℃伅 + this.forceRelogin() + } }) - - // 寤惰繜杩斿洖涓婁竴椤� - setTimeout(() => { - wx.navigateBack() - }, 1500) } else { throw new Error('淇濆瓨澶辫触') } @@ -385,5 +398,56 @@ // 鑾峰彇鏂囦欢鎵╁睍鍚� getFileExtension(fileName) { return fileName.split('.').pop().toLowerCase() + }, + + // 寮哄埗閲嶆柊鐧诲綍 + forceRelogin() { + wx.showLoading({ + title: '閲嶆柊鐧诲綍涓�...' + }) + + try { + // 娓呴櫎鏈湴瀛樺偍鐨勭櫥褰曚俊鎭� + wx.removeStorageSync('token') + wx.removeStorageSync('userInfo') + wx.removeStorageSync('sessionKey') + + // 娓呴櫎鍏ㄥ眬鏁版嵁 + app.globalData.token = null + app.globalData.userInfo = null + app.globalData.sessionKey = null + + console.log('宸叉竻闄ゆ湰鍦扮櫥褰曚俊鎭紝鍑嗗閲嶆柊鐧诲綍') + + // 閲嶆柊璋冪敤鐧诲綍娴佺▼ + app.login() + + // 寤惰繜涓�娈垫椂闂村悗闅愯棌loading骞惰繑鍥炰笂涓�椤� + setTimeout(() => { + wx.hideLoading() + wx.showToast({ + title: '閲嶆柊鐧诲綍瀹屾垚', + icon: 'success' + }) + + // 杩斿洖涓婁竴椤� + setTimeout(() => { + wx.navigateBack() + }, 1000) + }, 2000) + + } catch (error) { + console.error('寮哄埗閲嶆柊鐧诲綍澶辫触:', error) + wx.hideLoading() + wx.showToast({ + title: '閲嶆柊鐧诲綍澶辫触', + icon: 'none' + }) + + // 鍗充娇澶辫触涔熻繑鍥炰笂涓�椤� + setTimeout(() => { + wx.navigateBack() + }, 1500) + } } }) \ No newline at end of file diff --git a/wx/pages/project/detail.js b/wx/pages/project/detail.js index 5e56ab7..9e95a97 100644 --- a/wx/pages/project/detail.js +++ b/wx/pages/project/detail.js @@ -43,8 +43,6 @@ if (projectDetail.submissionFiles) { projectDetail.submissionFiles.forEach(file => { file.fileSizeText = this.formatFileSize(file.fileSize) - // 瀛楁宸茬粡鏄纭殑鍚嶇О锛屾棤闇�鏄犲皠 - // fullUrl, fullThumbUrl, fileSize, fileExt 閮芥槸姝g‘鐨勫瓧娈靛悕 }) } @@ -87,62 +85,42 @@ query GetProjectDetail($id: ID!) { activityPlayerDetail(id: $id) { id - playerInfo { - id - name - phone - gender - birthday - education - introduction - description - avatarUrl - avatar { - id - fullUrl - fullThumbUrl - name - fileSize - fileExt - } - userInfo { - userId - name - phone - avatarUrl - } - } - regionInfo { - id - name - fullPath - } - activityName + activityId + playerId + playerName + playerGender + playerPhone + playerEducation + playerBirthDate + playerIdCard + playerAddress projectName - description - feedback - state - stageId - submissionFiles { + projectDescription + projectCategory + projectTags + projectFiles { id - fullUrl - fullThumbUrl - name + fileName + fileUrl fileSize - fileExt - mediaType + fileType + uploadTime } - ratingForm { - schemeId - schemeName - items { - id - name - maxScore - orderNo - } - totalMaxScore + submitTime + reviewTime + reviewerId + reviewerName + score + rating { + id + judgeId + judgeName + score + feedback + ratingTime } + state + feedback } } ` @@ -157,14 +135,22 @@ // 鑾峰彇璇勫垎缁熻 async getRatingStatsFromAPI(projectId) { - // 鏆傛椂杩斿洖绌虹殑璇勫垎鏁版嵁锛岄伩鍏岹raphQL鏌ヨ閿欒 - // TODO: 闇�瑕佸悗绔彁渚涘悎閫傜殑璇勫垎缁熻鏌ヨ鎺ュ彛 - try { - return { - averageScore: null, - ratingCount: 0, - judgeRatings: [] + const query = ` + query GetRatingStats($activityPlayerId: ID!) { + ratingStats(activityPlayerId: $activityPlayerId) { + averageScore + totalRatings + scoreDistribution { + score + count + } + } } + ` + + try { + const result = await app.graphqlRequest(query, { activityPlayerId: projectId }) + return result.ratingStats } catch (error) { throw error } diff --git a/wx/pages/registration/registration.js b/wx/pages/registration/registration.js index 05a6728..3fd91ec 100644 --- a/wx/pages/registration/registration.js +++ b/wx/pages/registration/registration.js @@ -1094,8 +1094,8 @@ errors.name = '璇疯緭鍏ュ鍚�'; } - if (!formData.phone.trim()) { - errors.phone = '璇疯緭鍏ユ墜鏈哄彿'; + if (!formData.phone || !formData.phone.trim()) { + errors.phone = '璇峰厛鎺堟潈鑾峰彇鎵嬫満鍙�'; } else if (!/^1[3-9]\d{9}$/.test(formData.phone)) { errors.phone = '璇疯緭鍏ユ纭殑鎵嬫満鍙�'; } @@ -1200,6 +1200,17 @@ }) return } + + // 棰濆妫�鏌ワ細纭繚蹇呴』鎺堟潈鐢佃瘽鍙风爜 + if (!this.data.formData.phone || !this.data.formData.phone.trim()) { + wx.showModal({ + title: '闇�瑕佹巿鏉冩墜鏈哄彿', + content: '鏍规嵁骞冲彴瑙勫畾锛屽繀椤绘巿鏉冩墜鏈哄彿鐮佹墠鑳芥姤鍚嶅弬璧涖�傝鍏堣幏鍙栨墜鏈哄彿鐮佹巿鏉冦��', + showCancel: false, + confirmText: '鎴戠煡閬撲簡' + }) + return + } this.setData({ isSubmitting: true }) diff --git a/wx/pages/review/index.js b/wx/pages/review/index.js index b469a0b..cc40350 100644 --- a/wx/pages/review/index.js +++ b/wx/pages/review/index.js @@ -2,14 +2,6 @@ const app = getApp() const { graphqlRequest, formatDate } = require('../../lib/utils') -const GET_RATING_STATS_QUERY = ` - query GetRatingStats($activityPlayerId: ID!) { - judgeRatingsForPlayer(activityPlayerId: $activityPlayerId) { - hasRated - } - } -` - Page({ data: { loading: false, @@ -63,7 +55,7 @@ // 鍒囨崲閫夐」鍗� switchTab(e) { - const index = parseInt(e.currentTarget.dataset.index) || 0 + const index = parseInt(e.currentTarget.dataset.index) // 灏嗗瓧绗︿覆杞崲涓烘暟瀛� if (index === this.data.currentTab) return this.setData({ @@ -118,29 +110,10 @@ ]) } catch (error) { console.error('鍔犺浇鏁版嵁澶辫触:', error) - - // 妫�鏌ユ槸鍚︽槸璁よ瘉鐩稿叧閿欒 - if (error.message && ( - error.message.includes('娌℃湁鏉冮檺璁块棶') || - error.message.includes('璇峰厛鐧诲綍') || - error.message.includes('閲嶆柊鐧诲綍') - )) { - wx.showToast({ - title: '姝e湪閲嶆柊鐧诲綍...', - icon: 'loading', - duration: 2000 - }) - - // 绛夊緟涓�娈垫椂闂村悗閲嶈瘯 - setTimeout(() => { - this.loadData() - }, 2000) - } else { - wx.showToast({ - title: '鍔犺浇澶辫触锛岃閲嶈瘯', - icon: 'none' - }) - } + wx.showToast({ + title: '鍔犺浇澶辫触', + icon: 'none' + }) } finally { this.setData({ loading: false }) wx.stopPullDownRefresh() @@ -174,7 +147,7 @@ } // 鏍规嵁褰撳墠閫夐」鍗℃瀯寤轰笉鍚岀殑鏌ヨ - switch (currentTab) { + switch (parseInt(currentTab)) { // 纭繚currentTab鏄暟瀛� case 0: // 鎴戞湭璇勫 query = ` query GetUnReviewedProjects($page: Int!, $pageSize: Int!, $searchKeyword: String) { @@ -234,13 +207,39 @@ } ` break + default: + console.error('鏃犳晥鐨勯�夐」鍗$储寮�:', currentTab) + query = ` + query GetUnReviewedProjects($page: Int!, $pageSize: Int!, $searchKeyword: String) { + unReviewedProjects(page: $page, pageSize: $pageSize, searchKeyword: $searchKeyword) { + items { + id + projectName + activityName + stageName + studentName + submitTime + status + } + total + hasMore + } + } + ` + break + } + + // 妫�鏌uery鏄惁涓虹┖ + if (!query || query.trim() === '') { + console.error('GraphQL鏌ヨ涓虹┖锛屾棤娉曟墽琛岃姹�') + return } const result = await graphqlRequest(query, variables) if (result) { - const dataKey = currentTab === 0 ? 'unReviewedProjects' : - currentTab === 1 ? 'reviewedProjects' : + const dataKey = parseInt(currentTab) === 0 ? 'unReviewedProjects' : + parseInt(currentTab) === 1 ? 'reviewedProjects' : 'studentUnReviewedProjects' const data = result[dataKey] @@ -250,13 +249,13 @@ const projects = data.items.map(item => ({ ...item, submitTime: item.submitTime ? formatDate(item.submitTime) : '', - reviewTime: item.reviewTime ? formatDate(item.reviewTime) : '' + reviewTime: item.reviewTime ? formatDate(item.reviewTime) : '', + statusText: this.getStatusText(item.status), + statusType: this.getStatusType(item.status) })) - const projectsWithRatingCount = await this.enrichProjectsWithRatingCounts(projects) - this.setData({ - projectList: isLoadMore ? [...this.data.projectList, ...projectsWithRatingCount] : projectsWithRatingCount, + projectList: isLoadMore ? [...this.data.projectList, ...projects] : projects, hasMore: data.hasMore || false, currentPage: variables.page }) @@ -301,39 +300,26 @@ } }, - async enrichProjectsWithRatingCounts(projects) { - if (!Array.isArray(projects) || projects.length === 0) { - return projects || [] + // 鑾峰彇鐘舵�佹枃鏈� + getStatusText(status) { + const statusMap = { + 'SUBMITTED': '宸叉彁浜�', + 'UNDER_REVIEW': '璇勫涓�', + 'REVIEWED': '宸茶瘎瀹�', + 'REJECTED': '宸叉嫆缁�' } - - try { - const counts = await Promise.all(projects.map(project => this.getProjectRatingCount(project.id))) - return projects.map((project, index) => ({ - ...project, - ratingCount: counts[index] - })) - } catch (error) { - console.error('鎵归噺鑾峰彇璇勫娆℃暟澶辫触:', error) - return projects.map(project => ({ - ...project, - ratingCount: typeof project.ratingCount === 'number' ? project.ratingCount : 0 - })) - } + return statusMap[status] || status }, - async getProjectRatingCount(activityPlayerId) { - if (!activityPlayerId) { - return 0 + // 鑾峰彇鐘舵�佺被鍨� + getStatusType(status) { + const typeMap = { + 'SUBMITTED': 'info', + 'UNDER_REVIEW': 'warning', + 'REVIEWED': 'success', + 'REJECTED': 'danger' } - - try { - const result = await graphqlRequest(GET_RATING_STATS_QUERY, { activityPlayerId }) - const ratings = result?.judgeRatingsForPlayer || [] - return ratings.filter(item => item?.hasRated).length - } catch (error) { - console.error(`鑾峰彇椤圭洰 ${activityPlayerId} 鐨勮瘎瀹℃鏁板け璐�:`, error) - return 0 - } + return typeMap[status] || 'info' }, // 鑾峰彇绌虹姸鎬佹枃鏈� -- Gitblit v1.8.0