已知公众号原始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/
版权归作者所有,转载请注明出处

支付宝扫码支付时长控制|支付宝二维码交易时长设定 支付宝多种支付途径 新老接口绕晕官方小二 有事找技术支持避免走弯路 接支付经验

支付宝扫码支付的实现方式真心的多,来来来,看看你有没有印象。
alipay.mobile.public.qrcode.create(生成带参数的二维码) alipay.trade.page.payalipay.mobile.qrcode.manage
以上是收集到的支付宝扫码实现接口,还有当面付接口,这里就再列表了。总之接口变得更加产品化(折磨人)。一不小心就会掉到坑里。从技术的角度来讲,基础接口都是一样的,支付宝人为增加了产品facade接口,有好有坏了。好的是,细分了使用场景,避免迷失在大接口中。​同时,也好进行授权控制。
今天的目标是实现支付宝二维码有效进长配置,从之前电脑网站支付相关的支付宝接口经验上来分析,使用alipay.trade.page.pay​可以实现有效时长控制。但是,老项目采用了alipay.mobile.qrcode.manage​接口,支付宝开放平台都搜索不到这个接口,可想而知,这是一个多老的接口。
抱着试试看的态度问了一下支付宝技术支持。
@蚂蚁金服商户技术支持 alipay.mobile.qrcode.manage 这个接口支持设置二维码过期时间不@蚂蚁金服商户技术支持 转人工不支持设置过期时间@蚂蚁金服商户技术支持 使用哪个版本的支持设置过期时间您这边使用二维码管理接口是需要实现什么功能?@蚂蚁金服商户技术支持 扫码支付设置有效时长这个接口可以设置支付超时时间的。@蚂蚁金服商户技术支持 请给一份详细的完整的文档给我 谢谢支付宝二维码管理接口(关注技术岛公众号,回复alipay获取支付宝二维码管理接口文档)
从对话可以看出,支付宝技术支持一开始是说不支持设置有效时长的​。差点就换成alipay.trade.page.pay了,想想要把老接口项目大倒腾一下,挺大的工作量的。还好,支付宝技术支持又答复说支持了,否则一场不可避免的新老接口大梳理无法避免。
关键参数
pay_timeout 支付超时 时间 String 支付超时时间,单位为分钟, 最小 5 分钟最大两天,默认 15 分钟。 可空 30 logo
此参数为 ext_info中成员,直接在原来的接口参数中进行适当的调整,即可以满足二维码扫码时长的控制了。

腾讯云滑块验证码集成|阿里滑块验证码被破解 换个腾讯云验证码 云服务与黑产灰色产业链的爱恨情仇 附送腾讯云99元云主机下单入口

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

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

云服务被越来越多的企业所接受,风险也日趋集中,像阿里滑块验证码被破解了,影响的客户应该​不在少数。阿里按次数收费,无论是正常还是异常使用,收费​照常。单个云服务的接入,存在风险集中,无备选方案。今天分享的腾讯云验证码即是作为阿里滑块验证码​的备选方案。

使用阿里滑块验证码​的风险:

一、产品下线风险

目前阿里已经下线滑块验证码,已经开通的可以正常使用。这项云服务有点像被阿里抛弃的产品,可能不再进一步维护,要做下线处理。风险​可想而知。

二、破解风险与成本风险并存

目前阿里滑块验证码已经被破解,阿里也承认被破解了。但是收费正常,被​刷了,也不退款。​其中的成本风险,也是可想而知。
腾讯云验证码做为阿里滑块验证码的备选方案,今天就来分享一下有关腾讯云验证码的接入​。感觉还是比较简单的,可以结合官方文档与网上的示例进行。
先睹为快
实例导航https://i.zuime.com/2019/10/11/%E8%85%BE%E8%AE%AF%E4%BA%91%E9%AA%8C%E8%AF%81%E7%A0%81%E9%9B%86%E6%88%90/前端示例https://i.zuime.com/tencent/captcha验证接口https://i.zuime.com/tencent/verify?ticket=t02PT9ySVJ80sIcI3xYNU-2CkRrXxquqXcPsdNYstOny7DVDl7i00VqHK4RrTFxIijSv2FXgn0TpXg0aDfc6sTij0Nncf3NUtzV9a89AEJ65YTOqAY6S9gY4A**&[email protected]
文档
验证码后台文档 https://cloud.tencent.com/document/product/1110/36926Web 前端接入文档 https://cloud.tencent.com/document/product/1110/36841

