Compare commits

...

38 Commits

Author SHA1 Message Date
a663a73ff1 修正邮箱格式 2025-05-20 10:46:49 +09:00
2d27ebea35 VersionFix 2025-05-19 16:11:44 +09:00
d8b7943646 Merge http://107.189.3.96:3000/Hans119/Dog-1 2025-05-19 15:58:38 +09:00
f58c1813ff userName 2025-05-19 14:59:49 +09:00
57253951d3 增加邮箱,用户名,密码验证 2025-05-19 14:39:12 +09:00
6aec72e6ab 删除无用空构造函数 2025-05-19 14:38:18 +09:00
6c1f6d824e 0516PetEntityTableChanged 2025-05-16 13:34:43 +09:00
c668251a3e 做完啦萬歲ㄎㄎㄎ 2025-05-14 18:51:11 +09:00
da38955955 更新 src/main/java/co/jp/app/config/security/SecurityConfig.java 2025-05-14 17:45:00 +09:00
3afb26202b 更新 src/main/java/co/jp/app/config/security/filter/JwtAuthenticationFilter.java 2025-05-14 17:44:21 +09:00
z
c58105b8ca test追加 2025-05-14 16:01:58 +09:00
ef5f9f2853 Merge remote-tracking branch 'origin/master' 2025-05-14 15:06:10 +09:00
dcc6821d10 loopback 2025-05-14 15:06:02 +09:00
cd98de9213 revert c4ddec8775
revert ごめんね
2025-05-14 14:56:01 +09:00
c4ddec8775 ごめんね 2025-05-14 14:47:07 +09:00
f888dedfcd 再傳一次 2025-05-14 14:38:23 +09:00
30281a40b5 loopback 2025-05-14 14:36:57 +09:00
e5252961bd loopback 2025-05-14 14:36:29 +09:00
8af10ecb4e 删除 src/main/java/co/jp/app/service/ErraService.java 2025-05-14 14:27:41 +09:00
9eec90ef98 Merge remote-tracking branch 'origin/master' 2025-05-14 14:26:59 +09:00
bd06fc047d 练习 2025-05-14 14:24:33 +09:00
4ab23b99c8 controller變更 2025-05-14 14:19:51 +09:00
31639a6e98 CONTROLLER變更 2025-05-14 14:17:03 +09:00
2aae88278c 练习 2025-05-14 13:36:29 +09:00
f04ab7a947 增加Userdto传递用户注册信息 2025-05-14 13:11:40 +09:00
b6bcc69a83 更改allowCredentials设置。allowedOrigins允许所有设备发送请求 2025-05-14 12:55:23 +09:00
6a579104ba 应用全局异常抛出 2025-05-14 12:54:02 +09:00
8535dce094 chian修正 2025-05-14 12:48:14 +09:00
057a066404 修改注释 2025-05-13 12:41:51 +09:00
158a93d608 增加全局异常处理和业务异常处理 2025-05-13 12:41:41 +09:00
157afecd35 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/main/java/co/jp/app/common/ApiResponse.java
#	src/main/java/co/jp/app/controller/UserController.java
2025-05-13 11:40:25 +09:00
71cdb894c0 修改通用响应类,增加code。
并移除errrepository
2025-05-13 11:39:16 +09:00
41931a7f9a lol 2025-05-12 17:34:26 +09:00
z
264b9a528a 更新了 2025-05-12 17:21:40 +09:00
z
06cba204b5 ok 2025-05-12 17:07:08 +09:00
z
277fbd22cd sss 2025-05-12 16:49:04 +09:00
z
6d3f837d7c saved 2025-05-12 16:31:18 +09:00
76102067ab UrlChanged 2025-05-12 16:20:16 +09:00
52 changed files with 758 additions and 192 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

View File

