开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

慢聊Go之Go常见的Web 开发框架|Go主题月

发表于 2021-03-25

image.png

前言

自发布以来,Golang(Google的Go编程语言)已成为编写API和Web服务的强大且流行的选择。引入后,主流用户更喜欢这种编程语言。

在2016年12月进行的分析之后,在3595名受访者中,将近89%的人决定在工作中或工作之余去学习Golang。

在专有技能和选择方面,Golang在所有Web编程语言中排名最高。

今天,有点不在状态,就不好好的把Java与Go做更细节的对比了。我们今天大概的聊一下,目前比较好用的Go Web Framework。

Golang特点

Google的Go语言又名Golang,可以编译为快速运行的本机代码。我们之前也讲过,它的出现,为了让开发人员开发变得容易。

作为一种开放源代码,经过编译的编程语言,Golang帮助开发人员创建可靠且简单的软件。

该语言定义了诸如C和C ++之类的较为温和的语言的演变和创新。此外,Go拥有广泛且不断增长的用户。

Golang专为并发性和可伸缩性而设计,并且还使优化成为可能。使用这种编程语言,可以消除大量的代码键入和编写独特的API,而不会影响功能。这种编译语言可以在运行时之前执行所有代码检查工作。

Golang诞生的Web开发框架

image.png

1.Martini

Martini由Sinatra激活,实际上是一个轻量级的Web框架。它处理了一些基本问题,例如异常处理,路由和中间件的一些功能。

Martini能够做一些独特的事情,例如根据类型将各种数据集动态注入到处理程序中。

尽管这是一个很小的社区,但它确实很活跃,并且有二十个或以上的活跃插件。由于这是一个小型框架,因此要是使用的话,可能要附加更多组件。

2.Gin Gonic

该Web框架具有与Martini类似的API,但是表现的更好。

保守的Gin Gonic框架仅包含最重要的功能和库。这使其成为构建高性能REST API的理想选择。而且,这比Martini框架快40倍。

无论您是否添加渲染,JSON验证,嵌套组和中间件,它仍然保持其最终功能。该框架利用httprouter,这是Golang语言最快的HTTP路由器。

3.Beego

Beego与Python的Django网站框架相似。它拥有Web应用程序共有的广泛功能,并分为8个模块,可以根据需要避免或使用这些模块。

除了出现在最大的Web框架中的常规MVC元素外,它还集成了ORM(对象关系图)以访问数据,会话处理工具,内置的缓存处理程序,用于HTTP组件的常规操作的库以及日志记录系统。

Beego是另一种让人想起Django命令行工具的方式。例如,开发人员可以从一开始就使用bee命令来开发Beego应用程序或处理当前的应用程序。

4.网络/ HTTP

开发人员通常仅使用HTTP或net来开发整个XMPP服务器,因为所需的仅仅是其性能正常即可。

但是,复杂的Web应用程序通常需要中间件。还有一些吸引人的项目,可让其他Golang Web框架的中间件与标准HTTP或网络进行混合和匹配。

无疑,这个社区很大,因为用户可以再次使用许多其他项目中的位。

但是,它具有受限制的接口,并且没有定义最大化中间件的标准方法。

5.Buffalo

使用Buffalo,即可快速轻松地开发新的Web应用程序。Buffalo将在开始新项目时设置一切-从前端到后端Web建设。

Buffalo带有热重载功能,这意味着dev命令将自动观察.html和.go文件。然后,它将重新启动并重新开发二进制文件。

它是一个整体的Web建设生态系统,可帮助直接开发应用程序。

6.Mango

尽管Mango的创建者Paul Bellamy并没有积极维护它,但许多Golang用户仍在使用它。关于此Web框架,模块化是最好的选择。可以从不同的库中进行选择,以将它们合并到项目中。

Mango框架可帮助您尽可能轻松,快速地开发HTTP功能的可重用模块。而且,它由一系列应用程序和中间件组成,并包含在一个HTTP服务器对象中,以使代码保持自主。

7.Gorilla

Gorilla可能是运行时间最长,最大的Go Web框架。对于用户而言,该模块化框架可以具有尽可能少的数量或尽可能多的数量。这很好用,因为许多组件可以直接通过net / HTTP库重用。

在所有框架中,Gorilla可能是最大的英语社区。此外,它具有强大的WebSocket,因此您无需使用Pusher等第三方服务,就可以将与WebSocket类似的代码精确地附加到REST端点。

8. Gocraft

Gocraft是另一个强大但保守的框架,它提供了可扩展的快速路由功能。路由由它从标准库添加到HTTP或net程序包。

Gocraft是一个Go mux定制中间件软件包,它具有反射和转换功能,因此可以静态键入代码。

此外,可以编写自己的代码或使用内置的中间件添加其他功能。由于开发人员将性能放在首位,因此Gocraft对他们来说是一个了不起的选择。

此外,使用上述框架编写后端Web应用程序确实更加容易。

9.Goji

这是一个快速,轻量级的框架,主要优先考虑其简单性和组合能力。

Goji与net / http.ServeMux类似,是一个保守的HTTP请求多路复用器,它结合了Einhorn辅助功能。这种支持使开发人员可以在Goji中获得Web套接字帮助。

Goji的其他功能包括正常关机,可重新配置的中间件堆栈,URL模式等。

Goji作为Web框架已经满足了不同企业的众多请求。

总结

介绍了下常见的Go 的web开发框架,大家喜欢就好!!

明天,继续细节对比,助你转型!!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

技术转正及年终工作述职报告PPT模板

发表于 2021-03-25

很多技术的朋友转正或年总总结时都需要用PPT来进行转正演讲或述职演讲。

这不,马上年会了,作为技术负责人要代表技术部做一个年总的总结,在网上找了好久都没找到一个合适的述职报表的PPT模板。

经过不懈的努力,终于找到12套简约的述职报表模板。分享给大家,供大家参考:

模板一:

在这里插入图片描述

模板二:

在这里插入图片描述

模板三:

在这里插入图片描述

模板四:

在这里插入图片描述

模板五:

在这里插入图片描述

模板六:

在这里插入图片描述

模板七:

在这里插入图片描述

模板八:

在这里插入图片描述

模板九:

在这里插入图片描述

模板十:

在这里插入图片描述

模板十一:

在这里插入图片描述

模板十二:

在这里插入图片描述

不管现在有没有用,都赶紧收藏一下吧。完整的PPT模板,扫描关注下面的公众号“程序新视界”,回复“PPT”获得。


程序新视界

\

公众号“ 程序新视界”,一个让你软实力、硬技术同步提升的平台,提供海量资料

\

微信公众号:程序新视界

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

31 Go语言从入门到精通:包 1、包的概述 2、包的使用

发表于 2021-03-25

Go 语言像 Java 语言一样都拥有包的概念,通过使用包来组织源代码。包(package)是多个 Go 源码的集合,是一种高级的代码复用方案。Go语言中为我们提供了很多内置包,如 fmt、os、io 等。

任何 Go 源代码文件都必属于某个包,同时源码文件的第一行有效代码必须是package pacakgeName 语句,通过该语句声明自己所在的包。

1、包的概述

Go 语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称,虽然 Go 语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样代码结构会更加清晰。

1.1 包的定义

包的声明如下:

1
go复制代码package <package_name>

注意:包声明必须在 go 源文件的第一行定义。

例如,在 GOPATH/src/a/b/下定义包 c,则在 GOPATH/src/a/b/目录下的所有 go 源文件第一行需要声明 package c,而不是声明为 package a.b.c 或 package a/b/c,但在导入包时,需要带上路径 import a/b/c。(注意:这与 Java 语言中包的用法差异很大)

1.2 包的命名规范

清晰的代码结构,少不了规范命名,包的命名更为重要。规范命名的目的是为了让你的代码更清晰,别人读起来更快上手。包命名规范推荐如下:

  • 包名一般小写,使用一个简短且有意义的单词或缩写。
  • 包名与所在包的目录同名,看目录知包名。
  • 包名一般采用域名作为包目录结构,以此来确保唯一性。如:GOPATH/src/github.com/xcbeyond/projectName/...。
  • 程序入口包名必须是 main 包。
  • 同一目录下的所有 go 源文件都属于同一个包。

2、包的使用

有了包的存在,必然少不了对其导入,可借助 import 关键字导入想使用的包。具体语法如下:

1
arduino复制代码import "包路径"

注意:

  • import 导入语句需放在源码文件开头的包声明语句的下面。
  • 导入的包名需要使用双引号 "" 括起来。
  • 包名是从 GOPATH/src/ 后开始计算的,使用 / 进行路径分隔。

通常导入包有单行导入和多行导入两种方式。

单行导入:

1
2
go复制代码import "包1路径"
import "包2路径"

多行导入:

1
2
3
4
go复制代码import (
"包1路径"
"包2路径"
)

2.1 导入路径

关于包的导入路径有两种方式,分别是全路径导入和相对路径导入。

全路径导入:

包的绝对路径就是 GOROOT/src/ 或 GOPATH/src/ 后面包的存放路径,如下所示:

1
2
3
go复制代码import "lab/test"
import "database/sql/driver"
import "database/sql"

其中:

  • test 包是自定义的包,其源码位于 GOPATH/src/lab/test 目录下。
  • driver 包的源码位于 GOROOT/src/database/sql/driver 目录下。
  • sql 包的源码位于 GOROOT/src/database/sql 目录下。

相对路径导入:

相对路径只能用于导入GOPATH 下的包,标准包的导入只能使用全路径导入。

例如,包 a 的所在路径是 GOPATH/src/lab/a,包 b 的所在路径为 GOPATH/src/lab/b,如果在包 b 中导入包 a ,则可以使用相对路径导入方式。示例如下:

1
2
go复制代码// 相对路径导入
import "../a"

2.2 包的引用

包的引用有四种格式,下面以 fmt 包为例分别进行说明。

  1. 标准引用格式
1
go复制代码import "fmt"

用 fmt. 作为前缀来使用 fmt 包中的方法,这是最为常用的一种方式。

示例如下:

1
2
3
4
5
6
7
go复制代码package main

import "fmt"

func main() {
fmt.Println("Hello World!")
}
  1. 自定义别名引用格式

在导入包的时候,我们可以为导入的包设置别名,简化使用:

1
go复制代码import F "fmt"

其中 F 就是 fmt 包的别名,可以用 F.来代替标准引用格式的 fmt. 来作为前缀使用 fmt 包中的方法。

示例如下:

1
2
3
4
5
6
7
go复制代码package main

import F "fmt"

func main() {
F.Println("Hello World!")
}
  1. 省略引用格式
1
go复制代码import . "fmt"

相当于把 fmt 包直接合并到当前源文件中,在使用 fmt 包内的方法是可以不用加前缀 fmt.而直接引用。

示例如下:

1
2
3
4
5
6
7
go复制代码package main

import . "fmt"

func main() {
Println("Hello World!")
}
  1. 匿名引用格式

在引用某个包时,如果只是希望执行包初始化的 init 函数,而不使用包内部的数据时,可以使用匿名引用格式:

1
go复制代码import _ "fmt"

使用标准格式引用包,但代码中却没有使用包,编译器是会报错。如果包中有 init 初始化函数,则通过import _ "包的路径" 这种方式引用包,仅执行包的初始化函数,即使包没有 init 初始化函数,也不会引发编译器报错。

示例如下:

1
2
3
4
5
6
7
8
9
10
go复制代码package main

import (
_ "database/sql"
"fmt"
)

func main() {
Println("Hello World!")
}

2.3 包的初始化

通过前面的学习相信大家已经大体了解了 Go 程序的启动和加载过程,在执行 main 包的 main 函数之前, Go 引导程序会先对整个程序的包进行初始化。整个执行的流程如下图所示:

包的初始化.png

Go语言包的初始化有如下特点:

  • 包初始化程序从 main 函数引用的包开始,逐级查找包的引用,直到找到没有引用其他包的包,最终生成一个包引用的有向无环图。
  • Go 编译器会将有向无环图转换为一棵树,然后从树的叶子节点开始逐层向上对包进行初始化。
  • 单个包的初始化过程如上图所示,先初始化常量,然后是全局变量,最后执行包的 init 函数。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

做了六年多技术管理,聊一些经验总结

发表于 2021-03-25

前言

我是从 2014 年开始正式走上管理之路的,在那之前虽然也有带过几个初级程序员,但毕竟不是正式的管理职位。正式踏上管理岗是从做一个小主管开始的,刚开始只管理几个人;之后担任过一些业务线的技术负责人,管理十几二十人;最多时管理百人团队,负责整个研发部门。一路从技术主管,到技术经理,再到技术总监,中间也和别人合伙创业当过 CTO。有空降管理过现成的团队,也有不止一次从 0 到 1 组建团队的经验。

六年多的管理经验,说多不多,但说少也不少,肯定也有自己的一些心得体会,如今就用文字来和大伙分享我的一些经验总结。

我打算根据管理的三个级别来聊:技术主管、技术经理、技术总监。这里所说的这三个级别,并不是指代具体的管理岗位名称,可以简单理解为技术团队中的基层管理者、中层管理者、高层管理者,具体的再一一细说。

技术主管

如刚才所说,技术主管并不指代具体的岗位,而是指初级技术管理人员,主要负责管理某一垂直技术领域,包括管理该领域的基层技术人员。比如 Android 主管、iOS 主管、前端主管、Java 主管、Golang 主管等。有些公司也称为技术组长,而且也不一定设置明确岗位。另外,在有些小公司,管理层级少,可能就没有设置技术主管的岗位,而直接挂名技术经理,比如我的第一份正式管理岗,挂名就是 App 技术经理,管理 Android 和 iOS。那时候的我其实既是基层管理者,也是中层管理者。这在小公司很正常,甚至处于初创期的 CTO,还需要同时担任高层、中层、基层所有的管理工作。

技术主管所管理的人员一般只有几个人,多的可能十几个。如果人员超过 20 人,最好对团队根据细分领域做一下拆分。比如 App 人员如果超过 20 人,那就可以拆分为 Android 组和 iOS 组,每组再分别设置一个主管,而原先的 App 主管则可以升级为技术经理。

对公司部门来说,技术主管优先考虑肯定是从内部人员中提拔,条件不满足的情况下才从外部招聘。比如,组建新团队的时候;或当前团队都只有些初级工程师,缺乏能够独当一面的人;或团队中都是些技术宅,只想专研技术,不想做管理。这些情况下,一般就需要从外部招聘合适的人选。

能荣升技术主管的,一般工作经验 3-5 年,专业技术能力已经非常娴熟,可达到资深级别,还具备一定的架构能力,能够独当一面。学习能力、沟通能力、对业务的理解能力等,也都是比较出众的。一般可以对标阿里的 P6 级别。

想要做好技术主管的工作,也不是那么轻松的。作为一名技术主管,平时大部分时间依然还是用在了技术设计、写代码、解决 Bug 等工作,这和基层的程序员没多大区别。但是,技术主管除了需要做好这些程序员本身的工作之外,还需要花时间做开发任务的分解、分配,以及代码 review、技术设计评审、面试、和团队内外的人员沟通协作等管理工作。因此,想要做好技术主管的工作,提高时间管理的能力是很有必要的。不然的话,就会把自己搞得很忙很累,最后管理工作没做好,还影响了作为程序员本身的工作。也因此,有些人就会开始退缩,不愿意当技术主管,觉得会占用自己过多的时间,平时写代码的时间都不够,哪有时间做管理。想要在管理这条路上不断往上爬,这是必须要迈过去的第一道坎。

从程序员升级为技术主管,最核心的转变就是:从管理自我到管理他人。所以,我想谈谈关于管理他人的一点经验,主要还是分享下在选人和用人上我自己的一些做法。

关于招人选人,我有一个标准,也是我最看重的一点,那就是候选人的深度思考能力。不只是技术人员,包括产品经理、UI 设计、测试人员等,我都会考察他们的深度思考能力。深度思考能力越强的人,越能看到问题的本质,各方面的能力也会越优秀。

那么,如何考察候选人的深度思考能力呢?其实也简单,多问些为什么就可以了。比如,对于应聘架构师的候选人,可以类似下面这样层层追问下去:

  1. 问:你们系统采用什么样的架构?答:微服务架构
  2. 问:为什么采用微服务?答:为了快速迭代
  3. 问:为什么用了微服务就能实现快速迭代?答:服务间解耦,可以分小组分别独立开发、测试和部署
  4. 问:分了多少个小组?每个小组多少人?为什么这么分?答:……

这些相互关联的问题,是可以不断追问下去的,问题也并非有标准答案的,也并不是考察候选人是否知道正确答案,而是考察他是否思考过这些问题,是否有自己的一些想法。当然,候选人也不可能对所有领域的问题都能答得上来,所以尽量多方面考察,并尽量从候选人所熟悉的领域进行深入。

再说说用人方面,我比较崇尚于为下面每个人的自我成长而负责。我会去了解每个人的职业规划,为他们的职业发展路线提出建议,并在工作中不断给他们提供成长的机会,包括分配的任务、提供的技术指导和培训、定期的一对一沟通,等等。其实,从本质上来说,就是为了激发他们的善意和潜能。

我做基层管理时就已经开始实践选人用人的这些方法论,而且成效还非常不错。

符合我的标准招进来的人,做事基本都是很高效的,大多都能成为团队里的骨干成员,有时还能做到远超我的预期,有着突出的表现。不过,有时候,长时间没招到合适人选或急需用人时,我只能减低标准,这时候招进来的人,则有些参差不齐了,部分人虽然也能完成任务,但成果就是不尽如人意。

也因为我用人的方式注重于他们的成长,所以,他们也很尊重我、支持我、追随我。我管理过的团队,离职率也一向比较低。

作为基层管理者的技术主管,建议重点培养自己的以下能力:

  1. 专业技术能力:这是技术管理者的立身之本,肯定需要不断精进,如果技不如人,是无法服众的。
  2. 业务理解能力:对业务有正确的理解,甚至能理解到业务的本质需求,才能让技术实现价值。
  3. 任务分解能力:技术主管承担着开发任务分解分配的职责,如果分解不当,漏掉了一些环节,就会导致任务的延迟、质量的不可控,为项目带来了风险。
  4. 时间管理能力:管理者需要在有限的时间里高效地管理多种事情,自然就需要提高时间管理能力。
  5. 团队建设能力:管理者的核心价值就是打造出一支优秀的团队。
  6. 向上管理能力:向上管理没做好,会影响职业的发展,但切记,向上管理并不是拍上级的马屁。
  7. 领导力:领导力不同于管理力,不能靠职权,而是靠个人魅力,建议尽早培养。需要明白一点,大部分技术人员更喜欢被“领导”,而不是被“管理”。

