반응형

서버에 올리면 onLoad, onError event 둘다 안탈때가 있음 왜그런지는?

반응형
반응형

 

useEffect(() => {
    if (!imgFile) { return; }
    
    one.activeDimLayer(true);
    const objectUrl = URL.createObjectURL(imgFile)
    setPreview(objectUrl)
 
    return () => URL.revokeObjectURL(objectUrl) // free memory when ever this component is unmounted
  }, [imgFile])
반응형
반응형

- 보통 서버에서는 request size 1MB이 걸려있는 경우가 많음 

new File([imgFile.slice(chunkIndex * chunkSize, (chunkIndex + 1) * chunkSize)], imgFile.name)

- 위와 같이 하면 파일을 잘라서 분리가능

- 보낼때는 async await써서 순차적으로 보내지도록 해야함 

[js 소스]

const resultUrl = await uploadChunk(other.imgFile);
  const uploadChunk = async (imgFile) => {
    one.activeDimLayer(true);
    const chunkSize = 1024 * 1023
    const totalChunk = imgFile.size % chunkSize == 0 ? imgFile.size / chunkSize : Math.floor(imgFile.size / chunkSize) + 1;
    const path = window.location.search.split('=')[1] + '_' + window.location.pathname.split('/').pop() + '/' + (new Date()).getFullYear() + '/' + ((new Date()).getMonth() + 1).toString().padStart(2, '0') + '/'
    let chunkIndex = -1;
    let resultUrl;

    while (chunkIndex < totalChunk - 1) {
      chunkIndex += 1;
      let chunk = new File([imgFile.slice(chunkIndex * chunkSize, (chunkIndex + 1) * chunkSize)], imgFile.name);
      //console.log('chunk', chunk);
      const body = new FormData();
      body.append('DetailPath', '/' + path);
      body.append('totalChunk', totalChunk);
      body.append("chunkIndex", chunkIndex);
      body.append('UploadFiles', chunk);

      await fetch('api/farmos/files/manual/upload/' + path,
        { method: "post", headers: { Authorization: 'Bearer ' + localStorage.getItem('token') }, body })
        .then((res => {//console.log('res', res);
          if (res.status === 200) return res.json()
          else if (res.status === 206);
          else chunkIndex = totalChunk
        }))
        .then(res => { if (res?.filepath && res?.filename) resultUrl = window.origin + '/api/farmos/files/manual/download' + res.filepath + res.filename })
        .catch(err => {
          console.log('imgUploadError', err);
          chunkIndex = totalChunk
        });
    }
    one.activeDimLayer(true);
    return resultUrl;
  }

 

[java 소스]

    @ResponseBody
    @PostMapping(value = "/files/manual/upload/{menu}/{year}/{month}/")    
    public ResponseEntity<?> chunkUpload(@RequestParam("UploadFiles") MultipartFile file,
                                              @RequestParam(value = "chunkIndex", required = false, defaultValue = "0") int chunkIndex,
                                              @RequestParam(value = "totalChunk", required = false, defaultValue = "1") int totalChunk,
                                              @PathVariable String menu, 
                                              @PathVariable String year, 
                                              @PathVariable String month, 
                                              HttpServletRequest request 
     ) throws IOException {
     String screenDetailPath =  '/' + filterInjectedString(menu) + '/' + filterInjectedString(year) + '/' + filterInjectedString(month) + '/';
     Map<String, Object> resultFileStatus = mdmManualRule.chunkUpload(file, screenDetailPath, chunkIndex, totalChunk);
        boolean isDone = (boolean) resultFileStatus.get("status"); 
        if (isDone) {
//         String filename = resultFileStatus.get("filename").toString();
         return ResponseEntity.ok().body(resultFileStatus);
        } else {
         return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).build();
        } 
    }