@ -1,4 +1,4 @@
activeProfiles=pom.xml
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@ -1,59 +1,68 @@
package co.jp.app.common;
import java.util.Objects;
public class ApiResponse<T> {
//成功状况判定
private boolean success;
//状态码
private int code;
//状态信息
private String message;
//数据
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, null, data);
}
public static <T> ApiResponse<T> fail(String message) {
return new ApiResponse<>(false, message, null);
}
public ApiResponse() {
}
public ApiResponse(boolean success, String message, T data) {
this.success = success;
this.message = message;
private ApiResponse(ResultCode resultCode, T data) {
this.code = resultCode.getCode();
this.message = resultCode.getMessage();
this.success = (resultCode.getCode() == ResultCode.SUCCESS.getCode());
this.data = data;
}
private ApiResponse() {
}
public static <T> ApiResponse<T> fail() {return success(null);}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(ResultCode.SUCCESS, data);
}
public static <T> ApiResponse<T> fail(ResultCode resultCode) {
if (resultCode == ResultCode.SUCCESS) {
throw new IllegalArgumentException("Cannot use SUCCESS ResultCode for fail method. Use a specific error code.");
}
return new ApiResponse<>(resultCode, null);
}
public static <T> ApiResponse<T> fail(ResultCode resultCode, T data) {
if (resultCode == ResultCode.SUCCESS) {
throw new IllegalArgumentException("Cannot use SUCCESS ResultCode for fail method. Use a specific error code.");
}
return new ApiResponse<>(resultCode, data);
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "ApiResponse{" +
"success=" + success +
", code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';

View File

@ -0,0 +1,87 @@
package co.jp.app.common;
public enum ResultCode {
SUCCESS(200, "Success"),
// 客户端错误段 (1000 - 1999)
BAD_REQUEST(1000, "HTTP 400 Bad Request"),
UNAUTHORIZED(1001, "HTTP 401 Unauthorized"),
FORBIDDEN(1002, "HTTP 403 Forbidden"),
NOT_FOUND(1003, "HTTP 404 Not Found"),
METHOD_NOT_ALLOWED(1004, "HTTP 405 Method Not Allowed"),
REQUEST_TIMEOUT(1005, "HTTP 408 Request Timeout"),
CONFLICT(1006, "HTTP 409 Conflict"),
UNSUPPORTED_MEDIA_TYPE(1007, "HTTP 415 Unsupported Media Type"),
TOO_MANY_REQUESTS(1008, "HTTP 429 Too Many Requests"),
VALIDATION_ERROR(1009, "Parameter validation failure"),
// 服务端错误段 (2000 - 2999)
INTERNAL_SERVER_ERROR(2000, "HTTP 500 Internal Server Error"),
SERVICE_UNAVAILABLE(2001, "HTTP 503 Service Unavailable"),
GATEWAY_TIMEOUT(2002, "HTTP 504 Gateway Timeout"),
DATABASE_ERROR(2003, "Database error"),
NETWORK_ERROR(2004, "Network error"),
THIRD_PARTY_SERVICE_ERROR(2005, "Third-party service error"),
// ================================== 用户模块状态码 (3000 - 3999) ==================================
// 注册相关
// USER_REGISTRATION_SUCCESS(3000, "用户注册成功"),
USER_EMAIL_ALREADY_EXISTS(3001, "Email already exists"),
USER_USERNAME_ALREADY_EXISTS(3002, "Username"),
USER_EMAIL_NOT_VALID(3006, "Email is not valid"),
USER_PASSWORD_TOO_SHORT(3003, "password too short"),
USER_PASSWORD_TOO_WEAK(3004, "password too weak"),
USER_REGISTRATION_FAILED(3005, "User registration failed"),
// 登录相关
// USER_LOGIN_SUCCESS(3100, "登录成功"),
USER_ACCOUNT_NOT_FOUND(3101, "User account not found"),
USER_INVALID_CREDENTIALS(3102, "User invalid credentials"),
USER_ACCOUNT_LOCKED(3103, "User account locked"),
USER_ACCOUNT_DISABLED(3104, "User account disabled"),
USER_ACCOUNT_EXPIRED(3105, "User account expired"),
USER_LOGIN_FAILED(3106, "User login failed"),
USER_SESSION_EXPIRED(3107, "User session expired"),
USER_TOKEN_INVALID(3108, "User token invalid"),
USER_TOKEN_EXPIRED(3109, "User token expired(Token"),
USER_REFRESH_TOKEN_INVALID(3110, "User refresh token invalid"),
// USER_REFRESH_TOKEN_EXPIRED(3111, "User refresh token expired(Refresh Token"),
USER_LOGOUT_SUCCESS(3112, "loignout success"),
// 用户信息相关
USER_PROFILE_NOT_FOUND(3200, "User profile not found"),
USER_UPDATE_PROFILE_SUCCESS(3201, "User profile updated"),
USER_UPDATE_PROFILE_FAILED(3202, "User profile update failed"),
USER_CHANGE_PASSWORD_SUCCESS(3203, "Change password success"),
USER_CHANGE_PASSWORD_FAILED(3204, "Change password failed"),
USER_OLD_PASSWORD_MISMATCH(3205, "Old password mismatch"),
// 权限相关 (如果你的用户模块包含复杂权限)
// USER_PERMISSION_DENIED(3300, "用户权限不足(细粒度)"), // 可用于补充 FORBIDDEN
;
private final int code;
private final String message;
ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public String getMessage(String customDetail) {
return this.message + (customDetail == null || customDetail.isEmpty() ? "" : " (" + customDetail + ")");
}
}

View File

@ -10,9 +10,8 @@ public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 允许 /api/ 下的所有请求
.allowedOrigins("http://192.168.1.50:5173") // 允许来自该域的请求
.allowedOrigins("*") // 允许来自该域的请求
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的 HTTP 方法
.allowedHeaders("*") // 允许所有头部
.allowCredentials(true); // 允许发送 Cookie
.allowedHeaders("*"); // 允许所有头部
}
}

