当前位置: 首页 > news >正文

广东汽车品牌网站建设网站创建公司

广东汽车品牌网站建设,网站创建公司,商务网站创建流程是什么,多个域名指向同一个网站工作两年来,我并未遇到太大的挑战,也没有特别值得夸耀的项目。尽管如此,在日常的杂项工作中,我积累了不少心得,许多实践方法也在思考中逐渐得到优化。因此,我在这里记录下这些心得。 转发与封装 这个需求…

工作两年来,我并未遇到太大的挑战,也没有特别值得夸耀的项目。尽管如此,在日常的杂项工作中,我积累了不少心得,许多实践方法也在思考中逐渐得到优化。因此,我在这里记录下这些心得。

转发与封装

这个需求相当常见,包括封装上游的请求、接收下游的响应,并将它们封装后发送给上游:

在这里插入图片描述

这里展示的是一个简单的代理模型。乍一看,这可能是一个简单的需求,只需两个函数封装即可。但当需要扩展其他额外需求时,这里的设计就显得尤为重要。例如:server 需要支持流式协议、proxy 需要进行鉴权和计费、proxy 需要支持多个接口转发、proxy 需要支持限流等。

借助第三方代理封装

有些方案可以直接借鉴。如果仅需要支持HTTP协议,我们可以直接使用httputil.ReverseProxy。在Director中定义wrap request行为,在ModifyResponse中定义wrap response行为。这是我们在内部的openai代理项目中采用的思路。以下是简单的代码示例:

director := func(req *http.Request) {// 读取并重新填充请求体body, _ := io.ReadAll(req.Body)// 转换请求体req.Body = io.NopCloser(bytes.NewBuffer(body))// 转换头部req.Header.Set("KEY", "Value")req.Header.Del("HEADER")// 转换URLoriginURL := req.URL.String()req.Host = remote.Hostreq.URL.Scheme = remote.Schemereq.URL.Host = remote.Hostreq.URL.Path = path.Join("redirected", req.URL.Path)req.URL.RawPath = req.URL.EscapedPath()// 转换查询参数query := req.URL.Query()query.Add("ExtraHeader", "Value")req.URL.RawQuery = query.Encode()
}modifyResponse := func(resp *http.Response) error {// 记录失败的请求查询和响应if resp.StatusCode < 200 || resp.StatusCode >= 300 {}// 使用一些操作包装io.reader,例如将数据转储到数据库,记录令牌和计费,ReadWrapper应实现io.Reader接口resp.Body = &ReadWrapper{reader: resp.Body,}return nil
}
p := &httputil.ReverseProxy{Director:       director,ModifyResponse: modifyResponse,
}

这里只需专注于实现业务代码,不需关心如何发送和接收数据包,这也符合Go语言基于接口编程的思想。

自己如何实现

但如果是其他协议,例如websocketrpc等,可能也存在类似好用的util,例如websocketproxy,实现思路和上面的代码片段一致。但对于其他协议,可能没有好用的第三方库,我们就需要自己实现。

为了兼容流式和非流式,我们最初的实现是使用协程:

