diff --git a/src/main/java/geoinfo/regi/header/HeaderController.java b/src/main/java/geoinfo/regi/header/HeaderController.java index 41fb6a14..1d5e05d7 100644 --- a/src/main/java/geoinfo/regi/header/HeaderController.java +++ b/src/main/java/geoinfo/regi/header/HeaderController.java @@ -400,88 +400,88 @@ public class HeaderController { @RequestMapping(value = "/web/map/mapTop.do") public String mapTop(@RequestParam HashMap params, ModelMap model, HttpServletRequest request, HttpServletResponse response) throws Exception { - //request - String oPROJECT_CODE = StringUtils.defaultString((String) String.valueOf(params.get("PROJECT_CODE"))); //프로젝트코드 - String GUBUN = StringUtils.defaultString((String) String.valueOf(params.get("GUBUN"))); - - //gubun 직접입력일때 - String COORDINATE_2 = StringUtils.defaultString((String) String.valueOf(params.get("COORDINATE_2"))); - String HOLE_COORDINATE = StringUtils.defaultString((String) String.valueOf(params.get("HOLE_COORDINATE"))); - String oHOLE_CODE = StringUtils.defaultString((String) String.valueOf(params.get("HOLE_CODE"))); - String X = StringUtils.defaultString((String) String.valueOf(params.get("X"))); - String Y = StringUtils.defaultString((String) String.valueOf(params.get("Y"))); - - WebUtil wUtil = new WebUtil(); - EgovMap map = new EgovMap(); - ArrayList array = new ArrayList(); - int count = 0; - - String fX = ""; - String fY = ""; - //좌표 가져오기 - - //내용조회 - if("".equals(oPROJECT_CODE) == false){ - String sQry = ""; - - //시추공정보 가져오기 - if("POP".equals(GUBUN) == true){ - params.put("PROJECT_CODE", oPROJECT_CODE); - array = headerService.getHoleInfoTrue(params); - - - }else{ - params.put("PROJECT_CODE", oPROJECT_CODE); - params.put("HOLE_CODE", oHOLE_CODE); - array = headerService.getHoleInfoElse(params); + //request + String oPROJECT_CODE = StringUtils.defaultString((String) String.valueOf(params.get("PROJECT_CODE"))); //프로젝트코드 + String GUBUN = StringUtils.defaultString((String) String.valueOf(params.get("GUBUN"))); + + //gubun 직접입력일때 + String COORDINATE_2 = StringUtils.defaultString((String) String.valueOf(params.get("COORDINATE_2"))); + String HOLE_COORDINATE = StringUtils.defaultString((String) String.valueOf(params.get("HOLE_COORDINATE"))); + String oHOLE_CODE = StringUtils.defaultString((String) String.valueOf(params.get("HOLE_CODE"))); + String X = StringUtils.defaultString((String) String.valueOf(params.get("X"))); + String Y = StringUtils.defaultString((String) String.valueOf(params.get("Y"))); + + WebUtil wUtil = new WebUtil(); + EgovMap map = new EgovMap(); + ArrayList array = new ArrayList(); + int count = 0; + + String fX = ""; + String fY = ""; + //좌표 가져오기 + + //내용조회 + if("".equals(oPROJECT_CODE) == false){ + String sQry = ""; + + //시추공정보 가져오기 + if("POP".equals(GUBUN) == true){ + params.put("PROJECT_CODE", oPROJECT_CODE); + array = headerService.getHoleInfoTrue(params); + + + }else{ + params.put("PROJECT_CODE", oPROJECT_CODE); + params.put("HOLE_CODE", oHOLE_CODE); + array = headerService.getHoleInfoElse(params); + } + + // Array 로 받기 + } - - // Array 로 받기 - - } - - if("POP".equals(GUBUN) == false && "".equals(oHOLE_CODE) == false){ - - Double inX = 0.0 ; - Double inY = 0.0 ; - - //degree일때 - if("Degree".equals(COORDINATE_2) == true){ - - //경위도 일때 도분초 계산 - String[] arrayX = X.split("-"); - String[] arrayY = Y.split("-"); - - inX = wUtil.getDegreeLatLongMS(Double.parseDouble(arrayX[0]), Double.parseDouble(arrayX[1]), Double.parseDouble(arrayX[2])); - inY = wUtil.getDegreeLatLongMS(Double.parseDouble(arrayY[0]), Double.parseDouble(arrayY[1]), Double.parseDouble(arrayY[2])); - fX = inX+""; - fY = inY+""; - - }else{ - - //경위도로 변환 - HashMap map01 = wUtil.setCoordinateConvertXY(Double.parseDouble(X), Double.parseDouble(Y) , HOLE_COORDINATE , "4326"); - String tempX = wUtil.isNullOb(map01.get("X"),"0"); - String tempY = wUtil.isNullOb(map01.get("Y"),"0"); - inX = Double.parseDouble(tempX); - inY = Double.parseDouble(tempY); - fX = inX+""; - fY = inY+""; + + if("POP".equals(GUBUN) == false && "".equals(oHOLE_CODE) == false){ + + Double inX = 0.0 ; + Double inY = 0.0 ; + + //degree일때 + if("Degree".equals(COORDINATE_2) == true){ + + //경위도 일때 도분초 계산 + String[] arrayX = X.split("-"); + String[] arrayY = Y.split("-"); + + inX = wUtil.getDegreeLatLongMS(Double.parseDouble(arrayX[0]), Double.parseDouble(arrayX[1]), Double.parseDouble(arrayX[2])); + inY = wUtil.getDegreeLatLongMS(Double.parseDouble(arrayY[0]), Double.parseDouble(arrayY[1]), Double.parseDouble(arrayY[2])); + fX = inX+""; + fY = inY+""; + + }else{ + + //경위도로 변환 + HashMap map01 = wUtil.setCoordinateConvertXY(Double.parseDouble(X), Double.parseDouble(Y) , HOLE_COORDINATE , "4326"); + String tempX = wUtil.isNullOb(map01.get("X"),"0"); + String tempY = wUtil.isNullOb(map01.get("Y"),"0"); + inX = Double.parseDouble(tempX); + inY = Double.parseDouble(tempY); + fX = inX+""; + fY = inY+""; + } + + model.put("inX", inX); + model.put("inY", inY); } - - model.put("inX", inX); - model.put("inY", inY); - } - - - - - model.put("fX", fX); - model.put("fY", fY); - model.put("array", array); - model.put("gubun", GUBUN); - model.put("oHOLE_CODE", oHOLE_CODE); - model.put("COORDINATE_2", COORDINATE_2); + + + + + model.put("fX", fX); + model.put("fY", fY); + model.put("array", array); + model.put("gubun", GUBUN); + model.put("oHOLE_CODE", oHOLE_CODE); + model.put("COORDINATE_2", COORDINATE_2); diff --git a/src/main/java/geoinfo/regi/manageList/ManageListController.java b/src/main/java/geoinfo/regi/manageList/ManageListController.java index 39b09dc4..152ef950 100644 --- a/src/main/java/geoinfo/regi/manageList/ManageListController.java +++ b/src/main/java/geoinfo/regi/manageList/ManageListController.java @@ -102,7 +102,7 @@ public class ManageListController { return "/web/manage/list"; }; - // 지반정보등록 (관리자) + // 지반정보등록 (관리자) - 일반 입력자도 여기 로직을 통해 화면이 보여짐. @RequestMapping(value = "/meta_info.do") public String meta_info(@RequestParam HashMap params, ModelMap model, HttpServletRequest request, diff --git a/src/main/java/geoinfo/videos/VideoStreamingController.java b/src/main/java/geoinfo/videos/VideoStreamingController.java new file mode 100644 index 00000000..155700e3 --- /dev/null +++ b/src/main/java/geoinfo/videos/VideoStreamingController.java @@ -0,0 +1,195 @@ +package geoinfo.videos; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.stereotype.Controller; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import egovframework.com.cmm.service.EgovProperties; + +@Controller +@RequestMapping("/video") +public class VideoStreamingController { + + // 동영상 파일이 저장된 기본 경로 + private final String VIDEO_DIRECTORY = EgovProperties.getProperty("Geoinfo.FilePath") + "videos"; + + @RequestMapping(value = "/stream.do", method = RequestMethod.GET) + public void streamVideo(@RequestParam("name") String videoName, HttpServletRequest request, HttpServletResponse response) { + + // 1. 경로 및 파일 유효성 검사 + if (!StringUtils.hasText(videoName)) { + sendError(response, "비디오 파일명이 비어있습니다.", HttpServletResponse.SC_BAD_REQUEST); + return; + } + + // URL 인코딩된 파일 이름 디코딩 + String decodedVideoName; + try { + decodedVideoName = URLDecoder.decode(videoName, "UTF-8"); + } catch (UnsupportedEncodingException e) { + sendError(response, "비디오 파일명 디코딩에 실패했습니다.", HttpServletResponse.SC_BAD_REQUEST); + return; + } + + File videoFile = new File(VIDEO_DIRECTORY, decodedVideoName); + + // Path Traversal 공격을 방지하기 위한 보안 코드 추가 + try { + String videoDirPath = new File(VIDEO_DIRECTORY).getCanonicalPath(); + String requestedFilePath = videoFile.getCanonicalPath(); + + // 요청된 파일의 실제 경로가 허용된 비디오 디렉토리 내에 있는지 확인 + if (!requestedFilePath.startsWith(videoDirPath)) { + sendError(response, "잘못된 파일 경로입니다.", HttpServletResponse.SC_BAD_REQUEST); + return; + } + } catch (IOException e) { + // 경로 처리 중 오류 발생 + sendError(response, "파일 경로 처리 중 오류가 발생했습니다.", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + return; + } + + if (!videoFile.exists() || !videoFile.isFile()) { + sendError(response, "요청하신 동영상 파일을 찾을 수 없습니다.", HttpServletResponse.SC_NOT_FOUND); + return; + } + + RandomAccessFile randomFile = null; + OutputStream outputStream = null; + + try { + long rangeStart = 0; + long rangeEnd = 0; + boolean isRangeRequest = false; + + // 2. Range 헤더 파싱 + String rangeHeader = request.getHeader("Range"); + if (rangeHeader != null && rangeHeader.startsWith("bytes=")) { + isRangeRequest = true; + rangeHeader = rangeHeader.substring("bytes=".length()); + String[] ranges = rangeHeader.split("-"); + try { + rangeStart = Long.parseLong(ranges[0]); + if (ranges.length > 1) { + rangeEnd = Long.parseLong(ranges[1]); + } + } catch (NumberFormatException e) { + // Range 형식이 잘못된 경우 무시하고 전체 전송 시도 + rangeStart = 0; + rangeEnd = 0; + isRangeRequest = false; + } + } + + long fileLength = videoFile.length(); + + // Range 헤더가 없거나 잘못된 경우, rangeEnd를 파일 끝으로 설정 + if (rangeEnd == 0) { + rangeEnd = fileLength - 1; + } + + // 3. 응답 헤더 설정 + String fileName = videoFile.getName(); + String mimeType = "video/mp4"; // video/webm, video/ogg 등 파일에 맞게 설정 + + // 브라우저별 한글 파일명 처리 + String header = request.getHeader("User-Agent"); + String encodedFilename; + if (header.contains("MSIE") || header.contains("Trident")) { // IE, Edge + encodedFilename = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20"); + } else { // Chrome, Firefox, Opera + encodedFilename = new String(fileName.getBytes("UTF-8"), "ISO-8859-1"); + } + + response.setHeader("Content-Disposition", "inline; filename=\"" + encodedFilename + "\""); + response.setContentType(mimeType); + response.setHeader("Accept-Ranges", "bytes"); + + long rangeLength = rangeEnd - rangeStart + 1; + + if (isRangeRequest) { + // 스트리밍 (부분 전송) + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206 + response.setHeader("Content-Length", String.valueOf(rangeLength)); + response.setHeader("Content-Range", "bytes " + rangeStart + "-" + rangeEnd + "/" + fileLength); + } else { + // 전체 전송 + response.setStatus(HttpServletResponse.SC_OK); // 200 + response.setHeader("Content-Length", String.valueOf(fileLength)); + } + + // 4. 파일 데이터 전송 + randomFile = new RandomAccessFile(videoFile, "r"); + randomFile.seek(rangeStart); + + outputStream = response.getOutputStream(); + byte[] buffer = new byte[8192]; // 8KB 버퍼 + int bytesRead; + + long bytesToWrite = rangeLength; + while (bytesToWrite > 0 && (bytesRead = randomFile.read(buffer, 0, (int) Math.min(bytesToWrite, buffer.length))) != -1) { + outputStream.write(buffer, 0, bytesRead); + bytesToWrite -= bytesRead; + } + + outputStream.flush(); + + } catch (IOException e) { + // 클라이언트가 연결을 중단하는 경우 (예: 동영상 탐색) IOException이 발생할 수 있음 + // 이 경우는 정상적인 동작이므로 에러 로그를 남기지 않음 + } catch (Exception e) { + e.printStackTrace(); + sendError(response, "알 수 없는 오류가 발생했습니다.", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) {} + } + if (randomFile != null) { + try { + randomFile.close(); + } catch (IOException e) {} + } + } + } + + /** + * 클라이언트에게 에러 메시지를 전송하는 메소드. + * 한글 깨짐을 방지하기 위해 응답 인코딩을 UTF-8로 직접 설정합니다. + * @param response HttpServletResponse 객체 + * @param message 전송할 에러 메시지 + * @param status HTTP 상태 코드 + */ + private void sendError(HttpServletResponse response, String message, int status) { + try { + // 1. 응답 상태 코드를 직접 설정합니다. + response.setStatus(status); + + // 2. 응답의 컨텐츠 타입과 문자 인코딩을 UTF-8로 명확하게 지정합니다. + response.setContentType("text/plain; charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + + // 3. 응답 본문에 직접 에러 메시지를 작성합니다. + response.getWriter().write(message); + response.getWriter().flush(); + + } catch (IOException e) { + // 클라이언트가 연결을 끊었거나 다른 I/O 오류 발생 시 + e.printStackTrace(); + } + } +} diff --git a/src/main/webapp/WEB-INF/views/web/map/mapTop.jsp b/src/main/webapp/WEB-INF/views/web/map/mapTop.jsp index bd6cbfe1..59379f4f 100644 --- a/src/main/webapp/WEB-INF/views/web/map/mapTop.jsp +++ b/src/main/webapp/WEB-INF/views/web/map/mapTop.jsp @@ -8,6 +8,8 @@ + + 지도„ diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 47836ef5..c818bcdf 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -16,6 +16,7 @@ encodingFilter *.do *.json + /video/stream/* @@ -72,8 +73,12 @@ action - *.json + *.json + + action + /video/stream/* + ImageServlet net.sf.jasperreports.j2ee.servlets.ImageServlet