View File

@ -1,7 +1,6 @@
package co.jp.app.config.security;
import co.jp.app.config.security.filter.JwtAuthenticationFilter;
import co.jp.app.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@ -21,11 +20,11 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
@Configuration
public class SecurityConfig {
//private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsService userDetailsService;
public SecurityConfig(@Lazy JwtAuthenticationFilter jwtAuthenticationFilter, @Lazy UserDetailsService userDetailsService) {
//this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.userDetailsService = userDetailsService;
}
@ -47,29 +46,19 @@ public class SecurityConfig {
return authenticationConfiguration.getAuthenticationManager();
}
// @Bean
// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// http.csrf(AbstractHttpConfigurer::disable)
// .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// .authorizeHttpRequests(auth -> auth
// .requestMatchers("/api/user/login", "/api/user/register").permitAll()
// .anyRequest().authenticated()
// )
// .authenticationProvider(authenticationProvider())
// .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
//
// return http.build();
// }
// http config
@Bean
//暂时开放所有权限
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
);
.requestMatchers("/api/user/login", "/api/user/register", "/api/inuhouse", "/api/dogs/pet").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}

View File

@ -16,7 +16,6 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@ -36,14 +35,17 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
final String jwt;
final String username;
//不需要token直接返回Chain
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
//透过token读取username
jwt = authHeader.substring(7);
username = jwtService.extractUsername(jwt);
//如果username为空且认证为空
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
@ -54,7 +56,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
//name非数字特殊符号英文半角密码规则大小写特殊email符合格式
filterChain.doFilter(request, response);
}
}

View File