技术经理

技术主管作为基层管理人员,更多时候只是个执行者,要求能够「正确地做事」,能够带领一线团队高效地执行上级所交代的任务。

技术经理,作为中层管理人员,主要职责则是根据高层管理所确定的目标,制定实现目标的具体计划并保证实施,还要为最后的实现结果负责。

技术经理具体的工作职责,不同公司会有所不同,但主要可能包括:制定技术规范、制定工作计划、项目整体的架构设计和架构优化、跟进项目进度、团队建设、与其他部门的协调沟通等。

对技术经理的工作年限一般要求 5 年以上,技术上对架构能力的要求高一些,本身至少也应该是个能够独当一面的架构师或技术专家,可以对标阿里的 P7 级别。不过,在具体要求上,大厂和中小厂是不一样的。大厂对技术深度的要求会更高,小厂则比较看重技术广度。但大厂基本很少对外招聘管理岗,同级别的高 P 技术岗反而会招得多。所以,大部分人只能在中小企业发展管理路线。另外,技术经理也不一定是从技术主管升上去的,也可以从高 P 的技术专家转岗的。

在管理能力上,对技术主管所要求的也同样对技术经理有要求,而且要求更高。比如,业务理解能力,技术主管更多只是停留在对业务局部的一些点和线方面,而技术经理应该精通业务,对业务应有全局观。再比如,团队建设方面,技术主管更多只是偏于对个人提供技术指导,而技术经理则需要制定具体的团队建设方案,比如制定技术培训方案,以提高团队整体的技术水平。

技术经理还有一个核心工作就是培养技术主管。如何培养呢?最核心的一点就是要懂得授人以渔,教以方法论,而不是一旦出现问题就直接帮他解决问题。技术主管上任初期普遍会存在一些不足,比如,在任务分解方面会做得不太好,经常会分解得不彻底,会导致增加很多沟通成本甚至任务延迟;面试时也不太懂得如何抓重点,会浪费很多时间;团队成员出现分歧时,也不太懂得如何妥善处理。这些都需要技术经理花时间、花精力去慢慢指导技术主管,要让技术主管明白背后的方法论,而不要简单地丢给他解决方案。

我做技术经理的时候,还担任过公司里某些业务线的技术负责人,统筹管理项目的技术研发进度,其实就是项目管理。有些公司,会设置专岗来做项目管理,一般称为项目经理。但不少公司和我一样,是由技术经理兼做项目管理的。另外,还有部分公司,会由产品经理来兼做项目管理。

其实,要做好项目管理,对业务和技术两方面都熟悉是再好不过的。毕竟,从流程来看,项目管理包含了需求、设计、开发、测试、上线五个阶段,前两个阶段是业务强相关的,后三个阶段是技术强相关的。因此,最好的项目管理人员,应该是既懂业务又精于技术的,才能更好地统筹全局。但现实情况却是这样的人比较稀少,所以,更多时候,一个项目的前两个阶段主要由指定的产品经理进行管理,后三个阶段则由指定的技术负责人进行管理。而统筹全局的人,则从两人中再指定一人,或直接由上级领导来统筹。所以,确切来说,我当时所担任的项目管理,其实只是技术层面的项目管理,统筹项目全局的是我的上级领导。

技术层面的项目管理,我主要采用敏捷开发方法,并结合 TAPD 或 TOWER 等工具进行管理。项目管理涉及到的具体事务不少,我只挑几个重点说一下:

  • 代码分支管理:建议用 Git,不要用 SVN。要制定适合团队和项目情况的代码分支管理规范,可以从简单的 TrunkBased 模式开始,在实践中再不断去优化演进。
  • 每日站会:站会的时间控制在15分钟内,目的主要是同步项目进度,发言要简明扼要、关注重点、禁止报流水账,可提出问题,但切记不要在站会中讨论解决问题,留待会后再去沟通解决。
  • 复盘总结:每次版本迭代结束后,应该组织复盘总结会,这很重要,总结成功经验,吸取失败教训,有助于提升团队能力。
  • 质量管理:这应该是项目管理中最重要但却是最难管理的一块了,其会贯穿整个研发流程中几乎每一个阶段。主要的管理工具包括测试驱动开发、设计评审、code review 等。

作为中层管理者,技术经理一般不会对基层员工进行直接管理,因此,想要管理好下面的整个团队,更需要提升自己的领导力,通过领导力而不是职权来让基层员工信服。

技术总监

高层技术管理岗,大厂和中小厂在这个级别上对管理者的能力要求,差距非常大。比如,阿里的总监级别,职级一般得在 M4 以上,M4 对应于 P9。阿里的职级体系有两条线,P 系列为技术岗,M 系列为管理岗,对应关系为:

  • P6 = M1,主管
  • P7 = M2,经理
  • P8 = M3,资深经理
  • P9 = M4,总监
  • P10 = M5,资深总监

再往上就不列举了,马云卸任前是最高级别,为 M10。

而一般小公司的技术总监,跳到阿里可能只会给到 P7 级别,很优秀的可给到 P8,能达到 P9 的绝对是凤毛麟角。大部分技术总监难以达到 P9 或 P8,很多时候是因为技术深度达不到高 P 级别的要求。因为小公司的技术总监,能力更偏向于“全能型”,优势在于广度,而深度难免会成为短板。而大厂因为分工精细化,对广度反而没什么要求,但对深度要求很高。

另一方面,大厂的高 P 们跳去小公司当技术总监或 CTO,很多人也会面临广度不足的问题,难以很好地统筹全局。因此,习惯了大公司“精细化”模式的人也未必能满足小公司“全能型”的需求。

所以说,大厂和小厂的总监,几乎是两个不同的方向。而我自己也没有大厂总监的经验,所以我在这方面的经验主要适用于中小厂。

我的总监级别的管理经验,也有三年多了,具体岗位担任过技术总监、研发总监、CTO。管理的团队人员最多时近百人,最少时则是从 0 搭建。当 CTO 的时候责任最大,但团队的人员却是最少的,最多时也就 20 多人,后来因为熊市来了,资金链断裂,融资失败,团队最终解散。担任研发总监时,管理的团队是最大的,整个研发部门有百号人,包括技术人员,也包括产品和运营人员。

作为技术总监/研发总监/CTO,最核心的能力就是能够组建和管理整个研发部门,打造出一个高效的研发团队。

先聊聊从 0 组建团队的经验,这方面我有过两次经历。从 0 组建团队,最核心的还是如何才能招到合适的人选。最优的方案就是从自己的人脉中入手,以前带过的下属,或熟悉的同事、朋友,觉得优秀合适的可以试着挖过来,每个岗位上的人员,最好都是能够独当一面,后续有能力担任技术主管的。我当 CTO 时组建的团队,有好几个核心骨干就是我以前带过的下属,他们之所以愿意跟随我,部分原因还是因为认可我的领导力。这里要补充说一下,前面我就建议从技术主管开始就重点培养领导力,因为,领导力发挥作用的时候,不只是对在职的团队成员。

次优的方案就是靠别人推荐了,最后的方案才是进行社招。而不管是推荐还是社招,有些岗位,技术总监可能不熟悉相应的技术,就难以考察候选人的实际能力。我自己倒不存在这样的问题,毕竟我自己是个全栈。但大部分总监却非如此,那么,我提供三种方案:

  1. 请技术专家帮忙面试,并给予相应的酬劳。
  2. 请技术专家出一些面试题,并提供参考答案。
  3. 总监自己花时间去了解相应的技术。

这三种方案,效果一般也是由高到低。花点钱请相应的技术专家帮忙面试是最好的选择,现在普遍都是视频面试,也比较方便。

接着,再跟大伙分享下我管理近百人的整个研发部门的一些经验。整个团队包括了产品经理、设计师、开发、测试、运维、运营等人员,需并行研发多个项目。有些公司的研发部门可能不会包括产品经理、设计师、运营人员,不过没关系,管理方法也是一样的。

管理百人级别的研发团队,第一个核心工作,就是采用合适的组织结构。一般有三种类型的组织结构:职能型、项目型、矩阵型。

职能型的组织结构,即是对团队成员按不同职能划分为多个小组,比如分为:产品组、设计组、前端组、App组、Java 组、Golang 组、测试组、运维组、运营组。每个小组再分别设置一个组长,管理各职能小组的成员和相应的职能事务。主要优点就是能够发挥各职能小组的集中优势,人员调配上也有较大的灵活性。主要缺点就是在项目管理上,完成项目需多个小组相互配合,但项目经理缺少权力,协调难度较大,难以做到快速迭代。

项目型的组织结构,即是将团队成员按不同项目划分为多个项目组,每个项目组都分别有自己的产品、设计、开发、测试、运营等人员,每个项目组再分别设置一个项目经理,管理项目中的所有事务和人员。优点就是项目经理对项目可以全权负责,包括对项目成员也有全部权力,项目决策快、效率高,也可做到快速迭代。缺点则是项目组相对封闭,资源无法共享,很容易造成资源浪费,且项目之间缺乏交流,知识和经验也难以在不同项目组之间共享,对团队整体的提升造成阻碍。

矩阵型的组织结构,则是职能型和项目型的混合体,可对两种结构的优缺点进行取长补短,是目前大部分互联网公司所采用的方式。矩阵型结构,项目成员会有双重领导,职能经理和项目经理都是他/她的上级,对员工容易产生焦虑和压力。且如果权力划分不明确,两位领导容易产生冲突。

