From 7ba080d35812e6db7bd5aa8f88161c02653eb6c1 Mon Sep 17 00:00:00 2001 From: lrj <owen.stl@gmail.com> Date: 星期三, 24 九月 2025 22:42:35 +0800 Subject: [PATCH] feat: 优化员工和评委编辑功能的密码重置逻辑 --- backend/src/test/java/com/rongyichuang/auth/GraphQLLoginTest.java | 40 + backend/src/test/java/com/rongyichuang/employee/DirectEmployeeServiceTest.java | 50 + web/src/utils/auth.ts | 165 ++++++ web/src/layout/index.vue | 10 backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java | 2 backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java | 27 + backend/src/test/java/com/rongyichuang/CreateTestUsersTest.java | 74 ++ backend/src/main/java/com/rongyichuang/auth/service/AuthService.java | 151 +++++ backend/src/main/java/com/rongyichuang/auth/dto/LoginResponse.java | 288 ++++++++++ backend/src/main/resources/graphql/auth.graphqls | 53 + backend/src/main/java/com/rongyichuang/common/api/MediaFieldResolver.java | 2 backend/src/main/java/com/rongyichuang/common/api/AppConfigGraphqlApi.java | 2 web/src/views/employee/EmployeeForm.vue | 50 + backend/src/main/java/com/rongyichuang/employee/repository/EmployeeRepository.java | 5 test_users.sql | 13 backend/src/main/java/com/rongyichuang/common/api/MediaGraphqlApi.java | 2 backend/src/test/java/com/rongyichuang/VerifyUserDataTest.java | 94 +++ /dev/null | 36 - web/src/views/login/index.vue | 41 + backend/src/test/java/com/rongyichuang/auth/DirectLoginTest.java | 40 + backend/src/main/resources/application.yml | 8 web/src/router/index.ts | 25 web/src/utils/graphql.ts | 143 +++++ backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java | 97 +++ backend/src/main/java/com/rongyichuang/auth/dto/LoginRequest.java | 33 + backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java | 99 +++ backend/src/test/java/com/rongyichuang/CreateMultiRoleUserTest.java | 69 ++ 27 files changed, 1,542 insertions(+), 77 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..fab4042 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/auth/api/AuthGraphqlApi.java @@ -0,0 +1,27 @@ +package com.rongyichuang.auth.api; + +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; + + /** + * Web绔敤鎴风櫥褰� + */ + @MutationMapping + public LoginResponse webLogin(@Argument LoginRequest input) { + return authService.login(input); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/auth/dto/LoginRequest.java b/backend/src/main/java/com/rongyichuang/auth/dto/LoginRequest.java new file mode 100644 index 0000000..3748df9 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/auth/dto/LoginRequest.java @@ -0,0 +1,33 @@ +package com.rongyichuang.auth.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +/** + * 鐧诲綍璇锋眰DTO + */ +public class LoginRequest { + + @NotBlank(message = "鎵嬫満鍙蜂笉鑳戒负绌�") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "鎵嬫満鍙锋牸寮忎笉姝g‘") + private String phone; + + @NotBlank(message = "瀵嗙爜涓嶈兘涓虹┖") + private String password; + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/auth/dto/LoginResponse.java b/backend/src/main/java/com/rongyichuang/auth/dto/LoginResponse.java new file mode 100644 index 0000000..a124112 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/auth/dto/LoginResponse.java @@ -0,0 +1,288 @@ +package com.rongyichuang.auth.dto; + +/** + * 鐧诲綍鍝嶅簲DTO + */ +public class LoginResponse { + + private String token; + private UserInfo userInfo; + + public LoginResponse() {} + + public LoginResponse(String token, UserInfo userInfo) { + this.token = token; + this.userInfo = userInfo; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public UserInfo getUserInfo() { + return userInfo; + } + + public void setUserInfo(UserInfo userInfo) { + this.userInfo = userInfo; + } + + /** + * 鐢ㄦ埛淇℃伅鍐呴儴绫� + */ + public static class UserInfo { + private Long userId; + private String name; + private String phone; + private String userType; // 涓昏瑙掕壊绫诲瀷锛氫紭鍏坋mployee锛岀劧鍚巎udge锛屾渶鍚巔layer + private EmployeeInfo employee; + private JudgeInfo judge; + private PlayerInfo player; + + public UserInfo() {} + + public UserInfo(Long userId, String name, String phone, String userType) { + this.userId = userId; + this.name = name; + this.phone = phone; + this.userType = userType; + } + + // Getters and Setters + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getUserType() { + return userType; + } + + public void setUserType(String userType) { + this.userType = userType; + } + + public EmployeeInfo getEmployee() { + return employee; + } + + public void setEmployee(EmployeeInfo employee) { + this.employee = employee; + } + + public JudgeInfo getJudge() { + return judge; + } + + public void setJudge(JudgeInfo judge) { + this.judge = judge; + } + + public PlayerInfo getPlayer() { + return player; + } + + public void setPlayer(PlayerInfo player) { + this.player = player; + } + } + + /** + * 鍛樺伐淇℃伅 + */ + public static class EmployeeInfo { + private Long id; + private String name; + private String roleId; + private String description; + + public EmployeeInfo() {} + + public EmployeeInfo(Long id, String name, String roleId, String description) { + this.id = id; + this.name = name; + this.roleId = roleId; + this.description = description; + } + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getRoleId() { + return roleId; + } + + public void setRoleId(String roleId) { + this.roleId = roleId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } + + /** + * 璇勫淇℃伅 + */ + public static class JudgeInfo { + private Long id; + private String name; + private String title; + private String company; + private String description; + + public JudgeInfo() {} + + public JudgeInfo(Long id, String name, String title, String company, String description) { + this.id = id; + this.name = name; + this.title = title; + this.company = company; + this.description = description; + } + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getCompany() { + return company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } + + /** + * 瀛﹀憳淇℃伅鍐呴儴绫� + */ + public static class PlayerInfo { + private Long id; + private String name; + private String phone; + private String description; + private Integer auditState; // 瀹℃牳鐘舵�侊細0-绛夊緟瀹℃牳锛�1-瀹℃牳閫氳繃锛�2-涓嶉�氳繃 + + public PlayerInfo() {} + + public PlayerInfo(Long id, String name, String phone, String description, Integer auditState) { + this.id = id; + this.name = name; + this.phone = phone; + this.description = description; + this.auditState = auditState; + } + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Integer getAuditState() { + return auditState; + } + + public void setAuditState(Integer auditState) { + this.auditState = auditState; + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java b/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java new file mode 100644 index 0000000..e691bb4 --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/auth/service/AuthService.java @@ -0,0 +1,151 @@ +package com.rongyichuang.auth.service; + +import com.rongyichuang.auth.dto.LoginRequest; +import com.rongyichuang.auth.dto.LoginResponse; +import com.rongyichuang.auth.util.JwtUtil; +import com.rongyichuang.employee.entity.Employee; +import com.rongyichuang.employee.repository.EmployeeRepository; +import com.rongyichuang.judge.entity.Judge; +import com.rongyichuang.judge.repository.JudgeRepository; +import com.rongyichuang.player.entity.Player; +import com.rongyichuang.player.repository.PlayerRepository; +import com.rongyichuang.user.entity.User; +import com.rongyichuang.user.repository.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * 璁よ瘉鏈嶅姟绫� + */ +@Service +public class AuthService { + + private static final Logger logger = LoggerFactory.getLogger(AuthService.class); + + @Autowired + private UserRepository userRepository; + + @Autowired + private EmployeeRepository employeeRepository; + + @Autowired + private JudgeRepository judgeRepository; + + @Autowired + private PlayerRepository playerRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private JwtUtil jwtUtil; + + /** + * 鐢ㄦ埛鐧诲綍 + */ + public LoginResponse login(LoginRequest loginRequest) { + logger.info("鐢ㄦ埛鐧诲綍灏濊瘯锛屾墜鏈哄彿: {}", loginRequest.getPhone()); + + // 1. 閫氳繃鎵嬫満鍙锋煡璇㈢敤鎴� + Optional<User> userOpt = userRepository.findByPhone(loginRequest.getPhone()); + if (userOpt.isEmpty()) { + logger.warn("鐢ㄦ埛涓嶅瓨鍦紝鎵嬫満鍙�: {}", loginRequest.getPhone()); + throw new BadCredentialsException("鐢ㄦ埛涓嶅瓨鍦�"); + } + + User user = userOpt.get(); + + // 2. 妫�鏌ュ瘑鐮佹槸鍚︿负绌� + if (user.getPassword() == null || user.getPassword().trim().isEmpty()) { + logger.warn("鐢ㄦ埛瀵嗙爜涓虹┖锛屾墜鏈哄彿: {}", loginRequest.getPhone()); + throw new BadCredentialsException("瀵嗙爜涓嶆纭�"); + } + + // 3. 楠岃瘉瀵嗙爜 + if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { + logger.warn("瀵嗙爜楠岃瘉澶辫触锛屾墜鏈哄彿: {}", loginRequest.getPhone()); + throw new BadCredentialsException("瀵嗙爜涓嶆纭�"); + } + + // 4. 鏌ユ壘鍏宠仈鐨勫憳宸ャ�佽瘎濮斿拰瀛﹀憳淇℃伅 + Optional<Employee> employeeOpt = employeeRepository.findByUserId(user.getId()); + Optional<Judge> judgeOpt = judgeRepository.findByUserId(user.getId()); + Optional<Player> playerOpt = playerRepository.findByUserId(user.getId()); + + // 5. 妫�鏌ユ槸鍚︽湁鏉冮檺锛堝繀椤诲叧鑱斿憳宸ャ�佽瘎濮旀垨瀛﹀憳涓殑鑷冲皯涓�涓級 + // 娉ㄦ剰锛歐eb鐧诲綍鏆傛椂涓嶆敮鎸佸鍛樿鑹诧紝鍙厑璁稿憳宸ュ拰璇勫鐧诲綍 + if (employeeOpt.isEmpty() && judgeOpt.isEmpty()) { + logger.warn("鐢ㄦ埛娌℃湁鏉冮檺锛屾湭鍏宠仈鍛樺伐鎴栬瘎濮旓紝鎵嬫満鍙�: {}", loginRequest.getPhone()); + throw new BadCredentialsException("娌℃湁鏉冮檺"); + } + + // 6. 鐢熸垚JWT token + String token = jwtUtil.generateToken(user.getId(), user.getPhone()); + + // 7. 纭畾涓昏瑙掕壊绫诲瀷锛堜紭鍏堢骇锛歟mployee > judge > player锛� + String userType; + if (employeeOpt.isPresent()) { + userType = "employee"; + } else if (judgeOpt.isPresent()) { + userType = "judge"; + } else { + userType = "player"; + } + + // 8. 鏋勫缓鐢ㄦ埛淇℃伅 + LoginResponse.UserInfo userInfo = new LoginResponse.UserInfo( + user.getId(), + user.getName(), + user.getPhone(), + userType + ); + + // 9. 璁剧疆鎵�鏈夊叧鑱旂殑瑙掕壊淇℃伅 + if (employeeOpt.isPresent()) { + Employee employee = employeeOpt.get(); + userInfo.setEmployee(new LoginResponse.EmployeeInfo( + employee.getId(), + employee.getName(), + employee.getRoleId(), + employee.getDescription() + )); + logger.info("鍛樺伐鐧诲綍鎴愬姛锛孖D: {}, 濮撳悕: {}", employee.getId(), employee.getName()); + } + + if (judgeOpt.isPresent()) { + Judge judge = judgeOpt.get(); + userInfo.setJudge(new LoginResponse.JudgeInfo( + judge.getId(), + judge.getName(), + judge.getTitle(), + judge.getCompany(), + judge.getDescription() + )); + if (employeeOpt.isEmpty()) { + logger.info("璇勫鐧诲綍鎴愬姛锛孖D: {}, 濮撳悕: {}", judge.getId(), judge.getName()); + } + } + + if (playerOpt.isPresent()) { + Player player = playerOpt.get(); + userInfo.setPlayer(new LoginResponse.PlayerInfo( + player.getId(), + player.getName(), + player.getPhone(), + player.getDescription(), + player.getAuditState() + )); + if (employeeOpt.isEmpty() && judgeOpt.isEmpty()) { + logger.info("瀛﹀憳鐧诲綍鎴愬姛锛孖D: {}, 濮撳悕: {}", player.getId(), player.getName()); + } + } + + return new LoginResponse(token, userInfo); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java b/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java new file mode 100644 index 0000000..91b828e --- /dev/null +++ b/backend/src/main/java/com/rongyichuang/auth/util/JwtUtil.java @@ -0,0 +1,97 @@ +package com.rongyichuang.auth.util; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.util.Date; + +/** + * JWT宸ュ叿绫� + */ +@Component +public class JwtUtil { + + private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class); + + @Value("${app.jwt.secret}") + private String jwtSecret; + + @Value("${app.jwt.expiration:86400000}") // 榛樿24灏忔椂 + private long jwtExpiration; + + /** + * 鐢熸垚JWT token + */ + public String generateToken(Long userId, String phone) { + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + jwtExpiration); + + SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes()); + + return Jwts.builder() + .setSubject(userId.toString()) + .claim("phone", phone) + .setIssuedAt(now) + .setExpiration(expiryDate) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + /** + * 浠巘oken涓幏鍙栫敤鎴稩D + */ + public Long getUserIdFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return Long.parseLong(claims.getSubject()); + } + + /** + * 浠巘oken涓幏鍙栨墜鏈哄彿 + */ + public String getPhoneFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return claims.get("phone", String.class); + } + + /** + * 楠岃瘉token鏄惁鏈夋晥 + */ + public boolean validateToken(String token) { + try { + getClaimsFromToken(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + logger.error("JWT token楠岃瘉澶辫触: {}", e.getMessage()); + return false; + } + } + + /** + * 妫�鏌oken鏄惁杩囨湡 + */ + public boolean isTokenExpired(String token) { + try { + Claims claims = getClaimsFromToken(token); + return claims.getExpiration().before(new Date()); + } catch (JwtException | IllegalArgumentException e) { + return true; + } + } + + /** + * 浠巘oken涓В鏋怌laims + */ + private Claims getClaimsFromToken(String token) { + SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes()); + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rongyichuang/common/api/AppConfigGraphqlApi.java b/backend/src/main/java/com/rongyichuang/common/api/AppConfigGraphqlApi.java index 3d3a01b..fc7e702 100644 --- a/backend/src/main/java/com/rongyichuang/common/api/AppConfigGraphqlApi.java +++ b/backend/src/main/java/com/rongyichuang/common/api/AppConfigGraphqlApi.java @@ -8,7 +8,7 @@ @Controller public class AppConfigGraphqlApi { - @Value("${app.media-url:${app.media.url:}}") + @Value("${app.media-url}") private String mediaBaseUrl; @QueryMapping diff --git a/backend/src/main/java/com/rongyichuang/common/api/MediaFieldResolver.java b/backend/src/main/java/com/rongyichuang/common/api/MediaFieldResolver.java index f3e73a9..308ccac 100644 --- a/backend/src/main/java/com/rongyichuang/common/api/MediaFieldResolver.java +++ b/backend/src/main/java/com/rongyichuang/common/api/MediaFieldResolver.java @@ -10,7 +10,7 @@ @Controller public class MediaFieldResolver { - @Value("${app.media-url:${app.media.url:}}") + @Value("${app.media-url}") private String mediaBaseUrl; @SchemaMapping(typeName = "Media", field = "fullUrl") diff --git a/backend/src/main/java/com/rongyichuang/common/api/MediaGraphqlApi.java b/backend/src/main/java/com/rongyichuang/common/api/MediaGraphqlApi.java index 0ddc088..542979a 100644 --- a/backend/src/main/java/com/rongyichuang/common/api/MediaGraphqlApi.java +++ b/backend/src/main/java/com/rongyichuang/common/api/MediaGraphqlApi.java @@ -27,7 +27,7 @@ @Autowired private JdbcTemplate jdbcTemplate; - @Value("${app.media-url:${app.media.url:}}") + @Value("${app.media-url}") private String mediaBaseUrl; public MediaGraphqlApi(MediaRepository mediaRepository, MediaService mediaService) { 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 99ee7b1..0bf65c7 100644 --- a/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java +++ b/backend/src/main/java/com/rongyichuang/common/util/UserContextUtil.java @@ -1,5 +1,8 @@ package com.rongyichuang.common.util; +import com.rongyichuang.auth.util.JwtUtil; +import com.rongyichuang.employee.entity.Employee; +import com.rongyichuang.employee.repository.EmployeeRepository; import com.rongyichuang.judge.entity.Judge; import com.rongyichuang.judge.repository.JudgeRepository; import org.slf4j.Logger; @@ -8,7 +11,10 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import jakarta.servlet.http.HttpServletRequest; import java.util.Optional; /** @@ -23,30 +29,89 @@ @Autowired private JudgeRepository judgeRepository; + @Autowired + private EmployeeRepository employeeRepository; + + @Autowired + private JwtUtil jwtUtil; + /** * 鑾峰彇褰撳墠鐧诲綍鐢ㄦ埛ID - * 娉ㄦ剰锛氬綋鍓嶇郴缁熸殏鏃朵娇鐢ㄥ浐瀹氱敤鎴稩D锛屽悗缁渶瑕佹牴鎹疄闄呰璇佹満鍒朵慨鏀� + * 浠嶫WT token涓В鏋愮敤鎴稩D * * @return 鐢ㄦ埛ID */ 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); + return userId; + } + + // 濡傛灉娌℃湁鏈夋晥鐨凧WT token锛屽皾璇曚粠Spring Security涓婁笅鏂囪幏鍙� Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated() && !"anonymousUser".equals(authentication.getPrincipal())) { - // TODO: 浠庤璇佷俊鎭腑鑾峰彇鐪熷疄鐨勭敤鎴稩D - // 杩欓噷闇�瑕佹牴鎹疄闄呯殑璁よ瘉鏈哄埗鏉ュ疄鐜� - // 渚嬪锛氫粠JWT token涓В鏋愮敤鎴稩D锛屾垨浠嶶serDetails涓幏鍙� logger.debug("鑾峰彇鍒拌璇佺敤鎴�: {}", authentication.getName()); - return 1L; // 涓存椂杩斿洖鍥哄畾鐢ㄦ埛ID + // 濡傛灉璁よ瘉淇℃伅涓寘鍚敤鎴稩D锛屽彲浠ュ湪杩欓噷瑙f瀽 + // 鏆傛椂杩斿洖鍥哄畾鐢ㄦ埛ID鐢ㄤ簬鍏煎鎬� + return 1L; } } catch (Exception e) { logger.warn("鑾峰彇褰撳墠鐢ㄦ埛ID鏃跺彂鐢熷紓甯�: {}", e.getMessage()); } - // 濡傛灉娌℃湁璁よ瘉淇℃伅锛岃繑鍥為粯璁ょ敤鎴稩D锛堝紑鍙戦樁娈典娇鐢級 - logger.debug("鏈壘鍒拌璇佷俊鎭紝浣跨敤榛樿鐢ㄦ埛ID"); - return 1L; + // 濡傛灉娌℃湁璁よ瘉淇℃伅锛岃繑鍥瀗ull琛ㄧず鏈櫥褰� + logger.debug("鏈壘鍒版湁鏁堢殑璁よ瘉淇℃伅"); + return null; + } + + /** + * 浠嶩TTP璇锋眰涓幏鍙朖WT token + */ + private String getTokenFromRequest() { + try { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpServletRequest request = attributes.getRequest(); + String authHeader = request.getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + } + } catch (Exception e) { + logger.debug("鑾峰彇JWT token鏃跺彂鐢熷紓甯�: {}", e.getMessage()); + } + return null; + } + + /** + * 鑾峰彇褰撳墠鐢ㄦ埛鍏宠仈鐨勫憳宸ヤ俊鎭� + * + * @return 鍛樺伐淇℃伅锛屽鏋滃綋鍓嶇敤鎴蜂笉鏄憳宸ュ垯杩斿洖绌� + */ + public Optional<Employee> getCurrentEmployee() { + Long userId = getCurrentUserId(); + if (userId == null) { + logger.warn("鏃犳硶鑾峰彇褰撳墠鐢ㄦ埛ID"); + return Optional.empty(); + } + + try { + Optional<Employee> employee = employeeRepository.findByUserId(userId); + if (employee.isPresent()) { + logger.debug("鎵惧埌褰撳墠鐢ㄦ埛鍏宠仈鐨勫憳宸�: {}", employee.get().getName()); + } else { + logger.debug("褰撳墠鐢ㄦ埛(ID: {})涓嶆槸鍛樺伐", userId); + } + return employee; + } catch (Exception e) { + logger.error("鏌ヨ鍛樺伐淇℃伅鏃跺彂鐢熷紓甯�: {}", e.getMessage(), e); + return Optional.empty(); + } } /** @@ -76,6 +141,15 @@ } /** + * 鑾峰彇褰撳墠鐢ㄦ埛鍏宠仈鐨勫憳宸D + * + * @return 鍛樺伐ID锛屽鏋滃綋鍓嶇敤鎴蜂笉鏄憳宸ュ垯杩斿洖null + */ + public Long getCurrentEmployeeId() { + return getCurrentEmployee().map(Employee::getId).orElse(null); + } + + /** * 鑾峰彇褰撳墠鐢ㄦ埛鍏宠仈鐨勮瘎濮擨D * * @return 璇勫ID锛屽鏋滃綋鍓嶇敤鎴蜂笉鏄瘎濮斿垯杩斿洖null @@ -85,6 +159,15 @@ } /** + * 妫�鏌ュ綋鍓嶇敤鎴锋槸鍚︿负鍛樺伐 + * + * @return true濡傛灉褰撳墠鐢ㄦ埛鏄憳宸ワ紝鍚﹀垯false + */ + public boolean isCurrentUserEmployee() { + return getCurrentEmployee().isPresent(); + } + + /** * 妫�鏌ュ綋鍓嶇敤鎴锋槸鍚︿负璇勫 * * @return true濡傛灉褰撳墠鐢ㄦ埛鏄瘎濮旓紝鍚﹀垯false diff --git a/backend/src/main/java/com/rongyichuang/employee/repository/EmployeeRepository.java b/backend/src/main/java/com/rongyichuang/employee/repository/EmployeeRepository.java index 1ef9e6a..33d04b1 100644 --- a/backend/src/main/java/com/rongyichuang/employee/repository/EmployeeRepository.java +++ b/backend/src/main/java/com/rongyichuang/employee/repository/EmployeeRepository.java @@ -49,4 +49,9 @@ * 鏍规嵁瑙掕壊ID鏌ヨ鍛樺伐鍒楄〃 */ List<Employee> findByRoleId(String roleId); + + /** + * 鏍规嵁鐢ㄦ埛ID鏌ヨ鍛樺伐 + */ + Optional<Employee> findByUserId(Long userId); } \ No newline at end of file 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 f078372..4dbc167 100644 --- a/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java +++ b/backend/src/main/java/com/rongyichuang/judge/service/JudgeService.java @@ -40,7 +40,7 @@ private final MediaService mediaService; private final UserService userService; - @Value("${app.media-url:${app.media.url:}}") + @Value("${app.media-url}") private String mediaBaseUrl; public JudgeService(JudgeRepository judgeRepository, TagRepository tagRepository, diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 659006d..53b4514 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -65,11 +65,6 @@ bucket: ryc-1379367838 region: ap-chengdu -# JWT閰嶇疆 -jwt: - secret: ryc-jwt-secret-key-2024 - expiration: 86400000 # 24灏忔椂 - # 鏃ュ織閰嶇疆 logging: level: @@ -81,4 +76,7 @@ # 搴旂敤閰嶇疆 app: base-url: https://rych.9village.cn + jwt: + secret: ryc-jwt-secret-key-2024-secure-256bit-hmac-sha-algorithm-compatible + 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 new file mode 100644 index 0000000..d067fea --- /dev/null +++ b/backend/src/main/resources/graphql/auth.graphqls @@ -0,0 +1,53 @@ +extend type Mutation { + "Web绔敤鎴风櫥褰�" + webLogin(input: LoginRequest): LoginResponse +} + +"鐧诲綍璇锋眰" +input LoginRequest { + phone: String! + password: String! +} + +"鐧诲綍鍝嶅簲" +type LoginResponse { + token: String! + userInfo: UserInfo! +} + +"鐢ㄦ埛淇℃伅" +type UserInfo { + userId: ID! + name: String! + phone: String! + userType: String! + employee: EmployeeInfo + judge: JudgeInfo + player: PlayerInfo +} + +"鍛樺伐淇℃伅" +type EmployeeInfo { + id: ID! + name: String! + roleId: String! + description: String +} + +"璇勫淇℃伅" +type JudgeInfo { + id: ID! + name: String! + title: String + company: String + description: String +} + +"瀛﹀憳淇℃伅" +type PlayerInfo { + id: ID! + name: String! + phone: String + description: String + auditState: Int +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/CreateMultiRoleUserTest.java b/backend/src/test/java/com/rongyichuang/CreateMultiRoleUserTest.java new file mode 100644 index 0000000..78a5830 --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/CreateMultiRoleUserTest.java @@ -0,0 +1,69 @@ +package com.rongyichuang; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("dev") +public class CreateMultiRoleUserTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Test + public void createMultiRoleUser() { + try { + // 妫�鏌ユ槸鍚﹀凡瀛樺湪澶氳鑹叉祴璇曠敤鎴� + Integer userCount = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM t_user WHERE mobile = '13800000003'", + Integer.class + ); + + if (userCount > 0) { + System.out.println("澶氳鑹叉祴璇曠敤鎴峰凡瀛樺湪锛岃烦杩囧垱寤�"); + return; + } + + // 鍔犲瘑瀵嗙爜 (瀵嗙爜涓�: 123456) + String encodedPassword = passwordEncoder.encode("123456"); + + // 鎻掑叆澶氳鑹叉祴璇曠敤鎴峰埌 t_user 琛� + jdbcTemplate.update( + "INSERT INTO t_user (name, wx_openid, wx_unionid, state, mobile, password, create_time, update_time, version) VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW(), 0)", + "澶氳鑹叉祴璇曠敤鎴�", "test_multi_role_openid", "test_multi_role_unionid", 1, "13800000003", encodedPassword + ); + + // 鑾峰彇鐢ㄦ埛ID + Long userId = jdbcTemplate.queryForObject( + "SELECT id FROM t_user WHERE mobile = ?", Long.class, "13800000003" + ); + + // 鎻掑叆涓哄憳宸� + jdbcTemplate.update( + "INSERT INTO t_employee (name, phone, role_id, user_id, state, create_time, update_time, version, description) VALUES (?, ?, ?, ?, ?, NOW(), NOW(), 0, ?)", + "澶氳鑹叉祴璇曠敤鎴�", "13800000003", "MANAGER", userId, 1, "鏃㈡槸鍛樺伐鍙堟槸璇勫鐨勬祴璇曡处鍙�" + ); + + // 鎻掑叆涓鸿瘎濮� + jdbcTemplate.update( + "INSERT INTO t_judge (name, user_id, phone, gender, state, description, create_time, update_time, version, title, company, introduction) VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW(), 0, ?, ?, ?)", + "澶氳鑹叉祴璇曠敤鎴�", userId, "13800000003", 1, 1, "鏃㈡槸鍛樺伐鍙堟槸璇勫鐨勬祴璇曡处鍙�", "鎶�鏈�荤洃", "娴嬭瘯鍏徃", "鎷ユ湁绠$悊鍜屾妧鏈瘎瀹″弻閲嶈亴鑳�" + ); + + System.out.println("澶氳鑹叉祴璇曠敤鎴峰垱寤烘垚鍔燂紒"); + System.out.println("璐﹀彿: 13800000003, 瀵嗙爜: 123456"); + System.out.println("璇ョ敤鎴峰悓鏃跺叿鏈夊憳宸ュ拰璇勫瑙掕壊锛岀敤浜庢祴璇曡鑹蹭紭鍏堢骇"); + + } catch (Exception e) { + System.err.println("鍒涘缓澶氳鑹叉祴璇曠敤鎴峰け璐�: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/CreateTestUsersTest.java b/backend/src/test/java/com/rongyichuang/CreateTestUsersTest.java new file mode 100644 index 0000000..0f117ca --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/CreateTestUsersTest.java @@ -0,0 +1,74 @@ +package com.rongyichuang; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("dev") +public class CreateTestUsersTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Test + public void createTestUsers() { + try { + // 鍏堝垹闄ょ幇鏈夌殑娴嬭瘯鐢ㄦ埛锛堝鏋滃瓨鍦級 + System.out.println("鍒犻櫎鐜版湁娴嬭瘯鐢ㄦ埛..."); + jdbcTemplate.update("DELETE FROM t_employee WHERE phone IN ('13800000001', '13800000002')"); + jdbcTemplate.update("DELETE FROM t_judge WHERE phone IN ('13800000001', '13800000002')"); + jdbcTemplate.update("DELETE FROM t_user WHERE mobile IN ('13800000001', '13800000002') OR phone IN ('13800000001', '13800000002')"); + System.out.println("鐜版湁娴嬭瘯鐢ㄦ埛宸插垹闄�"); + + // 鍔犲瘑瀵嗙爜 (瀵嗙爜涓�: 123456) + String encodedPassword = passwordEncoder.encode("123456"); + + // 鎻掑叆娴嬭瘯鐢ㄦ埛鍒� t_user 琛� (鍖呭惈瀵嗙爜锛屽悓鏃惰缃畃hone鍜宮obile瀛楁) + jdbcTemplate.update( + "INSERT INTO t_user (name, wx_openid, wx_unionid, state, mobile, phone, password, create_time, update_time, version) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), 0)", + "娴嬭瘯鍛樺伐", "test_employee_openid", "test_employee_unionid", 1, "13800000001", "13800000001", encodedPassword + ); + + jdbcTemplate.update( + "INSERT INTO t_user (name, wx_openid, wx_unionid, state, mobile, phone, password, create_time, update_time, version) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), 0)", + "娴嬭瘯璇勫", "test_judge_openid", "test_judge_unionid", 1, "13800000002", "13800000002", encodedPassword + ); + + // 鑾峰彇鐢ㄦ埛ID + Long employeeUserId = jdbcTemplate.queryForObject( + "SELECT id FROM t_user WHERE mobile = ?", Long.class, "13800000001" + ); + + Long judgeUserId = jdbcTemplate.queryForObject( + "SELECT id FROM t_user WHERE mobile = ?", Long.class, "13800000002" + ); + + // 鎻掑叆娴嬭瘯鍛樺伐鍒� t_employee 琛� (涓嶅寘鍚瘑鐮�) + jdbcTemplate.update( + "INSERT INTO t_employee (name, phone, role_id, user_id, state, create_time, update_time, version, description) VALUES (?, ?, ?, ?, ?, NOW(), NOW(), 0, ?)", + "娴嬭瘯鍛樺伐", "13800000001", "ADMIN", employeeUserId, 1, "绯荤粺娴嬭瘯鍛樺伐璐﹀彿" + ); + + // 鎻掑叆娴嬭瘯璇勫鍒� t_judge 琛� + jdbcTemplate.update( + "INSERT INTO t_judge (name, user_id, phone, gender, state, description, create_time, update_time, version, title, company, introduction) VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW(), 0, ?, ?, ?)", + "娴嬭瘯璇勫", judgeUserId, "13800000002", 1, 1, "绯荤粺娴嬭瘯璇勫璐﹀彿", "楂樼骇鎶�鏈笓瀹�", "娴嬭瘯鍏徃", "鎷ユ湁涓板瘜鐨勬妧鏈瘎瀹$粡楠�" + ); + + System.out.println("娴嬭瘯鐢ㄦ埛鍒涘缓鎴愬姛锛�"); + System.out.println("鍛樺伐璐﹀彿: 13800000001, 瀵嗙爜: 123456"); + System.out.println("璇勫璐﹀彿: 13800000002, 瀵嗙爜: 123456"); + + } catch (Exception e) { + System.err.println("鍒涘缓娴嬭瘯鐢ㄦ埛澶辫触: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/VerifyUserDataTest.java b/backend/src/test/java/com/rongyichuang/VerifyUserDataTest.java new file mode 100644 index 0000000..8312829 --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/VerifyUserDataTest.java @@ -0,0 +1,94 @@ +package com.rongyichuang; + +import com.rongyichuang.user.entity.User; +import com.rongyichuang.user.repository.UserRepository; +import com.rongyichuang.employee.entity.Employee; +import com.rongyichuang.employee.repository.EmployeeRepository; +import com.rongyichuang.judge.entity.Judge; +import com.rongyichuang.judge.repository.JudgeRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.context.ActiveProfiles; + +import java.util.Optional; + +@SpringBootTest +@ActiveProfiles("dev") +public class VerifyUserDataTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private EmployeeRepository employeeRepository; + + @Autowired + private JudgeRepository judgeRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Test + public void verifyTestUsers() { + System.out.println("=== 楠岃瘉娴嬭瘯鐢ㄦ埛鏁版嵁 ==="); + + // 楠岃瘉鍛樺伐鐢ㄦ埛 + verifyUser("13800000001", "鍛樺伐"); + + // 楠岃瘉璇勫鐢ㄦ埛 + verifyUser("13800000002", "璇勫"); + + // 楠岃瘉澶氳鑹茬敤鎴� + verifyUser("13800000003", "澶氳鑹�"); + } + + private void verifyUser(String phone, String userType) { + System.out.println("\n--- 楠岃瘉" + userType + "鐢ㄦ埛: " + phone + " ---"); + + // 鏌ユ壘鐢ㄦ埛 + Optional<User> userOpt = userRepository.findByPhone(phone); + if (userOpt.isEmpty()) { + System.out.println("鉂� 鐢ㄦ埛涓嶅瓨鍦�"); + return; + } + + User user = userOpt.get(); + System.out.println("鉁� 鐢ㄦ埛瀛樺湪: ID=" + user.getId() + ", 濮撳悕=" + user.getName()); + + // 楠岃瘉瀵嗙爜 + if (user.getPassword() == null || user.getPassword().trim().isEmpty()) { + System.out.println("鉂� 鐢ㄦ埛瀵嗙爜涓虹┖"); + } else { + boolean passwordMatch = passwordEncoder.matches("123456", user.getPassword()); + System.out.println(passwordMatch ? "鉁� 瀵嗙爜楠岃瘉鎴愬姛" : "鉂� 瀵嗙爜楠岃瘉澶辫触"); + System.out.println("瀛樺偍鐨勫瘑鐮佸搱甯�: " + user.getPassword().substring(0, 20) + "..."); + } + + // 鏌ユ壘鍏宠仈鐨勫憳宸ヤ俊鎭� + Optional<Employee> employeeOpt = employeeRepository.findByUserId(user.getId()); + if (employeeOpt.isPresent()) { + Employee employee = employeeOpt.get(); + System.out.println("鉁� 鍛樺伐淇℃伅: ID=" + employee.getId() + ", 濮撳悕=" + employee.getName() + ", 瑙掕壊=" + employee.getRoleId()); + } else { + System.out.println("鉂� 鏃犲憳宸ヤ俊鎭�"); + } + + // 鏌ユ壘鍏宠仈鐨勮瘎濮斾俊鎭� + Optional<Judge> judgeOpt = judgeRepository.findByUserId(user.getId()); + if (judgeOpt.isPresent()) { + Judge judge = judgeOpt.get(); + System.out.println("鉁� 璇勫淇℃伅: ID=" + judge.getId() + ", 濮撳悕=" + judge.getName() + ", 鑱屼綅=" + judge.getTitle()); + } else { + System.out.println("鉂� 鏃犺瘎濮斾俊鎭�"); + } + + // 妫�鏌ユ潈闄� + if (employeeOpt.isEmpty() && judgeOpt.isEmpty()) { + System.out.println("鉂� 鐢ㄦ埛娌℃湁鏉冮檺锛堟棦涓嶆槸鍛樺伐涔熶笉鏄瘎濮旓級"); + } else { + System.out.println("鉁� 鐢ㄦ埛鏈夋潈闄�"); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/auth/DirectLoginTest.java b/backend/src/test/java/com/rongyichuang/auth/DirectLoginTest.java new file mode 100644 index 0000000..d3c078f --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/auth/DirectLoginTest.java @@ -0,0 +1,40 @@ +package com.rongyichuang.auth; + +import com.rongyichuang.auth.dto.LoginRequest; +import com.rongyichuang.auth.dto.LoginResponse; +import com.rongyichuang.auth.service.AuthService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class DirectLoginTest { + + @Autowired + private AuthService authService; + + @Test + public void testDirectLogin() { + try { + System.out.println("=== 寮�濮嬬洿鎺ユ祴璇旳uthService鐧诲綍鏂规硶 ==="); + + LoginRequest request = new LoginRequest(); + request.setPhone("13800000001"); + request.setPassword("123456"); + + System.out.println("鐧诲綍璇锋眰: phone=" + request.getPhone() + ", password=" + request.getPassword()); + + LoginResponse response = authService.login(request); + + System.out.println("鐧诲綍鎴愬姛!"); + System.out.println("Token: " + response.getToken()); + System.out.println("鐢ㄦ埛ID: " + response.getUserInfo().getUserId()); + System.out.println("鐢ㄦ埛鍚�: " + response.getUserInfo().getName()); + System.out.println("鐢ㄦ埛绫诲瀷: " + response.getUserInfo().getUserType()); + + } catch (Exception e) { + System.err.println("鐧诲綍澶辫触: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/auth/GraphQLLoginTest.java b/backend/src/test/java/com/rongyichuang/auth/GraphQLLoginTest.java new file mode 100644 index 0000000..6678371 --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/auth/GraphQLLoginTest.java @@ -0,0 +1,40 @@ +package com.rongyichuang.auth; + +import com.rongyichuang.auth.api.AuthGraphqlApi; +import com.rongyichuang.auth.dto.LoginRequest; +import com.rongyichuang.auth.dto.LoginResponse; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class GraphQLLoginTest { + + @Autowired + private AuthGraphqlApi authGraphqlApi; + + @Test + public void testGraphQLLogin() { + try { + System.out.println("=== 寮�濮嬫祴璇旼raphQL API灞傜櫥褰曟柟娉� ==="); + + LoginRequest request = new LoginRequest(); + request.setPhone("13800000001"); + request.setPassword("123456"); + + System.out.println("鐧诲綍璇锋眰: phone=" + request.getPhone() + ", password=" + request.getPassword()); + + LoginResponse response = authGraphqlApi.webLogin(request); + + System.out.println("GraphQL鐧诲綍鎴愬姛!"); + System.out.println("Token: " + response.getToken()); + System.out.println("鐢ㄦ埛ID: " + response.getUserInfo().getUserId()); + System.out.println("鐢ㄦ埛鍚�: " + response.getUserInfo().getName()); + System.out.println("鐢ㄦ埛绫诲瀷: " + response.getUserInfo().getUserType()); + + } catch (Exception e) { + System.err.println("GraphQL鐧诲綍澶辫触: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/rongyichuang/employee/DirectEmployeeServiceTest.java b/backend/src/test/java/com/rongyichuang/employee/DirectEmployeeServiceTest.java new file mode 100644 index 0000000..a847779 --- /dev/null +++ b/backend/src/test/java/com/rongyichuang/employee/DirectEmployeeServiceTest.java @@ -0,0 +1,50 @@ +package com.rongyichuang.employee; + +import com.rongyichuang.employee.dto.request.EmployeeInput; +import com.rongyichuang.employee.dto.response.EmployeeResponse; +import com.rongyichuang.employee.service.EmployeeService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +/** + * 鐩存帴娴嬭瘯EmployeeService鐨剆aveEmployee鏂规硶 + */ +@SpringBootTest +@ActiveProfiles("test") +public class DirectEmployeeServiceTest { + + @Autowired + private EmployeeService employeeService; + + @Test + public void testSaveEmployee() { + try { + // 鍒涘缓鍛樺伐杈撳叆 + EmployeeInput input = new EmployeeInput(); + input.setName("娴嬭瘯鍛樺伐Service"); + input.setPhone("13900000003"); + input.setPassword("test123"); + input.setRoleId("EMPLOYEE"); + input.setDescription("鐩存帴娴嬭瘯EmployeeService"); + + System.out.println("寮�濮嬫祴璇旹mployeeService.saveEmployee..."); + System.out.println("杈撳叆鍙傛暟: " + input.getName() + ", " + input.getPhone()); + + // 璋冪敤鏈嶅姟鏂规硶 + EmployeeResponse response = employeeService.saveEmployee(input); + + System.out.println("EmployeeService娴嬭瘯鎴愬姛!"); + System.out.println("鍛樺伐ID: " + response.getId()); + System.out.println("鍛樺伐濮撳悕: " + response.getName()); + System.out.println("鍛樺伐鎵嬫満: " + response.getPhone()); + System.out.println("鍛樺伐瑙掕壊: " + response.getRoleId()); + + } catch (Exception e) { + System.err.println("EmployeeService娴嬭瘯澶辫触: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + } +} \ No newline at end of file diff --git a/check_table.ps1 b/check_table.ps1 deleted file mode 100644 index a1577bc..0000000 --- a/check_table.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -$headers = @{ - "Content-Type" = "application/json" -} - -$body = @{ - query = "query { __schema { types { name fields { name type { name } } } } }" -} | ConvertTo-Json - -Write-Host "妫�鏌raphQL Schema..." -$response = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body -Headers $headers -Write-Host "Schema妫�鏌ュ畬鎴�" \ No newline at end of file diff --git a/test-media-query.json b/test-media-query.json deleted file mode 100644 index 909c8ab..0000000 --- a/test-media-query.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "query": "query MediasByTarget($targetType: Int!, $targetId: ID!) { mediasByTarget(targetType: $targetType, targetId: $targetId) { id name path fileExt mediaType fullUrl } }", - "variables": { - "targetType": 2, - "targetId": "1" - } -} \ No newline at end of file diff --git a/test_api.ps1 b/test_api.ps1 deleted file mode 100644 index f1f06a4..0000000 --- a/test_api.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -$headers = @{ - "Content-Type" = "application/json" -} - -$body = @{ - query = 'mutation { saveActivity(input: { name: "Test Competition", description: "This is a test competition", signupDeadline: "2025-10-01T10:00:00", matchTime: "2025-10-15T14:00:00", address: "Test Address", ratingSchemeId: 1, playerMax: 100, stages: [{ name: "Preliminary", description: "Preliminary stage", matchTime: "2025-10-15T14:00:00", address: "Preliminary Address", playerMax: 50 }, { name: "Final", description: "Final stage", matchTime: "2025-10-16T14:00:00", address: "Final Address", playerMax: 20 }], judges: [{ judgeId: 1, judgeName: "Judge1", stageIds: [] }, { judgeId: 2, judgeName: "Judge2", stageIds: [] }] }) { id name description } }' -} | ConvertTo-Json - -Write-Host "鍒涘缓姣旇禌娴嬭瘯..." -$response = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body -Headers $headers -Write-Host "鍝嶅簲缁撴灉:" -$response | ConvertTo-Json -Depth 10 \ No newline at end of file diff --git a/test_complete.ps1 b/test_complete.ps1 deleted file mode 100644 index 1026cef..0000000 --- a/test_complete.ps1 +++ /dev/null @@ -1,162 +0,0 @@ -$headers = @{ - "Content-Type" = "application/json" -} - -Write-Host "=== 瀹屾暣姣旇禌绠$悊娴嬭瘯娴佺▼ ===" - -# 娴嬭瘯1: 鍒涘缓姣旇禌鍜岄樁娈� -Write-Host "1. 鍒涘缓姣旇禌鍜岄樁娈�..." -$body1 = @{ - query = 'mutation { - saveActivity(input: { - name: "缂栫▼澶ц禌2024", - description: "骞村害缂栫▼绔炶禌", - signupDeadline: "2024-12-31T23:59:59", - ratingSchemeId: 1, - pid: 0, - stages: [ - { - name: "鍒濊禌", - description: "缂栫▼鍩虹娴嬭瘯", - matchTime: "2024-11-01T09:00:00", - ratingSchemeId: 1, - playerMax: 100 - }, - { - name: "鍐宠禌", - description: "楂樼骇缂栫▼鎸戞垬", - matchTime: "2024-11-15T09:00:00", - ratingSchemeId: 1, - playerMax: 20 - } - ] - }) { - id name description pid - stages { id name description playerMax } - } - }' -} | ConvertTo-Json -Depth 10 - -$response1 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body1 -Headers $headers -Write-Host "鍒涘缓缁撴灉:" -$response1 | ConvertTo-Json -Depth 10 - -if ($response1.data -and $response1.data.saveActivity) { - $competitionId = $response1.data.saveActivity.id - $stages = $response1.data.saveActivity.stages - Write-Host "姣旇禌鍒涘缓鎴愬姛锛孖D: $competitionId" - Write-Host "闃舵鏁伴噺: $($stages.Count)" - - if ($stages.Count -gt 0) { - $stage1Id = $stages[0].id - $stage2Id = if ($stages.Count -gt 1) { $stages[1].id } else { $stages[0].id } - - # 娴嬭瘯2: 娣诲姞璇勫鍒版瘮璧� - Write-Host "`n2. 娣诲姞璇勫鍒版瘮璧�..." - $body2 = @{ - query = "mutation { - saveActivity(input: { - id: $competitionId, - name: `"缂栫▼澶ц禌2024`", - description: `"骞村害缂栫▼绔炶禌`", - signupDeadline: `"2024-12-31T23:59:59`", - ratingSchemeId: 1, - pid: 0, - judges: [ - { - judgeId: 1, - judgeName: `"寮犳暀鎺坄", - stageIds: [$stage1Id, $stage2Id] - }, - { - judgeId: 2, - judgeName: `"鏉庝笓瀹禶", - stageIds: [$stage2Id] - } - ] - }) { - id name description - } - }" - } | ConvertTo-Json -Depth 10 - - $response2 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body2 -Headers $headers - Write-Host "娣诲姞璇勫缁撴灉:" - $response2 | ConvertTo-Json -Depth 10 - - # 娴嬭瘯3: 璇诲彇瀹屾暣姣旇禌淇℃伅 - Write-Host "`n3. 璇诲彇瀹屾暣姣旇禌淇℃伅..." - $body3 = @{ - query = "query { - activity(id: $competitionId) { - id name description pid - stages { id name description playerMax } - } - }" - } | ConvertTo-Json - - $response3 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body3 -Headers $headers - Write-Host "璇诲彇缁撴灉:" - $response3 | ConvertTo-Json -Depth 10 - - # 娴嬭瘯4: 淇敼姣旇禌淇℃伅 - Write-Host "`n4. 淇敼姣旇禌淇℃伅..." - $body4 = @{ - query = "mutation { - saveActivity(input: { - id: $competitionId, - name: `"缂栫▼澶ц禌2024 (宸叉洿鏂�)`", - description: `"骞村害缂栫▼绔炶禌 - 鏇存柊鐗坄", - signupDeadline: `"2024-12-31T23:59:59`", - ratingSchemeId: 1, - pid: 0, - stages: [ - { - id: $stage1Id, - name: `"鍒濊禌 (鏇存柊)`", - description: `"缂栫▼鍩虹娴嬭瘯 - 鏇存柊鐗坄", - matchTime: `"2024-11-01T09:00:00`", - ratingSchemeId: 1, - playerMax: 150 - }, - { - id: $stage2Id, - name: `"鍐宠禌`", - description: `"楂樼骇缂栫▼鎸戞垬`", - matchTime: `"2024-11-15T09:00:00`", - ratingSchemeId: 1, - playerMax: 20 - }, - { - name: `"鍔犺禌`", - description: `"棰濆鎸戞垬璧沗", - matchTime: `"2024-11-20T09:00:00`", - ratingSchemeId: 1, - playerMax: 10 - } - ] - }) { - id name description - stages { id name description playerMax } - } - }" - } | ConvertTo-Json -Depth 10 - - $response4 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body4 -Headers $headers - Write-Host "淇敼缁撴灉:" - $response4 | ConvertTo-Json -Depth 10 - - Write-Host "`n=== 娴嬭瘯瀹屾垚 ===" - Write-Host "鉁� 姣旇禌鍒涘缓鎴愬姛" - Write-Host "鉁� 闃舵鍒涘缓鎴愬姛" - Write-Host "鉁� 璇勫娣诲姞鎴愬姛" - Write-Host "鉁� 鏁版嵁璇诲彇鎴愬姛" - Write-Host "鉁� 鏁版嵁淇敼鎴愬姛" - } -} else { - Write-Host "姣旇禌鍒涘缓澶辫触" - if ($response1.errors) { - Write-Host "閿欒淇℃伅:" - $response1.errors | ConvertTo-Json -Depth 10 - } -} \ No newline at end of file diff --git a/test_complete_fixed.ps1 b/test_complete_fixed.ps1 deleted file mode 100644 index 8331145..0000000 --- a/test_complete_fixed.ps1 +++ /dev/null @@ -1,117 +0,0 @@ -[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 - -$headers = @{ - "Content-Type" = "application/json" -} - -Write-Host "=== Complete Competition Test ===" - -# Test 1: Create competition with stages -Write-Host "1. Creating competition with stages..." -$body1 = @{ - query = 'mutation { - saveActivity(input: { - name: "Programming Contest 2024", - description: "Annual programming competition", - signupDeadline: "2024-12-31T23:59:59", - ratingSchemeId: 1, - pid: 0, - stages: [ - { - name: "Preliminary", - description: "Basic programming test", - matchTime: "2024-11-01T09:00:00", - ratingSchemeId: 1, - playerMax: 100 - }, - { - name: "Final", - description: "Advanced programming challenge", - matchTime: "2024-11-15T09:00:00", - ratingSchemeId: 1, - playerMax: 20 - } - ] - }) { - id name description pid - stages { id name description playerMax } - } - }' -} | ConvertTo-Json -Depth 10 - -$response1 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body1 -Headers $headers -Write-Host "Creation result:" -$response1 | ConvertTo-Json -Depth 10 - -if ($response1.data -and $response1.data.saveActivity) { - $competitionId = $response1.data.saveActivity.id - $stages = $response1.data.saveActivity.stages - Write-Host "Competition created successfully, ID: $competitionId" - Write-Host "Number of stages: $($stages.Count)" - - if ($stages.Count -gt 0) { - $stage1Id = $stages[0].id - $stage2Id = if ($stages.Count -gt 1) { $stages[1].id } else { $stages[0].id } - - # Test 2: Add judges to competition - Write-Host "`n2. Adding judges to competition..." - $judgeQuery = @" -mutation { - saveActivity(input: { - id: $competitionId, - name: "Programming Contest 2024", - description: "Annual programming competition", - signupDeadline: "2024-12-31T23:59:59", - ratingSchemeId: 1, - pid: 0, - judges: [ - { - judgeId: 1, - judgeName: "Professor Zhang", - stageIds: [$stage1Id, $stage2Id] - }, - { - judgeId: 2, - judgeName: "Expert Li", - stageIds: [$stage2Id] - } - ] - }) { - id name description - } -} -"@ - - $body2 = @{ query = $judgeQuery } | ConvertTo-Json -Depth 10 - $response2 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body2 -Headers $headers - Write-Host "Judge addition result:" - $response2 | ConvertTo-Json -Depth 10 - - # Test 3: Read complete competition info - Write-Host "`n3. Reading complete competition info..." - $body3 = @{ - query = "query { - activity(id: $competitionId) { - id name description pid - stages { id name description playerMax } - } - }" - } | ConvertTo-Json - - $response3 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body3 -Headers $headers - Write-Host "Read result:" - $response3 | ConvertTo-Json -Depth 10 - - Write-Host "`n=== Test Complete ===" - Write-Host "鉁� Competition creation successful" - Write-Host "鉁� Stage creation successful" - Write-Host "鉁� Judge addition successful" - Write-Host "鉁� Data reading successful" - } -} else { - Write-Host "Competition creation failed" - if ($response1.errors) { - Write-Host "Error details:" - $response1.errors | ConvertTo-Json -Depth 10 - } -} \ No newline at end of file diff --git a/test_debug.ps1 b/test_debug.ps1 deleted file mode 100644 index 3311902..0000000 --- a/test_debug.ps1 +++ /dev/null @@ -1,77 +0,0 @@ -[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 - -$headers = @{ - "Content-Type" = "application/json" -} - -Write-Host "=== Debug Test ===" - -# Test: Create competition with stages and check database -Write-Host "1. Creating competition with stages..." -$body1 = @{ - query = 'mutation { - saveActivity(input: { - name: "Debug Test Competition", - description: "Debug test", - signupDeadline: "2024-12-31T23:59:59", - ratingSchemeId: 1, - pid: 0, - stages: [ - { - name: "Debug Stage 1", - description: "First debug stage", - matchTime: "2024-11-01T09:00:00", - ratingSchemeId: 1, - playerMax: 50 - } - ] - }) { - id name description pid - } - }' -} | ConvertTo-Json -Depth 10 - -$response1 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body1 -Headers $headers -Write-Host "Creation result:" -$response1 | ConvertTo-Json -Depth 10 - -if ($response1.data -and $response1.data.saveActivity) { - $competitionId = $response1.data.saveActivity.id - Write-Host "Competition created with ID: $competitionId" - - # Test: Query stages directly - Write-Host "`n2. Querying stages directly..." - $body2 = @{ - query = "query { - activityStages(competitionId: $competitionId) { - id name description playerMax pid - } - }" - } | ConvertTo-Json - - $response2 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body2 -Headers $headers - Write-Host "Stages query result:" - $response2 | ConvertTo-Json -Depth 10 - - # Test: Query competition with stages - Write-Host "`n3. Querying competition with stages..." - $body3 = @{ - query = "query { - activity(id: $competitionId) { - id name description pid - stages { id name description playerMax pid } - } - }" - } | ConvertTo-Json - - $response3 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body3 -Headers $headers - Write-Host "Competition query result:" - $response3 | ConvertTo-Json -Depth 10 - -} else { - Write-Host "Competition creation failed" - if ($response1.errors) { - Write-Host "Error details:" - $response1.errors | ConvertTo-Json -Depth 10 - } -} \ No newline at end of file diff --git a/test_employee_api_fix.ps1 b/test_employee_api_fix.ps1 deleted file mode 100644 index c01574e..0000000 --- a/test_employee_api_fix.ps1 +++ /dev/null @@ -1,43 +0,0 @@ -# 娴嬭瘯淇鍚庣殑鍛樺伐API -Write-Host "娴嬭瘯鍛樺伐API淇..." -ForegroundColor Green - -# 娴嬭瘯鑾峰彇鍛樺伐鍒楄〃 -Write-Host "1. 娴嬭瘯鑾峰彇鍛樺伐鍒楄〃..." -ForegroundColor Yellow -$getEmployeesQuery = @{ - query = "query GetEmployees { employees { id name phone roleCode description status createTime updateTime } }" -} | ConvertTo-Json -Compress - -try { - $response = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $getEmployeesQuery -ContentType "application/json" - Write-Host "鑾峰彇鍛樺伐鍒楄〃鎴愬姛" -ForegroundColor Green - Write-Host "鍛樺伐鏁伴噺: $($response.data.employees.Count)" -ForegroundColor Cyan -} catch { - Write-Host "鑾峰彇鍛樺伐鍒楄〃澶辫触: $($_.Exception.Message)" -ForegroundColor Red -} - -# 娴嬭瘯娣诲姞鍛樺伐 -Write-Host "`n2. 娴嬭瘯娣诲姞鍛樺伐..." -ForegroundColor Yellow -$addEmployeeMutation = @{ - query = "mutation SaveEmployee(`$input: EmployeeInput!) { saveEmployee(input: `$input) { id name phone roleCode description status } }" - variables = @{ - input = @{ - name = "娴嬭瘯鍛樺伐API淇" - phone = "13800138000" - password = "123456" - roleCode = "EMPLOYEE" - description = "API淇娴嬭瘯鍛樺伐" - } - } -} | ConvertTo-Json -Compress -Depth 3 - -try { - $response = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $addEmployeeMutation -ContentType "application/json" - Write-Host "娣诲姞鍛樺伐鎴愬姛" -ForegroundColor Green - $newEmployeeId = $response.data.saveEmployee.id - $newEmployeeName = $response.data.saveEmployee.name - Write-Host "鏂板憳宸D: $newEmployeeId, 濮撳悕: $newEmployeeName" -ForegroundColor Cyan -} catch { - Write-Host "娣诲姞鍛樺伐澶辫触: $($_.Exception.Message)" -ForegroundColor Red -} - -Write-Host "`n鍛樺伐API淇娴嬭瘯瀹屾垚!" -ForegroundColor Green \ No newline at end of file diff --git a/test_employee_management.ps1 b/test_employee_management.ps1 deleted file mode 100644 index eee0157..0000000 --- a/test_employee_management.ps1 +++ /dev/null @@ -1,65 +0,0 @@ -# Employee Management Test Script -Write-Host "=== Employee Management Test ===" -ForegroundColor Green - -$baseUrl = "http://localhost:8080/api/graphql" -$headers = @{ - "Content-Type" = "application/json" -} - -# Test 1: Get all employees -Write-Host "`n1. Testing get all employees..." -ForegroundColor Yellow -$query1 = '{"query":"query GetEmployees { employees { id name phone roleCode description status createTime updateTime } }"}' - -try { - $response1 = Invoke-RestMethod -Uri $baseUrl -Method POST -Body $query1 -Headers $headers - Write-Host "Success: Get employees list" -ForegroundColor Green - Write-Host "Employee count: $($response1.data.employees.Count)" -ForegroundColor Cyan - if ($response1.data.employees.Count -gt 0) { - Write-Host "First employee: $($response1.data.employees[0].name)" -ForegroundColor Cyan - } -} catch { - Write-Host "Failed: Get employees list - $($_.Exception.Message)" -ForegroundColor Red -} - -# Test 2: Add new employee -Write-Host "`n2. Testing add new employee..." -ForegroundColor Yellow -$mutation1 = '{"query":"mutation SaveEmployee($input: EmployeeInput!) { saveEmployee(input: $input) { id name phone roleCode description status createTime } }","variables":{"input":{"name":"Test Employee","phone":"13800138000","password":"123456","roleCode":"STAFF","description":"This is a test employee","status":"ACTIVE"}}}' - -try { - $response2 = Invoke-RestMethod -Uri $baseUrl -Method POST -Body $mutation1 -Headers $headers - Write-Host "Success: Add new employee" -ForegroundColor Green - $newEmployeeId = $response2.data.saveEmployee.id - Write-Host "New employee ID: $newEmployeeId" -ForegroundColor Cyan - Write-Host "New employee name: $($response2.data.saveEmployee.name)" -ForegroundColor Cyan -} catch { - Write-Host "Failed: Add new employee - $($_.Exception.Message)" -ForegroundColor Red - $newEmployeeId = $null -} - -# Test 3: Search employees by name -Write-Host "`n3. Testing search employees..." -ForegroundColor Yellow -$query2 = '{"query":"query SearchEmployees($name: String) { employeesByName(name: $name) { id name phone roleCode description status } }","variables":{"name":"Test"}}' - -try { - $response3 = Invoke-RestMethod -Uri $baseUrl -Method POST -Body $query2 -Headers $headers - Write-Host "Success: Search employees" -ForegroundColor Green - Write-Host "Search result count: $($response3.data.employeesByName.Count)" -ForegroundColor Cyan -} catch { - Write-Host "Failed: Search employees - $($_.Exception.Message)" -ForegroundColor Red -} - -# Test 4: Delete test employee (if created successfully) -if ($newEmployeeId) { - Write-Host "`n4. Testing delete employee..." -ForegroundColor Yellow - $mutation2 = "{`"query`":`"mutation DeleteEmployee(`$id: Long!) { deleteEmployee(id: `$id) }`",`"variables`":{`"id`":$newEmployeeId}}" - - try { - $response4 = Invoke-RestMethod -Uri $baseUrl -Method POST -Body $mutation2 -Headers $headers - Write-Host "Success: Delete employee" -ForegroundColor Green - } catch { - Write-Host "Failed: Delete employee - $($_.Exception.Message)" -ForegroundColor Red - } -} - -Write-Host "`n=== Test Complete ===" -ForegroundColor Green -Write-Host "Please check frontend page http://localhost:3000/#/employee to verify UI functionality" -ForegroundColor Cyan \ No newline at end of file diff --git a/test_no_state.ps1 b/test_no_state.ps1 deleted file mode 100644 index ba97100..0000000 --- a/test_no_state.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 - -$headers = @{ - "Content-Type" = "application/json" -} - -Write-Host "=== Test Without State Filter ===" - -# Test: Create a simple query without state filtering -Write-Host "1. Testing GraphQL introspection..." -$body1 = @{ - query = 'query { __schema { queryType { name } } }' -} | ConvertTo-Json - -$response1 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body1 -Headers $headers -Write-Host "Introspection result:" -$response1 | ConvertTo-Json -Depth 10 - -Write-Host "`n2. Testing simple mutation (should work)..." -$body2 = @{ - query = 'mutation { - saveActivity(input: { - name: "Simple Test", - description: "Simple test without stages", - signupDeadline: "2024-12-31T23:59:59", - ratingSchemeId: 1, - pid: 0 - }) { - id name - } - }' -} | ConvertTo-Json - -$response2 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body2 -Headers $headers -Write-Host "Simple mutation result:" -$response2 | ConvertTo-Json -Depth 10 \ No newline at end of file diff --git a/test_simple.ps1 b/test_simple.ps1 deleted file mode 100644 index ca66ef3..0000000 --- a/test_simple.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -$headers = @{ - "Content-Type" = "application/json" -} - -# 娴嬭瘯1: 鍒涘缓绠�鍗曟瘮璧涳紙涓嶅寘鍚瘎濮旓級 -$body1 = @{ - query = 'mutation { saveActivity(input: { name: "Test Competition", description: "Test Description", signupDeadline: "2024-12-31T23:59:59", ratingSchemeId: 1, pid: 0 }) { id name description pid } }' -} | ConvertTo-Json - -Write-Host "鍒涘缓绠�鍗曟瘮璧涙祴璇�..." -$response1 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body1 -Headers $headers -Write-Host "鍝嶅簲缁撴灉:" -$response1 | ConvertTo-Json -Depth 10 - -if ($response1.data -and $response1.data.saveActivity) { - $competitionId = $response1.data.saveActivity.id - Write-Host "姣旇禌鍒涘缓鎴愬姛锛孖D: $competitionId" - - # 娴嬭瘯2: 璇诲彇鍒涘缓鐨勬瘮璧� - $body2 = @{ - query = "query { activity(id: $competitionId) { id name description pid } }" - } | ConvertTo-Json - - Write-Host "璇诲彇姣旇禌娴嬭瘯..." - $response2 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body2 -Headers $headers - Write-Host "璇诲彇缁撴灉:" - $response2 | ConvertTo-Json -Depth 10 -} else { - Write-Host "姣旇禌鍒涘缓澶辫触" -} \ No newline at end of file diff --git a/test_simple_query.ps1 b/test_simple_query.ps1 deleted file mode 100644 index d8cb36a..0000000 --- a/test_simple_query.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 - -$headers = @{ - "Content-Type" = "application/json" -} - -Write-Host "=== Simple Query Test ===" - -# Test: Query all activities to see if basic query works -Write-Host "1. Querying all activities..." -$body1 = @{ - query = 'query { - allActivities { - id name pid - } - }' -} | ConvertTo-Json - -$response1 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body1 -Headers $headers -Write-Host "All activities result:" -$response1 | ConvertTo-Json -Depth 10 - -# Test: Query activities with pagination -Write-Host "`n2. Querying activities with pagination..." -$body2 = @{ - query = 'query { - activities(page: 0, size: 10) { - content { id name pid } - totalElements - } - }' -} | ConvertTo-Json - -$response2 = Invoke-RestMethod -Uri "http://localhost:8080/api/graphql" -Method POST -Body $body2 -Headers $headers -Write-Host "Paginated activities result:" -$response2 | ConvertTo-Json -Depth 10 \ No newline at end of file diff --git a/test_users.sql b/test_users.sql new file mode 100644 index 0000000..e7dc4c2 --- /dev/null +++ b/test_users.sql @@ -0,0 +1,13 @@ +-- 娴嬭瘯鐢ㄦ埛鏁版嵁 +-- 鎻掑叆娴嬭瘯鐢ㄦ埛鍒� t_user 琛� +INSERT INTO t_user (name, wx_open_id, wx_union_id, state, mobile, create_time, update_time, version) VALUES +('娴嬭瘯鍛樺伐', 'test_employee_openid', 'test_employee_unionid', 1, '13800000001', NOW(), NOW(), 0), +('娴嬭瘯璇勫', 'test_judge_openid', 'test_judge_unionid', 1, '13800000002', NOW(), NOW(), 0); + +-- 鎻掑叆娴嬭瘯鍛樺伐鍒� t_employee 琛� +INSERT INTO t_employee (name, phone, password, role_id, user_id, state, create_time, update_time, version, description) VALUES +('娴嬭瘯鍛樺伐', '13800000001', '$2a$10$N.zmdr9k7uOCQb96VdOYx.93UGOXOQAaJLVrpJeJEPPTJZaGqQ4oa', 'ADMIN', 1, 1, NOW(), NOW(), 0, '绯荤粺娴嬭瘯鍛樺伐璐﹀彿'); + +-- 鎻掑叆娴嬭瘯璇勫鍒� t_judge 琛� +INSERT INTO t_judge (name, user_id, phone, gender, state, description, create_time, update_time, version, title, company, introduction) VALUES +('娴嬭瘯璇勫', 2, '13800000002', 1, 1, '绯荤粺娴嬭瘯璇勫璐﹀彿', NOW(), NOW(), 0, '楂樼骇鎶�鏈笓瀹�', '娴嬭瘯鍏徃', '鎷ユ湁涓板瘜鐨勬妧鏈瘎瀹$粡楠�'); \ No newline at end of file diff --git a/web/src/layout/index.vue b/web/src/layout/index.vue index 03aaa51..75d787e 100644 --- a/web/src/layout/index.vue +++ b/web/src/layout/index.vue @@ -33,7 +33,8 @@ <el-dropdown @command="handleCommand"> <span class="user-info"> <el-icon><User /></el-icon> - <span>绠$悊鍛�</span> + <span>{{ currentUserName }}</span> + <el-tag size="small" style="margin-left: 8px">{{ currentUserRole }}</el-tag> <el-icon><ArrowDown /></el-icon> </span> <template #dropdown> @@ -58,9 +59,14 @@ import { computed } from 'vue' import { useRoute, useRouter } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' +import { getCurrentUserDisplayName, getCurrentUserRoleText, clearAuth } from '@/utils/auth' const route = useRoute() const router = useRouter() + +// 褰撳墠鐢ㄦ埛淇℃伅 +const currentUserName = computed(() => getCurrentUserDisplayName()) +const currentUserRole = computed(() => getCurrentUserRoleText()) // 鑿滃崟椤� const menuItems = [ @@ -90,7 +96,7 @@ type: 'warning' }) - localStorage.removeItem('token') + clearAuth() ElMessage.success('閫�鍑虹櫥褰曟垚鍔�') router.push('/login') } catch { diff --git a/web/src/router/index.ts b/web/src/router/index.ts index 854f6f1..731a900 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -1,4 +1,5 @@ import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' +import { isLoggedIn } from '@/utils/auth' const routes: RouteRecordRaw[] = [ { @@ -105,4 +106,28 @@ routes }) +// 璺敱瀹堝崼 +router.beforeEach((to, from, next) => { + // 濡傛灉鏄櫥褰曢〉闈紝鐩存帴鏀捐 + if (to.path === '/login') { + // 濡傛灉宸茬粡鐧诲綍锛岄噸瀹氬悜鍒伴椤� + if (isLoggedIn()) { + next('/') + } else { + next() + } + return + } + + // 妫�鏌ユ槸鍚﹀凡鐧诲綍 + if (!isLoggedIn()) { + // 鏈櫥褰曪紝閲嶅畾鍚戝埌鐧诲綍椤� + next('/login') + return + } + + // 宸茬櫥褰曪紝姝e父璁块棶 + next() +}) + export default router \ No newline at end of file diff --git a/web/src/utils/auth.ts b/web/src/utils/auth.ts new file mode 100644 index 0000000..a55e90c --- /dev/null +++ b/web/src/utils/auth.ts @@ -0,0 +1,165 @@ +/** + * 璁よ瘉鐩稿叧宸ュ叿鍑芥暟 + */ + +const TOKEN_KEY = 'auth_token' +const USER_INFO_KEY = 'user_info' + +// 鐢ㄦ埛淇℃伅鎺ュ彛瀹氫箟 +export interface UserInfo { + userId: string + name: string + phone: string + userType: string // 涓昏瑙掕壊绫诲瀷锛歟mployee > judge > player + employee?: { + id: string + name: string + roleId: string + description?: string + } + judge?: { + id: string + name: string + title?: string + company?: string + description?: string + } + player?: { + id: string + name: string + phone?: string + description?: string + auditState?: number + } +} + +// Token 绠$悊 - 浣跨敤 localStorage 鎸佷箙鍖栧瓨鍌� +export function setToken(token: string): void { + localStorage.setItem(TOKEN_KEY, token) +} + +export function getToken(): string | null { + return localStorage.getItem(TOKEN_KEY) +} + +export function removeToken(): void { + localStorage.removeItem(TOKEN_KEY) +} + +// 鐢ㄦ埛淇℃伅绠$悊 - 浣跨敤 localStorage 鎸佷箙鍖栧瓨鍌� +export function setUserInfo(userInfo: UserInfo): void { + localStorage.setItem(USER_INFO_KEY, JSON.stringify(userInfo)) +} + +export function getUserInfo(): UserInfo | null { + const userInfoStr = localStorage.getItem(USER_INFO_KEY) + if (userInfoStr) { + try { + return JSON.parse(userInfoStr) + } catch (error) { + console.error('瑙f瀽鐢ㄦ埛淇℃伅澶辫触:', error) + removeUserInfo() + } + } + return null +} + +export function removeUserInfo(): void { + localStorage.removeItem(USER_INFO_KEY) +} + +// 鐧诲綍鐘舵�佹鏌� +export function isLoggedIn(): boolean { + const token = getToken() + const userInfo = getUserInfo() + return !!(token && userInfo) +} + +// 娓呴櫎鎵�鏈夎璇佹暟鎹� +export function clearAuth(): void { + removeToken() + removeUserInfo() +} + +// 鑾峰彇褰撳墠鐢ㄦ埛鏄剧ず鍚嶇О锛堜紭鍏堢骇锛歟mployee > judge > player锛� +export function getCurrentUserDisplayName(): string { + const userInfo = getUserInfo() + if (!userInfo) return '鏈煡鐢ㄦ埛' + + // 鏍规嵁瑙掕壊浼樺厛绾ц繑鍥炲搴旂殑鍚嶇О + if (userInfo.employee) { + return userInfo.employee.name + } else if (userInfo.judge) { + return userInfo.judge.name + } else if (userInfo.player) { + return userInfo.player.name + } + + return userInfo.name || '鏈煡鐢ㄦ埛' +} + +// 鑾峰彇褰撳墠鐢ㄦ埛瑙掕壊 +export function getCurrentUserRole(): string { + const userInfo = getUserInfo() + if (!userInfo) return '' + + if (userInfo.employee) { + return userInfo.employee.description || '鍛樺伐' + } + + if (userInfo.judge) { + return userInfo.judge.title || '璇勫' + } + + return userInfo.userType === 'employee' ? '鍛樺伐' : '璇勫' +} + +// 鑾峰彇褰撳墠鐢ㄦ埛瑙掕壊鏄剧ず鏂囨湰 +export function getCurrentUserRoleText(): string { + const userInfo = getUserInfo() + const role = userInfo?.userType || 'unknown' + switch (role) { + case 'employee': + return '鍛樺伐' + case 'judge': + return '璇勫' + case 'player': + return '瀛﹀憳' + default: + return '鏈煡瑙掕壊' + } +} + +// 妫�鏌ュ綋鍓嶇敤鎴锋槸鍚︿负鍛樺伐 +export function isEmployee(): boolean { + const userInfo = getUserInfo() + return userInfo?.userType === 'employee' && !!userInfo.employee +} + +// 妫�鏌ュ綋鍓嶇敤鎴锋槸鍚︿负璇勫 +export function isJudge(): boolean { + const userInfo = getUserInfo() + return userInfo?.userType === 'judge' && !!userInfo.judge +} + +// 妫�鏌ュ綋鍓嶇敤鎴锋槸鍚︿负瀛﹀憳 +export function isPlayer(): boolean { + const userInfo = getUserInfo() + return userInfo?.userType === 'player' && !!userInfo.player +} + +// 鑾峰彇褰撳墠鐢ㄦ埛鐨勪富瑕佽鑹蹭俊鎭� +export function getCurrentRoleInfo(): any { + const userInfo = getUserInfo() + if (!userInfo) return null + + if (userInfo.employee) { + return userInfo.employee + } else if (userInfo.judge) { + return userInfo.judge + } else if (userInfo.player) { + return userInfo.player + } + + return null +} \ No newline at end of file diff --git a/web/src/utils/graphql.ts b/web/src/utils/graphql.ts new file mode 100644 index 0000000..4641b2d --- /dev/null +++ b/web/src/utils/graphql.ts @@ -0,0 +1,143 @@ +/** + * GraphQL瀹㈡埛绔伐鍏� + */ + +import { getToken } from './auth' + +// GraphQL绔偣 +const GRAPHQL_ENDPOINT = '/api/graphql' + +export interface GraphQLResponse<T = any> { + data?: T + errors?: Array<{ + message: string + locations?: Array<{ + line: number + column: number + }> + path?: string[] + }> +} + +/** + * 鍙戦�丟raphQL璇锋眰 + */ +export async function graphqlRequest<T = any>( + query: string, + variables?: Record<string, any> +): Promise<T> { + const token = getToken() + + const headers: Record<string, string> = { + 'Content-Type': 'application/json', + } + + // 濡傛灉鏈塼oken锛屾坊鍔犲埌璇锋眰澶� + if (token) { + headers['Authorization'] = `Bearer ${token}` + } + + const response = await fetch(GRAPHQL_ENDPOINT, { + method: 'POST', + headers, + body: JSON.stringify({ + query, + variables + }) + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const result: GraphQLResponse<T> = await response.json() + + if (result.errors && result.errors.length > 0) { + throw new Error(result.errors[0].message) + } + + if (!result.data) { + throw new Error('No data returned from GraphQL') + } + + return result.data +} + +/** + * 鐧诲綍API + */ +export interface LoginRequest { + phone: string + password: string +} + +export interface LoginResponse { + webLogin: { + token: string + userInfo: { + userId: string + name: string + phone: string + userType: string + employee?: { + id: string + name: string + roleId: string + description?: string + } + judge?: { + id: string + name: string + title?: string + company?: string + description?: string + } + player?: { + id: string + name: string + phone?: string + description?: string + auditState?: number + } + } + } +} + +export async function loginApi(request: LoginRequest): Promise<LoginResponse['webLogin']> { + const query = ` + mutation WebLogin($input: LoginRequest!) { + webLogin(input: $input) { + token + userInfo { + userId + name + phone + userType + employee { + id + name + roleId + description + } + judge { + id + name + title + company + description + } + player { + id + name + phone + description + auditState + } + } + } + } + ` + + const response = await graphqlRequest<LoginResponse>(query, { input: request }) + return response.webLogin +} \ No newline at end of file diff --git a/web/src/views/employee/EmployeeForm.vue b/web/src/views/employee/EmployeeForm.vue index 263cd38..3ab65c9 100644 --- a/web/src/views/employee/EmployeeForm.vue +++ b/web/src/views/employee/EmployeeForm.vue @@ -39,16 +39,27 @@ /> </el-form-item> - <el-form-item label="閲嶇疆瀵嗙爜" prop="password" v-if="isEdit"> - <el-input - v-model="form.password" - type="password" - placeholder="璇疯緭鍏ユ柊瀵嗙爜锛�6-20浣嶏級" - maxlength="20" - show-password - @focus="handlePasswordFocus" - @input="handlePasswordInput" - /> + <el-form-item label="瀵嗙爜" prop="password" v-if="isEdit"> + <div style="display: flex; align-items: center; gap: 10px;"> + <el-input + v-model="form.password" + type="password" + :placeholder="isPasswordModified ? '璇疯緭鍏ユ柊瀵嗙爜锛�6-20浣嶏紝鍖呭惈瀛楁瘝鍜屾暟瀛楋級' : '鐐瑰嚮閲嶇疆瀵嗙爜鎸夐挳鏉ヤ慨鏀瑰瘑鐮�'" + maxlength="20" + show-password + :disabled="!isPasswordModified" + @focus="handlePasswordFocus" + @input="handlePasswordInput" + style="flex: 1;" + /> + <el-button + type="primary" + size="small" + @click="handleResetPassword" + > + 閲嶇疆瀵嗙爜 + </el-button> + </div> </el-form-item> <el-form-item label="鍛樺伐瑙掕壊" prop="roleId"> @@ -164,10 +175,16 @@ password: [ { validator: (rule, value, callback) => { - if (!value) { + // 缂栬緫妯″紡涓嬶紝濡傛灉鏄崰浣嶇瀵嗙爜涓旀湭淇敼锛屽垯璺宠繃楠岃瘉 + if (isEdit.value && value === '鈥⑩�⑩�⑩�⑩�⑩�⑩�⑩��' && !isPasswordModified.value) { + callback() + return + } + + if (!value || value.trim() === '') { callback(new Error('璇疯緭鍏ョ櫥褰曞瘑鐮�')) - } else if (value.length < 6 || value.length > 20) { - callback(new Error('瀵嗙爜闀垮害搴斿湪6-20涓瓧绗︿箣闂�')) + } else if (!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d@$!%*?&]{6,}$/.test(value)) { + callback(new Error('瀵嗙爜鑷冲皯6涓瓧绗︼紝蹇呴』鍖呭惈瀛楁瘝鍜屾暟瀛�')) } else { callback() } @@ -235,6 +252,13 @@ } } +// 澶勭悊閲嶇疆瀵嗙爜 +const handleResetPassword = () => { + form.password = '' + isPasswordModified.value = true + ElMessage.success('瀵嗙爜宸叉竻绌猴紝璇疯緭鍏ユ柊瀵嗙爜') +} + // 鎻愪氦琛ㄥ崟 const handleSubmit = async () => { if (!formRef.value) return diff --git a/web/src/views/login/index.vue b/web/src/views/login/index.vue index 995bd4f..a8b0433 100644 --- a/web/src/views/login/index.vue +++ b/web/src/views/login/index.vue @@ -62,6 +62,8 @@ import { useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus' +import { loginApi } from '@/utils/graphql' +import { setToken, setUserInfo } from '@/utils/auth' const router = useRouter() const loginFormRef = ref<FormInstance>() @@ -93,17 +95,38 @@ await loginFormRef.value.validate() loading.value = true - // TODO: 璋冪敤鐧诲綍API - // 妯℃嫙鐧诲綍 - setTimeout(() => { - localStorage.setItem('token', 'mock-token-' + Date.now()) - ElMessage.success('鐧诲綍鎴愬姛') - router.push('/') - loading.value = false - }, 1000) + // 璋冪敤鐧诲綍API + const response = await loginApi({ + phone: loginForm.phone, + password: loginForm.password + }) - } catch (error) { + // 淇濆瓨token鍜岀敤鎴蜂俊鎭� + setToken(response.token) + setUserInfo(response.userInfo) + + ElMessage.success('鐧诲綍鎴愬姛') + router.push('/') + + } catch (error: any) { console.error('鐧诲綍澶辫触:', error) + + // 鏄剧ず閿欒淇℃伅 + let errorMessage = '鐧诲綍澶辫触' + if (error.message) { + if (error.message.includes('鐢ㄦ埛涓嶅瓨鍦�')) { + errorMessage = '鐢ㄦ埛涓嶅瓨鍦紝璇锋鏌ユ墜鏈哄彿' + } else if (error.message.includes('瀵嗙爜涓嶆纭�')) { + errorMessage = '瀵嗙爜涓嶆纭紝璇烽噸鏂拌緭鍏�' + } else if (error.message.includes('娌℃湁鏉冮檺')) { + errorMessage = '鎮ㄦ病鏈夎闂潈闄愶紝璇疯仈绯荤鐞嗗憳' + } else { + errorMessage = error.message + } + } + + ElMessage.error(errorMessage) + } finally { loading.value = false } } -- Gitblit v1.8.0