快速将大脚本分拆成多份并发执行脚本 巧用命令 更快更爽

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!

关注公众号,进入学海无涯,高人带路模式!!​split​再难,有人带路,轻松搞定

命令比较晦涩

split -l 2 1.txt -d -a 4 data_&&ls|grep data_|xargs -n1 -i{} mv {} {}.sh

$ split --helpUsage: split [OPTION]... [FILE [PREFIX]]Output pieces of FILE to PREFIXaa, PREFIXab, ...;default size is 1000 lines, and default PREFIX is 'x'.With no FILE, or when FILE is -, read standard input.Mandatory arguments to long options are mandatory for short options too.  -a, --suffix-length=N   generate suffixes of length N (default 2)      --additional-suffix=SUFFIX  append an additional SUFFIX to file names  -b, --bytes=SIZE        put SIZE bytes per output file  -C, --line-bytes=SIZE   put at most SIZE bytes of records per output file  -d                      use numeric suffixes starting at 0, not alphabetic      --numeric-suffixes[=FROM]  same as -d, but allow setting the start value  -e, --elide-empty-files  do not generate empty output files with '-n'      --filter=COMMAND    write to shell COMMAND; file name is $FILE  -l, --lines=NUMBER      put NUMBER lines/records per output file  -n, --number=CHUNKS     generate CHUNKS output files; see explanation below  -t, --separator=SEP     use SEP instead of newline as the record separator;                            '\0' (zero) specifies the NUL character  -u, --unbuffered        immediately copy input to output with '-n r/...'      --verbose           print a diagnostic just before each                            output file is opened      --help     display this help and exit      --version  output version information and exitThe SIZE argument is an integer and optional unit (example: 10K is 10*1024).Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000).CHUNKS may be:  N       split into N files based on size of input  K/N     output Kth of N to stdout  l/N     split into N files without splitting lines/records  l/K/N   output Kth of N to stdout without splitting lines/records  r/N     like 'l' but use round robin distribution  r/K/N   likewise but only output Kth of N to stdoutGNU coreutils online help: <http://www.gnu.org/software/coreutils/>Report split translation bugs to <http://translationproject.org/team/>Full documentation at: <http://www.gnu.org/software/coreutils/split>or available locally via: info '(coreutils) split invocation'
不要造低级的轮子,这个轮子一点都不低级,如果自己去写代码的话,也好实现,不过,有现成的工具就用上吧,而且直接使用git bash就能完成脚本拆分。
这已经实现了昨天所期望的自动拆分脚本,而脚本生成,执行进度监控并没有实现,不过,感觉已经比较爽了。一个适度的平衡点吧!split -l 2 1.txt -d -a 4 data_&&ls|grep data_|xargs -n1 -i{} mv {} {}.sh 这轮子顶很多很多行代码了,如果写个程序进行大文本拆成多份文本,想想得多少行,这么一行命令就按定

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

巧用正则+Chrome彩蛋功能=快速生成cUrl脚本 fetch、cUrl cmd、cUrl bash、PowerShell

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!学会一招神技能​

今天遇到一个需要批量调接口的场景,从数据库中查出上万条数据,再按接口规则生成带get参数的url请求,还要带登陆状态。

一、从数据库中捞出数据

将查出来的数据贴到notepad++中,大概是这样子的数据,示范一下

1  a  112  b  223  c  334  d  445  e  556  f  667  g  778  h  889  i  99

将空格换成%20,这个直接把空格换掉就是,示例数据没有演示这个场景,自行脑补一下。

二、使用chrome生成一个带cookie的请求

操作截图如上图所示,是不是很方便,cookies都带上了,有登陆状态,调接口相当方面。

curl "http://yue.ma/?i=9&m=6&p=11" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecure

三、使用正则替换,生成批量脚本

制作一个脚本正则模板

curl "http://yue.ma/?i=$1&m=$2&p=$3" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecure

使用notepad++替换功能

查找目标填写:

^([^\t]+)\t([^\t]+)\t([^\t]+)$

替换为填写:

curl "http://yue.ma/?i=$1&m=$2&p=$3" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecure

生成脚本