根据项目经理和职能经理权力的强弱关系,矩阵型结构还可以再细分为:弱矩阵、强矩阵、平衡矩阵。弱矩阵下,职能经理的权力更高,项目经理的角色更像个协调者。强矩阵则是项目经理有着更高权力,管理上更偏向于项目。平衡矩阵自然就是两位经理的权力都差不多,取平衡,而平衡之道其实也是最微妙的。

我这边主要尝试过项目型和弱矩阵型,从效果来看,弱矩阵型的组织架构会更加合适。至于平衡矩阵型,想要达到好的效果,需要精心建立管理体系,且对协调人的能力要求较高,而身边缺乏这样的人。另外,还有一种方案,就是让职能经理同时兼任项目经理,我曾任技术经理时就是这样,但我担任总监时,却缺乏符合要求的人。

作为技术总监,组建起研发团队只是第一步,想让这个团队变得高效,还需要做很多事情,也有很多方法,但回归到最本质上,还是要尽一切努力去激发成员们的善意与潜能。

总结

技术管理的方方面面还很多,限于篇幅,暂时就先聊这么多了。总结陈词,我只说两点:

  1. 技术一定不能落下,不管是主管,经理,还是总监,最最核心的还是技术。
  2. “管理的本质,是激发人的善意与潜能。” 谨记这句话并时刻践行之。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

职场必备 3 款免费画图工具推荐

发表于 2021-03-25

图片

为什么要画图

人在职场,沟通协作是重中之重。准确表达自己的思路和想法,将信息有效传送到协作者那里,做到理解无偏差,是做成事情,做好工作,推动事业成长的关键。

一般情况下,我们文字交流或者口头沟通比较多,但前者携带信息少,容易产生歧义,后者缺乏严谨性,容易造成我说东你听到西的现象。

如果想要更好的沟通,就需要有一种方式,既能携带充分的信息,避免歧义,又能保证严谨,交流不跑偏。

画图就是很好的方式。你有没有过这种时刻?向老板汇报的时候,噼里啪啦讲了一大堆,老板却云里雾里。其实只要有一张图,对着图给老板讲,它就容易接受很多。

所谓 一图胜千言。同学们要努力锻炼自己的画图功底啊,会对职场进阶有很大帮助!

工欲善其事,必先利其器!蜗牛今天就把自己常用的画图工具推荐给大家,帮助大家提升效率!

优质画图工具推荐

Zen Flowchart

工具特点:超简洁的流程图制作工具。虽说只有简单的形状和少数的颜色,也足以帮你画出简单优雅的图,比如常见流程图,组织架构图,系统流程图,算法流程图等等。画完可以导出 PNG 图片或者发布成实时文档使用。

推荐星级:⭐⭐⭐⭐

使用入口:zenflowchart.com

图片

图片

图片

组织架构图

ProcessOn

工具特点:在线作图工具的聚合平台,一个字:全!流程图、思维导图、UI 原型图、UML、网络拓扑图、组织架构图,你可能用到的,基本可以在这里找到。更优秀的是,这个平台开放了知识共享能力,也就是别人优秀的图,你可以直接拿来当模板用!缺点就是免费文件数有限。

推荐星级:⭐⭐⭐⭐

使用入口:processon.com

图片

图片

图片

diagrams

前身是 draw.io。

工具特点:在线作图工具的聚合平台,除了全之外,最令我惊喜的两个点,一个是导出的图片支持导入后再次编辑!另一个是可以直接导出到 GitHub 上,这对于程序员群体是相当友好便捷的。另外有丰富的剪贴画可以搜索直接用,蜗牛的很多图都会用这个工具。

使用入口:diagrams.net

推荐星级:⭐⭐⭐⭐⭐

图片

总结

本期蜗牛推荐的免费画图工具基本能覆盖各行业各工种的的大部分画图诉求,还有很多画图工具,它们各有优势和特点,需要基于具体的使用场景来做选型。蜗牛后边也会将自己常用的或者认为不错的画图工具找出来,继续推荐给同学们。


我是蜗牛,大厂程序员,专注技术原创和个人成长,正在互联网上摸爬滚打。欢迎关注我,和蜗牛一起成长,我们一起牛~下期见!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

EventListener注解 -【Spring底层原理】

发表于 2021-03-25

blog59

上篇我们讲到实现事件监听可以使用实现ApplicationListener接口 Spring中ApplicationListener -【Spring底层原理】,如果有多个方法需要监听事件,那岂不是每个方法都要放在类中实现ApplicationListener接口,这样并不是很方便,所以spring为我们提供了另外一种方式实现事件监听:使用@EventListener注解

一、注解用法

注解源码如下,有如下作用:

  • 可以作用在方法
  • 参数可以是class数组,可以写多个事件
  • 使用了该注解的方法,当容器中发布事件后,该方法会触发,类似实现ApplicationListener接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码 /*
* @author Stephane Nicoll
* @author Sam Brannen
* @since 4.2
* @see EventListenerMethodProcessor
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {

@AliasFor("classes")
Class<?>[] value() default {};

@AliasFor("value")
Class<?>[] classes() default {};

String condition() default "";
}

从注释可以看到是使用EventListenerMethodProcessor这个处理器来解析方法上的EventListener注解,EventListenerMethodProcessor主要则是通过其实现的接口SmartInitializingSingleton来进行处理的,后面会分析其源码。

二、实例分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
java复制代码// 启动测试类
@Test
public void TestMain(){
// 创建IOC容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
// 自己发布一个事件
applicationContext.publishEvent(new ApplicationEvent("自己发布的事件") {
});
applicationContext.close();
}

// 事件触发
@Service
public class UserService {
// 当容器中发布事件后,该方法会触发
@EventListener(classes = {ApplicationEvent.class})
public void listener1(ApplicationEvent event){
System.out.println("监听到的事件1:" + event);
}
@EventListener(classes = {ApplicationEvent.class})
public void listener2(ApplicationEvent event){
System.out.println("监听到的事件2:" + event);
}
}

// 配置类
@ComponentScan("listener")
@Configuration
public class AppConfig {
}

运行启动类,可以看到,两个事件都被触发了,使用@EventListener注解,方便让多个方法触发

image-20210324135958362

三、源码分析

上面讲到是使用EventListenerMethodProcessor这个处理器来解析方法上的EventListener注解,点进EventListenerMethodProcessor查看,发现实现了SmartInitializingSingleton接口,主要就是通过该接口实现的。

1
java复制代码public class EventListenerMethodProcessor implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public interface SmartInitializingSingleton {

/**
* Invoked right at the end of the singleton pre-instantiation phase,
* with a guarantee that all regular singleton beans have been created
* already. {@link ListableBeanFactory#getBeansOfType} calls within
* this method won't trigger accidental side effects during bootstrap.
* <p><b>NOTE:</b> This callback won't be triggered for singleton beans
* lazily initialized on demand after {@link BeanFactory} bootstrap,
* and not for any other bean scope either. Carefully use it for beans
* with the intended bootstrap semantics only.
*/
void afterSingletonsInstantiated();

}

SmartInitializingSingleton接口有个afterSingletonsInstantiated方法,当单实例bean全部创建完成,会触发这个接口,执行afterSingletonsInstantiated方法,类似于ContextRefreshedEvent

我们在afterSingletonsInstantiated方法上打上断点,看看源码是何时调用该方法执行的。

image-20210324144953217

通过方法调用栈,容器创建对象,调用refresh()方法刷新容器——>finishBeanFactoryInitialization(beanFactory)——>preInstantiateSingletons()初始化剩下的单实例bean

  • 创建所有的单实例bean
  • 获取所有创建好的单实例bean,判断各bean是否是SmartInitializingSingleton类型的
  • 如果是则调用afterSingletonsInstantiated方法

这里便到了我们上面分析的SmartInitializingSingleton#afterSingletonsInstantiated方法,也就是@EventListener注解注解起作用的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
java复制代码@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}

// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

// 遍历beanName,创建bean,即非懒加载单实例bean的初始化
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
// 创建对象
getBean(beanName);
}
}
}

// Trigger post-initialization callback for all applicable beans...
// 创建完bean后判断各bean是否实现了SmartInitializingSingleton,如果是则执行 smartSingleton.afterSingletonsInstantiated()方法
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize")
.tag("beanName", beanName);
SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
// 执行afterSingletonsInstantiated
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
smartInitialize.end();
}
}
}

四、总结

  1. IOC容器创建对象并refresh刷新
  2. finishBeanFactoryInitialization(beanFactory)——>preInstantiateSingletons()初始化剩下的单实例bean
    1. 创建所有的单实例bean
    2. 获取所有创建好的单实例bean,判断各bean是否是SmartInitializingSingleton类型的
    3. 如果是则调用afterSingletonsInstantiated方法

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python 怎么捕获警告?(注意:不是捕获异常)

发表于 2021-03-24
  1. 警告不是异常

你是不是经常在使用一些系统库或者第三方模块的时候,会出现一些既不是异常也不是错误的警告信息?

这些警告信息,有时候非常多,对于新手容易造成一些误判,以为是程序出错了。

实则不然,异常和错误,都是程序出现了一些问题,但是警告不同,他的紧急程度非常之低,以致于大多数的警告都是可以直接忽略的。

如果不想显示这些告警信息,可以直接加上参数 -W ignore 参数,就不会再显示了。

  1. 警告能捕获吗

