diff --git a/build.gradle b/build.gradle index ace8992..9a467ae 100644 --- a/build.gradle +++ b/build.gradle @@ -131,3 +131,4 @@ tasks.javadoc { tasks.named('test') { useJUnitPlatform() } + diff --git a/src/main/java/io/company/localhost/common/config/WebMvcConfig.java b/src/main/java/io/company/localhost/common/config/WebMvcConfig.java index 90c28f2..2d8d0c5 100644 --- a/src/main/java/io/company/localhost/common/config/WebMvcConfig.java +++ b/src/main/java/io/company/localhost/common/config/WebMvcConfig.java @@ -15,10 +15,14 @@ package io.company.localhost.common.config; import io.company.localhost.common.resolver.RequestToMapArgumentResolver; + +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.multipart.MultipartResolver; +import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -35,6 +39,12 @@ public class WebMvcConfig implements WebMvcConfigurer { public static final String WILD_CARD = "/**"; + @Value("${filePath.boardfile}") + private String boardFilePath; + + @Value("${filePath.profile}") + private String uploadPath; + @Override public void addInterceptors(InterceptorRegistry registry) { // 여기서 인터셉터를 추가할 수 있음 @@ -47,6 +57,13 @@ public class WebMvcConfig implements WebMvcConfigurer { registry .addResourceHandler("/index.html") .addResourceLocations("classpath:/static/index.html", "/index.html"); + + //게시판 에디터 안 이미지 업로드 경로 + registry.addResourceHandler("/upload/img/board/**") + .addResourceLocations("file:" + boardFilePath); + //프로필 이미지 업로드 경로 + registry.addResourceHandler("/upload/img/profile/**") + .addResourceLocations("file:" + uploadPath); } // Controller의 파라미터를 처리할 Resolver 등록 @@ -82,4 +99,11 @@ public class WebMvcConfig implements WebMvcConfigurer { return localeResolver; } + + @Bean + public MultipartResolver multipartResolver() { + return new StandardServletMultipartResolver(); + } + + } diff --git a/src/main/java/io/company/localhost/common/exception/code/CommonErrorCode.java b/src/main/java/io/company/localhost/common/exception/code/CommonErrorCode.java index 49a45a5..5d99fd4 100644 --- a/src/main/java/io/company/localhost/common/exception/code/CommonErrorCode.java +++ b/src/main/java/io/company/localhost/common/exception/code/CommonErrorCode.java @@ -23,13 +23,18 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public enum CommonErrorCode implements ErrorCode { - INVALID_PARAMETER(HttpStatus.BAD_REQUEST.value(),HttpStatus.BAD_REQUEST,"잘못된 매개변수가 포함되었습니다."), - RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND.value(),HttpStatus.NOT_FOUND,"리소스가 존재하지 않습니다"), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(),HttpStatus.INTERNAL_SERVER_ERROR,"내부 서버 오류"), + INVALID_PARAMETER(HttpStatus.BAD_REQUEST,"잘못된 매개변수가 포함되었습니다."), + RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND,"리소스가 존재하지 않습니다"), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,"내부 서버 오류"), ; private final long code; private final HttpStatus httpStatus; private final String message; + CommonErrorCode(HttpStatus httpStatus, String message) { + this.code = httpStatus.value(); + this.httpStatus = httpStatus; + this.message = message; + } } diff --git a/src/main/java/io/company/localhost/common/exception/code/UserErrorCode.java b/src/main/java/io/company/localhost/common/exception/code/UserErrorCode.java index d9ebbdf..c7a4be5 100644 --- a/src/main/java/io/company/localhost/common/exception/code/UserErrorCode.java +++ b/src/main/java/io/company/localhost/common/exception/code/UserErrorCode.java @@ -24,13 +24,23 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public enum UserErrorCode implements ErrorCode { - NOT_AUTH_USER(HttpStatus.UNAUTHORIZED.value(),HttpStatus.UNAUTHORIZED ,"로그인이 필요합니다."), - INACTIVE_USER(HttpStatus.FORBIDDEN.value(),HttpStatus.FORBIDDEN,"권한이 필요합니다."); + NOT_AUTH_USER(HttpStatus.UNAUTHORIZED ,"로그인이 필요합니다."), + INACTIVE_USER(HttpStatus.FORBIDDEN,"권한이 필요합니다."), + USER_NOT_FOUND(HttpStatus.UNAUTHORIZED,"아이디 혹은 비밀번호가 틀렸습니다."), + NOT_AUTHORIZED(HttpStatus.UNAUTHORIZED,"비인가 계정입니다."), + EXIT_USER(HttpStatus.UNAUTHORIZED,"탈퇴한 계정입니다."), + BAD_CREDENTIAL(HttpStatus.UNAUTHORIZED, "아이디 혹은 비밀번호 문제") + ; private final long code; private final HttpStatus httpStatus; private final String message; + UserErrorCode(HttpStatus httpStatus, String message) { + this.code = httpStatus.value(); + this.httpStatus = httpStatus; + this.message = message; + } public ApiResponse getApiResponse() { return ApiResponse.error(this.getHttpStatus() , this.getMessage()); diff --git a/src/main/java/io/company/localhost/common/security/config/SecurityConfig.java b/src/main/java/io/company/localhost/common/security/config/SecurityConfig.java index 2432122..d6ab1c3 100644 --- a/src/main/java/io/company/localhost/common/security/config/SecurityConfig.java +++ b/src/main/java/io/company/localhost/common/security/config/SecurityConfig.java @@ -21,10 +21,12 @@ import io.company.localhost.common.security.handler.MemberAuthSuccessHandler; import io.company.localhost.common.security.handler.RestAccessDeniedHandler; import io.company.localhost.common.security.handler.RestAuthenticationEntryPointHandler; import io.company.localhost.common.security.service.CustomRememberMeServices; +import io.company.localhost.common.security.service.TokenService; import io.company.localhost.common.security.service.MemberPrincipalDetailService; import io.company.localhost.common.security.session.AuthenticationSessionControlStrategy; import io.company.localhost.common.security.session.CustomSessionRegistryImpl; import lombok.RequiredArgsConstructor; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -50,6 +52,7 @@ public class SecurityConfig { // 의존성 주입 private final AuthenticationProvider memberAuthenticatorProvider; private final MemberPrincipalDetailService userDetailsService; + private final MemberAuthSuccessHandler successHandler; private final MemberAuthFailureHandler failureHandler; private final AuthorizationManager authorizationManager; @@ -62,6 +65,9 @@ public class SecurityConfig { // API 경로 관련 상수 설정 final String SECURITY_BASE_URL = "/api/user"; final String LOGIN_URL = SECURITY_BASE_URL + "/login"; + + final String LOGIN_KEY = "loginSecretKey"; + // 보안 필터 체인 설정 @Bean @@ -71,12 +77,11 @@ public class SecurityConfig { .authenticationProvider(memberAuthenticatorProvider) .build(); - MemberAuthSuccessHandler successHandler = new MemberAuthSuccessHandler(rememberMeServices()); - http .securityMatcher("/api/**") // '/api/**' 경로에 대해서만 보안 적용 - .authorizeHttpRequests(auth -> - auth.requestMatchers("/api/board/general").permitAll() // 특정 엔드포인트 허용 + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/board/**").hasRole("MEMBER") + .requestMatchers("/api/**").permitAll() // 특정 엔드포인트 허용 .anyRequest().authenticated() // 나머지 요청은 인증 필요 //auth.anyRequest().access(authorizationManager) // 모든 요청에 대해 권한 관리 ) @@ -116,9 +121,15 @@ public class SecurityConfig { @Bean public RememberMeServices rememberMeServices(){ - return new CustomRememberMeServices(REMEMBER_KEY , userDetailsService); + return new CustomRememberMeServices(tokenService(), userDetailsService); + } + + @Bean + public TokenService tokenService() { + return new TokenService(REMEMBER_KEY, LOGIN_KEY); } + // 세션 관리 protected ConcurrentSessionControlAuthenticationStrategy sessionControlStrategy() { AuthenticationSessionControlStrategy sessionControlStrategy = new AuthenticationSessionControlStrategy(sessionRegistry()); diff --git a/src/main/java/io/company/localhost/common/security/handler/MemberAuthFailureHandler.java b/src/main/java/io/company/localhost/common/security/handler/MemberAuthFailureHandler.java index 4ef6488..b978aaa 100644 --- a/src/main/java/io/company/localhost/common/security/handler/MemberAuthFailureHandler.java +++ b/src/main/java/io/company/localhost/common/security/handler/MemberAuthFailureHandler.java @@ -15,6 +15,8 @@ package io.company.localhost.common.security.handler; import com.fasterxml.jackson.databind.ObjectMapper; +import io.company.localhost.common.dto.ApiResponse; +import io.company.localhost.common.exception.code.UserErrorCode; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.MediaType; @@ -35,10 +37,17 @@ public class MemberAuthFailureHandler implements AuthenticationFailureHandler { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType(MediaType.APPLICATION_JSON_VALUE); - if (exception instanceof BadCredentialsException) { - mapper.writeValue(response.getWriter(),"아이디 혹은 비밀번호 문제"); + ApiResponse res = UserErrorCode.BAD_CREDENTIAL.getApiResponse(); + String message = exception.getMessage(); + + if (exception instanceof BadCredentialsException || message.startsWith("NOT_FOUND")) { + res = UserErrorCode.USER_NOT_FOUND.getApiResponse(); + } else if (message.startsWith("NOT_AUTHORIZED")) { + res = UserErrorCode.NOT_AUTHORIZED.getApiResponse(); + } else if (message.startsWith("EXIT")) { + res = UserErrorCode.EXIT_USER.getApiResponse(); } - mapper.writeValue(response.getWriter(), "인증 실패"); + response.getWriter().write(mapper.writeValueAsString(res)); } } diff --git a/src/main/java/io/company/localhost/common/security/handler/MemberAuthSuccessHandler.java b/src/main/java/io/company/localhost/common/security/handler/MemberAuthSuccessHandler.java index 8642552..45b3d53 100644 --- a/src/main/java/io/company/localhost/common/security/handler/MemberAuthSuccessHandler.java +++ b/src/main/java/io/company/localhost/common/security/handler/MemberAuthSuccessHandler.java @@ -15,54 +15,75 @@ package io.company.localhost.common.security.handler; import com.fasterxml.jackson.databind.ObjectMapper; +import io.company.localhost.common.dto.ApiResponse; +import io.company.localhost.common.dto.MapDto; +import io.company.localhost.common.security.service.TokenService; +import io.company.localhost.service.NetmemberService; import io.company.localhost.vo.MemberVo; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import lombok.extern.slf4j.Slf4j; + +import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.security.web.WebAttributes; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.stereotype.Component; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; @Slf4j @Component("successHandler") -public class MemberAuthSuccessHandler implements AuthenticationSuccessHandler{ +public class MemberAuthSuccessHandler implements AuthenticationSuccessHandler { - private final RememberMeServices rememberMeServices; + private final TokenService tokenService; + private final NetmemberService netmemberService; - public MemberAuthSuccessHandler(RememberMeServices rememberMeServices) { - this.rememberMeServices = rememberMeServices; - } + public MemberAuthSuccessHandler(@Lazy TokenService tokenService, NetmemberService netmemberService) { + this.tokenService = tokenService; + this.netmemberService = netmemberService; + } - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - - Boolean rememberMe = (Boolean) request.getAttribute("remember"); - if (rememberMe != null && rememberMe) { - rememberMeServices.loginSuccess(request, response, authentication); - } - - MemberVo member = (MemberVo) authentication.getPrincipal(); - response.setStatus(HttpStatus.OK.value()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - - mapper.writeValue(response.getWriter(), member); - - clearAuthenticationAttributes(request); - } - - protected final void clearAuthenticationAttributes(HttpServletRequest request) { - HttpSession session = request.getSession(false); - if (session == null) { + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + + // 로그인 성공한 사용자 가져오기 + Object principal = authentication.getPrincipal(); + if (!(principal instanceof MemberVo member)) { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); return; } - session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); - } + + // 로그인 토큰 생성 + String username = member.getLoginId(); + String loginToken = tokenService.generateToken(username, "login"); + + // DB에 저장 + MapDto map = new MapDto(); + map.put("id", username); + map.put("token", loginToken); + netmemberService.updateMemberToken(map); + + response.setStatus(HttpStatus.OK.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + response.getWriter().write(mapper.writeValueAsString(ApiResponse.ok("Success"))); + + clearAuthenticationAttributes(request); + } + + protected final void clearAuthenticationAttributes(HttpServletRequest request) { + HttpSession session = request.getSession(false); + if (session == null) { + return; + } + session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); + } } diff --git a/src/main/java/io/company/localhost/common/security/service/CustomRememberMeServices.java b/src/main/java/io/company/localhost/common/security/service/CustomRememberMeServices.java index 1d0a57f..8dcc58c 100644 --- a/src/main/java/io/company/localhost/common/security/service/CustomRememberMeServices.java +++ b/src/main/java/io/company/localhost/common/security/service/CustomRememberMeServices.java @@ -10,11 +10,12 @@ * DATE AUTHOR NOTE * ----------------------------------------------------------- * 24.12.06 조인제 최초 생성 - * + * 24.02.03 박지윤 토큰 로직 변경 *************************************************************/ package io.company.localhost.common.security.service; import io.company.localhost.common.security.details.MemberPrincipalDetails; +import io.company.localhost.service.NetmemberService; import io.company.localhost.vo.MemberVo; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; @@ -27,8 +28,6 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.RememberMeServices; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -38,12 +37,13 @@ public class CustomRememberMeServices implements RememberMeServices { private static final String REMEMBER_ME_COOKIE_NAME = "remember-me"; private static final long TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 365; // 1 year private static final String DELIMITER = ":"; + - private final String secretKey; + private final TokenService tokenService; private final UserDetailsService userDetailsService; - public CustomRememberMeServices(String secretKey, UserDetailsService userDetailsService) { - this.secretKey = secretKey; + public CustomRememberMeServices(TokenService tokenService, UserDetailsService userDetailsService) { + this.tokenService = tokenService; this.userDetailsService = userDetailsService; } @@ -55,33 +55,22 @@ public class CustomRememberMeServices implements RememberMeServices { return null; } - String[] tokenParts = decodeAndSplitCookie(rememberMeCookie.getValue()); - if (tokenParts == null || tokenParts.length != 3) { - return null; - } + String token = rememberMeCookie.getValue(); + + if (!tokenService.validateToken(token, "rememberme")) { + return null; + } - String username = tokenParts[0]; - long expiryTime; - try { - expiryTime = Long.parseLong(tokenParts[1]); - } catch (NumberFormatException e) { - return null; - } - - String signature = tokenParts[2]; - if (!isTokenValid(username, expiryTime, signature)) { - return null; - } - - if (System.currentTimeMillis() > expiryTime) { - return null; - } + String[] tokenParts = decodeAndSplitCookie(token); + String username = tokenParts[0]; UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (userDetails != null) { MemberPrincipalDetails memberDetails = (MemberPrincipalDetails) userDetails; MemberVo memberVo = memberDetails.member(); - RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(secretKey, memberVo, userDetails.getAuthorities()); + + String rememberMeSecretKey = tokenService.getRememberMeSecretKey(); + RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(rememberMeSecretKey, memberVo, userDetails.getAuthorities()); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); return auth; @@ -92,16 +81,19 @@ public class CustomRememberMeServices implements RememberMeServices { @Override public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { + Boolean rememberMe = (Boolean) request.getAttribute("remember"); + + if(!rememberMe){ + return; + } + Object principal = successfulAuthentication.getPrincipal(); if (!(principal instanceof MemberVo member)) { return; } String username = member.getLoginId(); - long expiryTime = System.currentTimeMillis() + (TOKEN_VALIDITY_SECONDS * 1000); - String signature = generateSignature(username, expiryTime); - String tokenValue = encodeToken(username, expiryTime, signature); - + String tokenValue = tokenService.generateToken(username, "rememberme"); SecurityContextHolder.getContext().setAuthentication(successfulAuthentication); @@ -142,25 +134,5 @@ public class CustomRememberMeServices implements RememberMeServices { return null; } } - - private String encodeToken(String username, long expiryTime, String signature) { - String tokenValue = username + DELIMITER + expiryTime + DELIMITER + signature; - return Base64.getEncoder().encodeToString(tokenValue.getBytes(StandardCharsets.UTF_8)); - } - - private boolean isTokenValid(String username, long expiryTime, String signature) { - String expectedSignature = generateSignature(username, expiryTime); - return expectedSignature.equals(signature); - } - - private String generateSignature(String username, long expiryTime) { - try { - String data = username + DELIMITER + expiryTime; - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); - return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes(StandardCharsets.UTF_8))); - } catch (Exception e) { - throw new IllegalStateException("Failed to generate signature", e); - } - } + } diff --git a/src/main/java/io/company/localhost/common/security/service/MemberPrincipalDetailService.java b/src/main/java/io/company/localhost/common/security/service/MemberPrincipalDetailService.java index 1239c37..07e1171 100644 --- a/src/main/java/io/company/localhost/common/security/service/MemberPrincipalDetailService.java +++ b/src/main/java/io/company/localhost/common/security/service/MemberPrincipalDetailService.java @@ -21,28 +21,28 @@ import org.springframework.stereotype.Service; import io.company.localhost.common.security.details.MemberPrincipalDetails; import io.company.localhost.vo.MemberVo; -import io.company.localhost.mapper.MemberMapper; +import io.company.localhost.mapper.NetmemberMapper; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class MemberPrincipalDetailService implements UserDetailsService { - private final MemberMapper memberMapper; + private final NetmemberMapper memberMapper; @Override public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException { MemberVo member = memberMapper.findByLoginId(id); - // 없을경우 에러 발생 + //FailHandler 에서 처리 if(member == null) - throw new UsernameNotFoundException(id + "을 찾을 수 없습니다."); + throw new UsernameNotFoundException("NOT_FOUND"); if(!"Y".equals(member.getIsUsed())) - throw new UsernameNotFoundException("사용할 수 없는 계정입니다."); + throw new UsernameNotFoundException("NOT_AUTHORIZED"); if(!"N".equals(member.getIsDel())) - throw new UsernameNotFoundException("삭제된 계정입니다."); + throw new UsernameNotFoundException("EXIT"); // MemberPrincipalDetails 에 Member 객체를 넘겨줌 return new MemberPrincipalDetails(member); diff --git a/src/main/java/io/company/localhost/common/security/service/TokenService.java b/src/main/java/io/company/localhost/common/security/service/TokenService.java new file mode 100644 index 0000000..165b0e8 --- /dev/null +++ b/src/main/java/io/company/localhost/common/security/service/TokenService.java @@ -0,0 +1,121 @@ +/************************************************************ + * + * @packageName : io.company.localhost.common.security.service + * @fileName : TokenService.java + * @author : 박지윤 + * @date : 24.01.24 + * @description : + * + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 24.01.24 박지윤 최초 생성 + *************************************************************/ +package io.company.localhost.common.security.service; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.springframework.stereotype.Service; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class TokenService { + private static final long REMBER_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 365; // 1년 + private static final long LOGIN_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 365; // 하루 + private static final String DELIMITER = ":"; + private final String rememberMeSecretKey; + private final String loginSecretKey; + + public TokenService(String rememberMeSecretKey, String loginSecretKey) { + this.rememberMeSecretKey = rememberMeSecretKey; + this.loginSecretKey = loginSecretKey; + } + + public String generateToken(String username, String tokenType) { + long expiryTime; + + // 토큰타입에 따라 만료기간 다르게 설정 + if ("rememberme".equals(tokenType)) { + expiryTime = System.currentTimeMillis() + (REMBER_TOKEN_VALIDITY_SECONDS * 1000); + } else if ("login".equals(tokenType)) { + expiryTime = System.currentTimeMillis() + (LOGIN_TOKEN_VALIDITY_SECONDS * 1000); + } else { + throw new IllegalArgumentException("Invalid token type"); + } + + String signature = generateSignature(username, expiryTime, tokenType); + return encodeToken(username, expiryTime, signature); + } + + public boolean validateToken(String token, String tokenType) { + try { + String[] tokenParts = decodeAndSplitToken(token); + if (tokenParts == null || tokenParts.length != 3) { + return false; + } + + String username = tokenParts[0]; + long expiryTime; + try { + expiryTime = Long.parseLong(tokenParts[1]); + } catch (NumberFormatException e) { + return false; + } + + String signature = tokenParts[2]; + if (!isTokenValid(username, expiryTime, signature, tokenType)) { + return false; + } + + return System.currentTimeMillis() <= expiryTime; + } catch (Exception e) { + return false; + } + } + + + private String generateSignature(String username, long expiryTime, String tokenType) { + try { + // tokenType에 따라 login 또는 remember secretKey 사용 + String secretKeyToUse = "rememberme".equals(tokenType) ? rememberMeSecretKey : loginSecretKey; + + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(secretKeyToUse.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); + String data = username + DELIMITER + expiryTime; + return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes(StandardCharsets.UTF_8))); + } catch (Exception e) { + throw new IllegalStateException("Failed to generate signature", e); + } + } + + + private String encodeToken(String username, long expiryTime, String signature) { + String tokenValue = username + DELIMITER + expiryTime + DELIMITER + signature; + return Base64.getEncoder().encodeToString(tokenValue.getBytes(StandardCharsets.UTF_8)); + } + + private String[] decodeAndSplitToken(String token) { + try { + String decodedValue = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8); + return decodedValue.split(DELIMITER); + } catch (Exception e) { + return null; + } + } + + private boolean isTokenValid(String username, long expiryTime, String signature, String tokenType) { + String expectedSignature = generateSignature(username, expiryTime, tokenType); + return expectedSignature.equals(signature); + } + + public String getRememberMeSecretKey() { + return rememberMeSecretKey; + } + +} \ No newline at end of file diff --git a/src/main/java/io/company/localhost/controller/api/BoardController.java b/src/main/java/io/company/localhost/controller/api/BoardController.java index 4b1888d..b306e51 100644 --- a/src/main/java/io/company/localhost/controller/api/BoardController.java +++ b/src/main/java/io/company/localhost/controller/api/BoardController.java @@ -1,25 +1,35 @@ +/************************************************************ + * + * @packageName : io.company.localhost.controller.api + * @fileName : BoardController.java + * @author : 서지희 + * @date : 25.01.07 + * @description : 게시판 + * + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 24.01.02 서지희 최초 생성 + * + *************************************************************/ + package io.company.localhost.controller.api; -import java.nio.charset.StandardCharsets; -import java.sql.Blob; +import java.math.BigInteger; import java.util.List; -import java.util.Map; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import com.github.pagehelper.PageInfo; + +import io.company.localhost.common.annotation.Member; +import io.company.localhost.common.annotation.ParameterCheck; import io.company.localhost.common.annotation.ReqMap; import io.company.localhost.common.dto.ApiResponse; import io.company.localhost.common.dto.MapDto; -import io.company.localhost.service.LocalBordService; +import io.company.localhost.service.commoncodService; +import io.company.localhost.service.localbordService; +import io.company.localhost.utils.AuthUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,108 +38,209 @@ import lombok.extern.slf4j.Slf4j; @RequiredArgsConstructor @Slf4j public class BoardController { - private final LocalBordService boardService; - - //공지사항 + + private final localbordService boardService; + private final commoncodService commoncodService; + + /** + * 공지사항 목록 조회 + * @ReqMap map 요청 파라미터 (searchKeyword) + * @return 전체 공지사항 목록 + */ + @Member + @ParameterCheck @GetMapping("/notices") - public ApiResponse>> getNotices() { - return ApiResponse.ok(boardService.getNotices()); + public ApiResponse> getNotices(@ReqMap MapDto map) { + return ApiResponse.ok(boardService.getNotices(map)); } - //비밀,자유게시판 + + /** + * 자유/익명 게시판 목록 조회 + * @ReqMap map 요청 파라미터 (page, searchKeyword, orderBy, size) + * @return 페이징된 자유/익명 게시판 목록 + */ + @Member + @ParameterCheck @GetMapping("/general") - public ApiResponse>> getGeneralPosts() { - List> posts = boardService.getGeneralPosts(); - for (Map post : posts) { - Object content = post.get("content"); - if (content instanceof Blob) { - Blob blob = (Blob) content; - try { - post.put("content", new String(blob.getBytes(1, (int) blob.length()), StandardCharsets.UTF_8)); - } catch (Exception e) { - post.put("content", ""); // 변환 실패 시 기본 값 설정 - } - } - } - System.out.println(posts); - return ApiResponse.ok(posts); + public ApiResponse> getGeneralPosts(@ReqMap MapDto map) { + return ApiResponse.ok(boardService.getGeneralPosts(map)); } - //게시물 작성 + + /** + * 게시물 작성 + * @ReqMap map 요청 파라미터 (LOCBRDTTL, LOCBRDCON, MEMBERSEQ, LOCBRDTYP, + * LOCBRDPWD(익명일 때만), LOCBRDCAT(지식커뮤니티만)) + * @return 작성된 게시물의 ID + */ + @Member + @ParameterCheck @PostMapping - public ApiResponse createBoard(@ReqMap MapDto map) { - boardService.createBoard(map); - return ApiResponse.ok("게시물이 작성되었습니다."); + public ApiResponse createBoard(@ReqMap MapDto map) { + Long userId = AuthUtil.getUser().getId(); + map.put("MEMBERSEQ", userId); + return ApiResponse.ok(boardService.createBoard(map)); } - // 첨부파일 추가 - @PostMapping("/{boardId}/attachments") - public ApiResponse uploadAttachment(@PathVariable Long boardId, @ReqMap MapDto map) { - map.put("LOCBRDSEQ", boardId); - log.info("Uploading attachment for board ID: {}", boardId); - boardService.addAttachment(map); - return ApiResponse.ok("첨부파일이 저장되었습니다."); + + /** + * 게시물 상세보기 + * @param boardId 게시물 ID + * @return 게시물 상세정보 + */ + @Member + @ParameterCheck + @GetMapping("/{boardId}") + public ApiResponse getBoardDetail(@PathVariable("boardId") Long boardId) { + return ApiResponse.ok(boardService.getBoardDetail(boardId)); } - // 게시물 삭제 + + /** + * 게시물 삭제 + * @param boardId 게시물 ID + * @return 삭제 결과 메시지 + */ + @Member + @ParameterCheck @DeleteMapping("/{boardId}") - public ApiResponse deleteBoard(@PathVariable Long boardId, @ReqMap MapDto map) { + public ApiResponse deleteBoard(@PathVariable("boardId") Long boardId, @ReqMap MapDto map) { map.put("LOCBRDSEQ", boardId); - log.info("Deleting board with ID: {}", boardId); boardService.deleteBoard(map); return ApiResponse.ok("게시물이 삭제되었습니다."); } - //게시물 수정 - @PutMapping - public ApiResponse updateBoard(@ReqMap MapDto map) { + + /** + * 게시물 수정 + * @param boardId 게시물 ID + * @ReqMap map 수정 데이터 (LOCBRDTTL, LOCBRDCON) + * @return 수정 결과 메시지 + */ + @Member + @ParameterCheck + @PutMapping("/{boardId}") + public ApiResponse updateBoard(@PathVariable("boardId") Long boardId, @ReqMap MapDto map) { + map.put("LOCBRDSEQ", boardId); boardService.updateBoard(map); return ApiResponse.ok("게시물이 수정되었습니다."); } - //게시물과 댓글에 좋아요/싫어요 추가 - @PostMapping("/{boardId}/reaction") - public ApiResponse reactToBoard(@PathVariable Long boardId, @ReqMap MapDto map) { - map.put("LOCBRDSEQ", boardId); - boardService.reactToBoard(map); - return ApiResponse.ok("반응이 추가되었습니다."); + + /** + * 첨부파일 추가 + * @ReqMap map 요청 파라미터 (CMNFLEREG, CMNFLESIZ, CMNFLEEXT, CMNFLEORG, CMNFLENAM, CMNFLEPAT, CMNBRDSEQ) + * @return 첨부파일 저장 결과 메시지 + */ + @Member + @ParameterCheck + @PostMapping("/{CMNBRDSEQ}/attachments") + public ApiResponse uploadAttachment(@ReqMap MapDto map) { + Long userId = AuthUtil.getUser().getId(); + map.put("CMNFLEREG", userId); + boardService.addAttachment(map); + return ApiResponse.ok("첨부파일이 저장되었습니다."); } - //댓글/대댓글 조회 + + /** + * 게시물, 댓글 좋아요/싫어요 추가 + * @ReqMap map 데이터 (LOCCMTSEQ, MEMBERSEQ, LOCGOBGOD, LOCGOBBAD, LOCBRDSEQ) + * @return 반응 추가 결과 메시지 + */ + @Member + @ParameterCheck + @PostMapping("/{LOCBRDSEQ}/{LOCCMTSEQ}/reaction") + public ApiResponse reactToBoard(@ReqMap MapDto map) { + Long userId = AuthUtil.getUser().getId(); + map.put("MEMBERSEQ", userId); + boardService.reactToBoard(map); + return ApiResponse.ok("반응이 성공적으로 처리되었습니다."); + } + + /** + * 댓글/대댓글 조회 + * @param boardId 게시물 ID + * @return 댓글과 대댓글의 계층 구조 데이터 + */ + @Member + @ParameterCheck @GetMapping("/{boardId}/comments") - public ApiResponse>> getComments(@PathVariable int boardId) { + public ApiResponse> getComments(@PathVariable("boardId") int boardId) { return ApiResponse.ok(boardService.getComments(boardId)); } - //댓글/대댓글 작성 - @PostMapping("/{boardId}/comment") - public ApiResponse addCommentOrReply(@PathVariable int boardId, @ReqMap MapDto map) { - map.put("LOCBRDSEQ", boardId); + + /** + * 댓글/대댓글 작성 + * @param boardId 게시물 ID + * @ReqMap map 댓글 데이터 (LOCBRDSEQ, LOCCMTRPY, LOCCMTPNT, LOCCMTPWD, MEMBERSEQ 등) + * @return 작성 결과 메시지 + */ + @Member + @ParameterCheck + @PostMapping("/{LOCBRDSEQ}/comment") + public ApiResponse addCommentOrReply(@ReqMap MapDto map) { + Long userId = AuthUtil.getUser().getId(); + map.put("MEMBERSEQ", userId); boardService.addCommentOrReply(map); return ApiResponse.ok("댓글 또는 대댓글이 작성되었습니다."); } - //댓글/대댓글 수정 + + /** + * 댓글/대댓글 수정 + * @param commentId 댓글 ID + * @ReqMap map 수정 데이터 (LOCCMTSEQ, LOCCMTRPY) + * @return 수정 결과 메시지 + */ + @Member + @ParameterCheck @PutMapping("/comment/{commentId}") - public ApiResponse updateComment(@PathVariable int commentId, @ReqMap MapDto map) { - map.put("LOCCMTSEQ", commentId); + public ApiResponse updateComment(@PathVariable("commentId") int commentId, @ReqMap MapDto map) { boardService.updateComment(map); return ApiResponse.ok("댓글이 수정되었습니다."); } - //댓글/대댓글 삭제 + + /** + * 댓글/대댓글 삭제 + * @param commentId 댓글 ID + * @ReqMap map 삭제 데이터 + * @return 삭제 결과 메시지 + */ + @Member + @ParameterCheck @DeleteMapping("/comment/{commentId}") - public ApiResponse deleteComment(@PathVariable int commentId, @ReqMap MapDto map) { - map.put("LOCCMTSEQ", commentId); + public ApiResponse deleteComment(@PathVariable("commentId") int commentId, @ReqMap MapDto map) { boardService.deleteComment(map); return ApiResponse.ok("댓글이 삭제되었습니다."); } - //비밀번호 확인 (게시물) + + /** + * 댓글 비밀번호 확인 + * @param commentId 댓글 ID + * @ReqMap map 비밀번호 데이터 + * @return 비밀번호 확인 결과 + */ + @Member + @ParameterCheck @PostMapping("/comment/{commentId}/password") - public ApiResponse checkCommentPassword(@PathVariable int commentId, @ReqMap MapDto map) { - map.put("LOCCMTSEQ", commentId); - return ApiResponse.ok(boardService.checkCommentPassword(map)); + public ApiResponse checkCommentPassword(@PathVariable("commentId") int commentId, @ReqMap MapDto map) { + return ApiResponse.ok(boardService.getCommentPassword(commentId).equals(map.getString("LOCCMTPWD"))); } - //비밀번호 확인 (댓글) + + /** + * 게시물 비밀번호 확인 + * @param boardId 게시물 ID + * @ReqMap map 비밀번호 데이터 + * @return 비밀번호 확인 결과 + */ + @Member + @ParameterCheck @PostMapping("/{boardId}/password") - public ApiResponse checkBoardPassword(@PathVariable int boardId, @ReqMap MapDto map) { - map.put("LOCBRDSEQ", boardId); - return ApiResponse.ok(boardService.checkBoardPassword(map)); + public ApiResponse checkBoardPassword(@PathVariable("boardId") int boardId, @ReqMap MapDto map) { + return ApiResponse.ok(boardService.getBoardPassword(boardId).equals(map.getString("LOCBRDPWD"))); } - // 비밀게시판 여부 확인 - @GetMapping("/{boardId}/isSecret") - public ApiResponse isSecretBoard(@PathVariable Long boardId) { - log.info("Checking if board ID {} is secret", boardId); - return ApiResponse.ok(boardService.isSecretBoard(boardId)); + + /** + * 카테고리 목록 조회 + * @return 카테고리 리스트 + */ + @GetMapping("/categories") + public ApiResponse> getCategories() { + List categories = commoncodService.getCategoryList(); + return ApiResponse.ok(categories); } -} +} \ No newline at end of file diff --git a/src/main/java/io/company/localhost/controller/api/VacationController.java b/src/main/java/io/company/localhost/controller/api/VacationController.java new file mode 100644 index 0000000..e8d110a --- /dev/null +++ b/src/main/java/io/company/localhost/controller/api/VacationController.java @@ -0,0 +1,57 @@ +package io.company.localhost.controller.api; +import java.util.List; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.company.localhost.common.annotation.ReqMap; +import io.company.localhost.common.dto.ApiResponse; +import io.company.localhost.common.dto.MapDto; +import io.company.localhost.service.localvacaService; // 서비스 클래스 경로 수정 +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/vacation") +public class VacationController { + + private final localvacaService localVacaService; + + @PostMapping + public ApiResponse saveVacations(@RequestBody List map) { + + for (MapDto request : map) { + // 각 요청 데이터의 필수 값 검증 + Integer employeeId = (Integer) request.get("employeeId"); + String date = request.getString("date"); + String type = request.getString("type"); + + if (employeeId == null || date == null || type == null) { + throw new IllegalArgumentException("요청 데이터에 누락된 값이 있습니다: " + request); + } + + // 데이터 저장 + localVacaService.insertVacation(request); + } + + // 성공적으로 저장된 경우 응답 반환 + return ApiResponse.ok("모든 휴가가 성공적으로 저장되었습니다."); + } + + /** + * 휴가 정보를 조회하여 프론트엔드로 전달 + */ + @GetMapping("/list") + public ApiResponse> getVacationList(@ReqMap MapDto map) { + + // 서비스 호출을 통해 데이터 조회 + List vacationList = localVacaService.getVacationList(map); + + return ApiResponse.ok(vacationList); + } +} diff --git a/src/main/java/io/company/localhost/controller/common/worddictController.java b/src/main/java/io/company/localhost/controller/api/worddictController.java similarity index 81% rename from src/main/java/io/company/localhost/controller/common/worddictController.java rename to src/main/java/io/company/localhost/controller/api/worddictController.java index 25e84f3..55159bf 100644 --- a/src/main/java/io/company/localhost/controller/common/worddictController.java +++ b/src/main/java/io/company/localhost/controller/api/worddictController.java @@ -1,6 +1,6 @@ /************************************************************ * - * @packageName : io.company.localhost.controller.common + * @packageName : io.company.localhost.controller.api * @fileName : worddictController.java * @author : 공현지 * @date : 25.01.07 @@ -12,7 +12,7 @@ * 24.12.06 공현지 최초 생성 * *************************************************************/ -package io.company.localhost.controller.common; +package io.company.localhost.controller.api; import java.util.List; @@ -23,8 +23,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.github.pagehelper.PageInfo; - import io.company.localhost.common.annotation.Member; import io.company.localhost.common.annotation.ParameterCheck; import io.company.localhost.common.annotation.ReqMap; @@ -54,10 +52,18 @@ public class worddictController { @Member @ParameterCheck @GetMapping("getWordList") - public ApiResponse> getWordList(@ReqMap MapDto map) { - PageInfo WordList = worddictyservice.getWordList(map); - return ApiResponse.ok(WordList); - } + public ApiResponse getWordList(@ReqMap MapDto map) { + + int total = worddictyservice.getTotal(map); + List wordList = worddictyservice.getWordList(map); + + MapDto OutData = new MapDto(); + OutData.put("total", total); + OutData.put("data", wordList); + + return ApiResponse.ok(OutData); + } + /** * 용어집 카테고리 목록 * @param @@ -70,6 +76,17 @@ public class worddictController { List WordCategoryList = commoncodservice.getWordCategory(); return ApiResponse.ok(WordCategoryList); } + /** + * 용어집 상세 조회 + * @param WRDDICSEQ 용어 번호 + * @return + */ + @Member + @ParameterCheck + @GetMapping("getWordDetail") + public ApiResponse getWordDetail(@ReqMap MapDto map) { + return ApiResponse.ok( worddictyservice.getWordDetail(map)); + } /** * 용어집 카테고리 등록 * @param CMNCODNAM 용어집 등록 카테고리 이름 @@ -84,11 +101,10 @@ public class worddictController { } /** * 용어 등록 - * @param WRDDICCAT 카테고리,WRDDICTTL 용어,WRDDICCON 내용 ,WRDDICRIK 링크 + * @param WRDDICCAT 카테고리 코드값 ,WRDDICTTL 용어,WRDDICCON 내용 ,WRDDICRIK 링크 * @return */ @Member - @ParameterCheck @PostMapping("insertWord") public ApiResponse insertWord(@AuthenticationPrincipal MemberVo memberVo,@ReqMap MapDto map) { @@ -96,7 +112,7 @@ public class worddictController { Long userId = AuthUtil.getUser().getId(); map.put("userId", userId); Long result = worddictyservice.insertWord(map); - + return ApiResponse.ok(result); } /** @@ -113,9 +129,7 @@ public class worddictController { Long userId = AuthUtil.getUser().getId(); map.put("userId", userId); Long result = worddictyservice.updateWord(map); - + return ApiResponse.ok(result); } - - } diff --git a/src/main/java/io/company/localhost/controller/common/ImageUploadController.java b/src/main/java/io/company/localhost/controller/common/ImageUploadController.java new file mode 100644 index 0000000..b4002a3 --- /dev/null +++ b/src/main/java/io/company/localhost/controller/common/ImageUploadController.java @@ -0,0 +1,71 @@ +/************************************************************ + * + * @packageName : io.company.localhost.controller.common + * @fileName : ImageUploadController.java + * @author : 공현지 + * @date : 25.01.16 + * @description : + * + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 25.01.16 공현지 최초 생성 + * + *************************************************************/ +package io.company.localhost.controller.common; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import io.company.localhost.common.annotation.ParameterCheck; +import io.company.localhost.common.annotation.ReqMap; +import io.company.localhost.common.dto.ApiResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequestMapping("/api/quilleditor") +@RequiredArgsConstructor +public class ImageUploadController { + + @Value("${filePath.boardfile}") + private String boardFilePath; + + /** + * quilleditor 안에서 삽입된 이미지를 서버에 저장하는 메소드 + * @form-data 서버에 저장된 이미지 경로와 이름 + * @return + */ + @ParameterCheck + @PostMapping("/upload") + public ApiResponse uploadImage(@ReqMap MultipartFile file) throws IOException { + + if (file.isEmpty()) { + return ApiResponse.error(HttpStatus.BAD_REQUEST, "File is empty"); + } + String originalFileName = file.getOriginalFilename(); + String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".")); + String fileName = UUID.randomUUID().toString() + fileExtension; + Path filePath = Paths.get(boardFilePath, fileName); + + Files.createDirectories(filePath.getParent()); + Files.write(filePath, file.getBytes()); + + String fileUrl = "upload/img/board/" + fileName; + + return ApiResponse.ok(fileUrl); + } + +} diff --git a/src/main/java/io/company/localhost/controller/common/UserController.java b/src/main/java/io/company/localhost/controller/common/UserController.java index 9c526dd..bde060a 100644 --- a/src/main/java/io/company/localhost/controller/common/UserController.java +++ b/src/main/java/io/company/localhost/controller/common/UserController.java @@ -10,6 +10,7 @@ * DATE AUTHOR NOTE * ----------------------------------------------------------- * 24.12.06 조인제 최초 생성 + * 24.01.17 박지윤 Register 합침 * *************************************************************/ package io.company.localhost.controller.common; @@ -17,7 +18,12 @@ package io.company.localhost.controller.common; import io.company.localhost.common.annotation.Admin; import io.company.localhost.common.annotation.Guest; import io.company.localhost.common.annotation.Member; +import io.company.localhost.common.annotation.ParameterCheck; +import io.company.localhost.common.annotation.ReqMap; import io.company.localhost.common.dto.ApiResponse; +import io.company.localhost.common.dto.MapDto; +import io.company.localhost.service.NetmemberService; +import io.company.localhost.service.commoncodService; import io.company.localhost.utils.AuthUtil; import io.company.localhost.utils.SessionListener; import io.company.localhost.vo.MemberVo; @@ -35,10 +41,15 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY; @@ -49,8 +60,113 @@ import static org.springframework.security.web.authentication.rememberme.Abstrac @RequiredArgsConstructor public class UserController { + private final commoncodService commoncodservice; + private final NetmemberService netmemberservice; - //security 인증 체크 + /** + * 사용 가능 색상 조회 + * + * @return ApiResponse> + * + */ + @ParameterCheck + @GetMapping("/color") + public ApiResponse> getColorList() { + List ColorList = commoncodservice.getColorList(); + return ApiResponse.ok(ColorList); + } + + /** + * MBTI 목록 조회 + * + * @return ApiResponse> + * + */ + @ParameterCheck + @GetMapping("/mbti") + public ApiResponse> getMbtiList() { + List MbtiList = commoncodservice.getMbtiList(); + return ApiResponse.ok(MbtiList); + } + + /** + * 비밀번호 힌트 목록 조회 + * + * @return ApiResponse> + * + */ + @ParameterCheck + @GetMapping("/pwhint") + public ApiResponse> getPwhintList() { + List PwhintList = commoncodservice.getPwhintList(); + return ApiResponse.ok(PwhintList); + } + + /** + * 회원가입 + * + * @param profile + * @param map + * @return ApiResponse + * @throws RuntimeException 파일 업로드 실패 시 + */ + @PostMapping("/join") + public ApiResponse register(@RequestParam("memberPrf") MultipartFile memberPrf, @ReqMap MapDto map) { + int member = netmemberservice.register(memberPrf, map); + return ApiResponse.ok(member); + } + + /** + * 아이디 중복 체크 + * + * @param memberIds + * @return ApiResponse + * + */ + @GetMapping("/checkId") + public ApiResponse selectCheckId(@RequestParam String memberIds) { + boolean isDuplicate = netmemberservice.selectCheckId(memberIds); + return ApiResponse.ok(!isDuplicate); + } + + /** + * 로그인 여부 체크 + * + * @return ApiResponse + */ + @GetMapping("/isLogin") + public ApiResponse checkLogin() { + boolean isLoggedIn = AuthUtil.isLoggedIn(); + return ApiResponse.ok(isLoggedIn); + } + + /** + * 비밀번호 재설정 member 체크 + * + * @param map + * @return ApiResponse + * + */ + @PostMapping("/pwReset") + public ApiResponse selectPwReset(@ReqMap MapDto map) { + boolean isPwReset = netmemberservice.selectPwReset(map); + return ApiResponse.ok(isPwReset); + } + + /** + * 비밀번호 재설정 + * + * @param map + * @return ApiResponse + * + */ + @PatchMapping("/pwNew") + public ApiResponse updatePassword(@ReqMap MapDto map) { + boolean isPwNew = netmemberservice.updatePassword(map); + return ApiResponse.ok(isPwNew); + } + + // security 인증 체크 @GetMapping("userInfo") public ApiResponse getUserInfo(@AuthenticationPrincipal MemberVo memberVo) { SecurityContextHolderStrategy contextHolderStrategy = SecurityContextHolder.getContextHolderStrategy(); @@ -67,9 +183,9 @@ public class UserController { return ApiResponse.ok(memberVo); } - //유저 세션 체크 + // 유저 세션 체크 @GetMapping(value = "check") - public ApiResponse check(){ + public ApiResponse check() { Map sessions = SessionListener.getSessions(); Map sessionData = new HashMap<>(); @@ -106,10 +222,7 @@ public class UserController { return ApiResponse.ok(remember); } - - - - //로그아웃 + // 로그아웃 @Guest @GetMapping("/logout") public ApiResponse logout(HttpServletRequest request, HttpServletResponse response) { @@ -133,6 +246,23 @@ public class UserController { return ApiResponse.ok(returnMessage); } + /** + * 사원 목록 전체 조회 + * + * + * + */ + @ParameterCheck + @GetMapping("/allUserList") + public ApiResponse getallUserList() { + List allUserList = netmemberservice.getallUserList(); + MemberVo user = AuthUtil.getUser(); + + MapDto outData = new MapDto(); + outData.put("allUserList", allUserList); + outData.put("user", user); + return ApiResponse.ok(outData); + } @Guest @GetMapping("get1") diff --git a/src/main/java/io/company/localhost/mapper/LocalBordMapper.java b/src/main/java/io/company/localhost/mapper/LocalBordMapper.java deleted file mode 100644 index d86c9a3..0000000 --- a/src/main/java/io/company/localhost/mapper/LocalBordMapper.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.company.localhost.mapper; - -import java.util.List; -import java.util.Map; - -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Param; - -import io.company.localhost.common.dto.MapDto; - -@Mapper -public interface LocalBordMapper { - // 공지사항 조회 - List> getNotices(); - - // 자유/비밀 게시판 조회 - List> getGeneralPosts(); - - // 게시물 작성 - void createBoard(MapDto map); - - // 첨부파일 저장 - void addAttachment(MapDto map); - - // 게시물 삭제 - void deleteBoard(MapDto map); - - // 게시물 수정 - void updateBoard(MapDto map); - - // 게시물 좋아요/싫어요 추가 - void reactToBoard(MapDto map); - - // 댓글 조회 - List> getComments(int boardSeq); - - // 댓글/대댓글 작성 - void addCommentOrReply(MapDto map); - - // 댓글/대댓글 수정 - void updateComment(MapDto map); - - // 댓글/대댓글 삭제 - void deleteComment(MapDto map); - - // 게시물 비밀번호 확인 - boolean checkBoardPassword(MapDto map); - - // 댓글 비밀번호 확인 - boolean checkCommentPassword(MapDto map); - - // 비밀 게시판 여부 확인 - boolean isSecretBoard(Long boardId); -} - diff --git a/src/main/java/io/company/localhost/mapper/MemberMapper.java b/src/main/java/io/company/localhost/mapper/NetmemberMapper.java similarity index 65% rename from src/main/java/io/company/localhost/mapper/MemberMapper.java rename to src/main/java/io/company/localhost/mapper/NetmemberMapper.java index f74c56e..4849b6e 100644 --- a/src/main/java/io/company/localhost/mapper/MemberMapper.java +++ b/src/main/java/io/company/localhost/mapper/NetmemberMapper.java @@ -14,12 +14,30 @@ *************************************************************/ package io.company.localhost.mapper; +import java.util.List; + import org.apache.ibatis.annotations.Mapper; +import io.company.localhost.common.dto.MapDto; import io.company.localhost.vo.MemberVo; @Mapper -public interface MemberMapper { +public interface NetmemberMapper { MemberVo findByLoginId(String id); + + int updateMemberToken(MapDto map); + + int insertMember(MapDto map); + + int selectCheckId(String memberIds); + + int selectPwReset(MapDto map); + + String selectPassword(String id); + + int updatePassword(MapDto map); + + List getallUserList(); + } diff --git a/src/main/java/io/company/localhost/mapper/commoncodMapper.java b/src/main/java/io/company/localhost/mapper/commoncodMapper.java index 9d560b8..b0a8241 100644 --- a/src/main/java/io/company/localhost/mapper/commoncodMapper.java +++ b/src/main/java/io/company/localhost/mapper/commoncodMapper.java @@ -26,5 +26,14 @@ public interface commoncodMapper { List getWordCategory(); Long insertCategory(MapDto map); - + + List getColorList(); + + List getMbtiList(); + + List getPwhintList(); + + int updateColorYon(String color); + + List getCategories(); } diff --git a/src/main/java/io/company/localhost/mapper/localbordMapper.java b/src/main/java/io/company/localhost/mapper/localbordMapper.java new file mode 100644 index 0000000..7190ee7 --- /dev/null +++ b/src/main/java/io/company/localhost/mapper/localbordMapper.java @@ -0,0 +1,98 @@ +package io.company.localhost.mapper; + +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import io.company.localhost.common.dto.MapDto; + +@Mapper +public interface localbordMapper { + // 공지사항 조회 + List getNotices(MapDto map); + + // 자유/비밀 게시판 조회 + List getGeneralPosts(MapDto map); + + // 조회수 증가 + void incrementViewCount(Long boardId); + + // 게시물 작성 + void createBoard(MapDto map); + + // 첨부파일 저장 + void addAttachment(MapDto map); + + // 게시물 삭제 + void deleteBoard(MapDto map); + + // 게시물 삭제시 댓글/대댓글 삭제 + void deleteCommentsByBoardId(MapDto map); + + // 게시물 수정 + void updateBoard(MapDto map); + + // 기존 반응 조회 + MapDto findReaction(MapDto map); + + // 새 반응 삽입 + void insertReaction(MapDto map); + + // 기존 반응 업데이트 + void updateReaction(MapDto map); + + // 댓글 조회 + List getComments(int boardSeq); + + // 댓글/대댓글 작성 + void addCommentOrReply(MapDto map); + + // 댓글/대댓글 수정 + void updateComment(MapDto map); + + // 대댓글인지 확인 + boolean isReply(MapDto map); + + // 댓글에 대댓글이 있는지 확인 + boolean hasReplies(MapDto map); + + // 댓글 내용만 삭제 처리 (대댓글 유지) + void softDeleteComment(MapDto map); + + // 댓글 삭제 (대댓글 없음) + void deleteComment(MapDto map); + + // 대댓글 삭제 + void deleteReply(MapDto map); + + // 게시물 비밀번호 조회 + String selectCommentPassword(int commentId); + + // 댓글 비밀번호 조회 + String selectBoardPassword(int boardId); + + // 게시물 상세보기 + MapDto selectBoardDetail(Long boardId); + + // 댓글 갯수 + int countComments(Long boardId); + + // 첨부파일 유무 + int countAttachments(Long boardId); + + // 게시물 좋아요/싫어요 개수 + MapDto getBoardReactions(Long boardId); + + // 댓글 좋아요/싫어요 개수 + List getCommentReactions(Long boardId); + + // 첨부파일 가져오기 + List selectAttachments(Long boardId); + + //댓글id 확인 + MapDto getCommentById(int commentId); + +} + diff --git a/src/main/java/io/company/localhost/mapper/localvacaMapper.java b/src/main/java/io/company/localhost/mapper/localvacaMapper.java new file mode 100644 index 0000000..21bb469 --- /dev/null +++ b/src/main/java/io/company/localhost/mapper/localvacaMapper.java @@ -0,0 +1,19 @@ +package io.company.localhost.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import io.company.localhost.common.dto.MapDto; + + +@Mapper +public interface localvacaMapper { + void insertVacation(MapDto map); + + List findVacations(MapDto map); +} + + + diff --git a/src/main/java/io/company/localhost/mapper/worddictyMapper.java b/src/main/java/io/company/localhost/mapper/worddictyMapper.java index f373ebf..369be30 100644 --- a/src/main/java/io/company/localhost/mapper/worddictyMapper.java +++ b/src/main/java/io/company/localhost/mapper/worddictyMapper.java @@ -29,6 +29,10 @@ public interface worddictyMapper { Long updateWord(MapDto map); + MapDto getWordDetail(MapDto map); + + int getTotal(MapDto map); + diff --git a/src/main/java/io/company/localhost/service/FileService.java b/src/main/java/io/company/localhost/service/FileService.java new file mode 100644 index 0000000..13a7fdc --- /dev/null +++ b/src/main/java/io/company/localhost/service/FileService.java @@ -0,0 +1,68 @@ +/************************************************************ + * + * @packageName : io.company.localhost.FileService + * @fileName : FileService.java + * @author : 박지윤 + * @date : 25.01.17 + * @description : + * + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 24.01.17 박지윤 최초 생성 + * + *************************************************************/ +package io.company.localhost.service; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.UUID; +import org.apache.commons.io.FilenameUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class FileService { + + + @Value("${filePath.profile}") + private String uploadPath; + + /** + * 파일 업로드 + * + * @param file + * @return + * @throws RuntimeException + */ + public String uploadFile(MultipartFile file) { + try { + // 원본 파일명 + String originalFilename = file.getOriginalFilename(); + // 파일 확장자 + String extension = FilenameUtils.getExtension(originalFilename); + // UUID를 사용하여 고유한 파일명 생성 + String newFilename = UUID.randomUUID().toString() + "." + extension; + + // 최종 저장 경로 생성 (기본경로 + 파일명) + Path targetPath = Paths.get(uploadPath, newFilename); + // 저장될 디렉토리가 없는 경우 생성 + Files.createDirectories(targetPath.getParent()); + + // 동일 파일명이 있을 경우 덮어쓰기 + Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING); + + // 저장된 파일의 상대 경로 반환 + return newFilename; + + } catch (IOException e) { + throw new RuntimeException("파일 업로드 실패: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/io/company/localhost/service/LocalBordService.java b/src/main/java/io/company/localhost/service/LocalBordService.java deleted file mode 100644 index 29490f0..0000000 --- a/src/main/java/io/company/localhost/service/LocalBordService.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.company.localhost.service; - -import java.util.List; -import java.util.Map; - -import org.springframework.stereotype.Service; - -import io.company.localhost.common.dto.MapDto; -import io.company.localhost.mapper.LocalBordMapper; -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -public class LocalBordService { - private final LocalBordMapper boardMapper; - - public List> getNotices() { - return boardMapper.getNotices(); - } - - public List> getGeneralPosts() { - return boardMapper.getGeneralPosts(); - } - - public void createBoard(MapDto map) { - boardMapper.createBoard(map); - } - - public void addAttachment(MapDto map) { - boardMapper.addAttachment(map); - } - - public void deleteBoard(MapDto map) { - boardMapper.deleteBoard(map); - } - - public void updateBoard(MapDto map) { - boardMapper.updateBoard(map); - } - - public void reactToBoard(MapDto map) { - boardMapper.reactToBoard(map); - } - - public List> getComments(int boardSeq) { - return boardMapper.getComments(boardSeq); - } - - public void addCommentOrReply(MapDto map) { - boardMapper.addCommentOrReply(map); - } - - public void updateComment(MapDto map) { - boardMapper.updateComment(map); - } - - public void deleteComment(MapDto map) { - boardMapper.deleteComment(map); - } - - public boolean checkBoardPassword(MapDto map) { - return boardMapper.checkBoardPassword(map); - } - - public boolean checkCommentPassword(MapDto map) { - return boardMapper.checkCommentPassword(map); - } - - - public boolean isSecretBoard(Long boardId) { - return boardMapper.isSecretBoard(boardId); - } - -} - - diff --git a/src/main/java/io/company/localhost/service/NetmemberService.java b/src/main/java/io/company/localhost/service/NetmemberService.java new file mode 100644 index 0000000..cbbb701 --- /dev/null +++ b/src/main/java/io/company/localhost/service/NetmemberService.java @@ -0,0 +1,140 @@ +/************************************************************ + * + * @packageName : io.company.localhost.RegisterService + * @fileName : RegisterService.java + * @author : 박지윤 + * @date : 25.01.17 + * @description : + * + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 24.01.17 박지윤 최초 생성 + * + *************************************************************/ +package io.company.localhost.service; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import io.company.localhost.common.dto.MapDto; +import io.company.localhost.mapper.NetmemberMapper; +import io.company.localhost.mapper.commoncodMapper; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class NetmemberService { + private final NetmemberMapper memberMapper; + private final commoncodMapper commoncodMapper; + private final DelegatingPasswordEncoder passwordEncoder; + private final FileService fileService; + + /** + * 회원 가입 + * + * @param profile + * @param map + * @return + */ + public int register(MultipartFile memberPrf, MapDto map) { + // 프로필 이미지 저장, 저장된 경로 가져옴 + String profilePath = fileService.uploadFile(memberPrf); + map.put("memberPrf", profilePath); + + // 비밀번호 암호화 및 저장 + String encodedPassword = passwordEncoder.encode(map.getString("memberPwd")); + map.put("memberPwd", encodedPassword); + + // 회원 기본 정보 설정 + map.put("memberRol", "ROLE_MEMBER"); + map.put("memberPos", 500107); + map.put("memberTkn", "Null"); + map.put("memberPrm", "Y"); + map.put("memberDel", "N"); + map.put("memberLea", "N"); + map.put("memberRdt", LocalDateTime.now()); + map.put("memberCdt", LocalDateTime.now()); + + // 회원 정보 저장 + int result = memberMapper.insertMember(map); + + // 선택한 색상 코드 사용 처리 + String color = map.getString("memberCol"); + commoncodMapper.updateColorYon(color); + + return result; + } + + /** + * 아이디 중복 체크 + * + * @param memberIds + * @return + */ + public boolean selectCheckId(String memberIds) { + return memberMapper.selectCheckId(memberIds) > 0; + } + + /** + * 사원 목록 전체 조회 + * + * @param + * @return + */ + public List getallUserList() { + return memberMapper.getallUserList(); + } + + /** + * 로그인 토큰 + * + * @param id, token + * @return + */ + public void updateMemberToken(MapDto map) { + memberMapper.updateMemberToken(map); + } + + /** + * 비밀번호 재설정 member 체크 + * + * @param map + * @return + */ + public boolean selectPwReset(MapDto map) { + return memberMapper.selectPwReset(map) > 0; + } + + /** + * 기존 비밀번호 체크 + * + * @param map + * @return + */ + public boolean selectPassword(MapDto map) { + String currentPassword = memberMapper.selectPassword(map.getString("id")); + String newPassword = map.getString("password"); + + // 기존 비밀번호, 새 비밀번호 같은지 확인 + return !passwordEncoder.matches(newPassword, currentPassword); + } + + /** + * 비밀번호 재설정 + * + * @param map + * @return + */ + public boolean updatePassword(MapDto map) { + String encodedPassword = passwordEncoder.encode(map.getString("password")); + map.put("password", encodedPassword); + System.out.println("암호화된 비밀번호: " + encodedPassword); + return memberMapper.updatePassword(map) > 0; + } + +} diff --git a/src/main/java/io/company/localhost/service/commoncodService.java b/src/main/java/io/company/localhost/service/commoncodService.java index c016993..c50422d 100644 --- a/src/main/java/io/company/localhost/service/commoncodService.java +++ b/src/main/java/io/company/localhost/service/commoncodService.java @@ -35,5 +35,19 @@ public class commoncodService { public Long insertCategory(MapDto map) { return commoncodmapper.insertCategory(map); } - + + public List getColorList() { + return commoncodmapper.getColorList(); + } + + public List getMbtiList() { + return commoncodmapper.getMbtiList(); + } + + public List getPwhintList() { + return commoncodmapper.getPwhintList(); + } + public List getCategoryList() { + return commoncodmapper.getCategories(); + } } diff --git a/src/main/java/io/company/localhost/service/localbordService.java b/src/main/java/io/company/localhost/service/localbordService.java new file mode 100644 index 0000000..7e6550a --- /dev/null +++ b/src/main/java/io/company/localhost/service/localbordService.java @@ -0,0 +1,304 @@ +package io.company.localhost.service; + +import java.io.ByteArrayInputStream; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; + +import io.company.localhost.common.dto.MapDto; +import io.company.localhost.mapper.localbordMapper; +import io.company.localhost.utils.PageUtil; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class localbordService { + private final localbordMapper boardMapper; + private static final long MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB + + public List getNotices(MapDto map) { + List posts = boardMapper.getNotices(map); + enrichPostsWithAdditionalData(posts); + return posts; + } + + public PageInfo getGeneralPosts(MapDto map) { + System.out.println(map); + int page = map.getString("page") != null ? Integer.parseInt(map.getString("page")) : 1; + int size = map.getString("size") != null ? Integer.parseInt(map.getString("size")) : 10; + + String orderBy = map.getString("orderBy"); + if (orderBy == null || (!orderBy.equals("date") && !orderBy.equals("views"))) { + map.put("orderBy", "date"); + } + + PageHelper.startPage(page, size); + + List result = boardMapper.getGeneralPosts(map); + enrichPostsWithAdditionalData(result); + + return PageUtil.redefineNavigation(new PageInfo<>(result, size)); + } + + public void incrementViewCount(Long boardId) { + boardMapper.incrementViewCount(boardId); + } + + public BigInteger createBoard(MapDto map) { + boardMapper.createBoard(map); + return (BigInteger) map.get("LOCBRDSEQ"); + } + + public void addAttachment(MapDto map) { + String boardSeqStr = (String) map.get("CMNBRDSEQ"); + Long boardSeq = Long.parseLong(boardSeqStr); + map.put("CMNBRDSEQ", boardSeq); + + String newFilename = UUID.randomUUID().toString(); + map.put("CMNFLENAM", newFilename); + + boardMapper.addAttachment(map); + } + + public void validateAttachmentsSize(List attachments) { + long totalSize = attachments.stream() + .mapToLong(attachment -> (Long) attachment.get("size")) + .sum(); + + if (totalSize > MAX_FILE_SIZE) { + throw new IllegalArgumentException("첨부파일의 총 용량이 5MB를 초과합니다."); + } + } + + public MapDto getBoardDetail(Long boardId) { + incrementViewCount(boardId); + MapDto boardDetail = boardMapper.selectBoardDetail(boardId); + if (boardDetail != null) { + enrichBoardDetail(boardDetail); + } + return boardDetail; + } + + public List getAttachments(Long boardId) { + return boardMapper.selectAttachments(boardId); + } + + public void deleteBoard(MapDto map) { + boardMapper.deleteCommentsByBoardId(map); + boardMapper.deleteBoard(map); + } + + public void updateBoard(MapDto map) { + boardMapper.updateBoard(map); + } + + public void reactToBoard(MapDto map) { + MapDto existingReaction = boardMapper.findReaction(map); + + if (existingReaction != null) { + boardMapper.updateReaction(map); + } else { + boardMapper.insertReaction(map); + } + } + + public List getComments(int boardSeq) { + return boardMapper.getComments(boardSeq); + } + + public void addCommentOrReply(MapDto map) { + if (map.get("LOCCMTPNT") == null) { + map.put("LOCCMTPNT", null); + } + boardMapper.addCommentOrReply(map); + } + + public void updateComment(MapDto map) { + boardMapper.updateComment(map); + } + + public void deleteComment(MapDto map) { + boolean isReply = boardMapper.isReply(map); + + if (isReply) { + boardMapper.deleteReply(map); + } else { + boolean hasReplies = boardMapper.hasReplies(map); + + if (hasReplies) { + boardMapper.softDeleteComment(map); + } else { + boardMapper.deleteComment(map); + } + } + } + + public String getCommentPassword(int commentId) { + return boardMapper.selectCommentPassword(commentId); + } + + public String getBoardPassword(int boardId) { + return boardMapper.selectBoardPassword(boardId); + } + + public MapDto getCommentById(int commentId) { + return boardMapper.getCommentById(commentId); + } + + public int getCommentCount(Long boardId) { + return boardMapper.countComments(boardId); + } + + public boolean hasAttachments(Long boardId) { + int count = boardMapper.countAttachments(boardId); + return count > 0; + } + + public MapDto getBoardReactions(Long boardId) { + return boardMapper.getBoardReactions(boardId); + } + + public List getCommentReactions(Long boardId) { + return boardMapper.getCommentReactions(boardId); + } + + private void enrichBoardDetail(MapDto boardDetail) { + long boardId = ((Number) boardDetail.get("id")).longValue(); + boardDetail.put("hasAttachment", hasAttachments(boardId)); + boardDetail.put("commentCount", getCommentCount(boardId)); + MapDto reactions = getBoardReactions(boardId); + boardDetail.put("likeCount", reactions.getOrDefault("likeCount", 0)); + boardDetail.put("dislikeCount", reactions.getOrDefault("dislikeCount", 0)); + + // Blob 데이터를 문자열로 변환 + Object content = boardDetail.get("content"); + if (content != null) { + String contentString = convertBlobToString(content); // Blob을 문자열로 변환 + boardDetail.put("content", contentString); // JSON 변환 가능 + } + } + + private String convertBlobToString(Object blob) { + try { + if (blob instanceof String) { + return (String) blob; // 이미 문자열인 경우 반환 + } else if (blob instanceof java.sql.Blob) { + java.sql.Blob sqlBlob = (java.sql.Blob) blob; + long blobLength = sqlBlob.length(); + byte[] blobBytes = sqlBlob.getBytes(1, (int) blobLength); + return new String(blobBytes, StandardCharsets.UTF_8); + } else if (blob instanceof ByteArrayInputStream) { + ByteArrayInputStream inputStream = (ByteArrayInputStream) blob; + byte[] bytes = inputStream.readAllBytes(); + return new String(bytes, StandardCharsets.UTF_8); + } else { + System.err.println("Unsupported blob type: " + blob.getClass()); + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to convert Blob to String: " + e.getMessage(), e); + } + return null; + } + + private String extractFirstImageUrl(String jsonContent) { + try { + // JSON 유효성 검사 + if (!isValidJson(jsonContent)) { + throw new IllegalArgumentException("Invalid JSON content: " + jsonContent); + } + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonContent); + + // JSON 배열 순회 + for (JsonNode node : rootNode) { + JsonNode insertNode = node.get("insert"); + if (insertNode != null && insertNode.has("image")) { + return insertNode.get("image").asText(); // 첫 번째 이미지 URL 반환 + } + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to extract first image URL: " + e.getMessage(), e); + } + return null; // 이미지가 없는 경우 + } + + private boolean isValidJson(String json) { + try { + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.readTree(json); // JSON 파싱 시도 + return true; // JSON이 유효하면 true 반환 + } catch (Exception e) { + return false; // 유효하지 않은 경우 false 반환 + } + } + + private String extractPlainTextFromJson(String jsonContent) { + StringBuilder plainTextBuilder = new StringBuilder(); + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonContent); + + // JSON 배열인지 확인 + if (!rootNode.isArray()) { + System.err.println("JSON content is not an array"); + return ""; + } + + // JSON 노드 순회 + for (JsonNode node : rootNode) { + JsonNode insertNode = node.get("insert"); + + // insert 노드가 텍스트인지 확인 + if (insertNode != null && insertNode.isTextual()) { + String text = insertNode.asText(); + + // '\n' 제거하고 순수 텍스트만 추가 + if (!text.trim().isEmpty() && !text.trim().equals("\n")) { + plainTextBuilder.append(text.trim()); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to extract plain text: " + e.getMessage(), e); + } + return plainTextBuilder.toString(); + } + + private void enrichPostsWithAdditionalData(List posts) { + for (MapDto post : posts) { + Object idObject = post.get("id"); + if (idObject instanceof Number) { + long postId = ((Number) idObject).longValue(); + post.put("commentCount", getCommentCount(postId)); + post.put("hasAttachment", hasAttachments(postId)); + MapDto reactions = getBoardReactions(postId); + post.put("likeCount", reactions.getOrDefault("likeCount", 0)); + post.put("dislikeCount", reactions.getOrDefault("dislikeCount", 0)); + + Object content = post.get("content"); + String contentString = convertBlobToString(content); + post.put("content", contentString); + + String firstImageUrl = extractFirstImageUrl(contentString); + post.put("firstImageUrl", firstImageUrl); + + String plainContent = extractPlainTextFromJson(contentString); + post.put("plainContent", plainContent); + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/company/localhost/service/localvacaService.java b/src/main/java/io/company/localhost/service/localvacaService.java new file mode 100644 index 0000000..b932f60 --- /dev/null +++ b/src/main/java/io/company/localhost/service/localvacaService.java @@ -0,0 +1,25 @@ +package io.company.localhost.service; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import io.company.localhost.common.dto.MapDto; +import io.company.localhost.mapper.localvacaMapper; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class localvacaService { + private final localvacaMapper localvacaMapper; + + + public void insertVacation(MapDto map) { + localvacaMapper.insertVacation(map); + } + + public List getVacationList(MapDto map) { + return localvacaMapper.findVacations(map); + } + +} diff --git a/src/main/java/io/company/localhost/service/worddictyService.java b/src/main/java/io/company/localhost/service/worddictyService.java index 826c0f9..56b131e 100644 --- a/src/main/java/io/company/localhost/service/worddictyService.java +++ b/src/main/java/io/company/localhost/service/worddictyService.java @@ -15,14 +15,13 @@ package io.company.localhost.service; -import org.springframework.stereotype.Service; +import java.util.ArrayList; +import java.util.List; -import com.github.pagehelper.PageHelper; -import com.github.pagehelper.PageInfo; +import org.springframework.stereotype.Service; import io.company.localhost.common.dto.MapDto; import io.company.localhost.mapper.worddictyMapper; -import io.company.localhost.utils.PageUtil; import lombok.RequiredArgsConstructor; @Service @@ -31,12 +30,36 @@ public class worddictyService { private final worddictyMapper worddictymapper; - public PageInfo getWordList(MapDto map) { - int page = map.getString("page") != null ? Integer.parseInt(map.getString("page")) : 1; - PageHelper.startPage(page, 10); - return PageUtil.redefineNavigation(new PageInfo<>(worddictymapper.getWordList(map),10)); + public List getWordList(MapDto map) { + List wordList = worddictymapper.getWordList(map); + List processedList = new ArrayList<>(); + + // 데이터 가공 + for (MapDto dto : wordList) { + MapDto author = new MapDto(); + author.put("profileImage", dto.remove("REGPRF")); + author.put("name", dto.remove("REGNAME")); + author.put("color", dto.remove("REGCOLOR")); + author.put("createdAt", dto.remove("REGRDT")); + + MapDto lastEditor = new MapDto(); + lastEditor.put("profileImage", dto.remove("UPDPRF")); + lastEditor.put("name", dto.remove("UPDNAME")); + lastEditor.put("color", dto.remove("UPDCOLOR")); + lastEditor.put("updatedAt", dto.remove("UPDUDT")); + + dto.put("author", author); + dto.put("lastEditor", lastEditor); + + MapDto processedDto = new MapDto(); + processedDto.putAll(dto); + processedList.add(processedDto); + } + return processedList; } + + public Long insertWord(MapDto map) { return worddictymapper.insertWord(map); } @@ -45,6 +68,14 @@ public class worddictyService { return worddictymapper.updateWord(map); } + public MapDto getWordDetail(MapDto map) { + return worddictymapper.getWordDetail(map); + } + + public int getTotal(MapDto map) { + return worddictymapper.getTotal(map); + } + diff --git a/src/main/java/io/company/localhost/utils/PageUtil.java b/src/main/java/io/company/localhost/utils/PageUtil.java index 8d92f10..cf7333a 100644 --- a/src/main/java/io/company/localhost/utils/PageUtil.java +++ b/src/main/java/io/company/localhost/utils/PageUtil.java @@ -57,6 +57,18 @@ public class PageUtil { list.setNavigateFirstPage(nav2[0]); list.setNavigateLastPage(nav2[nav2.length -1]); } + + // 페이지 그룹 크기 (예: 10개씩) + int groupSize = list.getNavigatePages(); + int totalPages = list.getPages(); + + // 현재 페이지 그룹 계산 + int currentGroup = (int) Math.ceil((double) currentPage / groupSize); + int totalGroups = (int) Math.ceil((double) totalPages / groupSize); + + // 이전/다음 그룹 존재 여부 설정 + list.setHasPreviousPage(currentGroup > 1); // 이전 그룹 존재 + list.setHasNextPage(currentGroup < totalGroups); // 다음 그룹 존재 return list; } diff --git a/src/main/java/io/company/localhost/vo/MemberVo.java b/src/main/java/io/company/localhost/vo/MemberVo.java index e063757..f7960eb 100644 --- a/src/main/java/io/company/localhost/vo/MemberVo.java +++ b/src/main/java/io/company/localhost/vo/MemberVo.java @@ -25,16 +25,28 @@ import java.util.Date; @AllArgsConstructor @ToString public class MemberVo { - private Long id; - private String loginId; - private String role; - private String name; - private String password; - private String email; - private String isUsed; - private String isDel; - private Date isrtDate; - private Date updtDate; - private Boolean remember; + private String loginId; // 사용자 아이디 + private String role; // 권한 + private String token; // 토큰 + private String profile; // 프로필사진 + private String name; // 이름 + private String password; // 비밀번호 + private String passwordhint; // 비밀번호힌트 + private String passwordRes; // 비밀번호힌트답변 + private int position; // 직급 + private String address; // 주소 + private String addressDetail; // 상세주소 + private String zipcode; // 우편번호 + private Date birth; // 생년월일 + private String phone; // 전화번호 + private Date regist; // 가입요청일 + private Date isCdt; // 입사일 + private String isUsed; // 허가여부 + private String isDel; // 퇴사여부 + private String isLea; // 휴직여부 + private int color; // 색상 + private int mbit; // MBTI + private Boolean remember; // 로그인 유지 } + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e5a0087..5099504 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,11 +4,6 @@ project: time-zone: Asia/Seoul spring: - datasource: - url: jdbc:mariadb://192.168.0.251:3306/localnet - username: root - password: host1234 - driver-class-name: org.mariadb.jdbc.Driver devtools: livereload: enabled: true @@ -23,6 +18,10 @@ spring: web: resources: add-mappings: false + servlet: + multipart: + max-file-size: 5MB + max-request-size: 5MB mybatis: mapper-locations: classpath:mapper/**/*.xml @@ -89,3 +88,10 @@ logging: connection: off io.company: DEBUG io.company.localhost.mapper: off + + +filePath: + boardfile: C:\\localhost-back\\upload\\img\\board\\ + profile: C:\\localhost-back\\upload\\img\\profile\\ + + diff --git a/src/main/resources/mapper/LocalBordMapper.xml b/src/main/resources/mapper/LocalBordMapper.xml deleted file mode 100644 index bf80815..0000000 --- a/src/main/resources/mapper/LocalBordMapper.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - INSERT INTO localbord (LOCBRDTTL, LOCBRDCON, LOCBRDCAT, MEMBERSEQ, LOCBRDCNT, LOCBRDRDT, LOCBRDUDT, LOCBRDPWD, LOCBRDTYP) - VALUES (#{LOCBRDTTL}, #{LOCBRDCON}, #{LOCBRDCAT}, #{MEMBERSEQ}, 0, NOW(), NOW(), #{LOCBRDPWD}, #{LOCBRDTYP}) - - - - - INSERT INTO commonfil (CMNBRDSEQ, CMNFLEPAT, CMNFLENAM, CMNFLEORG, CMNFLEEXT, CMNFLESIZ, CMNFLEREG, CMNFLERDT) - VALUES (#{CMNBRDSEQ}, #{CMNFLEPAT}, #{CMNFLENAM}, #{CMNFLEORG}, #{CMNFLEEXT}, #{CMNFLESIZ}, #{CMNFLEREG}, NOW()) - - - - - DELETE FROM localbord WHERE LOCBRDSEQ = #{LOCBRDSEQ} - - - - - UPDATE localbord - SET LOCBRDTTL = #{LOCBRDTTL}, LOCBRDCON = #{LOCBRDCON}, LOCBRDUDT = NOW() - WHERE LOCBRDSEQ = #{LOCBRDSEQ} - - - - - INSERT INTO localgorb (LOCBRDSEQ, LOCCMTSEQ, MEMBERSEQ, LOCGOBGOD, LOCGOBBAD ) - VALUES (#{LOCBRDSEQ}, #{LOCCMTSEQ}, #{MEMBERSEQ}, #{LOCGOBGOD}, #{LOCGOBBAD}) - ON DUPLICATE KEY UPDATE LOCGOBGOD = #{LOCGOBGOD}, LOCGOBBAD = #{LOCGOBBAD} - - - - - - - - INSERT INTO loccomt (LOCBRDSEQ, LOCCMTRPY, LOCCMTPWD, LOCCMTRDT, LOCCMTPNT) - VALUES (#{LOCBRDSEQ}, #{LOCCMTRPY}, #{LOCCMTPWD}, NOW(), #{LOCCMTPNT}) - - - - - UPDATE loccomt - SET LOCCMTRPY = #{LOCCMTRPY}, LOCCMTUDT = NOW() - WHERE LOCCMTSEQ = #{LOCCMTSEQ} AND LOCCMTPWD = #{LOCCMTPWD} - - - - - DELETE FROM loccomt - WHERE LOCCMTSEQ = #{LOCCMTSEQ} AND LOCCMTPWD = #{LOCCMTPWD} - - - - - - - - - - - - - diff --git a/src/main/resources/mapper/commoncodMapper.xml b/src/main/resources/mapper/commoncodMapper.xml index c4325e5..36c261a 100644 --- a/src/main/resources/mapper/commoncodMapper.xml +++ b/src/main/resources/mapper/commoncodMapper.xml @@ -29,4 +29,55 @@ WHERE CMNCODLV1 = 600; + + + + + + + + + UPDATE commoncod + SET CMNCODYON = 1 + WHERE CMNCODVAL = #{color}; + + + + + diff --git a/src/main/resources/mapper/localbordMapper.xml b/src/main/resources/mapper/localbordMapper.xml new file mode 100644 index 0000000..ed00e27 --- /dev/null +++ b/src/main/resources/mapper/localbordMapper.xml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + UPDATE localbord SET LOCBRDCNT = LOCBRDCNT + 1 WHERE LOCBRDSEQ = #{LOCBRDSEQ} + + + + + INSERT INTO localbord (LOCBRDTTL, LOCBRDCON, LOCBRDCAT, MEMBERSEQ, LOCBRDCNT, LOCBRDRDT, LOCBRDUDT, LOCBRDPWD, LOCBRDTYP) + VALUES (#{LOCBRDTTL}, #{LOCBRDCON}, #{LOCBRDCAT}, #{MEMBERSEQ}, 0, NOW(), NOW(), #{LOCBRDPWD}, #{LOCBRDTYP}) + + + + + INSERT INTO commonfil ( + CMNBRDSEQ,CMNFLENAM,CMNFLEORG,CMNFLEPAT, + CMNFLEEXT,CMNFLESIZ,CMNFLEREG,CMNFLERDT + ) VALUES ( + #{CMNBRDSEQ},#{CMNFLENAM},#{CMNFLEORG},#{CMNFLEPAT}, + #{CMNFLEEXT},#{CMNFLESIZ},#{CMNFLEREG},NOW() + ) + + + + + + + + + + + DELETE FROM localbord WHERE LOCBRDSEQ = #{LOCBRDSEQ} + + + + + DELETE FROM localcomt + WHERE LOCBRDSEQ = #{LOCBRDSEQ} + + + + + UPDATE localbord + SET LOCBRDTTL = #{LOCBRDTTL}, LOCBRDCON = #{LOCBRDCON}, LOCBRDUDT = NOW() + WHERE LOCBRDSEQ = #{LOCBRDSEQ} + + + + + + + + UPDATE localgorb + SET LOCGOBGOD = #{LOCGOBGOD}, LOCGOBBAD = #{LOCGOBBAD} + WHERE (LOCBRDSEQ = #{LOCBRDSEQ} OR (#{LOCBRDSEQ} IS NULL AND LOCBRDSEQ IS NULL)) + AND (LOCCMTSEQ = #{LOCCMTSEQ} OR (#{LOCCMTSEQ} IS NULL AND LOCCMTSEQ IS NULL)) + AND MEMBERSEQ = #{MEMBERSEQ} + + + + + INSERT INTO localgorb (LOCBRDSEQ, LOCCMTSEQ, MEMBERSEQ, LOCGOBGOD, LOCGOBBAD) + VALUES (#{LOCBRDSEQ}, #{LOCCMTSEQ}, #{MEMBERSEQ}, #{LOCGOBGOD}, #{LOCGOBBAD}) + + + + + + + + INSERT INTO localcomt (LOCBRDSEQ, LOCCMTRPY, LOCCMTPWD, LOCCMTRDT, LOCCMTUDT, LOCCMTPNT, MEMBERSEQ) + VALUES (#{LOCBRDSEQ}, #{LOCCMTRPY}, #{LOCCMTPWD}, NOW(), NOW() , #{LOCCMTPNT}, #{MEMBERSEQ}) + + + + + UPDATE localcomt + SET LOCCMTRPY = #{LOCCMTRPY}, LOCCMTUDT = NOW() + WHERE LOCCMTSEQ = #{LOCCMTSEQ} + + + + + UPDATE localcomt + SET LOCCMTRPY = '삭제된 댓글입니다' + WHERE LOCCMTSEQ = #{LOCCMTSEQ} + AND EXISTS ( + SELECT 1 FROM localcomt WHERE LOCCMTPNT = #{LOCCMTSEQ} + ) + + + + + DELETE FROM localcomt + WHERE LOCCMTSEQ = #{LOCCMTSEQ} + AND NOT EXISTS ( + SELECT 1 FROM localcomt WHERE LOCCMTPNT = #{LOCCMTSEQ} + ) + + + + + DELETE FROM localcomt + WHERE LOCCMTSEQ = #{LOCCMTSEQ} + AND LOCCMTPNT IS NOT NULL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/localvacaMapper.xml b/src/main/resources/mapper/localvacaMapper.xml new file mode 100644 index 0000000..e4b4694 --- /dev/null +++ b/src/main/resources/mapper/localvacaMapper.xml @@ -0,0 +1,20 @@ + + + + + + + INSERT INTO localvaca (MEMBERSEQ, LOCVACUDT, LOCVACTYP, LOCVACRDT) + VALUES (#{employeeId}, #{date}, #{type}, NOW()) + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/memberMapper.xml b/src/main/resources/mapper/memberMapper.xml deleted file mode 100644 index 6113de9..0000000 --- a/src/main/resources/mapper/memberMapper.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - diff --git a/src/main/resources/mapper/netmemberMapper.xml b/src/main/resources/mapper/netmemberMapper.xml new file mode 100644 index 0000000..9dff499 --- /dev/null +++ b/src/main/resources/mapper/netmemberMapper.xml @@ -0,0 +1,142 @@ + + + + + + + + UPDATE netmember + SET MEMBERTKN = #{token} + WHERE MEMBERIDS = #{id} + + + + + + INSERT INTO netmember ( + MEMBERIDS, + MEMBERROL, + MEMBERTKN, + MEMBERPRF, + MEMBERNAM, + MEMBERPWD, + MEMBERPWH, + MEMBERPWR, + MEMBERPOS, + MEMBERARR, + MEMBERDTL, + MEMBERZIP, + MEMBERBTH, + MEMBERTEL, + MEMBERRDT, + MEMBERCDT, + MEMBERPRM, + MEMBERDEL, + MEMBERLEA, + MEMBERCOL, + MEMBERMBT + ) VALUES ( + #{memberIds}, + #{memberRol}, + #{memberTkn}, + #{memberPrf}, + #{memberNam}, + #{memberPwd}, + #{memberPwh}, + #{memberPwr}, + #{memberPos}, + #{memberArr}, + #{memberDtl}, + #{memberZip}, + #{memberBth}, + #{memberTel}, + #{memberRdt}, + #{memberCdt}, + #{memberPrm}, + #{memberDel}, + #{memberLea}, + #{memberCol}, + #{memberMbt} + ) + + + + + + + + + + + + + + UPDATE netmember + SET MEMBERPWD = #{password} + WHERE MEMBERIDS = #{id} + + + + + + + diff --git a/src/main/resources/mapper/worddictyMapper.xml b/src/main/resources/mapper/worddictyMapper.xml index bfa6335..91bbda7 100644 --- a/src/main/resources/mapper/worddictyMapper.xml +++ b/src/main/resources/mapper/worddictyMapper.xml @@ -1,34 +1,191 @@ - + select + w.WRDDICSEQ, + w.WRDDICCAT, + w.WRDDICTTL, + w.WRDDICCON, + c.CMNCODNAM category, + m1.MEMBERPRF REGPRF, + m1.MEMBERNAM REGNAME, + cr.CMNCODNAM REGCOLOR, + w.WRDDICRDT REGRDT, + m2.MEMBERPRF UPDPRF, + m2.MEMBERNAM UPDNAME, + cu.CMNCODNAM UPDCOLOR, + w.WRDDICUDT UPDUDT + from + worddicty w + left join + commoncod c + on + w.WRDDICCAT = c.CMNCODVAL + left join + netmember m1 + on + w.WRDDICREG = m1.MEMBERSEQ + left join + commoncod cr + on + m1.MEMBERCOL = cr.CMNCODVAL -- 등록자 색상 + left join + netmember m2 + on + w.WRDDICUPD = m2.MEMBERSEQ + left join + commoncod cu + on + m2.MEMBERCOL = cu.CMNCODVAL -- 수정자 색상 + where + 1=1 + order by w.WRDDICRDT desc + insert into worddicty ( @@ -72,4 +229,11 @@ where WRDDICSEQ = #{WRDDICSEQ} +