python脚本批量清除文件bom头

Continue Read..

今天天气很好,我们来清除bom头吧!

修改了网上的2个脚本,原版是https://gist.github.com/yhben/5169561,py 2.x版本的,本机环境3.x做完3的兼容还是下不太好用

无法清除utf-8的bom头,翻了几篇文章,改掉了他的清理函数,效果棒棒哒

#coding=utf-8

'''
* 去除指定类型文件的bom头
* 版权所有
* @author      t6760915<t6760915@gmail.com>
* @version     $Id: trim_bom.py $
* 原版         https://gist.github.com/yhben/5169561
* 原版不能检测所有的UTF-8 + bom的文件
'''

import os
import sys
import codecs

class TrimBom:
    
    basePath = ''
    fileList = []
    BUFSIZE = 4096
    BOMLEN = len(codecs.BOM_UTF8)
    #trimExtList = ['php', 'css', 'js', 'py', 'pl', 'html', 'htm']
    trimExtList = ['php']

    def remove_bom(self, filepath):
        with open(filepath, 'r+b') as fp:
            chunk = fp.read(self.BUFSIZE)
            if chunk.startswith(codecs.BOM_UTF8):
                #print(filepath)
                i = 0
                chunk = chunk[self.BOMLEN:]
                while chunk:
                    fp.seek(i)
                    fp.write(chunk)
                    i += len(chunk)
                    fp.seek(self.BOMLEN, os.SEEK_CUR)
                    chunk = fp.read(self.BUFSIZE)
                fp.seek(-self.BOMLEN, os.SEEK_CUR)
                fp.truncate()
                print('Converted: ' + filepath)
            '''
            else:
                print(filepath + " file_encoding is utf8 without BOM.")
            '''

    #获取指定类型文件名列表
    def getFileListByExt(self, path):
        if not path:
            return False

        path = os.path.normpath(path)

        if not os.path.exists(path):
            return False

        if os.path.isfile(path) and path.split('.')[-1] in self.trimExtList:
            trimFlag = self.remove_bom(path)
            #print(path,trimFlag)
            if trimFlag:
                printf('process %s success...' , path.replace(self.basePath, '').replace('\\', '/'))
            
        elif os.path.isdir(path):
            fileNameList = os.listdir(path)
            for fileName in fileNameList:
                fileName = os.path.normpath('%s/%s' % (path,fileName))
                self.getFileListByExt(fileName)
        
        return False

    #运行函数入口
    def run(self, path):
        self.basePath = os.path.normpath(path)
        self.getFileListByExt(path)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('USEAGE:python %s dirName' , __file__)
        sys.exit(0)
    
    tObj = TrimBom()
    tObj.run(sys.argv[1])

声明:此文系舞林cuznwww.wulinlw.org)原创稿件,转载请保留版权

python脚本批量清除文件bom头

Continue Read..

今天天气很好,我们来清除bom头吧!

修改了网上的2个脚本,原版是https://gist.github.com/yhben/5169561,py 2.x版本的,本机环境3.x做完3的兼容还是下不太好用

无法清除utf-8的bom头,翻了几篇文章,改掉了他的清理函数,效果棒棒哒

#coding=utf-8

'''
* 去除指定类型文件的bom头
* 版权所有
* @author      t6760915<t6760915@gmail.com>
* @version     $Id: trim_bom.py $
* 原版         https://gist.github.com/yhben/5169561
* 原版不能检测所有的UTF-8 + bom的文件
'''

import os
import sys
import codecs