能捕获的只有错误异常,但是通过一系列的操作后,你可以将这些警告转化为异常。

这样一来,你就可以像异常一样去捕获他们了。

在不进行任何设置的情况下,警告会直接打印在终端上。

  1. 捕获警告方法一

在 warnings 中有一系列的过滤器。

值 处置
"default" 为发出警告的每个位置(模块+行号)打印第一个匹配警告
"error" 将匹配警告转换为异常
"ignore" 从不打印匹配的警告
"always" 总是打印匹配的警告
"module" 为发出警告的每个模块打印第一次匹配警告(无论行号如何)
"once" 无论位置如何,仅打印第一次出现的匹配警告

当你指定为 error 的时候,就会将匹配警告转换为异常。

之后你就可以通过异常的方式去捕获警告了。

1
2
3
4
5
6
7
python复制代码import warnings
warnings.filterwarnings('error')

try:
warnings.warn("deprecated", DeprecationWarning)
except Warning as e:
print(e)

运行后,效果如下

  1. 捕获警告方法二

如果你不想对在代码中去配置将警告转成异常。

1
2
3
4
5
6
python复制代码import warnings

try:
warnings.warn("deprecated", DeprecationWarning)
except Warning as e:
print(e)

可以在执行的时候,只要加上一个参数 -W error ,就可以实现一样的效果

1
2
shell复制代码$ python3 -W error demo.py
deprecated
  1. 捕获警告方法三

除了上面的方法之外 ,warnings 还自带了个捕获警告的上下文管理器。

当你加上 record=True 它会返回一个列表,列表里存放的是所有捕获到的警告,我将它赋值为 w,然后就可以将它打印出来了。

1
2
3
4
5
6
7
8
9
python复制代码import warnings

def do_warning():
warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings(record=True) as w:
do_warning()
if len(w) >0:
print(w[0].message)

运行后,效果如下


文章最后给大家介绍三个我自己写的在线文档:

第一个文档:PyCharm 中文指南 1.0 文档

花了两个多月的时间,整理了 100 个 PyCharm 的使用技巧,为了让新手能够直接上手,我花了很多的时间录制了上百张 GIF 动图,有兴趣的前往在线文档阅读。

第二个文档:PyCharm 黑魔法指南 1.0 文档

系统收录各种 Python 冷门知识,Python Shell 的多样玩法,令人疯狂的 Python 炫技操作,Python 的超详细进阶知识解读,非常实用的 Python 开发技巧等。

第三个文档:Python 中文指南 1.0 文档

花了三个月时间写的一本 适合零基础入门 Python 的全中文教程,搭配大量的代码案例,让初学者对 代码的运作效果有一个直观感受,教程既有深度又有广度,每篇文章都会标内容的难度,是基础还是进阶的,可供读者进行选择,是一本难得的 Python 中文电子教程。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

盘点认证协议 普及篇之SAML

发表于 2021-03-24

总文档 :文章目录

Github : github.com/black-ant

  • 纯约束型协议 : OAuth , SAML , OIDC , CAS
  • 服务器类协议 : RADIUS , Kerberos , ADFS
  • 认证方式类 : OTP , 生物认证 (人脸 , 声纹 , 指纹)
  • 认证服务器(附带) : AD , LDAP

这一篇主要说SAML , 这货老而弥坚 !

一 . 前言

SAML 其实算是一种格式规范 , 他的全称是安全断言标记语言(英语:Security Assertion Markup Language,简称SAML,发音sam-el)是一个基于XML的开源标准数据格式 . 而我们应用中的 SAML 是一种宏观实现 ,通过 SAML 格式来传输认证信息 .

通常情况下 , 开发人员解除到的都是 OAuth , 第一印象往往会感觉 SAML 是一个很 ‘古老’ 的协议 , 但是接触多了就会发现 ,SAML 在一些重量级的应用里面随处可见 , 比如 老牌的Windows Server , 其中就大量使用了 SAML 认证 , 这协议确实老而弥坚 .

一句话说清楚 SAML 是什么流程 :
IDP Metadata 为 SSO (Server ) 元数据 , SP Metadata 为 Client 元数据 , 元数据文件中包含其本身的认证数据 (认证地址 , 签名 , 公钥等) , Server 和 Client 互相持有对方的元数据 , 通过对方元数据中的公钥进行加密 ,通过签名校验合法性 , 再通过 认证地址进行请求或者回调 .

二 . SAML 主要概念

2.1 行为概念

用一句话来解释其中的关联 : 服务端(客户端) 读取元数据信息, 使用指定的协议 , 通过绑定 , 将断言发送给对方 !

断言 (Assertions) 即信息:

断言是在 SAML 中用来描述认证的对象,其中包括一个用户在什么时间、以什么方式被认证,同时还可以包括一些扩展信息,比如用户的 Email 地址和电话等等。

协议 (Protocol) 即通信:

协议规定如何执行不同的行为。这些行为被细化成一些列的 Request 和 Response 对象,而在这些请求和相应的对象中包含了行为所特别需要的信息。比如,认证请求协议(AuthnRequest Protocol)就规定了一个 SP 如何请求去获得一个被认证的与用户。

绑定 (Binding) 即传输:

绑定定义了 SAML 信息如何使用通信协议被传输的。比如,HTTP 重定向绑定,即声明 SAML 信息将通过 HTTP 重定向消息传输;再比如 SAML SOAP 绑定,声明了通过 SOAP 来传递 SAML 消息。比如上面 AuthnRequest 就声明了 Http-POst 绑定

元数据 (MetaData):

SAML 的元数据是配置数据,其包含关于 SAML 通信各方的信息,比如通信另一方的 ID、Web Service 的 IP 地址、所支持的绑定类型以及通信中实用的密钥等等。

2.2 说明概念

  • 认证声明:声明用户是否已经认证,通常用于单点登录。
  • 属性声明:声明某个 Subject 所具有的属性。
  • 授权决策声明:声明某个资源的权限,即一个用户在资源 R 上具有给定的 E 权限而能够执行 A 操作。

2.3 元数据层次

MeteData 文件中总共有四个层次 :

  • 层次 一 : Assertion : 断言 saml:Assertion
  • 层次 二 : Protocols :规定如何请求(samlrequest)和回复(samlresponse)saml消息,当然包含assertion消息samlp:AuthnRequest + samlp:Response
  • 层次 三 :Bindings : 绑定 ,决定用何种方式进行传输
  • 层次 四 :Profile : 配套方案 ,

saml-metedata.png

2.4 证书作用

之前说了2点 , 一个是Server/Client 持有双方的元数据 , 一个元数据中包含了详细的证书,密钥信息,具体的元数据文件我们后面再说 , 这里先说说元数据里面的证书 .

可信实体包含公钥的证书会以X.509证书格式发布在metadata中,而对应的私钥则安全保存在本地。

这些密钥被用于消息层面的签名和加密,而SAML消息在传输过程中由TLS协议来进行安全交换。

阶段一 : 当IDP 拿到 SP 的请求时 , 证书的作用并不明显 , 主要有如下的作用

  • 确定请求来着信任的 SP

阶段二 : 当 SP 获取 IDP 的反馈时 , SP 会做以下几件事

  • 通过签名确定来自已知的 IDP
  • 获取使用IDP私钥签名的内容
  • 使用 IDP 公钥验证签名
1
2
3
4
5
6
7
8
java复制代码//IDP MetaData 密钥相关
- signing : 签名
- encryption : 加密

//SP MetaData 密钥相关
- sign : 签名
- encryption : 加密
- ds:Signature : IDP 密钥信息

IDP Metedata 文件参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
xml复制代码
<EntityDescriptor
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" entityID="http://127.0.0.1/samlServer/idp">
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0">
<Extensions>
<shibmd:Scope regexp="false">scope</shibmd:Scope>
</Extensions>
<KeyDescriptor use="signing">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>...[签名信息]...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>...[公钥信息]...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://127.0.0.1/samlServer/idp/profile/SAML2/POST/SLO"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://127.0.0.1/samlServer/idp/profile/SAML2/POST/SSO"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://127.0.0.1/samlServer/idp/profile/SAML2/Redirect/SSO"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://127.0.0.1/sso/idp/profile/SAML2/SOAP/ECP"/>
</IDPSSODescriptor>
</EntityDescriptor>

SP Metedata 文件参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
xml复制代码
<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="_5p2k1rpmlbr0hphrontb6zwiplqac1xxpzvzdma" entityID="http://127.0.0.1:9081/mfa-client/saml/callback" validUntil="2040-05-07T14:08:50.499Z">
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#_5p2k1rpmlbr0hphrontb6zwiplqac1xxpzvzdma">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>GSp3lIKMQs70Q6FQYHWFhVaGKJv31AiRTuXOcyO78mk=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
...[IDP签名信息]...
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
...[IDP公钥信息]...
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<md:Extensions xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport">
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#hmac-sha384"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#hmac-sha512"/>
<alg:SigningMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>
<alg:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<alg:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#sha384"/>
<alg:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
</md:Extensions>
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.0:protocol urn:oasis:names:tc:SAML:1.1:protocol">
<md:Extensions xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init">
<init:RequestInitiator Binding="urn:oasis:names:tc:SAML:profiles:SSO:request-init" Location="http://127.0.0.1/client/saml/callback?client_name=samlClient"/>
</md:Extensions>
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>...[签名信息]...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>...[公钥信息]...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://127.0.0.1/client/saml/callback?client_name=samlClient&amp;logoutendpoint=true"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="http://127.0.0.1/client/saml/callback?client_name=samlClient&amp;logoutendpoint=true"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://127.0.0.1/client/saml/callback?client_name=samlClient&amp;logoutendpoint=true"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://127.0.0.1/client/saml/callback?client_name=samlClient&amp;logoutendpoint=true"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://127.0.0.1/client/saml/callback?client_name=SAML2Client" index="0"/>
<md:AttributeConsumingService index="0">
<md:RequestedAttribute FriendlyName="eduPersonPrincipalName" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="false"/>
</md:AttributeConsumingService>
</md:SPSSODescriptor>
</md:EntityDescriptor>

