1. 상황

  : 알리익스프레스에서 물품을 주문했으나, 배송상태가 잘 진행되다가 갑자기 "지역 법률에 의해 통관에서 걸림"이라는

    황당한 상태가 나올 경우가 있음.

    당연히 주문한 물품은 저렴하고 법에 저촉을 받지 않는 순수한 제품이었지요. 

  


2. 우체국 EMS 검색해보기

   1) 바로 위 이미지에 "추적번호" 빨간색으로 테두리 친 부분을 복사한다.

   2) 우체국 EMS사이트로 접속 후 오른쪽 하단의 "EMS행방조회"에서 추적번호를 입력한 뒤 조회한다.

      URL : https://ems.epost.go.kr/comm.RetrievePostagEMSSrvcCenter.postal

   


   3) 조회결과 확인

     : 조회하면 위 알리익스프레스 사이트에서 통관에 걸렸다는 물품이 국내 우체국으로 들어와 

       열심히 우리집으로 달려오고 있는것을 알 수 있다..^^



어때요? 도움이 많이 되셨나요? ~~~~~~~도움이 되셨다면 아래 꾹~~~


1. Linux에 Golang 설치방법

  1) 본인은 usr/local 하위에 설치할 것입니다.

     - 로긴 후 이동 : cd /usr/local


  2) Go 파일 다운로드

    - Go 사이트(https://golang.org/dl/)의 파일 다운로드페이지에서 linux 용 go 파일의 link를 복사한다.

   


   - 앞에서 이동한 Linux의 /usr/local 에 파일 다운로드(wget 이용, wget이 미설치인 경우 --> yum install wget )   

 

    wget https://dl.google.com/go/go1.11.2.linux-amd64.tar.gz --no-check-certificate



  3) 받은파일 압축해제 (그러면 go 폴더가 생긴다)

    tar -C /usr/local -xzf go1.11.2.linux-amd64.tar.gz


  4) 실행파일 path설정 (명력어 수행위치는 아무데서나 실행해도 됨)

    - 실행파일 path 환경등록

     export PATH=$PATH:/usr/local/go/bin

     

   - path적용(명력어 수행위치는 아무데서나 실행해도 됨)

     source ~/.bash_profile


  5) GOROOT 지정 (선택사항, 궂이 안해되 될 듯)
   : Go가 설치된 곳의 위치를 환경에 설정한다.
   - 환경에 GOROOT추가

    export GOROOT=/usr/local/go 

   - 환경적용

    source ~/.bash_profile

      

  6) GOPATH 지정 (필수, go get -u 패키지주소 로 패키지 설치시 GOPATH에 설치됨)

    : Go 는 실제 프로그램을 할 곳인 GOPATH를 지정해야 함.(여기서는 /usr/local/go-work 를 지정할것임.)

   - 일단 go-work폴더를 /usr/local하위에 만든다.

    mkdir /usr/local/go_work 

   - 환경에 GOPATH 추가  

    export GOPATH=/usr/local/go_work

   - 환경적용

    source ~/.bash_profile

  

  7) go 설치 확인해보기 (go 명령어로 version이 잘 나오면 설치가 잘 된 것임.)

     go version 



어때요? 도움이 많이 되셨나요? ~~~~~~~도움이 되셨다면 아래 꾹~~~


3. 실제 구현

   : 이번시간에는 앞에서 설명한 대로 실제 중복체크기능을 어떻게 구현할지 소스를 보면서 설명하도록 하겠습니다.


  1) javascript(product.js)에서 로직 추가

var fnObj = {};

var ACTIONS = axboot.actionExtend(fnObj, {

    PAGE_SEARCH: function (caller, act, data) {   

        axboot.ajax({

            type: "GET",

            url: ["product"],

            data: caller.searchView.getData(),

            callback: function (res) {

            //중복기능추가용: 조회할때 기존 조회건인지를 확인하기 위해 __searched__ 속성을 더 붙여준다.

            for ( var i = 0; i < res.list.length; i++) {

            res.list[i].__searched__= true;

                }

                caller.gridView01.setData(res);

            }

        });

        return false;

    },

    PAGE_SAVE: function (caller, act, data) {

        var saveList = [].concat(caller.gridView01.getData("modified"));

        saveList = saveList.concat(caller.gridView01.getData("deleted"));  


        axboot.ajax({

            type: "PUT",

            url: ["product"],

            data: JSON.stringify(saveList),

            callback: function (res) {

                ACTIONS.dispatch(ACTIONS.PAGE_SEARCH);

                axToast.push(LANG("onsave"));

            },

            options: {

                    onError: function(err){

                    //{"message":"신규로 추가하신 [제품코드]코드는 이미 등록된 제품코드입니다."}

                       axDialog.alert({

                        title: "Error",

                            theme: "primary",

                            width:450,

                            msg: err.message

                        });    

                 }

        }

        });

    },

    ITEM_ADD: function (caller, act, data) {

        caller.gridView01.addRow();

    },

    ITEM_DEL: function (caller, act, data) {   

        caller.gridView01.delRow("selected");    

    }

});


