236. 二叉树的*近公共祖先(JS实现)

236. 二叉树的*近公共祖先(JS实现)
\
1 题目
给定一个二叉树, 找到该树中两个指定节点的*近公共祖先。
百度百科中*近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,*近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的*近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的*近公共祖先是节点 5。因为根据定义*近公共祖先节点可以为节点本身。
链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree
2 思路
这道题用递归的方法,每个节点返回一个结果对象{p: true, q: false}用来标识该节点的孩子节点中,是否有两个指定节点。当p和q都为true时,该节点即为公共祖先节点
3代码
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
  let findBoth = 0;
  let father;
  function d(node, p, q) {
    let res = {}
    if (!node) return res;
    if (node.val === p.val) {
      res.p = true;
      findBoth++;
    }
    if (node.val === q.val) {
      res.q = true;
      findBoth++;
    }
    let resLeft = {};
    let resRight = {};
    if (findBoth < 2) {
      resLeft = d(node.left, p, q);
      resRight = d(node.right, p, q);
    }
    if ((res.p || resLeft.p || resRight.p) && (res.q || resLeft.q || resRight.q) && !father) father = node;
    return Object.assign({}, res, resLeft, resRight);
  }
  d(root, p, q);
  return father;
};

237. 删除链表中的节点(JS实现)

237. 删除链表中的节点(JS实现)
1 题目
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。
现有一个链表 – head = [4,5,1,9],它可以表示为:
示例 1:
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
链接:https://leetcode-cn.com/problems/delete-node-in-a-linked-list
2 思路
这道题思路很巧妙,我们不删除当前节点,而是删除当前节点后面的节点,并将后面节点的值赋给当前节点,*后看起来就像删除当前节点一样
3代码
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} node
 * @return {void} Do not return anything, modify node in-place instead.
 */
var deleteNode = function(node) {
    let temp = node.next;
    node.val = temp.val;
    node.next = temp.next;
    temp.next = null;
};

微服务如何拆分,能解决哪些问题?

微服务在*近几年大受欢迎,很多公司的研发人员都在考虑微服务架构,同时,随着 Docker 容器技术和自动化运维等相关技术发展,微服务变得更容易管理,这给了微服务架构良好的发展机会。

在做微服务的路上,拆分服务是个很热的话题。我们应该按照什么原则将现有的业务进行拆分?是否拆分得越细就越好?接下来一起谈谈服务拆分的策略和坚持的原则。

%title插图%num

拆分目的是什么?

在介绍如何拆分之前,我们需要了解下拆分的目的是什么,这样才不会在后续的拆分过程中忘了*初的目的。

拆分的本质是为了将复杂的问题简单化,那么我们在单体架构阶段遇到了哪些复杂性问题呢?首先来回想下当初为什么选用了单体架构,在电商项目刚启动的时候,我们只希望能尽快地将项目搭建起来,方便将产品更早的投放市场进行快速验证。在开发初期,这种架构确实给开发和运维带来了很大的便捷,主要体现在:

  • 开发简单直接,代码和项目集中式管理。
  • 排查问题时只需要排查这个应用就可以了,更有针对性。
  • 只需要维护一个工程,节省维护系统运行的人力成本。

但是随着功能越来越多,开发团队的规模越来越大,单体架构的缺陷慢慢体现出来,主要有以下几个方面:

在技术层面,数据库的连接数成为应用服务器扩容的瓶颈,因为连接 MySQL 的客户端数量是有限制的。

除此之外,单体架构增加了研发的成本抑制了研发效率的提升。比如公司的垂直电商系统团队会被按业务线拆分为不同的组。当如此多的小团队共同维护一套代码和一个系统时,在配合的过程中就会出现问题。不同的团队之间沟通少,假如一个团队需要一个发送短信的功能,那么有的研发同学会认为*快的方式不是询问其他团队是否有现成的,而是自己写一套,但是这种想法是不合适的,会造成功能服务的重复开发。由于代码部署在一起,每个人都向同一个代码库提交代码,代码冲突无法避免;同时功能之间耦合严重,可能你只是更改了很小的逻辑却导致其它功能不可用,从而在测试时需要对整体功能回归,延长了交付时间。模块之间互相依赖,一个小团队中的成员犯了一个错误,就可能会影响到其它团队维护的服务,对于整体系统稳定性影响很大。

*后,单体架构对于系统的运维也会有很大的影响。想象一下,在项目初期你的代码可能只有几千行,构建一次只需要一分钟,那么你可以很敏捷灵活地频繁上线变更修复问题。但是当你的系统扩充到几十万行甚至上百万行代码的时候,一次构建的过程包括编译、单元测试、打包和上传到正式环境,花费的时间可能达到十几分钟,并且任何小的修改,都需要构建整个项目,上线变更的过程非常不灵活。

而这些问题都可以通过微服务化拆分来解决。

为了方便你更好的理解这块,在此附上一份表格(内容来源:《持续演进的 Cloud Native:云原生架构下微服务*佳》一书),可以更直观地帮助你认识拆分的目的。

%title插图%num

%title插图%num

拆分时机应该如何决策?

产品初期,应该以单体架构优先。因为面对一个新的领域,对业务的理解很难在开始阶段就比较清晰,往往是经过一段时间之后,才能逐步稳定,如果拆分过早,导致边界拆分不合理或者拆的过细,反而会影响生产力。很多时候,从一个已有的单体架构中逐步划分服务,要比一开始就构建微服务简单得多。同时公司的产品并没有被市场验证过,有可能会失败,所以这个投入的风险也会比较高。

另外,在资源受限的情况下,采用微服务架构很多优势无法体现,性能上的劣势反而会比较明显。如下图所示。当业务复杂度达到一定程度后,微服务架构消耗的成本才会体现优势,并不是所有的场景都适合采用微服务架构,服务的划分应逐步进行,持续演进。产品初期,业务复杂度不高的时候,应该尽量采用单体架构。

%title插图%num

随着公司的商业模式逐渐得到验证,且产品获得了市场的认可,为了能加快产品的迭代效率快速占领市场,公司开始引进更多的开发同学,这时系统的复杂度会变得越来越高,就出现单体应用和团队规模之间出现矛盾,研发效率不升反降。上图中的交叉点表明,业务已经达到了一定的复杂度,单体应用已经无法满足业务增长的需求,研发效率开始下降,而这时就是需要考虑进行服务拆分的时机点。这个点需要架构师去权衡。笔者所在的公司,是当团队规模达到百人的时候,才考虑进行服务化。

当我们清楚了什么时候进行拆分,就可以直接落地了吗?不是的,微服务拆分的落地还要提前准备好配套的基础设施,如服务描述、注册中心、服务框架、服务监控、服务追踪、服务治理等几大基本组件,以上每个组件缺一不可,每个组件展开又包括很多技术门槛,比如,容器技术、持续部署、DevOps 等相关概念,以及人才的储备和观念的变化。微服务不仅仅是技术的升级,更是开发方式、组织架构、开发观念的转变。

至此,何时进行微服务的拆分,整体总结如下:

  1. 业务规模:业务模式得到市场的验证,需要进一步加快脚步快速占领市场,这时业务的规模变得越来越大,按产品生命周期来划分(导入期、成长期、成熟期、衰退期)这时一般在成长期阶段。如果是导入期,尽量采用单体架构。
  2. 团队规模:一般是团队达到百人的时候。
  1. 技术储备:领域驱动设计、注册中心、配置中心、日志系统、持续交付、监控系统、分布式定时任务、CAP 理论、分布式调用链、API 网关等等。
  2. 人才储备:精通微服务落地经验的架构师及相应开发同学。
  3. 研发效率:研发效率大幅下降,具体问题参加上面拆分目的里提到的。

%title插图%num

拆分时应该坚守哪些指导原则?

1. 单一服务内部功能高内聚低耦合

也就是说每个服务只完成自己职责内的任务,对于不是自己职责的功能交给其它服务来完成。

2. 闭包原则(CCP)

微服务的闭包原则就是当我们需要改变一个微服务的时候,所有依赖都在这个微服务的组件内,不需要修改其他微服务。

3. 服务自治、接口隔离原则