三 . SAML 流程

3.1 SAML 成员及交互

SAML 中有2个重要的成员 :

  • SP(Service Provider) : 向用户提供正式商业服务的实体,通常需要认证一个用户的身份;
  • IDP(Identity Provider) : 提供用户的身份鉴别,确保用户是其所声明的身份

SAML01.png

SAML 请求流程 :

  1. 请求端作为一个资源访问者 , 期望访问服务商的资源
  2. SP 发现这是一个未认证的请求 , 此时会返回一个HTML , Form 的隐藏域中有一个 SAML 的认证请求数据包 , 实际上这里不需要手动 , HTML 会通过JS 自动执行到 IDP 中
  3. 通过 JS 会自动提交到 IDP 中 , 此时 IDP 接收到请求后 , 返回验证界面( 2-3 步在浏览器端不可见 )
  4. IDP 给浏览器返回一个待登录的页面 (即常规的登录页)
  5. 用户进行了认证 , 并且成功提交到 IDP
  6. IDP 组装一个HTML Form , 其中包含一个Response ,Response 中有一个对成功用户的断言 (用户信息及权限) , 有认证情况 , 及私钥签名等
  7. HTML 被自动提交到 SP , 前面说了 第六步的时候会同时返回一个私钥 , 这一步中 ,SP 会通过公钥校验断言是否合法
  8. 剩下的就是判断是一个合法用户 ,然后重定向到请求的页面中

SAML 中的成员定位

关于 IDP 和 SP 的角色定位 , 与OAuth不同 , 认证中心和资源服务被刻意的区分为了2个概念 ,在这2个概率里面 , 我一直有一个纠结 : 身份信息到底到谁手上?

关于这个其实不需要太纠结 , IDP 里面是存在身份数据的 , 同样 SP 里面也可能存在身份数据 , 通常而言 , IDP是一个认证中心 , 它认证完成后生成的credential在绝大多数下只会包含一个Username , 然后SP会拿着username 去直接使用或者获取更详细的信息

就像很多业务流程一样 , 认证后返回得仅仅是一个ID , 而具体得Userinfo 放在哪 , 按照业务去规划就行

3.2 SAML 的详细交互

前置条件 : 持有双方的 Metedata

  • Server 持有 Client 的 Metedata , 其中有 Client 的公钥及重定向地址
  • Client 持有 Server 的 Metedata , 其中有 Server 的公钥及重定向地址 , 及签名等信息

3.3 SAML 请求格式

参考 @ www.samltool.com/generic_sso…

之前提到了 , Metedata 中包含了 Server / Client 的访问信息 , 例如 :

1
2
3
4
java复制代码<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://127.0.0.1/samlServer/idp/profile/SAML2/POST/SLO"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://127.0.0.1/samlServer/idp/profile/SAML2/POST/SSO"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://127.0.0.1/samlServer/idp/profile/SAML2/Redirect/SSO"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://127.0.0.1/sso/idp/profile/SAML2/SOAP/ECP"/>

其中清楚的声明了访问的地址以及访问的方式 : 例如 urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST 断言说明通过 Post 形式的请求的访问地址 ,urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect 则说明了通过 Redirect (Get) 请求时的访问地址

SAML AuthnRequest 请求格式

1
2
3
4
5
6
7
xml复制代码<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="ONELOGIN_809707f0030a5d00620c9d9df97f627afe9dcc24" Version="2.0" ProviderName="SP test" IssueInstant="2014-07-16T23:52:45Z" Destination="http://idp.example.com/SSOService.php" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="http://sp.example.com/demo1/index.php?acs">
<saml:Issuer>http://sp.example.com/demo1/metadata.php</saml:Issuer>
<samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" AllowCreate="true"/>
<samlp:RequestedAuthnContext Comparison="exact">
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
  • ID : 新生成的标识号
  • IssueInstant : 时间戳,表示生成时间
  • AssertionConsumerServiceURL : 服务提供者的SAML URL接口,标识提供者在此发送身份验证令牌。
  • Issuer : 服务提供者的EntityID(唯一标识符)
  • InResponseTo : 此响应所属的SAML请求的ID
  • Recipient : 服务提供者的EntityID(唯一标识符)

当然实际请求是加密的 :

1
2
3
java复制代码http://127.0.0.1/samlServer/idp/profile/SAML2/Redirect/SSO?
SAMLRequest=bM441nuRIzAjKeMM8RhegMFjZ4L4xPBHhAfHYqgnYDQnSxC++Qn5IocWuzuBGz7JQmT9C57nxjxgbFIatiqUCQN17aYrLn/mWE09C5mJMYlcV68ibEkbR/JKUQ+2u/N+mSD4/C/QvFvuB6BcJaXaz0h7NwGhHROUte6MoGJKMPE=
&RelayState=http%3A%2F%2F127.0.0.1%2FclientSaml%2Fcallback

3.4 SAML 返回格式案例

参考 @ www.samltool.com/generic_sso…

3.5 Logout 案例

注意 ,实际请求过程中都是加密 ,去这里可以自己解密看看 –> www.samltool.com/attributes.…

Logout Request : www.samltool.com/generic_slo…

1
2
3
4
5
xml复制代码
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="ONELOGIN_21df91a89767879fc0f7df6a1490c6000c81644d" Version="2.0" IssueInstant="2014-07-18T01:13:06Z" Destination="http://idp.example.com/SingleLogoutService.php">
<saml:Issuer>http://sp.example.com/demo1/metadata.php</saml:Issuer>
<saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">ONELOGIN_f92cc1834efc0f73e9c09f482fce80037a6251e7</saml:NameID>
</samlp:LogoutRequest>

Logout Response :

1
2
3
4
5
6
java复制代码<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_6c3737282f007720e736f0f4028feed8cb9b40291c" Version="2.0" IssueInstant="2014-07-18T01:13:06Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_21df91a89767879fc0f7df6a1490c6000c81644d">
<saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
</samlp:LogoutResponse>

四 . 请求案例

1
2
3
4
5
6
7
8
9
java复制代码
//Step 1 : Client → Server :

http://127.0.0.1/samlServer/idp/profile/SAML2/Redirect/SSO?SAMLRequest=....&RelayState=http%3A%2F%2F127.0.0.1%2FclientSaml%2Fcallback

// Step 2 : Server doLogin

// Step 3 : Server → callBack → Client
http://127.0.0.1/2FclientSaml/callback?SAMLRequest=....&RelayState=http%3A%2F%2F127.0.0.1%2FclientSaml%2Fcallback

五 . SAML FAQ

5.1 SAML 与 OAuth 的区别

  • SAML使用XML传递消息,而OAuth使用JavaScript对象表示法
  • OAuth提供了一种更简单的移动体验 (OAuth广泛使用API调用),而SAML则面向企业安全
  • OAuth比SAML更适合访问范围。访问范围是一种实践,一旦身份验证,只允许资源/应用中最低限度的访问。

就像之前说的 ,SAML 往往被一些大厂实践 , 是偏企业级的单点方式 , 一般人接触 , 第一感觉就是复杂 , 但是重量级及意味着更多的功能 ,更企业级的安全性 , 各有各的特色

附录 . 补充概念

Assertion :

SAML 消息(XML 文档)的一部分,它提供关于断言主题的事实(通常是关于经过身份验证的用户)。断言可以包含有关身份验证、关联属性或授权决策的信息

Artifact :

标识符,该标识符可用于从标识或使用后台通道绑定的服务提供者检索完整的 SAML 消息

Bnding :

用于传递 SAML 消息的机制。绑定分为前端通道绑定和后端通道绑定,前者使用用户的 web 浏览器进行消息传递(例如 HTTP-POST 或 HTTP-Redirect) ,后者使身份提供者和服务提供者直接通信(例如在 Artifact 绑定中使用 SOAP 调用)

Dscovery :

用于确定应该使用哪个身份提供程序来验证当前与服务提供程序交互的用户

Metadata :

描述一个或多个身份和服务提供者的文档。元数据通常包括实体标识符、公钥、端点 url、支持的绑定和配置文件以及其他功能或需求。身份和服务提供者之间的元数据交换通常是建立联合的第一步

Profile :

用于实现特定用例的协议、断言、绑定和处理指令的标准化组合,如单点登录、单点注销、发现、工件解析

Protocol :

为 SAML 消息定义格式(模式) ,用于实现特定功能,例如从 IDP 请求身份验证、执行单个注销或从 IDP 请求属性

Identity provider (IDP) 身份提供者(IDP) :

知道如何认证用户并使用联邦协议向服务提供者/中继方提供有关其身份的信息的实体

Service provider (SP) 服务供应商 :