var CODE = {};  //추가

// fnObj 기본 함수 스타트와 리사이즈

fnObj.pageStart = function () {

//

var _this = this;  


    axboot

        .call({

            type: "GET", url: "/api/v1/commonCodes", data: {groupCd: "ORIGIN"},

            callback: function (res) {

                var originList = [];

                res.list.forEach(function (n) {

                originList.push({

                CD: n.code, NM: n.name + "(" + n.code + ")"                        

                    });

                });

                this.originList = originList;

            }

        })        

        .done(function () {

            CODE = this; // this는 call을 통해 수집된 데이터들.

//위쪽 추가

            _this.pageButtonView.initView();  // this --> _this 로 변경

            _this.searchView.initView();  // this --> _this 로 변경

            _this.gridView01.initView();  // this --> _this 로 변경

    ACTIONS.dispatch(ACTIONS.PAGE_SEARCH);

        });  //추가

};


fnObj.pageResize = function () {


};



fnObj.pageButtonView = axboot.viewExtend({

    initView: function () {

        axboot.buttonClick(this, "data-page-btn", {

            "search": function () {

                ACTIONS.dispatch(ACTIONS.PAGE_SEARCH);

            },

            "save": function () {

                ACTIONS.dispatch(ACTIONS.PAGE_SAVE);

            }

        });

    }

});


//== view 시작

/**

 * searchView

 */

fnObj.searchView = axboot.viewExtend(axboot.searchView, {

    initView: function () {

        this.target = $(document["searchView0"]);

        this.target.attr("onsubmit", "return ACTIONS.dispatch(ACTIONS.PAGE_SEARCH);");

        this.filter = $("#filter");

    },

    getData: function () {

        return {

            pageNumber: this.pageNumber,

            pageSize: this.pageSize,

            filter: this.filter.val()

        }

    }

});



/**

 * gridView

 */

var list = [];

fnObj.gridView01 = axboot.viewExtend(axboot.gridView, {

    initView: function () {

        var _this = this;

        this.originList = CODE.originList; //추가

        

        this.target = axboot.gridBuilder({

            showRowSelector: true,

            frozenColumnIndex: 0,

            sortable: true,

            multipleSelect: true,

            target: $('[data-ax5grid="grid-view-01"]'), 

            columns: [

            {key: "prdtCd", label: "제품코드", width: 100, align: "center", editor: "text"},

                {key: "prdtNm", label: "제품명", width: 200, align: "center", editor: "text"},

                {

                    key: "origin", label: "원산지", width: 100,align: "center", editor: {

                    type: "select", config: {

                        columnKeys: {

                            optionValue: "CD", optionText: "NM"

                        },

                        options: this.originList //추가

                        /*

                        [

                        {"CD":"KR","NM":"한국(KR)"},

                        {"CD":"US","NM":"미국(US)"},

                        {"CD":"JP","NM":"일본(JP)"}

                        ]

                        */

                        

                    }

                  }

                },

                {key: "purchasePrice", label: "매입가격", width: 150, align: "right", editor: "number"},

                {key: "salesPrice", label: "판매가격", width: 150, align: "right", editor: "number"}          

            ],

            body: {

                onClick: function () {

                    this.self.select(this.dindex, {selectedClear: true});

                }

            }

        });


        axboot.buttonClick(this, "data-grid-view-01-btn", {

            "add": function () {

                ACTIONS.dispatch(ACTIONS.ITEM_ADD);

            },

            "delete": function () {

                ACTIONS.dispatch(ACTIONS.ITEM_DEL);

            }

        });

    },

    getData: function (_type) {   

        var _list = this.target.getList(_type);

        

        if (_type == "modified" || _type == "deleted") {

            list = ax5.util.filter(_list, function () {

                return this.prdtCd ;

            });

        } else {

            list = _list;

        }                

        

        return list;

    },

    addRow: function () {   

        this.target.addRow({__created__: true, origin: "KR"}, "last");

    },

    delRow: function delRow(_type) {    


    this.target.deleteRow(_type);  //여기서 무조건 deleteList에 추가해주고 있는것 같다.

   

    //그렇다보니 기존 조회된 데이터가 아닌 신규로 추가한 후 삭제해도 추가되기 때문에 

    //저장시 안넘어가도 되는 데이터(화면상에서만 행추가후 삭제시)까지 날라간다.

        

        //중복기능추가시: 조회된것이 아닌경우 deleteList에서 빼준다.        

        for(i=0;i<this.target.deletedList.length ;i++)

        {

        if(this.target.deletedList[i].__searched__==undefined)

        {

        this.target.deletedList[i]=null;

        }

        }

        

    }

});