class TrimBom:
    
    basePath = ''
    fileList = []
    BUFSIZE = 4096
    BOMLEN = len(codecs.BOM_UTF8)
    #trimExtList = ['php', 'css', 'js', 'py', 'pl', 'html', 'htm']
    trimExtList = ['php']

    def remove_bom(self, filepath):
        with open(filepath, 'r+b') as fp:
            chunk = fp.read(self.BUFSIZE)
            if chunk.startswith(codecs.BOM_UTF8):
                #print(filepath)
                i = 0
                chunk = chunk[self.BOMLEN:]
                while chunk:
                    fp.seek(i)
                    fp.write(chunk)
                    i += len(chunk)
                    fp.seek(self.BOMLEN, os.SEEK_CUR)
                    chunk = fp.read(self.BUFSIZE)
                fp.seek(-self.BOMLEN, os.SEEK_CUR)
                fp.truncate()
                print('Converted: ' + filepath)
            '''
            else:
                print(filepath + " file_encoding is utf8 without BOM.")
            '''

    #获取指定类型文件名列表
    def getFileListByExt(self, path):
        if not path:
            return False

        path = os.path.normpath(path)

        if not os.path.exists(path):
            return False

        if os.path.isfile(path) and path.split('.')[-1] in self.trimExtList:
            trimFlag = self.remove_bom(path)
            #print(path,trimFlag)
            if trimFlag:
                printf('process %s success...' , path.replace(self.basePath, '').replace('\\', '/'))
            
        elif os.path.isdir(path):
            fileNameList = os.listdir(path)
            for fileName in fileNameList:
                fileName = os.path.normpath('%s/%s' % (path,fileName))
                self.getFileListByExt(fileName)
        
        return False

    #运行函数入口
    def run(self, path):
        self.basePath = os.path.normpath(path)
        self.getFileListByExt(path)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('USEAGE:python %s dirName' , __file__)
        sys.exit(0)
    
    tObj = TrimBom()
    tObj.run(sys.argv[1])

声明:此文系舞林cuznwww.wulinlw.org)原创稿件,转载请保留版权

【php】代理接口下载

Continue Read..

流程是浏览器访问A接口  A代理B接口 B生成下载文件并触发浏览器下载

B返回给A的是一个文件流    如下图

 

点击查看原图

 

A代理分块下载

    //参数说明:

    //file_dir:文件所在目录

    //file_name:文件名

    function download($file_dir,$file_name){

        //获取文件大小

        $file_size = 0;

        $fileArr = get_headers($file_dir);

        foreach ($fileArr as $k=>$v){

            $arr = explode("Content-Length: ", $v);

            if(!empty($arr[1])){

                $file_size = $arr[1];

                break;

            }

        }

        if(empty($file_size)){

            return false;

        }

        header("Content-type: application/octet-stream");

        header("Accept-Ranges: bytes");

        header("Accept-Length: $file_size");

        header("Content-Disposition: attachment; filename=".$file_name);

    

        $fp = fopen($file_dir,"r");

        $buffer_size = 1024;

        $cur_pos = 0;

    

        while(!feof($fp)&&$file_size-$cur_pos>$buffer_size){

            $buffer = fread($fp,$buffer_size);

            echo $buffer;

            $cur_pos += $buffer_size;

        }

    

        $buffer = fread($fp,$file_size-$cur_pos);

        echo $buffer;

        fclose($fp);

        return true;

    }

 

 

        $parame["id"]      = intval(Yii::$app->request->get('id', 0));

        $zipName = $parame["gameId"]  .'.zip';

        $parame=http_build_query($parame);

        $this->download($this->pincheApiUrl."/userinfo/download?". $parame, $zipName);

 

 

 

 

    /**

     * 代理下载

     * @param unknown $file_dir 文件所在目录/文件流

     * @param unknown $file_name 生成文件名

     * @return boolean

     */

  function download($file_dir,$file_name){

        try {

            $fp =  fopen($file_dir,"r");

            $buffer_size = 1024;

            

            //读第一次,判断是否生成文件成功

            $buffer = fread($fp,$buffer_size);

            if($buffer == "无法找到文件!" || $buffer =="无法打开文件,或者文件创建失败"){

                echo $buffer;die;

            }

            

            header("Content-type: application/octet-stream");

            header("Accept-Ranges: bytes");

            header("Content-Disposition: attachment; filename=".$file_name);

            echo $buffer;

            

            while(!feof($fp)){

                $buffer = fread($fp,$buffer_size);

                echo $buffer;

            }

            fclose($fp);

            return true;

        }catch (\Exception $e) {   

//             print $e->getMessage();   

            echo "失败";

            exit();   

        }  

    }

 