@ -3,21 +3,23 @@ package co.jp.app.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import co.jp.app.entity.PetEntity;
import co.jp.app.service.PetService;
@Controller
@RestController
public class DownloadController {
@Autowired
private PetService service;
@GetMapping("/api/dogs/pet")
public String downloadById(@RequestParam List<Integer> id) {
service.getPetByID(id);
return "pet";
@GetMapping("/download-image")
public ResponseEntity<?> downloadById(@RequestParam List<Integer> id) {
List<PetEntity> list = service.getPetByID(id);
return ResponseEntity.ok(list);
}
}

View File

@ -3,22 +3,25 @@ package co.jp.app.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import co.jp.app.entity.PetEntity;
import co.jp.app.service.PetService;
@Controller
@RestController
public class PetController {
@Autowired
private PetService service;
@GetMapping("/api/dogs/pet")
public String getListByEntities(@RequestParam List<Integer> id) {
service.getPetByID(id);
return "pet";
@GetMapping("/inuhouse")
public ResponseEntity<?> getListByEntities(@RequestParam List<Integer> id) {
List<PetEntity> list = service.getPetByID(id);
return ResponseEntity.ok(list);
}
}

View File

@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
@ -18,13 +19,13 @@ public class UploadController {
@Autowired
private UploadService service;
@PostMapping("/api/dogs/upload")
public String upload() {
@PostMapping("/upload")
public ResponseEntity<?> upload() {
List<PetEntity> list = new ArrayList<PetEntity>();
service.saveAllPets(list);
List<PetEntity> pets = service.saveAllPets(list);
return "upload";
return ResponseEntity.ok(pets);
}

View File

@ -1,30 +1,29 @@
package co.jp.app.controller;
import co.jp.app.common.ApiResponse;
import co.jp.app.dto.LoginDto;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.service.JwtService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import co.jp.app.entity.ErrorEntity;
import co.jp.app.entity.UserEntity;
import co.jp.app.service.ErraService;
import co.jp.app.service.UserService;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import co.jp.app.common.ApiResponse;
import co.jp.app.dto.LoginDto;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.dto.UserDto;
import co.jp.app.entity.UserEntity;
import co.jp.app.service.JwtService;
import co.jp.app.service.UserService;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
@ -37,41 +36,33 @@ public class UserController {
this.jwtService = jwtService;
}
@PostMapping("/register")
public ResponseEntity<?> registerUser(@Valid @RequestBody RegistrationDto registrationDto) {
try {
@PostMapping("/api/user/register")
public ResponseEntity<ApiResponse<UserDto>> registerUser(@Valid @RequestBody RegistrationDto registrationDto) {
UserEntity registeredUser = userService.registerNewUser(registrationDto);
UserEntity registeredUser = userService.registerNewUser(registrationDto);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(registeredUser.getEmail()));
} catch (Exception e) {
UserDto userDto = new UserDto();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.fail("ユーザー登録失敗しました。"));
}
userDto.setEmail(registeredUser.getEmail());
userDto.setName(registeredUser.getName());
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(userDto));
}
@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginDto loginDto) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginDto.getEmail(), loginDto.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
@PostMapping("/api/user/login")
public ResponseEntity<ApiResponse<Map<String, String>>> authenticateUser(@Valid @RequestBody LoginDto loginDto) {
String jwtToken = jwtService.generateToken(userDetails); // 生成单一的Token
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginDto.getEmail(), loginDto.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
Map<String, String> tokenResponse = new HashMap<>();
tokenResponse.put("token", jwtToken);
String jwtToken = jwtService.generateToken(userDetails);
return ResponseEntity.ok(ApiResponse.success(tokenResponse));
Map<String, String> tokenResponse = new HashMap<>();
tokenResponse.put("token", jwtToken);
} catch (BadCredentialsException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ApiResponse.fail("メールアドレスまたはパスワードが間違っています。"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResponse.fail("サーバーエラー。"));
}
return ResponseEntity.ok(ApiResponse.success(tokenResponse));
}
}

View File

