Python如何安装cv2模块

通常由俩种方式,一种使用pip,无需选择opencv版本;一种手动选择opencv版本,使用whl文件下载

一. pip自动下载

pip install opencv-python

大多数的情况下,是可以的安装成功CV2,可是有时,这个指令安装的pip会出现CV2版本与python安装的版本,不匹配导致,你安装的opencv不成功,由于国外的网路问题,可以加入 -i 指定国内源

清华:https://pypi.tuna.tsinghua.edu.cn/simple

阿里云:https://mirrors.aliyun.com/pypi/simple/

中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/

华中理工大学:http://pypi.hustunique.com/

山东理工大学:http://pypi.sdutlinux.org/

二. 下载whl文件,手动选择opencv版本

1.先更新pip

python -m pip install --upgrade pip

2.从清华源中选择对应的opencv版本,中下载其他版本的库,因为根据安装的python 版本下载相应的镜像文件
比如 python 3.5.4 64bit
我下载的是

opencv_python-3.1.0.0-cp35-cp35m-win_amd64.whl

3.下载完成后,在whl文件对应的目录下,cmd 到whl对应文件目录下,执行

pip install opencv_python-3.1.0.0-cp35-cp35m-win_amd64.whl

结语

针对python cv2安装遇到的安装失败问题,提出多种方法,进行实验,本文的方法参考网页的方法,进行汇总。

You-get安装方法及使用教程

you-get爬虫,依赖于Python3.10,可以爬取网页无法下载的视频文件,具体步骤如下:
1,下载Python3.10无脑下一步安装
python语言生态非常繁荣,有大量使用python开发的工具。you-get 只是其中一个。关注我后续会介绍更多好用的 python 工具。
2,新建一个空白文件夹,清空地址栏输入cmd后回车打开“命令指示符”
3,输入以下字符下载you-get模块

pip install you-get

显示 Successfully installed you-get 字样即表示 you-get 安装成功。

这里解释一下,上述命令行 -i 后面的值代表使用清华大学的软件源,下载安装速度会比较快。
4,打开浏览器,复制视频所在地址链接
5,输入“you-get http://视频链接”

you-get https://www.bilibili.com/video/BV1st4y1D771?spm_id_from=333.851.b_62696c695f7265706f72745f64616e6365.7

记住删除地址后面.recommand后缀

另外如果是批量下载一个视频列表,可以在上述命令后加上 ” –playlist”, 例如:

you-get https://www.bilibili.com/video/BV1st4y1D771?spm_id_from=333.851.b_62696c695f7265706f72745f64616e6365.7 --playlist

 

6,下载的问题,you-get 帮你解决了,you-get下载的视频一般挺大,需要一个外置存储设备。我用的是闪迪 500 GB的移动固态硬盘,感觉比移动机械硬盘小巧很多,速度快,而且是 type-c,插我的 Macbook Pro 简直完美,不用转接线。

Protocol Buffers 短暂介绍

protobuf简介

protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。

protocol buffer初步使用

下面是一个简单的使用的例子:
首先需要定义一个.proto文件,其中需要定义你希望操作的对象的结构。

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;
 
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
 
  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
 
  repeated PhoneNumber phone = 4;
}

保存为person.proto文件,之后下载protoc编译工具,并解压,使用PB将proto文件生成java类。

protoc.exe --java_out=. person.proto

在指定的java_out目录下就可以生成java对应的类,这里生成了PersonOuterClass.java。将文件放入项目,并引入PB的jar包。

compile group: 'com.google.protobuf', name: 'protobuf-java', version: '3.1.0'

接下来就可以使用PB进行对象的操作了。

public static void main(String[] args) throws Exception{
        //创建对象
        PersonOuterClass.Person p= PersonOuterClass.Person.newBuilder().setName("my_name").setId(2).build();
        System.out.print(p.toString());
        //序列化对象
        FileOutputStream fos=new FileOutputStream("D://person");
        p.writeTo(fos);
        fos.close();
        //反序列化对象
        FileInputStream fis=new FileInputStream("D://person");
        PersonOuterClass.Person pread=PersonOuterClass.Person.parseFrom(fis);
        System.out.println(pread.toString());
        fis.close();
 }

与java原生的serializable接口进行比较

public class JavaPerson implements Serializable{
       public String name;
       public Integer id;
       public String email;
        
       public enum PhoneType{
           MOBILE,HOME,WORK
       }
       public class PhoneNumber{
           public String number;
           public PhoneType type;
       }
       List phone;
        
       public static void main(String[] args) throws Exception{
           JavaPerson jp=new JavaPerson();
           jp.name="my_name";
           jp.id=2;
 
           FileOutputStream fileOut = new FileOutputStream("d://person.ser");
           ObjectOutputStream outStream = new ObjectOutputStream(fileOut);
           outStream.writeObject(jp);
           outStream.close();
           fileOut.close();
 
 
           FileInputStream fileIn =new FileInputStream("d://person.ser");
           ObjectInputStream in = new ObjectInputStream(fileIn);
           jp = (JavaPerson) in.readObject();
           in.close();
           fileIn.close();
       }
   }

运行比较结果:

PB较java默认形式代码更简洁。
循环运行10000次序列化和反序列化后PB比java快约30%。
生成的文件PB有11字节而java有215字节。
PB提供额外的字段校验支持。

SwiftAdmin 极速开发框架

开发环境:Windows服务器版 VScode Apache MySQL5.7 PHP7 – PHP8

基于ThinkPHP Layui 完美契合,在开发上采用最精简最高效的做法去完成业务系统的需求,是一款优秀的中后台极速开发解决方案。

开发初衷


  1. SAPHP框架的开发,主要是为了减少在自己开发过程中的频繁造轮子,并且SAPHP框架主张简单就是高效的原则,所以最简单的东西才是效率最高的,可能你的应用场景很复杂,但是SAPHP足够应付!
  2. 在最开始接触互联网的时候,都是用一些开源的CMS系统制作自己的网站,后期因为扩展和二次开发的问题,导致觉得很多东西并不是那么简单易用,比如后台的很多JS代码封装的不是很好,而且界面可操作性很差,所以自己开发这款框架封装了很多常用的特性,足以满足日常后台的开发需要,在使用的过程中你会发现,SAPHP框架里面用的最多的是属性而不是对象,一是为了在书写HTML标签的时候方便。二是为了和layui本身区分开!这样让你更容易在这个上面进行扩展!
  3. 系统默认从基础控制器继承了增删改查操作。但这种方式并不适合大多数硬性的应用场景和逻辑需求,你可能在后期需要摈弃大多数利用了一键CURD的方法进行重载函数,虽然SAPHP框架里面也有,但框架的设计初衷是为了在易用性和操作性上折中找一个方案来做,当前基于第一个版本的SAPHP框架在这方面的表现还不是特别好。但随着应用场景检验和优化,本框架会逐步的进行完善和提高性能!
  4. 在市面上目前的开源极速开发框架的学习成本略高,想搞一个学习成本极低,但性能不低的框架(CMS系统)!
  5. 想着开发一款底层设计配置和应用分开的系统,这样对于很多小白用户不会在项目已经上线运行中的时候,误操作系统的配置导致数据丢失,错乱的问题。比如有些字段需要手动在数据库进行修改。

侧重点


  • SAPHP的架构和开发更倾向于内容管理系统[CMS]的方向,当然你也可以当中API系统使用
  • 系统默认的缓存机制为redis缓存,所以请确保安装redis扩展和服务器[摒弃操蛋的file缓存吧]
  • 如果你只是需要一个极简的API管理系统,那么建议你删除不需要的模块和菜单项!
  • 会侧重于SEO优化、客户管理、流量管理、蜘蛛池、区块链以及采集方面的应用!!!
  • 坚持偏向于社区版开源的方向,主要由社区共同的爱好者免费开发维护插件!!!
  • SAPHP已经上线插件市场🔧,适用于中小型企业采购付费商业版插件的使用!!
  • 本框架特别适合个人开发者和小型创业公司,找一款真正适合自己的框架不容易,所以先来试试SAPHP吧!

框架优势


  • 代码量最少最精简、逻辑简单清晰
  • 参考官方文档,只需会PHP JS 开箱即用
  • 界面基于ant design设计 [可操作性强]
  • 控制器与栏目管理双鉴权,满足日常大部分需求
  • 前端JavaScript鉴权,后端AUTH类鉴权,减少请求
  • 封装常用组件和快捷属性,小白即可快速二次开发
  • 支持全文索引XS/ElasticSearch轻松支持PB级数据
  • 通用型thinkPHP插件开发架构,可轻松迁移其他插件
  • 代码安全质量高,修复大部分低危、高危代码漏洞
  • 高占比AJAX数据调用,响应速度可媲美前后端分离

