人的血液7年会全部替换一遍,鱼的记忆只有7秒。
在我们生活和工作中,会遇到很多需要用到二维码的地方。二维码在很多场合使用,比如打开链接,扫码支付,扫码打开链接,扫码加好友等等。今天我们使用go来生成一下二维码试试,我们可以用这个库来生成。github.com/skip2/go-qrcode1、生成静态二维码比如我们经常用到的,扫码打开一个网页。package main import ( "image/color" "log" "github.com/skip2/go-qrcode" ) func main() { qr, err := qrcode.New("http://www.alingfeng.cn/", qrcode.Medium) if err != nil { log.Fatal(err) } else { qr.BackgroundColor = color.RGBA{255, 255, 255, 255} qr.ForegroundColor = color.Black qr.WriteFile(256, "./go_code.png") } }运行一下,就可以看到我们的二维码图片2、生成动态二维码也有很多时候我们会用到动态的二维码,如扫码支付,扫码打开链接,扫码加好友等,结合http 库,动态生成qrcode 并返回。而无需保存成图片。package main import ( "fmt" "log" "net/http" "time" "github.com/skip2/go-qrcode" ) func main() { http.HandleFunc("/qrcode", Qrcode) log.Fatal(http.ListenAndServe(":8008", nil)) } func Qrcode(w http.ResponseWriter, req *http.Request) { var err error defer func() { if err != nil { w.WriteHeader(500) return } }() q, err := qrcode.New(fmt.Sprintf("http://www.alingfeng.cn/?t=%d", time.Now().Unix()), qrcode.Medium) if err != nil { return } png, err := q.PNG(256) if err != nil { return } w.Header().Set("Content-Type", "image/png") w.Header().Set("Content-Length", fmt.Sprintf("%d", len(png))) w.Write(png) }然后我们访问地址就可以看到图片: 127.0.0.1:8008/qrcode,每刷新一下二维码会相应变化。
查看详情什么是MD5?MD5(Message-Digest Algorithm 5)是一种消息摘要算法,用于计算数据的哈希值。它可以将任意长度的数据转换为一个128位的哈希值,该哈希值可以用作数据的识别码。 MD5算法的计算过程非常简单:对数据进行哈希处理,生成一个128位的数字,该数字表示原始数据的特征值。由于MD5算法采用的是单向哈希函数,即只能从哈希值中计算出原始数据,而不能从原始数据中计算出哈希值,因此可以确保原始数据不被篡改。 虽然MD5算法被广泛使用,但其安全性一直受到质疑。SHA-256算法相比MD5算法更强,能够更好地保护数据的安全性。因此,在实际应用中,建议使用SHA-256等更强的算法来保护数据的安全性。什么是SHA-256?SHA-256(Secure Hash Algorithm 256)是一种哈希算法,常用于数字签名、文件完整性校验、数据加密等场景。它是一种消息认证码,用于确保数据完整性,可以有效地防止数据被篡改或者伪造。 SHA-256算法的输入是任意长度的二进制数据,输出是一个长度为32字节的哈希值。该哈希值可以用作数字签名的密钥,或者用于文件完整性校验。在数字签名中,使用SHA-256算法将数据和密钥一起计算,生成一个哈希值,该哈希值可以用于验证数据的完整性。在文件完整性校验中,将待校验的文件的前n个字节与一个哈希值进行比较,如果哈希值匹配,则说明文件未被篡改。 SHA-256算法的安全性得到了广泛认可,是目前最常用的哈希算法之一。不过需要注意的是,任何算法都不是绝对安全的,需要根据实际情况选择合适的算法和安全策略。示例(Go)md5生成:import ( "crypto/md5" ) // 计算哈希值 func hash(data string) (string, error) { h := md5.New() _, err := h.Write([]byte(data)) if err != nil { return "", err } return hex.EncodeToString(h.Sum(nil)), nil } // 使用示例 data := "hello world" result, err := hash(data) if err != nil { return } fmt.Println(result) // "b0baee9d279d34fa1dfd71aadb908c3f"SHA-256生成:import ( "crypto/sha256" ) // 计算哈希值 func hash(data string) (string, error) { h := sha256.New() _, err := h.Write([]byte(data)) if err != nil { return "", err } return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil } // 使用示例 data := "hello world" result, err := hash(data) if err != nil { return } fmt.Println(result) // "sha256:a3c25f34ed9f1dfcc4b4b41a8058d3438df8e224b3973ab0bb5408cddbc58ee3c2"
查看详情5W2H分析法概念5W:Who、When、Where、What、Why2H:How、How MuchWHAT:是什么?目的是什么?做什么工作?WHY:为什么要做?可不可以不做?有没有替代方案?WHO:谁?由谁来做?WHEN:何时?什么时间做?什么时机最适宜?WHERE:何处?在哪里做?HOW :怎么做?如何提高效率?如何实施?方法是什么?HOW MUCH:多少?做到什么程度?数量如何?质量水平如何?费用产出如何?使用场景对场景和业务流程进行全面梳理,有助于完善用户故事的描述,弥补考虑问题时的疏漏STAR分析法概念按照4个维度,对任务或者场景状态去思考及总结。S:环境(Situation)T:目标(Task)A:行动(Action)R:成果(Result)使用场景对于场景和业务流程进行梳理,有助于需求调研收集、完善用户故事的描述,规划和明确产品的关键功能SMART分析法概念按照5个维度,对去任务或者项目进考核目标和考核标准的思考。S:具体项(Specific)M:可度量(Measurable)A:可实现(Attainable)R:关联情况(Relevant)T:时限(Time-based)使用场景明确任务的边界范围,确定产品功能的需求实现效果,参考制定工作任务项规划。SWOT分析法概念态势分析,就是将与研究对象密切相关的各种主要内部的优势(Strengths)、劣势(Weakness)和外部的机会(Opportunities)、威胁(Threats)等,依照矩阵形式排列列出,然后用系统地把各种因素相互匹配起来加以分析,从中得出一系列相应的结论。SO(优势-机会):利用起来,增长性战略WO(弱点-机会):需要改进,扭转型战略ST(优势-威胁):监视起来,多种经营战略WT(弱点-威胁):需要消除,防御型战略使用场景竞品分析,自身产品的战略规划,制定核心功能方向麦肯斯MECE分解法概念全称 Mutually Exclusive Collectively Exhaustive,中文意思“相互独立,完全穷尽”。各部分之间相互独立 (Mutually Exclusive):每项工作之间要独立,每项工作之间不要有交叉和重叠。所有部分完全穷尽 (Collectively Exhaustive):全部内容是全面、周密的、不遗漏的。使用场景对场景/用户/业务等分类拆解和梳理,分析事务的各项因素及核心因素WBS工作分解结构概念工作分解结构,全称Work Breakdown Structure,简称WBS。WBS的过程,是把一个项目按一定的原则进行分解,项目分解成一项项工作,直到分解不下去为止。如:项目→任务→工作→活动,分解好的一项项工作,可以再对应到“人、时间、资金投入”中。使用场景明确项目任务的边界范围,梳理工作事项,制定工作进度计划,确定阶段性的可交付成果SCQA模型概念SCQA模型是一个“结构化表达”工具,是麦肯锡咨询顾问芭芭拉·明托在《金字塔原理》中提出的。S(Situation)情景——由大家都熟悉的情景、事实引入。C(Complication)冲突——实际情况往往和我们的要求有冲突。Q(Question)疑问——怎么办?A(Answer)回答——我们的解决方案是……使用场景在任何场合的表达,开场白,开会发言,演讲报告,都可以通过SCQA模型快速组织内容。还适用于归纳总结,提升工作效率。DFEAS模型概念基于人的动物本能的“DFEAS”模型Demand需求Find找到Evaluation评估Action行动Share分享使用场景DFEAS营销模式加入了(Demand需求)和(Evaluation评估),锁定有需求的客户进行传播,加入品牌影响力和销售力,这使得企业可以在数十亿网民中选择精准的、有需求的客户进行有效传播和影响;让客户在购买时的各个产品和品牌的对比中,企业因为预先布局而获得优势;AIDMA模型概念AIDMA是消费者行为学领域很成熟的理论模型之一,AIDMA(S)模型。Attention注意Interest兴趣Desire欲望Memory记忆Action行动使用场景消费者如何从接触到信息到最后达成购买的一种逻辑。作为营销者,你可以按照模型去构建自己的获客模型,去检验自己的获客模型是否有效。AISAS模型概念AISAS模式的转变。在全新的营销法则中,两个具备网络特质的“s”——search(搜索),share(分享)的出现,指出了互联网时代下搜索(Search)和分享(Share)的重要性,而不是一味地向用户进行单向的理念灌输,充分体现了互联网对于人们生活方式和消费行为的影响与改变。Attention:引起注意Interest:引起兴趣Search:进行搜索Action:购买行动Share:人人分享使用场景AISAS 模型是目前最通用的“用户决策行为分析模型”,作为运营者就需要考虑如何引导用户的消费行为,形成人人分享的病毒传播效果。SIPS模型概念SIPS模型Sympathize 共鸣—— Identify 确认—— Participate 参与—— Share 分享使用场景“SIPS模型”则深刻展现了社交媒体时代消费者行为的新特点,打破了传统单向的消费模式,更加注重消费者与企业、消费者与消费者之间的双向互动,强调用户的意见和行为受到聚合特定人群的影响,从而产生独特的内在规律。大众媒体之前,消费者行为模式是DFEAS:需求-搜寻-评价-行动-分享;大众媒体时代,消费者行为模式是AIDMA(S):注意-兴趣-欲望-记忆-行动-分享;网络分众时代,消费者行为模式变为AISAS:注意-兴趣-搜索-行动-分享;移动互联时代,消费者行为模式又变为SIPS:共鸣-确认-参与-分享。用户体验5要素概念对产品设计进行5个层级的划分,从“抽象”逐步到“具体”:战略层:对应产品目标,用户需求。战略是对目标,需求的商业化方案的归纳。范围层:对应产品的信息和功能点,涉及到产品的侧重点和取舍。结构层:对应产品的实际落地,产品在这个层面开始具体化。框架层:对应产品具体内容的呈现,产品进一步具体化,落实到界面。表现层:对应产品的视觉传达和交互体验,是产品的美化。使用场景对工作计划进行阶段化梳理,产品的全流程过程的协作中都可参考马斯洛需求层次理论概念由美国心理学家马斯洛提出,认为人的需求由五个等级构成,从下到上分别是:生理需求安全需求归属与爱的需求尊重需求自我实现的需求使用场景对于用户需求、用户痛点的分析KANO模型概念通过分析“需求实现程度”与“用户满意度”的影响,将需求分为以下5种类型:兴奋型需求:让用户意想不到的好需求,实现了以后用户会赞不绝口。期望型需求:随着需求的完成,用户满意度也逐步变好。无差异型需求:做出来后,用户满意度也不会变好。基本型需求:必备,理所应当。反向型需求:做出来后,用户满意度反而变糟。使用场景需求调研和需求分析,需求分类、需求优先级迭代规划,产品上线后的反馈验证参照工作流程优先级排序概念以“轻重缓急”的四象限法来确定需求优先级。马上做:如果你总是有紧急又重要的事情要做,说明你在时间管理上存在问题,设法减少它。计划做:尽可能地把时间花在重要但不紧急(第二象限)的事情上,这样才能减少第一象限的工作量。授权做:对于紧急但不重要的事情的处理原则是授权,让别人去做。减少做:不重要也不紧急的事情尽量少做。使用场景新产品需求的规划排期,功能开发和缺陷解决的优先级排序,确立产品下一次迭代优化的重点和方向,日常工作的排期和处理先后顺序PDCA循环概念PDCA循环的含义是将质量管理分为四个阶段,即:P:计划 (Plan) 包括方针和目标的确定,以及活动规划的制定。D:执行 (Do) 根据已知信息,设计具体的行动方案;再根据方案,执行具体行动。C:检查 (Check) 总结结果,明确效果,找出问题。A:处理 (Act) 对检查的结果和问题进行具体处理,没有解决的问题进入下一个PDCA循环。使用场景敏捷项目Scrum模式或MVP产品的规划、设计、开发,对于需求缺陷的跟踪流程,需求的优化和迭代AARRR模型概念用户增长的5个指标,分别是:A:获取(Acquisition)A:激活(Activation)R:留存(Retention)R:收入(Revenue)R:传播(Referral)使用场景理解获客和维护客户的原理,根据跳转情况验证用户体验细节,根据漏斗模型判断流失情况。RFM模型概念基于三个维度,分析客户消费行为特征。R:最近一次消费 (Recency)F:消费频率 (Frequency)M:消费金额 (Monetary)简单地根据三个维度高低划分进行的客户分类:重要价值客户:R近、F频次和M金额都很高,超级的忠实客户,重点维护的VIP。重要保持客户:R远、F频次和M金额都很高,说明这是个一段时间没来的忠诚客户。重要发展客户:R近、M金额很高,但F频次不高,忠诚度不高,是潜力用户。重要挽留客户:R远、F频次不高,但M金额高,是将要流失或者已经要流失的用户。事实上,维度的指标可以更细,就能得到更细分的用户分类。使用场景根据不同维度的多级指标对客户进行更细分的分类完善用户画像或用户标签用户金字塔模型概念运营工作时需要抓住的是金字塔顶端20%的用户;可以利用用户进行有效地管理用户;并且每个模块可以再进行拆解成小金字塔,作为管理工具,增加用户和用户之间的关系。使用场景产品拓新的时候,从KOL到KOC,进而逐渐到普通用户,如此这般可以更快速的获得产品启动。而且还可以对金字塔模型进行拆解,建立圈层文化。用户增长s型,J型曲线概念优秀产品的生命周期好比一条S形曲线:首先是初期阶段,产品的市场渗透率增长比较缓慢。随后,渗透率将进入一个从缓慢到快速的爬坡过程,也就是增长期。对于较差的产品来说,这个曲线不会趋于平稳,最终会走向零点。与之相反,优秀产品的曲线会有上升的过程,最后趋于平稳。使用场景根据实际情况选择有意义的指标和时间段,通过增加产品线,扩大总目标市场,开拓新市场,保证各个产品的增长曲线相互叠加,从而保证整体的持续增长。生命周期概念生命周期(Life Cycle)的概念应用很广泛,特别是在政治、经济、环境、技术、社会等诸多领域经常出现,其基本涵义可以通俗地理解为“从摇篮到坟墓”(Cradle-to-Grave)的整个过程。对于某个产品而言,就是从自然中来回到自然中去的全过程,也就是既包括制造产品所需要的原材料的采集、加工等生产过程,也包括产品贮存、运输等流通过程,还包括产品的使用过程以及产品报废或处置等废弃回到自然过程,这个过程构成了一个完整的产品的生命周期。使用场景不管是用户还是产品,都是有生命周期的说法,作为运营人员,需要根据用户/产品的不同阶段,指定不同的运营策略。90-9-1法则概念“90-9-1”规则是内容社区的一个知名理论,指的是在内容社区中 90%的用户只是浏览,9%的用户会参与讨论,只有 1%的用户会积极的创造内容。当然具体的数值多少不重要,核心是“参与度的极大不均衡”。使用场景虽然90-9-1法则在某些特定的社区已经被不复存在,但是我们依然可以通过这个法则去挖掘更优质是内容创造者。通过顶部流量用户引导底部90%的用户参与到内容创作中来北极星指标概念北极星指标又叫唯一重要指标,就是把商业闭环设计的核心价值主张,作为用户增长的第一关键指标,为公司发展提供了选择正确的方向。使用场景新的增长模式是数据驱动的增长模式,是精益地、敏捷地进行“构建-衡量-学习”循环,即通过对数据的分析提出假设,通过实验验证假设,衡量实验得到的数据,重复这一循环来找到增长点。LTV概念LTV(CLV):用户的终身价值LTV的相对准确的计算公式是:(某个客户每个月的购买频次*每次的客单价*毛利率)*(1/月流失率)。其中的LT(生命周期)=1/月流失率是得出平均每个客户在该平台能够留存的总时长是多少月。使用场景结合上面的用户生命周期来看,运营人员就是需要通过各种运营策略提高用户留存,尽一切可能延长用户的生命周期,并且在生命周期中尽一切可能产生商业价值。CAC概念CAC:用户获取成本CAC 是 Customer Acquisition Cost 的缩写,意思是“用户获取成本”。使用场景判断不同渠道下用户的获取成本,通过综合计算和优化投放策略,降低用户获取成本。小结:增量期:CAC低,LTV低。如果一款产品单独切入用户需求开辟蓝海市场,又恰好用户的需求确实没有满足,市场仍旧存在真空。那么早期用户的CAC也不会太高。这时候还是要关注产品的迭代和用户增长。成长期:CAC高,LTV低。某模式或领域证明其有可行性,或者单纯的资本热,就会吸引其他玩家入场,竞争对手出现,瓜分蛋糕。导致CAC的升高。用户增长的作用还是很重要,同时要做好市场和竞品分析,在竞争中保持自己的优势。成熟期:CAC高,LTV高。产品和用户产生黏性。例如社交产品的关系链,电商产品的会员,以及用户积分和等级体系等,都会让用户手机常驻几款APP。用户能开始展现商业价值,故称为成熟期。这时候需要关注的是商业化和变现,提高用户的价值。衰退期:CAC高,CLV低。市场已经饱和,没有持续的新用户为产品输血。这时候更应该关注用户的留存,延长产品的生命,同时关注新机遇。PBP概念PBP的意思是花出去的用户获取成本可以在多长时间内回本。如果忽略PBP(回收期),哪怕LTV>CAC公司也可能会出问题。一般市场上认为PBP在一年以内为佳,因为LTV的计算是可以长达5-10年的,但现金流和融资压力却是逐年累积的,PBP越短,越有利于公司的现金流和再投入,也能减轻公司的融资压力等。使用场景PBP就是加入了时间限制——多少时间内,LTV大于CAC。PBP太长,就要和用户流失和市场周期做抗争了。后记:当然,在实际工作中,还有很多相关的模型,比如GMV计算模式,转化率漏斗,ARPU值模型,PLC模型,Heart模型等等。这些都是需要我们在运营过程中不断学习的。参考链接:https://www.zhihu.com/question/423613286/answer/2334398764?utm_psn=1649690718703792128
查看详情什么是长连接和短连接?在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:Connection:keep-alive在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。Go WebSocket今天我们会用Go语言使用WebSocket,经过多方收集资料,整理了后端的服务端+客户端,然后服务端和前端的链接两个示例,下面是代码,供大家研究。今天我们用的是Gin框架实现一个长链接。1、服务端+客户端(后端)服务端:package main import ( "fmt" "log" "net/http" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{} func main() { // 使用gin框架,和普通的http协议的服务器没什么不一样 s := gin.Default() s.GET("/sendMsg", echo) _ = s.Run("127.0.0.1:8090") } func echo(c *gin.Context) { //服务升级,对于来到的http连接进行服务升级,升级到ws cn, err := upgrader.Upgrade(c.Writer, c.Request, nil) defer cn.Close() if err != nil { panic(err) } for { mt, message, err := cn.ReadMessage() fmt.Println("接受到消息", message) if err != nil { log.Println("server read:", err) break } log.Printf("server recv msg: %s", message) msg := string(message) fmt.Println(msg, "msg") if msg == "clent" { message = []byte("客户端来了") } err = cn.WriteMessage(mt, message) if err != nil { log.Println(" server write err:", err) break } } } 客户端:package main import ( "log" "net/url" "os" "os/signal" "time" "github.com/gorilla/websocket" ) func main() { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) u := url.URL{Scheme: "ws", Host: "127.0.0.1:8090", Path: "/sendMsg"} log.Printf("client1 connecting to %s", u.String()) c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { log.Fatal("dial server:", err) } defer c.Close() done := make(chan struct{}) go func() { defer close(done) for { _, message, err := c.ReadMessage() if err != nil { log.Println("client read err:", err) return } log.Printf("client recv msg: %s", message) } }() for { select { // if the goroutine is done , all are out case <-done: return case <-time.Tick(time.Second * 5): err := c.WriteMessage(websocket.TextMessage, []byte("clent")) if err != nil { log.Println("client write:", err) return } case <-interrupt: log.Println("client interrupt") err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { log.Println("client1 write close:", err) return } select { case <-done: case <-time.After(time.Second): } return } } } 分别启动服务端和客户端:服务端会根据客户端发来的消息回复2、服务端+前端(前后端)接下来,我们用前后端交互一下websocket前端页面:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>websocket测试</title> <script src="http://www.alingfeng.cn/js/jquery.min.js"></script> </head> <body> <button id="clickButton">点击测试websocket</button> <script> $("#clickButton").click(function () { var name = $("#name").val(); initWebpack(name); }); </script> <script> var planWebsocket = null; var planIP = "127.0.0.1"; // IP地址 var planPort = "8090"; // 端口号 function initWebpack(clickName) { //初始化websocket if ("WebSocket" in window) { planWebsocket = new WebSocket( "ws://" + planIP + ":" + planPort + "/sendMsg" ); // 通信地址 setInterval( (planWebsocket.onopen = function (event) { console.log("建立连接"); let sendData = { command: "sendMsg", data: [{ msg: "服务端,我发送了消息,注意查收!" }], }; planWebsocket.send(JSON.stringify(sendData)); // 发送获取数据的接口 }), 2000 ); planWebsocket.onmessage = function (event) { // console.log('收到消息:' + event.data) let data = JSON.parse(event.data); if (data.command == "sendMsg") { var planData = data.data; //返回的数据 console.log(planData); } else if (data.command == "getscenes") { // 其他命令 } }; planWebsocket.onclose = function (event) { console.log("连接关闭"); }; planWebsocket.onerror = function () { alert("websocket通信发生错误!"); }; } else { alert("该浏览器不支持websocket!"); } } // initWebpack(); //调用 </script> </body> </html> 服务:package main import ( "fmt" "log" "net/http" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) // var upgrader = websocket.Upgrader{} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, // 解决跨域问题 CheckOrigin: func(r *http.Request) bool { return true }, } func main() { // 使用gin框架,和普通的http协议的服务器没什么不一样 s := gin.Default() s.GET("/sendMsg", echo) _ = s.Run("127.0.0.1:8090") } func echo(c *gin.Context) { //服务升级,对于来到的http连接进行服务升级,升级到ws cn, err := upgrader.Upgrade(c.Writer, c.Request, nil) defer cn.Close() if err != nil { panic(err) } for { mt, message, err := cn.ReadMessage() fmt.Println("接受到消息", message) if err != nil { log.Println("server read:", err) break } log.Printf("server recv msg: %s", message) msg := string(message) fmt.Println(msg, "msg") // if msg == "clent1" { // message = []byte("客户端1来了") // } else if msg == "clent2" { // message = []byte("你好客户端2") // } message = []byte("客户端,我已接收") for i := 0; i < 100; i++ { time.Sleep(1000000000) err = cn.WriteMessage(mt, message) } if err != nil { log.Println(" server write err:", err) break } } } 测试:我们初步了解Go语言做websocket就可以了。
查看详情想要实现一个根据一个网站的域名,然后把整个网站的所有链接都爬取出来,以前都是Python爬取,突发奇想用Go也来弄一个,借鉴网上很多资料。整理了一个可以实现的功能,有兴趣的同学可以试试。package main import ( "flag" "fmt" "regexp" "time" "github.com/PuerkitoBio/goquery" "github.com/douyacun/gositemap" "github.com/gocolly/colly" "github.com/gocolly/colly/extensions" ) var priority float64 = 1 func main() { //命令参数获取 targetUrl := flag.String("url", "", "目标站点地址") path := flag.String("path", "", "目标站点地址") Parallelnum := flag.Int("parallel", 500, "并发数") Delay := flag.Int("delay", 0, "延迟(毫秒)") MaxLinks := flag.Int("maxlink", 50000, "最大链接数") //[必须调用]:从 arguments 中解析注册的 flag,不然参数获取都没值 flag.Parse() //参数验证 if len(*targetUrl) == 0 { panic("请传递目前站点地址") } if len(*path) == 0 { *path = fmt.Sprintf("gositemap/%s/", time.Now().Format("20060102_150405")) } st := gositemap.NewSiteMap() st.SetDefaultHost(*targetUrl) st.SetPretty(true) // 每个sitemap文件不能多于50000个链接,这里可以自己配置每个文件最多,如果超过MaxLinks,会自动生成sitemap_index.xml文件 st.SetMaxLinks(*MaxLinks) //xml文件输出地址 st.SetPublicPath(*path) t := time.Now() number := 1 //初始化 创建收集器 c := colly.NewCollector(func(c *colly.Collector) { extensions.RandomUserAgent(c) // 设置随机头 c.Async = true }, //过滤url,去除外链 colly.URLFilters( //regexp.MustCompile("^(https://www\\.uppdd\\.com/)"), regexp.MustCompile("^("+*targetUrl+"/(.*))"), ), ) //控制下速度 c.Limit(&colly.LimitRule{ //DomainGlob: "*uppdd.*", //过滤规则的作用域,不限制则全部链接皆使用该规则 DomainGlob: "*", Parallelism: *Parallelnum, Delay: time.Duration(*Delay), }) // 响应的格式为HTML,提取页面中的链接 c.OnHTML("a[href]", func(e *colly.HTMLElement) { link := e.Attr("href") href := e.Request.AbsoluteURL(link) // 访问url 内部会检查 是否符合 正则表达式 。如果不符合 终止访问该url c.Visit(href) }) c.OnHTML("body", func(e *colly.HTMLElement) { e.DOM.Each(func(i int, selection *goquery.Selection) { //匹配数据(页面dom结构) href := e.Request.AbsoluteURL(e.Attr("href")) //写入链接 url := gositemap.NewUrl() url.SetLoc(href) url.SetLastmod(time.Now()) url.SetChangefreq(gositemap.Daily) //获取页面权重 url.SetPriority(getPriority(number)) st.AppendUrl(url) number += 1 fmt.Printf("计数:%d,链接:%s \n", number, href) }) }) c.OnError(func(response *colly.Response, err error) { //fmt.Println(err) }) c.Visit(*targetUrl + "/") c.Wait() fmt.Printf("连接收集花费时间:%s,收集链接:%d个", time.Since(t), number) //bt,err := st.ToXml() //if err != nil{ // fmt.Printf("异常:%v", err) // return //} //byte切片转string,好查看是否有错误 //btString := string(bt) //输出 //fmt.Println(btString) //生成文件导出 filePath, err := st.Storage() if err != nil { fmt.Printf("%v", err) return } fmt.Println(filePath) } // 递减乱获取权重|很随意 func getPriority(num int) float64 { if num < 20 { return 1 } newS := float64(num) / priority numQ := int(newS) //fmt.Printf("权重:%d \n",numQ) if num >= numQ { priority -= 0.01 } return priority } 我们随便找个网址来试试!#启动命令,其他参数自己也可以尝试,然后完成会把所有的链接都存入一个sitemap.xml文件中 go run . -url=https://gs.dgg.cn然后这个也可以为SEO的同学,爬取整站链接,当做sitemap.xml文件
查看详情1、只愿你眉眼如初风华如故2、愿我是阳光,明媚而不忧伤3、我将悲欢和酒饮念你平生一展眉4、我的山水落在你的眉间5、凤凰涅槃金鹏展翅待我重出江湖时6、笑叹浮生若梦追忆年华似水7、愿世间美好都如约而至8、晚风吻尽荷花叶任我醉倒在池边9、你活在人间 等于罪恶滔天10、巴黎铁塔下一颗烂草东京樱花下一朵残花11、更相不敌莫让青许两白头12、不愿放手只想到白头13、路是我选的我愿颠沛流离14、恰好那天风烟俱净阳光正好15、ヮ随风飞舞的发丝ヮ风情万种的霓裳16、给我一杯清酒说笑着说别回头17、懂得珍惜才配拥有 懂我心伴我久18、你的感情像彩虹我伸开手却只能握到风19、但愿来日方长不是匆匆一场20、你的笑能抵过风霜是我见过最美的太阳
查看详情基本概念什么是消息队列消息队列是一种应用(进程)间的通信方式。生产者只需把消息发布到MQ,消费者只需重MQ中取出,可靠传递由消息队列中的消息系统来确保。消息队列有什么用消息队列是一种异步协作机制,最根本的用处在于将一些不需要即时生效的操作拆分出来异步执行,从而达到可靠传递、流量削峰等目的。比如如果有一个业务需要发送短信,可以在主流程完成之后发送消息到MQ后,让主流程完结。而由另外的线程拉取MQ的消息,完成发送短信的操作。常用的消息队列常用的MQ大概有ActiveMQ、RabbitMQ、RocketMQ、KafkaActiveMQ,基于Java优点:对Java的JMS支持最好;多线程并发;缺点:历史悠久,版本更新慢。现在慢慢用的少了;RabbitMQ,基于Erlang优点:生态丰富,是现在主流的MQ;支持多种客户端、支持AJAX;缺点:对想深入源码的Java选手不太友好;RocketMQ,基于Java优点:为海量数据打造;主张拉模式;天然集群、HA、负载均衡;缺点:生态较小Kafka,基于Scala优点:分布式高可拓展;高性能;容错强缺点:消息重复;乱序;维护成本高什么是RabbitMQ消息中间件erlang:一种并发函数式语言AMQP:Advanced Message Queuing Protocol,高级消息队列协议。由Exchange、Queue和Bind组成RabbitMQ是一个erlang开发的AMQP实现生产者将消息发送到Exchange上,通过Exchange从而Binding到Queues上。Exchange有三种具体类型:direct:如果消息中的RoutingKey和Binding中的BindingKey一致就转发fanout:消息被分发到所有队列中topic:将RoutingKey和队列的模式进行匹配应用场景异步可以理解为将遇到非必须的业务时,立即响应客户端,不关系业务何时完成比如在用户注册时,有将信息写入数据库和发送注册成功邮件两项业务。数据库写入完成即标志着用户注册成功,此时如果继续处理发送邮件的业务,会给客户端带来不必要的等待时间。引入消息队列后,在队列中写入完成注册的消息后,即可完成整个注册流程。至于邮件,可以等到邮件业务从消息队列中取出消息再发送。把不紧急的业务从主线中剥离出来,主线不必考虑不紧急的业务何时完成的时候,可以考虑使用消息队列实现异步。解耦考虑两个系统间存在消息传递,一个系统的故障会影响到整个业务的正常运转。可以用消息队列来保证消息可靠传递比如一个订单系统和一个库存系统,完成订单之后,需要进行库存调度。考虑到如果库存系统故障,会引起已完成的订单消息的丢失,而做很多异常处理会使业务变得臃肿。这个时候可考虑引入消息队列,使用消息队列保证可靠传输,从而减少业务逻辑。削峰考虑短时间的大量请求,可能会带来内存溢出、大面积连接超时等情况,使得服务器崩溃。引入消息队列后,可以控制请求到业务处理系统的流量,从而防止崩溃现象的出现。比如秒杀场景。大量请求同时涌入,服务器不能分配足够的资源响应,或者带宽不足,导致宕机。可以引入消息队列来限流,MQ通过限制同一时间的出口消息,使得流量在服务器能够承受的范围之内。等待一部分请求处理完成之后,再向业务处理系统导入新的消息。----------------------------------------------------------------------------------------------------------------------------------------昨天我们用docker在虚拟机上装了RabbitMq,今天我们就开始用它来实际操作一下。不说废话了,我们开始搞,我这里用的是Go语言。首先,我们先封装方法:package rabbitmq import ( "fmt" "github.com/streadway/amqp" ) type RabbitMq struct { Conn *amqp.Connection Ch *amqp.Channel QueueName string // 队列名称 ExchangeName string // 交换机名称 ExchangeType string // 交换机类型 RoutingKey string // routingKey } type QueueAndExchange struct { QueueName string // 队列名称 ExchangeName string // 交换机名称 ExchangeType string // 交换机类型 RoutingKey string // routingKey } func (r *RabbitMq) ConnMq() { conn, err := amqp.Dial("amqp://admin:123456@192.168.11.66:5672/my_vhost") if err != nil { fmt.Printf("连接mq出错,错误信息为:%v\n", err) return } r.Conn = conn } func (r *RabbitMq) CloseMq() { err := r.Conn.Close() if err != nil { fmt.Printf("关闭连接出错,错误信息为:%v\n", err) return } } // 开启channel通道 func (r *RabbitMq) OpenChan() { ch, err := r.Conn.Channel() if err != nil { fmt.Printf("开启channel通道出错,错误信息为:%v\n", err) return } r.Ch = ch } // 关闭channnel通道 func (r *RabbitMq) CloseChan() { err := r.Ch.Close() if err != nil { fmt.Printf("关闭channel通道出错,错误信息为:%v\n", err) } } // 生产者 func (r *RabbitMq) PublishMsg(body string) { ch := r.Ch // 创建队列 ch.QueueDeclare(r.QueueName, true, false, false, false, nil) // 创建交换机 ch.ExchangeDeclare(r.ExchangeName, r.ExchangeType, true, false, false, false, nil) // 队列绑定交换机 ch.QueueBind(r.QueueName, r.RoutingKey, r.ExchangeName, false, nil) // 生产任务 ch.Publish(r.ExchangeName, r.RoutingKey, false, false, amqp.Publishing{ ContentType: "text/plain", Body: []byte(body), DeliveryMode: amqp.Persistent, }) } // 创建实例 func NewRabbitMq(qe QueueAndExchange) RabbitMq { return RabbitMq{ QueueName: qe.QueueName, ExchangeName: qe.ExchangeName, ExchangeType: qe.ExchangeType, RoutingKey: qe.RoutingKey, } } 接下来我们就我们创建的生产者发送消息:package main import ( "test_rabbitmq/rabbitmq" "test_rabbitmq/utils" ) func main() { qe := rabbitmq.QueueAndExchange{ QueueName: "test_queue", ExchangeName: "test_exchange", ExchangeType: "direct", RoutingKey: "test_routingKey", } mq := rabbitmq.NewRabbitMq(qe) mq.ConnMq() mq.OpenChan() defer func() { mq.CloseMq() }() defer func() { mq.CloseChan() }() test_map := map[string]interface{}{ "mail": "9527@qq.com", "msg": "今天大太阳", } //这里我们发送100条消息 for i := 0; i < 100; i++ { mq.PublishMsg(utils.MapToStr(test_map)) } } 我们可以看到,发送的消息已经在队列当中了。然后我们开始消费消息:package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/streadway/amqp" ) func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Hello World") }) router.Run(":8081") } func init() { conn, err := amqp.Dial("amqp://admin:123456@192.168.11.66:5672/my_vhost") fmt.Println(err) defer conn.Close() ch, err_ch := conn.Channel() fmt.Println(err_ch) defer ch.Close() ch.Qos(1, 0, false) deliveries, err := ch.Consume("test_queue", "consumer", false, false, false, false, nil) if err != nil { fmt.Println(err) } //消费成功delivery.Ack(true) for delivery := range deliveries { delivery.Ack(true) body := string(delivery.Body) fmt.Println(body) fmt.Printf("%T\n", body) } } 然后我们可以看到数据已经取出来了,队列的消息也已经消费了。
查看详情首先要去docker仓库查找镜像,我们需要下载能可视化操作界面的rabbitMq,所以下载的是management版本。接下来,先建目录,主要是为了存放数据:#建目录 mkdir /server/rabbitmq/data接下来下载rabbitMq镜像:#拉镜像 docker pull rabbitmq:management #启动命令: docker run -d --name rabbitmq \ -p 5672:5672 -p 15672:15672 \ -v /server/rabbitmq/data:/var/lib/rabbitmq \ -e RABBITMQ_DEFAULT_VHOST=my_vhost \ -e RABBITMQ_DEFAULT_USER=admin \ -e RABBITMQ_DEFAULT_PASS=123456\ -m 300m \ --memory-swap=600m \ rabbitmq:management问题:go连接RabbitMQ "no access to this vhost"错误docker exit -it container_name /bin/sh rabbitmqctl add_vhost admin rabbitmqctl set_permissions -p 用户名 admin "." "." ".*"连接的时候指定对应的vhostamqp.Dial("amqp://username:password@ip:5672/admin”)
查看详情我们装了虚拟机很多时候都需要设置静态IP,不然我们的程序会经常因为IP改变造成各种各种的麻烦。一般默认的是动态IP#进入目录: cd /etc/sysconfig/network-scripts #查看网卡 ll #打开网卡,我这边是ifcfg-enp0s3 vi ifcfg-enp0s3 #这是默认的动态IP,网卡设置 TYPE=Ethernet PROXY_METHOD=none BROWSER_ONLY=no BOOTPROTO=dhcp DEFROUTE=yes IPV4_FAILURE_FATAL=no IPV6INIT=yes IPV6_AUTOCONF=yes IPV6_DEFROUTE=yes IPV6_FAILURE_FATAL=no IPV6_ADDR_GEN_MODE=stable-privacy NAME=enp0s3 UUID=fdeb4637-a424-4d21-82dd-467bd70e3f35 DEVICE=enp0s3 ONBOOT=yes修改为静态IPTYPE=Ethernet PROXY_METHOD=none BROWSER_ONLY=no BOOTPROTO=static IPADDR=192.168.11.66 NETMASK=255.255.255.0 GATEWAY=192.168.11.1 DNS1=114.114.114.114 DNS2=61.139.2.69 DEFROUTE=yes IPV4_FAILURE_FATAL=no IPV6INIT=yes IPV6_AUTOCONF=yes IPV6_DEFROUTE=yes IPV6_FAILURE_FATAL=no IPV6_ADDR_GEN_MODE=stable-privacy NAME=enp0s3 UUID=fdeb4637-a424-4d21-82dd-467bd70e3f35 DEVICE=enp0s3 ONBOOT=yes改完保存后,重启网络systemctl restart network主要是加这一段:BOOTPROTO=static IPADDR=192.168.11.66 NETMASK=255.255.255.0 GATEWAY=192.168.11.1 DNS1=114.114.114.114 DNS2=61.139.2.691、BOOTPROTO设置静态static,默认是dhcp2、NETMASK:子网掩码3、GATEWAY:默认网关4、DNS,默认是8.8.8.8下面怎么查找这些数据我虚拟机用的是网络桥接模式:
查看详情人的血液7年会全部替换一遍,鱼的记忆只有7秒。