├── .editorconfig
├── .github
└── workflows
│ └── docker-image.yml
├── .gitignore
├── .idea
├── .gitignore
├── modules.xml
├── toktik.iml
└── vcs.xml
├── Dockerfile
├── LICENSE
├── README.md
├── README_zh-CN.md
├── add-kitex-service.sh
├── basic-build-image
└── Dockerfile
├── basic-runtime-image
└── Dockerfile
├── build-all.sh
├── constant
├── biz
│ ├── auth.go
│ ├── comment.go
│ ├── favorite.go
│ ├── feed.go
│ ├── gwerror.go
│ ├── publish.go
│ ├── relation.go
│ ├── response.go
│ ├── statuscode.go
│ ├── user.go
│ └── wechat.go
└── config
│ ├── env.go
│ └── service.go
├── docker-compose.yaml
├── docs
├── HowToAddServices.md
└── images
│ └── cert-cut.png
├── go.mod
├── go.sum
├── idl
├── auth.proto
├── comment.proto
├── favorite.proto
├── feed.proto
├── publish.proto
├── relation.proto
├── user.proto
└── wechat.proto
├── kitex_gen
└── douyin
│ ├── auth
│ ├── auth.pb.fast.go
│ ├── auth.pb.go
│ └── authservice
│ │ ├── authservice.go
│ │ ├── client.go
│ │ ├── invoker.go
│ │ └── server.go
│ ├── comment
│ ├── comment.pb.fast.go
│ ├── comment.pb.go
│ └── commentservice
│ │ ├── client.go
│ │ ├── commentservice.go
│ │ ├── invoker.go
│ │ └── server.go
│ ├── favorite
│ ├── favorite.pb.fast.go
│ ├── favorite.pb.go
│ └── favoriteservice
│ │ ├── client.go
│ │ ├── favoriteservice.go
│ │ ├── invoker.go
│ │ └── server.go
│ ├── feed
│ ├── feed.pb.fast.go
│ ├── feed.pb.go
│ └── feedservice
│ │ ├── client.go
│ │ ├── feedservice.go
│ │ ├── invoker.go
│ │ └── server.go
│ ├── publish
│ ├── publish.pb.fast.go
│ ├── publish.pb.go
│ └── publishservice
│ │ ├── client.go
│ │ ├── invoker.go
│ │ ├── publishservice.go
│ │ └── server.go
│ ├── relation
│ ├── relation.pb.fast.go
│ ├── relation.pb.go
│ └── relationservice
│ │ ├── client.go
│ │ ├── invoker.go
│ │ ├── relationservice.go
│ │ └── server.go
│ ├── user
│ ├── user.pb.fast.go
│ ├── user.pb.go
│ └── userservice
│ │ ├── client.go
│ │ ├── invoker.go
│ │ ├── server.go
│ │ └── userservice.go
│ └── wechat
│ ├── wechat.pb.fast.go
│ ├── wechat.pb.go
│ └── wechatservice
│ ├── client.go
│ ├── invoker.go
│ ├── server.go
│ └── wechatservice.go
├── logging
└── init.go
├── manifests-dev
├── configmap.yaml
├── deployment-toktik-auth-api.yaml
├── deployment-toktik-comment-api.yaml
├── deployment-toktik-favorite-api.yaml
├── deployment-toktik-feed-api.yaml
├── deployment-toktik-http-api.yaml
├── deployment-toktik-publish-api.yaml
├── deployment-toktik-relation-api.yaml
├── deployment-toktik-user-api.yaml
├── deployment-toktik-wechat-api.yaml
├── service-toktik-auth-api.yaml
├── service-toktik-comment-api.yaml
├── service-toktik-favorite-api.yaml
├── service-toktik-feed-api.yaml
├── service-toktik-http-api.yaml
├── service-toktik-publish-api.yaml
├── service-toktik-relation-api.yaml
├── service-toktik-user-api.yaml
└── service-toktik-wechat-api.yaml
├── otel.yaml
├── repo
├── comments.gen.go
├── favorites.gen.go
├── gen.go
├── gen
│ └── gormGen.go
├── init.go
├── migrate
│ └── migrate.go
├── model
│ ├── comment.go
│ ├── favorite.go
│ ├── model.go
│ ├── relation.go
│ ├── token.go
│ ├── user.go
│ ├── user_test.go
│ └── video.go
├── relations.gen.go
├── user_tokens.gen.go
├── users.gen.go
└── videos.gen.go
├── rpc
└── http.go
├── service
├── auth
│ ├── build.sh
│ ├── handler.go
│ ├── handler_test.go
│ ├── kitex_info.yaml
│ ├── main.go
│ └── script
│ │ └── bootstrap.sh
├── comment
│ ├── build.sh
│ ├── handler.go
│ ├── handler_test.go
│ ├── kitex_info.yaml
│ ├── main.go
│ └── script
│ │ └── bootstrap.sh
├── favorite
│ ├── build.sh
│ ├── handler.go
│ ├── kitex_info.yaml
│ ├── main.go
│ └── script
│ │ └── bootstrap.sh
├── feed
│ ├── build.sh
│ ├── handler.go
│ ├── handler_test.go
│ ├── kitex_info.yaml
│ ├── main.go
│ └── script
│ │ └── bootstrap.sh
├── publish
│ ├── build.sh
│ ├── handler.go
│ ├── handler_test.go
│ ├── kitex_info.yaml
│ ├── main.go
│ ├── resources
│ │ └── bear.mp4
│ └── script
│ │ └── bootstrap.sh
├── relation
│ ├── build.sh
│ ├── handler.go
│ ├── handler_test.go
│ ├── kitex_info.yaml
│ ├── main.go
│ └── script
│ │ └── bootstrap.sh
├── user
│ ├── build.sh
│ ├── handler.go
│ ├── kitex_info.yaml
│ ├── main.go
│ └── script
│ │ └── bootstrap.sh
├── web
│ ├── auth
│ │ └── handler.go
│ ├── build.sh
│ ├── comment
│ │ └── handler.go
│ ├── favorite
│ │ └── handler.go
│ ├── feed
│ │ └── handler.go
│ ├── main.go
│ ├── mw
│ │ ├── auth.go
│ │ └── json.go
│ ├── publish
│ │ └── handler.go
│ ├── relation
│ │ └── handler.go
│ ├── user
│ │ └── handler.go
│ └── wechat
│ │ └── handler.go
└── wechat
│ ├── build.sh
│ ├── db
│ ├── gen.sh
│ ├── msg.pb.go
│ └── msg.proto
│ ├── handler.go
│ ├── handler_test.go
│ ├── kitex.yaml
│ ├── kitex_info.yaml
│ ├── main.go
│ └── script
│ └── bootstrap.sh
├── start.sh
├── storage
├── fs.go
├── provider.go
└── s3.go
├── test
├── e2e
│ ├── base_api_test.go
│ ├── common.go
│ ├── interact_api_test.go
│ └── social_api_test.go
└── mock
│ └── db.go
└── unit-test.sh
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | insert_final_newline = true
6 | end_of_line = lf
7 |
8 | [*.yaml]
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.github/workflows/docker-image.yml:
--------------------------------------------------------------------------------
1 | name: Docker Image CI
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: [ "feat-github-action-unit-test" ]
7 | pull_request:
8 | branches: [ "main" ]
9 |
10 | jobs:
11 |
12 | build:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 | -
19 | name: Set up Docker Buildx
20 | uses: docker/setup-buildx-action@v2
21 | -
22 | name: Build services into one image
23 | uses: docker/build-push-action@v4
24 | with:
25 | context: .
26 | push: false
27 | tags: toktik-srv:${{ github.sha }}
28 | cache-from: type=gha
29 | cache-to: type=gha,mode=max
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project ignore
2 | service/**/output/
3 | .env
4 | # Project ignore end
5 |
6 | # If you prefer the allow list template instead of the deny list, see community template:
7 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
8 | #
9 | # Binaries for programs and plugins
10 | *.exe
11 | *.exe~
12 | *.dll
13 | *.so
14 | *.dylib
15 |
16 | # Test binary, built with `go test -c`
17 | *.test
18 |
19 | # Output of the go coverage tool, specifically when used with LiteIDE
20 | *.out
21 |
22 | # Dependency directories (remove the comment below to include it)
23 | # vendor/
24 |
25 | # Go workspace file
26 | go.work
27 |
28 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
29 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
30 |
31 | # User-specific stuff
32 | .idea/**/workspace.xml
33 | .idea/**/tasks.xml
34 | .idea/**/usage.statistics.xml
35 | .idea/**/dictionaries
36 | .idea/**/shelf
37 |
38 | # AWS User-specific
39 | .idea/**/aws.xml
40 |
41 | # Generated files
42 | .idea/**/contentModel.xml
43 |
44 | # Sensitive or high-churn files
45 | .idea/**/dataSources/
46 | .idea/**/dataSources.ids
47 | .idea/**/dataSources.local.xml
48 | .idea/**/dataSources.xml
49 | .idea/**/sqlDataSources.xml
50 | .idea/**/dynamic.xml
51 | .idea/**/uiDesigner.xml
52 | .idea/**/dbnavigator.xml
53 |
54 | # Gradle
55 | .idea/**/gradle.xml
56 | .idea/**/libraries
57 |
58 | # Gradle and Maven with auto-import
59 | # When using Gradle or Maven with auto-import, you should exclude module files,
60 | # since they will be recreated, and may cause churn. Uncomment if using
61 | # auto-import.
62 | # .idea/artifacts
63 | # .idea/compiler.xml
64 | # .idea/jarRepositories.xml
65 | # .idea/modules.xml
66 | # .idea/*.iml
67 | # .idea/modules
68 | # *.iml
69 | # *.ipr
70 |
71 | # CMake
72 | cmake-build-*/
73 |
74 | # Mongo Explorer plugin
75 | .idea/**/mongoSettings.xml
76 |
77 | # File-based project format
78 | *.iws
79 |
80 | # IntelliJ
81 | out/
82 |
83 | # mpeltonen/sbt-idea plugin
84 | .idea_modules/
85 |
86 | # JIRA plugin
87 | atlassian-ide-plugin.xml
88 |
89 | # Cursive Clojure plugin
90 | .idea/replstate.xml
91 |
92 | # SonarLint plugin
93 | .idea/sonarlint/
94 |
95 | # Crashlytics plugin (for Android Studio and IntelliJ)
96 | com_crashlytics_export_strings.xml
97 | crashlytics.properties
98 | crashlytics-build.properties
99 | fabric.properties
100 |
101 | # Editor-based Rest Client
102 | .idea/httpRequests
103 |
104 | # Android studio 3.1+ serialized cache file
105 | .idea/caches/build_file_checksums.ser
106 |
107 | .vscode/*
108 | !.vscode/settings.json
109 | !.vscode/tasks.json
110 | !.vscode/launch.json
111 | !.vscode/extensions.json
112 | !.vscode/*.code-snippets
113 |
114 | # Local History for Visual Studio Code
115 | .history/
116 |
117 | # Built Visual Studio Code Extensions
118 | *.vsix
119 |
120 | # Service build output
121 | **/output/
122 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 | /protoeditor.xml
10 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/toktik.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # 编译镜像
2 | FROM docker.io/nicognaw/toktik-basic-build:v1 AS build
3 | ENV TZ=Asia/Shanghai
4 | ENV DEBIAN_FRONTEND=noninteractive
5 |
6 | # 构建依赖
7 | RUN apt-get update && \
8 | apt-get install -yq git ffmpeg libavcodec-dev libavutil-dev libavformat-dev libswscale-dev && \
9 | apt-get clean && \
10 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \
11 | rm -rf /var/lib/apt/lists/*
12 |
13 | # 获取文件
14 | RUN mkdir -p /source
15 | WORKDIR /source
16 | COPY . .
17 |
18 | # 编译
19 | RUN bash unit-test.sh && bash build-all.sh
20 |
21 | # 运行环境
22 | FROM docker.io/nicognaw/toktik-basic:v1
23 | ENV TZ=Asia/Shanghai
24 | ENV DEBIAN_FRONTEND=noninteractive
25 |
26 | # FFmpeg 及依赖
27 | RUN apt-get update && \
28 | apt-get install -yq ca-certificates && \
29 | apt-get clean && \
30 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \
31 | rm -rf /var/lib/apt/lists/*
32 |
33 | # RUN mkdir -p /data/apps/toktik-service-bundle
34 | WORKDIR /data/apps/toktik-service-bundle
35 |
36 | # 收集数据
37 | COPY --from=build /source/output/ .
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2023 Toktik-Team
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Toktik
2 |
3 | |  | ChatGPT integrated short video microservice application built with `Kitex` and `Hertz` , made by [Toktik-Team](https://github.com/Toktik-Team) in _The 5th Bytedance Youth Training Camp_. |
4 | | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
5 |
6 | **English** | [简体中文](README_zh-CN.md)
7 |
8 | _The 5th Bytedance Youth Training Camp_ Report Documentation:
9 |
10 | ## Recommend
11 |
12 | Now Recommend for [GuGoTik](https://github.com/GuGoOrg/GuGoTik) !
13 |
14 | TokTik is no longer maintained and has no planned maintenance plans, please go to [GuGoTik](https://github.com/GuGoOrg/GuGoTik). GuGoTik respects and follows TokTik's workline, improving and optimizing almost every components from it and adding more features. For best expermental experience, please use [GuGoTik](https://github.com/GuGoOrg/GuGoTik) instead of TokTik.
15 |
16 | Give us your stars if you like our project.
17 |
18 | ## Awards
19 |
20 | Earned the **_Best Project_** award in _the 5th Bytedance Youth Training Camp_.
21 |
22 | 
23 |
24 | ## Demo
25 |
26 | > Download Dousheng app from [here](https://bytedance.feishu.cn/docs/doccnM9KkBAdyDhg8qaeGlIz7S7#), and use this URL to test with the demo.
27 |
28 | https://toktik.xctra.cn/
29 |
30 | ## Project Structure
31 |
32 | - [constant](constant)
33 | - [biz](constant/biz) - Business logic related constants
34 | - [config](constant/config)
35 | - [env.go](constant/config/env.go) - Environment variable configs
36 | - [service.go](constant/config/service.go) - Service names and ports
37 | - [idl](idl)
38 | - [auth.proto](idl/auth.proto) - RPC definition of auth service
39 | - [comment.proto](idl/comment.proto) - RPC definition of comment service
40 | - [favorite.proto](idl/favorite.proto) - RPC definition of favorite service
41 | - [feed.proto](idl/feed.proto) - RPC definition of feed service
42 | - [publish.proto](idl/publish.proto) - RPC definition of publish service
43 | - [relation.proto](idl/relation.proto) - RPC definition of relation service
44 | - [user.proto](idl/user.proto) - RPC definition of user service
45 | - [wechat.proto](idl/wechat.proto) - RPC definition of chat service
46 | - [kitex_gen](kitex_gen) - Generated code by Kitex
47 | - [logging](logging) - Logging middleware
48 | - [manifests-dev](manifests-dev) - Kubernetes manifests
49 | - [repo](repo) - Database schemas and generated code by Gorm Gen
50 | - [service](service)
51 | - [auth](service/auth) - Auth service impl
52 | - [comment](service/comment) - Comment service impl
53 | - [favorite](service/favorite) - Favorite service impl
54 | - [feed](service/feed) - Feed service impl
55 | - [publish](service/publish) - Publish service impl
56 | - [relation](service/relation) - Relation service impl
57 | - [user](service/user) - User service impl
58 | - [web](service/web) - Web api gateway
59 | - [mw](service/web/mw) - Hertz middlewares
60 | - [wechat](service/wechat) - Chat service impl
61 | - [storage](storage) - Storage middleware, s3, local storage & volcengine [ImageX](https://www.volcengine.com/products/imagex) supported
62 | - [test](test)
63 | - [e2e](test/e2e) - End-to-end test
64 | - [mock](test/mock) - Mock data for unit test
65 |
66 | ## Prerequisite
67 |
68 | > This project does not support Windows, as Kitex does not support either.
69 |
70 | - Linux / MacOS
71 | - Go
72 | - FFmpeg
73 | - PostgreSQL
74 | - Redis
75 | - OpenTelemetry Collector
76 |
77 | For observability infrastructures, it's recommended to use:
78 |
79 | - Jaeger All in one
80 | - Victoria Metrics
81 | - Grafana
82 |
83 | ## Build
84 |
85 | Run `./build-all.sh` in your Linux environment to compile all services.
86 |
87 | ## Configurations
88 |
89 | Check out [`constant/config/env.go`](constant/config/env.go)
90 |
91 | ## Run
92 |
93 | - Run `start.sh --service ` to start a service.
94 | - `service_name` could be any of the sub directory of `./service`.
95 |
96 | ## Test
97 |
98 | ### Unit Test
99 |
100 | Run `./unit-test.sh`
101 |
102 | ### End-to-End Test
103 |
104 | Run `go test toktik/test/e2e -tags="e2e"`
105 |
106 | ## How to Contribute
107 |
108 | 1. Please following the [HowToAddServices](docs/HowToAddServices.md) to create your own service.
109 | 2. Create a new branch and make your changes.
110 | 3. Create a pull request to the `main` branch.
111 | 4. Wait for review and merge.
112 |
113 | ## Contributors
114 |
115 | - [Nico](https://github.com/nicognaW)
116 | - [Eric_Lian](https://github.com/ExerciseBook)
117 | - [Dark Litss](https://github.com/lss233)
118 | - [HikariLan](https://github.com/shaokeyibb)
119 | - [YunShu](https://github.com/Selflocking)
120 |
121 | ## License
122 |
123 | Toktik is licensed under the [MIT License](LICENSE).
124 |
--------------------------------------------------------------------------------
/README_zh-CN.md:
--------------------------------------------------------------------------------
1 | # Toktik
2 |
3 | |  | 集成 ChatGPT 的短视频微服务应用,使用 Kitex 和 Hertz 构建,由 [Toktik-Team](https://github.com/Toktik-Team) 开发,作为*第五届字节跳动青训营*大作业。 |
4 | | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
5 |
6 | [English](README.md) | **简体中文**
7 |
8 | *第五届字节跳动青训营*大作业汇报文档:
9 |
10 | ## Recommend
11 | 现在推荐使用 [GuGoTik](https://github.com/GuGoOrg/GuGoTik)!
12 |
13 | TokTik 目前不再维护且尚未有维护计划,推荐查看 [GuGoTik](https://github.com/GuGoOrg/GuGoTik)。GuGoTik 秉承 TokTik 的核心思想并扬长避短,匠心改进了几乎所有的模块并加入了更丰富的功能。为了让您得到更好的实验体验,请移玉步到 [GuGoTik](https://github.com/GuGoOrg/GuGoTik)。
14 |
15 | 如果你喜欢我们的项目的话,请给我们 Stars ~
16 |
17 | ## Awards
18 |
19 | 获得 *第五届字节跳动青训营* **_码如磐石奖(一等奖)_** 奖项.
20 |
21 | 
22 |
23 | ## 示例
24 |
25 | > 从 [此处](https://bytedance.feishu.cn/docs/doccnM9KkBAdyDhg8qaeGlIz7S7#) 下载抖声 app 后, 填入以下地址来体验 demo。
26 |
27 | https://toktik.xctra.cn/
28 |
29 | ## 项目结构
30 |
31 | - [constant](constant)
32 | - [biz](constant/biz) - 业务逻辑相关常量
33 | - [config](constant/config)
34 | - [env.go](constant/config/env.go) - 环境变量配置
35 | - [service.go](constant/config/service.go) - 服务名称和端口
36 | - [idl](idl)
37 | - [auth.proto](idl/auth.proto) - 认证服务 RPC 定义
38 | - [comment.proto](idl/comment.proto) - 评论服务 RPC 定义
39 | - [favorite.proto](idl/favorite.proto) - 点赞服务 RPC 定义
40 | - [feed.proto](idl/feed.proto) - 视频流服务 RPC 定义
41 | - [publish.proto](idl/publish.proto) - 视频发布服务 RPC 定义
42 | - [relation.proto](idl/relation.proto) - 关注服务 RPC 定义
43 | - [user.proto](idl/user.proto) - 用户服务 RPC 定义
44 | - [wechat.proto](idl/wechat.proto) - 聊天服务 RPC 定义
45 | - [kitex_gen](kitex_gen) - 由 Kitex 自动生成的代码
46 | - [logging](logging) - 日志中间件配置
47 | - [manifests-dev](manifests-dev) - Kubernetes 清单文件
48 | - [repo](repo) - 数据库概要和由 Gorm Gen 自动生成的代码
49 | - [service](service)
50 | - [auth](service/auth) - 鉴权服务实现
51 | - [comment](service/comment) - 评论服务实现
52 | - [favorite](service/favorite) - 点赞服务实现
53 | - [feed](service/feed) - 视频流服务实现
54 | - [publish](service/publish) - 视频发布服务实现
55 | - [relation](service/relation) - 关注服务实现
56 | - [user](service/user) - 用户服务实现
57 | - [web](service/web) - Web API 网关
58 | - [mw](service/web/mw) - Hertz 中间件
59 | - [wechat](service/wechat) - 聊天服务实现
60 | - [storage](storage) - 对象存储中间件,支持 Amazon S3 和本地存储和火山引擎 [ImageX](https://www.volcengine.com/products/imagex)
61 | - [test](test)
62 | - [e2e](test/e2e) - 端到端测试
63 | - [mock](test/mock) - 用于单元测试的 mock 数据
64 |
65 | ## 准备环境
66 |
67 | > 本项目不支持 Windows 操作系统,见 [Kitex](https://www.cloudwego.io/zh/docs/kitex/getting-started/#%E5%87%86%E5%A4%87-golang-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83)
68 |
69 | - Linux / MacOS
70 | - Go
71 | - FFmpeg
72 | - PostgreSQL
73 | - Redis
74 | - OpenTelemetry Collector
75 |
76 | 推荐使用以下可观测性基础设施:
77 |
78 | - Jaeger All in one
79 | - Victoria Metrics
80 | - Grafana
81 |
82 |
83 | ## 构建
84 |
85 | 执行 `./build-all.sh` 一次性构建所有服务.
86 |
87 | ## 配置
88 |
89 | 见 [`constant/config/env.go`](constant/config/env.go)
90 |
91 | ## 运行
92 |
93 | - 执行 `start.sh --service ` 启动任一服务.
94 | - `service_name` 可以是 `./service` 目录下任一子目录名称.
95 |
96 | ## 测试
97 |
98 | ### 单元测试
99 |
100 | 运行 `./unit-test.sh`
101 |
102 | ### 端到端测试
103 |
104 | 执行 `go test toktik/test/e2e -tags="e2e"`
105 |
106 | ## 如何贡献
107 |
108 | 1. 请遵循 [HowToAddServices](docs/HowToAddServices.md) 文件说明以创建新的服务。
109 | 2. 创建一个新的分支并做出更改。
110 | 3. 提交一个 Pull Request 到 `main` 分支。
111 | 4. 等待 review 和合并。
112 |
113 | ## 贡献者
114 |
115 | - [Nico](https://github.com/nicognaW)
116 | - [Eric_Lian](https://github.com/ExerciseBook)
117 | - [Dark Litss](https://github.com/lss233)
118 | - [HikariLan](https://github.com/shaokeyibb)
119 | - [YunShu](https://github.com/Selflocking)
120 |
121 | ## 协议
122 |
123 | Toktik is licensed under the [MIT License](LICENSE).
124 |
--------------------------------------------------------------------------------
/add-kitex-service.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # 检查 go 命令是否存在于 PATH 环境变量中
4 | if ! command -v go &>/dev/null; then
5 | echo "错误:go 命令未在 PATH 中找到。请安装或将其添加到 PATH 中。"
6 | exit 1
7 | fi
8 |
9 | # 检查 protoc 命令是否存在于 PATH 环境变量中
10 | if ! command -v protoc &>/dev/null; then
11 | echo "错误:protoc 命令未在 PATH 中找到。请安装或将其添加到 PATH 中。"
12 | # 检查操作系统是否为 macOS
13 | if [[ $(uname) == "Darwin" ]]; then
14 | # 如果是macOS,则检查brew是否在PATH中
15 | if ! command -v brew &>/dev/null; then
16 | echo "错误:brew 命令未在 PATH 中找到。请安装或将其添加到 PATH 中。"
17 | exit 1
18 | else
19 | echo "尝试安装 protoc......"
20 | brew install protobuf
21 | fi
22 | fi
23 | fi
24 |
25 | # 再次检查 protoc 命令是否存在于 PATH 环境变量中
26 | if ! command -v protoc &>/dev/null; then
27 | echo "错误:protoc 命令未在 PATH 中找到,看起来安装失败了。请手动安装。"
28 | exit 1
29 | fi
30 |
31 | # 检查 kitex 命令是否存在于 PATH 环境变量中
32 | if ! command -v kitex &>/dev/null; then
33 | echo "错误:kitex 命令未在 PATH 中找到,尝试安装......"
34 | go install github.com/cloudwego/kitex/tool/cmd/kitex@latest
35 | fi
36 |
37 | # 再次检查 kitex 命令是否存在于 PATH 环境变量中
38 | if ! command -v kitex &>/dev/null; then
39 | echo "错误:kitex 命令未在 PATH 中找到,看起来安装失败了。请手动安装。"
40 | exit 1
41 | fi
42 |
43 | mkdir -p kitex_gen
44 | kitex -module "toktik" -I idl/ idl/"$1".proto
45 |
46 | mkdir -p service/"$1"
47 | cd service/"$1" && kitex -module "toktik" -service "$1" -use toktik/kitex_gen/ -I ../../idl/ ../../idl/"$1".proto
48 |
49 | go mod tidy
50 |
--------------------------------------------------------------------------------
/basic-build-image/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker.io/golang:1.19.5-bullseye
2 |
3 | # FFmpeg 及依赖
4 | RUN apt-get update && \
5 | apt-get install -yq ffmpeg && \
6 | apt-get clean && \
7 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \
8 | rm -rf /var/lib/apt/lists/*
9 |
--------------------------------------------------------------------------------
/basic-runtime-image/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker.io/debian:bullseye-slim
2 |
3 | # FFmpeg 及依赖
4 | RUN apt-get update && \
5 | apt-get install -yq ffmpeg && \
6 | apt-get clean && \
7 | apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \
8 | rm -rf /var/lib/apt/lists/*
9 |
--------------------------------------------------------------------------------
/build-all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Exit if any error occured
4 | set -euxo pipefail
5 |
6 | SERVICE_DIR=$(pwd)/service
7 |
8 | # Remove the output directory
9 | echo "Removing previous output directory..."
10 | rm -rf output
11 |
12 | # Create the output directory and binaries directory
13 | echo "Creating output directory..."
14 | mkdir -p output/bin
15 |
16 | # Navigate to the service directory
17 | echo "Navigating to service directory..."
18 | cd $SERVICE_DIR || exit
19 |
20 | # Loop through all directories in the service directory
21 | for d in */ ; do
22 | echo "Building $d..."
23 | # Navigate into the current directory
24 | cd "$SERVICE_DIR/$d"
25 |
26 | # Remove the output directory
27 | echo "Removing previous output directory..."
28 | rm -rf output
29 |
30 | # Create the output directory
31 | echo "Creating output directory..."
32 | mkdir output
33 |
34 | # Check if the build.sh file exists, if not, skip the current directory
35 | if [ ! -f build.sh ]; then
36 | echo "build.sh file not found in $d, skipping..."
37 | continue
38 | fi
39 |
40 | # Execute the build.sh script
41 | echo "Executing build.sh..."
42 | if ! bash build.sh;
43 | then
44 | echo "Build failed"
45 | exit 1
46 | fi
47 |
48 |
49 | # Copy the binary to the output/binaries directory
50 | echo "Copying binary to output/bin..."
51 | cp output/bin/"$(basename "$d")" ../../output/bin/
52 |
53 | # Check if the bootstrap.sh file exists
54 | if [ -f output/bootstrap.sh ]; then
55 | echo "bootstrap.sh file found in $d, copying to output directory..."
56 | # Rename the bootstrap.sh file and copy it to the output directory
57 | mv output/bootstrap.sh ../../output/bootstrap-"$(basename "$d")".sh
58 | fi
59 |
60 | # Navigate back to the service directory
61 | echo "Finished building $d"
62 | done
63 |
--------------------------------------------------------------------------------
/constant/biz/auth.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | import "github.com/cloudwego/hertz/pkg/protocol/consts"
4 |
5 | var (
6 | TokenNotFoundMessage = "会话已过期"
7 | )
8 |
9 | var (
10 | NoUserNameOrPassWord = GWError{HTTPStatusCode: consts.StatusBadRequest, StatusCode: 400007, StatusMsg: "no username or password"}
11 | )
12 |
--------------------------------------------------------------------------------
/constant/biz/comment.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | import httpStatus "github.com/cloudwego/hertz/pkg/protocol/consts"
4 |
5 | var (
6 | UnauthorizedError = GWError{HTTPStatusCode: httpStatus.StatusUnauthorized, StatusCode: 400003, StatusMsg: "Unauthorized"}
7 | BadRequestError = GWError{HTTPStatusCode: httpStatus.StatusBadRequest, StatusCode: 400004, StatusMsg: "Bad request"}
8 | InternalServerError = GWError{HTTPStatusCode: httpStatus.StatusInternalServerError, StatusCode: 500006, StatusMsg: "Internal server error"}
9 | )
10 |
--------------------------------------------------------------------------------
/constant/biz/favorite.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | const (
4 | FailedToGetVideoList = 500001
5 | )
6 |
--------------------------------------------------------------------------------
/constant/biz/feed.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | import "github.com/cloudwego/hertz/pkg/protocol/consts"
4 |
5 | const (
6 | VideoCount = 30
7 | )
8 |
9 | var (
10 | InvalidLatestTime = GWError{HTTPStatusCode: consts.StatusBadRequest, StatusCode: 400001, StatusMsg: "Invalid latest_time"}
11 | )
12 |
--------------------------------------------------------------------------------
/constant/biz/gwerror.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | import (
4 | "fmt"
5 | "toktik/logging"
6 |
7 | "github.com/cloudwego/hertz/pkg/app"
8 | "github.com/cloudwego/hertz/pkg/protocol/consts"
9 | "github.com/sirupsen/logrus"
10 | )
11 |
12 | // GWError is the error struct for gateway.
13 | type GWError struct {
14 | HTTPStatusCode int
15 | StatusCode uint32
16 | StatusMsg string
17 | Cause *error
18 | Fields *logrus.Fields
19 | }
20 |
21 | func (G GWError) Error() string {
22 | return fmt.Sprintf("http status code: %d, status code: %d, status msg: %s, cause: %v", G.HTTPStatusCode, G.StatusCode, G.StatusMsg, G.Cause)
23 | }
24 |
25 | // extractFieldsFromRequest extracts important fields from request context for debugging purpose.
26 | func extractFieldsFromRequest(c *app.RequestContext) logrus.Fields {
27 | return logrus.Fields{"request_context_info": map[string]any{
28 | "request_id": c.Request.Header.Get("X-Request-Id"),
29 | "method": c.Method(),
30 | "host": c.Host(),
31 | "uri": c.URI(),
32 | "ip": c.ClientIP(),
33 | "ua": c.UserAgent(),
34 | "query_args": c.QueryArgs(),
35 | "post_args": c.PostArgs(),
36 | "content_type": c.ContentType(),
37 | "body": c.GetRawData(),
38 | "handler": c.HandlerName(),
39 | }}
40 | }
41 |
42 | // LaunchError logs the error and returns the error.
43 | func (G GWError) LaunchError(c *app.RequestContext) {
44 | logger := logging.Logger.WithFields(extractFieldsFromRequest(c))
45 | if G.Fields != nil {
46 | logger = logger.WithFields(*G.Fields)
47 | }
48 | if G.Cause != nil {
49 | logger = logger.WithField("cause", *G.Cause)
50 | }
51 | logger.Debugf("launch error: %v", G)
52 | c.JSON(G.HTTPStatusCode, map[string]any{
53 | "status_code": G.StatusCode,
54 | "status_msg": G.StatusMsg,
55 | })
56 | }
57 |
58 | // WithCause adds the cause to the error.
59 | func (G GWError) WithCause(err error) GWError {
60 | G.Cause = &err
61 | return G
62 | }
63 |
64 | // WithFields adds the fields to the error.
65 | func (G GWError) WithFields(fields *logrus.Fields) GWError {
66 | G.Fields = fields
67 | return G
68 | }
69 |
70 | var (
71 | RPCCallError = GWError{HTTPStatusCode: consts.StatusInternalServerError, StatusCode: 500001, StatusMsg: "RPC call error"}
72 | UnAuthorized = GWError{HTTPStatusCode: consts.StatusUnauthorized, StatusCode: 401001, StatusMsg: "Unauthorized"}
73 | InvalidArguments = GWError{HTTPStatusCode: consts.StatusBadRequest, StatusCode: 400002, StatusMsg: "Invalid arguments"}
74 | )
75 |
--------------------------------------------------------------------------------
/constant/biz/publish.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | import "github.com/cloudwego/hertz/pkg/protocol/consts"
4 |
5 | var (
6 | PublishActionSuccess = "Uploaded successfully!"
7 | )
8 |
9 | var (
10 | OpenFileFailedError = GWError{HTTPStatusCode: consts.StatusInternalServerError, StatusCode: 400005, StatusMsg: "Open file failed"}
11 | SizeNotMatchError = GWError{HTTPStatusCode: consts.StatusInternalServerError, StatusCode: 400006, StatusMsg: "Size not match"}
12 | )
13 |
--------------------------------------------------------------------------------
/constant/biz/relation.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | import "toktik/kitex_gen/douyin/user"
4 |
5 | var openAIImage = "https://bkimg.cdn.bcebos.com/pic/8b13632762d0f703918f0d436fac463d269758ee6faf?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2U4MA==,g_7,xp_5,yp_5"
6 | var openAIIntro = "ChatGPT is a language model created by OpenAI with the ability to understand and generate human-like text responses to a wide range of topics and questions."
7 | var zero uint32 = 0
8 |
9 | var ChatGPTUser = &user.User{
10 | Id: 0,
11 | Name: "ChatGPT",
12 | FollowCount: 0,
13 | FollowerCount: 100000000,
14 | IsFollow: true,
15 | Avatar: &openAIImage,
16 | BackgroundImage: &openAIImage,
17 | Signature: &openAIIntro,
18 | TotalFavorited: &zero,
19 | WorkCount: &zero,
20 | FavoriteCount: &zero,
21 | }
22 |
--------------------------------------------------------------------------------
/constant/biz/response.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | var (
4 | OkStatusMsg = "OK"
5 | BadRequestStatusMsg = "Unable to finish request, please check your parameters. If you think this is a bug, please contact us."
6 | ForbiddenStatusMsg = "You are not allowed to access this resource."
7 | InternalServerErrorStatusMsg = "The server had an error while processing your request. Sorry about that!"
8 | )
9 |
--------------------------------------------------------------------------------
/constant/biz/statuscode.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | // OK
4 | const (
5 | OkStatusCode = 0 + iota
6 | )
7 |
8 | // Service Not Available
9 | const (
10 | ServiceNotAvailable = 503000
11 | )
12 |
13 | // Internal Server Error
14 | const (
15 | RedisError = 500000 + iota
16 | ProtoMarshalError
17 | UnableToCreateComment
18 | UnableToDeleteComment
19 | UnableToQueryVideo
20 | UnableToQueryComment
21 | UnableToQueryUser
22 | SQLQueryErrorStatusCode
23 | Unable2GenerateUUID
24 | Unable2CreateThumbnail
25 | Unable2UploadVideo
26 | Unable2UploadCover
27 | Unable2CreateDBEntry
28 | RequestIsNil
29 | UnableToQueryFollowList
30 | UnableToQueryFollowerList
31 | UnableToQueryIsFollow
32 | UnableToDeleteRelation
33 | UnableToLike
34 | UnableToCancelLike
35 | UnableToQueryFavorite
36 | UnableToQueryTotalFavorited
37 | )
38 |
39 | // Bad Request
40 | const (
41 | UserNameExist = 400000 + iota
42 | UserNotFound
43 | InvalidCommentActionType
44 | VideoNotFound
45 | Unable2ParseLatestTimeStatusCode
46 | InvalidContentType
47 | RelationNotFound
48 | RelationAlreadyExists
49 | InvalidToUserId
50 | )
51 |
52 | // Unauthorized
53 | const (
54 | PasswordIncorrect = 401003
55 | TokenNotFound = 401001
56 | )
57 |
58 | // Forbidden
59 | const (
60 | ActorIDNotMatch = 403001
61 | )
62 |
--------------------------------------------------------------------------------
/constant/biz/user.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | import "github.com/cloudwego/hertz/pkg/protocol/consts"
4 |
5 | var (
6 | InvalidUserID = GWError{HTTPStatusCode: consts.StatusBadRequest, StatusCode: 400003, StatusMsg: "Invalid user_id"}
7 | )
8 |
--------------------------------------------------------------------------------
/constant/biz/wechat.go:
--------------------------------------------------------------------------------
1 | package biz
2 |
3 | import httpStatus "github.com/cloudwego/hertz/pkg/protocol/consts"
4 |
5 | var (
6 | InvalidActionType = GWError{HTTPStatusCode: httpStatus.StatusBadRequest, StatusCode: 400008, StatusMsg: "invalid action type"}
7 | )
8 |
--------------------------------------------------------------------------------
/constant/config/env.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "reflect"
8 | "strconv"
9 | "strings"
10 | "toktik/logging"
11 |
12 | "github.com/joho/godotenv"
13 | "github.com/sirupsen/logrus"
14 | )
15 |
16 | var EnvConfig = envConfigSchema{}
17 |
18 | func (s *envConfigSchema) GetDSN() string {
19 | return dsn
20 | }
21 |
22 | var dsn string
23 |
24 | func init() {
25 | envInit()
26 | envValidate()
27 | dsn = fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s",
28 | EnvConfig.PGSQL_HOST,
29 | EnvConfig.PGSQL_USER,
30 | EnvConfig.PGSQL_PASSWORD,
31 | EnvConfig.PGSQL_DBNAME,
32 | EnvConfig.PGSQL_PORT)
33 | }
34 |
35 | var defaultConfig = envConfigSchema{
36 | ENV: "dev",
37 |
38 | CONSUL_ADDR: "127.0.0.1:8500",
39 |
40 | EXPORT_ENDPOINT: "127.0.0.1:4317",
41 |
42 | PGSQL_HOST: "localhost",
43 | PGSQL_PORT: "5432",
44 | PGSQL_USER: "postgres",
45 | PGSQL_PASSWORD: "",
46 | PGSQL_DBNAME: "postgres",
47 |
48 | REDIS_ADDR: "localhost:6379",
49 | REDIS_PASSWORD: "",
50 | REDIS_DB: 0,
51 |
52 | STORAGE_TYPE: "s3",
53 | MAX_REQUEST_BODY_SIZE: 200 * 1024 * 1024,
54 |
55 | LOCAL_FS_LOCATION: "/tmp",
56 | LOCAL_FS_BASEURL: "http://localhost/",
57 |
58 | S3_ENDPOINT_URL: "http://localhost:9000",
59 | S3_PUBLIC_URL: "http://localhost:9000",
60 | S3_BUCKET: "bucket",
61 | S3_SECRET_ID: "minio",
62 | S3_SECRET_KEY: "12345678",
63 | S3_PATH_STYLE: "true",
64 |
65 | UNSPLASH_ACCESS_KEY: "access_key",
66 | }
67 |
68 | type envConfigSchema struct {
69 | ENV string `env:"ENV,DREAM_ENV"`
70 |
71 | CONSUL_ADDR string `env:"CONSUL_ADDR,DREAM_SERVICE_DISCOVERY_URI"`
72 |
73 | EXPORT_ENDPOINT string
74 |
75 | PGSQL_HOST string
76 | PGSQL_PORT string
77 | PGSQL_USER string
78 | PGSQL_PASSWORD string
79 | PGSQL_DBNAME string
80 |
81 | REDIS_ADDR string
82 | REDIS_PASSWORD string
83 | REDIS_DB int
84 |
85 | STORAGE_TYPE string
86 | MAX_REQUEST_BODY_SIZE int
87 |
88 | LOCAL_FS_LOCATION string
89 | LOCAL_FS_BASEURL string
90 |
91 | S3_ENDPOINT_URL string
92 | S3_PUBLIC_URL string
93 | S3_BUCKET string
94 | S3_SECRET_ID string
95 | S3_SECRET_KEY string
96 | S3_PATH_STYLE string
97 |
98 | UNSPLASH_ACCESS_KEY string
99 | }
100 |
101 | func (s *envConfigSchema) IsDev() bool {
102 | return s.ENV == "dev" || s.ENV == "TESTING"
103 | }
104 |
105 | func envValidate() {
106 | EnvConfig.CONSUL_ADDR = strings.TrimPrefix(EnvConfig.CONSUL_ADDR, "consul://")
107 | }
108 |
109 | // envInit Reads .env as environment variables and fill corresponding fields into EnvConfig.
110 | // To use a value from EnvConfig , simply call EnvConfig.FIELD like EnvConfig.ENV
111 | // Note: Please keep Env as the first field of envConfigSchema for better logging.
112 | func envInit() {
113 | err := godotenv.Load()
114 | if err != nil {
115 | log.Print("Error loading .env file, ignored")
116 | }
117 | v := reflect.ValueOf(defaultConfig)
118 | typeOfV := v.Type()
119 |
120 | for i := 0; i < v.NumField(); i++ {
121 | envNameAlt := make([]string, 0)
122 | fieldName := typeOfV.Field(i).Name
123 | fieldType := typeOfV.Field(i).Type
124 | fieldValue := v.Field(i).Interface()
125 |
126 | envNameAlt = append(envNameAlt, fieldName)
127 | if fieldTag, ok := typeOfV.Field(i).Tag.Lookup("env"); ok && len(fieldTag) > 0 {
128 | tags := strings.Split(fieldTag, ",")
129 | envNameAlt = append(envNameAlt, tags...)
130 | }
131 |
132 | switch fieldType {
133 | case reflect.TypeOf(0):
134 | {
135 | configDefaultValue, ok := fieldValue.(int)
136 | if !ok {
137 | logging.Logger.WithFields(logrus.Fields{
138 | "field": fieldName,
139 | "type": "int",
140 | "value": fieldValue,
141 | "env": envNameAlt,
142 | }).Warningf("Failed to parse default value")
143 | continue
144 | }
145 | envValue := resolveEnv(envNameAlt, fmt.Sprintf("%d", configDefaultValue))
146 | if EnvConfig.IsDev() {
147 | fmt.Printf("Reading field[ %s ] default: %v env: %s\n", fieldName, configDefaultValue, envValue)
148 | }
149 | if len(envValue) > 0 {
150 | envValueInteger, err := strconv.ParseInt(envValue, 10, 64)
151 | if err != nil {
152 | logging.Logger.WithFields(logrus.Fields{
153 | "field": fieldName,
154 | "type": "int",
155 | "value": fieldValue,
156 | "env": envNameAlt,
157 | }).Warningf("Failed to parse env value, ignored")
158 | continue
159 | }
160 | reflect.ValueOf(&EnvConfig).Elem().Field(i).SetInt(envValueInteger)
161 | }
162 | continue
163 | }
164 | case reflect.TypeOf(""):
165 | {
166 | configDefaultValue, ok := fieldValue.(string)
167 | if !ok {
168 | logging.Logger.WithFields(logrus.Fields{
169 | "field": fieldName,
170 | "type": "int",
171 | "value": fieldValue,
172 | "env": envNameAlt,
173 | }).Warningf("Failed to parse default value")
174 | continue
175 | }
176 | envValue := resolveEnv(envNameAlt, configDefaultValue)
177 |
178 | if EnvConfig.IsDev() {
179 | fmt.Printf("Reading field[ %s ] default: %v env: %s\n", fieldName, configDefaultValue, envValue)
180 | }
181 | if len(envValue) > 0 {
182 | reflect.ValueOf(&EnvConfig).Elem().Field(i).SetString(envValue)
183 | }
184 | }
185 | }
186 |
187 | }
188 | }
189 |
190 | func resolveEnv(configKeys []string, defaultValue string) string {
191 | for _, item := range configKeys {
192 | envValue := os.Getenv(item)
193 | if envValue != "" {
194 | return envValue
195 | }
196 | }
197 | return defaultValue
198 | }
199 |
--------------------------------------------------------------------------------
/constant/config/service.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | const WebServiceName = "toktik-api-gateway"
4 | const WebServiceAddr = ":40126"
5 |
6 | const AuthServiceName = "toktik-auth-api"
7 | const AuthServiceAddr = ":40127"
8 |
9 | const PublishServiceName = "toktik-publish-api"
10 | const PublishServiceAddr = ":40128"
11 |
12 | const FeedServiceName = "toktik-feed-api"
13 | const FeedServiceAddr = ":40129"
14 |
15 | const UserServiceName = "toktik-user-api"
16 | const UserServiceAddr = ":40130"
17 |
18 | const CommentServiceName = "toktik-comment-api"
19 | const CommentServiceAddr = ":40131"
20 |
21 | const WechatServiceName = "toktik-wechat"
22 | const WechatServiceAddr = ":40132"
23 |
24 | const RelationServiceName = "toktik-relation-api"
25 | const RelationServiceAddr = ":40133"
26 |
27 | const FavoriteServiceName = "toktik-favorite-api"
28 | const FavoriteServiceAddr = ":40134"
29 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: "3.7"
2 | services:
3 | # PostgreSQL
4 |
5 | db:
6 | image: postgres
7 | restart: always
8 | environment:
9 | POSTGRES_PASSWORD: example
10 |
11 | adminer:
12 | image: adminer
13 | restart: always
14 | ports:
15 | - 8080:8080
16 |
17 | # Collector
18 | otel-collector:
19 | image: otel/opentelemetry-collector-contrib-dev:latest
20 | command: [ "--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}" ]
21 | volumes:
22 | - ./otel.yaml:/etc/otel-collector-config.yaml
23 | ports:
24 | - "1888:1888" # pprof extension
25 | - "8888" # Prometheus metrics exposed by the collector
26 | - "8889:8889" # Prometheus exporter metrics
27 | - "13133:13133" # health_check extension
28 | - "4317:4317" # OTLP gRPC receiver
29 | - "55679" # zpages extension
30 | depends_on:
31 | - jaeger-all-in-one
32 |
33 | # Jaeger
34 | jaeger-all-in-one:
35 | image: jaegertracing/all-in-one:latest
36 | environment:
37 | - COLLECTOR_OTLP_ENABLED=true
38 | ports:
39 | - "16686:16686"
40 | - "14268"
41 | - "14250:14250"
42 | - "6831:6831"
43 |
44 | # Victoriametrics
45 | victoriametrics:
46 | container_name: victoriametrics
47 | image: victoriametrics/victoria-metrics
48 | ports:
49 | - "8428:8428"
50 | - "8089:8089"
51 | - "8089:8089/udp"
52 | - "2003:2003"
53 | - "2003:2003/udp"
54 | - "4242:4242"
55 | command:
56 | - '--storageDataPath=/storage'
57 | - '--graphiteListenAddr=:2003'
58 | - '--opentsdbListenAddr=:4242'
59 | - '--httpListenAddr=:8428'
60 | - '--influxListenAddr=:8089'
61 | restart: always
62 |
63 | # Grafana
64 | grafana:
65 | image: grafana/grafana:latest
66 | environment:
67 | - GF_AUTH_ANONYMOUS_ENABLED=true
68 | - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
69 | - GF_AUTH_DISABLE_LOGIN_FORM=true
70 | ports:
71 | - "3000:3000"
72 |
--------------------------------------------------------------------------------
/docs/HowToAddServices.md:
--------------------------------------------------------------------------------
1 | # How To Add Services - 如何添加一个服务
2 |
3 | 1. 确定要添加的服务的名称(如:`user`, `auth`, `feed` 等)可参考 `service/web/main.go` 下的 hertz group 或 API URL
4 | 2. 编写 proto IDL 文件,并将文件命名为`{服务名称}.proto` ,放入 idl 目录
5 | 3. 打开终端 cd 到项目根目录,然后调用 `./add-kitex-service.sh {服务名称}`
6 | 4. 此时 kitex 生成的代码将被放入 `kitex_gen` 目录(无需改动)和 `service/{服务名称}`
7 | 5. 完成服务业务逻辑并妥善修改 `service/{服务名称}/handler.go`
8 | 6. 在 `service/web` web api 中添加对 RPC 服务的调用
--------------------------------------------------------------------------------
/docs/images/cert-cut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Toktik-Team/toktik/fd1c18ea896125d0a71b45d546a4eed5094d15d0/docs/images/cert-cut.png
--------------------------------------------------------------------------------
/idl/auth.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package douyin.auth;
3 | option go_package = "douyin/auth";
4 |
5 | message LoginRequest {
6 | string username = 1; // 登录用户名
7 | string password = 2; // 登录密码
8 | }
9 |
10 | message LoginResponse {
11 | uint32 status_code = 1 [json_name = "status_code"]; // 状态码,0-成功,其他值-失败
12 | string status_msg = 2 [json_name = "status_msg"]; // 返回状态描述
13 | uint32 user_id = 3 [json_name = "user_id"]; // 用户id
14 | string token = 4; // 用户鉴权token
15 | }
16 |
17 | message RegisterRequest {
18 | string username = 1; // 注册用户名,最长32个字符
19 | string password = 2; // 密码,最长32个字符
20 | }
21 |
22 | message RegisterResponse {
23 | uint32 status_code = 1 [json_name = "status_code"]; // 状态码,0-成功,其他值-失败
24 | string status_msg = 2 [json_name = "status_msg"]; // 返回状态描述
25 | uint32 user_id = 3 [json_name = "user_id"]; // 用户id
26 | string token = 4; // 用户鉴权token
27 | }
28 |
29 | message AuthenticateRequest {
30 | string token = 1; // 用户鉴权token
31 | }
32 | message AuthenticateResponse {
33 | uint32 status_code = 1 [json_name = "status_code"]; // 状态码,0-成功,其他值-失败
34 | string status_msg = 2 [json_name = "status_msg"]; // 返回状态描述
35 | uint32 user_id = 3 [json_name = "user_id"]; // 用户id
36 | }
37 |
38 | service AuthService {
39 | rpc Authenticate (AuthenticateRequest) returns (AuthenticateResponse) {
40 | }
41 |
42 | rpc Register (RegisterRequest) returns (RegisterResponse) {
43 | }
44 |
45 | rpc Login (LoginRequest) returns (LoginResponse) {
46 | }
47 | }
--------------------------------------------------------------------------------
/idl/comment.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package douyin.comment;
3 | option go_package = "douyin/comment";
4 |
5 | import "user.proto";
6 |
7 | message Comment {
8 | uint32 id = 1;
9 | user.User user = 2;
10 | string content = 3;
11 | string create_date = 4;
12 | }
13 |
14 | enum ActionCommentType {
15 | ACTION_COMMENT_TYPE_UNSPECIFIED = 0; // Only for protobuf compatibility
16 | ACTION_COMMENT_TYPE_ADD = 1;
17 | ACTION_COMMENT_TYPE_DELETE = 2;
18 | }
19 |
20 | message ActionCommentRequest {
21 | uint32 actor_id = 1;
22 | uint32 video_id = 2;
23 | ActionCommentType action_type = 3;
24 | oneof action {
25 | string comment_text = 4;
26 | uint32 comment_id = 5;
27 | }
28 | }
29 |
30 | message ActionCommentResponse {
31 | uint32 status_code = 1;
32 | optional string status_msg = 2;
33 | optional Comment comment = 3;
34 | }
35 |
36 |
37 | message ListCommentRequest {
38 | uint32 actor_id = 1;
39 | uint32 video_id = 2;
40 | }
41 |
42 | message ListCommentResponse {
43 | uint32 status_code = 1;
44 | optional string status_msg = 2;
45 | repeated Comment comment_list = 3;
46 | }
47 |
48 | message CountCommentRequest {
49 | uint32 actor_id = 1;
50 | uint32 video_id = 2;
51 | }
52 |
53 | message CountCommentResponse {
54 | uint32 status_code = 1;
55 | optional string status_msg = 2;
56 | uint32 comment_count = 3;
57 | }
58 |
59 | service CommentService {
60 | rpc ActionComment(ActionCommentRequest) returns (ActionCommentResponse);
61 | rpc ListComment(ListCommentRequest) returns (ListCommentResponse);
62 | rpc CountComment(CountCommentRequest) returns(CountCommentResponse);
63 | }
64 |
--------------------------------------------------------------------------------
/idl/favorite.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package douyin.favorite;
3 | option go_package = "douyin/favorite";
4 |
5 | import "feed.proto";
6 |
7 | message FavoriteRequest {
8 | uint32 actor_id = 1; // 用户id
9 | uint32 video_id = 2; // 视频id
10 | uint32 action_type = 3; // 1-点赞,2-取消点赞
11 | }
12 |
13 | message FavoriteResponse {
14 | uint32 status_code = 1; // 状态码,0-成功,其他值-失败
15 | optional string status_msg = 2; // 返回状态描述
16 | }
17 |
18 | message FavoriteListRequest {
19 | uint32 actor_id = 1; // 发出请求的用户的id
20 | uint32 user_id = 2; // 用户id
21 | }
22 |
23 | message FavoriteListResponse {
24 | uint32 status_code = 1; // 状态码,0-成功,其他值-失败
25 | optional string status_msg = 2; // 返回状态描述
26 | repeated feed.Video video_list = 3; // 用户点赞视频列表
27 | }
28 |
29 | message IsFavoriteRequest {
30 | uint32 user_id = 1; // 用户id
31 | uint32 video_id = 2; // 视频id
32 | }
33 |
34 | message IsFavoriteResponse {
35 | bool result = 1; // 结果
36 | }
37 |
38 | message CountFavoriteRequest {
39 | uint32 video_id = 1; // 视频id
40 | }
41 |
42 | message CountFavoriteResponse {
43 | uint32 status_code = 1;
44 | optional string status_msg = 2;
45 | uint32 count = 3; // 点赞数
46 | }
47 |
48 | message CountUserFavoriteRequest {
49 | uint32 user_id = 1; // 用户id
50 | }
51 |
52 | message CountUserFavoriteResponse {
53 | uint32 status_code = 1;
54 | optional string status_msg = 2;
55 | uint32 count = 3; // 点赞数
56 | }
57 |
58 | message CountUserTotalFavoritedRequest {
59 | uint32 actor_id = 1;
60 | uint32 user_id = 2;
61 | }
62 |
63 | message CountUserTotalFavoritedResponse {
64 | uint32 status_code = 1;
65 | optional string status_msg = 2;
66 | uint32 count = 3; // 点赞数
67 | }
68 |
69 | service FavoriteService {
70 | rpc FavoriteAction (FavoriteRequest) returns (FavoriteResponse){
71 | }
72 | rpc FavoriteList (FavoriteListRequest) returns (FavoriteListResponse) {
73 | }
74 | rpc IsFavorite (IsFavoriteRequest) returns (IsFavoriteResponse) {
75 | }
76 | rpc CountFavorite (CountFavoriteRequest) returns (CountFavoriteResponse) {
77 | }
78 | rpc CountUserFavorite (CountUserFavoriteRequest) returns (CountUserFavoriteResponse) {
79 | }
80 | rpc CountUserTotalFavorited (CountUserTotalFavoritedRequest) returns (CountUserTotalFavoritedResponse) {
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/idl/feed.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package douyin.feed;
4 | option go_package = "douyin/feed";
5 |
6 | import "user.proto";
7 |
8 | message Video {
9 | uint32 id = 1;
10 | user.User author = 2;
11 | string play_url = 3;
12 | string cover_url = 4;
13 | uint32 favorite_count = 5;
14 | uint32 comment_count = 6;
15 | bool is_favorite = 7;
16 | string title = 8;
17 | }
18 |
19 | message ListFeedRequest {
20 | optional string latest_time = 1; // 限制返回视频的最新投稿时间戳,精确到秒,不填表示当前时间
21 | optional uint32 actor_id = 2; // 发送请求的用户的id
22 | }
23 |
24 | message ListFeedResponse {
25 | uint32 status_code = 1;
26 | optional string status_msg = 2;
27 | optional int64 next_time = 3;
28 | repeated Video video_list = 4;
29 | }
30 |
31 | message QueryVideosRequest {
32 | uint32 actor_id = 1;
33 | repeated uint32 video_ids = 2;
34 | }
35 |
36 | message QueryVideosResponse {
37 | uint32 status_code = 1;
38 | optional string status_msg = 2;
39 | repeated Video video_list = 3;
40 | }
41 |
42 | service FeedService {
43 | rpc ListVideos(ListFeedRequest) returns (ListFeedResponse);
44 | rpc QueryVideos(QueryVideosRequest) returns (QueryVideosResponse);
45 | }
46 |
--------------------------------------------------------------------------------
/idl/publish.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package douyin.publish;
3 | option go_package = "douyin/publish";
4 |
5 | import "feed.proto";
6 |
7 | message CreateVideoRequest {
8 | uint32 actor_id = 1; // 用户id
9 | bytes data = 2; // 视频数据
10 | string title = 3; // 视频标题
11 | }
12 |
13 | message CreateVideoResponse {
14 | uint32 status_code = 1 [json_name = "status_code"]; // 状态码,0-成功,其他值-失败
15 | string status_msg = 2 [json_name = "status_msg"]; // 返回状态描述
16 | }
17 |
18 | message ListVideoRequest{
19 | uint32 user_id = 1; // 用户id
20 | uint32 actor_id = 2; // 发送请求的用户的id
21 | }
22 |
23 | message ListVideoResponse{
24 | uint32 status_code = 1; // 状态码,0-成功,其他值-失败
25 | optional string status_msg = 2; // 返回状态描述
26 | repeated feed.Video video_list = 3; // 视频列表
27 | }
28 |
29 | message CountVideoRequest{
30 | uint32 user_id = 1; // 用户id
31 | }
32 |
33 | message CountVideoResponse{
34 | uint32 status_code = 1; // 状态码,0-成功,其他值-失败
35 | optional string status_msg = 2; // 返回状态描述
36 | uint32 count = 3; // 视频数量
37 | }
38 |
39 | service PublishService {
40 | rpc CreateVideo(CreateVideoRequest) returns (CreateVideoResponse) {}
41 | rpc ListVideo(ListVideoRequest) returns (ListVideoResponse) {}
42 | rpc CountVideo(CountVideoRequest) returns (CountVideoResponse) {}
43 | }
44 |
--------------------------------------------------------------------------------
/idl/relation.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package douyin.user;
4 | option go_package = "douyin/relation";
5 |
6 | import "user.proto";
7 |
8 | message RelationActionRequest {
9 | uint32 actor_id = 1; // 当前登录用户
10 | uint32 user_id = 2; // 对方用户id
11 | }
12 |
13 | message RelationActionResponse {
14 | uint32 status_code = 1; // 状态码,0-成功,其他值-失败
15 | string status_msg = 2; // 返回状态描述
16 | }
17 |
18 |
19 | message FollowListRequest {
20 | uint32 actor_id = 1; // 当前登录用户id
21 | uint32 user_id = 2; // 对方用户id
22 | }
23 |
24 | message FollowListResponse {
25 | int32 status_code = 1; // 状态码,0-成功,其他值-失败
26 | string status_msg = 2; // 返回状态描述
27 | repeated User user_list = 3; // 用户信息列表
28 | }
29 |
30 | message CountFollowListRequest {
31 | uint32 user_id = 1; // 用户id
32 | }
33 |
34 | message CountFollowListResponse {
35 | int32 status_code = 1; // 状态码,0-成功,其他值-失败
36 | string status_msg = 2; // 返回状态描述
37 | uint32 count = 3; // 关注数
38 | }
39 |
40 | message FollowerListRequest {
41 | uint32 actor_id = 1; // 当前登录用户id
42 | uint32 user_id = 2; // 对方用户id
43 | }
44 |
45 | message FollowerListResponse {
46 | int32 status_code = 1; // 状态码,0-成功,其他值-失败
47 | string status_msg = 2; // 返回状态描述
48 | repeated User user_list = 3; // 用户列表
49 | }
50 |
51 | message CountFollowerListRequest {
52 | uint32 user_id = 1; // 用户id
53 | }
54 |
55 | message CountFollowerListResponse {
56 | int32 status_code = 1; // 状态码,0-成功,其他值-失败
57 | string status_msg = 2; // 返回状态描述
58 | uint32 count = 3; // 粉丝数
59 | }
60 |
61 |
62 | message FriendListRequest {
63 | uint32 actor_id = 1; // 当前登录用户id
64 | uint32 user_id = 2; // 对方用户id
65 | }
66 |
67 | message FriendListResponse {
68 | int32 status_code = 1; // 状态码,0-成功,其他值-失败
69 | string status_msg = 2; // 返回状态描述
70 | repeated User user_list = 3; // 用户列表
71 | }
72 |
73 | message IsFollowRequest {
74 | uint32 actor_id = 1;
75 | uint32 user_id = 2;
76 | }
77 |
78 | message IsFollowResponse {
79 | bool result = 1; // 结果
80 | }
81 |
82 | service RelationService {
83 | rpc Follow (RelationActionRequest) returns (RelationActionResponse) {
84 | }
85 | rpc Unfollow (RelationActionRequest) returns (RelationActionResponse) {
86 | }
87 |
88 | rpc GetFollowList (FollowListRequest) returns (FollowListResponse) {
89 | }
90 | rpc CountFollowList (CountFollowListRequest) returns (CountFollowListResponse) {
91 | }
92 |
93 | rpc GetFollowerList (FollowerListRequest) returns (FollowerListResponse) {
94 | }
95 | rpc CountFollowerList (CountFollowerListRequest) returns (CountFollowerListResponse) {
96 | }
97 |
98 | rpc GetFriendList (FriendListRequest) returns (FriendListResponse) {
99 | }
100 | rpc IsFollow (IsFollowRequest) returns (IsFollowResponse) {
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/idl/user.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package douyin.user;
3 | option go_package = "douyin/user";
4 |
5 | message UserRequest {
6 | uint32 user_id = 1; // 用户id
7 | uint32 actor_id = 2; // 发送请求的用户的id
8 | }
9 |
10 | message UserResponse {
11 | uint32 status_code = 1; // 状态码,0-成功,其他值-失败
12 | optional string status_msg = 2; // 返回状态描述
13 | User user = 3; // 用户信息
14 | }
15 |
16 | message User {
17 | uint32 id = 1; // 用户id
18 | string name = 2; // 用户名称
19 | uint32 follow_count = 3; // 关注总数
20 | uint32 follower_count = 4; // 粉丝总数
21 | bool is_follow = 5; // true-已关注,false-未关注
22 | optional string avatar = 6; // 用户头像
23 | optional string background_image = 7; // 用户个人顶部大图
24 | optional string signature = 8; // 个人简介
25 | optional uint32 total_favorited = 9; // 获赞数量
26 | optional uint32 work_count = 10; // 作品数量
27 | optional uint32 favorite_count = 11; // 点赞数量
28 | }
29 |
30 |
31 | service UserService {
32 | rpc GetUser (UserRequest) returns (UserResponse) {
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/idl/wechat.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package douyin.wechat;
3 | option go_package = "douyin/wechat";
4 |
5 | message MessageChatRequest {
6 | uint32 sender_id = 1;
7 | uint32 receiver_id = 2;
8 | int64 pre_msg_time = 6;
9 | }
10 |
11 | message Message {
12 | uint32 id = 1;
13 | string content = 2;
14 | int64 create_time = 3;
15 | optional uint32 from_user_id = 4;
16 | optional uint32 to_user_id = 5;
17 | }
18 |
19 | message MessageChatResponse {
20 | uint32 status_code = 1;
21 | string status_msg = 2;
22 | repeated Message message_list = 3;
23 | }
24 |
25 | message MessageActionRequest {
26 | uint32 sender_id = 1;
27 | uint32 receiver_id = 2;
28 | uint32 action_type = 3;
29 | string content = 4;
30 | }
31 |
32 | message MessageActionResponse {
33 | uint32 status_code = 1;
34 | string status_msg = 2;
35 | }
36 |
37 | service WechatService {
38 | rpc WechatChat(MessageChatRequest) returns (MessageChatResponse) {}
39 | rpc WechatAction(MessageActionRequest) returns (MessageActionResponse) {}
40 | }
41 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/auth/authservice/client.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package authservice
4 |
5 | import (
6 | "context"
7 | client "github.com/cloudwego/kitex/client"
8 | callopt "github.com/cloudwego/kitex/client/callopt"
9 | auth "toktik/kitex_gen/douyin/auth"
10 | )
11 |
12 | // Client is designed to provide IDL-compatible methods with call-option parameter for kitex framework.
13 | type Client interface {
14 | Authenticate(ctx context.Context, Req *auth.AuthenticateRequest, callOptions ...callopt.Option) (r *auth.AuthenticateResponse, err error)
15 | Register(ctx context.Context, Req *auth.RegisterRequest, callOptions ...callopt.Option) (r *auth.RegisterResponse, err error)
16 | Login(ctx context.Context, Req *auth.LoginRequest, callOptions ...callopt.Option) (r *auth.LoginResponse, err error)
17 | }
18 |
19 | // NewClient creates a client for the service defined in IDL.
20 | func NewClient(destService string, opts ...client.Option) (Client, error) {
21 | var options []client.Option
22 | options = append(options, client.WithDestService(destService))
23 |
24 | options = append(options, opts...)
25 |
26 | kc, err := client.NewClient(serviceInfo(), options...)
27 | if err != nil {
28 | return nil, err
29 | }
30 | return &kAuthServiceClient{
31 | kClient: newServiceClient(kc),
32 | }, nil
33 | }
34 |
35 | // MustNewClient creates a client for the service defined in IDL. It panics if any error occurs.
36 | func MustNewClient(destService string, opts ...client.Option) Client {
37 | kc, err := NewClient(destService, opts...)
38 | if err != nil {
39 | panic(err)
40 | }
41 | return kc
42 | }
43 |
44 | type kAuthServiceClient struct {
45 | *kClient
46 | }
47 |
48 | func (p *kAuthServiceClient) Authenticate(ctx context.Context, Req *auth.AuthenticateRequest, callOptions ...callopt.Option) (r *auth.AuthenticateResponse, err error) {
49 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
50 | return p.kClient.Authenticate(ctx, Req)
51 | }
52 |
53 | func (p *kAuthServiceClient) Register(ctx context.Context, Req *auth.RegisterRequest, callOptions ...callopt.Option) (r *auth.RegisterResponse, err error) {
54 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
55 | return p.kClient.Register(ctx, Req)
56 | }
57 |
58 | func (p *kAuthServiceClient) Login(ctx context.Context, Req *auth.LoginRequest, callOptions ...callopt.Option) (r *auth.LoginResponse, err error) {
59 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
60 | return p.kClient.Login(ctx, Req)
61 | }
62 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/auth/authservice/invoker.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package authservice
4 |
5 | import (
6 | server "github.com/cloudwego/kitex/server"
7 | auth "toktik/kitex_gen/douyin/auth"
8 | )
9 |
10 | // NewInvoker creates a server.Invoker with the given handler and options.
11 | func NewInvoker(handler auth.AuthService, opts ...server.Option) server.Invoker {
12 | var options []server.Option
13 |
14 | options = append(options, opts...)
15 |
16 | s := server.NewInvoker(options...)
17 | if err := s.RegisterService(serviceInfo(), handler); err != nil {
18 | panic(err)
19 | }
20 | if err := s.Init(); err != nil {
21 | panic(err)
22 | }
23 | return s
24 | }
25 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/auth/authservice/server.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 | package authservice
3 |
4 | import (
5 | server "github.com/cloudwego/kitex/server"
6 | auth "toktik/kitex_gen/douyin/auth"
7 | )
8 |
9 | // NewServer creates a server.Server with the given handler and options.
10 | func NewServer(handler auth.AuthService, opts ...server.Option) server.Server {
11 | var options []server.Option
12 |
13 | options = append(options, opts...)
14 |
15 | svr := server.NewServer(options...)
16 | if err := svr.RegisterService(serviceInfo(), handler); err != nil {
17 | panic(err)
18 | }
19 | return svr
20 | }
21 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/comment/commentservice/client.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package commentservice
4 |
5 | import (
6 | "context"
7 | client "github.com/cloudwego/kitex/client"
8 | callopt "github.com/cloudwego/kitex/client/callopt"
9 | comment "toktik/kitex_gen/douyin/comment"
10 | )
11 |
12 | // Client is designed to provide IDL-compatible methods with call-option parameter for kitex framework.
13 | type Client interface {
14 | ActionComment(ctx context.Context, Req *comment.ActionCommentRequest, callOptions ...callopt.Option) (r *comment.ActionCommentResponse, err error)
15 | ListComment(ctx context.Context, Req *comment.ListCommentRequest, callOptions ...callopt.Option) (r *comment.ListCommentResponse, err error)
16 | CountComment(ctx context.Context, Req *comment.CountCommentRequest, callOptions ...callopt.Option) (r *comment.CountCommentResponse, err error)
17 | }
18 |
19 | // NewClient creates a client for the service defined in IDL.
20 | func NewClient(destService string, opts ...client.Option) (Client, error) {
21 | var options []client.Option
22 | options = append(options, client.WithDestService(destService))
23 |
24 | options = append(options, opts...)
25 |
26 | kc, err := client.NewClient(serviceInfo(), options...)
27 | if err != nil {
28 | return nil, err
29 | }
30 | return &kCommentServiceClient{
31 | kClient: newServiceClient(kc),
32 | }, nil
33 | }
34 |
35 | // MustNewClient creates a client for the service defined in IDL. It panics if any error occurs.
36 | func MustNewClient(destService string, opts ...client.Option) Client {
37 | kc, err := NewClient(destService, opts...)
38 | if err != nil {
39 | panic(err)
40 | }
41 | return kc
42 | }
43 |
44 | type kCommentServiceClient struct {
45 | *kClient
46 | }
47 |
48 | func (p *kCommentServiceClient) ActionComment(ctx context.Context, Req *comment.ActionCommentRequest, callOptions ...callopt.Option) (r *comment.ActionCommentResponse, err error) {
49 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
50 | return p.kClient.ActionComment(ctx, Req)
51 | }
52 |
53 | func (p *kCommentServiceClient) ListComment(ctx context.Context, Req *comment.ListCommentRequest, callOptions ...callopt.Option) (r *comment.ListCommentResponse, err error) {
54 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
55 | return p.kClient.ListComment(ctx, Req)
56 | }
57 |
58 | func (p *kCommentServiceClient) CountComment(ctx context.Context, Req *comment.CountCommentRequest, callOptions ...callopt.Option) (r *comment.CountCommentResponse, err error) {
59 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
60 | return p.kClient.CountComment(ctx, Req)
61 | }
62 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/comment/commentservice/invoker.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package commentservice
4 |
5 | import (
6 | server "github.com/cloudwego/kitex/server"
7 | comment "toktik/kitex_gen/douyin/comment"
8 | )
9 |
10 | // NewInvoker creates a server.Invoker with the given handler and options.
11 | func NewInvoker(handler comment.CommentService, opts ...server.Option) server.Invoker {
12 | var options []server.Option
13 |
14 | options = append(options, opts...)
15 |
16 | s := server.NewInvoker(options...)
17 | if err := s.RegisterService(serviceInfo(), handler); err != nil {
18 | panic(err)
19 | }
20 | if err := s.Init(); err != nil {
21 | panic(err)
22 | }
23 | return s
24 | }
25 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/comment/commentservice/server.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 | package commentservice
3 |
4 | import (
5 | server "github.com/cloudwego/kitex/server"
6 | comment "toktik/kitex_gen/douyin/comment"
7 | )
8 |
9 | // NewServer creates a server.Server with the given handler and options.
10 | func NewServer(handler comment.CommentService, opts ...server.Option) server.Server {
11 | var options []server.Option
12 |
13 | options = append(options, opts...)
14 |
15 | svr := server.NewServer(options...)
16 | if err := svr.RegisterService(serviceInfo(), handler); err != nil {
17 | panic(err)
18 | }
19 | return svr
20 | }
21 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/favorite/favoriteservice/client.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package favoriteservice
4 |
5 | import (
6 | "context"
7 | client "github.com/cloudwego/kitex/client"
8 | callopt "github.com/cloudwego/kitex/client/callopt"
9 | favorite "toktik/kitex_gen/douyin/favorite"
10 | )
11 |
12 | // Client is designed to provide IDL-compatible methods with call-option parameter for kitex framework.
13 | type Client interface {
14 | FavoriteAction(ctx context.Context, Req *favorite.FavoriteRequest, callOptions ...callopt.Option) (r *favorite.FavoriteResponse, err error)
15 | FavoriteList(ctx context.Context, Req *favorite.FavoriteListRequest, callOptions ...callopt.Option) (r *favorite.FavoriteListResponse, err error)
16 | IsFavorite(ctx context.Context, Req *favorite.IsFavoriteRequest, callOptions ...callopt.Option) (r *favorite.IsFavoriteResponse, err error)
17 | CountFavorite(ctx context.Context, Req *favorite.CountFavoriteRequest, callOptions ...callopt.Option) (r *favorite.CountFavoriteResponse, err error)
18 | CountUserFavorite(ctx context.Context, Req *favorite.CountUserFavoriteRequest, callOptions ...callopt.Option) (r *favorite.CountUserFavoriteResponse, err error)
19 | CountUserTotalFavorited(ctx context.Context, Req *favorite.CountUserTotalFavoritedRequest, callOptions ...callopt.Option) (r *favorite.CountUserTotalFavoritedResponse, err error)
20 | }
21 |
22 | // NewClient creates a client for the service defined in IDL.
23 | func NewClient(destService string, opts ...client.Option) (Client, error) {
24 | var options []client.Option
25 | options = append(options, client.WithDestService(destService))
26 |
27 | options = append(options, opts...)
28 |
29 | kc, err := client.NewClient(serviceInfo(), options...)
30 | if err != nil {
31 | return nil, err
32 | }
33 | return &kFavoriteServiceClient{
34 | kClient: newServiceClient(kc),
35 | }, nil
36 | }
37 |
38 | // MustNewClient creates a client for the service defined in IDL. It panics if any error occurs.
39 | func MustNewClient(destService string, opts ...client.Option) Client {
40 | kc, err := NewClient(destService, opts...)
41 | if err != nil {
42 | panic(err)
43 | }
44 | return kc
45 | }
46 |
47 | type kFavoriteServiceClient struct {
48 | *kClient
49 | }
50 |
51 | func (p *kFavoriteServiceClient) FavoriteAction(ctx context.Context, Req *favorite.FavoriteRequest, callOptions ...callopt.Option) (r *favorite.FavoriteResponse, err error) {
52 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
53 | return p.kClient.FavoriteAction(ctx, Req)
54 | }
55 |
56 | func (p *kFavoriteServiceClient) FavoriteList(ctx context.Context, Req *favorite.FavoriteListRequest, callOptions ...callopt.Option) (r *favorite.FavoriteListResponse, err error) {
57 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
58 | return p.kClient.FavoriteList(ctx, Req)
59 | }
60 |
61 | func (p *kFavoriteServiceClient) IsFavorite(ctx context.Context, Req *favorite.IsFavoriteRequest, callOptions ...callopt.Option) (r *favorite.IsFavoriteResponse, err error) {
62 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
63 | return p.kClient.IsFavorite(ctx, Req)
64 | }
65 |
66 | func (p *kFavoriteServiceClient) CountFavorite(ctx context.Context, Req *favorite.CountFavoriteRequest, callOptions ...callopt.Option) (r *favorite.CountFavoriteResponse, err error) {
67 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
68 | return p.kClient.CountFavorite(ctx, Req)
69 | }
70 |
71 | func (p *kFavoriteServiceClient) CountUserFavorite(ctx context.Context, Req *favorite.CountUserFavoriteRequest, callOptions ...callopt.Option) (r *favorite.CountUserFavoriteResponse, err error) {
72 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
73 | return p.kClient.CountUserFavorite(ctx, Req)
74 | }
75 |
76 | func (p *kFavoriteServiceClient) CountUserTotalFavorited(ctx context.Context, Req *favorite.CountUserTotalFavoritedRequest, callOptions ...callopt.Option) (r *favorite.CountUserTotalFavoritedResponse, err error) {
77 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
78 | return p.kClient.CountUserTotalFavorited(ctx, Req)
79 | }
80 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/favorite/favoriteservice/invoker.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package favoriteservice
4 |
5 | import (
6 | server "github.com/cloudwego/kitex/server"
7 | favorite "toktik/kitex_gen/douyin/favorite"
8 | )
9 |
10 | // NewInvoker creates a server.Invoker with the given handler and options.
11 | func NewInvoker(handler favorite.FavoriteService, opts ...server.Option) server.Invoker {
12 | var options []server.Option
13 |
14 | options = append(options, opts...)
15 |
16 | s := server.NewInvoker(options...)
17 | if err := s.RegisterService(serviceInfo(), handler); err != nil {
18 | panic(err)
19 | }
20 | if err := s.Init(); err != nil {
21 | panic(err)
22 | }
23 | return s
24 | }
25 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/favorite/favoriteservice/server.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 | package favoriteservice
3 |
4 | import (
5 | server "github.com/cloudwego/kitex/server"
6 | favorite "toktik/kitex_gen/douyin/favorite"
7 | )
8 |
9 | // NewServer creates a server.Server with the given handler and options.
10 | func NewServer(handler favorite.FavoriteService, opts ...server.Option) server.Server {
11 | var options []server.Option
12 |
13 | options = append(options, opts...)
14 |
15 | svr := server.NewServer(options...)
16 | if err := svr.RegisterService(serviceInfo(), handler); err != nil {
17 | panic(err)
18 | }
19 | return svr
20 | }
21 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/feed/feedservice/client.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package feedservice
4 |
5 | import (
6 | "context"
7 | client "github.com/cloudwego/kitex/client"
8 | callopt "github.com/cloudwego/kitex/client/callopt"
9 | feed "toktik/kitex_gen/douyin/feed"
10 | )
11 |
12 | // Client is designed to provide IDL-compatible methods with call-option parameter for kitex framework.
13 | type Client interface {
14 | ListVideos(ctx context.Context, Req *feed.ListFeedRequest, callOptions ...callopt.Option) (r *feed.ListFeedResponse, err error)
15 | QueryVideos(ctx context.Context, Req *feed.QueryVideosRequest, callOptions ...callopt.Option) (r *feed.QueryVideosResponse, err error)
16 | }
17 |
18 | // NewClient creates a client for the service defined in IDL.
19 | func NewClient(destService string, opts ...client.Option) (Client, error) {
20 | var options []client.Option
21 | options = append(options, client.WithDestService(destService))
22 |
23 | options = append(options, opts...)
24 |
25 | kc, err := client.NewClient(serviceInfo(), options...)
26 | if err != nil {
27 | return nil, err
28 | }
29 | return &kFeedServiceClient{
30 | kClient: newServiceClient(kc),
31 | }, nil
32 | }
33 |
34 | // MustNewClient creates a client for the service defined in IDL. It panics if any error occurs.
35 | func MustNewClient(destService string, opts ...client.Option) Client {
36 | kc, err := NewClient(destService, opts...)
37 | if err != nil {
38 | panic(err)
39 | }
40 | return kc
41 | }
42 |
43 | type kFeedServiceClient struct {
44 | *kClient
45 | }
46 |
47 | func (p *kFeedServiceClient) ListVideos(ctx context.Context, Req *feed.ListFeedRequest, callOptions ...callopt.Option) (r *feed.ListFeedResponse, err error) {
48 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
49 | return p.kClient.ListVideos(ctx, Req)
50 | }
51 |
52 | func (p *kFeedServiceClient) QueryVideos(ctx context.Context, Req *feed.QueryVideosRequest, callOptions ...callopt.Option) (r *feed.QueryVideosResponse, err error) {
53 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
54 | return p.kClient.QueryVideos(ctx, Req)
55 | }
56 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/feed/feedservice/invoker.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package feedservice
4 |
5 | import (
6 | server "github.com/cloudwego/kitex/server"
7 | feed "toktik/kitex_gen/douyin/feed"
8 | )
9 |
10 | // NewInvoker creates a server.Invoker with the given handler and options.
11 | func NewInvoker(handler feed.FeedService, opts ...server.Option) server.Invoker {
12 | var options []server.Option
13 |
14 | options = append(options, opts...)
15 |
16 | s := server.NewInvoker(options...)
17 | if err := s.RegisterService(serviceInfo(), handler); err != nil {
18 | panic(err)
19 | }
20 | if err := s.Init(); err != nil {
21 | panic(err)
22 | }
23 | return s
24 | }
25 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/feed/feedservice/server.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 | package feedservice
3 |
4 | import (
5 | server "github.com/cloudwego/kitex/server"
6 | feed "toktik/kitex_gen/douyin/feed"
7 | )
8 |
9 | // NewServer creates a server.Server with the given handler and options.
10 | func NewServer(handler feed.FeedService, opts ...server.Option) server.Server {
11 | var options []server.Option
12 |
13 | options = append(options, opts...)
14 |
15 | svr := server.NewServer(options...)
16 | if err := svr.RegisterService(serviceInfo(), handler); err != nil {
17 | panic(err)
18 | }
19 | return svr
20 | }
21 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/publish/publishservice/client.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package publishservice
4 |
5 | import (
6 | "context"
7 | client "github.com/cloudwego/kitex/client"
8 | callopt "github.com/cloudwego/kitex/client/callopt"
9 | publish "toktik/kitex_gen/douyin/publish"
10 | )
11 |
12 | // Client is designed to provide IDL-compatible methods with call-option parameter for kitex framework.
13 | type Client interface {
14 | CreateVideo(ctx context.Context, Req *publish.CreateVideoRequest, callOptions ...callopt.Option) (r *publish.CreateVideoResponse, err error)
15 | ListVideo(ctx context.Context, Req *publish.ListVideoRequest, callOptions ...callopt.Option) (r *publish.ListVideoResponse, err error)
16 | CountVideo(ctx context.Context, Req *publish.CountVideoRequest, callOptions ...callopt.Option) (r *publish.CountVideoResponse, err error)
17 | }
18 |
19 | // NewClient creates a client for the service defined in IDL.
20 | func NewClient(destService string, opts ...client.Option) (Client, error) {
21 | var options []client.Option
22 | options = append(options, client.WithDestService(destService))
23 |
24 | options = append(options, opts...)
25 |
26 | kc, err := client.NewClient(serviceInfo(), options...)
27 | if err != nil {
28 | return nil, err
29 | }
30 | return &kPublishServiceClient{
31 | kClient: newServiceClient(kc),
32 | }, nil
33 | }
34 |
35 | // MustNewClient creates a client for the service defined in IDL. It panics if any error occurs.
36 | func MustNewClient(destService string, opts ...client.Option) Client {
37 | kc, err := NewClient(destService, opts...)
38 | if err != nil {
39 | panic(err)
40 | }
41 | return kc
42 | }
43 |
44 | type kPublishServiceClient struct {
45 | *kClient
46 | }
47 |
48 | func (p *kPublishServiceClient) CreateVideo(ctx context.Context, Req *publish.CreateVideoRequest, callOptions ...callopt.Option) (r *publish.CreateVideoResponse, err error) {
49 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
50 | return p.kClient.CreateVideo(ctx, Req)
51 | }
52 |
53 | func (p *kPublishServiceClient) ListVideo(ctx context.Context, Req *publish.ListVideoRequest, callOptions ...callopt.Option) (r *publish.ListVideoResponse, err error) {
54 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
55 | return p.kClient.ListVideo(ctx, Req)
56 | }
57 |
58 | func (p *kPublishServiceClient) CountVideo(ctx context.Context, Req *publish.CountVideoRequest, callOptions ...callopt.Option) (r *publish.CountVideoResponse, err error) {
59 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
60 | return p.kClient.CountVideo(ctx, Req)
61 | }
62 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/publish/publishservice/invoker.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package publishservice
4 |
5 | import (
6 | server "github.com/cloudwego/kitex/server"
7 | publish "toktik/kitex_gen/douyin/publish"
8 | )
9 |
10 | // NewInvoker creates a server.Invoker with the given handler and options.
11 | func NewInvoker(handler publish.PublishService, opts ...server.Option) server.Invoker {
12 | var options []server.Option
13 |
14 | options = append(options, opts...)
15 |
16 | s := server.NewInvoker(options...)
17 | if err := s.RegisterService(serviceInfo(), handler); err != nil {
18 | panic(err)
19 | }
20 | if err := s.Init(); err != nil {
21 | panic(err)
22 | }
23 | return s
24 | }
25 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/publish/publishservice/server.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 | package publishservice
3 |
4 | import (
5 | server "github.com/cloudwego/kitex/server"
6 | publish "toktik/kitex_gen/douyin/publish"
7 | )
8 |
9 | // NewServer creates a server.Server with the given handler and options.
10 | func NewServer(handler publish.PublishService, opts ...server.Option) server.Server {
11 | var options []server.Option
12 |
13 | options = append(options, opts...)
14 |
15 | svr := server.NewServer(options...)
16 | if err := svr.RegisterService(serviceInfo(), handler); err != nil {
17 | panic(err)
18 | }
19 | return svr
20 | }
21 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/relation/relationservice/client.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package relationservice
4 |
5 | import (
6 | "context"
7 | client "github.com/cloudwego/kitex/client"
8 | callopt "github.com/cloudwego/kitex/client/callopt"
9 | relation "toktik/kitex_gen/douyin/relation"
10 | )
11 |
12 | // Client is designed to provide IDL-compatible methods with call-option parameter for kitex framework.
13 | type Client interface {
14 | Follow(ctx context.Context, Req *relation.RelationActionRequest, callOptions ...callopt.Option) (r *relation.RelationActionResponse, err error)
15 | Unfollow(ctx context.Context, Req *relation.RelationActionRequest, callOptions ...callopt.Option) (r *relation.RelationActionResponse, err error)
16 | GetFollowList(ctx context.Context, Req *relation.FollowListRequest, callOptions ...callopt.Option) (r *relation.FollowListResponse, err error)
17 | CountFollowList(ctx context.Context, Req *relation.CountFollowListRequest, callOptions ...callopt.Option) (r *relation.CountFollowListResponse, err error)
18 | GetFollowerList(ctx context.Context, Req *relation.FollowerListRequest, callOptions ...callopt.Option) (r *relation.FollowerListResponse, err error)
19 | CountFollowerList(ctx context.Context, Req *relation.CountFollowerListRequest, callOptions ...callopt.Option) (r *relation.CountFollowerListResponse, err error)
20 | GetFriendList(ctx context.Context, Req *relation.FriendListRequest, callOptions ...callopt.Option) (r *relation.FriendListResponse, err error)
21 | IsFollow(ctx context.Context, Req *relation.IsFollowRequest, callOptions ...callopt.Option) (r *relation.IsFollowResponse, err error)
22 | }
23 |
24 | // NewClient creates a client for the service defined in IDL.
25 | func NewClient(destService string, opts ...client.Option) (Client, error) {
26 | var options []client.Option
27 | options = append(options, client.WithDestService(destService))
28 |
29 | options = append(options, opts...)
30 |
31 | kc, err := client.NewClient(serviceInfo(), options...)
32 | if err != nil {
33 | return nil, err
34 | }
35 | return &kRelationServiceClient{
36 | kClient: newServiceClient(kc),
37 | }, nil
38 | }
39 |
40 | // MustNewClient creates a client for the service defined in IDL. It panics if any error occurs.
41 | func MustNewClient(destService string, opts ...client.Option) Client {
42 | kc, err := NewClient(destService, opts...)
43 | if err != nil {
44 | panic(err)
45 | }
46 | return kc
47 | }
48 |
49 | type kRelationServiceClient struct {
50 | *kClient
51 | }
52 |
53 | func (p *kRelationServiceClient) Follow(ctx context.Context, Req *relation.RelationActionRequest, callOptions ...callopt.Option) (r *relation.RelationActionResponse, err error) {
54 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
55 | return p.kClient.Follow(ctx, Req)
56 | }
57 |
58 | func (p *kRelationServiceClient) Unfollow(ctx context.Context, Req *relation.RelationActionRequest, callOptions ...callopt.Option) (r *relation.RelationActionResponse, err error) {
59 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
60 | return p.kClient.Unfollow(ctx, Req)
61 | }
62 |
63 | func (p *kRelationServiceClient) GetFollowList(ctx context.Context, Req *relation.FollowListRequest, callOptions ...callopt.Option) (r *relation.FollowListResponse, err error) {
64 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
65 | return p.kClient.GetFollowList(ctx, Req)
66 | }
67 |
68 | func (p *kRelationServiceClient) CountFollowList(ctx context.Context, Req *relation.CountFollowListRequest, callOptions ...callopt.Option) (r *relation.CountFollowListResponse, err error) {
69 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
70 | return p.kClient.CountFollowList(ctx, Req)
71 | }
72 |
73 | func (p *kRelationServiceClient) GetFollowerList(ctx context.Context, Req *relation.FollowerListRequest, callOptions ...callopt.Option) (r *relation.FollowerListResponse, err error) {
74 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
75 | return p.kClient.GetFollowerList(ctx, Req)
76 | }
77 |
78 | func (p *kRelationServiceClient) CountFollowerList(ctx context.Context, Req *relation.CountFollowerListRequest, callOptions ...callopt.Option) (r *relation.CountFollowerListResponse, err error) {
79 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
80 | return p.kClient.CountFollowerList(ctx, Req)
81 | }
82 |
83 | func (p *kRelationServiceClient) GetFriendList(ctx context.Context, Req *relation.FriendListRequest, callOptions ...callopt.Option) (r *relation.FriendListResponse, err error) {
84 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
85 | return p.kClient.GetFriendList(ctx, Req)
86 | }
87 |
88 | func (p *kRelationServiceClient) IsFollow(ctx context.Context, Req *relation.IsFollowRequest, callOptions ...callopt.Option) (r *relation.IsFollowResponse, err error) {
89 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
90 | return p.kClient.IsFollow(ctx, Req)
91 | }
92 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/relation/relationservice/invoker.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package relationservice
4 |
5 | import (
6 | server "github.com/cloudwego/kitex/server"
7 | relation "toktik/kitex_gen/douyin/relation"
8 | )
9 |
10 | // NewInvoker creates a server.Invoker with the given handler and options.
11 | func NewInvoker(handler relation.RelationService, opts ...server.Option) server.Invoker {
12 | var options []server.Option
13 |
14 | options = append(options, opts...)
15 |
16 | s := server.NewInvoker(options...)
17 | if err := s.RegisterService(serviceInfo(), handler); err != nil {
18 | panic(err)
19 | }
20 | if err := s.Init(); err != nil {
21 | panic(err)
22 | }
23 | return s
24 | }
25 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/relation/relationservice/server.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 | package relationservice
3 |
4 | import (
5 | server "github.com/cloudwego/kitex/server"
6 | relation "toktik/kitex_gen/douyin/relation"
7 | )
8 |
9 | // NewServer creates a server.Server with the given handler and options.
10 | func NewServer(handler relation.RelationService, opts ...server.Option) server.Server {
11 | var options []server.Option
12 |
13 | options = append(options, opts...)
14 |
15 | svr := server.NewServer(options...)
16 | if err := svr.RegisterService(serviceInfo(), handler); err != nil {
17 | panic(err)
18 | }
19 | return svr
20 | }
21 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/user/userservice/client.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package userservice
4 |
5 | import (
6 | "context"
7 | client "github.com/cloudwego/kitex/client"
8 | callopt "github.com/cloudwego/kitex/client/callopt"
9 | user "toktik/kitex_gen/douyin/user"
10 | )
11 |
12 | // Client is designed to provide IDL-compatible methods with call-option parameter for kitex framework.
13 | type Client interface {
14 | GetUser(ctx context.Context, Req *user.UserRequest, callOptions ...callopt.Option) (r *user.UserResponse, err error)
15 | }
16 |
17 | // NewClient creates a client for the service defined in IDL.
18 | func NewClient(destService string, opts ...client.Option) (Client, error) {
19 | var options []client.Option
20 | options = append(options, client.WithDestService(destService))
21 |
22 | options = append(options, opts...)
23 |
24 | kc, err := client.NewClient(serviceInfo(), options...)
25 | if err != nil {
26 | return nil, err
27 | }
28 | return &kUserServiceClient{
29 | kClient: newServiceClient(kc),
30 | }, nil
31 | }
32 |
33 | // MustNewClient creates a client for the service defined in IDL. It panics if any error occurs.
34 | func MustNewClient(destService string, opts ...client.Option) Client {
35 | kc, err := NewClient(destService, opts...)
36 | if err != nil {
37 | panic(err)
38 | }
39 | return kc
40 | }
41 |
42 | type kUserServiceClient struct {
43 | *kClient
44 | }
45 |
46 | func (p *kUserServiceClient) GetUser(ctx context.Context, Req *user.UserRequest, callOptions ...callopt.Option) (r *user.UserResponse, err error) {
47 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
48 | return p.kClient.GetUser(ctx, Req)
49 | }
50 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/user/userservice/invoker.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package userservice
4 |
5 | import (
6 | server "github.com/cloudwego/kitex/server"
7 | user "toktik/kitex_gen/douyin/user"
8 | )
9 |
10 | // NewInvoker creates a server.Invoker with the given handler and options.
11 | func NewInvoker(handler user.UserService, opts ...server.Option) server.Invoker {
12 | var options []server.Option
13 |
14 | options = append(options, opts...)
15 |
16 | s := server.NewInvoker(options...)
17 | if err := s.RegisterService(serviceInfo(), handler); err != nil {
18 | panic(err)
19 | }
20 | if err := s.Init(); err != nil {
21 | panic(err)
22 | }
23 | return s
24 | }
25 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/user/userservice/server.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 | package userservice
3 |
4 | import (
5 | server "github.com/cloudwego/kitex/server"
6 | user "toktik/kitex_gen/douyin/user"
7 | )
8 |
9 | // NewServer creates a server.Server with the given handler and options.
10 | func NewServer(handler user.UserService, opts ...server.Option) server.Server {
11 | var options []server.Option
12 |
13 | options = append(options, opts...)
14 |
15 | svr := server.NewServer(options...)
16 | if err := svr.RegisterService(serviceInfo(), handler); err != nil {
17 | panic(err)
18 | }
19 | return svr
20 | }
21 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/user/userservice/userservice.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package userservice
4 |
5 | import (
6 | "context"
7 | "fmt"
8 | client "github.com/cloudwego/kitex/client"
9 | kitex "github.com/cloudwego/kitex/pkg/serviceinfo"
10 | streaming "github.com/cloudwego/kitex/pkg/streaming"
11 | proto "google.golang.org/protobuf/proto"
12 | user "toktik/kitex_gen/douyin/user"
13 | )
14 |
15 | func serviceInfo() *kitex.ServiceInfo {
16 | return userServiceServiceInfo
17 | }
18 |
19 | var userServiceServiceInfo = NewServiceInfo()
20 |
21 | func NewServiceInfo() *kitex.ServiceInfo {
22 | serviceName := "UserService"
23 | handlerType := (*user.UserService)(nil)
24 | methods := map[string]kitex.MethodInfo{
25 | "GetUser": kitex.NewMethodInfo(getUserHandler, newGetUserArgs, newGetUserResult, false),
26 | }
27 | extra := map[string]interface{}{
28 | "PackageName": "douyin.user",
29 | }
30 | svcInfo := &kitex.ServiceInfo{
31 | ServiceName: serviceName,
32 | HandlerType: handlerType,
33 | Methods: methods,
34 | PayloadCodec: kitex.Protobuf,
35 | KiteXGenVersion: "v0.4.4",
36 | Extra: extra,
37 | }
38 | return svcInfo
39 | }
40 |
41 | func getUserHandler(ctx context.Context, handler interface{}, arg, result interface{}) error {
42 | switch s := arg.(type) {
43 | case *streaming.Args:
44 | st := s.Stream
45 | req := new(user.UserRequest)
46 | if err := st.RecvMsg(req); err != nil {
47 | return err
48 | }
49 | resp, err := handler.(user.UserService).GetUser(ctx, req)
50 | if err != nil {
51 | return err
52 | }
53 | if err := st.SendMsg(resp); err != nil {
54 | return err
55 | }
56 | case *GetUserArgs:
57 | success, err := handler.(user.UserService).GetUser(ctx, s.Req)
58 | if err != nil {
59 | return err
60 | }
61 | realResult := result.(*GetUserResult)
62 | realResult.Success = success
63 | }
64 | return nil
65 | }
66 | func newGetUserArgs() interface{} {
67 | return &GetUserArgs{}
68 | }
69 |
70 | func newGetUserResult() interface{} {
71 | return &GetUserResult{}
72 | }
73 |
74 | type GetUserArgs struct {
75 | Req *user.UserRequest
76 | }
77 |
78 | func (p *GetUserArgs) FastRead(buf []byte, _type int8, number int32) (n int, err error) {
79 | if !p.IsSetReq() {
80 | p.Req = new(user.UserRequest)
81 | }
82 | return p.Req.FastRead(buf, _type, number)
83 | }
84 |
85 | func (p *GetUserArgs) FastWrite(buf []byte) (n int) {
86 | if !p.IsSetReq() {
87 | return 0
88 | }
89 | return p.Req.FastWrite(buf)
90 | }
91 |
92 | func (p *GetUserArgs) Size() (n int) {
93 | if !p.IsSetReq() {
94 | return 0
95 | }
96 | return p.Req.Size()
97 | }
98 |
99 | func (p *GetUserArgs) Marshal(out []byte) ([]byte, error) {
100 | if !p.IsSetReq() {
101 | return out, fmt.Errorf("No req in GetUserArgs")
102 | }
103 | return proto.Marshal(p.Req)
104 | }
105 |
106 | func (p *GetUserArgs) Unmarshal(in []byte) error {
107 | msg := new(user.UserRequest)
108 | if err := proto.Unmarshal(in, msg); err != nil {
109 | return err
110 | }
111 | p.Req = msg
112 | return nil
113 | }
114 |
115 | var GetUserArgs_Req_DEFAULT *user.UserRequest
116 |
117 | func (p *GetUserArgs) GetReq() *user.UserRequest {
118 | if !p.IsSetReq() {
119 | return GetUserArgs_Req_DEFAULT
120 | }
121 | return p.Req
122 | }
123 |
124 | func (p *GetUserArgs) IsSetReq() bool {
125 | return p.Req != nil
126 | }
127 |
128 | type GetUserResult struct {
129 | Success *user.UserResponse
130 | }
131 |
132 | var GetUserResult_Success_DEFAULT *user.UserResponse
133 |
134 | func (p *GetUserResult) FastRead(buf []byte, _type int8, number int32) (n int, err error) {
135 | if !p.IsSetSuccess() {
136 | p.Success = new(user.UserResponse)
137 | }
138 | return p.Success.FastRead(buf, _type, number)
139 | }
140 |
141 | func (p *GetUserResult) FastWrite(buf []byte) (n int) {
142 | if !p.IsSetSuccess() {
143 | return 0
144 | }
145 | return p.Success.FastWrite(buf)
146 | }
147 |
148 | func (p *GetUserResult) Size() (n int) {
149 | if !p.IsSetSuccess() {
150 | return 0
151 | }
152 | return p.Success.Size()
153 | }
154 |
155 | func (p *GetUserResult) Marshal(out []byte) ([]byte, error) {
156 | if !p.IsSetSuccess() {
157 | return out, fmt.Errorf("No req in GetUserResult")
158 | }
159 | return proto.Marshal(p.Success)
160 | }
161 |
162 | func (p *GetUserResult) Unmarshal(in []byte) error {
163 | msg := new(user.UserResponse)
164 | if err := proto.Unmarshal(in, msg); err != nil {
165 | return err
166 | }
167 | p.Success = msg
168 | return nil
169 | }
170 |
171 | func (p *GetUserResult) GetSuccess() *user.UserResponse {
172 | if !p.IsSetSuccess() {
173 | return GetUserResult_Success_DEFAULT
174 | }
175 | return p.Success
176 | }
177 |
178 | func (p *GetUserResult) SetSuccess(x interface{}) {
179 | p.Success = x.(*user.UserResponse)
180 | }
181 |
182 | func (p *GetUserResult) IsSetSuccess() bool {
183 | return p.Success != nil
184 | }
185 |
186 | type kClient struct {
187 | c client.Client
188 | }
189 |
190 | func newServiceClient(c client.Client) *kClient {
191 | return &kClient{
192 | c: c,
193 | }
194 | }
195 |
196 | func (p *kClient) GetUser(ctx context.Context, Req *user.UserRequest) (r *user.UserResponse, err error) {
197 | var _args GetUserArgs
198 | _args.Req = Req
199 | var _result GetUserResult
200 | if err = p.c.Call(ctx, "GetUser", &_args, &_result); err != nil {
201 | return
202 | }
203 | return _result.GetSuccess(), nil
204 | }
205 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/wechat/wechatservice/client.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package wechatservice
4 |
5 | import (
6 | "context"
7 | client "github.com/cloudwego/kitex/client"
8 | callopt "github.com/cloudwego/kitex/client/callopt"
9 | wechat "toktik/kitex_gen/douyin/wechat"
10 | )
11 |
12 | // Client is designed to provide IDL-compatible methods with call-option parameter for kitex framework.
13 | type Client interface {
14 | WechatChat(ctx context.Context, Req *wechat.MessageChatRequest, callOptions ...callopt.Option) (r *wechat.MessageChatResponse, err error)
15 | WechatAction(ctx context.Context, Req *wechat.MessageActionRequest, callOptions ...callopt.Option) (r *wechat.MessageActionResponse, err error)
16 | }
17 |
18 | // NewClient creates a client for the service defined in IDL.
19 | func NewClient(destService string, opts ...client.Option) (Client, error) {
20 | var options []client.Option
21 | options = append(options, client.WithDestService(destService))
22 |
23 | options = append(options, opts...)
24 |
25 | kc, err := client.NewClient(serviceInfo(), options...)
26 | if err != nil {
27 | return nil, err
28 | }
29 | return &kWechatServiceClient{
30 | kClient: newServiceClient(kc),
31 | }, nil
32 | }
33 |
34 | // MustNewClient creates a client for the service defined in IDL. It panics if any error occurs.
35 | func MustNewClient(destService string, opts ...client.Option) Client {
36 | kc, err := NewClient(destService, opts...)
37 | if err != nil {
38 | panic(err)
39 | }
40 | return kc
41 | }
42 |
43 | type kWechatServiceClient struct {
44 | *kClient
45 | }
46 |
47 | func (p *kWechatServiceClient) WechatChat(ctx context.Context, Req *wechat.MessageChatRequest, callOptions ...callopt.Option) (r *wechat.MessageChatResponse, err error) {
48 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
49 | return p.kClient.WechatChat(ctx, Req)
50 | }
51 |
52 | func (p *kWechatServiceClient) WechatAction(ctx context.Context, Req *wechat.MessageActionRequest, callOptions ...callopt.Option) (r *wechat.MessageActionResponse, err error) {
53 | ctx = client.NewCtxWithCallOptions(ctx, callOptions)
54 | return p.kClient.WechatAction(ctx, Req)
55 | }
56 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/wechat/wechatservice/invoker.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 |
3 | package wechatservice
4 |
5 | import (
6 | server "github.com/cloudwego/kitex/server"
7 | wechat "toktik/kitex_gen/douyin/wechat"
8 | )
9 |
10 | // NewInvoker creates a server.Invoker with the given handler and options.
11 | func NewInvoker(handler wechat.WechatService, opts ...server.Option) server.Invoker {
12 | var options []server.Option
13 |
14 | options = append(options, opts...)
15 |
16 | s := server.NewInvoker(options...)
17 | if err := s.RegisterService(serviceInfo(), handler); err != nil {
18 | panic(err)
19 | }
20 | if err := s.Init(); err != nil {
21 | panic(err)
22 | }
23 | return s
24 | }
25 |
--------------------------------------------------------------------------------
/kitex_gen/douyin/wechat/wechatservice/server.go:
--------------------------------------------------------------------------------
1 | // Code generated by Kitex v0.4.4. DO NOT EDIT.
2 | package wechatservice
3 |
4 | import (
5 | server "github.com/cloudwego/kitex/server"
6 | wechat "toktik/kitex_gen/douyin/wechat"
7 | )
8 |
9 | // NewServer creates a server.Server with the given handler and options.
10 | func NewServer(handler wechat.WechatService, opts ...server.Option) server.Server {
11 | var options []server.Option
12 |
13 | options = append(options, opts...)
14 |
15 | svr := server.NewServer(options...)
16 | if err := svr.RegisterService(serviceInfo(), handler); err != nil {
17 | panic(err)
18 | }
19 | return svr
20 | }
21 |
--------------------------------------------------------------------------------
/logging/init.go:
--------------------------------------------------------------------------------
1 | package logging
2 |
3 | import (
4 | "os"
5 |
6 | log "github.com/sirupsen/logrus"
7 | )
8 |
9 | func init() {
10 | log.SetFormatter(&log.JSONFormatter{})
11 |
12 | log.SetOutput(os.Stdout)
13 |
14 | if os.Getenv("ENV") == "prod" {
15 | log.SetLevel(log.WarnLevel)
16 | } else {
17 | log.SetLevel(log.DebugLevel)
18 | }
19 | }
20 |
21 | var env = os.Getenv("ENV")
22 |
23 | // Logger Add fields you want to log by default.
24 | var Logger = log.WithFields(log.Fields{
25 | "ENV": env,
26 | })
27 |
--------------------------------------------------------------------------------
/manifests-dev/configmap.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: env-config
5 | namespace: toktik-service-bundle
6 | data:
7 | DREAM_APP_NAME: toktik-service-bundle
8 | DREAM_UNIT_NAME: toktik-service-bundle
9 | DREAM_APP_VERSION: "${CI_COMMIT_ID}"
10 | DREAM_APP_ROOT: /data/apps/toktik-service-bundle
11 | DREAM_CFG_ACCESS_KEY: ''
12 | DREAM_ENV: TESTING
13 | DREAM_REGION_NAME: tencent
14 | DREAM_SEC_APP_TOKEN: ''
15 | DREAM_SERVICE_DISCOVERY_URI: 'consul://consul-server.consul.svc.cluster.local:8500'
16 | DREAM_IMAGE_TAG: ${IMAGE_TAG}
17 |
--------------------------------------------------------------------------------
/manifests-dev/deployment-toktik-auth-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | sidecar.jaegertracing.io/inject: 'true'
6 | labels:
7 | app: toktik-auth-api
8 | name: toktik-auth-api
9 | namespace: toktik-service-bundle
10 | spec:
11 | selector:
12 | matchLabels:
13 | name: toktik-auth-api
14 | template:
15 | metadata:
16 | labels:
17 | app: toktik-auth-api
18 | branch: dev
19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID}
20 | name: toktik-auth-api
21 | dream-app: toktik-auth-api
22 | dream-unit: toktik-auth-api
23 | spec:
24 | imagePullSecrets:
25 | - name: regcred
26 | containers:
27 | - image: ${IMAGE}
28 | imagePullPolicy: IfNotPresent
29 | name: toktik-auth-api
30 | command:
31 | - /bin/bash
32 | args:
33 | - bootstrap-auth.sh
34 | envFrom:
35 | - configMapRef:
36 | name: env-config
37 | - configMapRef:
38 | name: config
39 | ports:
40 | - name: grpc-40127
41 | containerPort: 40127
42 | protocol: TCP
43 | resources:
44 | limits:
45 | cpu: 500m
46 | memory: 256Mi
47 | requests:
48 | cpu: 100m
49 | memory: 8Mi
50 | terminationGracePeriodSeconds: 30
51 |
--------------------------------------------------------------------------------
/manifests-dev/deployment-toktik-comment-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | sidecar.jaegertracing.io/inject: 'true'
6 | labels:
7 | app: toktik-comment-api
8 | name: toktik-comment-api
9 | namespace: toktik-service-bundle
10 | spec:
11 | selector:
12 | matchLabels:
13 | name: toktik-comment-api
14 | template:
15 | metadata:
16 | labels:
17 | app: toktik-comment-api
18 | branch: dev
19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID}
20 | name: toktik-comment-api
21 | dream-app: toktik-comment-api
22 | dream-unit: toktik-comment-api
23 | spec:
24 | imagePullSecrets:
25 | - name: regcred
26 | containers:
27 | - image: ${IMAGE}
28 | imagePullPolicy: IfNotPresent
29 | name: toktik-comment-api
30 | command:
31 | - /bin/bash
32 | args:
33 | - bootstrap-comment.sh
34 | envFrom:
35 | - configMapRef:
36 | name: env-config
37 | - configMapRef:
38 | name: config
39 | ports:
40 | - name: grpc-40131
41 | containerPort: 40131
42 | protocol: TCP
43 | resources:
44 | limits:
45 | cpu: 500m
46 | memory: 256Mi
47 | requests:
48 | cpu: 100m
49 | memory: 8Mi
50 | terminationGracePeriodSeconds: 30
51 |
--------------------------------------------------------------------------------
/manifests-dev/deployment-toktik-favorite-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | sidecar.jaegertracing.io/inject: 'true'
6 | labels:
7 | app: toktik-favorite-api
8 | name: toktik-favorite-api
9 | namespace: toktik-service-bundle
10 | spec:
11 | selector:
12 | matchLabels:
13 | name: toktik-favorite-api
14 | template:
15 | metadata:
16 | labels:
17 | app: toktik-favorite-api
18 | branch: dev
19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID}
20 | name: toktik-favorite-api
21 | dream-app: toktik-favorite-api
22 | dream-unit: toktik-favorite-api
23 | spec:
24 | imagePullSecrets:
25 | - name: regcred
26 | containers:
27 | - image: ${IMAGE}
28 | imagePullPolicy: IfNotPresent
29 | name: toktik-favorite-api
30 | command:
31 | - /bin/bash
32 | args:
33 | - bootstrap-favorite.sh
34 | envFrom:
35 | - configMapRef:
36 | name: env-config
37 | - configMapRef:
38 | name: config
39 | ports:
40 | - name: grpc-40134
41 | containerPort: 40134
42 | protocol: TCP
43 | resources:
44 | limits:
45 | cpu: 500m
46 | memory: 256Mi
47 | requests:
48 | cpu: 100m
49 | memory: 8Mi
50 | terminationGracePeriodSeconds: 30
51 |
--------------------------------------------------------------------------------
/manifests-dev/deployment-toktik-feed-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | sidecar.jaegertracing.io/inject: 'true'
6 | labels:
7 | app: toktik-feed-api
8 | name: toktik-feed-api
9 | namespace: toktik-service-bundle
10 | spec:
11 | selector:
12 | matchLabels:
13 | name: toktik-feed-api
14 | template:
15 | metadata:
16 | labels:
17 | app: toktik-feed-api
18 | branch: dev
19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID}
20 | name: toktik-feed-api
21 | dream-app: toktik-feed-api
22 | dream-unit: toktik-feed-api
23 | spec:
24 | imagePullSecrets:
25 | - name: regcred
26 | containers:
27 | - image: ${IMAGE}
28 | imagePullPolicy: IfNotPresent
29 | name: toktik-feed-api
30 | command:
31 | - /bin/bash
32 | args:
33 | - bootstrap-feed.sh
34 | envFrom:
35 | - configMapRef:
36 | name: env-config
37 | - configMapRef:
38 | name: config
39 | ports:
40 | - name: grpc-40129
41 | containerPort: 40129
42 | protocol: TCP
43 | resources:
44 | limits:
45 | cpu: 500m
46 | memory: 256Mi
47 | requests:
48 | cpu: 100m
49 | memory: 8Mi
50 | terminationGracePeriodSeconds: 30
51 |
--------------------------------------------------------------------------------
/manifests-dev/deployment-toktik-http-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | sidecar.jaegertracing.io/inject: 'true'
6 | labels:
7 | app: toktik-http-api
8 | name: toktik-http-api
9 | namespace: toktik-service-bundle
10 | spec:
11 | selector:
12 | matchLabels:
13 | name: toktik-http-api
14 | template:
15 | metadata:
16 | labels:
17 | app: toktik-http-api
18 | branch: dev
19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID}
20 | name: toktik-http-api
21 | dream-app: toktik-http-api
22 | dream-unit: toktik-http-api
23 | spec:
24 | imagePullSecrets:
25 | - name: regcred
26 | containers:
27 | - image: ${IMAGE}
28 | imagePullPolicy: IfNotPresent
29 | name: toktik-http-api
30 | command:
31 | - bin/web
32 | envFrom:
33 | - configMapRef:
34 | name: env-config
35 | - configMapRef:
36 | name: config
37 | ports:
38 | - name: http-40126
39 | containerPort: 40126
40 | protocol: TCP
41 | resources:
42 | limits:
43 | cpu: 500m
44 | memory: 256Mi
45 | requests:
46 | cpu: 100m
47 | memory: 8Mi
48 | terminationGracePeriodSeconds: 30
49 |
--------------------------------------------------------------------------------
/manifests-dev/deployment-toktik-publish-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | sidecar.jaegertracing.io/inject: 'true'
6 | labels:
7 | app: toktik-publish-api
8 | name: toktik-publish-api
9 | namespace: toktik-service-bundle
10 | spec:
11 | selector:
12 | matchLabels:
13 | name: toktik-publish-api
14 | template:
15 | metadata:
16 | labels:
17 | app: toktik-publish-api
18 | branch: dev
19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID}
20 | name: toktik-publish-api
21 | dream-app: toktik-publish-api
22 | dream-unit: toktik-publish-api
23 | spec:
24 | volumes:
25 | - name: volume-storage
26 | persistentVolumeClaim:
27 | claimName: storage
28 | imagePullSecrets:
29 | - name: regcred
30 | containers:
31 | - image: ${IMAGE}
32 | imagePullPolicy: IfNotPresent
33 | name: toktik-publish-api
34 | command:
35 | - /bin/bash
36 | args:
37 | - bootstrap-publish.sh
38 | envFrom:
39 | - configMapRef:
40 | name: env-config
41 | - configMapRef:
42 | name: config
43 | ports:
44 | - name: grpc-40128
45 | containerPort: 40128
46 | protocol: TCP
47 | resources:
48 | limits:
49 | cpu: 500m
50 | memory: 256Mi
51 | requests:
52 | cpu: 100m
53 | memory: 8Mi
54 | volumeMounts:
55 | - name: volume-storage
56 | mountPath: /data/storage
57 | terminationGracePeriodSeconds: 30
58 |
--------------------------------------------------------------------------------
/manifests-dev/deployment-toktik-relation-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | sidecar.jaegertracing.io/inject: 'true'
6 | labels:
7 | app: toktik-relation-api
8 | name: toktik-relation-api
9 | namespace: toktik-service-bundle
10 | spec:
11 | selector:
12 | matchLabels:
13 | name: toktik-relation-api
14 | template:
15 | metadata:
16 | labels:
17 | app: toktik-relation-api
18 | branch: dev
19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID}
20 | name: toktik-relation-api
21 | dream-app: toktik-relation-api
22 | dream-unit: toktik-relation-api
23 | spec:
24 | volumes:
25 | - name: volume-storage
26 | persistentVolumeClaim:
27 | claimName: storage
28 | imagePullSecrets:
29 | - name: regcred
30 | containers:
31 | - image: ${IMAGE}
32 | imagePullPolicy: IfNotPresent
33 | name: toktik-relation-api
34 | command:
35 | - /bin/bash
36 | args:
37 | - bootstrap-relation.sh
38 | envFrom:
39 | - configMapRef:
40 | name: env-config
41 | - configMapRef:
42 | name: config
43 | ports:
44 | - name: grpc-40133
45 | containerPort: 40133
46 | protocol: TCP
47 | resources:
48 | limits:
49 | cpu: 500m
50 | memory: 256Mi
51 | requests:
52 | cpu: 100m
53 | memory: 8Mi
54 | volumeMounts:
55 | - name: volume-storage
56 | mountPath: /data/storage
57 | terminationGracePeriodSeconds: 30
58 |
--------------------------------------------------------------------------------
/manifests-dev/deployment-toktik-user-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | sidecar.jaegertracing.io/inject: 'true'
6 | labels:
7 | app: toktik-user-api
8 | name: toktik-user-api
9 | namespace: toktik-service-bundle
10 | spec:
11 | selector:
12 | matchLabels:
13 | name: toktik-user-api
14 | template:
15 | metadata:
16 | labels:
17 | app: toktik-user-api
18 | branch: dev
19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID}
20 | name: toktik-user-api
21 | dream-app: toktik-user-api
22 | dream-unit: toktik-user-api
23 | spec:
24 | imagePullSecrets:
25 | - name: regcred
26 | containers:
27 | - image: ${IMAGE}
28 | imagePullPolicy: IfNotPresent
29 | name: toktik-user-api
30 | command:
31 | - /bin/bash
32 | args:
33 | - bootstrap-user.sh
34 | envFrom:
35 | - configMapRef:
36 | name: env-config
37 | - configMapRef:
38 | name: config
39 | ports:
40 | - name: grpc-40130
41 | containerPort: 40130
42 | protocol: TCP
43 | resources:
44 | limits:
45 | cpu: 500m
46 | memory: 256Mi
47 | requests:
48 | cpu: 100m
49 | memory: 8Mi
50 | terminationGracePeriodSeconds: 30
51 |
--------------------------------------------------------------------------------
/manifests-dev/deployment-toktik-wechat-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | annotations:
5 | sidecar.jaegertracing.io/inject: 'true'
6 | labels:
7 | app: toktik-wechat-api
8 | name: toktik-wechat-api
9 | namespace: toktik-service-bundle
10 | spec:
11 | selector:
12 | matchLabels:
13 | name: toktik-wechat-api
14 | template:
15 | metadata:
16 | labels:
17 | app: toktik-wechat-api
18 | branch: dev
19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID}
20 | name: toktik-wechat-api
21 | dream-app: toktik-wechat-api
22 | dream-unit: toktik-wechat-api
23 | spec:
24 | imagePullSecrets:
25 | - name: regcred
26 | containers:
27 | - image: ${IMAGE}
28 | imagePullPolicy: IfNotPresent
29 | name: toktik-wechat-api
30 | command:
31 | - /bin/bash
32 | args:
33 | - bootstrap-wechat.sh
34 | envFrom:
35 | - configMapRef:
36 | name: env-config
37 | - configMapRef:
38 | name: config
39 | ports:
40 | - name: grpc-40132
41 | containerPort: 40132
42 | protocol: TCP
43 | resources:
44 | limits:
45 | cpu: 500m
46 | memory: 256Mi
47 | requests:
48 | cpu: 100m
49 | memory: 8Mi
50 | terminationGracePeriodSeconds: 30
51 |
--------------------------------------------------------------------------------
/manifests-dev/service-toktik-auth-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | app: toktik-auth-api
6 | name: toktik-auth-api
7 | namespace: toktik-service-bundle
8 | spec:
9 | ports:
10 | - name: grpc
11 | port: 40127
12 | protocol: TCP
13 | targetPort: 40127
14 | selector:
15 | name: toktik-auth-api
16 | branch: dev
17 | type: ClusterIP
18 |
--------------------------------------------------------------------------------
/manifests-dev/service-toktik-comment-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | app: toktik-comment-api
6 | name: toktik-comment-api
7 | namespace: toktik-service-bundle
8 | spec:
9 | ports:
10 | - name: grpc
11 | port: 40131
12 | protocol: TCP
13 | targetPort: 40131
14 | selector:
15 | name: toktik-comment-api
16 | branch: dev
17 | type: ClusterIP
18 |
--------------------------------------------------------------------------------
/manifests-dev/service-toktik-favorite-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | app: toktik-favorite-api
6 | name: toktik-favorite-api
7 | namespace: toktik-service-bundle
8 | spec:
9 | ports:
10 | - name: grpc
11 | port: 40134
12 | protocol: TCP
13 | targetPort: 40134
14 | selector:
15 | name: toktik-favorite-api
16 | branch: dev
17 | type: ClusterIP
18 |
--------------------------------------------------------------------------------
/manifests-dev/service-toktik-feed-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | app: toktik-feed-api
6 | name: toktik-feed-api
7 | namespace: toktik-service-bundle
8 | spec:
9 | ports:
10 | - name: grpc
11 | port: 40129
12 | protocol: TCP
13 | targetPort: 40129
14 | selector:
15 | name: toktik-feed-api
16 | branch: dev
17 | type: ClusterIP
18 |
--------------------------------------------------------------------------------
/manifests-dev/service-toktik-http-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | app: toktik-http-api
6 | name: toktik-http-api
7 | namespace: toktik-service-bundle
8 | spec:
9 | ports:
10 | - name: http
11 | port: 40126
12 | protocol: TCP
13 | targetPort: 40126
14 | selector:
15 | name: toktik-http-api
16 | branch: dev
17 | type: ClusterIP
18 |
--------------------------------------------------------------------------------
/manifests-dev/service-toktik-publish-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | app: toktik-publish-api
6 | name: toktik-publish-api
7 | namespace: toktik-service-bundle
8 | spec:
9 | ports:
10 | - name: grpc
11 | port: 40128
12 | protocol: TCP
13 | targetPort: 40128
14 | selector:
15 | name: toktik-publish-api
16 | branch: dev
17 | type: ClusterIP
18 |
--------------------------------------------------------------------------------
/manifests-dev/service-toktik-relation-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | app: toktik-relation-api
6 | name: toktik-relation-api
7 | namespace: toktik-service-bundle
8 | spec:
9 | ports:
10 | - name: grpc
11 | port: 40133
12 | protocol: TCP
13 | targetPort: 40133
14 | selector:
15 | name: toktik-relation-api
16 | branch: dev
17 | type: ClusterIP
18 |
--------------------------------------------------------------------------------
/manifests-dev/service-toktik-user-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | app: toktik-user-api
6 | name: toktik-user-api
7 | namespace: toktik-service-bundle
8 | spec:
9 | ports:
10 | - name: grpc
11 | port: 40130
12 | protocol: TCP
13 | targetPort: 40130
14 | selector:
15 | name: toktik-user-api
16 | branch: dev
17 | type: ClusterIP
18 |
--------------------------------------------------------------------------------
/manifests-dev/service-toktik-wechat-api.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | labels:
5 | app: toktik-wechat-api
6 | name: toktik-wechat-api
7 | namespace: toktik-service-bundle
8 | spec:
9 | ports:
10 | - name: grpc
11 | port: 40132
12 | protocol: TCP
13 | targetPort: 40132
14 | selector:
15 | name: toktik-wechat-api
16 | branch: dev
17 | type: ClusterIP
18 |
--------------------------------------------------------------------------------
/otel.yaml:
--------------------------------------------------------------------------------
1 | receivers:
2 | otlp:
3 | protocols:
4 | grpc:
5 |
6 | exporters:
7 | prometheusremotewrite:
8 | endpoint: "http://victoriametrics:8428/api/v1/write"
9 |
10 | logging:
11 |
12 | jaeger:
13 | endpoint: jaeger-all-in-one:14250
14 | tls:
15 | insecure: true
16 |
17 | processors:
18 | batch:
19 |
20 | extensions:
21 | health_check:
22 | pprof:
23 | endpoint: :1888
24 | zpages:
25 | endpoint: :55679
26 |
27 | service:
28 | extensions: [ pprof, zpages, health_check ]
29 | pipelines:
30 | traces:
31 | receivers: [ otlp ]
32 | processors: [ batch ]
33 | exporters: [ logging, jaeger ]
34 | metrics:
35 | receivers: [ otlp ]
36 | processors: [ batch ]
37 | exporters: [ logging, prometheusremotewrite ]
38 |
--------------------------------------------------------------------------------
/repo/gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by gorm.io/gen. DO NOT EDIT.
2 | // Code generated by gorm.io/gen. DO NOT EDIT.
3 | // Code generated by gorm.io/gen. DO NOT EDIT.
4 |
5 | package repo
6 |
7 | import (
8 | "context"
9 | "database/sql"
10 |
11 | "gorm.io/gorm"
12 |
13 | "gorm.io/gen"
14 |
15 | "gorm.io/plugin/dbresolver"
16 | )
17 |
18 | var (
19 | Q = new(Query)
20 | Comment *comment
21 | Favorite *favorite
22 | Relation *relation
23 | User *user
24 | UserToken *userToken
25 | Video *video
26 | )
27 |
28 | func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
29 | *Q = *Use(db, opts...)
30 | Comment = &Q.Comment
31 | Favorite = &Q.Favorite
32 | Relation = &Q.Relation
33 | User = &Q.User
34 | UserToken = &Q.UserToken
35 | Video = &Q.Video
36 | }
37 |
38 | func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
39 | return &Query{
40 | db: db,
41 | Comment: newComment(db, opts...),
42 | Favorite: newFavorite(db, opts...),
43 | Relation: newRelation(db, opts...),
44 | User: newUser(db, opts...),
45 | UserToken: newUserToken(db, opts...),
46 | Video: newVideo(db, opts...),
47 | }
48 | }
49 |
50 | type Query struct {
51 | db *gorm.DB
52 |
53 | Comment comment
54 | Favorite favorite
55 | Relation relation
56 | User user
57 | UserToken userToken
58 | Video video
59 | }
60 |
61 | func (q *Query) Available() bool { return q.db != nil }
62 |
63 | func (q *Query) clone(db *gorm.DB) *Query {
64 | return &Query{
65 | db: db,
66 | Comment: q.Comment.clone(db),
67 | Favorite: q.Favorite.clone(db),
68 | Relation: q.Relation.clone(db),
69 | User: q.User.clone(db),
70 | UserToken: q.UserToken.clone(db),
71 | Video: q.Video.clone(db),
72 | }
73 | }
74 |
75 | func (q *Query) ReadDB() *Query {
76 | return q.ReplaceDB(q.db.Clauses(dbresolver.Read))
77 | }
78 |
79 | func (q *Query) WriteDB() *Query {
80 | return q.ReplaceDB(q.db.Clauses(dbresolver.Write))
81 | }
82 |
83 | func (q *Query) ReplaceDB(db *gorm.DB) *Query {
84 | return &Query{
85 | db: db,
86 | Comment: q.Comment.replaceDB(db),
87 | Favorite: q.Favorite.replaceDB(db),
88 | Relation: q.Relation.replaceDB(db),
89 | User: q.User.replaceDB(db),
90 | UserToken: q.UserToken.replaceDB(db),
91 | Video: q.Video.replaceDB(db),
92 | }
93 | }
94 |
95 | type queryCtx struct {
96 | Comment ICommentDo
97 | Favorite IFavoriteDo
98 | Relation IRelationDo
99 | User IUserDo
100 | UserToken IUserTokenDo
101 | Video IVideoDo
102 | }
103 |
104 | func (q *Query) WithContext(ctx context.Context) *queryCtx {
105 | return &queryCtx{
106 | Comment: q.Comment.WithContext(ctx),
107 | Favorite: q.Favorite.WithContext(ctx),
108 | Relation: q.Relation.WithContext(ctx),
109 | User: q.User.WithContext(ctx),
110 | UserToken: q.UserToken.WithContext(ctx),
111 | Video: q.Video.WithContext(ctx),
112 | }
113 | }
114 |
115 | func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error {
116 | return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...)
117 | }
118 |
119 | func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx {
120 | return &QueryTx{q.clone(q.db.Begin(opts...))}
121 | }
122 |
123 | type QueryTx struct{ *Query }
124 |
125 | func (q *QueryTx) Commit() error {
126 | return q.db.Commit().Error
127 | }
128 |
129 | func (q *QueryTx) Rollback() error {
130 | return q.db.Rollback().Error
131 | }
132 |
133 | func (q *QueryTx) SavePoint(name string) error {
134 | return q.db.SavePoint(name).Error
135 | }
136 |
137 | func (q *QueryTx) RollbackTo(name string) error {
138 | return q.db.RollbackTo(name).Error
139 | }
140 |
--------------------------------------------------------------------------------
/repo/gen/gormGen.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "toktik/constant/config"
5 | "toktik/repo/model"
6 |
7 | "gorm.io/driver/postgres"
8 | "gorm.io/gen"
9 | "gorm.io/gorm"
10 | )
11 |
12 | // Querier Dynamic SQL
13 | type Querier interface {
14 | // FilterWithNameAndRole SELECT * FROM @@table WHERE name = @name{{if role !=""}} AND role = @role{{end}}
15 | FilterWithNameAndRole(name, role string) ([]gen.T, error)
16 | }
17 |
18 | func main() {
19 | g := gen.NewGenerator(gen.Config{
20 | OutPath: "repo",
21 | Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // generate mode
22 | })
23 |
24 | gormdb, _ := gorm.Open(postgres.Open(config.EnvConfig.GetDSN()))
25 | g.UseDB(gormdb) // reuse your gorm db
26 |
27 | // Generate basic type-safe DAO API for struct `model.User` following conventions
28 | g.ApplyBasic(model.UserToken{}, model.Video{}, model.User{}, model.Comment{}, model.Relation{}, model.Favorite{})
29 |
30 | // Generate Type Safe API with Dynamic SQL defined on Querier interface
31 | g.ApplyInterface(func(Querier) {}, model.UserToken{}, model.Video{}, model.User{}, model.Comment{})
32 |
33 | // Generate the code
34 | g.Execute()
35 | }
36 |
--------------------------------------------------------------------------------
/repo/init.go:
--------------------------------------------------------------------------------
1 | package repo
2 |
3 | import (
4 | "time"
5 | "toktik/constant/config"
6 |
7 | "gorm.io/driver/postgres"
8 | "gorm.io/gorm"
9 | "gorm.io/gorm/logger"
10 | "gorm.io/plugin/opentelemetry/logging/logrus"
11 | "gorm.io/plugin/opentelemetry/tracing"
12 | )
13 |
14 | var DB *gorm.DB
15 |
16 | func init() {
17 | var err error
18 | gormlogrus := logger.New(
19 | logrus.NewWriter(),
20 | logger.Config{
21 | SlowThreshold: time.Millisecond,
22 | Colorful: false,
23 | LogLevel: logger.Info,
24 | },
25 | )
26 | DB, err = gorm.Open(
27 | postgres.Open(config.EnvConfig.GetDSN()),
28 | &gorm.Config{
29 | PrepareStmt: true,
30 | Logger: gormlogrus,
31 | },
32 | )
33 | SetDefault(DB)
34 | if err != nil {
35 | panic(err)
36 | }
37 |
38 | if err := DB.Use(tracing.NewPlugin()); err != nil {
39 | panic(err)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/repo/migrate/migrate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "toktik/constant/config"
6 | "toktik/repo/model"
7 |
8 | "gorm.io/driver/postgres"
9 | "gorm.io/gorm"
10 | )
11 |
12 | func main() {
13 | var err error
14 | db, err := gorm.Open(
15 | postgres.New(
16 | postgres.Config{
17 | DSN: config.EnvConfig.GetDSN(),
18 | }), &gorm.Config{
19 | DisableForeignKeyConstraintWhenMigrating: true,
20 | })
21 | if err != nil {
22 | panic(fmt.Errorf("db connection failed: %v", err))
23 | }
24 | err = db.AutoMigrate(&model.UserToken{}, &model.User{}, &model.Video{}, &model.Comment{}, &model.Relation{}, &model.Favorite{})
25 | if err != nil {
26 | panic(fmt.Errorf("db migrate failed: %v", err))
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/repo/model/comment.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // Comment 评论表 /*
4 | type Comment struct {
5 | Model
6 | CommentId uint32 `json:"comment_id" column:"comment_id" gorm:"not null;index:comment_video"`
7 | VideoId uint32 `json:"video_id" column:"video_id" gorm:"not null;index:comment_video"`
8 | UserId uint32 `json:"user_id" column:"user_id" gorm:"not null"`
9 | Content string `json:"content" column:"content"`
10 | }
11 |
--------------------------------------------------------------------------------
/repo/model/favorite.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // Favorite 点赞表 /*
4 | type Favorite struct {
5 | Model
6 | UserId uint32 `gorm:"not null;uniqueIndex:user_video"`
7 | VideoId uint32 `gorm:"not null;uniqueIndex:user_video;index:video"`
8 | }
9 |
--------------------------------------------------------------------------------
/repo/model/model.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "time"
5 |
6 | "gorm.io/gorm"
7 | )
8 |
9 | type Model struct {
10 | ID uint32 `gorm:"primaryKey"`
11 | CreatedAt time.Time
12 | UpdatedAt time.Time
13 | DeletedAt gorm.DeletedAt `gorm:"index"`
14 | }
15 |
--------------------------------------------------------------------------------
/repo/model/relation.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // Relation 关系表 /*
4 | type Relation struct {
5 | Model // 基础模型
6 | UserId uint32 `gorm:"not null;uniqueIndex:relation_user_target"` // 用户ID
7 | TargetId uint32 `gorm:"not null;uniqueIndex:relation_user_target"` // 目标ID
8 | }
9 |
--------------------------------------------------------------------------------
/repo/model/token.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | // UserToken 用户令牌儿表 /*
4 | type UserToken struct {
5 | Token string `gorm:"not null;primaryKey"`
6 | Username string `gorm:"not null;unique;size: 32"` // 用户名
7 | UserID uint32 `gorm:"not null;index"`
8 | Role string `gorm:"not null;default:0"` // 用户角色
9 | }
10 |
--------------------------------------------------------------------------------
/repo/model/user.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "encoding/json"
7 | "fmt"
8 | "io"
9 | "net/http"
10 | "strings"
11 | "toktik/constant/config"
12 | "toktik/logging"
13 | "toktik/rpc"
14 | )
15 |
16 | // User 用户表 /*
17 | type User struct {
18 | Model // 基础模型
19 | Username string `gorm:"not null;unique;size: 32;index"` // 用户名
20 | Password *string `gorm:"not null;size: 32"` // 密码
21 | Avatar *string // 用户头像
22 | BackgroundImage *string // 背景图片
23 | Signature *string // 个人简介
24 |
25 | updated bool
26 | }
27 |
28 | func (u *User) IsUpdated() bool {
29 | return u.updated
30 | }
31 |
32 | func (u *User) isEmail() bool {
33 | parts := strings.Split(u.Username, "@")
34 | if len(parts) != 2 {
35 | return false
36 | }
37 |
38 | userPart := parts[0]
39 | if len(userPart) == 0 {
40 | return false
41 | }
42 |
43 | domainPart := parts[1]
44 | if len(domainPart) < 3 {
45 | return false
46 | }
47 | if !strings.Contains(domainPart, ".") {
48 | return false
49 | }
50 | if strings.HasPrefix(domainPart, ".") || strings.HasSuffix(domainPart, ".") {
51 | return false
52 | }
53 |
54 | return true
55 | }
56 |
57 | func getEmailMD5(email string) (md5String string) {
58 | lowerEmail := strings.ToLower(email)
59 | hasher := md5.New()
60 | hasher.Write([]byte(lowerEmail))
61 | md5Bytes := hasher.Sum(nil)
62 | md5String = hex.EncodeToString(md5Bytes)
63 | return
64 | }
65 |
66 | type unsplashResponse []struct {
67 | Urls struct {
68 | Regular string `json:"regular"`
69 | } `json:"urls"`
70 | }
71 |
72 | func getImageFromUnsplash(query string) (url string, err error) {
73 | unsplashUrl := fmt.Sprintf("https://api.unsplash.com/photos/random?query=%s&count=1", query)
74 |
75 | resp, err := rpc.HttpRequest("GET", unsplashUrl, nil, rpc.WithAuthorizationHeader("Client-ID "+config.EnvConfig.UNSPLASH_ACCESS_KEY))
76 | if err != nil {
77 | return "", err
78 | }
79 |
80 | if resp.Body != nil {
81 | defer func(Body io.ReadCloser) {
82 | err := Body.Close()
83 | if err != nil {
84 | logging.Logger.Errorf("getImageFromUnsplash: %v", err)
85 | }
86 | }(resp.Body)
87 | }
88 | body, _ := io.ReadAll(resp.Body)
89 |
90 | var response unsplashResponse
91 | err = json.Unmarshal(body, &response)
92 | if err != nil {
93 | return "", err
94 | }
95 |
96 | url = response[0].Urls.Regular
97 |
98 | if url == "" {
99 | return "", fmt.Errorf("getImageFromUnsplash: url is empty")
100 | }
101 | return
102 | }
103 |
104 | func getCravatarUrl(email string) string {
105 | return `https://cravatar.cn/avatar/` + getEmailMD5(email) + `?d=` + "identicon"
106 | }
107 |
108 | func (u *User) GetBackgroundImage() (url string) {
109 | if u.BackgroundImage != nil && *u.BackgroundImage != "" {
110 | return *u.BackgroundImage
111 | }
112 |
113 | defer func() {
114 | u.BackgroundImage = &url
115 | u.updated = true
116 | }()
117 |
118 | unsplashURL, err := getImageFromUnsplash(u.Username)
119 | if err != nil {
120 | catURL, err := getImageFromUnsplash("cat")
121 | if err != nil {
122 | return getCravatarUrl(u.Username)
123 | }
124 | return catURL
125 | }
126 | return unsplashURL
127 | }
128 |
129 | func (u *User) GetUserAvatar() (url string) {
130 | if u.Avatar != nil && *u.Avatar != "" {
131 | return *u.Avatar
132 | }
133 |
134 | defer func() {
135 | u.Avatar = &url
136 | u.updated = true
137 | }()
138 |
139 | if u.isEmail() {
140 | return getCravatarUrl(u.Username)
141 | }
142 |
143 | unsplashURL, err := getImageFromUnsplash(u.Username)
144 | if err != nil || unsplashURL == "" {
145 | catURL, err := getImageFromUnsplash("cat")
146 | if err != nil || catURL == "" {
147 | return getCravatarUrl(u.Username)
148 | }
149 | return catURL
150 | }
151 |
152 | return unsplashURL
153 | }
154 |
155 | func (u *User) GetSignature() (signature string) {
156 | if u.Signature != nil &&
157 | *u.Signature != "" &&
158 | *u.Signature != u.Username /* For compatibility */ {
159 | return *u.Signature
160 | }
161 |
162 | defer func() {
163 | u.Signature = &signature
164 | u.updated = true
165 | }()
166 |
167 | resp, err := rpc.HttpRequest("GET", "https://v1.hitokoto.cn/?encode=text", nil)
168 | if err != nil {
169 | logging.Logger.Errorf("GetSignature: %v", err)
170 | signature = u.Username
171 | return
172 | }
173 |
174 | if resp.StatusCode != http.StatusOK {
175 | logging.Logger.Errorf("GetSignature: %v", err)
176 | signature = u.Username
177 | return
178 | }
179 |
180 | if resp.Body != nil {
181 | defer func(Body io.ReadCloser) {
182 | err := Body.Close()
183 | if err != nil {
184 | logging.Logger.Errorf("GetSignature: %v", err)
185 | }
186 | }(resp.Body)
187 | }
188 |
189 | body, err := io.ReadAll(resp.Body)
190 | if err != nil {
191 | logging.Logger.Errorf("GetSignature: %v", err)
192 | signature = u.Username
193 | return
194 | }
195 |
196 | signature = string(body)
197 |
198 | return
199 | }
200 |
--------------------------------------------------------------------------------
/repo/model/user_test.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/cloudwego/hertz/pkg/common/test/assert"
8 | )
9 |
10 | func TestUser_GetUserAvatar(t *testing.T) {
11 | exampleAvatar := "https://example.image"
12 | // should get default avatar
13 | var userHasAvatar = User{
14 | Username: "nyanki",
15 | Avatar: &exampleAvatar,
16 | }
17 | avatar := userHasAvatar.GetUserAvatar()
18 | assert.Assert(t, avatar == exampleAvatar)
19 | assert.Assert(t, !userHasAvatar.updated)
20 |
21 | // should get cravatar url when username is email
22 | var userHasEmail = User{
23 | Username: "example@email.com",
24 | }
25 | avatar = userHasEmail.GetUserAvatar()
26 | assert.Assert(t, avatar == getCravatarUrl(userHasEmail.Username))
27 | assert.Assert(t, userHasEmail.updated)
28 |
29 | // should get unsplash url when username is not email
30 | var userHasUsername = User{
31 | Username: "example",
32 | }
33 | avatar = userHasUsername.GetUserAvatar()
34 | assert.Assert(t, strings.HasPrefix(avatar, "https://"))
35 | assert.Assert(t, userHasUsername.updated)
36 | }
37 |
--------------------------------------------------------------------------------
/repo/model/video.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "time"
4 |
5 | // Video 视频表 /*
6 | type Video struct {
7 | Model
8 | UserId uint32 `json:"user_id" gorm:"not null;index"`
9 | Title string `json:"title"`
10 | FileName string `json:"play_name"`
11 | CoverName string `json:"cover_name"`
12 | CreatedAt time.Time `gorm:"index"`
13 | }
14 |
--------------------------------------------------------------------------------
/rpc/http.go:
--------------------------------------------------------------------------------
1 | package rpc
2 |
3 | import (
4 | "io"
5 | "net/http"
6 |
7 | "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
8 | )
9 |
10 | var (
11 | client http.Client
12 | )
13 |
14 | type HttpRequestOptClosure func(*http.Request)
15 |
16 | func WithAuthorizationHeader(authorization string) HttpRequestOptClosure {
17 | return func(req *http.Request) {
18 | req.Header.Add("Authorization", authorization)
19 | }
20 | }
21 |
22 | func HttpRequest(method, url string, body io.Reader, opt ...HttpRequestOptClosure) (*http.Response, error) {
23 | req, err := http.NewRequest(method, url, body)
24 | if err != nil {
25 | return nil, err
26 | }
27 |
28 | for _, item := range opt {
29 | item(req)
30 | }
31 |
32 | return client.Do(req)
33 | }
34 |
35 | func init() {
36 | client = http.Client{
37 | Transport: otelhttp.NewTransport(http.DefaultTransport),
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/service/auth/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RUN_NAME="auth"
3 |
4 | mkdir -p output/bin
5 | cp script/* output/
6 | chmod +x output/bootstrap.sh
7 |
8 | if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
9 | go build -o output/bin/${RUN_NAME}
10 | else
11 | go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
12 | fi
13 |
14 |
--------------------------------------------------------------------------------
/service/auth/handler.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "toktik/constant/biz"
6 | "toktik/kitex_gen/douyin/auth"
7 | "toktik/logging"
8 | "toktik/repo"
9 | commonModel "toktik/repo/model"
10 |
11 | "github.com/segmentio/ksuid"
12 | "github.com/sirupsen/logrus"
13 | "golang.org/x/crypto/bcrypt"
14 | )
15 |
16 | func HashPassword(password string) (string, error) {
17 | bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
18 | return string(bytes), err
19 | }
20 |
21 | func CheckPasswordHash(password, hash string) bool {
22 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
23 | return err == nil
24 | }
25 |
26 | // AuthServiceImpl implements the last service interface defined in the IDL.
27 | type AuthServiceImpl struct{}
28 |
29 | // Authenticate implements the AuthServiceImpl interface.
30 | func (s *AuthServiceImpl) Authenticate(ctx context.Context, req *auth.AuthenticateRequest) (resp *auth.AuthenticateResponse, err error) {
31 | logger := logging.Logger.WithFields(logrus.Fields{
32 | "method": "Authenticate",
33 | "token": req.Token,
34 | })
35 | logger.Debugf("Process start")
36 | if req == nil {
37 | logger.Warningf("request is nil")
38 | return &auth.AuthenticateResponse{
39 | StatusCode: biz.RequestIsNil,
40 | StatusMsg: biz.InternalServerErrorStatusMsg,
41 | }, nil
42 | }
43 | userToken := repo.Q.UserToken
44 | first, err := userToken.WithContext(ctx).Where(userToken.Token.Eq(req.Token)).First()
45 | if err != nil {
46 | logger.Warningf(biz.TokenNotFoundMessage)
47 | return &auth.AuthenticateResponse{
48 | StatusCode: biz.TokenNotFound,
49 | StatusMsg: biz.TokenNotFoundMessage,
50 | }, nil
51 | }
52 | resp = &auth.AuthenticateResponse{
53 | StatusCode: 0,
54 | StatusMsg: "success",
55 | UserId: first.UserID,
56 | }
57 | return
58 | }
59 |
60 | // Register implements the AuthServiceImpl interface.
61 | func (s *AuthServiceImpl) Register(ctx context.Context, req *auth.RegisterRequest) (resp *auth.RegisterResponse, err error) {
62 | resp = &auth.RegisterResponse{}
63 | user := repo.Q.User
64 | userToken := repo.Q.UserToken
65 | dbUser, err := user.WithContext(ctx).Where(user.Username.Eq(req.Username)).Select().Find()
66 | if err != nil {
67 | resp = &auth.RegisterResponse{
68 | StatusCode: biz.ServiceNotAvailable,
69 | StatusMsg: biz.InternalServerErrorStatusMsg,
70 | }
71 | return
72 | }
73 | if len(dbUser) > 0 {
74 | resp = &auth.RegisterResponse{
75 | StatusCode: biz.UserNameExist,
76 | StatusMsg: "用户名已存在",
77 | }
78 | return
79 | }
80 | hashedPwd, err := HashPassword(req.Password)
81 | if err != nil {
82 | resp = &auth.RegisterResponse{
83 | StatusCode: biz.ServiceNotAvailable,
84 | StatusMsg: biz.InternalServerErrorStatusMsg,
85 | }
86 | return
87 | }
88 |
89 | newUser := commonModel.User{
90 | Username: req.Username,
91 | Password: &hashedPwd,
92 | }
93 | err = user.WithContext(ctx).Save(
94 | &newUser)
95 | if err != nil {
96 | resp = &auth.RegisterResponse{
97 | StatusCode: biz.ServiceNotAvailable,
98 | StatusMsg: biz.InternalServerErrorStatusMsg,
99 | }
100 | return
101 | }
102 | token := ksuid.New().String()
103 | err = userToken.WithContext(ctx).Save(&commonModel.UserToken{
104 | Token: token,
105 | Username: newUser.Username,
106 | UserID: newUser.ID,
107 | })
108 |
109 | if err != nil {
110 | resp = &auth.RegisterResponse{
111 | StatusCode: biz.ServiceNotAvailable,
112 | StatusMsg: biz.InternalServerErrorStatusMsg,
113 | }
114 | return
115 | }
116 | resp.Token = token
117 | resp.UserId = newUser.ID
118 | resp.StatusCode = 0
119 | resp.StatusMsg = "success"
120 | return
121 | }
122 |
123 | // Login implements the AuthServiceImpl interface.
124 | func (s *AuthServiceImpl) Login(ctx context.Context, req *auth.LoginRequest) (resp *auth.LoginResponse, err error) {
125 | resp = &auth.LoginResponse{}
126 | user := repo.Q.User
127 | userToken := repo.Q.UserToken
128 | dbUser, err := user.WithContext(ctx).Where(user.Username.Eq(req.Username)).Select().Find()
129 | if err != nil {
130 | resp = &auth.LoginResponse{
131 | StatusCode: biz.ServiceNotAvailable,
132 | StatusMsg: biz.InternalServerErrorStatusMsg,
133 | }
134 | return
135 | }
136 | if len(dbUser) != 1 {
137 | resp = &auth.LoginResponse{
138 | StatusCode: biz.UserNotFound,
139 | StatusMsg: "用户不存在",
140 | }
141 | return
142 | }
143 | if !CheckPasswordHash(req.Password, *dbUser[0].Password) {
144 | resp = &auth.LoginResponse{
145 | StatusCode: biz.PasswordIncorrect,
146 | StatusMsg: "密码错误",
147 | }
148 | return
149 | }
150 |
151 | tokens, err := userToken.WithContext(ctx).Where(userToken.UserID.Eq(dbUser[0].ID)).Find()
152 | if len(tokens) == 1 {
153 | resp.Token = tokens[0].Token
154 | resp.UserId = dbUser[0].ID
155 | resp.StatusCode = 0
156 | resp.StatusMsg = "success"
157 | return
158 | }
159 | if err != nil {
160 | return nil, err
161 | }
162 | token := ksuid.New().String()
163 | err = userToken.WithContext(ctx).Save(&commonModel.UserToken{
164 | Token: token,
165 | Username: dbUser[0].Username,
166 | UserID: dbUser[0].ID,
167 | })
168 |
169 | if err != nil {
170 | resp = &auth.LoginResponse{
171 | StatusCode: biz.ServiceNotAvailable,
172 | StatusMsg: biz.InternalServerErrorStatusMsg,
173 | }
174 | return
175 | }
176 | resp.Token = token
177 | resp.UserId = dbUser[0].ID
178 | resp.StatusCode = 0
179 | resp.StatusMsg = "success"
180 | return
181 | }
182 |
--------------------------------------------------------------------------------
/service/auth/handler_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "reflect"
7 | "regexp"
8 | "testing"
9 | "toktik/kitex_gen/douyin/auth"
10 | "toktik/repo"
11 |
12 | "github.com/DATA-DOG/go-sqlmock"
13 | "gorm.io/driver/postgres"
14 | "gorm.io/gorm"
15 | )
16 |
17 | var successArg = struct {
18 | ctx context.Context
19 | req *auth.AuthenticateRequest
20 | }{ctx: context.Background(), req: &auth.AuthenticateRequest{Token: "authenticated-token"}}
21 |
22 | var successResp = &auth.AuthenticateResponse{
23 | StatusCode: 0,
24 | StatusMsg: "success",
25 | UserId: 114514,
26 | }
27 |
28 | func TestAuthServiceImpl_Authenticate(t *testing.T) {
29 | mock, db := NewDBMock(t)
30 | defer db.Close()
31 | mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user_tokens" WHERE "user_tokens"."token" = $1 ORDER BY "user_tokens"."token" LIMIT 1`)).
32 | WithArgs(sqlmock.AnyArg()).
33 | WillReturnRows(sqlmock.NewRows([]string{"id", "user_id", "token", "created_at", "updated_at"}).
34 | AddRow(1, 114514, "authenticated-token", "2021-01-01 00:00:00", "2021-01-01 00:00:00"))
35 |
36 | type args struct {
37 | ctx context.Context
38 | req *auth.AuthenticateRequest
39 | }
40 | tests := []struct {
41 | name string
42 | args args
43 | wantResp *auth.AuthenticateResponse
44 | wantErr bool
45 | }{
46 | {name: "should authenticate success", args: successArg, wantResp: successResp},
47 | }
48 | for _, tt := range tests {
49 | t.Run(tt.name, func(t *testing.T) {
50 | s := &AuthServiceImpl{}
51 | gotResp, err := s.Authenticate(tt.args.ctx, tt.args.req)
52 | if (err != nil) != tt.wantErr {
53 | t.Errorf("Authenticate() error = %v, wantErr %v", err, tt.wantErr)
54 | return
55 | }
56 | if !reflect.DeepEqual(gotResp, tt.wantResp) {
57 | t.Errorf("Authenticate() gotResp = %v, want %v", gotResp, tt.wantResp)
58 | }
59 | })
60 | }
61 | }
62 |
63 | func NewDBMock(t *testing.T) (sqlmock.Sqlmock, *sql.DB) {
64 | db, mock, err := sqlmock.New()
65 | DB, err := gorm.Open(postgres.New(postgres.Config{
66 | DSN: "sqlmock_db_0",
67 | DriverName: "postgres",
68 | Conn: db,
69 | PreferSimpleProtocol: true,
70 | }), &gorm.Config{})
71 | if err != nil {
72 | t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
73 | }
74 | repo.SetDefault(DB)
75 | return mock, db
76 | }
77 |
--------------------------------------------------------------------------------
/service/auth/kitex_info.yaml:
--------------------------------------------------------------------------------
1 | kitexinfo:
2 | ServiceName: 'auth'
3 | ToolVersion: 'v0.4.4'
4 |
5 |
--------------------------------------------------------------------------------
/service/auth/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "toktik/constant/config"
7 | auth "toktik/kitex_gen/douyin/auth/authservice"
8 |
9 | "github.com/cloudwego/kitex/pkg/rpcinfo"
10 | "github.com/cloudwego/kitex/server"
11 | "github.com/kitex-contrib/obs-opentelemetry/provider"
12 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
13 | consul "github.com/kitex-contrib/registry-consul"
14 | )
15 |
16 | func main() {
17 | var err error
18 |
19 | r, err := consul.NewConsulRegister(config.EnvConfig.CONSUL_ADDR)
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 |
24 | addr, err := net.ResolveTCPAddr("tcp", config.AuthServiceAddr)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | provider.NewOpenTelemetryProvider(
30 | provider.WithServiceName(config.AuthServiceName),
31 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
32 | provider.WithInsecure(),
33 | )
34 |
35 | srv := auth.NewServer(
36 | new(AuthServiceImpl),
37 | server.WithServiceAddr(addr),
38 | server.WithRegistry(r),
39 | server.WithSuite(tracing.NewServerSuite()),
40 | server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
41 | ServiceName: config.AuthServiceName,
42 | }),
43 | )
44 | err = srv.Run()
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/service/auth/script/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 | CURDIR=$(cd $(dirname $0); pwd)
3 |
4 | if [ "X$1" != "X" ]; then
5 | RUNTIME_ROOT=$1
6 | else
7 | RUNTIME_ROOT=${CURDIR}
8 | fi
9 |
10 | export KITEX_RUNTIME_ROOT=$RUNTIME_ROOT
11 | export KITEX_LOG_DIR="$RUNTIME_ROOT/log"
12 |
13 | if [ ! -d "$KITEX_LOG_DIR/app" ]; then
14 | mkdir -p "$KITEX_LOG_DIR/app"
15 | fi
16 |
17 | if [ ! -d "$KITEX_LOG_DIR/rpc" ]; then
18 | mkdir -p "$KITEX_LOG_DIR/rpc"
19 | fi
20 |
21 | exec "$CURDIR/bin/auth"
22 |
23 |
--------------------------------------------------------------------------------
/service/comment/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RUN_NAME="comment"
3 |
4 | mkdir -p output/bin
5 | cp script/* output/
6 | chmod +x output/bootstrap.sh
7 |
8 | if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
9 | go build -o output/bin/${RUN_NAME}
10 | else
11 | go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
12 | fi
13 |
14 |
--------------------------------------------------------------------------------
/service/comment/kitex_info.yaml:
--------------------------------------------------------------------------------
1 | kitexinfo:
2 | ServiceName: 'comment'
3 | ToolVersion: 'v0.4.4'
4 |
5 |
--------------------------------------------------------------------------------
/service/comment/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "toktik/constant/config"
7 | comment "toktik/kitex_gen/douyin/comment/commentservice"
8 |
9 | "github.com/cloudwego/kitex/pkg/rpcinfo"
10 | "github.com/cloudwego/kitex/server"
11 | "github.com/kitex-contrib/obs-opentelemetry/provider"
12 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
13 | consul "github.com/kitex-contrib/registry-consul"
14 | )
15 |
16 | func main() {
17 | var err error
18 |
19 | r, err := consul.NewConsulRegister(config.EnvConfig.CONSUL_ADDR)
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 |
24 | addr, err := net.ResolveTCPAddr("tcp", config.CommentServiceAddr)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | provider.NewOpenTelemetryProvider(
30 | provider.WithServiceName(config.CommentServiceName),
31 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
32 | provider.WithInsecure(),
33 | )
34 |
35 | srv := comment.NewServer(
36 | new(CommentServiceImpl),
37 | server.WithServiceAddr(addr),
38 | server.WithRegistry(r),
39 | server.WithSuite(tracing.NewServerSuite()),
40 | server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
41 | ServiceName: config.CommentServiceName,
42 | }),
43 | )
44 |
45 | err = srv.Run()
46 |
47 | if err != nil {
48 | log.Fatal(err)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/service/comment/script/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 | CURDIR=$(cd $(dirname $0); pwd)
3 |
4 | if [ "X$1" != "X" ]; then
5 | RUNTIME_ROOT=$1
6 | else
7 | RUNTIME_ROOT=${CURDIR}
8 | fi
9 |
10 | export KITEX_RUNTIME_ROOT=$RUNTIME_ROOT
11 | export KITEX_LOG_DIR="$RUNTIME_ROOT/log"
12 |
13 | if [ ! -d "$KITEX_LOG_DIR/app" ]; then
14 | mkdir -p "$KITEX_LOG_DIR/app"
15 | fi
16 |
17 | if [ ! -d "$KITEX_LOG_DIR/rpc" ]; then
18 | mkdir -p "$KITEX_LOG_DIR/rpc"
19 | fi
20 |
21 | exec "$CURDIR/bin/comment"
22 |
23 |
--------------------------------------------------------------------------------
/service/favorite/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RUN_NAME="favorite"
3 |
4 | mkdir -p output/bin
5 | cp script/* output/
6 | chmod +x output/bootstrap.sh
7 |
8 | if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
9 | go build -o output/bin/${RUN_NAME}
10 | else
11 | go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
12 | fi
13 |
14 |
--------------------------------------------------------------------------------
/service/favorite/kitex_info.yaml:
--------------------------------------------------------------------------------
1 | kitexinfo:
2 | ServiceName: 'favorite'
3 | ToolVersion: 'v0.4.4'
4 |
5 |
--------------------------------------------------------------------------------
/service/favorite/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "toktik/constant/config"
7 | favoriteService "toktik/kitex_gen/douyin/favorite/favoriteservice"
8 |
9 | "github.com/cloudwego/kitex/pkg/rpcinfo"
10 | "github.com/cloudwego/kitex/server"
11 | "github.com/kitex-contrib/obs-opentelemetry/provider"
12 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
13 | consul "github.com/kitex-contrib/registry-consul"
14 | )
15 |
16 | func main() {
17 | var err error
18 |
19 | r, err := consul.NewConsulRegister(config.EnvConfig.CONSUL_ADDR)
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 |
24 | addr, err := net.ResolveTCPAddr("tcp", config.FavoriteServiceAddr)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | provider.NewOpenTelemetryProvider(
30 | provider.WithServiceName(config.FavoriteServiceName),
31 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
32 | provider.WithInsecure(),
33 | )
34 |
35 | srv := favoriteService.NewServer(
36 | new(FavoriteServiceImpl),
37 | server.WithServiceAddr(addr),
38 | server.WithRegistry(r),
39 | server.WithSuite(tracing.NewServerSuite()),
40 | server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
41 | ServiceName: config.FavoriteServiceName,
42 | }),
43 | )
44 | err = srv.Run()
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/service/favorite/script/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 | CURDIR=$(cd $(dirname $0); pwd)
3 |
4 | if [ "X$1" != "X" ]; then
5 | RUNTIME_ROOT=$1
6 | else
7 | RUNTIME_ROOT=${CURDIR}
8 | fi
9 |
10 | export KITEX_RUNTIME_ROOT=$RUNTIME_ROOT
11 | export KITEX_LOG_DIR="$RUNTIME_ROOT/log"
12 |
13 | if [ ! -d "$KITEX_LOG_DIR/app" ]; then
14 | mkdir -p "$KITEX_LOG_DIR/app"
15 | fi
16 |
17 | if [ ! -d "$KITEX_LOG_DIR/rpc" ]; then
18 | mkdir -p "$KITEX_LOG_DIR/rpc"
19 | fi
20 |
21 | exec "$CURDIR/bin/favorite"
22 |
23 |
--------------------------------------------------------------------------------
/service/feed/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RUN_NAME="feed"
3 |
4 | mkdir -p output/bin
5 | cp script/* output/
6 | chmod +x output/bootstrap.sh
7 |
8 | if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
9 | go build -o output/bin/${RUN_NAME}
10 | else
11 | go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
12 | fi
13 |
14 |
--------------------------------------------------------------------------------
/service/feed/kitex_info.yaml:
--------------------------------------------------------------------------------
1 | kitexinfo:
2 | ServiceName: 'feed'
3 | ToolVersion: 'v0.4.4'
4 |
5 |
--------------------------------------------------------------------------------
/service/feed/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "toktik/constant/config"
7 | feed "toktik/kitex_gen/douyin/feed/feedservice"
8 |
9 | "github.com/cloudwego/kitex/pkg/rpcinfo"
10 | "github.com/cloudwego/kitex/server"
11 | "github.com/kitex-contrib/obs-opentelemetry/provider"
12 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
13 | consul "github.com/kitex-contrib/registry-consul"
14 | )
15 |
16 | func main() {
17 | var err error
18 |
19 | r, err := consul.NewConsulRegister(config.EnvConfig.CONSUL_ADDR)
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 |
24 | addr, err := net.ResolveTCPAddr("tcp", config.FeedServiceAddr)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | provider.NewOpenTelemetryProvider(
30 | provider.WithServiceName(config.FeedServiceName),
31 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
32 | provider.WithInsecure(),
33 | )
34 |
35 | srv := feed.NewServer(
36 | new(FeedServiceImpl),
37 | server.WithServiceAddr(addr),
38 | server.WithRegistry(r),
39 | server.WithSuite(tracing.NewServerSuite()),
40 | server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
41 | ServiceName: config.FeedServiceName,
42 | }),
43 | )
44 |
45 | err = srv.Run()
46 |
47 | if err != nil {
48 | log.Fatal(err)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/service/feed/script/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 | CURDIR=$(cd $(dirname $0); pwd)
3 |
4 | if [ "X$1" != "X" ]; then
5 | RUNTIME_ROOT=$1
6 | else
7 | RUNTIME_ROOT=${CURDIR}
8 | fi
9 |
10 | export KITEX_RUNTIME_ROOT=$RUNTIME_ROOT
11 | export KITEX_LOG_DIR="$RUNTIME_ROOT/log"
12 |
13 | if [ ! -d "$KITEX_LOG_DIR/app" ]; then
14 | mkdir -p "$KITEX_LOG_DIR/app"
15 | fi
16 |
17 | if [ ! -d "$KITEX_LOG_DIR/rpc" ]; then
18 | mkdir -p "$KITEX_LOG_DIR/rpc"
19 | fi
20 |
21 | exec "$CURDIR/bin/feed"
22 |
23 |
--------------------------------------------------------------------------------
/service/publish/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RUN_NAME="publish"
3 |
4 | mkdir -p output/bin
5 | cp script/* output/
6 | chmod +x output/bootstrap.sh
7 |
8 | if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
9 | go build -o output/bin/${RUN_NAME}
10 | else
11 | go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
12 | fi
13 |
14 |
--------------------------------------------------------------------------------
/service/publish/kitex_info.yaml:
--------------------------------------------------------------------------------
1 | kitexinfo:
2 | ServiceName: 'publish'
3 | ToolVersion: 'v0.4.4'
4 |
5 |
--------------------------------------------------------------------------------
/service/publish/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "toktik/constant/config"
7 | publish "toktik/kitex_gen/douyin/publish/publishservice"
8 |
9 | "github.com/kitex-contrib/obs-opentelemetry/provider"
10 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
11 |
12 | "github.com/cloudwego/kitex/server"
13 |
14 | "github.com/cloudwego/kitex/pkg/rpcinfo"
15 | consul "github.com/kitex-contrib/registry-consul"
16 | )
17 |
18 | func main() {
19 | r, err := consul.NewConsulRegister(config.EnvConfig.CONSUL_ADDR)
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 |
24 | addr, err := net.ResolveTCPAddr("tcp", config.PublishServiceAddr)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | provider.NewOpenTelemetryProvider(
30 | provider.WithServiceName(config.PublishServiceName),
31 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
32 | provider.WithInsecure(),
33 | )
34 |
35 | srv := publish.NewServer(
36 | new(PublishServiceImpl),
37 | server.WithServiceAddr(addr),
38 | server.WithRegistry(r),
39 | server.WithSuite(tracing.NewServerSuite()),
40 | server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
41 | ServiceName: config.PublishServiceName,
42 | }),
43 | )
44 | err = srv.Run()
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/service/publish/resources/bear.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Toktik-Team/toktik/fd1c18ea896125d0a71b45d546a4eed5094d15d0/service/publish/resources/bear.mp4
--------------------------------------------------------------------------------
/service/publish/script/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 | CURDIR=$(cd $(dirname $0); pwd)
3 |
4 | if [ "X$1" != "X" ]; then
5 | RUNTIME_ROOT=$1
6 | else
7 | RUNTIME_ROOT=${CURDIR}
8 | fi
9 |
10 | export KITEX_RUNTIME_ROOT=$RUNTIME_ROOT
11 | export KITEX_LOG_DIR="$RUNTIME_ROOT/log"
12 |
13 | if [ ! -d "$KITEX_LOG_DIR/app" ]; then
14 | mkdir -p "$KITEX_LOG_DIR/app"
15 | fi
16 |
17 | if [ ! -d "$KITEX_LOG_DIR/rpc" ]; then
18 | mkdir -p "$KITEX_LOG_DIR/rpc"
19 | fi
20 |
21 | exec "$CURDIR/bin/publish"
22 |
23 |
--------------------------------------------------------------------------------
/service/relation/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RUN_NAME="relation"
3 |
4 | mkdir -p output/bin
5 | cp script/* output/
6 | chmod +x output/bootstrap.sh
7 |
8 | if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
9 | go build -o output/bin/${RUN_NAME}
10 | else
11 | go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
12 | fi
13 |
14 |
--------------------------------------------------------------------------------
/service/relation/kitex_info.yaml:
--------------------------------------------------------------------------------
1 | kitexinfo:
2 | ServiceName: 'relation'
3 | ToolVersion: 'v0.4.4'
4 |
5 |
--------------------------------------------------------------------------------
/service/relation/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "toktik/constant/config"
7 | relation "toktik/kitex_gen/douyin/relation/relationservice"
8 |
9 | "github.com/kitex-contrib/obs-opentelemetry/provider"
10 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
11 |
12 | "github.com/cloudwego/kitex/server"
13 |
14 | "github.com/cloudwego/kitex/pkg/rpcinfo"
15 | consul "github.com/kitex-contrib/registry-consul"
16 | )
17 |
18 | func main() {
19 | r, err := consul.NewConsulRegister(config.EnvConfig.CONSUL_ADDR)
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 |
24 | addr, err := net.ResolveTCPAddr("tcp", config.RelationServiceAddr)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | provider.NewOpenTelemetryProvider(
30 | provider.WithServiceName(config.RelationServiceName),
31 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
32 | provider.WithInsecure(),
33 | )
34 |
35 | srv := relation.NewServer(
36 | new(RelationServiceImpl),
37 | server.WithServiceAddr(addr),
38 | server.WithRegistry(r),
39 | server.WithSuite(tracing.NewServerSuite()),
40 | server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
41 | ServiceName: config.RelationServiceName,
42 | }),
43 | )
44 | err = srv.Run()
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/service/relation/script/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 | CURDIR=$(cd $(dirname $0); pwd)
3 |
4 | if [ "X$1" != "X" ]; then
5 | RUNTIME_ROOT=$1
6 | else
7 | RUNTIME_ROOT=${CURDIR}
8 | fi
9 |
10 | export KITEX_RUNTIME_ROOT=$RUNTIME_ROOT
11 | export KITEX_LOG_DIR="$RUNTIME_ROOT/log"
12 |
13 | if [ ! -d "$KITEX_LOG_DIR/app" ]; then
14 | mkdir -p "$KITEX_LOG_DIR/app"
15 | fi
16 |
17 | if [ ! -d "$KITEX_LOG_DIR/rpc" ]; then
18 | mkdir -p "$KITEX_LOG_DIR/rpc"
19 | fi
20 |
21 | exec "$CURDIR/bin/relation"
22 |
23 |
--------------------------------------------------------------------------------
/service/user/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RUN_NAME="user"
3 |
4 | mkdir -p output/bin
5 | cp script/* output/
6 | chmod +x output/bootstrap.sh
7 |
8 | if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
9 | go build -o output/bin/${RUN_NAME}
10 | else
11 | go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
12 | fi
13 |
14 |
--------------------------------------------------------------------------------
/service/user/kitex_info.yaml:
--------------------------------------------------------------------------------
1 | kitexinfo:
2 | ServiceName: 'user'
3 | ToolVersion: 'v0.4.4'
4 |
5 |
--------------------------------------------------------------------------------
/service/user/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "toktik/constant/config"
7 | "toktik/kitex_gen/douyin/user/userservice"
8 |
9 | "github.com/cloudwego/kitex/pkg/rpcinfo"
10 | "github.com/cloudwego/kitex/server"
11 | "github.com/kitex-contrib/obs-opentelemetry/provider"
12 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
13 | consul "github.com/kitex-contrib/registry-consul"
14 | )
15 |
16 | func main() {
17 | var err error
18 |
19 | r, err := consul.NewConsulRegister(config.EnvConfig.CONSUL_ADDR)
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 |
24 | addr, err := net.ResolveTCPAddr("tcp", config.UserServiceAddr)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | provider.NewOpenTelemetryProvider(
30 | provider.WithServiceName(config.UserServiceName),
31 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
32 | provider.WithInsecure(),
33 | )
34 |
35 | srv := userservice.NewServer(
36 | new(UserServiceImpl),
37 | server.WithServiceAddr(addr),
38 | server.WithRegistry(r),
39 | server.WithSuite(tracing.NewServerSuite()),
40 | server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
41 | ServiceName: config.UserServiceName,
42 | }),
43 | )
44 | err = srv.Run()
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/service/user/script/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 | CURDIR=$(cd $(dirname $0); pwd)
3 |
4 | if [ "X$1" != "X" ]; then
5 | RUNTIME_ROOT=$1
6 | else
7 | RUNTIME_ROOT=${CURDIR}
8 | fi
9 |
10 | export KITEX_RUNTIME_ROOT=$RUNTIME_ROOT
11 | export KITEX_LOG_DIR="$RUNTIME_ROOT/log"
12 |
13 | if [ ! -d "$KITEX_LOG_DIR/app" ]; then
14 | mkdir -p "$KITEX_LOG_DIR/app"
15 | fi
16 |
17 | if [ ! -d "$KITEX_LOG_DIR/rpc" ]; then
18 | mkdir -p "$KITEX_LOG_DIR/rpc"
19 | fi
20 |
21 | exec "$CURDIR/bin/user"
22 |
23 |
--------------------------------------------------------------------------------
/service/web/auth/handler.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "context"
5 | "log"
6 | bizConstant "toktik/constant/biz"
7 | "toktik/constant/config"
8 | "toktik/kitex_gen/douyin/auth"
9 | authService "toktik/kitex_gen/douyin/auth/authservice"
10 | "toktik/logging"
11 |
12 | "github.com/kitex-contrib/obs-opentelemetry/provider"
13 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
14 |
15 | "github.com/cloudwego/hertz/pkg/app"
16 | httpStatus "github.com/cloudwego/hertz/pkg/protocol/consts"
17 | "github.com/cloudwego/kitex/client"
18 | consul "github.com/kitex-contrib/registry-consul"
19 | "github.com/sirupsen/logrus"
20 | )
21 |
22 | var Client authService.Client
23 |
24 | func init() {
25 | r, err := consul.NewConsulResolver(config.EnvConfig.CONSUL_ADDR)
26 | if err != nil {
27 | log.Fatal(err)
28 | }
29 | provider.NewOpenTelemetryProvider(
30 | provider.WithServiceName(config.AuthServiceName),
31 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
32 | provider.WithInsecure(),
33 | )
34 | Client, err = authService.NewClient(
35 | config.AuthServiceName,
36 | client.WithResolver(r),
37 | client.WithSuite(tracing.NewClientSuite()))
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 | }
42 |
43 | func Register(ctx context.Context, c *app.RequestContext) {
44 | methodFields := logrus.Fields{
45 | "method": "Register",
46 | }
47 | logger := logging.Logger.WithFields(methodFields)
48 | logger.Debugf("Process start")
49 |
50 | username, usernameExist := c.GetQuery("username")
51 | password, passwordExist := c.GetQuery("password")
52 | if !usernameExist || !passwordExist {
53 | bizConstant.NoUserNameOrPassWord.WithFields(&methodFields).LaunchError(c)
54 | return
55 | }
56 | logger.WithFields(logrus.Fields{
57 | "username": username,
58 | "password": password,
59 | }).Debugf("Executing register")
60 | registerResponse, err := Client.Register(ctx, &auth.RegisterRequest{
61 | Username: username,
62 | Password: password,
63 | })
64 | if err != nil {
65 | bizConstant.RPCCallError.WithCause(err).WithFields(&methodFields).LaunchError(c)
66 | return
67 | }
68 | logger.WithFields(logrus.Fields{
69 | "response": registerResponse,
70 | }).Debugf("Register success")
71 | c.JSON(httpStatus.StatusOK, registerResponse)
72 | }
73 |
74 | func Login(ctx context.Context, c *app.RequestContext) {
75 | methodFields := logrus.Fields{
76 | "method": "Login",
77 | }
78 | logger := logging.Logger.WithFields(methodFields)
79 | logger.Debugf("Process start")
80 |
81 | username, usernameExist := c.GetQuery("username")
82 | password, passwordExist := c.GetQuery("password")
83 | if !usernameExist || !passwordExist {
84 | bizConstant.NoUserNameOrPassWord.WithFields(&methodFields).LaunchError(c)
85 | return
86 | }
87 | logger.WithFields(logrus.Fields{
88 | "username": username,
89 | "password": password,
90 | }).Debugf("Executing login")
91 | loginResponse, err := Client.Login(ctx, &auth.LoginRequest{
92 | Username: username,
93 | Password: password,
94 | })
95 | if err != nil {
96 | bizConstant.RPCCallError.WithCause(err).WithFields(&methodFields).LaunchError(c)
97 | return
98 | }
99 | logger.WithFields(logrus.Fields{
100 | "response": loginResponse,
101 | }).Debugf("Login success")
102 | c.JSON(httpStatus.StatusOK, loginResponse)
103 | }
104 |
--------------------------------------------------------------------------------
/service/web/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RUN_NAME="web"
3 |
4 | mkdir -p output/bin
5 |
6 | if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
7 | go build -o output/bin/${RUN_NAME}
8 | else
9 | go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
10 | fi
11 |
--------------------------------------------------------------------------------
/service/web/comment/handler.go:
--------------------------------------------------------------------------------
1 | package comment
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "log"
8 | "strconv"
9 | "toktik/constant/biz"
10 | "toktik/constant/config"
11 | "toktik/kitex_gen/douyin/comment"
12 | "toktik/kitex_gen/douyin/comment/commentservice"
13 | "toktik/logging"
14 | "toktik/service/web/mw"
15 |
16 | "github.com/kitex-contrib/obs-opentelemetry/provider"
17 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
18 |
19 | "github.com/cloudwego/hertz/pkg/app"
20 | "github.com/cloudwego/hertz/pkg/protocol/consts"
21 | "github.com/cloudwego/kitex/client"
22 | consul "github.com/kitex-contrib/registry-consul"
23 | "github.com/sirupsen/logrus"
24 | )
25 |
26 | var commentClient commentservice.Client
27 |
28 | func init() {
29 | r, err := consul.NewConsulResolver(config.EnvConfig.CONSUL_ADDR)
30 | if err != nil {
31 | log.Fatal(err)
32 | }
33 | provider.NewOpenTelemetryProvider(
34 | provider.WithServiceName(config.CommentServiceName),
35 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
36 | provider.WithInsecure(),
37 | )
38 | commentClient, err = commentservice.NewClient(
39 | config.CommentServiceName,
40 | client.WithResolver(r),
41 | client.WithSuite(tracing.NewClientSuite()),
42 | )
43 | if err != nil {
44 | log.Fatal(err)
45 | }
46 | }
47 |
48 | func Action(ctx context.Context, c *app.RequestContext) {
49 | methodFields := logrus.Fields{
50 | "method": "CommentAction",
51 | }
52 | logger := logging.Logger.WithFields(methodFields)
53 | logger.Debugf("Process start")
54 |
55 | actorIdPtr, ok := mw.Auth(c, mw.WithAuthRequired())
56 | actorId := *actorIdPtr
57 | if !ok {
58 | return
59 | }
60 |
61 | videoId, videoIdExists := c.GetQuery("video_id")
62 | actionType, actionTypeExists := c.GetQuery("action_type")
63 | commentText, commentTextExists := c.GetQuery("comment_text")
64 | commentId, commentIdExists := c.GetQuery("comment_id")
65 |
66 | if !videoIdExists || !actionTypeExists {
67 | biz.BadRequestError.WithFields(&methodFields).LaunchError(c)
68 | return
69 | }
70 |
71 | pVideoId, err := strconv.ParseUint(videoId, 10, 32)
72 | pActionType, err := strconv.ParseInt(actionType, 10, 32)
73 |
74 | if err != nil {
75 | biz.BadRequestError.WithFields(&methodFields).WithCause(err).LaunchError(c)
76 | return
77 | }
78 |
79 | var rActionType = comment.ActionCommentType(pActionType)
80 |
81 | var (
82 | rErr error
83 | )
84 |
85 | switch rActionType {
86 | case comment.ActionCommentType_ACTION_COMMENT_TYPE_ADD:
87 | if !commentTextExists {
88 | rErr = errors.New("comment text is required")
89 | break
90 | }
91 | resp, err := commentClient.ActionComment(ctx, &comment.ActionCommentRequest{
92 | ActorId: actorId,
93 | VideoId: uint32(pVideoId),
94 | ActionType: rActionType,
95 | Action: &comment.ActionCommentRequest_CommentText{CommentText: commentText},
96 | })
97 | if err != nil {
98 | rErr = err
99 | break
100 | }
101 | logger.WithFields(logrus.Fields{
102 | "response": resp,
103 | }).Debugf("add comment success")
104 | c.JSON(
105 | consts.StatusOK,
106 | resp,
107 | )
108 | return
109 | case comment.ActionCommentType_ACTION_COMMENT_TYPE_DELETE:
110 | if !commentIdExists {
111 | rErr = errors.New("comment id is required")
112 | break
113 | }
114 | pCommentId, err := strconv.ParseUint(commentId, 10, 32)
115 | if err != nil {
116 | rErr = err
117 | break
118 | }
119 | resp, err := commentClient.ActionComment(ctx, &comment.ActionCommentRequest{
120 | ActorId: actorId,
121 | VideoId: uint32(pVideoId),
122 | ActionType: rActionType,
123 | Action: &comment.ActionCommentRequest_CommentId{CommentId: uint32(pCommentId)},
124 | })
125 | if err != nil {
126 | rErr = err
127 | break
128 | }
129 | logger.WithFields(logrus.Fields{
130 | "response": resp,
131 | }).Debugf("delete comment success")
132 | c.JSON(
133 | consts.StatusOK,
134 | resp,
135 | )
136 | return
137 | case comment.ActionCommentType_ACTION_COMMENT_TYPE_UNSPECIFIED:
138 | fallthrough
139 | default:
140 | rErr = errors.New(fmt.Sprintf("invalid action type: %d", rActionType))
141 | }
142 |
143 | if rErr != nil {
144 | biz.InternalServerError.WithCause(rErr).WithFields(&methodFields).LaunchError(c)
145 | return
146 | }
147 | }
148 |
149 | func List(ctx context.Context, c *app.RequestContext) {
150 | methodFields := logrus.Fields{
151 | "method": "CommentList",
152 | }
153 | logger := logging.Logger.WithFields(methodFields)
154 | logger.Debugf("Process start")
155 |
156 | actorIdPtr, ok := mw.Auth(c)
157 | actorId := *actorIdPtr
158 | if !ok {
159 | return
160 | }
161 |
162 | videoId, videoIdExists := c.GetQuery("video_id")
163 |
164 | if !videoIdExists {
165 | biz.BadRequestError.WithFields(&methodFields).LaunchError(c)
166 | return
167 | }
168 |
169 | pVideoId, err := strconv.ParseUint(videoId, 10, 32)
170 |
171 | if err != nil {
172 | biz.BadRequestError.WithFields(&methodFields).WithCause(err).LaunchError(c)
173 | return
174 | }
175 |
176 | resp, err := commentClient.ListComment(ctx, &comment.ListCommentRequest{
177 | ActorId: actorId,
178 | VideoId: uint32(pVideoId),
179 | })
180 |
181 | if err != nil {
182 | biz.InternalServerError.WithCause(err).WithFields(&methodFields).LaunchError(c)
183 | return
184 | }
185 |
186 | c.JSON(
187 | consts.StatusOK,
188 | resp,
189 | )
190 | }
191 |
--------------------------------------------------------------------------------
/service/web/favorite/handler.go:
--------------------------------------------------------------------------------
1 | package favorite
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | "strconv"
7 | "toktik/constant/biz"
8 | "toktik/constant/config"
9 | "toktik/kitex_gen/douyin/favorite"
10 | favoriteService "toktik/kitex_gen/douyin/favorite/favoriteservice"
11 | "toktik/logging"
12 | "toktik/service/web/mw"
13 |
14 | "github.com/cloudwego/hertz/pkg/app"
15 | "github.com/cloudwego/hertz/pkg/protocol/consts"
16 | "github.com/cloudwego/kitex/client"
17 | "github.com/kitex-contrib/obs-opentelemetry/provider"
18 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
19 | consul "github.com/kitex-contrib/registry-consul"
20 | "github.com/sirupsen/logrus"
21 | )
22 |
23 | var Client favoriteService.Client
24 |
25 | func init() {
26 | r, err := consul.NewConsulResolver(config.EnvConfig.CONSUL_ADDR)
27 | if err != nil {
28 | panic(err)
29 | }
30 | provider.NewOpenTelemetryProvider(
31 | provider.WithServiceName(config.FavoriteServiceName),
32 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
33 | provider.WithInsecure(),
34 | )
35 | Client, err = favoriteService.NewClient(
36 | config.FavoriteServiceName,
37 | client.WithResolver(r),
38 | client.WithSuite(tracing.NewClientSuite()),
39 | )
40 | if err != nil {
41 | panic(err)
42 | }
43 | }
44 |
45 | var logger = logging.Logger
46 |
47 | // 用于解析 Action 函数所需参数
48 | func parseParameters(c *app.RequestContext) (videoId uint32, actionType uint32, isEnd bool) {
49 | field := logrus.Fields{
50 | "method": "parseParameters",
51 | }
52 |
53 | isEnd = true
54 | // 获取参数
55 | qVideoId, videoIdExist := c.GetQuery("video_id")
56 | qActionType, actionTypeExist := c.GetQuery("action_type")
57 | if !videoIdExist || !actionTypeExist {
58 | biz.BadRequestError.
59 | WithFields(&field).
60 | LaunchError(c)
61 | return
62 | }
63 | // 解析 videoId
64 | temp, err := strconv.ParseUint(qVideoId, 10, 32)
65 | if err != nil {
66 | biz.BadRequestError.
67 | WithCause(err).
68 | WithFields(&field).
69 | LaunchError(c)
70 | return
71 | }
72 | videoId = uint32(temp)
73 | // 解析 actionType
74 | temp, err = strconv.ParseUint(qActionType, 10, 32)
75 | if err != nil {
76 | biz.BadRequestError.
77 | WithCause(err).
78 | WithFields(&field).
79 | LaunchError(c)
80 | return
81 | }
82 | actionType = uint32(temp)
83 | if actionType != 1 && actionType != 2 {
84 | biz.BadRequestError.
85 | WithFields(&field).
86 | LaunchError(c)
87 | return
88 | }
89 |
90 | isEnd = false
91 | return
92 | }
93 |
94 | // Action 处理点赞和取消点赞
95 | func Action(ctx context.Context, c *app.RequestContext) {
96 | field := logrus.Fields{
97 | "method": "Action",
98 | }
99 | logger.WithFields(field).Debugf("Process start")
100 |
101 | actorIdPtr, ok := mw.Auth(c, mw.WithAuthRequired())
102 | actorId := *actorIdPtr
103 | if !ok {
104 | return
105 | }
106 |
107 | videoId, actionType, isEnd := parseParameters(c)
108 | if isEnd {
109 | return
110 | }
111 |
112 | response, err := Client.FavoriteAction(ctx, &favorite.FavoriteRequest{
113 | ActorId: actorId,
114 | VideoId: videoId,
115 | ActionType: actionType,
116 | })
117 |
118 | if err != nil {
119 | biz.RPCCallError.WithCause(err).WithFields(&field).LaunchError(c)
120 | return
121 | }
122 |
123 | c.JSON(
124 | http.StatusOK,
125 | response,
126 | )
127 | return
128 | }
129 |
130 | // List 列出用户所有点赞视频
131 | func List(ctx context.Context, c *app.RequestContext) {
132 | field := logrus.Fields{
133 | "method": "List",
134 | }
135 | logger.WithFields(field).Info("Process start")
136 |
137 | actorIdPtr, ok := mw.Auth(c)
138 | actorId := *actorIdPtr
139 | if !ok {
140 | return
141 | }
142 |
143 | qUserId, userIdExist := c.GetQuery("user_id")
144 | if !userIdExist {
145 | biz.BadRequestError.WithFields(&field).LaunchError(c)
146 | return
147 | }
148 |
149 | userId, err := strconv.ParseUint(qUserId, 10, 32)
150 | if err != nil {
151 | biz.BadRequestError.WithCause(err).WithFields(&field).LaunchError(c)
152 | return
153 | }
154 |
155 | response, err := Client.FavoriteList(ctx, &favorite.FavoriteListRequest{
156 | ActorId: actorId,
157 | UserId: uint32(userId),
158 | })
159 |
160 | if err != nil {
161 | biz.RPCCallError.WithCause(err).WithFields(&field).LaunchError(c)
162 | return
163 | }
164 |
165 | c.JSON(
166 | consts.StatusOK,
167 | response,
168 | )
169 | return
170 | }
171 |
--------------------------------------------------------------------------------
/service/web/feed/handler.go:
--------------------------------------------------------------------------------
1 | package feed
2 |
3 | import (
4 | "context"
5 | "strconv"
6 | bizConstant "toktik/constant/biz"
7 | "toktik/constant/config"
8 | "toktik/kitex_gen/douyin/feed"
9 | feedService "toktik/kitex_gen/douyin/feed/feedservice"
10 | "toktik/logging"
11 | "toktik/service/web/mw"
12 |
13 | "github.com/kitex-contrib/obs-opentelemetry/provider"
14 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
15 |
16 | "github.com/cloudwego/hertz/pkg/app"
17 | httpStatus "github.com/cloudwego/hertz/pkg/protocol/consts"
18 | "github.com/cloudwego/kitex/client"
19 | consul "github.com/kitex-contrib/registry-consul"
20 | "github.com/sirupsen/logrus"
21 | )
22 |
23 | var feedClient feedService.Client
24 |
25 | func init() {
26 | r, err := consul.NewConsulResolver(config.EnvConfig.CONSUL_ADDR)
27 | if err != nil {
28 | logging.Logger.WithError(err).Fatal("init feed client failed")
29 | panic(err)
30 | }
31 | provider.NewOpenTelemetryProvider(
32 | provider.WithServiceName(config.FeedServiceName),
33 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
34 | provider.WithInsecure(),
35 | )
36 | feedClient, err = feedService.NewClient(
37 | config.FeedServiceName,
38 | client.WithResolver(r),
39 | client.WithSuite(tracing.NewClientSuite()),
40 | )
41 | if err != nil {
42 | logging.Logger.WithError(err).Fatal("init feed client failed")
43 | panic(err)
44 | }
45 | }
46 | func Action(ctx context.Context, c *app.RequestContext) {
47 | methodFields := logrus.Fields{
48 | "method": "FeedAction",
49 | }
50 | logger := logging.Logger.WithFields(methodFields)
51 | logger.Debugf("Process start")
52 |
53 | latestTime := c.Query("latest_time")
54 | if _, err := strconv.Atoi(latestTime); latestTime != "" && err != nil {
55 | bizConstant.InvalidLatestTime.
56 | WithCause(err).
57 | WithFields(&methodFields).
58 | LaunchError(c)
59 | return
60 | }
61 |
62 | actorIdPtr, ok := mw.Auth(c)
63 | actorId := *actorIdPtr
64 | if !ok {
65 | return
66 | }
67 |
68 | logger.WithFields(logrus.Fields{
69 | "latestTime": latestTime,
70 | "actorId": actorId,
71 | }).Debugf("Executing get feed")
72 | response, err := feedClient.ListVideos(ctx, &feed.ListFeedRequest{
73 | LatestTime: &latestTime,
74 | ActorId: actorIdPtr,
75 | })
76 |
77 | if err != nil {
78 | bizConstant.RPCCallError.
79 | WithCause(err).
80 | WithFields(&methodFields).
81 | LaunchError(c)
82 | return
83 | }
84 |
85 | logger.WithFields(logrus.Fields{
86 | "response": response,
87 | }).Debugf("Getting feed success")
88 | c.JSON(httpStatus.StatusOK, response)
89 | }
90 |
--------------------------------------------------------------------------------
/service/web/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "toktik/constant/config"
6 | "toktik/service/web/auth"
7 | "toktik/service/web/comment"
8 | "toktik/service/web/favorite"
9 | "toktik/service/web/feed"
10 | "toktik/service/web/mw"
11 | "toktik/service/web/publish"
12 | "toktik/service/web/relation"
13 | "toktik/service/web/user"
14 | "toktik/service/web/wechat"
15 |
16 | "github.com/cloudwego/hertz/pkg/app/server"
17 | "github.com/hertz-contrib/gzip"
18 | "github.com/hertz-contrib/obs-opentelemetry/provider"
19 | "github.com/hertz-contrib/obs-opentelemetry/tracing"
20 | "github.com/hertz-contrib/pprof"
21 | "github.com/hertz-contrib/swagger"
22 | swaggerFiles "github.com/swaggo/files"
23 | )
24 |
25 | func main() {
26 | p := provider.NewOpenTelemetryProvider(
27 | provider.WithServiceName(config.WebServiceName),
28 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
29 | provider.WithInsecure(),
30 | )
31 | defer p.Shutdown(context.Background())
32 |
33 | tracer, cfg := tracing.NewServerTracer()
34 | h := server.Default(
35 | server.WithHostPorts(config.WebServiceAddr),
36 | server.WithMaxRequestBodySize(config.EnvConfig.MAX_REQUEST_BODY_SIZE),
37 | tracer,
38 | )
39 | h.Use(gzip.Gzip(gzip.DefaultCompression))
40 | h.Use(mw.AuthMiddleware())
41 | h.Use(tracing.ServerMiddleware(cfg))
42 | pprof.Register(h)
43 |
44 | douyin := h.Group("/douyin")
45 |
46 | // feed service
47 | douyin.GET("/feed", feed.Action)
48 |
49 | // user service
50 | userGroup := douyin.Group("/user")
51 | userGroup.POST("/register/", auth.Register)
52 | userGroup.POST("/login/", auth.Login)
53 | userGroup.GET("/", user.GetUserInfo)
54 |
55 | // publish service
56 | publishGroup := douyin.Group("/publish")
57 | publishGroup.POST("/action/", publish.Action)
58 | publishGroup.GET("/list", publish.List)
59 |
60 | // favorite service
61 | favoriteGroup := douyin.Group("/favorite")
62 | favoriteGroup.POST("/action/", favorite.Action)
63 | favoriteGroup.GET("/list/", favorite.List)
64 |
65 | // comment service
66 | commentGroup := douyin.Group("/comment")
67 | commentGroup.POST("/action/", comment.Action)
68 | commentGroup.GET("/list/", comment.List)
69 |
70 | // relation service
71 | relationGroup := douyin.Group("/relation")
72 | relationGroup.POST("/action/", relation.RelationAction)
73 | relationGroup.GET("/follow/list/", relation.GetFollowList)
74 | relationGroup.GET("/follower/list/", relation.GetFollowerList)
75 | relationGroup.GET("/friend/list/", relation.GetFriendList)
76 |
77 | // message service
78 | messageGroup := douyin.Group("/message")
79 | messageGroup.POST("/action/", wechat.MessageAction)
80 | messageGroup.GET("/chat/", wechat.MessageChat)
81 |
82 | url := swagger.URL("http://localhost:8080/swagger/doc.json") // The url pointing to API definition
83 | h.GET("/swagger/*any", swagger.WrapHandler(swaggerFiles.Handler, url))
84 |
85 | h.Spin()
86 | }
87 |
--------------------------------------------------------------------------------
/service/web/mw/auth.go:
--------------------------------------------------------------------------------
1 | package mw
2 |
3 | import (
4 | "context"
5 | "toktik/constant/biz"
6 | "toktik/kitex_gen/douyin/auth"
7 | authHandler "toktik/service/web/auth"
8 |
9 | "github.com/cloudwego/hertz/pkg/app"
10 | "github.com/cloudwego/hertz/pkg/common/hlog"
11 | )
12 |
13 | // AuthResult Authentication result enum
14 | const (
15 | // authResultSuccess Authentication success
16 | authResultSuccess string = "success"
17 | // authResultNoToken Authentication failed due to no token
18 | authResultNoToken string = "no_token"
19 | // authResultUnknown Authentication failed due to unknown reason
20 | authResultUnknown string = "unknown"
21 | )
22 |
23 | const (
24 | authResultKey = "authentication_result"
25 | userIdKey = "user_id"
26 | )
27 |
28 | func init() {
29 | hlog.Info("using auth")
30 | }
31 |
32 | func AuthMiddleware() app.HandlerFunc {
33 | return func(ctx context.Context, rc *app.RequestContext) {
34 | var token string
35 | formToken := string(rc.FormValue("token"))
36 |
37 | if formToken != "" {
38 | token = formToken
39 | }
40 |
41 | if token == "" {
42 | rc.Set(authResultKey, authResultNoToken)
43 | rc.Set(userIdKey, 0)
44 | rc.Next(ctx)
45 | return
46 | }
47 |
48 | authResp, err := authHandler.Client.Authenticate(ctx, &auth.AuthenticateRequest{Token: token})
49 | if err != nil {
50 | rc.Set(authResultKey, authResultUnknown)
51 | rc.Set(userIdKey, 0)
52 | rc.Next(ctx)
53 | return
54 | }
55 | if authResp.StatusCode == 0 {
56 | rc.Set(authResultKey, authResultSuccess)
57 | rc.Set(userIdKey, authResp.UserId)
58 | } else {
59 | rc.Set(authResultKey, authResultUnknown)
60 | rc.Set(userIdKey, 0)
61 | }
62 | rc.Next(ctx)
63 | }
64 | }
65 |
66 | type config struct {
67 | authRequired bool
68 | }
69 |
70 | // Option opts for opentelemetry tracer provider
71 | type Option interface {
72 | apply(cfg *config)
73 | }
74 |
75 | type option func(cfg *config)
76 |
77 | func (fn option) apply(cfg *config) {
78 | fn(cfg)
79 | }
80 |
81 | func newConfig(opts []Option) *config {
82 | cfg := &config{}
83 |
84 | for _, opt := range opts {
85 | opt.apply(cfg)
86 | }
87 |
88 | return cfg
89 | }
90 |
91 | func WithAuthRequired() Option {
92 | return option(func(cfg *config) {
93 | cfg.authRequired = true
94 | })
95 | }
96 |
97 | func Auth(c *app.RequestContext, opts ...Option) (actorIdPtr *uint32, ok bool) {
98 | cfg := newConfig(opts)
99 | actorIdPtr = new(uint32)
100 | switch c.GetString(authResultKey) {
101 | case authResultSuccess:
102 | *actorIdPtr = c.GetUint32(userIdKey)
103 | case authResultNoToken:
104 | // CHATGPT GENERATED CR COMMENT:
105 | // 这段代码中 fallthrough 和 break 的使用方法是正确的,不会产生 bug。
106 | //
107 | // 在 switch 语句中,使用 fallthrough 语句可以使程序继续执行下一个 case 语句,而不进行判断。在上述代码中,如果
108 | // authResultKey 的值为 AUTH_RESULT_NO_TOKEN,程序会先判断 !cfg.authRequired,如果为 true,则跳出
109 | // switch 语句,执行后续代码;如果为 false,则会继续执行下一个 case 语句,即 default,然后执行
110 | // biz.UnAuthorized.LaunchError(c),返回 nil 和 false。
111 | //
112 | // 而在 switch 语句中,使用 break 语句可以跳出 switch 语句,不再继续执行下一个 case 语句。在上述代码中,如果
113 | // authResultKey 的值为 AUTH_RESULT_SUCCESS,程序会执行 actorId =
114 | // c.GetUint32(userIdKey),然后直接跳出 switch 语句,返回 actorIdPtr 和 true。
115 | //
116 | // 因此,使用 fallthrough 和 break 的方式是正确的,不会产生 bug。
117 | if !cfg.authRequired {
118 | break
119 | }
120 | fallthrough
121 | default:
122 | biz.UnAuthorized.LaunchError(c)
123 | return nil, false
124 | }
125 | return actorIdPtr, true
126 | }
127 |
--------------------------------------------------------------------------------
/service/web/mw/json.go:
--------------------------------------------------------------------------------
1 | package mw
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/cloudwego/hertz/pkg/app/server/render"
7 | "github.com/cloudwego/hertz/pkg/common/hlog"
8 | "google.golang.org/protobuf/encoding/protojson"
9 | "google.golang.org/protobuf/proto"
10 | )
11 |
12 | var m = protojson.MarshalOptions{
13 | EmitUnpopulated: true,
14 | UseProtoNames: true,
15 | }
16 |
17 | func init() {
18 | hlog.Info("using protojson")
19 | render.ResetJSONMarshal(marshal)
20 | }
21 |
22 | func marshal(v any) ([]byte, error) {
23 | switch v := v.(type) {
24 | case proto.Message:
25 | return m.Marshal(v)
26 | default:
27 | return json.Marshal(v)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/service/web/publish/handler.go:
--------------------------------------------------------------------------------
1 | package publish
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "mime/multipart"
7 | "strconv"
8 | bizConstant "toktik/constant/biz"
9 | "toktik/constant/config"
10 | "toktik/kitex_gen/douyin/publish"
11 | publishService "toktik/kitex_gen/douyin/publish/publishservice"
12 | "toktik/logging"
13 | "toktik/service/web/mw"
14 |
15 | "github.com/kitex-contrib/obs-opentelemetry/provider"
16 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
17 |
18 | "github.com/cloudwego/hertz/pkg/app"
19 | httpStatus "github.com/cloudwego/hertz/pkg/protocol/consts"
20 | "github.com/cloudwego/kitex/client"
21 | consul "github.com/kitex-contrib/registry-consul"
22 | "github.com/sirupsen/logrus"
23 | )
24 |
25 | var publishClient publishService.Client
26 |
27 | func init() {
28 | r, err := consul.NewConsulResolver(config.EnvConfig.CONSUL_ADDR)
29 | if err != nil {
30 | panic(err)
31 | }
32 | provider.NewOpenTelemetryProvider(
33 | provider.WithServiceName(config.PublishServiceName),
34 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
35 | provider.WithInsecure(),
36 | )
37 | publishClient, err = publishService.NewClient(
38 | config.PublishServiceName,
39 | client.WithResolver(r),
40 | client.WithSuite(tracing.NewClientSuite()),
41 | )
42 | if err != nil {
43 | panic(err)
44 | }
45 | }
46 |
47 | func paramValidate(c *app.RequestContext) (err error) {
48 | var wrappedError error
49 | form, err := c.Request.MultipartForm()
50 | if err != nil {
51 | wrappedError = fmt.Errorf("invalid form: %w", err)
52 | }
53 | title := form.Value["title"]
54 | if len(title) <= 0 {
55 | wrappedError = fmt.Errorf("not title")
56 | }
57 |
58 | data := form.File["data"]
59 | if len(data) <= 0 {
60 | wrappedError = fmt.Errorf("not data")
61 | }
62 | if wrappedError != nil {
63 | return wrappedError
64 | }
65 | return nil
66 | }
67 |
68 | func Action(ctx context.Context, c *app.RequestContext) {
69 | methodFields := logrus.Fields{
70 | "method": "PublishAction",
71 | }
72 | logger := logging.Logger.WithFields(methodFields)
73 | logger.Debugf("Process start")
74 |
75 | actorIdPtr, ok := mw.Auth(c, mw.WithAuthRequired())
76 | actorId := *actorIdPtr
77 | if !ok {
78 | return
79 | }
80 |
81 | if err := paramValidate(c); err != nil {
82 | bizConstant.InvalidArguments.WithCause(err).WithFields(&methodFields).LaunchError(c)
83 | return
84 | }
85 |
86 | form, _ := c.MultipartForm()
87 | title := form.Value["title"][0]
88 | file := form.File["data"][0]
89 | opened, _ := file.Open()
90 | defer func(opened multipart.File) {
91 | err := opened.Close()
92 | if err != nil {
93 | logger.WithFields(logrus.Fields{
94 | "error": err,
95 | }).Errorf("opened.Close() failed")
96 | }
97 | }(opened)
98 | var data = make([]byte, file.Size)
99 | readSize, err := opened.Read(data)
100 | if err != nil {
101 | bizConstant.OpenFileFailedError.WithCause(err).WithFields(&methodFields).LaunchError(c)
102 | return
103 | }
104 | if readSize != int(file.Size) {
105 | bizConstant.SizeNotMatchError.WithCause(err).WithFields(&methodFields).LaunchError(c)
106 | return
107 | }
108 |
109 | logger.WithFields(logrus.Fields{
110 | "actorId": actorId,
111 | "title": title,
112 | "dataSize": len(data),
113 | }).Debugf("Executing create video")
114 | publishResp, err := publishClient.CreateVideo(ctx, &publish.CreateVideoRequest{
115 | ActorId: actorId,
116 | Data: data,
117 | Title: title,
118 | })
119 | if err != nil {
120 | bizConstant.RPCCallError.WithCause(err).WithFields(&methodFields).LaunchError(c)
121 | return
122 | }
123 | logger.WithFields(logrus.Fields{
124 | "response": publishResp,
125 | }).Debugf("Create video success")
126 | c.JSON(
127 | httpStatus.StatusOK,
128 | publishResp,
129 | )
130 | }
131 |
132 | func List(ctx context.Context, c *app.RequestContext) {
133 | methodFields := logrus.Fields{
134 | "method": "CommentAction",
135 | }
136 | logger := logging.Logger.WithFields(methodFields)
137 | logger.Debugf("Process start")
138 |
139 | actorIdPtr, ok := mw.Auth(c)
140 | actorId := *actorIdPtr
141 | if !ok {
142 | return
143 | }
144 |
145 | userId, userIdExists := c.GetQuery("user_id")
146 |
147 | if !userIdExists {
148 | bizConstant.InvalidArguments.WithFields(&methodFields).LaunchError(c)
149 | }
150 |
151 | pUserId, err := strconv.ParseUint(userId, 10, 32)
152 |
153 | if err != nil {
154 | bizConstant.BadRequestError.WithFields(&methodFields).WithCause(err).LaunchError(c)
155 | return
156 | }
157 |
158 | resp, err := publishClient.ListVideo(ctx, &publish.ListVideoRequest{
159 | UserId: uint32(pUserId),
160 | ActorId: actorId,
161 | })
162 |
163 | if err != nil {
164 | bizConstant.InternalServerError.WithCause(err).WithFields(&methodFields).LaunchError(c)
165 | return
166 | }
167 |
168 | c.JSON(
169 | httpStatus.StatusOK,
170 | resp,
171 | )
172 | }
173 |
--------------------------------------------------------------------------------
/service/web/user/handler.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "context"
5 | "strconv"
6 | "toktik/constant/biz"
7 | "toktik/constant/config"
8 | "toktik/kitex_gen/douyin/user"
9 | userService "toktik/kitex_gen/douyin/user/userservice"
10 | "toktik/logging"
11 | "toktik/service/web/mw"
12 |
13 | "github.com/kitex-contrib/obs-opentelemetry/provider"
14 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
15 |
16 | "github.com/cloudwego/hertz/pkg/app"
17 | httpStatus "github.com/cloudwego/hertz/pkg/protocol/consts"
18 | "github.com/cloudwego/kitex/client"
19 | consul "github.com/kitex-contrib/registry-consul"
20 | "github.com/sirupsen/logrus"
21 | )
22 |
23 | var userClient userService.Client
24 |
25 | func init() {
26 | r, err := consul.NewConsulResolver(config.EnvConfig.CONSUL_ADDR)
27 | if err != nil {
28 | panic(err)
29 | }
30 | provider.NewOpenTelemetryProvider(
31 | provider.WithServiceName(config.UserServiceName),
32 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
33 | provider.WithInsecure(),
34 | )
35 | userClient, err = userService.NewClient(
36 | config.UserServiceName,
37 | client.WithResolver(r),
38 | client.WithSuite(tracing.NewClientSuite()),
39 | )
40 | if err != nil {
41 | panic(err)
42 | }
43 | }
44 |
45 | func GetUserInfo(ctx context.Context, c *app.RequestContext) {
46 | methodFields := logrus.Fields{
47 | "method": "GetUserInfo",
48 | }
49 | logger := logging.Logger
50 | logger.WithFields(methodFields).Info("Process start")
51 |
52 | actorIdPtr, ok := mw.Auth(c)
53 | actorId := *actorIdPtr
54 | if !ok {
55 | return
56 | }
57 |
58 | userId, idExist := c.GetQuery("user_id")
59 | id, err := strconv.Atoi(userId)
60 |
61 | if !idExist || err != nil {
62 | biz.InvalidUserID.WithCause(err).WithFields(&methodFields).LaunchError(c)
63 | return
64 | }
65 |
66 | logger.WithField("user_id", id).Debugf("Executing get user info")
67 | resp, err := userClient.GetUser(ctx, &user.UserRequest{
68 | UserId: uint32(id),
69 | ActorId: actorId,
70 | })
71 |
72 | if err != nil {
73 | biz.RPCCallError.WithCause(err).WithFields(&methodFields).LaunchError(c)
74 | return
75 | }
76 |
77 | logger.WithField("response", resp).Debugf("Get user info success")
78 | c.JSON(
79 | httpStatus.StatusOK,
80 | resp,
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/service/web/wechat/handler.go:
--------------------------------------------------------------------------------
1 | package wechat
2 |
3 | import (
4 | "context"
5 | "log"
6 | "strconv"
7 | bizConstant "toktik/constant/biz"
8 | bizConfig "toktik/constant/config"
9 | "toktik/kitex_gen/douyin/wechat"
10 | "toktik/kitex_gen/douyin/wechat/wechatservice"
11 | "toktik/logging"
12 | "toktik/service/web/mw"
13 |
14 | "github.com/cloudwego/hertz/pkg/app"
15 | httpStatus "github.com/cloudwego/hertz/pkg/protocol/consts"
16 | "github.com/cloudwego/kitex/client"
17 | "github.com/kitex-contrib/obs-opentelemetry/provider"
18 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
19 | consul "github.com/kitex-contrib/registry-consul"
20 | "github.com/sirupsen/logrus"
21 | )
22 |
23 | var Client wechatservice.Client
24 |
25 | func init() {
26 | r, err := consul.NewConsulResolver(bizConfig.EnvConfig.CONSUL_ADDR)
27 | if err != nil {
28 | log.Fatal(err)
29 | }
30 | provider.NewOpenTelemetryProvider(
31 | provider.WithServiceName(bizConfig.WechatServiceName),
32 | provider.WithExportEndpoint(bizConfig.EnvConfig.EXPORT_ENDPOINT),
33 | provider.WithInsecure(),
34 | )
35 | Client, err = wechatservice.NewClient(
36 | bizConfig.WechatServiceName,
37 | client.WithResolver(r),
38 | client.WithSuite(tracing.NewClientSuite()),
39 | )
40 | if err != nil {
41 | log.Fatal(err)
42 | }
43 | }
44 |
45 | func MessageAction(ctx context.Context, c *app.RequestContext) {
46 | methodFields := logrus.Fields{
47 | "method": "MessageAction",
48 | }
49 | logger := logging.Logger
50 | logger.WithFields(methodFields).Debugf("Process start")
51 |
52 | actorIdPtr, ok := mw.Auth(c, mw.WithAuthRequired())
53 | actorId := *actorIdPtr
54 | if !ok {
55 | return
56 | }
57 | actionType, exist := c.GetQuery("action_type")
58 | if !exist {
59 | bizConstant.InvalidActionType.WithFields(&methodFields).LaunchError(c)
60 | return
61 | }
62 | if i, err := strconv.Atoi(actionType); err != nil || i != 1 {
63 | err2Launch := bizConstant.InvalidActionType.WithFields(&methodFields)
64 | if err != nil {
65 | err2Launch = err2Launch.WithCause(err)
66 | }
67 | err2Launch.LaunchError(c)
68 | return
69 | }
70 |
71 | receiverID, exist := c.GetQuery("to_user_id")
72 | if !exist {
73 | bizConstant.InvalidArguments.WithFields(&methodFields).LaunchError(c)
74 | return
75 | }
76 | receiverIDInt, err := strconv.ParseInt(receiverID, 10, 32)
77 | if err != nil {
78 | bizConstant.InvalidArguments.WithCause(err).WithFields(&methodFields).LaunchError(c)
79 | return
80 | }
81 |
82 | content, exist := c.GetQuery("content")
83 | if !exist {
84 | bizConstant.InvalidArguments.WithFields(&methodFields).LaunchError(c)
85 | return
86 | }
87 |
88 | logger.WithFields(logrus.Fields{
89 | "action_type": actionType,
90 | "to_user_id": receiverIDInt,
91 | "content": content,
92 | }).Debugf("Executing message action")
93 |
94 | messageActionResponse, err := Client.WechatAction(ctx, &wechat.MessageActionRequest{
95 | SenderId: actorId,
96 | ReceiverId: uint32(receiverIDInt),
97 | ActionType: 1,
98 | Content: content,
99 | })
100 |
101 | if err != nil {
102 | bizConstant.RPCCallError.WithCause(err).WithFields(&methodFields).LaunchError(c)
103 | return
104 | }
105 | logger.WithFields(logrus.Fields{
106 | "response": messageActionResponse,
107 | }).Debugf("Message action success")
108 | c.JSON(httpStatus.StatusOK, messageActionResponse)
109 | }
110 |
111 | func MessageChat(ctx context.Context, c *app.RequestContext) {
112 | methodFields := logrus.Fields{
113 | "method": "MessageChat",
114 | }
115 | logger := logging.Logger
116 | logger.WithFields(methodFields).Debugf("Process start")
117 |
118 | actorIdPtr, ok := mw.Auth(c, mw.WithAuthRequired())
119 | actorId := *actorIdPtr
120 | if !ok {
121 | return
122 | }
123 |
124 | receiverIdPtr, exist := c.GetQuery("to_user_id")
125 | if !exist {
126 | bizConstant.InvalidArguments.WithFields(&methodFields).LaunchError(c)
127 | return
128 | }
129 | receiverId, err := strconv.ParseInt(receiverIdPtr, 10, 32)
130 | if err != nil {
131 | bizConstant.InvalidArguments.WithCause(err).WithFields(&methodFields).LaunchError(c)
132 | return
133 | }
134 |
135 | preMsgTimeStr, exist := c.GetQuery("pre_msg_time")
136 | if !exist {
137 | preMsgTimeStr = "0"
138 | }
139 | preMsgTime, err := strconv.ParseInt(preMsgTimeStr, 10, 64)
140 | if err != nil {
141 | bizConstant.InvalidArguments.WithCause(err).WithFields(&methodFields).LaunchError(c)
142 | return
143 | }
144 |
145 | logger.WithFields(logrus.Fields{
146 | "to_user_id": receiverId,
147 | "pre_msg_time": preMsgTimeStr,
148 | }).Debugf("Executing message chat")
149 |
150 | messageActionResponse, err := Client.WechatChat(ctx, &wechat.MessageChatRequest{
151 | SenderId: actorId,
152 | ReceiverId: uint32(receiverId),
153 | PreMsgTime: preMsgTime,
154 | })
155 |
156 | if err != nil {
157 | bizConstant.RPCCallError.WithCause(err).WithFields(&methodFields).LaunchError(c)
158 | return
159 | }
160 | logger.WithFields(logrus.Fields{
161 | "response": messageActionResponse,
162 | }).Debugf("Message chat success")
163 | c.JSON(httpStatus.StatusOK, messageActionResponse)
164 | }
165 |
--------------------------------------------------------------------------------
/service/wechat/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | RUN_NAME="wechat"
3 |
4 | mkdir -p output/bin
5 | cp script/* output/
6 | chmod +x output/bootstrap.sh
7 |
8 | if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
9 | go build -o output/bin/${RUN_NAME}
10 | else
11 | go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
12 | fi
13 |
14 |
--------------------------------------------------------------------------------
/service/wechat/db/gen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | protoc --go_out=../../../../ msg.proto
4 |
--------------------------------------------------------------------------------
/service/wechat/db/msg.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package toktik.service.wechat.db;
3 | option go_package = "toktik/service/wechat/db";
4 |
5 | message ChatMessage {
6 | uint32 from = 1;
7 | uint32 to = 2;
8 | string msg = 3;
9 | int64 time = 4;
10 | }
11 |
12 | message ChatGPTMessage {
13 | uint32 sender_id = 1;
14 | string msg = 2;
15 | bool reset_session = 3;
16 | int64 time = 4;
17 | }
18 |
--------------------------------------------------------------------------------
/service/wechat/handler_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "reflect"
7 | "testing"
8 | "toktik/kitex_gen/douyin/wechat"
9 | )
10 |
11 | func TestWechatServiceImpl_WechatAction(t *testing.T) {
12 | type args struct {
13 | ctx context.Context
14 | req *wechat.MessageActionRequest
15 | }
16 | tests := []struct {
17 | name string
18 | args args
19 | wantResp *wechat.MessageActionResponse
20 | wantErr bool
21 | }{
22 | // TODO: Use testcontainers to test redis
23 | //{"should pass", args{context.Background(), &wechat.MessageActionRequest{SenderId: 114, ReceiverId: 514, ActionType: 1, Content: `Hi, nice 2 meet u.`}}, &wechat.MessageActionResponse{StatusCode: 0, StatusMsg: "success"}, false},
24 | }
25 | for _, tt := range tests {
26 | t.Run(tt.name, func(t *testing.T) {
27 | s := &WechatServiceImpl{}
28 | gotResp, err := s.WechatAction(tt.args.ctx, tt.args.req)
29 | if (err != nil) != tt.wantErr {
30 | t.Errorf("WechatAction() error = %v, wantErr %v", err, tt.wantErr)
31 | return
32 | }
33 | if !reflect.DeepEqual(gotResp, tt.wantResp) {
34 | t.Errorf("WechatAction() gotResp = %v, want %v", gotResp, tt.wantResp)
35 | }
36 | })
37 | }
38 | }
39 |
40 | func TestWechatServiceImpl_WechatChat(t *testing.T) {
41 | type args struct {
42 | ctx context.Context
43 | req *wechat.MessageChatRequest
44 | }
45 | tests := []struct {
46 | name string
47 | args args
48 | wantResp *wechat.MessageChatResponse
49 | wantErr bool
50 | }{
51 | // TODO: Use testcontainers to test redis
52 | //{"should pass", args{context.Background(), &wechat.MessageChatRequest{SenderId: 114, ReceiverId: 514}}, &wechat.MessageChatResponse{StatusCode: 0, StatusMsg: "success"}, false},
53 | }
54 | for _, tt := range tests {
55 | t.Run(tt.name, func(t *testing.T) {
56 | s := &WechatServiceImpl{}
57 | gotResp, err := s.WechatChat(tt.args.ctx, tt.args.req)
58 | if (err != nil) != tt.wantErr {
59 | t.Errorf("WechatChat() error = %v, wantErr %v", err, tt.wantErr)
60 | return
61 | }
62 | if !reflect.DeepEqual(gotResp, tt.wantResp) {
63 | t.Errorf("WechatChat() gotResp = %v, want %v", gotResp, tt.wantResp)
64 | }
65 | })
66 | }
67 | }
68 |
69 | func TestWechatServiceImpl_generateKey(t *testing.T) {
70 | test1Result := "chat:0:0"
71 | test2Result := "chat:1:2"
72 | test3Result := "chat:1:2"
73 | test4Result := fmt.Sprintf("chat:%d:%d", 1<<32-1, 1<<32-1)
74 | test5Result := fmt.Sprintf("chat:%d:%d", 1<<32-2, 1<<32-1)
75 |
76 | getUint32Ptr := func(i uint32) *uint32 {
77 | return &i
78 | }
79 |
80 | type args struct {
81 | uid1 *uint32
82 | uid2 *uint32
83 | }
84 | tests := []struct {
85 | name string
86 | args args
87 | want func(s *string) bool
88 | }{
89 | {"test1", args{uid1: new(uint32), uid2: new(uint32)}, func(s *string) bool {
90 | return *s == test1Result
91 | }},
92 | {"test2", args{uid1: getUint32Ptr(1), uid2: getUint32Ptr(2)}, func(s *string) bool {
93 | return *s == test2Result
94 | }},
95 | {"test3", args{uid1: getUint32Ptr(2), uid2: getUint32Ptr(1)}, func(s *string) bool {
96 | return *s == test3Result
97 | }},
98 | {"test4", args{uid1: getUint32Ptr(1<<32 - 1), uid2: getUint32Ptr(1<<32 - 1)}, func(s *string) bool {
99 | return *s == test4Result
100 | }},
101 | {"test5", args{uid1: getUint32Ptr(1<<32 - 2), uid2: getUint32Ptr(1<<32 - 1)}, func(s *string) bool {
102 | return *s == test5Result
103 | }},
104 | }
105 | for _, tt := range tests {
106 | t.Run(tt.name, func(t *testing.T) {
107 | s := &WechatServiceImpl{}
108 | if got := s.generateKey(tt.args.uid1, tt.args.uid2); !tt.want(got) {
109 | t.Errorf("generateKey() = %v", got)
110 | }
111 | })
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/service/wechat/kitex.yaml:
--------------------------------------------------------------------------------
1 | kitexinfo:
2 | ServiceName: 'wechat'
3 | ToolVersion: 'v0.4.4'
4 |
5 |
--------------------------------------------------------------------------------
/service/wechat/kitex_info.yaml:
--------------------------------------------------------------------------------
1 | kitexinfo:
2 | ServiceName: 'wechat'
3 | ToolVersion: 'v0.4.4'
4 |
5 |
--------------------------------------------------------------------------------
/service/wechat/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "toktik/constant/config"
7 | "toktik/kitex_gen/douyin/wechat/wechatservice"
8 |
9 | "github.com/cloudwego/kitex/pkg/rpcinfo"
10 | "github.com/cloudwego/kitex/server"
11 | "github.com/kitex-contrib/obs-opentelemetry/provider"
12 | "github.com/kitex-contrib/obs-opentelemetry/tracing"
13 | consul "github.com/kitex-contrib/registry-consul"
14 | )
15 |
16 | func main() {
17 | var err error
18 |
19 | r, err := consul.NewConsulRegister(config.EnvConfig.CONSUL_ADDR)
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 |
24 | addr, err := net.ResolveTCPAddr("tcp", config.WechatServiceAddr)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | provider.NewOpenTelemetryProvider(
30 | provider.WithServiceName(config.WechatServiceName),
31 | provider.WithExportEndpoint(config.EnvConfig.EXPORT_ENDPOINT),
32 | provider.WithInsecure(),
33 | )
34 |
35 | srv := wechatservice.NewServer(
36 | new(WechatServiceImpl),
37 | server.WithServiceAddr(addr),
38 | server.WithRegistry(r),
39 | server.WithSuite(tracing.NewServerSuite()),
40 | server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
41 | ServiceName: config.WechatServiceName,
42 | }),
43 | )
44 | err = srv.Run()
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/service/wechat/script/bootstrap.sh:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env bash
2 | CURDIR=$(cd $(dirname $0); pwd)
3 |
4 | if [ "X$1" != "X" ]; then
5 | RUNTIME_ROOT=$1
6 | else
7 | RUNTIME_ROOT=${CURDIR}
8 | fi
9 |
10 | export KITEX_RUNTIME_ROOT=$RUNTIME_ROOT
11 | export KITEX_LOG_DIR="$RUNTIME_ROOT/log"
12 |
13 | if [ ! -d "$KITEX_LOG_DIR/app" ]; then
14 | mkdir -p "$KITEX_LOG_DIR/app"
15 | fi
16 |
17 | if [ ! -d "$KITEX_LOG_DIR/rpc" ]; then
18 | mkdir -p "$KITEX_LOG_DIR/rpc"
19 | fi
20 |
21 | exec "$CURDIR/bin/wechat"
22 |
23 |
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | output_dir="output"
4 | services_dir="service/"
5 | services=$(ls "$services_dir")
6 |
7 | while [[ $# -gt 0 ]]
8 | do
9 | case "$1" in
10 | --service)
11 | service_name="$2"
12 | shift 2
13 | ;;
14 | *)
15 | echo "Unknown option: $1"
16 | exit 1
17 | ;;
18 | esac
19 | done
20 |
21 | if [ -z "$service_name" ]; then
22 | echo "Error: --service option is required."
23 | exit 1
24 | fi
25 |
26 | found=false
27 | for s in $services; do
28 | if [ "$s" == "$service_name" ]; then
29 | found=true
30 | break
31 | fi
32 | done
33 |
34 | if [ "$found" = false ]; then
35 | echo "Error: Unrecognized service name: $service_name"
36 | printf 'Available service names:\n%s\n' "$services"
37 | exit 1
38 | fi
39 |
40 | command="$output_dir/bin/$service_name"
41 |
42 | # Check if the bootstrap.sh file exists
43 | if [ -f output/bootstrap-"${service_name}".sh ]; then
44 | command="$output_dir/bootstrap-${service_name}.sh"
45 | fi
46 |
47 | if [ ! -f "$command" ]; then
48 | echo "Error: Service binary not found: $command"
49 | exit 1
50 | fi
51 |
52 | "$command"
--------------------------------------------------------------------------------
/storage/fs.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "io"
5 | "net/url"
6 | "os"
7 | "path"
8 | "toktik/constant/config"
9 | "toktik/logging"
10 |
11 | "github.com/sirupsen/logrus"
12 | )
13 |
14 | type FSStorage struct {
15 | }
16 |
17 | func (f FSStorage) Upload(fileName string, content io.Reader) (output *PutObjectOutput, err error) {
18 | methodFields := logrus.Fields{
19 | "function": "FSStorage.Upload",
20 | "file_name": fileName,
21 | }
22 | logger := logging.Logger.WithFields(methodFields)
23 | logger.Debug("Process start")
24 |
25 | all, err := io.ReadAll(content)
26 | if err != nil {
27 | logger.WithFields(logrus.Fields{
28 | "err": err,
29 | }).Debug("failed reading content")
30 | return nil, err
31 | }
32 | filePath := path.Join(config.EnvConfig.LOCAL_FS_LOCATION, fileName)
33 | dir := path.Dir(filePath)
34 | err = os.MkdirAll(dir, os.FileMode(0755))
35 | if err != nil {
36 | logger.WithFields(logrus.Fields{
37 | "err": err,
38 | }).Debug("failed writing creating directory before writing file")
39 | return nil, err
40 | }
41 | err = os.WriteFile(filePath, all, os.FileMode(0755))
42 | if err != nil {
43 | logger.WithFields(logrus.Fields{
44 | "err": err,
45 | }).Debug("failed writing content to file")
46 | return nil, err
47 | }
48 | return &PutObjectOutput{}, nil
49 | }
50 |
51 | func (f FSStorage) GetLink(fileName string) (string, error) {
52 | return url.JoinPath(config.EnvConfig.LOCAL_FS_BASEURL, fileName)
53 | }
54 |
--------------------------------------------------------------------------------
/storage/provider.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "io"
5 | "toktik/constant/config"
6 | "toktik/logging"
7 |
8 | "github.com/sirupsen/logrus"
9 | )
10 |
11 | var Instance storageProvider = S3Storage{}
12 |
13 | func init() {
14 | if config.EnvConfig.STORAGE_TYPE == "fs" {
15 | Instance = FSStorage{}
16 | }
17 | logging.Logger.WithFields(logrus.Fields{
18 | "storage_type": config.EnvConfig.STORAGE_TYPE,
19 | }).Info("storage init")
20 | }
21 |
22 | type PutObjectOutput struct {
23 | }
24 |
25 | type storageProvider interface {
26 | Upload(fileName string, content io.Reader) (*PutObjectOutput, error)
27 | GetLink(fileName string) (string, error)
28 | }
29 |
30 | // Upload to the s3 storage using given fileName
31 | func Upload(fileName string, content io.Reader) (*PutObjectOutput, error) {
32 | return Instance.Upload(fileName, content)
33 | }
34 |
35 | func GetLink(fileName string) (string, error) {
36 | return Instance.GetLink(fileName)
37 | }
38 |
--------------------------------------------------------------------------------
/storage/s3.go:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import (
4 | "context"
5 | "io"
6 | "log"
7 | "net/url"
8 | "strconv"
9 | appcfg "toktik/constant/config"
10 |
11 | "github.com/aws/aws-sdk-go-v2/aws"
12 | "github.com/aws/aws-sdk-go-v2/config"
13 | "github.com/aws/aws-sdk-go-v2/credentials"
14 | "github.com/aws/aws-sdk-go-v2/service/s3"
15 | )
16 |
17 | var client *s3.Client
18 |
19 | func init() {
20 | r2Resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...any) (aws.Endpoint, error) {
21 | return aws.Endpoint{
22 | URL: appcfg.EnvConfig.S3_ENDPOINT_URL,
23 | }, nil
24 | })
25 |
26 | cfg, err := config.LoadDefaultConfig(context.TODO(),
27 | config.WithEndpointResolverWithOptions(r2Resolver),
28 | config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(appcfg.EnvConfig.S3_SECRET_ID, appcfg.EnvConfig.S3_SECRET_KEY, "")),
29 | )
30 | if err != nil {
31 | log.Fatal(err)
32 | }
33 |
34 | client = s3.NewFromConfig(cfg, func(o *s3.Options) {
35 | // Required when using minio
36 | o.UsePathStyle, _ = strconv.ParseBool(appcfg.EnvConfig.S3_PATH_STYLE)
37 | })
38 | }
39 |
40 | type S3Storage struct {
41 | }
42 |
43 | func (s S3Storage) Upload(fileName string, content io.Reader) (*PutObjectOutput, error) {
44 | _, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
45 | Bucket: &appcfg.EnvConfig.S3_BUCKET,
46 | Key: &fileName,
47 | Body: content,
48 | })
49 |
50 | return &PutObjectOutput{}, err
51 | }
52 |
53 | func (s S3Storage) GetLink(fileName string) (string, error) {
54 | return url.JoinPath(appcfg.EnvConfig.S3_PUBLIC_URL, fileName)
55 | }
56 |
--------------------------------------------------------------------------------
/test/e2e/base_api_test.go:
--------------------------------------------------------------------------------
1 | //go:build e2e
2 |
3 | // Progress:
4 | // All tests are synced with 2023-2-20 version of the API. Not sure if the API will change in the future.
5 |
6 | package main
7 |
8 | import (
9 | "fmt"
10 | "net/http"
11 | "strconv"
12 | "testing"
13 |
14 | "github.com/segmentio/ksuid"
15 | )
16 |
17 | // TestFeed tests the procedure of unauthenticated users getting feed.
18 | func TestFeed(t *testing.T) {
19 | e := newExpect(t)
20 | feedResp := e.GET("/douyin/feed/").Expect().Status(http.StatusOK).JSON().Object()
21 | feedResp.Value("status_code").Number().Equal(0)
22 | feedResp.Value("status_msg").String().NotEmpty()
23 | feedResp.Value("video_list").Array().Length().Gt(0)
24 | nextTime := feedResp.Value("next_time").String()
25 | nextTimeInt, err := strconv.ParseInt((*nextTime).Raw(), 10, 64)
26 | if err != nil {
27 | t.Error(err)
28 | }
29 | // check if nextTimeInt is a valid timestamp later than 2023-01-01 00:00:00
30 | if nextTimeInt < 1672502400000 {
31 | t.Error("next_time is not a valid timestamp")
32 | }
33 |
34 | for _, element := range feedResp.Value("video_list").Array().Iter() {
35 | video := element.Object()
36 | video.ContainsKey("id")
37 | author := video.Value("author").Object()
38 |
39 | ValidateUser(author)
40 |
41 | video.Value("play_url").String().NotEmpty()
42 | video.Value("cover_url").String().NotEmpty()
43 | video.ContainsKey("favorite_count")
44 | video.ContainsKey("comment_count")
45 | video.ContainsKey("is_favorite")
46 | video.Value("title").String().NotEmpty()
47 | }
48 | }
49 |
50 | // TestUserAction tests a whole user register & login & fetch user info procedure.
51 | func TestUserAction(t *testing.T) {
52 | e := newExpect(t)
53 |
54 | registerValue := fmt.Sprintf("douyin_test_%s", ksuid.New().String())
55 |
56 | registerResp := e.POST("/douyin/user/register/").
57 | WithQuery("username", registerValue).WithQuery("password", registerValue).
58 | WithFormField("username", registerValue).WithFormField("password", registerValue).
59 | Expect().
60 | Status(http.StatusOK).
61 | JSON().Object()
62 | registerResp.Value("status_code").Number().Equal(0)
63 | registerResp.Value("status_msg").String().NotEmpty()
64 | registerResp.Value("user_id").Number().Gt(0)
65 | registerResp.Value("token").String().NotEmpty()
66 |
67 | loginResp := e.POST("/douyin/user/login/").
68 | WithQuery("username", registerValue).WithQuery("password", registerValue).
69 | WithFormField("username", registerValue).WithFormField("password", registerValue).
70 | Expect().
71 | Status(http.StatusOK).
72 | JSON().Object()
73 | loginResp.Value("status_code").Number().Equal(0)
74 | registerResp.Value("status_msg").String().NotEmpty()
75 | loginResp.Value("user_id").Number().Gt(0)
76 | loginResp.Value("token").String().NotEmpty()
77 |
78 | token := loginResp.Value("token").String().Raw()
79 | userId := loginResp.Value("user_id").Number().Raw()
80 |
81 | userResp := e.GET("/douyin/user/").
82 | WithQuery("user_id", userId).
83 | WithQuery("token", token).
84 | Expect().
85 | Status(http.StatusOK).
86 | JSON().Object()
87 | userResp.Value("status_code").Number().Equal(0)
88 | userResp.Value("status_msg").String().NotEmpty()
89 | userInfo := userResp.Value("user").Object()
90 | ValidateUser(userInfo)
91 | }
92 |
93 | // TestPublish tests the procedure of a publish & check the publish list of the user.
94 | func TestPublish(t *testing.T) {
95 | e := newExpect(t)
96 |
97 | userId, token := getTestUserToken(testUserA, e)
98 |
99 | publishResp := e.POST("/douyin/publish/action/").
100 | WithMultipart().
101 | WithFile("data", "../../service/publish/resources/bear.mp4").
102 | WithFormField("token", token).
103 | WithFormField("title", "Bear").
104 | Expect().
105 | Status(http.StatusOK).
106 | JSON().Object()
107 | publishResp.Value("status_code").Number().Equal(0)
108 | publishResp.Value("status_msg").String().NotEmpty()
109 |
110 | publishListResp := e.GET("/douyin/publish/list/").
111 | WithQuery("user_id", userId).WithQuery("token", token).
112 | Expect().
113 | Status(http.StatusOK).
114 | JSON().Object()
115 | publishListResp.Value("status_code").Number().Equal(0)
116 | publishListResp.Value("video_list").Array().Length().Gt(0)
117 |
118 | for _, element := range publishListResp.Value("video_list").Array().Iter() {
119 | video := element.Object()
120 | video.ContainsKey("id")
121 | video.ContainsKey("author")
122 | video.Value("play_url").String().NotEmpty()
123 | video.Value("cover_url").String().NotEmpty()
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/test/e2e/common.go:
--------------------------------------------------------------------------------
1 | //go:build e2e
2 |
3 | package main
4 |
5 | import (
6 | "net/http"
7 | "testing"
8 |
9 | "github.com/gavv/httpexpect/v2"
10 | )
11 |
12 | var serverAddr = "https://toktik.xctra.cn"
13 |
14 | // TODO: use safer way to store test user credentials
15 | var testUserA = "douyinTestUserA"
16 | var testUserB = "douyinTestUserB"
17 |
18 | func newExpect(t *testing.T) *httpexpect.Expect {
19 | return httpexpect.WithConfig(httpexpect.Config{
20 | Client: http.DefaultClient,
21 | BaseURL: serverAddr,
22 | Reporter: httpexpect.NewAssertReporter(t),
23 | Printers: []httpexpect.Printer{
24 | httpexpect.NewDebugPrinter(t, true),
25 | },
26 | })
27 | }
28 |
29 | func getTestUserToken(user string, e *httpexpect.Expect) (int, string) {
30 | registerResp := e.POST("/douyin/user/register/").
31 | WithQuery("username", user).WithQuery("password", user).
32 | WithFormField("username", user).WithFormField("password", user).
33 | Expect().
34 | Status(http.StatusOK).
35 | JSON().Object()
36 |
37 | userId := 0
38 | token := registerResp.Value("token").String().Raw()
39 | if len(token) == 0 {
40 | loginResp := e.POST("/douyin/user/login/").
41 | WithQuery("username", user).WithQuery("password", user).
42 | WithFormField("username", user).WithFormField("password", user).
43 | Expect().
44 | Status(http.StatusOK).
45 | JSON().Object()
46 | loginToken := loginResp.Value("token").String()
47 | loginToken.Length().Gt(0)
48 | token = loginToken.Raw()
49 | userId = int(loginResp.Value("user_id").Number().Raw())
50 | } else {
51 | userId = int(registerResp.Value("user_id").Number().Raw())
52 | }
53 | return userId, token
54 | }
55 |
56 | func ValidateUser(user *httpexpect.Object) {
57 | user.ContainsKey("id")
58 | user.Value("name").String().NotEmpty()
59 | user.ContainsKey("follow_count")
60 | user.ContainsKey("follower_count")
61 | user.ContainsKey("is_follow")
62 | user.Value("avatar").String().NotEmpty()
63 | user.Value("background_image").String().NotEmpty()
64 | user.Value("signature").String().NotEmpty()
65 | user.ContainsKey("total_favorited") // TODO: determine if this field should be string or int
66 | user.ContainsKey("work_count")
67 | user.ContainsKey("favorite_count")
68 | }
69 |
--------------------------------------------------------------------------------
/test/e2e/interact_api_test.go:
--------------------------------------------------------------------------------
1 | //go:build e2e
2 |
3 | // Progress:
4 | // All tests are synced with 2023-2-20 version of the API. Not sure if the API will change in the future.
5 |
6 | package main
7 |
8 | import (
9 | "net/http"
10 | "testing"
11 |
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | // TestFavorite tests the procedure of like & unlike
16 | func TestFavorite(t *testing.T) {
17 | e := newExpect(t)
18 |
19 | feedResp := e.GET("/douyin/feed/").Expect().Status(http.StatusOK).JSON().Object()
20 | feedResp.Value("status_code").Number().Equal(0)
21 | feedResp.Value("video_list").Array().Length().Gt(0)
22 | firstVideo := feedResp.Value("video_list").Array().First().Object()
23 | videoId := firstVideo.Value("id").Number().Raw()
24 |
25 | userId, token := getTestUserToken(testUserA, e)
26 |
27 | favoriteResp := e.POST("/douyin/favorite/action/").
28 | WithQuery("token", token).WithQuery("video_id", videoId).WithQuery("action_type", 1).
29 | WithFormField("token", token).WithFormField("video_id", videoId).WithFormField("action_type", 1).
30 | Expect().
31 | Status(http.StatusOK).
32 | JSON().Object()
33 | favoriteResp.Value("status_code").Number().Equal(0)
34 | favoriteResp.Value("status_msg").String().NotEmpty()
35 |
36 | favoriteListResp := e.GET("/douyin/favorite/list/").
37 | WithQuery("token", token).WithQuery("user_id", userId).
38 | WithFormField("token", token).WithFormField("user_id", userId).
39 | Expect().
40 | Status(http.StatusOK).
41 | JSON().Object()
42 | favoriteListResp.Value("status_code").Number().Equal(0)
43 | favoriteListResp.Value("status_msg").String().NotEmpty()
44 | for _, element := range favoriteListResp.Value("video_list").Array().Iter() {
45 | video := element.Object()
46 | video.ContainsKey("id")
47 | ValidateUser(video.Value("author").Object())
48 | video.Value("play_url").String().NotEmpty()
49 | video.Value("cover_url").String().NotEmpty()
50 | video.ContainsKey("favorite_count")
51 | video.ContainsKey("comment_count")
52 | video.ContainsKey("is_favorite")
53 | video.Value("title").String().NotEmpty()
54 | }
55 |
56 | unlikeResp := e.POST("/douyin/favorite/action/").
57 | WithQuery("token", token).WithQuery("video_id", videoId).WithQuery("action_type", 2).
58 | WithFormField("token", token).WithFormField("video_id", videoId).WithFormField("action_type", 2).
59 | Expect().
60 | Status(http.StatusOK).
61 | JSON().Object()
62 | unlikeResp.Value("status_code").Number().Equal(0)
63 | unlikeResp.Value("status_msg").String().NotEmpty()
64 | }
65 |
66 | // TestComment tests the procedure of add, list & delete a comment.
67 | func TestComment(t *testing.T) {
68 | e := newExpect(t)
69 |
70 | feedResp := e.GET("/douyin/feed/").Expect().Status(http.StatusOK).JSON().Object()
71 | feedResp.Value("status_code").Number().Equal(0)
72 | feedResp.Value("video_list").Array().Length().Gt(0)
73 | firstVideo := feedResp.Value("video_list").Array().First().Object()
74 | videoId := firstVideo.Value("id").Number().Raw()
75 |
76 | _, token := getTestUserToken(testUserA, e)
77 |
78 | addCommentResp := e.POST("/douyin/comment/action/").
79 | WithQuery("token", token).WithQuery("video_id", videoId).WithQuery("action_type", 1).WithQuery("comment_text", "测试评论").
80 | WithFormField("token", token).WithFormField("video_id", videoId).WithFormField("action_type", 1).WithFormField("comment_text", "测试评论").
81 | Expect().
82 | Status(http.StatusOK).
83 | JSON().Object()
84 | addCommentResp.Value("status_code").Number().Equal(0)
85 | addCommentResp.Value("status_msg").String().NotEmpty()
86 | addCommentResp.Value("comment").Object().Value("id").Number().Gt(0)
87 | ValidateUser(addCommentResp.Value("comment").Object().Value("user").Object())
88 | commentId := int(addCommentResp.Value("comment").Object().Value("id").Number().Raw())
89 |
90 | commentListResp := e.GET("/douyin/comment/list/").
91 | WithQuery("token", token).WithQuery("video_id", videoId).
92 | WithFormField("token", token).WithFormField("video_id", videoId).
93 | Expect().
94 | Status(http.StatusOK).
95 | JSON().Object()
96 | commentListResp.Value("status_code").Number().Equal(0)
97 | commentListResp.Value("status_msg").String().NotEmpty()
98 |
99 | containTestComment := false
100 | for _, element := range commentListResp.Value("comment_list").Array().Iter() {
101 | comment := element.Object()
102 | comment.ContainsKey("id")
103 | ValidateUser(comment.Value("user").Object())
104 | comment.Value("content").String().NotEmpty()
105 | comment.Value("create_date").String().NotEmpty()
106 | if int(comment.Value("id").Number().Raw()) == commentId {
107 | containTestComment = true
108 | }
109 | }
110 |
111 | assert.True(t, containTestComment, "Can't find test comment in list")
112 |
113 | delCommentResp := e.POST("/douyin/comment/action/").
114 | WithQuery("token", token).WithQuery("video_id", videoId).WithQuery("action_type", 2).WithQuery("comment_id", commentId).
115 | WithFormField("token", token).WithFormField("video_id", videoId).WithFormField("action_type", 2).WithFormField("comment_id", commentId).
116 | Expect().
117 | Status(http.StatusOK).
118 | JSON().Object()
119 | delCommentResp.Value("status_code").Number().Equal(0)
120 | }
121 |
--------------------------------------------------------------------------------
/test/e2e/social_api_test.go:
--------------------------------------------------------------------------------
1 | //go:build e2e
2 |
3 | // Progress:
4 | // All tests are synced with 2023-2-20 version of the API. Not sure if the API will change in the future.
5 |
6 | package main
7 |
8 | import (
9 | "net/http"
10 | "testing"
11 |
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | // TestRelation tests the procedure of follow, list users.
16 | func TestRelation(t *testing.T) {
17 | e := newExpect(t)
18 |
19 | userIdA, tokenA := getTestUserToken(testUserA, e)
20 | userIdB, tokenB := getTestUserToken(testUserB, e)
21 |
22 | relationResp := e.POST("/douyin/relation/action/").
23 | WithQuery("token", tokenA).WithQuery("to_user_id", userIdB).WithQuery("action_type", 1).
24 | WithFormField("token", tokenA).WithFormField("to_user_id", userIdB).WithFormField("action_type", 1).
25 | Expect().
26 | Status(http.StatusOK).
27 | JSON().Object()
28 | relationResp.Value("status_code").Number().Equal(0)
29 | relationResp.Value("status_msg").String().NotEmpty()
30 |
31 | followListResp := e.GET("/douyin/relation/follow/list/").
32 | WithQuery("token", tokenA).WithQuery("user_id", userIdA).
33 | WithFormField("token", tokenA).WithFormField("user_id", userIdA).
34 | Expect().
35 | Status(http.StatusOK).
36 | JSON().Object()
37 | followListResp.Value("status_code").Number().Equal(0)
38 | followListResp.Value("user_list").Array().NotEmpty()
39 |
40 | containTestUserB := false
41 | for _, element := range followListResp.Value("user_list").Array().Iter() {
42 | user := element.Object()
43 | user.ContainsKey("id")
44 | ValidateUser(user)
45 | if int(user.Value("id").Number().Raw()) == userIdB {
46 | containTestUserB = true
47 | }
48 | }
49 | assert.True(t, containTestUserB, "Follow test user failed")
50 |
51 | followerListResp := e.GET("/douyin/relation/follower/list/").
52 | WithQuery("token", tokenB).WithQuery("user_id", userIdB).
53 | WithFormField("token", tokenB).WithFormField("user_id", userIdB).
54 | Expect().
55 | Status(http.StatusOK).
56 | JSON().Object()
57 | followerListResp.Value("status_code").Number().Equal(0)
58 | followerListResp.Value("user_list").Array().NotEmpty()
59 |
60 | containTestUserA := false
61 | for _, element := range followerListResp.Value("user_list").Array().Iter() {
62 | user := element.Object()
63 | user.ContainsKey("id")
64 | ValidateUser(user)
65 | if int(user.Value("id").Number().Raw()) == userIdA {
66 | containTestUserA = true
67 | }
68 | }
69 | assert.True(t, containTestUserA, "Follower test user failed")
70 | }
71 |
72 | // TestChat tests the procedure of sending and receiving messages.
73 | func TestChat(t *testing.T) {
74 | e := newExpect(t)
75 |
76 | userIdA, tokenA := getTestUserToken(testUserA, e)
77 | userIdB, tokenB := getTestUserToken(testUserB, e)
78 |
79 | messageResp := e.POST("/douyin/message/action/").
80 | WithQuery("token", tokenA).WithQuery("to_user_id", userIdB).WithQuery("action_type", 1).WithQuery("content", "Send to UserB").
81 | WithFormField("token", tokenA).WithFormField("to_user_id", userIdB).WithFormField("action_type", 1).WithQuery("content", "Send to UserB").
82 | Expect().
83 | Status(http.StatusOK).
84 | JSON().Object()
85 | messageResp.Value("status_code").Number().Equal(0)
86 | messageResp.Value("status_msg").String().NotEmpty()
87 |
88 | chatResp := e.GET("/douyin/message/chat/").
89 | WithQuery("token", tokenA).WithQuery("to_user_id", userIdB).
90 | WithFormField("token", tokenA).WithFormField("to_user_id", userIdB).
91 | Expect().
92 | Status(http.StatusOK).
93 | JSON().Object()
94 | chatResp.Value("status_code").Number().Equal(0)
95 | chatResp.Value("status_msg").String().NotEmpty()
96 | chatResp.Value("message_list").Array().Length().Gt(0)
97 | chatResp.Value("message_list").Array().First().Object().Value("content").String().Equal("Send to UserB")
98 | chatResp.Value("message_list").Array().First().Object().Value("create_time").String().NotEmpty()
99 |
100 | chatResp = e.GET("/douyin/message/chat/").
101 | WithQuery("token", tokenB).WithQuery("to_user_id", userIdA).
102 | WithFormField("token", tokenB).WithFormField("to_user_id", userIdA).
103 | Expect().
104 | Status(http.StatusOK).
105 | JSON().Object()
106 | chatResp.Value("status_code").Number().Equal(0)
107 | chatResp.Value("status_msg").String().NotEmpty()
108 | chatResp.Value("message_list").Array().Length().Gt(0)
109 | chatResp.Value("message_list").Array().First().Object().Value("content").String().Equal("Send to UserB")
110 | chatResp.Value("message_list").Array().First().Object().Value("create_time").String().NotEmpty()
111 | }
112 |
--------------------------------------------------------------------------------
/test/mock/db.go:
--------------------------------------------------------------------------------
1 | package mock
2 |
3 | import (
4 | "database/sql"
5 | "log"
6 | "toktik/repo"
7 |
8 | "github.com/DATA-DOG/go-sqlmock"
9 | "gorm.io/driver/postgres"
10 | "gorm.io/gorm"
11 | )
12 |
13 | var DBMock sqlmock.Sqlmock
14 | var Conn *sql.DB
15 |
16 | func init() {
17 | var err error
18 | Conn, DBMock, err = sqlmock.New()
19 | db, err := gorm.Open(postgres.New(postgres.Config{
20 | DSN: "sqlmock_db_0",
21 | DriverName: "postgres",
22 | Conn: Conn,
23 | PreferSimpleProtocol: true,
24 | }), &gorm.Config{})
25 | if err != nil {
26 | log.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
27 | }
28 | repo.SetDefault(db)
29 | }
30 |
--------------------------------------------------------------------------------
/unit-test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | for dir in service/*/; do
4 | pushd "$dir" || exit
5 | go test -gcflags=-l -v -cover ./...
6 | popd || exit
7 | done
8 |
--------------------------------------------------------------------------------