尽量消除对其他服务的强依赖,这样可以降低沟通成本,提升服务稳定性。服务通过标准的接口隔离,隐藏内部实现细节。这使得服务可以独立开发、测试、部署、运行,以服务为单位持续交付。

4. 持续演进原则

在服务拆分的初期,你其实很难确定服务究竟要拆成什么样。从微服务这几个字来看,服务的粒度貌似应该足够小,但是服务多了也会带来问题,服务数量快速增长会带来架构复杂度急剧升高,开发、测试、运维等环节很难快速适应,会导致故障率大幅增加,可用性降低,非必要情况,应逐步划分,持续演进,避免服务数量的爆炸性增长,这等同于灰度发布的效果,先拿出几个不太重要的功能拆分出一个服务做试验,如果出现故障,则可以减少故障的影响范围。

5. 拆分的过程尽量避免影响产品的日常功能迭代

也就是说要一边做产品功能迭代,一边完成服务化拆分。比如优先剥离比较独立的边界服务(如短信服务等),从非核心的服务出发减少拆分对现有业务的影响,也给团队一个练习、试错的机会。同时当两个服务存在依赖关系时优先拆分被依赖的服务。

6. 服务接口的定义要具备可扩展性

服务拆分之后,由于服务是以独立进程的方式部署,所以服务之间通信就不再是进程内部的方法调用而是跨进程的网络通信了。在这种通信模型下服务接口的定义要具备可扩展性,否则在服务变更时会造成意想不到的错误。比如微服务的接口因为升级把之前的三个参数改成了四个,上线后导致调用方大量报错,推荐做法服务接口的参数类型*好是封装类,这样如果增加参数就不必变更接口的签名,而只需要在类中添加字段就可以了

7. 避免环形依赖与双向依赖

尽量不要有服务之间的环形依赖或双向依赖,原因是存在这种情况说明我们的功能边界没有化分清楚或者有通用的功能没有下沉下来。

%title插图%num

8. 阶段性合并

随着你对业务领域理解的逐渐深入或者业务本身逻辑发生了比较大的变化,亦或者之前的拆分没有考虑的很清楚,导致拆分后的服务边界变得越来越混乱,这时就要重新梳理领域边界,不断纠正拆分的合理性。

%title插图%num

拆分的粒度是不是越细越好?

目前很多传统的单体应用再向微服务架构进行升级改造,如果拆分粒度太细会增加运维复杂度,粒度过大又起不到效果,那么改造过程中如何平衡拆分粒度呢?

%title插图%num

1、弓箭原理

平衡拆分粒度可以从两方面进行权衡,一是业务发展的复杂度,二是团队规模的人数。如上图,它就像弓箭一样,只有当业务复杂度和团队人数足够大的时候,射出的服务拆分粒度这把剑才会飞的更远,发挥出*大的威力。

比如说电商的商品服务,当我们把商品从大的单体里拆分出来的时候,就商品服务本身来讲,逻辑并没有足够复杂到 2~3 个人没法维护的地步,这时我们没有必要继续将商品服务拆的更细,但是随着业务的发展,商品的业务逻辑变的越来越复杂,可能同时服务公司的多个平台,此时你会发现商品服务本身面临的问题跟单体架构阶段面临的问题基本一样,这个阶段就需要我们将商品拆成更细粒度的服务,比如,库存服务、价格服务、类目服务、商品基础信息服务等等。

虽然业务复杂度已经满足了,如果公司此时没有足够的人力(招聘不及时或员工异动比较多),服务*好也不要拆分,拆分会因为人力的不足导致更多的问题,如研发效率大幅下降(一个开发负责与其不匹配数量的服务)。这里引申另外一个问题,一个微服务究竟需要几个开发维护是比较理性的?我引用下李云华老师在”从零开始学架构“ 中的一段经典论述,可以解决此问题。

2、三个火枪手原则

为什么说是三个人分配一个服务是比较理性的?而不是 4 个,也不是 2 个呢?

首先,从系统规模来讲,3 个人负责开发一个系统,系统的复杂度刚好达到每个人都能全面理解整个系统,又能够进行分工的粒度;如果是 2 个人开发一个系统,系统的复杂度不够,开发人员可能觉得无法体现自己的技术实力;如果是 4 个甚至更多人开发一个系统,系统复杂度又会无法让开发人员对系统的细节都了解很深。

其次,从团队管理来说,3 个人可以形成一个稳定的备份,即使 1 个人休假或者调配到其他系统,剩余 2 个人还可以支撑;如果是 2 个人,抽调 1 个后剩余的 1 个人压力很大;如果是 1 个人,这就是单点了,团队没有备份,某些情况下是很危险的,假如这个人休假了,系统出问题了怎么办?

*后,从技术提升的角度来讲,3 个人的技术小组既能够形成有效的讨论,又能够快速达成一致意见;如果是 2 个人,可能会出现互相坚持自己的意见,或者 2 个人经验都不足导致设计缺陷;如果是 1 个人,由于没有人跟他进行技术讨论,很可能陷入思维盲区导致重大问题;如果是 4 个人或者更多,可能有的参与的人员并没有认真参与,只是完成任务而已。

“三个火枪手”的原则主要应用于微服务设计和开发阶段,如果微服务经过一段时间发展后已经比较稳定,处于维护期了,无须太多的开发,那么平均 1 个人维护 1 个微服务甚至几个微服务都可以。当然考虑到人员备份问题,每个微服务*好都安排 2 个人维护,每个人都可以维护多个微服务。

综上所诉,拆分粒度不是越细越好,粒度需要符合弓箭原理及三个火枪手原则。

%title插图%num

拆分策略有哪些?

拆分策略可以按功能和非功能维度进行考虑,功能维度主要是划分清楚业务的边界,非功能维度主要考虑六点包括扩展性、复用性、高性能、高可用、安全性、异构性。接下来详细介绍下。

1、功能维度

功能维度主要是划分清楚业务边界,采用的主要设计方法可以利用 DDD(关于 DDD 的理论知识可以参考网上其它资料),DDD 的战略设计会建立领域模型,可以通过领域模型指导微服务的拆分,主要分四步进行:

  • *步,找出领域实体和值对象等领域对象。
  • 第二步,找出聚合根,根据实体、值对象与聚合根的依赖关系,建立聚合。
  • 第三步,根据业务及语义边界等因素,定义限界上下文。
  • 第四步,每一个限界上下文可以拆分为一个对应的微服务,但也要考虑一些非功能因素。

以电商的场景为例,交易链路划分的限界上下文如下图左半部分,根据一个限界上下文可以设计一个微服务,拆解出来的微服务如下图右侧部分。

%title插图%num

2、非功能维度

当我们按照功能维度进行拆分后,并不是就万事大吉了,大部分场景下,我们还需要加入其它维度进一步拆分,才能*终解决单体架构带来的问题。

  • 扩展性:区分系统中变与不变的部分,不变的部分一般是成熟的、通用的服务功能,变的部分一般是改动比较多、满足业务迭代扩展性需要的功能,我们可以将不变的部分拆分出来,作为共用的服务,将变的部分独立出来满足个性化扩展需要。同时根据二八原则,系统中经常变动的部分大约只占 20%,而剩下的 80% 基本不变或*少变化,这样的拆分也解决了发布频率过多而影响成熟服务稳定性的问题。
  • 复用性:不同的业务里或服务里经常会出现重复的功能,比如每个服务都有鉴权、限流、安全及日志监控等功能,可以将这些通过的功能拆分出来形成独立的服务,也就是微服务里面的 API 网关。在如,对于滴滴业务,有快车和顺风车业务,其中都涉及到了订单支付的功能,那么就可以将订单支付独立出来,作为通用服务服务好上层业务。如下图:

%title插图%num

  • 高性能:将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其它服务。常见的拆分方式和具体的性能瓶颈有关,例如电商的抢购,性能压力*大的是入口的排队功能,可以将排队功能独立为一个服务。同时,我们也可以基于读写分离来拆分,比如电商的商品信息,在 App 端主要是商详有大量的读取操作,但是写入端商家中心访问量确很少。因此可以对流量较大或较为核心的服务做读写分离,拆分为两个服务发布,一个负责读,另外一个负责写。还有数据一致性是另一个基于性能维度拆分需要考虑的点,对于强一致的数据,属于强耦合,尽量放在同一个服务中(但是有时会因为各种原因需要进行拆分,那就需要有响应的机制进行保证),弱一致性通常可以拆分为不同的服务。