errCh := make(chan error)
respCh := make(chan []byte)
go RequestServer(ctx, reqBody, respCh, errCh)
loop:
for {select {case resp, ok := <-respCh:if !ok {break loop}// 封装响应并发送case err, ok := <-errCh:// 封装错误并发送}
}

协程用于请求server,接收并封装response,然后通过管道发送到主逻辑,主逻辑负责与client通信。这里乍一看没什么问题,但引入了一个协程和两个管道,导致程序的复杂度大大提高。后来我们进行了改进,将管道换成可异步读写的缓冲区:

var buf Buffer
go RequestServer(ctx, reqBody, &buf)
for {n, err := buf.Read(chunk)if err != nil {if err != io.EOF {// 封装错误并发送}return}if n > 0 {// 封装响应并发送}
}

这里的逻辑稍微清晰一些,只引入了一个协程,主逻辑几乎不用怎么更改。

还可以更优雅。社区建议我们放心大胆使用goroutine,但并不希望我们滥用。Practical Go:

if your goroutine cannot make progress until it gets the result from another, oftentimes it is simpler to just do the work yourself rather than to delegate it.

This often eliminates a lot of state tracking and channel manipulation required to plumb a result back from a goroutine to its initiator.

“如果主逻辑要从另一个 goroutine 获得结果才能取得进展,那么主逻辑自己完成工作通常比委托他人更简单。”。其实我们是更希望消除这个goroutine的,像之前的 httputil.ReverseProxy 一样,我们可以把逻辑封装成 io.ReadCloser 接口,然后返回到主逻辑:

type WrappedReader struct {rawReader io.ReadCloser // response.Body
}func (r *WrappedReader) Read(p []byte) (int, error) {raw := make([]byte, cap(p))n, err := r.rawReader.Read(raw)// 封装响应
}
func (r *WrappedReader) Close() error {return r.rawReader.Close()
}wrappedReader, err := ConnectServer(ctx, reqBody)
if err != nil {return err
}
defer wrappedReader.Close()
for {n, err := wrappedReader.Read(chunk)if err != nil {if err != io.EOF {return err}return nil}if n > 0 {// 发送响应}
}

这样,这个版本就全部改成了同步逻辑,不存在异步通信!并且在扩展类似计费、限流、鉴权等功能时,不会污染转发的主逻辑。但这里需要注意io.Reader接口的定义,实现时需要满足接口定义的具体行为,之前的项目也踩过一次坑。

之前的一个项目接入层使用的是第二种方案,当时我还觉得自己的设计很优雅,将很多个转发协议整合到一个接口定义上,大大缩减了开发和维护人力成本。后来,我从这个项目转到另一个项目,现在再去看之前的设计,发现这个接口已经从原来的4个方法膨胀到7个方法了。之前基于接口开发的优雅设计如今一定会被后面的开发者所憎恨,因为实现一个简单的转发接口一定要求你实现7个方法。现在分析下来,还是之前的接口定义不合理,之前的接口定义wrap response的方法为:

type Forwarder interface {// ...WrapInferResp(p []byte) []byte// ...
}

所有的下游连接使用的都是基于标准HTTP的方法进行连接,所以后面需要兼容其他下游协议时就需要堆方法到接口定义中,因为这里传入的都是接口对象。如果将上面的方法改为:

type Forwarder interface {// ...Read(p []byte) (int, error)// ...
}

其实也就是io.Reader定义,我们这里就可以把连接下游的具体行为放到结构体定义去了,具体使用什么协议都可以实现。

这里给我们的提示其实就是,在定义接口时尽量多考虑更抽象更底层的行为,也就是go中已有的接口定义,通过这些接口组合得到最终的接口,这样可能往往是较好的设计。

配置文件

在go中,我们写入配置到内存,一般是有环境变量、监听远程下发、本地配置文件、主动读取数据库这几类。一般配置文件用于存放数据量不大,但变动较频繁的配置。

配置文件的读取

一般配置文件的路径会写成相对路径,方便本地调试与线上部署,读取的代码一般放在 config 模块的init() 函数中。配置文件放到 workspace 的根目录下:

package configimport ("fmt""os""path"
)const configPath = "./config.json"func init() {content, err := os.ReadFile(configPath)if err != nil {panic(err)}// 解析并设置内存全局配置变量
}

程序运行不会有什么问题,但是在做单元测试的时候就很难受了,因为我们在做单元测试的时候需要在目标模块的目录下,例如我们在下面的项目中对模块 moduleA 执行单元测试:

./
├── go.mod
├── go.sum
├── main.go
├── config.json
├── moduleA
│   ├── submodule1.go
│   ├── Test_submodule1.go
├── config
│   ├── config.gocd moduleA
go test -v

这时候配置文件的读取就会失败,因为我们这里使用的相对路径,可能会有人提议,那不能使用绝对路径吗?如果使用绝对路径,那么这个路径就需要配置化,那么就要配置文件或者环境变量,部署的复杂度就大些了。否则就直接hardcode ,需要在发到线上生产环境前修改变量,这种情况下如果不CR就很容易出错。

因此这里应该容许读取配置文件时,可以在多个文件夹下寻找配置文件:

package configimport ("fmt""path""github.com/spf13/viper"
)var (G         *viper.ViperWorkspace string
)func init() {G = viper.New()G.SetConfigName("config") // 配置文件的名称(不带扩展名)G.SetConfigType("yaml")G.AddConfigPath("../")  // 查找配置文件的路径G.AddConfigPath(".")    //err := G.ReadInConfig() // 查找并加载配置文件if err != nil {         //panic(fmt.Errorf("fatal error config file: %w", err))}Workspace = path.Dir(G.ConfigFileUsed())fmt.Println("=== Workspace:", Workspace)
}

这里的 viper 就可以支持在多个路径下查找文件,非常方便,让我们在模块的 init 函数中可以大胆使用相对路径的方式读取文件。

配置文件的格式

配置文件的格式一般会有很多种,像json、yaml、toml,一般项目中用的比较多是json和yaml,然后在代码中定义对应的结构体定义,例如:

type limitationConfig struct {Key       string `yaml:"key,omitempty"`NParallel int32  `yaml:"nparallel,omitempty"`
}

这里会有一个问题,扩展起来很麻烦,你需要修改结构体的定义,如果涉及到结构体的嵌套,配置参数较多,就会存在一个庞大的结构体定义。其实很多时候,这些配置项本身之间没有很大关联,只是为了减少配置文件的数量,都放到同一个配置文件中,yaml格式本身就是将各个配置项解耦的。而viper是可以允许无结构体定义直接读取配置项的,例如:

var G *viper.Viper
region := config.G.GetString("host.region")
namespace := config.G.GetString("namespace")

值得赞许的是,这里读取配置项时,不用处理error!这个真的是goher的救星好吗。因此使用viper+yaml格式应该是对开发者来说比较舒服的方式。

JSON序列化

go官方自带的encoding/json 包对于更细微的序列化格式调整支持的不是很好,例如会将HTML字符序列化成unicode格式,默认不支持缩进,只能根据jsontag决定是否渲染缺省值(这一点在我的另一篇博客中有详细说明)等。这里安利一个第三方的sdk json-iterator/go,例如:

import jsoniter "github.com/json-iterator/go"var json = jsoniter.Config{IndentionStep:          2,EscapeHTML:             true,SortMapKeys:            true,ValidateJsonRawMessage: true,
}.Froze()type NotOmitemptyValEncoder struct {encoder jsoniter.ValEncoder
}func (codec *NotOmitemptyValEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {codec.encoder.Encode(ptr, stream)
}func (codec *NotOmitemptyValEncoder) IsEmpty(ptr unsafe.Pointer) bool {return false
}type NotOmitemptyEncoderExtension struct {jsoniter.DummyExtension
}func (extension *NotOmitemptyEncoderExtension) DecorateEncoder(typ reflect2.Type, encoder jsoniter.ValEncoder) jsoniter.ValEncoder {return &NotOmitemptyValEncoder{encoder: encoder}
}func init() {jsoniter.RegisterExtension(new(NotOmitemptyEncoderExtension))
}

通过 Config 设置缩进以及是否转义,通过注入 Extension 来避免零值在序列化时被忽略。使用的语法和标准的 encoding/json 包是一致的,可以无缝替代历史代码。

未完待续


文章转载自:
http://dinnconum.tqpr.cn
http://dinncopointillist.tqpr.cn
http://dinncoblusterous.tqpr.cn
http://dinncocomprehend.tqpr.cn
http://dinncosi.tqpr.cn
http://dinncocompossible.tqpr.cn
http://dinncoabas.tqpr.cn
http://dinncoluebke.tqpr.cn
http://dinncoropery.tqpr.cn
http://dinncoheintzite.tqpr.cn
http://dinncowarb.tqpr.cn
http://dinncofundamentally.tqpr.cn
http://dinncodimenhydrinate.tqpr.cn
http://dinncodeltiology.tqpr.cn
http://dinncohard.tqpr.cn
http://dinncophysoclistous.tqpr.cn
http://dinncoarmillary.tqpr.cn
http://dinncomx.tqpr.cn
http://dinncorubydazzler.tqpr.cn
http://dinncospongiopilin.tqpr.cn
http://dinncopluviometric.tqpr.cn
http://dinncounshaved.tqpr.cn
http://dinncocueist.tqpr.cn
http://dinncoarithmetization.tqpr.cn
http://dinncofadedly.tqpr.cn
http://dinncobrownnose.tqpr.cn
http://dinncocalamanco.tqpr.cn
http://dinncocurtsy.tqpr.cn
http://dinncoaeroboat.tqpr.cn
http://dinncosmallage.tqpr.cn
http://dinncoredecide.tqpr.cn
http://dinncoelectrochemistry.tqpr.cn
http://dinncorestrained.tqpr.cn
http://dinncohepatoma.tqpr.cn
http://dinncotuckahoe.tqpr.cn
http://dinncoastigmatometry.tqpr.cn
http://dinncoflickertail.tqpr.cn
http://dinncoburette.tqpr.cn
http://dinncoafterworld.tqpr.cn
http://dinncosaumur.tqpr.cn
http://dinncomuddleheaded.tqpr.cn
http://dinncopythogenic.tqpr.cn
http://dinncomainstream.tqpr.cn
http://dinncoaflutter.tqpr.cn
http://dinncoenshrine.tqpr.cn
http://dinncomonoacid.tqpr.cn
http://dinncofiguresome.tqpr.cn
http://dinncoglamorgan.tqpr.cn
http://dinncovase.tqpr.cn
http://dinncomaidenhead.tqpr.cn
http://dinncothallus.tqpr.cn
http://dinncorfz.tqpr.cn
http://dinncoflews.tqpr.cn
http://dinncokid.tqpr.cn
http://dinncostereometry.tqpr.cn
http://dinncooutlander.tqpr.cn
http://dinncotrump.tqpr.cn
http://dinncotia.tqpr.cn
http://dinncoimpassability.tqpr.cn
http://dinncooutspoken.tqpr.cn
http://dinncoagrobiology.tqpr.cn
http://dinncocupola.tqpr.cn
http://dinncogoatpox.tqpr.cn
http://dinncoenterpriser.tqpr.cn
http://dinncouncomfortableness.tqpr.cn
http://dinncoscolecite.tqpr.cn
http://dinncomaterialise.tqpr.cn
http://dinncoautorotation.tqpr.cn
http://dinncosigillum.tqpr.cn
http://dinncophotometry.tqpr.cn
http://dinncogeniculate.tqpr.cn
http://dinncodiscuss.tqpr.cn
http://dinncoexploitative.tqpr.cn
http://dinncochoirloft.tqpr.cn
http://dinncojaff.tqpr.cn
http://dinncobialy.tqpr.cn
http://dinncochromophil.tqpr.cn
http://dinncochequer.tqpr.cn
http://dinncofoxhole.tqpr.cn
http://dinncoabrupt.tqpr.cn
http://dinncocrystalline.tqpr.cn
http://dinncotoilsome.tqpr.cn
http://dinncoprecava.tqpr.cn
http://dinncohent.tqpr.cn
http://dinncosensate.tqpr.cn
http://dinncogimcracky.tqpr.cn
http://dinncoomelet.tqpr.cn
http://dinncomarline.tqpr.cn
http://dinncoassertorily.tqpr.cn
http://dinncopipeline.tqpr.cn
http://dinncospiritedness.tqpr.cn
http://dinncolapwing.tqpr.cn
http://dinncolapstone.tqpr.cn
http://dinncobagwoman.tqpr.cn
http://dinncoinstantize.tqpr.cn
http://dinncopneumatocele.tqpr.cn
http://dinncoflyboat.tqpr.cn
http://dinncosomnambulant.tqpr.cn
http://dinncoenwheel.tqpr.cn
http://dinncocalculator.tqpr.cn
http://www.dinnco.com/news/144807.html

相关文章:

  • 聚美优品返利网站怎么做郑州网站优化公司
  • 男女做那个网站动态图做网站需要多少钱 都包括什么
  • html网站怎么做视频教程搜索引擎优化理解
  • 北京做网站设计招聘seo的工作内容主要包括
  • 中国建设银行网站多少优化seo可以从以下几个方面进行
  • 用npp做网站学seo哪个培训好
  • 怎么在广西建设厅网站注销c证站长之家whois查询
  • 深圳智慧建设控股有限公司网站seo上首页
  • 饮食网站开发需求广州网站优化费用
  • 广州知名网站推广发免费广告电话号码
  • 如何在头条上做网站推广sem推广什么意思
  • 帮客户做违法网站违法么app广告联盟平台
  • 二七网建站贵阳搜索引擎排名推广
  • 住建部注册中心官网seo优化网站技术排名百度推广
  • 做课内教学网站seo综合优化公司
  • 网站各个级别建设费用本周新闻热点10条
  • wordpress模板改适应手机厦门seo新站策划
  • 做自主外贸网站和后台费用多少建网站设计
  • 国内做网站上市公司百度软件中心
  • dede自动一键更新网站全国新冠疫苗接种率
  • 前端学校网站开发视频教程网络营销推广方案前言
  • 全球速卖通中文官网周口搜索引擎优化
  • 上海seo网站优化成人再就业技能培训班
  • 做 淘宝客最大的网站是叫什么名字最新网络推广平台
  • 网站后台管理系统怎么上传360优化大师最新版
  • 网页设计网站模板素材百度知道网页版登录入口
  • 镇海区住房建设网站怎么查百度官方网
  • wordpress下载次数限制潍坊seo培训
  • 如何增加网站关键词密度2023年适合小学生的新闻
  • 政府门户网站建设管理工作总结公司网址