go-spring 使用学习 - V2EX
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
zeromake

go-spring 使用学习

  •  
  •   zeromake
    zeromake Dec 28, 2019 7759 views
    This topic created in 2331 days ago, the information mentioned may be changed or developed.

    前言

    • 最近发现了 go-spring 并且发布了 v1.0.0-beta 版。
    • 看了一下感觉挺不错的,最近离职在家学习就花了一天时间学习这边记录一下

    一、安装

    # 拉取 go spring $ go get github.com/go-spring/go-spring@master # 如果需要使用 go-spring 做 web 服务需要以下包 # go-spring-boot-starter 是使用 spring-boot 包装的支持 web 以及其它的启动器 $ g get github.com/go-spring/go-spring-boot-starter@master # go-spring-web 则是配合 go-spring-boot-starter 使用的各种 web 框架的封装 $ go get github.com/go-spring/go-spring-web@master 

    由于 go-spring 现在还是 beta 版,每天都有可能有一些重要更新建议拉取最新的 master

    不过到了后面 go-spring 正式版也许就不需要直接手动拉取 @master 了,请自行判断。

    二、go-spring 项目包结构介绍

    $ tree . -L 1 . ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RunAllTests.sh ├── RunCodeCheck.sh ├── RunGoDoc.sh ├── boot-starter ├── go.mod ├── go.sum ├── package-info.go ├── spring-boot ├── spring-core ├── starter-echo ├── starter-gin └── starter-web 6 directories, 9 files 

    其中 starter 本来在 go-spring-boot-starter 仓库里,作者为减少引入包已经把这些 starter 移动到了 go-spring 仓库里。

    starter 部分的暂时无视,这样一看就只剩下 spring-corespring-bootboot-starter

    • spring-core 是用于 IoC 容器注入的核心库。
    • spring-boot 是使用了 spring-core 构建的配置自动载入,还有注入的对象的启动和关闭的统一管理。
    • boot-starter 简单启动和监听信号包装器。

    三、一个简单 gin web 服务

    package main import ( SpringWeb "github.com/go-spring/go-spring-web/spring-web" SpringBoot "github.com/go-spring/go-spring/spring-boot" "net/http" _ "github.com/go-spring/go-spring/starter-gin" _ "github.com/go-spring/go-spring/starter-web" ) func init() { SpringBoot.RegisterBean(new(Controller)).InitFunc(func(c *Controller) { SpringBoot.GetMapping("/", c.Home) }) } type Controller struct{} func (c *Controller) Home(ctx SpringWeb.WebContext) { ctx.String( http.StatusOK, "OK!") } func main() { SpringBoot.RunApplication("config/") } 
    • 其中 init 方法里我们注册了一个 Controller 的空实例,这个不一定要在 init 中注册,可以在 SpringBoot.RunApplication 调用前的任意地方注册,使用 init 的原因是可以不依赖包内部方法只需要导入即可注入。
    • 然后通过 InitFunc 注册路由,SpringBoot.GetMapping 是统一封装的路由挂载器
    • Home(ctx SpringWeb.WebContext) 里的 SpringWeb.WebContext 则封装了请求响应操作。
    • github.com/go-spring/go-spring/starter-gin 导入替换为 github.com/go-spring/go-spring/starter-echo 可以直接替换为 echo 框架。

    执行该文件会打出大量的注册初始化日志,正式版应该会能够关闭。

    $ go run main.go register bean "github.com/go-spring/go-spring/starter-web/WebStarter.WebServerStarter:*WebStarter.WebServerStarter" register bean "main/main.Controller:*main.Controller" register bean "github.com/go-spring/go-spring/spring-boot/SpringBoot.DefaultApplicationContext:*SpringBoot.DefaultApplicationContext" register bean "github.com/go-spring/go-spring/starter-web/WebStarter.WebServerConfig:*WebStarter.WebServerConfig" wire bean github.com/go-spring/go-spring/spring-boot/SpringBoot.DefaultApplicationContext:*SpringBoot.DefaultApplicationContext success wire bean "github.com/go-spring/go-spring/spring-boot/SpringBoot.DefaultApplicationContext:*SpringBoot.DefaultApplicationContext" wire bean github.com/go-spring/go-spring/starter-web/WebStarter.WebServerConfig:*WebStarter.WebServerConfig success wire bean "github.com/go-spring/go-spring/starter-web/WebStarter.WebServerConfig:*WebStarter.WebServerConfig" wire bean github.com/go-spring/go-spring/starter-web/WebStarter.WebServerStarter:*WebStarter.WebServerStarter success wire bean "github.com/go-spring/go-spring/starter-web/WebStarter.WebServerStarter:*WebStarter.WebServerStarter" wire bean main/main.Controller:*main.Controller success wire bean "main/main.Controller:*main.Controller" spring boot started http server started on :8080 

    访问 http://127.0.0.1 可以看到上面的代码效果。

    该章节代码见 post-1 分支。

    四、拆分 controller 并自动注册路由

    现代项目都是 controller + service 外加一个实体层,这里我们试着把 controller 拆分出去。

    新建一个 controllers 目录下面创建一个 controllers.go 来导入各个独立的 controller

    controllers/home/home.go

    package home import ( SpringWeb "github.com/go-spring/go-spring-web/spring-web" SpringBoot "github.com/go-spring/go-spring/spring-boot" "net/http" ) type Controller struct {} func init() { SpringBoot.RegisterBean(new(Controller)).InitFunc(func(c *Controller) { SpringBoot.GetMapping("/", c.Home) }) } func (c *Controller) Home(ctx SpringWeb.WebContext) { ctx.String( http.StatusOK, "OK!") } 

    controllers/controllers.go

    package controllers // 导入各个 controller 即可实现路由挂载 import ( _ "github.com/zeromake/spring-web-demo/controllers/home" ) 

    main.go

    package main import ( _ "github.com/go-spring/go-spring/starter-gin" _ "github.com/go-spring/go-spring/starter-web" SpringBoot "github.com/go-spring/go-spring/spring-boot" _ "github.com/zeromake/spring-web-demo/controllers" ) func main() { SpringBoot.RunApplication("config/") } 

    重新运行 go run main.go 访问浏览器能获得相同的效果,这样我们就把 controller 拆分出去了。

    该章节代码见 post-2 分支。

    五、构建 service 的自动注入到 controller

    上面说到 controller 的主要的能力为路由注册,参数处理复杂的逻辑应当拆分到 service 当中。

    在我使用 go-spring 之前都是手动的构建一个 map[string]interface{} 然后把 service 按照自定义名字挂进去。

    然后在 controller 构建时从这个 map 中取出并强制转换为 service 类型或者抽象的接口。

    这个方案问题蛮大的,手动的 service 名称容易出错,而且注册和在 controller 注入都是非常麻烦的,而且错误处理也都没做。

    但是这一切有了 go-spring 就不一样了,我只需要在 service 注册,在 controller 里的结构体里声明这个 service 类型实例就可以使用。

    为了不作为一个示例而太简单让学习者觉得没有什么意义,我决定做一个上传的能力,先看未拆分 service 的情况

    controllers/upload/upload.go

    package upload import ( // …… ) type Controller struct{} func init() { SpringBoot.RegisterBean(new(Controller))InitFunc(func(c *Controller) { SpringBoot.GetMapping("/upload", c.Upload) }) } func (c *Controller) Upload(ctx SpringWeb.WebContext) { file, err := ctx.FormFile("file") if err != nil { // …… return } w, err := file.Open() if err != nil { // …… return } defer func() { _ = w.Close() }() out := path.Join("temp", file.Filename) if !PathExists(out) { dir := path.Dir(out) if !PathExists(dir) { err = os.MkdirAll(dir, DIR_MARK) if err != nil { // …… return } } dst, err := os.OpenFile(out, FILE_FLAG, FILE_MAEK) if err != nil { // …… return } defer func() { _ = dst.Close() }() _, err = io.Copy(dst, w) if err != nil { // …… return } } else { // …… return } ctx.JSON( http.StatusOK, gin.H{ "code": 0, "message": http.StatusText( http.StatusOK), "data": map[string]string{ "url": out, }, }) } func PathExists(path string) bool { // …… } 

    运行 go run main.go 然后用 curl 上传测试。

    $ curl -F "file=@./README.md" http://127.0.0.1:8080/upload {"code":0,"data":{"url":"temp/README.md"},"message":"OK"} # 重复上传会发现文件已存在 $ curl -F "file=@./README.md" http://127.0.0.1:8080/upload {"code":1,"message":"该文件已存在"} 

    在项目下的 temp 文件夹中能够找到上传后的文件。

    以上能正常运行但是 controller 中包含了大量的逻辑而且均为文件操作 api 耦合性过高。

    我们需要把上面的的文件操作拆分到 service 当中。

    services/file/file.go

    将文件操作逻辑抽取为 PutObject(name string, r io.Reader, size int64) (err error)ExistsObject(name string) bool

    package file type Service struct{} func init() { SpringBoot.RegisterBean(new(Service)) } func (s *Service) PutObject(name string, r io.Reader, size int64) (err error) { // …… } func (s *Service) ExistsObject(name string) bool { // …… } 

    services/services.go

    package services import ( _ "github.com/zeromake/spring-web-demo/services/file" ) 

    main.go

    增加 services 的导入。

    package main import ( // …… _ "github.com/zeromake/spring-web-demo/services" ) func main() { SpringBoot.RunApplication("config/") } 

    controllers/upload/upload.go

    Controller 上声明 File 并设置 tag autowire,这样 spring-boot 会自动注入 service 那边注册的实例。

    package upload import ( "github.com/gin-gonic/gin" SpringWeb "github.com/go-spring/go-spring-web/spring-web" SpringBoot "github.com/go-spring/go-spring/spring-boot" "github.com/zeromake/spring-web-demo/services/file" "net/http" "path" ) type Controller struct { File *file.Service `autowire:""` } func (c *Controller) Upload(ctx SpringWeb.WebContext) { // …… if !c.File.ExistsObject(out) { err = c.File.PutObject(out, w, f.Size) if err != nil { ctx.JSON( http.StatusInternalServerError, gin.H{ "code": 1, "message": "保存失败", "error": err.Error(), }) return } } else { ctx.JSON( http.StatusBadRequest, gin.H{ "code": 1, "message": "该文件已存在", }) return } // …… } 

    重新运行 go run main.go 并测试,功能正常

    $ rm temp/README.md $ curl -F "file=@./README.md" http://127.0.0.1:8080/upload {"code":0,"data":{"url":"temp/README.md"},"message":"OK"} $ curl -F "file=@./README.md" http://127.0.0.1:8080/upload {"code":1,"message":"该文件已存在"} 

    未拆分 service 的完整代码在 post-3 拆分了 service 的完整代码在 post-4

    六、spring-boot 加载配置注入对象

    我们启动服务时有传入一个 config/ 这个实际上是配置文件搜索路径。

    SpringBoot.RunApplication("config/") 

    spring-boot 支持不少格式的配置和命名方式,这些都不介绍了。

    只介绍一下怎么使用这些文件

    config/application.toml

    [spring.application] name = "demo-config" [file] dir = "temp" 

    controllers/upload/upload.gocontroller 使用配置替换硬编码的保存文件夹路径, value:"${file.dir}" 对应配置文件的路径绑定。

    type Controller struct { File *file.Service `autowire:""` Dir string `value:"${file.dir}"` } func (c *Controller) Upload(ctx SpringWeb.WebContext) { // …… // 替换为注入的配置 out := path.Join(c.Dir, f.Filename) // …… } 

    当然 spring-boot 也支持对结构体实例化配置数据还有默认值。

     type Config struct { Dir string `value:"${file.dir=tmp}"` } type Controller struct { File *file.Service `autowire:""` Config Config } func (c *Controller) Upload(ctx SpringWeb.WebContext) { // …… // 替换为注入的配置 out := path.Join(c.Config.Dir, f.Filename) // …… } 

    该章完整代码在 post-5

    七、通过接口类型解除 controller 对 service 的依赖

    以上代码已经很完整了,但是 controller 直接导入 service 造成对逻辑的直接依赖,这样会照成很高的代码耦合,而且导入 service 包也比较麻烦。

    这里我们可以使用 interface 来做到解除依赖,这样不仅解决的导入的问题也能够快速的替换 serivce 的实现。

    types/services.go

    之前抽取的抽象方法派上用处了。

    package types import ( "io" ) type FileProvider interface { PutObject(name string, r io.Reader, size int64) error ExistsObject(name string) bool } 

    controllers/upload/upload.go

    然后把 *file.Service 类型替换为 types.FileProvider 即可,spring-boot 会自动匹配接口对应的实例。

    type Controller struct { File types.FileProvider `autowire:""` Dir string `value:"${file.dir}"` } 

    该章完整代码在 post-6

    八、通过 Condition 来限制 Bean 的注册来做到不同的 service 切换

    上面我们说到用 interface 结构后是可以替换不同的逻辑实现的,这里我们就来一个对象存储和本地文件存储能力的更换,可以通过配置文件替换文件操作逻辑实现。

    这里使用 minio 作为远端对象存储服务。

    docker-compose 这里我们用 docker 快速创建一个本地的 minio 服务。

    version: "3" services: minio: image: "minio/minio:RELEASE.2019-10-12T01-39-57Z" volumes: - "./minio:/data" ports: - "9000:9000" environment: MINIO_ACCESS_KEY: minio MINIO_SECRET_KEY: minio123 command: - "server" - "/data" 

    config/application.toml 添加 minio 配置

    [minio] enable = true host = "127.0.0.1" port = 9000 access = "minio" secret = "minio123" secure = false bucket = "demo" 

    modules/minio/minio.go 单独的用 module 来做 minio 的客户端初始化。

    package minio type MinioConfig struct { Enable bool `value:"${minio.enable:=true}"` // 是否启用 HTTP Host string `value:"${minio.host:=127.0.0.1}"` // HTTP host Port int `value:"${minio.port:=9000}"` // HTTP 端口 Access string `value:"${minio.access:=}"` // Access Secret string `value:"${minio.secret:=}"` // Secret Secure bool `value:"${minio.secure:=true}"` // Secure Bucket string `value:"${minio.bucket:=}"` } func init() { SpringBoot.RegisterNameBeanFn( // 给这个实例起个名字 "minioClient", // 自动注入 minio 配置 func(config MinioConfig) *minio.Client { // …… }, // 前面的 0 代表参数位置,后面则是配置前缀 "0:${}", // ConditionOnPropertyValue 会检查配置文件来确认是否注册 ).ConditionOnPropertyValue( "minio.enable", true, ) } 

    记得收集导入到 main.go

    services/file/file.go

    本地存储 service 需要在没有注册 minioClient 的情况才注册。

    func init() { SpringBoot.RegisterBean(new(Service)).ConditionOnMissingBean("minioClient") } 

    services/minio/minio.go

    package minio type Service struct { // 自动注入 minio client Client *minio.Client `autowire:""` Bucket string `value:"${minio.bucket:=}"` } func init() { // 在已注册了 minioClient 才注册 SpringBoot.RegisterBean(new(Service)).ConditionOnBean("minioClient") } func (s *Service) PutObject(name string, r io.Reader, size int64) error { // …… } func (s *Service) ExistsObject(name string) bool { // …… } 

    然后启动 docker-compose up -d minio 启动 minio 服务。

    修改 config/application.tomlminio.enable 可以切换存储能力。

    本章完整代码在 post-7

    求职

    我是 zeromake 现在我离职中。

    希望能够找到一个合适的新工作。

    目标:Golang 开发,厦门优先

    我的在线简历: zeromake 的简历

    顺便推广一下: docker-debug

    版权信息

    本文作者: zeromake

    原文链接: [https://blog.zeromake.com/pages/go-spring-learn]https://blog.zeromake.com/pages/go-spring-learn

    最后更新: 2019-12-22 17:20:57+08:00

    版权声明: 本博客所有文章除特别声明外, 均采用 CC BY-NC-SA 4.0 许可协议. 转载请注明出处!

    35 replies    2019-12-31 09:27:31 +08:00
    waising
        1
    waising  
       Dec 28, 2019
    刚要从 java 到 go。。。又来 spring。。
    zeromake
        2
    zeromake  
    OP
       Dec 28, 2019
    @waising #1 那你可以手动的做实例化并且在个个组件间使用,不过也可以用一个更简单的注入工具 https://github.com/uber-go/dig 一共就三个方法。
    container := dig.New()
    zjsxwc
        3
    zjsxwc  
       Dec 28, 2019
    把 go 写成 java。。
    我写 go 就简单粗暴了,直接撸 init 函数把依赖注入到全局定义的 var sync.Map 变量里
    zeromake
        4
    zeromake  
    OP
       Dec 28, 2019
    @zjsxwc #3 我之前也这么干,然后找到了 `dig` 和 `go-spring`
    ArJun
        5
    ArJun  
       Dec 28, 2019   1
    spring 那套太重了不喜欢
    CEBBCAT
        6
    CEBBCAT  
       Dec 28, 2019 via Android
    来踩一踩,另外:
    异端!!!
    tairan2006
        7
    tairan2006  
       Dec 28, 2019
    你用 Spring 干啥,如果只是为了 DI,Google 有官方的 wire
    zeromake
        8
    zeromake  
    OP
       Dec 28, 2019
    @tairan2006 #7
    我去看了一下 wire,感觉有点难用还不如用 dig 的感觉
    mailmac
        9
    mailmac  
       Dec 28, 2019 via Android
    楼主去了两家听说技术要求比较高的公司。厦门区域的话,几个大厂投一下看看。小公司薪资都一般
    zeromake
        10
    zeromake  
    OP
       Dec 28, 2019
    @mailmac #9
    投过一个亿联,也是薪资一般
    gramyang
        11
    gramyang  
       Dec 28, 2019 via Android   1
    。。。感觉更嗦了
    zeromake
        12
    zeromake  
    OP
       Dec 28, 2019
    @mailmac #9
    还有就是两家里的后面那家稿定现在的 node 后端有点炸,技术要求并不高,面试倒是挺难的,我撸了一整年的 crud。
    我觉得要去稿定要么去前端编辑器那边,要么去稿定的 ai 部门,网站后端部门今年没有一个人晋升职级了,前端编辑器好几个都升了。
    zeromake
        13
    zeromake  
    OP
       Dec 28, 2019
    @gramyang #11

    还好吧至少比手动做依赖注入方便的多,也比 dig 的少了很多代码,就是 go-spring 的 SpringBoot 强制管理了配置和服务的启动关闭有点入侵的感觉。
    PiersSoCool
        14
    PiersSoCool  
       Dec 28, 2019   1
    我想用 Spring 为啥不用 Java。。。
    zeromake
        15
    zeromake  
    OP
       Dec 28, 2019
    @PiersSoCool #14
    你搞反了我是想在 go 里用 spring 的依赖注入能力而已,原来我的 web 项目的 controller 和 service 之间的依赖关系和实例化都很麻烦,用了 dig 稍微好点,但是实例化后的对象的属性设置还是要手动做,用了 go-spring 直接就告别了对实例化后的对象做属性设置。
    zunceng
        16
    zunceng  
       Dec 28, 2019
    第一反应是 spring 追杀我到 golang 了

    现在微服务的玩法还没碰到过一个微服务复杂到要用依赖注入来处理 想用依赖注入可以用 https://github.com/uber-go/fx
    zeromake
        17
    zeromake  
    OP
       Dec 28, 2019
    @zunceng #16
    在用 go-spring 之前就是用的 fx 的底层 dig 做注入啦,不过因为实例化的逻辑还是得自己写就换 go-spring 了。
    paragon
        18
    paragon  
       Dec 28, 2019
    看见 go 都开始 spring 我就放心了~
    tairan2006
        19
    tairan2006  
       Dec 28, 2019
    @zeromake 反射对性能有影响啊
    zeromake
        20
    zeromake  
    OP
       Dec 28, 2019
    @tairan2006 #19 依赖注入只是启动时的依赖获取吧。
    janxin
        21
    janxin  
       Dec 28, 2019   1
    你为什么不直接用 Spring,是什么原因让你这么自虐...
    zeromake
        22
    zeromake  
    OP
       Dec 28, 2019
    @janxin #21
    因为我只是拿来做 go 的依赖注入啊,又不是 spring 全套。
    petelin
        23
    petelin  
       Dec 28, 2019 via iPhone
    依赖为什么要诸如 每一个都放到自己的包里 然后搞个单例 传来传去不香吗
    manami
        24
    manami  
       Dec 28, 2019 via Android   1
    穿着棉袄去洗澡
    zeromake
        25
    zeromake  
    OP
       Dec 28, 2019
    @petelin #23 不香……很麻烦
    lenqu
        26
    lenqu  
       Dec 28, 2019
    一次只干一件事
    dodo2012
        27
    dodo2012  
       Dec 28, 2019   1
    算了,我选择自己写,或者用 gin,这搞的太复杂了又
    TypeErrorNone
        28
    TypeErrorNone  
       Dec 29, 2019
    真够费劲的,就你们这些人闲的没事,搞这些花里胡哨的东西,go 追求的就是简洁,别整天生搬硬套
    cnbattle
        29
    cnbattle  
       Dec 29, 2019 via Android
    go 不需要依赖注入
    slyang5
        30
    slyang5  
       Dec 29, 2019
    那为什么不用 JAVA 啊 。。。。。。
    zeromake
        31
    zeromake  
    OP
       Dec 29, 2019
    @slyang5 #30
    @TypeErrorNone #28
    @cnbattle #29

    真的是够了,我这边是已经有了项目而且也都是 go 写的,现在只是使用 go-spring 做 service 和 controller 的依赖注入。

    使用 go-spring 可以简化我的 service, controller 实例化。
    cs419
        32
    cs419  
       Dec 29, 2019
    很好奇用了 spring 这名字侵权不
    https://github.com/go-spring 一堆的 spring
    go-spring-boot-demo
    go-spring-web
    go-spring-boot-starter
    go-spring-website
    go-spring-rpc
    go-spring-parent
    go-spring-redis
    go-spring-orm
    Kaiv2
        33
    Kaiv2  
       Dec 30, 2019 via Android
    感觉很好啊,java 程序员转 go 更容易上手了
    Edward4074
        34
    Edward4074  
       Dec 30, 2019
    厦门能有 20K 的很少吧
    brucewuio
        35
    brucewuio  
       Dec 31, 2019
    写 Go 我从来不用第三方的库 直接在 SDK 上撸 酸爽
    About     Help/a>     Advertise     Blog     API     FAQ     Solana     985 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 139ms UTC 23:04 PVG 07:04 LAX 16:04 JFK 19:04
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86