[spring] 공지사항 게시판 주요 기능 (파일처리 등)
by mini_min[spring] 공지사항 게시판 주요 기능 (파일처리 등)
✔️ 공지사항 게시판 주요 기능
- NoticeService 부분
✨ listNoticeTop 은 공지사항 리스트 출력
✨ 맨 아래 4개는 파일 처리 관련 Service
public interface NoticeService {
public void insertNotice(Notice dto, String pathname) throws Exception;
public int dataCount(Map<String, Object> map);
public List<Notice> listNotice(Map<String, Object> map);
public List<Notice> listNoticeTop();
public void updateHitCount(long num) throws Exception;
public Notice readNotice(long num);
public Notice preReadNotice(Map<String, Object> map);
public Notice nextReadNotice(Map<String, Object> map);
public void updateNotice(Notice dto, String pathname) throws Exception;
public void deleteNotice(long num, String pathname) throws Exception;
public void insertFile(Notice dto) throws Exception;
public List<Notice> listFile(long num);
public Notice readFile(long fileNum);
public void deleteFile(Map<String, Object> map) throws Exception;
}
✔️ FileManager
- 파일을 업로드 하기 위한 메소드이다.
: partFile 업로드할 파일정보를 가지고 있는 MultipartFile 객체
: pathname 파일을 저장할 경로
return 은 서버에 저장된 새로운 파일의 이름을 저장한다. 파일 이름이 한글이거나 하면 인코딩 해야하는 문제 등이 있을 수 있기 때문에 새로운 파일 이름으로 저장해준다고 보면 된다.
public String doFileUpload(MultipartFile partFile, String pathname) throws Exception {
String saveFilename = null;
if (partFile == null || partFile.isEmpty()) {
return null;
}
// 클라이언트가 업로드한 파일의 이름
String originalFilename = partFile.getOriginalFilename();
if (originalFilename == null || originalFilename.length() == 0) {
return null;
}
// 확장자
String fileExt = originalFilename.substring(originalFilename.lastIndexOf("."));
if (fileExt == null || fileExt.length() == 0) {
return null;
}
// 서버에 저장할 새로운 파일명을 만든다.
saveFilename = String.format("%1$tY%1$tm%1$td%1$tH%1$tM%1$tS", Calendar.getInstance())
+ System.nanoTime() + fileExt;
String fullpathname = pathname + File.separator + saveFilename;
// 업로드할 경로가 존재하지 않는 경우 폴더를 생성 한다.
File f = new File(fullpathname);
if (! f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
partFile.transferTo(f);
return saveFilename;
}
- 파일을 다운로드 하는 메소드
: saveFilename 서버에 저장된 파일이름
: originalFilename 클라이언트가 업로드한 파일이름
: pathname 파일이 저장된 경로 !!
: response 응답할 HttpServletResponse 객체
return 은 파일 파운로드 성공 여부를 보낸다.
public boolean doFileDownload(String saveFilename, String originalFilename, String pathname, HttpServletResponse resp) {
String fullpathname = pathname + File.separator + saveFilename;
try {
if (originalFilename == null || originalFilename.length() == 0) {
originalFilename = saveFilename;
}
originalFilename = new String(originalFilename.getBytes("euc-kr"), "8859_1");
File file = new File(fullpathname);
if (file.exists()) {
byte readByte[] = new byte[4096];
resp.setContentType("application/octet-stream");
resp.setHeader("Content-disposition", "attachment;filename=" + originalFilename);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
// javax.servlet.ServletOutputStream os = resp.getOutputStream();
OutputStream os = resp.getOutputStream();
int read;
while ((read = bis.read(readByte, 0, 4096)) != -1) {
os.write(readByte, 0, read);
}
os.flush();
os.close();
bis.close();
return true;
}
} catch (Exception e) {
}
return false;
}
- 파일을 zip 압축 파일로 다운로드 하는 메소드
: sources 폴더명을 포함한 서버에 저장된 압축할 파일들(경로 포함)
: originals 압축할 파일들이 압축될 때의 파일명
: zipFilename 다운로드할 때 클라이언트에 표시할 zip 파일명
: response 응답할 HttpServletResponse 객체
return 은 파일 파운로드 성공 여부이다.
public boolean doZipFileDownload(String[] sources, String[] originals, String zipFilename, HttpServletResponse resp) {
String pathname = System.getProperty("user.dir") + File.separator + "temp";
String archiveFilename;
// 파일들을 압축
archiveFilename = fileCompression(sources, originals, pathname);
if (archiveFilename == null) {
return false;
}
// 파일 다운로드
boolean b = doFileDownload(archiveFilename, zipFilename, pathname, resp);
// 압축한 zip 파일 삭제
String fullpathname = pathname + File.separator + archiveFilename;
doFileDelete(fullpathname);
return b;
}
- 파일을 압축하는 메소드
: sources 폴더명을 포함한 압축할 파일들
: originals 압축할 파일들이 압축될 때의 파일명
: pathname 압축 파일을 저장할 경로
return 은 압축된 파일명이다.
public String fileCompression(String[] sources, String[] originals, String pathname) {
String archiveFilename = null;
String fullpathname = null;
final int MAX_SIZE = 2048;
byte[] buf = new byte[MAX_SIZE];
String s;
File f;
ZipOutputStream zos = null;
FileInputStream fis = null;
try {
f = new File(pathname);
if (!f.exists()) {
f.mkdirs();
}
archiveFilename = String.format("%1$tY%1$tm%1$td%1$tH%1$tM%1$tS", Calendar.getInstance())
+ System.nanoTime() + ".zip";
fullpathname = pathname + File.separator + archiveFilename;
zos = new ZipOutputStream(new FileOutputStream(fullpathname));
int length;
for (int idx = 0; idx < sources.length; idx++) {
fis = new FileInputStream(sources[idx]);
// 압축파일에 압축되는 파일명
// zos.putNextEntry(new ZipEntry(sources[idx]));
if (originals != null && originals.length >= idx) {
if (originals[idx].indexOf(File.separator) == -1) {
s = originals[idx];
} else {
s = originals[idx].substring(originals[idx].lastIndexOf(File.separator));
}
} else {
s = sources[idx].substring(sources[idx].lastIndexOf(File.separator));
}
if (s.indexOf(File.separator) == -1) {
s = File.separator + s;
}
zos.putNextEntry(new ZipEntry(s));
length = 0;
while ((length = fis.read(buf)) > 0) {
zos.write(buf, 0, length);
}
zos.closeEntry();
fis.close();
}
fis.close();
} catch (IOException e) {
} finally {
try {
zos.closeEntry();
zos.close();
} catch (IOException e) {
}
}
f = new File(fullpathname);
if (!f.exists()) {
return null;
}
return archiveFilename;
}
✔️ ServiceImpl - 공지글 작성
공통 부분
@Autowired
private CommonDAO dao;
@Autowired
private FileManager fileManager;
📓 공지글 작성 ServiceImpl
1. seq 를 부르는 mapper 가져오기 (dto에 저장)
2. 공지글 insert mapper 가져오기 (dto 값 넣기)
3. dto.getSelectFile().isEmpty() 가 false 가 아니라면 MultipartFile mf 에 for 문으로 저장 & 파일 업로드
4. dto에 fileName, saveFileName, size 모두 저장
5. insertFile 실행하기
@Override
public void insertNotice(Notice dto, String pathname) throws Exception {
try {
long seq = dao.selectOne("notice.seq");
dto.setNum(seq);
dao.insertData("notice.insertNotice", dto);
// 파일 업로드
if(! dto.getSelectFile().isEmpty()) {
for(MultipartFile mf : dto.getSelectFile()) {
String saveFilename = fileManager.doFileUpload(mf, pathname);
if(saveFilename == null) {
continue;
}
String originalFilename = mf.getOriginalFilename();
long fileSize = mf.getSize();
dto.setOriginalFilename(originalFilename);
dto.setSaveFilename(saveFilename);
dto.setFileSize(fileSize);
dao.insertData("notice.insertFile", dto);
}
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
✔️ Controller - 공지글 작성
공통 부분
@Autowired
private NoticeService service;
@Autowired
private MyUtil myUtil;
@Autowired
private FileManager fileManager;
writeForm / writeSubmit
관리자로 로그인한 경우가 아니면 (관리자 51번 코드) notice/list 로 돌아가도록 한다.
@RequestMapping(value = "write", method = RequestMethod.GET)
public String writeForm(Model model, HttpSession session) throws Exception {
SessionInfo info = (SessionInfo) session.getAttribute("member");
if (info.getMembership() < 51) {
return "redirect:/notice/list";
}
model.addAttribute("mode", "write");
return ".notice.write";
}
insert 하기 위해서 pathname 이 필요하기 때문에 root 경로를 불러서 이용해 pathname 을 만들어서 insert 해준다.
ServiceImpl 에서 insertNotice 할 때 saveFileName 을 FileManger 클래스의 doFileUpload 메소드로 만드는데 이때 pathname 이 필요하다. 서버에 저장할 새로운 파일명을 만들어서 pathname + File.separator + saveFilename; 가 합쳐진 fullpathname 을 생성한다.
@RequestMapping(value = "write", method = RequestMethod.POST)
public String writeSubmit(Notice dto, HttpSession session) throws Exception {
SessionInfo info = (SessionInfo) session.getAttribute("member");
if (info.getMembership() < 51) {
return "redirect:/notice/list";
}
try {
String root = session.getServletContext().getRealPath("/");
String pathname = root + "uploads" + File.separator + "notice";
dto.setUserId(info.getUserId());
service.insertNotice(dto, pathname);
} catch (Exception e) {
}
return "redirect:/notice/list";
}
✔️ ServiceImpl - 리스트
dataCount
dataCount mapper 는 keyword 가 있는 경우(검색 기능을 이용한 경우)를 고려해 키워드 검색 where 조건을 붙여서 만든다.
@Override
public int dataCount(Map<String, Object> map) {
int result = 0;
try {
result = dao.selectOne("notice.dataCount", map);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
📓 공지글 리스트 ServiceImpl
1. 공지글 리스트와 공지 등록된 top공지글 리스트를 가져온다.
@Override
public List<Notice> listNotice(Map<String, Object> map) {
List<Notice> list = null;
try {
list = dao.selectList("notice.listNotice", map);
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
@Override
public List<Notice> listNoticeTop() {
List<Notice> list = null;
try {
list = dao.selectList("notice.listNoticeTop");
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
✔️ Controller - 공지글 리스트
필요한 것
page 파라미터 (디폴트 1) int current_page
검색조건 파라미터 (디폴트 all) String condition
키워드 파라미터 (디폴트 "") String keyword
HttpServletRequest req (getMethod 판단, contextPath 구할 때 필요)
Model model
1. GET 방식에서 keyword decode
2. 전체 페이지 수를 구하기 위해 필요한 것 - dataCount
dataCount mapper 에 들어가는 map 파라미터를 만든다. (물론 키워드, 컨티션 모두 추가한 상태로)
dataCount 가 구해지면 myUtil 에 paging 메소드에 dataCount 와 size 를 넣어서 전체 페이지수를 구한다.
3. 현재 페이지 수가 전체 페이지 수 보다 커지지 않도록 조절한다.
4. offset 까지 구하고 글 리스트를 구해는 service.listNotice(map); 호출
5. Date 객체로 현재 시간을 endDate 로 만들고 게시글이 등록된 reg_date 를 begindate 로 저장한다. ✨최신 게시글을 처리하기 위해
6. endDate - beginDate 의 차이를 dto.gap 으로 저장한다.
== listUrl, query, articleUrl 을 만들어서 paging 처리 및 addAttribute 로 넘긴다.
@RequestMapping(value = "list")
public String list(@RequestParam(value = "page", defaultValue = "1") int current_page,
@RequestParam(defaultValue = "all") String condition,
@RequestParam(defaultValue = "") String keyword,
HttpServletRequest req,
Model model) throws Exception {
int size = 10; // 한 화면에 보여주는 게시물 수
int total_page = 0;
int dataCount = 0;
if (req.getMethod().equalsIgnoreCase("GET")) { // GET 방식인 경우
keyword = URLDecoder.decode(keyword, "utf-8");
}
// 전체 페이지 수
Map<String, Object> map = new HashMap<String, Object>();
map.put("condition", condition);
map.put("keyword", keyword);
dataCount = service.dataCount(map);
if (dataCount != 0) {
total_page = myUtil.pageCount(dataCount, size);
}
// 다른 사람이 자료를 삭제하여 전체 페이지수가 변화 된 경우
if (total_page < current_page) {
current_page = total_page;
}
// 1페이지인 경우 공지리스트 가져오기
List<Notice> noticeList = null;
if (current_page == 1) {
noticeList = service.listNoticeTop();
}
// 리스트에 출력할 데이터를 가져오기
int offset = (current_page - 1) * size;
if(offset < 0) offset = 0;
map.put("offset", offset);
map.put("size", size);
// 글 리스트
List<Notice> list = service.listNotice(map);
Date endDate = new Date();
long gap;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (Notice dto : list) {
Date beginDate = sdf.parse(dto.getReg_date());
// 차이(시간)
gap = (endDate.getTime() - beginDate.getTime()) / (60*60*1000);
dto.setGap(gap);
dto.setReg_date(dto.getReg_date().substring(0, 10));
}
String cp = req.getContextPath();
String query = "";
String listUrl = cp + "/notice/list";
String articleUrl = cp + "/notice/article?page=" + current_page;
if (keyword.length() != 0) {
query = "condition=" + condition + "&keyword=" + URLEncoder.encode(keyword, "utf-8");
}
if (query.length() != 0) {
listUrl = cp + "/notice/list?" + query;
articleUrl = cp + "/notice/article?page=" + current_page + "&" + query;
}
String paging = myUtil.paging(current_page, total_page, listUrl);
model.addAttribute("noticeList", noticeList);
model.addAttribute("list", list);
model.addAttribute("page", current_page);
model.addAttribute("dataCount", dataCount);
model.addAttribute("size", size);
model.addAttribute("total_page", total_page);
model.addAttribute("paging", paging);
model.addAttribute("articleUrl", articleUrl);
model.addAttribute("condition", condition);
model.addAttribute("keyword", keyword);
return ".notice.list";
}
+ zip 다운로드
ServiceImpl 부분
@Override
public List<Notice> listFile(long num) {
List<Notice> listFile = null;
try {
listFile = dao.selectList("notice.listFile", num);
} catch (Exception e) {
e.printStackTrace();
}
return listFile;
}
Controller 부분 필요한 것
게시글 번호 long num
HttpServletResponse resp,
HttpSession session
1. 파일을 불러오기 위해 pathname 이 필요하다.
2. 해당 게시글에 저장된 파일 리스트를 불러오기 위해 service.listFile(num) 호출
3. 만약 파일 리스트가 있다면, zipFilename 은 게시글 번호.zip 으로 설정
파일리스트의 크기 만큼 for 문을 돌린다.
4. source 배열에는 저장된 파일 이름의 주소값을 넣고 originals 배열에는 기존 파일의 이름으로 된 주소값을 넣는다.
5. fileManager 클래스에서 doZipFileDownload 메소드를 부른다. (파일들을 zip 으로 압축해서 다운로드하게 한다)
해당 메소드는 상단에 있으니 참고하기
6. 파일 다운로드가 성공하면 ture 반환 (압축한 파일을 자동 다운로드 되게 해주고 이후 압축한 파일은 삭제한다.)
@RequestMapping(value = "zipdownload")
public void zipdownload(@RequestParam long num,
HttpServletResponse resp,
HttpSession session) throws Exception {
String root = session.getServletContext().getRealPath("/");
String pathname = root + "uploads" + File.separator + "notice";
boolean b = false;
List<Notice> listFile = service.listFile(num);
if(listFile.size() > 0) {
String[] sources = new String[listFile.size()];
String[] originals = new String[listFile.size()];
String zipFilename = num+".zip";
for(int idx = 0; idx < listFile.size(); idx++) {
sources[idx] = pathname+File.separator+listFile.get(idx).getSaveFilename();
originals[idx] = pathname+File.separator+listFile.get(idx).getOriginalFilename();
}
b = fileManager.doZipFileDownload(sources, originals, zipFilename, resp);
}
if(! b) {
try {
resp.setContentType("text/html; charset=utf-8");
PrintWriter out = resp.getWriter();
out.print("<script>alert('파일 다운로드가 불가능합니다. !'); history.back();</script>");
} catch (Exception e) {
}
}
}
✔️ ServiceImpl - 공지글 보기
공통 부분
게시글 내용 (해당 게시글 번호 필요)
이전글, 다음글 제목 (검색 기능 고려하기)
조회수 증가 (해당 게시글 번호 필요)
@Override
public Notice readNotice(long num) {
Notice dto = null;
try {
dto = dao.selectOne("notice.readNotice", num);
} catch (Exception e) {
e.printStackTrace();
}
return dto;
}
@Override
public void updateHitCount(long num) throws Exception {
try {
dao.updateData("notice.updateHitCount", num);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
@Override
public Notice preReadNotice(Map<String, Object> map) {
Notice dto = null;
try {
dto = dao.selectOne("notice.preReadNotice", map);
} catch (Exception e) {
e.printStackTrace();
}
return dto;
}
@Override
public Notice nextReadNotice(Map<String, Object> map) {
Notice dto = null;
try {
dto = dao.selectOne("notice.nextReadNotice", map);
} catch (Exception e) {
e.printStackTrace();
}
return dto;
}
✔️ Controller - 공지글 리스트
필요한 것들
게시글 번호 long num,
페이지 번호 String page,
검색조건 파라미터 (기본 all) String condition,
키워드 파라미터 (기본 "") String keyword
Model model
1. keyword 디코딩 해주기
2. 검색 조건이 있는 경우에서 page 파라미터를 포함한 query 를 만들어준다.
3. 조회수 증가 updateHitCount(num) 호출
4. service.readNotice(num) 를 호출해서 dto 에 저장하는데 만약 해당 게시글이 없으면 notice/list + query 로 리다이렉트 넘겨준다.
5. 이전글, 다음글은 map 에 조건, 키워드, 게시글 번호를 저장해서 서비스를 호출하여 dto 에 저장한다.
6. 파일 또한 서비스를 호출해서 list 객체에 저장한다.
7. 모든 파라미터, 데이터를 addAttribute로 저장해서 넘긴다.
@RequestMapping(value = "article")
public String article(@RequestParam long num,
@RequestParam String page,
@RequestParam(defaultValue = "all") String condition,
@RequestParam(defaultValue = "") String keyword,
Model model) throws Exception {
keyword = URLDecoder.decode(keyword, "utf-8");
String query = "page=" + page;
if (keyword.length() != 0) {
query += "&condition=" + condition + "&keyword=" + URLEncoder.encode(keyword, "UTF-8");
}
service.updateHitCount(num);
Notice dto = service.readNotice(num);
if (dto == null) {
return "redirect:/notice/list?" + query;
}
dto.setContent(dto.getContent().replaceAll("\n", "<br>"));
// 이전 글, 다음 글
Map<String, Object> map = new HashMap<String, Object>();
map.put("condition", condition);
map.put("keyword", keyword);
map.put("num", num);
Notice preReadDto = service.preReadNotice(map);
Notice nextReadDto = service.nextReadNotice(map);
// 파일
List<Notice> listFile = service.listFile(num);
model.addAttribute("dto", dto);
model.addAttribute("preReadDto", preReadDto);
model.addAttribute("nextReadDto", nextReadDto);
model.addAttribute("listFile", listFile);
model.addAttribute("page", page);
model.addAttribute("query", query);
return ".notice.article";
}
+ 파일 다운로드
Service 부분
파일 번호를 통해 파일을 읽어낸다.
@Override
public Notice readFile(long fileNum) {
Notice dto = null;
try {
dto = dao.selectOne("notice.readFile", fileNum);
} catch (Exception e) {
e.printStackTrace();
}
return dto;
}
Controller 부분
필요한 것들
파일 번호 long fileNum (article 에서 글 내용을 불러올 때 파일 정보들도 불러왔다.)
HttpServletResponse resp,
HttpSession session
1. 파일을 fileManager 클래스를 통해 다운 받으려면 pathname 이 필요하다(다운받을 파일의 경로)
2. service.readFile(fileNum) 로 파일 정보를 읽어 온다.
3. 파일이 존재하는 경우 saveFilename, originalFilename 로 저장한다.
4. fileManager.doFileDownload 메소드를 호출해서 파일을 다운 받는다.
@RequestMapping(value = "download")
public void download(@RequestParam long fileNum,
HttpServletResponse resp,
HttpSession session) throws Exception {
String root = session.getServletContext().getRealPath("/");
String pathname = root + "uploads" + File.separator + "notice";
boolean b = false;
Notice dto = service.readFile(fileNum);
if(dto != null) {
String saveFilename = dto.getSaveFilename();
String originalFilename = dto.getOriginalFilename();
b = fileManager.doFileDownload(saveFilename, originalFilename, pathname, resp);
}
if(! b) {
try {
resp.setContentType("text/html; charset=utf-8");
PrintWriter out = resp.getWriter();
out.print("<script>alert('파일 다운로드가 불가능합니다. !'); history.back();</script>");
} catch (Exception e) {
}
}
}
'Spring' 카테고리의 다른 글
[spring] 트랜잭션 처리 주의사항 (0) | 2022.11.26 |
---|---|
[spring] WebSocket 웹 소켓 실시간 채팅 (0) | 2022.11.22 |
[Mybatis] Mybatis 개념, 내용 공부 (0) | 2022.11.19 |
[spring] MemberController (RedirectAttributes 사용) (0) | 2022.11.19 |
[spring] HandlerInterceptor 개념 + 적용 (0) | 2022.11.19 |
블로그의 정보
개발자 미니민의 개발로그
mini_min