Compare commits

...

66 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
34de018ef0 修正符号错误 2025-05-12 14:09:36 +09:00
90a1bdf243 Merge remote-tracking branch 'origin/master' 2025-05-12 14:07:05 +09:00
c415f059aa 增加@Lazy注解消除依赖循环 2025-05-12 14:06:12 +09:00
z
032424e7c6 changed format 2025-05-12 11:50:08 +09:00
f335896f4f 移除autowired 2025-05-09 15:35:13 +09:00
320a344c04 增加jetbrains.annotations.NotNull注释 2025-05-09 14:55:25 +09:00
53a484dd2b 增加jetbrains.annotations.NotNull依赖 2025-05-09 14:54:44 +09:00
6ff61fccfa 修改SignatureException依赖 2025-05-09 14:42:29 +09:00
203fc9866d 增加CorsConfig处理跨域 2025-05-09 13:55:03 +09:00
94308202e9 增加CorsConfig处理跨域 2025-05-09 13:54:12 +09:00
225e89df5d 重大修改 2025-05-08 15:54:59 +09:00
75103a3a8a 增加注册和登录的数据传输 2025-05-08 15:54:09 +09:00
37a8ea12c4 增加jwtfilter 2025-05-08 15:53:21 +09:00
779d94ba1b 增加jwtservice 2025-05-08 15:53:09 +09:00
f6ee15f4f9 增加securityconfig 2025-05-08 15:52:51 +09:00
3c4397f430 重写controller,增加了用户注册和登录接口 2025-05-08 15:51:59 +09:00
ab8f36d000 文件名更正 2025-05-08 15:50:04 +09:00
6e71978a77 文件名更正 2025-05-08 15:49:51 +09:00
44d934bc1c 增加jwt密钥和设置 2025-05-08 15:49:20 +09:00
74 changed files with 1313 additions and 289 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

@ -78,6 +78,12 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>

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

@ -0,0 +1,17 @@
package co.jp.app.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 允许 /api/ 下的所有请求
.allowedOrigins("*") // 允许来自该域的请求
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的 HTTP 方法
.allowedHeaders("*"); // 允许所有头部
}
}

View File

@ -1,15 +0,0 @@
package co.jp.app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,64 @@
package co.jp.app.config.security;
import co.jp.app.config.security.filter.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsService userDetailsService;
public SecurityConfig(@Lazy JwtAuthenticationFilter jwtAuthenticationFilter, @Lazy UserDetailsService userDetailsService) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.userDetailsService = userDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
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", "/api/inuhouse", "/api/dogs/pet").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}

View File

@ -0,0 +1,62 @@
package co.jp.app.config.security.filter;
import co.jp.app.service.JwtService;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Lazy;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtService jwtService, UserDetailsService userDetailsService) {
this.jwtService = jwtService;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
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)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
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

@ -1,37 +0,0 @@
package co.jp.app.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import co.jp.app.entity.ErrorEntity;
import co.jp.app.entity.UserEntity;
import co.jp.app.service.ErraService;
import co.jp.app.service.UserService;
@CrossOrigin("http://192.168.1.50:5173")
@RestController("/api/login")
public class LoginController {
@Autowired
private UserService userService;
@Autowired
private ErraService erraService;
@GetMapping("/status")
public String getStatusByNameOrEmail() {
String input="aaa";
if (userByName == null && userByEmail == null) {
return "全項目に入力してください";
}
// 如果有找到,就固定使用 ID 1001 去查 erraEntity
ErrorEntity erra = erraService.getStatusById(1001);
return erra.getStatus();
}
}

View File

@ -3,23 +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.saveAll(list);
List<PetEntity> pets = service.saveAllPets(list);
return "upload";
return ResponseEntity.ok(pets);
}

View File

