├── .gitignore ├── Dockerfile ├── README.md ├── comment ├── .gitignore ├── Dockerfile ├── Makefile ├── center │ └── consul.go ├── cmd │ └── main.go ├── config │ ├── api.go │ ├── config.go │ └── manager.go ├── go.mod ├── go.sum ├── internal │ ├── server │ │ ├── comment.go │ │ ├── health.go │ │ └── manager.go │ ├── service │ │ ├── comment.pb.go │ │ ├── comment_grpc.pb.go │ │ └── proto │ │ │ ├── .gitkeep │ │ │ └── comment.proto │ └── store │ │ ├── cache │ │ ├── config.go │ │ ├── manager.go │ │ └── token.go │ │ └── local │ │ ├── comment.go │ │ ├── comment_test.go │ │ ├── config.go │ │ └── manager.go └── pkg │ └── jwt.go ├── example ├── README.md ├── gateway │ ├── Dockerfile │ ├── Makefile │ ├── api │ │ ├── config.go │ │ ├── hello.go │ │ ├── manager.go │ │ └── user.go │ ├── center │ │ └── consul.go │ ├── config │ │ ├── api.go │ │ ├── config.go │ │ └── manager.go │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── service │ │ ├── hello.pb.go │ │ ├── hello_grpc.pb.go │ │ ├── proto │ │ ├── hello.proto │ │ └── user.proto │ │ ├── user.pb.go │ │ └── user_grpc.pb.go ├── hello │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── center │ │ └── consul.go │ ├── cmd │ │ └── hello │ │ │ └── main.go │ ├── config │ │ ├── api.go │ │ ├── config.go │ │ └── manager.go │ ├── go.mod │ ├── go.sum │ ├── internal │ │ ├── server │ │ │ ├── health.go │ │ │ ├── hello.go │ │ │ └── manager.go │ │ ├── service │ │ │ ├── hello.pb.go │ │ │ ├── hello_grpc.pb.go │ │ │ └── proto │ │ │ │ └── hello.proto │ │ └── store │ │ │ ├── cache │ │ │ ├── config.go │ │ │ └── manager.go │ │ │ └── local │ │ │ ├── config.go │ │ │ └── manager.go │ └── pkg │ │ └── token │ │ └── jwt.go └── user │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── center │ └── consul.go │ ├── cmd │ └── user │ │ └── main.go │ ├── config │ ├── api.go │ ├── config.go │ └── manager.go │ ├── go.mod │ ├── go.sum │ ├── internal │ ├── server │ │ ├── health.go │ │ ├── manager.go │ │ └── user.go │ ├── service │ │ ├── proto │ │ │ └── user.proto │ │ ├── user.pb.go │ │ └── user_grpc.pb.go │ └── store │ │ ├── cache │ │ ├── config.go │ │ ├── manager.go │ │ └── token.go │ │ └── local │ │ ├── config.go │ │ ├── manager.go │ │ └── user.go │ └── pkg │ ├── ecrypto │ └── md5.go │ └── token │ └── jwt.go ├── favorite ├── .gitignore ├── Dockerfile ├── Makefile ├── center │ └── consul.go ├── cmd │ └── main.go ├── config │ ├── api.go │ ├── config.go │ └── manager.go ├── go.mod ├── go.sum ├── internal │ ├── server │ │ ├── favorite.go │ │ ├── health.go │ │ └── manager.go │ ├── service │ │ ├── favoriate.pb.go │ │ ├── favoriate_grpc.pb.go │ │ └── proto │ │ │ └── favoriate.proto │ └── store │ │ ├── cache │ │ ├── config.go │ │ ├── manager.go │ │ └── token.go │ │ └── local │ │ ├── config.go │ │ ├── favorite.go │ │ └── manager.go └── pkg │ └── token │ └── jwt.go ├── feed ├── .gitignore ├── Dockerfile ├── center │ └── consul.go ├── cmd │ └── main.go ├── config │ ├── api.go │ ├── config.go │ └── manager.go ├── go.mod ├── go.sum ├── internal │ ├── server │ │ ├── feed.go │ │ ├── feed_test.go │ │ ├── health.go │ │ └── manager.go │ ├── service │ │ ├── Makefile │ │ ├── feed.pb.go │ │ ├── feed_grpc.pb.go │ │ └── proto │ │ │ └── feed.proto │ └── store │ │ ├── cache │ │ ├── config.go │ │ └── manager.go │ │ ├── local │ │ ├── config.go │ │ ├── create │ │ │ └── main.go │ │ ├── feed.go │ │ └── manager.go │ │ └── obs │ │ ├── config.go │ │ ├── feed.go │ │ └── manager.go ├── main.go └── pkg │ └── util.go ├── gateway ├── Dockerfile ├── Makefile ├── api │ ├── benchmark_comment_test.go │ ├── comment.go │ ├── comment_test.go │ ├── config.go │ ├── favorite.go │ ├── feed.go │ ├── manager.go │ ├── publish.go │ ├── user.go │ └── user_test.go ├── center │ └── consul.go ├── config │ ├── api.go │ ├── config.go │ └── manager.go ├── go.mod ├── go.sum ├── main.go └── service │ ├── comment.pb.go │ ├── comment_grpc.pb.go │ ├── favoriate.pb.go │ ├── favoriate_grpc.pb.go │ ├── feed.pb.go │ ├── feed_grpc.pb.go │ ├── proto │ ├── comment.proto │ ├── favoriate.proto │ ├── feed.proto │ ├── publish.proto │ └── userProto.proto │ ├── publish.pb.go │ ├── publish_grpc.pb.go │ ├── userProto.pb.go │ └── userProto_grpc.pb.go ├── go.mod ├── go.sum ├── publish ├── .gitignore ├── Dockerfile ├── center │ └── consul.go ├── cmd │ └── main.go ├── config │ ├── api.go │ ├── config.go │ └── manager.go ├── go.mod ├── go.sum ├── internal │ ├── server │ │ ├── health.go │ │ ├── manager.go │ │ ├── publish.go │ │ ├── publish_test.go │ │ ├── test1.jpg │ │ └── test1.mp4 │ ├── service │ │ ├── Makefile │ │ ├── proto │ │ │ └── publish.proto │ │ ├── publish.pb.go │ │ └── publish_grpc.pb.go │ └── store │ │ ├── cache │ │ ├── config.go │ │ └── manager.go │ │ ├── local │ │ ├── config.go │ │ ├── create │ │ │ └── main.go │ │ ├── manager.go │ │ ├── publish.go │ │ └── publish_test.go │ │ └── obs │ │ ├── api.go │ │ ├── api_test.go │ │ ├── config.go │ │ ├── manager.go │ │ └── test1.mp4 ├── main.go └── pkg │ ├── jwt │ ├── jwt.go │ └── jwt_test.go │ └── util │ ├── util.go │ └── wget-log └── user ├── .gitignore ├── Dockerfile ├── Makefile ├── center └── consul.go ├── cmd ├── .gitkeep └── main.go ├── config ├── api.go ├── config.go └── manager.go ├── go.mod ├── go.sum ├── internal ├── server │ ├── health.go │ ├── manager.go │ ├── user.go │ └── user_test.go ├── service │ ├── proto │ │ ├── .gitkeep │ │ └── userProto.proto │ ├── userProto.pb.go │ └── userProto_grpc.pb.go └── store │ ├── cache │ ├── config.go │ ├── manager.go │ └── token.go │ └── local │ ├── config.go │ ├── manager.go │ ├── user.go │ └── user_test.go └── pkg ├── .gitkeep ├── encoder.go ├── encoder_test.go ├── jwt.go ├── snowflake.go └── snowflake_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # use for java 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # use for golang 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Binaries for programs and plugins 28 | *.exe 29 | *.exe~ 30 | *.dll 31 | *.so 32 | *.dylib 33 | 34 | # Test binary, built with `go test -c` 35 | *.test 36 | 37 | # Output of the go coverage tool, specifically when used with LiteIDE 38 | *.out 39 | 40 | # Dependency directories (remove the comment below to include it) 41 | # vendor/ 42 | golang/main.go 43 | 44 | .vscode 45 | .idea 46 | # use for javascript 47 | # compiled output 48 | /dist 49 | /tmp 50 | /out-tsc 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | 64 | # nyc test coverage 65 | .nyc_output 66 | 67 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 68 | .grunt 69 | 70 | # Bower dependency directory (https://bower.io/) 71 | bower_components 72 | 73 | # node-waf configuration 74 | .lock-wscript 75 | 76 | # IDEs and editors 77 | .idea 78 | .project 79 | .classpath 80 | .c9/ 81 | *.launch 82 | .settings/ 83 | *.sublime-workspace 84 | 85 | # IDE - VSCode 86 | .vscode/* 87 | !.vscode/settings.json 88 | !.vscode/tasks.json 89 | !.vscode/launch.json 90 | !.vscode/extensions.json 91 | jsconfig.json 92 | Top Interview Questions/Solution.ts 93 | Top Interview Questions/Solution.java 94 | # misc 95 | .sass-cache 96 | connect.lock 97 | typings 98 | 99 | # Logs 100 | logs 101 | *.log 102 | npm-debug.log* 103 | yarn-debug.log* 104 | yarn-error.log* 105 | 106 | 107 | # Dependency directories 108 | node_modules/ 109 | jspm_packages/ 110 | 111 | # Optional npm cache directory 112 | .npm 113 | 114 | # Optional eslint cache 115 | .eslintcache 116 | 117 | # Optional REPL history 118 | .node_repl_history 119 | 120 | # Output of 'npm pack' 121 | *.tgz 122 | 123 | # Yarn Integrity file 124 | .yarn-integrity 125 | 126 | # dotenv environment variables file 127 | .env 128 | 129 | # next.js build output 130 | .next 131 | 132 | # Lerna 133 | lerna-debug.log 134 | 135 | # System Files 136 | .DS_Store 137 | Thumbs.db 138 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/Dockerfile -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tiktok 2 | 3 | 基于 grpc RPC微服务 + gin HTTP服务完成的第5届字节跳动青训营-极简抖音后端项目 4 | 5 | ## 项目结构 6 | 7 | ```FILE 8 | grpc模块 9 | . 10 | ├── center ------------> 注册中心相关 11 | ├── cmd ---------------> grpc服务启动main文件 12 | ├── config ------------> 读取配置相关 13 | ├── internal ----------> 具体业务逻辑存放目录 14 | │   ├── server --------> grpc服务的具体实现 15 | │ └── service -------> 存放grpc生成的文件以及proto文件 16 | │   ├──proto ------> 存放proto文件 17 | │ └── store ---------> 数据库以及缓存相关 18 | │   ├── local -----> 存放model相关的操作 19 | │ └── cache -----> 存放缓存相关的操作 20 | ├── pkg ---------------> 项目所需工具类 21 | ├── .gitignore 22 | ├── Dockerfile 23 | ├── Makefile 24 | 25 | gateway 26 | . 27 | ├── api ---------------> gin相关的接口实现 28 | ├── center ------------> 注册中心相关 29 | ├── config ------------> 读取配置相关 30 | ├── service -----------> 存放grpc生成的文件以及proto文件 31 | │   ├──proto ----------> 存放proto文件 32 | ├── main.go -----------> gateway模块启动main文件 33 | ├── .gitignore 34 | ├── Dockerfile 35 | ├── Makefile 36 | ``` 37 | 38 | - 采用RPC框架grpc,基于 **RPC服务** + **Gin 提供 HTTP服务** 39 | - 基于《[接口文档在线分享- Apifox](https://www.apifox.cn/apidoc/shared-8cc50618-0da6-4d5e-a398-76f3b8f766c5/)》提供的接口进行开发,使用《[极简抖音App使用说明 - 青训营版](https://bytedance.feishu.cn/docs/doccnM9KkBAdyDhg8qaeGlIz7S7) 》提供的APK进行Demo测试, **功能完整实现** ,前端接口匹配良好 40 | - 使用 **JWT** 进行用户token的校验 41 | - 使用 **Consul** 进行服务发现和服务注册,以及配置文件的KV读取 42 | - 使用 **Minio** 实现视频文件和图片的对象存储 43 | - 使用 **Gorm** 对 MySQL 进行 ORM 操作 44 | - 使用**redis**作为Nosql缓存 45 | - [ ] 使用OpenTelemetry实现链路跟踪 46 | - [ ] 数据库表建立了索引和外检约束,对于具有关联性的操作一旦出错立刻回滚,保证数据一致性和安全性 47 | 48 | ![image](https://user-images.githubusercontent.com/65102150/227455443-f986e5a0-c7cc-4a33-bdc0-c022c0dfa8ac.png) 49 | 50 | 51 | 模块解析: 52 | 53 | > consul 54 | > 55 | 56 | consul是一种分布式服务工具,提供: 57 | 58 | - 服务发现 59 | - 健康检查 60 | - K-V存储 61 | - 多数据中心 62 | 63 | 特点: 64 | 65 | - consul常常以集群的方式部署,采用主从模式,有server和client两种节点。server节点保存数据,client节点负责健康检查和转发请求 66 | - 使用http接口进行服务发现,可以通过http请求来注册服务和查询服务,也可以通过http api来获取服务的健康状态和元数据 67 | - 使用raft算法保证服务的一致性,Server节点中有一个Leader节点和多个Follower及节点,Leader节点会将数据同步到Follower节点,当Leader节点挂掉时会启动选举机制产生一个新的Leader 68 | - 支持多数据中心,不同数据中心之间通过WAN GOSSIP进行报文交互,可以实现跨区域的服务发现和配置共享 69 | - 提供HTTP接口、DNS接口和SDK接口,可以方便地注册服务、发现服务、获取服务的健康状态和元数据 70 | 71 | ## 模块分析 72 | 73 | gateway模块: 74 | 75 | - api:存放gin的接口实现 76 | - Route函数注册路由 77 | - 对微服务进行负载均衡 78 | - 构建request并发送给微服务,等待微服务的response 79 | - 构建JSON(status code),返回给客户端 80 | - center:存放注册中心相关 81 | - init函数:初始化一个consul客户端,连接到指定的地址 82 | - GetValue函数:从Consul的KV存储中读取指定的键值对 83 | - Register函数:向Consul注册一个服务,包括服务的名称、ID、地址、端口等信息 84 | - Resolver函数:从Consul中解析一个服务的地址,并使用gRPC进行通信。这个函数使用了一个第三方库grpc-consul-resolver来实现gRPC与Consul的集成。这个函数还指定了一些参数,如等待时间、筛选条件和负载均衡策略 85 | - config:存放读取配置相关 86 | - service:存放grpc生成的文件和protp文件 87 | 88 | 功能模块(publish模块): 89 | 90 | - center:包含了一个用于连接服务中心的客户端 91 | - cmd:包含了一个用于启动服务的命令行工具 92 | - config:包含了一些用于管理服务配置的类型和函数 93 | - internal:包含了一些内部使用的包 94 | - pkg:包含了一些公共使用的包 95 | 96 | ## Consul与ETCD的差异 97 | 98 | - consul使用gossip协议来管理节点成员关系、失败检测、事件广播等 99 | - consul提供了更多功能,比如健康检查、服务注册、DNS接口,ACL等。而ETCD更专注于核心的键值存储功能 100 | 101 | ## 链路跟踪 102 | 103 | 链路跟踪是一种用于分析和监控应用程序的方法,尤其是使用微服务架构构建的应用程序。它可以通过记录和关联每个请求在不同服务之间的路径,来帮助定位性能问题和故障 104 | 105 | OpenTelemetry是一个开源项目,它统一了追踪、指标和日志的规范,定义了它们之间的联系。它可以让开发者无需改动或者很小的改动就可以接入不同的链路跟踪系统 106 | 107 | -------------------------------------------------------------------------------- /comment/.gitignore: -------------------------------------------------------------------------------- 1 | # use for java 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # use for golang 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Binaries for programs and plugins 28 | *.exe 29 | *.exe~ 30 | *.dll 31 | *.so 32 | *.dylib 33 | 34 | # Test binary, built with `go test -c` 35 | *.test 36 | 37 | # Output of the go coverage tool, specifically when used with LiteIDE 38 | *.out 39 | 40 | # Dependency directories (remove the comment below to include it) 41 | # vendor/ 42 | golang/main.go 43 | 44 | .vscode 45 | .idea 46 | # use for javascript 47 | # compiled output 48 | /dist 49 | /tmp 50 | /out-tsc 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | 64 | # nyc test coverage 65 | .nyc_output 66 | 67 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 68 | .grunt 69 | 70 | # Bower dependency directory (https://bower.io/) 71 | bower_components 72 | 73 | # node-waf configuration 74 | .lock-wscript 75 | 76 | # IDEs and editors 77 | .idea 78 | .project 79 | .classpath 80 | .c9/ 81 | *.launch 82 | .settings/ 83 | *.sublime-workspace 84 | 85 | # IDE - VSCode 86 | .vscode/* 87 | !.vscode/settings.json 88 | !.vscode/tasks.json 89 | !.vscode/launch.json 90 | !.vscode/extensions.json 91 | jsconfig.json 92 | Top Interview Questions/Solution.ts 93 | Top Interview Questions/Solution.java 94 | # misc 95 | .sass-cache 96 | connect.lock 97 | typings 98 | 99 | # Logs 100 | logs 101 | *.log 102 | npm-debug.log* 103 | yarn-debug.log* 104 | yarn-error.log* 105 | 106 | 107 | # Dependency directories 108 | node_modules/ 109 | jspm_packages/ 110 | 111 | # Optional npm cache directory 112 | .npm 113 | 114 | # Optional eslint cache 115 | .eslintcache 116 | 117 | # Optional REPL history 118 | .node_repl_history 119 | 120 | # Output of 'npm pack' 121 | *.tgz 122 | 123 | # Yarn Integrity file 124 | .yarn-integrity 125 | 126 | # dotenv environment variables file 127 | .env 128 | 129 | # next.js build output 130 | .next 131 | 132 | # Lerna 133 | lerna-debug.log 134 | 135 | # System Files 136 | .DS_Store 137 | Thumbs.db 138 | -------------------------------------------------------------------------------- /comment/Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/comment/Dockerfile -------------------------------------------------------------------------------- /comment/Makefile: -------------------------------------------------------------------------------- 1 | protoc: 2 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 3 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 4 | protoc --go_out=./ --go-grpc_out=./ ./internal/service/proto/*.proto -------------------------------------------------------------------------------- /comment/center/consul.go: -------------------------------------------------------------------------------- 1 | package center 2 | 3 | import ( 4 | "log" 5 | 6 | consul "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | var ( 10 | addr = "47.108.66.104:8500" 11 | client *consul.Client 12 | ) 13 | 14 | func init() { 15 | var err error 16 | config := consul.DefaultConfig() 17 | config.Address = addr 18 | client, err = consul.NewClient(config) 19 | if err != nil { 20 | log.Fatalf("failed to init consul: %v", err) 21 | } 22 | } 23 | 24 | func GetValue(key string) ([]byte, error) { 25 | res, _, err := client.KV().Get(key, nil) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return res.Value, nil 30 | } 31 | 32 | func Register(reg consul.AgentServiceRegistration) error { 33 | agent := client.Agent() 34 | 35 | return agent.ServiceRegister(®) 36 | } 37 | -------------------------------------------------------------------------------- /comment/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/JirafaYe/comment/center" 7 | "github.com/JirafaYe/comment/internal/server" 8 | "github.com/JirafaYe/comment/internal/service" 9 | consul "github.com/hashicorp/consul/api" 10 | "google.golang.org/grpc" 11 | "log" 12 | "net" 13 | "net/http" 14 | _ "net/http/pprof" 15 | ) 16 | 17 | var ( 18 | addr = flag.String("addr", "127.0.0.1", "The server address") 19 | port = flag.Int("port", 8082, "The server port") 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | 25 | //runtime.SetBlockProfileRate(1) 26 | go func() { 27 | http.ListenAndServe(":10001", nil) 28 | }() 29 | 30 | // 服务注册 31 | err := center.Register(consul.AgentServiceRegistration{ 32 | ID: "comment-1", // 服务节点的名称 33 | Name: "comment", // 服务名称 34 | Port: *port, // 服务端口 35 | Address: *addr, // 服务 IP 36 | //Check: &consul.AgentServiceCheck{ // 健康检查 37 | // Interval: "5s", // 健康检查间隔 38 | // GRPC: fmt.Sprintf("%v:%v/%v", *addr, *port, "health"), 39 | // DeregisterCriticalServiceAfter: "10s", // 注销时间,相当于过期时间 40 | //}, 41 | }) 42 | if err != nil { 43 | log.Fatalf("failed to register service: %v", err) 44 | } 45 | 46 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 47 | if err != nil { 48 | log.Fatalf("failed to listen: %v", err) 49 | } 50 | s := grpc.NewServer() 51 | service.RegisterCommentServer(s, &server.CommentServer{}) 52 | // 健康检查 53 | //grpc_health_v1.RegisterHealthServer(s, &server.HealthImpl{}) 54 | log.Printf("server listening at %v", lis.Addr()) 55 | if err := s.Serve(lis); err != nil { 56 | log.Fatalf("failed to serve: %v", err) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /comment/config/api.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var handler *Manager 4 | 5 | func init() { 6 | handler = New() 7 | } 8 | 9 | func ReadConfig(c Config) error { 10 | return handler.ReadConfig(c) 11 | } 12 | -------------------------------------------------------------------------------- /comment/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config interface { 4 | Key() string 5 | } 6 | -------------------------------------------------------------------------------- /comment/config/manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/JirafaYe/comment/center" 6 | "log" 7 | ) 8 | 9 | type Manager struct { 10 | } 11 | 12 | func New() *Manager { 13 | return &Manager{} 14 | } 15 | 16 | func (m *Manager) ReadConfig(c Config) error { 17 | log.Println("[CONFIG] READING", c.Key()) 18 | config, err := center.GetValue(c.Key()) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = json.Unmarshal(config, &c) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /comment/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/comment 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/go-redis/redis/v8 v8.11.5 8 | github.com/hashicorp/consul/api v1.18.0 9 | google.golang.org/grpc v1.52.0 10 | google.golang.org/protobuf v1.28.1 11 | gorm.io/driver/mysql v1.4.5 12 | gorm.io/gorm v1.24.3 13 | ) 14 | 15 | require ( 16 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 17 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 18 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 19 | github.com/fatih/color v1.9.0 // indirect 20 | github.com/go-sql-driver/mysql v1.7.0 // indirect 21 | github.com/golang/protobuf v1.5.2 // indirect 22 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 23 | github.com/hashicorp/go-hclog v0.12.0 // indirect 24 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 25 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 26 | github.com/hashicorp/golang-lru v0.5.4 // indirect 27 | github.com/hashicorp/serf v0.10.1 // indirect 28 | github.com/jinzhu/inflection v1.0.0 // indirect 29 | github.com/jinzhu/now v1.1.5 // indirect 30 | github.com/mattn/go-colorable v0.1.6 // indirect 31 | github.com/mattn/go-isatty v0.0.12 // indirect 32 | github.com/mitchellh/go-homedir v1.1.0 // indirect 33 | github.com/mitchellh/mapstructure v1.4.1 // indirect 34 | golang.org/x/net v0.4.0 // indirect 35 | golang.org/x/sys v0.3.0 // indirect 36 | golang.org/x/text v0.5.0 // indirect 37 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /comment/internal/server/comment.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/JirafaYe/comment/internal/service" 8 | "github.com/JirafaYe/comment/internal/store/local" 9 | "github.com/JirafaYe/comment/pkg" 10 | "log" 11 | "sync" 12 | ) 13 | 14 | type CommentServer struct { 15 | service.UnimplementedCommentServer 16 | } 17 | 18 | func (c *CommentServer) ListComments(ctx context.Context, req *service.ListRequest) (*service.ListCommentsResponse, error) { 19 | resp, paresErr := pkg.ParseToken(req.Token) 20 | if paresErr != nil { 21 | log.Println("解析token失败", paresErr) 22 | return nil, errors.New("解析token失败") 23 | } else if !m.cacher.IsUserTokenExist(resp.Username) { 24 | log.Println("用户token不存在") 25 | return nil, errors.New("token登录验证失败") 26 | } 27 | 28 | commentList, err := m.localer.SelectCommentListByVideoId(req.VideoId) 29 | if err != nil { 30 | log.Println("获取评论列表失败", err) 31 | return nil, errors.New("获取评论列表失败") 32 | } 33 | 34 | var ids []int64 35 | list := make([]*service.CommentBody, len(commentList)) 36 | userMap := make(map[int64]*service.CommentUser) 37 | id := make(map[int64]interface{}) 38 | 39 | for _, comment := range commentList { 40 | id[comment.AuthorId] = nil 41 | } 42 | 43 | for k, _ := range id { 44 | ids = append(ids, k) 45 | } 46 | 47 | if len(ids) != 0 { 48 | msg, err := m.localer.GetUserMsg(ids) 49 | if err != nil { 50 | log.Println("获取用户信息失败") 51 | return nil, errors.New("获取用户信息失败") 52 | } 53 | 54 | for _, user := range msg { 55 | //log.Println(user) 56 | userMap[user.Id] = &user 57 | } 58 | } 59 | 60 | for i, comment := range commentList { 61 | //log.Println(userMap[comment.AuthorId], comment.AuthorId) 62 | list[i] = ConvertCommentBody(comment, userMap[comment.AuthorId]) 63 | } 64 | 65 | return &service.ListCommentsResponse{ 66 | StatusCode: 0, 67 | StatusMsg: "success", 68 | CommentList: list, 69 | }, nil 70 | } 71 | 72 | func (c *CommentServer) OperateComment(ctx context.Context, req *service.CommentRequest) (*service.CommentOperationResponse, error) { 73 | comment := ConvertCommentRequest(req) 74 | 75 | resp, paresErr := pkg.ParseToken(req.Token) 76 | if paresErr != nil { 77 | log.Println("解析token失败", paresErr) 78 | return nil, errors.New("解析token失败") 79 | } else if !m.cacher.IsUserTokenExist(resp.Username) { 80 | log.Println("用户token不存在") 81 | return nil, errors.New("token登录验证失败") 82 | } 83 | 84 | user := &service.CommentUser{ 85 | Id: resp.Id, 86 | Name: resp.Username, 87 | } 88 | comment.AuthorId = resp.Id 89 | 90 | var err error 91 | var wg sync.WaitGroup 92 | wg.Add(2) 93 | 94 | if req.ActionType == 1 { 95 | ok, err := m.localer.SelectVideoById(comment.VideoId) 96 | if !ok || err != nil { 97 | log.Println("校验视频合法性失败") 98 | return nil, errors.New("校验视频合法性失败") 99 | } 100 | err = m.localer.InsertComment(&comment) 101 | if err != nil { 102 | return nil, err 103 | } 104 | } else if req.ActionType == 2 { 105 | err = m.localer.DeleteComment(comment) 106 | if err != nil { 107 | log.Print("删除评论失败: ", err) 108 | return nil, err 109 | } 110 | } 111 | 112 | return &service.CommentOperationResponse{ 113 | StatusCode: 0, 114 | StatusMsg: "success", 115 | Comment: ConvertCommentBody(comment, user), 116 | }, err 117 | } 118 | 119 | func ConvertCommentRequest(request *service.CommentRequest) local.Comment { 120 | return local.Comment{ 121 | Id: request.CommentId, 122 | AuthorId: request.AuthorId, 123 | VideoId: request.VideoId, 124 | Msg: request.Msg, 125 | } 126 | } 127 | 128 | func ConvertCommentBody(c local.Comment, u *service.CommentUser) *service.CommentBody { 129 | return &service.CommentBody{ 130 | Id: c.Id, 131 | User: u, 132 | Content: c.Msg, 133 | CreateDate: fmt.Sprintf("%d-%d", int(c.CreatedAt.Month()), c.CreatedAt.Day()), 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /comment/internal/server/health.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/health/grpc_health_v1" 7 | ) 8 | 9 | type HealthImpl struct{} 10 | 11 | func (h *HealthImpl) Check(_ context.Context, _ *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { 12 | return &grpc_health_v1.HealthCheckResponse{ 13 | Status: grpc_health_v1.HealthCheckResponse_SERVING, 14 | }, nil 15 | } 16 | 17 | func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error { 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /comment/internal/server/manager.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/JirafaYe/comment/internal/store/cache" 5 | "github.com/JirafaYe/comment/internal/store/local" 6 | "log" 7 | ) 8 | 9 | var m *Manager 10 | 11 | type Manager struct { 12 | localer *local.Manager 13 | cacher *cache.Manager 14 | } 15 | 16 | func init() { 17 | var err error 18 | localer, err := local.New() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | cacher, err := cache.New() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | m = &Manager{ 28 | localer: localer, 29 | cacher: cacher, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /comment/internal/service/proto/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/comment/internal/service/proto/.gitkeep -------------------------------------------------------------------------------- /comment/internal/service/proto/comment.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "../"; 4 | 5 | package service; 6 | 7 | message CommentRequest{ 8 | int64 author_id=1; 9 | int32 action_type=2; 10 | int32 comment_id=3; 11 | int32 video_id=4; 12 | string msg=5; 13 | string token=6; 14 | } 15 | 16 | message ListRequest{ 17 | int32 video_id=1; 18 | string token=2; 19 | } 20 | 21 | message CommentOperationResponse{ 22 | int32 status_code=1; 23 | string status_msg=2; 24 | CommentBody comment=3; 25 | } 26 | 27 | message ListCommentsResponse{ 28 | int32 status_code=1; 29 | string status_msg=2; 30 | repeated CommentBody comment_list=3; 31 | } 32 | 33 | message CommentBody{ 34 | int32 id=1; 35 | CommentUser user=2; 36 | string content=3; 37 | string create_date=4; 38 | } 39 | 40 | message CommentUser{ 41 | int64 id = 1; 42 | string name = 2; 43 | int64 follow_count = 3; // 关注总数 44 | int64 follower_count = 4; // 粉丝总数 45 | bool is_follow = 5; 46 | } 47 | 48 | service Comment{ 49 | rpc OperateComment(CommentRequest) returns (CommentOperationResponse) {} 50 | rpc ListComments(ListRequest) returns (ListCommentsResponse) {} 51 | } -------------------------------------------------------------------------------- /comment/internal/store/cache/config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/JirafaYe/comment/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Addr string `json:"addr"` 10 | Port string `json:"port"` 11 | Password string `json:"password"` 12 | } 13 | 14 | func (c *Config) Key() string { 15 | return "tiktok/cache" 16 | } 17 | 18 | var C Config 19 | 20 | func init() { 21 | err := config.ReadConfig(&C) 22 | if err != nil { 23 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /comment/internal/store/cache/manager.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "github.com/go-redis/redis/v8" 6 | ) 7 | 8 | type Manager struct { 9 | handler *redis.Client 10 | } 11 | 12 | func New() (*Manager, error) { 13 | m := &Manager{ 14 | handler: redis.NewClient( 15 | &redis.Options{ 16 | Addr: C.Addr + ":" + C.Port, 17 | Password: C.Password, 18 | }, 19 | ), 20 | } 21 | return m, m.handler.Ping(context.Background()).Err() 22 | } 23 | -------------------------------------------------------------------------------- /comment/internal/store/cache/token.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | const ( 8 | UserToken = "USER_TOKEN" 9 | ) 10 | 11 | func (m *Manager) IsUserTokenExist(username string) bool { 12 | exists, err := m.handler.HExists(context.Background(), UserToken, username).Result() 13 | return err == nil && exists 14 | } 15 | -------------------------------------------------------------------------------- /comment/internal/store/local/comment.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "errors" 5 | "github.com/JirafaYe/comment/internal/service" 6 | "gorm.io/gorm" 7 | "time" 8 | ) 9 | 10 | // video结构体 11 | type Video struct { 12 | gorm.Model 13 | PlayURL string `gorm:"column:play_url; type:varchar(200)"` 14 | CoverURL string `gorm:"column:cover_url; type:varchar(200)"` 15 | Title string `gorm:"column:title; type:varchar(200)"` 16 | FavoriteCount int64 `gorm:"column:favorite_count; type:bigint"` 17 | CommentCount int64 `gorm:"column:comment_count; type:bigint"` 18 | IsFavorite int16 `gorm:"column:is_favorite; type:tinyint"` 19 | UserId int64 `gorm:"column:user_id; type:bigint"` 20 | } 21 | 22 | type Comment struct { 23 | Id int32 `json:"id"` 24 | AuthorId int64 `json:"author_id"` 25 | VideoId int32 `json:"video_id"` 26 | Msg string `json:"msg"` 27 | CreatedAt time.Time `json:"created_at"` 28 | UpdatedAt time.Time `json:"updated_at"` 29 | DeletedAt gorm.DeletedAt `json:"deleted_at"` 30 | } 31 | 32 | // User结构体 33 | type User struct { 34 | gorm.Model 35 | Name string `gorm:"column:username; type:varchar(200)"` 36 | FollowerCount int64 `gorm:"column:follower_count; type:bigint"` 37 | FollowCount int64 `gorm:"column:follow_count; type:bigint"` 38 | } 39 | 40 | func (c Comment) TableName() string { 41 | return "t_comment" 42 | } 43 | 44 | func (v Video) TableName() string { 45 | return "t_video" 46 | } 47 | 48 | func (u User) TableName() string { 49 | return "t_user" 50 | } 51 | 52 | func (m *Manager) GetUserMsg(id []int64) ([]service.CommentUser, error) { 53 | var users []service.CommentUser 54 | tx := m.handler.Model(&User{}).Select("id,username name").Where(id).Find(&users) 55 | return users, tx.Error 56 | } 57 | 58 | func (m *Manager) InsertComment(comment *Comment) error { 59 | var err error 60 | transaction := m.handler.Begin() 61 | 62 | rows := transaction.Create(comment) 63 | if rows.RowsAffected != 1 { 64 | err = errors.New("插入失败") 65 | transaction.Rollback() 66 | return err 67 | } 68 | err = transaction.Model(&Video{}).Where("id = ?", comment.VideoId). 69 | UpdateColumn("comment_count", gorm.Expr("comment_count + ?", 1)).Error 70 | if err != nil { 71 | transaction.Rollback() 72 | return err 73 | } 74 | transaction.Commit() 75 | return nil 76 | } 77 | 78 | func (m *Manager) DeleteComment(comment Comment) error { 79 | var err error 80 | 81 | rows := m.handler.Where("id = ?", comment.Id).Delete(&Comment{}) 82 | if rows.RowsAffected != 1 { 83 | err = errors.New("无效评论") 84 | return err 85 | } 86 | 87 | return m.UpdateCommentsCountByVideoId(comment.VideoId, -1) 88 | 89 | } 90 | 91 | // videoId合法性校验 92 | func (m *Manager) SelectVideoById(id int32) (bool, error) { 93 | var video Video 94 | tx := m.handler.Where("id=?", id).Select("id").Find(&video) 95 | err := tx.Error 96 | 97 | if err != nil { 98 | return false, err 99 | } 100 | if 0 != tx.RowsAffected { 101 | return true, nil 102 | } else { 103 | return false, nil 104 | } 105 | } 106 | 107 | func (m *Manager) SelectCommentNumsByVideoId(id int32) (int64, error) { 108 | var cnt int64 109 | err := m.handler.Model(&Comment{}).Where("video_id=?", id).Count(&cnt).Error 110 | return cnt, err 111 | } 112 | 113 | func (m *Manager) SelectCommentListByVideoId(id int32) ([]Comment, error) { 114 | var list []Comment 115 | err := m.handler.Where("video_id = ?", id).Order("created_at").Find(&list).Error 116 | return list, err 117 | } 118 | 119 | // 更新评论数 120 | func (m *Manager) UpdateCommentsCountByVideoId(id int32, num int32) error { 121 | return m.handler.Model(&Video{}).Where("id = ?", id). 122 | UpdateColumn("comment_count", gorm.Expr("comment_count + ?", num)).Error 123 | } 124 | -------------------------------------------------------------------------------- /comment/internal/store/local/comment_test.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "testing" 8 | ) 9 | 10 | func TestInsert(t *testing.T) { 11 | manager, err := New() 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | c := Comment{ 16 | AuthorId: 1, 17 | VideoId: 1234, 18 | Msg: "hello test", 19 | } 20 | 21 | err = manager.InsertComment(&c) 22 | 23 | if err != nil { 24 | log.Println(err) 25 | } 26 | 27 | marshal, _ := json.Marshal(c) 28 | fmt.Printf("%s", string(marshal)) 29 | 30 | err = manager.DeleteComment(c) 31 | if err != nil { 32 | fmt.Println(err) 33 | } 34 | 35 | } 36 | 37 | func TestUpdate(t *testing.T) { 38 | manager, err := New() 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | err = manager.UpdateCommentsCountByVideoId(16, 1) 44 | 45 | if err != nil { 46 | log.Println(err) 47 | } 48 | 49 | } 50 | func TestSelect(t *testing.T) { 51 | manager, _ := New() 52 | i, err := manager.SelectCommentNumsByVideoId(1000) 53 | if err != nil { 54 | fmt.Println(err) 55 | } 56 | fmt.Printf("有%d条评论", i) 57 | } 58 | 59 | func TestList(t *testing.T) { 60 | manager, _ := New() 61 | comment, err := manager.SelectCommentListByVideoId(1) 62 | if err != nil { 63 | fmt.Println(err) 64 | } 65 | for _, v := range comment { 66 | fmt.Println(v) 67 | } 68 | } 69 | 70 | func TestGetMsg(t *testing.T) { 71 | manager, _ := New() 72 | msg, _ := manager.GetUserMsg([]int64{1, 2}) 73 | for _, v := range msg { 74 | log.Printf("%#v", v) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /comment/internal/store/local/config.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/JirafaYe/comment/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Host string `json:"host"` 10 | Port string `json:"port"` 11 | User string `json:"user"` 12 | Name string `json:"name"` 13 | Password string `json:"password"` 14 | } 15 | 16 | func (c *Config) Key() string { 17 | return "tiktok/local" 18 | } 19 | 20 | var C Config 21 | 22 | func init() { 23 | err := config.ReadConfig(&C) 24 | if err != nil { 25 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /comment/internal/store/local/manager.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "fmt" 5 | "gorm.io/driver/mysql" 6 | "gorm.io/gorm" 7 | "gorm.io/gorm/logger" 8 | "log" 9 | "os" 10 | ) 11 | 12 | type Manager struct { 13 | handler *gorm.DB 14 | } 15 | 16 | func New() (*Manager, error) { 17 | newLogger := logger.New( 18 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 19 | logger.Config{ 20 | SlowThreshold: 200, // 慢 SQL 阈值 21 | LogLevel: logger.Warn, // 日志级别 22 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 23 | Colorful: true, // 禁用彩色打印 24 | }, 25 | ) 26 | 27 | db, err := gorm.Open( 28 | mysql.Open( 29 | fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", 30 | C.User, 31 | C.Password, 32 | C.Host, 33 | C.Port, 34 | C.Name, 35 | ), 36 | ), 37 | &gorm.Config{ 38 | Logger: newLogger, 39 | }, 40 | ) 41 | return &Manager{ 42 | handler: db, 43 | }, err 44 | } 45 | -------------------------------------------------------------------------------- /comment/pkg/jwt.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | ) 6 | 7 | var jwtSecret = []byte("tiktok.user") 8 | 9 | type Claims struct { 10 | Id int64 `json:"id"` 11 | Username string `json:"username"` 12 | jwt.StandardClaims 13 | } 14 | 15 | // ParseToken 验证用户token 16 | func ParseToken(token string) (*Claims, error) { 17 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 18 | return jwtSecret, nil 19 | }) 20 | if tokenClaims != nil { 21 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 22 | return claims, nil 23 | } 24 | } 25 | return nil, err 26 | } 27 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example-demo 2 | 3 | ## 运行 4 | ``` 5 | git clone git@github.com:JirafaYe/tiktok.git 6 | cd example 7 | cd user; go run cmd/user/main.go -addr="运行服务器ip" 8 | cd hello; go run cmd/hello/main.go -addr="运行服务器ip" 9 | cd gateway; go run main.go 10 | ``` -------------------------------------------------------------------------------- /example/gateway/Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/example/gateway/Dockerfile -------------------------------------------------------------------------------- /example/gateway/Makefile: -------------------------------------------------------------------------------- 1 | protoc: 2 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 3 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 4 | protoc --go_out=./ --go-grpc_out=./ ./service/proto/*.proto -------------------------------------------------------------------------------- /example/gateway/api/config.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/JirafaYe/example/gateway/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | IP string `json:"ip"` 10 | Port string `json:"port"` 11 | } 12 | 13 | func (c *Config) Key() string { 14 | return "tiktok/example/api" 15 | } 16 | 17 | var C Config 18 | 19 | func init() { 20 | err := config.ReadConfig(&C) 21 | if err != nil { 22 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/gateway/api/hello.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/JirafaYe/example/gateway/center" 8 | "github.com/JirafaYe/example/gateway/service" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func (m *Manager) RouteHello() { 13 | m.handler.POST("/hello", m.hello) 14 | } 15 | 16 | type helloMsg struct { 17 | Token string `json:"token"` 18 | } 19 | 20 | func (m *Manager) hello(ctx *gin.Context) { 21 | var msg helloMsg 22 | // 获取请求中的json数据 23 | if err := ctx.ShouldBindJSON(&msg); err != nil { 24 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 25 | return 26 | } 27 | 28 | // center.Resolver() 参数为调用的服务名 29 | // 该函数会进行自动负载均衡并返回一个*grpc.ClientConn 30 | conn, err := center.Resolver("hello") 31 | if err != nil { 32 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 33 | return 34 | } 35 | defer conn.Close() 36 | 37 | c := service.NewHelloClient(conn) 38 | response, _ := c.SayHello(context.Background(), &service.HelloRequest{ 39 | Token: msg.Token, 40 | }) 41 | 42 | ctx.JSON(int(response.Code), gin.H{"msg": response.Msg, "data": map[string]any{ 43 | "data": response.Data, 44 | }}) 45 | } 46 | -------------------------------------------------------------------------------- /example/gateway/api/manager.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-contrib/cors" 5 | "github.com/gin-gonic/gin" 6 | "log" 7 | "reflect" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type Manager struct { 13 | handler *gin.Engine 14 | } 15 | 16 | func New() *Manager { 17 | return &Manager{ 18 | handler: gin.Default(), 19 | } 20 | } 21 | 22 | func (m *Manager) Run() error { 23 | err := m.load() 24 | if err != nil { 25 | return err 26 | } 27 | return m.handler.Run(C.IP + ":" + C.Port) 28 | } 29 | 30 | func (m *Manager) load() (err error) { 31 | err = m.loadPlugin() 32 | if err != nil { 33 | return 34 | } 35 | return m.loadRoute() 36 | } 37 | 38 | func (m *Manager) loadPlugin() error { 39 | m.loadCORS() 40 | return nil 41 | } 42 | 43 | func (m *Manager) loadCORS() error { 44 | // iris cors 45 | //crs := cors.New(cors.Options{ 46 | // AllowedOrigins: []string{"*"}, 47 | // AllowedMethods: []string{"POST", "GET", "OPTIONS", "DELETE"}, 48 | // MaxAge: 3600, 49 | // AllowedHeaders: []string{"*"}, 50 | // AllowCredentials: true, 51 | //}) 52 | //m.handler.UseRouter(crs) 53 | 54 | // gin cors 55 | crs := cors.New(cors.Config{ 56 | AllowOrigins: []string{"*"}, 57 | AllowMethods: []string{"PUT", "PATCH", "POST", "GET", "DELETE"}, 58 | AllowHeaders: []string{"Origin", "Authorization", "Content-Type"}, 59 | ExposeHeaders: []string{"Content-Type"}, 60 | AllowCredentials: true, 61 | AllowOriginFunc: func(origin string) bool { 62 | return true 63 | }, 64 | MaxAge: 24 * time.Hour, 65 | }) 66 | m.handler.Use(crs) 67 | return nil 68 | } 69 | 70 | func (m *Manager) loadRoute() error { 71 | t := reflect.TypeOf(m) 72 | for i := 0; i < t.NumMethod(); i++ { 73 | f := t.Method(i) 74 | if strings.HasPrefix(f.Name, "Route") && 75 | f.Type.NumOut() == 0 && 76 | f.Type.NumIn() == 1 { 77 | log.Println("[GATEWAY] LOAD ROUTE:", f.Name) 78 | f.Func.Call([]reflect.Value{ 79 | reflect.ValueOf(m), 80 | }) 81 | } 82 | } 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /example/gateway/api/user.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/JirafaYe/example/gateway/center" 8 | "github.com/JirafaYe/example/gateway/service" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // RouteUser 注册路由且该函数必须以Route前缀 13 | // main.go文件运行时会通过反射来查看有Route前缀的函数来进行路由注册 14 | func (m *Manager) RouteUser() { 15 | p := m.handler.Group("/user") 16 | p.POST("/register", m.register) 17 | p.POST("/login", m.login) 18 | p.POST("/logout", m.logout) 19 | } 20 | 21 | type loginMsg struct { 22 | Username string `json:"username"` 23 | Password string `json:"password"` 24 | } 25 | 26 | type registerMsg struct { 27 | Username string `json:"username"` 28 | Password string `json:"password"` 29 | } 30 | 31 | type logoutMsg struct { 32 | Token string `json:"token"` 33 | } 34 | 35 | func (m *Manager) register(ctx *gin.Context) { 36 | var msg registerMsg 37 | // 获取请求中的json数据 38 | if err := ctx.ShouldBindJSON(&msg); err != nil { 39 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 40 | return 41 | } 42 | 43 | // center.Resolver() 参数为调用的服务名 44 | // 该函数会进行自动负载均衡并返回一个*grpc.ClientConn 45 | conn, err := center.Resolver("user") 46 | if err != nil { 47 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 48 | return 49 | } 50 | defer conn.Close() 51 | c := service.NewUserClient(conn) 52 | 53 | response, _ := c.Register(context.Background(), &service.RegisterRequest{ 54 | Username: msg.Username, 55 | Password: msg.Password, 56 | }) 57 | 58 | ctx.JSON(int(response.Code), gin.H{"msg": response.Msg, "data": map[string]any{ 59 | "user_id": response.UserId, 60 | }}) 61 | } 62 | 63 | func (m *Manager) login(ctx *gin.Context) { 64 | var msg loginMsg 65 | // 获取请求中的json数据 66 | if err := ctx.ShouldBindJSON(&msg); err != nil { 67 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 68 | return 69 | } 70 | // center.Resolver() 参数为调用的服务名 71 | // 该函数会进行自动负载均衡并返回一个*grpc.ClientConn 72 | conn, err := center.Resolver("user") 73 | if err != nil { 74 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 75 | return 76 | } 77 | defer conn.Close() 78 | c := service.NewUserClient(conn) 79 | 80 | response, _ := c.Login(context.Background(), &service.LoginRequest{ 81 | Username: msg.Username, 82 | Password: msg.Password, 83 | }) 84 | 85 | ctx.JSON(int(response.Code), gin.H{"msg": response.Msg, "data": map[string]any{ 86 | "token": response.Token, 87 | }}) 88 | } 89 | 90 | func (m *Manager) logout(ctx *gin.Context) { 91 | var msg logoutMsg 92 | // 获取请求中的json数据 93 | if err := ctx.ShouldBindJSON(&msg); err != nil { 94 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 95 | return 96 | } 97 | // center.Resolver() 参数为调用的服务名 98 | // 该函数会进行自动负载均衡并返回一个*grpc.ClientConn 99 | conn, err := center.Resolver("user") 100 | if err != nil { 101 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 102 | return 103 | } 104 | defer conn.Close() 105 | c := service.NewUserClient(conn) 106 | 107 | response, _ := c.Logout(context.Background(), &service.LogoutRequest{ 108 | Token: msg.Token, 109 | }) 110 | 111 | ctx.JSON(int(response.Code), gin.H{"msg": response.Msg}) 112 | } 113 | -------------------------------------------------------------------------------- /example/gateway/center/consul.go: -------------------------------------------------------------------------------- 1 | package center 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | consul "github.com/hashicorp/consul/api" 8 | _ "github.com/mbobakov/grpc-consul-resolver" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/credentials/insecure" 11 | ) 12 | 13 | var ( 14 | addr = "47.108.66.104:8500" 15 | client *consul.Client 16 | ) 17 | 18 | func init() { 19 | var err error 20 | config := consul.DefaultConfig() 21 | config.Address = addr 22 | client, err = consul.NewClient(config) 23 | if err != nil { 24 | log.Fatalf("failed to init consul: %v", err) 25 | } 26 | } 27 | 28 | // GetValue consul KV读取 29 | func GetValue(key string) ([]byte, error) { 30 | res, _, err := client.KV().Get(key, nil) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return res.Value, nil 35 | } 36 | 37 | // Register consul服务注册 38 | func Register(reg consul.AgentServiceRegistration) error { 39 | agent := client.Agent() 40 | 41 | return agent.ServiceRegister(®) 42 | } 43 | 44 | // Resolver 对服务进行负载均衡 45 | func Resolver(name string) (*grpc.ClientConn, error) { 46 | conn, err := grpc.Dial( 47 | // name 拉取的服务名 wait=14s 等待时间 tag=manual 筛选条件 48 | fmt.Sprintf("consul://%v/%v?wait=14s&tag=manual", addr, name), 49 | grpc.WithTransportCredentials(insecure.NewCredentials()), 50 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), 51 | ) 52 | if err != nil { 53 | return nil, err 54 | } 55 | return conn, nil 56 | } 57 | -------------------------------------------------------------------------------- /example/gateway/config/api.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var handler *Manager 4 | 5 | func init() { 6 | handler = New() 7 | } 8 | 9 | func ReadConfig(c Config) error { 10 | return handler.ReadConfig(c) 11 | } 12 | -------------------------------------------------------------------------------- /example/gateway/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config interface { 4 | Key() string 5 | } 6 | -------------------------------------------------------------------------------- /example/gateway/config/manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/JirafaYe/example/gateway/center" 6 | "log" 7 | ) 8 | 9 | type Manager struct { 10 | } 11 | 12 | func New() *Manager { 13 | return &Manager{} 14 | } 15 | 16 | func (m *Manager) ReadConfig(c Config) error { 17 | log.Println("[CONFIG] READING", c.Key()) 18 | config, err := center.GetValue(c.Key()) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = json.Unmarshal(config, &c) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /example/gateway/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/example/gateway 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gin-contrib/cors v1.4.0 7 | github.com/gin-gonic/gin v1.8.2 8 | github.com/hashicorp/consul/api v1.18.0 9 | github.com/mbobakov/grpc-consul-resolver v1.4.4 10 | google.golang.org/grpc v1.52.0 11 | google.golang.org/protobuf v1.28.1 12 | ) 13 | 14 | require ( 15 | github.com/armon/go-metrics v0.3.2 // indirect 16 | github.com/fatih/color v1.9.0 // indirect 17 | github.com/gin-contrib/sse v0.1.0 // indirect 18 | github.com/go-playground/form v3.1.4+incompatible // indirect 19 | github.com/go-playground/locales v0.14.1 // indirect 20 | github.com/go-playground/universal-translator v0.18.0 // indirect 21 | github.com/go-playground/validator/v10 v10.11.1 // indirect 22 | github.com/goccy/go-json v0.10.0 // indirect 23 | github.com/golang/protobuf v1.5.2 // indirect 24 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 25 | github.com/hashicorp/go-hclog v0.12.0 // indirect 26 | github.com/hashicorp/go-immutable-radix v1.1.0 // indirect 27 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 28 | github.com/hashicorp/golang-lru v0.5.4 // indirect 29 | github.com/hashicorp/serf v0.10.1 // indirect 30 | github.com/jpillora/backoff v1.0.0 // indirect 31 | github.com/json-iterator/go v1.1.12 // indirect 32 | github.com/leodido/go-urn v1.2.1 // indirect 33 | github.com/mattn/go-colorable v0.1.6 // indirect 34 | github.com/mattn/go-isatty v0.0.17 // indirect 35 | github.com/mitchellh/go-homedir v1.1.0 // indirect 36 | github.com/mitchellh/mapstructure v1.4.1 // indirect 37 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 38 | github.com/modern-go/reflect2 v1.0.2 // indirect 39 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 40 | github.com/pkg/errors v0.9.1 // indirect 41 | github.com/ugorji/go/codec v1.2.8 // indirect 42 | golang.org/x/crypto v0.5.0 // indirect 43 | golang.org/x/net v0.5.0 // indirect 44 | golang.org/x/sys v0.4.0 // indirect 45 | golang.org/x/text v0.6.0 // indirect 46 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect 47 | gopkg.in/yaml.v2 v2.4.0 // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /example/gateway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/JirafaYe/example/gateway/api" 5 | "log" 6 | ) 7 | 8 | func init() { 9 | log.SetFlags(log.Llongfile) 10 | } 11 | 12 | func main() { 13 | app := api.New() 14 | err := app.Run() 15 | if err != nil { 16 | log.Fatalf("failed to run server, %v", err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /example/gateway/service/hello_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package service 4 | 5 | import ( 6 | context "context" 7 | grpc "google.golang.org/grpc" 8 | codes "google.golang.org/grpc/codes" 9 | status "google.golang.org/grpc/status" 10 | ) 11 | 12 | // This is a compile-time assertion to ensure that this generated file 13 | // is compatible with the grpc package it is being compiled against. 14 | // Requires gRPC-Go v1.32.0 or later. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // HelloClient is the client API for Hello service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type HelloClient interface { 21 | SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) 22 | } 23 | 24 | type helloClient struct { 25 | cc grpc.ClientConnInterface 26 | } 27 | 28 | func NewHelloClient(cc grpc.ClientConnInterface) HelloClient { 29 | return &helloClient{cc} 30 | } 31 | 32 | func (c *helloClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { 33 | out := new(HelloResponse) 34 | err := c.cc.Invoke(ctx, "/service.Hello/SayHello", in, out, opts...) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return out, nil 39 | } 40 | 41 | // HelloServer is the server API for Hello service. 42 | // All implementations must embed UnimplementedHelloServer 43 | // for forward compatibility 44 | type HelloServer interface { 45 | SayHello(context.Context, *HelloRequest) (*HelloResponse, error) 46 | mustEmbedUnimplementedHelloServer() 47 | } 48 | 49 | // UnimplementedHelloServer must be embedded to have forward compatible implementations. 50 | type UnimplementedHelloServer struct { 51 | } 52 | 53 | func (UnimplementedHelloServer) SayHello(context.Context, *HelloRequest) (*HelloResponse, error) { 54 | return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") 55 | } 56 | func (UnimplementedHelloServer) mustEmbedUnimplementedHelloServer() {} 57 | 58 | // UnsafeHelloServer may be embedded to opt out of forward compatibility for this service. 59 | // Use of this interface is not recommended, as added methods to HelloServer will 60 | // result in compilation errors. 61 | type UnsafeHelloServer interface { 62 | mustEmbedUnimplementedHelloServer() 63 | } 64 | 65 | func RegisterHelloServer(s grpc.ServiceRegistrar, srv HelloServer) { 66 | s.RegisterService(&Hello_ServiceDesc, srv) 67 | } 68 | 69 | func _Hello_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 70 | in := new(HelloRequest) 71 | if err := dec(in); err != nil { 72 | return nil, err 73 | } 74 | if interceptor == nil { 75 | return srv.(HelloServer).SayHello(ctx, in) 76 | } 77 | info := &grpc.UnaryServerInfo{ 78 | Server: srv, 79 | FullMethod: "/service.Hello/SayHello", 80 | } 81 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 82 | return srv.(HelloServer).SayHello(ctx, req.(*HelloRequest)) 83 | } 84 | return interceptor(ctx, in, info, handler) 85 | } 86 | 87 | // Hello_ServiceDesc is the grpc.ServiceDesc for Hello service. 88 | // It's only intended for direct use with grpc.RegisterService, 89 | // and not to be introspected or modified (even as a copy) 90 | var Hello_ServiceDesc = grpc.ServiceDesc{ 91 | ServiceName: "service.Hello", 92 | HandlerType: (*HelloServer)(nil), 93 | Methods: []grpc.MethodDesc{ 94 | { 95 | MethodName: "SayHello", 96 | Handler: _Hello_SayHello_Handler, 97 | }, 98 | }, 99 | Streams: []grpc.StreamDesc{}, 100 | Metadata: "service/proto/hello.proto", 101 | } 102 | -------------------------------------------------------------------------------- /example/gateway/service/proto/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./internal/service;service"; 4 | 5 | package service; 6 | 7 | message HelloRequest { 8 | string token = 1; 9 | } 10 | 11 | message HelloResponse { 12 | int32 code = 1; 13 | string msg = 2; 14 | string data = 3; 15 | } 16 | 17 | service Hello { 18 | rpc SayHello(HelloRequest) returns (HelloResponse) {} 19 | } -------------------------------------------------------------------------------- /example/gateway/service/proto/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./internal/service;service"; 4 | 5 | package service; 6 | 7 | message RegisterRequest { 8 | string username = 1; 9 | string password = 2; 10 | } 11 | 12 | message RegisterResponse { 13 | int32 code = 1; 14 | string msg = 2; 15 | int32 user_id = 3; 16 | } 17 | 18 | message LoginRequest { 19 | string username = 1; 20 | string password = 2; 21 | } 22 | 23 | message LoginResponse { 24 | int32 code = 1; 25 | string msg = 2; 26 | string token = 3; 27 | } 28 | 29 | message LogoutRequest { 30 | string token = 1; 31 | } 32 | 33 | message LogoutResponse { 34 | int32 code = 1; 35 | string msg = 2; 36 | } 37 | 38 | service User { 39 | rpc Login(LoginRequest) returns (LoginResponse) {} 40 | rpc Register(RegisterRequest) returns (RegisterResponse) {} 41 | rpc Logout(LogoutRequest) returns (LogoutResponse) {} 42 | } -------------------------------------------------------------------------------- /example/hello/.gitignore: -------------------------------------------------------------------------------- 1 | # use for java 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # use for golang 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Binaries for programs and plugins 28 | *.exe 29 | *.exe~ 30 | *.dll 31 | *.so 32 | *.dylib 33 | 34 | # Test binary, built with `go test -c` 35 | *.test 36 | 37 | # Output of the go coverage tool, specifically when used with LiteIDE 38 | *.out 39 | 40 | # Dependency directories (remove the comment below to include it) 41 | # vendor/ 42 | golang/main.go 43 | 44 | .vscode 45 | .idea 46 | # use for javascript 47 | # compiled output 48 | /dist 49 | /tmp 50 | /out-tsc 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | 64 | # nyc test coverage 65 | .nyc_output 66 | 67 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 68 | .grunt 69 | 70 | # Bower dependency directory (https://bower.io/) 71 | bower_components 72 | 73 | # node-waf configuration 74 | .lock-wscript 75 | 76 | # IDEs and editors 77 | .idea 78 | .project 79 | .classpath 80 | .c9/ 81 | *.launch 82 | .settings/ 83 | *.sublime-workspace 84 | 85 | # IDE - VSCode 86 | .vscode/* 87 | !.vscode/settings.json 88 | !.vscode/tasks.json 89 | !.vscode/launch.json 90 | !.vscode/extensions.json 91 | jsconfig.json 92 | Top Interview Questions/Solution.ts 93 | Top Interview Questions/Solution.java 94 | # misc 95 | .sass-cache 96 | connect.lock 97 | typings 98 | 99 | # Logs 100 | logs 101 | *.log 102 | npm-debug.log* 103 | yarn-debug.log* 104 | yarn-error.log* 105 | 106 | 107 | # Dependency directories 108 | node_modules/ 109 | jspm_packages/ 110 | 111 | # Optional npm cache directory 112 | .npm 113 | 114 | # Optional eslint cache 115 | .eslintcache 116 | 117 | # Optional REPL history 118 | .node_repl_history 119 | 120 | # Output of 'npm pack' 121 | *.tgz 122 | 123 | # Yarn Integrity file 124 | .yarn-integrity 125 | 126 | # dotenv environment variables file 127 | .env 128 | 129 | # next.js build output 130 | .next 131 | 132 | # Lerna 133 | lerna-debug.log 134 | 135 | # System Files 136 | .DS_Store 137 | Thumbs.db 138 | -------------------------------------------------------------------------------- /example/hello/Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/example/hello/Dockerfile -------------------------------------------------------------------------------- /example/hello/Makefile: -------------------------------------------------------------------------------- 1 | protoc: 2 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 3 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 4 | protoc --go_out=./ --go-grpc_out=./ ./internal/service/proto/*.proto -------------------------------------------------------------------------------- /example/hello/center/consul.go: -------------------------------------------------------------------------------- 1 | package center 2 | 3 | import ( 4 | "log" 5 | 6 | consul "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | var ( 10 | addr = "47.108.66.104:8500" 11 | client *consul.Client 12 | ) 13 | 14 | func init() { 15 | var err error 16 | config := consul.DefaultConfig() 17 | config.Address = addr 18 | client, err = consul.NewClient(config) 19 | if err != nil { 20 | log.Fatalf("failed to init consul: %v", err) 21 | } 22 | } 23 | 24 | func GetValue(key string) ([]byte, error) { 25 | res, _, err := client.KV().Get(key, nil) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return res.Value, nil 30 | } 31 | 32 | func Register(reg consul.AgentServiceRegistration) error { 33 | agent := client.Agent() 34 | 35 | return agent.ServiceRegister(®) 36 | } 37 | -------------------------------------------------------------------------------- /example/hello/cmd/hello/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | 9 | "github.com/JirafaYe/example/hello/center" 10 | "github.com/JirafaYe/example/hello/internal/server" 11 | "github.com/JirafaYe/example/hello/internal/service" 12 | consul "github.com/hashicorp/consul/api" 13 | "google.golang.org/grpc" 14 | "google.golang.org/grpc/health/grpc_health_v1" 15 | ) 16 | 17 | var ( 18 | addr = flag.String("addr", "127.0.0.1", "The server address") 19 | port = flag.Int("port", 50052, "The server port") 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | 25 | // 服务注册 26 | err := center.Register(consul.AgentServiceRegistration{ 27 | ID: "hello-1", // 服务节点的名称 28 | Name: "hello", // 服务名称 29 | Port: *port, // 服务端口 30 | Address: *addr, // 服务 IP 31 | Check: &consul.AgentServiceCheck{ // 健康检查 32 | Interval: "5s", // 健康检查间隔 33 | GRPC: fmt.Sprintf("%v:%v/%v", *addr, *port, "health"), 34 | DeregisterCriticalServiceAfter: "10s", // 注销时间,相当于过期时间 35 | }, 36 | }) 37 | if err != nil { 38 | log.Fatalf("failed to register service: %v", err) 39 | } 40 | 41 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 42 | if err != nil { 43 | log.Fatalf("failed to listen: %v", err) 44 | } 45 | s := grpc.NewServer() 46 | service.RegisterHelloServer(s, &server.HelloSrv{}) 47 | // 健康检查 48 | grpc_health_v1.RegisterHealthServer(s, &server.HealthImpl{}) 49 | log.Printf("server listening at %v", lis.Addr()) 50 | if err := s.Serve(lis); err != nil { 51 | log.Fatalf("failed to serve: %v", err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/hello/config/api.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var handler *Manager 4 | 5 | func init() { 6 | handler = New() 7 | } 8 | 9 | func ReadConfig(c Config) error { 10 | return handler.ReadConfig(c) 11 | } 12 | -------------------------------------------------------------------------------- /example/hello/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config interface { 4 | Key() string 5 | } 6 | -------------------------------------------------------------------------------- /example/hello/config/manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/JirafaYe/example/hello/center" 6 | "log" 7 | ) 8 | 9 | type Manager struct { 10 | } 11 | 12 | func New() *Manager { 13 | return &Manager{} 14 | } 15 | 16 | func (m *Manager) ReadConfig(c Config) error { 17 | log.Println("[CONFIG] READING", c.Key()) 18 | config, err := center.GetValue(c.Key()) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = json.Unmarshal(config, &c) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /example/hello/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/example/hello 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/go-redis/redis/v8 v8.11.5 8 | github.com/hashicorp/consul/api v1.18.0 9 | google.golang.org/grpc v1.52.0 10 | google.golang.org/protobuf v1.28.1 11 | gorm.io/driver/mysql v1.4.5 12 | gorm.io/gorm v1.24.3 13 | ) 14 | 15 | require ( 16 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 17 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 18 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 19 | github.com/fatih/color v1.9.0 // indirect 20 | github.com/go-sql-driver/mysql v1.7.0 // indirect 21 | github.com/golang/protobuf v1.5.2 // indirect 22 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 23 | github.com/hashicorp/go-hclog v0.12.0 // indirect 24 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 25 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 26 | github.com/hashicorp/golang-lru v0.5.4 // indirect 27 | github.com/hashicorp/serf v0.10.1 // indirect 28 | github.com/jinzhu/inflection v1.0.0 // indirect 29 | github.com/jinzhu/now v1.1.5 // indirect 30 | github.com/mattn/go-colorable v0.1.6 // indirect 31 | github.com/mattn/go-isatty v0.0.12 // indirect 32 | github.com/mitchellh/go-homedir v1.1.0 // indirect 33 | github.com/mitchellh/mapstructure v1.4.1 // indirect 34 | golang.org/x/net v0.4.0 // indirect 35 | golang.org/x/sys v0.3.0 // indirect 36 | golang.org/x/text v0.5.0 // indirect 37 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /example/hello/internal/server/health.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/health/grpc_health_v1" 7 | ) 8 | 9 | type HealthImpl struct{} 10 | 11 | func (h *HealthImpl) Check(_ context.Context, _ *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { 12 | return &grpc_health_v1.HealthCheckResponse{ 13 | Status: grpc_health_v1.HealthCheckResponse_SERVING, 14 | }, nil 15 | } 16 | 17 | func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error { 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /example/hello/internal/server/hello.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/JirafaYe/example/hello/internal/service" 9 | "github.com/JirafaYe/example/hello/pkg/token" 10 | ) 11 | 12 | type HelloSrv struct { 13 | service.UnimplementedHelloServer 14 | } 15 | 16 | func (h *HelloSrv) SayHello(_ context.Context, request *service.HelloRequest) (*service.HelloResponse, error) { 17 | tokenString := request.Token 18 | claims, err := token.ParseToken(tokenString) 19 | if err != nil { 20 | return &service.HelloResponse{ 21 | Code: http.StatusInternalServerError, 22 | }, nil 23 | } 24 | return &service.HelloResponse{ 25 | Code: http.StatusOK, 26 | Msg: "成功", 27 | Data: fmt.Sprintf("Hello %v.", claims.Username), 28 | }, nil 29 | } 30 | -------------------------------------------------------------------------------- /example/hello/internal/server/manager.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/JirafaYe/example/hello/internal/store/cache" 5 | "github.com/JirafaYe/example/hello/internal/store/local" 6 | "log" 7 | ) 8 | 9 | var m *Manager 10 | 11 | type Manager struct { 12 | localer *local.Manager 13 | cacher *cache.Manager 14 | } 15 | 16 | func init() { 17 | var err error 18 | localer, err := local.New() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | cacher, err := cache.New() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | m = &Manager{ 28 | localer: localer, 29 | cacher: cacher, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/hello/internal/service/hello_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package service 4 | 5 | import ( 6 | context "context" 7 | grpc "google.golang.org/grpc" 8 | codes "google.golang.org/grpc/codes" 9 | status "google.golang.org/grpc/status" 10 | ) 11 | 12 | // This is a compile-time assertion to ensure that this generated file 13 | // is compatible with the grpc package it is being compiled against. 14 | // Requires gRPC-Go v1.32.0 or later. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // HelloClient is the client API for Hello service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type HelloClient interface { 21 | SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) 22 | } 23 | 24 | type helloClient struct { 25 | cc grpc.ClientConnInterface 26 | } 27 | 28 | func NewHelloClient(cc grpc.ClientConnInterface) HelloClient { 29 | return &helloClient{cc} 30 | } 31 | 32 | func (c *helloClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { 33 | out := new(HelloResponse) 34 | err := c.cc.Invoke(ctx, "/service.Hello/SayHello", in, out, opts...) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return out, nil 39 | } 40 | 41 | // HelloServer is the server API for Hello service. 42 | // All implementations must embed UnimplementedHelloServer 43 | // for forward compatibility 44 | type HelloServer interface { 45 | SayHello(context.Context, *HelloRequest) (*HelloResponse, error) 46 | mustEmbedUnimplementedHelloServer() 47 | } 48 | 49 | // UnimplementedHelloServer must be embedded to have forward compatible implementations. 50 | type UnimplementedHelloServer struct { 51 | } 52 | 53 | func (UnimplementedHelloServer) SayHello(context.Context, *HelloRequest) (*HelloResponse, error) { 54 | return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") 55 | } 56 | func (UnimplementedHelloServer) mustEmbedUnimplementedHelloServer() {} 57 | 58 | // UnsafeHelloServer may be embedded to opt out of forward compatibility for this service. 59 | // Use of this interface is not recommended, as added methods to HelloServer will 60 | // result in compilation errors. 61 | type UnsafeHelloServer interface { 62 | mustEmbedUnimplementedHelloServer() 63 | } 64 | 65 | func RegisterHelloServer(s grpc.ServiceRegistrar, srv HelloServer) { 66 | s.RegisterService(&Hello_ServiceDesc, srv) 67 | } 68 | 69 | func _Hello_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 70 | in := new(HelloRequest) 71 | if err := dec(in); err != nil { 72 | return nil, err 73 | } 74 | if interceptor == nil { 75 | return srv.(HelloServer).SayHello(ctx, in) 76 | } 77 | info := &grpc.UnaryServerInfo{ 78 | Server: srv, 79 | FullMethod: "/service.Hello/SayHello", 80 | } 81 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 82 | return srv.(HelloServer).SayHello(ctx, req.(*HelloRequest)) 83 | } 84 | return interceptor(ctx, in, info, handler) 85 | } 86 | 87 | // Hello_ServiceDesc is the grpc.ServiceDesc for Hello service. 88 | // It's only intended for direct use with grpc.RegisterService, 89 | // and not to be introspected or modified (even as a copy) 90 | var Hello_ServiceDesc = grpc.ServiceDesc{ 91 | ServiceName: "service.Hello", 92 | HandlerType: (*HelloServer)(nil), 93 | Methods: []grpc.MethodDesc{ 94 | { 95 | MethodName: "SayHello", 96 | Handler: _Hello_SayHello_Handler, 97 | }, 98 | }, 99 | Streams: []grpc.StreamDesc{}, 100 | Metadata: "internal/service/proto/hello.proto", 101 | } 102 | -------------------------------------------------------------------------------- /example/hello/internal/service/proto/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./internal/service;service"; 4 | 5 | package service; 6 | 7 | message HelloRequest { 8 | string token = 1; 9 | } 10 | 11 | message HelloResponse { 12 | int32 code = 1; 13 | string msg = 2; 14 | string data = 3; 15 | } 16 | 17 | service Hello { 18 | rpc SayHello(HelloRequest) returns (HelloResponse) {} 19 | } -------------------------------------------------------------------------------- /example/hello/internal/store/cache/config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/JirafaYe/example/hello/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Addr string `json:"addr"` 10 | Port string `json:"port"` 11 | Password string `json:"password"` 12 | } 13 | 14 | func (c *Config) Key() string { 15 | return "tiktok/example/cache" 16 | } 17 | 18 | var C Config 19 | 20 | func init() { 21 | err := config.ReadConfig(&C) 22 | if err != nil { 23 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/hello/internal/store/cache/manager.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "github.com/go-redis/redis/v8" 6 | ) 7 | 8 | type Manager struct { 9 | handler *redis.Client 10 | } 11 | 12 | func New() (*Manager, error) { 13 | m := &Manager{ 14 | handler: redis.NewClient( 15 | &redis.Options{ 16 | Addr: C.Addr + ":" + C.Port, 17 | Password: C.Password, 18 | }, 19 | ), 20 | } 21 | return m, m.handler.Ping(context.Background()).Err() 22 | } 23 | -------------------------------------------------------------------------------- /example/hello/internal/store/local/config.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/JirafaYe/example/hello/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Host string `json:"host"` 10 | Port string `json:"port"` 11 | User string `json:"user"` 12 | Name string `json:"name"` 13 | Password string `json:"password"` 14 | } 15 | 16 | func (c *Config) Key() string { 17 | return "tiktok/example/local" 18 | } 19 | 20 | var C Config 21 | 22 | func init() { 23 | err := config.ReadConfig(&C) 24 | if err != nil { 25 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/hello/internal/store/local/manager.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gorm.io/driver/mysql" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type Manager struct { 11 | handler *gorm.DB 12 | } 13 | 14 | func New() (*Manager, error) { 15 | db, err := gorm.Open( 16 | mysql.Open( 17 | fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", 18 | C.User, 19 | C.Password, 20 | C.Host, 21 | C.Port, 22 | C.Name, 23 | ), 24 | ), 25 | &gorm.Config{}, 26 | ) 27 | return &Manager{ 28 | handler: db, 29 | }, err 30 | } 31 | -------------------------------------------------------------------------------- /example/hello/pkg/token/jwt.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/dgrijalva/jwt-go" 7 | ) 8 | 9 | var jwtSecret = []byte("tiktok.example") 10 | 11 | type Claims struct { 12 | Id int32 `json:"id"` 13 | Username string `json:"username"` 14 | jwt.StandardClaims 15 | } 16 | 17 | //GenerateToken 签发用户Token 18 | func GenerateToken(id int32, username string) (string, error) { 19 | nowTime := time.Now() 20 | expireTime := nowTime.Add(7 * 24 * time.Hour) 21 | claims := Claims{ 22 | Id: id, 23 | Username: username, 24 | StandardClaims: jwt.StandardClaims{ 25 | ExpiresAt: expireTime.Unix(), 26 | Issuer: "tiktok.example", 27 | }, 28 | } 29 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 30 | token, err := tokenClaims.SignedString(jwtSecret) 31 | return token, err 32 | } 33 | 34 | //ParseToken 验证用户token 35 | func ParseToken(token string) (*Claims, error) { 36 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 37 | return jwtSecret, nil 38 | }) 39 | if tokenClaims != nil { 40 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 41 | return claims, nil 42 | } 43 | } 44 | return nil, err 45 | } 46 | -------------------------------------------------------------------------------- /example/user/.gitignore: -------------------------------------------------------------------------------- 1 | # use for java 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # use for golang 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Binaries for programs and plugins 28 | *.exe 29 | *.exe~ 30 | *.dll 31 | *.so 32 | *.dylib 33 | 34 | # Test binary, built with `go test -c` 35 | *.test 36 | 37 | # Output of the go coverage tool, specifically when used with LiteIDE 38 | *.out 39 | 40 | # Dependency directories (remove the comment below to include it) 41 | # vendor/ 42 | golang/main.go 43 | 44 | .vscode 45 | .idea 46 | # use for javascript 47 | # compiled output 48 | /dist 49 | /tmp 50 | /out-tsc 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | 64 | # nyc test coverage 65 | .nyc_output 66 | 67 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 68 | .grunt 69 | 70 | # Bower dependency directory (https://bower.io/) 71 | bower_components 72 | 73 | # node-waf configuration 74 | .lock-wscript 75 | 76 | # IDEs and editors 77 | .idea 78 | .project 79 | .classpath 80 | .c9/ 81 | *.launch 82 | .settings/ 83 | *.sublime-workspace 84 | 85 | # IDE - VSCode 86 | .vscode/* 87 | !.vscode/settings.json 88 | !.vscode/tasks.json 89 | !.vscode/launch.json 90 | !.vscode/extensions.json 91 | jsconfig.json 92 | Top Interview Questions/Solution.ts 93 | Top Interview Questions/Solution.java 94 | # misc 95 | .sass-cache 96 | connect.lock 97 | typings 98 | 99 | # Logs 100 | logs 101 | *.log 102 | npm-debug.log* 103 | yarn-debug.log* 104 | yarn-error.log* 105 | 106 | 107 | # Dependency directories 108 | node_modules/ 109 | jspm_packages/ 110 | 111 | # Optional npm cache directory 112 | .npm 113 | 114 | # Optional eslint cache 115 | .eslintcache 116 | 117 | # Optional REPL history 118 | .node_repl_history 119 | 120 | # Output of 'npm pack' 121 | *.tgz 122 | 123 | # Yarn Integrity file 124 | .yarn-integrity 125 | 126 | # dotenv environment variables file 127 | .env 128 | 129 | # next.js build output 130 | .next 131 | 132 | # Lerna 133 | lerna-debug.log 134 | 135 | # System Files 136 | .DS_Store 137 | Thumbs.db 138 | -------------------------------------------------------------------------------- /example/user/Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/example/user/Dockerfile -------------------------------------------------------------------------------- /example/user/Makefile: -------------------------------------------------------------------------------- 1 | protoc: 2 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 3 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 4 | protoc --go_out=./ --go-grpc_out=./ ./internal/service/proto/*.proto -------------------------------------------------------------------------------- /example/user/center/consul.go: -------------------------------------------------------------------------------- 1 | package center 2 | 3 | import ( 4 | "log" 5 | 6 | consul "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | var ( 10 | addr = "47.108.66.104:8500" 11 | client *consul.Client 12 | ) 13 | 14 | func init() { 15 | var err error 16 | config := consul.DefaultConfig() 17 | config.Address = addr 18 | client, err = consul.NewClient(config) 19 | if err != nil { 20 | log.Fatalf("failed to init consul: %v", err) 21 | } 22 | } 23 | 24 | func GetValue(key string) ([]byte, error) { 25 | res, _, err := client.KV().Get(key, nil) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return res.Value, nil 30 | } 31 | 32 | func Register(reg consul.AgentServiceRegistration) error { 33 | agent := client.Agent() 34 | 35 | return agent.ServiceRegister(®) 36 | } 37 | -------------------------------------------------------------------------------- /example/user/cmd/user/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | 9 | "github.com/JirafaYe/example/user/center" 10 | "github.com/JirafaYe/example/user/internal/server" 11 | "github.com/JirafaYe/example/user/internal/service" 12 | consul "github.com/hashicorp/consul/api" 13 | "google.golang.org/grpc" 14 | "google.golang.org/grpc/health/grpc_health_v1" 15 | ) 16 | 17 | var ( 18 | addr = flag.String("addr", "127.0.0.1", "The server address") 19 | port = flag.Int("port", 50051, "The server port") 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 25 | if err != nil { 26 | log.Fatalf("failed to listen: %v", err) 27 | } 28 | s := grpc.NewServer() 29 | service.RegisterUserServer(s, &server.UserSrv{}) 30 | // 健康检查 31 | grpc_health_v1.RegisterHealthServer(s, &server.HealthImpl{}) 32 | 33 | // 服务注册 34 | err = center.Register(consul.AgentServiceRegistration{ 35 | ID: "user-1", // 服务节点的名称 36 | Name: "user", // 服务名称 37 | Port: *port, // 服务端口 38 | Address: *addr, // 服务 IP 39 | Check: &consul.AgentServiceCheck{ // 健康检查 40 | Interval: "5s", // 健康检查间隔 41 | GRPC: fmt.Sprintf("%v:%v/%v", *addr, *port, "health"), 42 | DeregisterCriticalServiceAfter: "10s", // 注销时间,相当于过期时间 43 | }, 44 | }) 45 | if err != nil { 46 | log.Fatalf("failed to register service: %v", err) 47 | } 48 | 49 | log.Printf("server listening at %v", lis.Addr()) 50 | if err := s.Serve(lis); err != nil { 51 | log.Fatalf("failed to serve: %v", err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/user/config/api.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var handler *Manager 4 | 5 | func init() { 6 | handler = New() 7 | } 8 | 9 | func ReadConfig(c Config) error { 10 | return handler.ReadConfig(c) 11 | } 12 | -------------------------------------------------------------------------------- /example/user/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config interface { 4 | Key() string 5 | } 6 | -------------------------------------------------------------------------------- /example/user/config/manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/JirafaYe/example/user/center" 6 | "log" 7 | ) 8 | 9 | type Manager struct { 10 | } 11 | 12 | func New() *Manager { 13 | return &Manager{} 14 | } 15 | 16 | func (m *Manager) ReadConfig(c Config) error { 17 | log.Println("[CONFIG] READING", c.Key()) 18 | config, err := center.GetValue(c.Key()) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = json.Unmarshal(config, &c) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /example/user/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/example/user 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/go-redis/redis/v8 v8.11.5 8 | github.com/hashicorp/consul/api v1.18.0 9 | google.golang.org/grpc v1.52.0 10 | google.golang.org/protobuf v1.28.1 11 | gorm.io/driver/mysql v1.4.5 12 | gorm.io/gorm v1.24.3 13 | ) 14 | 15 | require ( 16 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 17 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 18 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 19 | github.com/fatih/color v1.9.0 // indirect 20 | github.com/go-sql-driver/mysql v1.7.0 // indirect 21 | github.com/golang/protobuf v1.5.2 // indirect 22 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 23 | github.com/hashicorp/go-hclog v0.12.0 // indirect 24 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 25 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 26 | github.com/hashicorp/golang-lru v0.5.4 // indirect 27 | github.com/hashicorp/serf v0.10.1 // indirect 28 | github.com/jinzhu/inflection v1.0.0 // indirect 29 | github.com/jinzhu/now v1.1.5 // indirect 30 | github.com/mattn/go-colorable v0.1.6 // indirect 31 | github.com/mattn/go-isatty v0.0.12 // indirect 32 | github.com/mitchellh/go-homedir v1.1.0 // indirect 33 | github.com/mitchellh/mapstructure v1.4.1 // indirect 34 | golang.org/x/net v0.4.0 // indirect 35 | golang.org/x/sys v0.3.0 // indirect 36 | golang.org/x/text v0.5.0 // indirect 37 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /example/user/internal/server/health.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/health/grpc_health_v1" 7 | ) 8 | 9 | type HealthImpl struct{} 10 | 11 | func (h *HealthImpl) Check(_ context.Context, _ *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { 12 | return &grpc_health_v1.HealthCheckResponse{ 13 | Status: grpc_health_v1.HealthCheckResponse_SERVING, 14 | }, nil 15 | } 16 | 17 | func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error { 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /example/user/internal/server/manager.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/JirafaYe/example/user/internal/store/cache" 5 | "github.com/JirafaYe/example/user/internal/store/local" 6 | "log" 7 | ) 8 | 9 | var m *Manager 10 | 11 | type Manager struct { 12 | localer *local.Manager 13 | cacher *cache.Manager 14 | } 15 | 16 | func init() { 17 | var err error 18 | localer, err := local.New() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | cacher, err := cache.New() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | m = &Manager{ 28 | localer: localer, 29 | cacher: cacher, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/user/internal/server/user.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/JirafaYe/example/user/internal/service" 8 | "github.com/JirafaYe/example/user/internal/store/local" 9 | "github.com/JirafaYe/example/user/pkg/ecrypto" 10 | "github.com/JirafaYe/example/user/pkg/token" 11 | "gorm.io/gorm" 12 | ) 13 | 14 | // UserSrv grpc中关于user相关的服务具体实现 15 | type UserSrv struct { 16 | service.UnimplementedUserServer 17 | } 18 | 19 | func (u *UserSrv) Login(_ context.Context, request *service.LoginRequest) (*service.LoginResponse, error) { 20 | username, password := request.Username, request.Password 21 | 22 | user, err := m.localer.SelectUserByUsername(username) 23 | if err != nil { 24 | if err == gorm.ErrRecordNotFound { 25 | return &service.LoginResponse{ 26 | Code: http.StatusOK, 27 | Msg: "用户名不存在", 28 | }, nil 29 | } 30 | return &service.LoginResponse{ 31 | Code: http.StatusInternalServerError, 32 | }, nil 33 | } 34 | 35 | if ecrypto.ToMd5(password) != user.Password { 36 | return &service.LoginResponse{ 37 | Code: http.StatusOK, 38 | Msg: "用户名或密码错误", 39 | }, nil 40 | } 41 | 42 | tokenString, err := token.GenerateToken(user.Id, user.Username) 43 | if err != nil { 44 | return &service.LoginResponse{ 45 | Code: http.StatusInternalServerError, 46 | }, nil 47 | } 48 | err = m.cacher.SetToken(tokenString) 49 | if err != nil { 50 | return &service.LoginResponse{ 51 | Code: http.StatusInternalServerError, 52 | }, nil 53 | } 54 | 55 | return &service.LoginResponse{ 56 | Code: http.StatusOK, 57 | Msg: "登录成功", 58 | Token: tokenString, 59 | }, nil 60 | } 61 | 62 | func (u *UserSrv) Register(_ context.Context, request *service.RegisterRequest) (*service.RegisterResponse, error) { 63 | username, password := request.Username, request.Password 64 | 65 | if len(username) < 6 || len(username) > 16 { 66 | return &service.RegisterResponse{ 67 | Code: http.StatusOK, 68 | Msg: "用户名非法", 69 | }, nil 70 | } 71 | 72 | if len(password) < 8 || len(password) > 16 { 73 | return &service.RegisterResponse{ 74 | Code: http.StatusOK, 75 | Msg: "密码非法", 76 | }, nil 77 | } 78 | 79 | user := local.User{ 80 | Username: username, 81 | Password: ecrypto.ToMd5(password), 82 | } 83 | err := m.localer.InsertUser(user) 84 | if err != nil { 85 | return &service.RegisterResponse{ 86 | Code: http.StatusOK, 87 | Msg: "用户名已存在", 88 | }, nil 89 | } 90 | 91 | return &service.RegisterResponse{ 92 | Code: http.StatusOK, 93 | Msg: "注册成功", 94 | UserId: user.Id, 95 | }, nil 96 | } 97 | 98 | func (u *UserSrv) Logout(_ context.Context, request *service.LogoutRequest) (*service.LogoutResponse, error) { 99 | tokenString := request.Token 100 | _, err := token.ParseToken(tokenString) 101 | if err != nil || !m.cacher.IsTokenExist(tokenString) { 102 | return &service.LogoutResponse{ 103 | Code: http.StatusOK, 104 | Msg: "token已经过期", 105 | }, nil 106 | } 107 | 108 | err = m.cacher.DelToken(tokenString) 109 | if err != nil { 110 | return &service.LogoutResponse{ 111 | Code: http.StatusInternalServerError, 112 | }, nil 113 | } 114 | 115 | return &service.LogoutResponse{ 116 | Code: http.StatusOK, 117 | Msg: "登出成功", 118 | }, nil 119 | } 120 | -------------------------------------------------------------------------------- /example/user/internal/service/proto/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./internal/service;service"; 4 | 5 | package service; 6 | 7 | message RegisterRequest { 8 | string username = 1; 9 | string password = 2; 10 | } 11 | 12 | message RegisterResponse { 13 | int32 code = 1; 14 | string msg = 2; 15 | int32 user_id = 3; 16 | } 17 | 18 | message LoginRequest { 19 | string username = 1; 20 | string password = 2; 21 | } 22 | 23 | message LoginResponse { 24 | int32 code = 1; 25 | string msg = 2; 26 | string token = 3; 27 | } 28 | 29 | message LogoutRequest { 30 | string token = 1; 31 | } 32 | 33 | message LogoutResponse { 34 | int32 code = 1; 35 | string msg = 2; 36 | } 37 | 38 | service User { 39 | rpc Login(LoginRequest) returns (LoginResponse) {} 40 | rpc Register(RegisterRequest) returns (RegisterResponse) {} 41 | rpc Logout(LogoutRequest) returns (LogoutResponse) {} 42 | } -------------------------------------------------------------------------------- /example/user/internal/store/cache/config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/JirafaYe/example/user/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Addr string `json:"addr"` 10 | Port string `json:"port"` 11 | Password string `json:"password"` 12 | } 13 | 14 | func (c *Config) Key() string { 15 | return "tiktok/example/cache" 16 | } 17 | 18 | var C Config 19 | 20 | func init() { 21 | err := config.ReadConfig(&C) 22 | if err != nil { 23 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/user/internal/store/cache/manager.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "github.com/go-redis/redis/v8" 6 | ) 7 | 8 | type Manager struct { 9 | handler *redis.Client 10 | } 11 | 12 | func New() (*Manager, error) { 13 | m := &Manager{ 14 | handler: redis.NewClient( 15 | &redis.Options{ 16 | Addr: C.Addr + ":" + C.Port, 17 | Password: C.Password, 18 | }, 19 | ), 20 | } 21 | return m, m.handler.Ping(context.Background()).Err() 22 | } 23 | -------------------------------------------------------------------------------- /example/user/internal/store/cache/token.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | func (m *Manager) SetToken(token string) error { 9 | return m.handler.Set(context.Background(), token, "", time.Hour*24*7).Err() 10 | } 11 | 12 | func (m *Manager) DelToken(token string) error { 13 | return m.handler.Del(context.Background(), token).Err() 14 | } 15 | 16 | func (m *Manager) IsTokenExist(token string) bool { 17 | num, err := m.handler.Exists(context.Background(), token).Result() 18 | return err == nil && num != 0 19 | } 20 | -------------------------------------------------------------------------------- /example/user/internal/store/local/config.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/JirafaYe/example/user/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Host string `json:"host"` 10 | Port string `json:"port"` 11 | User string `json:"user"` 12 | Name string `json:"name"` 13 | Password string `json:"password"` 14 | } 15 | 16 | func (c *Config) Key() string { 17 | return "tiktok/example/local" 18 | } 19 | 20 | var C Config 21 | 22 | func init() { 23 | err := config.ReadConfig(&C) 24 | if err != nil { 25 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/user/internal/store/local/manager.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gorm.io/driver/mysql" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type Manager struct { 11 | handler *gorm.DB 12 | } 13 | 14 | func New() (*Manager, error) { 15 | db, err := gorm.Open( 16 | mysql.Open( 17 | fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", 18 | C.User, 19 | C.Password, 20 | C.Host, 21 | C.Port, 22 | C.Name, 23 | ), 24 | ), 25 | &gorm.Config{}, 26 | ) 27 | return &Manager{ 28 | handler: db, 29 | }, err 30 | } 31 | -------------------------------------------------------------------------------- /example/user/internal/store/local/user.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | type User struct { 4 | Id int32 `json:"id"` 5 | Username string `json:"username"` 6 | Password string `json:"password"` 7 | } 8 | 9 | var user = "t_user" 10 | 11 | func (m *Manager) InsertUser(u User) error { 12 | return m.handler.Table(user).Create(&u).Error 13 | } 14 | 15 | func (m *Manager) SelectUserByUsername(username string) (User, error) { 16 | var u User 17 | err := m.handler.Table(user).Where("username = ?", username).Take(&u).Error 18 | return u, err 19 | } 20 | -------------------------------------------------------------------------------- /example/user/pkg/ecrypto/md5.go: -------------------------------------------------------------------------------- 1 | package ecrypto 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | ) 7 | 8 | func ToMd5(msg string) string { 9 | return fmt.Sprintf("%x", md5.Sum([]byte(msg))) 10 | } 11 | -------------------------------------------------------------------------------- /example/user/pkg/token/jwt.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | "time" 6 | ) 7 | 8 | var jwtSecret = []byte("tiktok.example") 9 | 10 | type Claims struct { 11 | Id int32 `json:"id"` 12 | Username string `json:"username"` 13 | jwt.StandardClaims 14 | } 15 | 16 | //GenerateToken 签发用户Token 17 | func GenerateToken(id int32, username string) (string, error) { 18 | nowTime := time.Now() 19 | expireTime := nowTime.Add(7 * 24 * time.Hour) 20 | claims := Claims{ 21 | Id: id, 22 | Username: username, 23 | StandardClaims: jwt.StandardClaims{ 24 | ExpiresAt: expireTime.Unix(), 25 | Issuer: "tiktok.example", 26 | }, 27 | } 28 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 29 | token, err := tokenClaims.SignedString(jwtSecret) 30 | return token, err 31 | } 32 | 33 | //ParseToken 验证用户token 34 | func ParseToken(token string) (*Claims, error) { 35 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 36 | return jwtSecret, nil 37 | }) 38 | if tokenClaims != nil { 39 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 40 | return claims, nil 41 | } 42 | } 43 | return nil, err 44 | } 45 | -------------------------------------------------------------------------------- /favorite/.gitignore: -------------------------------------------------------------------------------- 1 | # use for java 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # use for golang 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Binaries for programs and plugins 28 | *.exe 29 | *.exe~ 30 | *.dll 31 | *.so 32 | *.dylib 33 | 34 | # Test binary, built with `go test -c` 35 | *.test 36 | 37 | # Output of the go coverage tool, specifically when used with LiteIDE 38 | *.out 39 | 40 | # Dependency directories (remove the comment below to include it) 41 | # vendor/ 42 | golang/main.go 43 | 44 | .vscode 45 | .idea 46 | # use for javascript 47 | # compiled output 48 | /dist 49 | /tmp 50 | /out-tsc 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | 64 | # nyc test coverage 65 | .nyc_output 66 | 67 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 68 | .grunt 69 | 70 | # Bower dependency directory (https://bower.io/) 71 | bower_components 72 | 73 | # node-waf configuration 74 | .lock-wscript 75 | 76 | # IDEs and editors 77 | .idea 78 | .project 79 | .classpath 80 | .c9/ 81 | *.launch 82 | .settings/ 83 | *.sublime-workspace 84 | 85 | # IDE - VSCode 86 | .vscode/* 87 | !.vscode/settings.json 88 | !.vscode/tasks.json 89 | !.vscode/launch.json 90 | !.vscode/extensions.json 91 | jsconfig.json 92 | Top Interview Questions/Solution.ts 93 | Top Interview Questions/Solution.java 94 | # misc 95 | .sass-cache 96 | connect.lock 97 | typings 98 | 99 | # Logs 100 | logs 101 | *.log 102 | npm-debug.log* 103 | yarn-debug.log* 104 | yarn-error.log* 105 | 106 | 107 | # Dependency directories 108 | node_modules/ 109 | jspm_packages/ 110 | 111 | # Optional npm cache directory 112 | .npm 113 | 114 | # Optional eslint cache 115 | .eslintcache 116 | 117 | # Optional REPL history 118 | .node_repl_history 119 | 120 | # Output of 'npm pack' 121 | *.tgz 122 | 123 | # Yarn Integrity file 124 | .yarn-integrity 125 | 126 | # dotenv environment variables file 127 | .env 128 | 129 | # next.js build output 130 | .next 131 | 132 | # Lerna 133 | lerna-debug.log 134 | 135 | # System Files 136 | .DS_Store 137 | Thumbs.db 138 | -------------------------------------------------------------------------------- /favorite/Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/favorite/Dockerfile -------------------------------------------------------------------------------- /favorite/Makefile: -------------------------------------------------------------------------------- 1 | protoc: 2 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 3 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 4 | protoc --go_out=./ --go-grpc_out=./ ./internal/service/proto/*.proto -------------------------------------------------------------------------------- /favorite/center/consul.go: -------------------------------------------------------------------------------- 1 | package center 2 | 3 | import ( 4 | "log" 5 | 6 | consul "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | var ( 10 | addr = "47.108.66.104:8500" 11 | client *consul.Client 12 | ) 13 | 14 | func init() { 15 | var err error 16 | config := consul.DefaultConfig() 17 | config.Address = addr 18 | client, err = consul.NewClient(config) 19 | if err != nil { 20 | log.Fatalf("failed to init consul: %v", err) 21 | } 22 | } 23 | 24 | func GetValue(key string) ([]byte, error) { 25 | res, _, err := client.KV().Get(key, nil) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return res.Value, nil 30 | } 31 | 32 | func Register(reg consul.AgentServiceRegistration) error { 33 | agent := client.Agent() 34 | 35 | return agent.ServiceRegister(®) 36 | } 37 | -------------------------------------------------------------------------------- /favorite/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | 9 | "github.com/JirafaYe/favorite/center" 10 | "github.com/JirafaYe/favorite/internal/server" 11 | "github.com/JirafaYe/favorite/internal/service" 12 | consul "github.com/hashicorp/consul/api" 13 | "google.golang.org/grpc" 14 | "google.golang.org/grpc/health/grpc_health_v1" 15 | ) 16 | 17 | var ( 18 | addr = flag.String("addr", "127.0.0.1", "The server address") 19 | port = flag.Int("port", 9527, "The server port") 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 25 | if err != nil { 26 | log.Fatalf("failed to listen: %v", err) 27 | } 28 | s := grpc.NewServer() 29 | service.RegisterFavoriteServer(s, &server.FavoriteServer{}) 30 | // 健康检查 31 | grpc_health_v1.RegisterHealthServer(s, &server.HealthImpl{}) 32 | 33 | // 服务注册 34 | err = center.Register(consul.AgentServiceRegistration{ 35 | ID: "favorite-1", // 服务节点的名称 36 | Name: "favorite", // 服务名称 37 | Port: *port, // 服务端口 38 | Address: *addr, // 服务 IP 39 | //Check: &consul.AgentServiceCheck{ // 健康检查 40 | // Interval: "5s", // 健康检查间隔 41 | // GRPC: fmt.Sprintf("%v:%v/%v", *addr, *port, "health"), 42 | // DeregisterCriticalServiceAfter: "10s", // 注销时间,相当于过期时间 43 | //}, 44 | }) 45 | if err != nil { 46 | log.Fatalf("failed to register service: %v", err) 47 | } 48 | 49 | log.Printf("server listening at %v", lis.Addr()) 50 | if err := s.Serve(lis); err != nil { 51 | log.Fatalf("failed to serve: %v", err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /favorite/config/api.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var handler *Manager 4 | 5 | func init() { 6 | handler = New() 7 | } 8 | 9 | func ReadConfig(c Config) error { 10 | return handler.ReadConfig(c) 11 | } 12 | -------------------------------------------------------------------------------- /favorite/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config interface { 4 | Key() string 5 | } 6 | -------------------------------------------------------------------------------- /favorite/config/manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/JirafaYe/favorite/center" 6 | "log" 7 | ) 8 | 9 | type Manager struct { 10 | } 11 | 12 | func New() *Manager { 13 | return &Manager{} 14 | } 15 | 16 | func (m *Manager) ReadConfig(c Config) error { 17 | log.Println("[CONFIG] READING", c.Key()) 18 | config, err := center.GetValue(c.Key()) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = json.Unmarshal(config, &c) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /favorite/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/favorite 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 7 | github.com/go-redis/redis/v8 v8.11.5 8 | github.com/hashicorp/consul/api v1.18.0 9 | google.golang.org/grpc v1.52.0 10 | google.golang.org/protobuf v1.28.1 11 | gorm.io/driver/mysql v1.4.5 12 | gorm.io/gorm v1.24.3 13 | ) 14 | 15 | require ( 16 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 17 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 18 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 19 | github.com/fatih/color v1.9.0 // indirect 20 | github.com/go-sql-driver/mysql v1.7.0 // indirect 21 | github.com/golang/protobuf v1.5.2 // indirect 22 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 23 | github.com/hashicorp/go-hclog v0.12.0 // indirect 24 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 25 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 26 | github.com/hashicorp/golang-lru v0.5.4 // indirect 27 | github.com/hashicorp/serf v0.10.1 // indirect 28 | github.com/jinzhu/inflection v1.0.0 // indirect 29 | github.com/jinzhu/now v1.1.5 // indirect 30 | github.com/mattn/go-colorable v0.1.6 // indirect 31 | github.com/mattn/go-isatty v0.0.12 // indirect 32 | github.com/mitchellh/go-homedir v1.1.0 // indirect 33 | github.com/mitchellh/mapstructure v1.4.1 // indirect 34 | golang.org/x/net v0.4.0 // indirect 35 | golang.org/x/sys v0.3.0 // indirect 36 | golang.org/x/text v0.5.0 // indirect 37 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /favorite/internal/server/health.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/health/grpc_health_v1" 7 | ) 8 | 9 | type HealthImpl struct{} 10 | 11 | func (h *HealthImpl) Check(_ context.Context, _ *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { 12 | return &grpc_health_v1.HealthCheckResponse{ 13 | Status: grpc_health_v1.HealthCheckResponse_SERVING, 14 | }, nil 15 | } 16 | 17 | func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error { 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /favorite/internal/server/manager.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/JirafaYe/favorite/internal/store/cache" 5 | "github.com/JirafaYe/favorite/internal/store/local" 6 | "log" 7 | ) 8 | 9 | var m *Manager 10 | 11 | type Manager struct { 12 | localer *local.Manager 13 | cacher *cache.Manager 14 | } 15 | 16 | func init() { 17 | var err error 18 | localer, err := local.New() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | cacher, err := cache.New() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | m = &Manager{ 28 | localer: localer, 29 | cacher: cacher, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /favorite/internal/service/proto/favoriate.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | option go_package = "../service"; 3 | package service; 4 | 5 | // 1. 点赞 6 | message FavoriteActionRequest { 7 | string token = 1; // 用户鉴权token 8 | int64 video_id = 2; // 视频id 9 | int32 action_type = 3; // 1-点赞,2-取消点赞 10 | } 11 | 12 | message FavoriteActionResponse { 13 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 14 | optional string status_msg = 2; // 返回状态描述 15 | } 16 | 17 | // 2. 喜欢列表 18 | message FavoriteListRequest { 19 | int64 user_id = 1; // 用户id 20 | string token = 2; // 用户鉴权token 21 | } 22 | 23 | message FavoriteListResponse { 24 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 25 | optional string status_msg = 2; // 返回状态描述 26 | repeated VideoFavor video_list = 3; // 用户点赞视频列表 27 | } 28 | 29 | message VideoFavor { 30 | int64 id = 1; // 视频唯一标识 31 | UserFavor author = 2; // 视频作者信息 32 | string play_url = 3; // 视频播放地址 33 | string cover_url = 4; // 视频封面地址 34 | int64 favorite_count = 5; // 视频的点赞总数 35 | int64 comment_count = 6; // 视频的评论总数 36 | bool is_favorite = 7; // true-已点赞,false-未点赞 37 | string title = 8; // 视频标题 38 | } 39 | 40 | message UserFavor { 41 | int64 id = 1; // 用户id 42 | string name = 2; // 用户名称 43 | optional int64 follow_count = 3; // 关注总数 44 | optional int64 follower_count = 4; // 粉丝总数 45 | bool is_follow = 5; // true-已关注,false-未关注 46 | } 47 | 48 | service Favorite { 49 | rpc FavoriteAction(FavoriteActionRequest) returns (FavoriteActionResponse) {} 50 | rpc GetFavoriteList(FavoriteListRequest) returns (FavoriteListResponse) {} 51 | } -------------------------------------------------------------------------------- /favorite/internal/store/cache/config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/JirafaYe/favorite/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Addr string `json:"addr"` 10 | Port string `json:"port"` 11 | Password string `json:"password"` 12 | } 13 | 14 | func (c *Config) Key() string { 15 | return "tiktok/cache" 16 | } 17 | 18 | var C Config 19 | 20 | func init() { 21 | err := config.ReadConfig(&C) 22 | if err != nil { 23 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /favorite/internal/store/cache/manager.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "github.com/go-redis/redis/v8" 6 | ) 7 | 8 | type Manager struct { 9 | handler *redis.Client 10 | } 11 | 12 | func New() (*Manager, error) { 13 | m := &Manager{ 14 | handler: redis.NewClient( 15 | &redis.Options{ 16 | Addr: C.Addr + ":" + C.Port, 17 | Password: C.Password, 18 | }, 19 | ), 20 | } 21 | return m, m.handler.Ping(context.Background()).Err() 22 | } 23 | -------------------------------------------------------------------------------- /favorite/internal/store/cache/token.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | func (m *Manager) SetToken(token string) error { 9 | return m.handler.Set(context.Background(), token, "", time.Hour*24*7).Err() 10 | } 11 | 12 | func (m *Manager) DelToken(token string) error { 13 | return m.handler.Del(context.Background(), token).Err() 14 | } 15 | 16 | func (m *Manager) IsTokenExist(token string) bool { 17 | num, err := m.handler.Exists(context.Background(), token).Result() 18 | return err == nil && num != 0 19 | } 20 | -------------------------------------------------------------------------------- /favorite/internal/store/local/config.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/JirafaYe/favorite/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Host string `json:"host"` 10 | Port string `json:"port"` 11 | User string `json:"user"` 12 | Name string `json:"name"` 13 | Password string `json:"password"` 14 | } 15 | 16 | func (c *Config) Key() string { 17 | return "tiktok/local" 18 | } 19 | 20 | var C Config 21 | 22 | func init() { 23 | err := config.ReadConfig(&C) 24 | if err != nil { 25 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /favorite/internal/store/local/favorite.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/JirafaYe/favorite/internal/service" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type UserFavorite struct { 9 | gorm.Model 10 | UserId int64 `gorm:"primaryKey;autoIncrement:false"` 11 | VideoId int64 `gorm:"primaryKey;autoIncrement:false"` 12 | } 13 | 14 | type Video struct { 15 | gorm.Model 16 | PlayURL string `gorm:"column:play_url; type:varchar(200)"` 17 | CoverURL string `gorm:"column:cover_url; type:varchar(200)"` 18 | Title string `gorm:"column:title; type:varchar(200)"` 19 | FavoriteCount int64 `gorm:"column:favorite_count; type:bigint"` 20 | CommentCount int64 `gorm:"column:comment_count; type:bigint"` 21 | IsFavorite bool `gorm:"column:is_favorite; type:tinyint"` 22 | UserId int64 `gorm:"column:user_id; type:bigint"` 23 | } 24 | 25 | type User struct { 26 | gorm.Model 27 | Name string `gorm:"column:name; type:varchar(200)"` 28 | FollowerCount int64 `gorm:"column:follower_count; type:bigint"` 29 | FollowCount int64 `gorm:"column:follow_count; type:bigint"` 30 | } 31 | 32 | type UserFavoriteIndex struct { 33 | } 34 | 35 | var favoriteTableName = "t_favorite" 36 | var videoTableName = "t_video" 37 | var userTableName = "t_user" 38 | 39 | func (m *Manager) SelectUserFavorite(userId int64, videoId int64) (UserFavorite, error) { 40 | var u UserFavorite 41 | err := m.handler.Table(favoriteTableName).Where("user_id = ? AND video_id = ?", userId, videoId).Take(&u).Error 42 | return u, err 43 | } 44 | 45 | func (m *Manager) InsertUserFavorite(userId int64, videoId int64) error { 46 | u := UserFavorite{ 47 | UserId: userId, 48 | VideoId: videoId, 49 | } 50 | return m.handler.Table(favoriteTableName).Create(&u).Error 51 | } 52 | 53 | func (m *Manager) UpdateVideoLike(videoId int64, num int) error { 54 | v := &Video{} 55 | v.ID = uint(videoId) 56 | return m.handler.Table(videoTableName).Model(v).UpdateColumn("favorite_count", gorm.Expr("favorite_count + ?", num)).Error 57 | } 58 | 59 | func (m *Manager) DeleteUserFavorite(userId int64, videoId int64) error { 60 | // 物理删除 61 | return m.handler.Table(favoriteTableName).Unscoped().Delete(&UserFavorite{}, "user_id = ? AND video_id = ?", userId, videoId).Error 62 | } 63 | 64 | func (m *Manager) SelectLikesByUser(userId int64) ([]int64, error) { 65 | var userFavorites []int64 66 | err := m.handler.Table(favoriteTableName).Select("video_id").Where("user_id = ?", userId).Find(&userFavorites).Error 67 | return userFavorites, err 68 | } 69 | 70 | func (m *Manager) SelectVideos(videoIds []int64) ([]Video, error) { 71 | var videos []Video 72 | err := m.handler.Table(videoTableName).Where(videoIds).Find(&videos).Error 73 | return videos, err 74 | } 75 | 76 | func (m *Manager) SelectUsers(userIds []int64) ([]service.UserFavor, error) { 77 | var users []service.UserFavor 78 | err := m.handler.Table(userTableName).Where(userIds).Find(&users).Error 79 | return users, err 80 | } 81 | -------------------------------------------------------------------------------- /favorite/internal/store/local/manager.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gorm.io/driver/mysql" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type Manager struct { 11 | handler *gorm.DB 12 | } 13 | 14 | func New() (*Manager, error) { 15 | db, err := gorm.Open( 16 | mysql.Open( 17 | fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", 18 | C.User, 19 | C.Password, 20 | C.Host, 21 | C.Port, 22 | C.Name, 23 | ), 24 | ), 25 | &gorm.Config{}, 26 | ) 27 | return &Manager{ 28 | handler: db, 29 | }, err 30 | } 31 | -------------------------------------------------------------------------------- /favorite/pkg/token/jwt.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | "time" 6 | ) 7 | 8 | var jwtSecret = []byte("tiktok.user") 9 | 10 | type Claims struct { 11 | Id int64 `json:"id"` 12 | Username string `json:"username"` 13 | jwt.StandardClaims 14 | } 15 | 16 | //GenerateToken 签发用户Token 17 | func GenerateToken(id int64, username string) (string, error) { 18 | nowTime := time.Now() 19 | expireTime := nowTime.Add(7 * 24 * time.Hour) 20 | claims := Claims{ 21 | Id: id, 22 | Username: username, 23 | StandardClaims: jwt.StandardClaims{ 24 | ExpiresAt: expireTime.Unix(), 25 | Issuer: "tiktok.example", 26 | }, 27 | } 28 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 29 | token, err := tokenClaims.SignedString(jwtSecret) 30 | return token, err 31 | } 32 | 33 | //ParseToken 验证用户token 34 | func ParseToken(token string) (*Claims, error) { 35 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 36 | return jwtSecret, nil 37 | }) 38 | if tokenClaims != nil { 39 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 40 | return claims, nil 41 | } 42 | } 43 | return nil, err 44 | } 45 | -------------------------------------------------------------------------------- /feed/.gitignore: -------------------------------------------------------------------------------- 1 | # use for java 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # use for golang 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Binaries for programs and plugins 28 | *.exe 29 | *.exe~ 30 | *.dll 31 | *.so 32 | *.dylib 33 | 34 | # Test binary, built with `go test -c` 35 | *.test 36 | 37 | # Output of the go coverage tool, specifically when used with LiteIDE 38 | *.out 39 | 40 | # Dependency directories (remove the comment below to include it) 41 | # vendor/ 42 | golang/main.go 43 | 44 | .vscode 45 | .idea 46 | # use for javascript 47 | # compiled output 48 | /dist 49 | /tmp 50 | /out-tsc 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | 64 | # nyc test coverage 65 | .nyc_output 66 | 67 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 68 | .grunt 69 | 70 | # Bower dependency directory (https://bower.io/) 71 | bower_components 72 | 73 | # node-waf configuration 74 | .lock-wscript 75 | 76 | # IDEs and editors 77 | .idea 78 | .project 79 | .classpath 80 | .c9/ 81 | *.launch 82 | .settings/ 83 | *.sublime-workspace 84 | 85 | # IDE - VSCode 86 | .vscode/* 87 | !.vscode/settings.json 88 | !.vscode/tasks.json 89 | !.vscode/launch.json 90 | !.vscode/extensions.json 91 | jsconfig.json 92 | Top Interview Questions/Solution.ts 93 | Top Interview Questions/Solution.java 94 | # misc 95 | .sass-cache 96 | connect.lock 97 | typings 98 | 99 | # Logs 100 | logs 101 | *.log 102 | npm-debug.log* 103 | yarn-debug.log* 104 | yarn-error.log* 105 | 106 | 107 | # Dependency directories 108 | node_modules/ 109 | jspm_packages/ 110 | 111 | # Optional npm cache directory 112 | .npm 113 | 114 | # Optional eslint cache 115 | .eslintcache 116 | 117 | # Optional REPL history 118 | .node_repl_history 119 | 120 | # Output of 'npm pack' 121 | *.tgz 122 | 123 | # Yarn Integrity file 124 | .yarn-integrity 125 | 126 | # dotenv environment variables file 127 | .env 128 | 129 | # next.js build output 130 | .next 131 | 132 | # Lerna 133 | lerna-debug.log 134 | 135 | # System Files 136 | .DS_Store 137 | Thumbs.db 138 | -------------------------------------------------------------------------------- /feed/Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/feed/Dockerfile -------------------------------------------------------------------------------- /feed/center/consul.go: -------------------------------------------------------------------------------- 1 | package center 2 | 3 | import ( 4 | "log" 5 | 6 | consul "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | var ( 10 | addr = "47.108.66.104:8500" 11 | //addr = "192.168.3.126:8500" 12 | client *consul.Client 13 | ) 14 | 15 | func init() { 16 | var err error 17 | config := consul.DefaultConfig() 18 | config.Address = addr 19 | client, err = consul.NewClient(config) 20 | if err != nil { 21 | log.Fatalf("failed to init consul: %v", err) 22 | } 23 | } 24 | 25 | func GetValue(key string) ([]byte, error) { 26 | res, _, err := client.KV().Get(key, nil) 27 | if err != nil { 28 | return nil, err 29 | } 30 | return res.Value, nil 31 | } 32 | 33 | func Register(reg consul.AgentServiceRegistration) error { 34 | agent := client.Agent() 35 | 36 | return agent.ServiceRegister(®) 37 | } 38 | -------------------------------------------------------------------------------- /feed/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/JirafaYe/feed/center" 7 | "github.com/JirafaYe/feed/internal/server" 8 | "github.com/JirafaYe/feed/internal/service" 9 | consul "github.com/hashicorp/consul/api" 10 | "google.golang.org/grpc" 11 | "log" 12 | "net" 13 | ) 14 | 15 | var ( 16 | addr = flag.String("addr", "127.0.0.1", "The server address") 17 | port = flag.Int("port", 8899, "The server port") 18 | ) 19 | 20 | func main() { 21 | flag.Parse() 22 | 23 | // 服务注册 24 | err := center.Register(consul.AgentServiceRegistration{ 25 | ID: "feed-1", // 服务节点的名称 26 | Name: "feed", // 服务名称 27 | Port: *port, // 服务端口 28 | Address: *addr, // 服务 IP 29 | //Check: &consul.AgentServiceCheck{ // 健康检查 30 | // Interval: "5s", // 健康检查间隔 31 | // GRPC: fmt.Sprintf("%v:%v/%v", *addr, *port, "health"), 32 | // DeregisterCriticalServiceAfter: "10s", // 注销时间,相当于过期时间 33 | //}, 34 | }) 35 | if err != nil { 36 | log.Fatalf("failed to register service: %v", err) 37 | } 38 | 39 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 40 | if err != nil { 41 | log.Fatalf("failed to listen: %v", err) 42 | } 43 | s := grpc.NewServer() 44 | service.RegisterFeedServer(s, &server.FeedServer{}) 45 | // 健康检查 46 | //grpc_health_v1.RegisterHealthServer(s, &server.HealthImpl{}) 47 | //log.Printf("server listening at %v", lis.Addr()) 48 | if err := s.Serve(lis); err != nil { 49 | log.Fatalf("failed to serve: %v", err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /feed/config/api.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var handler *Manager 4 | 5 | func init() { 6 | handler = New() 7 | } 8 | 9 | func ReadConfig(c Config) error { 10 | return handler.ReadConfig(c) 11 | } 12 | -------------------------------------------------------------------------------- /feed/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config interface { 4 | Key() string 5 | } 6 | -------------------------------------------------------------------------------- /feed/config/manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/JirafaYe/feed/center" 6 | "log" 7 | ) 8 | 9 | type Manager struct { 10 | } 11 | 12 | func New() *Manager { 13 | return &Manager{} 14 | } 15 | 16 | func (m *Manager) ReadConfig(c Config) error { 17 | log.Println("[CONFIG] READING", c.Key()) 18 | config, err := center.GetValue(c.Key()) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = json.Unmarshal(config, &c) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /feed/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/feed 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.8.2 7 | github.com/go-redis/redis/v8 v8.11.5 8 | github.com/hashicorp/consul/api v1.18.0 9 | github.com/minio/minio-go/v7 v7.0.47 10 | google.golang.org/grpc v1.52.3 11 | google.golang.org/protobuf v1.28.1 12 | gorm.io/driver/mysql v1.4.5 13 | gorm.io/gorm v1.24.3 14 | ) 15 | 16 | require ( 17 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 18 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 19 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 20 | github.com/dustin/go-humanize v1.0.0 // indirect 21 | github.com/fatih/color v1.9.0 // indirect 22 | github.com/gin-contrib/sse v0.1.0 // indirect 23 | github.com/go-playground/locales v0.14.0 // indirect 24 | github.com/go-playground/universal-translator v0.18.0 // indirect 25 | github.com/go-playground/validator/v10 v10.11.1 // indirect 26 | github.com/go-sql-driver/mysql v1.7.0 // indirect 27 | github.com/goccy/go-json v0.9.11 // indirect 28 | github.com/golang/protobuf v1.5.2 // indirect 29 | github.com/google/uuid v1.3.0 // indirect 30 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 31 | github.com/hashicorp/go-hclog v0.12.0 // indirect 32 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 33 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 34 | github.com/hashicorp/golang-lru v0.5.4 // indirect 35 | github.com/hashicorp/serf v0.10.1 // indirect 36 | github.com/jinzhu/inflection v1.0.0 // indirect 37 | github.com/jinzhu/now v1.1.5 // indirect 38 | github.com/json-iterator/go v1.1.12 // indirect 39 | github.com/klauspost/compress v1.15.9 // indirect 40 | github.com/klauspost/cpuid/v2 v2.1.0 // indirect 41 | github.com/leodido/go-urn v1.2.1 // indirect 42 | github.com/mattn/go-colorable v0.1.6 // indirect 43 | github.com/mattn/go-isatty v0.0.16 // indirect 44 | github.com/minio/md5-simd v1.1.2 // indirect 45 | github.com/minio/sha256-simd v1.0.0 // indirect 46 | github.com/mitchellh/go-homedir v1.1.0 // indirect 47 | github.com/mitchellh/mapstructure v1.4.1 // indirect 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 49 | github.com/modern-go/reflect2 v1.0.2 // indirect 50 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 51 | github.com/rs/xid v1.4.0 // indirect 52 | github.com/sirupsen/logrus v1.9.0 // indirect 53 | github.com/ugorji/go/codec v1.2.7 // indirect 54 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect 55 | golang.org/x/net v0.5.0 // indirect 56 | golang.org/x/sys v0.4.0 // indirect 57 | golang.org/x/text v0.6.0 // indirect 58 | google.golang.org/genproto v0.0.0-20230131230820-1c016267d619 // indirect 59 | gopkg.in/ini.v1 v1.66.6 // indirect 60 | gopkg.in/yaml.v2 v2.4.0 // indirect 61 | ) 62 | -------------------------------------------------------------------------------- /feed/internal/server/feed.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/JirafaYe/feed/internal/service" 7 | "github.com/JirafaYe/feed/internal/store/obs" 8 | "github.com/JirafaYe/feed/pkg" 9 | "time" 10 | ) 11 | 12 | const ( 13 | MaxNumVideos = 2 14 | ) 15 | 16 | type FeedServer struct { 17 | service.UnimplementedFeedServer 18 | } 19 | 20 | // FeedVideo 21 | // TODO: 添加对token和time的错误处理 22 | func (f *FeedServer) FeedVideo(ctx context.Context, request *service.TiktokFeedRequest) (*service.TiktokFeedResponse, error) { 23 | var response service.TiktokFeedResponse 24 | response.StatusCode = 0 25 | response.StatusMsg = util.NewString("OK") 26 | t := time.UnixMilli(*request.LatestTime) 27 | videos := m.localer.QueryVideosBefore(MaxNumVideos, t) 28 | //for _, v := range videos { 29 | // fmt.Println(v) 30 | //} 31 | latestTime := time.Now().UnixMilli() 32 | for _, v := range videos { 33 | user := m.localer.QueryUserById(int64(v.UserId)) 34 | latestTime = util.Min(v.CreatedAt.UnixMilli(), latestTime) 35 | response.VideoList = append(response.VideoList, &service.VideoFeed{ 36 | Id: int64(v.ID), 37 | Author: &service.UserFeed{ 38 | Id: int64(user.ID), 39 | Name: user.Name, 40 | FollowerCount: &user.FollowerCount, 41 | FollowCount: &user.FollowCount, 42 | IsFollow: true, 43 | }, 44 | PlayUrl: obs.GetVideoPrefix() + v.PlayURL, 45 | CoverUrl: obs.GetImagePrefix() + v.CoverURL, 46 | CommentCount: v.CommentCount, 47 | FavoriteCount: v.FavoriteCount, 48 | IsFavorite: v.IsFavorite == 1, 49 | Title: v.Title, 50 | }) 51 | } 52 | fmt.Println("lat ", time.UnixMilli(latestTime)) 53 | response.NextTime = &latestTime 54 | return &response, nil 55 | } 56 | -------------------------------------------------------------------------------- /feed/internal/server/feed_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/JirafaYe/feed/internal/service" 7 | util "github.com/JirafaYe/feed/pkg" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/credentials/insecure" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func TestFeedServer_FeedVideo(t *testing.T) { 15 | conn, err := grpc.Dial("127.0.0.1:8899", grpc.WithTransportCredentials(insecure.NewCredentials())) 16 | if err != nil { 17 | fmt.Println(err) 18 | } 19 | defer conn.Close() 20 | 21 | c := service.NewFeedClient(conn) 22 | var req service.TiktokFeedRequest 23 | ti := time.Now().Unix() 24 | req.LatestTime = &ti 25 | req.Token = util.NewString("") 26 | r, err := c.FeedVideo(context.Background(), &req) 27 | fmt.Println(r) 28 | 29 | } 30 | 31 | func TestTime(t *testing.T) { 32 | tt := time.UnixMilli(1676973263380) 33 | fmt.Println(tt) 34 | } 35 | -------------------------------------------------------------------------------- /feed/internal/server/health.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/health/grpc_health_v1" 7 | ) 8 | 9 | type HealthImpl struct{} 10 | 11 | func (h *HealthImpl) Check(_ context.Context, _ *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { 12 | return &grpc_health_v1.HealthCheckResponse{ 13 | Status: grpc_health_v1.HealthCheckResponse_SERVING, 14 | }, nil 15 | } 16 | 17 | func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error { 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /feed/internal/server/manager.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | // 4 | import ( 5 | "github.com/JirafaYe/feed/internal/store/cache" 6 | "github.com/JirafaYe/feed/internal/store/local" 7 | "github.com/JirafaYe/feed/internal/store/obs" 8 | "log" 9 | ) 10 | 11 | var m *Manager 12 | 13 | type Manager struct { 14 | localer *local.Manager 15 | cacher *cache.Manager 16 | objectStorer *obs.Manager 17 | } 18 | 19 | func init() { 20 | var err error 21 | localer, err := local.New() 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | cacher, err := cache.New() 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | objectStorer, err := obs.New() 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | m = &Manager{ 36 | localer: localer, 37 | cacher: cacher, 38 | objectStorer: objectStorer, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /feed/internal/service/Makefile: -------------------------------------------------------------------------------- 1 | protoc: 2 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 3 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 4 | protoc --go_out=./ --go-grpc_out=./ ./proto/*.proto -------------------------------------------------------------------------------- /feed/internal/service/proto/feed.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "../service"; 4 | package service; 5 | message tiktok_feed_request{ 6 | optional int64 latest_time = 1; 7 | optional string token = 2; 8 | } 9 | 10 | message tiktok_feed_response { 11 | int32 status_code = 1; 12 | optional string status_msg = 2; 13 | repeated VideoFeed video_list = 3; 14 | optional int64 next_time = 4; 15 | } 16 | 17 | message VideoFeed { 18 | int64 id = 1; 19 | UserFeed author = 2; 20 | string play_url = 3; 21 | string cover_url = 4; 22 | int64 favorite_count = 5; 23 | int64 comment_count = 6; 24 | bool is_favorite = 7; 25 | string title = 8; 26 | } 27 | 28 | message UserFeed { 29 | int64 id = 1; 30 | string name = 2; 31 | optional int64 follow_count = 3; // 关注总数 32 | optional int64 follower_count = 4; // 粉丝总数 33 | bool is_follow = 5; 34 | } 35 | 36 | 37 | service Feed { 38 | rpc feedVideo (tiktok_feed_request) returns (tiktok_feed_response); 39 | } -------------------------------------------------------------------------------- /feed/internal/store/cache/config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/JirafaYe/feed/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Addr string `json:"addr"` 10 | Port string `json:"port"` 11 | Password string `json:"password"` 12 | } 13 | 14 | func (c *Config) Key() string { 15 | return "tiktok/cache" 16 | } 17 | 18 | var C Config 19 | 20 | func init() { 21 | err := config.ReadConfig(&C) 22 | if err != nil { 23 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /feed/internal/store/cache/manager.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "github.com/go-redis/redis/v8" 6 | ) 7 | 8 | type Manager struct { 9 | handler *redis.Client 10 | } 11 | 12 | func New() (*Manager, error) { 13 | m := &Manager{ 14 | handler: redis.NewClient( 15 | &redis.Options{ 16 | Addr: C.Addr + ":" + C.Port, 17 | Password: C.Password, 18 | }, 19 | ), 20 | } 21 | return m, m.handler.Ping(context.Background()).Err() 22 | } 23 | -------------------------------------------------------------------------------- /feed/internal/store/local/config.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/JirafaYe/feed/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Host string `json:"host"` 10 | Port string `json:"port"` 11 | User string `json:"user"` 12 | Name string `json:"name"` 13 | Password string `json:"password"` 14 | } 15 | 16 | func (c *Config) Key() string { 17 | return "tiktok/local" 18 | } 19 | 20 | var C Config 21 | 22 | func init() { 23 | err := config.ReadConfig(&C) 24 | if err != nil { 25 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /feed/internal/store/local/create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/JirafaYe/feed/internal/store/local" 5 | "gorm.io/driver/mysql" 6 | "gorm.io/gorm" 7 | "log" 8 | ) 9 | 10 | func main() { 11 | dsn := "root:xh020914@tcp(47.108.66.104:33306)/tiktok?charset=utf8mb4&parseTime=True&loc=Local" 12 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 13 | if err != nil { 14 | log.Println(err) 15 | } 16 | generateTestData(db) 17 | 18 | } 19 | 20 | func generateTable(db *gorm.DB) { 21 | err := db.Set("gorm:table_options", "CHARSET=utf8").AutoMigrate(&local.User{}, &local.Video{}) 22 | if err != nil { 23 | return 24 | } 25 | 26 | err = db.Migrator().RenameTable("users", "t_user") 27 | if err != nil { 28 | return 29 | } 30 | err = db.Migrator().RenameTable("videos", "t_video") 31 | if err != nil { 32 | return 33 | } 34 | } 35 | 36 | func generateTestData(db *gorm.DB) { 37 | var users []local.User 38 | var videos []local.Video 39 | users = append(users, local.User{Name: "zhangsan"}, local.User{Name: "lisi"}) 40 | videos = append(videos, local.Video{ 41 | PlayURL: "404 Not Found", 42 | CoverURL: "404 Not Found", 43 | Title: "Test Data", 44 | }) 45 | //db.Table("t_user").Create(&users) 46 | db.Table("t_video").Create(&videos) 47 | 48 | } 49 | -------------------------------------------------------------------------------- /feed/internal/store/local/feed.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | "time" 6 | ) 7 | 8 | type Video struct { 9 | gorm.Model 10 | PlayURL string `gorm:"column:play_url; type:varchar(200)"` 11 | CoverURL string `gorm:"column:cover_url; type:varchar(200)"` 12 | Title string `gorm:"column:title; type:varchar(200)"` 13 | FavoriteCount int64 `gorm:"column:favorite_count; type:bigint"` 14 | CommentCount int64 `gorm:"column:comment_count; type:bigint"` 15 | IsFavorite int16 `gorm:"column:is_favorite; type:tinyint"` 16 | UserId int64 `gorm:"column:user_id; type:bigint"` 17 | } 18 | 19 | type User struct { 20 | gorm.Model 21 | Name string `gorm:"column:name; type:varchar(200)"` 22 | FollowerCount int64 `gorm:"column:follower_count; type:bigint"` 23 | FollowCount int64 `gorm:"column:follow_count; type:bigint"` 24 | } 25 | 26 | func (m *Manager) QueryVideosBefore(n int, date time.Time) (videos []*Video) { 27 | db := m.handler.Table("t_video") 28 | db.Where("created_at < ?", date).Order("created_at desc").Limit(n).Find(&videos) 29 | return 30 | } 31 | 32 | func (m *Manager) QueryNameById(id int64) (name string) { 33 | db := m.handler.Table("t_user") 34 | var user User 35 | db.Where("id = ?", id).Find(&user) 36 | name = user.Name 37 | return 38 | } 39 | 40 | func (m *Manager) QueryUserById(id int64) (user User) { 41 | db := m.handler.Table("t_user") 42 | db.Where("id = ?", id).Find(&user) 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /feed/internal/store/local/manager.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gorm.io/driver/mysql" 7 | "gorm.io/gorm" 8 | "gorm.io/gorm/logger" 9 | "log" 10 | "os" 11 | ) 12 | 13 | type Manager struct { 14 | handler *gorm.DB 15 | } 16 | 17 | func New() (*Manager, error) { 18 | newLogger := logger.New( 19 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 20 | logger.Config{ 21 | SlowThreshold: 200, // 慢 SQL 阈值 22 | LogLevel: logger.Warn, // 日志级别 23 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 24 | Colorful: true, // 禁用彩色打印 25 | }, 26 | ) 27 | db, err := gorm.Open( 28 | mysql.Open( 29 | fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", 30 | C.User, 31 | C.Password, 32 | C.Host, 33 | C.Port, 34 | C.Name, 35 | ), 36 | ), 37 | &gorm.Config{ 38 | Logger: newLogger, 39 | }, 40 | ) 41 | return &Manager{ 42 | handler: db, 43 | }, err 44 | } 45 | -------------------------------------------------------------------------------- /feed/internal/store/obs/config.go: -------------------------------------------------------------------------------- 1 | package obs 2 | 3 | import ( 4 | "github.com/JirafaYe/feed/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Address string `json:"address"` 10 | SecretId string `json:"secret_id"` 11 | SecretKey string `json:"secret_key"` 12 | } 13 | 14 | func (c *Config) Key() string { 15 | return "tiktok/obs" 16 | } 17 | 18 | var C Config 19 | 20 | func init() { 21 | err := config.ReadConfig(&C) 22 | if err != nil { 23 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /feed/internal/store/obs/feed.go: -------------------------------------------------------------------------------- 1 | package obs 2 | 3 | func GetVideoPrefix() string { 4 | return "http://" + C.Address + "/videos/" 5 | } 6 | 7 | func GetImagePrefix() string { 8 | return "http://" + C.Address + "/images/" 9 | } 10 | -------------------------------------------------------------------------------- /feed/internal/store/obs/manager.go: -------------------------------------------------------------------------------- 1 | package obs 2 | 3 | import ( 4 | "github.com/minio/minio-go/v7" 5 | "github.com/minio/minio-go/v7/pkg/credentials" 6 | ) 7 | 8 | type Manager struct { 9 | handle *minio.Client 10 | } 11 | 12 | func New() (*Manager, error) { 13 | client, err := minio.New(C.Address, &minio.Options{ 14 | Creds: credentials.NewStaticV4(C.SecretId, C.SecretKey, ""), 15 | Secure: false}) 16 | return &Manager{client}, err 17 | } 18 | -------------------------------------------------------------------------------- /feed/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/JirafaYe/feed/internal/server" 5 | "github.com/JirafaYe/feed/internal/service" 6 | "google.golang.org/grpc" 7 | "log" 8 | "net" 9 | ) 10 | 11 | func main() { 12 | s := grpc.NewServer() 13 | service.RegisterFeedServer(s, &server.FeedServer{}) 14 | 15 | lis, err := net.Listen("tcp", ":8888") 16 | if err != nil { 17 | log.Fatalf("net.Listen err: %v", err) 18 | } 19 | 20 | err = s.Serve(lis) 21 | if err != nil { 22 | log.Fatalf("server.Serve err: %v", err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /feed/pkg/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func Min(a, b int64) int64 { 4 | if a < b { 5 | return a 6 | } else { 7 | return b 8 | } 9 | } 10 | 11 | func Max(a, b int64) int64 { 12 | if a > b { 13 | return a 14 | } else { 15 | return b 16 | } 17 | } 18 | 19 | func NewString(s string) *string { 20 | return &s 21 | } 22 | -------------------------------------------------------------------------------- /gateway/Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/gateway/Dockerfile -------------------------------------------------------------------------------- /gateway/Makefile: -------------------------------------------------------------------------------- 1 | protoc: 2 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 3 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 4 | protoc --go_out=./ --go-grpc_out=./ ./service/proto/*.proto -------------------------------------------------------------------------------- /gateway/api/benchmark_comment_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "net/http/httptest" 7 | "strconv" 8 | "testing" 9 | ) 10 | 11 | var engine *gin.Engine 12 | 13 | func TestMain(m *testing.M) { 14 | engine = route() 15 | m.Run() 16 | } 17 | 18 | var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjI2NzI3MDEwMDYxNDY0NzgwOCwidXNlcm5hbWUiOiIyNTgyMTAxNDU4QHFxLmNvbSIsImV4cCI6MTY3ODA4NTcyOCwiaXNzIjoidGlrdG9rLnVzZXIifQ.3mFOUFJBJtsnnJsg_uvBJpSixMf8mZtdFkeT3RvIebI" 19 | 20 | func BenchmarkCommentAction1Route(b *testing.B) { 21 | for i := 0; i < b.N; i++ { 22 | url := "http://localhost:8088/douyin/comment/action/?token=" + token + "&video_id=1&action_type=1&comment_text=%E4%BD%A0%E5%A5%BD" 23 | recorder := httptest.NewRecorder() 24 | request, _ := http.NewRequest("POST", url, nil) 25 | engine.ServeHTTP(recorder, request) 26 | } 27 | } 28 | 29 | func BenchmarkCommentAction2Route(b *testing.B) { 30 | for i := 0; i < b.N; i++ { 31 | url := "http://localhost:8088/douyin/comment/action/?token=" + token + "&video_id=1&action_type=2&comment_text=%E4%BD%A0%E5%A5%BD&comment_id=" 32 | url = url + strconv.Itoa(i+146) 33 | recorder := httptest.NewRecorder() 34 | request, _ := http.NewRequest("POST", url, nil) 35 | engine.ServeHTTP(recorder, request) 36 | } 37 | } 38 | 39 | func BenchmarkCommentIllegalActionRoute(b *testing.B) { 40 | for i := 0; i < b.N; i++ { 41 | url := "http://localhost:8088/douyin/comment/action/?token=" + token + "&video_id=1&action_type=3" 42 | recorder := httptest.NewRecorder() 43 | request, _ := http.NewRequest("POST", url, nil) 44 | engine.ServeHTTP(recorder, request) 45 | } 46 | } 47 | 48 | func BenchmarkCommentList(b *testing.B) { 49 | for i := 0; i < b.N; i++ { 50 | url := "http://localhost:8088/douyin/comment/list/?token=" + token + "&video_id=1" 51 | recorder := httptest.NewRecorder() 52 | request, _ := http.NewRequest("GET", url, nil) 53 | engine.ServeHTTP(recorder, request) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /gateway/api/comment_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/stretchr/testify/assert" 6 | "log" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | ) 11 | 12 | func TestCommentAction1Route(t *testing.T) { 13 | engine := route() 14 | 15 | url := "http://localhost:8088/douyin/comment/action/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjI2ODMzNTcyNzM4MzE1NDY4OCwidXNlcm5hbWUiOiJxcXFxcXEiLCJleHAiOjE2Nzc1NzgxODEsImlzcyI6InRpa3Rvay51c2VyIn0.444I8M8xaZdAr-PH8nriyRScUWyukmjGTg11Xfy1EOE&video_id=1&action_type=1&comment_text=%E4%BD%A0%E5%A5%BD" 16 | recorder := httptest.NewRecorder() 17 | request, _ := http.NewRequest("POST", url, nil) 18 | engine.ServeHTTP(recorder, request) 19 | 20 | var resp CommentOperationResponse 21 | json.Unmarshal(recorder.Body.Bytes(), &resp) 22 | 23 | assert.Equal(t, 200, recorder.Code) 24 | assert.Equal(t, 0, int(resp.StatusCode)) 25 | 26 | log.Println(resp) 27 | } 28 | 29 | func TestCommentAction2Route(t *testing.T) { 30 | engine := route() 31 | 32 | url := "http://localhost:8088/douyin/comment/action/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjI2ODMzNTcyNzM4MzE1NDY4OCwidXNlcm5hbWUiOiJxcXFxcXEiLCJleHAiOjE2Nzc1NzgxODEsImlzcyI6InRpa3Rvay51c2VyIn0.444I8M8xaZdAr-PH8nriyRScUWyukmjGTg11Xfy1EOE&video_id=1&action_type=2&comment_text=%E4%BD%A0%E5%A5%BD&comment_id=140" 33 | recorder := httptest.NewRecorder() 34 | request, _ := http.NewRequest("POST", url, nil) 35 | engine.ServeHTTP(recorder, request) 36 | 37 | var resp CommentOperationResponse 38 | json.Unmarshal(recorder.Body.Bytes(), &resp) 39 | 40 | assert.Equal(t, 200, recorder.Code) 41 | assert.Equal(t, 0, int(resp.StatusCode)) 42 | 43 | log.Println(resp) 44 | } 45 | 46 | func TestCommentActionIllegalRoute(t *testing.T) { 47 | engine := route() 48 | 49 | url := "http://localhost:8088/douyin/comment/action/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjI2ODMzNTcyNzM4MzE1NDY4OCwidXNlcm5hbWUiOiJxcXFxcXEiLCJleHAiOjE2Nzc1NzgxODEsImlzcyI6InRpa3Rvay51c2VyIn0.444I8M8xaZdAr-PH8nriyRScUWyukmjGTg11Xfy1EOE&video_id=1&action_type=3" 50 | recorder := httptest.NewRecorder() 51 | request, _ := http.NewRequest("POST", url, nil) 52 | engine.ServeHTTP(recorder, request) 53 | 54 | var resp CommentOperationResponse 55 | json.Unmarshal(recorder.Body.Bytes(), &resp) 56 | 57 | assert.Equal(t, 500, recorder.Code) 58 | assert.Equal(t, 500, int(resp.StatusCode)) 59 | 60 | log.Println(resp) 61 | } 62 | 63 | func TestCommentListRoute(t *testing.T) { 64 | engine := route() 65 | 66 | url := "http://localhost:8088/douyin/comment/list/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjI2ODMzNTcyNzM4MzE1NDY4OCwidXNlcm5hbWUiOiJxcXFxcXEiLCJleHAiOjE2Nzc1NzgxODEsImlzcyI6InRpa3Rvay51c2VyIn0.444I8M8xaZdAr-PH8nriyRScUWyukmjGTg11Xfy1EOE&video_id=1" 67 | recorder := httptest.NewRecorder() 68 | request, _ := http.NewRequest("GET", url, nil) 69 | engine.ServeHTTP(recorder, request) 70 | 71 | var resp ListCommentResponse 72 | json.Unmarshal(recorder.Body.Bytes(), &resp) 73 | 74 | assert.Equal(t, 200, recorder.Code) 75 | assert.Equal(t, 0, int(resp.StatusCode)) 76 | 77 | log.Println(resp) 78 | } 79 | -------------------------------------------------------------------------------- /gateway/api/config.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/JirafaYe/gateway/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | IP string `json:"ip"` 10 | Port string `json:"port"` 11 | } 12 | 13 | func (c *Config) Key() string { 14 | return "tiktok/api" 15 | } 16 | 17 | var C Config 18 | 19 | func init() { 20 | err := config.ReadConfig(&C) 21 | if err != nil { 22 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gateway/api/favorite.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "github.com/JirafaYe/gateway/center" 6 | "github.com/JirafaYe/gateway/service" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | "strconv" 10 | ) 11 | 12 | func (m *Manager) RouteFavorite() { 13 | m.handler.POST("/douyin/favorite/action/", m.favoriteAction) 14 | m.handler.GET("/douyin/favorite/list/", m.favoriteList) 15 | } 16 | 17 | func (m *Manager) favoriteAction(ctx *gin.Context) { 18 | token := ctx.Query("token") 19 | vId := ctx.Query("video_id") 20 | aType := ctx.Query("action_type") 21 | 22 | if token == "" || vId == "" || aType == "" { 23 | ctx.JSON(http.StatusInternalServerError, gin.H{ 24 | "status_code": http.StatusInternalServerError, 25 | "status_msg": "Invalid request", 26 | }) 27 | return 28 | } 29 | 30 | videoId, _ := strconv.Atoi(vId) 31 | actionType, _ := strconv.Atoi(aType) 32 | 33 | // connect grpc server 34 | conn, err := center.Resolver("favorite") 35 | if err != nil { 36 | ctx.JSON(http.StatusInternalServerError, gin.H{ 37 | "status_code": http.StatusInternalServerError, 38 | "status_msg": err.Error(), 39 | }) 40 | return 41 | } 42 | defer conn.Close() 43 | 44 | client := service.NewFavoriteClient(conn) 45 | 46 | res, err := client.FavoriteAction(context.Background(), &service.FavoriteActionRequest{ 47 | Token: token, 48 | VideoId: int64(videoId), 49 | ActionType: int32(actionType), 50 | }) 51 | if err != nil { 52 | ctx.JSON(http.StatusInternalServerError, gin.H{ 53 | "status_code": http.StatusInternalServerError, 54 | "status_msg": err.Error(), 55 | }) 56 | return 57 | } 58 | 59 | ctx.JSON(http.StatusOK, gin.H{ 60 | "status_code": res.StatusCode, 61 | "status_msg": res.StatusMsg, 62 | }) 63 | } 64 | 65 | func (m *Manager) favoriteList(ctx *gin.Context) { 66 | userId, _ := strconv.Atoi(ctx.Query("user_id")) 67 | token := ctx.Query("token") 68 | 69 | // connect grpc server 70 | conn, err := center.Resolver("favorite") 71 | if err != nil { 72 | ctx.JSON(http.StatusInternalServerError, gin.H{ 73 | "status_code": http.StatusInternalServerError, 74 | "status_msg": err.Error(), 75 | }) 76 | return 77 | } 78 | defer conn.Close() 79 | 80 | client := service.NewFavoriteClient(conn) 81 | 82 | res, err := client.GetFavoriteList(context.Background(), &service.FavoriteListRequest{ 83 | UserId: int64(userId), 84 | Token: token, 85 | }) 86 | if err != nil { 87 | ctx.JSON(http.StatusInternalServerError, gin.H{ 88 | "status_code": http.StatusInternalServerError, 89 | "status_msg": err.Error(), 90 | }) 91 | return 92 | } 93 | 94 | ctx.JSON(http.StatusOK, gin.H{ 95 | "status_code": res.StatusCode, 96 | "status_msg": res.StatusMsg, 97 | "video_list": res.VideoList, 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /gateway/api/feed.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "context" 5 | "github.com/JirafaYe/gateway/center" 6 | "github.com/JirafaYe/gateway/service" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | type FeedResponse struct { 14 | NextTime *int64 `json:"next_time"` // 本次返回的视频中,发布最早的时间,作为下次请求时的latest_time 15 | StatusCode int64 `json:"status_code"` // 状态码,0-成功,其他值-失败 16 | StatusMsg *string `json:"status_msg"` // 返回状态描述 17 | VideoList []*Video `json:"video_list"` // 视频列表 18 | } 19 | 20 | // Video 21 | type Video struct { 22 | Author User `json:"author"` // 视频作者信息 23 | CommentCount int64 `json:"comment_count"` // 视频的评论总数 24 | CoverURL string `json:"cover_url"` // 视频封面地址 25 | FavoriteCount int64 `json:"favorite_count"` // 视频的点赞总数 26 | ID int64 `json:"id"` // 视频唯一标识 27 | IsFavorite bool `json:"is_favorite"` // true-已点赞,false-未点赞 28 | PlayURL string `json:"play_url"` // 视频播放地址 29 | Title string `json:"title"` // 视频标题 30 | } 31 | 32 | // 视频作者信息 33 | // 34 | // User 35 | type User struct { 36 | FollowCount int64 `json:"follow_count"` // 关注总数 37 | FollowerCount int64 `json:"follower_count"` // 粉丝总数 38 | ID int64 `json:"id"` // 用户id 39 | IsFollow bool `json:"is_follow"` // true-已关注,false-未关注 40 | Name string `json:"name"` // 用户名称 41 | } 42 | 43 | func (m *Manager) RouteFeed() { 44 | m.handler.GET("/douyin/feed/", m.feed) 45 | } 46 | 47 | func (m *Manager) feed(c *gin.Context) { 48 | latestTimeStr := c.Query("latest_time") 49 | latestTime, err := strconv.ParseInt(latestTimeStr, 10, 64) 50 | if latestTime <= 0 { 51 | latestTime = time.Now().UnixMilli() 52 | } 53 | token := c.Query("token") 54 | 55 | // 该函数会进行自动负载均衡并返回一个*grpc.ClientConn 56 | conn, err := center.Resolver("feed") 57 | if err != nil { 58 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 59 | return 60 | } 61 | defer conn.Close() 62 | 63 | client := service.NewFeedClient(conn) 64 | rpcResponse, _ := client.FeedVideo(context.Background(), &service.TiktokFeedRequest{ 65 | Token: &token, 66 | LatestTime: &latestTime, 67 | }) 68 | 69 | response := FeedResponse{ 70 | StatusCode: int64(rpcResponse.StatusCode), 71 | StatusMsg: rpcResponse.StatusMsg, 72 | NextTime: rpcResponse.NextTime, 73 | } 74 | response.VideoList = make([]*Video, len(rpcResponse.VideoList)) 75 | for i, v := range rpcResponse.VideoList { 76 | response.VideoList[i] = &Video{ 77 | ID: v.Id, 78 | Author: User{ 79 | ID: v.Author.Id, 80 | Name: v.Author.Name, 81 | FollowCount: *v.Author.FollowCount, 82 | FollowerCount: *v.Author.FollowerCount, 83 | IsFollow: v.Author.IsFollow, 84 | }, 85 | PlayURL: v.PlayUrl, 86 | CoverURL: v.CoverUrl, 87 | FavoriteCount: v.FavoriteCount, 88 | CommentCount: v.CommentCount, 89 | IsFavorite: v.IsFavorite, 90 | Title: v.Title, 91 | } 92 | } 93 | 94 | c.JSON(int(response.StatusCode), response) 95 | } 96 | -------------------------------------------------------------------------------- /gateway/api/manager.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-contrib/cors" 5 | "github.com/gin-gonic/gin" 6 | "log" 7 | "reflect" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type Manager struct { 13 | handler *gin.Engine 14 | } 15 | 16 | func New() *Manager { 17 | return &Manager{ 18 | handler: gin.Default(), 19 | } 20 | } 21 | 22 | func (m *Manager) Run() error { 23 | err := m.load() 24 | if err != nil { 25 | return err 26 | } 27 | return m.handler.Run(C.IP + ":" + C.Port) 28 | } 29 | 30 | func (m *Manager) load() (err error) { 31 | err = m.loadPlugin() 32 | if err != nil { 33 | return 34 | } 35 | return m.loadRoute() 36 | } 37 | 38 | func (m *Manager) loadPlugin() error { 39 | m.loadCORS() 40 | return nil 41 | } 42 | 43 | func (m *Manager) loadCORS() error { 44 | // iris cors 45 | //crs := cors.New(cors.Options{ 46 | // AllowedOrigins: []string{"*"}, 47 | // AllowedMethods: []string{"POST", "GET", "OPTIONS", "DELETE"}, 48 | // MaxAge: 3600, 49 | // AllowedHeaders: []string{"*"}, 50 | // AllowCredentials: true, 51 | //}) 52 | //m.handler.UseRouter(crs) 53 | 54 | // gin cors 55 | crs := cors.New(cors.Config{ 56 | AllowOrigins: []string{"*"}, 57 | AllowMethods: []string{"PUT", "PATCH", "POST", "GET", "DELETE"}, 58 | AllowHeaders: []string{"Origin", "Authorization", "Content-Type"}, 59 | ExposeHeaders: []string{"Content-Type"}, 60 | AllowCredentials: true, 61 | AllowOriginFunc: func(origin string) bool { 62 | return true 63 | }, 64 | MaxAge: 24 * time.Hour, 65 | }) 66 | m.handler.Use(crs) 67 | return nil 68 | } 69 | 70 | func (m *Manager) loadRoute() error { 71 | t := reflect.TypeOf(m) 72 | for i := 0; i < t.NumMethod(); i++ { 73 | f := t.Method(i) 74 | if strings.HasPrefix(f.Name, "Route") && 75 | f.Type.NumOut() == 0 && 76 | f.Type.NumIn() == 1 { 77 | log.Println("[GATEWAY] LOAD ROUTE:", f.Name) 78 | f.Func.Call([]reflect.Value{ 79 | reflect.ValueOf(m), 80 | }) 81 | } 82 | } 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /gateway/center/consul.go: -------------------------------------------------------------------------------- 1 | package center 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | consul "github.com/hashicorp/consul/api" 8 | _ "github.com/mbobakov/grpc-consul-resolver" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/credentials/insecure" 11 | ) 12 | 13 | var ( 14 | addr = "47.108.66.104:8500" 15 | client *consul.Client 16 | ) 17 | 18 | func init() { 19 | var err error 20 | config := consul.DefaultConfig() 21 | config.Address = addr 22 | client, err = consul.NewClient(config) 23 | if err != nil { 24 | log.Fatalf("failed to init consul: %v", err) 25 | } 26 | } 27 | 28 | // GetValue consul KV读取 29 | func GetValue(key string) ([]byte, error) { 30 | res, _, err := client.KV().Get(key, nil) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return res.Value, nil 35 | } 36 | 37 | // Register consul服务注册 38 | func Register(reg consul.AgentServiceRegistration) error { 39 | agent := client.Agent() 40 | 41 | return agent.ServiceRegister(®) 42 | } 43 | 44 | // Resolver 对服务进行负载均衡 45 | func Resolver(name string) (*grpc.ClientConn, error) { 46 | conn, err := grpc.Dial( 47 | // name 拉取的服务名 wait=14s 等待时间 tag=manual 筛选条件 48 | fmt.Sprintf("consul://%v/%v?wait=14s&tag=manual", addr, name), 49 | grpc.WithTransportCredentials(insecure.NewCredentials()), 50 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), 51 | ) 52 | if err != nil { 53 | return nil, err 54 | } 55 | return conn, nil 56 | } 57 | -------------------------------------------------------------------------------- /gateway/config/api.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var handler *Manager 4 | 5 | func init() { 6 | handler = New() 7 | } 8 | 9 | func ReadConfig(c Config) error { 10 | return handler.ReadConfig(c) 11 | } 12 | -------------------------------------------------------------------------------- /gateway/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config interface { 4 | Key() string 5 | } 6 | -------------------------------------------------------------------------------- /gateway/config/manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/JirafaYe/gateway/center" 6 | "log" 7 | ) 8 | 9 | type Manager struct { 10 | } 11 | 12 | func New() *Manager { 13 | return &Manager{} 14 | } 15 | 16 | func (m *Manager) ReadConfig(c Config) error { 17 | log.Println("[CONFIG] READING", c.Key()) 18 | config, err := center.GetValue(c.Key()) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = json.Unmarshal(config, &c) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /gateway/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/gateway 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gin-contrib/cors v1.4.0 7 | github.com/gin-gonic/gin v1.8.2 8 | github.com/hashicorp/consul/api v1.18.0 9 | github.com/mbobakov/grpc-consul-resolver v1.4.4 10 | github.com/stretchr/testify v1.8.1 11 | google.golang.org/grpc v1.52.0 12 | google.golang.org/protobuf v1.28.1 13 | ) 14 | 15 | require ( 16 | github.com/armon/go-metrics v0.3.2 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/fatih/color v1.9.0 // indirect 19 | github.com/gin-contrib/sse v0.1.0 // indirect 20 | github.com/go-playground/form v3.1.4+incompatible // indirect 21 | github.com/go-playground/locales v0.14.1 // indirect 22 | github.com/go-playground/universal-translator v0.18.0 // indirect 23 | github.com/go-playground/validator/v10 v10.11.1 // indirect 24 | github.com/goccy/go-json v0.10.0 // indirect 25 | github.com/golang/protobuf v1.5.2 // indirect 26 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 27 | github.com/hashicorp/go-hclog v0.12.0 // indirect 28 | github.com/hashicorp/go-immutable-radix v1.1.0 // indirect 29 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 30 | github.com/hashicorp/golang-lru v0.5.4 // indirect 31 | github.com/hashicorp/serf v0.10.1 // indirect 32 | github.com/jpillora/backoff v1.0.0 // indirect 33 | github.com/json-iterator/go v1.1.12 // indirect 34 | github.com/leodido/go-urn v1.2.1 // indirect 35 | github.com/mattn/go-colorable v0.1.6 // indirect 36 | github.com/mattn/go-isatty v0.0.17 // indirect 37 | github.com/mitchellh/go-homedir v1.1.0 // indirect 38 | github.com/mitchellh/mapstructure v1.4.1 // indirect 39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 40 | github.com/modern-go/reflect2 v1.0.2 // indirect 41 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 42 | github.com/pkg/errors v0.9.1 // indirect 43 | github.com/pmezard/go-difflib v1.0.0 // indirect 44 | github.com/ugorji/go/codec v1.2.8 // indirect 45 | golang.org/x/crypto v0.5.0 // indirect 46 | golang.org/x/net v0.5.0 // indirect 47 | golang.org/x/sys v0.4.0 // indirect 48 | golang.org/x/text v0.6.0 // indirect 49 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect 50 | gopkg.in/yaml.v2 v2.4.0 // indirect 51 | gopkg.in/yaml.v3 v3.0.1 // indirect 52 | ) 53 | -------------------------------------------------------------------------------- /gateway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/JirafaYe/gateway/api" 5 | "log" 6 | ) 7 | 8 | func init() { 9 | log.SetFlags(log.Llongfile) 10 | } 11 | 12 | func main() { 13 | app := api.New() 14 | err := app.Run() 15 | if err != nil { 16 | log.Fatalf("failed to run server, %v", err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gateway/service/proto/comment.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "../"; 4 | 5 | package service; 6 | 7 | message CommentRequest{ 8 | int64 author_id=1; 9 | int32 action_type=2; 10 | int32 comment_id=3; 11 | int32 video_id=4; 12 | string msg=5; 13 | string token=6; 14 | } 15 | 16 | message ListRequest{ 17 | int32 video_id=1; 18 | string token=2; 19 | } 20 | 21 | message CommentOperationResponse{ 22 | int32 status_code=1; 23 | string status_msg=2; 24 | CommentBody comment=3; 25 | } 26 | 27 | message ListCommentsResponse{ 28 | int32 status_code=1; 29 | string status_msg=2; 30 | repeated CommentBody comment_list=3; 31 | } 32 | 33 | message CommentBody{ 34 | int32 id=1; 35 | CommentUser user=2; 36 | string content=3; 37 | string create_date=4; 38 | } 39 | 40 | message CommentUser{ 41 | int64 id = 1; 42 | string name = 2; 43 | int64 follow_count = 3; // 关注总数 44 | int64 follower_count = 4; // 粉丝总数 45 | bool is_follow = 5; 46 | } 47 | 48 | service Comment{ 49 | rpc OperateComment(CommentRequest) returns (CommentOperationResponse) {} 50 | rpc ListComments(ListRequest) returns (ListCommentsResponse) {} 51 | } -------------------------------------------------------------------------------- /gateway/service/proto/favoriate.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | option go_package = "../service"; 3 | package service; 4 | 5 | // 1. 点赞 6 | message FavoriteActionRequest { 7 | string token = 1; // 用户鉴权token 8 | int64 video_id = 2; // 视频id 9 | int32 action_type = 3; // 1-点赞,2-取消点赞 10 | } 11 | 12 | message FavoriteActionResponse { 13 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 14 | optional string status_msg = 2; // 返回状态描述 15 | } 16 | 17 | // 2. 喜欢列表 18 | message FavoriteListRequest { 19 | int64 user_id = 1; // 用户id 20 | string token = 2; // 用户鉴权token 21 | } 22 | 23 | message FavoriteListResponse { 24 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 25 | optional string status_msg = 2; // 返回状态描述 26 | repeated VideoFavor video_list = 3; // 用户点赞视频列表 27 | } 28 | 29 | message VideoFavor { 30 | int64 id = 1; // 视频唯一标识 31 | UserFavor author = 2; // 视频作者信息 32 | string play_url = 3; // 视频播放地址 33 | string cover_url = 4; // 视频封面地址 34 | int64 favorite_count = 5; // 视频的点赞总数 35 | int64 comment_count = 6; // 视频的评论总数 36 | bool is_favorite = 7; // true-已点赞,false-未点赞 37 | string title = 8; // 视频标题 38 | } 39 | 40 | message UserFavor { 41 | int64 id = 1; // 用户id 42 | string name = 2; // 用户名称 43 | optional int64 follow_count = 3; // 关注总数 44 | optional int64 follower_count = 4; // 粉丝总数 45 | bool is_follow = 5; // true-已关注,false-未关注 46 | } 47 | 48 | service Favorite { 49 | rpc FavoriteAction(FavoriteActionRequest) returns (FavoriteActionResponse) {} 50 | rpc GetFavoriteList(FavoriteListRequest) returns (FavoriteListResponse) {} 51 | } -------------------------------------------------------------------------------- /gateway/service/proto/feed.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "../service"; 4 | package service; 5 | message tiktok_feed_request{ 6 | optional int64 latest_time = 1; 7 | optional string token = 2; 8 | } 9 | 10 | message tiktok_feed_response { 11 | int32 status_code = 1; 12 | optional string status_msg = 2; 13 | repeated Video video_list = 3; 14 | optional int64 next_time = 4; 15 | } 16 | 17 | message Video { 18 | int64 id = 1; 19 | User author = 2; 20 | string play_url = 3; 21 | string cover_url = 4; 22 | int64 favorite_count = 5; 23 | int64 comment_count = 6; 24 | bool is_favorite = 7; 25 | string title = 8; 26 | } 27 | 28 | message User { 29 | int64 id = 1; 30 | string name = 2; 31 | optional int64 follow_count = 3; // 关注总数 32 | optional int64 follower_count = 4; // 粉丝总数 33 | bool is_follow = 5; 34 | } 35 | 36 | 37 | service Feed { 38 | rpc feedVideo (tiktok_feed_request) returns (tiktok_feed_response); 39 | } -------------------------------------------------------------------------------- /gateway/service/proto/publish.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "../service"; 4 | 5 | package service; 6 | 7 | // publish action, POST 8 | 9 | message PublishActionRequest { 10 | string token = 1; 11 | bytes data = 2; 12 | string title = 3; 13 | } 14 | 15 | message PublishActionResponse { 16 | int32 status_code = 1; 17 | //optional string status_msg = 2; 18 | string status_msg = 2; 19 | } 20 | 21 | // GET list 22 | 23 | message PublishListRequest { 24 | int64 user_id = 1; 25 | string token = 2; 26 | } 27 | 28 | message PublishListResponse { 29 | int32 status_code = 1; 30 | //optional string status_msg = 2; 31 | string status_msg = 2; 32 | repeated PubVideo video_list = 3; 33 | } 34 | 35 | message PubVideo { 36 | int64 id = 1; 37 | PubUser author = 2; 38 | string play_url = 3; 39 | string cover_url = 4; 40 | int64 favorite_count = 5; 41 | int64 comment_count = 6; 42 | bool is_favorite = 7; 43 | string title = 8; 44 | } 45 | 46 | message PubUser { 47 | int64 id = 1; 48 | string name = 2; 49 | // optional int64 follow_count = 3; 50 | // optional int64 follower_count = 4; 51 | int64 follow_count = 3; 52 | int64 follower_count = 4; 53 | bool is_follow= 5; 54 | } 55 | 56 | service Publish { 57 | rpc PubAction (PublishActionRequest) returns (PublishActionResponse); 58 | rpc PubList (PublishListRequest) returns (PublishListResponse); 59 | } -------------------------------------------------------------------------------- /gateway/service/proto/userProto.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./service;service"; 4 | 5 | package service; 6 | 7 | message RegisterRequest { 8 | string username = 1; 9 | string password = 2; 10 | } 11 | 12 | message RegisterResponse { 13 | int32 status_code = 1; 14 | string status_msg = 2; 15 | int64 user_id = 3; 16 | string token = 4; 17 | } 18 | 19 | message LoginRequest { 20 | string username = 1; 21 | string password = 2; 22 | } 23 | 24 | message LoginResponse { 25 | int32 status_code = 1; 26 | string status_msg = 2; 27 | int64 user_id = 3; 28 | string token = 4; 29 | } 30 | 31 | message IsLoginRequest { 32 | string token = 1; 33 | } 34 | 35 | message IsLoginResponse { 36 | int32 code = 1; 37 | string msg = 2; 38 | } 39 | 40 | message UserRequest{ 41 | int64 user_id = 1; 42 | string token = 2; 43 | } 44 | message UserResponse{ 45 | int32 status_code = 1; 46 | string status_msg = 2; 47 | UserMsg user = 3; 48 | } 49 | message UserMsg{ 50 | int64 id = 1; 51 | string name = 2; 52 | int32 follow_count = 3; 53 | int32 follower_count = 4; 54 | bool is_follow = 5; 55 | } 56 | service UserProto{ 57 | rpc Login(LoginRequest) returns (LoginResponse) {} 58 | rpc Register(RegisterRequest) returns (RegisterResponse) {} 59 | rpc IsLogin(IsLoginRequest)returns (IsLoginResponse){} 60 | rpc GetUserMsg(UserRequest)returns (UserResponse){} 61 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/tiktok 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/gin-contrib/sse v0.1.0 // indirect 7 | github.com/gin-gonic/gin v1.8.2 // indirect 8 | github.com/go-playground/locales v0.14.0 // indirect 9 | github.com/go-playground/universal-translator v0.18.0 // indirect 10 | github.com/go-playground/validator/v10 v10.11.1 // indirect 11 | github.com/goccy/go-json v0.9.11 // indirect 12 | github.com/json-iterator/go v1.1.12 // indirect 13 | github.com/leodido/go-urn v1.2.1 // indirect 14 | github.com/mattn/go-isatty v0.0.16 // indirect 15 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 16 | github.com/modern-go/reflect2 v1.0.2 // indirect 17 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 18 | github.com/ugorji/go/codec v1.2.7 // indirect 19 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect 20 | golang.org/x/net v0.4.0 // indirect 21 | golang.org/x/sys v0.3.0 // indirect 22 | golang.org/x/text v0.5.0 // indirect 23 | google.golang.org/protobuf v1.28.1 // indirect 24 | gopkg.in/yaml.v2 v2.4.0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /publish/.gitignore: -------------------------------------------------------------------------------- 1 | # use for java 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # use for golang 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Binaries for programs and plugins 28 | *.exe 29 | *.exe~ 30 | *.dll 31 | *.so 32 | *.dylib 33 | 34 | # Test binary, built with `go test -c` 35 | *.test 36 | 37 | # Output of the go coverage tool, specifically when used with LiteIDE 38 | *.out 39 | 40 | # Dependency directories (remove the comment below to include it) 41 | # vendor/ 42 | golang/main.go 43 | 44 | .vscode 45 | .idea 46 | # use for javascript 47 | # compiled output 48 | /dist 49 | /tmp 50 | /out-tsc 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | 64 | # nyc test coverage 65 | .nyc_output 66 | 67 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 68 | .grunt 69 | 70 | # Bower dependency directory (https://bower.io/) 71 | bower_components 72 | 73 | # node-waf configuration 74 | .lock-wscript 75 | 76 | # IDEs and editors 77 | .idea 78 | .project 79 | .classpath 80 | .c9/ 81 | *.launch 82 | .settings/ 83 | *.sublime-workspace 84 | 85 | # IDE - VSCode 86 | .vscode/* 87 | !.vscode/settings.json 88 | !.vscode/tasks.json 89 | !.vscode/launch.json 90 | !.vscode/extensions.json 91 | jsconfig.json 92 | Top Interview Questions/Solution.ts 93 | Top Interview Questions/Solution.java 94 | # misc 95 | .sass-cache 96 | connect.lock 97 | typings 98 | 99 | # Logs 100 | logs 101 | *.log 102 | npm-debug.log* 103 | yarn-debug.log* 104 | yarn-error.log* 105 | 106 | 107 | # Dependency directories 108 | node_modules/ 109 | jspm_packages/ 110 | 111 | # Optional npm cache directory 112 | .npm 113 | 114 | # Optional eslint cache 115 | .eslintcache 116 | 117 | # Optional REPL history 118 | .node_repl_history 119 | 120 | # Output of 'npm pack' 121 | *.tgz 122 | 123 | # Yarn Integrity file 124 | .yarn-integrity 125 | 126 | # dotenv environment variables file 127 | .env 128 | 129 | # next.js build output 130 | .next 131 | 132 | # Lerna 133 | lerna-debug.log 134 | 135 | # System Files 136 | .DS_Store 137 | Thumbs.db 138 | -------------------------------------------------------------------------------- /publish/Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/publish/Dockerfile -------------------------------------------------------------------------------- /publish/center/consul.go: -------------------------------------------------------------------------------- 1 | package center 2 | 3 | import ( 4 | "log" 5 | 6 | consul "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | var ( 10 | addr = "47.108.66.104:8500" 11 | client *consul.Client 12 | ) 13 | 14 | func init() { 15 | var err error 16 | config := consul.DefaultConfig() 17 | config.Address = addr 18 | client, err = consul.NewClient(config) 19 | if err != nil { 20 | log.Fatalf("failed to init consul: %v", err) 21 | } 22 | } 23 | 24 | func GetValue(key string) ([]byte, error) { 25 | res, _, err := client.KV().Get(key, nil) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return res.Value, nil 30 | } 31 | 32 | func Register(reg consul.AgentServiceRegistration) error { 33 | agent := client.Agent() 34 | 35 | return agent.ServiceRegister(®) 36 | } 37 | -------------------------------------------------------------------------------- /publish/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | 9 | "github.com/JirafaYe/publish/center" 10 | "github.com/JirafaYe/publish/internal/server" 11 | "github.com/JirafaYe/publish/internal/service" 12 | consul "github.com/hashicorp/consul/api" 13 | "google.golang.org/grpc" 14 | //"google.golang.org/grpc/health/grpc_health_v1" 15 | ) 16 | 17 | var ( 18 | addr = flag.String("addr", "127.0.0.1", "The server address") 19 | //addr = flag.String("addr", "101.33.249.244", "The server address") 20 | port = flag.Int("port", 11451, "The server port") 21 | ) 22 | 23 | func main() { 24 | flag.Parse() 25 | 26 | // 服务注册 27 | err := center.Register(consul.AgentServiceRegistration{ 28 | ID: "publish-1", // 服务节点的名称 29 | Name: "publish", // 服务名称 30 | Port: *port, // 服务端口 31 | Address: *addr, // 服务 IP 32 | // Check: &consul.AgentServiceCheck{ // 健康检查 33 | // Interval: "5s", // 健康检查间隔 34 | // GRPC: fmt.Sprintf("%v:%v/%v", *addr, *port, "health"), 35 | // DeregisterCriticalServiceAfter: "10s", // 注销时间,相当于过期时间 36 | // }, 37 | }) 38 | if err != nil { 39 | log.Fatalf("failed to register service: %v", err) 40 | } 41 | 42 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 43 | if err != nil { 44 | log.Fatalf("failed to listen: %v", err) 45 | } 46 | s := grpc.NewServer() 47 | service.RegisterPublishServer(s, &server.PublishSrv{}) 48 | // 健康检查 49 | // grpc_health_v1.RegisterHealthServer(s, &server.HealthImpl{}) 50 | 51 | log.Printf("server listening at %v", lis.Addr()) 52 | if err := s.Serve(lis); err != nil { 53 | log.Fatalf("failed to serve: %v", err) 54 | } 55 | } -------------------------------------------------------------------------------- /publish/config/api.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var handler *Manager 4 | 5 | func init() { 6 | handler = New() 7 | } 8 | 9 | func ReadConfig(c Config) error { 10 | return handler.ReadConfig(c) 11 | } 12 | -------------------------------------------------------------------------------- /publish/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config interface { 4 | Key() string 5 | } 6 | -------------------------------------------------------------------------------- /publish/config/manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/JirafaYe/publish/center" 6 | "log" 7 | ) 8 | 9 | type Manager struct { 10 | } 11 | 12 | func New() *Manager { 13 | return &Manager{} 14 | } 15 | 16 | func (m *Manager) ReadConfig(c Config) error { 17 | log.Println("[CONFIG] READING", c.Key()) 18 | config, err := center.GetValue(c.Key()) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = json.Unmarshal(config, &c) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /publish/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/publish 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/cloudwego/kitex v0.4.4 7 | github.com/go-redis/redis/v8 v8.11.5 8 | github.com/gofrs/uuid v4.4.0+incompatible 9 | github.com/golang-jwt/jwt v3.2.2+incompatible 10 | github.com/hashicorp/consul/api v1.18.0 11 | github.com/minio/minio-go/v7 v7.0.47 12 | github.com/u2takey/ffmpeg-go v0.4.1 13 | google.golang.org/grpc v1.52.0 14 | google.golang.org/protobuf v1.28.1 15 | gorm.io/driver/mysql v1.4.5 16 | gorm.io/gorm v1.24.3 17 | ) 18 | 19 | require ( 20 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 21 | github.com/aws/aws-sdk-go v1.38.20 // indirect 22 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 23 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 24 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 25 | github.com/dustin/go-humanize v1.0.0 // indirect 26 | github.com/fatih/color v1.9.0 // indirect 27 | github.com/go-sql-driver/mysql v1.7.0 // indirect 28 | github.com/golang/protobuf v1.5.2 // indirect 29 | github.com/google/uuid v1.3.0 // indirect 30 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 31 | github.com/hashicorp/go-hclog v0.12.0 // indirect 32 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 33 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 34 | github.com/hashicorp/golang-lru v0.5.4 // indirect 35 | github.com/hashicorp/serf v0.10.1 // indirect 36 | github.com/jinzhu/inflection v1.0.0 // indirect 37 | github.com/jinzhu/now v1.1.5 // indirect 38 | github.com/jmespath/go-jmespath v0.4.0 // indirect 39 | github.com/json-iterator/go v1.1.12 // indirect 40 | github.com/klauspost/compress v1.15.9 // indirect 41 | github.com/klauspost/cpuid/v2 v2.1.0 // indirect 42 | github.com/mattn/go-colorable v0.1.6 // indirect 43 | github.com/mattn/go-isatty v0.0.13 // indirect 44 | github.com/minio/md5-simd v1.1.2 // indirect 45 | github.com/minio/sha256-simd v1.0.0 // indirect 46 | github.com/mitchellh/go-homedir v1.1.0 // indirect 47 | github.com/mitchellh/mapstructure v1.4.1 // indirect 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 49 | github.com/modern-go/reflect2 v1.0.2 // indirect 50 | github.com/rs/xid v1.4.0 // indirect 51 | github.com/sirupsen/logrus v1.9.0 // indirect 52 | github.com/u2takey/go-utils v0.3.1 // indirect 53 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect 54 | golang.org/x/net v0.4.0 // indirect 55 | golang.org/x/sys v0.3.0 // indirect 56 | golang.org/x/text v0.5.0 // indirect 57 | google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect 58 | gopkg.in/ini.v1 v1.66.6 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /publish/internal/server/health.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/health/grpc_health_v1" 7 | ) 8 | 9 | type HealthImpl struct{} 10 | 11 | func (h *HealthImpl) Check(_ context.Context, _ *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { 12 | return &grpc_health_v1.HealthCheckResponse{ 13 | Status: grpc_health_v1.HealthCheckResponse_SERVING, 14 | }, nil 15 | } 16 | 17 | func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error { 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /publish/internal/server/manager.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/JirafaYe/publish/internal/store/cache" 5 | "github.com/JirafaYe/publish/internal/store/local" 6 | "github.com/JirafaYe/publish/internal/store/obs" 7 | "log" 8 | ) 9 | 10 | var m *Manager 11 | 12 | type Manager struct { 13 | localer *local.Manager 14 | cacher *cache.Manager 15 | objectStorer *obs.Manager 16 | } 17 | 18 | func init() { 19 | var err error 20 | localer, err := local.New() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | cacher, err := cache.New() 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | objectStorer, err := obs.New() 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | 34 | m = &Manager{ 35 | localer: localer, 36 | cacher: cacher, 37 | objectStorer: objectStorer, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /publish/internal/server/publish_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "testing" 5 | // "image/jpeg" 6 | // "image" 7 | // "os" 8 | 9 | "context" 10 | "fmt" 11 | "io/ioutil" 12 | 13 | // "github.com/JirafaYe/publish/internal/store/obs" 14 | // "github.com/JirafaYe/publish/internal/store/local" 15 | "github.com/JirafaYe/publish/internal/service" 16 | 17 | "google.golang.org/grpc" 18 | "google.golang.org/grpc/credentials/insecure" 19 | ) 20 | 21 | func TestPubAction(t *testing.T) { 22 | conn, err := grpc.Dial("127.0.0.1:11451", grpc.WithTransportCredentials(insecure.NewCredentials())) 23 | if err != nil { 24 | fmt.Println(err) 25 | } 26 | defer conn.Close() 27 | 28 | c := service.NewPublishClient(conn) 29 | tmpData, err := ioutil.ReadFile("test1.mp4") 30 | tmpRequest := &service.PublishActionRequest{ 31 | Token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NCwidXNlcm5hbWUiOiJoZXdlbiIsImV4cCI6MTY3NzU3MjczMCwiaXNzIjoidGlrdG9rLnVzZXIifQ.UYG47jJPtjfHb-MzhBOBoa7KTQFdrqSDayCWHP-TDUs", 32 | Data: tmpData, 33 | Title: "the Galaxy new", 34 | } 35 | 36 | res, err := c.PubAction(context.Background(), tmpRequest) 37 | if err!= nil { 38 | t.Errorf("error creating: %v", err) 39 | } 40 | fmt.Println(res.StatusCode) 41 | fmt.Println(res.StatusMsg) 42 | } 43 | 44 | // func SaveImageAsJpeg(img image.Image, filename string) (err error) { 45 | // jpegFile, err := os.Create(filename) 46 | // defer jpegFile.Close() 47 | // if err != nil { 48 | // return err 49 | // } 50 | // err = jpeg.Encode(jpegFile, img, &jpeg.Options{Quality: 100}) 51 | // return err 52 | // } 53 | 54 | // func TestReadFrameAsJpeg(t *testing.T) { 55 | // filePath := "http://47.108.66.104:9000/videos/test1.mp4" 56 | 57 | // img, err := readFrameAsJpeg(filePath) 58 | // if err != nil { 59 | // t.Errorf("Unexpected error %v", err) 60 | // } 61 | // err = SaveImageAsJpeg(img, "test1.jpg") 62 | // if err!= nil { 63 | // t.Errorf("fail save image as jpeg due to: %v\n", err) 64 | // } 65 | // } -------------------------------------------------------------------------------- /publish/internal/server/test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/publish/internal/server/test1.jpg -------------------------------------------------------------------------------- /publish/internal/server/test1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/publish/internal/server/test1.mp4 -------------------------------------------------------------------------------- /publish/internal/service/Makefile: -------------------------------------------------------------------------------- 1 | protoc: 2 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 3 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 4 | protoc --go_out=./ --go-grpc_out=./ ./proto/*.proto -------------------------------------------------------------------------------- /publish/internal/service/proto/publish.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "../service"; 4 | 5 | package service; 6 | 7 | // publish action, POST 8 | 9 | message PublishActionRequest { 10 | string token = 1; 11 | bytes data = 2; 12 | string title = 3; 13 | } 14 | 15 | message PublishActionResponse { 16 | int32 status_code = 1; 17 | //optional string status_msg = 2; 18 | string status_msg = 2; 19 | } 20 | 21 | // GET list 22 | 23 | message PublishListRequest { 24 | int64 user_id = 1; 25 | string token = 2; 26 | } 27 | 28 | message PublishListResponse { 29 | int32 status_code = 1; 30 | //optional string status_msg = 2; 31 | string status_msg = 2; 32 | repeated PubVideo video_list = 3; 33 | } 34 | 35 | message PubVideo { 36 | int64 id = 1; 37 | PubUser author = 2; 38 | string play_url = 3; 39 | string cover_url = 4; 40 | int64 favorite_count = 5; 41 | int64 comment_count = 6; 42 | bool is_favorite = 7; 43 | string title = 8; 44 | } 45 | 46 | message PubUser { 47 | int64 id = 1; 48 | string name = 2; 49 | // optional int64 follow_count = 3; 50 | // optional int64 follower_count = 4; 51 | int64 follow_count = 3; 52 | int64 follower_count = 4; 53 | bool is_follow= 5; 54 | } 55 | 56 | service Publish { 57 | rpc PubAction (PublishActionRequest) returns (PublishActionResponse); 58 | rpc PubList (PublishListRequest) returns (PublishListResponse); 59 | } -------------------------------------------------------------------------------- /publish/internal/store/cache/config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/JirafaYe/publish/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Addr string `json:"addr"` 10 | Port string `json:"port"` 11 | Password string `json:"password"` 12 | } 13 | 14 | func (c *Config) Key() string { 15 | return "tiktok/cache" 16 | } 17 | 18 | var C Config 19 | 20 | func init() { 21 | err := config.ReadConfig(&C) 22 | if err != nil { 23 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /publish/internal/store/cache/manager.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "github.com/go-redis/redis/v8" 6 | ) 7 | 8 | type Manager struct { 9 | handler *redis.Client 10 | } 11 | 12 | func New() (*Manager, error) { 13 | m := &Manager{ 14 | handler: redis.NewClient( 15 | &redis.Options{ 16 | Addr: C.Addr + ":" + C.Port, 17 | Password: C.Password, 18 | }, 19 | ), 20 | } 21 | return m, m.handler.Ping(context.Background()).Err() 22 | } 23 | -------------------------------------------------------------------------------- /publish/internal/store/local/config.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/JirafaYe/publish/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Host string `json:"host"` 10 | Port string `json:"port"` 11 | User string `json:"user"` 12 | Name string `json:"name"` 13 | Password string `json:"password"` 14 | } 15 | 16 | func (c *Config) Key() string { 17 | return "tiktok/local" 18 | } 19 | 20 | var C Config 21 | 22 | func init() { 23 | err := config.ReadConfig(&C) 24 | if err != nil { 25 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /publish/internal/store/local/create/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/JirafaYe/publish/internal/store/local" 5 | "gorm.io/driver/mysql" 6 | "gorm.io/gorm" 7 | "log" 8 | ) 9 | 10 | func main() { 11 | dsn := "root:xh020914@tcp(47.108.66.104:33306)/tiktok?charset=utf8mb4&parseTime=True&loc=Local" 12 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 13 | if err != nil { 14 | log.Println(err) 15 | } 16 | generateTestData(db) 17 | 18 | } 19 | 20 | func generateTable(db *gorm.DB) { 21 | err := db.Set("gorm:table_options", "CHARSET=utf8").AutoMigrate(&local.User{}, &local.Video{}) 22 | if err != nil { 23 | return 24 | } 25 | 26 | err = db.Migrator().RenameTable("users", "t_user") 27 | if err != nil { 28 | return 29 | } 30 | err = db.Migrator().RenameTable("videos", "t_video") 31 | if err != nil { 32 | return 33 | } 34 | } 35 | 36 | func generateTestData(db *gorm.DB) { 37 | var users []local.User 38 | var videos []local.Video 39 | users = append(users, local.User{Name: "zhangsan"}, local.User{Name: "lisi"}) 40 | videos = append(videos, local.Video{ 41 | PlayURL: "404 Not Found", 42 | CoverURL: "404 Not Found", 43 | Title: "Test Data", 44 | }) 45 | //db.Table("t_user").Create(&users) 46 | db.Table("t_video").Create(&videos) 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /publish/internal/store/local/manager.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "fmt" 5 | "gorm.io/driver/mysql" 6 | "gorm.io/gorm" 7 | "gorm.io/gorm/logger" 8 | "log" 9 | "os" 10 | ) 11 | 12 | type Manager struct { 13 | handler *gorm.DB 14 | } 15 | 16 | func New() (*Manager, error) { 17 | newLogger := logger.New( 18 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 19 | logger.Config{ 20 | SlowThreshold: 200, // 慢 SQL 阈值 21 | LogLevel: logger.Warn, // 日志级别 22 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 23 | Colorful: true, // 禁用彩色打印 24 | }, 25 | ) 26 | 27 | db, err := gorm.Open( 28 | mysql.Open( 29 | fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", 30 | C.User, 31 | C.Password, 32 | C.Host, 33 | C.Port, 34 | C.Name, 35 | ), 36 | ), 37 | &gorm.Config{ 38 | Logger: newLogger, 39 | }, 40 | ) 41 | return &Manager{ 42 | handler: db, 43 | }, err 44 | } 45 | -------------------------------------------------------------------------------- /publish/internal/store/local/publish_test.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "testing" 5 | //"context" 6 | //"time" 7 | "fmt" 8 | ) 9 | 10 | func TestCreateVideo(t *testing.T) { 11 | // db 12 | manager, _ := New() 13 | // Setup context to test 14 | //ctx := context.Background() 15 | // ctxWithTimeout, cancel := context.WithTimeout(ctx, 10*time.Second) 16 | // defer cancel() 17 | 18 | video := &Video{ 19 | UserId: int64(1234), 20 | PlayURL: "http://47.108.66.104:9000/videos/test1.mp4", 21 | CoverURL: "http://47.108.66.104:9000/covers/test1.jpg", 22 | Title: "the Galaxy", 23 | } 24 | 25 | //err := manager.CreateVideo(ctx, video) 26 | err := manager.CreateVideo(video) 27 | if err != nil { 28 | t.Errorf("failed to create video since: %v", err) 29 | } 30 | 31 | // Assert variable was created 32 | db := manager.handler.Table("t_video") 33 | var videoRes Video 34 | err = db.Where("user_id = ?", int64(1234)).Find(&videoRes).Error 35 | if err != nil { 36 | t.Errorf("failed to find video: %v", err) 37 | } 38 | fmt.Println(videoRes) 39 | } 40 | 41 | func TestQueryVideoByUserId(t *testing.T) { 42 | manager, _ := New() 43 | // TODO: 表里没数据,无法测试,先改回来吧 44 | videos, err := manager.QueryVideoByUserId(1234) 45 | if err!= nil { 46 | t.Errorf("failed to QueryVideoByUserId: %v", err) 47 | } 48 | fmt.Println("video length: ", len(videos)) 49 | } 50 | 51 | /* 52 | func (m *Manager) QueryVideosByUserId(userId int64) ([]*Video, error) { 53 | var videos []*Video 54 | db := m.handler.Table("t_video") 55 | err := db.Where("user_id = ?", userId).Order("created_date desc").Find(&videos).Error 56 | if err != nil { 57 | return nil, err 58 | } 59 | return videos, nil 60 | } 61 | */ -------------------------------------------------------------------------------- /publish/internal/store/obs/api.go: -------------------------------------------------------------------------------- 1 | package obs 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/url" 7 | "time" 8 | "log" 9 | "github.com/minio/minio-go/v7" 10 | ) 11 | 12 | // minioCLient == manager.handler 13 | 14 | // UploadFile 上传视频的request的protocol buffer中有data字段,用reader去读取,然后上传至minio 15 | func (m *Manager)UploadFile(bucketName string, objectName string, reader io.Reader, objectsize int64) error { 16 | ctx := context.Background() 17 | n, err := m.handler.PutObject(ctx, bucketName, objectName, reader, objectsize, minio.PutObjectOptions{ 18 | ContentType: "video/mp4", 19 | }) 20 | if err != nil { 21 | log.Printf("upload %s of size %d failed, %s", bucketName, objectsize, err) 22 | return err 23 | } 24 | log.Printf("upload %s of bytes %d successfully", objectName, n.Size) 25 | return nil 26 | } 27 | 28 | // GetFileURL 从minio获取文件URL 29 | func (m *Manager)GetFileURL(bucketName string, fileName string, expires time.Duration) (*url.URL, error) { 30 | ctx := context.Background() 31 | reqParams := make(url.Values) 32 | if expires <= 0 { 33 | expires = time.Second * 60 * 60 *24 34 | } 35 | presignedURL, err := m.handler.PresignedGetObject(ctx, bucketName, fileName, expires, reqParams) 36 | if err != nil { 37 | log.Printf("get url of file %s from bucket %s failed, %s", fileName, bucketName, err) 38 | return nil, err 39 | } 40 | // TODO URL可能要做截取 41 | return presignedURL, nil 42 | } 43 | 44 | // publish list 45 | 46 | func GetVideoPrefix() string { 47 | return "http://" + C.Address + "/videos/" 48 | } 49 | 50 | func GetImagePrefix() string { 51 | return "http://" + C.Address + "/images/" 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /publish/internal/store/obs/api_test.go: -------------------------------------------------------------------------------- 1 | package obs 2 | 3 | import ( 4 | "testing" 5 | "io/ioutil" 6 | "bytes" 7 | "time" 8 | ) 9 | 10 | func TestUploadFile(t *testing.T) { 11 | // 初始化Manager实例进行测试 12 | manager, _ := New() 13 | // 使用模拟文件数据创建 io.Reader 14 | data, err := ioutil.ReadFile("test1.mp4") 15 | if err!= nil { 16 | t.Errorf("can't read video file by filePath: %v", err) 17 | } 18 | size := int64(len(data)) 19 | reader := bytes.NewReader(data) 20 | 21 | // 调用 UploadFile 并断言正确性 22 | if err := manager.UploadFile("videos", "test1.mp4", reader, size); err != nil { 23 | t.Errorf("unexpected error: %v", err) 24 | } 25 | } 26 | 27 | func TestGetFileURL(t *testing.T) { 28 | // 初始化Manager实例进行测试 29 | manager, _ := New() 30 | bucketName := "videos" 31 | fileName := "test1.mp4" 32 | expires := time.Second * 60 * 60 * 24 33 | 34 | url, err := manager.GetFileURL(bucketName, fileName, expires) 35 | if err != nil { 36 | t.Errorf("get url of file %s from bucket %s failed: %s", fileName, bucketName, err) 37 | } 38 | if url == nil { 39 | t.Errorf("url is nil") 40 | } 41 | t.Logf("get url of file %s from bucket %s: %s", fileName, bucketName, url) 42 | } 43 | 44 | -------------------------------------------------------------------------------- /publish/internal/store/obs/config.go: -------------------------------------------------------------------------------- 1 | package obs 2 | 3 | import ( 4 | "github.com/JirafaYe/publish/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Address string `json:"address"` 10 | SecretId string `json:"secret_id"` 11 | SecretKey string `json:"secret_key"` 12 | } 13 | 14 | func (c *Config) Key() string { 15 | return "tiktok/obs" 16 | } 17 | 18 | var C Config 19 | 20 | func init() { 21 | err := config.ReadConfig(&C) 22 | if err != nil { 23 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /publish/internal/store/obs/manager.go: -------------------------------------------------------------------------------- 1 | package obs 2 | 3 | import ( 4 | "github.com/minio/minio-go/v7" 5 | "github.com/minio/minio-go/v7/pkg/credentials" 6 | ) 7 | 8 | type Manager struct { 9 | handler *minio.Client 10 | } 11 | 12 | // Minio对象初始化 13 | func New() (*Manager, error) { 14 | handler, err := minio.New(C.Address, &minio.Options{ 15 | Creds: credentials.NewStaticV4(C.SecretId, C.SecretKey, ""), 16 | Secure: false}) 17 | return &Manager{ 18 | handler: handler, 19 | }, err 20 | } 21 | 22 | // // minioClient == Manager.handler 23 | -------------------------------------------------------------------------------- /publish/internal/store/obs/test1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/publish/internal/store/obs/test1.mp4 -------------------------------------------------------------------------------- /publish/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // import ( 4 | // "github.com/JirafaYe/feed/internal/server" 5 | // "github.com/JirafaYe/feed/internal/service" 6 | // "google.golang.org/grpc" 7 | // "log" 8 | // "net" 9 | // ) 10 | 11 | // func main() { 12 | // s := grpc.NewServer() 13 | // service.RegisterFeedServer(s, &server.FeedServer{}) 14 | 15 | // lis, err := net.Listen("tcp", ":8888") 16 | // if err != nil { 17 | // log.Fatalf("net.Listen err: %v", err) 18 | // } 19 | 20 | // err = s.Serve(lis) 21 | // if err != nil { 22 | // log.Fatalf("server.Serve err: %v", err) 23 | // } 24 | // } 25 | 26 | import ( 27 | "github.com/JirafaYe/publish/internal/server" 28 | "github.com/JirafaYe/publish/internal/service" 29 | "google.golang.org/grpc" 30 | "log" 31 | "net" 32 | ) 33 | 34 | func main() { 35 | s := grpc.NewServer() 36 | service.RegisterPublishServer(s, &server.PublishSrv{}) 37 | 38 | lis, err := net.Listen("tcp", ":8888") 39 | if err != nil { 40 | log.Fatalf("net.Listen err: %v", err) 41 | } 42 | 43 | err = s.Serve(lis) 44 | if err != nil { 45 | log.Fatalf("server.Serve err: %v", err) 46 | } 47 | } 48 | 49 | // type PublishServer interface { 50 | // PubAction(context.Context, *PublishActionRequest) (*PublishActionResponse, error) 51 | // PubList(context.Context, *PublishListRequest) (*PublishListResponse, error) 52 | // mustEmbedUnimplementedPublishServer() 53 | // } 54 | 55 | -------------------------------------------------------------------------------- /publish/pkg/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | "time" 6 | ) 7 | 8 | var jwtSecret = []byte("tiktok.user") 9 | 10 | type Claims struct { 11 | Id int64 `json:"id"` 12 | Username string `json:"username"` 13 | jwt.StandardClaims 14 | } 15 | 16 | // GenerateToken 签发用户Token 17 | func GenerateToken(id int64, username string) (string, error) { 18 | nowTime := time.Now() 19 | expireTime := nowTime.Add(7 * 24 * time.Hour) 20 | claims := Claims{ 21 | Id: id, 22 | Username: username, 23 | StandardClaims: jwt.StandardClaims{ 24 | ExpiresAt: expireTime.Unix(), 25 | Issuer: "tiktok.user", 26 | }, 27 | } 28 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 29 | token, err := tokenClaims.SignedString(jwtSecret) 30 | return token, err 31 | } 32 | 33 | // ParseToken 验证用户token 34 | func ParseToken(token string) (*Claims, error) { 35 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 36 | return jwtSecret, nil 37 | }) 38 | if tokenClaims != nil { 39 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 40 | return claims, nil 41 | } 42 | } 43 | return nil, err 44 | } 45 | -------------------------------------------------------------------------------- /publish/pkg/jwt/jwt_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGenerateToken(t *testing.T) { 8 | userName := "hewen" 9 | userId := 4 10 | token, err := GenerateToken(int64(userId), userName) 11 | if err!= nil { 12 | t.Error(err) 13 | } 14 | t.Log(token) 15 | } -------------------------------------------------------------------------------- /publish/pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "fmt" 7 | ) 8 | 9 | func NewString(s string) *string { 10 | return &s 11 | } 12 | 13 | func GetVideo(filePath string) ([]byte) { 14 | file, err := ioutil.ReadFile(filePath) 15 | if err!= nil { 16 | log.Printf("can't read video file by filePath: %v", err) 17 | } 18 | fmt.Printf("stream type: %T\n", file) 19 | return file 20 | } 21 | -------------------------------------------------------------------------------- /user/.gitignore: -------------------------------------------------------------------------------- 1 | # use for java 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # use for golang 24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 25 | hs_err_pid* 26 | 27 | # Binaries for programs and plugins 28 | *.exe 29 | *.exe~ 30 | *.dll 31 | *.so 32 | *.dylib 33 | 34 | # Test binary, built with `go test -c` 35 | *.test 36 | 37 | # Output of the go coverage tool, specifically when used with LiteIDE 38 | *.out 39 | 40 | # Dependency directories (remove the comment below to include it) 41 | # vendor/ 42 | golang/main.go 43 | 44 | .vscode 45 | .idea 46 | # use for javascript 47 | # compiled output 48 | /dist 49 | /tmp 50 | /out-tsc 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Directory for instrumented libs generated by jscoverage/JSCover 59 | lib-cov 60 | 61 | # Coverage directory used by tools like istanbul 62 | coverage 63 | 64 | # nyc test coverage 65 | .nyc_output 66 | 67 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 68 | .grunt 69 | 70 | # Bower dependency directory (https://bower.io/) 71 | bower_components 72 | 73 | # node-waf configuration 74 | .lock-wscript 75 | 76 | # IDEs and editors 77 | .idea 78 | .project 79 | .classpath 80 | .c9/ 81 | *.launch 82 | .settings/ 83 | *.sublime-workspace 84 | 85 | # IDE - VSCode 86 | .vscode/* 87 | !.vscode/settings.json 88 | !.vscode/tasks.json 89 | !.vscode/launch.json 90 | !.vscode/extensions.json 91 | jsconfig.json 92 | Top Interview Questions/Solution.ts 93 | Top Interview Questions/Solution.java 94 | # misc 95 | .sass-cache 96 | connect.lock 97 | typings 98 | 99 | # Logs 100 | logs 101 | *.log 102 | npm-debug.log* 103 | yarn-debug.log* 104 | yarn-error.log* 105 | 106 | 107 | # Dependency directories 108 | node_modules/ 109 | jspm_packages/ 110 | 111 | # Optional npm cache directory 112 | .npm 113 | 114 | # Optional eslint cache 115 | .eslintcache 116 | 117 | # Optional REPL history 118 | .node_repl_history 119 | 120 | # Output of 'npm pack' 121 | *.tgz 122 | 123 | # Yarn Integrity file 124 | .yarn-integrity 125 | 126 | # dotenv environment variables file 127 | .env 128 | 129 | # next.js build output 130 | .next 131 | 132 | # Lerna 133 | lerna-debug.log 134 | 135 | # System Files 136 | .DS_Store 137 | Thumbs.db 138 | -------------------------------------------------------------------------------- /user/Dockerfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/user/Dockerfile -------------------------------------------------------------------------------- /user/Makefile: -------------------------------------------------------------------------------- 1 | protoc: 2 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 3 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 4 | protoc --go_out=./ --go-grpc_out=./ ./internal/service/proto/*.proto -------------------------------------------------------------------------------- /user/center/consul.go: -------------------------------------------------------------------------------- 1 | package center 2 | 3 | import ( 4 | "log" 5 | 6 | consul "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | var ( 10 | addr = "47.108.66.104:8500" 11 | client *consul.Client 12 | ) 13 | 14 | func init() { 15 | var err error 16 | config := consul.DefaultConfig() 17 | config.Address = addr 18 | client, err = consul.NewClient(config) 19 | if err != nil { 20 | log.Fatalf("failed to init consul: %v", err) 21 | } 22 | } 23 | 24 | func GetValue(key string) ([]byte, error) { 25 | res, _, err := client.KV().Get(key, nil) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return res.Value, nil 30 | } 31 | 32 | func Register(reg consul.AgentServiceRegistration) error { 33 | agent := client.Agent() 34 | 35 | return agent.ServiceRegister(®) 36 | } 37 | -------------------------------------------------------------------------------- /user/cmd/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/user/cmd/.gitkeep -------------------------------------------------------------------------------- /user/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/JirafaYe/user/center" 7 | "github.com/JirafaYe/user/internal/server" 8 | "github.com/JirafaYe/user/internal/service" 9 | consul "github.com/hashicorp/consul/api" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/health/grpc_health_v1" 12 | "log" 13 | "net" 14 | ) 15 | 16 | var ( 17 | addr = flag.String("addr", "127.0.0.1", "The server address") 18 | port = flag.Int("port", 50000, "The server port") 19 | ) 20 | 21 | func main() { 22 | flag.Parse() 23 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) 24 | if err != nil { 25 | log.Fatalf("failed to listen: %v", err) 26 | } 27 | s := grpc.NewServer() 28 | service.RegisterUserProtoServer(s, &server.UserSrv{}) 29 | // 健康检查 30 | grpc_health_v1.RegisterHealthServer(s, &server.HealthImpl{}) 31 | 32 | // 服务注册 33 | err = center.Register(consul.AgentServiceRegistration{ 34 | ID: "user-1", // 服务节点的名称 35 | Name: "user", // 服务名称 36 | Port: *port, // 服务端口 37 | Address: *addr, // 服务 IP 38 | //Check: &consul.AgentServiceCheck{ // 健康检查 39 | // Interval: "5s", // 健康检查间隔 40 | // GRPC: fmt.Sprintf("%v:%v/%v", *addr, *port, "health"), 41 | // DeregisterCriticalServiceAfter: "10s", // 注销时间,相当于过期时间 42 | //}, 43 | }) 44 | if err != nil { 45 | log.Fatalf("failed to register service: %v", err) 46 | } 47 | 48 | log.Printf("server listening at %v", lis.Addr()) 49 | if err := s.Serve(lis); err != nil { 50 | log.Fatalf("failed to serve: %v", err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /user/config/api.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var handler *Manager 4 | 5 | func init() { 6 | handler = New() 7 | } 8 | 9 | func ReadConfig(c Config) error { 10 | return handler.ReadConfig(c) 11 | } 12 | -------------------------------------------------------------------------------- /user/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config interface { 4 | Key() string 5 | } 6 | -------------------------------------------------------------------------------- /user/config/manager.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/JirafaYe/user/center" 6 | "log" 7 | ) 8 | 9 | type Manager struct { 10 | } 11 | 12 | func New() *Manager { 13 | return &Manager{} 14 | } 15 | 16 | func (m *Manager) ReadConfig(c Config) error { 17 | log.Println("[CONFIG] READING", c.Key()) 18 | config, err := center.GetValue(c.Key()) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = json.Unmarshal(config, &c) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /user/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/JirafaYe/user 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/bwmarrin/snowflake v0.3.0 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 8 | github.com/go-redis/redis/v8 v8.11.5 9 | github.com/hashicorp/consul/api v1.18.0 10 | github.com/onsi/gomega v1.18.1 11 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 12 | google.golang.org/grpc v1.52.0-dev 13 | google.golang.org/protobuf v1.28.1 14 | gorm.io/driver/mysql v1.4.5 15 | gorm.io/gorm v1.24.3 16 | ) 17 | 18 | require ( 19 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 20 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 21 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 22 | github.com/fatih/color v1.9.0 // indirect 23 | github.com/go-sql-driver/mysql v1.7.0 // indirect 24 | github.com/golang/protobuf v1.5.2 // indirect 25 | github.com/hashicorp/go-cleanhttp v0.5.1 // indirect 26 | github.com/hashicorp/go-hclog v0.12.0 // indirect 27 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 28 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 29 | github.com/hashicorp/golang-lru v0.5.4 // indirect 30 | github.com/hashicorp/serf v0.10.1 // indirect 31 | github.com/jinzhu/inflection v1.0.0 // indirect 32 | github.com/jinzhu/now v1.1.5 // indirect 33 | github.com/mattn/go-colorable v0.1.6 // indirect 34 | github.com/mattn/go-isatty v0.0.12 // indirect 35 | github.com/mitchellh/go-homedir v1.1.0 // indirect 36 | github.com/mitchellh/mapstructure v1.4.1 // indirect 37 | golang.org/x/net v0.4.0 // indirect 38 | golang.org/x/sys v0.3.0 // indirect 39 | golang.org/x/text v0.5.0 // indirect 40 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 41 | gopkg.in/yaml.v2 v2.4.0 // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /user/internal/server/health.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/health/grpc_health_v1" 7 | ) 8 | 9 | type HealthImpl struct{} 10 | 11 | func (h *HealthImpl) Check(_ context.Context, _ *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) { 12 | return &grpc_health_v1.HealthCheckResponse{ 13 | Status: grpc_health_v1.HealthCheckResponse_SERVING, 14 | }, nil 15 | } 16 | 17 | func (h *HealthImpl) Watch(req *grpc_health_v1.HealthCheckRequest, w grpc_health_v1.Health_WatchServer) error { 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /user/internal/server/manager.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/JirafaYe/user/internal/store/cache" 5 | "github.com/JirafaYe/user/internal/store/local" 6 | "log" 7 | ) 8 | 9 | var m *Manager 10 | 11 | type Manager struct { 12 | localer *local.Manager 13 | cacher *cache.Manager 14 | } 15 | 16 | func init() { 17 | var err error 18 | localer, err := local.New() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | cacher, err := cache.New() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | m = &Manager{ 28 | localer: localer, 29 | cacher: cacher, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /user/internal/server/user.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "github.com/JirafaYe/user/internal/service" 6 | "github.com/JirafaYe/user/pkg" 7 | ) 8 | 9 | type UserSrv struct { 10 | service.UnimplementedUserProtoServer 11 | } 12 | 13 | func (u *UserSrv) Register(_ context.Context, request *service.RegisterRequest) (*service.RegisterResponse, error) { 14 | username, password := request.Username, request.Password 15 | if exit := m.localer.GetUsernameExit(username); exit { 16 | return &service.RegisterResponse{ 17 | StatusCode: 1, 18 | StatusMsg: "用户名已存在", 19 | UserId: 0, 20 | Token: "", 21 | }, nil 22 | } 23 | if id, register := m.localer.Register(username, password); register { 24 | token := setUserToken(id, username) 25 | return &service.RegisterResponse{ 26 | StatusCode: 0, 27 | StatusMsg: "注册成功", 28 | UserId: id, 29 | Token: token, 30 | }, nil 31 | } else { 32 | return &service.RegisterResponse{ 33 | StatusCode: 1, 34 | StatusMsg: "注册失败", 35 | UserId: id, 36 | Token: "", 37 | }, nil 38 | } 39 | } 40 | 41 | // 设置用户token 42 | func setUserToken(id int64, username string) string { 43 | token, err := pkg.GenerateToken(id, username) 44 | if err != nil { 45 | panic(err) 46 | } 47 | err = m.cacher.SetUserToken(token, username) 48 | if err != nil { 49 | panic(err) 50 | } 51 | return token 52 | } 53 | 54 | func parseToken(token string) string { 55 | claims, err := pkg.ParseToken(token) 56 | if err != nil { 57 | panic(err) 58 | } 59 | return claims.Username 60 | } 61 | 62 | func (u *UserSrv) Login(_ context.Context, request *service.LoginRequest) (*service.LoginResponse, error) { 63 | username, password := request.Username, request.Password 64 | login, id, err := m.localer.Login(username, password) 65 | if login { 66 | token := setUserToken(id, username) 67 | return &service.LoginResponse{ 68 | StatusCode: 0, 69 | StatusMsg: "登录成功", 70 | UserId: id, 71 | Token: token, 72 | }, nil 73 | } else { 74 | return &service.LoginResponse{ 75 | StatusCode: 1, 76 | StatusMsg: "登录失败", 77 | UserId: 0, 78 | Token: "", 79 | }, err 80 | } 81 | } 82 | 83 | func (u *UserSrv) IsLogin(_ context.Context, request *service.IsLoginRequest) (*service.IsLoginResponse, error) { 84 | token := request.Token 85 | username := parseToken(token) 86 | if exist := m.cacher.IsUserTokenExist(username); exist { 87 | return &service.IsLoginResponse{ 88 | Code: 0, 89 | Msg: "获取成功", 90 | }, nil 91 | } else { 92 | return &service.IsLoginResponse{ 93 | Code: 1, 94 | Msg: "获取失败", 95 | }, nil 96 | } 97 | } 98 | 99 | func (u *UserSrv) GetUserMsg(_ context.Context, request *service.UserRequest) (*service.UserResponse, error) { 100 | token := request.Token 101 | claims, err := pkg.ParseToken(token) 102 | if err != nil { 103 | return &service.UserResponse{ 104 | StatusCode: 1, 105 | StatusMsg: "token解析错误", 106 | User: nil, 107 | }, err 108 | } 109 | username := claims.Username 110 | userMsg := m.localer.GetUserMsg(username) 111 | usg := &service.UserMsg{ 112 | Id: userMsg.ID, 113 | Name: userMsg.Name, 114 | FollowCount: 0, 115 | FollowerCount: 0, 116 | IsFollow: false, 117 | } 118 | return &service.UserResponse{ 119 | StatusCode: 0, 120 | StatusMsg: "获取成功", 121 | User: usg, 122 | }, nil 123 | } 124 | -------------------------------------------------------------------------------- /user/internal/server/user_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/JirafaYe/user/pkg" 5 | "testing" 6 | ) 7 | 8 | func TestA(t *testing.T) { 9 | token, err := pkg.GenerateToken(21521512234, "1125887000@qq.com") 10 | if err != nil { 11 | panic(err) 12 | } 13 | err = m.cacher.SetUserToken(token, "1125887000@qq.com") 14 | if err != nil { 15 | panic(err) 16 | } 17 | } 18 | 19 | func TestB(t *testing.T) { 20 | err := m.cacher.DelUserToken("1125887001@qq.com") 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /user/internal/service/proto/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/user/internal/service/proto/.gitkeep -------------------------------------------------------------------------------- /user/internal/service/proto/userProto.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "./internal/service;service"; 4 | 5 | package service; 6 | 7 | message RegisterRequest { 8 | string username = 1; 9 | string password = 2; 10 | } 11 | 12 | message RegisterResponse { 13 | int32 status_code = 1; 14 | string status_msg = 2; 15 | int64 user_id = 3; 16 | string token = 4; 17 | } 18 | 19 | message LoginRequest { 20 | string username = 1; 21 | string password = 2; 22 | } 23 | 24 | message LoginResponse { 25 | int32 status_code = 1; 26 | string status_msg = 2; 27 | int64 user_id = 3; 28 | string token = 4; 29 | } 30 | 31 | message IsLoginRequest { 32 | string token = 1; 33 | } 34 | 35 | message IsLoginResponse { 36 | int32 code = 1; 37 | string msg = 2; 38 | } 39 | 40 | message UserRequest{ 41 | int64 user_id = 1; 42 | string token = 2; 43 | } 44 | message UserResponse{ 45 | int32 status_code = 1; 46 | string status_msg = 2; 47 | UserMsg user = 3; 48 | } 49 | message UserMsg{ 50 | int64 id = 1; 51 | string name = 2; 52 | int32 follow_count = 3; 53 | int32 follower_count = 4; 54 | bool is_follow = 5; 55 | } 56 | service UserProto{ 57 | rpc Login(LoginRequest) returns (LoginResponse) {} 58 | rpc Register(RegisterRequest) returns (RegisterResponse) {} 59 | rpc IsLogin(IsLoginRequest)returns (IsLoginResponse){} 60 | rpc GetUserMsg(UserRequest)returns (UserResponse){} 61 | } -------------------------------------------------------------------------------- /user/internal/store/cache/config.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/JirafaYe/user/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Addr string `json:"addr"` 10 | Port string `json:"port"` 11 | Password string `json:"password"` 12 | } 13 | 14 | func (c *Config) Key() string { 15 | return "tiktok/cache" 16 | } 17 | 18 | var C Config 19 | 20 | func init() { 21 | err := config.ReadConfig(&C) 22 | if err != nil { 23 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /user/internal/store/cache/manager.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "github.com/go-redis/redis/v8" 6 | ) 7 | 8 | type Manager struct { 9 | handler *redis.Client 10 | } 11 | 12 | func New() (*Manager, error) { 13 | m := &Manager{ 14 | handler: redis.NewClient( 15 | &redis.Options{ 16 | Addr: C.Addr + ":" + C.Port, 17 | Password: C.Password, 18 | }, 19 | ), 20 | } 21 | return m, m.handler.Ping(context.Background()).Err() 22 | } 23 | -------------------------------------------------------------------------------- /user/internal/store/cache/token.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | const ( 8 | UserToken = "USER_TOKEN" 9 | ) 10 | 11 | func (m *Manager) SetUserToken(token string, username string) error { 12 | err := m.handler.HSet(context.Background(), UserToken, username, token).Err() 13 | return err 14 | } 15 | 16 | func (m *Manager) DelUserToken(username string) error { 17 | return m.handler.HDel(context.Background(), UserToken, username).Err() 18 | 19 | } 20 | 21 | func (m *Manager) IsUserTokenExist(username string) bool { 22 | exists, err := m.handler.HExists(context.Background(), UserToken, username).Result() 23 | return err == nil && exists 24 | } 25 | -------------------------------------------------------------------------------- /user/internal/store/local/config.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/JirafaYe/user/config" 5 | "log" 6 | ) 7 | 8 | type Config struct { 9 | Host string `json:"host"` 10 | Port string `json:"port"` 11 | User string `json:"user"` 12 | Name string `json:"name"` 13 | Password string `json:"password"` 14 | } 15 | 16 | func (c *Config) Key() string { 17 | return "tiktok/local" 18 | } 19 | 20 | var C Config 21 | 22 | func init() { 23 | err := config.ReadConfig(&C) 24 | if err != nil { 25 | log.Fatalf("failed to load config %v, errno: %v", C.Key(), err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /user/internal/store/local/manager.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "fmt" 5 | 6 | "gorm.io/driver/mysql" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type Manager struct { 11 | handler *gorm.DB 12 | } 13 | 14 | func New() (*Manager, error) { 15 | db, err := gorm.Open( 16 | mysql.Open( 17 | fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", 18 | C.User, 19 | C.Password, 20 | C.Host, 21 | C.Port, 22 | C.Name, 23 | ), 24 | ), 25 | &gorm.Config{}, 26 | ) 27 | return &Manager{ 28 | handler: db, 29 | }, err 30 | } 31 | -------------------------------------------------------------------------------- /user/internal/store/local/user.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/JirafaYe/user/pkg" 5 | "strconv" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type User struct { 11 | ID int64 `gorm:"column:id"` 12 | Username string `gorm:"column:username"` 13 | Password string `gorm:"column:password"` 14 | CreateAt time.Time `gorm:"column:created_at"` 15 | UpdatedAt time.Time `gorm:"column:updated_at"` 16 | DeletedAt *time.Time `gorm:"column:deleted_at"` 17 | Name string `gorm:"column:name"` 18 | } 19 | 20 | var user = "t_user" 21 | 22 | func (m *Manager) GetUsernameExit(username string) bool { 23 | 24 | err := m.handler.Table(user).Where("username=?", username).First(&user, username).Error 25 | if err != nil { 26 | return false 27 | } 28 | return true 29 | } 30 | 31 | func (m *Manager) Register(username, password string) (int64, bool) { 32 | if err := pkg.Init("2006-01-02", 1); err != nil { 33 | panic(err) 34 | return 0, false 35 | } 36 | id := pkg.GenID() 37 | password = pkg.SaltEncodePwd(password) 38 | num := strconv.FormatInt(id, 10) 39 | name := "未命名" + num[15:19] 40 | u := User{ 41 | ID: id, 42 | Username: username, 43 | Password: password, 44 | CreateAt: time.Now(), 45 | UpdatedAt: time.Now(), 46 | DeletedAt: nil, 47 | Name: name, 48 | } 49 | if err := m.handler.Table(user).Create(u).Error; err != nil { 50 | panic(err) 51 | return 0, false 52 | } 53 | return id, true 54 | } 55 | 56 | func (m *Manager) Login(username, password string) (bool, int64, error) { 57 | var u User 58 | err := m.handler.Table(user).Where("username=?", username).First(&u).Error 59 | if err != nil { 60 | panic(err) 61 | return false, u.ID, err 62 | } 63 | pwd := strings.Split(u.Password, "$") 64 | userPassword := pkg.VerifyUserPassword(password, pwd[1], pwd[2]) 65 | return userPassword, u.ID, nil 66 | } 67 | 68 | func (m *Manager) GetUserMsg(username string) User { 69 | var u User 70 | err := m.handler.Table(user).Where("username=?", username).First(&u).Error 71 | if err != nil { 72 | panic(err) 73 | return u 74 | } 75 | return u 76 | } 77 | -------------------------------------------------------------------------------- /user/internal/store/local/user_test.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "fmt" 5 | "github.com/JirafaYe/user/pkg" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func TestA(t *testing.T) { 11 | if err := pkg.Init("2006-01-02", 1); err != nil { 12 | fmt.Printf("init failed, err:%v\n", err) 13 | return 14 | } 15 | id := pkg.GenID() 16 | num := id >> 48 17 | fmt.Println(num) 18 | name := "未命令" + strconv.Itoa(int(num)) 19 | fmt.Println(name) 20 | } 21 | 22 | func TestB(t *testing.T) { 23 | if err := pkg.Init("2006-01-02", 1); err != nil { 24 | fmt.Printf("init failed, err:%v\n", err) 25 | return 26 | } 27 | 28 | for i := 0; i < 1000; i++ { 29 | id := pkg.GenID() 30 | num := id >> 52 31 | fmt.Println(num) 32 | } 33 | } 34 | 35 | func TestC(t *testing.T) { 36 | if err := pkg.Init("2006-01-02", 1); err != nil { 37 | fmt.Printf("init failed, err:%v\n", err) 38 | return 39 | } 40 | 41 | for i := 0; i < 1000; i++ { 42 | id := pkg.GenID() 43 | num := strconv.FormatInt(id, 10) 44 | fmt.Println(id) 45 | fmt.Println(num) 46 | fmt.Println(num[15:19]) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /user/pkg/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JirafaYe/tiktok/69f461d72d026463a02c747da98ac02354fba775/user/pkg/.gitkeep -------------------------------------------------------------------------------- /user/pkg/encoder.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/sha512" 6 | "encoding/hex" 7 | "fmt" 8 | "golang.org/x/crypto/pbkdf2" 9 | "hash" 10 | ) 11 | 12 | const ( 13 | defaultSaltLen = 256 14 | defaultIterations = 10000 15 | defaultKeyLen = 512 16 | ) 17 | 18 | var defaultHashFunction = sha512.New 19 | 20 | // Options is a struct for custom values of salt length, number of iterations, the encoded key's length, 21 | // and the hash function being used. If set to `nil`, default options are used: 22 | // &Options{ 256, 10000, 512, "sha512" } 23 | type Options struct { 24 | SaltLen int 25 | Iterations int 26 | KeyLen int 27 | HashFunction func() hash.Hash 28 | } 29 | 30 | func generateSalt(length int) []byte { 31 | const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 32 | salt := make([]byte, length) 33 | rand.Read(salt) 34 | for key, val := range salt { 35 | salt[key] = alphanum[val%byte(len(alphanum))] 36 | } 37 | return salt 38 | } 39 | 40 | // Encode takes two arguments, a raw password, and a pointer to an Options struct. 41 | // In order to use default options, pass `nil` as the second argument. 42 | // It returns the generated salt and encoded key for the user. 43 | func Encode(rawPwd string, options *Options) (string, string) { 44 | if options == nil { 45 | salt := generateSalt(defaultSaltLen) 46 | encodedPwd := pbkdf2.Key([]byte(rawPwd), salt, defaultIterations, defaultKeyLen, defaultHashFunction) 47 | return string(salt), hex.EncodeToString(encodedPwd) 48 | } 49 | salt := generateSalt(options.SaltLen) 50 | encodedPwd := pbkdf2.Key([]byte(rawPwd), salt, options.Iterations, options.KeyLen, options.HashFunction) 51 | return string(salt), hex.EncodeToString(encodedPwd) 52 | } 53 | 54 | // Verify takes four arguments, the raw password, its generated salt, the encoded password, 55 | // and a pointer to the Options struct, and returns a boolean value determining whether the password is the correct one or not. 56 | // Passing `nil` as the last argument resorts to default options. 57 | func Verify(rawPwd string, salt string, encodedPwd string, options *Options) bool { 58 | if options == nil { 59 | return encodedPwd == hex.EncodeToString(pbkdf2.Key([]byte(rawPwd), []byte(salt), defaultIterations, defaultKeyLen, defaultHashFunction)) 60 | } 61 | return encodedPwd == hex.EncodeToString(pbkdf2.Key([]byte(rawPwd), []byte(salt), options.Iterations, options.KeyLen, options.HashFunction)) 62 | } 63 | 64 | func SaltEncodePwd(pwd string) string { 65 | options := &Options{SaltLen: 6, Iterations: 10000, KeyLen: 12, HashFunction: sha512.New} 66 | salt, pwds := Encode(pwd, options) 67 | password := fmt.Sprintf("$%s$%s", salt, pwds) 68 | return password 69 | } 70 | 71 | func VerifyUserPassword(rawPwd, salt, encodePwd string) bool { 72 | options := &Options{SaltLen: 6, Iterations: 10000, KeyLen: 12, HashFunction: sha512.New} 73 | return Verify(rawPwd, salt, encodePwd, options) 74 | } 75 | -------------------------------------------------------------------------------- /user/pkg/encoder_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha512" 6 | "fmt" 7 | "golang.org/x/crypto/bcrypt" 8 | "strings" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestEncode(t *testing.T) { 14 | //// 方式一:使用默认选项 15 | //salt, encodedPwd := Encode("generic password", nil) 16 | //check := Verify("generic password", salt, encodedPwd, nil) 17 | //fmt.Println(encodedPwd) 18 | //fmt.Println(check) // true 19 | // 20 | //// 方式二:使用自定义选项 21 | //options :=Options{SaltLen: 32, Iterations: 10000, KeyLen: 32, HashFunction: md5.New} 22 | //salt, encodedPwd = Encode("generic password", options) 23 | //fmt.Printf("%T\n", encodedPwd) 24 | //fmt.Println(encodedPwd) 25 | //fmt.Println(salt) 26 | //check = Verify("generic password", salt, encodedPwd, options) 27 | //fmt.Println(check) // true 28 | options := &Options{SaltLen: 6, Iterations: 10000, KeyLen: 12, HashFunction: md5.New} 29 | pwd := SaltEncodePwd("1234512334") 30 | fmt.Println(pwd) 31 | fmt.Println(len(pwd)) 32 | fmt.Println(options) 33 | verify := Verify("1234512334", strings.Split(pwd, "$")[1], strings.Split(pwd, "$")[2], options) 34 | fmt.Println(verify) 35 | } 36 | 37 | func TestAb(t *testing.T) { 38 | options := &Options{SaltLen: 6, Iterations: 10000, KeyLen: 12, HashFunction: sha512.New} 39 | fmt.Println(options) 40 | verify := Verify("123", "saWDvH", "609c54ceaaa9c8c53c3ebebf", options) 41 | fmt.Println(verify) 42 | } 43 | 44 | func TestAa(t *testing.T) { 45 | options := &Options{SaltLen: 6, Iterations: 10000, KeyLen: 12, HashFunction: sha512.New} 46 | salt, pwd := Encode("123", options) 47 | fmt.Println(options) 48 | println(fmt.Sprintf("$%s$%s", salt, pwd)) 49 | } 50 | 51 | func TestB(t *testing.T) { 52 | password := []byte("thisIsPassWord") 53 | //password2 := []byte("thisIsPas1sWord") 54 | nowG := time.Now() 55 | hashedPassword, _ := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) 56 | fmt.Println("加密后", string(hashedPassword), "耗时", time.Now().Sub(nowG)) 57 | nowC := time.Now() 58 | err := bcrypt.CompareHashAndPassword([]byte("$2a$10$TakvaOGlwUVmZwXnzfhecOXsxc/Xoyu7RU5DlBxkvarLb2kPBFr4m"), []byte("123")) 59 | fmt.Println(err) 60 | fmt.Println("验证耗费时间", time.Now().Sub(nowC)) 61 | fmt.Println(err) 62 | 63 | } 64 | -------------------------------------------------------------------------------- /user/pkg/jwt.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | "time" 6 | ) 7 | 8 | var jwtSecret = []byte("tiktok.user") 9 | 10 | type Claims struct { 11 | Id int64 `json:"id"` 12 | Username string `json:"username"` 13 | jwt.StandardClaims 14 | } 15 | 16 | // GenerateToken 签发用户Token 17 | func GenerateToken(id int64, username string) (string, error) { 18 | nowTime := time.Now() 19 | expireTime := nowTime.Add(7 * 24 * time.Hour) 20 | claims := Claims{ 21 | Id: id, 22 | Username: username, 23 | StandardClaims: jwt.StandardClaims{ 24 | ExpiresAt: expireTime.Unix(), 25 | Issuer: "tiktok.user", 26 | }, 27 | } 28 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 29 | token, err := tokenClaims.SignedString(jwtSecret) 30 | return token, err 31 | } 32 | 33 | // ParseToken 验证用户token 34 | func ParseToken(token string) (*Claims, error) { 35 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 36 | return jwtSecret, nil 37 | }) 38 | if tokenClaims != nil { 39 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 40 | return claims, nil 41 | } 42 | } 43 | return nil, err 44 | } 45 | -------------------------------------------------------------------------------- /user/pkg/snowflake.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | _ "github.com/onsi/gomega" 5 | "time" 6 | 7 | sf "github.com/bwmarrin/snowflake" 8 | ) 9 | 10 | var node *sf.Node 11 | 12 | func Init(startTime string, machineID int64) (err error) { 13 | var st time.Time 14 | st, err = time.Parse("2006-01-02", startTime) 15 | if err != nil { 16 | return 17 | } 18 | sf.Epoch = st.UnixNano() / 1000000 19 | node, err = sf.NewNode(machineID) 20 | return 21 | } 22 | func GenID() int64 { 23 | return node.Generate().Int64() 24 | } 25 | -------------------------------------------------------------------------------- /user/pkg/snowflake_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestSnowFlake(t *testing.T) { 10 | if err := Init("2006-01-02", 1); err != nil { 11 | fmt.Printf("init failed, err:%v\n", err) 12 | return 13 | } 14 | maps := make(map[int64]interface{}, 0) 15 | //测试重复性,经过测试,10000000个id生成需要3s左右,且不存在重复 16 | test1(maps) 17 | 18 | //取余九位数,在1.5ms内并发1w次,不会出现重复(我自己的抖音号是九位数,且九位数能覆盖上亿用户) 19 | 20 | for i := 0; i < 100; i++ { 21 | test2(maps) 22 | } 23 | time.Sleep(1000000000000000) 24 | 25 | } 26 | func test1(maps map[int64]interface{}) { 27 | start := time.Now() 28 | for i := 0; i < 10000000; i++ { 29 | id := GenID() 30 | if _, ok := maps[id]; ok { 31 | fmt.Printf("出现重复") 32 | } 33 | maps[id] = 0 34 | } 35 | end := time.Since(start) 36 | fmt.Printf("容量大小:%v\n", len(maps)) 37 | fmt.Println(end) 38 | } 39 | 40 | func test2(maps map[int64]interface{}) { 41 | //maps := make(map[int64]interface{}, 0) 42 | start := time.Now() 43 | for i := 0; i < 100000; i++ { 44 | id := GenID() % 100000000 45 | if _, ok := maps[id]; ok { 46 | fmt.Printf("出现重复\n") 47 | } 48 | maps[id] = 0 49 | } 50 | end := time.Since(start) 51 | fmt.Printf("容量大小:%v\n", len(maps)) 52 | fmt.Println(end) 53 | } 54 | --------------------------------------------------------------------------------