curl "http://yue.ma/?i=1&m=a&p=11" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 Windows NT 10.0; Win64; x64 AppleWebKit/537.36 KHTML, like Gecko Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecurecurl "http://yue.ma/?i=2&m=b&p=22" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 Windows NT 10.0; Win64; x64 AppleWebKit/537.36 KHTML, like Gecko Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecurecurl "http://yue.ma/?i=3&m=c&p=33" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 Windows NT 10.0; Win64; x64 AppleWebKit/537.36 KHTML, like Gecko Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecurecurl "http://yue.ma/?i=4&m=d&p=44" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 Windows NT 10.0; Win64; x64 AppleWebKit/537.36 KHTML, like Gecko Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecurecurl "http://yue.ma/?i=5&m=e&p=55" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 Windows NT 10.0; Win64; x64 AppleWebKit/537.36 KHTML, like Gecko Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecurecurl "http://yue.ma/?i=6&m=f&p=66" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 Windows NT 10.0; Win64; x64 AppleWebKit/537.36 KHTML, like Gecko Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecurecurl "http://yue.ma/?i=7&m=g&p=77" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 Windows NT 10.0; Win64; x64 AppleWebKit/537.36 KHTML, like Gecko Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecurecurl "http://yue.ma/?i=8&m=h&p=88" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 Windows NT 10.0; Win64; x64 AppleWebKit/537.36 KHTML, like Gecko Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecurecurl "http://yue.ma/?i=9&m=i&p=99" -H "Connection: keep-alive" -H "Cache-Control: max-age=0" -H "Upgrade-Insecure-Requests: 1" -H "User-Agent: Mozilla/5.0 Windows NT 10.0; Win64; x64 AppleWebKit/537.36 KHTML, like Gecko Chrome/78.0.3904.108 Safari/537.36" -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cookie: __51cke__=; __tins__18251311=^%^7B^%^22sid^%^22^%^3A^%^201575548726427^%^2C^%^20^%^22vd^%^22^%^3A^%^203^%^2C^%^20^%^22expires^%^22^%^3A^%^201575551216664^%^7D; __51laig__=3" --compressed --insecure
执行脚本,我使用git bash执行的,直接新建一个bash.sh,把生成的代码放进去,执行脚本
./bash.sh
等着几万条调用执行吧!no no no,数据过多,可以拆成多份bash脚本执行,可以加快速度。这种执行方式有一个好处就是,不需要额外去写程序,直接一顿replace生成脚本跑起来。不好的就是命令太多的话,执行并没有进度显示。
可以写个辅助的脚本执行工具,自动拆份脚本并行执行,显示显示进度什么的。嗯嗯,学会一下^([^\t]+)\t([^\t]+)\t([^\t]+)$ 与chrome的脚本生成功能,非常不错!

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

FileBeat CPU占用率过高Case处理 | max_procs: 1

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!Filebeat​再难,有人带路,轻松搞定