集成功能


  • [√] API模块 支持token鉴权,支持细分规则
  • [√] CMS模块 系统内容CMS模块,搭配模板,开箱即用
  • [√] 用户管理 用户是系统操作者,该功能主要完成系统用户配置。
  • [√] 公司管理 设置公司常用信息,前端标签调用
  • [√] 部门管理 配置系统组织机构(部门、小组),树结构展现支持数据权限。
  • [√] 岗位管理 配置系统用户所属担任职务。
  • [√] 菜单管理 配置系统菜单,操作权限,按钮、栏目等权限标识等。
  • [√] 角色管理 角色菜单权限分配、设置角色按机构进行数据范围权限划分。
  • [√] 插件管理 可开发定制属于自己的插件,可安装升级社区插件!!!
  • [√] 导航管理 支持导航定制,小分类导航配置适合SEO
  • [√] 内容管理 系统默认模型数据已完成后端数据录入,可快速二次开发!!!!
  • [√] 广告管理 运营必选功能,获取广告代码自动校验过期时间
  • [√] 数据字典 对系统中经常使用的一些较为固定的数据进行维护,并使用自定义标签交互
  • [√] 操作日志 用户后台操作日志,全局异常、SQL注入等记录
  • [√] TAG过滤 支持违规词、敏感词配置
  • [√] 短信平台 支持阿里云、腾讯云短信发送
  • [√] 附件上传 支持FTP、阿里云、腾讯云OSS附件上传
  • [√] 全文检索 支持XunSearch、ElasticSearch集群PB级全文检索
  • [ ] 服务监控 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。
  • [ ] 定时任务 在线(添加、修改、删除)任务调度包含执行结果日志。
  • [ ] 代码生成 前后端代码的生成(php、html、layui、sql)支持一键CRUD 。

Golang框架选型比较: goframe, beego, iris和gin

由于工作需要,这些年来也接触了不少的开发框架,Golang的开发框架比较多,不过基本都是Web”框架”为主。这里稍微打了个引号,因为大部分”框架”从设计和功能定位上来讲,充其量都只能算是一个组件,需要项目使用的话得自己四处再去找找其他的组件,或者自己造轮子。如果用于Web开发,这些”框架”的Web开发能力均已完备,无太大差别,且均是自标准库net/http.Server的二次封装。由于框架众多,这里笔者只选择了几个曾做过技术选型评估、较为熟悉,且目前比较流行和典型的Golang”框架”,从适用于业务项目开发框架的角度,做一个简单的横向比较,以便大家在项目框架选型时做个参考。

评估指标

指标
说明
指标
说明
基本介绍 来源各自官网。
模块化设计 是否支持模块化插拔设计、模块之间低耦合设计,是否可以独立使用其中某部分组件。
模块完善度 框架提供的功能模块是否丰富。模块能否能覆盖日常普遍的开发需求。
使用易用性 易用性不仅仅是值框架好不好用,更多是团队能否在低成本下快速接入,长期来看能否低成本维护。
文档完善性 参考官网提供的介绍资料,包括但不限于:文档、视频、示例、案例资料。同时,本地中文文档支持也是参考项。
工程化完备 是否能够快速接入项目开发,是否提供项目接入规范、设计模式、开发工具链,文档是否完善、源码是否易读、是否便于长期维护。
开发模式 框架适用的开发模式,或者官方推荐的开发模式。
工程规范 项目接入时的开发规范,如目录规范、设计规范、编码规范、命名规范等。
社区活跃 官方与社区沟通是否便捷,问题是否能够快速解答,BUG是否能够快速响应处理。
开发工具链 项目开发时使用到的CLI开发工具,如初始化项目、交叉编译、代码生成、swagger、热编译能力等等。
Web: 性能测试 来源第三方评测 https://github.com/the-benchmarker/web-frameworks 。
Web: 路由冲突处理 存在路由注册冲突时有无良好的解决方案,在业务项目开发中比较常见。
Web: 域名支持 Web路由是否支持域名绑定,甚至多域名的绑定。
Web: 文件服务 Web服务是否提供静态资源的访问能力。
Web: 优雅重启/关闭 Web服务在重启时不会影响请求执行,关闭时会等待正在执行的请求处理完,新请求不再接入。
ORM 框架是否自带ORM组件,ORM组件是业务项目的核心组件。无论是自研还是通过第三方组件引入。
Session 框架是否提供会话管理组件,无论是通用型Session组件,还是仅针对于Web服务的Session组件。
I18N支持 国际化组件支持(常用但非核心组件)。
配置管理 配置管理也是框架需要完备的核心组件能力。
日志组件 日志组件也是框架需要完备的核心组件能力。
数据校验 数据校验也是框架需要完备的核心组件能力。
缓存管理 缓存管理也是框架需要完备的核心组件能力。无论是内存还是Redis,无论是自研还是通过第三方组件引入。
资源打包 支持将依赖的文件资源例如静态资源、配置文件等固定文件编译到可执行文件中。框架组件自动支持资源检索。
链路跟踪 框架是否具备分布式链路跟踪能力,分布式跟踪在微服务架构中是必不可少的能力。
测试框架 框架是否支持单元测试接入,提供单元测试接入规范。无论是使用标准库还是第三方测试框架。
突出优点 比较明显的几点优点。
突出缺点 比较明显的几点缺点。

横向比较

  • 以下部分对比参数涉及评分的部分,满分总共按照10分为标准。
  • 如果标记为”-“的部分,表示不支持或者需要引入第三方插件支持。
  • 以下特性如果官网提供文档则直接提供文档地址,找不到文档但是笔者知道有就会简单标注。
  • 各个”框架”功能特性实现不同,在文档、功能、易用性上存在较大差异,各位朋友可自行查阅链接。

 

GoFrame
Beego
Iris
Gin
比较版本 v1.15.2 v1.12.3 v12.0.2 v1.6.3
项目类型 开源(国内) 开源(国内) 开源(海外) 开源(海外)
开源协议 MIT Apache-2 BSD-3-Clause MIT
框架类型 模块化框架 Web框架 Web”框架” Web”框架”
基本介绍 工程完备、简单易用,模块化、高质量、高性能、企业级开发框架。 最简单易用的企业级Go应用开发框架。 目前发展最快的Go Web框架。提供完整的MVC功能并且面向未来。 一个Go语言写的HTTP Web框架。它提供了Martini风格的API并有更好的性能。
项目地址 github.com/gogf/gf github.com/beego/beego github.com/kataras/iris github.com/gin-gonic/gin
官网地址 goframe.org beego.me iris-go.com gin-gonic.com
模块化设计
模块完善度 10 6 4 2
使用易用性 9 9 9 10
文档完善度 10 8 6 4
工程化完备 10 8 5 1
社区活跃 9 10 9 10
开发模式 模块引入三层架构、MVC MVC MVC
工程规范 分层设计对象设计 项目结构
开发工具链 gf工具链 bee工具链
Web: 性能测试 8 8 8 9
Web: HTTPS HTTPS & TLS 支持 CustomHttpConfiguration 支持
Web: HTTP2 支持 支持
Web: WebSocket WebSocket服务
Web: 分组路由 路由注册-分组路由 Namespace GroupingRoutes
Web: 路由冲突处理
Web: 域名支持 域名绑定
Web: 文件服务 静态文件服务 静态文件处理 ServingStaticFiles
Web: 多端口/实例 多端口监听多实例监听 RunMultipleServiceUsingIris
Web: 优雅重启/关闭 平滑重启特性 热升级 GracefulShutdownOrRestart GracefulRestartOrStop
ORM ORM文档 ORM文档
Session Session Session
I18N支持 I18N I18N Localization
模板引擎 模板引擎 View设计 TemplateRendering HtmlRendering
配置管理 配置管理 参数配置 CustomHttpConfig
日志组件 日志组件 Logging
数据校验 数据校验 表单数据验证 CustomValidators
缓存管理 缓存管理 Cache
资源打包 资源管理 bee工具bale命令
链路跟踪 链路跟踪
测试框架 单元测试 Testing Testing
突出优点 goframe主要以工程化和企业级方向为主,特别是模块化设计和工程化设计思想非常棒。针对业务项目而言,提供了开发规范、项目规范、命名规范、设计模式、开发工具链、丰富的模块、高质量代码和文档,社区活跃。作者也是资深的PHP开发者,PHP转Go的小伙伴会倍感亲切。 beego开源的比较早,最早的一款功能比较全面的Golang开发框架,一直在Golang领域有着比较大的影响力,作者谢大多年组织着国内影响力比较大GopherCN活动。beego有着比较丰富的开发模块、开箱即用,提供了基于MVC设计模式的项目结构、开发工具链,主要定位为Web开发,当然也可以用于非Web项目开发。 iris主要侧重于Web开发,提供了Web开发的一系列功能组件,基于MVC开发模式。iris这一年发展比较快,从一个Web Server的组件,也慢慢朝着beego的设计方向努力。 gin专注于轻量级的Web Server,比较简单,易于理解,路由和中间件设计不错,可以看做替代标准库net/http.Server的路由加强版web server。献给爱造轮子的朋友们。
突出缺点 开源时间较晚,推广过于佛系,目前主要面向国内用户,未推广海外。 起步较早,自谢大创业后,近几年发展较慢。非模块化设计,对第三方重量级模块依赖较多。 号称性能最强,结果平平。非模块化设计。最近两年开始朝beego方向发展,但整体框架能力还不完备,需要加油。 功能简单易用,既是优点,也是缺点。

