{"id":27420313,"url":"https://github.com/tjdtls690/saladit","last_synced_at":"2026-04-14T10:31:55.919Z","repository":{"id":41832624,"uuid":"461142776","full_name":"tjdtls690/saladit","owner":"tjdtls690","description":"main project team project","archived":false,"fork":false,"pushed_at":"2023-11-20T10:38:16.000Z","size":9422,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"dev","last_synced_at":"2025-10-19T11:29:03.060Z","etag":null,"topics":["ajax","css","html","java","javascript","jquery","json","mybatis","mysql","spring","spring-mvc","springframework"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tjdtls690.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-02-19T09:22:43.000Z","updated_at":"2022-07-20T15:37:49.000Z","dependencies_parsed_at":"2025-04-14T10:23:03.540Z","dependency_job_id":null,"html_url":"https://github.com/tjdtls690/saladit","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tjdtls690/saladit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjdtls690%2Fsaladit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjdtls690%2Fsaladit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjdtls690%2Fsaladit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjdtls690%2Fsaladit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tjdtls690","download_url":"https://codeload.github.com/tjdtls690/saladit/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjdtls690%2Fsaladit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31793212,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ajax","css","html","java","javascript","jquery","json","mybatis","mysql","spring","spring-mvc","springframework"],"created_at":"2025-04-14T10:12:56.697Z","updated_at":"2026-04-14T10:31:55.903Z","avatar_url":"https://github.com/tjdtls690.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# :pushpin: Saladit!\n\u003e건강식 품목별 판매 웹 사이트 \u003cbr/\u003e\n\n\u003c/br\u003e\n\n## 0. 목차\n  1. [제작 기간 \u0026 참여 인원](https://github.com/tjdtls690/saladit#1-%EC%A0%9C%EC%9E%91-%EA%B8%B0%EA%B0%84--%EC%B0%B8%EC%97%AC-%EC%9D%B8%EC%9B%90-%EA%B0%9C%EB%B0%9C-4%EA%B0%9C%EC%9B%94%EC%B0%A8)\n  2. [사용 기술](https://github.com/tjdtls690/saladit#2-%EC%82%AC%EC%9A%A9-%EA%B8%B0%EC%88%A0)\n  3. [핵심 ERD](https://github.com/tjdtls690/saladit#3-%ED%95%B5%EC%8B%AC-erd)\n  4. [핵심 기능](https://github.com/tjdtls690/saladit#4-%ED%95%B5%EC%8B%AC-%EA%B8%B0%EB%8A%A5)\n  5. [핵심 트러블 슈팅](https://github.com/tjdtls690/saladit#5-%ED%95%B5%EC%8B%AC-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85)\n  6. [그 외 트러블 슈팅](https://github.com/tjdtls690/saladit#6-%EA%B7%B8-%EC%99%B8-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85)\n\n\u003c/br\u003e\n\n## 1. 제작 기간 \u0026 참여 인원 (개발 4개월차)\n- 2022.2.18 ~ 2022.3.30 (40일)\n- 구성 인원 : Back-end 6명\n\n\u003c/br\u003e\n\n## 2. 사용 기술\n#### `Back-end`\n  - Java 8\n  - Spring Framework\n  - Spring MVC\n  - Mybatis \n#### `Front-end`\n  - HTML \n  - CSS \n  - JS\n  - JQuery\n  - BootStrap\n#### `DevOps`\n  - MySQL 5.7\n  - AWS - EC2, S3, RDS\n\n\u003c/br\u003e\n\n## 3. 핵심 ERD\n\n### 사진을 클릭하여 크게 보실 수 있습니다.\n\n- **첫번째 사진의 payment_item_mapping 테이블의 payment_item_mapping_item_code(결제 상품 고유 번호) 컬럼**과 \n- **두번째 사진의 item, subscribe 테이블의 각각의 Primary key인 고유번호 컬럼들**이 외래키로 묶여있지만 한 이미지에 전부 넣을 수 없어 불가피하게 두 개의 이미지로 나눠놓았습니다.\n\n![](https://tjdtls690.github.io/assets/img/github_img/main_erd.PNG)\n\n![](https://tjdtls690.github.io/assets/img/github_img/sub_erd.PNG)\n\n\n## 4. 핵심 기능\n이 서비스의 핵심 기능은 **'고객의 상품 주문 / 결제'** 와 **'관리자의 통계 시스템'** 입니다.  \n\n- (1) **고객**은 **상품의 주문 및 결제**를 하고, 결제한 상품에 한해서 **이미지 첨부가 가능한 리뷰를 댓글 형식으로 작성**할 수 있습니다. \n- (2) **관리자**는 **매출 통계 표시 및 파일로 다운로드(PDF, Excel)** 가 가능하고,  **회원 관리와 쿠폰 관리 등등**이 있습니다.\n\u003cbr/\u003e\n\n**핵심 기능 설명**을 펼쳐서 기능의 흐름을 보면, 서비스가 어떻게 동작하는지 알 수 있습니다.  \n\n\u003cbr/\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e핵심 기능 설명 펼치기\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv markdown=\"1\"\u003e\n\n### 4.1. 전체 흐름\n![](https://tjdtls690.github.io/assets/img/github_img/important_flow01.PNG)\n\n### 4.2. 사용자 요청 (JSP \u003c - \u003e Controller)\n\n- #### 회원가입\n\n  - ##### 이메일 정규식, 이미 존재하는지 여부 체크 (Controller) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/signup/SignUpController.java#L48)\n\n    - 이미 존재한다면 카카오, 구글, 샐러딧 중 어떤 타입으로 가입되어있는지 모달창을 띄워 알려줍니다.\n\n  - **휴대폰 번호 인증** \n\n    - ##### 휴대폰 번호 정규식 체크 (Controller) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/signup/SignUpController.java#L72)\n\n    - ##### 문자 인증 과정\n\n      - ##### (1) 인증번호 문자를 보내는 과정 (네이버 SMS API) (Controller) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/naver_sms_api/NaverSensSMS.java#L68)\n\n         - 랜덤 숫자 6자리를 뽑고 세션에 들어가있는 MemberVO에 저장해줍니다.\n         - 네이버 SMS API 를 이용해 랜덤 숫자 6자리를 포함한 문자를 보냅니다.\n\n      - ##### (2) 세션에 저장된 6자리 숫자와 사용자가 적은 숫자를 비교 (Controller) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/signup/SignUpController.java#L123)\n\n        \u003cbr/\u003e\n\n- #### 로그인\n  \n  - ##### 카카오 로그인 API (화면단 : JavaScript) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/webapp/WEB-INF/views/kakaoLogin.jsp#L18)\n\n    - 카카오 로그인 팝업창을 띄워서 로그인 정보를 **로그인 타입 체크 메서드**에 전달\n  \n  - ##### 로그인 타입 체크 (Controller) - 카카오로 로그인 시 :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/login/LoginController.java#L65)\n  \n    - 이메일이 구글이거나 샐러딧 전용 이메일이라면 다시 로그인 화면으로 돌려보내며 경고창을 띄웁니다.\n  \n    - 카카오 이메일이 맞다면, 세션에 회원 정보를 DB에서 가져와서 넣고 메인페이지로 이동합니다.\n  \n      \n  \u003cbr/\u003e\n\t\n- #### 주문 페이지로 이동 (3가지 루트를 전부 감안하여 만든 메서드) (Controller) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/order/OrderController.java#L32)\n\n  - ###### (1) 상품 상세페이지에서 구매 버튼 클릭\n\n  - ###### (2) 장바구니에서 구매 버튼 클릭\n\n  - ###### (3) 바로주문하기 버튼 클릭\n\n     - ##### 3가지 루트를 커버하기 위해, 필요한 데이터들은 전부 배열 형식으로 받아서 처리.\n\n       \u003cbr/\u003e\n\n- #### 주문 페이지 (주문하기 버튼 클릭 후) -\u003e 결제 페이지로 이동\n\n  - ###### 여러개가 나오는 데이터 종류들은 List VO에, 하나씩 나오는 데이터는 단일 데이터 VO에 넣어서 컨트롤러에 전달 (화면단 : Javascript) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/webapp/WEB-INF/views/order.jsp#L2593)\n\n  - ###### 리스트 VO(PaymentSingleListVO), 단일 데이터 VO(PaymentSingleSideInfoVO) 를 통해 결제 페이지에 데이터 전달 (Controller) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/paymentSingle/PaymentSingleController.java#L33)\n\n    - 리스트 형식으로 받는 클래스 코드 (PaymentSingleListVO) **(Java) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/paymentSingle/PaymentSingleListVO.java#L5)**\n\n      \u003cbr/\u003e\n\n- #### 결제 페이지 -\u003e 결제 완료 페이지\n\n  - ##### (1) 결제하기 버튼 클릭 시\n\n     - ###### 가격이 0원 이상일 때 (iamport API 를 이용해서 결제 진행 후 데이터 넘기기) (화면단 : Javascript) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/webapp/WEB-INF/views/paymentSingle.jsp#L493)\n\n     - ###### 포인트, 쿠폰으로 인해 가격이 0원일 때 (API 는 진행하지 않고 바로 데이터 넘기기) (화면단 : Javascript) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/webapp/WEB-INF/views/paymentSingle.jsp#L218)\n\n  - ##### (2) 결제 데이터 전달, 장바구니 목록 갱신, 쿠폰 갱신, 포인트 갱신의 역할을 담당하는 메서드 (Controller) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/paymentComplete/PaymentCompleteController.java#L37)\n\n    \u003cbr/\u003e\n\n- #### 구매한 상품 리뷰 작성 (댓글) - AWS S3에 이미지 파일 업로드 포함\n\n  - ##### 리뷰 정보 (별점, 내용, 이미지 파일 업로드, 해당 상품 정보 등등) 를 DB에 저장 후, 리뷰 작성 혜택인 포인트 적립하는 메서드 (Controller) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/my_review_write/MyReviewWriteController.java#L126)\n\n  - ##### 이미지 파일을 AWS S3 에 업로드할 때 사용되는 AwsS3 클래스 코드\n\n    - createS3Client() 메서드로 AmazonS3 객체 생성 후 **(Service) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/s3/AwsS3.java#L70)**\n\n    - upload(InputStream is, String key, String contentType, long contentLength) 메서드로 S3 에 이미지 파일 업로드 실행 **(Service) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/s3/AwsS3.java#L94)**\n\n      \u003cbr/\u003e\n\n- #### 카카오 우편번호 (주소) 서비스 API\n\n  - ##### 먼저 카카오 주소 서비스 API 의 iframe 을 담기 위한 모달창을 띄웁니다. (화면단 : JavaScript) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/webapp/WEB-INF/views/order.jsp#L2262)\n  - ##### 띄워진 모달창 안에 API iframe 을 껴맞춰 넣어서 띄웁니다. (화면단 : JavaScript) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/webapp/WEB-INF/views/order.jsp#L87)\n  \n  \u003cbr/\u003e\n  \n\n### 4.3. Controller \u003c - \u003e Service\n\n- ##### 상품(데이터) 목록 요청 (대부분의 Controller \u003c - \u003e Service 계층은 이러한 구조를 가지고 있습니다.)\n\n  - ##### 요청 확인 (Controller) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/order/OrderController.java#L123)\n\n    - 화면단에서 태그 정보(DetailVO.item_tag_main)와 모달창 상품 목록 요청을 함께 받고, 그 정보들을 Service 로 넘깁니다. \n\n  - ##### 결과 응답 (Service) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/order/impl/OrderServiceImpl.java#L18)\n\n    - Service 계층에서 Controller에서 넘어온 데이터를 통해 단품, 구독 상품간의 분류를 거치고 Repository 로 넘겨서 데이터를 받아옵니다.\n\n      \n\u003cbr/\u003e\n\n### 4.4. Repository (DAO) - 4.3 과 이어진 구조로 설명\n\n- **Service 의 요청을 처리  (Repository) :pushpin: [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/java/project/spring/web/order/impl/OrderDAO.java#L17)**\n\n  - Service로부터 넘어온 데이터를 SqlSessionTemplate 을 통해 Mybatis로 넘깁니다.\n\n  - Mybatis에서 반환 된 상품 리스트(데이터) 를 받아서 자신을 호출한 Service로 응답합니다.\n\n    \u003cbr/\u003e\n\n### 4.5. MyBatis (MySQL)\n\n- ##### Repository -\u003e MyBatis - 4.4 와 이어진 구조로 설명\n\n  - (1) applicationContext.xml 에서 sqlSessionTemplate 의 bean 객체 생성 **(xml 설정파일) :pushpin:  [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/webapp/WEB-INF/spring/applicationContext.xml#L42)**\n\n  - (2) mybatis 환경설정 파일(mybatis-config.xml) 에 SQL 매퍼파일 등록 **(xml 설정 파일) :pushpin:  [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/resources/mybatis-config.xml#L44)**\n\n  - (3) 매퍼파일에서 SQL 문을 통해 상품 데이터를 반환 **(xml 매퍼 파일) :pushpin:  [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/resources/mappings/order-mapping.xml#L38)**\n\n    \u003cbr/\u003e\n\n- ##### 상품 검색 요청에 대한 응답 SQL문 :pushpin:  [코드 확인](https://github.com/tjdtls690/mainProject/blob/dev/src/main/resources/mappings/search-mapping.xml#L39)\n\n  - ##### 두 테이블을 left join으로 합쳐서 데이터를 가공 및 리스트로 반환\n  \n    - (상품 이미지, 이름, 가격) + (상품의 리뷰 평균 별점, 리뷰 총 개수)\n\n\u003c/div\u003e\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n## 5. 핵심 트러블 슈팅\n\n### 5.1. **통계 데이터 PDF 파일 다운로드 처리 문제 해결**\n\n#### 1) 문제 상황\n\n- 관리자 페이지의 통계 데이터를 PDF로 다운 받으려 할 때, 클라이언트 경로로 다운로드 되지 않는 문제가 발생했습니다.\n\n#### 2) 원인 분석\n\n- PDF 다운로드 경로가 \"C:\\금주 매출.pdf\" 로 잡혀있는데, war파일로 배포 후 war파일 안의 폴더로 경로가 잡혀버렸습니다. (클라이언트로 다운이 안받아짐)\n- 때문에 클라이언트 경로가 아닌 서버 경로로 다운로드가 되었습니다.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e기존 코드(클릭)\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv markdown=\"1\"\u003e\n\n~~~java\n@RequestMapping(\"/pdfDown.mdo\")\npublic void pdfDown(HttpServletRequest request)throws Exception{\n\n    try {\n        Document document = new Document(); // pdf문서를 처리하는 객체\n\n        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(\"C:\\\\금주 매출.pdf\"));\n        // pdf파일의 저장경로를 C드라이브에 '금주 매출.pdf'로 저장한다는 뜻\n\n        document.open(); // 웹페이지에 접근하는 객체를 연다\n        String path =request.getSession().getServletContext().getRealPath(\"/\");\n\n        /* 중간 코드 생략 */\n\n        document.add(table); // 웹접근 객체에 table를 저장한다.\n        document.close(); // 저장이 끝났으면 document객체를 닫는다.\n        System.out.println(\"성공\");\n\n    }catch (Exception e) {\n        System.out.println(\"실패\");\n        e.printStackTrace();\n    }\n}\n~~~\n\u003c/div\u003e\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n#### 3) 문제 해결\n\n- **내용을 집어넣은 pdf 파일을 아예 새 탭으로 띄워서, 클라이언트에서 새 탭을 통해 인쇄와 미리보기가 전부 가능하도록 변경하여 문제를 해결했습니다.**\n- 문제 해결 과정\n    - pom.xml에 itext에 대한 의존성을 추가했습니다.\n    - application.xml 설정 파일에 pdf를 출력할 클래스를 빈 객체로 생성한 뒤,  해당 빈 객체를 pdf라는 뷰 이름이 왔을 때 동작 할 클래스로 설정합니다.\n    - 리스트로 pdf에 찍을 값을 정제해서 pdf 란 이름으로 뷰 이름을 보냅니다.\n    - 최종적으로 pdf 내용을 만들고 출력할 빈 객체를 통해, 자바 코드로 문서에 쓰일 표를 만들어 그 표에 데이터를 넣어서 새 탭에 PDF를 출력하도록 구현했습니다.\n  \n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e개선된 코드 (클릭)\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv markdown=\"1\"\u003e\n\t\n~~~java\n// 1번\n// pom.xml 에 의존성 추가\n// PDF 출력을 위한 의존성 설정\n\u003cdependency\u003e\n\t\u003cgroupId\u003ecom.lowagie\u003c/groupId\u003e\n\t\u003cartifactId\u003eitext\u003c/artifactId\u003e\n\t\u003cversion\u003e2.1.7\u003c/version\u003e\n\u003c/dependency\u003e\n    \n    \n// 2번\n// application.xml 부모 설정파일에 pdf 출력할 클래스를 빈객체로 생성\n// 빈객체 생성\n\u003ccontext:component-scan base-package=\"com.pdf.*\" /\u003e\n    \n// Controller에서 'pdf' 로 뷰 이름 전송 시 밑의 com.pdf.web.PdfDownView 클래스가 처리 후 화면출력\n\u003cbean class=\"org.springframework.web.servlet.view.BeanNameViewResolver\"\u003e\n    \u003cproperty name=\"order\" value=\"0\" /\u003e\n\u003c/bean\u003e\n    \n// pdf라는 뷰 이름이 왔을 때 출력할 뷰를 설정\n\u003cbean id=\"pdf\" class=\"com.pdf.web.PdfDownView\"/\u003e\n    \n    \n// 3번\n// 컨트롤러에서 리스트로 pdf에 찍을 값을 넘긴 후 pdf 란 이름으로 뷰 이름 보내기\n@RequestMapping(\"/pdfDown.mdo\")\npublic String pdfDownload(Model model){\n    //날짜용 메서드\n    LocalDate now = LocalDate.now();\n    DecimalFormat df = new DecimalFormat(\"00\");\n    Calendar currentCalendar = Calendar.getInstance();\n    // 이번 년도\t--\u003e 2022\n    int year = now.getYear();\n    //이번달\t\t--\u003e 03\n    String month  = df.format(currentCalendar.get(Calendar.MONTH) + 1);\n    // 이번달 시작일\n    String startDay = year+\"-\"+month+\"-\"+\"01\";\n    // 이번달 마지막일\n    int str = currentCalendar.getActualMaximum(Calendar.DAY_OF_MONTH);\n    String endDay = year+\"-\"+month+\"-\"+str;\n    AdminReportDayVO vo = new AdminReportDayVO();\n    vo.setDate(startDay);\n    vo.setImpl(endDay);\n    List\u003cAdminReportDayVO\u003e list2 = adminReportDayService.reportMonth(vo);\n    //출력할 뷰 이름 리턴\n    model.addAttribute(\"list\", list2);\n    return \"pdf\";\n}\n// 4번\n// com.pdf.web.PdfDownView 클래스의 pdf를 새창으로 띄워서 출력하는 메서드\n\t//첫번째 매개변수가 Controller가 넘겨준 데이터 \n    //두번째 매개변수는 출력할 문서\n@Override\nprotected void buildPdfDocument(Map\u003cString, Object\u003e model, Document doc, PdfWriter writer, HttpServletRequest request,HttpServletResponse response) throws Exception {\n    List\u003cString\u003e list = (List\u003cString\u003e)model.get(\"list\");\n    XMLWorkerHelper helper = XMLWorkerHelper.getInstance();\n    //테이블을 생성\n    //1열 list.size()+1 행으로 생성\n    Table table = new Table(1,list.size()+1);\n    \n    /* 중간 코드 생략 */\n    \n    String path =request.getSession().getServletContext().getRealPath(\"/\");\n    BaseFont baseFont= BaseFont.createFont(path +\"/resources/pdfFresh/fresh.ttf\".replace('/', File.separatorChar), BaseFont.IDENTITY_H, BaseFont.EMBEDDED);\n    Font font = new Font(baseFont);\n    Cell cell = new Cell(new Paragraph(\"결제 영수증\",font));\n    cell.setHeader(true);\n    table.addCell(cell);\n    table.endHeaders();\n    //데이터를 테이블의 셀에 출력\n    for(String language : list){\n        Cell imsi = new Cell(new Paragraph(language,font));\n        table.addCell(imsi);\n    }\n    //문서에 테이블 추가\n    doc.add(table);\n}\n~~~\n\n\u003c/div\u003e\n\u003c/details\u003e\n\n\u003cbr/\u003e\n \n#### (3) 시연 \n\t\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003ePDF 파일 다운 시연 영상 (10초)\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv markdown=\"1\"\u003e\n\t\nhttps://user-images.githubusercontent.com/85877080/166142993-e414ae1b-5f20-4118-aeed-555b77dad81b.mp4\n\t\n\u003c/div\u003e\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n### 5.2. **DB에서 한 컬럼에 여러개의 이미지 URL을 한 번에 처리해야 하는 문제 해결**\n\n#### 1) 문제 상황\n\n- 각 상품의 소개를 위한 이미지 URL  컬럼 개수가 너무 많아서 데이터 관리가 쉽지 않은 현상이 발생했습니다.\n\n#### 2) 원인 분석\n\n- 각 상품의 소개 이미지 개수가 평균적으로 10개 이상입니다.\n- 상품의 개수가 많고 각 상품의 소개에서 쓰이는 이미지의 개수가 전부 다 달라서, 공통으로 이미지 URL을 관리하기가 어렵습니다.\n\n#### 3) 문제 해결 - 고유한 구분자를 사용하여 하나의 컬럼으로 이미지 URL 관리\n\n- URL에서 쓰이지 않을만한 고유한 구분자를 지정합니다.\n- 각 상품의 소개 이미지 URL들을 해당 구분자를 사이에 끼워 넣어서 하나의 문자열로 붙입니다.\n- 그 문자열을 하나의 컬럼에 저장합니다.\n- 각 상품 소개 페이지에서 데이터를 조회할 때, 그 문자열을 구분자를 기준으로 나누어서 List로 가져옵니다.\n- 그 리스트 안에 있는 모든 이미지 URL을 순차적으로 뽑아냅니다.\n\n#### 4) 현재 저의 생각 - 더 좋은 문제 해결법 (작성 날짜 : 2023년 11월)\n\n- 이 프로젝트를 할 당시에 URL을 구분자를 활용하여 하나의 컬럼으로 관리하는 것으로 문제를 해결했었습니다.\n- 하지만 지금 다시 생각해보면, 차라리 **상품 테이블과 1대N 관계인 테이블을 하나 더 만들어서** 각 상품마다 여러개의 Image URL을 넣을 수 있게 했으면 더 좋았겠다는 생각이 듭니다.\n\n\u003c/br\u003e\n\n### 5.3. 달력 처리 문제 (달력 알고리즘 직접 구현)\n\n#### 1) 문제 상황\n\n  - 상품 선택 후, 배송 예정일을 선택할 때 달력을 띄우기 위해 DatePicker API 를 이용해 가져왔지만 해당 페이지와의 CSS 가 너무 엇나가는 느낌이 드는 문제가 발생 (CSS 의 조절이 불가능)\n\n\n#### 2) 문제 해결\n\n  - 현재 달력 모달창의 CSS를 페이지에 맞게 수정 후 달력의 날짜가 매일 갱신되도록 달력 알고리즘을 직접 구현\n  - 개선된 코드 참조\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e개선된 코드 1번 (클릭)\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv markdown=\"1\"\u003e\n\n~~~java\n// 1번\n// 화면단에서 연, 월, 일을 받아서 윤년, 월을 분류해서 그에 맞는 배열 길이를 넣습니다.\n// 화면단에서 받은 getDay (현재 날짜가 일주일 중 몇번째 요일인지) 정보를 달력 모달창에 넘겨서 \n\t// 월마다 어떤 요일부터 시작하는지 구현하도록 합니다.\n\n@RequestMapping(\"/orderDateCalendar.do\")\npublic ModelAndView orderDateCalendarDo(ModelAndView mav, String year, String month, String day, String getDay) {\n    System.out.println(Integer.parseInt(month));\n    String[] dateNum;\n    if(Integer.parseInt(month) == 1 || Integer.parseInt(month) == 3 || Integer.parseInt(month) == 5 || Integer.parseInt(month) == 7\n       || Integer.parseInt(month) == 8 || Integer.parseInt(month) == 10 || Integer.parseInt(month) == 12) {\n        dateNum = new String[38];\n    }else if(Integer.parseInt(month) == 2) {\n        if(Integer.parseInt(year) % 4 == 0){\n            if(Integer.parseInt(year) % 400 != 0 \u0026\u0026 Integer.parseInt(year) % 100 == 0) {\n                dateNum = new String[35];\n            }else {\n                dateNum = new String[36];\n            }\n        }else {\n            dateNum = new String[35];\n        }\n    }else {\n        dateNum = new String[37];\n    }\n    int getDayNum = Integer.parseInt(getDay);\n    String[] getD = new String[getDayNum];\n    int mon = Integer.parseInt(month);\n\n    mav.addObject(\"remainSu\", getDayNum);\n    mav.addObject(\"remainCheck\", getD);\n    mav.addObject(\"year\", year);\n    mav.addObject(\"check\", day);\n    mav.addObject(\"month\", mon);\n    mav.addObject(\"list\", dateNum);\n    mav.setViewName(\"orderDateCalendar\");\n    return mav;\n}\n~~~\n\n\u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e개선된 코드 2번 (클릭)\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv markdown=\"1\"\u003e\n\t\n~~~jsp\n\u003c!-- 2번 --\u003e\n\n\u003c!-- 조건 4가지를 if문으로 나눠서, 위치를 지정해주는 style의 grid-area 속성을 통해 알고리즘 구현 --\u003e\n\u003c!-- 1. 저번 달에 속한 날짜 (빈칸 비워두는 용도)--\u003e\n\u003c!-- 2. 이번 달에 속했지만 오늘 날짜보다 전인 날(오늘날짜 포함) --\u003e\n\u003c!-- 3. 이번 달에 속하면서 오늘 날짜보다 후의 날 --\u003e\n\u003c!-- 4. 달력을 5줄로 맞춰야 하기에 4줄로 끝나는 달은 공백 날짜 일주일을 더 추가 --\u003e\n\n\u003c!-- span 태그 id속성, text에 날짜 입력 --\u003e\n\u003c!-- 보기 쉽게 하기 위해 class 속성은 전부 지웠습니다. --\u003e\n\n\n\n\u003c!-- 1. 저번 달에 속한 날짜 (빈칸 비워두는 용도)--\u003e\n\u003cc:forEach var=\"remain\" items=\"${remainCheck }\" varStatus=\"i\"\u003e\n\t\n    \u003c!-- grid-area 속성에 알고리즘 적용 --\u003e\n    \u003cdiv data-v-3ca35a05=\"\" style=\"grid-area: 2/${i.count}/auto/auto;\"\u003e\n        \u003cdiv data-v-2aac4f42=\"\" data-v-4a5f2beb=\"\" attributes=\"[object Object]\" data-v-3ca35a05=\"\"\u003e\n            \u003cdiv data-v-2aac4f42=\"\"\u003e\n                \u003cspan data-v-2aac4f42=\"\"\u003e\u003c/span\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n\u003c/c:forEach\u003e\n\n\n\n\u003cc:forEach var=\"list1\" items=\"${list }\" varStatus=\"i\"\u003e\n    \u003cc:if test=\"${fn:length(list) == 38}\"\u003e\n        \u003cc:if test=\"${i.count \u003c= 31}\"\u003e\n\t\t\n\t    \u003c!-- grid-area 속성에 알고리즘 적용 --\u003e\n            \u003cdiv data-v-3ca35a05=\"\" style=\"grid-area: ${(i.count + remainSu - 1) / 7 + 2}/${(i.count + remainSu - 1) % 7 + 1}/auto/auto;\"\u003e\n                \u003cdiv data-v-2aac4f42=\"\" data-v-4a5f2beb=\"\" data-v-3ca35a05=\"\"\u003e\n                    \n                    \n                    \u003c!-- 2. 이번 달에 속했지만 오늘 날짜보다 전인 날(오늘날짜 포함) --\u003e\n                    \u003cc:if test=\"${i.count \u003c= check }\"\u003e\n                        \u003cdiv data-v-2aac4f42=\"\" class=\"vc-h-full\"\u003e\n                            \u003cspan data-v-2aac4f42=\"\" tabindex=\"-1\" id=\"${year}/${month}/${i.count}\"\u003e${i.count }\u003c/span\u003e\n                        \u003c/div\u003e\n                    \u003c/c:if\u003e\n                    \n                    \n                    \u003c!-- 3. 이번 달에 속하면서 오늘 날짜보다 후의 날 --\u003e\n                    \u003cc:if test=\"${i.count \u003e check }\"\u003e\n                        \u003cdiv data-v-2aac4f42=\"\" class=\"vc-h-full\"\u003e\n                            \u003cspan data-v-2aac4f42=\"\" tabindex=\"-1\" id=\"${year}/${month}/${i.count}\"\u003e${i.count }\u003c/span\u003e\n                        \u003c/div\u003e\n                    \u003c/c:if\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/c:if\u003e\n        \n        \n        \u003c!-- 4. 달력을 5줄로 맞춰야 하기에 4줄로 끝나는 달은 공백 날짜 일주일을 더 추가 --\u003e\n        \u003cc:if test=\"${i.count \u003e 31 \u0026\u0026 remainSu \u003c 5}\"\u003e\n\t\t\n\t    \u003c!-- grid-area 속성에 알고리즘 적용 --\u003e\n            \u003cdiv data-v-3ca35a05=\"\" style=\"grid-area: ${(i.count + remainSu - 1) / 7 + 2}/${(i.count + remainSu - 1) % 7 + 1}/auto/auto;\"\u003e\n                \u003cdiv data-v-2aac4f42=\"\" data-v-4a5f2beb=\"\" data-v-3ca35a05=\"\"\u003e\n                    \u003cdiv data-v-2aac4f42=\"\" \u003e\n                        \u003cspan data-v-2aac4f42=\"\" id=\"${year}/${month}/${i.count}\"\u003e27\u003c/span\u003e\n                    \u003c/div\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/c:if\u003e\n    \u003c/c:if\u003e\n    \n    \n    \u003c!-- 이 이후론 넘어온 배열의 길이마다 조건만 다르게 주면서 위의 형식과 동일하게 3번 더 반복하기에 생략 --\u003e\n    \n    \n    \u003cc:if test=\"${fn:length(list) == 37}\"\u003e\n        \u003cc:if test=\"${i.count \u003c= 30}\"\u003e\n            \u003cdiv data-v-3ca35a05=\"\" style=\"grid-area: ${(i.count + remainSu - 1) / 7 + 2}/${(i.count + remainSu - 1) % 7 + 1}/auto/auto;\"\u003e\n                \n                \t\t\t\u003c!-- 생략 --\u003e\n    \u003c/c:if\u003e\n\u003c/c:forEach\u003e\n~~~\n\n\u003c/div\u003e\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\t\n- ### (3) 시연 \n\t\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e달력 모달창 시연 영상 (10초)\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv markdown=\"1\"\u003e\n\t\nhttps://user-images.githubusercontent.com/85877080/166143499-eda5c261-bb66-4ca5-bb93-2969040bf612.mp4\n\t\n\u003c/div\u003e\n\u003c/details\u003e\n\n\u003cbr/\u003e\n\n## 6. 그 외 트러블 슈팅\n\n### 1) aws s3 엑세스 키, 시크릿 키 처리 방법\n  - #### 실수로 깃허브에 AWS S3의 엑세스, 시크릿 키를 올리게 돼서 AWS 계정 폐쇄 위기 직면\n  - ##### 해결 \n\n\n    - (1) AWS 루트 계정 암호와 IAM(Identity and Access Management) 사용자의 암호 변경\n\n    - (2) AWS Management 콘솔을 확인하여 모든 사용이 승인되었는지 확인\n\n    - ##### (3) 자바 파일이 아닌 DB에 엑세스 키, 시크릿 키를 저장하고, 필요 시 꺼내서 사용하도록 변경\n\t\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e개선된 코드 (클릭)\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv markdown=\"1\"\u003e\n\n```java\n// 관리자의 상품 등록 코드 중...\n    \n// 메인이미지 업로드\ntry {\n        AwsS3 awsS3 = new AwsS3();\n        \n        // awsS3Service 서비스 클래스를 통해 DB에서 키 꺼내오기\n        AwsS3VO avo = awsS3Service.getKey();\n        awsS3.setAccessKey(avo.getA_key());\n        awsS3.setSecretKey(avo.getS_key());\n        \n        // 받아온 키를 통해 AmazonS3 객체 생성\n        awsS3.createS3Client();\n        String key = \"menu/\" + uploadFile.getOriginalFilename();\n        InputStream is = uploadFile.getInputStream();\n        String contentType = uploadFile.getContentType();\n        long contentLength = uploadFile.getSize();\n        \n        // 이미지 파일 S3에 업로드\n        awsS3.upload(is, key, contentType, contentLength);\n    \n        // DB에 S3 이미지 파일 URL 저장\n        vo.setItem_image(url + key);\n\tadminItemService.itemInsert(vo);\n} catch (IOException e) {\n\tSystem.out.println(\"메인이미지 업로드 실패\");\n}\n```\n\n\u003c/div\u003e\n\u003c/details\u003e\n\t\n\u003cbr/\u003e\n\n### 2) WAS(tomcat 서버)를 통해 war 파일로 배포 후 구글 로그인 API가 \"popup_closed_by_user\" 경고창이 뜨면서 로그인이 안되는 문제\n  - 해결 : 크롬 우측 상단 더보기 -\u003e 도구 더보기 -\u003e 인터넷 사용 기록 삭제 -\u003e 캐시된 이미지 및 파일만 체크 -\u003e 인터넷 사용 기록 삭제 버튼 클릭\n\t\n\u003cbr/\u003e\n\n### 3) 카카오 로그인 API 테스트 실행 시 두번째 로그인부터 정보 제공 동의 화면이 띄워지지 않는 문제\n  -  해결 : https://accounts.kakao.com/weblogin/account/info 링크(계정관리 사이트)에 들어가서 **상단에 계정 이용 탭 -\u003e 외부 서비스 전체보기 -\u003e 해당 서비스(앱) 연결 끊기**\n\t\n\u003cbr/\u003e\n\n### 4) 이미지 미리보기 처리 문제\n- 이미지 추가 버튼 클릭 -\u003e 이미지 파일을 선택 후, 선택한 이미지 파일을 버튼 옆에 미리보기 할 수 있도록 개선\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e개선된 코드 (클릭)\u003c/b\u003e\u003c/summary\u003e\n\u003cdiv markdown=\"1\"\u003e\n\n```javascript\nfunction readURL1(input) {\n    \n    // 인풋 태그에 파일이 있는 경우\n    if (input.files \u0026\u0026 input.files[0]) {\n        \n        // FileReader 인스턴스 생성\n        var reader = new FileReader();\n        \n        // 이미지가 로드 된 경우\n        reader.onload = function (e) {\n            // 이미지 미리보기 띄우기\n            $(\".preview-images1\").css({\"background\":\"url(\" + e.target.result + \")\"});\n        }\n        \n        // reader 가 이미지 읽도록 하기\n        reader.readAsDataURL(input.files[0]);\n    }\n\n    $('#image-file-button1').parent().css('display', 'none');\n    $('#image-file-button2').parent().css('display', '');\n}\n```\n\n\u003c/div\u003e\n\u003c/details\u003e \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftjdtls690%2Fsaladit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftjdtls690%2Fsaladit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftjdtls690%2Fsaladit/lists"}