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