%title插图%num

  • 高可用:将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。具体拆分的时候,核心服务可以是一个也可以是多个,只要*终的服务数量满足“三个火枪手”的原则就可以。比如针对商家服务,可以拆分一个核心服务一个非核心服务,核心服务供交易服务访问,非核心提供给商家中心访问。
  • 安全性:不同的服务可能对信息安全有不同的要求,因此把需要高度安全的服务拆分出来,进行区别部署,比如设置特定的 DMZ 区域对服务进行分区部署,可以更有针对性地满足信息安全的要求,也可以降低对防火墙等安全设备吞吐量、并发性等方面的要求,降低成本,提高效率。
  • 异构性:对于对开发语言种类有要求的业务场景,可以用不同的语言将其功能独立出来实现一个独立服务。

以上几种拆分方式不是多选一,而是可以根据实际情况自由排列组合。同时拆分不仅仅是架构上的调整,也意味着要在组织结构上做出相应的适应性优化,以确保拆分后的服务由相对独立的团队负责维护。

%title插图%num服务都拆了为什么还要合并?

古希腊哲学家赫拉克利特曾经说过:“人不能两次踏进同一条河流。”随着时间的流逝,任何事物的状态都会发生变化。线上系统同样如此,即使一个系统在不同时刻的状况也*不会一模一样。现在拆分出来的服务粒度也许合适,但谁能保证这个粒度能够一直正确呢。

服务都拆了为什么还要合,就是要不断适应新的业务发展阶段,笔者这里做个类比看大家是否清晰,拆相当于我们开发代码,合相当于重构代码,为什么要重构呢,相信你肯定知道。微服务的合也是一样的道理,随着我们对应用程序领域的了解越来越深,它们可能会随着时间的推移而变化。例如,你可能会发现由于过多的进程间通信而导致特定的分解效率低下,导致你必须把一些服务组合在一起。

同时因为人员和服务数量的不匹配,导致的维护成本增加,也是导致服务合并的一个重要原因。例如,今年疫情的影响导致很多企业开始大量裁员,人员流失但是服务的数量确没有变,造成服务数量和人员的不平衡,一个开发同学同时要维护至少 5 个服务的开发,效率大幅下降。

那么如果微服务数量过多和资源不匹配,则可以考虑合并多个微服务到服务包,部署到一台服务器,这样可以节省服务运行时的基础资源消耗也降低了维护成本。需要注意的是,虽然服务包是运行在一个进程中,但是服务包内的服务依然要满足微服务定义,以便在未来某一天要重新拆开的时候可以很快就分离。服务合并到服务包示意图如下:

%title插图%num

%title插图%num

拆分过程中要注意的风险

1、不打无准备之仗

开发团队是否具备足够的经验,能否驾驭微服务的技术栈,可能是*个需要考虑的点。这里并不是要求团队必须具备完善的经验才能启动服务拆分,如果团队中有这方面的专家固然是*好的。如果没有,那可能就需要事先进行充分的技术论证和预演,至少不打无准备之仗。避免哪个简单就先拆哪个,哪个新业务要上了,先起一个服务再说。否则可能在一些分布式常见的问题上会踩坑,比如服务器资源不够、运维困难、服务之间调用混乱、调用重试、超时机制、分布式事务等等。

2、不断纠正

我们需要承认我们的认知是有限的,只能基于目前的业务状态和有限的对未来的预测来制定出一个相对合适的拆分方案,而不是所谓的*优方案,任何方案都只能保证在当下提供了相对合适的粒度和划分原则,要时刻做好在未来的末一个时刻会变得不和时宜、需要再次调整的准备。因此随着业务的演进,需要我们重新审视服务的划分是否合理,如服务拆的太细,导致人员效率反而下降,故障的概率也大大增加,则需要重新划分好领域边界。

3、要做行动派,而不是理论派

在具体怎么拆分上,也不要太纠结于是否合适,不动手怎么知道合不合适呢?如果拆了之后发现真的不合适,在重新调整就好了。你可能会说,重新调整成本比较高。但实际上这个问题的本质是有没有针对服务化架构搭建起一套完成的能力体系,比如服务治理平台、数据迁移工具、数据双写等等,如果有的话,重新调整的成本是不会太高的。

MySQL 避坑指南之隐式数据类型转换

今天我们来聊聊 MySQL 中存在的隐式数据类型转换以及可能带来的问题。

当两个不同类型的数据进行运算时,为了使得它们能够兼容,MySQL 可能会执行隐式的数据类型转换。例如,MySQL 在需要时会自动将字符串转换为数字,反之亦然。

  1. mysql> SELECT 1+‘1’;
  2. -> 2
  3. mysql> SELECT CONCAT(2,‘ test’);
  4.         -> ‘2 test’

我们也可使用 CAST() 函数将数字显式转换为字符串。CONCAT() 函数中的隐式类型转换是因为它只能接收字符串类型的参数。

  1. mysql> SELECT 38.8, CAST(38.8 AS CHAR);
  2. -> 38.8, ‘38.8’
  3. mysql> SELECT 38.8, CONCAT(38.8);
  4.         -> 38.8‘38.8’

以下是比较运算中的类型转换规则:

如果任意一个参数为 NULL,比较的结果为 NULL,<=> 相等比较运算符除外。NULL <=> NULL 的运算结果为 true,不需要进行类型转换。

如果两个参数都是字符串,执行字符串比较。

如果两个参数都是整数,执行整数比较。

如果不是和数字进行比较,十六进制数值将被看作二进制字符串。

如果一个参数是 TIMESTAMP 或者 DATETIME 字段,另一个参数是常量,该常量将会在比较之前转换为时间戳类型。这一规则是为了更好地支持 ODBC 规范。IN() 运算符中的参数不会执行这一转换。为了保险起见,记得在执行比较运算时使用完整的日期时间、日期或者时间字符串。例如,在使用 BTWEEN 运算符判断日期或者时间数据时,利用 CAST() 函数将数据的类型显示转换成相应的类型。

返回单行结果的子查询不会被当作常量。例如,当一个返回整数的子查询和 DATETIME 数据进行比较时,DATETIME 将会被转换为整数类型,而不会将子查询的结果转换为时间类型。如果想要执行日期时间比较,可以使用 CAST() 函数显式将子查询的结果转换为 DATETIME 类型。

如果一个参数为精确数字类型(decimal),比较的方法取决于另一个参数的类型。如果另一个参数是精确数字或者整数类型,使用精确数字比较;如果另一个参数是浮点数类型,使用浮点数比较。

其他情况下,使用浮点数比较。例如,字符串和精确数字的比较使用浮点数比较方法。

关于时间类型之间的转换规则,可以参考官方文档。

以下示例演示了将字符串转换为数字的比较操作:

  1. mysql> SELECT 1 > ‘6x’;
  2. -> 0
  3. mysql> SELECT 7 > ‘6x’;
  4. -> 1
  5. mysql> SELECT 0 > ‘x6’;
  6. -> 0
  7. mysql> SELECT 0 = ‘x6’;
  8.         -> 1

如果将字符串类型的字段和数字进行比较,MySQL 无法使用该字段上的索引快速查找数据。例如,str_col 是一个索引字段,该索引无法用于以下语句:

SELECT * FROM tbl_name WHERE str_col=1;

问题的原因在于很多不同的字符串都可以转换为数字 1,例如’1’、’ 1’ 或者 ‘1a’。

浮点数和 INTEGER 类型的超大数值之间的比较是近似比较,因为整数在比较之前需要转换为双精度浮点数,双精度浮点数无法精确地表示所有的 64 位整数。例如,整数 253 + 1 无法使用浮点数进行表示,只能近似为 253 或者 253 + 2。

举例来说,以下只有*个比较运算中的两个值相等,但是两个比较运算都返回了 true(1):

  1. mysql> SELECT ‘9223372036854775807’ = 9223372036854775807;
  2. -> 1
  3. mysql> SELECT ‘9223372036854775807’ = 9223372036854775806;
  4.         -> 1