==================================CURL==================================

目前有一个接口 A  http://192.168.137.73/test2.php

$localFileName = "123.zip";

 

$fp =  fopen($localFileName,"r");

$buffer_size = 1024;

//             var_dump($_SERVER);die;

//读第一次,判断是否生成文件成功

$buffer = fread($fp,$buffer_size);

if($buffer == "无法找到文件!" || $buffer =="无法打开文件,或者文件创建失败"){

    echo $buffer;die;

}

 

header("Content-type: application/octet-stream");

header("Accept-Ranges: bytes");

header("Content-Disposition: attachment; filename=".$localFileName);

echo $buffer;

 

while(!feof($fp)){

    $buffer = fread($fp,$buffer_size);

    echo $buffer;

}

fclose($fp);

 

 

现在需要curl 请求这个接口实现代理下载,  分段接收

 

    public static function curlDownload($url, $params,$method = "GET",$header = array(),$cookie="") {

        $ch = curl_init();

        //下载一个超过1G的大文件

        curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/binary","Expect:") );

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);

        switch ($method){

            case "GET":

                if (! empty($params)) {

                    $url = $url . (strpos($url, '?') ? '&' : '?') . (is_array($params) ? http_build_query($params) : $params);

                }

                curl_setopt($ch, CURLOPT_URL, $url);

                break;

            case "POST":

                curl_setopt($ch, CURLOPT_POSTFIELDS, $params);

                curl_setopt($ch, CURLOPT_URL, $url);

                break;

        }

        if(!empty($header)){

            curl_setopt ( $ch, CURLOPT_HTTPHEADER, $header );

        }

        if(!empty($cookie)){

            curl_setopt($ch, CURLOPT_COOKIE, $cookie);

        }

        //分段返回header

        curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $string){

            $length = strlen($string);

            $arr = explode("Content-Disposition: attachment; filename=", $string);

            if(!empty($arr) && !empty($arr[1])){

                header('Cache-Control: public');

                header('Content-Description: File Transfer');

                header('Content-Transfer-Encoding: binary'); //告诉浏览器,这是二进制文件

                header("Content-Disposition: attachment; filename=$arr[1]");

            }

//             echo "Header: $string<br />\n";

            return $length;

        });

        //分段缓存  

        curl_setopt($ch, CURLOPT_BUFFERSIZE, 2048);

        //分段返回body  和长度

        curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $string){

            $length = strlen($string);

            echo $string;

//             echo "Received $length bytes<br />\n";

            return $length;

        });

        

        curl_exec($ch);

        

        if ($error = curl_error($ch)) {

            echo "Error: $error<br />\n";

        }

    }



跳转到代理上传文章

声明:此文系舞林cuznwww.wulinlw.org)原创稿件,转载请保留版权

支付系统设计2 -- 支付系统的对账处理

Continue Read..

转载自:http://blog.lixf.cn/essay/2016/10/10/account-2-reconciliation

可以说,对账是支付系统最头疼的事情。每一笔交易,都要做到各参与者的记录能够吻合,没有偏差。对账系统的工作,是发现有差异的记录,即轧帐;然后通过人工或者自动的方式,解决这些差异,即平帐。

对电商系统来说,每一笔交易,在所有相关主体侧都要能对得上:

  • 交易主体,如果发起人是个人,必须能够从个人交易历史记录中找到这笔交易。但大部分人不会保留电子记录,所以一般是提供可以下载的账单或交易记录,让用户自己对去。
  • 交易对手,一般是商户。商户侧对账处理同用户侧,也仅仅提供对账单。
  • 交易渠道侧,这是对账的重点,一是核实交易流水,二是核实交易佣金,毕竟是租用人家通道做结算的。