您的应用程序与身份提供者通信,以获取与其交互的用户的信息。身份验证状态和用户属性等用户信息是以安全断言的形式提供的

Single Sign-On (SSO) 单点登录(SSO) :

允许访问多个网站的进程,而不需要重复提供身份验证所需的凭证。可以使用各种联邦协议(如 SAML、 WS-Federation、 OpenID 或 OAuth)来实现 SSO 用例。身份验证方式、用户属性、授权决策或安全令牌等信息通常作为单点登录的一部分提供给服务提供者

Single Logout (SLO) 单一登出(SLO) :

在使用单点登录访问的所有资源上处理终止身份验证会话。通常使用的技术包括将用户重定向到每个 SSO 参与者或发送注销 SOAP 消息

参考与感谢

讲道理 , 挺多的 ,但是记得时候又没有把地址记下来 , 不好找了 , 直接祝大家身体健康吧

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

IntelliJ IDEA根据wsdl生成WebServic

发表于 2021-03-24

最近项目要用到一个发票开票接口,但是对方居然用的是webService的方式调用,而我们用的是SpringBoot

IDEA在2019的版本后就没有显示的集成WebService了,导致找了很多种方式,都没有很详细的说明白,具体要
怎么处理,刚好弄清楚,写个笔记记录下

我的IDEA版本2020.1, JDK1.8 项目环境:Springboot

1.新建module,选择Apach Axis

image.png
image.png

2.点击下一步,输入项目名称,后点完成

image.png

3.出现如下的界面,勾选需要生成的代码,这里要注意 一定要勾选所有的,尤其是测试类代码,可以让你直接测试代码是否生成OK

image.png

4.就会生成如下代码,其中测试类要放到测试模块

image.png

5.测试,直接找到对应的测试方法 ,Debug 跑一下 确认代码是否生成OK了

image.png

6.最后再项目调试的时候,发现缺了一些maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xml复制代码<dependency>
<groupId>org.apache.axis</groupId>
<artifactId>axis</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>javax.xml.rpc</groupId>
<artifactId>javax.xml.rpc-api</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>commons-discovery</groupId>
<artifactId>commons-discovery</artifactId>
<version>0.4</version>
</dependency>

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

人工智能--遗传算法(旅行商问题)

发表于 2021-03-24

人工智能**是研究使计算机来模拟人的某些思维过程和智能行为(如学习、推理、思考、规划等)的学科,主要包括计算机实现智能的原理、制造类似于人脑智能的计算机,使计算机能实现更高层次的应用。人工智能将涉及到计算机科学、 **心理学 、哲学和语言学等学科。可以说几乎是自然科学和社会科学的所有学科,其范围已远远超出了计算机科学的范畴,人工智能与思维科学**的关系是实践和理论的关系,人工智能是处于思维科学的技术应用层次,是它的一个应用分支。从思维观点看,人工智能不仅限于逻辑思维,要考虑形象思维、灵感思维才能促进人工智能的突破性的发展,数学常被认为是多种学科的基础科学,数学也进入语言、思维领域,人工智能学科也必须借用数学工具,数学不仅在标准逻辑、 **模糊数学等范围发挥作用,数学进入人工智能学科,它们将互相促进而更快地发展。

遗传算法是计算数学中用于解决最佳化的搜索算法 ,是进化算法的一种。进化算法最初是借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择以及杂交等。遗传算法通常实现方式为一种计算机模拟。对于一个最优化问题,一定数量的候选解(称为个体)的抽象表示(称为染色体)的种群向更好的解进化。传统上,解用二进制表示(即0和1的串),但也可以用其他表示方法。进化从完全随机个体的种群开始,之后一代一代发生。在每一代中,整个种群的适应度被评价,从当前种群中随机地选择多个个体(基于它们的适应度),通过自然选择和突变产生新的生命种群,该种群在算法的下一次迭代中成为当前种群。

  • **上机题简介**
  • 用C++语言编写和调试一个用遗传算法解旅行商TSP问题的程序。目的是学会运用知识表示方法和搜索策略求解一些考验智力的简单问题,熟悉简单智能算法的开发过程并理解其实现原理。

用遗传算法解旅行商TSP问题:假设有一个旅行商人要拜访n个城市,他必须选择所要走的路径,路径的限制是每个城市只能拜访一次,而且最后要回到原来出发的城市。路径的选择目标是要求得的路径路程为所有路径之中的最小值。

在遗传算法解旅行商TSP问题当中程序总体围绕了遗传算法的三个主要步骤:选择–复制,交叉,变异。给定了10个种群,即10条染色体,每条染色体都是除首位外不重复的点组成,首尾相同保证路线是闭合的,所以一条染色体包含11个点。

遗传算法解旅行商TSP问题实验原理:

TSP问题就是寻找一条最短的遍历n个城市的最短路径,即搜索自然数子集W={1,2..n}(W的元素表示对n个城市的编号)的一个排列。

遗传算法是具有“生成+检测”的迭代过程的搜索算法。它的基本处理流程如图1所示。由此流程图可见,遗传算法是一种群体型操作,该操作以群体中的所有个体为对象。选择( Selection)、交叉(Crossover)和变异(Mutation) 是遗传算法的3个主要操作算子,它们构成了所谓的遗传操作( genetic operation),使遗传算法具有了其它传统方法所没有的特性。遗传算子包含如下6个基本因素:

(1)参数编码:由于遗传算法不能直接处理解空间的解数据,因此必须通过编码将它们表示成遗传空间的基因型串结构数据。

(2)生成初始群体:由于遗传算法的群体型操作需要,所以必须为遗传操作准备一个由若干初始解组成的初始群体。初始群体的每个个体都是通过随机方法产生。

( 3)适应度评估检测:遗传算法在搜索进化过程中一般不需要其他外部信息,仅用适应度( fitness) 值来评估个体或解的优劣,并作为以后遗传操作的依据。

(4)选择(selection): 选择或复制操作是为了从当前群体中选出优良的个体,使它们有机会作为父代为下一代繁殖子孙。个体适应度越高,其被选择的机会就越多。此处采用与适用度成比例的概率方法进行选择。具体地说,就是首先计算群体中所有个体适应度的总和,再计算每个个体的适应度所占的比例,并以此作为相应的选择概率。

(5)交叉操作:交叉操作是遗传算法中最主要的遗传操作。简单的交叉(即一点交叉)可分两步进行:首先对种群中个体进行随机配对:其次,在配对个体中随机设定交叉处,配对个体彼此交换部分信息。

(6)变异:变异操作是按位(bit) 进行的,即把某一位的内容进行变异。变异操作同样也是随机进行的。一般而言,变异概率P都取得较小。变异操作是十分微妙的遗传操作,它需要和交叉操作配合使用,目的是挖掘群体中个体的多样性,克服有可能限于局部解的弊病。

遗传算法 解旅行商TSP问题 程序功能结构图:

流程图:

数据结构定义 :

//定义染色体的结构

struct Chrom

{

int cityArr[cityNum]; //染色体的基因编码

char name; //染色体的名称

float adapt; //染色体的适应度

int dis; //染色体的路径长度

};

struct Chrom genes[popSize]; //定义基因库(结构体数组)

struct Chrom genesNew[popSize]; //重新建立一个新的种群

struct Chrom temp; //定义临时公用结点

names[cityNum] = {‘A’,’B’,’C’,’D’,’E’,’F’,’G’,’H’,’I’,’J’}; //城市名称

distance[cityNum][cityNum] //城市距离矩阵

函数方法定义:

initGroup() //初始化基因库

popFitness() //计算种群所有染色体的个体适应度

chooseBest() //返回最优秀的一条染色体

select() // 选择操作

cross() //交叉操作

mutation() //变异操作

**遗传算法** **解旅行商TSP问题** **C语言代码:**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
objectivec复制代码

#include "stdio.h"
#include "stdlib.h"
#include "windows.h"
#include "time.h"

#define cityNum 10 //城市数量(基因数量)(染色体长度)
#define popSize 10 //种群大小(尺寸)
#define croRate 0.85 //交叉概率
#define mutRate 0.1 //变异概率
#define MAX 999 //进化代数


//定义染色体的结构
struct Chrom
{
int cityArr[cityNum]; //染色体的基因编码
char name; //染色体的名称
float adapt; //染色体的适应度
int dis; //染色体的路径长度
};
struct Chrom genes[popSize]; //定义基因库(结构体数组)
struct Chrom genesNew[popSize]; //重新建立一个新的种群
struct Chrom temp; //定义临时公用结点


char names[cityNum] = {'A','B','C','D','E','F','G','H','I','J'}; //城市名称