字符串转换为浮点数与整数转换为浮点数的方式可能不同。整数可能使用 CPU 转换为浮点数,而字符串可能使用浮点数乘法进行逐位转换。另外,转换结果可能受到各种因素的影响,例如计算机的架构、编译器版本或者优化级别等。避免这种问题的方法之一就是使用 CAST() 函数,这样数据就不会被隐式转换为浮点数。

  1. mysql> SELECT CAST(‘9223372036854775807’ AS UNSIGNED) = 9223372036854775806;
  2.         -> 0

关于浮点数比较的更多信息,可以参考官方文档。

MySQL 服务器提供了一个转换库 dtoa,可以支持字符串或者 DECIMAL 数据和近似数字(FLOAT/DOUBLE)之间的基本转换功能:

跨平台的一致性转换结果,例如,可以消除 Unix 和 Windows 之间的差异。

可以精确表示之前无法提供足够精度的数据,例如接近 IEEE 限制的数据。

以尽可能高的精度将数字转换成字符串格式。dtoa 的精度总是等于或者高于标准 C 代码库函数。

数字或者时间类型到字符串的隐式转换结果的字符集和排序规则取决于 character_set_connection 和 collation_connection 系统变量。(这些变量通常使用 SET NAMES 进行设置。关于连接的字符集的信息,可以参考官方文档。)

这意味着这种转换的结果是一个非二进制的字符串(CHAR、VARCHAR 或者 LONGTEXT),除非连接字符集被设置为 binary。此时,转换结果是一个二进制字符串(BINARY、VARBINARY 或者 LONGBLOB)。

对于整数类型的表达式,前文所述的表达式求值和表达式赋值有所不同。例如以下语句:

CREATE TABLE t SELECT integer_expr;

这种情况下,表 t 的字段类型取决于整数表达式的长度,可能是 INT 或者 BIGINT。如果表达式的*大长度超过了 INT,使用 BIGINT 类型。这就意味着我们可以通过一个足够长的表达式创建 BIGINT 类型的字段:

  1. CREATE TABLE t SELECT 000000000000000000000 AS col;
  2. DESC t;
  3. Field|Type |Null|Key|Default|Extra|
  4. —–+——+—-+—+——-+—–+
  5. col  |bigint|NO  |   |0      |     |

JSON 数据的比较分为两种情况。*层次的比较基于被比较数据的 JSON 类型,如果两个类型不同,比较的结果取决于具有更高优先级的类型;如果两个数据的 JSON 类型相同,使用具体的类型规则进行第二层次的比较。对于 JSON 和非 JSON 数据的比较,先将非 JSON 数据转换为 JSON 类型,然后进行比较。详细信息可以参考官方文档。

阿里云肖力:跳过量变过程的安全质变

我从事网络安全工作将近20年,处理过各类攻击威胁,经历了云下云上安全的建设。

云计算的安全工作从10年前开始,我们摸着石头过河,搭建了阿里云平台的防护体系,帮助各行业用户在云上构建企业安全能力。

云原生的出现进一步加深了我对安全的理解和思考。未来我们要实现的,也许已经不再称之安全防护,而是一套长在云里的原生“免疫系统”。

%title插图%num

云下20年:外挂式安全

2000年到2020年国内诞生了上千家安全公司,提供上百种品类的安全产品,介绍手册里对使用体验从未改变的一句描述是:即插即用。然而,再无缝的外挂也难以避免兼容性问题。其次,实际情况因接口统一性、兼容性等问题,标注即插即用的设备,1个月都部署不进去的情况比比皆是。

停留在大部分人记忆中“熊猫烧香”级别的炫技式攻击,早已过时。去年,全球疫情和新常态远程办公出现,我们观测到了高度复杂的攻击。几个月前发生的SolarWinds APT攻击,国际顶级安全公司沦陷。阿里云成功防御的资源耗尽型DDoS攻击,刷新了历史观测*大规模记录。对企业的损失来说,*新的勒索软件攻击已经动辄赎金要求数亿。

试想一下,企业数字资产安全,在这种情况下会处于什么状态?

云直接改变了这种安全现状。

1月爆发的Incaseformat蠕虫病毒,主要依赖U盘进行传播,云上空间实现天然免疫,默认不受该传播方式影响,所有云上用户无感知度过这个舆论影响很大的安全事件。

云原生容器具备镜像快照功能,遇到勒索软件攻击数据被加密,用户通过这个功能快速恢复,而不需要去交付赎金。

云原生安全发展方向,我从安全技术和理念里两方面,总结为内置、前置两大关键词。

  • 内置——单点的防护能力打碎重组,融入基础设施本身。
  • 前置——在更上游的阶段考虑安全,树立一个信任和一个怀疑。

%title插图%num

原生安全技术:融入基础设施的免疫系统

阿里云自身的安全实践已久,无论基于广义云原生还是狭义云原生概念,面向未来的几个技术趋势已经越来越清晰。

1、安全成公共资源实现按需调用

大部分企业安全资源是非常有限的,却存在一个矛盾点:即需要能支撑峰值流量,大部分时间用量却是打不满的。

比如阿里巴巴自身业务,双11无疑是一个流量峰值,而全年业务由一个峰值+多个波峰+波谷构成,峰值和波谷的区别可能非常大,安全没必要储备了大量“粮草”处于“待命”状态。

安全能力服务化(SaaS化),是一个行业内展望已久的趋势,安全能不能按需调用?

我去年经常讲一个例子,疫情期间钉钉1小时扩容2万台服务器,安全防护实现小时级覆盖。云下场景的同类企业,每台设备都需要上架、调适,串联在链路上做防御阻断,至少需要1个月时间。

云环境中,业务系统上线只需要完成接入动作,安全保护随之而来。

2、基础设施天然具备检测防护能力

安全能力直接内置在基础设施节点中。流量通过某些节点时,比如SLB负载均衡和CDN边缘计算,直接完成安全检测。同一份带宽资源,业务提速做到无感保护。

遍布基础设施的各安全能力节点,面对风险如同开启了 “上帝视角”, 单点威胁实现全网秒级协同,提升了全IT环境的风险反应和处理速度。过去几年,阿里云在一些客户重大事件保障和大型实战演习中,攻防能力一直是榜首的位置。一方面优势来自于自身技术的储备,更多的则来自于基于云的全局威胁发现和联动处置能力。

3、攻击主动修复实现无感防御

十几年前我们做安全,系统不行全靠人肉来补。

有时候一个漏洞出现,几十上百个应用,要挨个手动排查。修复过程中业务不能下线,还要做到用户无感,导致后台的操作缓慢又痛苦。这种被迫降速,又进一步拉长攻击窗口期,提升了业务风险。

今天,阿里云上的漏洞修复,已经变得非常简单。一旦漏洞出现,云自动开启防护罩,保证攻击打不进来,云也将持续进化实现自动修复。

我们把很多可能产生问题的难点,在IT建设的时候就思考和解决掉了,安全人员看到的是相对简单的统一控制台,通过业务逻辑来进行安全策略的配置,把精力聚焦到高价值的事情上。

%title插图%num

原生安全理念:*对信任和持续怀疑

现代商业复杂程度远远高于过去,简单是消解复杂的*佳路径,安全理念需要被化约。

企业员工的位移和身份动态变化的速度,大概是过去的N倍速。数据可能产生于任意终端、任意人员、任意地理位置。数据可能存储在公共云、私有云、边缘计算节点……这其中发生的计算、处理和交换动作更是形成了复杂的交叉网状结构。

安全防护看似无处下手,这也是“免疫系统”的重要性。我们全方位审视安全,抽丝剥茧去看背后的逻辑。

云上数据的生命周期旅程可能发生于IT系统的大脑、心脏、甚至末梢,像血液一样在企业内流转,为各器官的运作服务,信息流代替工作流在推动着业务的发展。如何保证整个系统的安全?

1、云即信任

云原生安全的进化,在不断缩小信任成本,让基础设施本身成为更加高可用、高安全等级的可信计算环境。

芯片级硬件可信

芯片级安全,是当前技术领域内*高等级的安全。硬件的不可篡改性,决定了其成为*高等级安全的基础。

