package com.mydreamjob.controller; import com.mydreamjob.auth.AuthService; import com.mydreamjob.auth.JwtTokenProvider; import com.mydreamjob.auth.LoginRequest; import com.mydreamjob.auth.SmsVerificationService; import com.mydreamjob.common.api.ApiResponse; import com.mydreamjob.common.exception.BusinessException; import com.mydreamjob.common.exception.ErrorCode; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintViolationException; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; /** * 认证相关接口 */ @Tag(name = "认证", description = "登录、验证码、用户信息") @RestController @RequestMapping("/auth") @RequiredArgsConstructor @Validated public class AuthController { private final AuthService authService; private final SmsVerificationService smsVerificationService; private final JwtTokenProvider jwtTokenProvider; @Operation(summary = "发送短信验证码", description = "开发/测试使用 Mock 发送实现;生产环境未接入短信网关时返回服务不可用。验证码不会写入日志。") @PostMapping("/sms/send") public ResponseEntity>> sendSmsCode( @RequestParam @NotBlank(message = "phone is required") @Pattern(regexp = "[0-9]{11}", message = "phone must contain exactly 11 digits") String phone, @RequestParam(defaultValue = "login") String purpose, HttpServletRequest request) { try { smsVerificationService.sendCode(phone, purpose, getRequestId(request), request.getRemoteAddr(), request.getHeader("User-Agent")); return ResponseEntity.ok(ApiResponse.ok( Map.of("message", "验证码已发送(Mock)"), getRequestId(request))); } catch (ConstraintViolationException e) { return ResponseEntity.badRequest().body( ApiResponse.badRequest(e.getMessage(), getRequestId(request))); } catch (Exception e) { return ResponseEntity.badRequest().body( ApiResponse.error(400, e.getMessage(), getRequestId(request))); } } @Operation(summary = "Mock 登录", description = "使用手机号+验证码登录,返回 JWT token") @PostMapping("/login") public ResponseEntity>> mockLogin( @Valid @RequestBody LoginRequest body, HttpServletRequest request) { String phone = body.getPhone(); String code = body.getCode(); String purpose = body.getPurpose() == null || body.getPurpose().isBlank() ? "login" : body.getPurpose(); try { Map result = authService.mockLogin(phone, code, purpose, getRequestId(request), request.getRemoteAddr(), request.getHeader("User-Agent")); return ResponseEntity.ok(ApiResponse.ok(result, getRequestId(request))); } catch (BusinessException e) { // 已注销手机号:保留 HTTP 409 / 业务 code=9303,交由全局异常处理器映射。 // 其余业务异常(如验证码错误、参数校验)维持既有 400/code=400 行为。 if (e.getErrorCodeInt() == ErrorCode.ACCOUNT_DELETED.getCode()) { throw e; } return ResponseEntity.badRequest().body( ApiResponse.error(400, e.getMessage(), getRequestId(request))); } catch (Exception e) { return ResponseEntity.badRequest().body( ApiResponse.error(400, e.getMessage(), getRequestId(request))); } } @Operation(summary = "当前用户信息", description = "需要 JWT 认证") @GetMapping("/me") public ResponseEntity>> me(HttpServletRequest request) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth == null || !auth.isAuthenticated()) { return ResponseEntity.status(401).body( ApiResponse.unauthorized("not authenticated", getRequestId(request))); } Long userId = Long.valueOf(auth.getName()); return ResponseEntity.ok(ApiResponse.ok(authService.getCurrentUser(userId), getRequestId(request))); } private String getRequestId(HttpServletRequest request) { return request.getHeader("X-Request-Id"); } }