스프링부트 (2.7) & mybatis
1. pom.xml
<!-- 썸네일 라이브러리 --> <dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.17</version> </dependency> <!-- 파일 타입 체크 라이브러리 --> <dependency> <groupId>org.apache.tika</groupId> <artifactId>tika-parsers</artifactId> <version>2.4.1</version> <type>pom</type> </dependency> <dependency> <groupId>org.apache.tika</groupId> <artifactId>tika-core</artifactId> <version>2.4.1</version> </dependency>
2.TABLE
CREATE TABLE `T_UPLOAD_FILE` ( `UPLOAD_FILE_SEQ` bigint NOT NULL AUTO_INCREMENT COMMENT '파일테이블 PK', `BOARD_SEQ` bigint null COMMENT '게시판 테이블 PK', `BOARD_TYPE` varchar(255) DEFAULT 'NONE' COMMENT '게시판 종류', `PATHNAME` VARCHAR(100) NOT NULL COMMENT '전체경로', `FILENAME` VARCHAR(50) NOT NULL COMMENT '파일명' , `ORIGINAL_FILENAME` VARCHAR(100) NOT NULL COMMENT '원본 파일명' , `SIZE` INT(11) NOT NULL COMMENT '파일크기', `CONTENT_TYPE` VARCHAR(50) NOT NULL COMMENT '컨텐츠 종류' , `RESOURCE_PATHNAME` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '리소스 파일경로' , `THUMBNAIL_PATHNAME` VARCHAR(100) NULL COMMENT '썸네일 전체경로', `THUMBNAIL_RESOURCE_PATHNAME` VARCHAR(100) NULL DEFAULT '' COMMENT '썸네일 리소스파일경로' , `REG_DATE` timestamp NOT null DEFAULT CURRENT_TIMESTAMP() COMMENT '등록일', PRIMARY KEY (`UPLOAD_FILE_SEQ`) USING BTREE )ENGINE=InnoDB COLLATE='utf8mb4_unicode_ci' COMMENT='파일';
3.UploadFile
import java.util.Date; import kr.net.macaronics.mvc.domain.enums.BoardType; import lombok.Getter; import lombok.Setter; @Getter @Setter public class UploadFile { private int uploadFileSeq; //파일테이블 PK private Integer boardSeq; //게시판 테이블 PK private BoardType boardType; //게시판 종류 private String pathname; //전체경로 private String filename; //파일명 private String originalFilename; //원본 파일명 private int size; //파일크기 private String contentType; //컨텐츠 종류 private String resourcePathname; //리소스 파일경로 private String thumbnailPathname; //썸네일 전체경로 private String thumbnailResourcePathname; //썸네일 리소스파일경로 private Date regDate; //등록일 }
4.UploadFileDTO
import java.util.Date; import kr.net.macaronics.mvc.domain.enums.BoardType; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; /** 파일 데이터를 가져올시 파라미터 */ @Getter @AllArgsConstructor @NoArgsConstructor @ToString public class UploadFileDTO { private int uploadFileSeq; //파일테이블 PK private Integer boardSeq; //게시판 테이블 PK private BoardType boardType; //게시판 종류 private String pathname; //전체경로 private String filename; //파일명 private String originalFilename; //원본 파일명 private int size; //파일크기 private String contentType; //컨텐츠 종류 private String resourcePathname; //리소스 파일경로 private String thumbnailPathname; //썸네일 전체경로 private String thumbnailResourcePathname; //썸네일 리소스파일경로 private Date regDate; //등록일 }
5. UploadFileInsertDTO
import kr.net.macaronics.mvc.domain.enums.BoardTypeInsert; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; /** 등록시 파라미터 */ @Getter @AllArgsConstructor @NoArgsConstructor @ToString public class UploadFileInsertDTO { private int uploadFileSeq; //파일테이블 PK private Integer boardSeq; //게시판 테이블 PK private BoardTypeInsert boardType; //게시판 종류 private String pathname; //전체경로 private String filename; //파일명 private String originalFilename; //원본 파일명 private int size; //파일크기 private String contentType; //컨텐츠 종류 private String resourcePathname; //리소스 파일경로 private String thumbnailPathname; //썸네일 전체경로 private String thumbnailResourcePathname; //썸네일 리소스파일경로 @Builder public UploadFileInsertDTO (Integer boardSeq ,BoardTypeInsert boardType, String pathname, String filename, String originalFilename, int size, String contentType, String resourcePathname, String thumbnailPathname, String thumbnailResourcePathname) { this.boardSeq = boardSeq; this.boardType = boardType; this.pathname = pathname; this.filename = filename; this.originalFilename = originalFilename; this.size = size; this.contentType = contentType; this.resourcePathname = resourcePathname; this.thumbnailPathname = thumbnailPathname; this.thumbnailResourcePathname = thumbnailResourcePathname; } }
6. 컨트롤러
1)UploadFileApiController
import java.io.File; import java.io.FileInputStream; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.annotations.Delete; import org.springframework.ui.Model; import org.springframework.util.FileCopyUtils; 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.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import kr.net.macaronics.configuration.GlobalConfig; import kr.net.macaronics.configuration.exception.BaseException; import kr.net.macaronics.configuration.http.BaseResponse; import kr.net.macaronics.configuration.http.BaseResponseCode; import kr.net.macaronics.mvc.domain.dto.UploadFileDTO; import kr.net.macaronics.mvc.domain.dto.UploadFileInsertDTO; import kr.net.macaronics.mvc.domain.enums.ThumbnailType; import kr.net.macaronics.mvc.service.UploadFileService; import kr.net.macaronics.utils.DownloadView; import kr.net.macaronics.utils.pagination2.MysqlPageMaker; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.coobird.thumbnailator.Thumbnails; @Slf4j @RestController @RequestMapping("/api/file") @Api(tags="파일 업로드 API") @RequiredArgsConstructor public class UploadFileApiController { private final GlobalConfig config; private final UploadFileService uploadFileService; @PostMapping("/save") @ApiOperation(value="테스트-파일업로드", notes = "파일업로드 샘플1") public BaseResponse<Boolean> save(@RequestParam("uploadFile") MultipartFile multipartFile) throws Exception { if(multipartFile==null || multipartFile.isEmpty()) { throw new BaseException(BaseResponseCode.DATA_IS_NULL.name()); } //날짜폴더를 추가 String currentDate=new SimpleDateFormat("yyyyMMdd").format(Calendar.getInstance().getTime()); log.debug("config : {} ", config); String uploadFilePath=config.getUploadFilePath()+currentDate+"/"; log.info("uploadFilePath : {} " , uploadFilePath); String prefix=multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf(".")+1, multipartFile.getOriginalFilename().length()); String filename=UUID.randomUUID().toString()+"."+prefix; File folder =new File(uploadFilePath); if(!folder.isDirectory()) { folder.mkdirs(); } String pathname=uploadFilePath +filename; String resourcePathname =config.getUploadResourcePath()+currentDate+"/"+filename; File dest=new File(pathname); log.info("dest : {}", dest); multipartFile.transferTo(dest); //파일업로드 된 후 DB에 저장 UploadFileInsertDTO uploadFileDTO=UploadFileInsertDTO.builder() .contentType(multipartFile.getContentType()) //컨텐츠 종류 .originalFilename(multipartFile.getOriginalFilename()) //원본파일명 .filename(filename) //저장파일명 .pathname(pathname) //실제파일 저장경로 .size((int)multipartFile.getSize()) //파일크기 .resourcePathname(resourcePathname) //static resource 접근 경로 .build(); uploadFileService.save(uploadFileDTO); return new BaseResponse<Boolean>(true); } /** * 업로드된 파일 썸네일 만들기 공통 throws Exception * http://localhost:8080/api/file/thumbnail/make/1/WEB_MAIN * */ @GetMapping("/thumbnail/make/{uploadFileSeq}/{thumbnailType}") @ApiOperation(value="테스트-업로드된 파일 썸네일 만들기", notes = "파일업로드 샘플2") public BaseResponse<String> thumb(@PathVariable int uploadFileSeq, @PathVariable ThumbnailType thumbnailType ) throws Exception{ UploadFileDTO uploadFileDTO=uploadFileService.get(uploadFileSeq); String pathname=uploadFileDTO.getPathname(); String thumbnailPathname=uploadFileDTO.getPathname().replace(".", thumbnailType.width()+"_"+thumbnailType.height()+"."); File thumbanilFile=new File(thumbnailPathname); if(!thumbanilFile.isFile()) { Thumbnails.of(new File(pathname)) .size(thumbnailType.width(), thumbnailType.height()) .toFile(new File(thumbnailPathname)); } return new BaseResponse<String>(uploadFileDTO.getResourcePathname().replace(".", thumbnailType.width()+"_"+thumbnailType.height()+".")); } /**thumbnailator 라이브러리를 통한 업로드된 썸네일 이미지 출력 * http://localhost:8080/api/file/thumbnail/output/1/WIDTH_200 * */ @GetMapping("/thumbnail/output/{uploadFileSeq}/{thumbnailType}") @ApiOperation(value="테스트-thumbnailator 라이브러리를 통한 업로드된 썸네일 이미지 출력", notes = "파일업로드 샘플3") public void thumbnailOutput(@PathVariable int uploadFileSeq, @PathVariable ThumbnailType thumbnailType, HttpServletResponse response ) throws Exception{ UploadFileDTO uploadFileDTO=uploadFileService.get(uploadFileSeq); String pathname=uploadFileDTO.getPathname(); String thumbanilPathname=uploadFileDTO.getPathname().replace(".", thumbnailType.width()+"_"+thumbnailType.height()+"."); File thumbanilFile=new File(thumbanilPathname); if(!thumbanilFile.isFile()) { Thumbnails.of(new File(pathname)) .size(thumbnailType.width(), thumbnailType.height()) .toFile(new File(thumbanilPathname)); } response.setContentType(uploadFileDTO.getContentType()); FileCopyUtils.copy(new FileInputStream(thumbanilFile), response.getOutputStream()); } /*****************************************************************************************************************/ /*****************************************************************************************************************/ /*****************************************************************************************************************/ /** 파일업로드 - 이미지일 경우 썸네일등록 */ @PostMapping("/fileSave") @ApiOperation(value="파일 등록 - 이미지일 경우 썸네일등록", notes = "서비스에서 작업 - MimeType 파일 위조 변조 체크") public BaseResponse<UploadFileDTO> fileSave(@RequestParam("uploadFile") MultipartFile multipartFile) throws Exception { int uploadFileSeq=uploadFileService.fileSave(multipartFile, null); return new BaseResponse<UploadFileDTO>(uploadFileService.get(uploadFileSeq)); } /** 멀티파일업로드 - 이미지일 경우 썸네일등록 */ @PostMapping("/multiFileSave") @ApiOperation(value="멀티파일 등록 - 이미지일 경우 썸네일등록", notes = "멀티파일 등록") public BaseResponse<List<UploadFileDTO>> multiFileSave(@RequestParam("uploadFile") List<MultipartFile> multipartFile) throws Exception { List<UploadFileDTO> uploadFileDTOList=uploadFileService.multiFileSave(multipartFile, null); return new BaseResponse<List<UploadFileDTO>>(uploadFileDTOList); } /** 전체 파일목록 불러오기 * http://localhost:8080/api/file/fileList * */ @GetMapping({"/fileList"}) @ApiOperation(value="전체 파일목록 불러오기", notes="전체 파일목록 불러오기 ") public BaseResponse<List<UploadFileDTO>> fileList( @ApiParam MysqlPageMaker pageMaker ) throws Exception{ Map<String, Object> map=new HashMap<>(); map.put("pageMaker", pageMaker); map.put("boardSeq", ""); pageMaker.setTotalCount(uploadFileService.getTotalCount(map)); return new BaseResponse<List<UploadFileDTO>>(uploadFileService.fileList(map), pageMaker); } /** 게시판 pk 파일목록 불러오기 * http://localhost:8080/api/file/boarddPkFileList/1 * */ @GetMapping({"/boarddPkFileList/{boardSeq}"}) @ApiOperation(value="게시판 번호에 따른 파일 목록 불러오기", notes="boardSeq 파일목록 불러오기 " ) public BaseResponse<List<UploadFileDTO>> boarddPkFileList( @ApiParam MysqlPageMaker pageMaker, @ApiParam @PathVariable int boardSeq ) throws Exception{ Map<String, Object> map=new HashMap<>(); map.put("pageMaker", pageMaker); map.put("boardSeq", boardSeq); int totalCount =uploadFileService.getTotalCount(map); pageMaker.setTotalCount(totalCount); return new BaseResponse<List<UploadFileDTO>>(uploadFileService.bordPkFileList(map), pageMaker); } /** 게시판 boardType 별 파일목록 불러오기 * */ @GetMapping({"/boardTypeFileList/{boardType}"}) @ApiOperation(value="게시판 타입별 파일 목록 불러오기", notes="boardType 별 파일목록 불러오기 ") public BaseResponse<List<UploadFileDTO>> boardTypeFileList( @ApiParam MysqlPageMaker pageMaker, @ApiParam @PathVariable String boardType ) throws Exception{ Map<String, Object> map=new HashMap<>(); map.put("pageMaker", pageMaker); map.put("boardSeq", ""); map.put("searchType", boardType); log.info("map {} ", map.toString()); int totalCount =uploadFileService.getTotalCount(map); pageMaker.setTotalCount(totalCount); return new BaseResponse<List<UploadFileDTO>>(uploadFileService.bordPkFileList(map), pageMaker); } /** 파일정보가져오기 */ @GetMapping("/getFileInfo/{uploadFileSeq}") @ApiOperation(value="파일 정보가져오기", notes = "파일 다운로드") public BaseResponse<UploadFileDTO> getFileInfo(@PathVariable int uploadFileSeq , Model model) throws Exception { UploadFileDTO uploadFileDTO=uploadFileService.get(uploadFileSeq); if(uploadFileDTO==null) { throw new BaseException("해당 파일이 존재하지 않습니다."); } return new BaseResponse<UploadFileDTO>(uploadFileDTO); } /** 파일 다운로드 */ @GetMapping(value = "/fileDownload/{uploadFileSeq}") @ApiOperation(value="파일 다운로드", notes = "파일 다운로드") public DownloadView fileDownload(@PathVariable int uploadFileSeq , Model model) throws Exception { UploadFileDTO uploadFileDTO=uploadFileService.get(uploadFileSeq); if(uploadFileDTO==null) { throw new BaseException("해당 파일이 존재하지 않습니다."); } File file = new File(uploadFileDTO.getPathname()); model.addAttribute("downloadFile", file); model.addAttribute("orignalName", uploadFileDTO.getOriginalFilename()); DownloadView downloadView=new DownloadView(); return downloadView; } /** 파일 삭제 * http://localhost:8080/api/file/fileDelate/1 * */ @DeleteMapping("/fileDelate/{uploadFileSeq}") @ApiOperation(value="파일 삭제", notes = "파일 삭제") public BaseResponse<String> fileDelete(@PathVariable int uploadFileSeq) throws Exception{ uploadFileService.fileDelete(uploadFileSeq); return new BaseResponse<String>("success", BaseResponseCode.SUCCESS, "삭제 처리되었습니다."); } }
2) UploadFileController
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/file") public class UploadFileController { /** 파일업로드 테스트 페이지 */ @GetMapping("/fileUpload") public String fileUpload() { return "file_upload"; } /** 파일 목록 페이지 */ @GetMapping("/fileList") public String fileList() { return "file_list"; } }
7.UploadFileService
import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import kr.net.macaronics.configuration.GlobalConfig; import kr.net.macaronics.configuration.exception.BaseException; import kr.net.macaronics.configuration.http.BaseResponseCode; import kr.net.macaronics.mvc.domain.dto.BoardDTO; import kr.net.macaronics.mvc.domain.dto.BoardInsertDTO; import kr.net.macaronics.mvc.domain.dto.UploadFileDTO; import kr.net.macaronics.mvc.domain.dto.UploadFileInsertDTO; import kr.net.macaronics.mvc.domain.enums.BoardTypeInsert; import kr.net.macaronics.mvc.domain.enums.ThumbnailType; import kr.net.macaronics.mvc.repository.UploadFileRepository; import kr.net.macaronics.utils.FileFilter; import kr.net.macaronics.utils.pagination2.MysqlPageMaker; import lombok.extern.slf4j.Slf4j; import net.coobird.thumbnailator.Thumbnails; /** 업로드파일 Service */ @Service @Slf4j public class UploadFileService { @Autowired private UploadFileRepository uploadFileRepository; @Autowired private GlobalConfig config; /** 등록처리 */ public int save(UploadFileInsertDTO uploadFileInsertDTO) throws Exception{ return uploadFileRepository.save(uploadFileInsertDTO); } public UploadFileDTO get(int uploadFileSeq) throws Exception { return uploadFileRepository.get(uploadFileSeq); } public int fileSave(MultipartFile multipartFile, BoardInsertDTO boardInsertDTO) throws Exception { if(multipartFile==null || multipartFile.isEmpty()) { throw new BaseException(BaseResponseCode.DATA_IS_NULL.name()); } Integer boardSeq=null; //게시판 업로드일 파일 경우 - 게시판 테이블 PK BoardTypeInsert boardType=BoardTypeInsert.NONE; //게시판 타입 if(boardInsertDTO!=null) { boardSeq=boardInsertDTO.getBoardSeq(); boardType=boardInsertDTO.getBoardType(); } //1.업로드 가능한지 파일인지 체크 if(!FileFilter.isPermisionFileMimeType(multipartFile)) { throw new BaseException("업로드할 수 없는 파일입니다."); }; //2.날짜폴더를 추가 - 이미지가 아닌파일은 files 디렉토리에 저장 String currentDate=new SimpleDateFormat("yyyyMMdd").format(Calendar.getInstance().getTime()); String uploadFilePath=config.getUploadFilePath()+"files/"+currentDate+"/"; //3.이미지파일 체크 - 이미지파일은 images 디렉토리에 저장 boolean isImage=false; if(FileFilter.validImgFile(multipartFile)) { isImage=true; uploadFilePath=config.getUploadFilePath()+"images/"+currentDate+"/"; } //4.랜덤 파일명 생성 및 디렉토리 생성 String prefix=multipartFile.getOriginalFilename(). substring(multipartFile.getOriginalFilename().lastIndexOf(".")+1, multipartFile.getOriginalFilename().length()); String filename=UUID.randomUUID().toString()+"."+prefix; File folder =new File(uploadFilePath); if(!folder.isDirectory()) { folder.mkdirs(); } //5.URL 주소에서 보여줄 파일경로 String pathname=uploadFilePath +filename; String resourcePathname =config.getUploadResourcePath()+(isImage?"images/":"files/") +currentDate+"/"+filename; File dest=new File(pathname); //6.파일 생성 log.info("dest : {}", dest); multipartFile.transferTo(dest); String thumbnailPathname=null; //썸네일 전체경로 String thumbnailResourcePathname=null; //썸네일 리소스파일경로 //7.업로드 파일일 이미지 일경우 썸네일 생성 if(isImage){ thumbnailPathname=pathname.replace(".", "_"+ThumbnailType.WEB_MAIN.width()+"_"+ThumbnailType.WEB_MAIN.height()+"."); File thumbanilFile=new File(thumbnailPathname); if(!thumbanilFile.isFile()) { Thumbnails.of(new File(pathname)) .size(ThumbnailType.WEB_MAIN.width(), ThumbnailType.WEB_MAIN.height()) .toFile(new File(thumbnailPathname)); thumbnailResourcePathname =resourcePathname.replace(".", "_"+ThumbnailType.WEB_MAIN.width()+"_"+ThumbnailType.WEB_MAIN.height()+"."); } } //9.UploadFileInsertDTO 객체에 저장 UploadFileInsertDTO uploadFileInsertDTO=UploadFileInsertDTO.builder() .boardSeq(boardSeq) .boardType(boardType) .contentType(multipartFile.getContentType()) //컨텐츠 종류 .originalFilename(multipartFile.getOriginalFilename()) //원본파일명 .filename(filename) //저장파일명 .pathname(pathname) //실제파일 저장경로 .size((int)multipartFile.getSize()) //파일크기 .resourcePathname(resourcePathname) //static resource 접근 경로 .thumbnailPathname(thumbnailPathname) .thumbnailResourcePathname(thumbnailResourcePathname) .build(); uploadFileRepository.save(uploadFileInsertDTO); //9.파일업로드 된 후 DB에 저장후 반화 return uploadFileInsertDTO.getUploadFileSeq(); } public int getTotalCount(Map<String, Object> map) throws Exception { return uploadFileRepository.getTotalCount(map); } public List<UploadFileDTO> fileList(Map<String, Object> map) throws Exception { return uploadFileRepository.fileList(map); } public List<UploadFileDTO> bordPkFileList(Map<String, Object> map) throws Exception { return uploadFileRepository.bordPkFileList(map); } public void fileDelete(int uploadFileSeq) throws Exception { UploadFileDTO uploadFileDTO=uploadFileRepository.get(uploadFileSeq); if(uploadFileDTO==null) { throw new BaseException("해당 파일이 존재하지 않습니다."); } File file=new File(uploadFileDTO.getPathname()); if(file.exists())file.delete(); else throw new BaseException("해당 파일이 존재하지 않습니다."); //썸네일 삭제 if(!StringUtils.isEmpty(uploadFileDTO.getThumbnailPathname())) { File thumbnailFile=new File(uploadFileDTO.getThumbnailPathname()); if(thumbnailFile.exists()) thumbnailFile.delete(); else throw new BaseException("해당 파일이 존재하지 않습니다."); } //DB에서 삭제처리 uploadFileRepository.fileDelete(uploadFileSeq); } public List<UploadFileDTO> multiFileSave(List<MultipartFile> multipartFile, BoardInsertDTO boardInsertDTO) throws Exception { List<Integer> uploadFileSeq=new ArrayList<Integer>(); for(MultipartFile multiFile : multipartFile) { uploadFileSeq.add(fileSave(multiFile, null)); } return uploadFileRepository.fileSelectList(uploadFileSeq); } }
8.UploadFileRepository
import java.util.List; import java.util.Map; import org.springframework.stereotype.Repository; import kr.net.macaronics.mvc.domain.dto.UploadFileDTO; import kr.net.macaronics.mvc.domain.dto.UploadFileInsertDTO; /** 업로드 파일 Repository */ @Repository public interface UploadFileRepository { public int save(UploadFileInsertDTO uploadFileInsertDTO) throws Exception; public UploadFileDTO get(int uploadFileSeq) throws Exception; public int getTotalCount(Map<String, Object> map) throws Exception; public List<UploadFileDTO> fileList(Map<String, Object> map) throws Exception; public List<UploadFileDTO> bordPkFileList(Map<String, Object> map) throws Exception; public void fileDelete(int uploadFileSeq) throws Exception; public List<UploadFileDTO> fileSelectList(List<Integer> uploadFileSeq) throws Exception; }
9.UploadFile.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"> <mapper namespace="kr.net.macaronics.mvc.repository.UploadFileRepository"> <insert id="save" parameterType="kr.net.macaronics.mvc.domain.dto.UploadFileInsertDTO" useGeneratedKeys="true" keyProperty="uploadFileSeq"> INSERT INTO T_UPLOAD_FILE (BOARD_SEQ , BOARD_TYPE ,PATHNAME, FILENAME, ORIGINAL_FILENAME, `SIZE`, CONTENT_TYPE, RESOURCE_PATHNAME , THUMBNAIL_PATHNAME, THUMBNAIL_RESOURCE_PATHNAME) VALUES(#{boardSeq}, #{boardType}, #{pathname}, #{filename}, #{originalFilename}, #{size}, #{contentType}, #{resourcePathname} , #{thumbnailPathname}, #{thumbnailResourcePathname}) </insert> <select id="get" resultType="kr.net.macaronics.mvc.domain.dto.UploadFileDTO"> SELECT UPLOAD_FILE_SEQ , BOARD_SEQ, BOARD_TYPE, PATHNAME, FILENAME, ORIGINAL_FILENAME, `SIZE`, CONTENT_TYPE, RESOURCE_PATHNAME , THUMBNAIL_PATHNAME, THUMBNAIL_RESOURCE_PATHNAME, REG_DATE FROM T_UPLOAD_FILE UF WHERE UF.UPLOAD_FILE_SEQ =#{uploadFileSeq} </select> <select id="fileList" resultType="kr.net.macaronics.mvc.domain.dto.UploadFileDTO" parameterType="java.util.HashMap"> SELECT UPLOAD_FILE_SEQ, BOARD_SEQ, BOARD_TYPE, PATHNAME, FILENAME, ORIGINAL_FILENAME, `SIZE`, CONTENT_TYPE, RESOURCE_PATHNAME, THUMBNAIL_PATHNAME, THUMBNAIL_RESOURCE_PATHNAME, REG_DATE FROM T_UPLOAD_FILE U <include refid="searchSql"></include> ORDER BY U.REG_DATE DESC LIMIT #{pageMaker.pageStart} , #{pageMaker.perPageNum} </select> <sql id="searchSql" > <where> <if test='boardSeq !=null and !boardSeq.equals("")'> AND U.BOARD_SEQ = #{boardSeq} </if> <if test="@org.apache.commons.lang3.StringUtils@isNotEmpty(searchType)"> <if test="searchType == 'NOTICE'.toString()"> AND U.BOARD_TYPE ='NOTICE' </if> <if test="searchType == 'FAQ'.toString()"> AND U.BOARD_TYPE ='FAQ' </if> <if test="searchType == 'INQUIRY'.toString()"> AND U.BOARD_TYPE ='INQUIRY' </if> </if> </where> </sql> <select id="getTotalCount" resultType="int" parameterType="java.util.HashMap"> SELECT COUNT(U.UPLOAD_FILE_SEQ) AS CNT FROM T_UPLOAD_FILE U <include refid="searchSql"></include> </select> <select id="bordPkFileList" resultType="kr.net.macaronics.mvc.domain.dto.UploadFileDTO"> SELECT UPLOAD_FILE_SEQ, BOARD_SEQ, BOARD_TYPE, PATHNAME, FILENAME, ORIGINAL_FILENAME, `SIZE`, CONTENT_TYPE, RESOURCE_PATHNAME, THUMBNAIL_PATHNAME, THUMBNAIL_RESOURCE_PATHNAME, REG_DATE FROM T_UPLOAD_FILE U <include refid="searchSql"></include> ORDER BY U.REG_DATE DESC LIMIT #{pageMaker.pageStart} , #{pageMaker.perPageNum} </select> <delete id="fileDelete"> DELETE FROM T_UPLOAD_FILE WHERE UPLOAD_FILE_SEQ =#{uploadFileSeq} </delete> <select id="fileSelectList" resultType="kr.net.macaronics.mvc.domain.dto.UploadFileDTO"> SELECT * FROM T_UPLOAD_FILE WHERE UPLOAD_FILE_SEQ IN( <foreach collection="uploadFileSeq" item="value" separator=","> #{value} </foreach> ) </select> </mapper>
10. 개발 환경에 따른 파일 출력 설정 GlobalConfig, WebMvcConfig
GlobalConfig
import java.util.Properties; import javax.annotation.PostConstruct; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PropertiesLoaderUtils; import lombok.Data; import lombok.extern.slf4j.Slf4j; /** * global ~.properties 값을 읽어와 글로벌 변수설정하는 클래스 */ @Slf4j @Data public class GlobalConfig { @Autowired private ApplicationContext context; @Autowired private ResourceLoader resourceLoader; private String uploadFilePath;//파일 저장 경로 private String schedulerCronExample1;//스케쥴처리 private String uploadResourcePath;//웹 브라우저 출력시 업로드파일경로 private boolean local; private boolean dev; private boolean prod; /** * @PostConstruct는 의존성 주입이 이루어진 후 초기화를 수행하는 메서드이다. * @PostConstruct가 붙은 메서드는 클래스가 service를 수행하기 전에 발생한다. * 이 메서드는 다른 리소스에서 호출되지 않는다해도 수행된다. @PostConstruct의 사용 이유 1) 생성자가 호출되었을 때, 빈은 초기화되지 않았음(의존성 주입이 이루어지지 않았음) 이럴 때 @PostConstruct를 사용하면 의존성 주입이 끝나고 실행됨이 보장되므로 빈의 초기화에 대해서 걱정할 필요가 없다. 2) bean 의 생애주기에서 오직 한 번만 수행된다는 것을 보장한다. (어플리케이션이 실행될 때 한번만 실행됨) 따라서 bean이 여러 번 초기화되는 걸 방지할 수 있다 여기서는, ApplicationContext, ResourceLoader 가 의존성 주입이 완료되었는지에 대해 염려할 필요가 없다. */ @PostConstruct public void init(){ log.info("GlobalConfig-init" ); String[] activeProfiles =context.getEnvironment().getActiveProfiles(); String activeProfile="local"; // 기본위치 local if(ObjectUtils.isNotEmpty(activeProfiles)) { activeProfile=activeProfiles[0]; } String resourcePath=String.format("classpath:globals/global-%s.properties", activeProfile); try { Resource resource=resourceLoader.getResource(resourcePath); Properties properties=PropertiesLoaderUtils.loadProperties(resource); this.uploadFilePath=properties.getProperty("uploadFile.path"); this.schedulerCronExample1 =properties.getProperty("scheduler.cron.example1"); this.uploadResourcePath=properties.getProperty("uploadFile.resourcePath"); this.local=activeProfile.equals("local"); this.dev=activeProfile.equals("dev"); this.prod=activeProfile.equals("prod"); }catch (Exception e) { log.error("e", e); } } }
WebMvcConfig
import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import kr.net.macaronics.configuration.handler.BaseHandlerInterceptor; import kr.net.macaronics.utils.pagination.MySQLPageRequestHandleMethodArgumentResolver; import lombok.extern.slf4j.Slf4j; @Configuration @Slf4j public class WebMvcConfig implements WebMvcConfigurer { private static final String WINDOW_FILE ="file:///"; private static final String LINUX_FILE= "file:"; /** addResourceHandlers 설명) WebMvcConfigurer interface를 상속받아 addResourceHandlers method를 오버 라이딩하고 리소스 등록 및 핸들러를 관리하는 객체인 ResourceHandlerRegistry를 통해 리소스의 위치와 리소스와 매칭 될 url을 설정. addResourceHandler : 리소스와 연결될 URL path를 지정(클라이언트가 파일에 접근하기 위해 요청하는 url) localhost:8080/imagePath/** addResourceLocations: 실제 리소스가 존재하는 외부 경로를 지정합니다. 경로의 마지막은 반드시 " / "로 끝나야 하고, 로컬 디스크 경로일 경우 file:/// 접두어를 꼭 붙인다. 이렇게 설정하면 클라이언트로부터 http://호스트 주소:포트/imagePath/testImage.jpg 와 같은 요청이 들어 왔을 때 /home/uploadedImage/testImage.jpg 파일로 연결. 예) private String connectPath = "/imagePath/**"; private String resourcePath = "file:///home/uploadedImage"; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(connectPath) .addResourceLocations(resourcePath); } */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //업로드 파일 static resource 접근 경로 String resourcePattern=config().getUploadResourcePath() +"**"; log.info("addResourceHandlers -resourcePattern {}", resourcePattern); if(config().isLocal()) { //로컬(윈도우환경) registry.addResourceHandler(resourcePattern) .addResourceLocations(WINDOW_FILE+config().getUploadFilePath()); log.info("윈도우 환경"); log.info(" resourcePattern - {}", resourcePattern); log.info(" addResourceLocations - {}", WINDOW_FILE+config().getUploadFilePath() ); }if(config().isDev()) { //개발환경(윈도우환경) log.info("개발환경(윈도우환경)"); registry.addResourceHandler(resourcePattern) .addResourceLocations(WINDOW_FILE+config().getUploadFilePath()); log.info(" resourcePattern - {}", resourcePattern); log.info(" addResourceLocations - {}", WINDOW_FILE+config().getUploadFilePath() ); }else { //리눅스 또는 유닉스 환경 log.info("리눅스 또는 유닉스 환경"); registry.addResourceHandler(resourcePattern) .addResourceLocations(LINUX_FILE+config().getUploadFilePath()); } } }
11. 다운로드
DownloadView
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URLEncoder; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.util.FileCopyUtils; import org.springframework.web.servlet.view.AbstractView; public class DownloadView extends AbstractView{ public DownloadView() { setContentType("application/download; charset=utf-8"); } @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { File file = (File)model.get("downloadFile"); String orignalName = (String)model.get("orignalName"); response.setContentType(getContentType()); response.setContentLength((int)file.length()); String fileName = null; if (orignalName != null && orignalName.trim().length() > 0) { fileName = orignalName; } else { fileName = file.getName(); } fileName = URLEncoder.encode(fileName, "utf-8"); fileName = fileName.replaceAll("\\+", "%20"); response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";"); response.setHeader("Content-Transfer-Encoding", "binary"); OutputStream out = response.getOutputStream(); FileInputStream fis = null; try { fis = new FileInputStream(file); FileCopyUtils.copy(fis, out); } finally { if(fis != null) { try { fis.close(); } catch(IOException e) { e.printStackTrace(); } } } out.flush(); } }
12. Tika를 이용한 MimeType 파일 위조 변조 체크
import java.io.IOException; import java.util.Arrays; import java.util.List; import org.apache.tika.Tika; import org.springframework.web.multipart.MultipartFile; import lombok.extern.slf4j.Slf4j; /**업로드 파일체크 * Tika를 이용한 MimeType 파일 위조 변조 체크 * **/ @Slf4j public class FileFilter { private static final Tika tika = new Tika(); /** 이미지 파일체크 */ public static boolean validImgFile(MultipartFile multipartFile) throws Exception { try { List<String> notValidTypeList = Arrays.asList("image/gif", "image/jpeg", "image/png", "image/bmp"); String mimeType = tika.detect(multipartFile.getInputStream()); return notValidTypeList.stream().anyMatch(notValidType -> notValidType.equalsIgnoreCase(mimeType)); } catch (IOException e) { e.printStackTrace(); return false; } } /** 업로드 가능한 파일 MimeType 체크 */ public static boolean isPermisionFileMimeType(MultipartFile multipartFile) throws Exception { //MIME 타입의 전체 목록 - https://developer.mozilla.org/ko/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types try { List<String> notValidTypeList = Arrays.asList( "image/gif", "image/jpeg", "image/png", "image/bmp", "application/pdf", "video/mp4" ,"application/zip" ,"application/x-zip-compressed" ,"application/x-tika-msoffice" , "video/quicktime" ); //application/x-tika-msoffice => hwp , msoffice 파일 //video/quicktime mp4 파일 String mimeType = tika.detect(multipartFile.getInputStream()); log.info("MimeType : " + mimeType); boolean isValid = notValidTypeList.stream().anyMatch(notValidType -> notValidType.equalsIgnoreCase(mimeType)); return isValid; } catch (IOException e) { e.printStackTrace(); return false; } } }
13. 뷰
파일 등록
1) file_upload.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>파일업로드 테스트</title> {{>layout/head}} <link rel="stylesheet" href="/css/file_upload.css" > </head> <body> <div class="row mt-5" id="file-upload-container"> <div class="col-md-4 offset-md-2 text-center"> <div class="mb-5 d-flex justify-content-end"> <a class="btn btn-info text-white" href="fileList">파일목록</a> </div> <h2>1.파일 업로드 테스트</h2> <form action="/file/save" enctype="multipart/form-data" method="post"> <div class="input-group"> <input type="file" class="form-control" name="uploadFile" id="uploadFile1" data-view="image_container1" onchange="setThumbnail(event);"> <button class="btn btn-primary" type="button" id="uploadBtn1">파일업로드</button> </div> </form> <br> <div class="imgView" id="image_container1"></div> <br> <br> <br> <br> <br> <h2>2.파일 업로드 테스트2</h2> <form action="/file/fileSave" enctype="multipart/form-data" method="post"> <div class="input-group"> <input type="file" class="form-control" name="uploadFile" id="uploadFile2" data-view="image_container2" onchange="setThumbnail(event);"> <button class="btn btn-primary" type="button" id="uploadBtn2">파일업로드</button> </div> </form> <br> <div class="imgView" id="image_container2"></div> <br> <br> <br> <br> <br> <h2>3.멀티 파일 업로드 테스트</h2> <form action="/file/fileSave" enctype="multipart/form-data" method="post"> <div class="input-group"> <input type="file" class="form-control" name="uploadFile" id="uploadFile3" data-view="image_container3" onchange="setThumbnail(event);" multiple> <button class="btn btn-primary" type="button" id="uploadBtn3">파일업로드</button> </div> </form> <br> <div class="imgView" id="image_container3"></div> </div> </div> {{>layout/footer}} <script src="/js/file_upload.js"></script> </body> </html>
2) file_upload.js
let fileUpload= { init:function(){ document.querySelector("#uploadBtn1").addEventListener("click", function(event){ fileUpload.uploadBtn1(); }); document.querySelector("#uploadBtn2").addEventListener("click", function(event){ fileUpload.uploadBtn2(); }); document.querySelector("#uploadBtn3").addEventListener("click", function(event){ fileUpload.uploadBtn3(); }); }, //1.파일 업로드 테스트 uploadBtn1:function(){ let formData = new FormData(); const inputFile =document.getElementById("uploadFile1"); const files = inputFile.files; if(inputFile.value==undefined || inputFile.value==""){ return; } //formdata에 파일 데이터 추가 for(let i=0; i<files.length; i++){ if(!checkFileName(files[i].name, files[i].size)){ console.log("file error"); return false; } formData.append("uploadFile", files[i]); } const params={ method:"POST", body:formData } fetch('/api/file/save', params).then((response)=>response.json()) .then((res)=>{ if(res.resState=="success"){ document.querySelector("#uploadFile1").value=""; document.querySelector("#image_container1").innerHTML="<textarea>"+JSON.stringify(res, null, 4) + "</textarea>"; alert("업로드 처리 되었습니다."); } }).catch((error)=>{ console.log("error:", error); }) }, //2.파일 업로드 테스트2 uploadBtn2:function(){ let formData = new FormData(); const inputFile=document.getElementById("uploadFile2"); const files = inputFile.files; if(inputFile.value==undefined || inputFile.value==""){ return; } //formdata에 파일 데이터 추가 for(let i=0; i<files.length; i++){ if(!checkFileName(files[i].name, files[i].size)){ console.log("file error"); return false; } formData.append("uploadFile", files[i]); } $.ajax({ type:"POST", url : '/api/file/fileSave', processData: false, contentType: false, data: formData, success: function(res){ if(res.resState=="success"){ document.querySelector("#uploadFile2").value=""; document.querySelector("#image_container2").innerHTML="<textarea>"+JSON.stringify(res, null, 4) + "</textarea>"; alert("업로드 처리 되었습니다."); } }, error: function(err){ console.log("err:", err) } }) } , //3.멀티 파일 업로드 테스트 uploadBtn3:function(){ let formData = new FormData(); const inputFile = document.getElementById("uploadFile3"); const files = inputFile.files; if(inputFile.value==undefined || inputFile.value==""){ return; } //formdata에 파일 데이터 추가 for(let i=0; i<files.length; i++){ if(!checkFileName(files[i].name, files[i].size)){ console.log("file error"); return false; } formData.append("uploadFile", files[i]); } const params={ method:"POST", body:formData } fetch('/api/file/multiFileSave', params).then((response)=>response.json()) .then((res)=>{ if(res.resState=="success"){ document.querySelector("#uploadFile3").value=""; document.querySelector("#image_container3").innerHTML="<textarea>"+JSON.stringify(res, null, 4) + "</textarea>"; alert("업로드 처리 되었습니다."); } }).catch((error)=>{ console.log("error:", error); }) } } //파일 확장자 체크 및 사이즈 체크 function checkFileName(str, fileSize){ //1. 확장자 체크 const ext = str.split('.').pop().toLowerCase(); const ableExts=['bmp' , 'hwp', 'jpg', 'pdf', 'png', 'xls', 'zip', 'pptx', 'xlsx', 'jpeg', 'doc', 'gif' ,'mp4']; if( ableExts.indexOf(ext) ==-1 ) { //alert(ext); alert(ext+' 파일은 업로드 하실 수 없습니다.'); return false; } //2. 파일명에 특수문자 체크 const pattern = /[\{\}\/?,;:|*~`!^\+<>@\#$%&\\\=\'\"]/gi; if(pattern.test(str) ){ //alert("파일명에 허용된 특수문자는 '-', '_', '(', ')', '[', ']', '.' 입니다."); alert('파일명에 특수문자를 제거해주세요.'); return false; } const maxSize = 1024*1024*17; //517MB if(fileSize >= maxSize){ alert("파일 사이즈 초과"); return false; } return true; } //이미지 미리보기 function setThumbnail(event) { const dataView=$(event.target).attr("data-view"); document.querySelector("div#"+dataView).innerHTML=""; for (let image of event.target.files) { // 확장자 체크 const ext = image.name.split('.').pop().toLowerCase(); const ableExt=['jpg','png','jpeg','gif']; if(ableExt.indexOf(ext) > -1) { //console.log("이미지"); const reader = new FileReader(); reader.onload = function(event) { let img = document.createElement("img"); img.setAttribute("src", event.target.result); img.style.width="auto"; img.style.height="100px"; document.querySelector("div#"+dataView).appendChild(img); }; reader.readAsDataURL(image); } } } fileUpload.init();
파일 목록
1) file_list.html
<!DOCTYPE html> <html> <head> {{>layout/head}} <title>파일목록</title> <link rel="stylesheet" href="/css/file_upload.css"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" /> </head> <body> <div class="container"> <div class="row mt-5"> <div class="col-md-12 text-center"> <div class="mb-5 d-flex justify-content-end"> <a class="btn btn-info text-white" href="fileUpload">파일등록</a> </div> <table class="table table-striped" id="fileUpload"> <thead> <tr> <th colspan="8"> <div id="displayCount" class="d-flex justify-content-end"></div> </th> </tr> </thead> <thead> <tr> <th>upload_file_seq</th> <th>thumbnail_resource_pathname</th> <th>board_seq</th> <th>board_type</th> <th>filename</th> <th>original_filename</th> <th>reg_date</th> <th>삭제</th> </tr> </thead> <tbody> <tr> <td class="text-center" colspan="8">등록된 데이터가 없습니다.</td> </tr> </tbody> </table> <ul id="pagingul"> </ul> </div> </div> </div> {{>layout/footer}} <script src="/js/file_list.js"></script> </body> </html>
2) file_list.js
let totalData; //총 데이터 수 let dataPerPage; //한 페이지에 나타낼 글 수 let pageCount; //페이징에 나타낼 페이지 수 let currentPage; //현재 페이지 let fileList = { init: function() { this.list(); }, list: function(page) { if (page == undefined || page == "") { page = 1; } fetch("/api/file/fileList?page=" + page) .then((response)=>response.json()) .then(res=>{ console.log(res.pageMaker); totalData = res.pageMaker.totalCount; dataPerPage = res.pageMaker.perPageNum; pageCount = res.pageMaker.displayPageNum; currentPage = res.pageMaker.page; if (res.resState == "success") { let html = ""; res.data.forEach(function(item) { const regDate = item.regDate.replace('T', ' ').substring(0, 19); const ext=item.originalFilename.split('.').pop().toLowerCase(); const ableExt=['jpg','png','jpeg','gif']; let img=`<span class="material-symbols-outlined" style="font-size:90px" >file_Open</span>`; if(ableExt.indexOf(ext) > -1) { if(item.thumbnailResourcePathname==null){ img="썸네일 이미지 없음"; }else{ img=`<img class="img-thumbnail rounded max-auto d-inline" src="${item.thumbnailResourcePathname}">`; } } html += `<tr> <td>${item.uploadFileSeq}</td> <td class="text-center">${img}</td> <td>${item.boardSeq}</td> <td>${item.boardType != null ? item.boardType.label : ""}</td> <td>${item.filename}</td> <td>${item.originalFilename}</td> <td>${regDate}</td> <th><button class="btn btn-sm btn-danger fileDeleteBtn" data-id="${item.uploadFileSeq}" >삭제</button></th> </tr> `; }); document.querySelector("tbody").innerHTML = html; } //페이징 표시 호출 fileList.paging(); //삭제 이벤트 추가 let fileDeletes=document.querySelectorAll(".fileDeleteBtn"); for(let i=0; i<fileDeletes.length; i++){ fileDeletes[i].addEventListener("click", function(e){ fileList.fileDelete(e.target.getAttribute("data-id")); }); } }).catch(error=>console.log("error : {}", error)); }, //파일 삭제 fileDelete:function(uploadFileSeq){ if(confirm("정말 삭제 하시겠습니까?")){ console.log("파일 삭제 {} ",uploadFileSeq); fetch("/api/file/fileDelate/"+uploadFileSeq,{ method:"DELETE" }) .then((response)=>response.json()) .then((res)=>{ if(res.resState==="success"){ currentPage=1; fileList.list(); } }); } }, // 페이징 표시 함수 //function(totalData, dataPerPage, pageCount, currentPage) paging: function(){ console.log("currentPage : " + currentPage); totalPage = Math.ceil(totalData / dataPerPage); //총 페이지 수 if (totalPage < pageCount) { pageCount = totalPage; } let pageGroup = Math.ceil(currentPage / pageCount); // 페이지 그룹 let last = pageGroup * pageCount; //화면에 보여질 마지막 페이지 번호 if (last > totalPage) { last = totalPage; } let first = last - (pageCount - 1); //화면에 보여질 첫번째 페이지 번호 let next = last + 1; let prev = first - 1; let pageHtml = ""; if (prev > 0) { pageHtml += "<li><a href='#' id='prev'> « </a></li>"; } //페이징 번호 표시 for (var i = first; i <= last; i++) { if (currentPage == i) { pageHtml += "<li class='on'><a href='#' id='" + i + "'>" + i + "</a></li>"; } else { pageHtml += "<li><a href='#' id='" + i + "'>" + i + "</a></li>"; } } if (last < totalPage) { pageHtml += "<li><a href='#' id='next'> » </a></li>"; } document.querySelector("#pagingul").innerHTML = pageHtml; let displayCount = ""; displayCount = "현재 1 - " + totalPage + " (" + currentPage + "페이지) / " + totalData + "건"; document.querySelector("#displayCount").innerText = displayCount; //페이징 번호 클릭 이벤트 const paginationClass = document.querySelectorAll("#pagingul li a"); for (let i = 0; i < paginationClass.length; i++) { paginationClass[i].addEventListener("click", function(e) { e.preventDefault(); let $id = this.getAttribute("id") selectedPage = this.innerText; console.log("선택한 페이지 ", selectedPage); if ($id == "next") selectedPage = next; if ($id == "prev") selectedPage = prev; fileList.list(selectedPage); }); } } } //파일 삭제 function fileDelete(uploadFileSeq){ alert(uploadFileSeq); } fileList.init();
소스 :
댓글 ( 4)
댓글 남기기