阿里云在去年10月,业内首发基于SGX2.0和TPM的可信虚拟化实例,*早完成了芯片级硬件安全的落地。*新推出的第七代ECS实例,全量搭载安全芯片作为硬件可信根,实现服务器的可信启动,确保零篡改。这意味着真正意义上*次实现了能够支持大数据运算的安全可信环境。

用户不需要再关心硬件层的基础上,任何篡改异常可被*时间发现,从而更专注于安全开发,进一步减少代码量。

数据默认透明加密

加密是*原始的数据保护方式,这并不是一个安全新概念。

而云上的数据加密是一个更天然的过程,原生数据自“出生”默认加密。云上产生的数据,实现自动加密,数据迁移上云默认落盘加密,关键业务敏感数据实现字节级加密。

云基础设施还提供公钥密码应用系统,在数据加密的基础上再加一把锁。

密码系统可以自动或自定义改变密码,这个听起来很普通,但实际需要基础设施层算法精巧设计的功能叫“密钥轮转”。公共云有一个主密钥,默认每天轮转一次,用户自有密钥可从天到年为单位自定义设置轮转周期,让被破解成为不可能。

2、对动态因素持续怀疑的零信任

数据总是由人创造的。企业各环节线上化,每个人都可能是数据的生产者。

无论是企业访问OA系统、审批系统、公司邮件、视频会议等传统需求,还是远程开发、测试、运维、客服等复杂场景,从身份认证、网络准入、动态权限管理等方式入手,到通过网络能力实现安全的内网准入,实现打造持续怀疑、动态监测和认证的安全云环境。

当云作为IT基础设施,算力成为像水、电、煤一样的公共资源,这其中安全意味着什么不言而喻。我们也希望打造全世界*安全的云,在越来越复杂中,提供越来越简单的选择。

sh协议命令行远程连接linux服务器并上传下载文件

一、环境
debian 9

二、步骤

$ apt install ssh #安装ssh,windows平台无需安装cmd直接用即可
1
远程连接:

$ ssh root@192.168.1.1 # @后面跟上自己服务器IP,回车输入密码即可连接上
1
上传文件:

scp -r ./本机路径 root@ip:/目标路径
1
例:

scp -r ./* root@192.168.1.1:/root/www
1
下载文件:
scp root@ip:/目标路径 /本机路径

例:

scp root@192.168.1.1:/root/www /d/下载
1
Ps: 以上内容仅供参考,不足之处敬请指正,转载请注明出处。

自动化部署脚本(windows上传到linux)

挤了点时间出来学maven+springMVC,然后demo部署到服务器上。maven打包后部署到服务器上,重启tomcat。这个路线有点长,所以写个辅助脚本减少时间成本。现在记录一下。

 

先介绍两个工具,PUTTY和WINSCP。

 

PUTTY是一个不常用的,可以在windows平台通过ssh连接到linux的工具。好处是这个工具特别小,免安装。可是功能不强(语法高亮这点就够了)。所以并不常用。(常用的是secureCRT &xshell)然而,putty在windows平台可以通过命令行的形式执行。一般putty会作为脚本工具使用的。

 

WINSCP是一个windows平台下,通过ssh协议复制东西到linux服务器的工具。也支持命令行的形式。

%title插图%num

脚本逻辑是这样的:

  • 打包
  • 打包结果上传到服务器
  • 服务器处理(部署、重启服务器)

 

1、打包。
例如maven打包,而且打包后不自动退出。(文件后缀是bat)
call mvn clean package -U -Dmaven.test.skip=true

 

2、上传到服务器。

用winscp,执行脚本。参考如下:
--打开控制台
WinSCP.exe /script="E:\eclipse_mars_workspace\singleBbs\autoUpdate\updateLoadScript.sh"

uploadLoadScript的内容参考:

--连接服务器,上传文件
open sftp://account:password@ip:port
put E:\eclipse_mars_workspace\singleBbs\target\singleBbs.war
exit

put一个文件到用户下的根目录

3、服务器上处理(替换、重启)

在bat脚本中,通过putty登录,并且执行服务器脚本

PUTTY.EXE -ssh -pw password account@ip -m serverScript.sh

 

关闭tomcat,部署,启动tomcat。

/root/tomcat7/tomcat7/bin/catalina.sh stop
sleep 1
rm /root/tomcat7/tomcat7/webapps/singleBbs.war
rm -r /root/tomcat7/tomcat7/webapps/singleBbs
cp ~/singleBbs.war /root/tomcat7/tomcat7/webapps/singleBbs.war
/root/tomcat7/tomcat7/bin/catalina.sh start
sleep 1
rm ~/singleBbs.war
exit

 

后记

  一开始考我是虑用git的钩子实现。大概逻辑是: 提交代码到git,git打标签的时候触发钩子从而打包、部署。
  这样子做坏处是服务器要安装gitlab(钩子嘛),maven。好处是有版本策略可以跟踪代码部署的情况。其实,很多大厂都是这样方式实现的。不过作为个人开发、研发环境,还是通过复制的方式部署更轻量。

Android 监控软键盘确定 搜索 按钮并赋予点击事件

android的实践开发中,为了界面的美观,往往那些搜索框并没有带搜索按钮,而是调用了软键盘的搜索按钮,完成这次时间
  • 1
  • 2

这里写图片描述
这里写图片描述

好吧!直接上代码!

<EditText
        android:id="@+id/my_chat_seach"
        android:layout_width="fill_parent"
        android:layout_height="23dp"
        android:layout_centerVertical="true"
        android:layout_marginRight="6dip"
        android:layout_toRightOf="@id/my_seach_item_1_button"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:hint="@string/search"
        android:imeOptions="actionSearch"
        android:singleLine="true"
        android:textColor="#8e8787"
        android:textSize="13sp" />

xml配置文件中 *重要的一个属性是: android:imeOptions=”actionSearch”,从而调用软键盘时,回车键就会显示搜索二字。
同时在androidMainfest.xml文件中在此Activity中写入 android:windowSoftInputMode=”adjustPan”,可以防止软键盘会把原来的界面挤上去的问题。
那么在该activity中,如何操作呢?

seachEditText = (EditText) findViewById(R.id.my_chat_seach);
watchSearch();

然后

/**
     * @方法说明:监控软键盘的的搜索按钮
     * @方法名称:watchSearch
     * @返回值:void
     */
    public void watchSearch() {
        seachEditText.setOnEditorActionListener(new OnEditorActionListener() {

            @Override
            public boolean onEditorAction(TextView v, int actionId,
                    KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                    // 先隐藏键盘
                    ((InputMethodManager) seachEditText.getContext()
                            .getSystemService(Context.INPUT_METHOD_SERVICE))
                            .hideSoftInputFromWindow(ChatFriendsGroudSeach.this
                                    .getCurrentFocus().getWindowToken(),
                                    InputMethodManager.HIDE_NOT_ALWAYS);
                    // 搜索,进行自己要的操作...
                    seachList(viewIndex);//这里是我要做的操作!
                    return true;
                }
                return false;
            }
        });
    }

好的!完成!

初始加载时edittext不自动获取焦点的方法

在做一个搜索功能时,刚进入界面,edittext直接自动获取焦点,这显然不是我想要的。

那么问题来了:如何关闭EditText自动获取焦点的?

尝试了好几个方法,在Java代码添加focus控制,在xml文件中的EditText控件中添加focusable约束等,都不适用于我的这个问题。

 

解决办法:在xml文件中EditText外框架里添加上android:focusable=”true” android:focusableInTouchMode=”true”,这样问题就解决了。

 

[html] view plain copy
  1. <RelativeLayout    
  2.         android:id=“@+id/title”    
  3.         android:layout_width=“fill_parent”    
  4.         android:layout_height=“wrap_content”    
  5.         android:layout_alignParentLeft=“true”    
  6.         android:layout_alignParentRight=“true”    
  7.         android:layout_alignParentTop=“true”    
  8.         android:background=“#F8F8FF”    
  9.         android:focusable=“true”    
  10.         android:focusableInTouchMode=“true”    
  11.         android:paddingBottom=“6dp” >    
  12.         <EditText    
  13.             android:id=“@+id/search”    
  14.             android:layout_width=“wrap_content”    
  15.             android:layout_height=“wrap_content”    
  16.             android:layout_alignParentLeft=“true”    
  17.             android:layout_marginLeft=“10dp”    
  18.             android:layout_marginRight=“5dp”    
  19.             android:layout_marginTop=“5dp”    
  20.             android:layout_toLeftOf=“@+id/auto_add”    
  21.             android:background=“@drawable/rounded_edittext”    
  22.             android:drawableLeft=“@drawable/search”    
  23.             android:ems=“10”    
  24.             android:hint=“@string/hint_search”    
  25.             android:imeOptions=“actionSearch”    
  26.             android:inputType=“text”    
  27.             android:singleLine=“true”    
  28.             android:textSize=“15sp” >    
  29.         </EditText>   

 