那有哪些记录需要对账? 目前主要是两个:一个是交易记录;一个是退款记录。

对账处理流程

一般来说,对账流程涉及到如下步骤: 渠道对账单下载、本地交易记录准备、轧账、平账。

渠道对账单下载

银行,第三方支付,银联等,基本都会提供对账单下载的功能。不过也有少数工作做不到位或者太到位的银行,只提供账单查询后台,不提供对账单下载功能。 对开发人员来说,这里有几个坑:

  • 对账单格式不一。文本,XML,csv的都有。为了后续能够统一处理,在账单下载完成后,需要进行标准化处理。

  • 下载方式不一,HTTP,HTTPS,FTP的,都有。下载程序需要按照渠道的协议来处理。

  • 下载时间不一,一般是凌晨1点后,到中午12才能用的也有。如果在预定的时间取不到数据,需要注意重试读取。

  • 稳定性差。FTP服务器出问题那是常有的事。渠道侧解决方案往往就是重启。所以重试机制是必要的。

看一下第三方支付的对账单情况:

渠道 对账周期 账单提供方式 账单文件格式
支付宝 每天 2:10 HTTPS XML
支付宝退款 每天3:10 HTTPS XML
百付宝 每天7:00 FTP TXT
百付宝退款 每天7:00 FTP TXT
微信支付 每天10:30 HTTPS TXT
微信退款 每天10:30 HTTPS TXT

技术选型上,HTTP(S)用apache httpclient即可实现链接池和断点续传, FTP也可以使用Apache Commons Net API。 但不管是哪一个,都需要设置重试次数和链接超时间。重试次数和间隔的设置需要小心,重试太频繁,容易把服务器打死.;时间间隔太大,又会阻塞后续处理步骤。5~10分钟是一个合适的重试间隔区间。

链接超时指在服务器出现问题时,连接在指定时间内获取不到数据即自动断开。这个很容易被忽略。我们有一次系统出问题,是渠道侧的FTP假死后重启,导致我们的客户端挂住,一直在等待重新链接。

渠道对账单标准化

找个例子大家看看, 比如微信的对账单,他是csv格式的,包括如下信息:

  1. 交易时间:这是在微信侧的支付完成的时间。 这个时间会成为一个陷阱。

  2. 公众账号ID,商户号,子商户号,设备号: 这些信息需要做验证,确保是自己的单子,不要让微信把老王家的单子也给发过来了;

  3. 微信订单号,商户订单号: 这两个是对单的核心。前者是微信侧产生的订单号,在微信支付接口返回值中有。但是万一收不到这个返回值,那在本地记录中可能就空了。 后者是我们发送给微信的订单号,一般用这个来做对单依据。两边的数据中都会有这个值。

  4. 用户标识,交易类型,交易状态,付款银行,货币种类,总金额,企业红包金额: 这几个就是对单的核心字段,必须确保双方是一致的。

  5. 商品名称,商户数据包,手续费,费率:这些是可选验证。

微信对账单

而某宝的对账单,是文本格式的,用空格隔开。他们家的就简单很多,只有商户订单号,交易流水号,交易时间,支付时间,付款方,交易金额,交易类型,交易状态这些字段。某宝对账单

由于每个渠道的账单格式都不尽相同, 在得到账单后,下一步是对账单做标准化处理,这样轧帐以及后续工作就可以统一处理了。 标准化后的账单数据可以放在文件系统或者数据库中。这取决于交易数据量。每天百万以上的量,还是使用文件系统,比较合适。数据库操作相对比较慢,也浪费资源。 基于文件系统的标准化涉及如下内容:

文件格式标准化 统一使用csv或者json或者xml格式。如果是使用hadoop或者spark来对账,使用csv是个不错的选择。

文件存储统一化 文件目录,文件名都需要遵循统一命名规范。

