Compare commits

...

16 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
42 changed files with 330 additions and 89 deletions

1
.gitignore vendored Normal file
View File

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

View File

@ -1,2 +1,5 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8

View File

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

View File

@ -2,9 +2,9 @@ package co.jp.app.common;
public class ApiResponse<T> {
//状态判定
//成功状况判定
private boolean success;
//状
//状
private int code;
//状态信息
private String message;
@ -18,12 +18,13 @@ public class ApiResponse<T> {
this.data = data;
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(ResultCode.SUCCESS, data);
private ApiResponse() {
}
public static <T> ApiResponse<T> success() {
return success(null);
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) {

View File

@ -28,7 +28,11 @@ public enum ResultCode {
// 注册相关
// USER_REGISTRATION_SUCCESS(3000, "用户注册成功"),
USER_EMAIL_ALREADY_EXISTS(3001, "Email already exists"),
// USER_USERNAME_ALREADY_EXISTS(3002, "Username"),
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"),

View File

@ -46,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", "/api/inuhouse").permitAll()
.requestMatchers("/api/user/login", "/api/user/register", "/api/inuhouse", "/api/dogs/pet").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider())

View File

@ -35,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)) {
@ -53,7 +56,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
//name非数字特殊符号英文半角密码规则大小写特殊email符合格式
filterChain.doFilter(request, response);
}
}

View File

@ -16,7 +16,7 @@ public class DownloadController {
@Autowired
private PetService service;
@GetMapping("/api/dogs/download")
@GetMapping("/download-image")
public ResponseEntity<?> downloadById(@RequestParam List<Integer> id) {
List<PetEntity> list = service.getPetByID(id);
return ResponseEntity.ok(list);

View File

@ -18,7 +18,7 @@ public class PetController {
@Autowired
private PetService service;
@GetMapping("/api/dogs/pet")
@GetMapping("/inuhouse")
public ResponseEntity<?> getListByEntities(@RequestParam List<Integer> id) {
List<PetEntity> list = service.getPetByID(id);

View File

@ -19,7 +19,7 @@ public class UploadController {
@Autowired
private UploadService service;
@PostMapping("/api/dogs/upload")
@PostMapping("/upload")
public ResponseEntity<?> upload() {
List<PetEntity> list = new ArrayList<PetEntity>();

View File

@ -1,30 +1,29 @@
package co.jp.app.controller;
import co.jp.app.common.ApiResponse;
import co.jp.app.common.ResultCode;
import co.jp.app.dto.LoginDto;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.dto.UserDto;
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.UserEntity;
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,7 +36,7 @@ public class UserController {
this.jwtService = jwtService;
}
@PostMapping("/register")
@PostMapping("/api/user/register")
public ResponseEntity<ApiResponse<UserDto>> registerUser(@Valid @RequestBody RegistrationDto registrationDto) {
UserEntity registeredUser = userService.registerNewUser(registrationDto);
@ -50,7 +49,7 @@ public class UserController {
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(userDto));
}
@PostMapping("/login")
@PostMapping("/api/user/login")
public ResponseEntity<ApiResponse<Map<String, String>>> authenticateUser(@Valid @RequestBody LoginDto loginDto) {
Authentication authentication = authenticationManager.authenticate(

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

@ -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

@ -1,14 +1,11 @@
package co.jp.app.exception;
import co.jp.app.common.ApiResponse;
import co.jp.app.common.ResultCode;
import co.jp.app.exception.BusinessException;
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.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@ -18,12 +15,13 @@ import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;
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);
// 业务异常

View File

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

View File

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

View File

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

View File

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

View File

@ -14,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

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

View File

@ -3,8 +3,6 @@ package co.jp.app.service;
import java.util.Collection;
import java.util.Collections;
import co.jp.app.common.ResultCode;
import co.jp.app.exception.BusinessException;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@ -16,8 +14,10 @@ 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
@ -31,18 +31,12 @@ public class UserService implements UserDetailsService {
this.passwordEncoder = passwordEncoder;
}
@Transactional
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");
}
//密码最短6位限制
if (registrationDto.getPassword() == null || registrationDto.getPassword().length() < 6) {
throw new BusinessException(ResultCode.USER_PASSWORD_TOO_SHORT);
}
UserEntity newUser = new UserEntity();
newUser.setName(registrationDto.getName());
newUser.setEmail(registrationDto.getEmail());
@ -50,7 +44,7 @@ public class UserService implements UserDetailsService {
return userRepository.save(newUser);
}
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

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

@ -1,2 +0,0 @@
/META-INF/
/co/

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.