@SuppressWarnings("unchecked")
    public Map<String, Object> chunkUpload(MultipartFile file, String screenDetailPath, int chunkIndex, int totalChunk) throws IOException {
     // 파일 업로드 위치
        String uploadDir;
        String os = System.getProperty("os.name").toLowerCase();
        if (os.contains("win")) {
            // System.out.println("Windows");
         uploadDir = "C:";
        } else {
     // System.out.println("ubuntu");
     uploadDir = "";
    }
        
        if (uploadPath == null) {
         uploadPath = "/app/WAS/FARMOS/uploadfile";
        }

        uploadDir = uploadDir + uploadPath + screenDetailPath;

        File dir = new File(uploadDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        if (os.contains("win")) {
         uploadDir = uploadDir.replaceAll("/", "\\\\");
        }
        String seperator;
        if (os.contains("win")) {
         seperator = "/";
        } else {
         seperator = "\\";
        }

// 임시 저장 파일 이름
        String filename = file.getOriginalFilename() + ".part" + chunkIndex;

        Path filePath = Paths.get(uploadDir, filename);
        // 임시 저장
        Files.write(filePath, file.getBytes());
        Map<String, Object> result = new HashMap<String, Object>();
// 마지막 조각이 전송 됐을 경우
        if (chunkIndex == totalChunk-1) {
            String[] split = file.getOriginalFilename().split("\\.");
            String outputFilename = UUID.randomUUID() + "." + split[split.length-1];
            // need file duplication check in while ?
            
            Path outputFile = Paths.get(uploadDir, outputFilename);
            Files.createFile(outputFile);
            
            // merge temp file
            for (int i = 0; i < totalChunk; i++) {
                Path chunkFile = Paths.get(uploadDir, file.getOriginalFilename() + ".part" + i);
                Files.write(outputFile, Files.readAllBytes(chunkFile), StandardOpenOption.APPEND);
                // delete temp file
                Files.delete(chunkFile);
            }
            log.info("File uploaded successfully");
            result.put("filename", outputFilename);
            result.put("filelabel", file.getOriginalFilename());
            result.put("filepath", screenDetailPath);
            result.put("status", true);
            return result;
        } else {
         result.put("status", false);
            return result;
        }
    }

 

반응형
반응형

* 새창이 생기는 팝업과 비교되는 개념

* syncfusion 정의에서는 아래 컴포넌트 동작을 막느냐 아니냐 막으면 모달

Specifies the Boolean value whether the dialog can be displayed as modal or non-modal.

  • Modal: It creates overlay that disable interaction with the parent application and user should respond with modal before continuing with other applications.
  • Modeless: It does not prevent user interaction with parent application.

Defaults to false

반응형
반응형
반응형
반응형

DROPDOWNS

https://www.syncfusion.com/react-components/react-combobox

 

<!--td {border: 1px solid #cccccc;}br {mso-data-placement:same-cell;}-->

  선택화살표 노출여부 자유입력 가능여부 필터링(검색) 가능여부
AutoComplete x o o
ListBox x x ?
ComboBox o o o
Dropdown List o x o
MultiSelect Dropdown o x o
Dropdown Tree o x o
Mention x o o
반응형
반응형

frontend

image upload시 서버 전송 하기 전에 frontend 로직진행하는 부분

<input
        accept="image/*"
        id={`input_${type}`}
        type="file"
        className="logo_preview_input"
        onChange={e => {
          if (e.target.files && e.target.files.length >= 1) {
            const reader = new FileReader();
            const img = e.target.files[0];
            reader.readAsDataURL(img);
            reader.onload = e2 => {
              setImg(e2.target.result);
              setImgName(img.name);
            };
          }
        }}
      />
      <label htmlFor={`input_${type}`} className="btn_logo_preview">
        <Button component="span" className="btn_color4">
          Select File
        </Button>
      </label>
      <p className="file_name">{imgName}</p>

 

업로드된 파일을 서버전송할 때 new FormData()를 써서 이안에 image file이랑 다른 string이랑 같이넣어줌

        const bgImg = document.getElementById('input_bg').files[0];//undefined if not uploaded
        //if (!bgImg) alert('Please select a background image file to update !');
        const previewImg = document.getElementById('input_preview').files[0];//undefined if not uploaded
        //if (!previewImg) alert('Please select a preview image file to update !');
        const buttonImg = document.getElementById('input_button').files[0];
        requestBody = new FormData();
        requestBody.append('tabName', content.tabName);
        requestBody.append('tabOrder', content.tabOrder);
        requestBody.append('themeApply', content.themeApply ? 'Y' : 'N');
        if (bgImg) requestBody.append('bgImg', bgImg);
        if (previewImg) requestBody.append('previewImg', previewImg);
        if (buttonImg) requestBody.append('buttonImg', buttonImg);

전송시 header는 보통 multipart/form-data를 사용하는 것 같은데 왜인지 이렇게 보내면 에러가 났고 아예 설정안하고 보내니 정상동작하였음

//const headers = new Headers({ 'Content-Type': 'application/json' });
//const headers = new Headers({ 'Content-Type': 'multipart/form-data' });

backend

  • formData로 보낸 것들도 url?q=형식으로 get으로 보낸것을 받는 것처럼 @RequestParam으로 받음
  • 그 중 이미지는 org.springframework.web.multipart.MultipartFile로 받음
  • 이미지외에 string으로 보낸 것들은 Map<String, Object>로 몽땅 받음
  • 만약 이름은 bgImg, previewImg이렇게 맞춰서 보냈는데 실제로는 파일이 아니라 string을 보내면 null로 받아짐
@PatchMapping("tab_withImg/{tabId}")
  public Tab updateTabWithImg(@CurrentUser UserPrincipal currentUser, @PathVariable String tabId,
      @RequestParam Map<String, Object> data, @RequestParam(required = false) MultipartFile bgImg,
      @RequestParam(required = false) MultipartFile previewImg, @RequestParam(required = false) MultipartFile buttonImg)
      throws Exception {
    logger.info("#updateTab_WithImg|currentUser=" + currentUser.getProvider());
    return homeAppAdminService.updateTab(tabId, data, bgImg, previewImg, buttonImg);
  }

 

  • multipart file을 받아서 로컬내 저장하고 처리하는 부분
  • 일반적으로 File쓰는 방법으로 저장하면 에러가 나서 java.nio.file.Path/Paths 써서 처리했었던듯
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.web.multipart.MultipartFile;


  private String uploadFile(String type, MultipartFile multipartFile) throws Exception {
    String uploadDirStr = UPLOAD_DIR == null ? "/engn001/tomcat/8.5/servers/portal_8180/uploads" : UPLOAD_DIR;
    File uploadDir = new File(uploadDirStr);
    if (!uploadDir.exists() && !uploadDir.mkdirs())
      throw new Exception("uploadDir mkdir Fail!");
    File uploadFile = new File(uploadDirStr + "/" + multipartFile.getOriginalFilename());
    if (!uploadFile.getAbsolutePath().equals(uploadFile.getCanonicalPath()))
      throw new Exception("파일경로 및 파일명을 확인하십시오.");

    Path path = Paths.get(uploadDirStr + "/" + multipartFile.getOriginalFilename()).toAbsolutePath();
    multipartFile.transferTo(path.toFile());
    // multipartFile.transferTo(uploadFile);//file not found error
    String resultGftsUrl = uploadGfts(type, "KIC", uploadFile);
    uploadGfts(type, "AIC", uploadFile);
    uploadGfts(type, "EIC", uploadFile);
    uploadFile.delete();
    return resultGftsUrl.replaceFirst("http://kic-", "");
  }

 

반응형
반응형

[a tag]

<a href={questionModalSrc} download="88a2f203a144c233726b8ffcdccc9ed8.png">

href 주소의 Type이 이미지나 파일이어야만 다운로드가능,

  • download='img.png'에서 img.png는 변경가능
  • 확장자 안쓰면 알아서 다운로드시 Type에 해당하는 확장자 붙여줌,
  • img.htm이런식으로 확장자 잘못 붙여도 다운로드 가능

 

 <a href={`/board/viewimage?file=${encodeURIComponent(questionModalSrc)}`}>

Type이 document (html문서)라면 다운로드 에러남
href가 file Type이 아니면 이런식으로 다운로드 에러남, 이 때 img.htm의 확장자 htm은 저절로 알아서 붙은것

 

 

 

 

 

[모든 element에 적용가능한 방법] 클릭시 임시로 a tag생성하고 클릭되도록

onClick={() => { const link = document.createElement('a');  
                link.href = \`/common/files/01. CDP Admin Operation Policy.docx\`;  
                link.target = '\_blank';//링크클릭시 새창에서 열도록, 링크여는목적 아니므로 없어도 될듯  
                link.download = '다운로드될 파일명'; 이 부분이 없으면 자동다운로드 안됨
                document.body.appendChild(link);  
                link.click();  
                document.body.removeChild(link);  
              }}

 

 

 

 

 

외부에 있는 자원을 다운로드

API 구현부분

  • 테스트해보니 요청하는 헤더내의 content_type: application/json이지만 응답헤더의 Content-Type: application/octet-stream
    Content-Disposition: attachement; filename="RBSPOT.json"로 응답함
  • 참고로 다른 요청->응답은 아래와 같았음
    • 일반적인 json Fetch/XHR request -> response
      • content-type: application/json -> Content-Type: application/json;charset=UTF-8
    • 파일을 자동으로 다운로드되도록 하는 request -> response
      • content-type: application/json -> Content-Type: application/octet-stream
        Content-Disposition: attachement; filename="RBSPOT.json"
    • 파일을 서버로 업로드하는 request -> response
      • Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryCr6S7zpBWkWbkGAB ->
@Description("MetaDataFile Download from Admin.")
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
@RequestMapping("/api/admin/apiCallLog/{apiCallLogId}/file")
public ResponseEntity<?> getMetaDataFile(@CurrentUser UserPrincipal currentUser,
    @PathVariable Long apiCallLogId) {

  Map<String, Object> resultMap = new HashMap<String, Object>();
  URL url = new URL(...어찌어찌해서 얻어옴);
  InputStreamResource resource = new InputStreamResource(url.openStream());
  resultMap.put("fileName", apiCallInfo.getFileName());
  resultMap.put("resource", resource);

  return ResponseEntity.ok().contentType(MediaType.parseMediaType("application/octet-stream"))
      .header(HttpHeaders.CONTENT_DISPOSITION,
          "attachement; filename=\"" + resultMap.get("fileName") + "\"")
      .body(resultMap.get("resource"));
}

구현된 API call하는 부분

export async function getFileDownload(requestPath, requestMethod, rowData) {
  await fetch(API_BASE_URL + requestPath, {
    method: 'GET',
    headers: getHeaders(),
  })
    .then(response => {
      return response.blob();
    })
    .then(blob => {
      const url = window.URL.createObjectURL(
        new Blob([blob]),
      );
      const link = document.createElement('a');
      link.href = url;
      link.setAttribute(
        'download',
        rowData.fileName,
      );//filename to be downloaded, it can be used as link.download=fileName
      //link.style='display:none'; //OK it is not given
      document.body.appendChild(link);
      link.click();
      link.parentNode.removeChild(link);
    })
    .catch(err => {
      alert('error')
    });
}

서버 내부 자원(public공간내 file) 다운로드

<button
                  type="button"
                  className="quick_link link1"
                  onClick={() => {
                    const link = document.createElement('a');
                    link.href = `/common/files/01. CDP Admin Operation Policy_(ENG)20210428.docx`;
                    link.target = '_blank';
                    link.download = '01. CDP Admin Operation Policy_(ENG)20210428.docx';
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                  }}>

서버응답을 받아 파일을 내려주거나 내부소스 파일을 내려주거나 다운로드 하는 부분의 공통점

const link = document.createElement('a');
link.href = `/common/files/01. CDP Admin Operation Policy_(ENG)20210428.docx`;//여기에서 차이남
//link.target = '_blank'; //없어도됨
link.download = '01. CDP Admin Operation Policy_(ENG)20210428.docx'; //link.setAttribute('download', 파일명)과 동일
document.body.appendChild(link);
link.click();
document.body.removeChild(link);//link.remove(); link.parentNode.removeChild(link);와 동일

서버응답을 받아 파일생성하는 부분

new Blob([arrayContetns], options) options는 생략가능

new Blob([response], {type" res.headers[content-type]})

Blob을 URL로 변환

window.URL.createObjectURL(blob)

URL.revokeObjectURL( window.URL.createObjectURL(blob) ) 여기서 window.URL=URL인듯

반응형
반응형
import useMediaQuery from '@material-ui/core/useMediaQuery';
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
반응형
반응형

an XML-based markup language for describing two-dimensional based vector graphics

반응형

+ Recent posts