为了加快处理速度,我们使用hdfs作为文件系统,有利于后续的对账的处理。

本地交易记录准备

本地交易记录的准备,总的来说有如下方法: - 啥都不做,直接用原始数据。鉴于大部分系统使用的是mysql,这也意味着在MySQL上做对账。对账时需要大量的数据查找工作,必然会影响线上业务。在数据规模较大,比如超过100万时,就不太合适了。

  • 当然,还有一个选择是使用备库来执行对账,这样既简单,也不影响线上业务。这是典型的空间换时间的做法。

  • 如果业务大到需要分表分库才能处理,那对账数据准备也不一样。使用分库也不现实,因为分库一般是按照主体id,而不是渠道id,来分库,这样对账就需要在多个库上进行,效率反而降低了。而对分表分库建立从库也非常耗费资源。这种情况下,需要同步一份数据到(hdfs)文件系统中,或者NOSQL数据库上。

由于交易记录是支付系统核心数据,有大量的应用,如信用、风控等,都需要交易记录数据。这些应用对交易记录的需求还不完全一致,为了提升性能, 交易记录会使用异步的方式来将数据投递给使用方。 交易记录在入库时,投递消息到消息系统中。使用方监听这个消息,一旦收到新消息,则从交易记录库中查询数据,获取数据并更新到库中。关于此类数据同步的文章不少,这里就不详细介绍。

轧帐

轧帐是按照客户订单号来比较本地交易记录和渠道交易记录是否一致。从算法角度,是计算两个数组的差异。在单机运行时,可以采用的算法不少,这里不详细介绍。 我们推荐采用mapreduce来轧帐,这有个优势,可以按照订单号将渠道提供的记录和本地记录shuffle到同一个reduce处理上,这样就可以很容易进行数据比对。 轧帐中最大的坑,莫过于切分点的问题。比如以整0点为切分点,那存在一个问题,本地23:59发起的交易,到了渠道侧,可能会在00:01处理,这一笔交易变成第二天的帐了。实际处理中,一笔交易在渠道侧处理,花上几分钟都有可能。 对于切分点附近无法确认的帐,做一个时间窗,在时间窗内的数据,留待第二天对账时继续处理。

平帐

发现两边不一致的数据,那应该如何处理?数据量不大时,记录起来,人工甄别就行。但如果数据量很大,每天上千条,人工处理就成本太高了。这个没有统一的处理方法,需要根据有问题的数据,做个分析,然后做自动处理。 针对交易记录的对账的处理,主要有如下情况:

  • 本地未支付,支付渠道已支付。这主要是本地未正确接收到渠道下发的异步通知导致。 一般处理是将本地状态修改为已支付,并做响应的后续处理,比如通知业务方等。

  • 本地已支付,支付渠道已支付,但是金额不同,这个需要人工核查。

  • 本地已支付,但是支付渠道中无记录;或者本地无记录,支付渠道有记录。在排除跨日因素外,这种情况非常少见,需要了解具体原因后做处理。

针对退款的对账处理,主要有如下情况:

  • 本地未退款,支付渠道已退款,则以支付渠道为准,修改本地为已退款状态,并出发后续处理。

  • 本地已退款、支付渠道已退款,但是金额不同,需要人工核查;

  • 本地已退款,但是支付渠道无记录;或者支付渠道有记录,但是本地没有。 在排除跨日因素外, 这种情况非常少见,需要了解具体原因后做处理。

总之,对账工作,即复杂也不复杂。需要细心,对业务要有深入的了解,并选择合适的架构。

声明:此文系舞林cuznwww.wulinlw.org)原创稿件,转载请保留版权

支付系统设计1 -- 账户模型

Continue Read..
转载自:http://blog.lixf.cn/essay/2016/10/08/account-1/

账户体系是支付系统的基础,它的设计直接影响整个系统的特性。这里探讨如何针对电子商务系统的账户体系设计。我们从一些基本概念开始入手,了解怎么建模。

