• 项目实战从0到1之hive(33)大数据项目之电商数仓(用户行为数据采集)(一)


    第1章 数据仓库概念

    img

    第2章 项目需求及架构设计

    2.1 项目需求分析

    img

    2.2 项目框架

    2.2.1 技术选型

    img

    2.2.2 系统数据流程设计

    img

    2.2.3 框架版本选型

    img

    img

    2.2.4 服务器选型

    img

    2.2.5 集群资源规划设计

    img

    2)测试集群服务器规划

    img

    第3章 数据生成模块

    3.1 埋点数据基本格式

    • 公共字段:基本所有安卓手机都包含的字段

    • 业务字段:埋点上报的字段,有具体的业务类型

    下面就是一个示例,表示业务字段的上传。

    {
    "ap":"xxxxx",//项目数据来源 app pc
    "cm": {  //公共字段
           "mid": "",  // (String) 设备唯一标识
           "uid": "",  // (String) 用户标识
           "vc": "1",  // (String) versionCode,程序版本号
           "vn": "1.0",  // (String) versionName,程序版本名
           "l": "zh",  // (String) language系统语言
           "sr": "",  // (String) 渠道号,应用从哪个渠道来的。
           "os": "7.1.1",  // (String) Android系统版本
           "ar": "CN",  // (String) area区域
           "md": "BBB100-1",  // (String) model手机型号
           "ba": "blackberry",  // (String) brand手机品牌
           "sv": "V2.2.1",  // (String) sdkVersion
           "g": "",  // (String) gmail
           "hw": "1620x1080",  // (String) heightXwidth,屏幕宽高
           "t": "1506047606608",  // (String) 客户端日志产生时的时间
           "nw": "WIFI",  // (String) 网络模式
           "ln": 0,  // (double) lng经度
           "la": 0  // (double) lat 纬度
      },
    "et": [  //事件
              {
                   "ett": "1506047605364",  //客户端事件产生时间
                   "en": "display",  //事件名称
                   "kv": {  //事件结果,以key-value形式自行定义
                       "goodsid": "236",
                       "action": "1",
                       "extend1": "1",
    "place": "2",
    "category": "75"
                  }
              }
          ]
    }

    示例日志(服务器时间戳 | 日志):

    1540934156385|{
       "ap": "gmall",
       "cm": {
           "uid": "1234",
           "vc": "2",
           "vn": "1.0",
           "la": "EN",
           "sr": "",
           "os": "7.1.1",
           "ar": "CN",
           "md": "BBB100-1",
           "ba": "blackberry",
           "sv": "V2.2.1",
           "g": "abc@gmail.com",
           "hw": "1620x1080",
           "t": "1506047606608",
           "nw": "WIFI",
           "ln": 0
      },
           "et": [
              {
                   "ett": "1506047605364",  //客户端事件产生时间
                   "en": "display",  //事件名称
                   "kv": {  //事件结果,以key-value形式自行定义
                       "goodsid": "236",
                       "action": "1",
                       "extend1": "1",
    "place": "2",
    "category": "75"
                  }
              },{
                   "ett": "1552352626835",
                   "en": "active_background",
                   "kv": {
                        "active_source": "1"
                  }
              }
          ]
      }
    }

    下面是各个埋点日志格式。其中商品点击属于信息流的范畴

    3.2 事件日志数据

    3.2.1 商品列表页(loading)

    事件名称:loading

    img

    img

    3.2.2 商品点击(display)

    事件标签:display

    img

    img

    3.2.3 商品详情页(newsdetail)

    事件标签:newsdetail

    img

    img

    3.2.4 广告(ad)

    事件名称:ad

    img

    img

    3.2.5 消息通知(notification)

    事件标签:notification

    img

    3.2.6 用户前台活跃(active_foreground)

    事件标签: active_foreground img

    3.2.7 用户后台活跃(active_background)

    事件标签: active_background

    img

    3.2.8 评论(comment)

    描述:评论表 img

    img

    3.2.9 收藏(favorites)

    描述:收藏

    img

    3.2.10 点赞(praise)

    描述:所有的点赞表 img

    img

    3.2.11 错误日志

    img

    3.3 启动日志数据

    事件标签: start

    img

    {
       "action":"1",
       "ar":"MX",
       "ba":"HTC",
       "detail":"",
       "en":"start",
       "entry":"2",
       "extend1":"",
       "g":"43R2SEQX@gmail.com",
       "hw":"640*960",
       "l":"en",
       "la":"20.4",
       "ln":"-99.3",
       "loading_time":"2",
       "md":"HTC-2",
       "mid":"995",
       "nw":"4G",
       "open_ad_type":"2",
       "os":"8.1.2",
       "sr":"B",
       "sv":"V2.0.6",
       "t":"1561472502444",
       "uid":"995",
       "vc":"10",
       "vn":"1.3.4"
    }

    3.4 数据生成脚本

    img

    3.4.1 创建Maven工程

    1)创建log-collector 2)创建一个包名:com.kgg.appclient 3)在com.kgg.appclient包下创建一个类,AppMain。 4)在pom.xml文件中添加如下内容

    <!--版本号统一-->
    <properties>
       <slf4j.version>1.7.20</slf4j.version>
       <logback.version>1.0.7</logback.version>
    </properties>

    <dependencies>
       <!--阿里巴巴开源json解析框架-->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
           <version>1.2.51</version>
       </dependency>

       <!--日志生成框架-->
       <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-core</artifactId>
           <version>${logback.version}</version>
       </dependency>
       <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-classic</artifactId>
           <version>${logback.version}</version>
       </dependency>
    </dependencies>

    <!--编译打包插件-->
    <build>
       <plugins>
           <plugin>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>2.3.2</version>
               <configuration>
                   <source>1.8</source>
                   <target>1.8</target>
               </configuration>
           </plugin>
           <plugin>
               <artifactId>maven-assembly-plugin </artifactId>
               <configuration>
                   <descriptorRefs>
                       <descriptorRef>jar-with-dependencies</descriptorRef>
                   </descriptorRefs>
                   <archive>
                       <manifest>
                           <mainClass>com.kgg.appclient.AppMain</mainClass>
                       </manifest>
                   </archive>
               </configuration>
               <executions>
                   <execution>
                       <id>make-assembly</id>
                       <phase>package</phase>
                       <goals>
                           <goal>single</goal>
                       </goals>
                   </execution>
               </executions>
           </plugin>
       </plugins>
    </build>

    注意:com.kgg.appclient.AppMain要和自己建的全类名一致。

    3.4.2 公共字段Bean

    1)创建包名:com.kgg.bean 2)在com.kgg.bean包下依次创建如下bean对象

    package com.kgg.bean;
    /**
    * 公共日志
    */
    public class AppBase{

       private String mid; // (String) 设备唯一标识
       private String uid; // (String) 用户uid
       private String vc;  // (String) versionCode,程序版本号
       private String vn;  // (String) versionName,程序版本名
       private String l;   // (String) 系统语言
       private String sr;  // (String) 渠道号,应用从哪个渠道来的。
       private String os;  // (String) Android系统版本
       private String ar;  // (String) 区域
       private String md;  // (String) 手机型号
       private String ba;  // (String) 手机品牌
       private String sv;  // (String) sdkVersion
       private String g;   // (String) gmail
       private String hw;  // (String) heightXwidth,屏幕宽高
       private String t;   // (String) 客户端日志产生时的时间
       private String nw;  // (String) 网络模式
       private String ln;  // (double) lng经度
       private String la;  // (double) lat 纬度

       public String getMid() {
           return mid;
      }

       public void setMid(String mid) {
           this.mid = mid;
      }

       public String getUid() {
           return uid;
      }

       public void setUid(String uid) {
           this.uid = uid;
      }

       public String getVc() {
           return vc;
      }

       public void setVc(String vc) {
           this.vc = vc;
      }

       public String getVn() {
           return vn;
      }

       public void setVn(String vn) {
           this.vn = vn;
      }

       public String getL() {
           return l;
      }

       public void setL(String l) {
           this.l = l;
      }

       public String getSr() {
           return sr;
      }

       public void setSr(String sr) {
           this.sr = sr;
      }

       public String getOs() {
           return os;
      }

       public void setOs(String os) {
           this.os = os;
      }

       public String getAr() {
           return ar;
      }

       public void setAr(String ar) {
           this.ar = ar;
      }

       public String getMd() {
           return md;
      }

       public void setMd(String md) {
           this.md = md;
      }

       public String getBa() {
           return ba;
      }

       public void setBa(String ba) {
           this.ba = ba;
      }

       public String getSv() {
           return sv;
      }

       public void setSv(String sv) {
           this.sv = sv;
      }

       public String getG() {
           return g;
      }

       public void setG(String g) {
           this.g = g;
      }

       public String getHw() {
           return hw;
      }

       public void setHw(String hw) {
           this.hw = hw;
      }

       public String getT() {
           return t;
      }

       public void setT(String t) {
           this.t = t;
      }

       public String getNw() {
           return nw;
      }

       public void setNw(String nw) {
           this.nw = nw;
      }

       public String getLn() {
           return ln;
      }

       public void setLn(String ln) {
           this.ln = ln;
      }

       public String getLa() {
           return la;
      }

       public void setLa(String la) {
           this.la = la;
      }
    }

    3.4.3 启动日志Bean

    package com.kgg.bean;
    /**
    * 启动日志
    */
    public class AppStart extends AppBase {

       private String entry;//入口: push=1,widget=2,icon=3,notification=4, lockscreen_widget =5
       private String open_ad_type;//开屏广告类型: 开屏原生广告=1, 开屏插屏广告=2
       private String action;//状态:成功=1 失败=2
       private String loading_time;//加载时长:计算下拉开始到接口返回数据的时间,(开始加载报0,加载成功或加载失败才上报时间)
       private String detail;//失败码(没有则上报空)
       private String extend1;//失败的message(没有则上报空)
       private String en;//启动日志类型标记

       public String getEntry() {
           return entry;
      }

       public void setEntry(String entry) {
           this.entry = entry;
      }

       public String getOpen_ad_type() {
           return open_ad_type;
      }

       public void setOpen_ad_type(String open_ad_type) {
           this.open_ad_type = open_ad_type;
      }

       public String getAction() {
           return action;
      }

       public void setAction(String action) {
           this.action = action;
      }

       public String getLoading_time() {
           return loading_time;
      }

       public void setLoading_time(String loading_time) {
           this.loading_time = loading_time;
      }

       public String getDetail() {
           return detail;
      }

       public void setDetail(String detail) {
           this.detail = detail;
      }

       public String getExtend1() {
           return extend1;
      }

       public void setExtend1(String extend1) {
           this.extend1 = extend1;
      }

       public String getEn() {
           return en;
      }

       public void setEn(String en) {
           this.en = en;
      }
    }

    3.4.3 错误日志Bean

    package com.kgg.bean;
    /**
    * 错误日志
    */
    public class AppErrorLog {

       private String errorBrief;    //错误摘要
       private String errorDetail;   //错误详情

       public String getErrorBrief() {
           return errorBrief;
      }

       public void setErrorBrief(String errorBrief) {
           this.errorBrief = errorBrief;
      }

       public String getErrorDetail() {
           return errorDetail;
      }

       public void setErrorDetail(String errorDetail) {
           this.errorDetail = errorDetail;
      }
    }

    3.4.4 事件日志Bean之商品点击

    package com.kgg.bean;
    /**
    * 商品点击日志
    */
    public class AppDisplay {

       private String action;//动作:曝光商品=1,点击商品=2,
       private String goodsid;//商品ID(服务端下发的ID)
       private String place;//顺序(第几条商品,第一条为0,第二条为1,如此类推)
       private String extend1;//曝光类型:1 - 首次曝光 2-重复曝光(没有使用)
       private String category;//分类ID(服务端定义的分类ID)

       public String getAction() {
           return action;
      }

       public void setAction(String action) {
           this.action = action;
      }

       public String getGoodsid() {
           return goodsid;
      }

       public void setGoodsid(String goodsid) {
           this.goodsid = goodsid;
      }

       public String getPlace() {
           return place;
      }

       public void setPlace(String place) {
           this.place = place;
      }

       public String getExtend1() {
           return extend1;
      }

       public void setExtend1(String extend1) {
           this.extend1 = extend1;
      }

       public String getCategory() {
           return category;
      }

       public void setCategory(String category) {
           this.category = category;
      }
    }

    3.4.5 事件日志Bean之商品详情页

    package com.kgg.bean;
    /**
    * 商品详情
    */
    public class AppNewsDetail {

       private String entry;//页面入口来源:应用首页=1、push=2、详情页相关推荐=3
       private String action;//动作:开始加载=1,加载成功=2(pv),加载失败=3, 退出页面=4
       private String goodsid;//商品ID(服务端下发的ID)
       private String showtype;//商品样式:0、无图1、一张大图2、两张图3、三张小图4、一张小图5、一张大图两张小图   来源于详情页相关推荐的商品,上报样式都为0(因为都是左文右图)
       private String news_staytime;//页面停留时长:从商品开始加载时开始计算,到用户关闭页面所用的时间。若中途用跳转到其它页面了,则暂停计时,待回到详情页时恢复计时。或中途划出的时间超过10分钟,则本次计时作废,不上报本次数据。如未加载成功退出,则报空。
       private String loading_time;//加载时长:计算页面开始加载到接口返回数据的时间 (开始加载报0,加载成功或加载失败才上报时间)
       private String type1;//加载失败码:把加载失败状态码报回来(报空为加载成功,没有失败)
       private String category;//分类ID(服务端定义的分类ID)

       public String getEntry() {
           return entry;
      }

       public void setEntry(String entry) {
           this.entry = entry;
      }

       public String getAction() {
           return action;
      }

       public void setAction(String action) {
           this.action = action;
      }

       public String getGoodsid() {
           return goodsid;
      }

       public void setGoodsid(String goodsid) {
           this.goodsid = goodsid;
      }

       public String getShowtype() {
           return showtype;
      }

       public void setShowtype(String showtype) {
           this.showtype = showtype;
      }

       public String getNews_staytime() {
           return news_staytime;
      }

       public void setNews_staytime(String news_staytime) {
           this.news_staytime = news_staytime;
      }

       public String getLoading_time() {
           return loading_time;
      }

       public void setLoading_time(String loading_time) {
           this.loading_time = loading_time;
      }

       public String getType1() {
           return type1;
      }

       public void setType1(String type1) {
           this.type1 = type1;
      }

       public String getCategory() {
           return category;
      }

       public void setCategory(String category) {
           this.category = category;
      }
    }

    3.4.6 事件日志Bean之商品列表页

    package com.kgg.bean;
    /**
    * 商品列表
    */
    public class AppLoading {
       private String action;//动作:开始加载=1,加载成功=2,加载失败=3
       private String loading_time;//加载时长:计算下拉开始到接口返回数据的时间,(开始加载报0,加载成功或加载失败才上报时间)
       private String loading_way;//加载类型:1-读取缓存,2-从接口拉新数据   (加载成功才上报加载类型)
       private String extend1;//扩展字段 Extend1
       private String extend2;//扩展字段 Extend2
       private String type;//加载类型:自动加载=1,用户下拽加载=2,底部加载=3(底部条触发点击底部提示条/点击返回顶部加载)
       private String type1;//加载失败码:把加载失败状态码报回来(报空为加载成功,没有失败)

       public String getAction() {
           return action;
      }

       public void setAction(String action) {
           this.action = action;
      }

       public String getLoading_time() {
           return loading_time;
      }

       public void setLoading_time(String loading_time) {
           this.loading_time = loading_time;
      }

       public String getLoading_way() {
           return loading_way;
      }

       public void setLoading_way(String loading_way) {
           this.loading_way = loading_way;
      }

       public String getExtend1() {
           return extend1;
      }

       public void setExtend1(String extend1) {
           this.extend1 = extend1;
      }

       public String getExtend2() {
           return extend2;
      }

       public void setExtend2(String extend2) {
           this.extend2 = extend2;
      }

       public String getType() {
           return type;
      }

       public void setType(String type) {
           this.type = type;
      }

       public String getType1() {
           return type1;
      }

       public void setType1(String type1) {
           this.type1 = type1;
      }
    }

    3.4.7 事件日志Bean之广告

    package com.kgg.bean;
    /**
    * 广告
    */
    public class AppAd {

       private String entry;//入口:商品列表页=1 应用首页=2 商品详情页=3
       private String action;//动作:请求广告=1 取缓存广告=2 广告位展示=3 广告展示=4 广告点击=5
       private String content;//状态:成功=1 失败=2
       private String detail;//失败码(没有则上报空)
       private String source;//广告来源:admob=1 facebook=2 ADX(百度)=3 VK(俄罗斯)=4
       private String behavior;//用户行为:   主动获取广告=1   被动获取广告=2
       private String newstype;//Type: 1- 图文 2-图集 3-段子 4-GIF 5-视频 6-调查 7-纯文 8-视频+图文 9-GIF+图文 0-其他
       private String show_style;//内容样式:无图(纯文字)=6 一张大图=1 三站小图+文=4 一张小图=2 一张大图两张小图+文=3 图集+文 = 5
                              //一张大图+文=11   GIF大图+文=12 视频(大图)+文 = 13
                              //来源于详情页相关推荐的商品,上报样式都为0(因为都是左文右图)

       public String getEntry() {
           return entry;
      }

       public void setEntry(String entry) {
           this.entry = entry;
      }

       public String getAction() {
           return action;
      }

       public void setAction(String action) {
           this.action = action;
      }

       public String getContent() {
           return content;
      }

       public void setContent(String content) {
           this.content = content;
      }

       public String getDetail() {
           return detail;
      }

       public void setDetail(String detail) {
           this.detail = detail;
      }

       public String getSource() {
           return source;
      }

       public void setSource(String source) {
           this.source = source;
      }

       public String getBehavior() {
           return behavior;
      }

       public void setBehavior(String behavior) {
           this.behavior = behavior;
      }

       public String getNewstype() {
           return newstype;
      }

       public void setNewstype(String newstype) {
           this.newstype = newstype;
      }

       public String getShow_style() {
           return show_style;
      }

       public void setShow_style(String show_style) {
           this.show_style = show_style;
      }
    }

    3.4.8 事件日志Bean之消息通知

    package com.kgg.bean;
    /**
    * 消息通知日志
    */
    public class AppNotification {
       private String action;//动作:通知产生=1,通知弹出=2,通知点击=3,常驻通知展示(不重复上报,一天之内只报一次)=4
       private String type;//通知id:预警通知=1,天气预报(早=2,晚=3),常驻=4
       private String ap_time;//客户端弹出时间
       private String content;//备用字段

       public String getAction() {
           return action;
      }

       public void setAction(String action) {
           this.action = action;
      }

       public String getType() {
           return type;
      }

       public void setType(String type) {
           this.type = type;
      }

       public String getAp_time() {
           return ap_time;
      }

       public void setAp_time(String ap_time) {
           this.ap_time = ap_time;
      }

       public String getContent() {
           return content;
      }

       public void setContent(String content) {
           this.content = content;
      }
    }

    3.4.9 事件日志Bean之用户前台活跃

    package com.kgg.bean;
    /**
    * 用户前台活跃
    */
    public class AppActive_foreground {
       private String push_id;//推送的消息的id,如果不是从推送消息打开,传空
       private String access;//1.push 2.icon 3.其他

       public String getPush_id() {
           return push_id;
      }

       public void setPush_id(String push_id) {
           this.push_id = push_id;
      }

       public String getAccess() {
           return access;
      }

       public void setAccess(String access) {
           this.access = access;
      }
    }

    3.4.10 事件日志Bean之用户后台活跃

    package com.kgg.bean;
    /**
    * 用户后台活跃
    */
    public class AppActive_background {
       private String active_source;//1=upgrade,2=download(下载),3=plugin_upgrade

       public String getActive_source() {
           return active_source;
      }

       public void setActive_source(String active_source) {
           this.active_source = active_source;
      }
    }

    3.4.11 事件日志Bean之用户评论

    package com.kgg.bean;
    /**
    * 评论
    */
    public class AppComment {

       private int comment_id;//评论表
       private int userid;//用户id
       private  int p_comment_id;//父级评论id(为0则是一级评论,不为0则是回复)
       private String content;//评论内容
       private String addtime;//创建时间
       private int other_id;//评论的相关id
       private int praise_count;//点赞数量
       private int reply_count;//回复数量

       public int getComment_id() {
           return comment_id;
      }

       public void setComment_id(int comment_id) {
           this.comment_id = comment_id;
      }

       public int getUserid() {
           return userid;
      }

       public void setUserid(int userid) {
           this.userid = userid;
      }

       public int getP_comment_id() {
           return p_comment_id;
      }

       public void setP_comment_id(int p_comment_id) {
           this.p_comment_id = p_comment_id;
      }

       public String getContent() {
           return content;
      }

       public void setContent(String content) {
           this.content = content;
      }

       public String getAddtime() {
           return addtime;
      }

       public void setAddtime(String addtime) {
           this.addtime = addtime;
      }

       public int getOther_id() {
           return other_id;
      }

       public void setOther_id(int other_id) {
           this.other_id = other_id;
      }

       public int getPraise_count() {
           return praise_count;
      }

       public void setPraise_count(int praise_count) {
           this.praise_count = praise_count;
      }

       public int getReply_count() {
           return reply_count;
      }

       public void setReply_count(int reply_count) {
           this.reply_count = reply_count;
      }
    }

    3.4.12 事件日志Bean之用户收藏

    package com.kgg.bean;
    /**
    * 收藏
    */
    public class AppFavorites {
       private int id;//主键
       private int course_id;//商品id
       private int userid;//用户ID
       private String add_time;//创建时间

       public int getId() {
           return id;
      }

       public void setId(int id) {
           this.id = id;
      }

       public int getCourse_id() {
           return course_id;
      }

       public void setCourse_id(int course_id) {
           this.course_id = course_id;
      }

       public int getUserid() {
           return userid;
      }

       public void setUserid(int userid) {
           this.userid = userid;
      }

       public String getAdd_time() {
           return add_time;
      }

       public void setAdd_time(String add_time) {
           this.add_time = add_time;
      }
    }

    3.4.13 事件日志Bean之用户点赞

    package com.kgg.bean;
    /**
    * 点赞
    */
    public class AppPraise {
       private int id; //主键id
       private int userid;//用户id
       private int target_id;//点赞的对象id
       private int type;//点赞类型 1问答点赞 2问答评论点赞 3 文章点赞数4 评论点赞
       private String add_time;//添加时间

       public int getId() {
           return id;
      }

       public void setId(int id) {
           this.id = id;
      }

       public int getUserid() {
           return userid;
      }

       public void setUserid(int userid) {
           this.userid = userid;
      }

       public int getTarget_id() {
           return target_id;
      }

       public void setTarget_id(int target_id) {
           this.target_id = target_id;
      }

       public int getType() {
           return type;
      }

       public void setType(int type) {
           this.type = type;
      }

       public String getAdd_time() {
           return add_time;
      }

       public void setAdd_time(String add_time) {
           this.add_time = add_time;
      }
    }

    3.4.14 主函数

    img

    在AppMain类中添加如下内容:

    package com.kgg.appclient;

    import java.io.UnsupportedEncodingException;
    import java.util.Random;

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    import com.kgg.bean.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    /**
    * 日志行为数据模拟
    */
    public class AppMain {

       private final static Logger logger = LoggerFactory.getLogger(AppMain.class);
       private static Random rand = new Random();

       // 设备id
       private static int s_mid = 0;

       // 用户id
       private static int s_uid = 0;

       // 商品id
       private static int s_goodsid = 0;

       public static void main(String[] args) {

           // 参数一:控制发送每条的延时时间,默认是0
           Long delay = args.length > 0 ? Long.parseLong(args[0]) : 0L;

           // 参数二:循环遍历次数
           int loop_len = args.length > 1 ? Integer.parseInt(args[1]) : 1000;

           // 生成数据
           generateLog(delay, loop_len);
      }

       private static void generateLog(Long delay, int loop_len) {

           for (int i = 0; i < loop_len; i++) {

               int flag = rand.nextInt(2);

               switch (flag) {
                   case (0):
                       //应用启动
                       AppStart appStart = generateStart();
                       String jsonString = JSON.toJSONString(appStart);

                       //控制台打印
                       logger.info(jsonString);
                       break;

                   case (1):

                       JSONObject json = new JSONObject();

                       json.put("ap", "app");
                       json.put("cm", generateComFields());

                       JSONArray eventsArray = new JSONArray();

                       // 事件日志
                       // 商品点击,展示
                       if (rand.nextBoolean()) {
                           eventsArray.add(generateDisplay());
                           json.put("et", eventsArray);
                      }

                       // 商品详情页
                       if (rand.nextBoolean()) {
                           eventsArray.add(generateNewsDetail());
                           json.put("et", eventsArray);
                      }

                       // 商品列表页
                       if (rand.nextBoolean()) {
                           eventsArray.add(generateNewList());
                           json.put("et", eventsArray);
                      }

                       // 广告
                       if (rand.nextBoolean()) {
                           eventsArray.add(generateAd());
                           json.put("et", eventsArray);
                      }

                       // 消息通知
                       if (rand.nextBoolean()) {
                           eventsArray.add(generateNotification());
                           json.put("et", eventsArray);
                      }

                       // 用户前台活跃
                       if (rand.nextBoolean()) {
                           eventsArray.add(generatbeforeground());
                           json.put("et", eventsArray);
                      }

                       // 用户后台活跃
                       if (rand.nextBoolean()) {
                           eventsArray.add(generateBackground());
                           json.put("et", eventsArray);
                      }

                       //故障日志
                       if (rand.nextBoolean()) {
                           eventsArray.add(generateError());
                           json.put("et", eventsArray);
                      }

                       // 用户评论
                       if (rand.nextBoolean()) {
                           eventsArray.add(generateComment());
                           json.put("et", eventsArray);
                      }

                       // 用户收藏
                       if (rand.nextBoolean()) {
                           eventsArray.add(generateFavorites());
                           json.put("et", eventsArray);
                      }

                       // 用户点赞
                       if (rand.nextBoolean()) {
                           eventsArray.add(generatePraise());
                           json.put("et", eventsArray);
                      }

                       //时间
                       long millis = System.currentTimeMillis();

                       //控制台打印
                       logger.info(millis + "|" + json.toJSONString());
                       break;
              }

               // 延迟
               try {
                   Thread.sleep(delay);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      }

       /**
        * 公共字段设置
        */
       private static JSONObject generateComFields() {

           AppBase appBase = new AppBase();

           //设备id
           appBase.setMid(s_mid + "");
           s_mid++;

           // 用户id
           appBase.setUid(s_uid + "");
           s_uid++;

           // 程序版本号 5,6等
           appBase.setVc("" + rand.nextInt(20));

           //程序版本名 v1.1.1
           appBase.setVn("1." + rand.nextInt(4) + "." + rand.nextInt(10));

           // 安卓系统版本
           appBase.setOs("8." + rand.nextInt(3) + "." + rand.nextInt(10));

           // 语言 es,en,pt
           int flag = rand.nextInt(3);
           switch (flag) {
               case (0):
                   appBase.setL("es");
                   break;
               case (1):
                   appBase.setL("en");
                   break;
               case (2):
                   appBase.setL("pt");
                   break;
          }

           // 渠道号   从哪个渠道来的
           appBase.setSr(getRandomChar(1));

           // 区域
           flag = rand.nextInt(2);
           switch (flag) {
               case 0:
                   appBase.setAr("BR");
               case 1:
                   appBase.setAr("MX");
          }

           // 手机品牌 ba ,手机型号 md,就取2位数字了
           flag = rand.nextInt(3);
           switch (flag) {
               case 0:
                   appBase.setBa("Sumsung");
                   appBase.setMd("sumsung-" + rand.nextInt(20));
                   break;
               case 1:
                   appBase.setBa("Huawei");
                   appBase.setMd("Huawei-" + rand.nextInt(20));
                   break;
               case 2:
                   appBase.setBa("HTC");
                   appBase.setMd("HTC-" + rand.nextInt(20));
                   break;
          }

           // 嵌入sdk的版本
           appBase.setSv("V2." + rand.nextInt(10) + "." + rand.nextInt(10));
           // gmail
           appBase.setG(getRandomCharAndNumr(8) + "@gmail.com");

           // 屏幕宽高 hw
           flag = rand.nextInt(4);
           switch (flag) {
               case 0:
                   appBase.setHw("640*960");
                   break;
               case 1:
                   appBase.setHw("640*1136");
                   break;
               case 2:
                   appBase.setHw("750*1134");
                   break;
               case 3:
                   appBase.setHw("1080*1920");
                   break;
          }

           // 客户端产生日志时间
           long millis = System.currentTimeMillis();
           appBase.setT("" + (millis - rand.nextInt(99999999)));

           // 手机网络模式 3G,4G,WIFI
           flag = rand.nextInt(3);
           switch (flag) {
               case 0:
                   appBase.setNw("3G");
                   break;
               case 1:
                   appBase.setNw("4G");
                   break;
               case 2:
                   appBase.setNw("WIFI");
                   break;
          }

           // 拉丁美洲 西经34°46′至西经117°09;北纬32°42′至南纬53°54′
           // 经度
           appBase.setLn((-34 - rand.nextInt(83) - rand.nextInt(60) / 10.0) + "");
           // 纬度
           appBase.setLa((32 - rand.nextInt(85) - rand.nextInt(60) / 10.0) + "");

           return (JSONObject) JSON.toJSON(appBase);
      }

       /**
        * 商品展示事件
        */
       private static JSONObject generateDisplay() {

           AppDisplay appDisplay = new AppDisplay();

           boolean boolFlag = rand.nextInt(10) < 7;

           // 动作:曝光商品=1,点击商品=2,
           if (boolFlag) {
               appDisplay.setAction("1");
          } else {
               appDisplay.setAction("2");
          }

           // 商品id
           String goodsId = s_goodsid + "";
           s_goodsid++;

           appDisplay.setGoodsid(goodsId);

           // 顺序 设置成6条吧
           int flag = rand.nextInt(6);
           appDisplay.setPlace("" + flag);

           // 曝光类型
           flag = 1 + rand.nextInt(2);
           appDisplay.setExtend1("" + flag);

           // 分类
           flag = 1 + rand.nextInt(100);
           appDisplay.setCategory("" + flag);

           JSONObject jsonObject = (JSONObject) JSON.toJSON(appDisplay);

           return packEventJson("display", jsonObject);
      }

       /**
        * 商品详情页
        */
       private static JSONObject generateNewsDetail() {

           AppNewsDetail appNewsDetail = new AppNewsDetail();

           // 页面入口来源
           int flag = 1 + rand.nextInt(3);
           appNewsDetail.setEntry(flag + "");

           // 动作
           appNewsDetail.setAction("" + (rand.nextInt(4) + 1));

           // 商品id
           appNewsDetail.setGoodsid(s_goodsid + "");

           // 商品来源类型
           flag = 1 + rand.nextInt(3);
           appNewsDetail.setShowtype(flag + "");

           // 商品样式
           flag = rand.nextInt(6);
           appNewsDetail.setShowtype("" + flag);

           // 页面停留时长
           flag = rand.nextInt(10) * rand.nextInt(7);
           appNewsDetail.setNews_staytime(flag + "");

           // 加载时长
           flag = rand.nextInt(10) * rand.nextInt(7);
           appNewsDetail.setLoading_time(flag + "");

           // 加载失败码
           flag = rand.nextInt(10);
           switch (flag) {
               case 1:
                   appNewsDetail.setType1("102");
                   break;
               case 2:
                   appNewsDetail.setType1("201");
                   break;
               case 3:
                   appNewsDetail.setType1("325");
                   break;
               case 4:
                   appNewsDetail.setType1("433");
                   break;
               case 5:
                   appNewsDetail.setType1("542");
                   break;
               default:
                   appNewsDetail.setType1("");
                   break;
          }

           // 分类
           flag = 1 + rand.nextInt(100);
           appNewsDetail.setCategory("" + flag);

           JSONObject eventJson = (JSONObject) JSON.toJSON(appNewsDetail);

           return packEventJson("newsdetail", eventJson);
      }

       /**
        * 商品列表
        */
       private static JSONObject generateNewList() {

           AppLoading appLoading = new AppLoading();

           // 动作
           int flag = rand.nextInt(3) + 1;
           appLoading.setAction(flag + "");

           // 加载时长
           flag = rand.nextInt(10) * rand.nextInt(7);
           appLoading.setLoading_time(flag + "");

           // 失败码
           flag = rand.nextInt(10);
           switch (flag) {
               case 1:
                   appLoading.setType1("102");
                   break;
               case 2:
                   appLoading.setType1("201");
                   break;
               case 3:
                   appLoading.setType1("325");
                   break;
               case 4:
                   appLoading.setType1("433");
                   break;
               case 5:
                   appLoading.setType1("542");
                   break;
               default:
                   appLoading.setType1("");
                   break;
          }

           // 页面 加载类型
           flag = 1 + rand.nextInt(2);
           appLoading.setLoading_way("" + flag);

           // 扩展字段1
           appLoading.setExtend1("");

           // 扩展字段2
           appLoading.setExtend2("");

           // 用户加载类型
           flag = 1 + rand.nextInt(3);
           appLoading.setType("" + flag);

           JSONObject jsonObject = (JSONObject) JSON.toJSON(appLoading);

           return packEventJson("loading", jsonObject);
      }

       /**
        * 广告相关字段
        */
       private static JSONObject generateAd() {

           AppAd appAd = new AppAd();

           // 入口
           int flag = rand.nextInt(3) + 1;
           appAd.setEntry(flag + "");

           // 动作
           flag = rand.nextInt(5) + 1;
           appAd.setAction(flag + "");

           // 状态
           flag = rand.nextInt(10) > 6 ? 2 : 1;
           appAd.setContent(flag + "");

           // 失败码
           flag = rand.nextInt(10);
           switch (flag) {
               case 1:
                   appAd.setDetail("102");
                   break;
               case 2:
                   appAd.setDetail("201");
                   break;
               case 3:
                   appAd.setDetail("325");
                   break;
               case 4:
                   appAd.setDetail("433");
                   break;
               case 5:
                   appAd.setDetail("542");
                   break;
               default:
                   appAd.setDetail("");
                   break;
          }

           // 广告来源
           flag = rand.nextInt(4) + 1;
           appAd.setSource(flag + "");

           // 用户行为
           flag = rand.nextInt(2) + 1;
           appAd.setBehavior(flag + "");

           // 商品类型
           flag = rand.nextInt(10);
           appAd.setNewstype("" + flag);

           // 展示样式
           flag = rand.nextInt(6);
           appAd.setShow_style("" + flag);

           JSONObject jsonObject = (JSONObject) JSON.toJSON(appAd);

           return packEventJson("ad", jsonObject);
      }

       /**
        * 启动日志
        */
       private static AppStart generateStart() {

           AppStart appStart = new AppStart();

           //设备id
           appStart.setMid(s_mid + "");
           s_mid++;

           // 用户id
           appStart.setUid(s_uid + "");
           s_uid++;

           // 程序版本号 5,6等
           appStart.setVc("" + rand.nextInt(20));

           //程序版本名 v1.1.1
           appStart.setVn("1." + rand.nextInt(4) + "." + rand.nextInt(10));

           // 安卓系统版本
           appStart.setOs("8." + rand.nextInt(3) + "." + rand.nextInt(10));

           //设置日志类型
           appStart.setEn("start");

           //   语言 es,en,pt
           int flag = rand.nextInt(3);
           switch (flag) {
               case (0):
                   appStart.setL("es");
                   break;
               case (1):
                   appStart.setL("en");
                   break;
               case (2):
                   appStart.setL("pt");
                   break;
          }

           // 渠道号   从哪个渠道来的
           appStart.setSr(getRandomChar(1));

           // 区域
           flag = rand.nextInt(2);
           switch (flag) {
               case 0:
                   appStart.setAr("BR");
               case 1:
                   appStart.setAr("MX");
          }

           // 手机品牌 ba ,手机型号 md,就取2位数字了
           flag = rand.nextInt(3);
           switch (flag) {
               case 0:
                   appStart.setBa("Sumsung");
                   appStart.setMd("sumsung-" + rand.nextInt(20));
                   break;
               case 1:
                   appStart.setBa("Huawei");
                   appStart.setMd("Huawei-" + rand.nextInt(20));
                   break;
               case 2:
                   appStart.setBa("HTC");
                   appStart.setMd("HTC-" + rand.nextInt(20));
                   break;
          }

           // 嵌入sdk的版本
           appStart.setSv("V2." + rand.nextInt(10) + "." + rand.nextInt(10));
           // gmail
           appStart.setG(getRandomCharAndNumr(8) + "@gmail.com");

           // 屏幕宽高 hw
           flag = rand.nextInt(4);
           switch (flag) {
               case 0:
                   appStart.setHw("640*960");
                   break;
               case 1:
                   appStart.setHw("640*1136");
                   break;
               case 2:
                   appStart.setHw("750*1134");
                   break;
               case 3:
                   appStart.setHw("1080*1920");
                   break;
          }

           // 客户端产生日志时间
           long millis = System.currentTimeMillis();
           appStart.setT("" + (millis - rand.nextInt(99999999)));

           // 手机网络模式 3G,4G,WIFI
           flag = rand.nextInt(3);
           switch (flag) {
               case 0:
                   appStart.setNw("3G");
                   break;
               case 1:
                   appStart.setNw("4G");
                   break;
               case 2:
                   appStart.setNw("WIFI");
                   break;
          }

           // 拉丁美洲 西经34°46′至西经117°09;北纬32°42′至南纬53°54′
           // 经度
           appStart.setLn((-34 - rand.nextInt(83) - rand.nextInt(60) / 10.0) + "");
           // 纬度
           appStart.setLa((32 - rand.nextInt(85) - rand.nextInt(60) / 10.0) + "");

           // 入口
           flag = rand.nextInt(5) + 1;
           appStart.setEntry(flag + "");

           // 开屏广告类型
           flag = rand.nextInt(2) + 1;
           appStart.setOpen_ad_type(flag + "");

           // 状态
           flag = rand.nextInt(10) > 8 ? 2 : 1;
           appStart.setAction(flag + "");

           // 加载时长
           appStart.setLoading_time(rand.nextInt(20) + "");

           // 失败码
           flag = rand.nextInt(10);
           switch (flag) {
               case 1:
                   appStart.setDetail("102");
                   break;
               case 2:
                   appStart.setDetail("201");
                   break;
               case 3:
                   appStart.setDetail("325");
                   break;
               case 4:
                   appStart.setDetail("433");
                   break;
               case 5:
                   appStart.setDetail("542");
                   break;
               default:
                   appStart.setDetail("");
                   break;
          }

           // 扩展字段
           appStart.setExtend1("");

           return appStart;
      }
       /**
        * 消息通知
        */
       private static JSONObject generateNotification() {

           AppNotification appNotification = new AppNotification();

           int flag = rand.nextInt(4) + 1;

           // 动作
           appNotification.setAction(flag + "");

           // 通知id
           flag = rand.nextInt(4) + 1;
           appNotification.setType(flag + "");

           // 客户端弹时间
           appNotification.setAp_time((System.currentTimeMillis() - rand.nextInt(99999999)) + "");

           // 备用字段
           appNotification.setContent("");

           JSONObject jsonObject = (JSONObject) JSON.toJSON(appNotification);

           return packEventJson("notification", jsonObject);
      }

       /**
        * 前台活跃
        */
       private static JSONObject generatbeforeground() {

           AppActive_foreground appActive_foreground = new AppActive_foreground();

           // 推送消息的id
           int flag = rand.nextInt(2);
           switch (flag) {
               case 1:
                   appActive_foreground.setAccess(flag + "");
                   break;
               default:
                   appActive_foreground.setAccess("");
                   break;
          }

           // 1.push 2.icon 3.其他
           flag = rand.nextInt(3) + 1;
           appActive_foreground.setPush_id(flag + "");

           JSONObject jsonObject = (JSONObject) JSON.toJSON(appActive_foreground);

           return packEventJson("active_foreground", jsonObject);
      }

       /**
        * 后台活跃
        */
       private static JSONObject generateBackground() {

           AppActive_background appActive_background = new AppActive_background();

           // 启动源
           int flag = rand.nextInt(3) + 1;
           appActive_background.setActive_source(flag + "");

           JSONObject jsonObject = (JSONObject) JSON.toJSON(appActive_background);

           return packEventJson("active_background", jsonObject);
      }

       /**
        * 错误日志数据
        */
       private static JSONObject generateError() {

           AppErrorLog appErrorLog = new AppErrorLog();

           String[] errorBriefs = {"at cn.lift.dfdf.web.AbstractBaseController.validInbound(AbstractBaseController.java:72)", "at cn.lift.appIn.control.CommandUtil.getInfo(CommandUtil.java:67)"};        //错误摘要
           String[] errorDetails = {"java.lang.NullPointerException\n   " + "at cn.lift.appIn.web.AbstractBaseController.validInbound(AbstractBaseController.java:72)\n " + "at cn.lift.dfdf.web.AbstractBaseController.validInbound", "at cn.lift.dfdfdf.control.CommandUtil.getInfo(CommandUtil.java:67)\n " + "at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" + " at java.lang.reflect.Method.invoke(Method.java:606)\n"};        //错误详情

           //错误摘要
           appErrorLog.setErrorBrief(errorBriefs[rand.nextInt(errorBriefs.length)]);
           //错误详情
           appErrorLog.setErrorDetail(errorDetails[rand.nextInt(errorDetails.length)]);

           JSONObject jsonObject = (JSONObject) JSON.toJSON(appErrorLog);

           return packEventJson("error", jsonObject);
      }

       /**
        * 为各个事件类型的公共字段(时间、事件类型、Json数据)拼接
        */
       private static JSONObject packEventJson(String eventName, JSONObject jsonObject) {

           JSONObject eventJson = new JSONObject();

           eventJson.put("ett", (System.currentTimeMillis() - rand.nextInt(99999999)) + "");
           eventJson.put("en", eventName);
           eventJson.put("kv", jsonObject);

           return eventJson;
      }

       /**
        * 获取随机字母组合
        *
        * @param length 字符串长度
        */
       private static String getRandomChar(Integer length) {

           StringBuilder str = new StringBuilder();
           Random random = new Random();

           for (int i = 0; i < length; i++) {
               // 字符串
               str.append((char) (65 + random.nextInt(26)));// 取得大写字母
          }

           return str.toString();
      }

       /**
        * 获取随机字母数字组合
        *
        * @param length 字符串长度
        */
       private static String getRandomCharAndNumr(Integer length) {

           StringBuilder str = new StringBuilder();
           Random random = new Random();

           for (int i = 0; i < length; i++) {

               boolean b = random.nextBoolean();

               if (b) { // 字符串
                   // int choice = random.nextBoolean() ? 65 : 97; 取得65大写字母还是97小写字母
                   str.append((char) (65 + random.nextInt(26)));// 取得大写字母
              } else { // 数字
                   str.append(String.valueOf(random.nextInt(10)));
              }
          }

           return str.toString();
      }

       /**
        * 收藏
        */
       private static JSONObject generateFavorites() {

           AppFavorites favorites = new AppFavorites();

           favorites.setCourse_id(rand.nextInt(10));
           favorites.setUserid(rand.nextInt(10));
           favorites.setAdd_time((System.currentTimeMillis() - rand.nextInt(99999999)) + "");

           JSONObject jsonObject = (JSONObject) JSON.toJSON(favorites);

           return packEventJson("favorites", jsonObject);
      }

       /**
        * 点赞
        */
       private static JSONObject generatePraise() {

           AppPraise praise = new AppPraise();

           praise.setId(rand.nextInt(10));
           praise.setUserid(rand.nextInt(10));
           praise.setTarget_id(rand.nextInt(10));
           praise.setType(rand.nextInt(4) + 1);
           praise.setAdd_time((System.currentTimeMillis() - rand.nextInt(99999999)) + "");

           JSONObject jsonObject = (JSONObject) JSON.toJSON(praise);

           return packEventJson("praise", jsonObject);
      }

       /**
        * 评论
        */
       private static JSONObject generateComment() {

           AppComment comment = new AppComment();

           comment.setComment_id(rand.nextInt(10));
           comment.setUserid(rand.nextInt(10));
           comment.setP_comment_id(rand.nextInt(5));

           comment.setContent(getCONTENT());
           comment.setAddtime((System.currentTimeMillis() - rand.nextInt(99999999)) + "");

           comment.setOther_id(rand.nextInt(10));
           comment.setPraise_count(rand.nextInt(1000));
           comment.setReply_count(rand.nextInt(200));

           JSONObject jsonObject = (JSONObject) JSON.toJSON(comment);

           return packEventJson("comment", jsonObject);
      }

       /**
        * 生成单个汉字
        */
       private static char getRandomChar() {

           String str = "";
           int hightPos; //
           int lowPos;

           Random random = new Random();

           //随机生成汉字的两个字节
           hightPos = (176 + Math.abs(random.nextInt(39)));
           lowPos = (161 + Math.abs(random.nextInt(93)));

           byte[] b = new byte[2];
           b[0] = (Integer.valueOf(hightPos)).byteValue();
           b[1] = (Integer.valueOf(lowPos)).byteValue();

           try {
               str = new String(b, "GBK");
          } catch (UnsupportedEncodingException e) {
               e.printStackTrace();
               System.out.println("错误");
          }

           return str.charAt(0);
      }

       /**
        * 拼接成多个汉字
        */
       private static String getCONTENT() {

           StringBuilder str = new StringBuilder();

           for (int i = 0; i < rand.nextInt(100); i++) {
               str.append(getRandomChar());
          }

           return str.toString();
      }
    }

    3.4.15 配置日志打印Logback

    Logback主要用于在磁盘和控制台打印日志。

    Logback具体使用: 1)在resources文件夹下创建logback.xml文件。 2)在logback.xml文件中填写如下配置

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="false">
      <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
      <property name="LOG_HOME" value="/tmp/logs/" />

      <!-- 控制台输出 -->
      <appender name="STDOUT"
         class="ch.qos.logback.core.ConsoleAppender">
         <encoder
            class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
         </encoder>
      </appender>
     
      <!-- 按照每天生成日志文件。存储事件日志 -->
      <appender name="FILE"
         class="ch.qos.logback.core.rolling.RollingFileAppender">
         <!-- <File>${LOG_HOME}/app.log</File>设置日志不超过${log.max.size}时的保存路径,注意,如果是web项目会保存到Tomcat的bin目录 下 -->  
         <rollingPolicy
            class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/app-%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
         </rollingPolicy>
         <encoder
            class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%msg%n</pattern>
         </encoder>
         <!--日志文件最大的大小 -->
         <triggeringPolicy
            class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
         </triggeringPolicy>
      </appender>

       <!--异步打印日志-->
       <appender name ="ASYNC_FILE" class= "ch.qos.logback.classic.AsyncAppender">
           <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
           <discardingThreshold >0</discardingThreshold>
           <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
           <queueSize>512</queueSize>
           <!-- 添加附加的appender,最多只能添加一个 -->
           <appender-ref ref = "FILE"/>
       </appender>

       <!-- 日志输出级别 -->
      <root level="INFO">
         <appender-ref ref="STDOUT" />
         <appender-ref ref="ASYNC_FILE" />
         <appender-ref ref="error" />
      </root>
    </configuration>

    3.4.16 打包

    1)采用Maven对程序打包

    img

    2)采用带依赖的jar包。包含了程序运行需要的所有依赖。

    img

    3)后续日志生成过程,在安装完Hadoop和Zookeeper之后执行。

  • 相关阅读:
    对于.net config文件中加密使用
    删除windows不想要的服务
    ora12514: tns: 监听程序当前无法识别连接描述符中请求的服务
    读《.NET设计规范》笔记
    创建服务总结
    加载XML文档
    XMLHttpRequest对象的使用
    ReportView控件的使用
    C#代码的编译过程
    Qt vs设置可执行程序图标
  • 原文地址:https://www.cnblogs.com/huanghanyu/p/14272572.html
Copyright © 2020-2023  润新知