前端代码参考

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head>  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">  <title>腾讯云验证码 demo</title>    <link rel="stylesheet" type="text/css" href="/css/style.css"/>    <link rel="stylesheet" type="text/css" href="/css/markdown.css"/>  <script src="https://ssl.captcha.qq.com/TCaptcha.js"></script></head><body>腾讯云验证码 demo<button id="TencentCaptcha"     data-appid="2046804662"     data-cbfn="callback"     type="button">验证</button><!--===============================================================================================-->  <script src="/jquery/jquery-3.2.1.min.js"></script>  <script src="/jquery/jquery.datetimepicker.full.min.js"></script>  <script src="/jquery/jquery-confirm.min.js"></script><!--===============================================================================================-->  <script src="/js/main.js"></script>  <script src="/js/jblog-admin.js"></script><script>window.callback = function(res){ console.log(res) // res(用户主动关闭验证码)= {ret: 2, ticket: null} // res(验证成功) = {ret: 0, ticket: "String", randstr: "String"} if(res.ret === 0){     alert(res.ticket);   // 票据     $.get("/tencent/verify",       {         ticket:res.ticket,         randstr:res.randstr       }       ,function(data){       alert(data);     }); }}</script></body></html>

后端代码:agent

package jblog.guohai.org.bll.agent;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Service;import com.tencentcloudapi.captcha.v20190722.CaptchaClient;import com.tencentcloudapi.captcha.v20190722.models.DescribeCaptchaResultRequest;import com.tencentcloudapi.captcha.v20190722.models.DescribeCaptchaResultResponse;import com.tencentcloudapi.common.Credential;import com.tencentcloudapi.common.exception.TencentCloudSDKException;import com.tencentcloudapi.common.profile.ClientProfile;@Servicepublic class TencentCaptchaAgent {  /**   * 网关地址   */  @Value("${tencent.secretId}")  private String secretId;  @Value("${tencent.secretKey}")  private String secretKey;    /**   * 网关地址   */  @Value("${tencent.captcha.captchaAppId}")  private Long captchaAppId;  @Value("${tencent.captcha.appSecretKey}")  private String appSecretKey;      public String verify(String ticket,String randstr,String userIp){     try{              // 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey              Credential cred = new Credential(secretId, secretKey);                            // 实例化要请求产品(以cvm为例)的client对象              ClientProfile clientProfile = new ClientProfile();              clientProfile.setSignMethod(ClientProfile.SIGN_TC3_256);              CaptchaClient client = new CaptchaClient(cred, "ap-beijing", clientProfile);                            // 实例化一个请求对象              DescribeCaptchaResultRequest req = new DescribeCaptchaResultRequest();              req.setCaptchaType(9L);              req.setTicket(ticket);              req.setCaptchaAppId(captchaAppId);              req.setAppSecretKey(appSecretKey);              req.setRandstr(randstr);              req.setUserIp(userIp);                                          // 通过client对象调用想要访问的接口,需要传入请求对象              DescribeCaptchaResultResponse resp = client.DescribeCaptchaResult(req);                            // 输出json格式的字符串回包              System.out.println(DescribeCaptchaResultRequest.toJsonString(resp));              return DescribeCaptchaResultRequest.toJsonString(resp);          } catch (TencentCloudSDKException e) {                  System.out.println(e.toString());                return e.toString();          }  }}

后端代码 :控制器

package jblog.guohai.org.web;import javax.servlet.http.HttpServletRequest;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import jblog.guohai.org.bll.agent.TencentCaptchaAgent;/** * 腾讯云 *  * @author zhongdaiqi * */@Controller@RequestMapping("/tencent")public class TencentController {  @Autowired  private TencentCaptchaAgent tencentCaptchaAgent;  @RequestMapping(value = "captcha")  public String captcha(Model model) throws Exception {    return "tencent/captcha";  }  @RequestMapping(value = "verify")  @ResponseBody  public String verify(Model model, String ticket, String randstr,HttpServletRequest request) throws Exception {    return tencentCaptchaAgent.verify(ticket, randstr,request.getRemoteAddr());  }}

后端代码:POM包

<dependency>        <groupId>com.tencentcloudapi</groupId>        <artifactId>tencentcloud-sdk-java</artifactId>        <!-- go to https://search.maven.org/search?q=tencentcloud-sdk-java and get the latest version. -->        <!-- 请到https://search.maven.org/search?q=tencentcloud-sdk-java查询最新版本 -->        <version>3.0.95</version>    </dependency>

以上代码仅供参考,未处理坏味道,​请谨慎使用!获取完整的可运行项目代码方式:关注公众号,回复”tencent”​。

关注公众号Yuema约吗,每天进步一点点

腾讯云福利

​【新用户限量秒杀】热门云产品限量秒杀,云服务器1核1G 首年99元

识别二维码进入活动现场!

作者:钟代麒

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

支付宝网银直连SDK封装记下|强力去掉支付宝网银直连DEMO中56个坏味道 余下6个坏味道 Sonarqube无视阿里支付宝光环

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

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

每次看动漫看美剧,都有狗血的前情回顾,占了好长的时间,有种说不同的酸痛感!腰疼!想不到,我也活成了自己讨厌的人,我也要写个前情回顾 

前情回顾

上回讲到接阿里蚂蚁金服支付宝网银直连的整体代码结构,提到封装了一个alipay-bankpay-sdk,并画了2个对比图,介绍了一下这么干的好处,代码清晰,通用性强。按文章的条理来讲,您应该先看上篇,再来看这篇。

关注Yuema约吗公众号,回复”bankpay”,查看《支付宝网银直连SDK封装记上|强力去掉支付宝网银直连DEMO中56个坏味道 余下6个坏味道 Sonarqube无视阿里支付宝光环》

本文向导

本文主要分享一下封装支付宝网银直连sdk的思路、坏味道清理,并做个小小的总结。完整的看完本文,您应该学会一种取巧的sdk封装思路并能运行于实战,并且能看到一些更加简洁的代码写法,并能学会如何从BTA中学东西,并从中剔除不良习惯。

封装思路

虽然阿里支付宝的程序员在代码中明确表明给出来的demo仅供参考blabla的。但是整体上来讲,封装sdk还是主要使用他们的demo,从作为sdk的角度对代码进行细微调整。

/* * *类名:AlipaySubmit *功能:支付宝各接口请求提交类 *详细:构造支付宝各接口表单HTML文本,获取远程HTTP数据 *版本:3.3 *日期:2012-08-13 *说明: *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 *该代码仅供学习和研究支付宝接口使用,只是提供一个参考。 */

封装思路一

做为sdk,我去掉了demo中的AlipayConfig.java,因为不同的项目所使用到的商户号、密钥等都不相同,放在sdk中,不妥当。去掉AlipayConfig.java后,其他的代码要相应的调整一下。我们将配置以参数的形式,传递进去。这样子确保,我们能通过其他方式配置网银直连所需的商户信息。我采用了这种思路,很简单很傻瓜式的调整一下。

封装思路二

可以参考最新的支付宝sdk的封装方式,抽象一个AlipayClient出来,初始化的时候,把AlipayConfig.java的工作做了。我没有使用这种思路,但是感觉这是一种不错的思路。调整稍微大点。

坏味道清理

程序的世界可以说很大很大,咱们就像盲人摸象。坏味道清理是将代码中不规范不合理的地方进行调整。通过使用sonarqube对阿里的网银直连demo进行扫描,发现了56个坏味道,还有1个bug,1个漏洞。

坏味道1:命名空间不规范

支付宝com.alipay.util.httpClientcom.alipay.util.httpclient

坏味道2:冗余写法,不够简洁

Replace the type specification in this constructor call with the diamond operator ("<>"). (sonar.java.source not set. Assuming 7 or greater.)            List<String> keys = new ArrayList<String>(sPara.keySet());            List<String> keys = new ArrayList<>(sPara.keySet());

坏味道3:多余的转化

String name = (String) keys.get(i);Remove this unnecessary cast to "String".

坏味道4:直接返回即可

String strResult = response.getStringResult(charset);return strResult;Immediately return this expression instead of assigning it to the temporary variable "strResult".

坏味道5:这里不要线程安全

Replace the synchronized class "StringBuffer" by an unsynchronized one such as "StringBuilder".StringBuffer result = new StringBuffer();

坏味道6:静态类不要初始化

Add a private constructor to hide the implicit public one.

坏味道7:一行代码搞定

修改前:if (isSign && responseTxt.equals("true")) {            return true;        } else {            return false;        }Replace this if-then-else statement by a single return statement.修正后:return isSign && responseTxt.equals("true");

坏味道8:局部变量命名不规范

input_charsetRename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

坏味道9:static final变量命名不规范

 public static final String dtLong                  = "yyyyMMddHHmmss";  Rename this constant name to match the regular expression '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.

坏味道10:参数不符合规范

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.private static boolean getSignVeryfy(Map<String, String> Params, String sign,String signType,String key,String charset)

坏味道11:局部变量命名不符合规范

String veryfy_url = HTTPS_VERIFY_URL + "partner=" + partner + "&notify_id=" + notify_id;Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

坏味道12:多此一举

inputLine = in.readLine().toString();"readLine" returns a string, there's no need to call "toString()".

坏味道13:一行代码搞定

Replace this if-then-else statement by a single return statement.if(mysign.equals(sign)) {Replace this if-then-else statement by a single return statement.clumsy            return true;        }        else {            return false;        }

坏味道14:局部变量命名不规范

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.private static String verifyResponse(String notify_id,String partner)

坏味道15:抛异常不规范

public static String query_timestamp(String partner,String charset) throws MalformedURLException, DocumentException, IOExceptionRemove the declaration of thrown exception 'java.net.MalformedURLException' which is a subclass of 'java.io.IOException'.

坏味道16:Use a StringBuilder instead

Use a StringBuilder instead.StringBuilder  prestr = new StringBuilder();        for (int i = 0; i < keys.size(); i++) {            String key = keys.get(i);            String value = params.get(key);            if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符               prestr.append(key + "=" + value);            } else {               prestr.append(key + "=" + value + "&");            }        }        return prestr.toString();    String prestr = "";        for (int i = 0; i < keys.size(); i++) {            String key = keys.get(i);            String value = params.get(key);            if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符                prestr = prestr + key + "=" + value;            } else {                prestr = prestr + key + "=" + value + "&";            }        }        return prestr;

坏味道17:不规范的变量命名

private static String              DEFAULT_CHARSET                     = "GBK";Rename this field "DEFAULT_CHARSET" to match the regular expression '^[a-z][a-zA-Z0-9]*$'.private static final String              DEFAULT_CHARSET                     = "GBK";

坏味道18:不规范的变量命名

/** 默认等待HttpConnectionManager返回连接超时(只有在达到最大连接数时起作用):1秒*/    private static final long          defaultHttpConnectionManagerTimeout = 3 * 1000L;        Rename this constant name to match the regular expression '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.        private static final long          DEFAULT_HTTP_CONNECTION_MANAGER_TIMEOUT = 3 * 1000L;

坏味道19:不规范的变量命名

Iterate over the "entrySet" instead of the "keySet".When only the keys from a map are needed in a loop, iterating the keySet makes sense. But when both the key and the value are needed, it's more efficient to iterate the entrySet, which will give access to both the key and value, instead.for (String key : sArray.keySet()) {            String value = sArray.get(key);            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")                || key.equalsIgnoreCase("sign_type")) {                continue;            }            result.put(key, value);        }                for (Entry<String,String> entry : sArray.entrySet()) {            String key = entry.getKey();            String value = entry.getValue();            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")                || key.equalsIgnoreCase("sign_type")) {                continue;            }            result.put(key, value);        }

坏味道20:漏洞

Use a logger to log this exception.try {            URL url = new URL(urlvalue);            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();            BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));            inputLine = in.readLine();        } catch (Exception e) {            e.printStackTrace();//漏洞            inputLine = "";        }

还有几个坏味道,我也不想修改了。

从56个坏味道来看,变量命名规范、线程安全、代码简洁度、性能都有涉及,可以说阿里支付宝提供的这个demo,将一堆坏味道传染给了千千万万程序员。其实,也不用太惊慌,使用sonarqube这个开源的工具进行代码质量检查,就能防坑于未然,即能享受支付宝的demo成果,也能做得更好。
网银直连的demo比较老,最近看阿里支付宝的sdk,还是很规范的。不过,网银直连还是得在坑里爬行。希望这两篇支付宝网银直连的经验分享,能让更多的小伙伴快速出坑,节省些时间~

关注Yuema约吗公众号,回复”sq”,学习sonarqube代码质量检查工具使用与安装。

关注Yuema约吗公众号,回复”bankpay”,查看《支付宝网银直连SDK封装记上|强力去掉支付宝网银直连DEMO中56个坏味道 余下6个坏味道 Sonarqube无视阿里支付宝光环》,了解代码结构的艺术。

作者:钟代麒

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