@ -0,0 +1,68 @@
package co.jp.app.controller;
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
public class UserController {
private final UserService userService;
private final AuthenticationManager authenticationManager;
private final JwtService jwtService;
public UserController(UserService userService, AuthenticationManager authenticationManager, JwtService jwtService) {
this.userService = userService;
this.authenticationManager = authenticationManager;
this.jwtService = jwtService;
}
@PostMapping("/api/user/register")
public ResponseEntity<ApiResponse<UserDto>> registerUser(@Valid @RequestBody RegistrationDto registrationDto) {
UserEntity registeredUser = userService.registerNewUser(registrationDto);
UserDto userDto = new UserDto();
userDto.setEmail(registeredUser.getEmail());
userDto.setName(registeredUser.getName());
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(userDto));
}
@PostMapping("/api/user/login")
public ResponseEntity<ApiResponse<Map<String, String>>> authenticateUser(@Valid @RequestBody LoginDto loginDto) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginDto.getEmail(), loginDto.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String jwtToken = jwtService.generateToken(userDetails);
Map<String, String> tokenResponse = new HashMap<>();
tokenResponse.put("token", jwtToken);
return ResponseEntity.ok(ApiResponse.success(tokenResponse));
}
}

View File

@ -0,0 +1,32 @@
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() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,49 @@
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() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

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