@ -1,9 +1,17 @@
package co.jp.app.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class LoginDto {
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确,请输入有效的邮箱地址")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 30, message = "密码长度必须在6到30位之间")
private String password;
public String getEmail() {

View File

@ -1,11 +1,26 @@
package co.jp.app.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
public class RegistrationDto {
//注username可以为空
@Size(min = 2, max = 20, message = "用户名长度必须在2到20个字符之间")
@Pattern(
regexp = "^[a-zA-Z\\p{script=Han}]+$",
message = "用户名只能包含大小写英文字母和汉字,不能使用数字和标点符号"
)
private String name;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确,请输入有效的邮箱地址")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 30, message = "密码长度必须在6到30位之间")
private String password;
public String getName() {

View File

@ -0,0 +1,24 @@
package co.jp.app.dto;
public class UserDto {
private String email;
private String name;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -7,7 +7,7 @@ import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "Pet")
@Table(name = "Pet_Entity")
public class PetEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)

View File

@ -0,0 +1,38 @@
package co.jp.app.exception;
import co.jp.app.common.ResultCode;
public class BusinessException extends RuntimeException {
private final ResultCode resultCode;
private final String detailMessage; // 附加详细信息
public BusinessException(ResultCode resultCode) {
super(resultCode.getMessage());
this.resultCode = resultCode;
this.detailMessage = null;
}
// 有详细信息的构造函数
public BusinessException(ResultCode resultCode, String detailMessage) {
super(detailMessage != null && !detailMessage.isEmpty() ? detailMessage : resultCode.getMessage());
this.resultCode = resultCode;
this.detailMessage = detailMessage;
}
// 无详细信息的构造函数
public BusinessException(ResultCode resultCode, Throwable cause) {
super(resultCode.getMessage(), cause);
this.resultCode = resultCode;
this.detailMessage = null;
}
public ResultCode getResultCode() {
return resultCode;
}
public String getDetailMessage() {
return detailMessage;
}
}

View File

@ -0,0 +1,125 @@
package co.jp.app.exception;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import co.jp.app.common.ApiResponse;
import co.jp.app.common.ResultCode;
@RestControllerAdvice
public class GlobalExceptionHandler {
// slf4j日志记录器
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Object>> handleBusinessException(BusinessException ex) {
logger.warn("业务异常: Code={}, Message={}, Detail={}", ex.getResultCode().getCode(), ex.getResultCode().getMessage(), ex.getDetailMessage(), ex);
ApiResponse<Object> body;
if (ex.getDetailMessage() != null && !ex.getDetailMessage().isEmpty()) {
body = ApiResponse.fail(ex.getResultCode(), ex.getDetailMessage());
} else {
body = ApiResponse.fail(ex.getResultCode());
}
HttpStatus httpStatus = determineHttpStatusFromResultCode(ex.getResultCode());
return new ResponseEntity<>(body, httpStatus);
}
// 参数校验异常
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
public ResponseEntity<ApiResponse<Object>> handleValidationExceptions(BindException ex) { // BindException 是 MethodArgumentNotValidException 的父类
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.collect(Collectors.joining("; "));
logger.warn("参数校验失败: {}", errorMessage, ex);
return new ResponseEntity<>(ApiResponse.fail(ResultCode.VALIDATION_ERROR, errorMessage), HttpStatus.BAD_REQUEST);
}
// 参数缺失异常
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<ApiResponse<Object>> handleMissingServletRequestParameterException(MissingServletRequestParameterException ex) {
String message = "请求参数 '" + ex.getParameterName() + "' 不能为空";
logger.warn(message, ex);
return new ResponseEntity<>(ApiResponse.fail(ResultCode.BAD_REQUEST, message), HttpStatus.BAD_REQUEST);
}
// Spring Security 用户认证异常
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ApiResponse<Object>> handleAuthenticationException(AuthenticationException ex) {
logger.warn("认证失败: {}", ex.getMessage(), ex);
if (ex instanceof BadCredentialsException) {
return new ResponseEntity<>(ApiResponse.fail(ResultCode.USER_INVALID_CREDENTIALS), HttpStatus.UNAUTHORIZED);
} else if (ex instanceof UsernameNotFoundException) {
return new ResponseEntity<>(ApiResponse.fail(ResultCode.USER_INVALID_CREDENTIALS, "用户名或密码错误(用户不存在)"), HttpStatus.UNAUTHORIZED);
}
return new ResponseEntity<>(ApiResponse.fail(ResultCode.UNAUTHORIZED, ex.getMessage()), HttpStatus.UNAUTHORIZED);
}
// Spring Security 权限异常
// @ExceptionHandler(AccessDeniedException.class)
// public ResponseEntity<ApiResponse<Object>> handleAccessDeniedException(AccessDeniedException ex) {
// logger.warn("权限不足: {}", ex.getMessage(), ex);
// return new ResponseEntity<>(ApiResponse.fail(ResultCode.FORBIDDEN), HttpStatus.FORBIDDEN);
// }
// 其他异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Object>> handleAllUncaughtException(Exception ex) {
logger.error("发生未捕获的服务器内部错误!", ex);
return new ResponseEntity<>(ApiResponse.fail(ResultCode.INTERNAL_SERVER_ERROR), HttpStatus.INTERNAL_SERVER_ERROR);
}
private HttpStatus determineHttpStatusFromResultCode(ResultCode resultCode) {
if (resultCode == null) return HttpStatus.INTERNAL_SERVER_ERROR;
switch (resultCode) {
case SUCCESS:
return HttpStatus.OK;
case UNAUTHORIZED:
case USER_INVALID_CREDENTIALS:
case USER_TOKEN_INVALID:
case USER_TOKEN_EXPIRED:
return HttpStatus.UNAUTHORIZED;
case FORBIDDEN:
return HttpStatus.FORBIDDEN;
case NOT_FOUND:
case USER_ACCOUNT_NOT_FOUND:
case USER_PROFILE_NOT_FOUND:
return HttpStatus.NOT_FOUND;
case CONFLICT:
case USER_EMAIL_ALREADY_EXISTS:
return HttpStatus.CONFLICT;
case BAD_REQUEST:
case VALIDATION_ERROR:
case USER_PASSWORD_TOO_SHORT:
return HttpStatus.BAD_REQUEST;
default:
if (resultCode.getCode() >= 2000 && resultCode.getCode() < 3000) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.BAD_REQUEST;
}
}
}

View File

@ -1,17 +1,12 @@
package co.jp.app.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import co.jp.app.entity.PetEntity;
@Repository
public interface DownloadRepository extends JpaRepository<PetEntity, Integer>{
@Override
default List<PetEntity> findAllById(Iterable<Integer> id) {
return findAllById(id);
}
}

View File

@ -2,15 +2,12 @@ package co.jp.app.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import co.jp.app.entity.ErrorEntity;
@Repository
public interface ErrRepository extends JpaRepository<ErrorEntity, Integer>{
public default ErrorEntity getById(@Param("id") int id) {
return getById(id);
}
}

View File

@ -1,15 +1,10 @@
package co.jp.app.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import co.jp.app.entity.PetEntity;
@Repository
public interface PetRepository extends JpaRepository<PetEntity, Integer> {
@Override
default List<PetEntity> findAllById(Iterable<Integer> id) {
return findAllById(id);
}
}

View File

@ -1,18 +1,12 @@
package co.jp.app.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import co.jp.app.entity.PetEntity;
@Repository
public interface UploadRepository extends JpaRepository<PetEntity, Integer>{
@Override
default <S extends PetEntity> List<S> saveAll(Iterable<S> entities) {
// TODO 自動生成されたメソッド・スタブ
return saveAll(entities);
}
}

View File

@ -1,13 +1,12 @@
package co.jp.app.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
import co.jp.app.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import co.jp.app.entity.UserEntity;
@Repository
public interface UserRepository extends JpaRepository<UserEntity, Integer> {
@ -15,4 +14,8 @@ public interface UserRepository extends JpaRepository<UserEntity, Integer> {
boolean existsByEmail(String email);
Optional<UserEntity> findByEmail(String email);
boolean existsByName(String name);
Optional<UserEntity> findByName(String name);
}

View File

@ -1,19 +0,0 @@
package co.jp.app.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import co.jp.app.entity.ErrorEntity;
import co.jp.app.repository.ErrRepository;
@Service
public class ErraService {
@Autowired
ErrRepository erraRepository;
public ErrorEntity getStatusById(int id) {
return erraRepository.getById(id);
}
}

View File

@ -3,9 +3,6 @@ package co.jp.app.service;
import java.util.Collection;
import java.util.Collections;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.entity.UserEntity;
import co.jp.app.repository.UserRepository;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@ -15,9 +12,14 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import co.jp.app.common.ResultCode;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.entity.UserEntity;
import co.jp.app.exception.BusinessException;
import co.jp.app.repository.UserRepository;
@Service
public class UserService implements UserDetailsService {
@ -29,11 +31,10 @@ public class UserService implements UserDetailsService {
this.passwordEncoder = passwordEncoder;
}
@Transactional
public UserEntity registerNewUser(@NotNull RegistrationDto registrationDto) throws Exception {
public UserEntity registerNewUser(@NotNull RegistrationDto registrationDto) throws BusinessException {
if (userRepository.existsByEmail(registrationDto.getEmail())) {
throw new Exception("error: Email" + registrationDto.getEmail() + " had been used");
throw new BusinessException(ResultCode.USER_EMAIL_ALREADY_EXISTS,"error: Email" + registrationDto.getEmail() + " had been used");
}
UserEntity newUser = new UserEntity();
@ -50,16 +51,17 @@ public class UserService implements UserDetailsService {
UserEntity userEntity = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email + " not found"));
Collection<? extends GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
Collection<? extends GrantedAuthority> authorities = Collections
.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
return new User(
userEntity.getEmail(),
userEntity.getPassword(),
true, // enabled
true, // accountNonExpired
true, // credentialsNonExpired
true, // accountNonLocked
authorities // role
true, // enabled
true, // accountNonExpired
true, // credentialsNonExpired
true, // accountNonLocked
authorities // role
);
}
}

View File

@ -0,0 +1,168 @@
package co.jp.app;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.function.Executable;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import co.jp.app.common.ResultCode;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.entity.UserEntity;
import co.jp.app.exception.BusinessException;
import co.jp.app.repository.UserRepository;
import co.jp.app.service.UserService;
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserService userService;
private RegistrationDto registrationDto;
private UserEntity userEntity;
@BeforeEach
public void setUp() {
registrationDto = new RegistrationDto();
registrationDto.setName("Test User");
registrationDto.setEmail("test@example.com");
registrationDto.setPassword("password123");
userEntity = new UserEntity();
userEntity.setName("Test User");
userEntity.setEmail("test@example.com");
userEntity.setPassword("encodedPassword");
}
@Test
@DisplayName("新用户注册成功")
public void registerNewUser_success() throws BusinessException {
// Arrange
when(userRepository.existsByEmail(anyString())).thenReturn(false);
when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword");
when(userRepository.save(any(UserEntity.class))).thenReturn(userEntity);
// Act
UserEntity savedUser = userService.registerNewUser(registrationDto);
// Assert
assertNotNull(savedUser);
assertEquals(userEntity.getEmail(), savedUser.getEmail());
assertEquals("encodedPassword", savedUser.getPassword());
verify(userRepository, times(1)).existsByEmail("test@example.com");
verify(passwordEncoder, times(1)).encode("password123");
verify(userRepository, times(1)).save(any(UserEntity.class));
}
@Test
@DisplayName("用户注册 - 邮箱已存在")
public void registerNewUser_emailAlreadyExists() {
// Arrange
when(userRepository.existsByEmail(anyString())).thenReturn(true);
// Act & Assert
BusinessException exception = assertThrows(BusinessException.class, new Executable() {
public void execute() throws Throwable {
userService.registerNewUser(registrationDto);
}
});
assertEquals(ResultCode.USER_EMAIL_ALREADY_EXISTS, exception.getResultCode());
verify(userRepository, times(1)).existsByEmail("test@example.com");
verify(passwordEncoder, never()).encode(anyString());
verify(userRepository, never()).save(any(UserEntity.class));
}
@Test
@DisplayName("用户注册 - 密码过短")
public void registerNewUser_passwordTooShort() {
// Arrange
registrationDto.setPassword("123"); // 设置一个短密码
when(userRepository.existsByEmail(anyString())).thenReturn(false);
// Act & Assert
BusinessException exception = assertThrows(BusinessException.class, new Executable() {
public void execute() throws Throwable {
userService.registerNewUser(registrationDto);
}
});
assertEquals(ResultCode.USER_PASSWORD_TOO_SHORT, exception.getResultCode());
verify(userRepository, times(1)).existsByEmail("test@example.com");
verify(passwordEncoder, never()).encode(anyString());
verify(userRepository, never()).save(any(UserEntity.class));
}
@Test
@DisplayName("用户注册 - 密码为null")
public void registerNewUser_passwordIsNull() {
// Arrange
registrationDto.setPassword(null); // 设置密码为null
when(userRepository.existsByEmail(anyString())).thenReturn(false);
// Act & Assert
BusinessException exception = assertThrows(BusinessException.class, new Executable() {
public void execute() throws Throwable {
userService.registerNewUser(registrationDto);
}
});
assertEquals(ResultCode.USER_PASSWORD_TOO_SHORT, exception.getResultCode());
verify(userRepository, times(1)).existsByEmail("test@example.com");
verify(passwordEncoder, never()).encode(anyString());
verify(userRepository, never()).save(any(UserEntity.class));
}
@Test
@DisplayName("通过邮箱加载用户 - 用户存在")
public void loadUserByUsername_userFound() {
// Arrange
when(userRepository.findByEmail(anyString())).thenReturn(Optional.of(userEntity));
// Act
UserDetails userDetails = userService.loadUserByUsername("test@example.com");
// Assert
assertNotNull(userDetails);
assertEquals(userEntity.getEmail(), userDetails.getUsername());
assertEquals(userEntity.getPassword(), userDetails.getPassword());
assertTrue(userDetails.getAuthorities().stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals("ROLE_USER")));
verify(userRepository, times(1)).findByEmail("test@example.com");
}
@Test
@DisplayName("通过邮箱加载用户 - 用户不存在")
public void loadUserByUsername_userNotFound() {
// Arrange
when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty());
// Act & Assert
UsernameNotFoundException exception = assertThrows(UsernameNotFoundException.class, new Executable() {
public void execute() throws Throwable {
userService.loadUserByUsername("unknown@example.com");
}
});
assertEquals("unknown@example.com not found", exception.getMessage());
verify(userRepository, times(1)).findByEmail("unknown@example.com");
}
}