这是一份很详细的 Retrofit 2.0 使用教程

前言
  • Andrroid开发中,网络请求十分常用
  • 而在Android网络请求库中,Retrofit是当下*热的一个网络请求库

Github截图

  • 今天,我将献上一份非常详细Retrofit v2.0的使用教程,希望你们会喜欢。

如果对Retrofit v2.0的源码感兴趣,可看文章:Android:手把手带你深入剖析 Retrofit 2.0 源码


目录

目录


1. 简介

Retrofit简介

特别注意:

  • 准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
  • 原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装

本质过程

  • App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作
  • 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析

2. 与其他开源请求库对比

除了Retrofit,如今Android中主流的网络请求框架有:

  • Android-Async-Http
  • Volley
  • OkHttp

下面是简单介绍:

网络请求加载 - 介绍

一图让你了解全部的网络请求库和他们之间的区别!

网络请求库 - 对比


附:各个主流网络请求库的Github地址

  • Android-Async-Http
  • Volley
  • OkHttp
  • Retrofit

3. 使用介绍

使用 Retrofit 的步骤共有7个:

步骤1:添加Retrofit库的依赖
步骤2:创建 接收服务器返回数据 的类
步骤3:创建 用于描述网络请求 的接口
步骤4:创建 Retrofit 实例
步骤5:创建 网络请求接口实例 并 配置网络请求参数
步骤6:发送网络请求(异步 / 同步)

封装了 数据转换、线程切换的操作

步骤7: 处理服务器返回的数据

接下来,我们一步步进行讲解。

步骤1:添加Retrofit库的依赖

1. 在 Gradle加入Retrofit库的依赖

由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖

build.gradle

dependencies {
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    // Retrofit库
    compile 'com.squareup.okhttp3:okhttp:3.1.2'
    // Okhttp库
  }

 

2. 添加 网络权限
AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

 

步骤2:创建 接收服务器返回数据 的类

Reception.java

public class Reception {
    ...
    // 根据返回数据的格式和数据解析方式(Json、XML等)定义
    // 下面会在实例进行说明
        }

 

步骤3:创建 用于描述网络请求 的接口

  • Retrofit将 Http请求 抽象成 Java接口:采用 注解 描述网络请求参数 和配置网络请求参数
    1. 用 动态代理 动态 将该接口的注解“翻译”成一个 Http 请求,*后再执行 Http 请求
    2. 注:接口中的每个方法的参数都需要使用注解标注,否则会报错

GetRequest_Interface.interface

public interface GetRequest_Interface {

    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
    Call<Translation>  getCall();
    // @GET注解的作用:采用Get方法发送网络请求

    // getCall() = 接收网络请求数据的方法
    // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
    // 如果想直接获得Responsebody中的内容,可以定义网络请求返回值为Call<ResponseBody>
}

 

下面详细介绍Retrofit 网络请求接口 的注解类型。

注解类型

注解类型

注解说明

*类:网络请求方法

网络请求方法注解

详细说明:
a. @GET、@POST、@PUT、@DELETE、@HEAD
以上方法分别对应 HTTP中的网络请求方式

public interface GetRequest_Interface {

    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
    Call<Translation>  getCall();
    // @GET注解的作用:采用Get方法发送网络请求
    // getCall() = 接收网络请求数据的方法
    // 其中返回类型为Call<*>,*是接收数据的类(即上面定义的Translation类)
}

 

此处特意说明URL的组成:Retrofit把 网络请求的URL 分成了两部分设置:

// 第1部分:在网络请求接口的注解设置
 @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
Call<Translation>  getCall();

// 第2部分:在创建Retrofit实例时通过.baseUrl()设置
Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") //设置网络请求的Url地址
                .addConverterFactory(GsonConverterFactory.create()) //设置数据解析器
                .build();

// 从上面看出:一个请求的URL可以通过 替换块 和 请求方法的参数 来进行动态的URL更新。
// 替换块是由 被{}包裹起来的字符串构成
// 即:Retrofit支持动态改变网络请求根目录

 

  • 网络请求的完整 Url =在创建Retrofit实例时通过.baseUrl()设置 +网络请求接口的注解设置(下面称 “path“ )
  • 具体整合的规则如下:

URL整合规则

建议采用第三种方式来配置,并尽量使用同一种路径形式。

b. @HTTP

  • 作用:替换@GET、@POST、@PUT、@DELETE、@HEAD注解的作用 及 更多功能拓展
  • 具体使用:通过属性method、path、hasBody进行设置
public interface GetRequest_Interface {
    /**
     * method:网络请求的方法(区分大小写)
     * path:网络请求地址路径
     * hasBody:是否有请求体
     */
    @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getCall(@Path("id") int id);
    // {id} 表示是一个变量
    // method 的值 retrofit 不会做处理,所以要自行保证准确
}

 

第二类:标记

标记类注解

a. @FormUrlEncoded

  • 作用:表示发送form-encoded的数据

每个键值对需要用@Filed来注解键名,随后的对象需要提供值。

b. @Multipart

 

  • 作用:表示发送form-encoded的数据(适用于 有文件 上传的场景)

每个键值对需要用@Part来注解键名,随后的对象需要提供值。
具体使用如下:
GetRequest_Interface

public interface GetRequest_Interface {
        /**
         *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
         * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);

        /**
         * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
         * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
         */
        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);

}

// 具体使用
       GetRequest_Interface service = retrofit.create(GetRequest_Interface.class);
        // @FormUrlEncoded 
        Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);

        //  @Multipart
        RequestBody name = RequestBody.create(textType, "Carson");
        RequestBody age = RequestBody.create(textType, "24");

        MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
        Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);

 

第三类:网络请求参数

网络请求参数注解

详细说明

a. @Header & @Headers

  • 作用:添加请求头 &添加不固定的请求头
  • 具体使用如下:
// @Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)

// @Headers
@Headers("Authorization: authorization")
@GET("user")
Call<User> getUser()

// 以上的效果是一致的。
// 区别在于使用场景和使用方式
// 1. 使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
// 2. 使用方式:@Header作用于方法的参数;@Headers作用于方法

 

b. @Body

 

  • 作用:以 Post方式 传递 自定义数据类型 给服务器
  • 特别注意:如果提交的是一个Map,那么作用相当于 @Field

不过Map要经过 FormBody.Builder 类处理成为符合 Okhttp 格式的表单,如:

FormBody.Builder builder = new FormBody.Builder();
builder.add("key","value");

 

c. @Field & @FieldMap

  • 作用:发送 Post请求 时提交请求的表单字段
  • 具体使用:与 @FormUrlEncoded 注解配合使用
public interface GetRequest_Interface {
        /**
         *表明是一个表单格式的请求(Content-Type:application/x-www-form-urlencoded)
         * <code>Field("username")</code> 表示将后面的 <code>String name</code> 中name的取值作为 username 的值
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded1(@Field("username") String name, @Field("age") int age);

/**
         * Map的key作为表单的键
         */
        @POST("/form")
        @FormUrlEncoded
        Call<ResponseBody> testFormUrlEncoded2(@FieldMap Map<String, Object> map);

}

// 具体使用
         // @Field
        Call<ResponseBody> call1 = service.testFormUrlEncoded1("Carson", 24);

        // @FieldMap
        // 实现的效果与上面相同,但要传入Map
        Map<String, Object> map = new HashMap<>();
        map.put("username", "Carson");
        map.put("age", 24);
        Call<ResponseBody> call2 = service.testFormUrlEncoded2(map);

 

d. @Part & @PartMap

  • 作用:发送 Post请求 时提交请求的表单字段

    与@Field的区别:功能相同,但携带的参数类型更加丰富,包括数据流,所以适用于 有文件上传 的场景

  • 具体使用:与 @Multipart 注解配合使用
