豪翔天下

Change My World by Program

0%

原文地址:阮一峰的网络日志

网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备……)。

因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现”API First“的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。我以前写过一篇《理解RESTful架构》,探讨如何理解这个概念。

今天,我将介绍RESTfulAPI的设计细节,探讨如何设计一套合理、好用的API。我的主要参考了两篇文章(12)。

一、协议

API与用户的通信协议,总是使用HTTPs协议

二、域名

应该尽量将API部署在专用域名之下。

1
https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

1
https://example.org/api/

三、版本(Versioning)

应该将API的版本号放入URL。

1
https://api.example.com/v1/

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

四、路径(Endpoint)

路径又称”终点”(endpoint),表示API的具体网址。

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。

举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

五、HTTP动词

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

  • GET(SELECT):从服务器取出资源(一项或多项)。

  • POST(CREATE):在服务器新建一个资源。

    • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。

    • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。

    • DELETE(DELETE):从服务器删除资源。
      还有两个不常用的HTTP动词。

    • HEAD:获取资源的元数据。

    • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
      下面是一些例子。

    • GET /zoos:列出所有动物园

    • POST /zoos:新建一个动物园

    • GET /zoos/ID:获取某个指定动物园的信息

    • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)

    • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)

    • DELETE /zoos/ID:删除某个动物园

    • GET /zoos/ID/animals:列出某个指定动物园的所有动物

    • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

