[lottery] 06_协议处理(重要)——协议的封装

Android 4.0

协议的封装

一、协议封装之前的问题
// 未封装协议前的问题
// 5人的项目组,100个请求
// 问题一:代码编写极易出错,不好维护
// 问题二:抽取代码,不好维护(一套抽取的结果)
// 问题三:学习协议,5*0.5天(一个人)
// 问题四:学习情况的检验,5*0.5天
// 问题五:协议变更()

// 如何规避风险,项目的影响最小化
// 解决:使用协议封装:抽取的操作(一个人完成),面向对象
协议封装
节点对象化
节点序列化
请求接口(抽象)化
协议通用化
技术点:
1、日期时间的格式化找SimpleDateFormat,数字的格式化找DecimalFormat,格式化就找java.text包
2、查看某个类的继承关系,选中该类,Ctrl+T
二、发送协议的封装
1、叶子leaf节点的封装-Leaf.java
①待封装的叶子:
//<agenterid>889931</agenterid>
②封装的代码:
package cn.zengfansheng.lottery.net.protocol;
 
import java.io.IOException;
 
import org.xmlpull.v1.XmlSerializer;
 
/**
* 1、叶子节点的类
* @author hacket
*/

public class Leaf {
 
    /*
        serializer.startTag(null, "agenterid");
        serializer.text("889931");
        serializer.endTag(null, "agenterid");
    */

 
    /**
     * 节点名
     */

    private String tagName;
    /**
     * 节点所对应的值
     */

    private String tagValue;
 
    // 1、因为要保证tagName不能为空,所以私有构造器,并提供一个tagName的构造器,但tagValue不是必须的
    // private Leaf(){}
    public Leaf(String tagName) {
        this.tagName = tagName;
    }
    public Leaf(String tagName, String tagValue) {
        super();
        this.tagName = tagName;
        this.tagValue = tagValue;
    }
 
    //2、出于安全考虑,将setTagName给去掉,不能设置tagName
    public String getTagValue() {
        return tagValue;
    }
    public void setTagValue(String tagValue) {
        this.tagValue = tagValue;
    }
    public String getTagName() {
        return tagName;
    }
 
    /**
     * 3、叶子节点的序列化
     */