public interface GetRequest_Interface {

          /**
         * {@link Part} 后面支持三种类型,{@link RequestBody}、{@link okhttp3.MultipartBody.Part} 、任意类型
         * 除 {@link okhttp3.MultipartBody.Part} 以外,其它类型都必须带上表单字段({@link okhttp3.MultipartBody.Part} 中已经包含了表单字段的信息),
         */
        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload1(@Part("name") RequestBody name, @Part("age") RequestBody age, @Part MultipartBody.Part file);

        /**
         * PartMap 注解支持一个Map作为参数,支持 {@link RequestBody } 类型,
         * 如果有其它的类型,会被{@link retrofit2.Converter}转换,如后面会介绍的 使用{@link com.google.gson.Gson} 的 {@link retrofit2.converter.gson.GsonRequestBodyConverter}
         * 所以{@link MultipartBody.Part} 就不适用了,所以文件只能用<b> @Part MultipartBody.Part </b>
         */
        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload2(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);

        @POST("/form")
        @Multipart
        Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> args);
}

// 具体使用
 MediaType textType = MediaType.parse("text/plain");
        RequestBody name = RequestBody.create(textType, "Carson");
        RequestBody age = RequestBody.create(textType, "24");
        RequestBody file = RequestBody.create(MediaType.parse("application/octet-stream"), "这里是模拟文件的内容");

        // @Part
        MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", "test.txt", file);
        Call<ResponseBody> call3 = service.testFileUpload1(name, age, filePart);
        ResponseBodyPrinter.printResponseBody(call3);

        // @PartMap
        // 实现和上面同样的效果
        Map<String, RequestBody> fileUpload2Args = new HashMap<>();
        fileUpload2Args.put("name", name);
        fileUpload2Args.put("age", age);
        //这里并不会被当成文件,因为没有文件名(包含在Content-Disposition请求头中),但上面的 filePart 有
        //fileUpload2Args.put("file", file);
        Call<ResponseBody> call4 = service.testFileUpload2(fileUpload2Args, filePart); //单独处理文件
        ResponseBodyPrinter.printResponseBody(call4);
}

 

e. @Query和@QueryMap

  • 作用:用于 @GET 方法的查询参数(Query = Url 中 ‘?’ 后面的 key-value)

    如:url = http://www.println.net/?cate=android,其中,Query = cate

  • 具体使用:配置时只需要在接口方法中增加一个参数即可:
   @GET("/")    
   Call<String> cate(@Query("cate") String cate);
}

// 其使用方式同 @Field与@FieldMap,这里不作过多描述

 

f. @Path

  • 作用:URL地址的缺省值
  • 具体使用:
public interface GetRequest_Interface {

        @GET("users/{user}/repos")
        Call<ResponseBody>  getBlog(@Path("user") String user );
        // 访问的API是:https://api.github.com/users/{user}/repos
        // 在发起请求时, {user} 会被替换为方法的*个参数 user(被@Path注解作用)
    }

 

g. @Url

  • 作用:直接传入一个请求的 URL变量 用于URL设置
  • 具体使用:
public interface GetRequest_Interface {

        @GET
        Call<ResponseBody> testUrlAndQuery(@Url String url, @Query("showAll") boolean showAll);
       // 当有URL注解时,@GET传入的URL就可以省略
       // 当GET、POST...HTTP等方法中没有设置Url时,则必须使用 {@link Url}提供

}

 

汇总

汇总

步骤4:创建 Retrofit 实例

 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") // 设置网络请求的Url地址
                .addConverterFactory(GsonConverterFactory.create()) // 设置数据解析器
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平台
                .build();

 

a. 关于数据解析器(Converter)

  • Retrofit支持多种数据解析方式
  • 使用时需要在Gradle添加依赖
数据解析器 Gradle依赖
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2

b. 关于网络请求适配器(CallAdapter)

  • Retrofit支持多种网络请求适配器方式:guava、Java8和rxjava

    使用时如使用的是 Android 默认的 CallAdapter,则不需要添加网络请求适配器的依赖,否则则需要按照需求进行添加
    Retrofit 提供的 CallAdapter

  • 使用时需要在Gradle添加依赖:
网络请求适配器 Gradle依赖
guava com.squareup.retrofit2:adapter-guava:2.0.2
Java8 com.squareup.retrofit2:adapter-java8:2.0.2
rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

步骤5:创建 网络请求接口实例

        // 创建 网络请求接口 的实例
        GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);

        //对 发送请求 进行封装
        Call<Reception> call = request.getCall();

 

步骤6:发送网络请求(异步 / 同步)

封装了 数据转换、线程切换的操作

//发送网络请求(异步)
        call.enqueue(new Callback<Translation>() {
            //请求成功时回调
            @Override
            public void onResponse(Call<Translation> call, Response<Translation> response) {
                //请求处理,输出结果
                response.body().show();
            }

            //请求失败时候的回调
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                System.out.println("连接失败");
            }
        });

// 发送网络请求(同步)
Response<Reception> response = call.execute();

 

步骤7:处理返回数据

通过response类的 body()对返回的数据进行处理

//发送网络请求(异步)
        call.enqueue(new Callback<Translation>() {
            //请求成功时回调
            @Override
            public void onResponse(Call<Translation> call, Response<Translation> response) {
                // 对返回数据进行处理
                response.body().show();
            }

            //请求失败时候的回调
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                System.out.println("连接失败");
            }
        });

// 发送网络请求(同步)
  Response<Reception> response = call.execute();
  // 对返回数据进行处理
  response.body().show();

 


4. 实例讲解

接下来,我将用两个实例分别对 Retrofit GET方式 和 POST方式进行 网络请求 讲解。

4.1 实例1

  • 实现功能:将中文翻译成英文
  • 实现方案:采用Get方法对 金山词霸API 发送网络请求

    采用 Gson 进行数据解析

金山词典

  • 步骤说明

步骤1:添加Retrofit库的依赖
步骤2:创建 接收服务器返回数据 的类
步骤3:创建 用于描述网络请求 的接口
步骤4:创建 Retrofit 实例
步骤5:创建 网络请求接口实例 并 配置网络请求参数
步骤6:发送网络请求(采用*常用的异步方式)

封装了 数据转换、线程切换的操作

步骤7: 处理服务器返回的数据

接下来,我们一步步进行讲解。

  • 具体使用

步骤1:添加Retrofit库的依赖

1. 在 Gradle加入Retrofit库的依赖

由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖

build.gradle

dependencies {
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    // Retrofit库
    compile 'com.squareup.okhttp3:okhttp:3.1.2'
    // Okhttp库
  }

 

2. 添加 网络权限
AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

 

步骤2:创建 接收服务器返回数据 的类

  • 金山词霸API 的数据格式说明如下:
// URL模板
http://fy.iciba.com/ajax.php

// URL实例
http://fy.iciba.com/ajax.php?a=fy&f=auto&t=auto&w=hello%20world

// 参数说明:
// a:固定值 fy
// f:原文内容类型,日语取 ja,中文取 zh,英语取 en,韩语取 ko,德语取 de,西班牙语取 es,法语取 fr,自动则取 auto
// t:译文内容类型,日语取 ja,中文取 zh,英语取 en,韩语取 ko,德语取 de,西班牙语取 es,法语取 fr,自动则取 auto
// w:查询内容

 

API格式说明

  • 根据 金山词霸API 的数据格式,创建 接收服务器返回数据 的类:

Translation.java

public class Translation {
        private int status;

    private content content;
    private static class content {
        private String from;
        private String to;
        private String vendor;
        private String out;
        private int errNo;
    }

    //定义 输出返回数据 的方法
    public void show() {
        System.out.println(status);

        System.out.println(content.from);
        System.out.println(content.to);
        System.out.println(content.vendor);
        System.out.println(content.out);
        System.out.println(content.errNo);
    }
}

 

步骤3:创建 用于描述网络请求 的接口

采用 注解 描述 网络请求参数。
GetRequest_Interface.java

public interface GetRequest_Interface {