经验分享

不同的需求场景,存在不同的选择。选择适合的工具,解决适合的问题。

开源不存在孰好孰坏之分,开源作者能够本着开源精神给大家分享技术成果用以学习和使用,这本身就是一件非常不易并且值得称道的事情。

最后,笔者在这里跟大家分享一下自己所在团队的情况,以及在Golang技术栈转型过程中所走的弯路,希望能在框架选型这一环节,能给大家作一定参考。

团队最初痛点

团队转型Golang技术栈的一些背景。主要几点:

  1. 团队后端最初的主要技术栈为PHP,由于业务发展需要,进行微服务改造。第一版微服务采用了PHP+JsonRpc的通信方式。
  2. 随着项目增多,公司也组件了自己的DevOps团队,底层部署转向了Docker+Kubernetes容器架构,并且引入了Golang技术栈。
  3. 由于一些痛点,通过一段时间对PHPGolang的比较,团队决定快速转型Golang技术栈,主要痛点如下:
    1. PHP项目在业务复杂后、项目中后期的开发和维护成本整体偏高。主要原因还是其过高的灵活性,非结构化的变量设计,参差不齐的开发人员素质。
    2. 上云容器化部署后,PHPDevOps效率太低。复杂的Composer版本管理,超大的Docker镜像大小,都影响着DevOps的效率。相比较而言,Golang效率极其高效。
    3. JsonRpc通信协议设计下,接口的扩展性和灵活性很高,但服务之间很难快速确定接口的输入与输出定义,只能根据文档和示例进行对接和维护。由于代码和文档分离,大部分场景下接口文档维护往往滞后于接口变化。随着服务的不断增加,非结构化的通信协议管理使得服务接口的开发和维护成本进一步提高。
    4. JsonRpc的通信协议本质基于HTTP1.x+Json,执行效率过低,算不上真正的微服务通信协议,很难对接上主流的服务治理框架。相比较基于HTTP2.xgRPC协议有着成熟微服务开发框架和服务治理解决方案。
    5. 业务梳理的考量,PHPGolang技术栈的迁移,其实也是一次技术重构的契机,在技术重构的过程中也重新梳理业务系统设计,偿还技术债务。

进一步的痛点

Golang确实足够简单,相比较其他的解释类开发语言,没有过多的语法糖和语言特性,因此团队上手很快,并快速完成了一部分业务系统的技术重构。但随之而来的是更加严重的痛点。主要几点:

  1. 轮子过多:Golang实在太简单了,以至于我们的团队成员爆发了压抑许久的闷骚劲,充分发挥”造后不管”的造轮精神,开发/封装了许多大大小小的轮子。这些轮子均能满足最基本的功能,例如:日志、配置、缓存等等。但轮子并不是实现一个基础功能的半成品就了事,需要保证功能性、稳定性、扩展性和可维护性,要能结合更多生产实践验证,更需要能够长期维护、持续进行迭代改进。否则,就是一堆大小不一的成人玩具。造轮一时爽,维护火葬场。直到现在,我们还在为分散在100多个Golang项目中的数十个成人玩具做大统一的事情痛苦不已。当然,这个问题也跟组织架构和团队管理也有很大关系。
  2. 不成体系:
    1. 我们坚信一个package只做一件事情,并且特地使用单仓库包的形式进行包管理,相当于每个package都是独立维护的git仓库。其实单仓库包package设计并不存在必要性,反而独立的单仓库包提高了组件和框架的维护成本。
    2. 这种单仓库包设计难以形成技术体系,在团队技术管理上,难以形成统一的技术框架。单仓包显得很孤立,而一个技术体系的建立除了需要制定规范和标准,更需要技术框架来准确落地。一个成体系的、统一的技术框架,至少涉及到数十个基础技术组件,不可能完全孤立设计。每一个package的基础功能实现都很简单,但是如何能够统一组织在一起却不是一件简单的事情,这需要团队的技术管理者需要有一定的技术底蕴、格局和前瞻性,而不是和普通开发者那样眼界只能局限于package本身。
    3. 这种孤立的单仓库包设计,对于业务项目的规范化约束不强,每一个组件都可以独立替换,也至于痛点1的问题越发严重(连日志组件都好几套,虽然都满足基本的日志规范设计)。最终,我们最初引以为傲的单仓库包设计,最终变成了一堆散沙。例如,就连需要增加标准化的链路跟踪功能,由于单仓库包过于散乱和不统一,使得推进改进成本极其高昂。
    4. 除了使得技术体系难以建立,技术规范难以准确落地之外,每个组件的易用性也设计得较差。举个简单例子,我们的日志组件、缓存组件、数据库组件、HTTP/gRPC Server组件都需要对接配置管理功能,单仓包需要保证低耦合设计,因此开发者在使用的时候需要先手动读取配置、并转换为目标配置对象、并注入到对应的组件初始化方法中,随后才能将该对象运用到业务逻辑中,若干个业务项目均是重复此步骤。其实goframe在这块的易用性设计就挺不错,每个包当然是独立设计的,在统一的技术框架体系下,再独立提供一个耦合的单例模块将常用的对象进行单例化封装,自动实现配置读取、配置对象转换、配置对象注入及组件对象初始化,开发者仅需要调用一个单例方法即可。而这个常用单例模块,成为了我们技术框架体系的一部分,极大地提高了业务项目的开发和维护效率。
  3. 版本不一致:在业务项目不断增多之后,轮子版本不一致性也越来越明显。什么是版本不一致?举个例子。我们有个轮子叫做httpClient,总共发布了10来个版本;我们总共有100多个Golang项目,几乎每个版本都在使用。我们提交了一个bug fix,却难以让所有项目都能更新。对其他的轮子也是类似的情况,况且我们也有数十个各种轮子,被各个项目独立使用中。

经过反思总结,总结了以下几点:

  1. 团队需要一个统一的技术框架,而不是东拼西凑的一堆单仓库包。
  2. 我们只需要维护一个框架的版本,而不是维护数十个单仓库包的版本。
  3. 框架的组件必须模块化、低耦合设计,保证内部组件也可以独立引用。
  4. 核心组件严格禁止单仓库包设计,并且必须由框架统一维护。

最终的抉择

走过这么多弯路之后,我们决心建立一套成体系的Golang开发框架。除了要求团队能够快速学习,维护成本低,并且我们最主要的诉求,是核心组件不能是半成品,框架必须是上过大规模生产验证的,稳定和成熟的。随着,我们重新对行业中流行的技术框架做了技术评估,包括上面说的那些框架。原本的初衷是想将内部的各个轮子统一做一个成体系的框架,在开源项目中找一些有价值的参考。

后来找到了goframe,仔细评估和学习了框架设计,发现框架设计思想和我们的经验总结如出一则!

这里不得不提一件尴尬的事情。其实最开始转Golang之前(2019年中旬)也做过一些调研,那时goframe版本还不高,并且我们负责评估的团队成员有一种先入为主的思想,看到模块和文档这么多,感觉应该挺复杂,性能应该不高,于是没怎么看就PASS。后来选择一些看起来简单的开源轮子自己做了些二次封装。