支付账户和登录账号

账户体系设计首先要区分两个概念,支付账户和登录账号。 这是两个不同业务领域的概念。 支付账户 指用户在支付系统中用于交易的资金所有者权益的凭证。 登录账号 指用户在系统中的登录的凭证和个人信息。 一个用户可以有多个登录账户,一个登录账户可以有多个支付账户,比如零钱账户,储值卡账户等。 一般来说,支付账户不会在多个登录账户之间共用。如果没有特殊说明,下文中的账户,都默认指支付账户。

账户的设计需求

在支付系统中,账户的设置,主要是从如下几个方面来考虑:

  • 交易的需求,比如检查账户是否被锁定、余额是否足够、是否有效等。

  • 记账的需求,按照公司会计需求记录账户上的所有行为,包括支出、充值、转账等。

  • 对账的需求,包括和支付渠道、商户、个人的对账需求,核对交易和账户余额是否正确。

  • 风控的需求,如反洗钱、反欺诈等,都需要依赖于账户体系来提供核心数据。本文暂不分析这个内容,将在《支付风控》、《支付反洗钱》这两篇文章中详细分析

  • 信用的需求,对用户、资产、商户等主体进行信用评估时,也需要依赖账户体系来提供的核心数据。本文也暂不分析这内容,将在《信用与支付》一文中分析。

这五个需求,按照其设计的优先级,也是从支付、记账、对账、风控来进行。 支付系统根据其发展所处的阶段,逐步将新增需求纳入设计中。

交易与账户

账户设置,一般是从交易开始的。 交易的实现必须有账户的支持,账户是交易的基本构成元素。 从支付系统的角度,交易中涉及到的资金流是资金从一个账户流向另一个账户。 发起交易的一方,被称之为交易主体,他可以是个人,也可以是一个机构。 资金从该主体所拥有的账户中流出。 而接收交易的一方,被称为交易对手,他也可以是个人,或者机构。 和第三方支付或者金融机构的交易不同,电商系统中,交易还会涉及到渠道。 由于电商系统本身并无清结算的资质,所有资金从交易主体到交易对手的账户的流动,在大部分情况下,并没有经过电商系统,而是由电商系统调用支付渠道提供的接口,由它来完成真正的支付过程。 当然,渠道也不是活雷锋,在这过程中,渠道要收取费用。所以,在电商系统中,一次交易会涉及到三个账户: 交易主体账户、交易对手账户以及支付渠道账户。 如何在这三个账户中完成一次交易,我们将在后续的《交易和记账》一文中详细分析。

记账与账户

公司的会计需要对每一笔交易都要做详细的记录,即记账。 公司每天都产生大量的交易行为,为了便于管理和统计,一个简单的方法是对交易进行分类,比如食品、带宽、办公用品等等。 这个分类,按照公司的规模和业务复杂度,可以有一级,二级,三级或者更多级的结构,这被称之为会计科目。 记账时,除了交易明细,还需要在每个级别上对交易额进行汇总。 一般来说,一级科目上汇总称为总帐科目,而详细记录称为明细科目。 在电商系统中,由于涉及到的参与方较多,记账也相对复杂,但基本方法也是类似的。 电商的参与者可以分为商户、买家和渠道,对这三类参与者,都需要分别建立总帐账户和明细账户。

内部账户和外部账户

当用户使用银行卡来支付时,电商支付系统需要和银行对接,从用户银行卡所代表的账户上扣除资金。对接了银行,第三方支付等机构的电商支付系统,它需要连接到用户在这些机构的账户来执行扣款或者充值操作,这些账户或称为外部账户。对外部账户,支付系统只能记录账户在本系统的明细以及累计消费额,无法得知账户真正余额。 不少电商在玩零钱的概念,也就是让用户充值到零钱,使用的时候就直接从零钱中扣除。这就需要零钱账号。这是电商系统中自己设立的账号,所以也叫内部账号,可以知道账号的全部消费明细和余额。 当然,除了零钱账号,也可以有储值卡账号,信用账号等。那问题来了,什么时候需要建立账户,比如优惠券,需要账户吗? 一次消费的储值卡和可以充值的储值卡,需要建立账户吗?这里先埋个雷,后续介绍支付和记账时,给出答案。