    public void serializer(XmlSerializer serializer) {
        try {
 
            serializer.startTag(null, tagName);
 
            // 防止tagValue为null
            if (tagValue == null) {
                tagValue = "";
            }
            serializer.text(tagValue);
            serializer.endTag(null, tagName);
 
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2、头header的封装
①待封装的头信息
<header>              
    <agenterid>889931</agenterid>
    <source>ivr</source>
    <compress>DES</compress>

    <messengerid>20091113101533000001</messengerid> 
    <timestamp>20091113101533</timestamp>
    <digest>7ec8582632678032d25866bd4bce114f</digest>

    <transactiontype>12002</transactiontype>
    <username>13200000000</username>
</header>
②封装后的代码
a、由于有8个叶子,所以需要声明出8个Leaf对象,每个对象代表一个叶子
b、对于叶子中不变的值,可以直接在构造方法中设置tagValue,而对于那些需要用代码拼凑出来tagValue,
c、引用到了其他对象中的东西,作为参数传递进来
package cn.zengfansheng.lottery.net.protocol;
 
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
 
import org.apache.commons.codec.digest.DigestUtils;
import org.xmlpull.v1.XmlSerializer;
 
import cn.zengfansheng.lottery.ConstantValue;
 
/**
* 2、头信息的封装
*
* @author hacket
*/

public class Header {
 
    // 这三个值是固定的
    private Leaf agenterid = new Leaf("agenterid", ConstantValue.AGENTERID);
    private Leaf source = new Leaf("source", ConstantValue.SOURCE);
    private Leaf compress = new Leaf("compress", ConstantValue.COMPRESS);
 
    // 下面的值需要用代码拼凑起来的
    private Leaf messengerid = new Leaf("messengerid");
 
    private Leaf timestamp = new Leaf("timestamp");
    private Leaf digest = new Leaf("digest");
 
    // 这两个是服务器给设置的
    private Leaf transactiontype = new Leaf("transactiontype");
    private Leaf username = new Leaf("username");
 
    public Leaf getTransactiontype() {
        return transactiontype;
    }
    public Leaf getUsername() {
        return username;
    }
    // 有8个叶子
     /*<header>             
         <agenterid>889931</agenterid>
         <source>ivr</source>
         <compress>DES</compress>
 
         <messengerid>20091113101533000001</messengerid>
         <timestamp>20091113101533</timestamp>
         <digest>7ec8582632678032d25866bd4bce114f</digest>
 
         <transactiontype>12002</transactiontype>
         <username>13200000000</username>
     </header>*/

    /**
     * 序列化头信息
     * @param serializer
     * @param bodyInfo  完整的body内容:没有经过DES加密
     */

    public void serializer(XmlSerializer serializer,String bodyInfo) {
        try {
            // header开始
            serializer.startTag(null, "header");
 
            // 1、agenterid
            agenterid.serializer(serializer);
            // 2、source
            source.serializer(serializer);
            // 3、compress
            compress.serializer(serializer);
 
            // 获取当前系统时间
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss",Locale.CHINA);
            String time = sdf.format(new Date());
 
            // TODO :获取六位随机数,不足6位数的补零,和SimpleDateFormat用法类似
            DecimalFormat df = new DecimalFormat("000000");
            Random random = new Random();
            int randomNum = random.nextInt(99999) + 1;
            String randomValue = df.format(randomNum);
 
            // 4、messengerid = 时间戳 + 六位随机数(不足六位的话补零)
            messengerid.setTagValue(time + randomValue);
            messengerid.serializer(serializer);
 
            // 5、timestamp:时间戳
            timestamp.setTagValue(time);
            timestamp.serializer(serializer);
 
            // TODO :6、时间戳+代理密码+完整的body的md5值(由于Body不是header中的内容,所以可将其作为参数传递进来)
            String agentPass = ConstantValue.AGENTER_PASSWORD;
            String md5Value = DigestUtils.md5Hex(time + agentPass + bodyInfo);// 处理Md5数据
            digest.setTagValue(md5Value);
            digest.serializer(serializer);
 
            // 7、transactiontype和username
            // 由于这两个是服务器写的,所以这里不能写,要进行判空处理,leaf中已经做了处理
            transactiontype.serializer(serializer);
            username.serializer(serializer);
 
            serializer.endTag(null, "header");
 
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
三、Element的封装
①分析:由于一个Element代表的就是一个请求,而不同的请求,封装的内容不一样,所以需要将多个请求对象的共性内容给抽取出来,作为一个抽象类模板,不同的请求,只需要继承该模板,实现里面具体的实现就可以了
② 需要封装的xml文件
<element>
    <lotteryid>118</lotteryid>
    <issues>1</issues>
</element>
③抽象类Element.java
package cn.zengfansheng.lottery.net.protocol;
import org.xmlpull.v1.XmlSerializer;
/**
 * 3、Element请求内容的封装(35个请求)
 * @author hacket
 */
public abstract class Element {
    // 由于请求信息根据不能的请求,会有不同的节点,所以不能写死了
    
    //由具体的子类来实现具体的请求的封装
    /**
     * 1、请求内容的序列化-生成对应的请求类型中的请求信息
     * @param serializer
     */
    public abstract void serializer(XmlSerializer serializer);
    
    // 由于不同的请求有专门的数字代表,所以也要由子类实现
    /**
     * 2、获取代表该请求类型的请求码
     * @return 返回代表该请求类型的请求码
     */
    public abstract String getTransactiontype();
}
④可销售期信息查询 CurrentIssueElement.java
package cn.zengfansheng.lottery.net.protocol.element;

import java.io.IOException;

import org.xmlpull.v1.XmlSerializer;

import cn.zengfansheng.lottery.net.protocol.Element;
import cn.zengfansheng.lottery.net.protocol.Leaf;

/**
 * 1、可销售期信息查询
 * @author hacket
 */

public class CurrentIssueElement extends Element {

    // 玩法编号
    private Leaf lotteryid = new Leaf("lotteryid");
    // 需要获取的最大期数max=100期,1表示当前销售期
    private Leaf issues = new Leaf("issues");

    @Override
    public void serializer(XmlSerializer serializer) {
        /*
          <element>
             <lotteryid>118</lotteryid>
             <issues>1</issues>
          </element>
         */

       
        try {
            serializer.startTag(null, "element");

            String tagValue = "118";
            lotteryid.setTagValue(tagValue);
            lotteryid.serializer(serializer);

            issues.setTagValue("1");
            issues.serializer(serializer);

            serializer.endTag(null, "element");

        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public String getTransactiontype() {
        return "12002";// 查询指定玩法可销售期信息(12002)
    }
}
⑤ 用户登陆密码验证UserLoginElement.java
package cn.zengfansheng.lottery.net.protocol.element;

import java.io.IOException;
import org.xmlpull.v1.XmlSerializer;
import cn.zengfansheng.lottery.net.protocol.Element;
import cn.zengfansheng.lottery.net.protocol.Leaf;
/**
 * 2、用户登陆密码验证(14001)
 * @author hacket
 */

public class UserLoginElement extends Element {

    // 账户安全密码
    private Leaf actpassword = new Leaf("actpassword");

    @Override
    public void serializer(XmlSerializer serializer) {
        try {
            // 用户登陆:
            // actpassword string 5-20 账户安全密码
           
            serializer.startTag(null, "element");
           
            actpassword.setTagValue("123456");
            actpassword.serializer(serializer);

            serializer.endTag(null, "element");
           
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getTransactiontype() {
        return "14001";
    }
}
四、elements、body
①分析:elements中由多个element构成,所以只需要在body中用一个List<Element>就可以表示elements节点了
②对应的xml文件
 <body>
    <elements>
          <element>
                 <lotteryid>118</lotteryid>
                 <issues>1</issues>
          </element>
    </elements> 
</body>  
③ body请求的内容封装:Body.java 
package cn.zengfansheng.lottery.net.protocol;
 
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
 
import org.apache.commons.lang3.StringUtils;
import org.xmlpull.v1.XmlSerializer;
 
import android.util.Xml;
import cn.zengfansheng.lottery.ConstantValue;
import cn.zengfansheng.lottery.util.DES;
 
/**
* 3、body信息-消息体信息封装
* @author hacket
*/

public class Body {
 
    // 代表<elements></elements>节点
    private List<Element> elements = new ArrayList<Element>();
 
    /**
     * 1、body内容的序列化
     * @param serializer
     */

    public void serializer(XmlSerializer serializer) {
        /*<elements>
            <element>
                   <lotteryid>118</lotteryid>
                   <issues>1</issues>
            </element>
        </elements> */

        try {
 
            serializer.startTag(null, "body");
            serializer.startTag(null, "elements");
 
            //循环遍历集合,将每一个element给序列化
            for (Element element : elements) {
                element.serializer(serializer);
            }
 
            serializer.endTag(null, "elements");
            serializer.endTag(null, "body");
 
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 2、获取List集合
     * @return
     */

    public List<Element> getElements() {
        return elements;
    }
 
    /**
     * 3、获取完整的body
     * 既然要获取完整的body,那么就要重新用一个xmlSerializer
     * @return
     */

    public String getWholeBodyInfo() {
 
        XmlSerializer serializer = Xml.newSerializer();
 
        try {
            Writer writer = new StringWriter();
            serializer.setOutput(writer);
 
            this.serializer(serializer);
 
            serializer.flush();// 一定要记得刷新
            // serializer.endDocument();// 不能调用它,否则output就关闭了
 
            return writer.toString();
 
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
    /**
     * 4、获取DES加密后的body,是body标签中的内容,不包括body标签
     * @return
     */

    public String getDesBodyInfo() {
 
        String body = this.getWholeBodyInfo();
 
        //用des加密的是body之间的内容,所以需要截取
        String elementStr = StringUtils.substringBetween(body, "<body>", "</body>");
 
        DES des = new DES();
 
        //des.authcode("wewweewewew=","DECODE","0102030405060708")
        return des.authcode(elementStr, "DECODE", ConstantValue.DES_PASSWORD);
        // DES加密
        // 预留一部分时间,2天,调试加密的数据
        // ①算法(DES)实现(统一算法实现)
        // ②加密的元数据内容不一致:空格或回车换行(文本比对工具)
    }
}
五、完整的message的封装 
①存在的问题
// 存在问题:
    // ①完整的body获取
    // 解决:在body中提供一个获取完整body的方法
    // ②transactiontype获取
    // 解决:由对应的请求类型Element返回
    // ③设置请求参数的入口
    // 解决:在Message中提供获取指定Element的xml生成文件
    // ④body里面数据加密
    // 解决:在body中提供一个获取加密body中内容的方法  
②完整的xml,message
<?xml version=”1.0” encoding=”utf-8”?>
<message version="1.0">
    <header>              
        <agenterid>889931</agenterid>
        <source>ivr</source>
        <compress>DES</compress>
        <messengerid>20091113101533000001</messengerid> 
        <timestamp>20091113101533</timestamp>
        <digest>7ec8582632678032d25866bd4bce114f</digest>
        <transactiontype>12002</transactiontype>
        <username>13200000000</username>
    </header>
    <body>
        <elements>
              <element>
                     <lotteryid>118</lotteryid>
                     <issues>1</issues>
              </element>
        </elements> 
    </body>
</message>  
③ message的封装-完整的xml生成
package cn.zengfansheng.lottery.net.protocol;
 
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
 
import org.xmlpull.v1.XmlSerializer;
 
import android.util.Xml;
 
/**
* 4、整体的xml文件封装-message封装
* @author hacket
*/

public class Message {
 
    // 第一个部分,头部分header
    private Header header = new Header();
    // 第二部分,请求体内容body
    private Body body = new Body();
 
    /**
     * 1、序列化message
     * @param serializer
     */

    public void serializer(XmlSerializer serializer) {
 
        try {
            // message
            serializer.startTag(null, "message");
            serializer.attribute(null, "version", "1.0");
 
            // TODO 1、header,完整的body,用于给digest生成md5值用的
            header.serializer(serializer, body.getWholeBodyInfo());
 
            // 2、body、写DES加密后的body
            // body.serializer(serializer);
            // 由于要调用body的des加密后的xml,只需要body之间的内容,所以不能用上面的
            serializer.startTag(null, "body");
            serializer.text(body.getDesBodyInfo());// 加密后的body标签类的内容
            serializer.endTag(null, "body");
 
            serializer.endTag(null, "message");
 
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 2、获取完整的xml文件信息,请求内容
     * @param element 要生成的请求的xml文件的Element
     * @return
     */

    public String getXml(Element element) {
 
        // 进行element的判空,如果为null,那么说明element,就不知道该xml要服务器做什么
        if (element == null) {
            throw new IllegalArgumentException("请求参数element is not null!!!");
        }
 
        // 为body添加对应的element
        body.getElements().add(element);
        // 设置每个element的请求类型
        header.getTransactiontype().setTagValue(element.getTransactiontype());
 
        try {
            XmlSerializer serializer = Xml.newSerializer();
 
            Writer writer = new StringWriter();// 选择某个类,ctrl+t,查看该类下的子类(包括孙子)
            serializer.setOutput(writer);
            serializer.startDocument("utf-8", true);
 
            this.serializer(serializer);
 
            serializer.endDocument();
 
            return writer.toString();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
 
    /**
     * 3、获取头header
     * @return
     */

    public Header getHeader() {
        return header;
    }
 
    /**
     * 4、获取内容体body
     * @return
     */

    public Body getBody() {
        return body;
    }
}
六、测试
只需要将对应的请求类型的实现类传递给Message的getXml()方法就可以生成对应的xml文件
Message message = new Message();
CurrentIssueElement currentIssueElement = new CurrentIssueElement();
String xml = message.getXml(currentIssueElement);// 一句代码生成xml文件