View File

@ -0,0 +1,70 @@
package co.jp.app;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.entity.UserEntity;
import co.jp.app.exception.BusinessException;
import co.jp.app.repository.UserRepository;
import co.jp.app.service.UserService;
@ExtendWith(MockitoExtension.class)
public class UserServiceTest1 {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserService userService;
private RegistrationDto registrationDto;
private UserEntity userEntity;
@BeforeEach
public void setUp() {
registrationDto = new RegistrationDto();
registrationDto.setName("Test User");
registrationDto.setEmail("test@example.com");
registrationDto.setPassword("password123");
userEntity = new UserEntity();
userEntity.setName("Test User");
userEntity.setEmail("test@example.com");
userEntity.setPassword("encodedPassword");
}
@Test
@DisplayName("新用户注册成功")
public void registerNewUser_success() throws BusinessException {
// Arrange
when(userRepository.existsByEmail(anyString())).thenReturn(false);
when(passwordEncoder.encode(anyString())).thenReturn("encodedPassword");
when(userRepository.save(any(UserEntity.class))).thenReturn(userEntity);
// Act
UserEntity savedUser = userService.registerNewUser(registrationDto);
// Assert
assertNotNull(savedUser);
assertEquals(userEntity.getEmail(), savedUser.getEmail());
assertEquals("encodedPassword", savedUser.getPassword());
verify(userRepository, times(1)).existsByEmail("test@example.com");
verify(passwordEncoder, times(1)).encode("password123");
verify(userRepository, times(1)).save(any(UserEntity.class));
}
}