 @GET("ajax.php?a=fy&f=auto&t=auto&w=hello%20world")
    Call<Translation> getCall();
    // 注解里传入 网络请求 的部分URL地址
    // Retrofit把网络请求的URL分成了两部分:一部分放在Retrofit对象里,另一部分放在网络请求接口里
    // 如果接口里的url是一个完整的网址,那么放在Retrofit对象里的URL可以忽略
    // getCall()是接受网络请求数据的方法
}

 

接下来的步骤均在GetRequest.java内实现(看注释)

步骤4:创建Retrofit对象
步骤5:创建 网络请求接口 的实例
步骤6:发送网络请求

以*常用的 异步请求 为例

步骤7:处理返回数据

GetRequest.java

public class GetRequest extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        request();
        // 使用Retrofit封装的方法
    }
    public void request() {

        //步骤4:创建Retrofit对象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fy.iciba.com/") // 设置 网络请求 Url
                .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
                .build();

        // 步骤5:创建 网络请求接口 的实例
        GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);

        //对 发送请求 进行封装
        Call<Translation> call = request.getCall();

        //步骤6:发送网络请求(异步)
        call.enqueue(new Callback<Translation>() {
            //请求成功时回调
            @Override
            public void onResponse(Call<Translation> call, Response<Translation> response) {
                // 步骤7:处理返回的数据结果
                response.body().show();
            }

            //请求失败时回调
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                System.out.println("连接失败");
            }
        });
    }
}

 

由于此处采用了 Gson 解析,所以需要在 Gradle加入依赖
build.gradle

compile 'com.squareup.retrofit2:converter-gson:2.0.2'
  • 1

运行结果

运行结果

Demo地址

Carson_Ho的Github:https://github.com/Carson-Ho/RetrofitDemo


4.2 实例2

  • 实现的功能:将 英文 翻译成 中文
  • 实现方法:采用Post方法对 有道API 发送网络请求

    采用 Gson 进行数据解析

有道翻译

  • 使用步骤

步骤1:添加Retrofit库的依赖
步骤2:创建 接收服务器返回数据 的类
步骤3:创建 用于描述网络请求 的接口
步骤4:创建 Retrofit 实例
步骤5:创建 网络请求接口实例 并 配置网络请求参数
步骤6:发送网络请求(采用*常用的异步方式)

封装了 数据转换、线程切换的操作

步骤7: 处理服务器返回的数据

接下来,我们一步步进行Retrofit的使用。

  • 具体使用

步骤1:添加Retrofit库的依赖

1. 在 Gradle加入Retrofit库的依赖

由于Retrofit是基于OkHttp,所以还需要添加OkHttp库依赖

build.gradle

dependencies {
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    // Retrofit库
    compile 'com.squareup.okhttp3:okhttp:3.1.2'
    // Okhttp库
  }

 

2. 添加 网络权限
AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

步骤2:创建 接收服务器返回数据 的类

  • API 的数据格式说明如下:
// URL
http://fanyi.youdao.com/translate

// URL实例
http://fanyi.youdao.com/translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=


// 参数说明
// doctype:json 或 xml
// jsonversion:如果 doctype 值是 xml,则去除该值,若 doctype 值是 json,该值为空即可
// xmlVersion:如果 doctype 值是 json,则去除该值,若 doctype 值是 xml,该值为空即可
// type:语言自动检测时为 null,为 null 时可为空。英译中为 EN2ZH_CN,中译英为 ZH_CN2EN,日译中为 JA2ZH_CN,中译日为 ZH_CN2JA,韩译中为 KR2ZH_CN,中译韩为 ZH_CN2KR,中译法为 ZH_CN2FR,法译中为 FR2ZH_CN
// keyform:mdict. + 版本号 + .手机平台。可为空
// model:手机型号。可为空
// mid:平台版本。可为空
// imei:???。可为空
// vendor:应用下载平台。可为空
// screen:屏幕宽高。可为空
// ssid:用户名。可为空
// abtest:???。可为空

// 请求方式说明
// 请求方式:POST
// 请求体:i
// 请求格式:x-www-form-urlencoded

 

数据格式说明

  • 根据 有道API 的数据格式,创建 接收服务器返回数据 的类:

Translation.java

public class Translation1 {

    private String type;
    private int errorCode;
    private int elapsedTime;
    private List<List<TranslateResultBean>> translateResult;

    public String getType() {
        return type;
    }

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

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public int getElapsedTime() {
        return elapsedTime;
    }

    public void setElapsedTime(int elapsedTime) {
        this.elapsedTime = elapsedTime;
    }

    public List<List<TranslateResultBean>> getTranslateResult() {
        return translateResult;
    }

    public void setTranslateResult(List<List<TranslateResultBean>> translateResult) {
        this.translateResult = translateResult;
    }

    public static class TranslateResultBean {
        /**
         * src : merry me
         * tgt : 我快乐
         */

        public String src;
        public String tgt;

        public String getSrc() {
            return src;
        }

        public void setSrc(String src) {
            this.src = src;
        }

        public String getTgt() {
            return tgt;
        }

        public void setTgt(String tgt) {
            this.tgt = tgt;
        }
    }

}

 

步骤3:创建 用于描述网络请求 的接口

采用 注解 描述 网络请求参数。

PostRequest_Interface.java

public interface PostRequest_Interface {

    @POST("translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=")
    @FormUrlEncoded
    Call<Translation1> getCall(@Field("i") String targetSentence);
    //采用@Post表示Post方法进行请求(传入部分url地址)
    // 采用@FormUrlEncoded注解的原因:API规定采用请求格式x-www-form-urlencoded,即表单形式
    // 需要配合@Field 向服务器提交需要的字段
}

 

接下来的步骤均在PostRequest.java内实现(看注释)

步骤4:创建Retrofit对象
步骤5:创建 网络请求接口 的实例
步骤6:发送网络请求

以*常用的 异步请求 为例

步骤7:处理返回数据

PostRequest.java

public class PostRequest extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        request();
    }
    public void request() {

        //步骤4:创建Retrofit对象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") // 设置 网络请求 Url
                .addConverterFactory(GsonConverterFactory.create()) //设置使用Gson解析(记得加入依赖)
                .build();

        // 步骤5:创建 网络请求接口 的实例
        PostRequest_Interface request = retrofit.create(PostRequest_Interface.class);

        //对 发送请求 进行封装(设置需要翻译的内容)
        Call<Translation1> call = request.getCall("I love you");

        //步骤6:发送网络请求(异步)
        call.enqueue(new Callback<Translation1>() {

            //请求成功时回调
            @Override
            public void onResponse(Call<Translation1> call, Response<Translation1> response) {
                // 步骤7:处理返回的数据结果:输出翻译的内容
                System.out.println(response.body().getTranslateResult().get(0).get(0).getTgt());
            }

            //请求失败时回调
            @Override
            public void onFailure(Call<Translation1> call, Throwable throwable) {
                System.out.println("请求失败");
                System.out.println(throwable.getMessage());
            }
        });
    }


}

 

由于此处采用了 Gson 解析,所以需要在 Gradle 加入依赖
build.gradle

compile 'com.squareup.retrofit2:converter-gson:2.0.2'
  • 1

运行结果

运行结果

Demo地址

Carson_Ho的Github:https://github.com/Carson-Ho/RetrofitDemo


5. Retrofit 的拓展使用

  • Retrofit的使用场景非常丰富,如支持RxJavaPrototocobuff
  • 具体设置也非常简单 & 方便:
<-- 主要在创建Retrofit对象中设置 -->
Retrofit retrofit = new Retrofit.Builder()
  .baseUrl(""http://fanyi.youdao.com/"")
  .addConverterFactory(ProtoConverterFactory.create()) // 支持Prototocobuff解析
  .addConverterFactory(GsonConverterFactory.create()) // 支持Gson解析
  .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava
  .build();

 

具体关于 RxJava的使用这里就不展开,请期待下篇关于 Rxjava的文章。


6. 总结

  • 看完本文,相信你已经非常熟悉 Retrofit 2.0 的使用
  • 如果你希望继续阅读 Retrofit 2.0 的源码,请看我写的文章:Android:手把手带你深入剖析 Retrofit 2.0 源码
  • 接下来,我将继续分析与 Retrofit 配合使用的 RxJava,有兴趣可以继续关注Carson_Ho的安卓开发笔记