这次经过一段时间的仔细调研和源码学习,得出一个结论,goframe框架的框架架构、模块化和工程化设计思想非常棒,执行效率很高,模块不仅丰富,而且质量之高,令人惊叹至极!相比较我们之前写的那些半成品轮子,简直就是小巫见大巫。团队踩了一年多的坑,才发现团队确实需要一个统一的技术框架而不是一堆不成体系的轮子,其实人家早已给了一条明光大道,并且一直在前面默默努力。

经过团队内部的调研和讨论,我们决定使用goframe逐步重构我们的业务项目。由于goframe是模块化设计的,因此我们也可以对一些模块做必要的替换。重构过程比较顺利,基础技术框架的重构并不会对业务逻辑造成什么影响,反而通过goframe的工程化思想和很棒的开发工具链,在统一技术框架后,极大地提高了项目的开发和维护效率,使得团队可以专心于业务开发,部门也陆续有了更多的产出。目前我们已经有大部门业务项目专向了goframe。平台每日流量千万级别。

最后,感谢开源作者们的默默贡献!我们也在努力推动团队秉着来源社区,回馈社区的思想,未来也会更多参与社区贡献。

Golang 微框架 Gin 简介

所谓框架

框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用,甚至有的时候,脱离了框架,一些开发者都不会写程序了。成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用,当这些方面都得心应手的时候,可以尝试改造一些框架,或是自己创造一个。

曾经我以为Python世界里的框架已经够多了,后来发现相比golang简直小巫见大巫。golang提供的net/http库已经很好了,对于http的协议的实现非常好,基于此再造框架,也不会是难事,因此生态中出现了很多框架。既然构造框架的门槛变低了,那么低门槛同样也会带来质量参差不齐的框架。

考察了几个框架,通过其github的活跃度,维护的team,以及生产环境中的使用率。发现Gin还是一个可以学习的轻巧框架。

Gin

Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活,容错方便等特点。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

在 Go语言开发的 Web 框架中,有两款著名 Web 框架分别是 Martini 和 Gin,两款 Web 框架相比较的话,Gin 自己说它比 Martini 要强很多。

Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json。总之在 Go语言开发领域是一款值得好好研究的 Web 框架,开源网址:https://github.com/gin-gonic/gin

首先需要安装,安装比较简单,使用go get即可:

go get gopkg.in/gin-gonic/gin.v1

Hello World

使用Gin实现Hello world非常简单,创建一个router,然后使用其Run的方法:

import (
    "gopkg.in/gin-gonic/gin.v1"
    "net/http"
)

func main(){
    
    router := gin.Default()

    router.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello World")
    })
    router.Run(":8000")
}

简单几行代码,就能实现一个web服务。使用gin的Default方法创建一个路由handler。然后通过HTTP方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把request和response都封装到gin.Context的上下文环境。最后是启动路由的Run方法监听端口。麻雀虽小,五脏俱全。当然,除了GET方法,gin也支持POST,PUT,DELETE,OPTION等常用的restful方法。

restful路由

gin的路由来自httprouter库。因此httprouter具有的功能,gin也具有,不过gin不支持路由正则表达式:

func main(){
    router := gin.Default()
    
    router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
    })
}
冒号:加上一个参数名组成路由参数。可以使用c.Params的方法读取其值。当然这个值是字串string。诸如/user/rsj217,和/user/hello都可以匹配,而/user//user/rsj217/不会被匹配。
curl http://127.0.0.1:8000/user/rsj217
Hello rsj217%                                                                 curl http://127.0.0.1:8000/user/rsj217/
404 page not found%                                                                curl http://127.0.0.1:8000/user/
404 page not found%
冒号:加上一个参数名组成路由参数。可以使用c.Params的方法读取其值。当然这个值是字串string。诸如/user/rsj217,和/user/hello都可以匹配,而/user//user/rsj217/不会被匹配。
curl http://127.0.0.1:8000/user/rsj217
Hello rsj217%                                                                 curl http://127.0.0.1:8000/user/rsj217/
404 page not found%                                                              curl http://127.0.0.1:8000/user/
404 page not found%

除了:,gin还提供了*号处理参数,*号能匹配的规则就更多。

func main(){
    router := gin.Default()
    
    router.GET("/user/:name/*action", func(c *gin.Context) {
        name := c.Param("name")
        action := c.Param("action")
        message := name + " is " + action
        c.String(http.StatusOK, message)
    })
}

访问效果如下

curl http://127.0.0.1:8000/user/rsj217/
rsj217 is /%                                                                  curl http://127.0.0.1:8000/user/rsj217/中国
rsj217 is /中国%

query string参数与body参数

web提供的服务通常是client和server的交互。其中客户端向服务器发送请求,除了路由参数,其他的参数无非两种,查询字符串query string和报文体body参数。所谓query string,即路由用,用?以后连接的key1=value2&key2=value2的形式的参数。当然这个key-value是经过urlencode编码。

query string

对于参数的处理,经常会出现参数不存在的情况,对于是否提供默认值,gin也考虑了,并且给出了一个优雅的方案:

func main(){
    router := gin.Default()
    router.GET("/welcome", func(c *gin.Context) {
        firstname := c.DefaultQuery("firstname", "Guest")
        lastname := c.Query("lastname")

        c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    })
  router.Run()
}

使用c.DefaultQuery方法读取参数,其中当参数不存在的时候,提供一个默认值。使用Query方法读取正常参数,当参数不存在的时候,返回空字串:

curl http://127.0.0.1:8000/welcome
Hello Guest %                                                                 curl http://127.0.0.1:8000/welcome\?firstname\=中国
Hello 中国 %                                                                  curl http://127.0.0.1:8000/welcome\?firstname\=中国\&lastname\=天朝
Hello 中国 天朝%                                                              curl http://127.0.0.1:8000/welcome\?firstname\=\&lastname\=天朝
Hello  天朝%
curl http://127.0.0.1:8000/welcome\?firstname\=%E4%B8%AD%E5%9B%BD
Hello 中国 %

之所以使用中文,是为了说明urlencode。注意,当firstname为空字串的时候,并不会使用默认的Guest值,空值也是值,DefaultQuery只作用于key不存在的时候,提供默认值。

body

http的报文体传输数据就比query string稍微复杂一点,常见的格式就有四种。例如application/jsonapplication/x-www-form-urlencoded, application/xmlmultipart/form-data。后面一个主要用于图片上传。json格式的很好理解,urlencode其实也不难,无非就是把query string的内容,放到了body体里,同样也需要urlencode。默认情况下,c.PostFROM解析的是x-www-form-urlencodedfrom-data的参数。

func main(){
    router := gin.Default()
    router.POST("/form_post", func(c *gin.Context) {
        message := c.PostForm("message")
        nick := c.DefaultPostForm("nick", "anonymous")

        c.JSON(http.StatusOK, gin.H{
            "status":  gin.H{
                "status_code": http.StatusOK,
                "status":      "ok",
            },
            "message": message,
            "nick":    nick,
        })
    })
}

与get处理query参数一样,post方法也提供了处理默认参数的情况。同理,如果参数不存在,将会得到空字串。

curl -X POST http://127.0.0.1:8000/form_post -H "Content-Type:application/x-www-form-urlencoded" -d "message=hello&nick=rsj217" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   104  100    79  100    25  48555  15365 --:--:-- --:--:-- --:--:-- 79000
{
    "message": "hello",
    "nick": "rsj217",
    "status": {
        "status": "ok",
        "status_code": 200
    }
}

发送数据给服务端,并不是post方法才行,put方法一样也可以。同时querystring和body也不是分开的,两个同时发送也可以:

func main(){
    router := gin.Default()
    
    router.PUT("/post", func(c *gin.Context) {
        id := c.Query("id")
        page := c.DefaultQuery("page", "0")
        name := c.PostForm("name")
        message := c.PostForm("message")
        fmt.Printf("id: %s; page: %s; name: %s; message: %s \n", id, page, name, message)
        c.JSON(http.StatusOK, gin.H{
            "status_code": http.StatusOK,
        })
    })
}

上面的例子,展示了同时使用查询字串和body参数发送数据给服务器。

文件上传

上传单个文件

前面介绍了基本的发送数据,其中multipart/form-data转用于文件上传。gin文件上传也很方便,和原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中了。

