1. 程式人生 > >元旦三天假期,實現一個電商退單管理系統【三】

元旦三天假期,實現一個電商退單管理系統【三】

一、服務端介面實現

服務端介面用於接收客戶端登入、快遞公司查詢、同步訂單、查詢已同步訂單等業務。主要用tp3.2完成。只寫了一個controller,資料庫查詢都寫在controller裡了。

tp本來的設計思想,也不分業務邏輯層和資料層。他把資料層和model混合在一起,業務邏輯層和controller合在一起。對於小型專案,快速實現,變更快速響應有很大優勢。我這邊業務邏輯不是很多,只用了一個入口。

入口處,先放接收一個方法名,根據方法名,再去執行相應的業務邏輯。

介面模板拿了一個以前做手機app的文件改了下,設計如下:

 

入口函式

1. 擴充套件了一個簡單的列舉值作為與客戶端約定的狀態碼。PHP處理這個有點麻煩。msg不知道怎麼對應,還是直接在各處寫漢字。

2. 簡單的通用返回體getRepsonseMessage結構與客戶端保持一致。

3. 簽名演算法根據流水和入參做了字典排序,然後再加密,與客戶端做對比。

4. 只有入口函式是public,其他全部是private。

5. 除幾個公共入參外,其他入參都由自己的子函式接收。大部分用php的I方法,訂單同步因為要傳json字串,所以用了$_POST。

public function interface(){
        $transid = I("transid");
        $ts = I("ts");
        $sign = I("sign");
        $method = I("method");
        $this->logger("引數:$transid,$ts,$sign,$method");
        if(!$sign || !$transid || !$ts || !$method){
            $this->getResponseMessage(ResponseCode::EMPTY_PARAM,"引數為空","");
        }else{
            if($this->checkSign($sign) || $method == "upgrade"){
                switch($method){
                    case "upgrade": //版本檢測
                        $this->upgrade();
                        break;
                    case "login": //登入
                        $this->login(I("uname"),I("ucode"));
                        break;
                    case 'changepwd': //密碼修改
                        $this->changepwd();
                    case "scan": //掃碼
                        $this->scan();
                        break;
                    case "syncorders": //同步
                        $this->syncorders();
                        break;
                    case "userinfo": //使用者中心資料來源
                        $this->userinfo();
                        break;
                    case "express":  //快遞公司清單
                        $this->express();
                        break;
                    case "express_byid": //根據id查詢快遞公司
                        $this->express_byid();
                        break;
                    case "express_daycount": //今日快遞公司統計
                        $this->express_daycount();
                        break;
                    case "backorder_daylist": //今日退單列表
                        $this->backorder_daylist();
                        break;
                    case 'getprebackorder_bybarcode': //根據掃碼單號獲取訂單詳情
                        $this->getprebackorder_bybarcode();
                        break;
                    case 'getprebackorder_byid': //根據id獲取訂單
                        $this->getprebackorder_byid();
                        break;
                    case 'preorder_unusal_deal': //異常訂單處理
                        $this->preorder_unusal_deal();
                        break;
                    case 'backorder_forcheck': //待拆包訂單
                        $this->backorder_forcheck();
                        break;
                    case 'backorder_forcheck_submit': //提交拆包檢查結果
                        $this->backorder_forcheck_submit();
                        break;
                    default:
                        $this->getResponseMessage(ResponseCode::UNKNOWN_METHOD,"未知的方法名!","");
                        break;
                }
            }else{
                $this->getResponseMessage(ResponseCode::SIGN_ERROR,"簽名檢驗失敗","");
            }
        }
    }

 

返回體:

private function getResponseMessage($code,$msg,$data="",$mark1=0,$mark2=""){
   return $this->ajaxReturn(array("code"=>$code,"msg"=>$msg,"data"=>$data,"mark1"=>$mark1,"mark2"=>$mark2));
}

 

以登入方法為例:

/**
     *  method: login
     */
    private function login($uname,$ucode){
        $user = M("User")->where(array("user_code"=>$uname))->find();
        if(!$user){
            $this->getResponseMessage(ResponseCode::INVALID_USER,"錯誤的使用者名稱或密碼",null);
        }else{
            //客戶端應該上傳md5以後的密碼
            if($user["password"]==$ucode){
                $_SESSION["user"] = $user;
                //更新登入次數和最後登入時間
                $user["logincount"] = $user["logincount"]+1;
                M("User")->save($user);
                $this->getResponseMessage(ResponseCode::SUCCESS,"登入成功",$user);
            }else{
                $this->getResponseMessage(ResponseCode::INVALID_USER,"錯誤的使用者名稱或密碼",null);
            }
        }
    }

 

二、後臺服務實現

後臺服務主要功能為:

1. 超過一定時間訂單轉為超時訂單。

2. 預退貨單匹配入庫單轉拆包。

3. 退貨單如果沒有預退貨單,自動轉拆包(需求變更)。

4. 丟包單匹配入庫單轉拆包。

庫表結構比較簡單,需求產生過變化,一開始入庫單是不轉自動拆包的,後來客戶提取此需求,我沒有擴充套件再建新表了,就在預退貨單表裡加了一個order_from欄位,1是後臺錄入預退貨,2是入庫單沒有匹配到預退貨單自動拆包,如下圖:

考慮到需要常駐後臺程序,執行定時任務,伺服器為centos,使用springboot搭建,mvc結構,持久層用了jdbcTemplate,表關係比較簡單,沒有用事務。JdbcTemplate為了獲取剛插入的自增鍵ID,費了老鼻子勁。

dao層實現主要幾個功能如下:

超時訂單,為了後臺修改超時天數能實時生效,每次執行定時任務,查詢一下超時天數:

 /**
     *  超時訂單
     */
    public void updateExpiredPrebackorder(){
        String sql = "SELECT config_value FROM tb_config WHERE config_code='expire_time'";
        int days = this.jdbcTemplate.queryForObject(sql,int.class);
        log.info("超時設定為:"+days);
        sql = "SELECT * FROM tb_prebackorder WHERE prebackorder_status=1 AND DATEDIFF(now(),prebackorder_date)>=?";
        List<PrebackorderEntity> list = this.jdbcTemplate.query(sql,new BeanPropertyRowMapper(PrebackorderEntity.class),days);
        for (PrebackorderEntity pbo:
             list) {
            sql = "UPDATE tb_prebackorder SET prebackorder_status=5  WHERE prebackorder_id=?";
            this.jdbcTemplate.update(sql,pbo.getPrebackorder_id());
            //寫日誌
            sql = "INSERT INTO tb_flows(flows_date,flows_man,flows_content,flow_forms,flow_forms_id) VALUES(now(),?,?,?,?)";
            log.info("設定為超時:"+pbo.getPrebackorder_id());
            jdbcTemplate.update(sql,"系統服務","超過"+days+"天未匹配退貨單,自動設定為超時","prebackorder",pbo.getPrebackorder_id());
        }
    }

丟包單匹配入庫單:

/**
     * 丟包單匹配入庫單程序
     *  考慮到所有的backorder都會流轉到prebackorder,所有隻要與prebackorder進行匹配即可
     *  只有prebackstatus=2(拆包)的才能進行匹配,其他狀態不能匹配,如手工錄入預退貨單、超時單等都不能進行匹配,2是從掃碼入庫單過來的,才能保證是入庫的。
     */
    public void lostOrdersMatchs(){
        String sql = "SELECT a.lostorder_id,b.prebackorder_id FROM tb_lostorder a INNER JOIN tb_prebackorder b ON b.prebackorder_code=a.lostorder_code " +
                "WHERE backstore_flag=0 AND prebackorder_status=2";
        List<Map<String,Object>> list = this.jdbcTemplate.queryForList(sql);
        log.info("進行一次丟包單匹配,共匹配到"+list.size()+"條入庫單");
        for (Map<String,Object> map:
             list) {
            //1. 更新lostorder的backstore_flag為1
            String sql1 = "UPDATE tb_lostorder SET backstore_flag=1 WHERE lostorder_id="+map.get("lostorder_id");
            //2. 更新prebackorder的狀態為2(拆包檢驗)
            String sql2 = "UPDATE tb_prebackorder SET prebackorder_status=2 WHERE prebackorder_id="+map.get("prebackorder_id");
            //3. 插入一條流程記錄
            String sql3 = "INSERT INTO tb_flows(flows_date,flows_man,flows_content,flow_forms,flow_forms_id) " +
                    "VALUES(now(),'系統服務','丟包單入庫轉拆包','prebackorder',"+map.get("prebackorder_id")+")";
            log.info("更新入庫單"+map.get("prebackorder_id")+"進入拆包流程");
            this.jdbcTemplate.batchUpdate(sql1,sql2,sql3);
        }
    }

退貨入庫單(即快遞掃碼入庫),如果沒有預退貨單匹配成功,自動進拆包檢驗流程:

/**
     * 將超過一定時間的退貨單,自動流轉進拆包檢驗
     */
    public  void setBackorder2Prebackorder(){
        String sql = "SELECT * FROM tb_backorder WHERE match_preorder_flag=0";
        List<BackorderEntity> list = this.jdbcTemplate.query(sql,new BeanPropertyRowMapper(BackorderEntity.class));
        for (BackorderEntity bo:
             list) {
            //先要判斷,如果已存在預退貨單號,則不能再次插入
            List<PrebackorderEntity> pbolist = this.jdbcTemplate.query("SELECT * FROM tb_prebackorder WHERE prebackorder_code=?",new BeanPropertyRowMapper(PrebackorderEntity.class),
                    bo.getBackorder_code());
            if(pbolist!=null && pbolist.size()>0){
                log.info("匹配一條,但單號重複,不用進prebackorder,("+bo.getBackorder_code()+"),直接將該訂單matc_preorder_flag改為1。");
                this.jdbcTemplate.update("UPDATE tb_backorder SET match_preorder_flag=1 WHERE backorder_id=?",bo.getBackorder_id());
            }else{
                sql = "INSERT INTO tb_prebackorder (prebackorder_code,prebackorder_date,express_id,userid,prebackorder_status,add_date,match_flag,match_date,match_code,prebackorder_from)" +
                        " VALUES (?,?,?,?,?,?,?,?,?,?)";
                KeyHolder keyHolder = new GeneratedKeyHolder();
                //直接進入狀態2,退貨單匹配成功,待拆包
                PreparedStatementCreatorFactory pscf = new PreparedStatementCreatorFactory(sql,new int[]{
                        Types.VARCHAR, Types.DATE,Types.INTEGER,Types.INTEGER,Types.INTEGER,Types.DATE,Types.INTEGER,Types.DATE,Types.VARCHAR,Types.INTEGER
                });
                pscf.setReturnGeneratedKeys(true);
                Object[] obj = new Object[]{
                        bo.getBackorder_code(),bo.getBackorder_date(),bo.getExpress_id(),bo.getUserid(),2,new Date(),1,new Date(),bo.getBackorder_code(),2
                };
                PreparedStatementCreator psc = pscf.newPreparedStatementCreator(obj);
                int result = this.jdbcTemplate.update(psc,keyHolder);
                //加一行flows
                int preid = keyHolder.getKey().intValue();
                sql = "INSERT INTO tb_flows(flows_date,flows_man,flows_content,flow_forms,flow_forms_id) VALUES(now(),?,?,?,?)";
                jdbcTemplate.update(sql,"系統服務","退貨單自動流轉","prebackorder",preid);
                //更新入庫單為已匹配
                this.jdbcTemplate.update("UPDATE tb_backorder SET match_preorder_flag=1 WHERE backorder_id=?",bo.getBackorder_id());
                log.info("自動流轉進入拆包訂單");
            }
        }
    }

預退貨單匹配入庫單:

/**
     * 匹配預退貨訂單與退貨入庫單
     */
    public void MatchPreOrders(){
        String sql = "SELECT * FROM tb_prebackorder WHERE prebackorder_status IN (1,5)"; //超時訂單也可以匹配重新入庫
        List<PrebackorderEntity> list = this.jdbcTemplate.query(sql,new BeanPropertyRowMapper(PrebackorderEntity.class));
        for (PrebackorderEntity pbo:
             list) {
            String msql = "SELECT * FROM tb_backorder WHERE backorder_code=? LIMIT 1";
            List<BackorderEntity> bolist = this.jdbcTemplate.query(msql,new BeanPropertyRowMapper(BackorderEntity.class),
                    pbo.getPrebackorder_code());
            if(bolist!=null && bolist.size()>0){
                matchOrders(pbo, bolist.get(0));
                sql = "INSERT INTO tb_flows(flows_date,flows_man,flows_content,flow_forms,flow_forms_id) VALUES(now(),?,?,?,?)";
                jdbcTemplate.update(sql,"系統服務","自動匹配退貨單","prebackorder",pbo.getPrebackorder_id());
                //再更新下退貨單表為匹配成功
                jdbcTemplate.update("UPDATE tb_backorder SET match_preorder_flag=1 WHERE backorder_id=?",bolist.get(0).getBackorder_id());
                log.info("匹配成功一條記錄("+pbo.getPrebackorder_code()+")");
            }else{
                //有的退單,是在原單號上加一個字首,所以如果預退單號的indexof入庫單號大於0,則匹配成功
                sql = "SELECT * FROM tb_backorder WHERE LOCATE(backorder_code,'"+pbo.getPrebackorder_code()+"')>1 LIMIT 1";
                bolist = this.jdbcTemplate.query(sql,new BeanPropertyRowMapper(BackorderEntity.class));
                if(bolist!=null && bolist.size()>0){
                    matchOrders(pbo, bolist.get(0));
                    log.info("匹配成功一條記錄("+pbo.getPrebackorder_code()+")");
                    //寫日誌
                    sql = "INSERT INTO tb_flows(flows_date,flows_man,flows_content,flow_forms,flow_forms_id) VALUES(now(),?,?,?,?)";
                    jdbcTemplate.update(sql,"系統服務","自動匹配退貨單","prebackorder",pbo.getPrebackorder_id());
                    //同步更新退貨單表為匹配成功
                    jdbcTemplate.update("UPDATE tb_backorder SET match_preorder_flag=1 WHERE backorder_id=?",bolist.get(0).getBackorder_id());
                    log.info("模糊匹配成功一條記錄");
                }
            }
        }
    }

 

service層,沒有具體的業務邏輯,略。

定時任務:

 /**
     * 每10秒鐘執行一次
     */
    @Scheduled(cron = "0/10 * * * * *" )
    public void Match(){
        this.service.MatchPreOrders();
        log.info("執行一次匹配退單流程");
    }

    /**
     * 每天0點執行一次
     */
    @Scheduled(cron = "0 0 0 * * ?")
    public void updateExpiredPrebackorder(){
        log.info("更新一次超時記錄");
        this.service.updateExpiredPrebackorder();
    }

    /**
     * 每30秒鐘執行一次
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void setBackorder2Prebackorder(){
        log.info("執行一次:退貨單自動流轉至拆包");
        this.service.setBackorder2Prebackorder();
        log.info("執行一次:丟包單自動匹配");
        this.service.lostOrdersMatchs();
    }