六、过滤信息(Filltering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置。
    • ?page=2&per_page=100:指定第几页,以及每页的记录数。
    • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
    • ?animal_type_id=1:指定筛选条件
      参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET
      /animals?zoo_id=ID 的含义是相同的。

七、状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [_]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [_]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [_] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [_]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
    状态码的完全列表参见这里

八、错误处理(Error handling)

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

1
2
3
{
error: "Invalid API key"
}

九、返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

  • GET /collection:返回资源对象的列表(数组)
  • GET /collection/resource:返回单个资源对象
    • POST /collection:返回新生成的资源对象
    • PUT /collection/resource:返回完整的资源对象
    • PATCH /collection/resource:返回完整的资源对象
    • DELETE /collection/resource:返回一个空文档

十、Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

1
2
3
4
5
6
7
8
{
"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}
}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

1
2
3
4
5
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

1
2
3
4
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

十一、其他

(1)API的身份认证应该使用OAuth 2.0框架。

(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

(完)

扩展阅读

RESETful API 设计规范

微软的Rest API设计指南

个人总结

  • RESTful设计风格是仅仅针对API的设计,其他的,比如新建功能页面的url还是需要自己另外定义的,当然可以在后面直接加参数,比如GET /zoos?add=1

  • 对于文件的上传,无法使用application/json,而只能使用Multipart/form-data的方式

  • 如果我们要是用名称而不是ID来作为url的关键字,那么可能出现关键字与url重复的问题,例如/users/:username/cars/users/cars,这个例子不是很恰当,但是已经可以看出问题了,前者表示某个用户所拥有的车,后者表示属于所有人的车,但是如果有个人的名字就叫cars呢,就会出现设计上的错误。为了规避这种情况,最好的办法就是提取出几个关键字,应该尽量少,例如github就不能注册名为teams的账号,注册时就会提示这是一个保留字。这只是大多数情况,少数情况,资源并不完全属于我们,我们无法确定资源是否会占用保留字,那么这时候就只能添加特殊字符了,例如$,另外一个做法是使用下划线,例如/users/_regist

  • 有些人喜欢所有的接口的http状态码全部返回200,然后从返回的Json数据里面判断请求是否正常,理由却是统一管理返回数据格式,前端更好判断。我的理解是,这样完全不符合restful的设计规范。首先,无法保证请求永远返回200,所以,前端反而会多写一些判断;另外,如果按照请求错误的不同返回不同的http状态码,也是一种规范,因为http状态码对应的错误原因本身就是统一的;还有一点,对于日志监控来说,比如ELK这种自动分析日志的工具,当然是返回http状态码更好一点。

    这里还有另外一种将错误信息具体化的方法,就是在HTTP_CODE外,添加一个错误码头进行返回,例如HTTP_CODE=403X-status=4031可以表示用户密码错误等具体错误信息。另外HTTP_CODE其实是支持用小数进行扩展的,例如403表示禁止访问,那么403.1可以表示禁止可执行访问,403.2表示禁止读访问

  • 返回数据结构可以这样定义

    1
    2
    3
    4
    5
    {
    "code": "业务状态码",
    "message": "业务状态信息",
    "data": {} 或者 []
    }

其实Python3的字符串默认是unicode格式了,但utf-8并不能解决所有问题。首先,我们得在所有的文件前加上这样一句:

# -_- coding:utf-8 -_-
或者




# coding: utf-8

其次,就是注意一般的常见的编码格式,爬去某些奇葩的网页时可能会遇到的,这时候只需要使用对应的转换进行了,如果不知道网页的格式就只能一个一个猜了,常见的有

ASCII
GB18030
GB2312
GBK
UTF-8

直到上个月,我都还一直以为能使用个第三方库,想要什么功能去Google一下肯定能搜索到,这样就算是高手了。可是,最
,我还一直想,为什么即使是BAT这样的大公司也都问那些在实际开发中根本就用不到的网上一搜就一大片的东西,感觉他
事却让我意识到自己犯了一个多大的错。

在这里,我想把只会堆砌代码的程序员称呼为码农,当然不是贬低,而是确实这个词比较直观,比如当年给进城务工的人士
在我看来,内功包括三个方面:

熟练程度

这绝对不是一朝一夕就能简单掌握的,必须日复一日年复一年的写代码,才能达到所谓“熟练”的程度。不然,每次就像我之
甚至是很基础的语法问题都得去搜一下,然后,每次遇到问题都重复地去寻找答案,完全没有技术含量,可却从另一方面说明,你仅仅只是一个码农。

创造能力

我不鼓励重复造轮子,但你得保证,在没有轮子的时候能自己造一个出来。前阵子因为业务的需要,得破解一下某系统的验证码。结果去Google一下才让我大吃一惊,只发现了一个很古老的东西,pytesseract,是对Google Tesseract的一个简单的封装。可是使用起来却极其麻烦。没办法,只能计划着自己造,我以前跟着一位导师研究过一点AI方面的知识,知道这种验证码是可以实现很高识别就只能呵呵了。

系统架构

天地万物为我所用。程序员,垒砌代码,总得知道在哪儿垒吧。如果,能够自己设计系统架构,能够清楚明白每一种技术选型的利与弊,那样,一个完整的工程才真正属于你,否则,你永远只属于你那一方没人愿意去碰的基础的代码,而且,我总感觉,像这种重复性的劳动,总有一天会被更智能的东西取代。

当年,小米刚出来的时候,所有硬件都是出自别人之手,当时我很仰慕,能够集中所有最好的东西做出来一个产品,这样,应该很牛了吧。可后来发现,小米牛的不是这点,它也在造轮子,至少我认为小米系统,是国内最好的一个改装版android,很难想像,小米没有MIUI会是多么的脆弱。

内功这种东西,有没有最重要,用不用才是另一回事儿。不过,是有一些工作是只需要码农的,那就是外包,而且是低质量项目的外包。

转自:http://www.admin10000.com/document/5744.html

许很多人还不知道,知乎在规模上是仅次于百度贴吧和豆瓣的中文互联网最大的UGC(用户生成内容)社区。知乎创业三年来,从0开始,到现在已经有了100多台服务器。
目前知乎的注册用户超过了1100万,每个月有超过8000万人使用;网站每个月的PV超过2.2亿,差不多每秒钟的动态请求超过2500。

在ArchSummit北京2014大会上,知乎联合创始人兼 CTO
李申申带来了知乎创业三年多来的首次全面技术分享(幻灯片下载)。本文系根据演讲内容整理而成。

初期架构选型

在2010年10月真正开始动手做知乎这个产品时,包含李申申在内,最初只有两位工程师;到2010年12月份上线时,工程师是四个。

知乎的主力开发语言是Python。因为Python简单且强大,能够快速上手,开发效率高,而且社区活跃,团队成员也比较喜欢。

知乎使用的是Tornado框架。因为它支持异步,很适合做实时Comet应用,而且简单轻量,学习成本低,再就是有FriendFeed
的成熟案例,Facebook 的社区支持。知乎的产品有个特性,就是希望跟浏览器端建立一个长连接,便于实时推送Feed和通知,所以Tornado比较合适。

最初整个团队的精力全部放在产品功能的开发上,而其他方面,基本上能节约时间、能省的都用最简单的方法来解决,当然这在后期也带来了一些问题。

最初的想法是用云主机,节省成本。知乎的第一台服务器是512MB内存的Linode主机。但是网站上线后,内测受欢迎程度超出预期,很多用户反馈网站很慢。跨国网络
延迟比想象的要大,特别是国内的网络不均衡,全国各地用户访问的情况都不太一样。这个问题,再加上当时要做域名备案,知乎又回到了自己买机器找机房的老路上。

买了机器、找了机房之后又遇到了新的问题,服务经常宕掉。当时服务商的机器内存总是出问题,动不动就重启。终于有一次机器宕掉起不来了,这时知乎就做了Web和数据库
的高可用。创业就是这样一个情况,永远不知道明早醒来的时候会面临什么样的问题。

这是当时那个阶段的架构图,Web和数据库都做了主从。当时的图片服务托管在又拍云上。除了主从,为了性能更好还做了读写分离。为解决同步问题,又添加了一个
服务器来跑离线脚本,避免对线上服务造成响应延迟。另外,为改进内网的吞吐量延迟,还更换了设备,使整个内网的吞吐量翻了20倍。

在2011年上半年时,知乎对Redis已经很依赖。除了最开始的队列、搜索在用,后来像Cache也开始使用,单机存储成为瓶颈,所以引入了分片
同时做了一致性

知乎团队是一个很相信工具的团队,相信工具可以提升效率。工具其实是一个过程,工具并没有所谓的最好的工具,只有最适合的工具。而且它是在整个过程中,随着整
个状态的变化、环境的变化在不断发生变化的。知乎自己开发或使用过的工具包括Profiling(函数级追踪请求,分析调优)、Werkzeug(方便调试的工具)、
Puppet(配置管理)和Shipit(一键上线或回滚)等。

日志系统

知乎最初是邀请制的,2011年下半年,知乎上线了申请注册,没有邀请码的用户也可以通过填写一些资料申请注册知乎。用户量又上了一个台阶,这时就有了一些发
广告的账户,需要扫除广告。日志系统的需求提上日程。

这个日志系统必须支持分布式收集、集中存储、实时、可订阅和简单等特性。当时调研了一些开源系统,比如Scribe总体不错,但是不支持订阅。Kafka是Scala
开发的,但是团队在Scala方面积累较少,Flume也是类似,而且比较重。所以开发团队选择了自己开发一个日志系统——Kids(Kids Is Data
Stream)。顾名思义,Kids是用来汇集各种数据流的。

Kids参考了Scribe的思路。Kdis在每台服务器上可以配置成Agent或Server。Agent直接接受来自应用的消息,把消息汇集之后,可以打给下一个
Agent或者直接打给中心Server。订阅日志时,可以从Server上获取,也可以从中心节点的一些Agent上获取。

具体细节如下图所示:

知乎还基于Kids做了一个Web小工具(Kids Explorer),支持实时看线上日志,现在已经成为调试线上问题最主要的工具。

Kids已经开源,放到了Github上。

事件驱动的架构

知乎这个产品有一个特点,最早在添加一个答案后,后续的操作其实只有更新通知、更新动态。但是随着整个功能的增加,又多出了一些更新索引、更新计数、内容审查等操作,
后续操作五花八门。如果按照传统方式,维护逻辑会越来越庞大,维护性也会非常差。这种场景很适合事件驱动方式,所以开发团队对整个架构做了调整,做了事件驱动的架构。

这时首先需要的是一个消息队列,它应该可以获取到各种各样的事件,而且对一致性有很高的要求。针对这个需求,知乎开发了一个叫Sink的小工具。它拿到消息后,先做本
地的保存、持久化,然后再把消息分发出去。如果那台机器挂掉了,重启时可以完整恢复,确保消息不会丢失。然后它通过Miller开发框架,把消息放到任务队列。Sin
k更像是串行消息订阅服务,但任务需要并行化处理, Beanstalkd就派上了用场,由其对任务进行全周期管理。架构如下图所示:

举例而言,如果现在有用户回答了问题,首先系统会把问题写到MySQL里面,把消息塞到Sink,然后把问题返回给用户。Sink通过Miller把任务发给
Beanstalkd,Worker自己可以找到任务并处理。

最开始上线时,每秒钟有10个消息,然后有70个任务产生。现在每秒钟有100个事件,有1500个任务产生,就是通过现在的事件驱动架构支撑的。

页面渲染优化

知乎在2013年时每天有上百万的PV,页面渲染其实是计算密集型的,另外因为要获取数据,所以也有IO密集型的特点。这时开发团队就对页面进行了组件化,还升级了数
据获取机制。知乎按照整个页面组件树的结构,自上而下分层地获取数据,当上层的数据已经获取了,下层的数据就不需要再下去了,有几层基本上就有几次数据获取。

结合这个思路,知乎自己做了一套模板渲染开发框架——ZhihuNode。

经历了一系列改进之后,页面的性能大幅度提升。问题页面从500ms 减少到150ms,Feed页面从1s减少到600ms。

面向服务的架构(SOA)

随着知乎的功能越来越庞杂,整个系统也越来越大。知乎是怎么做的服务化呢?

首先需要一个最基本的RPC框架,RPC框架也经历了好几版演进。

第一版是Wish,它是一个严格定义序列化的模型。传输层用到了STP,这是自己写的很简单的传输协议,跑在TCP上。一开始用的还不错,因为一开始只写了一两个服务
。但是随着服务增多,一些问题开始出现,首先是ProtocolBuffer会 生成一些描述代码,很冗长,放到整个库里显得很丑陋。另外严格的定义使其不便使用。这
时有位工程师开发了新的RPC框架——Snow。它使用简单的JSON做数据序列化。但是松散的数据定义面对的问题是,比如说服务要去升级,要改写数据结构,很难知道
有哪几个服务在使用,也很难通知它们,往往错误就发生了。于是又出了第三个RPC框架,写RPC框架的工程师,希望结合前面两个框架的特点,首先保持Snow简单,其
次需要相对严格的序列化协议。这一版本引入了 Apache
Avro。同时加入了特别的机制,在传输层和序列化协议这一层都做成了可插拔的方式,既可以用JSON,也可以用Avro,传输层可以用STP,也可以用二进制协议。

再就是搭了一个服务注册发现,只需要简单的定义服务的名字就可以找到服务在哪台机器上。同时,知乎也有相应的调优的工具,基于Zipkin开发了自己的
Tracing系统。

按照调用关系,知乎的服务分成了3层:聚合层、内容层和基础层。按属性又可以分成3类:数据服务、逻辑服务和通道服务。数据服务主要是一些要做特殊数据类型的存储,比
如图片服务。逻辑服务更多的是CPU密集、计算密集的操作,比如答案格式的定义、解析等。通道服务的特点是没有存储,更多是做一个转发,比如说Sink。

这是引入服务化之后整体的架构。

Python,我最喜欢的语言。但是,在其强大的功能以及强制的编码格式背后,也会引来一大波的编码方面的困扰,所以依然得需要进行一些规范化。

注释

函数或者文档的注释使用三引号,结尾空一行

函数注释需要注明三个参数:Args(参数)、Return(返回值)和Raises(抛出的错误),例如:

def exampleFunc(one, two):
    """
    这里是函数的功能





Args:
    one: 参数一的注释
    two: 参数二的注释

Return:返回值的解释,如果返回值比较复杂,比如是一个json数据,那么还需要将返回的格式卸载这儿

Raises: 非必须
"""</pre>

** 类的注释**,依然得有一行文档字符串,若有共有属性,需要在注释处表名,例如:

class exampleClass(基类):
    """
    类的注释





Attributes:
    公有属性1: 解释
"""</pre>

** 文件注释:**一般包括了编码信息、版权、许可声明、模块头等信息,例如:

# coding = utf-8




# Copyright 2015 ........




"""
这里是模块头,用一行文字概括文件或模块或脚本的作用
"""

命名

module_name:模块
package_name:包
ClassName:类
method_name:方法
ExceptionName:错误
function_name:函数
GLOBAL_VAR_NAME:全局变量
function_parameter_name:函数参数
local_var_name:局部变量
has_或is_:定义布尔类型元素

空行

两个函数的定义之间空两行,而方法或者语句模块之间则只空一行

空格

二元操作符之间添加空格

其它

对于常字符串,Python可以使用小括号将行隐式地连接在一起而不用在每行末尾加上加号,例如:

a = (
    'wang'
    'hao'
)
print(a)




# 打印出来就是wanghao

![](http://7xnc86.com1.z0.glb.clouddn.com/the-difference-of-frame-and-
framework_0.jpg)

该文章是我之前在麦库里收藏的,但不知为何现在已找不到原文链接了,网上都是些转载文章,还不完整。如果作者能看到,希望作者可以联系我。为使文章结构更清晰,下文稍
有改动。

架构和框架的区别

笔者发现,人们对软件架构存在非常多的误解,其中一个最为普遍的误解就是:将架构和框架(Framework)混为一谈。本文阐述了它们的区别。算是对思辨成果的一个
应用吧。一图胜千言,下图切中肯綮地点出了架构和框架的区别。一句话,框架是软件,架构不是软件。

框架是一种特殊的软件,它并不能提供完整无缺的解决方案,而是为你构建解决方案提供良好的基础。框架是半成品。典型地,框架是系统或子系统的半成品;框架中的服务可以被最终应用直接调用,而框架中的扩展点是供应用开发人员定制的“可变化点”。

软件架构不是软件,而是关于软件如何设计的重要决策。软件架构决策涉及到如何将软件系统分解成不同的部分、各部分之间的静态结构关系和动态交互关系等。经过完整的开发过程之后,这些架构决策将体现在最终开发出的软件系统中;当然,引入软件框架之后,整个开发过程变成了“分两步走”,而架构决策往往会体现在框架之中。或许,人们常把架构和框架混为一谈的原因就在于此吧。

理解了本图,我们就很容易理解Frank Buschmann等人在《面向模式的软件体系结构(第一卷)》中为框架所下的定义了,其中也提到了框架和架构的关系:
_框架是一个可实例化的、部分完成的软件系统或子系统,它为一组系统或子系统定义了架构,并提供了构造系统的基本构造块,还为实现特定功能定义了可调整点。在面向对象环境中,框架由抽象类和具体类组成_。(A framework is a partially complete software (sub-) system that is intended to be instantiated. It defines the architecture for a family of (sub-) systems and provides the basic building blocks to create them. It also defines the places where adaptations for specific functionality should be made. In an object-oriented environment a framework consists of abstract and concrete classes.)

在以前经常和同事、朋友,甚至是合作伙伴之间进行一些技术上的交流,很多时候他们给我的印象是软件“架构”和“框架”被混为一谈,而我也曾经经历过这个阶段。在理解上
从模糊混淆到有所认识是需要投入足够的时间来进行理解的。软件“架构”和“框架”是两个不同的概念,但它们也是相互关联的。

软件框架是一特殊的软件,由实际的代码构建而成,是软件系统、子系统的半成品。软件框架为具体的解决方案提供了基础,提供了基础服务和可扩展点,同时软件框架也建立了一些约束,开发人员在此基础上进行特定业务功能的定制开发。例如,在J2EE企业级应用程序开发中,经常使用struts+spring+hibernate来搭建一个基本的项目结构,在没有其他特殊系统需求的前提下,_这就是一个软件框架_。

软件架构是引导如何设计软件框架的重要决策。它决定了软件系统如何划分,在一定程度上描述了被划分的各个部分之间的静态、动态关系。软件架构的决策体现在软件系统的框架中。

框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。可以说,一个框架是一个可复用的设计构件,它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程,表现为一组抽象类以及其实例之间协作的方法,它为构件复用提供了上下文(Context)关系。因此构件库的大规模重用也需要框架。

构件领域框架方法在很大程度上借鉴了硬件技术发展的成就,它是构件技术、软件体系结构研究和应用软件开发三者发展结合的产物。在很多情况下,框架通常以构件库的形式出
现,但构件库只是框架的一个重要部分。框架的关键还在于框架内对象间的交互模式和控制流模式。

框架比构件可定制性强。在某种程度上,将构件和框架看成两个不同但彼此协作的技术或许更好。框架为构件提供重用的环境,为构件处理错误、交换数据及激活操作提供了标准
的方法。

应用框架的概念也很简单。它并不是包含构件应用程序的小片程序,而是实现了某应用领域通用完备功能(除去特殊应用的部分)的底层服务。使用这种框架的编程人员可以在一
个通用功能已经实现的基础上开始具体的系统开发。框架提供了所有应用期望的默认行为的类集合。具体的应用通过重写子类(该子类属于框架的默认行为)或组装对象来支持应
用专用的行为。

应用框架强调的是软件的设计重用性和系统的可扩充性,以缩短大型应用软件系统的开发周期,提高开发质量。与传统的基于类库的面向对象重用技术比较,应用框架更注重于面
向专业领域的软件重用。应用框架具有领域相关性,构件根据框架进行复合而生成可运行的系统。框架的粒度越大,其中包含的领域知识就更加完整。

框架,即framework。其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟
的,不断升级的软件。

框架目前还没有统一的定义,其中Ralph Johnson所给出的定义基本上为大多数研究人员所接受:

一个框架是一个可复用设计,它是由一组抽象类及其实例间协作关系来表达的 【Johnson 98】。

这个定义是从框架内涵的角度来定义框架的,当然也可以从框架用途的角度来给出框架的定义:

一个框架是在一个给定的问题领域内,一个应用程序的一部分设计与实现【Bosch 97】。

从以上两个定义可以看出,框架是对特定应用领域中的应用系统的部分设计和实现的整体结构。_框架将应用系统划分为类和对象,定义类和对象的责任,类和对象如何互相协作
,以及对象之间的控制线程。_这些共有的设计因素由框架预先定义,应用开发人员只须关注于特定的应用系统特有部分。框架刻画了其应用领域所共有的设计决策,所
以说框架着重于设计复用,尽管框架中可能包含用某种程序设计语言实现的具体类。

一个基于框架开发的应用系统包含一个或多个框架,与框架相关的构件类,以及与应用系统相关的功能扩展。与应用系统相关的扩展包括与应用系统相关的类和对象。应用系统可
能仅仅复用了面向对象框架的一部分,或者说,它可能需要对框架进行一些适应性修改,以满足系统需求。

面向对象的框架作为一种可复用的软件,在基于框架的软件开发过程中会涉及到框架的开发和利用两个方面的工作。框架的开发阶段在于产生领域中可复用的设计。该阶段的主要
结果是框架以及与框架相关的构件类。该阶段的一个重要活动是框架的演变和维护。象所有软件一样,框架也易于变化。产生变化的原因很多,如应用出错,业务领域变化,等等

不论是哪一种技术,最终都是为业务发展而服务的。从业务的角度来讲。首先,框架的是为了企业的业务发展和战略规划而服务的,他服从于企业的愿景(vision);其次
,框架最重要的目标是提高企业的竞争能力,包括降低成本、提高质量、改善客户满意程度,控制进度等方面。最后,框架实现这一目标的方式是进行有效的知识积累。软件开发
是一种知识活动,因此知识的聚集和积累是至关重要的。框架能够采用一种结构化的方式对某个特定的业务领域进行描述,也就是将这个领域相关的技术以代码、文档、模型等方
式固化下来。

详细介绍一下框架的作用

一、框架要解决的问题

框架要解决的最重要的一个问题是技术整合的问题,在J2EE的框架中,有着各种各样的技术,不同的软件企业需要从J2EE中选择不同的技术,这就使得软件企业
最终的应用依赖于这些技术,技术自身的复杂性和技术的风险性将会直接对应用造成冲击。而应用是软件企业的核心,是竞争力的关键所在,因此应该将应用自身的设计和具体的
实现技术解耦。这样,软件企业的研发将集中在应用的设计上,而不是具体的技术实现,技术实现是应用的底层支撑,它不应该直接对应用产生影响。

要理解这一点,我们来举一些例子:

一个做视频流应用的软件企业,他为电广行业提供整体的解决方案。他的优势在于将各种各样的视频硬件、服务器、和管理结合起来,因此他扮演的是一个集成商的角色。因此他
的核心价值在于使用软件技术将不同的硬件整合起来,并在硬件的整合层面上提供一个统一的管理平台。所以他的精力应该放在解决两个问题:

如何找到一种方法,将不同的硬件整合起来,注意,这里的整合并不是技术整合,而是一种思路上的整合。_首先要考虑的绝对不是要使用什么技术,而是这些硬件需要提供哪些
服务,需要以什么样的方式进行管理_。因此,这时候做的事情实际上是对领域进行建模。例如,我们定义任何一种硬件都需要提供两种能力,一种是统一的管理接口,用于对所
有硬件统一管理;另一种是服务接口,系统平台可以查询硬件所能够提供的服务,并调用这些服务。所以,设计的规范将会针对两种能力进行。

另一个问题是如何描述这个管理系统的规范。你需要描述各种管理活动,以及管理中所涉及的不同实体。因为管理系统是针对硬件的管理,所以它是构架在硬件整合平台之上的。

在完成业务层面的设计之后,我们再来看看具体的技术实现。光有规范和设计是不够的,我们还需要选择一个优秀的技术。由于是对不同硬件的整合,我们想到采用Java提供
的JMX技术。JMX技术适合用来进行系统整合,它定义了一个通用的规范,并给出了远程管理端口的一些默认实现。JMX已经经过了实践的检验,不少的应用服务器都采用
了以JMX为基础的结构,例如有名的JBoss。JMX已经是一个很好的开始了,但是我们还需要在JMX的基础上再做一些工作。

二、什么要用框架?

因为软件系统发展到今天已经很复杂了,特别是服务器端软件,设计到的知识,内容,问题太多。在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础工作,你只
需要集中精力完成系统的业务逻辑设计。而且框架一般是成熟,稳健的,他可以处理系统很多细节问题,比如,事物处理,安全性,数据流控制等问题。还有框架一般都经过很多
人使用,所以结构很好,所以扩展性也很好,而且它是不断升级的,你可以直接享受别人升级代码带来的好处。

框架一般处在低层应用平台(如J2EE)和高层业务逻辑之间的中间层。软件为什么要分层?为了实现“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易
于延展,易于分配资源…总之好处很多啦。

三、为什么要进行框架开发?

框架的最大好处就是重用。面向对象系统获得的最大的复用方式就是框架,一个大的应用系统往往可能由多层互相协作的框架组成。

由于框架能重用代码,因此从一已有构件库中建立应用变得非常容易,因为构件都采用框架统一定义的接口,从而使构件间的通信简单。

框架能重用设计。它提供可重用的抽象算法及高层设计,并能将大系统分解成更小的构件,而且能描述构件间的内部接口。这些标准接口使在已有的构件基础上通过组装建立各种
各样的系统成为可能。只要符合接口定义,新的构件就能插入框架中,构件设计者就能重用构架的bsp;
框架还能重用分析。所有的人员若按照框架的思想来分析事务,那么就能将它划分为同样的构件,采用相似的解决方法,从而使采用同一框架的分析人员之间能进行沟通。

采用框架技术进行软件开发的主要特点包括:

  • 领域内的软件结构一致性好
  • 建立更加开放的系统
  • 重用代码大大增加,软件生产效率和质量也得到了提高
  • 软件设计人员要专注于对领域的了解,使需求分析更充分
  • 存储了经验,可以让那些经验丰富的人员去设计框架和领域构件,而不必限于低层编程
  • 允许采用快速原型技术
  • 有利于在一个项目内多人协同工作
  • 大粒度的重用使得平均开发费用降低,开发速度加快,开发人员减少,维护费用降低,而参数化框架使得适应性、灵活性增强

四、与框架相关的概念

1\. 白盒与黑盒框架

框架可分为白盒(White-Box)与黑盒(Black-Box)两种框架。

基于继承的框架被称为白盒框架。所谓白盒即具备可视性,被继承的父类的内部实现细节对子类而言都是可知的。利用白盒框架的应用开发者通过衍生子类或重写父类的成员方法来开发系统。子类的实现很大程度上依赖于父类的实现,这种依赖性限制了重用的灵活性和完全性。但解决这种局限性的方法可以是只继承抽象父类,因为抽象类基本上不提供具体的实现。白盒框架是一个程序骨架,而用户衍生出的子类是这个骨架上的附属品。

基于对象构件组装的框架就是黑盒框架。应用开发者通过整理、组装对象来获得系统的实现。用户只须了解构件的_外部接口_,无须了解内部的具体实现。另外,组装比继承更为灵活,它能动态地改变,继承只是一个静态编译时的概念。

在理想情况下,任何所需的功能都可通过组装已有的构件得到,事实上可获得的构件远远不能满足需求,有时通过继承获得新的构件比利用已有构件组装新构件更容易,因此白盒
和黑盒将同时应用于系统的开发中。不过白盒框架趋向于向黑盒框架发展,黑盒框架也是系统开发希望达到的理想目标。

2\. 热点、食谱以及好莱坞原则

成功的框架开发需要确定领域专用的“热点” (Hot spot)。应用开发者在框架的基础上进行开发,只须扩展框架的某些部分,_“热点”就是在应用领域的一种扩展
槽,开发者根据自己的需要填充这些扩展槽_。“热点”使框架具有灵活性,如在具体的实现中,扩展槽可以被看成是一些抽象类,开发者通过重写抽象方法获得具体实现。

“食谱” (Cookbook)就是描述如何使用框架方法的文档。在“食谱”中包含了许多“烹饪”方法,这些“烹饪”方法相当于一些具体的操作步骤,描述了为解决某一
专门问题如何使用框架的详细方法。框架的内部设计和实现细节通常不出现在“食谱”中。

框架的一个重要特征就是用户定义的方法经常被框架自身调用,而不是从用户的应用代码中调用。这种机制常称为“好莱坞原则”(Hollywood
Principle)或“别调用我们,我们会调用您”。

又搞了一天才搞好,主要原因是没看懂错误提示信息。

webhook:简单地说,使用webhook可以让每次在本地push到github上去后,让服务器自动pull下来,这样就不用每次提交然后手动pull的过程了。

下面是详细配置过程(基于laravel):

  1. 首先,得在laravel里添加一个路由,然后指向某个文件,例如路由为’/webhook’,然后指向PHP文件为’webhook.php’,然后在该文件内添加如下内容,该文件即是服务器响应Github webhook请求的文件:

     <?php
         $dir = '/var/www/test';
         echo shell_exec("cd /var/www/test");
         echo shell_exec("sudo git pull -f git@github.com:haoflynet/guake.git 2>&1");
     ?>
    
  2. 用户权限问题,由于执行该PHP文件的用户是apache的默认用户www-data,所以我在上面的命令中使用的是’sudo’,这样可以不用给www-
    data用户生成ssh,然后又出现各种混乱的问题。但是www-
    data又怎么免输密码执行git命令呢,还好linux提供了这么一个方面的操作,在’/etc/sudoers’文件里有如下内容:

     # User privilege specification
     root ALL=(ALL:ALL) ALL  # 默认的root用户可以在所有平台使用所有权限
     www-data ALL=NOPASSWD:/usr/bin/git # 这里给www-data赋予免密码执行git的权限
    
  3. 在仓库中添加钩子,执行vim .git/hooks/post-receive,然后添加如下内容,最后还要加入可执行权限:

     GIT_WORK_TREE=/var/www git checkout -f
    
  4. 在Github上进行设置

    如图所示添加webhook,设置中可以选择各种出发事件event,一般默认是push事件,在Payload
    URL中设置自己刚才所设置的URL,最后添加即可完成。

  5. 测试
    测试钩子的时候,可以直接在Github上面看到执行的列表,还可以看到每一个POST的详细信息以及响应信息:

TroubleShooting:

  • Laravel 5中如果要设置webhook,该POST路由不能使用CSRF

  • Django同样要禁用csrf:

      import os
      from django.views.decorators.csrf import csrf_exempt
      from django.http import HttpResponse
    
      @csrf_exempt
      def webhook(request):
          os.system('cd /var/www/admin')
      os.system('git pull -f git@github.com:haoflynet/admin.git 2>&1')
          return HttpResponse('ok')
    

Android API Levels版本

Android Studio 的使用

Gradle和Gradle Plugin对应关系

生成APK/ABB文件

  • 生成APK文件: Build -> Build Bundles/APK(s) -> Build APK(s)

  • 生成能上传到google playstore的签名了的bundle文件(.abb格式)

    • Build -> Generate Signed Bundle/APK,生成的时候记得勾选Export encrypted key for enrolling published apps in Google Play App Signing(导出的key的格式是pepk)

    • 如果是第一次上传,可以点击Create new生成一个新的keystore文件(一般会以.keystore或者.jks结尾)

    • 一个keystore可能包含多个alias的key,可以多次点击Create new来生成即可

    • 如果之前没有上传过google play console,那么在console里面直接新建一个release即可,不用先上传key这些

    • 如果之前已经上传了upload key certificate到google play console里面,那么必须用之前的来生成才行,否则上传会提示SHA-1指纹不一致。可以在后台查看Setup -> App Integrity -> Upload key certificate看是否有了,注意这里的Download certificate只是下载公钥,没啥用的。

    • 如果已经有上传证书并且丢失了的话,只能联系google重新生成一个了(注意是Upload key certificate,而不是App signing key certificate),在这里提交或者页面的contact our support team,下面有一些问题

      • Is your app enrolled in Play App Signing by Google Play? 选择Yes,然后选择I have an upload key-related issue,然后选择I lost my upload key,选这个。会提示你生成一个新的.pem格式的文件,并且会直接给你生成的命令

        1
        2
        keytool -genkeypair -alias upload -keyalg RSA -keysize 2048 -validity 9125 -keystore keystore.jks # 这条命令其实就是在Android studio里面Create new的功能
        keytool -export -rfc -alias upload -file upload_certificate.pem -keystore keystore.jks # 会生成PEM文件的
      • Is your app enrolled in Play App Signing by Google Play? 选择No,然后选择I lost my upload key,不要选这个,google说它不能重置,你必须创建新的app

    • 获取keystore的sha-1指纹: keytool -list -v -keystore {keystore_name} -alias {alias_name}

物理设备镜像到电脑

Settings -> Tools -> Device mirroring -> Enable mirroring of physical Android devices

模拟器里面实现震动

  • 目前能找到能行的方法只有在模拟器设置里面选择Virtual sensors -> Move,然后拖动X、Y、Z三个轴移动,不能太快也不能太慢

Google Play Console的使用

  • 即使google play console审核通过了,且也被邀请加入测试了,也要等很久才能在app store里面搜索得到并且下载,可以直接搜索的
  • Activity log中可以查看最近的操作日志
  • 审核被拒: Please provide login credentials: 需要在Policy -> App content -> App access -> Manage 中添加Login Credentials
阅读全文 »

写这篇文章源于年前一个程序爱好者的QQ群里的一次讨论,很想说说自己的想法。以下是我们的聊天内容(本来想直接上传截图的,但是太小看不清,所以一字一字敲出来吧)

A:家里人给介绍一个工作,在一个效益很好的单位干文职
A即是主人公
A:我想,我以后不会再做程序方面的工作了
A:…可是我还是对PHP比较有兴趣
A:for($i=╯﹏╰; $i < 999999; $i++){ echo $i;}
B:这种工作应该不会太忙吧
我上场啦
A:是的
B:还不是可以在闲暇的时候写代码
A:家里人都想找个稳定的
让他们去找呗。。。
A:..我觉得 如果爱好 就要一心一意的
不同意,反正我爱好很多,如果一辈子只有一个爱好那多无聊呀
A:想了很久 真想 破釜沉舟!!
破釜沉舟这个词我是去年在决定不考研的时候用过,当时一下就把所有的书给卖了
C:那么问题来了
A:妈的 就是 不敢
D:。。。
C:到底应该是践行自己的目标呢?还是开一辈子挖掘机
不是蓝翔毕业能开好挖掘机吗,哈哈
D:亮点是不敢
有的人怕的是违反父命,有的人怕的是未知的人生
B:无法理解你们已经工作了的人
由于大人总是说我幼稚,所以有可能确实是我没去社会无法理解你们
A:要是敢了 我也不会这么说…
如果真的觉得自己不能勇敢一次,就没必要说出来了
A:现实!
A:马勒个B
E:我父母当年安排进入事业单位,年少轻狂,坚决不去,结果现在惨兮兮。。。
这是重点任务,只有经历过的,才有资格说话
D:不能反抗 那你就躺好姿势享受就是了
生活就像强奸,要么反抗要么就去享受;工作就像轮奸,您不行就让别人上 ;社会就像自慰,所有的都要靠自己的双手来解决
A:我不能说,如果我继续干PHP,以后能怎么样怎么样,但是现在就是选择
我知道,他们会说如果按照他们的干,以后能怎么样怎么样
A:我就是知道这些,才特别特别的纠结
A:就是选择if else,执行了,if下面就跟你没关系了
D:呵呵
E:惨兮兮怎么了,好歹自己选的
A:你后悔么?如果让你再选你怎么选?
E:有时候哭起来,说不后悔是假的。但是心里还是觉得,自己能选择还是好
这句话说到我心坎里来了
F:我就是听从了父母的安排,每天就像在等死一样!现在觉得我不能再坐以待毙!才开始自学网页方面的知识,虽然我不了解网页!但是我有学习的能力!
另一个例子出来了,无论怎样,看样子他已经找到了自己想要走的路了

虽然我还在上大学,但我已经体会到生活中有各种各样的if else,很多情况,我们只能选择一条路。但无论选择哪一条路,未来都是未知的,只是从过往的经验来看,总
有那么一条路要诱人一点。选择诱人的那条路无可厚非。但别人的现在并不一定会成为你的未来,既然都是未知的,为何不选择自己喜欢的那条路呢,既然都可能会后悔,你想想
选择哪一条路会更后悔呢?下面是罗伯特·弗罗斯特的一首诗,我们应该都听过:

黄色的林子里有两条路
很遗憾我无法同时选择两者
身在旅途的我久久站立
对着其中一条极目眺望
直到它蜿蜒拐进远处的树丛

我选择了另外的一条,天经地义
也许更为诱人
因为它充满荆棘,需要开拓
然而这样的路过
并未引起太大的改变

那天清晨这两条小路一起静卧在
无人踩过的树叶丛中
哦,我把另一条路留给了明天
明知路连着路
我不知是否该回头

我将轻轻叹息,叙述这一切
许多许多年以后:
林子里有两条路,我——
选择了行人稀少的那一条
它改变了我的一生

记得以前在wamp设置虚拟目录的时候都还挺简单的,干脆直接没有记录下来,但这次遇到一些坑,特记录于此。

系统环境:Windows 7 + WampServer2.5

注意事项:下面的步骤一定要参考其完整路径,切勿直接在资源管理器里进行搜索,因为在C:\wamp\bin\apache\apache2.4.9\conf目录下居然有一个original,应该是用来保存最原始的配置信息,对这里面的配置文件进行的更改并不会影响当前的使用。

配置过程:

  1. 首先在C:\\wamp\\bin\\apache\\apache2.4.9\\conf\\httpd.conf目录中找到下面两行:

    1
    2
    # Virtual hosts
    Include conf/extra/httpd-vhosts.conf # 去掉这一行的注释
  2. 然后仍然在该文件中找到监听端口的配置

    1
    2
    3
    4
    # Listen 12.34.56.78:80
    Listen 0.0.0.0:80
    Listen [::0]:80
    Listen 8080 # 这一行是自己添加的,我想要它监听8080端口
  3. 将vhost的配置文件C:\\wamp\\bin\\apache\\apache2.4.9\\conf\\extra\\httpd-vhosts.conf修改为如下(去掉原来的):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <VirtualHost *:80>
    DocumentRoot "c:/wamp/www"
    ServerName localhost
    ServerAlias localhost

    <Directory "c:/wamp/www">
    AllowOverride All
    Require local
    </Directory>

    </VirtualHost>
TroubleShooting
  • wamp局域网403 Forbidden解决方法: 出现在我想使用手机通过电脑分享的wifi网络访问电脑中的wamp服务。其实和linux一样,只是linux使用Allow from all,而windows使用Require all granted。解决方法如下:
    1.首先找到wamp的apache配置文件目录,我的在C:\\wamp\\bin\\apache\\apache2.4.9\\conf\\http.conf,然后打开它后找到,如下几行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # Each directory to which Apache has access can be configured with respect
    # to which services and features are allowed and/or disabled in that
    # directory (and its subdirectories).
    #
    # First, we configure the "default" to be a very restrictive set of
    # features.
    #
    <Directory />
    AllowOverride All
    #Order Deny,Allow # 注释掉
    Require all granted # 添加这一行
    </Directory>

    <VirtualHost *:8080>
    DocumentRoot "f:/workspace/laravel/public"
    ServerName localhost
    ServerAlias localhost
    <Directory "f:/workspace/laravel/public">
    AllowOverride All
    Require local
    </Directory>
    </VirtualHost>

    然后重启Apache即可