收款账户和收单账户

当电商要对接银行时,往往都会被要求开设一个收款账户。用户通过这个银行来支付时,钱就被转到这个账户上。 对第三方支付也是一样。收款账户是开设在银行或者第三方支付这边的, 即渠道侧。 一般来说,渠道每天都可以提供这个账户的交易流水供电商对账用。 这样在电商这边,渠道就成为一个收单机构。 所以在电商这边,建立这个收款账户对应的对账用的收单账号,用来记录通过这个渠道进行的各项交易流水。

账户建模

说了这么多,目的是为了对账户建模。 账户模型是和公司业务密切相关的,公司不同规模,发展的不同阶段需要不同的模型。 账户建模本身包括三大核心模型:实体模型、账户模型和交易模型。 从交易模型中可以衍生出针对各个角色的账户流水,即明细模型,用于支持对账。

实体模型

实体模型和用户、商户模型有重叠的地方,这里专门针对支付而设置的各个实体属性。 一般来说,支付相关的实体模型需要包括如下的属性:

  • 用户ID,一般直接映射到登录账户的ID;。

  • 是否允许执行支付;

  • 支付密码;

  • 用于设置或者重置支付密码的手机号;

  • 用户设置或者重置支付密码的邮箱;

  • 用户的安全等级,根据业务需要来设置。

账户模型

根据业务需要,可以设置多种账户,如支付账户、预付卡账户、代扣账户、零钱账户、结算账户等。 从类别上来说,这里的账户,一般指总账账户。一般来说电商系统中涉及的账户类型有:

  • 虚拟币账号:用户和使用奇点奇豆的商户都需要建立虚拟币账户。

  • 代扣账号: 用来支持订阅类型的定期代扣;

  • 零钱账号:即电商的内部账号,用户、商户、清算单位需要建立零钱账户

  • 第三方支付账号:用户在第三方支付机构建立的账户。

  • 银行卡账号:用户的银行卡信息,每个卡对应一个账户。

  • 结算账号:用来支持和第三方支付公司、银行进行结算用。 第三方支付需要为每个商户号建立结算账号;银行需要为借记卡、贷记卡分别建立结算账号(有必要吗?银行卡直连时使用)。

  • 代扣代缴账户:用来支持代扣税款业务。

对这些账户,需要设置如下属性: 基本属性,包括:

  • 账户号,或称为账户ID,一般是系统自动生成。特别注意的是,要事先约定好账户ID的规则。比如头三位用来表示账户类型,后几位用来表示账户编号等。务必保证根据账号号能够快速确定账户类型,并且保证账户号是不重复的。

  • 账户名称,一般是由用户自己设置的,显示用。

  • 账户使用的货币类型,注意虽然一张银行卡可以支持多个币种,实际在内部,还是针对每个币种建立独立的子账户。 涉及到多币种的账户,也可以采用类似的建模方案。

  • 会计科目代码,一般是一级会计科目的代码。

账户控制相关:

  • 是否允许充值;

  • 是否允许提现;

  • 是否允许透支;

  • 是否允许支付;

  • 是否允许转账进入;

  • 是否允许转账转出;

  • 是否有安全保障;

  • 是否激活;

  • 是否冻结;

资金相关:

  • 当前账户余额:等于可用余额+冻结余额;

  • 当前账户可用余额;

  • 当前账户冻结的余额。冻结余额指在账户上暂不能使用的额度。在支付的时候,往往是先冻结,商品出库后, 再实际执行扣款。

银行卡、第三方支付信息:

  • 第三方实体的ID;

  • 第三方账号,如银行卡号或者在第三方支付的open_id等;

  • 第三方的app_id;

  • 账号的失效日期,该账号什么时候失效。