2) Controller(ProductController) 에서 로직 추가

package com.dasdes.shopmng.controllers;


import com.chequer.axboot.core.api.response.Responses;

import com.chequer.axboot.core.controllers.BaseController;

import com.chequer.axboot.core.parameter.RequestParams;

import org.springframework.stereotype.Controller;


import com.chequer.axboot.core.api.ApiException;

import com.chequer.axboot.core.api.response.ApiResponse;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import com.dasdes.shopmng.domain.prdt.Product;

import com.dasdes.shopmng.domain.prdt.ProductService;

import com.wordnik.swagger.annotations.ApiImplicitParam;  

import com.wordnik.swagger.annotations.ApiImplicitParams; 


import javax.inject.Inject;

import java.util.List;


@Controller

@RequestMapping(value = "/api/v1/product")

public class ProductController extends BaseController {


    @Inject

    private ProductService productService;


    @RequestMapping(method = RequestMethod.GET, produces = APPLICATION_JSON)

    @ApiImplicitParams({

            @ApiImplicitParam(name = "prdtCd", value = "제품코드", dataType = "String", paramType = "query"),

            @ApiImplicitParam(name = "prdtNm", value = "제품명", dataType = "String", paramType = "query"),

            @ApiImplicitParam(name = "filter", value = "검색어", dataType = "String", paramType = "query")

    })    

    public Responses.ListResponse list(RequestParams<Product> requestParams) {

        List<Product> list = productService.gets(requestParams);

        return Responses.ListResponse.of(list);

    }


    @RequestMapping(method = {RequestMethod.PUT}, produces = APPLICATION_JSON)

    public ApiResponse save(@RequestBody List<Product> request) {

   

    //중복체크 후 중복발생 시 Exception핸들러로 받는다.

    try

    {

           productService.savePrdt(request);

    }

    catch(ApiException e)

    {

    return handleApiException(e);

    }

        return ok();

    }


 3) ProductService 에서 로직 추가

package com.dasdes.shopmng.domain.prdt;


import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional; //추가

import org.springframework.web.bind.annotation.ExceptionHandler;


import com.dasdes.shopmng.domain.BaseService;

import com.querydsl.core.BooleanBuilder;  //추가


import javax.inject.Inject;

import com.chequer.axboot.core.parameter.RequestParams;

import java.util.List;


import com.chequer.axboot.core.api.ApiException;



//import org.slf4j.Logger;

//import org.slf4j.LoggerFactory;


@Service

public class ProductService extends BaseService<Product, String>  {

//private final Logger logger = LoggerFactory.getLogger(ProductService.class);

    private ProductRepository productRepository;


    @Inject

    public ProductService(ProductRepository productRepository) {

        super(productRepository);

        this.productRepository = productRepository;

    }


    public List<Product> gets(RequestParams<Product> requestParams) {

    String prdtCd=requestParams.getString("prdtCd", "");

    String prdtNm=requestParams.getString("prdtNm", "");

    String filter = requestParams.getString("filter");

   

    BooleanBuilder builder = new BooleanBuilder();



        if (isNotEmpty(prdtCd)) {

            builder.and(qProduct.prdtCd.eq(prdtCd));

        }



        if (isNotEmpty(prdtNm)) {

            builder.and(qProduct.prdtNm.eq(prdtNm));

        }


        List<Product> prdtList = select().from(qProduct).where(builder).orderBy(qProduct.prdtCd.asc(), qProduct.prdtNm.asc()).fetch();


        if (isNotEmpty(filter)) {

        prdtList = filter(prdtList, filter);

        }

   

        return prdtList;

    }

    

    

    @Transactional

