精华内容
下载资源
问答
  • golang微服务框架go-zero系列-4:go-zero文件服务 go-zero本身支持文件服务,但是我们需要写相关的handler文件,本文目的在于 不写任何一个和文件相关的handler 如果有新的文件,直接把文件模板到某个特定目录就好,不要...

    golang微服务框架go-zero系列-4:go-zero文件服务

    go-zero本身支持文件服务,但是我们需要写相关的handler文件,本文目的在于

    • 不写任何一个和文件相关的handler
    • 如果有新的文件,直接把文件模板到某个特定目录就好,不要动任何go代码

    需求在这里,开撸吧

    在代码开始前,你可能需要阅读

    golang微服务框架go-zero系列-1:在go-zero中使用XormV2
    golang微服务框架go-zero系列-2:在go-zero中使用jwt-token鉴权实践
    golang微服务框架go-zero系列-3:扩展go-zero,使之支持html模板解析自动化

    注意

    微服务讲究资源分离,实际生产过程中尽量使用专业的文件服务器或者OSS等第三方存储平台

    file服务实现思路

    gin中有专门的static file服务封装,go-zero目前并没有提供。目前go-zero提供非常严格的路径匹配,如
    访问
    /asset/l1.jpg 将映射到 /asset/:1对应的handlerlv1
    /asset/l1/l2.jpg 将映射到 /asset/:1/:2对应的handlerlv2
    这有如下俩种情况

    映射指定路径到单个文件

    比如我们需要访问favourite.ico,系统指向./www/favourite.ico文件,代码如下

    //处理函数,传入文件地址
    func filehandler(filepath string) http.HandlerFunc {
    	return func(w http.ResponseWriter, req *http.Request) {
    		http.ServeFile(w, req, filepath)
    	}
    }
    

    在router里面直接调用AddRoute方法添加单个路由

    func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
    
    //这里直接添加单个
    engine.AddRoute(
    				rest.Route{
    					Method:  http.MethodGet,
    					Path:    "/favourite.ico",
    					Handler: filehandler("./www/favourite.ico"),
    				})
    }
    

    映射指定目录并提供服务

    实际过程中我们需要对外暴露某一个目录,比如/assets/目录,该目录下存放一些资源文件如css,js,img

    tree /f
    +---assets                                     
    |   +---css                                    
    |   +---fonts                                  
    |   +---images                                 
    |   +---js                                     
    |   \---plugins                                
    |       +---font-awesome                       
    |       |   +---css                            
    |       |   \---fonts                          
    |       +---fontawesome                        
    |       |   +---css                            
    |       |   \---fonts                          
    |       +---ionicons                           
    |       |   +---css                            
    |       |   \---fonts                          
    |       +---jquery.contextmenu                 
    |       |   \---images                         
    |       +---jquery.pin                         
    |       |   +---css                            
    |       |   \---images                         
    |       +---jqueryui-1.12.1                    
    |       |   +---external                       
    |       |   |   \---jquery                     
    |       |   \---images                         
    |       \---swiper-4.5.3                       
    |           +---css                            
    |           \---js                             
    

    如果使用单个文件的方式来实现,肯定不合理,因为router会非常大,怎么解决这个问题?我们可以使用如下方法实现文件夹服务

    //
    func dirhandler(patern, filedir string) http.HandlerFunc {
    
    	return func(w http.ResponseWriter, req *http.Request) {
    		handler := http.StripPrefix(patern, http.FileServer(http.Dir(filedir)))
    		handler.ServeHTTP(w, req)
    
    	}
    }
    

    如上函数的核心是http.StripPrefix(patern, http.FileServer(http.Dir(filedir)))函数,这个函数的核心功能是将映patern格式映射到某一个目录filedir

    • patern:请求路径格式/assets/:1,/assets/:1/:2这种
    • filedir:映射对应的文件夹./assets/这种

    那么我们只需要构建多级文件访问格式和dirhandler的映射关系即可

    func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
    
    			//这里注册
    			dirlevel := []string{":1", ":2", ":3", ":4", ":5", ":6", ":7", ":8"}
    			patern := "/asset/"
    			dirpath := "./assets/"
    			for i := 1; i < len(dirlevel); i++ {
    				path := prefix + strings.Join(dirlevel[:i], "/")
    				//最后生成 /asset
    				engine.AddRoute(
    					rest.Route{
    						Method:  http.MethodGet,
    						Path:    path,
    						Handler: dirhandler(patern,dirpath),
    					})
    
    				logx.Infof("register dir  %s  %s", path,dirpath)
    			}
    }
    

    404

    404可以在main函数中配置

    rt := router.NewPatRouter()
    	rt.SetNotFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    		//这里内容可以定制
    		w.Write([]byte("服务器开小差了,这里可定制"))
    	}))
    	server := rest.MustNewServer(c.RestConf, rest.WithRouter(rt))
    

    此时请求http://127.0.0.1:8888/hello,系统响应
    服务器开小差了,这里可定制

    测试

    启动系统后运行

    E:\workspace@go\gozero\file>go run file.go
    2020/09/05 20:18:24 {"@timestamp":"2020-09-05T20:18:24.682+08","level":"info","content":"{{{file-api { console logs info false 0 100} pro  { 0 }} 0.0.0.0 8081 false 10000 1048576 3000 900 {false 0s []}} [/asset/=./assets]}"} 
    {"@timestamp":"2020-09-05T20:18:24.682+08","level":"info","content":"register dir  /asset/:1  ./assets"}
    {"@timestamp":"2020-09-05T20:18:24.683+08","level":"info","content":"register dir  /asset/:1/:2  ./assets"}
    {"@timestamp":"2020-09-05T20:18:24.683+08","level":"info","content":"register dir  /asset/:1/:2/:3  ./assets"}
    {"@timestamp":"2020-09-05T20:18:24.683+08","level":"info","content":"register dir  /asset/:1/:2/:3/:4  ./assets"}
    {"@timestamp":"2020-09-05T20:18:24.697+08","level":"info","content":"register dir  /asset/:1/:2/:3/:4/:5  ./assets"}
    {"@timestamp":"2020-09-05T20:18:24.697+08","level":"info","content":"register dir  /asset/:1/:2/:3/:4/:5/:6  ./assets"}
    {"@timestamp":"2020-09-05T20:18:24.698+08","level":"info","content":"register dir  /asset/:1/:2/:3/:4/:5/:6/:7  ./assets"}
    
    

    访问系统都能正常响应

    http://127.0.0.1:8888/asset/images/avatar.jpg
    http://127.0.0.1:8888/asset/js/test.js
    http://127.0.0.1:8888/asset/js/lv2/test.js

    注意,请求的是/asset/** 不是/assets/**

    思考一下

    我们可以在NotFoundHandler中根据req.URL.path来实现文件服务,如何实现呢?

    下章预告

    单体应用需要的基本梳理完,明天开始微服务

    本文代码获取

    关注公众号betaidea 输入file即可获得本文相关代码
    关注公众号betaidea 输入html即可获得html解析相关代码
    关注公众号betaidea 输入jwt即可获得gozero集成jwt-token相关代码
    关注公众号betaidea 输入gozero即可gozero入门代码

    广而告之

    送福利了uniapp用户福音来啦!
    历经数十万用户考验,我们的客服系统终于对外提供服务了。
    你还在为商城接入客服烦恼吗?只需一行代码,即可接入啦!!
    只需一行代码!!!

    /*kefu.vue*/
    <template>
    	<view>
    		<IdeaKefu :siteid="siteId"  ></IdeaKefu>
    	</view>
    </template>
    
    <script>
    	import IdeaKefu from "@/components/idea-kefu/idea-kefu.vue"
        export default {
    		components:{
    			IdeaKefu
    		},
    		data() {
    			return {
    				siteId:2
    			}
    		}
        }   
    

    效果杠杠的
    客服效果

    开发文档地址
    http://kefu.techidea8.com/html/wiki/

    展开全文
  • 项目前端是基于Ant Design Pro来创建的,并基于go-zero来创建的一个前分离的管理系统 1.项目预览 1.1用户 1.2角色 1.3菜单 1.4机构 1.5字典 1。6日志
  • 项目前端是基于Ant Design Pro来创建的,基本上是基于go-zero来创建的一个前分离分离的管理系统 1.安装node_modules: npm install 2.启动项目 npm run dev 3.项目预览 3.1用户 3.2角色 3.3菜单 3.4机构 3.5字典 3....
  • alpha-go-ZERO原文

    2018-05-08 14:24:00
    alpha-go-ZERO原文alpha-go-ZERO原文alpha-go-ZERO原文alpha-go-ZERO原文alpha-go-ZERO原文
  • 扩展go-zero,使之支持html模板解析自动化 go-zero本身支持html模板解析,我们只需要添加url对应模板解hanlder,实现逻辑就可以了 但是winlion太懒了,我甚至想 不写任何一个和模板相关的...如果对go-zero已经了解,直接跳

    扩展go-zero,使之支持html模板解析自动化

    go-zero本身支持html模板解析,我们只需要添加url对应模板解hanlder,实现逻辑就可以了

    但是winlion太懒了,我甚至想

    • 不写任何一个和模板相关的handler
    • 如果有新的模板,直接把模板到某个特定目录就好,不要动任何go代码
    • 在开发环境下没有缓存,修改了模板文件无需重启

    需求在这里,开撸吧

    在代码开始前,你可能需要阅读

    金光灿灿的Gorm V2+适合创业的golang微服务框架go-zero实战
    如果对go-zero已经了解,直接跳过吧

    创建项目

    生成go.mod文件

    以如下指令创建项目

    mkdir html
    cd html
    go mod init  html
    

    定义html.api

    本文设计API如下

    描述 格式 方法 参数 返回 是否需要鉴权
    用户登录 /open/authorization post mobile:手机号,passwd:密码,code:图片验证码 id:用户ID,token:用户token

    根据以上描述,书写api的模板文件如下

    type (
    	UserOptReq struct {
    		mobile string `form:"mobile"`
    		passwd string `form:"passwd"`
    		code   string `form:"code,optional"`
    	}
    
    	UserOptResp struct {
    		id    uint   `json:"id"`
    		token string `json:"token"`
    	}
    )
    
    service html-api {
    	@server(
    		handler: authorizationHandler
    		folder: open
    	)
    	post /open/authorization(UserOptReq) returns(UserOptResp)
    	
    }
    
    

    注意

    • 本文和html模板相关,可以不适用goctl工具
    • 但是由于使用工具可以为我们节省很多搭建框架相关的工作,所以建议使用用ctl生成

    生成代码

    采用如下指令生成代码

    goctl api  go   -api   html.api   -dir  .
    

    此时用go run html.go指令可以发现系统以及运行

    html模板自动解析实现思路

    模板解析需要了解如下俩个已知知识点

    • html网页输出本质上是get请求输出
    • 相对于一个项目来说,模板文件个数是有限的,因此我们可以将模板枚举出来,完成访模板名称和请求之间的映射

    对于第一个,我们可以构建get路由来实现请求,以首页请求http://127.0.0.1:8888/index.html为例,核心代码如下,

    	htmltplrouter:= rest.Route{
    			Method:  http.MethodGet,
    			Path:    "/index.html",
    			Handler: htmlhandler(...),
    	}
    
    	engine.AddRoute(htmltplrouter)
    

    在上述代码中,htmlhandler函数实现了对请求的响应,也就是解析了模板并将模板内容输出

    //gloabtemplate:全局解析的模板参数
    //tplname:模板名称,
    //serverCtx 应用配置
    func htmlhandler(gloabtemplate *template.Template, tplname string, serverCtx *svc.ServiceContext) http.HandlerFunc {
    	return func(w http.ResponseWriter, r *http.Request) {
    		//模板名字就是r.URL.Path
    		t := gloabtemplate
    		//如果是调试模式,则支持热解析
    		if serverCtx.Config.Debug {
    			t, _ = template.New("").Funcs(FuncMap()).ParseGlob(serverCtx.Config.TemplatePattern)
    		}
    		err := t.ExecuteTemplate(w, tplname, r.URL.Query())
    		if err != nil {
    			httpx.Error(w, err)
    		}
    	}
    }
    
    

    如何建立uri和模板名称之间的映射关系

    这里有几个点需要强调:

    • 在golang中,每个包含模板内容的html文件会被解析成一个模板,如在view/www/下新建test.html文件,即使里面没有内容,系统也会将其解析得到一个名叫test.html的模板。
    • 如果在模板文件以template标签中定义名称为www/test.html的模板,则系统又会解析得到一个名叫www/test.html的模板,此时存在俩个模板,一个名叫test.html,一个名叫www/test.html

    view/www/test.html文件内容如下

    {{define "www/test.html"}}
    <h1>这是模板www/test.html的内容</h1>
    {{end}}
    
    

    因此我们可以取巧,将模板名称命名成需要建立映射关系的uri
    比如外部通过http://127.0.0.1:8888/www/test.html来访问,此时req.URI.path为/www/test.html 我们可以用这个作为模板名称

    如何枚举模板

    这里用到了ParseGlob函数,这个函数本质上是对filepath.ParseGlob()template.ParseFiles()的封装,可以遍历满足一定格式的路径的所有文件,假设我们建立模板存放目录internal\view如下

    tree /F /A
    |   go.mod
    |   go.sum
    |   html.api
    |   html.go
    |   readme.md
    |
    +---etc
    |       html-api.yaml
    |
    \---internal
        +---config
        |       config.go
        |
        +---handler
        |   |   routes.go
        |   |
        |   \---open
        |           authorizationhandler.go
        |
        +---logic
        |   \---open
        |           authorizationlogic.go
        |
        +---svc
        |       servicecontext.go
        |
        +---types
        |       types.go
        |
        \---view
            +---public
            |       footer.html
            |       header.html
            |
            \---www
                    index.html
                    test.html
    

    则我们可以使用格式字符串 ./internal/view/**/* 来遍历并解析并解析模板,建立模板和uri之间的对应关系,核心代码如下

      gloabtemplate,err:=template.New("").Funcs(FuncMap()).ParseGlob("./internal/view/**/*")
      //range轮询 
      for _, tpl := range gloabtemplate.Templates() {
    		patern := tpl.Name()
    		if !strings.HasPrefix(patern, "/") {
    			patern = "/" + patern
    		}
    
    		//首页默认index.html index.htm index.php
    		tplname := tpl.Name()
    		if 0 == len(tplname) {
    			tplname = serverCtx.Config.TemplateIndex
    		}
    
    		pageRouters = append(pageRouters, rest.Route{
    			Method:  http.MethodGet,
    			Path:    patern,
    			Handler: htmlhandler(gloabtemplate, tplname, serverCtx),
    		})
    		logx.Infof("register page %s  %s", patern, tplname)
    	}
    	//添加到engin路由中
    	engine.AddRoutes(pageRouters)
    

    如何在模板中使用函数

    有时候我们需要在模板中使用函数,则需要用到函数映射功能,golang提供接口函数Funcs()来注入,

    假设我们需要在/www/version.html中查看系统版本,应该怎么做呢?

    1. 定义相关函数
    //handlers\funcs.go
    package handler
    
    import (
    	"html/template"
    )
    
    //定义
    var funcsMap template.FuncMap = make(template.FuncMap)
    
    func FuncMap() template.FuncMap {
    
    	funcsMap["version"] = version
    	funcsMap["hello"] = hello
    
    	return funcsMap
    }
    func version() string {
    	//这个函数返回当前版本号0.0.1
    	return "0.01"
    
    }
    func hello(str string) string {
    	//这个函数返回当前版本号0.0.1
    	return "hello "+ str
    
    }
    

    应用可以通过 template.New("").Funcs(FuncMap())来注入响应函数

    1. 定义模板文件
      新建文件view/www/version.html,内容如下
    {{define "www/version.html"}}
    <h1>当前版本号:{{version}}</h1>
    <h1>这里测试带参数的函数:{{hello "word"}}</h1>
    {{end}}
    
    1. 无参数的函数展示
      此时模板文件中通过 {{version}} 即可调用并显示版本号0.01

    2. 有参数的函数
      对应有参数的函数,按照参数顺序排列,中间用空格隔开

    3. 以上显示结果

    当前版本号:0.01
    这里测试带参数的函数:hello word
    

    如何模板嵌套

    使用templete指令进行嵌套

    新建view/public/header.html内容如下

    <!-- 顶部菜单 Start -->
    <div class="top-menu-wrapper index-menu">
        <h1>这是Head</h1>
    </div>
    

    新建view/public/footer.html内容如下

    <!-- 顶部菜单 Start -->
    <div class="top-menu-wrapper index-menu">
        <h1>这是footer</h1>
    </div>
    

    新建view/www/index.html文件,内容如下

    <!DOCTYPE html>
    <html>
    <head></head>
    <body>
    {{template "header.html" .}}
    <div class="content-box" data-spy="scroll" data-target=".section-scrollspy"> 
      <h1>这是Index的内容</h1> 
    </div> 
    {{template "footer.html" .}}
    </body>
    </html>
    

    此时编译后即可得到如下内容

    这是Head
    这是Index的内容
    这是footer

    如何在模板中使用变量

    • 在模板中直接使用
      首先需要将变量暴露到模板中,这里我们使用到了ExecuteTemplate函数,该函数第三个参数即可以在模板里面访问的参数,比如如下代码,则在模板中可以访问Query了
    	data := r.URI.Query
    	err := t.ExecuteTemplate(w, tplname, data)
    

    新建view/www/arg.html文件

    {{define "www/arg.html"}}
    <h5>arga={{.arga}}</h5>
    <h5>argb={{.argb}}</h5>
    {{end}}
    

    请求访问方式http://127.0.0.1:8888/www/arg.html?arga=123&argb=456

    系统返回结果

    arga=[123]
    argb=[456]
    
    • 在嵌套模板中使用

    在嵌套模板中使用需要将对象传入,方式是在模板名后加一个.,如下
    新建view/www/embd.html文件

    {{define "www/embd.html"}}
    没加点:{{template "www/arg.html"}}
    =======
    加点:{{template "www/arg.html" .}}
    {{end}}
    

    结果如下

    没加点:
    <h5>arga=</h5>
    <h5>argb=</h5>
    =======
    加点:
    <h5>arga=[123]</h5>
    <h5>argb=[456]</h5>
    
    
    

    如何实现模板热更新

    假设我们的应用支持开发模式和生产模式,在生产模式下,由于有性能考虑,系统不需要每次访问都解析模板。而在开发模式下,每个模板有所任何小的修改,我们都希望模板能自动更新,怎么实现这个功能呢?
    方案很多,有文件监听方案,如github.com/fsnotify/fsnotify监听模板目录,也有标记位方案,无论模板有没有变动,只要是开发模式,每次请求都重新加载模板并解析,gin就是这种方案,本文也采用这种方案,核心代码如下

    //模板名字就是r.URL.Path
    		t := gloabtemplate
    		//如果是debug模式
    		if serverCtx.Config.Debug {
    			//每次都重新解析
    			t, _ = template.New("").Funcs(FuncMap()).ParseGlob(serverCtx.Config.TemplatePattern)
    		}
    		err := t.ExecuteTemplate(w, tplname, r.URL.Query())
    

    如何设置首页

    本质上是指定/请求对应的模板,以及系统错误对应的模板

    for _, tpl := range gloabtemplate.Templates() {
    		patern := tpl.Name()
    		if !strings.HasPrefix(patern, "/") {
    			patern = "/" + patern
    		}
    
    		//处理首页逻辑
    		tplname := tpl.Name()
    		if 0 == len(tplname) {
    			//模板名称为""那么就默认首页吧
    			//恰好/对应的模板名称为"",
    			tplname = serverCtx.Config.TemplateIndex
    		}
    
    		pageRouters = append(pageRouters, rest.Route{
    			Method:  http.MethodGet,
    			Path:    patern,
    			Handler: htmlhandler(gloabtemplate, tplname, serverCtx),
    		})
    		logx.Infof("register page %s  %s", patern, tplname)
    	}
    
    

    404等页面

    目前可以实现业务逻辑层面的404定制,如httpx.Error方法可用404.html替代。
    对于部分场景如访问一个不存在的url,则需要go-zero官方提供支持,并开发接口。

    集成

    以上操作完成后,我们得到如下项目目录,

    tree /F /A
    
    |   go.mod                                                                                                         
    |   go.sum                                                                                                         
    |   html.api                                                                                                       
    |   html.go                                                                                                        
    |   readme.md                                                                                                      
    |                                                                                                                  
    +---etc                                                                                                            
    |       html-api.yaml                                                                                              
    |                                                                                                                  
    \---internal                                                                                                       
        +---config                                                                                                     
        |       config.go                                                                                              
        |                                                                                                              
        +---handler                                                                                                    
        |   |   funcs.go                                                                                               
        |   |   html.go                                                                                                
        |   |   routes.go                                                                                              
        |   |                                                                                                          
        |   \---open                                                                                                   
        |           authorizationhandler.go                                                                            
        |                                                                                                              
        +---logic                                                                                                      
        |   \---open                                                                                                   
        |           authorizationlogic.go                                                                              
        |                                                                                                              
        +---svc                                                                                                        
        |       servicecontext.go                                                                                      
        |                                                                                                              
        +---types                                                                                                      
        |       types.go                                                                                               
        |                                                                                                              
        \---view                                                                                                       
            +---public                                                                                                 
            |       404.html                                                                                           
            |       footer.html                                                                                        
            |       header.html                                                                                        
            |                                                                                                          
            \---www                                                                                                    
                    arg.html                                                                                           
                    embd.html                                                                                          
                    func.html                                                                                          
                    index.html                                                                                         
                    test.html                                                                                          
                                                                                                                       
    

    routes.go中添加如下代码段即可

    func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
    	engine.AddRoutes([]rest.Route{
    		{
    			Method:  http.MethodPost,
    			Path:    "/open/authorization",
    			Handler: open.AuthorizationHandler(serverCtx),
    		},
    	})
    	//添加这个代码段
    	RegisterHtmlHandlers(engine, serverCtx)
    }
    

    本文代码获取

    关注公众号betaidea 输入html即可获得html解析相关代码
    关注公众号betaidea 输入jwt即可获得gozero集成jwt-token相关代码
    关注公众号betaidea 输入gozero即可gozero入门代码

    下一篇预告

    目前貌似还没找到go-zero对static file支持的例子,类似gin哪样做静态资源服务貌的例子,那么明天就写一个吧。
    在go-zero的路由框架下寻找解决方案。
    《用go-zero 支持文件服务》

    广而告之

    送福利了uniapp用户福音来啦!
    历经数十万用户考验,我们的客服系统终于对外提供服务了。
    你还在为商城接入客服烦恼吗?只需一行代码,即可接入啦!!
    只需一行代码!!!

    /*kefu.vue*/
    <template>
    	<view>
    		<IdeaKefu :siteid="siteId"  ></IdeaKefu>
    	</view>
    </template>
    
    <script>
    	import IdeaKefu from "@/components/idea-kefu/idea-kefu.vue"
        export default {
    		components:{
    			IdeaKefu
    		},
    		data() {
    			return {
    				siteId:2
    			}
    		}
        }   
    

    效果杠杠的
    客服效果

    开发文档地址
    http://kefu.techidea8.com/html/wiki/

    展开全文
  • 为什么使用go-zero 可以有第三个选择 golang圈子不大,微服务框架框架屈指可数:除了go-micro、go-kit,几乎没有其他选择。go-zero为此提供第三个可能。 go-micro 对webapi支持并不友好,需要运行micro指令,难以定制 ...

    为什么使用go-zero

    可以有第三个选择

    • golang圈子不大,微服务框架框架屈指可数:除了go-micro、go-kit,几乎没有其他选择。go-zero为此提供第三个可能。
    • go-micro 对webapi支持并不友好,需要运行micro指令,难以定制

    创业产品需要一款支持业务成长的框架

    我们到底需要什么样的框架?我们需要一款可以支持业务成长的框架!产品运营初期,比如需求验证阶段,我们并不需要采取微服务架构,因为运营成本太高。一款单体应用足以应付。随着业务发展,微服务成为必要,此时我们希望不进行太多的代码工作量,柔性升级。这正是go-zero价值所在

    go-zero是什么

    以下安利是copy的的,具体参考https://github.com/tal-tech/go-zero
    go-zero是一个集成了各种工程实践的包含web和rpc框架,有如下主要特点:

    • 强大的工具支持,尽可能少的代码编写
    • 极简的接口
    • 完全兼容net/http
    • 支持中间件,方便扩展
    • 高性能
    • 面向故障编程,弹性设计
    • 内建服务发现、负载均衡
    • 内建限流、熔断、降载,且自动触发,自动恢复
    • API参数自动校验
    • 超时级联控制
    • 自动缓存控制
    • 链路跟踪、统计报警等
    • 高并发支撑,稳定保障了晓黑板疫情期间每天的流量洪峰

    怎么用

    在阅读本文档前,请将golang 升级到go14及以上版本,并开启go module支持,GO14以上只是为了支持Gorm

    export GOPROXY=https://goproxy.io,direct
    export GO111MODULE=on 
    

    安装goctl

    goctl是go-zero配套的代码生成器,偷懒神器,毕竟写代码大多时间是体力活
    如何安装呢?先把源代码下载下来吧!

    git clone https://github.com/tal-tech/go-zero
    cd go-zero/tools/goctl
    go build goctl.go
    

    最后生成goctl.exe 复制到$gopath/bin

    goctl指令说明

    自行浏览文档吧https://github.com/tal-tech/go-zero/blob/master/tools/goctl/goctl.md

    本文用到指令如下

    goctl api      go       -api             open.api            -dir                     .
    
    #代码说明如下
    goctl api      go       -api             open.api            -dir                     .
     |      |        |         |                 |                  |                      | 
          生成api  go语言     指定api模板文件   模板文件名称         指定生成代码存放路径     当前文件夹
    

    创建项目

    生成go.mod文件

    以如下指令创建项目

    mkdir hello
    cd hello
    go mod init  hello
    

    定义hello.api

    本文设计API如下

    描述 格式 方法 参数 返回
    用户注册 /open/register post mobile:手机号,passwd:密码,code:图片验证码 id:用户ID,token:用户token
    用户登录 /open/authorization post mobile:手机号,passwd:密码,code:图片验证码 id:用户ID,token:用户token
    图片验证码请求 /open/verify get ticket:图片验证码的id data:base64格式的图片

    根据以上描述,书写api的模板文件如下

    
    type (
    	UserOptReq struct {
    		mobile string `json:"mobile"`
    		passwd string `json:"passwd"`
    		code   string `json:"code"`
    	}
    
    	UserOptResp struct {
    		id    uint   `json:"id"`
    		token string `json:"token"`
    	}
    	//图片验证码支持
    	VerifyReq struct {
    		ticket string `json:"ticket"`
    	}
    	//图片验证码支持
    	VerifyResp struct {
    		data string `json:"data"`
    	}
    )
    
    service open-api {
    	@doc(
            summary: 公开的api函数
            desc: >
            register 用户注册,
            authorization 用户登录,
            verify 图片验证码接口
        )
    	@server(
    		handler: registerHandler
    		folder: open
    	)
    	post /open/register(UserOptReq) returns(UserOptResp)
    	
    	
    	@server(
    		handler: authorizationHandler
    		folder: open
    	)
    	post /open/authorization(UserOptReq) returns(UserOptResp)
    
    	@server(
    		handler: verifyHandler
    		folder: open
    	)
    	post /open/verify(VerifyReq) returns(VerifyResp)
    	
    }
    
    

    注意

    • 一个文件里面只能有一个service
    • 工具最后会以type里面模型为样板生成各种结构体,所以参数和结构体保持一致即可
    • 如果我们需要分文件夹管理业务, 可以用folder属性来定义

    生成代码

    采用如下指令生成代码

    goctl api  go   -api   open.api   -dir  .
    

    最后代码结构如下

    #tree /F /A
    |   go.mod
    |   go.sum
    |   hello.api
    |   open.go
    |
    +---etc
    |       open-api.yaml
    |
    \---internal
        +---config
        |       config.go
        |
        +---handler
        |   |   routes.go
        |   |
        |   \---open
        |           authorizationhandler.go
        |           registerhandler.go
        |           verifyhandler.go
        |
        +---logic
        |   \---open
        |           authorizationlogic.go
        |           registerlogic.go
        |           verifylogic.go
        |
        +---svc
        |       servicecontext.go
        |
        \---types
                types.go                                          
    

    运行一下

    go run open.go
    

    测试一下

    curl http://127.0.0.1:8888/open/register -X POST -H "Content-Type: application/json" -d {\"mobile\":\"15367151352\",\"passwd\":\"testpwd\",\"code\":\"asdf\"}
    {"id":0,"token":""}
    

    集成明星产品Gorm V2

    金珠大佬升级了Gorm V2集成测试一下吧

    配置文件

    配置文件在etc/open-api.yaml

    Name: open-api
    Host: 0.0.0.0
    Port: 8888
    DataSourceName: root:1D007648b4f8@(127.0.0.1:3306)/gozero?charset=utf8
    

    etc/open-api.yaml中添加参数DataSourceName,
    internal/config/config.go中添加DataSourceName

    type Config struct {
    	rest.RestConf
    	DataSourceName string
    }
    
    

    关于配置文件,系统内置了一部分关键字 如Cache,资料不多;基本上可以随便配置,然后在Conf中定义同名变量即可。

    启动Gorm支持

    修改svc/servicecontext.go代码如下

    package svc
    
    import (
    	"hello/internal/config"
    	"hello/internal/models"
    
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/schema"
    )
    
    type ServiceContext struct {
    	Config  config.Config
    	DbEngin *gorm.DB
    }
    
    func NewServiceContext(c config.Config) *ServiceContext {
        //启动Gorm支持
    	db, err := gorm.Open(mysql.Open(c.DataSourceName), &gorm.Config{
    		NamingStrategy: schema.NamingStrategy{
    			TablePrefix:   "tech_", // 表名前缀,`User` 的表名应该是 `t_users`
    			SingularTable: true,    // 使用单数表名,启用该选项,此时,`User` 的表名应该是 `t_user`
    		},
        })
        //如果出错就GameOver了
    	if err != nil {
    		panic(err)
        }
        //自动同步更新表结构,不要建表了O(∩_∩)O哈哈~
    	db.AutoMigrate(&models.User{})
    
    	return &ServiceContext{Config: c, DbEngin: db}
    }
    
    

    新建模型文件

    新建models\models.go文件

    //models\models.go文件
    package models
    
    import (
    	"errors"
    	"hello/internal/utils"
    
    	"gorm.io/gorm"
    )
    
    type User struct {
    	gorm.Model
    	Mobile string `gorm:"index:mobile;type:varchar(13)"`
    	Passwd string `gorm:"type:varchar(64)"`
    }
    //在创建前检验验证一下密码的有效性
    func (u *User) BeforeCreate(db *gorm.DB) error {
    	if len(u.Passwd) < 6 {
    		return errors.New("密码太简单了")
        }
        //对密码进行加密存储
    	u.Passwd = utils.Password(u.Passwd)
    	return nil
    }
    

    utils.Password是我们编写的工具包,代码如下

    package utils
    
    import (
    	"fmt"
    
    	"golang.org/x/crypto/bcrypt"
    )
    
    //密码加密
    func Password(plainpwd string) string {
        //谷歌的加密包
    	hash, err := bcrypt.GenerateFromPassword([]byte(plainpwd), bcrypt.DefaultCost) //加密处理
    	if err != nil {
    		fmt.Println(err)
    	}
    	encodePWD := string(hash) // 保存在数据库的密码,虽然每次生成都不同,只需保存一份即可
    	return encodePWD
    }
    //密码校验
    func CheckPassword(plainpwd, cryptedpwd string) bool {
    	err := bcrypt.CompareHashAndPassword([]byte(cryptedpwd), []byte(plainpwd)) //验证(对比)
    	return err == nil
    }
    
    

    实现业务逻辑

    logic\open\registerlogic.go中修改代码如下

    package logic
    
    import (
    	"context"
    
    	"hello/internal/models"
    	"hello/internal/svc"
    	"hello/internal/types"
    
    	"github.com/tal-tech/go-zero/core/logx"
    )
    
    type RegisterLogic struct {
    	ctx context.Context
    	logx.Logger
    	svcCtx *svc.ServiceContext
    }
    
    func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) RegisterLogic {
    	return RegisterLogic{
    		ctx:    ctx,
    		Logger: logx.WithContext(ctx),
    		svcCtx: svcCtx,
    	}
    }
    
    func (l *RegisterLogic) Register(req types.UserOptReq) (*types.UserOptResp, error) {
    	user := models.User{
    		Mobile: req.Mobile,
    		Passwd: req.Passwd,
    	}
    	result := l.svcCtx.DbEngin.Create(&user)
    	return &types.UserOptResp{
    		Id: user.ID,
    	}, result.Error
    }
    
    
    • RegisterLogic中添加svcCtx *svc.ServiceContext,因为需要用到里面的DbEngin
    • NewRegisterLogic 配置svcCtx
    • 在Register函数中实现逻辑result := l.svcCtx.DbEngin.Create(&user)

    测试一下

    >curl http://127.0.0.1:8888/open/register -X POST -H "Content-Type: application/json" -d {\"mobile\":\"15367151352\",\"passwd\":\"testpwd\"}
    {"id":3,"token":""}
    

    期待更新的功能点

    go-zero

    接口定义希望支持多种content-type

    UserOptReq struct {
    	mobile string `json:"mobile" form:"mobile" xml:"mobile"`
    	passwd string `json:"passwd" form:"passwd" xml:"passwd"`
    	code   string `json:"code" form:"code" xml:"code"`
    }
    

    一种可能的解决方法是
    修改github.com/tal-tech/go-zero/rest/httpx/requests.go中的Parse成如下模型

    func Parse(r *http.Request, v interface{}) error {
    	if err := ParsePath(r, v); err != nil {
    		return err
    	}
    	if strings.Contains(r.Header.Get(ContentType), multipartFormData) {
    		return ParseForm(r, v)
    	} else if strings.Contains(r.Header.Get(ContentType), urlencodeformdata) {
    		return ParseForm(r, v)
    	} else if strings.Contains(r.Header.Get(ContentType), applicationjson) {
    		return ParseJsonBody(r, v)
    	} else {
    		return errors.New("不支持的请求类型")
    	}
    }
    

    支持一个文件多个方法

    比如如下写法,则生成俩个方法在verifyHandler.go文件中

    	@server(
    		handler: verifyHandler
    		folder: open
    	)
    	post /open/verify(VerifyReq) returns(VerifyResp)
        post /open/authorization(UserOptReq) returns(UserOptResp)
    

    gorm v2

    建议默认SingularTable属性为true

    NamingStrategy: schema.NamingStrategy{
    			TablePrefix:   "tech_", // 表名前缀,`User` 的表名应该是 `t_users`
    			SingularTable: true,    // 使用单数表名,启用该选项,此时,`User` 的表名应该是 `t_user`
    		},
    

    建议增强缓存功能

    建议提供缓存如redis/memcache/内存缓存支持

    本文代码获取

    关注公众号betaidea 输入gozero或者gormv2即可获得

    广而告之

    送福利了uniapp用户福音来啦!
    历经数十万用户考验,我们的客服系统终于对外提供服务了。
    你还在为商城接入客服烦恼吗?只需一行代码,即可接入啦!!
    只需一行代码!!!

    /*kefu.vue*/
    <template>
    	<view>
    		<IdeaKefu :siteid="siteId"  ></IdeaKefu>
    	</view>
    </template>
    
    <script>
    	import IdeaKefu from "@/components/idea-kefu/idea-kefu.vue"
        export default {
    		components:{
    			IdeaKefu
    		},
    		data() {
    			return {
    				siteId:2
    			}
    		}
        }   
    

    效果杠杠的
    客服效果

    开发文档地址
    http://kefu.techidea8.com/html/wiki/

    展开全文
  • 金光灿灿的Gorm V2+适合创业的golang微服务框架go-zero实战 创建项目 生成go.mod文件 以如下指令创建项目 mkdir jwttoken cd jwttoken go mod init jwttoken 定义user.api 本文设计API如下 描述 格式 方法 参数...

    阅读本文前你需要阅读

    金光灿灿的Gorm V2+适合创业的golang微服务框架go-zero实战

    创建项目

    生成go.mod文件

    以如下指令创建项目

    mkdir jwttoken
    cd jwttoken
    go mod init  jwttoken
    

    定义user.api

    本文设计API如下

    描述 格式 方法 参数 返回 是否需要鉴权
    用户登录 /open/authorization post mobile:手机号,passwd:密码,code:图片验证码 id:用户ID,token:用户token
    更新用户信息 /user/update post mobile:用户手机号 token:用户新的token

    根据以上描述,书写api的模板文件如下

    
    type (
    	UserOptReq struct {
    		mobile string `form:"mobile"`
    		passwd string `form:"passwd"`
    		code   string `form:"code,optional"`
    	}
    
    	UserOptResp struct {
    		id    uint   `json:"id"`
    		token string `json:"token"`
    	}
    	//修改
    	UserUpdateReq struct {
    		id     uint   `form:"id"`
    		mobile string `form:"mobile,optional"`
    	}
    )
    
    service user-api {
    	@server(
    		handler: authorizationHandler
    		folder: open
    	)
    	post /open/authorization(UserOptReq) returns(UserOptResp)
    
    	@server(
    		handler: edituserHandler
    		folder: user
    	)
    	post /user/update(UserUpdateReq) returns(UserOptResp)
    	
    }
    
    

    注意

    • 一个文件里面只能有一个service
    • 工具最后会以type里面模型为样板生成各种结构体,所以参数和结构体保持一致即可
    • 如果我们需要分文件夹管理业务, 可以用folder属性来定义

    生成代码

    采用如下指令生成代码

    goctl api  go   -api   user.api   -dir  .
    

    运行一下

    go run open.go
    

    测试一下

    curl http://127.0.0.1:8888/open/authorization -X POST -d "mobile=15367151352&passwd=123rte&code=asasa"\"passwd\":\"testpwd\",\"code\":\"asdf\"}
    {"id":0,"token":""}
    

    中间件实现鉴权

    handler下新建auth.go文件,关键代码如下

    
    //鉴权白名单,在这里面的是不需要鉴权的
    var whiteList []string = []string{
    	"/open/",
    }
    
    //鉴权中间件
    func Auth(next http.HandlerFunc) http.HandlerFunc {
    	return func(w http.ResponseWriter, r *http.Request) {
    		w.Header().Add("X-Middleware", "auth")
    		uri := r.RequestURI
    		//默认不在
    		isInWhiteList := false
    		//判断请求是否包含白名单中的元素
    		for _, v := range whiteList {
    			if strings.Contains(uri, v) {
    				isInWhiteList = true
    			}
    		}
    		//如果爱白名单里面直接通过
    		if isInWhiteList {
    			next(w, r)
    			return
    		}
    		//否则获取前端header 里面的X-Token字段,这个就是token	
    		token := r.Header.Get("X-Token")
    		//工具类见util\jwttoken.go
    		_, err := utils.DecodeJwtToken(token)
    		//如果有错直接返回error
    		if err != nil {
    			httpx.Error(w, err)
    			return
    		}
    		//没报错就继续
    		next(w, r)
    	}
    }
    

    routers.go中添加一行代码

    func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext) {
    	//添加这行代码
    	engine.Use(Auth)
    	///。。
    }	
    
    

    生成jwttoken

    logic\open\authorizationlogic.go中实现jwttoken的获取

    func (l *AuthorizationLogic) Authorization(req types.UserOptReq) (*types.UserOptResp, error) {
    	//这个是生成jwttoken的工具类
    	token, err := utils.EncodeJwtToken(map[string]interface{}{
    		"role": "kefu",
    		"id":   "10086",
    	})
    	return &types.UserOptResp{
    		Token: token,
    	}, err
    }
    
    

    测试

    不携带token时访问

    >curl http://127.0.0.1:8888/user/update -X POST -d "mobile=15367151352&id=123"
    鉴权失败,缺少鉴权参数
    

    获取token

    >curl http://127.0.0.1:8081/open/authorization -X POST -d "mobile=15367151352&passwd=123rte&code=asasa"
    {"id":1599063149,"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTkzMjIzNDksImlkIjoiMTUzNjcxNTEzNTIifQ.jcdg3c2rdigPO5ZTxcDilVGERAuMIdY9BUmMNX3ZA9c"}
    

    携带token时访问

    >curl http://127.0.0.1:8888/user/update -X POST -H "X-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTkzMjIzNDksImlkIjoiMTUzNjcxNTEzNTIifQ.jcdg3c2rdigPO5ZTxcDilVGERAuMIdY9BUmMNX3ZA9c" -d "mobile=15367151352&id=123"
    # 请求成功
    {"id":123,"token":""}
    

    携带错误的token时访问

    >curl http://127.0.0.1:8888/user/update -X POST -H "X-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTkzMjIzNDksImlkIjoiMTUzNjcxNTEzNTIifQ.jcdg3c2rdigPO5ZTxcDilVGERAuMIdY9BUmMNX3ZA9c0000" -d "mobile=15367151352&id=123"
    # 返回签名无效
    signature is invalid
    

    本文代码获取

    关注公众号betaidea 输入jwt即可获得
    关注公众号betaidea 输入gozero即可gozero入门代码

    广而告之

    送福利了uniapp用户福音来啦!
    历经数十万用户考验,我们的客服系统终于对外提供服务了。
    你还在为商城接入客服烦恼吗?只需一行代码,即可接入啦!!
    只需一行代码!!!

    /*kefu.vue*/
    <template>
    	<view>
    		<IdeaKefu :siteid="siteId"  ></IdeaKefu>
    	</view>
    </template>
    
    <script>
    	import IdeaKefu from "@/components/idea-kefu/idea-kefu.vue"
        export default {
    		components:{
    			IdeaKefu
    		},
    		data() {
    			return {
    				siteId:2
    			}
    		}
        }   
    

    效果杠杠的
    客服效果

    开发文档地址
    http://kefu.techidea8.com/html/wiki/

    展开全文
  • go-zero是一个Web和rpc框架,内置了许多工程实践。 它的诞生是为了通过弹性设计确保繁忙服务的稳定性,并且多年来一直为拥有数千万用户的站点提供服务。 go-zero包含简单的API描述语法和称为goctl代码生成工具。 您...
  • go-zero是一个集成了各种工程实践的网站和rpc框架。通过弹性设计保障了大并发服务端的稳定性,使其充分地进行了实战检验。 go-zero包含极简的API定义和生成工具goctl,可以根据定义的api文件一键生成Go,iOS,...
  • 为什么使用go-zero 可以有第三个选择 golang圈子不大,微服务框架框架屈指可数:除了go-micro、go-kit,几乎没有其他选择。go-zero为此提供第三个可能。go-micro 对webapi支持并不友好,需要运行micro指令,难以定制 ...
  • go-zero 微服务框架介绍

    千次阅读 2020-08-27 13:15:05
    0. go-zero 介绍 go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。 go-zero 包含极简的 API 定义和生成工具 goctl,可以根据定义的 api ...
  • go-zero是一个集成了各种工程实践的web和rpc框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。包含极简的API定义和生成工具,可以一键生成Go, iOS, Android, Dart, TypeScript, JavaScript代码...
  • 云原生 go-zero 微服务框架介绍

    千次阅读 2020-08-24 09:55:06
    0. go-zero 介绍go-zero 是一个集成了各种工程实践的 web 和 rpc 框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。go-zero 包含极简的 ...
  • 0. go-zero介绍 go-zero是一个集成了各种工程实践的web和rpc框架。通过弹性设计保障了大并发服务端的稳定性,经受了充分的实战检验。 go-zero包含极简的API定义和生成工具goctl,可以根据定义的api文件一键生成Go, ...
  • go-zero之gozero+gorm

    2020-09-21 14:09:27
    适合创业的golang微服务框架go-zero + 金光灿灿的gorm V2实践 为什么使用go-zero 可以有第三个选择 golang圈子不大,微服务框架框架屈指可数:除了go-micro、go-kit,几乎没有其他选择。go-zero为此提供第三个可能。 ...
  • go-zero微服务框架入门教程

    千次阅读 2020-09-02 18:36:33
    为什么使用go-zero 你还在手撕微服务?快试试 go-zero 的微服务自动生成神器,这可能是我见过最简单好用的微服务框架。 还有比它更简单好用的吗?欢迎留言评论和推荐。 几分钟搞定个接口和微服务,还不用担心稳定...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,085
精华内容 2,034
关键字:

go-zero