지금까지 제품등록을 알아봤지만 웹에서 이렇게 바로 등록하는 경우는 잘 없다. md들이 그렇게 잘 안하고 업체들한테 정보를 받아서 그걸 엑셀로 만들어서 한꺼번에 올리거나 아니면 인바운드 api나 아웃바운드 api를 이용해서 제품을 등록하는 경우가 많다.
이번시간엔 엑셀로 등록하는 경우를 알아보고 다음엔 인바운드 api(putter api)를 만들어서 api를 이용해 등록하는 경우도 알아보도록 하자.
제품을 등록하기 위해 엑셀에 필요한 정보가 뭘까? 그 정보를 엑셀에 쭈욱 입력하면 된다. 그리고 그걸 하나씩 받아서 디비에 입력해주면 된다. 네이버 스마트스토어 같은 경우엔 이미지를 올리고 엑셀을 올리는 방식을 이용하고 있는데 여기서는 이미지는 따로 올려서 이미지 경로를 알고 있다고 가정하고 단순히 엑셀 하나만 올려서 제품을 등록하는 방법을 알아보자.
보통 쇼핑몰에서 엑셀로 등록하는 경우엔 엑셀 샘플 파일이 존재한다. 그 엑셀 샘플 파일을 먼저 만들어 보자. 샘플파일은 첨부로 등록해두었으니 참고해라.
php에서 엑셀파일을 처리하기 위해서는 phpspreadsheet라는 라이브러리가 필요하다. phpexcel이란게 유명하지만 php가 버전업이 되면서 사용에 어려움이 있다. 그래서 여기서는 phpspreadshet를 쓸것이다.
내 컴에 composer가 설치가 안돼서 phpspreadsheet를 사용할수가 없었다. 이리저리 알아보다가 아래에 고마운 분을 발견했다.
나처럼 composer가 안돼서 phpspreadsheet를 못 설치한 사람이 있다면 위 블로그 글을 참조해서 phpspreadsheet를 설치해보자.
그럼 시작해보자. 우선 엑셀을 등록할 수 있도록 화면을 만들어보자.
<?php
include $_SERVER["DOCUMENT_ROOT"]."/inc/header.php";
if(!$_SESSION['AUID']){
echo "<script>alert('권한이 없습니다.');history.back();</script>";
exit;
}
$pageNumber = $_GET['pageNumber']??1;//현재 페이지, 없으면 1
if($pageNumber < 1) $pageNumber = 1;
$pageCount = $_GET['pageCount']??10;//페이지당 몇개씩 보여줄지, 없으면 10
$startLimit = ($pageNumber-1)*$pageCount;//쿼리의 limit 시작 부분
$firstPageNumber = $_GET['firstPageNumber'];
$cate1 = $_GET['cate1'];
$cate2 = $_GET['cate2'];
$cate3 = $_GET['cate3'];
$ismain = $_GET["ismain"];
$isnew = $_GET["isnew"];
$isbest = $_GET["isbest"];
$isrecom = $_GET["isrecom"];
$sale_end_date=$_GET["sale_end_date"];
$search_keyword=$_GET["search_keyword"];
$query="select * from category where step=1";
$result = $mysqli->query($query) or die("query error => ".$mysqli->error);
while($rs = $result->fetch_object()){
$cate1array[]=$rs;
}
if($cate1){
$query="select * from category where step=2 and pcode='".$cate1."'";
$result = $mysqli->query($query) or die("query error => ".$mysqli->error);
while($rs = $result->fetch_object()){
$cate2array[]=$rs;
}
}
if($cate2){
$query="select * from category where step=3 and pcode='".$cate2."'";
$result = $mysqli->query($query) or die("query error => ".$mysqli->error);
while($rs = $result->fetch_object()){
$cate3array[]=$rs;
}
}
$scate=$cate1.$cate2.$cate3;//카테고리를 모두 묶음
if($scate){
$search_where=" and cate like '".$scate."%'";//like 검색으로 검색
}
if($ismain){//값이 있는 경우에만 검색 조건에 추가한다.
$search_where.=" and ismain=1";
}
if($isnew){
$search_where.=" and isnew=1";
}
if($isbest){
$search_where.=" and isbest=1";
}
if($isrecom){
$search_where.=" and isrecom=1";
}
if($sale_end_date){
$search_where.=" and sale_end_date<='".$sale_end_date."'";
}
if($search_keyword){
$search_where.=" and (name like '%".$search_keyword."%' or content like '%".$search_keyword."%')";//like 검색으로 검색
}
$sql = "select *, (select sum(cnt) from wms w where w.pid=p.pid) as sumcnt from products p where 1=1";
$sql .= $search_where;
$order = " order by pid desc";//마지막에 등록한걸 먼저 보여줌
$limit = " limit $startLimit, $pageCount";
$query = $sql.$order.$limit;
//echo "query=>".$query."<br>";
$result = $mysqli->query($query) or die("query error => ".$mysqli->error);
while($rs = $result->fetch_object()){
$rsc[]=$rs;
}
//전체게시물 수 구하기
$sqlcnt = "select count(*) as cnt from products where 1=1";
$sqlcnt .= $search_where;
$countresult = $mysqli->query($sqlcnt) or die("query error => ".$mysqli->error);
$rscnt = $countresult->fetch_object();
$totalCount = $rscnt->cnt;//전체 갯수를 구한다.
$totalPage = ceil($totalCount/$pageCount);//전체 페이지를 구한다.
if($firstPageNumber < 1) $firstPageNumber = 1;
$lastPageNumber = $firstPageNumber + $pageCount - 1;//페이징 나오는 부분에서 레인지를 정한다.
if($lastPageNumber > $totalPage) $lastPageNumber = $totalPage;
function isStatus($n){
switch($n) {
case -1:$rs="판매중지";
break;
case 0:$rs="대기";
break;
case 1:$rs="판매중";
break;
}
return $rs;
}
?>
<div style="text-align:center;"><h3>제품 리스트</h3></div>
<form method="get" action="<?php echo $_SERVER["PHP_SELF"]?>">
<div class="row g-3" style="padding-bottom:10px;">
<div class="col-md-4">
<select class="form-select" name="cate1" id="cate1" aria-label="Default select example">
<option value="">대분류</option>
<?php
foreach($cate1array as $c){
?>
<option value="<?php echo $c->code;?>" <?php if($cate1==$c->code){echo "selected";}?>><?php echo $c->name;?></option>
<?php }?>
</select>
</div>
<div class="col-md-4">
<select class="form-select" name="cate2" id="cate2" aria-label="Default select example">
<option value="">중분류</option>
<?php
foreach($cate2array as $c){
?>
<option value="<?php echo $c->code;?>" <?php if($cate2==$c->code){echo "selected";}?>><?php echo $c->name;?></option>
<?php }?>
</select>
</div>
<div class="col-md-4">
<select class="form-select" name="cate3" id="cate3" aria-label="Default select example">
<option value="">소분류</option>
<?php
foreach($cate3array as $c){
?>
<option value="<?php echo $c->code;?>" <?php if($cate3==$c->code){echo "selected";}?>><?php echo $c->name;?></option>
<?php }?>
</select>
</div>
</div>
<div class="input-group mb-12" style="width:100%;padding-bottom:10px;">
<input class="form-check-input" type="checkbox" name="ismain" id="ismain" value="1" <?php if($ismain){echo "checked";}?>>메인
<input class="form-check-input" type="checkbox" name="isnew" id="isnew" value="1" <?php if($isnew){echo "checked";}?>>신제품
<input class="form-check-input" type="checkbox" name="isbest" id="isbest" value="1" <?php if($isbest){echo "checked";}?>>베스트
<input class="form-check-input" type="checkbox" name="isrecom" id="isrecom" value="1" <?php if($isrecom){echo "checked";}?>>추천
판매종료일:<input type="text" class="form-control" style="max-width:150px;" name="sale_end_date" id="sale_end_date" value="<?php echo $sale_end_date?>">
<input type="text" class="form-control" name="search_keyword" id="search_keyword" placeholder="제목과 내용에서 검색합니다." value="<?php echo $search_keyword;?>" aria-label="Recipient's username" aria-describedby="button-addon2">
<button class="btn btn-outline-secondary" type="submit" id="search">검색</button>
</div>
</form>
<form method="get" name="plist" action="plist_save.php">
<div style="text-align:right;padding:10px;">
<button class="btn btn-primary" type="submit">변경내용저장</button>
</div>
<table class="table table-sm table-bordered">
<thead>
<tr style="text-align:center;">
<th scope="col">사진</th>
<th scope="col">제품명</th>
<th scope="col">가격</th>
<th scope="col">재고</th>
<th scope="col">메인</th>
<th scope="col">신제품</th>
<th scope="col">베스트</th>
<th scope="col">추천</th>
<th scope="col">상태</th>
</tr>
</thead>
<tbody>
<?php
foreach($rsc as $r){
?>
<input type="hidden" name="pid[]" value="<?php echo $r->pid;?>">
<tr>
<th scope="row" style="width:100px;"><img src="<?php echo $r->thumbnail;?>" style="max-width:100px;"></th>
<td><?php echo $r->name;?></td>
<td style="text-align:right;"><s><?php echo number_format($r->price);?>원</s>
<?php if($r->sale_price>0){?>
<br>
<?php echo number_format($r->sale_price);?>원
<?php }?>
</td>
<td style="text-align:right;"><?php echo number_format($r->sumcnt);?>EA</td>
<td style="text-align:center;"><input type="checkbox" name="ismain[<?php echo $r->pid;?>]" id="ismain_<?php echo $r->pid;?>" value="1" <?php if($r->ismain){echo "checked";}?>></td>
<td style="text-align:center;"><input type="checkbox" name="isnew[<?php echo $r->pid;?>]" id="isnew_<?php echo $r->pid;?>" value="1" <?php if($r->isnew){echo "checked";}?>></td>
<td style="text-align:center;"><input type="checkbox" name="isbest[<?php echo $r->pid;?>]" id="isbest_<?php echo $r->pid;?>" value="1" <?php if($r->isbest){echo "checked";}?>></td>
<td style="text-align:center;"><input type="checkbox" name="isrecom[<?php echo $r->pid;?>]" id="isrecom_<?php echo $r->pid;?>" value="1" <?php if($r->isrecom){echo "checked";}?>></td>
<td style="text-align:center;">
<select class="form-select" style="max-width:120px;" name="stat[<?php echo $r->pid;?>]" id="stat" aria-label="Default select example">
<option value="-1" <?php if($r->status==-1){echo "selected";}?>>판매중지</option>
<option value="0" <?php if($r->status==0){echo "selected";}?>>대기</option>
<option value="1" <?php if($r->status==1){echo "selected";}?>>판매중</option>
</select>
</td>
<td>
<a href="product_view.php?pid=<?php echo $r->pid;?>"><button class="btn btn-primary" type="button">보기</button></a>
</td>
</tr>
<?php }?>
</tbody>
</table>
</form>
<a href="product_up.php">
<button class="btn btn-primary" type="button">제품등록</button>
</a>
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#excelSave">엑셀로등록</button>
<a href="/admin/product/product_sample.xlsx">[샘플엑셀파일다운로드]</a>
<!-- Modal -->
<div class="modal fade" id="excelSave" tabindex="-1" aria-labelledby="excelSaveLabel" aria-hidden="true">
<form method="post" action="excel_save.php" enctype="multipart/form-data">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="excelSaveLabel">엑셀등록</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<input type="file" class="form-control" name="efile" id="efile">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
<button type="submit" class="btn btn-primary">등록</button>
</div>
</div>
</div></form>
</div>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script>
$(function(){
$("#sale_end_date").datepicker({ dateFormat: 'yy-mm-dd' });
});
$("#cate1").change(function(){
var cate1 = $("#cate1 option:selected").val();
var data = {
cate1 : cate1
};
$.ajax({
async : false ,
type : 'post' ,
url : 'category2.php' ,
data : data ,
dataType : 'html' ,
error : function() {} ,
success : function(return_data) {
$("#cate2").html(return_data);
}
});
});
$("#cate2").change(function(){
var cate2 = $("#cate2 option:selected").val();
var data = {
cate2 : cate2
};
$.ajax({
async : false ,
type : 'post' ,
url : 'category3.php' ,
data : data ,
dataType : 'html' ,
error : function() {} ,
success : function(return_data) {
$("#cate3").html(return_data);
}
});
});
</script>
<?php
include $_SERVER["DOCUMENT_ROOT"]."/inc/footer.php";
?>
엑셀샘플 파일을 받을 수 있도록 준비해준다. 이 샘플 파일을 잘 만들어야 문의가 줄어든다.
샘플 파일대로 엑셀파일을 만들고 엑셀을 등록해서 처리해보자.
<?php session_start();
ini_set( 'display_errors', '0' );
include $_SERVER["DOCUMENT_ROOT"]."/inc/dbcon.php";
require_once($_SERVER["DOCUMENT_ROOT"].'/lib/PhpOffice/Psr/autoloader.php');
require_once($_SERVER["DOCUMENT_ROOT"].'/lib/PhpOffice/PhpSpreadsheet/autoloader.php');
use PhpOffice\PhpSpreadsheet\IOFactory;
if(!$_SESSION['AUID']){
$retun_data = array("result"=>"member");
echo json_encode($retun_data);
exit;
}
if($_FILES['efile']['size']>10240000){//10메가
$retun_data = array("result"=>"size");
echo json_encode($retun_data);
exit;
}
$filename = $_FILES['efile']['tmp_name'];//업로드한 파일
$spreadsheet = IOFactory::load($filename);//읽기
$Rows = $spreadsheet->getActiveSheet()->toArray();//모든 row를 가져오기
$maxrows=count($Rows);//최대로우 구하기
try {
for($i=1;$i<$maxrows;$i++){//첫줄은 제목이니까 1부터 시작
if($Rows[$i][0] and $Rows[$i][1]and $Rows[$i][2]){//필수값에 값이 있는 제품만 등록, 다하면 더 좋겠지만 여기까지만
$query="INSERT INTO products
(name
, cate
, content
, thumbnail
, price
, sale_price
, sale_ratio
, cnt
, isnew
, isbest
, isrecom
, ismain
, locate
, userid
, sale_end_date
, reg_date
, delivery_fee)
VALUES ('".$Rows[$i][0]."'
, '".$Rows[$i][1]."'
, '".$Rows[$i][2]."'
, '".$Rows[$i][3]."'
, ".$Rows[$i][4]."
, ".$Rows[$i][5]."
, ".$Rows[$i][6]."
, ".$Rows[$i][7]."
, ".$Rows[$i][8]."
, ".$Rows[$i][9]."
, ".$Rows[$i][10]."
, ".$Rows[$i][11]."
, ".$Rows[$i][12]."
, '".$_SESSION['AUID']."'
, '".date("Y-m-d",strtotime($Rows[$i][13]))."'
, now()
, ".$Rows[$i][14].")";
$rs=$mysqli->query($query) or die($mysqli->error);
$pid = $mysqli -> insert_id;
}
}
} catch (exception $e) {
echo '엑셀파일을 읽는도중 오류가 발생하였습니다.!';
}
echo "<script>alert('등록했습니다.');location.href='/admin/product/product_list.php';</script>";
?>
기본적으로 옵션이 없는 상태에서의 제품등록부터 알아보자. 이렇게 등록하는것이 일반적인 것이 아니다 . 여기다 한참 뭔가를 더 해줄것이다. 지금은 이렇게 단순하게 보자.
소스를 길게 쓰는건 보기 좋게 하기 위함이다. 간혹 이상한 개발자들이 소스를 어렵게 보게 해놓는 경우가 있다. 난독화를 하는 경우도 있는데 난독화는 프로그램을 써서 난독화 시키는 거고 남들 보기 어렵게 하려고 희안하게 하는 놈들이 있는데 그건 안좋은 습관이다. 자신을 위해서도 안좋다. 주석도 잘 달고 나중에도 보기 편하게 작업하자.
내가보건 남이 보건 누가 보더라도 쉽게 볼 수 있게 소스를 짜는 것이 좋다. 그래야 오류도 줄어든다.
자 이렇게 해서 등록해보자. 등록이 안됐다면 쿼리 부분의 오타등을 확인해보자. 쿼리를 화면에 찍은 후 쿼리를 복사해서 디비 콘솔창에서 실행보면서 실수를 수정하는 것이 좋다.
지금 이렇게 한건 그냥 단순히 엑셀을 읽어서 products 테이블에 입력하는 방법을 알아 본것이다. 당장 재고만 해도 저렇게 하지 않도록 했으니까 조금 수정해보겠다.
저장하는 테이블이 2개 이상이 되면 가급적이면 트랜잭션을 지정하는 것이 좋다. 트랜잭션까지 포함해서 작업을 해보겠다.
<?php session_start();
ini_set( 'display_errors', '0' );
include $_SERVER["DOCUMENT_ROOT"]."/inc/dbcon.php";
require_once($_SERVER["DOCUMENT_ROOT"].'/lib/PhpOffice/Psr/autoloader.php');
require_once($_SERVER["DOCUMENT_ROOT"].'/lib/PhpOffice/PhpSpreadsheet/autoloader.php');
use PhpOffice\PhpSpreadsheet\IOFactory;
if(!$_SESSION['AUID']){
$retun_data = array("result"=>"member");
echo json_encode($retun_data);
exit;
}
if($_FILES['efile']['size']>10240000){//10메가
$retun_data = array("result"=>"size");
echo json_encode($retun_data);
exit;
}
$filename = $_FILES['efile']['tmp_name'];//업로드한 파일
$spreadsheet = IOFactory::load($filename);//읽기
$Rows = $spreadsheet->getActiveSheet()->toArray();//모든 row를 가져오기
$maxrows=count($Rows);//최대로우 구하기
$mysqli->autocommit(FALSE);//커밋이 안되도록 지정
try {
for($i=1;$i<$maxrows;$i++){//첫줄은 제목이니까 1부터 시작
if($Rows[$i][0] and $Rows[$i][1]and $Rows[$i][2]){//필수값에 값이 있는 제품만 등록, 다하면 더 좋겠지만 여기까지만
$query="INSERT INTO products
(name
, cate
, content
, thumbnail
, price
, sale_price
, sale_ratio
, cnt
, isnew
, isbest
, isrecom
, ismain
, locate
, userid
, sale_end_date
, reg_date
, delivery_fee)
VALUES ('".$Rows[$i][0]."'
, '".$Rows[$i][1]."'
, '".$Rows[$i][2]."'
, '".$Rows[$i][3]."'
, ".$Rows[$i][4]."
, ".$Rows[$i][5]."
, ".$Rows[$i][6]."
, ".$Rows[$i][7]."
, ".$Rows[$i][8]."
, ".$Rows[$i][9]."
, ".$Rows[$i][10]."
, ".$Rows[$i][11]."
, ".$Rows[$i][12]."
, '".$_SESSION['AUID']."'
, '".date("Y-m-d",strtotime($Rows[$i][13]))."'
, now()
, ".$Rows[$i][14].")";
$rs=$mysqli->query($query) or die($mysqli->error);
$pid = $mysqli -> insert_id;
//재고입력
$cnt = $Rows[$i][7];//재고
$wmsQuery="INSERT INTO wms
(pid, wcode, cnt)
VALUES (".$pid.",'".$wcode."',".$cnt.")";
$mysqli->query($wmsQuery) or die($mysqli->error);
}
}
$mysqli->commit();//디비에 커밋한다.
echo "<script>alert('등록했습니다.');location.href='/admin/product/product_list.php';</script>";
exit;
} catch (exception $e) {
$mysqli->rollback();//저장한 테이블이 있다면 롤백한다.
echo "<script>alert('등록하지 못했습니다. 관리자에게 문의해주십시오.');history.back();</script>";
exit;
}
?>
이렇게 트랜잭션을 적용했고 재고도 입력하도록 수정했다. 하지만 옵션이 있는 경우엔 또 달라지게 된다. 다음엔 옵션이 있는 경우에 어떻게 처리할지 확인해보자.