Compare commits

...

47 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
2617b4a5ae 暂时开放所有权限 2025-05-12 16:39:08 +09:00
z
6d3f837d7c saved 2025-05-12 16:31:18 +09:00
76102067ab UrlChanged 2025-05-12 16:20:16 +09:00
38e91f45b6 修正版本 2025-05-12 16:02:18 +09:00
b23ba3b92a 修正版本 2025-05-12 15:59:39 +09:00
10a51413d0 Merge remote-tracking branch 'origin/master' 2025-05-12 15:58:55 +09:00
b113c43959 修正版本 2025-05-12 15:58:45 +09:00
f6099d382f 删除 src/main/java/co/jp/app/repository/userRepository.java 2025-05-12 15:50:16 +09:00
e307eb06cc 0512修正 2025-05-12 15:09:01 +09:00
9ea0721375 reupload 2025-05-12 14:19:49 +09:00
cacdef44d7 renew 2025-05-12 14:13:25 +09:00
56 changed files with 826 additions and 166 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

1
bin Submodule

Submodule bin added at aa8d8275e8

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;
@ -47,12 +46,13 @@ public class SecurityConfig {
return authenticationConfiguration.getAuthenticationManager();
}
// http config
@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()
.requestMatchers("/api/user/login", "/api/user/register", "/api/inuhouse", "/api/dogs/pet").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider())
@ -60,4 +60,5 @@ public class SecurityConfig {
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

@ -0,0 +1,25 @@
package co.jp.app.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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;
@RestController
public class DownloadController {
@Autowired
private PetService service;
@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,26 +4,28 @@ 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;
import co.jp.app.entity.PetEntity;
import co.jp.app.service.UploadService;
@CrossOrigin("http://192.168.1.50:5173")
@Controller
public class UploadController {
@Autowired
//private UploadService service;
private UploadService service;
@PostMapping("/api/dogs/upload")
public String upload() {
@PostMapping("/upload")
public ResponseEntity<?> upload() {
List<PetEntity> list = new ArrayList<PetEntity>();
//service.saveAll(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

@ -0,0 +1,12 @@
package co.jp.app.repository;
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>{
}

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

@ -0,0 +1,23 @@
package co.jp.app.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import co.jp.app.entity.PetEntity;
import co.jp.app.repository.DownloadRepository;
@Service
public class DownloadService {
@Autowired
private DownloadRepository downloadDao;
public List<PetEntity> getPetByID(Iterable<Integer> id) {
return downloadDao.findAllById(id);
}
}

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

@ -16,6 +16,6 @@ public class PetService {
public List<PetEntity> getPetByID(Iterable<Integer> id) {
return dao.findAllById(id);
}
}

View File

@ -0,0 +1,21 @@
package co.jp.app.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import co.jp.app.entity.PetEntity;
import co.jp.app.repository.UploadRepository;
@Service
public class UploadService {
@Autowired
private UploadRepository uploadDao;
public List<PetEntity> saveAllPets(List<PetEntity> entities) {
return uploadDao.saveAll(entities);
}
}

View File

@ -2,11 +2,7 @@ package co.jp.app.service;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
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;
@ -16,11 +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 co.jp.app.entity.PetEntity;
import co.jp.app.repository.UploadRepository;
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 {
@ -32,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("エラー:メール:" + registrationDto.getEmail() + " はすでに登録されました。");
throw new BusinessException(ResultCode.USER_EMAIL_ALREADY_EXISTS,"error: Email" + registrationDto.getEmail() + " had been used");
}
UserEntity newUser = new UserEntity();
@ -46,23 +44,24 @@ public class UserService implements UserDetailsService {
return userRepository.save(newUser);
}
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email + " not found"));
Collection<? extends GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); // 示例给所有用户一个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 // 用户的权限集合
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 +0,0 @@
/co/

View File

@ -1,7 +0,0 @@
#Generated by Maven Integration for Eclipse
#Mon May 12 11:38:52 JST 2025
m2e.projectLocation=E\:\\jugyo\\asciimg-master\\Dog-1
m2e.projectName=dog-1
groupId=co.jp.app
artifactId=dog-2
version=0.0.1-SNAPSHOT

View File

@ -10,4 +10,4 @@ spring.datasource.password=coder
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
jwt.secret=wM7Pz4BxvZ5NcLaBpgJm0eRQ5ztc3W5+OPH0E7g3gcQ=
jwt.token-expiration-ms==900000
jwt.token-expiration-ms=900000

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.