    public void savePrdt(List<Product> product) {

    Product prdt=null;

   

    for(int i=0;i<product.size();i++)

    {

        prdt=(Product)product.get(i);

   

        //신규추가된 데이터에 대해 기존에 있는데이터인지 체크

        if(prdt.isCreated() && exists(prdt.getId()))

        {

    throw new ApiException("신규로 추가하신 ["+prdt.getId()+"]코드는 이미 등록된 제품코드입니다.");

        }

    }

        save(product);

    }


4) 최종결과확인

  : 위와같이 소스를 수정하고 서버를 재기동 후 중복제품코드를 추가해서 입력해본다.





어때요? 도움이 많이 되셨나요? ~~~~~~~도움이 되셨다면 아래 꾹~~~

1. 원래 Axboot의 처리방식

  : 신규 행추가 후 기존의 키값과 동일한 키값을 입력 후 저장시 원래있던 데이터를 행추가시 입력한 데이터로 

    업데이트 해버린다.

   ==> 실제 업무화면에서는 사고가 발생할 여지가 있는 부분임.

          실수로 기존의 키값과 동일한 값을 추가하여 나머지 데이터는 다른값을 입력하고 저장했을 경우

          의도치 않게 기존에 있던 데이터가 업데이트되어 사고가 발생할 수 있다.

          보통은 신규로 행추가 했을 경우 기존값과 중복된 키값을 입력했을 경우 에러를 벹어내도록 구현된다.

   1) 기존 처리방식의 예시화면

      - 기존데이터 상태


  - 기존에 있는 데이터와 중복되는 제품코드가 11 인 데이터를 추가함(제품명,원산지,매입가격,판매가격은 다르게 입력)

    (데이터가 많으면 인지하지 못할 수 있음)


  - 저장시 기존의 데이터가 있다는 경고 없이 그냥 저장됨.


2. 중복체크로직 추가

  1) javascript에서 로직 추가

     - 조회 후 조회값인지 화면에서 행추가한 값인지 상태값 체크할 수 있는 "__searched__" key를 조회결과 처리부분의

      json 에 추가해준다. 

      ==>이유 : 신규로 행추가 후 중복값 입력 후 저장했을 경우 에러메시지가 발생해서 중복값을 삭제 후 

                    저장했을 경우 화면에서는 없어졌지만 계속 값이 deletedList라는 json key에 "__create__" key로 저장되어있어

                    계속 중복된다는 메시지가 리턴됨. 그래서 화면에서만 추가/삭제된 것은 deletedList에서 없애주기 위해서는

                    기존에 조회된 값인지의 여부를 판단할 필요가 있어 "__searched__" flag가 필요함.

    - 저장(PAGE_SAVE) 시 에러가 발생할 경우 메시지처리하는 부분을 추가함.

    - Grid의 delRow 부분에 행삭제시 기존에 조회된 값이 아닌 경우 불필요하게 데이터가 서버로 날라가는것을

       방지하기 위해 deletedList에서 "__searched__" key가 없는(undefined) 것은 삭제하는 로직 추가


  2) Controller 에서 로직 추가

    - Exception을 받아 화면으로 넘기는 처리한다.

      ==> axboot에 있는 class를 활용한다. 

   : ApiException 으로 받아 com.chequer.axboot.core.controllers.BaseController.handleApiException을 화면으로 넘긴다.


  3) ProductService 에서 로직 추가

    - 신규추가된 데이터에 대해 기존에 있는데이터인지 체크해서 중복 시 ApiException으로 메시지를 리턴한다.


   

다음 글에 중복등록체크로직 추가 -2 에서 실제 구현되는 방식을 보시겠습니다.


어때요? 도움이 많이 되셨나요? ~~~~~~~도움이 되셨다면 아래 꾹~~~

1. Grid의 행추가시 자주 사용하는 값을 초기값으로 셋팅

- 앞서 작성했던 프로그램의 연속성상에서 간단히 수정만 하면 됨.

- webapp>assets>js>view>shopmng>product.js 를 수정한다. 

- gridView 하위에 있는 addRow function 에 초기값을 셋팅해준다.


2. 예제

  : 아래의 예는 원산지를 한국(KR)로 셋팅하는 예제이다.

addRow: function () {

    this.target.addRow({__created__: true, origin: "KR"}, "last");

 },


- grid의 원산지 컬럼인 origin 에 "KR"로 셋팅하면 행추가시 바로 "KR"로 셋팅된다.





어때요? 도움이 많이 되셨나요? ~~~~~~~도움이 되셨다면 아래 꾹~~~

+ Recent posts