func main(){
    router := gin.Default()
    
    router.POST("/upload", func(c *gin.Context) {
        name := c.PostForm("name")
        fmt.Println(name)
        file, header, err := c.Request.FormFile("upload")
        if err != nil {
            c.String(http.StatusBadRequest, "Bad request")
            return
        }
        filename := header.Filename

        fmt.Println(file, err, filename)

        out, err := os.Create(filename)
        if err != nil {
            log.Fatal(err)
        }
        defer out.Close()
        _, err = io.Copy(out, file)
        if err != nil {
            log.Fatal(err)
        }
        c.String(http.StatusCreated, "upload successful")
    })
    router.Run(":8000")
}

使用c.Request.FormFile解析客户端文件name属性。如果不传文件,则会抛错,因此需要处理这个错误。一种方式是直接返回。然后使用os的操作,把文件数据复制到硬盘上。

使用下面的命令可以测试上传,注意upload为c.Request.FormFile指定的参数,其值必须要是绝对路径:

curl -X POST http://127.0.0.1:8000/upload -F "upload=@/Users/ghost/Desktop/pic.jpg" -H "Content-Type: multipart/form-data"

表单上传

上面我们使用的都是curl上传,实际上,用户上传图片更多是通过表单,或者ajax和一些requests的请求完成。下面展示一下web的form表单如何上传。

我们先要写一个表单页面,因此需要引入gin如何render模板。前面我们见识了c.String和c.JSON。下面就来看看c.HTML方法。

首先需要定义一个模板的文件夹。然后调用c.HTML渲染模板,可以通过gin.H给模板传值。至此,无论是String,JSON还是HTML,以及后面的XML和YAML,都可以看到Gin封装的接口简明易用。

创建一个文件夹templates,然后再里面创建html文件upload.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upload</title>
</head>
<body>
<h3>Single Upload</h3>
<form action="/upload", method="post" enctype="multipart/form-data">
<input type="text" value="hello gin" />
<input type="file" name="upload" />
<input type="submit" value="upload" />
</form>


<h3>Multi Upload</h3>
<form action="/multi/upload", method="post" enctype="multipart/form-data">
<input type="text" value="hello gin" />
<input type="file" name="upload" />
<input type="file" name="upload" />
<input type="submit" value="upload" />
</form>

</body>
</html>

upload 很简单,没有参数。一个用于单个文件上传,一个用于多个文件上传。

    router.LoadHTMLGlob("templates/*")
    router.GET("/upload", func(c *gin.Context) {
        c.HTML(http.StatusOK, "upload.html", gin.H{})
    })

使用LoadHTMLGlob定义模板文件路径。

参数绑定

我们已经见识了x-www-form-urlencoded类型的参数处理,现在越来越多的应用习惯使用JSON来通信,也就是无论返回的response还是提交的request,其content-type类型都是application/json的格式。而对于一些旧的web表单页还是x-www-form-urlencoded的形式,这就需要我们的服务器能改hold住这多种content-type的参数了。

Python的世界里很好解决,毕竟动态语言不需要实现定义数据模型。因此可以写一个装饰器将两个格式的数据封装成一个数据模型。golang中要处理并非易事,好在有gin,他们的model bind功能非常强大。

 

type User struct {
    Username string `form:"username" json:"username" binding:"required"`
    Passwd   string `form:"passwd" json:"passwd" bdinding:"required"`
    Age      int    `form:"age" json:"age"`
}

func main(){
    router := gin.Default()
    
    router.POST("/login", func(c *gin.Context) {
        var user User
        var err error
        contentType := c.Request.Header.Get("Content-Type")

        switch contentType {
        case "application/json":
            err = c.BindJSON(&user)
        case "application/x-www-form-urlencoded":
            err = c.BindWith(&user, binding.Form)
        }

        if err != nil {
            fmt.Println(err)
            log.Fatal(err)
        }

        c.JSON(http.StatusOK, gin.H{
            "user":   user.Username,
            "passwd": user.Passwd,
            "age":    user.Age,
        })

    })

}

先定义一个User模型结构体,然后针对客户端的content-type,一次使BindJSONBindWith方法。

curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&age=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    79  100    46  100    33  41181  29543 --:--:-- --:--:-- --:--:-- 46000
{
    "age": 21,
    "passwd": "123",
    "username": "rsj217"
}
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&new=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    78  100    45  100    33  37751  27684 --:--:-- --:--:-- --:--:-- 45000
{
    "age": 0,
    "passwd": "123",
    "username": "rsj217"
}
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&new=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded

可以看到,结构体中,设置了binding标签的字段(username和passwd),如果没传会抛错误。非banding的字段(age),对于客户端没有传,User结构会用零值填充。对于User结构没有的参数,会自动被忽略。

改成json的效果类似:

curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "age": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    96  100    46  100    50  32670  35511 --:--:-- --:--:-- --:--:-- 50000
{
    "age": 21,
    "passwd": "123",
    "username": "rsj217"
}
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    95  100    45  100    50  49559  55066 --:--:-- --:--:-- --:--:-- 50000
{
    "age": 0,
    "passwd": "123",
    "username": "rsj217"
}
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": 123, "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded

当然,gin还提供了更加高级方法,c.Bind,它会更加content-type自动推断是bind表单还是json的参数。

    router.POST("/login", func(c *gin.Context) {
        var user User
        
        err := c.Bind(&user)
        if err != nil {
            fmt.Println(err)
            log.Fatal(err)
        }

        c.JSON(http.StatusOK, gin.H{
            "username":   user.Username,
            "passwd":     user.Passwd,
            "age":        user.Age,
        })

    })

多格式渲染

既然请求可以使用不同的content-type,响应也如此。通常响应会有html,text,plain,json和xml等。
gin提供了很优雅的渲染方法。到目前为止,我们已经见识了c.String, c.JSON,c.HTML,下面介绍一下c.XML。

    router.GET("/render", func(c *gin.Context) {
        contentType := c.DefaultQuery("content_type", "json")
        if contentType == "json" {
            c.JSON(http.StatusOK, gin.H{
                "user":   "rsj217",
                "passwd": "123",
            })
        } else if contentType == "xml" {
            c.XML(http.StatusOK, gin.H{
                "user":   "rsj217",
                "passwd": "123",
            })
        }

    })

结果如下:

curl http://127.0.0.1:8000/render\?content_type\=json
{"passwd":"123","user":"rsj217"}
curl http://127.0.0.1:8000/render\?content_type\=xml
<map><user>rsj217</user><passwd>123</passwd></map>%

重定向

gin对于重定向的请求,相当简单。调用上下文的Redirect方法:

    router.GET("/redict/google", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "https://google.com")
    })

分组路由

熟悉Flask的同学应该很了解蓝图分组。Flask提供了蓝图用于管理组织分组api。gin也提供了这样的功能,让你的代码逻辑更加模块化,同时分组也易于定义中间件的使用范围。

    v1 := router.Group("/v1")

    v1.GET("/login", func(c *gin.Context) {
        c.String(http.StatusOK, "v1 login")
    })

    v2 := router.Group("/v2")

    v2.GET("/login", func(c *gin.Context) {
        c.String(http.StatusOK, "v2 login")
    })

访问效果如下:

curl http://127.0.0.1:8000/v1/login
v1 login%                                                                                 
curl http://127.0.0.1:8000/v2/login
v2 login%

middleware中间件

golang的net/http设计的一大特点就是特别容易构建中间件。gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起作用。对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。中间件分为全局中间件,单个路由中间件和群组中间件。

全局中间件

先定义一个中间件函数:

func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("before middleware")
        c.Set("request", "clinet_request")
        c.Next()
        fmt.Println("before middleware")
    }
}

该函数很简单,只会给c上下文添加一个属性,并赋值。后面的路由处理器,可以根据被中间件装饰后提取其值。需要注意,虽然名为全局中间件,只要注册中间件的过程之前设置的路由,将不会受注册的中间件所影响。只有注册了中间件一下代码的路由函数规则,才会被中间件装饰。

    router.Use(MiddleWare())
    {
        router.GET("/middleware", func(c *gin.Context) {
            request := c.MustGet("request").(string)
            req, _ := c.Get("request")
            c.JSON(http.StatusOK, gin.H{
                "middile_request": request,
                "request": req,
            })
        })
    }

使用router装饰中间件,然后在/middlerware即可读取request的值,注意在router.Use(MiddleWare())代码以上的路由函数,将不会有被中间件装饰的效果。

curl  http://127.0.0.1:8000/middleware
{"middile_request":"clinet_request","request":"clinet_request"}