View File

@ -0,0 +1,74 @@
package co.jp.app.dogtestbyadmin;
import co.jp.app.dto.LoginDto;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.dto.UserDto;
import co.jp.app.entity.UserEntity;
import co.jp.app.repository.UserRepository;
import co.jp.app.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import static org.mockito.Mockito.when;
@SpringBootTest
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@InjectMocks
private UserService userService;
private RegistrationDto registrationDto;
private LoginDto loginDto;
private UserDto userDto;
private UserEntity userEntity;
@BeforeEach
void setUp() {
registrationDto = new RegistrationDto();
registrationDto.setEmail("<EMAIL>");
registrationDto.setName("test");
registrationDto.setPassword("<PASSWORD>");
loginDto = new LoginDto();
loginDto.setEmail("<EMAIL>");
loginDto.setPassword("<PASSWORD>");
userDto = new UserDto();
userDto.setEmail("<EMAIL>");
userDto.setName("test");
}
@Test
void testRegisterNewUser_normal () throws Exception{
when(userRepository.existsByEmail(registrationDto.getEmail())).thenReturn(false);
when(passwordEncoder.encode(registrationDto.getPassword())).thenReturn(registrationDto.getPassword());
when(userRepository.save(userEntity)).thenAnswer(invocation -> invocation.getArgument(0));
}
@Test
void testRegisterNewUser() throws Exception{
}
@Test
void TestLoadUserByUsername() throws Exception{
}
}

View File

@ -1,7 +0,0 @@
#Generated by Maven Integration for Eclipse
#Mon May 12 14:19:16 JST 2025
m2e.projectLocation=C\:\\Users\\ichbi\\OneDrive\\\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7\\dog-1
m2e.projectName=dog-1
groupId=co.jp.app
artifactId=dog-2
version=0.0.1-SNAPSHOT

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.