@ -5,10 +5,11 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "Pet")
public class PetEntity{
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Table(name = "Pet_Entity")
public class PetEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private int ID;
@ -27,15 +28,15 @@ public class PetEntity{
//犬の健康状態
private String status;
//犬の圖片
private String image;
public String getImage() {
return image;
}
private String image;
public void setImage(String image) {
this.image = image;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getName() {
return name;
@ -92,6 +93,7 @@ public class PetEntity{
public void setStatus(String status) {
this.status = status;
}
public int getID() {
return ID;
}
@ -99,5 +101,5 @@ public class PetEntity{
public void setID(int iD) {
ID = iD;
}
}

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

@ -0,0 +1,13 @@
package co.jp.app.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import co.jp.app.entity.ErrorEntity;
@Repository
public interface ErrRepository extends JpaRepository<ErrorEntity, Integer>{
}

View File

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

View File

@ -1,18 +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.ErraRepository;
@Service
public class ErraService {
@Autowired
ErraRepository erraRepository;
public ErrorEntity getStatusById(int id) {
return erraRepository.getById(id);
}
}

View File

@ -0,0 +1,106 @@
package co.jp.app.service;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
public class JwtService {
private static final Logger logger = LoggerFactory.getLogger(JwtService.class);
//log
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.token-expiration-ms}")
private long tokenExpirationMs;
@org.jetbrains.annotations.NotNull
private Key getSignKey() {
byte[] keyBytes = Base64.getDecoder().decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
public String extractUsername(String token) {
try {
return extractClaim(token, Claims::getSubject);
} catch (ExpiredJwtException e) {
logger.warn("JWT token is expired when extracting username: {}", e.getMessage());
return e.getClaims().getSubject();
} catch (MalformedJwtException | SignatureException | UnsupportedJwtException | IllegalArgumentException e) {
logger.error("Invalid JWT token when extracting username: {}", e.getMessage());
return null;
}
}
public <T> T extractClaim(String token, @NotNull Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public String generateToken(@NotNull UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + tokenExpirationMs)) // 使用配置的过期时间
.signWith(getSignKey(), SignatureAlgorithm.HS256)
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
try {
final String username = extractUsername(token);
if (username == null) {
return false;
}
return (username.equals(userDetails.getUsername()) && !isTokenActuallyExpired(token));
} catch (ExpiredJwtException e) {
logger.warn("Token validation failed: Expired JWT - {}", e.getMessage());
return false;
} catch (MalformedJwtException | SignatureException | UnsupportedJwtException | IllegalArgumentException e) {
logger.error("Token validation failed: Invalid JWT (format, signature, etc.) - {}", e.getMessage());
return false;
}
}
private boolean isTokenActuallyExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
}

View File

@ -8,7 +8,6 @@ import org.springframework.stereotype.Service;
import co.jp.app.entity.PetEntity;
import co.jp.app.repository.PetRepository;
@Service
public class PetService {
@Autowired
@ -17,6 +16,6 @@ public class PetService {
public List<PetEntity> getPetByID(Iterable<Integer> id) {
return dao.findAllById(id);
}
}

View File

@ -14,10 +14,8 @@ public class UploadService {
@Autowired
private UploadRepository uploadDao;
public List<PetEntity> saveAll(Iterable<PetEntity> entities) {
return uploadDao.saveAll(entities);
}
public List<PetEntity> saveAllPets(List<PetEntity> entities) {
return uploadDao.saveAll(entities);
}
}

View File

@ -1,52 +1,67 @@
package co.jp.app.service;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.Collections;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
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 {
public class UserService implements UserDetailsService {
private final UserRepository userEntityRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
public UserService(UserRepository userEntityRepository, PasswordEncoder passwordEncoder ) {
this.userEntityRepository = userEntityRepository;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Transactional // 整个注册过程应该是一个事务
public UserEntity registerNewUser(String name, String email, String rawPassword) throws Exception {
// 1. 检查邮箱是否已被注册
if (userEntityRepository.existsByEmail(email)) {
throw new Exception("错误:该邮箱地址已被注册!"); // 或者自定义异常
public UserEntity registerNewUser(@NotNull RegistrationDto registrationDto) throws BusinessException {
if (userRepository.existsByEmail(registrationDto.getEmail())) {
throw new BusinessException(ResultCode.USER_EMAIL_ALREADY_EXISTS,"error: Email" + registrationDto.getEmail() + " had been used");
}
// (可选) 检查用户名是否已被注册 (如果您有用户名字段)
// if (userEntityRepository.existsByUsername(username)) {
// throw new Exception("错误:该用户名已被注册!");
// }
// 2. 创建新的 UserEntity 对象
UserEntity newUser = new UserEntity();
newUser.setName(name);
newUser.setEmail(email);
newUser.setName(registrationDto.getName());
newUser.setEmail(registrationDto.getEmail());
newUser.setPassword(passwordEncoder.encode(registrationDto.getPassword()));
// 3. 对密码进行哈希加密 (非常重要!)
// String hashedPassword = passwordEncoder.encode(rawPassword);
// newUser.setPassword(hashedPassword);
newUser.setPassword(rawPassword); // 实际项目中必须加密!这里为了简化先直接赋值
// 4. 设置其他默认属性,例如账户状态、角色等 (如果需要)
// newUser.setActive(true);
// newUser.setRoles(...);
// 5. 保存新用户到数据库
return userEntityRepository.save(newUser);
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"));
return new User(
userEntity.getEmail(),
userEntity.getPassword(),
true, // enabled
true, // accountNonExpired
true, // credentialsNonExpired
true, // accountNonLocked
authorities // role
);
}
}

View File

@ -7,4 +7,7 @@ spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.datasource.url=jdbc:mysql://192.168.1.192:3306/dog
spring.datasource.username=coder
spring.datasource.password=coder
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
jwt.secret=wM7Pz4BxvZ5NcLaBpgJm0eRQ5ztc3W5+OPH0E7g3gcQ=
jwt.token-expiration-ms=900000

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
#Wed May 07 16:32:03 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

View File

@ -1,87 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>co.jp.app</groupId>
<artifactId>dog-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dog-1</name>
<description>dog introduce project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>co.jp.app</groupId>
<artifactId>dog-2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dog-1</name>
<description>dog introduce project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -7,4 +7,7 @@ spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.datasource.url=jdbc:mysql://192.168.1.192:3306/dog
spring.datasource.username=coder
spring.datasource.password=coder
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
jwt.secret=wM7Pz4BxvZ5NcLaBpgJm0eRQ5ztc3W5+OPH0E7g3gcQ=
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.