int distance[cityNum][cityNum] = {{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, //城市距离矩阵
{ 1, 0, 1, 2, 3, 4, 5, 6, 7, 8 },
{ 2, 1, 0, 1, 2, 3, 4, 5, 6, 7 },
{ 3, 2, 1, 0, 1, 2, 3, 4, 5, 6 },
{ 4, 3, 2, 1, 0, 1, 2, 3, 4, 5 },
{ 5, 4, 3, 2, 1, 0, 1, 2, 3, 4 },
{ 6, 5, 4, 3, 2, 1, 0, 1, 2, 3 },
{ 7, 6, 5, 4, 3, 2, 1, 0, 1, 2 },
{ 8, 7, 6, 5, 4, 3, 2, 1, 0, 1 },
{ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }}; //最优解为18

void initGroup()
{
//初始化基因库
int i,j,k;
int t = 0;
int flag = 0;
srand(time(NULL));//初始化随机种子,防止随机数每次重复,常常使用系统时间来初始化,当srand()的参数值固定的时候,rand()获得的数也是固定的
for(i = 0; i < popSize; i ++)
{
//使用临时结点开始赋值
temp.name = names[i];
temp.adapt = 0.0f;
temp.dis = 0;
//产生10个不相同的数字
for(j = 0; j < cityNum;)
{
t = rand()%cityNum; //随机产生0-9的数
flag = 1;
for(k = 0; k < j; k ++)
{
if(genes[i].cityArr[k] == t)
{
flag = 0;
break;
}
}
if(flag)
{
temp.cityArr[j] = t;
genes[i] = temp;//存入结构体数组,产生一个个体
j++;
}
}
}
}

//计算种群所有染色体的个体适应度
void popFitness()
{
int i,n1,n2;
for(i = 0; i < popSize; i ++)
{
genes[i].dis = 0;
for(int j = 1;j < cityNum; j ++)
{
n1 = genes[i].cityArr[j-1];
n2 = genes[i].cityArr[j];
genes[i].dis += distance[n1][n2];
}
genes[i].dis += distance[genes[i].cityArr[0]][genes[i].cityArr[cityNum-1]];
genes[i].adapt = (float)1/genes[i].dis; //每条染色体的路径总和(个体适应度)
}
}

//返回最优秀的一条染色体
int chooseBest()
{
int choose = 0;
float best = 0.0f;
best = genes[0].adapt;
for(int i = 0; i < popSize; i ++)
{
if(genes[i].adapt < best)
{
best = genes[i].adapt;
choose = i;
}
}
return choose;
}


// 选择操作
void select()
{
float biggestSum = 0.0f;
float adapt_pro[popSize];
float pick = 0.0f;
int i;
for(i = 0; i < popSize; i ++)
{
biggestSum += genes[i].adapt; // 总概率
}
for(i = 0; i < popSize; i ++)
{
adapt_pro[i] = genes[i].adapt / biggestSum; // 概率数组
}
// 轮盘赌
for(i = 0;i < popSize; i ++)
{
pick = (float)rand()/RAND_MAX; // 0到1之间的随机数

for(int j = 0; j < popSize; j ++)
{
pick = pick - adapt_pro[j];
if(pick <= 0)
{
genesNew[i] = genes[j];
break;
}
}
}
for(i = 0;i < popSize; i++)
{
genes[i] = genesNew[i];
}
}

void cross() // 交叉操作
{
float pick;
int choice1,choice2;
int pos1,pos2;
int temp;
int conflict1[popSize]; // 冲突位置
int conflict2[popSize];
int num1;
int num2;
int index1,index2;
int move = 0; // 当前移动的位置
while(move < popSize-1)
{
pick = (float)rand()/RAND_MAX; // 用于决定是否进行交叉操作

if(pick > croRate) //两条染色体是否相爱
{
move += 2;
continue; // 本次不进行交叉
}

// 采用部分映射杂交
choice1 = move; // 用于选取杂交的两个父代
choice2 = move+1; // 注意避免下标越界

pos1 = rand()%popSize;
pos2 = rand()%popSize;

while(pos1 > popSize -2 || pos1 < 1)//如果位置在开头或结尾(因为全部交换无意义)
{
pos1 = rand()%popSize;
}
while(pos2 > popSize -2 || pos2 < 1)
{
pos2 = rand()%popSize;
}

if(pos1 > pos2)
{
temp = pos1;
pos1 = pos2;
pos2 = temp; // 交换pos1和pos2的位置
}

for(int j = pos1;j <= pos2; j++)// 逐个交换顺序
{
temp = genes[choice1].cityArr[j];
genes[choice1].cityArr[j] = genes[choice2].cityArr[j];
genes[choice2].cityArr[j] = temp;
}

num1 = 0;
num2 = 0;

if(pos1 > 0 && pos2 < popSize - 1)//分三段
{
for(int j =0;j < pos1;j++)
{
for(int k = pos1; k <= pos2; k++)
{
if(genes[choice1].cityArr[j] == genes[choice1].cityArr[k])
{
conflict1[num1] = j;
num1 ++;
}
if(genes[choice2].cityArr[j] == genes[choice2].cityArr[k])
{
conflict2[num2] = j;
num2 ++;
}
}
}

for(j = pos2 + 1;j < popSize;j++)
{
for(int k = pos1; k <= pos2; k ++)
{
if(genes[choice1].cityArr[j] == genes[choice1].cityArr[k])
{
conflict1[num1] = j;
num1 ++;
}
if(genes[choice2].cityArr[j] == genes[choice2].cityArr[k])
{
conflict2[num2] = j;
num2 ++;
}
}

}
}
if((num1 == num2) && num1 > 0)
{
for(int j = 0;j < num1; j ++)
{
index1 = conflict1[j];
index2 = conflict2[j];
temp = genes[choice1].cityArr[index1]; // 交换冲突的位置
genes[choice1].cityArr[index1] = genes[choice2].cityArr[index2];
genes[choice2].cityArr[index2] = temp;
}
}
move += 2;
}
}

//变异操作
void mutation()
{
double pick;
int pos1,pos2,temp;
for(int i = 0;i < popSize; i ++)
{
pick = (float)rand()/RAND_MAX; // 用于判断是否进行变异操作
if(pick > mutRate)
{
continue;
}
pos1 = rand()%popSize;
pos2 = rand()%popSize;
while(pos1 > popSize - 1)
{
pos1 = rand()%popSize;
}
while(pos2 > popSize - 1)
{
pos2 = rand()%popSize;
}

int a = genes[i].dis;
temp = genes[i].cityArr[pos1];
genes[i].cityArr[pos1] = genes[i].cityArr[pos2];
genes[i].cityArr[pos2] = temp;

popFitness();//更新数据
//此步骤的作用在于检查是否变异后得到的个体比变异前更优秀了,如若往坏的方向变化了,那还不如不变异了
//(强制返回,虽然有点违背事物的客观发展规律,但为了增强程序的收敛性,该操作还是有必要的)(偷笑)
if(genes[i].dis > a)
{
temp = genes[i].cityArr[pos1];
genes[i].cityArr[pos1] = genes[i].cityArr[pos2];
genes[i].cityArr[pos2] = temp;
}
}
}

int main()
{
char c = 0;
printf("\n\t\t******************************** 遗传算法求解TSP(旅行商)问题 *********************************\n");
initGroup(); //初始化
popFitness(); //更新数据
//输出配置信息
printf("\n\t\t基因长度:%d",cityNum);
printf("\t种群大小:%d",popSize);
printf("\t交叉概率:%.2f",croRate);
printf("\t变异概率:%.2f",mutRate);
printf("\t进化代数:%d",MAX);
printf("\t预设最优解:18");
printf("\n\n\t\t**********************************************************************************************\n");


//输出距离矩阵
printf("\n\n\t\t--------- 城市距离矩阵 ---------\n");
printf("\t\t");
int i,j;
for(i = 0; i < cityNum; i ++)
{
for(j = 0;j < cityNum; j ++)
{
printf(" %d",distance[i][j]);
}
printf("\n\t\t");
}
printf("--------------------------------\n");

//输出路径信息
printf("\n\t\t-------- 初始种群基因库 --------\n");
printf("\t\t ");

for(i = 0; i < cityNum; i ++)
{
printf(" %c",genes[i].name);
}
printf("\n\t\t");
for(i = 0;i < cityNum; i ++)
{
printf("%c",genes[i].name);
for(j = 0; j < cityNum; j ++)
{
printf(" %d",genes[i].cityArr[j]);
}
printf("\n\t\t");
}
printf("--------------------------------\n");

do
{
printf("\n\t\t寻求最优解中:");
//通过不断进化,直到达到定义的进化代数
for(i = 0; i < MAX; i ++)
{
select();
cross();
popFitness();//更新数据
mutation();
popFitness();//更新数据
int temp = (int)MAX/20;

}
printf("完成");

printf("\n\n\t\t最优路径:");
for(i = 0; i < cityNum ; i++)
{
printf("%d-->",genes[chooseBest()].cityArr[i]);
}
printf("%d",genes[chooseBest()].cityArr[0]);
printf("\n\n\t\t路径长度:%d",genes[chooseBest()].dis);
printf("\n\n\t\t是否再试一次?(Y/y) 是/(N/n) 否:");
fflush(stdin);
c = getchar();
fflush(stdin);
if(c=='N'||c=='n')
{
break;
}
}while(1);

printf("\n\t\t");
return 0;
}
  • **总结体会**

在旅行商问题当中由遗传算法对以上情况的求解(10座城市),可以看出,用传算法来求解TSP问题是可行的。用遗传算法求解时,增加选代次数,可以得到更加优良的结果,但是会需要更长的时间,即一个优良的结果往往是以时间力代价的,这种情况要依据具体问题具体分析,平衡两者在问题求解时的比重。参数设置得好坏往往对遗传算法的性能和结果有着重要的影响,往往在解决某一问题的时候,需要设定的参数很多,如何获得最佳的参数组合是一个很重要的问题。另外,对选择算法进行优化,会提高遗传算法的性能,这些都需要在实践中不断科累和广泛涉猎优良算法。最后,遗传算法得到的未必是最优解,我们可以根据需要进行多次求解,从而比较得出符合要求的结果。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…699700701…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%