Published 2021. 8. 15. 19:06
시작하기에 앞서 테이블을 생성해야 합니다. |
CREATE TABLE `tb_board_file` (
`BOARD_FILE_IDX` int NOT NULL AUTO_INCREMENT,
`BOARD_IDX` int DEFAULT NULL,
`FILE_ID` int DEFAULT NULL,
`USE_YN` varchar(1) DEFAULT 'Y',
PRIMARY KEY (`BOARD_FILE_IDX`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='게시판 파일테이블'
CREATE TABLE `tb_file` (
`FILE_ID` int NOT NULL AUTO_INCREMENT COMMENT '시퀀스',
`ORIG_NM` varchar(200) DEFAULT NULL COMMENT '파일 오리지널 이름',
`LOGI_NM` varchar(200) DEFAULT NULL COMMENT '파일 서버에 올라갔을때 이름',
`LOGI_PATH` varchar(200) DEFAULT NULL COMMENT '파일 서버에 올라갔을때 path',
`EXT` varchar(10) DEFAULT NULL COMMENT '파일 확장자',
`SIZE` int DEFAULT NULL COMMENT '사이즈',
`REG_DT` datetime DEFAULT NULL,
PRIMARY KEY (`FILE_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=83 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='파일 저장'
Url.JAVA |
package com.board.common;
/* api url 정의 */
public final class Url {
public static final String TILES_ROOT = "/tiles/view";
public static final String TILES_SINGLE = "/tiles/single";
public static final String TILES_AJAX = "/tiles/ajax";
/* 로그인 */
public static final class AUTH {
/* 로그인 url */
public static final String LOGIN = "/auth/login";
/* 로그인 jsp */
public static final String LOGIN_JSP = TILES_SINGLE + "/auth/login";
/* 회원가입 url */
public static final String JOIN = "/auth/join";
/* 회원가입 jsp */
public static final String JOIN_JSP = TILES_ROOT + "/auth/join";
/* 사용자 등록 */
public static final String INSERT_USER = "/auth/insertUser";
/* 로그인 인증 요청 */
public static final String LOGIN_PROC = "/auth/login-proc";
/* 로그아웃 요청 */
public static final String LOGOUT_PROC = "/auth/logout-proc";
}
/** 공통 */
public static final class COMMON{
/* 파일 업로드 */
public static final String FILE_UPLOAD = "/file-upload";
/** 파일 다운로드 */
public static final String FILE_DOWNLOAD = "/file-download";
}
/* 메인 화면 */
public static final class MAIN {
public static final String _MAIN_AJAX_ROOT_PATH = "/main/ajax";
/* 메인 url */
public static final String MAIN = "/";
/* 메인 jsp */
public static final String MAIN_JSP = TILES_ROOT + "/main/list";
/* 메인 리스트 ajax */
public static final String MAIN_LIST_AJAX = _MAIN_AJAX_ROOT_PATH + "/list-view";
/* 메인 글쓰기 */
public static final String MAIN_WRITE = "/board/write";
/* 메인 글쓰기 jsp */
public static final String MAIN_WRITE_JSP = TILES_ROOT + "/main/write";
/* 메인 수정화면 */
public static final String MAIN_UPDATE = "/board/update";
/* 메인 글쓰기 jsp */
public static final String MAIN_UPDATE_JSP = TILES_ROOT + "/main/update";
/** 게시판 삭제 */
public static final String MAIN_DELETE = "/board/delete";
}
}
MainController.java |
package com.board.controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.board.common.CoTopComponent;
import com.board.common.Constants;
import com.board.common.Url;
import com.board.common.Url.MAIN;
import com.board.dao.FileMapper;
import com.board.dao.MainMapper;
import com.board.service.MainService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
public class MainController extends CoTopComponent {
@Autowired MainService mainService;
@Autowired MainMapper mainMapper;
@Autowired FileMapper fileMapper;
//profile
@Value("${spring.profile.active")
private String profile;
//메인화면
@GetMapping(Url.MAIN.MAIN)
public String main() {
return Url.MAIN.MAIN_JSP;
}
//메인화면 리스트 ajax
@GetMapping(Url.MAIN.MAIN_LIST_AJAX)
public String mainListAjax(@RequestParam Map<String, Object> params
,Pageable pageable
,Model model
) {
model.addAttribute("boardList", mainService.selectBoardList(params, pageable, Integer.parseInt(params.get("size").toString())));
model.addAttribute("resultDataTotal", mainMapper.selectBoardListCnt(params));
return makePageDispatcherUrl(MAIN.MAIN_LIST_AJAX, MAIN._MAIN_AJAX_ROOT_PATH);
}
//글쓰기화면
@GetMapping(Url.MAIN.MAIN_WRITE)
public String write() {
return Url.MAIN.MAIN_WRITE_JSP;
}
//글등록
@ResponseBody
@PostMapping(Url.MAIN.MAIN_WRITE)
public Map<String, Object> writeSubmit(@RequestBody Map<String, Object> params) {
log.info("params={}", params);
//글등록
mainMapper.insertBoard(params);
//파일 등록할게 있을경우만
insertBoardFile(params);
return params;
}
//수정화면
@GetMapping(Url.MAIN.MAIN_UPDATE+"/{boardIdx}")
public String update(@PathVariable("boardIdx") int boardIdx, Model model) {
log.info("boardIdx={}", boardIdx);
//게시판 상세 데이터 조회
Map<String, Object> boardInfo = mainMapper.selectBoard(boardIdx);
//게시판 상세 파일 리스트 조회
List<Map<String, Object>> boardFileInfo = mainMapper.selectBoardFile(boardIdx);
if(boardInfo != null) {
model.addAttribute("boardInfo", boardInfo);
model.addAttribute("boardIdx", boardIdx);
model.addAttribute("boardFileInfo", boardFileInfo);
//조회수 업데이트
mainService.updateViewCount(boardIdx);
}
else {
model.addAttribute("boardIdx", "");
}
return Url.MAIN.MAIN_UPDATE_JSP;
}
//글수정
@ResponseBody
@PostMapping(Url.MAIN.MAIN_UPDATE)
public Map<String, Object> updateSubmit(@RequestBody Map<String, Object> params) {
log.info("params={}", params);
mainMapper.updateBoard(params);
//파일 등록할게 있을경우만
insertBoardFile(params);
//넘어온 파일 삭제 시퀀스 삭제처리
if(params.get("deleteFileIdxs") != null) {
String deleteFileIdxs = (String) params.get("deleteFileIdxs");
String[] fileIdxsArray = deleteFileIdxs.split(",");
//해당 시퀀스 삭제처리
for(int i=0; i<fileIdxsArray.length; i++) {
String fileId = fileIdxsArray[i];
fileMapper.deleteFile(fileId);
}
}
return params;
}
/** 게시판 삭제 */
@ResponseBody
@PostMapping(Url.MAIN.MAIN_DELETE)
public List<String> deleteSubmit(@RequestBody List<String> boardIdxArray) {
log.info("boardIdxArray={}", boardIdxArray);
mainService.deleteBoard(boardIdxArray);
return boardIdxArray;
}
//server health check
@RequestMapping(value= { Constants.HEALTH_CHECK_URL }, produces=MediaType.TEXT_HTML_VALUE)
public void healthCheck( HttpServletRequest req, HttpServletResponse res ) throws IOException {
String ip = req.getHeader("X-FORWARDED-FOR");
if (ip == null) ip = req.getRemoteAddr();
PrintWriter pw = res.getWriter();
pw.write(" - Active Profile : " + profile + "\n");
pw.write(" - Client IP : " + ip);
pw.close();
}
//게시판 파일 등록
private void insertBoardFile(Map<String, Object> params) {
//파일 등록할게 있을경우만
if(params.get("fileIdxs") != null) {
//파일 등록
String fileIdxs = ((String) params.get("fileIdxs")).replace("[", "").replace("]", "");
String[] fileIdxArray = fileIdxs.split(",");
for (int i=0; i<fileIdxArray.length; i++) {
params.put("fileId", fileIdxArray[i]);
mainMapper.insertBoardFile(params);
}
}
}
}
MainMapper.java |
package com.board.dao;
import java.util.List;
import java.util.Map;
import org.springframework.data.domain.Pageable;
import org.apache.ibatis.annotations.Param;
public interface MainMapper {
//글쓰기 리스트 조회
public List<Map<String, Object>> selectBoardList(
@Param(value = "params") Map<String, Object> params
,@Param(value = "pageable") Pageable pageable
,@Param(value = "size") int size);
//글쓰기 리스트 count
int selectBoardListCnt(@Param(value = "params") Map<String, Object> params);
//글쓰기 등록
public int insertBoard(Map<String, Object> params);
//글 수정
public int updateBoard(Map<String, Object> params);
//글쓰기 상세조회
public Map<String, Object> selectBoard(int boardIdx);
//게시판 파일리스트 조회
public List<Map<String, Object>> selectBoardFile(int boardIdx);
//조회수 업데이트
public int updateViewCount(Map<String, Object> params);
//겍시판 삭제
public int deleteBoard(String boardIdx);
//글쓰기 파일 등록
public int insertBoardFile(Map<String, Object> params);
}
MainMapper.xml |
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- FAQ SQL Mapper -->
<mapper namespace="com.board.dao.MainMapper">
<!-- 게시판 리스트 조회 -->
<select id="selectBoardList" resultType="CamelMap">
SELECT
TB.BOARD_IDX
,TB.BOARD_TITLE
,TB.BOARD_CONTENT
,TB.REG_ID
,TB.VIEW_COUNT
,TB.USE_YN
,TB.REG_DATE
,(SELECT TC.CODE_EXP FROM TB_CODE TC WHERE TC.CODE_NO = '200' AND TC.CODE_NAME = TA.AUTHORITY) AS REG_NAME
,(SELECT COUNT(*) FROM TB_BOARD_FILE TBF WHERE TBF.BOARD_IDX = TB.BOARD_IDX ) AS FILE_COUNT
FROM
TB_BOARD TB
,TB_AUTHORITIES TA
WHERE
USE_YN = 'Y'
AND
TB.REG_ID = TA.USER_ID
ORDER BY TB.BOARD_IDX DESC
<if test="pageable != null">
LIMIT #{pageable.offset}, #{size}
</if>
</select>
<!-- 게시판 count -->
<select id="selectBoardListCnt" resultType="int">
SELECT
COUNT(*)
FROM
TB_BOARD
WHERE
USE_YN = 'Y'
</select>
<!-- 게시판 상세 조회 -->
<select id="selectBoard" resultType="CamelMap">
SELECT
TB.BOARD_IDX
,TB.BOARD_TITLE
,TB.BOARD_CONTENT
,TB.REG_ID
,TB.VIEW_COUNT
,TB.USE_YN
,TB.REG_DATE
,(SELECT TC.CODE_EXP FROM TB_CODE TC WHERE TC.CODE_NO = '200' AND TC.CODE_NAME = TA.AUTHORITY) AS REG_NAME
FROM
TB_BOARD TB
,TB_AUTHORITIES TA
WHERE
USE_YN = 'Y'
AND
TB.REG_ID = TA.USER_ID
AND
TB.BOARD_IDX = #{boardIdx}
</select>
<!-- 게시판 파일 리스트 조회 -->
<select id="selectBoardFile" resultType="CamelMap">
SELECT
TBF.BOARD_FILE_IDX
,TBF.BOARD_IDX
,TBF.FILE_ID
,TBF.USE_YN
,TF.ORIG_NM
,TF.LOGI_PATH
FROM
TB_BOARD_FILE TBF
,TB_FILE TF
WHERE
TBF.USE_YN = 'Y'
AND
TBF.BOARD_IDX = #{boardIdx}
AND
TBF.FILE_ID = TF.FILE_ID
</select>
<!-- 게시판 등록 -->
<insert id="insertBoard" useGeneratedKeys="true" keyProperty="boardIdx">
INSERT INTO
TB_BOARD(
BOARD_TITLE
,BOARD_CONTENT
,REG_ID
,REG_DATE
)
VALUES(
#{boardTitle}
,#{boardContent}
,#{userId}
,NOW()
)
</insert>
<!-- 게시판 수정 -->
<update id="updateBoard">
UPDATE
TB_BOARD
SET
BOARD_TITLE = #{boardTitle}
,BOARD_CONTENT = #{boardContent}
WHERE
BOARD_IDX = #{boardIdx}
</update>
<!-- 조회수 업데이트 -->
<update id="updateViewCount">
UPDATE
TB_BOARD T1
SET
T1.VIEW_COUNT = #{viewCount}
WHERE
T1.BOARD_IDX = #{boardIdx}
</update>
<!-- 게시판 삭제 -->
<update id="deleteBoard">
UPDATE
TB_BOARD
SET
USE_YN = 'N'
WHERE
BOARD_IDX = #{boardIdx}
</update>
<!-- 게시판 파일 테이블 insert -->
<insert id="insertBoardFile">
INSERT INTO
TB_BOARD_FILE(
BOARD_IDX
,FILE_ID
)
VALUES(
#{boardIdx}
,#{fileId}
)
</insert>
</mapper>
FileController.java |
package com.board.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.board.common.Url.COMMON;
import com.board.dao.FileMapper;
import com.board.service.FileService;
import lombok.extern.slf4j.Slf4j;
/* 파일 controller */
@Controller
@Slf4j
public class FileController {
@Autowired FileService fileService;
@Autowired FileMapper fileMapper;
/** 멀티파일 업로드 */
@PostMapping(value={COMMON.FILE_UPLOAD})
@ResponseBody
public Map<String, Object> fileUpload(
@RequestParam("article_file") List<MultipartFile> multipartFile
, HttpServletRequest request) throws IOException {
log.info("파일 컨트롤러 진입");
return fileService.uploadFile(request, multipartFile);
}
/** 파일 다운로드
* @throws UnsupportedEncodingException */
@GetMapping(value = {COMMON.FILE_DOWNLOAD+"/{fileIdx}"})
@ResponseBody
public void downloadFile(HttpServletResponse res, @PathVariable String fileIdx) throws UnsupportedEncodingException {
//파일 조회
Map<String, Object> fileInfo = fileMapper.getFileInfo(fileIdx);
//파일 경로
Path saveFilePath = Paths.get(fileInfo.get("logiPath") + File.separator + fileInfo.get("logiNm"));
//해당 경로에 파일이 없으면
if(!saveFilePath.toFile().exists()) {
throw new RuntimeException("file not found");
}
res.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode((String) fileInfo.get("origNm"), "UTF-8") + "\";");
res.setHeader("Content-Transfer-Encoding", "binary");
res.setHeader("Content-Type", "application/download; utf-8");
res.setHeader("Pragma", "no-cache;");
res.setHeader("Expires", "-1;");
FileInputStream fis = null;
try {
fis = new FileInputStream(saveFilePath.toFile());
FileCopyUtils.copy(fis, res.getOutputStream());
res.getOutputStream().flush();
}
catch (Exception e) {
throw new RuntimeException(e);
}
finally {
try {
fis.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
FileService.java |
package com.board.service;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import com.board.dao.FileMapper;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service("fileService")
public class FileService {
@Autowired Environment env;
@Autowired FileService fileService;
@Autowired FileMapper fileMapper;
private final String UPLOAD_FILE_PATH = "common.public_upload_local_path";
/** 파일 업로드 */
public Map<String, Object> uploadFile(HttpServletRequest request, List<MultipartFile> multipartFile) throws IOException{
Map<String, Object> result = new HashMap<String, Object>();
//파일 시퀀스 리스트
List<String> fileIds = new ArrayList<String>();
result.put("result", "FAIL");
String _filePath = String.valueOf(request.getParameter("filePath")).equals("null") ? env.getProperty(UPLOAD_FILE_PATH) : env.getProperty(UPLOAD_FILE_PATH)+String.valueOf(request.getParameter("filePath")+"/");
try {
// 파일이 있을때 탄다.
if(multipartFile.size() > 0 && !multipartFile.get(0).getOriginalFilename().equals("")) {
for(MultipartFile file : multipartFile) {
String originalFileName = file.getOriginalFilename(); //오리지날 파일명
String extension = originalFileName.substring(originalFileName.lastIndexOf(".")); //파일 확장자
String savedFileName = UUID.randomUUID() + extension; //저장될 파일 명
File targetFile = new File(_filePath + savedFileName);
//파일 저장후 db insert
result.put("pyscPath", _filePath);
result.put("pyscNm", savedFileName);
result.put("origNm", originalFileName);
result.put("fileExt", extension);
result.put("contentType", file.getContentType());
result.put("fileSize", file.getSize());
//파일 insert
fileService.insertFile(result);
log.info("fileId={}", result.get("fileId"));
//배열에 담기
fileIds.add(String.valueOf(result.get("fileId")));
try {
InputStream fileStream = file.getInputStream();
FileUtils.copyInputStreamToFile(fileStream, targetFile); //파일 저장
} catch (Exception e) {
//파일삭제
FileUtils.deleteQuietly(targetFile); //저장된 현재 파일 삭제
e.printStackTrace();
result.put("result", "FAIL");
break;
}
}
result.put("fileIdxs", fileIds.toString());
result.put("result", "OK");
}
// 파일 아무것도 첨부 안했을때 탄다.(게시판일때, 업로드 없이 글을 등록하는경우)
else {
result.put("result", "OK");
}
}catch(Exception e){
e.printStackTrace();
result.put("result", "FAIL");
}
return result;
}
/** 파일 저장 db */
@Transactional
public int insertFile(Map<String, Object> params) {
return fileMapper.insertFile(params);
}
}
FileMapper.java |
package com.board.dao;
import java.util.Map;
/* file dao */
public interface FileMapper {
/** 파일 등록 */
int insertFile(Map<String, Object> file);
/** 파일 조회 */
Map<String, Object> getFileInfo(String fileId);
/** 해당 파일 삭제처리 */
int deleteFile(String fileId);
}
FileMapper.xml |
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- FAQ SQL Mapper -->
<mapper namespace="com.board.dao.FileMapper">
<insert id="insertFile" useGeneratedKeys="true" keyProperty="fileId" >
INSERT INTO TB_FILE (
ORIG_NM
,LOGI_NM
,LOGI_PATH
,EXT
,SIZE
,REG_DT
)VALUES(
#{origNm}
,#{pyscNm}
,#{pyscPath}
,#{fileExt}
,#{fileSize}
,NOW()
)
</insert>
<!-- 파일 조회 -->
<select id="getFileInfo" resultType="CamelMap">
SELECT
FILE_ID
,ORIG_NM
,LOGI_NM
,LOGI_PATH
,EXT
,SIZE
,REG_DT
FROM
TB_FILE
WHERE
FILE_ID = #{fileId}
</select>
<!-- 파일 논리삭제 -->
<update id="deleteFile">
UPDATE
TB_BOARD_FILE
SET
USE_YN = 'N'
WHERE
FILE_ID = #{fileId}
</update>
</mapper>
SecurityConfig.java |
package com.board.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.board.common.Constants;
import com.board.common.Url;
import com.board.common.Url.COMMON;
@Configuration
@EnableWebSecurity
@ComponentScan(value = Constants.APP_DEFAULT_PACKAGE_NAME)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired private LoginSuccessHandler loginSuccessHandler;
@Autowired private LoginFailureHandler loginFailureHandler;
// @Autowired private CustomerUserDetailsService customUserDetailsService;
/**
* Configure.
*
* @param auth the auth
* @throws Exception the exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(customUserDetailsService);
auth.authenticationProvider(new AdminAuthenticationProvider());
super.configure(auth);
}
/**
* http 요청 검사.
*
* @param http the http
* @throws Exception the exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// replay 어택을 막기 위한 csrf 토큰의 생성을 비활성화(disabled) 처리
.csrf().disable()
// pdf viewer 에서 'X-Frame-Options' to 'DENY' 대응
.headers().frameOptions().disable().and()
// 요청에 대한 권한 매핑
.authorizeRequests()
.antMatchers( "/auth/**" ).permitAll() // 패스워드찾기,회원가입
.antMatchers( "/" ).permitAll()
.antMatchers( COMMON.FILE_DOWNLOAD + "/**" ).permitAll() //파일다운로드
.antMatchers( "/**/ajax/**" ).permitAll()
.antMatchers( "/board/**" ).permitAll()
.antMatchers( "/resource/**/images/**" ).permitAll() // image
.anyRequest().authenticated() // 모든 요청에 대해 권한 확인이 필요
// .anyRequest().permitAll()
.and()
// 로그인 화면 설정
.formLogin()
.permitAll()
.loginPage( Url.AUTH.LOGIN )
.loginProcessingUrl( Url.AUTH.LOGIN_PROC )
.successHandler( loginSuccessHandler )
.failureHandler( loginFailureHandler )
.usernameParameter( USERNAME_PARAM )
.passwordParameter( PASSWORD_PARAM )
.and()
.logout()
.logoutUrl( Url.AUTH.LOGOUT_PROC )
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and()
// 세션 관리
.sessionManagement()
.maximumSessions(200) /* session 허용 갯수 */
.expiredUrl(Url.AUTH.LOGIN) /* session 만료시 이동 페이지*/
.sessionRegistry(sessionRegistry()) // 세션을 목록에 담아둠
.maxSessionsPreventsLogin(true) /* 동일한 사용자 로그인시 x, false 일 경우 기존 사용자 */
;
}
/**
* web요청 검사.
*
* @param web the web
* @throws Exception the exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
// Security Debug
// web.debug(true);
web
.ignoring()
// static 리소스 경로는 webSecurity 검사 제외
.antMatchers( Constants.STATIC_RESOURCES_URL_PATTERNS )
.antMatchers( Constants.HEALTH_CHECK_URL )
.antMatchers(HttpMethod.GET, "/exception/**")
;
super.configure(web);
}
/**
* PasswordEncoder.
*
* @return the password encoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SessionRegistry sessionRegistry() {
return new SpringSecuritySessionRegistImpl();
}
/**
* AuthenticationProvider
* <br>관리자의 계정정보를 통해 로그인 인증을 처리합니다.
*
* @return the authentication provider
* @see kr.mediaflow.fdwm.config.DatabaseConfig
*/
// @Bean
// public AuthenticationProvider daoAuthenticationProvider() {
// DaoAuthenticationProvider impl = new DaoAuthenticationProvider();
// impl.setUserDetailsService(customUserDetailsService);
// impl.setPasswordEncoder(new BCryptPasswordEncoder());
// impl.setHideUserNotFoundExceptions(false);
// return impl;
// }
/** 관리자 아이디 파라미터 이름 : {@value #USERNAME_PARAM}. */
public static final String USERNAME_PARAM = "un";
/** 관리자 비밀번호 파라미터 이름 : {@value #PASSWORD_PARAK}. */
public static final String PASSWORD_PARAM = "up";
}
list-view.jsp |
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%@ include file="/WEB-INF/template/constants.jsp"%>
<form>
<table class="table table-hover">
<colgroup>
<col width="2%" />
<col width="5%" />
<col width="20%" />
<col width="5%" />
<col width="5%" />
<col width="5%" />
<col width="5%" />
</colgroup>
<thead>
<tr>
<th>
<label class="checkbox-inline">
<input type="checkbox" id="allCheckBox" class="chk" onclick="allChecked(this)">
</label>
</th>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>날짜</th>
<th>조회수</th>
<th>파일유무</th>
</tr>
</thead>
<tbody>
<c:forEach var="board" items="${boardList.content}" varStatus="vs">
<tr>
<td>
<label class="checkbox-inline">
<input type="checkbox" class="chk" name="cchk" onclick="cchkClicked()" value="${board.boardIdx}">
</label>
<td>${resultDataTotal - (boardList.size * boardList.number) - vs.index}</td>
<td><a href='/board/update/${board.boardIdx}'>${board.boardTitle}</a></td>
<td>${board.regName}</td>
<td>
<fmt:parseDate value="${board.regDate}" var="regDate" pattern="yyyy-MM-dd HH:mm:ss.s"/>
<fmt:formatDate value="${board.regDate}" pattern="yyyy-MM-dd"/>
</td>
<td>${board.viewCount}</td>
<td ${board.fileCount }>
<c:if test="${board.fileCount != 0}">
<img src="/images/file_icon.png" style="width:20px; height:auto; vertical-align: center; "/>
</c:if>
</td>
</tr>
</c:forEach>
<c:if test="${resultDataTotal == 0}">
<tr>
<center><td colspan="6" style="text-align: center;">등록된 게시판 리스트가 없습니다.</td></center>
</tr>
</c:if>
</tbody>
</table>
<!-- ADMIN 권한일경우에만 글쓰기 권한있음 -->
<c:if test="${sessUserInfo.authority == 'ADMIN'}">
<div class="text-right">
<a href='javascript:boardDelete();' class="btn btn-danger">글삭제</a>
<a href='/board/write' class="btn btn-primary">글쓰기</a>
</div>
</c:if>
<div class="text-center">
<c:if test="${resultDataTotal != 0}">
<ul class="pagination">
<paginator:print goPageScript="goPage" curPage="${boardList.number}" totPages="${boardList.totalPages}"/>
</ul>
</c:if>
<!--
<ul class="pagination">
<li><a href="#">prev</a></li>
<li><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li><a href="#">next</a></li>
</ul>
-->
</div>
</form>
write.jsp |
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="/WEB-INF/template/constants.jsp"%>
<body>
<article>
<div class="container" role="main">
<h2>게시판 글쓰기</h2>
<div class="mb-3">
<label for="title">제목</label>
<input type="text" class="form-control" id="boardTitle" name="boardTitle" placeholder="제목을 입력해 주세요">
</div>
<br>
<div class="mb-3">
<label for="reg_id">작성자</label>
<input type="text" class="form-control" id="regId" name="regId" value="${sessUserInfo.authorityNm}" readonly>
</div>
<br>
<div class="mb-3">
<label for="content">내용</label>
<textarea class="form-control" rows="5" id="boardContent" name="boardContent" placeholder="내용을 입력해 주세요"></textarea>
</div>
<br>
<div class="mb-3">
<form name="dataForm" id="dataForm" onsubmit="return registerAction()">
<button id="btn-upload" onclick="fileClick()" type="button" style="border: 1px solid #ddd; outline: none;">파일 추가</button>
<input id="input_file" onChange="fileChange(this)" multiple="multiple" type="file" style="display:none;">
<span style="font-size:10px; color: gray;">※첨부파일은 최대 10개까지 등록이 가능합니다.</span>
<br>
<br>
<br>
<div class="data_file_txt" id="data_file_txt">
<span>첨부 파일</span>
<br />
<div id="articlefileChange">
</div>
</div>
</form>
</div>
<br>
<div>
<button onclick="registerAction()" type="button" class="btn btn-sm btn-primary" id="btnSave">저장</button>
<!-- <button onclick="registerAction()" type="button" class="btn btn-sm btn-primary" id="btnSave">파입업로드 테스트</button> -->
<button onclick="location.href='/'" type="button" class="btn btn-sm btn-primary" id="btnList">목록</button>
</div>
</div>
</article>
</body>
write-js.jsp |
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<script>
//파일 현재 필드 갯수
var fileCount = 0;
//전체 업로드 갯수
var totalCount = 10;
//파일 고유 넘버
var fileNum = 0;
//첨부파일 배열
var content_files = new Array();
//파일 시퀀스들
var fileIdxs = "";
//파일 추가 클릭
function fileClick(){
$("#input_file").click();
}
//파일 change 이벤트
function fileChange(target){
var files = target.files;
//파일 배열 담기
var filesArr = Array.prototype.slice.call(files);
if(fileCount + filesArr.length > totalCount){
alert("파일은 최대 "+totalCount+"개까지 업로드 할 수 있습니다.");
return false;
}
else{
fileCount = fileCount + filesArr.length;
}
// 각각의 파일 배열담기 및 기타
filesArr.forEach(function (f) {
var reader = new FileReader();
reader.onload = function (e) {
content_files.push(f);
console.log(content_files)
$('#articlefileChange').append(
'<div id="file' + fileNum + '" onclick="fileDelete(\'file' + fileNum + '\')">'
+ '<font style="font-size:12px">' + f.name + '</font>'
+ '<img src="/images/icon_minus.png" style="width:20px; height:auto; vertical-align: middle; cursor: pointer;"/>'
+ '<div/>'
);
fileNum++;
};
reader.readAsDataURL(f);
});
//초기화 한다.
$("#input_file").val("");
}
//파일 삭제
function fileDelete(fileNum){
var no = fileNum.replace(/[^0-9]/g, "");
content_files[no].is_delete = true;
$('#' + fileNum).remove();
fileCount --;
}
//게시판 등록
function writeSubmit(){
//step2. 게시판 등록
var params = {
boardTitle : $.trim($("#boardTitle").val())
,boardContent : $.trim($("#boardContent").val())
,userId : "${sessUserInfo.userId}"
,fileIdxs : fileIdxs
}
if(params.boardTitle == ""){
alert("제목을 입력해주세요.");
return false;
}
else if(params.boardContent == ""){
alert("내용을 입력해주세요.");
return false;
}
$.ajax({
type : 'POST'
,url : '/board/write'
,dataType : 'json'
,data : JSON.stringify(params)
,contentType: 'application/json'
,success : function(result) {
alert("해당글이 정상적으로 등록되었습니다.");
location.href="/";
},
error: function(request, status, error) {
}
})
}
//파일 저장
function registerAction(){
console.log(content_files);
var form = $("form")[0];
var formData = new FormData(form);
for (var x = 0; x < content_files.length; x++) {
// 삭제 안한것만 담아 준다.
if(!content_files[x].is_delete){
formData.append("article_file", content_files[x]);
formData.append("filePath", "/main");
}
}
/*
* 파일업로드 multiple ajax처리
*/
$.ajax({
type: "POST",
enctype: "multipart/form-data",
url: "/file-upload",
data : formData,
processData: false,
contentType: false,
success: function (data) {
console.log(data)
//파일 시퀀스들
fileIdxs = data.fileIdxs;
if(data.result == "OK"){
writeSubmit();
} else
alert("서버내 오류로 처리가 지연되고있습니다. 잠시 후 다시 시도해주세요");
},
error: function (xhr, status, error) {
alert("서버오류로 지연되고있습니다. 잠시 후 다시 시도해주시기 바랍니다.");
}
});
}
</script>
update.jsp |
<%@ page language="java" session="true" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ include file="/WEB-INF/template/constants.jsp"%>
<body>
<article>
<div class="container" role="main">
<h2>게시판 상세</h2>
<form name="form" id="form" role="form" method="post" action="${pageContext.request.contextPath}/board/saveBoard">
<div class="mb-3">
<label for="title">제목</label>
<input type="text" class="form-control" id="boardTitle" name="boardTitle" placeholder="제목을 입력해 주세요" value="${boardInfo.boardTitle}" <c:if test="${sessUserInfo.authority != 'ADMIN'}">readonly</c:if>>
<input type="hidden" id="boardIdx" value="${boardIdx}" />
</div>
<div class="mb-3">
<label for="reg_id">작성자</label>
<input type="text" class="form-control" id="regId" name="regId" value="${boardInfo.regName}" readonly>
</div>
<div class="mb-3">
<label for="content">내용</label>
<textarea class="form-control" rows="5" id="boardContent" name="boardContent" placeholder="내용을 입력해 주세요" <c:if test="${sessUserInfo.authority != 'ADMIN'}">readonly</c:if>>${boardInfo.boardContent}</textarea>
</div>
<!-- 관리자가 아닐경우 -->
<c:if test="${sessUserInfo.authority != 'ADMIN'}">
<div class="mb-3">
<div class="data_file_txt" id="data_file_txt">
<span>첨부 파일</span>
<br>
<div id="articlefileChange">
<c:forEach var="boardFileInfo" items="${boardFileInfo}" varStatus="vs">
<div id="file${vs.index}">
<a href="/file-download/${boardFileInfo.fileId}">
<font style="font-size:12px">${boardFileInfo.origNm}</font>
<img src="/images/file_icon.png" style="width:20px; height:auto; vertical-align: middle; cursor: pointer;">
</a>
</div>
</c:forEach>
</div>
</div>
</div>
</c:if>
<!-- 관리자일경우 -->
<c:if test="${sessUserInfo.authority == 'ADMIN'}">
<div class="mb-3">
<form name="dataForm" id="dataForm" onsubmit="return registerAction()">
<button id="btn-upload" onclick="fileClick()" type="button" style="border: 1px solid #ddd; outline: none;">파일 추가</button>
<input id="input_file" onChange="fileChange(this)" multiple="multiple" type="file" style="display:none;">
<span style="font-size:10px; color: gray;">※첨부파일은 최대 10개까지 등록이 가능합니다.</span>
<br>
<br>
<br>
<div class="data_file_txt" id="data_file_txt">
<span>첨부 파일</span>
<br />
<div id="articlefileChange">
<c:forEach var="boardFileInfo" items="${boardFileInfo}" varStatus="vs">
<div class="attachDiv">
<a href="/file-download/${boardFileInfo.fileId}">
<font style="font-size:12px">${boardFileInfo.origNm}</font>
<img src="/images/file_icon.png" style="width:20px; height:auto; vertical-align: middle; cursor: pointer;">
</a>
<a class="beforeDeleteFile" data-attr="${boardFileInfo.fileId}">
<img src="/images/icon_minus.png" style="width:20px; height:auto; vertical-align: middle; cursor: pointer;">
</a>
</div>
</c:forEach>
</div>
</div>
</form>
</div>
</c:if>
</form>
<br>
<c:if test="${sessUserInfo.authority == 'ADMIN'}">
<div>
<button onclick="registerAction()" type="button" class="btn btn-sm btn-primary" id="btnSave">수정</button>
<button onclick="boardDelete()" type="button" class="btn btn-sm btn-danger" >삭제</button>
<button onclick="location.href='/'" type="button" class="btn btn-sm btn-primary" id="btnList">목록</button>
</div>
</c:if>
</div>
</article>
</body>
update-js.jsp |
<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<script>
//파일 현재 필드 갯수
var fileCount = 0;
//전체 업로드 갯수
var totalCount = 10;
//파일 고유 넘버
var fileNum = 0;
//첨부파일 배열
var content_files = new Array();
//파일 시퀀스들
var fileIdxs = "";
//이전에 등록한 파일 삭제 클릭시 시퀀스
var deleteFileIdxs = [];
$(document).ready(function(){
updateCtrl.init();
});
var updateCtrl = {
init : function(){
this.bindData();
this.bindEvent();
},
bindData : function(){
//게시물번호가 없을경우
if("${boardIdx}" == ""){
alert("해당 게시물은 없는 번호입니다.");
location.href="/";
}
},
bindEvent : function(){
$(".beforeDeleteFile").click(function(){
deleteFileIdxs.push($(this).attr("data-attr"));
$(this).parents(".attachDiv").remove();
});
}
}
//수정 버튼
function updateSubmit(){
var params = {
boardTitle : $.trim($("#boardTitle").val())
,boardContent : $.trim($("#boardContent").val())
,userId : "${sessUserInfo.userId}"
,boardIdx : $("#boardIdx").val()
,fileIdxs : fileIdxs
,deleteFileIdxs : deleteFileIdxs.toString()
}
console.log(params);
if(params.boardTitle == ""){
alert("제목을 입력해주세요.");
return false;
}
else if(params.boardContent == ""){
alert("내용을 입력해주세요.");
return false;
}
$.ajax({
type : 'POST'
,url : '/board/update'
,dataType : 'json'
,data : JSON.stringify(params)
,contentType: 'application/json'
,success : function(result) {
alert("해당글이 정상적으로 수정되었습니다.");
location.href="/";
},
error: function(request, status, error) {
}
})
}
//게시판 삭제하기
function boardDelete(){
var boardIdxArray = [];
boardIdxArray.push("${boardIdx}");
console.log(boardIdxArray);
if(boardIdxArray == ""){
alert("삭제할 항목을 선택해주세요.");
return false;
}
var confirmAlert = confirm('정말로 삭제하시겠습니까?');
if(confirmAlert){
$.ajax({
type : 'POST'
,url : '/board/delete'
,dataType : 'json'
,data : JSON.stringify(boardIdxArray)
,contentType: 'application/json'
,success : function(result) {
alert("해당글이 정상적으로 삭제되었습니다.");
location.href="/";
},
error: function(request, status, error) {
}
})
}
}
//파일 추가 클릭
function fileClick(){
$("#input_file").click();
}
//파일 change 이벤트
function fileChange(target){
var files = target.files;
//파일 배열 담기
var filesArr = Array.prototype.slice.call(files);
if(fileCount + filesArr.length > totalCount){
alert("파일은 최대 "+totalCount+"개까지 업로드 할 수 있습니다.");
return false;
}
else{
fileCount = fileCount + filesArr.length;
}
// 각각의 파일 배열담기 및 기타
filesArr.forEach(function (f) {
var reader = new FileReader();
reader.onload = function (e) {
content_files.push(f);
console.log(content_files)
$('#articlefileChange').append(
'<div id="file' + fileNum + '" onclick="fileDelete(\'file' + fileNum + '\')">'
+ '<font style="font-size:12px">' + f.name + '</font>'
+ '<img src="/images/icon_minus.png" style="width:20px; height:auto; vertical-align: middle; cursor: pointer;"/>'
+ '<div/>'
);
fileNum++;
};
reader.readAsDataURL(f);
});
//초기화 한다.
$("#input_file").val("");
}
//파일 저장
function registerAction(){
console.log(content_files);
var form = $("form")[0];
var formData = new FormData(form);
for (var x = 0; x < content_files.length; x++) {
// 삭제 안한것만 담아 준다.
if(!content_files[x].is_delete){
formData.append("article_file", content_files[x]);
formData.append("filePath", "/main");
}
}
/*
* 파일업로드 multiple ajax처리
*/
$.ajax({
type: "POST",
enctype: "multipart/form-data",
url: "/file-upload",
data : formData,
processData: false,
contentType: false,
success: function (data) {
console.log(data)
//파일 시퀀스들
fileIdxs = data.fileIdxs;
if(data.result == "OK"){
updateSubmit();
} else
alert("서버내 오류로 처리가 지연되고있습니다. 잠시 후 다시 시도해주세요");
},
error: function (xhr, status, error) {
alert("서버오류로 지연되고있습니다. 잠시 후 다시 시도해주시기 바랍니다.");
}
});
}
//파일 삭제
function fileDelete(fileNum){
var no = fileNum.replace(/[^0-9]/g, "");
content_files[no].is_delete = true;
$('#' + fileNum).remove();
fileCount --;
}
//이전에 등록한 파일 삭제 버튼 클릭시
function deleteFileId(target, fileId){
console.log(target);
//배열에 삭제 시퀀스 넣기
deleteFileIdxs.push(fileId);
}
</script>
리스트 화면 (로그인하지 않았을경우) |
-> 파일있는 항목은 파일유무에 이미지가 보입니다.
상세화면 (로그인하지 않았을경우) |
-> 상세화면과 업로드한 파일을 다운로드 할수 있습니다.
글쓰기 화면 |
파일추가 클릭시 |
게시판 수정화면 (관리자 로그인일경우) |
파일다운로드 |
'스프링' 카테고리의 다른 글
[Spring boot] 웹서비스 ec2에 배포하기 (RDS 데이터베이스 연동) (0) | 2021.10.06 |
---|---|
[SPRING JPA] 쿼리 파라미터 로그 남기기 p6spy (0) | 2021.09.07 |
[Spring Boot] 6. 게시판 체크박스 삭제하기(Gradle+Mybatis+멀티프로젝트+MYSQL+STS) (1) | 2021.08.13 |
[Spring Boot] 5. logback 설정 (Gradle+Mybatis+멀티프로젝트+MYSQL+STS) (0) | 2021.08.12 |
[Spring] sts4에서 jsp 사용하기 (0) | 2021.08.12 |