feat: 优化员工和评委编辑功能的密码重置逻辑
- 优化EmployeeForm.vue中的密码验证和重置功能
- 添加密码重置按钮和相关逻辑
- 完善认证系统和GraphQL API
- 清理测试文件和临时脚本
- 修复登录和用户管理相关功能
11个文件已修改
15个文件已添加
11个文件已删除
New file |
| | |
| | | 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); |
| | | } |
| | | } |
New file |
| | |
| | | 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 = "手机号格式不正确") |
| | | 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; |
| | | } |
| | | } |
New file |
| | |
| | | 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; // 主要角色类型:优先employee,然后judge,最后player |
| | | 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; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | 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. 检查是否有权限(必须关联员工、评委或学员中的至少一个) |
| | | // 注意:Web登录暂时不支持学员角色,只允许员工和评委登录 |
| | | if (employeeOpt.isEmpty() && judgeOpt.isEmpty()) { |
| | | logger.warn("用户没有权限,未关联员工或评委,手机号: {}", loginRequest.getPhone()); |
| | | throw new BadCredentialsException("没有权限"); |
| | | } |
| | | |
| | | // 6. 生成JWT token |
| | | String token = jwtUtil.generateToken(user.getId(), user.getPhone()); |
| | | |
| | | // 7. 确定主要角色类型(优先级:employee > 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("员工登录成功,ID: {}, 姓名: {}", 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("评委登录成功,ID: {}, 姓名: {}", 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("学员登录成功,ID: {}, 姓名: {}", player.getId(), player.getName()); |
| | | } |
| | | } |
| | | |
| | | return new LoginResponse(token, userInfo); |
| | | } |
| | | } |
New file |
| | |
| | | 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(); |
| | | } |
| | | |
| | | /** |
| | | * 从token中获取用户ID |
| | | */ |
| | | public Long getUserIdFromToken(String token) { |
| | | Claims claims = getClaimsFromToken(token); |
| | | return Long.parseLong(claims.getSubject()); |
| | | } |
| | | |
| | | /** |
| | | * 从token中获取手机号 |
| | | */ |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检查token是否过期 |
| | | */ |
| | | public boolean isTokenExpired(String token) { |
| | | try { |
| | | Claims claims = getClaimsFromToken(token); |
| | | return claims.getExpiration().before(new Date()); |
| | | } catch (JwtException | IllegalArgumentException e) { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 从token中解析Claims |
| | | */ |
| | | private Claims getClaimsFromToken(String token) { |
| | | SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes()); |
| | | return Jwts.parserBuilder() |
| | | .setSigningKey(key) |
| | | .build() |
| | | .parseClaimsJws(token) |
| | | .getBody(); |
| | | } |
| | | } |
| | |
| | | @Controller |
| | | public class AppConfigGraphqlApi { |
| | | |
| | | @Value("${app.media-url:${app.media.url:}}") |
| | | @Value("${app.media-url}") |
| | | private String mediaBaseUrl; |
| | | |
| | | @QueryMapping |
| | |
| | | @Controller |
| | | public class MediaFieldResolver { |
| | | |
| | | @Value("${app.media-url:${app.media.url:}}") |
| | | @Value("${app.media-url}") |
| | | private String mediaBaseUrl; |
| | | |
| | | @SchemaMapping(typeName = "Media", field = "fullUrl") |
| | |
| | | @Autowired |
| | | private JdbcTemplate jdbcTemplate; |
| | | |
| | | @Value("${app.media-url:${app.media.url:}}") |
| | | @Value("${app.media-url}") |
| | | private String mediaBaseUrl; |
| | | |
| | | public MediaGraphqlApi(MediaRepository mediaRepository, MediaService mediaService) { |
| | |
| | | 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; |
| | |
| | | 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; |
| | | |
| | | /** |
| | |
| | | @Autowired |
| | | private JudgeRepository judgeRepository; |
| | | |
| | | @Autowired |
| | | private EmployeeRepository employeeRepository; |
| | | |
| | | @Autowired |
| | | private JwtUtil jwtUtil; |
| | | |
| | | /** |
| | | * 获取当前登录用户ID |
| | | * 注意:当前系统暂时使用固定用户ID,后续需要根据实际认证机制修改 |
| | | * 从JWT token中解析用户ID |
| | | * |
| | | * @return 用户ID |
| | | */ |
| | | public Long getCurrentUserId() { |
| | | try { |
| | | // 首先尝试从HTTP请求头中获取JWT token |
| | | String token = getTokenFromRequest(); |
| | | if (token != null && jwtUtil.validateToken(token)) { |
| | | Long userId = jwtUtil.getUserIdFromToken(token); |
| | | logger.debug("从JWT token中获取到用户ID: {}", userId); |
| | | return userId; |
| | | } |
| | | |
| | | // 如果没有有效的JWT token,尝试从Spring Security上下文获取 |
| | | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); |
| | | if (authentication != null && authentication.isAuthenticated() && |
| | | !"anonymousUser".equals(authentication.getPrincipal())) { |
| | | // TODO: 从认证信息中获取真实的用户ID |
| | | // 这里需要根据实际的认证机制来实现 |
| | | // 例如:从JWT token中解析用户ID,或从UserDetails中获取 |
| | | logger.debug("获取到认证用户: {}", authentication.getName()); |
| | | return 1L; // 临时返回固定用户ID |
| | | // 如果认证信息中包含用户ID,可以在这里解析 |
| | | // 暂时返回固定用户ID用于兼容性 |
| | | return 1L; |
| | | } |
| | | } catch (Exception e) { |
| | | logger.warn("获取当前用户ID时发生异常: {}", e.getMessage()); |
| | | } |
| | | |
| | | // 如果没有认证信息,返回默认用户ID(开发阶段使用) |
| | | logger.debug("未找到认证信息,使用默认用户ID"); |
| | | return 1L; |
| | | // 如果没有认证信息,返回null表示未登录 |
| | | logger.debug("未找到有效的认证信息"); |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 从HTTP请求中获取JWT 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(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 获取当前用户关联的员工ID |
| | | * |
| | | * @return 员工ID,如果当前用户不是员工则返回null |
| | | */ |
| | | public Long getCurrentEmployeeId() { |
| | | return getCurrentEmployee().map(Employee::getId).orElse(null); |
| | | } |
| | | |
| | | /** |
| | | * 获取当前用户关联的评委ID |
| | | * |
| | | * @return 评委ID,如果当前用户不是评委则返回null |
| | |
| | | } |
| | | |
| | | /** |
| | | * 检查当前用户是否为员工 |
| | | * |
| | | * @return true如果当前用户是员工,否则false |
| | | */ |
| | | public boolean isCurrentUserEmployee() { |
| | | return getCurrentEmployee().isPresent(); |
| | | } |
| | | |
| | | /** |
| | | * 检查当前用户是否为评委 |
| | | * |
| | | * @return true如果当前用户是评委,否则false |
| | |
| | | * 根据角色ID查询员工列表 |
| | | */ |
| | | List<Employee> findByRoleId(String roleId); |
| | | |
| | | /** |
| | | * 根据用户ID查询员工 |
| | | */ |
| | | Optional<Employee> findByUserId(Long userId); |
| | | } |
| | |
| | | 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, |
| | |
| | | bucket: ryc-1379367838 |
| | | region: ap-chengdu |
| | | |
| | | # JWT配置 |
| | | jwt: |
| | | secret: ryc-jwt-secret-key-2024 |
| | | expiration: 86400000 # 24小时 |
| | | |
| | | # 日志配置 |
| | | logging: |
| | | level: |
| | |
| | | # 应用配置 |
| | | 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 |
New file |
| | |
| | | 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 |
| | | } |
New file |
| | |
| | | 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(); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | 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 表 (包含密码,同时设置phone和mobile字段) |
| | | 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(); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | 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("✅ 用户有权限"); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | 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("=== 开始直接测试AuthService登录方法 ==="); |
| | | |
| | | 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(); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | 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("=== 开始测试GraphQL 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(); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | 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的saveEmployee方法 |
| | | */ |
| | | @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("开始测试EmployeeService.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; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | -- 测试用户数据 |
| | | -- 插入测试用户到 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, '高级技术专家', '测试公司', '拥有丰富的技术评审经验'); |
| | |
| | | <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> |
| | |
| | | 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 = [ |
| | |
| | | type: 'warning' |
| | | }) |
| | | |
| | | localStorage.removeItem('token') |
| | | clearAuth() |
| | | ElMessage.success('退出登录成功') |
| | | router.push('/login') |
| | | } catch { |
| | |
| | | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' |
| | | import { isLoggedIn } from '@/utils/auth' |
| | | |
| | | const routes: RouteRecordRaw[] = [ |
| | | { |
| | |
| | | routes |
| | | }) |
| | | |
| | | // 路由守卫 |
| | | router.beforeEach((to, from, next) => { |
| | | // 如果是登录页面,直接放行 |
| | | if (to.path === '/login') { |
| | | // 如果已经登录,重定向到首页 |
| | | if (isLoggedIn()) { |
| | | next('/') |
| | | } else { |
| | | next() |
| | | } |
| | | return |
| | | } |
| | | |
| | | // 检查是否已登录 |
| | | if (!isLoggedIn()) { |
| | | // 未登录,重定向到登录页 |
| | | next('/login') |
| | | return |
| | | } |
| | | |
| | | // 已登录,正常访问 |
| | | next() |
| | | }) |
| | | |
| | | export default router |
New file |
| | |
| | | /** |
| | | * 认证相关工具函数 |
| | | */ |
| | | |
| | | const TOKEN_KEY = 'auth_token' |
| | | const USER_INFO_KEY = 'user_info' |
| | | |
| | | // 用户信息接口定义 |
| | | export interface UserInfo { |
| | | userId: string |
| | | name: string |
| | | phone: string |
| | | userType: string // 主要角色类型:employee > 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('解析用户信息失败:', 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() |
| | | } |
| | | |
| | | // 获取当前用户显示名称(优先级:employee > 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 |
| | | } |
New file |
| | |
| | | /** |
| | | * 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[] |
| | | }> |
| | | } |
| | | |
| | | /** |
| | | * 发送GraphQL请求 |
| | | */ |
| | | 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', |
| | | } |
| | | |
| | | // 如果有token,添加到请求头 |
| | | 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 |
| | | } |
| | |
| | | /> |
| | | </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"> |
| | |
| | | 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() |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | // 处理重置密码 |
| | | const handleResetPassword = () => { |
| | | form.password = '' |
| | | isPasswordModified.value = true |
| | | ElMessage.success('密码已清空,请输入新密码') |
| | | } |
| | | |
| | | // 提交表单 |
| | | const handleSubmit = async () => { |
| | | if (!formRef.value) return |
| | |
| | | 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>() |
| | |
| | | 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 |
| | | } |
| | | } |