코디잉
28_공동구매 게시물 작성폼 ② 본문
작성폼에서 남은 작업은!
게시물 다 작성하고 <등록> 버튼 누르면,
- 필수 입력 사항 입력됐는지 체크 + 제대로 입력 안 됐으면 빨간 테두리+alert(), focus() 처리
- 결제 팝업: 참여자와 진행자 구분해서 팝업 창에서 보여줄 멘트 다르게 하려고 계획했었으니 그거 작업
- 결제 진행 후(결제 팝업) , 글 업로드(DB INSERT)
이전 게시물과 숫자는 이어서 작성..!
2022.09.21 - [PROJECT/같이사자] - 27_공동구매 게시물 작성폼 ①
휴우 작성폼 마지막이라 간단하게 봤는데 생각보다 할 게 많았다 ㅋㅋ
9) 게시물 다 작성하고 <등록> 버튼 누르면,
① 필수 입력 사항 입력됐는지 체크 + 제대로 입력 안 됐으면 빨간 테두리+alert(), focus() 처리
입력폼에 입력해야 될 사항이 많아서, 그냥 alert('필수 입력 사항이 입력되지 않았습니다.');라고 하면 어디인지 모를 수 있으니....
친절하게 하는게 나을 거 같았다.
그래서 필수 입력 사항(대표사진, 대분류, 소분류, 제목, URL, 유통기한/체크박스, 상품의총금액+배송비, 모집상품개수, 모집마감날짜/시간, 거래희망날짜/시간, 거래위치, 본인이구매할상품개수)이 입력되지 않았다면, 각각 alert()와 css 처리를 해줬다.
다른 필수 사항들은 값이 비었는지만 체크했고, 특별히 추가 조건을 입력한 케이스는,
유통기한, 상품의총금액+배송비, 모집상품개수, 구매개수이다.
- 유통기한: 사이트에 날짜 선택과 '알 수 없음' 이라는 2가지 선택지가 있다보니 둘 다 비어있을 경우를 처리
if ($('#expiration_date').val() == '' && $("#expiration_date_checkbox").prop('checked') == false) {
$('#expiration_date').css('border-color', 'red');
$('#expiration_date').focus();
alert('유통기한을 입력해주세요.');
return;
}
- 상품의총금액+배송비: 값이 비어있는 경우 OR 1보다 작은 경우(0이나 음수 체크) OR 0으로 시작하는 경우를 처리
if ($('#total_price').val() == '' || $('#total_price').val() < 1
|| $('#total_price').val().substring(0,1) == 0) {
$('#total_price').css('border-color', 'red');
$('#total_price').focus();
alert('상품의 총 금액 + 배송비를 다시 확인해주세요.');
return;
}
- 모집상품개수: 값이 비어있는 경우 OR 2보다 작은 경우(0, 1이나 음수 체크) OR 0으로 시작하는 경우를 처리
진행자가 공동구매를 진행하기 위해서는 최소 본인이 1개는 구입해야 한다. 또, 다른 회원이 참여할 수 있는 공동구매를
열어야 하므로 모집상품의 최소 개수는 2개이다.
if ($('#goods_num').val() == '' || $('#goods_num').val() < 2
|| $('#goods_num').val().substring(0,1) == 0) {
$('#goods_num').css('border-color', 'red');
$('#goods_num').focus();
alert('모집상품개수를 다시 확인해주세요(최소 2개)');
return;
}
- 구매개수: 구매개수가 0개인 경우, 구매개수 >= 모집상품개수인 경우 처리
위의 설명과 동일하게, 진행자의 최소 구매 개수는 1개이며, 타회원이 참여하려면 '구매개수 < 모집상품개수' 여야 한다.
if ($('#buy_number').val() < 1) {
$('.pro-qty').css('border', '1px solid red');
$('#buy_number').focus();
alert('구매 개수를 다시 확인해주세요(최소 1개)');
return;
}
if ($('#buy_number').val() >= $('#goods_num').val()) {
$('#goods_num').css('border-color', 'red');
$('.pro-qty').css('border', '1px solid red');
$('#goods_num').focus();
alert('모집상품개수는 구매 개수보다 커야합니다.');
return;
}
<사이트 화면 예시>
· 유통기한 입력 및 선택하지 않았을 때
· 상품의 총 금액 + 배송비를 입력하지 않거나 잘못 입력했을 때,
· 모집상품개수 < 구매 개수 입력했을 때,
② 결제 팝업: 참여자와 진행자 구분해서 팝업 창에서 보여줄 멘트 다르게 하려고 계획했었으니 그거 작업
제대로 입력 후, <등록> 버튼을 누르면 진행자 결제 팝업이 뜬다.
공동구매 참여 결제창에서는 type=participant를 get방식으로 보냈고, 이번에는 진행 결제창이니까 type=host로 보내서 그걸 사용해서 참여자/진행자 멘트를 다르게 해줬다.
이런 식으로,,!
<th colspan="2">
<%
if (request.getParameter("type").equals("host")) {
%>
<h4 class="hostMsg">포인트 결제 후, 공동구매 진행이 시작됩니다.</h4>
<%
} else if (request.getParameter("type").equals("participant")) {
%>
<h4 class="participantMsg">포인트 결제 후, 공동구매 참여가 완료됩니다.</h4>
<%
}
%>
</th>
③ 결제 진행 후(결제 팝업) , 글 업로드(DB INSERT)
처음 계획은 여기 쓴 것처럼, 결제 팝업에서 결제를 진행하고, 공동구매 게시물을 업로드시키려고 했다.
그런데 공동구매 게시물 insert 프로시저를 작성하고 사용하려니까, 위에처럼 하려면 프로시저가 아니라
따로따로 결제 insert, 게시물 insert를 시켜주는 방법만 생각났다.
그래서 작성폼(부모창)에서 <등록> → 결제 팝업(자식창)에서 <x> 버튼이 아니라, !! <결제하기> !! 를 누르고 창이 닫혔을 때에만
작성폼(부모창)의 FORM을 SUBMIT(); 시켜서 DB에 INSERT를 시켜주도록 처리했다.
- 결제팝업(자식창) 스크립트 코드: 진행자의 결제 팝업일 때, <결제하기> 누르면 팝업 닫히게 처리
// <결제하기> 버튼 클릭 시,
$(".buypostPayBtn").click(function() {
:
:
else if (type === 'host') {
window.close();
}
- 🔥공동구매 작성폼(부모창) 스크립트 코드: 『자식창.onbeforeunload = function() {}』
let popup = window.open("buypostpay.lion?type=host&buy_num=" + buy_num + "&price=" + price
, "_blank", "top=150,left=550,width=505,height=685");
// 결제 팝업에서 <결제하기> 누르고 창 닫혔을 때,
// → 결제 진행 + 글 업로드(DB INSERT)
popup.onbeforeunload = function()
{
:
// DB INSERT 하기 전에 데이터 처리해야 할 작업들 진행
:
// 폼 제출
$('#buypostInsertForm').submit();
};
위의 코드에 적혀있는 'DB INSERT 하기 전에 처리한 데이터': 유통기한, 모집마감일시, 거래희망일시, 내용, 결제총액
- datepicker / timepicker 사용한 것들 (유통기한, 모집마감일시, 거래희망일시)
DB에 유통기한, 모집마감일시, 거래희망일시는 모두 DATE 타입의 'YYYY-MM-DD HH24:MI:SS' 형식이다.
그래서 기본적으로 유통기한, 모집마감일시, 거래희망일시가 입력되면, 현재 폼에서는 날짜와 시간을 따로 입력받았기 때문에 DB에 넣기 위해 문자열을 합쳐줬다.
- 유통기한 : 유통기한은 '알 수 없음' 이라는 선택지가 있기 때문에,
'알 수 없음' → NULL 을 넣도록하고, 아닐 경우에는 DATE 타입으로 맞춰서 넣을 수 있도록 처리했다.
- 내용: 내용을 적지 않았거나, 공백만 입력해 놓은 경우에 NULL 넣도록 처리
- 결제 총액: 프로시저 변수로 결제 총액을 넘겨줘야 해서 같이 넘겨주기 위해서 처리
// 결제 팝업에서 <결제하기> 누르고 창 닫혔을 때,
// → 결제 진행 + 글 업로드(DB INSERT)
popup.onbeforeunload = function()
{
// 유통기한 일시 작업
let expiration_datetime = $('#expiration_date').val();
if ($("#expiration_date_checkbox").prop('checked') == true)
expiration_datetime = '';
else
expiration_datetime += ' 00:00:00';
$('#expiration_date').val(expiration_datetime);
// 모집마감일시 작업
let deadline = $('#deadline').val() + ' ' + $('#deadlineTime').val();
$('#deadline').val(deadline);
// 거래희망일시 작업
let trade_datetime = $('#trade_date').val() + ' ' + $('#tradeTime').val();
$('#trade_date').val(trade_datetime);
// 내용 공백/공백만 여러개 작업
if ($.trim($('#content').val()) == '')
$('#content').val('');
// 결제 총액 작업
$('#amount').val(price * buy_num);
// 폼 제출
$('#buypostInsertForm').submit();
};
이제 공동구매 INSERT 프로시저를 보자.
이 프로시저에서는 '결제INSERT + 공동구매 게시물 INSERT (+ 공동구매 내용에 사진이 있다면, 공동구매 사진테이블 INSERT) + 공동구매 참여자 INSERT' 작업을 수행한다.
--▷ 공동구매 게시물 입력
--(결제INSERT + 공구INSERT (+내용사진있으면사진테이블) + 공구참여자INSERT)
CREATE OR REPLACE PROCEDURE PRC_BP_INSERT
( V_AMOUNT IN PAYMENT.AMOUNT%TYPE
, V_MEMBER_CODE IN MEMBER.CODE%TYPE
, V_TITLE IN BUYPOST.TITLE%TYPE
, V_GOODS_PHOTO_PATH IN BUYPOST.GOODS_PHOTO_PATH%TYPE
, V_URL IN BUYPOST.URL%TYPE
, V_CONTENT IN BUYPOST.CONTENT%TYPE
--, V_EXPIRATION_DATETIME IN BUYPOST.EXPIRATION_DATETIME%TYPE
, V_EXPIRATION_DATETIME IN VARCHAR2
, V_TOTAL_PRICE IN BUYPOST.TOTAL_PRICE%TYPE
, V_GOODS_NUM IN BUYPOST.GOODS_NUM%TYPE
--, V_DEADLINE IN BUYPOST.DEADLINE%TYPE
, V_DEADLINE IN VARCHAR2
--, V_TRADE_DATETIME IN BUYPOST.TRADE_DATETIME%TYPE
, V_TRADE_DATETIME IN VARCHAR2
, V_LOCATION_X IN BUYPOST.LOCATION_X%TYPE
, V_LOCATION_Y IN BUYPOST.LOCATION_Y%TYPE
, V_REGION IN BUYPOST.REGION%TYPE
, V_SUB_CATE_CODE IN SUB_CATE.CODE%TYPE
, V_GOODS_PHOTO_NAME IN BUYPOST.GOODS_PHOTO_NAME%TYPE
, V_CONTENT_PHOTO_NAME IN BUYPOST_PHOTO.NAME%TYPE
, V_CONTENT_PHOTO_PATH IN BUYPOST_PHOTO.PATH%TYPE
, V_BUY_NUMBER IN BUYPOST_PARTICIPANT.BUY_NUMBER%TYPE
)
IS
BUYPOST_CODE BUYPOST.CODE%TYPE;
BEGIN
BUYPOST_CODE := 'BP'||BUYPOST_SEQ.NEXTVAL;
-- 공동구매 INSERT
INSERT INTO BUYPOST(CODE, TITLE, GOODS_PHOTO_PATH, URL, CONTENT, EXPIRATION_DATETIME
, TOTAL_PRICE, GOODS_NUM, DEADLINE, TRADE_DATETIME
, LOCATION_X, LOCATION_Y, REGION, WRITE_DATETIME
, MEMBER_CODE, SUB_CATE_CODE, GOODS_PHOTO_NAME)
VALUES(BUYPOST_CODE, V_TITLE, V_GOODS_PHOTO_PATH, V_URL, V_CONTENT
, TO_DATE(V_EXPIRATION_DATETIME, 'YYYY-MM-DD HH24:MI:SS'), V_TOTAL_PRICE, V_GOODS_NUM
, TO_DATE(V_DEADLINE, 'YYYY-MM-DD HH24:MI:SS')
, TO_DATE(V_TRADE_DATETIME, 'YYYY-MM-DD HH24:MI:SS')
, V_LOCATION_X, V_LOCATION_Y, V_REGION, SYSDATE, V_MEMBER_CODE
, V_SUB_CATE_CODE, V_GOODS_PHOTO_NAME);
-- 공동구매 사진 INSERT
IF V_CONTENT_PHOTO_NAME IS NOT NULL THEN
INSERT INTO BUYPOST_PHOTO(CODE, PATH, BUYPOST_CODE, NAME)
VALUES('BPP'||BUYPOST_PHOTO_SEQ.NEXTVAL, V_CONTENT_PHOTO_PATH, BUYPOST_CODE, V_CONTENT_PHOTO_NAME);
END IF;
-- 결제 INSERT
INSERT INTO PAYMENT(CODE, AMOUNT, DATETIME, BUYPOST_CODE, MEMBER_CODE)
VALUES('P'||PAYMENT_SEQ.NEXTVAL, V_AMOUNT, SYSDATE, BUYPOST_CODE, V_MEMBER_CODE);
-- 공동구매참여자 INSERT
INSERT INTO BUYPOST_PARTICIPANT(CODE, BUY_NUMBER, BUYPOST_CODE, MEMBER_CODE)
VALUES('BPPA'||BUYPOST_PARTICIPANT_SEQ.NEXTVAL, V_BUY_NUMBER, BUYPOST_CODE, V_MEMBER_CODE);
END;
🔥 입력 매개변수 쪽 주석 관련 에러난 것
ORA-01861: 리터럴이 형식 문자열과 일치하지 않음
ORA-01861: literal does not match format string
→ 'session 설정을 다시 안해줘서 그런가?' 했는데, 그건 아니였다.
→→ JAVA에서 유통기한, 모집마감일시, 거래희망일시 모두 문자열로 받는데,
프로시저에서는 입력 매개변수 타입을 DATE 타입으로 해놔서
🔥 PRC_BP_INSERT 프로시저의 INSERT 순서
[현재] 공동구매테이블 → (공동구매사진테이블) → 결제테이블 → 공동구매참여자테이블 이지만,
[[기존]] 결제테이블 → 공동구매테이블 → (공동구매사진테이블) → 공동구매참여자테이블 이었다.
공동구매 참여할 때와 마찬가지로 코드 흐름과 논리적으로 결제를 하고나서 공동구매 진행이 시작되는 거니까 그렇게 작성했었다.
그런데 아래와 같은 에러가 났다.
ORA-02291: integrity constraint (LION.PAYMENT_BUYPOST_FK) violated - parent key not found
해당 에러메세지를 보고 생각해보니, 결제 테이블에는 공동구매 게시물의 코드를 참조하고 있었다.
그래서 프로시저를 작성할 때에는, 결제 테이블에 공동구매 게시물 코드가 들어가니까, 공동구매 코드를 변수로 처음에 만들어서 넣어줬었는데.....
해당 코드의 공동구매 게시물은 아직 만들지 않았기 때문에 참조할 공동구매 게시물이 없던 것,,,ㅎㅎㅎㅎ
그래서 순서를 바꿔줬다...!
다시 코드로 돌아와서,
- dao Interface
//--▷ 공동구매 게시물 INSERT
public void insertBuypost(BuypostDTO buypost);
- dao.xml
<!-- 공동구매 게시물 INSERT -->
<select id="insertBuypost" statementType="CALLABLE">
{CALL PRC_BP_INSERT
(#{amount}, #{member_code}, #{title}, #{goods_photo_path}, #{url}, #{content, jdbcType=VARCHAR}
, #{expiration_datetime}, #{total_price}, #{goods_num}, #{deadline}, #{trade_datetime}
, #{location_x}, #{location_y}, #{region}, #{sub_cate_code}, #{goods_photo_name}
, #{content_photo_name, jdbcType=VARCHAR}, #{content_photo_path, jdbcType=VARCHAR}, #{buy_number})}
</select>
🔥 insert 프로시저에서 예외 발생
Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='content_photo_name', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting null for parameter #17 with JdbcType OTHER . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: java.sql.SQLException: 부적합한 열 유형: 1111
→ 현재 게시물을 작성할 때, 내용/내용사진/내용사진경로에는 null이 들어갈 수 있다.
그런데 mybatis에서 코드로 설정을 안해줘서 에러가 발생했다!
mybatis에서 값이 null인 경우 따로 처리해주지 않으면 insert, update할 때 에러가 발생한다.
Mapper XML 파일에서 쿼리를 작성할 때 null이 들어올 수 있는 파라미터에 위의 코드와 같이 데이터 타입을 작성해주면
정상적으로 실행된다.
→ ex) #{content, jdbcType=VARCHAR}
이제 프로시저 작업은 다 끝났다.
마지막으로, 사진을 서버에 저장해주기 위한 작업.....! 아래 사이트에서 알려준 방법을 사용했다.
https://zero-gravity.tistory.com/168?category=557981
[JSP] MultipartRequest를 이용한 파일 업로드
웹페이지에서 MultipartRequest객체를 이용한 파일업로드 방법을 소개한다. 다른 방법들도 뒤적뒤적 해봤지만, MultipartRequest를 이용한 방법이 제일 쉬웠다. 먼저 MultipartRequest를 이용하기 위해
zero-gravity.tistory.com
1) cos.jar 파일을 lib 폴더에 넣어줌
2) view페이지의 form에 『method="post" enctype="multipart/form-data"』 설정
3) controller에 코드 작성
(🔥 **enctype을 "multipart/form-data"로 선언하고 submit한 데이터들은 request 객체가 아닌
MultipartRequest 객체로 불러와야 한다.)
// 공동구매 게시물 insert
@RequestMapping("/buypostinsert.lion")
public String insertBuypost(HttpServletRequest request, Model model) throws IOException
{
// 파일이 저장될 서버의 경로
String savePath = request.getServletContext().getRealPath("img/buypost");
// 파일 크기 15MB로 제한
int sizeLimit = 1024*1024*15;
// 아래와 같이 MultipartRequest 를 생성만 해주면 파일이 업로드된다.(파일 자체의 업로드 완료)
MultipartRequest multi = new MultipartRequest(request, savePath, sizeLimit, "utf-8", new DefaultFileRenamePolicy());
// MultipartRequest 로 전송받은 데이터를 불러온다.
// enctype을 "multipart/form-data"로 선언하고 submit 한 데이터들은
// request 객체가 아닌 MultipartRequest 객체로 불러와야 한다.
BuypostDTO buypost = new BuypostDTO();
buypost.setAmount(multi.getParameter("amount"));
:
String goods_photo_name = multi.getFilesystemName("goods_photo_name");
String fileFullPath = savePath + "/" + goods_photo_name;
buypost.setGoods_photo_name(goods_photo_name);
buypost.setGoods_photo_path(fileFullPath);
:
IBuypostDAO dao = sqlSession.getMapper(IBuypostDAO.class);
dao.insertBuypost(buypost);
:
:
return "redirect:buypostarticle.lion?code=" + buypost_code;
}
└→ 게시물 작성을 완료하면, 작성한 게시물의 상세보기 페이지로 이동한다.
그래서 방금 작성한 게시물의 코드를 쿼리로 가져와서, 상세보기 페이지로 이동한다.
<!-- 회원이 가장 최근에 쓴 공동구매 게시물 코드(INSERT 후, 게시물 상세보기) -->
<select id="showInsertBuypost" resultType="java.lang.String">
SELECT NVL2(MAX(TO_NUMBER(SUBSTR(CODE, 3))), 'BP'||MAX(TO_NUMBER(SUBSTR(CODE, 3))), NULL) "CODE"
FROM BUYPOST
WHERE MEMBER_CODE = #{member_code}
AND TITLE LIKE #{title}
</select>
<사이트 화면 예시>
· 게시물 작성 → 결제팝업창 <결제하기> 클릭 → 공동구매 게시물 작성 오류 발생했을 때이다.
· 게시물 작성 → 결제팝업창 <결제하기> 클릭 → 공동구매 게시물 작성 정상적으로 완료됐을 때이다.
아까 입력한 내용들, 거래 위치 지도, 내용과 내용에 첨부한 사진 다 잘 보여진다.
게시물 작성 후, 메인으로 가면 게시물을 확인할 수 있다.
그리고 최근공구(7일 이내에 작성된 게시물)에서도 확인 가능~
끝 ㅋㅅㅋ!

'PROJECT > 같이사자(공동구매)' 카테고리의 다른 글
같이사자 Github (Readme) (0) | 2023.07.27 |
---|---|
27_공동구매 게시물 작성폼 ① (0) | 2022.09.21 |
25_공동구매 게시물 상세보기 ⑤ 버튼 관련_1 (0) | 2022.09.17 |
24_마이페이지>찜 (0) | 2022.09.09 |
23_공동구매 게시물 상세보기 ④ 찜(♡) (0) | 2022.09.06 |