如果没有注册就使用MustGet方法读取c的值将会抛错,可以使用Get方法取而代之。

上面的注册装饰方式,会让所有下面所写的代码都默认使用了router的注册过的中间件。

单个路由中间件

当然,gin也提供了针对指定的路由函数进行注册。

    router.GET("/before", MiddleWare(), func(c *gin.Context) {
        request := c.MustGet("request").(string)
        c.JSON(http.StatusOK, gin.H{
            "middile_request": request,
        })
    })

把上述代码写在 router.Use(Middleware())之前,同样也能看见/before被装饰了中间件。

群组中间件

群组的中间件也类似,只要在对于的群组路由上注册中间件函数即可:

authorized := router.Group("/", MyMiddelware())
// 或者这样用:
authorized := router.Group("/")
authorized.Use(MyMiddelware())
{
    authorized.POST("/login", loginEndpoint)
}

群组可以嵌套,因为中间件也可以根据群组的嵌套规则嵌套。

中间件实践

中间件最大的作用,莫过于用于一些记录log,错误handler,还有就是对部分接口的鉴权。下面就实现一个简易的鉴权中间件。

    router.GET("/auth/signin", func(c *gin.Context) {
        cookie := &http.Cookie{
            Name:     "session_id",
            Value:    "123",
            Path:     "/",
            HttpOnly: true,
        }
        http.SetCookie(c.Writer, cookie)
        c.String(http.StatusOK, "Login successful")
    })

    router.GET("/home", AuthMiddleWare(), func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"data": "home"})
    })

登录函数会设置一个session_id的cookie,注意这里需要指定path为/,不然gin会自动设置cookie的path为/auth,一个特别奇怪的问题。/homne的逻辑很简单,使用中间件AuthMiddleWare注册之后,将会先执行AuthMiddleWare的逻辑,然后才到/home的逻辑。

AuthMiddleWare的代码如下:

func AuthMiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        if cookie, err := c.Request.Cookie("session_id"); err == nil {
            value := cookie.Value
            fmt.Println(value)
            if value == "123" {
                c.Next()
                return
            }
        }
        c.JSON(http.StatusUnauthorized, gin.H{
            "error": "Unauthorized",
        })
        c.Abort()
        return
    }
}

从上下文的请求中读取cookie,然后校对cookie,如果有问题,则终止请求,直接返回,这里使用了c.Abort()方法。

In [7]: resp = requests.get('http://127.0.0.1:8000/home')

In [8]: resp.json()
Out[8]: {u'error': u'Unauthorized'}

In [9]: login = requests.get('http://127.0.0.1:8000/auth/signin')

In [10]: login.cookies
Out[10]: <RequestsCookieJar[Cookie(version=0, name='session_id', value='123', port=None, port_specified=False, domain='127.0.0.1', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)]>

In [11]: resp = requests.get('http://127.0.0.1:8000/home', cookies=login.cookies)

In [12]: resp.json()
Out[12]: {u'data': u'home'}

异步协程

golang的高并发一大利器就是协程。gin里可以借助协程实现异步任务。因为涉及异步过程,请求的上下文需要copy到异步的上下文,并且这个上下文是只读的。

    router.GET("/sync", func(c *gin.Context) {
        time.Sleep(5 * time.Second)
        log.Println("Done! in path" + c.Request.URL.Path)
    })

    router.GET("/async", func(c *gin.Context) {
        cCp := c.Copy()
        go func() {
            time.Sleep(5 * time.Second)
            log.Println("Done! in path" + cCp.Request.URL.Path)
        }()
    })

在请求的时候,sleep5秒钟,同步的逻辑可以看到,服务的进程睡眠了。异步的逻辑则看到响应返回了,然后程序还在后台的协程处理。

自定义router

gin不仅可以使用框架本身的router进行Run,也可以配合使用net/http本身的功能:

func main() {
    router := gin.Default()
    http.ListenAndServe(":8080", router)
}

或者