Case: Filebeat内存占用率过高
处理​方案
max_procs: 1
问题渐进明细,遇到这种别人的东西CPU占用率过高,是不太好下手的​。解决这个问题的思路有2个,1是网上找踩过坑的资料,2是再现一下问题,寻找​思路。
一、找资料
FileBeat出现cpu占用过高,有说是FileBeat版本与kafka版本不一致,也有说监控文件过多,没有及时inactive的,还有就是限制c​pu使用数量的。最开始找资料的时候,并没有太看到限制cpu核数的​。所以​踩坑还是一踩到底。
二、再现问题
2.1 LogAuto模拟大量日志
为了重现问题,在本地启动一个Filebeat,输出为控制台,采集目录有多个,写了一个日志生成小工具。
using System;using System.Collections.Generic;using System.Configuration;using System.IO;using System.Linq;using System.Text;using System.Threading;namespace LogAuto{    class Program    {        static void Main(string[] args)        {            String siteName = ConfigurationManager.AppSettings["siteName"]??"default Site";            // 模拟 100个站点            List<LogThread> logThreadPool = new List<LogThread>();            for(int i=0;i<100;i++){                LogThread logThread = new LogThread();                logThread.logTag = String.Format("{0}", i);                logThread.siteName = siteName;                logThreadPool.Add(logThread);                new Thread(new ThreadStart(logThread.WriteLog)).Start();            }        }    }}
using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;namespace LogAuto{    class LogThread    {        public String logTag { get;set;}        public String siteName { get;set;}        public void WriteLog() {            while (true)            {                File.AppendAllText(String.Format("{0}.log.log", logTag), String.Format("产生一条日志 {0} {1}\r\n", siteName, DateTime.Now));                Console.WriteLine(String.Format("{2}输出一条日志:{0} {1}", siteName, DateTime.Now, logTag));                //Thread.Sleep(1000);            }        }    }}

​不管启多少个LogAuto,生成日志,filebeat采集日志,完全看不出Filebeat有什么不爽,于是,​这个模拟算是失败了。连多行日志都没有模拟出来,也没有让Filebeat​有任何压力。

​2.2 模拟大量线上日志

using System;using System.Collections.Generic;using System.Configuration;using System.Linq;using System.Net;using System.Text;using System.Threading;namespace LoadTest{    class Program    {        static void Main(string[] args)        {            int threadCount = int.Parse(ConfigurationManager.AppSettings["ThreadCount"] ?? "1000");            int threadSleep = int.Parse(ConfigurationManager.AppSettings["ThreadSleep"] ?? "1000");            String targetUrl = ConfigurationManager.AppSettings["TargetUrl"] ?? "http://yue.ma/?fbtest";            List<Thread> logThreadPool = new List<Thread>();            for (int i = 0; i < threadCount; i++)            {                Thread tread = new Thread((m) =>                {                    while (true) {                        try                        {                            WebClient wc = new WebClient();                            String data = wc.DownloadString(String.Format("{0}?r={1}", targetUrl, DateTime.Now));                            Console.WriteLine(String.Format("线程 {0} 返回结果 {1} 时间 {2}", m, data, DateTime.Now));                            Thread.Sleep(threadSleep);                        }                        catch (Exception ex) {                            Console.WriteLine(ex.Message);                        }                    }                });                logThreadPool.Add(tread);                tread.Start(i);            }            Console.WriteLine("输入任意字符退出");            Console.ReadKey();        }    }}

​启动了多个LoadTest实例,线程数都1000,压力是上去了​。发现FileBeat没有任何压力,完全不对​劲。查看FileBeat日志,filebeat压根就没有工作,kafka\logstash\elasticsearch统统没有工作。

​ 2.3 完整的日志收集环境+模拟大量线上日志

​这里要推荐linux下实用的history命令,可以查看前辈们使用的命令​。

​history

      从history命令历史中去揣摩命令,其实是一种乐趣,但是效率不高。再次分享一下前辈总结出来的命令清单

    ​    ​玩日志系统必备的17条操作命令|启动zookeeper kafka logstash storm 查看日志 我手上最珍贵的命令集

    ​测试结果压力一上去,w2wp.exe占用的cpu自己都能跑满100%,有没有filebeat,这cpu都能到100%,开着filebeat,filebeat能抢走40%到80%的cpu,随着日志压力大小会有所变化。

    ​2.4 完整的日志收集环境+模拟大量线上日志+Filebeat限制cpu

    ​当业务忙的时候,日志也多,这个时候,日志收集​就不要抢戏了。做为测试环境,配置​低,就限制1个cpu内核。开启压测后,显示Filebeat的cpu被限制在20%以下,不会出现Filebeat抢占过高的业务​资源。

​​小结:本次Filebeat占用CPU过高的case处理,最终的结论是max_procs: 1,为了这13个字符,设计了多种压测环境,回过头来想想,其实一开始就想限制cpu使用,万万没有想到是max_procs​。资源是有限的, cpu、内存,都是这样子​。嗯,cpu就这么控制一下​!​

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

已知公众号原始id反查公众号信息|这种神操作也可以 再也不用怕忘记公众号了 用公众号原始id直接生成公众号二维码 谁才会忘公众号

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!公众号二维码再难,有人带路,轻松搞定

问题:已知公众号原始Id:gh_e289dcf40338,求公众号信息

解法:

格式:

https://open.weixin.qq.com/qr/code?username=公众号原始id

拼出来

https://open.weixin.qq.com/qr/code?username=gh_e289dcf40338

效果图

怎么产生的问题?

不知道谁会忘记公众号信息,这也是很奇怪的!能记住公众号的原始id却不记得公众号基本信息?哈哈!

二维码连接好处

通过这种方式生成公众号的二维码有一个好处,不用再存储图片了。图片的大小远大小于这行链接。样式也是微信官方的样式!

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

银企直连|平安银行与招商银行的银企直连有种神似 还是熟悉的玩法 前置机、报文、加密解密、古老的报文 高门槛高服务费 对接传统银行

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!银企直连再难,有人带路,轻松搞定

招商银行银企直连怎么收费

您好,银企直联主要是可以将企业网银与企业财务软件连接的方式,一般企业申请银企“直联”会收取服务费:开通10000元,每月基本服务费200元,或按协议价格收取。通过银企直联转账手续费同企业网银支付功能收费。

这个收费标准是否已经让一波小伙伴放弃了银企直连,如果每家银行都这个标准来算的话,全部接下来,成本也不少。这就是第三方支付的生存意义了。结合之前写的平安银行的银企直连,再来看招商银行的,会发现,基本是一相通的,莫非出自一个外包公司之手?

温故而知新

平安银行对接|银企对接扫雷排坑实战经验分享 感受一下财大气粗的银行对接 感受一下等长报文的痛苦 未曾经历无以感受他人在坑中崩溃状

熟悉的前置机

对比平安银行的对接玩法,再来看招商银行的银企直连,又看到了这么一个熟悉的前置机”神兽”,这跟支付宝对接、微信对接的玩法,出入相当的大。

demo

java版demo就4个文件HttpRequest.java、SaxHandler.java、SocketRequest.java、XmlPacket.java

import java.io.*;import java.net.*;import java.util.Map;import java.util.Properties;/** *  * HTTP通讯范例: 直接支付 *  */public class HttpRequest {  /**   * 生成请求报文   *    * @return   */  private String getRequestStr() {    // 构造支付的请求报文    XmlPacket xmlPkt = new XmlPacket("Payment", "USRA01");    Map mpPodInfo = new Properties();    mpPodInfo.put("BUSCOD", "N02031");    xmlPkt.putProperty("SDKPAYRQX", mpPodInfo);    Map mpPayInfo = new Properties();    mpPayInfo.put("YURREF", "201009270001");    mpPayInfo.put("DBTACC", "571905400910411");    mpPayInfo.put("DBTBBK", "57");    mpPayInfo.put("DBTBNK", "招商银行杭州分行营业部");    mpPayInfo.put("DBTNAM", "NEXT TEST");    mpPayInfo.put("DBTREL", "0000007715");    mpPayInfo.put("TRSAMT", "1.01");    mpPayInfo.put("CCYNBR", "10");    mpPayInfo.put("STLCHN", "N");    mpPayInfo.put("NUSAGE", "费用报销款");    mpPayInfo.put("CRTACC", "571905400810812");    mpPayInfo.put("CRTNAM", "测试收款户");    mpPayInfo.put("CRTBNK", "招商银行");    mpPayInfo.put("CTYCOD", "ZJHZ");    mpPayInfo.put("CRTSQN", "摘要信息:[1.01]");    xmlPkt.putProperty("SDKPAYDTX", mpPayInfo);    return xmlPkt.toXmlString();  }  /**   * 连接前置机,发送请求报文,获得返回报文   *    * @param data   * @return   * @throws MalformedURLException   */  private String sendRequest(String data) {    String result = "";    try {      URL url;      url = new URL("http://localhost:8080");      HttpURLConnection conn;      conn = (HttpURLConnection) url.openConnection();      conn.setRequestMethod("POST");      conn.setDoInput(true);      conn.setDoOutput(true);      OutputStream os;      os = conn.getOutputStream();      os.write(data.toString().getBytes("gbk"));      os.close();      BufferedReader br = new BufferedReader(new InputStreamReader(conn          .getInputStream()));      String line;      while ((line = br.readLine()) != null) {        result += line;      }      System.out.println(result);      br.close();    } catch (MalformedURLException e) {      e.printStackTrace();    } catch (UnsupportedEncodingException e) {      e.printStackTrace();    } catch (ProtocolException e) {      e.printStackTrace();    } catch (IOException e) {      e.printStackTrace();    }    return result;  }  /**   * 处理返回的结果   *    * @param result   */  private void processResult(String result) {    if (result != null && result.length() > 0) {      XmlPacket pktRsp = XmlPacket.valueOf(result);      if (pktRsp != null) {        String sRetCod = pktRsp.getRETCOD();        if (sRetCod.equals("0")) {          Map propPayResult = pktRsp.getProperty("NTQPAYRQZ", 0);          String sREQSTS = (String) propPayResult.get("REQSTS");          String sRTNFLG = (String) propPayResult.get("RTNFLG");          if (sREQSTS.equals("FIN") && sRTNFLG.equals("F")) {            System.out.println("支付失败:"                + propPayResult.get("ERRTXT"));          } else {            System.out.println("支付已被银行受理(支付状态:" + sREQSTS + ")");          }        } else if (sRetCod.equals("-9")) {          System.out.println("支付未知异常,请查询支付结果确认支付状态,错误信息:"              + pktRsp.getERRMSG());        } else {          System.out.println("支付失败:" + pktRsp.getERRMSG());        }      } else {        System.out.println("响应报文解析失败");      }    }  }  public static void main(String[] args) {    try {      HttpRequest request = new HttpRequest();      // 生成请求报文      String data = request.getRequestStr();      // 连接前置机,发送请求报文,获得返回报文      String result = request.sendRequest(data);      // 处理返回的结果      request.processResult(result);    } catch (Exception e) {      System.out.println(e.getMessage());    }  }}
import org.xml.sax.Attributes;import org.xml.sax.SAXException;import org.xml.sax.helpers.DefaultHandler;import java.util.*;/** *  * 招行XML报文解析类 * */public class SaxHandler extends DefaultHandler {  int layer=0;  String curSectionName;  String curKey;  String curValue;  XmlPacket pktData;  Map mpRecord;    public SaxHandler(XmlPacket data){    curSectionName = "";    curKey = "";    curValue = "";    pktData = data;    mpRecord = new Properties();  }  public void startElement(String uri, String localName, String qName,      Attributes attributes) throws SAXException {    layer++;    if(layer==2){      curSectionName = qName;    }else if(layer==3){      curKey = qName;    }  }  public void endElement(String uri, String localName, String qName)      throws SAXException {    if(layer==2){      pktData.putProperty(curSectionName, mpRecord);      mpRecord = new Properties();    }else if(layer==3){      mpRecord.put(curKey, curValue);      if(curSectionName.equals("INFO")){        if(curKey.equals("FUNNAM")){          pktData.setFUNNAM(curValue);        }else if(curKey.equals("LGNNAM")){          pktData.setLGNNAM(curValue);        }else if(curKey.equals("RETCOD")){          pktData.setRETCOD(curValue);        }else if(curKey.equals("ERRMSG")){          pktData.setERRMSG(curValue);        }      }    }    curValue = "";    layer--;  }  public void characters(char[] ch, int start, int length)      throws SAXException {    if(layer==3){      String value = new String(ch, start, length);      if(ch.equals("\n")){        curValue += "\r\n";      }else{        curValue += value;      }    }  }}
import java.io.*;import java.net.*;import java.util.*;/** * SOCKET通讯范例:查询账户信息 */public class SocketRequest {  /**   * 生成请求报文   *    * @return   */  private String getRequestStr() {    // 构造查询余额的请求报文    XmlPacket xmlPkt = new XmlPacket("GetAccInfo", "USRA01");    Map mpAccInfo = new Properties();    mpAccInfo.put("BBKNBR", "57");    mpAccInfo.put("ACCNBR", "571905400610301");    xmlPkt.putProperty("SDKACINFX", mpAccInfo);    return xmlPkt.toXmlString();  }  /**   * 连接前置机,发送请求报文,获得返回报文   *    * @param data   * @return   */  private String sendRequest(String data) {    // 连接前置机:Ip + port    String hostname = "localhost";    int port = 8080;    String result = null;    try {      InetAddress addr = InetAddress.getByName(hostname);      Socket socket = new Socket(addr, port);      // 设置2分钟通讯超时时间      socket.setSoTimeout(120 * 1000);      DataOutputStream wr = new DataOutputStream(socket.getOutputStream());      // 通讯头为8位长度,右补空格:先补充8位空格,再取前8位作为报文头      String strLen = String.valueOf(data.getBytes().length) + "        ";      wr.write((strLen.substring(0, 8) + data).getBytes());      wr.flush();      DataInputStream rd = new DataInputStream(socket.getInputStream());      // 接收返回报文的长度      byte rcvLen[] = new byte[8];      rd.read(rcvLen);      String sLen = new String(rcvLen);      int iSum = 0;      try {        iSum = Integer.valueOf(sLen.trim());      } catch (NumberFormatException e) {        System.out.println("报文头格式错误:" + sLen);      }      if (iSum > 0) {        System.out.println("响应报文长度:" + iSum);        // 接收返回报文的内容          int nRecv = 0, nOffset = 0;        byte[] rcvData = new byte[iSum];// data        while (iSum > 0) {          nRecv = rd.read(rcvData, nOffset, iSum);          if (nRecv < 0)            break;          nOffset += nRecv;          iSum -= nRecv;        }        result = new String(rcvData);        System.out.println("响应报文内容:" + result);      }      wr.close();      rd.close();    } catch (java.net.SocketTimeoutException e) {      System.out.println("通讯超时:" + e.getMessage());    } catch (IOException e) {      System.out.println(e.getMessage());    }    return result;  }  /**   * 处理返回的结果   *    * @param result   */  private void processResult(String result) {    if (result != null && result.length() > 0) {      XmlPacket pktRsp = XmlPacket.valueOf(result);      if (pktRsp != null) {        if (pktRsp.isError()) {          System.out.println("取账户信息失败:" + pktRsp.getERRMSG());        } else {          Map propAcc = pktRsp.getProperty("NTQACINFZ", 0);          System.out.println("账户" + propAcc.get("ACCNBR") + "的联机余额:"              + propAcc.get("ONLBLV"));        }      } else {        System.out.println("响应报文解析失败");      }    }  }  public static void main(String[] args) {    try {      SocketRequest request = new SocketRequest();      // 生成请求报文      String data = request.getRequestStr();      // 连接前置机,发送请求报文,获得返回报文      String result = request.sendRequest(data);      // 处理返回的结果      request.processResult(result);    } catch (Exception e) {      System.out.println(e.getMessage());    }  }}
import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.util.*;import javax.xml.parsers.ParserConfigurationException;import javax.xml.parsers.SAXParser;import javax.xml.parsers.SAXParserFactory;import java.io.ByteArrayInputStream;import org.xml.sax.SAXException;/** *  * 招行XML通讯报文类 * */public class XmlPacket{  protected String FUNNAM;  protected final String DATTYP="2";//报文类型固定为2  protected String LGNNAM;  protected String RETCOD;  protected String ERRMSG;  protected Map data; //<String,Vector>    public XmlPacket(){    data = new Properties();  }    public XmlPacket(String sFUNNAM){    FUNNAM = sFUNNAM;    data = new Properties();  }    public XmlPacket(String sFUNNAM, String sLGNNAM){    FUNNAM = sFUNNAM;    LGNNAM = sLGNNAM;    data = new Properties();  }    public String getFUNNAM() {    return FUNNAM;  }  public void setFUNNAM(String fUNNAM) {    FUNNAM = fUNNAM;  }  public String getLGNNAM() {    return LGNNAM;  }  public void setLGNNAM(String lGNNAM) {    LGNNAM = lGNNAM;  }  public String getRETCOD() {    return RETCOD;  }  public void setRETCOD(String rETCOD) {    RETCOD = rETCOD;  }  public String getERRMSG() {    return ERRMSG;  }  public void setERRMSG(String eRRMSG) {    ERRMSG = eRRMSG;  }    /**   * XML报文返回头中内容是否表示成功   * @return   */  public boolean isError(){    if(RETCOD.equals("0")){      return false;    }else{      return true;    }  }    /**   * 插入数据记录   * @param sSectionName   * @param mpData <String, String>   */  public void putProperty(String sSectionName, Map mpData){    if(data.containsKey(sSectionName)){      Vector vt = (Vector)data.get(sSectionName);      vt.add(mpData);    }else{      Vector vt = new Vector();      vt.add(mpData);      data.put(sSectionName, vt);      }      }    /**   * 取得指定接口的数据记录   * @param sSectionName   * @param index 索引,从0开始   * @return Map<String,String>   */  public Map getProperty(String sSectionName, int index){    if(data.containsKey(sSectionName)){      return (Map)((Vector)data.get(sSectionName)).get(index);    }else{      return null;    }  }    /**   * 取得制定接口数据记录数   * @param sSectionName   * @return   */  public int getSectionSize(String sSectionName){    if(data.containsKey(sSectionName)){      Vector sec = (Vector)data.get(sSectionName);      return sec.size();    }    return 0;  }    /**   * 把报文转换成XML字符串   * @return   */  public String toXmlString(){    StringBuffer sfData = new StringBuffer(        "<?xml version='1.0' encoding = 'GBK'?>");    sfData.append("<CMBSDKPGK>");    sfData        .append("<INFO><FUNNAM>"+FUNNAM+"</FUNNAM><DATTYP>"+DATTYP+"</DATTYP><LGNNAM>"+LGNNAM+"</LGNNAM></INFO>");    int secSize = data.size();    Iterator itr = data.keySet().iterator();    while(itr.hasNext()){      String secName = (String)itr.next();      Vector vt = (Vector)data.get(secName);      for(int i=0; i<vt.size(); i++){        Map record = (Map)vt.get(i);        Iterator itr2 = record.keySet().iterator();        sfData.append("<"+secName+">");        while(itr2.hasNext()){          String datakey = (String)itr2.next();          String dataValue = (String)record.get(datakey);          sfData.append("<"+datakey+">");          sfData.append(dataValue);          sfData.append("</"+datakey+">");        }        sfData.append("</"+secName+">");      }    }    sfData.append("</CMBSDKPGK>");        return sfData.toString();  }    /**   * 解析xml字符串,并转换为报文对象   * @param message   */  public static XmlPacket valueOf(String message) {    SAXParserFactory saxfac = SAXParserFactory.newInstance();    try {      SAXParser saxparser = saxfac.newSAXParser();      ByteArrayInputStream is = new ByteArrayInputStream(message.getBytes());      XmlPacket xmlPkt= new XmlPacket();      saxparser.parse(is, new SaxHandler(xmlPkt));      is.close();      return xmlPkt;    } catch (ParserConfigurationException e) {      e.printStackTrace();    } catch (SAXException e) {      e.printStackTrace();    } catch (IOException e) {      e.printStackTrace();    }      return null;  }}

C#版demo

相对java版,C#版的demo好歹是一个工程,用vs可以直接打开。

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Net;using System.Net.Sockets;using System.IO;namespace CSharpTest{    class Program    {        static void Main()        {            //报文内容,用户自己根据接口组装            string strSendData = "<?xml version=\"1.0\" encoding = \"GBK\"?><CMBSDKPGK><INFO><FUNNAM>SDKCSFDFBRTIMG</FUNNAM><DATTYP>2</DATTYP><LGNNAM>QGZ01</LGNNAM></INFO><CSRRCFDFY0><EACNBR>216082647110001</EACNBR><BEGDAT>20170426</BEGDAT><ENDDAT>20170526</ENDDAT><RRCFLG>1</RRCFLG><RRCCOD>HD002016</RRCCOD></CSRRCFDFY0></CMBSDKPGK>";            //前置机部署的IP地址            string strIP = "127.0.0.1";            //Socket传输方式前置机的监听端口            int iSocketPort = 1080;            //Http传输方式前置机的监听端口            int iHttpPort = 8080;            string strReciveData = string.Empty;            Console.WriteLine("请选择通讯方式:1.Socket     2.HTTP");            string strType = Console.ReadLine();            if (strType.Equals("1"))            {                strReciveData = Send_Socket(strSendData, strIP, iSocketPort);            }            else if (strType.Equals("2"))            {                strReciveData = Send_Http(strSendData, strIP, iHttpPort);            }            else            {                Console.WriteLine("输入有误!");            }            Console.WriteLine(strReciveData);            Console.WriteLine("按Enter键退出!");            Console.ReadLine();        }        static private string Send_Socket(string strSendData, string strIP, int iPort)        {            string strDataLen = Convert.ToString(Encoding.Default.GetBytes(strSendData).Length).PadLeft(8, '0');    //长度8字节左补0            string sReturn = string.Empty;            IPAddress ip = IPAddress.Parse(strIP);    //前置机IP地址            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 30 * 1000);            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 30 * 1000);            try            {                socket.Connect(new IPEndPoint(ip, iPort));                byte[] dataToBeSend = Encoding.Default.GetBytes(strDataLen + strSendData);                socket.Send(dataToBeSend);                byte[] receivedData = new byte[3000];                int recvLen = socket.Receive(receivedData);                if (recvLen == 0)                {                    throw new Exception("返回数据为空");                }                sReturn = Encoding.Default.GetString(receivedData, 0, recvLen);            }            catch (Exception ex)            {                sReturn = ex.Message;            }            if (socket != null)            {                socket.Close();            }            return sReturn;        }        static private string Send_Http(string strSendData, string strIP, int iPort)        {            string strUrl = "http://" + strIP + ":" + iPort.ToString();    //前置机的地址和监听端口            string sReturn = string.Empty;            try            {                byte[] byteArray = Encoding.Default.GetBytes(strSendData);                HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(new Uri(strUrl));                webReq.Method = "POST";                webReq.ContentType = "application/x-www-form-urlencoded";                webReq.Timeout = 30 * 1000;                webReq.ContentLength = byteArray.Length;                Stream newStream = webReq.GetRequestStream();                newStream.Write(byteArray, 0, byteArray.Length);                newStream.Close();                HttpWebResponse response = (HttpWebResponse)webReq.GetResponse();                StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.Default);                sReturn = sr.ReadToEnd();                sr.Close();                response.Close();                newStream.Close();            }            catch (Exception ex)            {                sReturn = ex.Message;            }            return sReturn;        }    }}

 

招商银行的网银直连文档可以直接在官方查到
https://u.ebank.cmbchina.com/CmbBank_GenShell/UI/Help/DCBank2/Main.aspx
 对比招商银行的直连文档、再看看平安银行的直连文档,再对比第三方支付提供的sdk,对接体验,嗯嗯,还是第三方好!

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

一点都不打脸的帮同事搜索|自己不会,但是可以搜索啊 搜索也是需要技巧的,有时候也需要运气!千万不要鄙视那些搜索出结果的学习小伙伴

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!问题再难,有人问可以搜索,轻松搞定

一点都不打脸

的帮同事搜索|自己不会,但是可以搜索啊 搜索也是需要技巧的,有时候也需要运气!千万不要鄙视那些搜索出结果的学习小伙伴!

搜索一下,没有主角光坏也不行,没有运气也不行!今天搜索nginx为什么抢不到80端口就是纯靠运气!

今天算是一个运气比较好的,又陷入了趣事回忆中。

N前年,满怀信心的帮人配置Lync机器人,拿出自己总结的小经验,准备秀一把,预想是分分钟钟就能搞定的事情!
到现场,凉菜了
域环境有问题
机器人配置不成功
于是开始了一场搜索行动
立即将服务器机器人改成客户端机器人
凌晨2点才搞定
 
 
真的很惊险
今天的往事回忆篇完美收工!

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

nginx启动失败的case处理经验|前端竟然安装了SqlServer Reporting service 有点怪怪的抢后端故事

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!Nginx再难,有人带路,轻松搞定

Nginx启动报错:

nginx: [emerg] bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a socket in a way forbidden by its access permissions)

查端口占用

D:\nginx-1.12.2>netstat -aon|findstr “80”
TCP    0.0.0.0:80             0.0.0.0:0              LISTENING       4

C:\>netstat -aon|findstr “80”

通常情况,直接把IIS关掉,然后再启动nginx即可以成功启动。然而关掉之后,却不行不行…

于是我就帮前端搜搜搜,搜到了一个

sqlserver reporting services

怎么也没有想到,前端会安装报表服务

这个小故事真诠释出了坑不再多,够神奇就好。想不想不到报表服务器会抢80端口,而且显示是由系统占用了。不过,处理完这个之后,还是感觉豁然开朗。为了让前端正常调试,我一度将自己的电脑配置成了nginx服务器对外服务。

如果你恰好路过,看到了,一定记得nginx端口占用可能是由iis,也可能是由sqlserver reporting servicesh占用导致。这也很好的解释了当初为什么老是被抢端口,而且抢得还那么那么快。

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

二维码生成压测实战|二维码图片合成内存爆表 Connection_Dropped_List_Full JS渲染二维码兼容PK赛

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!压测再难,有人带路,轻松搞定

这是当面付的续集,想不到继集这么多。这回是压测的故事了。收银台的二维码是自己生成的,本身的逻辑比之前调用支付宝的更加“轻量级”,无需进行外网的请求,只涉及到自身的缓存操作。所以这个环节的压测数据不会是一个问题,于少会优于当前的。最大的问题是,生成二维码图片了。

一、服务器端合成图片

压测目标定得高的话,会导致大量的 503请求。

通过查看如下IIS日志

%Systemroot%\System32\LogFiles\HTTPERR\httperr1.log

可以查看到onnection_Dropped_List_Full、QueueFull的状态。加大IIS队列之后,压测数值更漂亮掉了。

合成图片,优化了一下LOGO图片,因为都是一样的,所以搞成了一个单例,避免重复读取文件。测试退化成了压测ThoughtWorks.QRCode.Codec与IIS队列了。

二、巧妙的给自己找台阶下

最后的压测结果,划一道线的话,会比较尴尬。结果实际峰值估算出一个合理的预期峰值来当压测标准的话,就很好的过了。这里有个不太好解决的事情是,优化图片合成有点像个死胡同,时间与技能都确实。这个台阶找得好,华丽的结合业务峰值,即不打脸,也不耽误进度,还有历史数据说话。

三、被抛弃的客户端JS渲染二维码图片方案

服务器端合合成二维图片,占内存,未调整IIS队列之前,未优化之前,压测数据惨不忍睹。我是很想使用JS来处理二维码图片,不让服务器端生成图片,这样子可以完美的绕过压测性能问题。但是这个方案不给过,考虑到js兼容性问题,也没法测试兼容性,就罢了。

四、内存暴表

有的时候,我们需要改变一下,适当的结合业务峰值,嗯嗯嗯,就是这样子。今天的压测算是尝试优化,不断被压测数据打脸,在优化到“极致”的时候,再用历史峰值找了个台阶下,避免了掉进入图片合成优化的深坑中。
故事,就是这样子,没有无限的资源可以投入进去,只能取舍一下。当面付这回,算是收尾了吧。不然,又得写续集了。敬请期待下一个回合。

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

支付宝当面付集成继集|一顿猛操作 当面付二维码生成 自主收银台功能 验签回调 公钥密钥更新 AES配置统统就位妥当 支付宝扫码啦

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!当面付​再难,有人带路,轻松搞定
感觉当面付的集成差不多告一段落了,今天也花了比较多的时候将当面付接到外网测试环境,进行全方位联调​。这一天下来也算是比较​充实了。一直有各种小问题出现,一直在处理问题。从不同的角度来看这个次当面付的集成的话,也能得出很多不同的观点与​思路。从流水账的角度看这一天的话,也是很自然与​平凡的一天。
一到公司,我在群里提前打个招呼,联系财务更新密钥,本以为正式更新密钥可能会墨迹到10点,但是没有,​9点就去更新密钥了。这一路走过去的话,我到是很开心,想想今天还是很顺利的开局,一分钟也没有​落下。
第一次更新完密钥后,我特意取了个版本号v1​。预想的话,这一更新,这个商户号的当面付功能就应该能正常使用了,接口通,二维码出,支付宝能扫。结果,却依旧​。v2,v3,新生成密钥,拿正常商户的密钥设置,一切都像见鬼了一样。向支付宝的技术支持反馈问题,也是让我更新一下密钥,真的就是没有用​!于是,换个大招,不仅更新了RSA公钥私密钥,也更新了AES,同时把身份申请为IVS集成商,因为发生变化的因素多了,也搞不明白到底是哪个地方​发生的真实效果。支付宝功能也是很神秘滴!话说,之前签约支付宝当面付,提供产品冲突不让签约,直接在技术支持群里@支付宝机器人转人工,一波反馈,又悄悄的开通了​。更新完密钥,将商户号接通,算是一个大步。
调通后,还得将自主收银台(其实就是一个html页面)配置配置,有效时长​得调整对。因为很次构建编译时间比较长,走内网部署系统,加上那14分钟的代码扫描,整个过程得20分钟,避免消耗在20分钟的部署时长?有的时候真心避免不了​。人在江湖身不由已。
对于Memcache实例能否共用的问题,也是第一时间考虑,更多当面付的分享,且看​下回分解。​

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处

支付宝当面付集成经验分享|RSA RSA2 AES alipay沙箱环境 正式环境 签名失效 DIY当面付收银台QrCode图片

 Yuema约吗?一起学技术,一起成长!学海无涯 高人带路系列

程序的世界,就是有坑的地方!分享踩坑的心得与体验!每天分享一点点!
关注公众号,进入学海无涯,高人带路模式!!当面付再难,加班也要搞定​

上回有聊到支付宝扫码支付的几种实现方式,老接口实现的扫码,与现在支付宝当面付的实现方式,完全是两码事了,差太多了,我好难。接口不再返回QrCodeImageUrl,仅返回QrCode,同时也没有收银台提供了。当把当面付的功能写完之后,内外网联调,各种意想不到的问题都跑过来了。在这里记录一下这场战役的一些感受。

一、支付宝沙箱vs支付宝正式环境

原本沙箱是用来任性的支付宝,完成上正式前踩坑的绝佳环境,但是这次研发体验却发现支付宝沙箱埋了一堆坑,就连支付宝对接的“机器人”都不建议使用沙箱。

1.1 验签方式异常

沙箱里使用RSA畅通无阻,让人已经形成了基本的RSA1\RSA2的两种接口验签概念,RSA1咱们对应上公钥方式,RSA2咱们对应证书方式,结果沙箱里写死的RSA,在正式上通不过,得换成RSA2。

1.2 接口功能异常

在沙箱里设置交易超时时长为一分钟,刚扫完码就失效了。只好在沙箱(沙盒)里配置5分钟交易时长。测试QA们卡一分钟,简直就是坑得一波一波的。

二、环境配置稍微有点多

为了适应支付宝沙盒环境与正式环境的切换,特意弄成了可配置的,结果联调的时候,有时候会忘掉,就出现了神一样的提示语。

The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or a non-white space character among the padding characters

三、当面付收银台功能

老接口有一个功能是生成一个商品码,买家扫码后,会有一个简单的收银界面,买家可以调整商品数量,确认完后,支付宝又会通知我方接口生成订单,然后支付宝发生支付操作。新版的支付宝当面付,不提供这个收银界面。带来了新的问题,得自己实现收银界面,生成收银界面的二维码,要解决修改host后,正式的支付宝客户端无法访问到测试的内网站点。

3.1 二维码生成问题

收银台接收了比较多的参数,参数一多,二维生成失败,直接报超过数组标,将二维码的生成版本改成0,也就是让二维码随着内容自己适应大小,到是让二维码出来了,开始还能识别出来。当我对些敏感参数进行加密处理后,url变得稍微有些长,二维码生成出来的效果是密密麻麻的,手机识别好久都识别不出来。解决方案:参数仅传一个token,对应的参数放到后端的内存中(redis,memcache之类的东东里)。Token生成的话,类似于短链接生成,保持不重复,短小就好。这种规则,跟生成单号有些类似,要是琢磨最佳的生产算法的话,还得仔细分析分析,拿到最短的又能满足要求的。为了节省时间,可以直接使用Guid.

内容多,生成的码就密集。

比如如下链接:

http://yue.ma/?看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧看看这个码有多么密集吧

生成二维码效果:

识别起来会比较费劲,看起来也有点不舒服。

如果换成简短的链接,比如:

http://yue.ma/?qr=helloworld

生成出来的二维码就比较清爽了。

3.2 外网联调

修改hosts不行,那就直能提供一个真实的host,支付宝有防止host补“黑”的功能。试试微信,也有这样子的功能。对于研发人员来讲,这算一个坏消息。由是在一个专用的测试站点,带有真实的dns解析,完成。

3.3 惊天动地的QrCodeImageUrl

支付宝老接口返回QrCode以及对应的ImageUrl,当我劲了九牛二户之力,生成完二维码图片,按着老规则返回给客户端,吓坏了,一堆乱码。这不是一张图片,还是一个超小型的网页,里面放了一张图片。这下子,前端工程师上战场了,调整大小,位置,很是需要耐心。

四、密钥大坑

应用公钥2048、应用私钥2048、支付宝公钥,因为是非对称的密钥配置,应用的公钥要告诉支付宝,支付宝要把TA的公钥告诉咱们,这都好理解是吧。就是有行小字比较伤人。密钥也分java使用,非java使用。

正好前阵子玩java版当面付,结果把密钥都无脑的配置成了默认的java版本PKCS8,结果就在坑里爬了好久好久。

今天的当面付经验分享就告一段落了,主要的踩坑点都已经标记出来,每个人看问题的角度与处理速度不一样,也许也帮不上什么忙,就当自己记录一下自己的心得好了。关注公众号,且看下回分解。

让天地下没有难接的支付​

作者:钟代麒

出处:http://www.jishudao.com/
版权归作者所有,转载请注明出处