注意,有些第三方信息是不能保存的,如用户的账号密码、信用卡的CV号等。 为了避免账户信息被爬库或者数据库信息意外泄露,一般还需要对敏感字段,如密码等,进行加密保存,甚至保存到另外的表中。 更进一步,为了避免账户信息被意外修改,还可以增加一个校验字段,在写入数据时设置该字段,在读取数据时做校验,一旦发现数据有问题,则关闭该账号。

交易模型

交易记录,交易流水,账户流水,交易台账,这三个容易混淆的概念,从数据上来说,却并不复杂,它们的核心是交易流水,账户流水是从账户视角的交易流水。那对一笔交易,涉及到的方方面面内容很多,有哪些需要记录的呢?考虑到交易记录将被用于风控和信用分析,能收集到的信息是越全面越好。

  • 流水号:每一笔交易的流水号都不一样。需要根据业务情况详细设计流水号。这个号往往也是对交易表做分表分库的依据。

  • 交易记录创建时间;

  • 交易记录最后修改时间;

  • 会计科目代码

  • 关联的订单号,由商户提供;

  • 订单名称、描述、关联的地址等信息;

  • 费用信息,包括: 结算货币类型、原始费用、实际费用等;

  • 交易主体信息,记录主体ID、类型、名字、账号、账号类型、使用的IP地址、手机号、平台、通知邮箱、当前位置等。 这些信息虽然可以从主体表中获取,但考虑主体表信息随时会被修改,所以这里需要记录详细的各原始信息。

  • 交易对手信息,记录对手主体的ID,类型,名字,账号,账号类型,手机号,平台,通知邮箱等。

  • 交易渠道信息,记录所使用的交易渠道的实体id,渠道账户,渠道执行支付的时间、渠道侧返回的订单号等。如果有错误发生,还需要记录从渠道接收到的错误信息和错误码。

总结

如上内容,不管是账户还是交易,模型都很复杂。是否有必要记录这么多信息,如何在交易中使用这些模型,请关注后续文章。

头条的同学们,记得帮忙点赞啊。

感谢您对本文的关注,如需要及时收到凤凰牌老熊的最新作品,请扫码关注“凤凰牌老熊”的微信公众号,谢谢。


声明:此文系舞林cuznwww.wulinlw.org)原创稿件,转载请保留版权

【linux】安装supervisord 配置logstash启动

Continue Read..
yum install python-setuptools
easy_install supervisor

/usr/bin/supervisord -c /etc/supervisord.conf
/usr/bin/supervisorctl stop all


Error: Another program is already listening on a port that one of our HTTP servers is configured to use. Shut this program down first before starting
解决方法:

find / -name supervisor.sock
unlink /name/supervisor.sock

[program:elkpro_1]
environment=LS_HEAP_SIZE=5000m
directory=/opt/logstash
command=/opt/logstash/bin/logstash -f /etc/logstash/pro1.conf --pluginpath /opt/logstash/plugins/ -w 10 -l /var/log/logstash/pro1.log
[program:elkpro_2]
environment=LS_HEAP_SIZE=5000m
directory=/opt/logstash
command=/opt/logstash/bin/logstash -f /etc/logstash/pro2.conf --pluginpath /opt/logstash/plugins/ -w 10 -l /var/log/logstash/pro2.log



[program:output_tcp_jq]
environment=LS_HEAP_SIZE=5000m
directory=/cuzn/logstash-2.3.4
command=/cuzn/logstash-2.3.4/bin/logstash -f /cuzn/logstash-2.3.4/vconf/output_tcp_jq.conf --pluginpath /cuzn/logstash-2.3.4 -w 10 -l /cuzn/logstash-2.3.4/log/output_tcp_jq.log

测试  ps aux | grep output_tcp_jq

声明:此文系舞林cuznwww.wulinlw.org)原创稿件,转载请保留版权