func main() {
    router := gin.Default()

    s := &http.Server{
        Addr:           ":8000",
        Handler:        router,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    s.ListenAndServe()
}

当然还有一个优雅的重启和结束进程的方案。后面将会探索使用supervisor管理golang的进程。

总结

Gin是一个轻巧而强大的golang web框架。涉及常见开发的功能,我们都做了简单的介绍。关于服务的启动,请求参数的处理和响应格式的渲染,以及针对上传和中间件鉴权做了例子。更好的掌握来自实践,同时gin的源码注释很详细,可以阅读源码了解更多详细的功能和魔法特性。

php8 源码编译安装

简述

PHP 团队于2020年11月26日宣布 PHP 8 正式发布!这意味着将不会有 PHP 7.5 版本。PHP8 目前正处于非常活跃的开发阶段,所以在接下来的几个月里,情况可能会发生很大的变化。我也分享一些研究PHP 8 的心得,希望PHPer大家一起共同进步。首先说一下最受关注的JIT。

JIT

由于 PHP 8 是一个新的大版本,因此升级版本,代码被破坏的可能性更高。如果项目始终保持运行 PHP 的最新版本,那么升级相对来说就会轻松很多,因为在 7. * 版本中,大多数重大更改均已弃用。除重大更改外,PHP 8 还带来了一些不错的新功能,比如说 JIT 编译器 , 联合类型 , 属性,以及更多。很多人可能对JIT有很深的误解,觉得引入JIT之后性能就能提高10倍跟V8平起平坐了,事实上不是这样的。JIT技术的水很深,动态语言的JIT尤其困难,V8的诞生几乎可以说是一个技术奇迹。以PHP社区的技术水平,我谨慎地不看好他们解决这个问题的能力,毕竟Facebook的HHVM也没有完全解决,最后是靠Hacklang补全PHP的语法功能之后才基本圆满解决的。

 

动态语言的JIT本质要解决的问题之中,生成汇编只是一小部分,对于弱类型和动态类型语言来说,优化内存布局也是重点。例如,对于JavaScript和Python来说,以前对象内部是一个HashMap,这种数据结构的访问效率比较低,导致访问对象的每个属性都很慢,在JIT之后会将它优化成类似C++的平铺式的布局,将属性的值按顺序放在特定的位置上,这就带来一些新的要求:

  1. 没有类型标注的情况下,JIT只能猜测类型而无法肯定,那么使用优化的类型布局之前需要进行额外的检测,判断是否的确为预想的类型;

  2. 属性的类型也需要进一步推测,使用时也需要检验;

  3. JavaScript、Python乃至PHP都支持在对象创建之后为它添加新的属性。之前符合推测的类型后来添加或者删除了属性,要怎么处理?

 

  除此之外,调用函数时候如何优化调用开销也是一个重点,本质上跟优化对象的内存布局是类似的,可以将传入参数看成是构建一个有多个属性的对象,每个属性的类型不同。局部变量也需要有选择性地优化到寄存器、栈和堆当中。

PHP在这里的优势是支持类型标注,缺点是所有Hacklang里面修改掉的部分:

1. 不支持泛型,尤其是array类型不支持泛型。将一个变量类型标注为array几乎没有任何帮助,PHP中的array可以是顺序表也可以是hashmap,还可以混着,value的类型也不确定,这些都对类型优化有很高要求。Hacklang就推荐废掉array改用vector等几个确定类型且支持泛型的数据结构。

  2. reference这个功能,这个功能非常容易成为内存布局优化的障碍,也会阻碍JIT生成高效代码,尤其是数组中可以存储reference这件事,JIT编译器完全无法从字面上判断某条对array元素赋值的语句是否会影响环境中的其它变量的值。这也是为什么Hacklang直接删掉了这个功能。

  3. 其他参考Hacklang的变更

之前版本(PHP7)抠解释器实现带来的性能优化也会是一个阻碍,JIT的时候这些都得放弃掉,因为内存布局不一样了,这样可能导致最初的时候许多应用JIT反而变慢。所以,PHP8如果解决不了这些问题,最大的可能是许多microbenchmark速度大幅上升,但整体应用性能持平,自娱自乐。

源码编译安装

1. 安装依赖

yum -y install autoconf freetype gd libpng libpng-devel libjpeg libxml2 libxml2-devel zlib curl curl-devel net-snmp-devel libjpeg-devel

2. oniguruma 依赖包

yum install autoconf automake libtool
wget https://github.com/kkos/oniguruma/archive/v6.9.4.tar.gz -O oniguruma-6.9.4.tar.gz
tar xf oniguruma-6.9.4.tar.gz && cd oniguruma-6.9.4
./autogen.sh && ./configure --prefix=/usr
make && make install

4.新建用户

userdel www
groupadd www
useradd -g www -M -d /data/www -s /sbin/nologin www &> /dev/null

5. 下载源码包

wget  https://www.php.net/distributions/php-8.0.7.tar.gz

6. 解压

tar -zxvf php-8.0.7.tar.gz
cd php-8.0.7

7. 编译和安装 (选择自己需要的安装)

./configure --prefix=/usr/local/php \
--with-config-file-path=/usr/local/php \
--enable-mbstring  \
--enable-ftp  \
--enable-gd   \
--enable-gd-jis-conv \
--enable-mysqlnd \
--enable-pdo   \
--enable-sockets   \
--enable-fpm   \
--enable-xml  \
--enable-soap  \
--enable-pcntl   \
--enable-cli   \
--with-openssl  \
--with-mysqli=mysqlnd   \
--with-pdo-mysql=mysqlnd   \
--enable-mysqlnd-compression-support \
--with-pear   \
--with-zlib  \
--with-iconv  \
--with-curl   \
--build=arm-linux && make && make install

7. 开始配置

新增环境变量

vim /etc/profile
##最后增加 
export PHP=/usr/local/php
export PATH=$PHP/bin:$PHP/sbin:$PATH
source /etc/profile 

8. 配置PHP

cp php.ini-production /usr/local/php/etc/php.ini
cp /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.conf
cp /usr/local/php/etc/php-fpm.d/www.conf.default /usr/local/php/etc/php-fpm.d/www.conf
cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
chmod +x /etc/init.d/php-fpm

9. 测试

[root@localhost www]# php -v 
PHP 8.0.7 (cli) (built: Jun 25 2021 10:10:16) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.7, Copyright (c) Zend Technologies

 

编译过程错误解决

若在 configure 过程中,遇到提示 configure: error,留意看下方的错误详细信息。以下 列出了在配置过程中出现的错误情况之关键信息摘要,未列举到的其它错误情况,解决方 法如出一辙:

Error #1:

configure: error: Package requirements (libxml-2.0 >= 2.7.6) were not met:
No package ‘libxml-2.0’ found
解决方法:

yum install libxml2-devel.x86_64

Error #2:
configure: error: Package requirements (sqlite3 > 3.7.4) were not met:
No package ‘sqlite3’ found

解决方法:

yum install sqlite-devel.x86_64

安装配置匿名访问网络Tor洋葱浏览器

概述

tor工作原理

Tor由已经安装了Tor软件的电脑连接网络而成。它之所以被称为onion,是因为它的结构就跟洋葱相同,你只能看出它的外表,而想要看到核心,就必须把它层层的剥开。即每个路由器间的传输都经过点对点密钥(symmetric key)来加密,形成有层次的结构。它中间所经过的各节点,都好像洋葱的一层皮,把客户端包在里面,算是保护信息来源的一种方式。

用 Tor 创建一条私有网络路径时,用户的软件或客户端通过网络上的中继递增地建立一条由若干加密连接组成的环路(circuit)。环路一次扩展一跳(hop),环路上的中继仅仅知道它从哪一个中继接收数据以及向哪一个中继发送数据。没有一台单独的中继会知道数据包的完整路径。客户端与环路上的每一跳都协商一组独立的密钥,这样可以保证数据通过任何一跳时都无法跟踪。

tor browser浏览器

tor browser的最大特点就是匿名性。

  • 您的互联网服务提供商和任何本地的监视者都将无法查看您的连接、跟踪您的网络活动,包括您所访问网站的名称和地址。
  • 您使用的网站和服务的运营商以及任何监视它们的人都将看到连接来自 Tor 网络而不是您的互联网IP地址,并且不知道您是谁,除非您明确标识自己。

安装

1. Tor安装配置

首先我们下载安装tor browser
下载地址: https://tor-browser.en.softonic.com/

安装时根据网络环境选择直连安装、网络受限安装(内置网桥连接、获取网桥连接、代理连接)。直连安装是网络可直接访问国外受限站点的网络环境;网络受限安装是当前网络不能直接访问国外受限站点,如有通过代理可访问受限站点选择代理连接,没有代理可选择内置网桥连接或获取网桥连接。

获取Tor网桥方法:

(一)mail自助获取tor的桥接ip
用 gmail等给 [email protected] 发一封内容为 get bridges 的邮件,主题随意,或标题 get transport obfs3 正文 get transport obfs3 或 标题 get transport scramblesuit 正文 get transport scramblesuit

(二)直接随机获取网桥ip,https://bridges.torproject.org/打开网页获得网桥ip。

获得的网桥样子是:

obfs4 139.59.140.35:8491 B1137CEB247F5FF9B57DA3160E6931D0269FF010 cert=g92HymBTzn1KzyizGjJkXxT8YRJw/QC3LEfoVFWuv9but3AqLd916ZoB+fo/6xJqWB0faQ iat-mode=0
obfs4 185.178.93.141:9060 05012FE073A4B522580E6FE77F3FD92E1ABF6FD1 cert=377UalzVX10eI5raeIVmJFFvq0FpKEF12igXoKe2rMJWGJxScKzMm9dph9m4v8hz0y3hDg iat-mode=0
obfs4 192.241.152.208:444 05277333D6A14B6E706CF5A189C57C97C471189B cert=2j8mktwt8pA4LGmqBUFeDfkGbVE0U0MqYBIbt6/ePMRrlrk9UhHQeeyw8WqgGPa1xYzyaQ iat-mode=0

填加网桥:

** 请注意:只添加前面的地址和端口, 最后面的长串数字字母是数字指纹,不要添加。

Tor官方其实也更推荐obfs4 https://2019.www.torproject.org/docs/pluggable-transports

代理连接:

这种方法比较常用,原因是比较快,可以用免费的节点分享网址或者自己搭建(以后讲方法,欢迎关注)
免费的节点分享网址:http://free-proxy.cz/en

2. Tor高级选项配置方法

在 Tor Browser\Data\Tor目录中找到torrc文件,用记事本等文本编辑器打开这个文件,可以看到里面有一些参数,这个文件是tor的核心设置文件。

以下为排除的节点(StrictNodes 1为坚决执行)
ExcludeNodes {cn},{hk},{mo},{sg},{th},{pk},{by},{ru},{ir},{vn},{ph},{my},{cu}
ExcludeExitNodes {cn},{hk},{mo},{sg},{th},{pk},{by},{ru},{ir},{vn},{ph},{my},{cu}
StrictNodes 1
注:
ExcludeNodes 是指排除节点,即把括号中的国家的节点从tor链路上除去;
ExcludeExitNodes 是指“排除“出口”节点”,,即tor的出口节点要排除括号中的国家的节点。

指定出口节点:

StrictExitNodes 1
ExitNodes {us}
这里us是指限定美国的ip为出口ip,你可以改为任何国家,国家代码请参考:https://zh.wikipedia.org/zh-cn/ISO_3166-1

补充:opera浏览器自带VPN功能同样可以访问国外受限站点。将计算机区域设置为中国台湾,安装好opera并在opera设置里将首选语言选为繁体,清除缓除重启opera,再到opera设置的高级选项里的隐私安全设置里即有VPN启用选择项,启用VPN后可在浏览器地址栏看到VPN字样,点击此字样可定位VPN位置。如果当前网络环境本身就在(已启用其他代理)国外IP,就不需改变计算机区域位置及opera语言设置,直接到opera设置高级选择项就可选择是否开启VPN功能。

3.Tor 的匿名性(挺好)

Tor 的一些特性使得其匿名性略高于普通浏览器的无痕浏览模式:参考 Chrome 无痕浏览的工作原理;以及 Tor 安全设置说明:https://tb-manual.torproject.org/zh-CN/security-settings/

访问 https://ip.sb/ 或 https://whoer.net/zh 查看你的IP;既不是你的真实IP(网络运营商分配的IP),也不是你的代理IP(机场),而是 Tor 的三层叠加的最后一层 IP;

X-ui,支持多协议多用户的Xray 面板!V2-ui全面停止更新

前言

sprov 这位大神开发的这套面板程序,很是方便,可以可视化的搭建SS、V2ray、Xray、Trojan等热门的协议,并且可以实时看到 VPS 的性能状态以及流量的使用情况。

那在经过将近两年的不断更新之后,V2-ui面板,迎来了一个比较大的转折——停止更新了。

sprov 大神又用 GO 语言重新开发了一套面板 X-UI。那这套面板相比原来的 V2-ui,兼容性更强,也便于维护, GO 语言的性能更好,而且内存占用也会相对的低一些。

功能介绍

  • 系统状态监控
  • 支持多用户多协议,网页可视化操作
  • 支持的协议:vmess、vless、trojan、shadowsocks、dokodemo-door、socks、http
  • 支持配置更多传输配置
  • 流量统计,限制流量限制到期时间
  • 可自定义 xray 配置模板
  • 支持 https 访问面板(自备域名 + ssl 证书)
  • 更多高级配置项,详见该项目的 GitHub:点击访问

安装&升级

bash <(curl -Ls https://raw.githubusercontent.com/vaxilu/x-ui/master/install.sh)

安装后,在浏览器中打开 http://<服务器 IP>:65432 即可访问面板,默认用户名和密码都是 admin。

登陆后请修改用户名和密码

更换面板端口

x-ui 打开脚本面板,更换面板端口为你想用的端口即可,记得开放防火墙

添加节点

添加节点的过程 youtube 有很多视频,不再赘述

最简单的就是添加一个 vmess+websocket,其他保持全默认即可

手动安装&升级

  1. 首先从 https://github.com/vaxilu/x-ui/releases 下载最新的压缩包,一般选择amd64架构
  2. 然后将这个压缩包上传到服务器的/root/目录下,并使用root用户登录服务器

如果你的服务器 cpu 架构不是amd64,自行将命令中的amd64替换为其他架构

cd /root/
rm x-ui/ /usr/local/x-ui/ /usr/bin/x-ui -rf
tar zxvf x-ui-linux-amd64.tar.gz
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
cp x-ui/x-ui.sh /usr/bin/x-ui
cp -f x-ui/x-ui.service /etc/systemd/system/
mv x-ui/ /usr/local/
systemctl daemon-reload
systemctl enable x-ui
systemctl restart x-ui

建议系统

  • CentOS 7+
  • Ubuntu 16+
  • Debian 8+

常见问题

从 v2-ui 迁移

首先在安装了 v2-ui 的服务器上安装最新版 x-ui,然后使用以下命令进行迁移,将迁移本机 v2-ui 的所有 inbound 账号数据至 x-ui,面板设置和用户名密码不会迁移

迁移成功后请关闭 v2-ui并且重启 x-ui,否则 v2-ui 的 inbound 会与 x-ui 的 inbound 会产生端口冲突

x-ui v2-ui

更新系统

安装相关依赖
centos 系统下

yum update -y                                                                                                
apt-get update -y && apt-get install curl -y

ubuntu 系统下

apt update -y
apt-get update -y && apt-get install curl -y

若出现 bash curl: command not found 等错误,请先安装 curl

CentOS 系统:

yum install curl -y

Debian 或 Ubuntu 系统:

apt install curl -y

错误提示:-bash curl: command not found

Ubuntu、Debian 如果出现无法安装 Curl, 需要先升级

sudo apt-get update

申请 SSL 证书

下面环境的安装方式,大家根据自己的系统选择命令安装就好了。

更新及安装组件

apt update -y          # Debian/Ubuntu 命令
apt install -y curl    #Debian/Ubuntu 命令
apt install -y socat    #Debian/Ubuntu 命令
yum update -y          #CentOS 命令
yum install -y curl    #CentOS 命令
yum install -y socat    #CentOS 命令

安装 Acme 脚本

curl https://get.acme.sh | sh

80 端口空闲的证书申请方式

自行更换代码中的域名、邮箱为你解析的域名及邮箱

~/.acme.sh/acme.sh --register-account -m [email protected]
~/.acme.sh/acme.sh  --issue -d mydomain.com   --standalone

安装证书到指定文件夹

自行更换代码中的域名为你解析的域名

~/.acme.sh/acme.sh --installcert -d mydomain.com --key-file /root/private.key --fullchain-file /root/cert.crt

FunAdmin开发框架系统 支持PHP8.0(ThinkPHP6.X + Layui +Requirejs)

介绍

FunAdmin是基于ThinkPHP6.X + Layui +Requirejs的敏捷开发的后台管理系统。

演示站点

演示地址: fundemo.funadmin.com (账号:admin,密码:123456。备注:只有查看信息的权限

主要特性

  • 基于auth的权限管理系统
    • 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
    • 支持管理员多角色管理
  • CURD 命令行模式助您快速开发
  • 完善的菜单管理
    • 分模块管理
    • 无限极菜单
  • 二级域名部署支持,同时域名支持绑定到插件
  • 多语言支持,服务端及客户端支持
  • 丰富的插件市场
  • 完善的上传插件组件功能
    • 本地存储
    • 阿里云OSS建议使用
    • 腾讯云COS
    • 七牛云OSS
  • 完善的前端功能组件开发
    • 自适应手机、平板、PC,自使用IE+谷歌+火狐等浏览器
    • 基于RequireJS进行JS模块管理,按需加载
    • 对form表单二次封装,只需要使用函数就可以展示表
    • 对layui table ,form表格再次封装,方便使用
  • 完善的后台操作日志
    • 记录用户的详细操作信息 操作ip 等
  • 文件附件管理
  • 后台路径自定义,防止别人找到对应的后台地址

项目介绍

FunAdmin 基于thinkphp6.X +Layui2.6.*+requirejs开发权限(RBAC)管理框架,框架中集成了权限管理、模块管理、插件管理、后台支持多主题切换、配置管理、会员管理等常用功能模块,以方便开发者快速构建自己的应用。框架专注于为中小企业提供最佳的行业基础后台框架解决方案,执行效率、扩展性、稳定性值得信赖,操作体验流畅,使用非常优化,欢迎大家使用及进行二次开发。

  • PHP支持php8.0的快速开发框架 建议使用PHP8
  • 这是一个有趣的后台管理系统,这是可以让你节约时间的系统
  • 这是一款快速、高效、便捷、灵活敏捷的应用开发框架。
  • 系统采用最新版TinkPHP6框架开发,底层安全可靠,数据查询更快,运行效率更高,网站速度更快, 后续随官网升级而升级
  • 密码动态加密,相同密码入库具有唯一性,用户信息安全牢固,告别简单md5加密
  • 自适应前端,桌面和移动端访问界面友好简洁,模块清晰
  • 兼容ie11 + firefox + Chrome +360 等浏览器
  • UI组件化,只需要写函数就可以成就后台表单
  • 内置CURD 命令行模式,帮助助您快速开发系统
  • 模块化:全新的架构和模块化的开发机制,便于灵活扩展和二次开发。
  • 强大的表单管理,只需要使用函数即可成就表单
  • layui采用最新layui2.6.X 框架
  • 适用范围:可以开发OA、ERP、BPM、CRM、WMS、TMS、MIS、BI、电商平台后台、物流管理系统、快递管理系统、教务管理系统等各类管理软件。
  • require.js 模块化开发 一个命令即可打包js,css ; node r.js -o backend-build.js
  • restful api 接口,接口使用jwt接口验证等
  • …更多功能尽请关注

环境要求:

  • PHP >= 7.4 支持php8.0
  • PDO PHP Extension
  • MBstring PHP Extension
  • CURL PHP Extension
  • 开启静态重写
  • 要求环境支持pathinfo
  • Mysql 5.7及以上
  • Apache 或 Nginx

功能特性

  • 严谨规范: 提供一套有利于团队协作的结构设计、编码、数据等规范。
  • 高效灵活: 清晰的分层设计,解耦设计更能灵活应对需求变更。
  • 严谨安全: 清晰的系统执行流程,严谨的异常检测和安全机制,详细的日志统计,为系统保驾护航。
  • 组件化: 完善的组件化设计,丰富的表单组件,让开发列表和表单更得心应手。无需前端开发,省时省力。
  • 简单上手快: 结构清晰、代码规范、在开发快速的同时还兼顾性能的极致追求。
  • 自身特色: 权限管理、组件丰富、第三方应用多、分层解耦化设计和先进的设计思想。
  • 高级进阶: 分布式、负载均衡、集群、Redis、分库分表。

插件

  • ** CMS内容管理插件(免费)
  • ** BBS社区插件
  • ** 编辑器插件
  • ** 微信管理插件
  • ** 更多请查看 插件列表

开发者信息

  • 开源协议:Apache 2.0