Browse Source

初始化

master
Viviman 2 months ago
commit
77744424ad
  1. 15
      .gitignore
  2. 164
      .golangci-lint.yml
  3. 26
      CONTRIBUTING.md
  4. 27
      Dockerfile
  5. 201
      LICENSE
  6. 9
      Makefile
  7. 184
      README.md
  8. 60
      README_ZH.md
  9. 36
      action.sh
  10. 3
      applications/api/.hz
  11. 8
      applications/api/Makefile
  12. 83
      applications/api/biz/handler/api/comment_service.go
  13. 80
      applications/api/biz/handler/api/favorite_service.go
  14. 52
      applications/api/biz/handler/api/feed_service.go
  15. 85
      applications/api/biz/handler/api/message_service.go
  16. 94
      applications/api/biz/handler/api/publish_service.go
  17. 144
      applications/api/biz/handler/api/relation_service.go
  18. 94
      applications/api/biz/handler/api/user_service.go
  19. 229
      applications/api/biz/handler/pack.go
  20. 28
      applications/api/biz/handler/ping.go
  21. 3443
      applications/api/biz/model/api/api.pb.go
  22. 480
      applications/api/biz/model/api/hz.pb.go
  23. 67
      applications/api/biz/router/api/api.go
  24. 160
      applications/api/biz/router/api/middleware.go
  25. 14
      applications/api/biz/router/register.go
  26. 164
      applications/api/chat/client.go
  27. 66
      applications/api/chat/hub.go
  28. 1537
      applications/api/docs/docs.go
  29. 1517
      applications/api/docs/swagger.json
  30. 192
      applications/api/initialize/init_hertz.go
  31. 121
      applications/api/initialize/jwt.go
  32. 24
      applications/api/initialize/redis.go
  33. 87
      applications/api/initialize/rpc/comment.go
  34. 87
      applications/api/initialize/rpc/favorite.go
  35. 75
      applications/api/initialize/rpc/feed.go
  36. 29
      applications/api/initialize/rpc/init.go
  37. 87
      applications/api/initialize/rpc/message.go
  38. 88
      applications/api/initialize/rpc/publish.go
  39. 111
      applications/api/initialize/rpc/relation.go
  40. 99
      applications/api/initialize/rpc/user.go
  41. 11
      applications/api/initialize/viper.go
  42. 53
      applications/api/main.go
  43. 24
      applications/api/router.go
  44. 16
      applications/api/router_gen.go
  45. 2
      applications/comment/Makefile
  46. 5
      applications/comment/README.md
  47. 75
      applications/comment/api/comment_api/api.go
  48. 97
      applications/comment/api/comment_api/pack.go
  49. 11
      applications/comment/api/comment_api/validate.go
  50. 12
      applications/comment/build.sh
  51. 45
      applications/comment/domain/entity/comment/check_options.go
  52. 101
      applications/comment/domain/entity/comment/entity.go
  53. 25
      applications/comment/domain/entity/comment/list.go
  54. 71
      applications/comment/domain/entity/comment/new_options.go
  55. 15
      applications/comment/domain/entity/comment_count/check_options.go
  56. 55
      applications/comment/domain/entity/comment_count/entity.go
  57. 19
      applications/comment/domain/entity/comment_count/list.go
  58. 15
      applications/comment/domain/entity/comment_count/option.go
  59. 18
      applications/comment/infra/misc/config.go
  60. 9
      applications/comment/infra/misc/const.go
  61. 33
      applications/comment/infra/misc/err.go
  62. 19
      applications/comment/infra/misc/row_key.go
  63. 17
      applications/comment/infra/model/comment.go
  64. 10
      applications/comment/infra/model/comment_count.go
  65. 65
      applications/comment/infra/model/comment_hb.go
  66. 395
      applications/comment/infra/query/comment_counts.gen.go
  67. 423
      applications/comment/infra/query/comments.gen.go
  68. 107
      applications/comment/infra/query/gen.go
  69. 4
      applications/comment/infra/query/var.go
  70. 78
      applications/comment/infra/repository/comment_count_repo/repository.go
  71. 94
      applications/comment/infra/repository/comment_hb_repo/repository.go
  72. 61
      applications/comment/infra/repository/comment_repo/repository.go
  73. 18
      applications/comment/infra/rpc/rpc.go
  74. 98
      applications/comment/main.go
  75. 21
      applications/comment/script/bootstrap.sh
  76. 234
      applications/comment/services/comment_service/service.go
  77. 101
      applications/comment/services/comment_service/timer.go
  78. 29
      applications/comment/services/comment_service/utils.go
  79. 2
      applications/favorite/Makefile
  80. 12
      applications/favorite/build.sh
  81. 39
      applications/favorite/dal/gen.go
  82. 21
      applications/favorite/dal/migrate/main.go
  83. 10
      applications/favorite/dal/model/count.go
  84. 12
      applications/favorite/dal/model/favorite.go
  85. 396
      applications/favorite/dal/query/favorite_counts.gen.go
  86. 404
      applications/favorite/dal/query/favorites.gen.go
  87. 107
      applications/favorite/dal/query/gen.go
  88. 4
      applications/favorite/dal/query/var.go
  89. 24
      applications/favorite/handler/favorite_action.go
  90. 23
      applications/favorite/handler/favorite_count.go
  91. 35
      applications/favorite/handler/favorite_list.go
  92. 20
      applications/favorite/handler/is_favorite.go
  93. 3
      applications/favorite/handler/typedef.go
  94. 43
      applications/favorite/main.go
  95. 18
      applications/favorite/misc/config.go
  96. 22
      applications/favorite/misc/const.go
  97. 27
      applications/favorite/misc/err.go
  98. 5
      applications/favorite/misc/var.go
  99. 10
      applications/favorite/pack/favorite_action.go
  100. 13
      applications/favorite/pack/favorite_count.go

15
.gitignore

@ -0,0 +1,15 @@
.vscode/
.idea/
hbase-data/
hbase-2.1.3/
hbase-zoo-data/
*_data/
tmp
log
zip
__debug*
applications/api/docs/swagger.yaml
go.sum
/.github/

164
.golangci-lint.yml

@ -0,0 +1,164 @@
linters:
disable-all: true # 关闭其他linter
enable:
# 进制使用非ASCII字符
- asciicheck
- bidichk
# 降低代码复杂度
- cyclop
- gocognit
- gocyclo
- maintidx
# 高可拓展性的go源码linter
- gocritic
# 禁止保留未使用的代码块
#---------------------------------------
#- deadcode
# The linter 'deadcode' is deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter
# 新版本的linter已经不建议使用deadcode了,不使用的代码不会被报错了
#---------------------------------------
- ineffassign
# 减少代码拷贝
- dupl
# 禁止两个time.Duration类型相乘
- durationcheck
# 所有err都要处理
- errcheck
# 在Go 1.13之后使用errors.Wrap可能导致的问题
- errorlint
# 检查switch的全面性,以免遗漏场景
- exhaustive
# 禁止将for-range value的指针暴露给外部
- exportloopref
# 禁止使用特定的标识符
- forbidigo
# 禁止出现长函数
- funlen
# 控制golang的包引用的顺序
- gci
# 禁止使用全局变量 --需要使用 //nolint:gochecknoglobals // 说明原因
- gochecknoglobals
# 禁止使用init函数
- gochecknoinits
# 如果有相同的string变量请使用consts替换
- goconst
# 检查if语句是否有简单的语法
- ifshort
# 禁止出现长语句
- lll
# struct禁止包含context.Context字段
- containedctx
# 返回两个参数,一个数据,一个是err,禁止两个都是nil
- nilnil
# 禁止使用Sprintf去构造URL中的host和port
- nosprintfhostport
# 如果知道slice大小,定义时需分配空间
- prealloc
# 检查prometheus meteics的指标名是否规范
- promlinter
# 强制要求const/import/var在一个组
- grouper
# 检查go1.17的版本是否使用os.Setenv替换t.Setenv
- tenv
# 检查变量名长度
- varnamelen
# 强制一致性的impotr别名
- importas
# 类型断言时需检查是否成功
- forcetypeassert
# 保证类型、常量、变量和函数的声明顺序和数量
- decorder
# 检查err的定义规范--types类型的定义是以Error结尾的,常量的定义是Err打头的
- errname
# SQL Query方法错误检查
- execinquery
# 禁止errors使用'=='和'!='等表达式--与nil和io.EOF比较除外
- goerr113
# 官方代码格式化
- gofmt
- gofumpt
- goimports
# 禁止使用魔法数字
- gomnd
# 检查依赖的黑白名单
- gomodguard
# 检查类似printf的函数是否以f结尾
- goprintffuncname
# 安全检查
- gosec
# 官方错误检查
- govet
# 检查拼写错误
- misspell
# 如果函数过长,禁用裸返回
- nakedret
# 禁止深度嵌套的if语句
- nestif
# 如果使用nolint指令需要给出理由-- //nolint:gochecknoglobals // 原因
- nolintlint
# 禁止使用Go关键字命名
- predeclared
# 去掉没有必要的type转换
- unconvert
# 强制使用空行
- wsl
# 检查文件头部和尾部的换行
- whitespace
# 替换golint的
- revive
# 测试代码使用*_test的包目录
- testpackage
# 启用并行测试
- paralleltest
# 检查帮助函数里面有没有调用t.Helper()函数
- thelper
linters-settings:
errcheck:
check-type-assertions: true # 检查类型断言
check-blank: true # 检查使用 _ 来处理错误
errorlint:
errorf: true # 检查fmt.Errorf错误是否用%w
exhaustive:
check-generated: false # 不检查生成的文件
default-signifies-exhaustive: false # 不检查是否有default
funlen:
lines: 65 # 一个函数总行数限制
statements: 40 # 检查函数中语句的数量
gci:
sections:
- standard # 标准库
- default # 默认按字典顺序排序
- prefix(cos/lobbyplatform) # 特殊前缀的包
gomodguard: # 检查依赖的黑白名单
allowed:
# List of allowed modules.
# Default: []
modules:
- gopkg.in/yaml.v2
# List of allowed module domains.
# Default: []
domains:
- golang.org
blocked:
# List of blocked modules.
# Default: []
modules:
# Blocked module.
- github.com/uudashr/go-module:
# Recommended modules that should be used instead. (Optional)
recommendations:
- golang.org/x/mod
# Reason why the recommended module should be used. (Optional)
reason: "`mod` is the official go.mod parser library."
# List of blocked module version constraints.
# Default: []
versions:
# Blocked module with version constraint.
- github.com/mitchellh/go-homedir:
# Version constraint, see https://github.com/Masterminds/semver#basic-comparisons.
version: "< 1.1.0"
# Reason why the version constraint exists. (Optional)
reason: "testing if blocked version constraint works."
# Set to true to raise lint issues for packages that are loaded from a local path via replace directive.
# Default: false
local_replace_directives: false

26
CONTRIBUTING.md

@ -0,0 +1,26 @@
# How to contribute
## Issue & Discussion
Any issues and discussions are welcome. Feel free for submit an issue or open a discussion.
If you want to submit an issue, we suggest that use the given issue templates. Of course, you can also submit one without any templates or using any other templates. The first goal is to make everyone understand your ideas.
## Pull Request
We use git-flow as our branch organization, as known as FDD.
At least, if you want to open a pull request you should follow these steps:
1. Fork the repository
2. Create a new branch
3. Do something & write some code
4. Commit and push
5. Open a pull request
For a better flow to manage PRs, we suggest that:
1. Use the given PR templates
2. Use `rebase` to resolve conflicts
3. Submit as few commit as possible in a PR
4. Use a linter to check code before open a PR

27
Dockerfile

@ -0,0 +1,27 @@
FROM golang:1.18-alpine3.16 AS builder
ARG target
ENV target=${target}
ARG proxy=https://proxy.golang.org
ENV proxy=${proxy}
RUN echo ${proxy}
WORKDIR /build
ENV GOPROXY ${proxy}
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -a -o serve ./applications/${target}/
FROM alpine:3.16 AS doutok-serve
WORKDIR /app
RUN mkdir tmp
COPY --from=builder /build/serve /app
COPY --from=builder /build/config /app/config
ENTRYPOINT ["/app/serve"]

201
LICENSE

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

9
Makefile

@ -0,0 +1,9 @@
genIdl:
kitex -module github.com/TremblingV5/DouTok -type protobuf -I ./proto/ -service $(module) ./proto/$(module).proto && rm handler.go && rm -rf ./script && rm kitex.yaml && rm build.sh && rm main.go
install-swagger:
go install github.com/swaggo/swag/cmd/swag@latest
generate-swagger: install-swagger
@cd applications/api && $(MAKE) swag && cd -
@rm -rf ./applications/api/docs/swagger.yaml

184
README.md

@ -0,0 +1,184 @@
DouTok目前已转战V2:https://github.com/cloudzenith/DouTok,本仓库不再高强度维护
**为什么要开一个V2版本的仓库?**
DouTok最初是字节跳动青训营的参赛项目(并取得了比较靠前的名次),在参加青训营的过程中,由于种种原因,导致项目中存在诸多不合理的地方。
例如:DouTok现版本的微服务划分不够合理,拆的过于零碎,也许看起来很“微服务”,但与实际工作生产环境上的服务划分却背道而驰,微服务的划分不应过分追求“微”,而是适应项目发展,在完善基本设计的前提下进行拆分。除此之外,过多的“微服务”导致维护过程中的困难,想要调试就需要启动非常多的服务,对维护过程非常折磨。
在青训营之后,我们开始考虑继续维护DouTok。让DouTok继续扩张的一个卡点是其本身没有前端,只能依赖青训营中提供的“抖声”APP。为了让DouTok顺利扩张,所以我们决定开发一个全新的V2版本。在V2版本中,DouTok减少了服务的划分,增加了前端项目,虽然现阶段依然不够完整,但是已经具备了继续扩张的土壤。
对参与过DouTok维护的所有同学表示感谢!
![image-20230223111012814](./documents/imgs/banner.jpeg)
DouTok is a backend for TikTok client based on Kitex and Hertz.
![Hertz](https://img.shields.io/static/v1?label=Golang&message=1.18&color=brightgreen&style=plastic&logo=go) ![Hertz](https://img.shields.io/static/v1?label=Hertz&message=using&color=green&style=plastic&logo=go) ![Hertz](https://img.shields.io/static/v1?label=Kitex&message=using&color=yellowgreen&style=plastic&logo=go) ![Hertz](https://img.shields.io/static/v1?label=gorm/gen&message=using&color=yellow&style=plastic&logo=etcd) ![Hertz](https://img.shields.io/static/v1?label=etcd&message=3.4&color=orange&style=plastic&logo=etcd) ![Hertz](https://img.shields.io/static/v1?label=MySQL&message=8.0&color=red&style=plastic&logo=mysql) ![Hertz](https://img.shields.io/static/v1?label=Redis&message=7.0&color=blue&style=plastic&logo=redis) ![Hertz](https://img.shields.io/static/v1?label=HBase&message=2.1.3&color=blueviolet&style=plastic&logo=ApacheHadoop) ![Hertz](https://img.shields.io/static/v1?label=kafka&message=Tencent&color=ff69b4&style=plastic&logo=ApacheKafka)
## Documents
DouTok now have a documents site: [https://doutok.zhengfei.xin](https://doutok.zhengfei.xin)
## Quick Start
1. Deploy dependencies
Deploy some dependencies such as MySQL, Redis etc. Run them by using `docker-compose` with `.yml` files in `./deploy` or deploy them by yourself.
```sh
docker-compose -f ./deploy/env.yml up -d
```
2. Update config files
There's an elegant way to run applications in this repo which is using `docker-compose`. So update config files in `./config_docker_compose` if you use `docker-comopse`. If you don't want to run them in this way, you must update config files in `./config` for ensuring them working.
3. Run applications by using `docker-compose`
Everything is ready now, ensure that there's a `docker-compose.yml` in the root directory and run this command in terminal
```sh
docker-compose up -d
```
### Download Client
We can download `.apk` file from `./ui` of this repo. Now the client is only support Android. After downloading and installing of this app. We can open it firstly. Then we can click `我` on the right bottom to enter configure page. After opening `高级配置`, we can input base url of the backend. An example is `http://localhost:8080/`.
## Architecture
### Technology Architecture
![image-20230223111253963](https://baize-blog-images.oss-cn-shanghai.aliyuncs.com/img/image-20230223111253963.png)
### Tracing
> Visit `http://127.0.0.1:16686/` on browser.
![image-20230223111410359](https://baize-blog-images.oss-cn-shanghai.aliyuncs.com/img/image-20230223111410359.png)
### Metric
> Visit `http://127.0.0.1:3000/` on browser.
![image-20230223111633341](https://baize-blog-images.oss-cn-shanghai.aliyuncs.com/img/image-20230223111633341.png)
### API Introduce
| 接口 | 功能 | 读/写 | 特点 | 备注 |
| ------------------------------- | ---------- | ----- | ------------ | ------------------ |
| /douyin/feed/ | 视频流接口 | 读 | 根本功能接口 | 存储优化,缓存优化 |
| /douyin/user/register/ | 注册接口 | 写 | | |
| /douyin/user/login/ | 登陆接口 | 读 | | |
| /douyin/user/ | 用户信息 | 读 | | |
| /douyin/publish/action/ | 发布视频 | 写 | | 存储优化 |
| /douyin/publish/list/ | 发布列表 | 读 | | 缓存优化 |
| /douyin/favorite/action/ | 点赞接口 | 写 | 操作数大 | 延迟处理 |
| /douyin/favorite/list/ | 点赞列表 | 读 | | 缓存优化 |
| /douyin/comment/action/ | 评论接口 | 写 | 操作数大 | 延迟处理 |
| /douyin/comment/list/ | 评论列表 | 读 | | 缓存优化 |
| /douyin/relation/action/ | 关系操作 | 写 | 操作数大 | 延迟处理 |
| /douyin/relation/follow/list/ | 关注列表 | 读 | | 缓存优化 |
| /douyin/relation/follower/list/ | 粉丝列表 | 读 | | 缓存优化 |
| /douyin/relation/friend/list/ | 朋友列表 | 读 | | 缓存优化 |
| /douyin/message/chat/ | 聊天记录 | 读 | | 存储优化,缓存优化 |
| /douyin/message/action/ | 消息操作 | 写 | | 延迟处理 |
## CI/CD
- Build docker images by using github actions
- Push docker images to private repo created with Aliyun by using github actions
- DOING: Try to run applications with k8s automatically
## Directories
```tree
.
├── applications // 模块逻辑目录
│ ├── api // api模块逻辑实现
│ │ ├── biz // api模块主要逻辑实现
│ │ │ ├── handler
│ │ │ │ ├── api
│ │ │ │ ├── pack.go
│ │ │ │ └── ping.go
│ │ │ ├── model
│ │ │ │ └── api
│ │ │ └── router
│ │ │ ├── api
│ │ │ └── register.go
│ │ ├── chat
│ │ ├── initialize
│ │ │ ├── init_hertz.go
│ │ │ ├── jwt.go
│ │ │ ├── redis.go
│ │ │ ├── rpc
│ │ │ └── viper.go
│ │ ├── main.go
│ │ ├── Makefile
│ │ ├── router_gen.go
│ │ └── router.go
│ ├── comment // comment模块逻辑实现
│ │ ├── build.sh
│ │ ├── dal // 数据层
│ │ │ ├── gen.go // gorm/gen生成代码
│ │ │ ├── migrate // gorm自动迁移
│ │ │ │ └── main.go
│ │ │ ├── model // model层
│ │ │ └── query // gorm/gen生成结果
│ │ │ ├── comment_counts.gen.go
│ │ │ ├── comments.gen.go
│ │ │ ├── gen.go
│ │ │ └── var.go
│ │ ├── handler // RPC服务的接口入口
│ │ ├── main.go // 模块入口
│ │ ├── Makefile
│ │ ├── misc // 模块所需的一些简单零散逻辑
│ │ ├── pack // 将service层提供的数据查询结果包装成接口的返回消息
│ │ ├── rpc // 初始化调用其他微服务
│ │ ├── script
│ │ ├── service // 数据查询
│ ├── favorite // favorite模块
│ ├── feed // feed模块
│ ├── message // message模块
│ ├── publish // publish模块
│ ├── relation // relation模块
│ └── user // user模块
├── config // 项目所需要的配置文件
├── deploy // 项目所需的环境部署
├── documents // 相关文档
├── go.mod
├── go.sum
├── kitex_gen // Kitex生成的代码
│ ├── comment
│ ├── favorite
│ ├── feed
│ ├── message
│ ├── publish
│ ├── relation
│ └── user
├── pkg // 项目所依赖的一些公共包
│ ├── constants // 常量包
│ ├── dlog // 日志包
│ ├── dtviper // 配置包
│ ├── errno // 错误码包
│ ├── hbaseHandle // HBase操作包
│ ├── initHelper // 初始化服务助手
│ ├── kafka // Kafka操作包
│ ├── middleware // 中间件
│ ├── misc // 一些零散逻辑
│ ├── mock // Mock测试包
│ │ ├── comment
│ │ ├── favorite
│ │ ├── feed
│ │ ├── message
│ │ ├── publish
│ │ ├── relation
│ │ └── user
│ ├── mysqlIniter // MySQL操作包
│ ├── ossHandle // OSS操作包
│ ├── redisHandle // Redis操作包
│ ├── safeMap // 线程安全的Map
│ └── utils // 其他组件
├── proto // 基于Protobuf3完成的IDL
├── README.md
└── scripts
```

60
README_ZH.md

@ -0,0 +1,60 @@
## 技术栈
### 服务端
- go1.18 及以上
- HTTP 框架:[Hertz](https://www.cloudwego.io/docs/hertz/)
- RPC 框架:[Kitex](https://www.cloudwego.io/docs/kitex/)
- 中间件:kafka、redis
- 存储:mysql、hbase、minio
#### 项目启动
🌟 下面的内容需要配合 `DouTok/guidlines.md` 食用
0. 安卓手机 + pc,处在一个局域网内。
1. 克隆 reborn 分支代码(该分支仅用于验证项目启动,只能用于测试用户注册登陆流程,其他功能可能有问题,预计两天内合入main,开放所有功能)
2. 修改 hosts 文件,添加 `127.0.0.1 hb-master`
3. 将 `./env/dependencies.yml``KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.1.119:9092` 的ip 替换成自己的局域网 IP。
4. 启动依赖环境:docker-compose -f ./env/dependencies.yml up -d(reborn 版相比 main 少了一些依赖,会影响部分功能,但是不影响开发者验证启动流程正确性)。
5. 登陆 mysql,在 DouTok 数据库中执行 scripts/DouTok.sql。
6. 通过容器访问 kafka,为需要 kafka 的服务创建 topic。
```shell
# 以交互式模式进入容器
docker exec -it 容器id /bin/bash
# 进入Kafka目录
cd /opt/kafka_2.13-2.8.1/
# 创建名为message_1的主题,1个分区,每个分区1个副本
bin/kafka-topics.sh --create --zookeeper zookeeper:2181 --replication-factor 1 --partitions 1 --topic message_1
# 创建名为relation_1的主题,1个分区,每个分区1个副本
bin/kafka-topics.sh --create --zookeeper zookeeper:2181 --replication-factor 1 --partitions 1 --topic relation_1
```
7. 启动服务(因为项目在迭代中,所以不同服务启动方式有些不同,详情参考 guidelines.md,将启动过程中涉及到的 kafka 的 ip 设置成局域网的 ip,对应配置文件路径为 DouTok/config/xxx,xxx 对应某一个服务。
8. 打开客户端,右下角“我”长按。
![image-20240227000742899](https://baize-blog-images.oss-cn-shanghai.aliyuncs.com/img/image-20240227000742899.png)
9. 跳转至输入连接的服务端口,这里输入连接的后端网关服务地址(最后一个 "/" 不要忘记):
![image-20240227000823094](https://baize-blog-images.oss-cn-shanghai.aliyuncs.com/img/image-20240227000823094.png)
10. 体验注册登陆等功能。
### 前端
- 技术栈:React Hooks、TypeScript、Redux Saga、Vite
- 项目目录
![image-20240226234007434](https://baize-blog-images.oss-cn-shanghai.aliyuncs.com/img/image-20240226234007434.png)
#### 项目启动
```shell
npm install
npm run dev
```

36
action.sh

@ -0,0 +1,36 @@
#!/bin/bash
services=(
"api" "comment" "commentDomain" "favorite" "favoriteDomain"
"feed" "message" "messageDomain" "publish" "relation" "relationDomain"
"user" "userDomain" "videoDomain"
)
if [ $1 == "start" ]; then
for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13
do
nohup go run ./applications/${services[i]}/ > ./log/${services[i]}.out 2>&1 &
done
fi
if [ $1 == "stop" ]; then
for i in 0 1 2 3 4 5 6 7 8 9 10 11 12 13
do
process=`ps -ef | grep "go run ./applications/${services[i]}/" | grep -v grep | awk '{print $2}'`
for j in $process
do
echo "Kill: ${services[i]} $j"
kill -9 $j
done
done
for i in 8088 8087 8086 8085 8084 8083 8082 8081 8079 8078 8077 8076 8075 8074
do
process=`lsof -i:$i | grep LISTEN | awk '{print $2}'`
for j in $process
do
echo "Kill: port $i $j"
kill -9 $j
done
done
fi

3
applications/api/.hz

@ -0,0 +1,3 @@
// Code generated by hz. DO NOT EDIT.
hz version: v0.5.0

8
applications/api/Makefile

@ -0,0 +1,8 @@
init:
hz new -idl ../../proto/api.proto -mod github.com/TremblingV5/DouTok/applications/api
update:
hz update -idl ../../proto/api.proto
swag:
swag init --parseDependency

83
applications/api/biz/handler/api/comment_service.go

@ -0,0 +1,83 @@
// Code generated by hertz generator.
package api
import (
"context"
"github.com/TremblingV5/DouTok/applications/api/biz/handler"
"github.com/TremblingV5/DouTok/applications/api/initialize"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/TremblingV5/DouTok/kitex_gen/comment"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/hertz-contrib/jwt"
api "github.com/TremblingV5/DouTok/applications/api/biz/model/api"
"github.com/cloudwego/hertz/pkg/app"
)
// CommentAction .
//
// @Tags Comment评论
//
// @Summary 添加或删除评论
// @Description
// @Param req body api.DouyinCommentActionRequest true "评论操作信息"
// @Success 200 {object} comment.DouyinCommentActionResponse
// @Failure default {object} api.DouyinCommentActionResponse
// @router /douyin/comment/action [POST]
func CommentAction(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinCommentActionRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildCommendActionResp(errno.ErrBind))
return
}
userId := int64(jwt.ExtractClaims(ctx, c)[initialize.AuthMiddleware.IdentityKey].(float64))
rpcReq := comment.DouyinCommentActionRequest{
VideoId: req.VideoId,
ActionType: req.ActionType,
UserId: userId,
CommentId: req.CommentId,
CommentText: req.CommentText,
}
resp, err := rpc.CommentAction(ctx, rpc.CommentClient, &rpcReq)
if err != nil {
handler.SendResponse(c, handler.BuildCommendActionResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}
// CommentList .
//
// @Tags Comment评论
//
// @Summary 获取某个视频之下的评论列表
// @Description
// @Param req query api.DouyinCommentListRequest true "获取评论的参数"
// @Success 200 {object} comment.DouyinCommentListResponse
// @Failure default {object} api.DouyinCommentListResponse
// @router /douyin/comment/list [GET]
func CommentList(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinCommentListRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildCommendListResp(errno.ErrBind))
return
}
resp, err := rpc.CommentList(ctx, rpc.CommentClient, &comment.DouyinCommentListRequest{
VideoId: req.VideoId,
})
if err != nil {
handler.SendResponse(c, handler.BuildCommendListResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}

80
applications/api/biz/handler/api/favorite_service.go

@ -0,0 +1,80 @@
// Code generated by hertz generator.
package api
import (
"context"
"github.com/TremblingV5/DouTok/applications/api/biz/handler"
"github.com/TremblingV5/DouTok/applications/api/initialize"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/TremblingV5/DouTok/kitex_gen/favorite"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/hertz-contrib/jwt"
api "github.com/TremblingV5/DouTok/applications/api/biz/model/api"
"github.com/cloudwego/hertz/pkg/app"
)
// FavoriteAction .
//
// @Tags Favorite点赞
//
// @Summary 点赞或取消点赞
// @Description
// @Param req body api.DouyinFavoriteActionRequest true "点赞操作信息"
// @Success 200 {object} favorite.DouyinFavoriteActionResponse
// @Failure default {object} api.DouyinFavoriteActionResponse
// @router /douyin/favorite/action [POST]
func FavoriteAction(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinFavoriteActionRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildFavoriteActionResp(errno.ErrBind))
return
}
userId := int64(jwt.ExtractClaims(ctx, c)[initialize.AuthMiddleware.IdentityKey].(float64))
resp, err := rpc.FavoriteAction(ctx, rpc.FavoriteClient, &favorite.DouyinFavoriteActionRequest{
VideoId: req.VideoId,
ActionType: req.ActionType,
UserId: userId,
})
if err != nil {
handler.SendResponse(c, handler.BuildFavoriteActionResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}
// FavoriteList .
//
// @Tags Favorite点赞
//
// @Summary 返回点赞视频列表
// @Description
// @Param req query api.DouyinFavoriteListRequest true "获取某个用户点赞了的视频的参数"
// @Success 200 {object} favorite.DouyinFavoriteListResponse
// @Failure default {object} api.DouyinFavoriteListResponse
//
// @router /douyin/favorite/list [GET]
func FavoriteList(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinFavoriteListRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildFavoriteListResp(errno.ErrBind))
return
}
resp, err := rpc.FavoriteList(ctx, rpc.FavoriteClient, &favorite.DouyinFavoriteListRequest{
UserId: req.UserId,
})
if err != nil {
handler.SendResponse(c, handler.BuildFavoriteListResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}

52
applications/api/biz/handler/api/feed_service.go

@ -0,0 +1,52 @@
// Code generated by hertz generator.
package api
import (
"context"
"github.com/TremblingV5/DouTok/applications/api/biz/handler"
"github.com/TremblingV5/DouTok/applications/api/initialize"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/TremblingV5/DouTok/kitex_gen/feed"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/hertz-contrib/jwt"
api "github.com/TremblingV5/DouTok/applications/api/biz/model/api"
"github.com/cloudwego/hertz/pkg/app"
)
// GetUserFeed .
//
// @Tags Feed视频流相关
//
// @Summary 返回一个视频列表
// @Description
// @Param req query api.DouyinFeedRequest false "返回哪些视频的限制参数"
// @Success 200 {object} feed.DouyinFeedResponse
// @Failure default {object} api.DouyinFeedResponse
// @router /douyin/feed [GET]
func GetUserFeed(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinFeedRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildGetUserFeedResp(errno.ErrBind))
return
}
userId := int64(0)
if req.Token != "" {
userId = int64(jwt.ExtractClaims(ctx, c)[initialize.AuthMiddleware.IdentityKey].(float64))
}
resp, err := rpc.GetUserFeed(ctx, rpc.FeedClient, &feed.DouyinFeedRequest{
LatestTime: req.LatestTime,
UserId: userId,
})
if err != nil {
handler.SendResponse(c, handler.BuildGetUserFeedResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}

85
applications/api/biz/handler/api/message_service.go

@ -0,0 +1,85 @@
// Code generated by hertz generator.
package api
import (
"context"
"github.com/TremblingV5/DouTok/applications/api/biz/handler"
"github.com/TremblingV5/DouTok/applications/api/initialize"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/TremblingV5/DouTok/kitex_gen/message"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/hertz-contrib/jwt"
api "github.com/TremblingV5/DouTok/applications/api/biz/model/api"
"github.com/cloudwego/hertz/pkg/app"
)
// MessageChat .
//
// @Tags Message聊天相关
//
// @Summary 获取和某人的聊天记录
// @Description
// @Param req query api.DouyinMessageChatRequest true "获取聊天记录的参数"
// @Success 200 {object} message.DouyinMessageChatResponse
// @Failure default {object} api.DouyinMessageChatResponse
// @router /douyin/message/chat [GET]
func MessageChat(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinMessageChatRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildMessageChatResp(errno.ErrBind))
return
}
userId := int64(jwt.ExtractClaims(ctx, c)[initialize.AuthMiddleware.IdentityKey].(float64))
resp, err := rpc.MessageChat(ctx, rpc.MessageClient, &message.DouyinMessageChatRequest{
UserId: userId,
ToUserId: req.ToUserId,
PreMsgTime: req.PreMsgTime,
})
if err != nil {
handler.SendResponse(c, handler.BuildMessageChatResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}
// MessageAction .
//
// @Tags Message聊天相关
//
// @Summary 发送消息操作
// @Description
// @Param req body api.DouyinMessageActionRequest true "发送的消息的相关信息"
// @Success 200 {object} message.DouyinMessageActionResponse
// @Failure default {object} api.DouyinMessageActionResponse
// @router /douyin/message/action [POST]
func MessageAction(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinMessageActionRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildMessageActionResp(errno.ErrBind))
return
}
userId := int64(jwt.ExtractClaims(ctx, c)[initialize.AuthMiddleware.IdentityKey].(float64))
resp, err := rpc.MessageAction(ctx, rpc.MessageClient, &message.DouyinMessageActionRequest{
UserId: userId,
ToUserId: req.ToUserId,
ActionType: req.ActionType,
Content: req.Content,
})
if err != nil {
handler.SendResponse(c, handler.BuildMessageActionResp(errno.ConvertErr(err)))
return
}
handler.SendResponse(c, resp)
//chat.ServeWs(ctx, c)
}

94
applications/api/biz/handler/api/publish_service.go

@ -0,0 +1,94 @@
// Code generated by hertz generator.
package api
import (
"bytes"
"context"
"io"
"log"
"github.com/TremblingV5/DouTok/applications/api/biz/handler"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/TremblingV5/DouTok/kitex_gen/publish"
"github.com/TremblingV5/DouTok/pkg/errno"
api "github.com/TremblingV5/DouTok/applications/api/biz/model/api"
"github.com/cloudwego/hertz/pkg/app"
)
// PublishAction .
//
// @Tags Publish视频投稿相关
//
// @Summary 发布视频操作
// @Description
// @Param token formData string true "用户鉴权token"
// @Param title formData string true "视频标题"
// @Param data formData file true "视频数据"
// @Success 200 {object} publish.DouyinPublishActionResponse
// @Failure default {object} api.DouyinPublishActionResponse
// @router /douyin/publish/action [POST]
func PublishAction(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinPublishActionRequest
// err = c.BindAndValidate(&req)
// if err != nil {
// handler.SendResponse(c, handler.BuildPublishActionResp(errno.ErrBind))
// return
// }
req.Token = c.PostForm("token")
req.Title = c.PostForm("title")
fs, _ := c.FormFile("data")
f, _ := fs.Open()
buff := new(bytes.Buffer)
_, err = io.Copy(buff, f)
if err != nil {
log.Panicln(err)
}
req.Data = buff.Bytes()
// TODO 这个绑定是否能够实现二进制文件的绑定(待测试)
resp, err := rpc.PublishAction(ctx, rpc.PublishClient, &publish.DouyinPublishActionRequest{
Title: req.Title,
Data: req.Data,
UserId: int64(c.Keys["user_id"].(float64)),
})
if err != nil {
handler.SendResponse(c, handler.BuildPublishActionResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}
// PublishList .
//
// @Tags Publish视频投稿相关
//
// @Summary 获取用户已发布视频的列表
// @Description
// @Param req query api.DouyinPublishListRequest true "获取某个用户发布的视频列表的参数"
// @Success 200 {object} publish.DouyinPublishListResponse
// @Failure default {object} api.DouyinPublishListResponse
// @router /douyin/publish/list [GET]
func PublishList(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinPublishListRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildPublishListResp(errno.ErrBind))
return
}
resp, err := rpc.PublishList(ctx, rpc.PublishClient, &publish.DouyinPublishListRequest{
UserId: req.UserId,
})
if err != nil {
handler.SendResponse(c, handler.BuildPublishListResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}

144
applications/api/biz/handler/api/relation_service.go

@ -0,0 +1,144 @@
// Code generated by hertz generator.
package api
import (
"context"
"github.com/TremblingV5/DouTok/applications/api/biz/handler"
"github.com/TremblingV5/DouTok/applications/api/initialize"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/TremblingV5/DouTok/kitex_gen/relation"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/hertz-contrib/jwt"
api "github.com/TremblingV5/DouTok/applications/api/biz/model/api"
"github.com/cloudwego/hertz/pkg/app"
)
// RelationAction .
//
// @Tags Relation关注
//
// @Summary 关注或取消关注
// @Description
// @Param req body api.DouyinRelationActionRequest true "关注操作信息"
// @Success 200 {object} relation.DouyinRelationActionResponse
// @Failure default {object} api.DouyinRelationActionResponse
// @router /douyin/relation/action [POST]
func RelationAction(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinRelationActionRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildRelationActionResp(errno.ErrBind))
return
}
userId := int64(jwt.ExtractClaims(ctx, c)[initialize.AuthMiddleware.IdentityKey].(float64))
rpcReq := relation.DouyinRelationActionRequest{
UserId: userId,
ToUserId: req.ToUserId,
ActionType: req.ActionType,
}
resp, err := rpc.RelationAction(ctx, rpc.RelationClient, &rpcReq)
if err != nil {
handler.SendResponse(c, handler.BuildRelationActionResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}
// RelationFollowList .
//
// @Tags Relation关注
//
// @Summary 获取已关注用户的列表
// @Description
// @Param req query api.DouyinRelationFollowListRequest true "获取操作信息"
// @Success 200 {object} relation.DouyinRelationFollowListResponse
// @Failure default {object} api.DouyinRelationFollowListResponse
//
// @router /douyin/relation/follow/list [GET]
func RelationFollowList(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinRelationFollowListRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildRelationFollowListResp(errno.ErrBind))
return
}
resp, err := rpc.RelationFollowList(ctx, rpc.RelationClient, &relation.DouyinRelationFollowListRequest{
UserId: req.UserId,
})
if err != nil {
handler.SendResponse(c, handler.BuildRelationFollowListResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}
// RelationFollowerList .
//
// @Tags Relation关注
//
// @Summary 获取粉丝用户列表
// @Description
// @Param req query api.DouyinRelationFollowerListRequest true "获取操作信息"
// @Success 200 {object} relation.DouyinRelationFollowerListResponse
// @Failure default {object} api.DouyinRelationFollowerListResponse
// @router /douyin/relation/follower/list [GET]
func RelationFollowerList(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinRelationFollowerListRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildRelationFollowerListResp(errno.ErrBind))
return
}
resp, err := rpc.RelationFollowerList(ctx, rpc.RelationClient, &relation.DouyinRelationFollowerListRequest{
UserId: req.UserId,
})
if err != nil {
handler.SendResponse(c, handler.BuildRelationFollowerListResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}
// RelationFriendList .
//
// @Tags Relation关注
//
// @Summary 获取好友列表
// @Description
// @Param req query api.DouyinRelationFollowerListRequest true "获取操作信息"
// @Success 200 {object} relation.DouyinRelationFollowerListResponse
// @Failure default {object} api.DouyinRelationFollowerListResponse
// @router /douyin/relation/friend/list [GET]
//
// 内部为调用获取粉丝列表
func RelationFriendList(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinRelationFriendListRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildRelationFriendListResp(errno.ErrBind))
return
}
resp, err := rpc.RelationFriendList(ctx, rpc.RelationClient, &relation.DouyinRelationFriendListRequest{
UserId: req.UserId,
})
if err != nil {
handler.SendResponse(c, handler.BuildRelationFriendListResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}

94
applications/api/biz/handler/api/user_service.go

@ -0,0 +1,94 @@
// Code generated by hertz generator.
package api
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/TremblingV5/DouTok/applications/api/biz/handler"
api "github.com/TremblingV5/DouTok/applications/api/biz/model/api"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/TremblingV5/DouTok/kitex_gen/user"
"github.com/TremblingV5/DouTok/pkg/errno"
)
// Register .
//
// @Tags User用户相关
//
// @Summary 用户注册
// @Description 添加一个用户到数据库中
// @Param req body api.DouyinUserRegisterRequest true "用户信息"
// @Success 200 {object} user.DouyinUserResponse
// @Failure default {object} api.DouyinUserRegisterResponse
// @router /douyin/user/register [POST]
func Register(ctx context.Context, c *app.RequestContext) {
var err error
var req api.DouyinUserRegisterRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildUserRegisterResp(errno.ErrBind))
return
}
if len(req.Username) == 0 || len(req.Password) == 0 {
handler.SendResponse(c, handler.BuildUserRegisterResp(errno.ErrBind))
return
}
resp, err := rpc.Register(ctx, rpc.UserClient, &user.DouyinUserRegisterRequest{
Username: req.Username,
Password: req.Password,
})
if err != nil {
handler.SendResponse(c, handler.BuildUserRegisterResp(errno.ConvertErr(err)))
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}
// GetUserById .
//
// @Tags User用户相关
//
// @Summary 通过用户ID获取用户
// @Description
// @Param req query api.DouyinUserRequest true "指明需要获取的用户的参数"
// @Success 200 {object} user.DouyinUserResponse
// @Failure default {object} api.DouyinUserResponse
// @router /douyin/user [GET]
func GetUserById(ctx context.Context, c *app.RequestContext) {
// 如果是需要授权访问的接口,则在进入时已经被中间件从 body 中获取 token 解析完成了,这里无需额外解析
var err error
var req api.DouyinUserRequest
err = c.BindAndValidate(&req)
if err != nil {
handler.SendResponse(c, handler.BuildGetUserResp(errno.ErrBind))
return
}
resp, err := rpc.GetUserById(ctx, rpc.UserClient, &user.DouyinUserRequest{
UserId: req.UserId,
})
if err != nil {
handler.SendResponse(c, handler.BuildGetUserResp(errno.ConvertErr(err)))
return
}
// TODO 此处直接返回了 rpc 的 resp
handler.SendResponse(c, resp)
}
// Login 确实有登录的接口,但是业务逻辑是在JWT中,写在这里是为了生成接口文档
//
// @Tags User用户相关
//
// @Summary 用户登录
// @Description 输入账号密码登录获取Token
// @Param req body api.DouyinUserLoginRequest true "用户信息"
// @Success 200 {object} user.DouyinUserResponse
// @Failure default {object} api.DouyinUserLoginResponse
// @router /douyin/user/login [POST]
func Login() {
}

229
applications/api/biz/handler/pack.go

@ -0,0 +1,229 @@
package handler
import (
"errors"
"github.com/TremblingV5/DouTok/applications/api/biz/model/api"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
// SendResponse pack response
func SendResponse(c *app.RequestContext, response interface{}) {
c.JSON(consts.StatusOK, response)
}
// message
func messageChatResp(err errno.ErrNo) *api.DouyinMessageChatResponse {
return &api.DouyinMessageChatResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func messageActionResp(err errno.ErrNo) *api.DouyinMessageActionResponse {
return &api.DouyinMessageActionResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func BuildMessageChatResp(err error) *api.DouyinMessageChatResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return messageChatResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return messageChatResp(e)
}
func BuildMessageActionResp(err error) *api.DouyinMessageActionResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return messageActionResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return messageActionResp(e)
}
// user
func userRegisterResp(err errno.ErrNo) *api.DouyinUserRegisterResponse {
return &api.DouyinUserRegisterResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func userLoginResp(err errno.ErrNo) *api.DouyinUserLoginResponse {
return &api.DouyinUserLoginResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func getUserResp(err errno.ErrNo) *api.DouyinUserResponse {
return &api.DouyinUserResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func BuildUserRegisterResp(err error) *api.DouyinUserRegisterResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return userRegisterResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return userRegisterResp(e)
}
func BuildUserLoginResp(err error) *api.DouyinUserLoginResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return userLoginResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return userLoginResp(e)
}
func BuildGetUserResp(err error) *api.DouyinUserResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return getUserResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return getUserResp(e)
}
// relation
func relationActionResp(err errno.ErrNo) *api.DouyinRelationActionResponse {
return &api.DouyinRelationActionResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func relationFollowListResp(err errno.ErrNo) *api.DouyinRelationFollowListResponse {
return &api.DouyinRelationFollowListResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func relationFollowerListResp(err errno.ErrNo) *api.DouyinRelationFollowerListResponse {
return &api.DouyinRelationFollowerListResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func relationFriendListResp(err errno.ErrNo) *api.DouyinRelationFriendListResponse {
return &api.DouyinRelationFriendListResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func BuildRelationActionResp(err error) *api.DouyinRelationActionResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return relationActionResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return relationActionResp(e)
}
func BuildRelationFollowListResp(err error) *api.DouyinRelationFollowListResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return relationFollowListResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return relationFollowListResp(e)
}
func BuildRelationFollowerListResp(err error) *api.DouyinRelationFollowerListResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return relationFollowerListResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return relationFollowerListResp(e)
}
func BuildRelationFriendListResp(err error) *api.DouyinRelationFriendListResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return relationFriendListResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return relationFriendListResp(e)
}
// publish
func publishActionResp(err errno.ErrNo) *api.DouyinPublishActionResponse {
return &api.DouyinPublishActionResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func publishListResp(err errno.ErrNo) *api.DouyinPublishListResponse {
return &api.DouyinPublishListResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func BuildPublishActionResp(err error) *api.DouyinPublishActionResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return publishActionResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return publishActionResp(e)
}
func BuildPublishListResp(err error) *api.DouyinPublishListResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return publishListResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return publishListResp(e)
}
// feed
func getUserFeedResp(err errno.ErrNo) *api.DouyinFeedResponse {
return &api.DouyinFeedResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func BuildGetUserFeedResp(err error) *api.DouyinFeedResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return getUserFeedResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return getUserFeedResp(e)
}
// favorite
func favoriteActionResp(err errno.ErrNo) *api.DouyinFavoriteActionResponse {
return &api.DouyinFavoriteActionResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func favoriteListResp(err errno.ErrNo) *api.DouyinFavoriteListResponse {
return &api.DouyinFavoriteListResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func BuildFavoriteActionResp(err error) *api.DouyinFavoriteActionResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return favoriteActionResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return favoriteActionResp(e)
}
func BuildFavoriteListResp(err error) *api.DouyinFavoriteListResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return favoriteListResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return favoriteListResp(e)
}
// comment
func commentActionResp(err errno.ErrNo) *api.DouyinCommentActionResponse {
return &api.DouyinCommentActionResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func commentListResp(err errno.ErrNo) *api.DouyinCommentListResponse {
return &api.DouyinCommentListResponse{StatusCode: int32(err.ErrCode), StatusMsg: err.ErrMsg}
}
func BuildCommendActionResp(err error) *api.DouyinCommentActionResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return commentActionResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return commentActionResp(e)
}
func BuildCommendListResp(err error) *api.DouyinCommentListResponse {
e := errno.ErrNo{}
if errors.As(err, &e) {
return commentListResp(e)
}
e = errno.InternalErr.WithMessage(err.Error())
return commentListResp(e)
}

28
applications/api/biz/handler/ping.go

@ -0,0 +1,28 @@
// Code generated by hertz generator.
package handler
import (
"context"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
// Ping 测试 handler
//
// @Summary Ping测试
// @Description 测试 Description
//
// @Tags Ping
//
// @Accept application/json
// @Produce application/json
// @Router /ping [get]
// @success 200 {string} map[string]string
func Ping(ctx context.Context, c *app.RequestContext) {
c.JSON(consts.StatusOK, utils.H{
"message": "pong",
})
}

3443
applications/api/biz/model/api/api.pb.go

File diff suppressed because it is too large

480
applications/api/biz/model/api/hz.pb.go

@ -0,0 +1,480 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v3.19.4
// source: hz.proto
package api
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
descriptorpb "google.golang.org/protobuf/types/descriptorpb"
reflect "reflect"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var file_hz_proto_extTypes = []protoimpl.ExtensionInfo{
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50101,
Name: "api.raw_body",
Tag: "bytes,50101,opt,name=raw_body",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50102,
Name: "api.query",
Tag: "bytes,50102,opt,name=query",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50103,
Name: "api.header",
Tag: "bytes,50103,opt,name=header",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50104,
Name: "api.cookie",
Tag: "bytes,50104,opt,name=cookie",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50105,
Name: "api.body",
Tag: "bytes,50105,opt,name=body",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50106,
Name: "api.path",
Tag: "bytes,50106,opt,name=path",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50107,
Name: "api.vd",
Tag: "bytes,50107,opt,name=vd",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50108,
Name: "api.form",
Tag: "bytes,50108,opt,name=form",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 51001,
Name: "api.go_tag",
Tag: "bytes,51001,opt,name=go_tag",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50109,
Name: "api.js_conv",
Tag: "bytes,50109,opt,name=js_conv",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50201,
Name: "api.get",
Tag: "bytes,50201,opt,name=get",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50202,
Name: "api.post",
Tag: "bytes,50202,opt,name=post",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50203,
Name: "api.put",
Tag: "bytes,50203,opt,name=put",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50204,
Name: "api.delete",
Tag: "bytes,50204,opt,name=delete",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50205,
Name: "api.patch",
Tag: "bytes,50205,opt,name=patch",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50206,
Name: "api.options",
Tag: "bytes,50206,opt,name=options",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50207,
Name: "api.head",
Tag: "bytes,50207,opt,name=head",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50208,
Name: "api.any",
Tag: "bytes,50208,opt,name=any",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50301,
Name: "api.gen_path",
Tag: "bytes,50301,opt,name=gen_path",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50302,
Name: "api.api_version",
Tag: "bytes,50302,opt,name=api_version",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50303,
Name: "api.tag",
Tag: "bytes,50303,opt,name=tag",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50304,
Name: "api.name",
Tag: "bytes,50304,opt,name=name",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50305,
Name: "api.api_level",
Tag: "bytes,50305,opt,name=api_level",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50306,
Name: "api.serializer",
Tag: "bytes,50306,opt,name=serializer",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50307,
Name: "api.param",
Tag: "bytes,50307,opt,name=param",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
ExtensionType: (*string)(nil),
Field: 50308,
Name: "api.baseurl",
Tag: "bytes,50308,opt,name=baseurl",
Filename: "hz.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*int32)(nil),
Field: 50401,
Name: "api.http_code",
Tag: "varint,50401,opt,name=http_code",
Filename: "hz.proto",
},
}
// Extension fields to descriptorpb.FieldOptions.
var (
// optional string raw_body = 50101;
E_RawBody = &file_hz_proto_extTypes[0]
// optional string query = 50102;
E_Query = &file_hz_proto_extTypes[1]
// optional string header = 50103;
E_Header = &file_hz_proto_extTypes[2]
// optional string cookie = 50104;
E_Cookie = &file_hz_proto_extTypes[3]
// optional string body = 50105;
E_Body = &file_hz_proto_extTypes[4]
// optional string path = 50106;
E_Path = &file_hz_proto_extTypes[5]
// optional string vd = 50107;
E_Vd = &file_hz_proto_extTypes[6]
// optional string form = 50108;
E_Form = &file_hz_proto_extTypes[7]
// optional string go_tag = 51001;
E_GoTag = &file_hz_proto_extTypes[8]
// optional string js_conv = 50109;
E_JsConv = &file_hz_proto_extTypes[9]
)
// Extension fields to descriptorpb.MethodOptions.
var (
// optional string get = 50201;
E_Get = &file_hz_proto_extTypes[10]
// optional string post = 50202;
E_Post = &file_hz_proto_extTypes[11]
// optional string put = 50203;
E_Put = &file_hz_proto_extTypes[12]
// optional string delete = 50204;
E_Delete = &file_hz_proto_extTypes[13]
// optional string patch = 50205;
E_Patch = &file_hz_proto_extTypes[14]
// optional string options = 50206;
E_Options = &file_hz_proto_extTypes[15]
// optional string head = 50207;
E_Head = &file_hz_proto_extTypes[16]
// optional string any = 50208;
E_Any = &file_hz_proto_extTypes[17]
// optional string gen_path = 50301;
E_GenPath = &file_hz_proto_extTypes[18]
// optional string api_version = 50302;
E_ApiVersion = &file_hz_proto_extTypes[19]
// optional string tag = 50303;
E_Tag = &file_hz_proto_extTypes[20]
// optional string name = 50304;
E_Name = &file_hz_proto_extTypes[21]
// optional string api_level = 50305;
E_ApiLevel = &file_hz_proto_extTypes[22]
// optional string serializer = 50306;
E_Serializer = &file_hz_proto_extTypes[23]
// optional string param = 50307;
E_Param = &file_hz_proto_extTypes[24]
// optional string baseurl = 50308;
E_Baseurl = &file_hz_proto_extTypes[25]
)
// Extension fields to descriptorpb.EnumValueOptions.
var (
// optional int32 http_code = 50401;
E_HttpCode = &file_hz_proto_extTypes[26]
)
var File_hz_proto protoreflect.FileDescriptor
var file_hz_proto_rawDesc = []byte{
0x0a, 0x08, 0x68, 0x7a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a,
0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x3a, 0x3a, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1d, 0x2e,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb5, 0x87, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x61, 0x77, 0x42, 0x6f, 0x64, 0x79, 0x3a, 0x35, 0x0a,
0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb6, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71,
0x75, 0x65, 0x72, 0x79, 0x3a, 0x37, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1d,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb7, 0x87,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x3a, 0x37, 0x0a,
0x06, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb8, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x3a, 0x33, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1d,
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb9, 0x87,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x3a, 0x33, 0x0a, 0x04, 0x70,
0x61, 0x74, 0x68, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x18, 0xba, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68,
0x3a, 0x2f, 0x0a, 0x02, 0x76, 0x64, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbb, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x76,
0x64, 0x3a, 0x33, 0x0a, 0x04, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c,
0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbc, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x04, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x36, 0x0a, 0x06, 0x67, 0x6f, 0x5f, 0x74, 0x61, 0x67,
0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
0xb9, 0x8e, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x6f, 0x54, 0x61, 0x67, 0x3a, 0x38,
0x0a, 0x07, 0x6a, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x76, 0x12, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c,
0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xbd, 0x87, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x6a, 0x73, 0x43, 0x6f, 0x6e, 0x76, 0x3a, 0x32, 0x0a, 0x03, 0x67, 0x65, 0x74, 0x12,
0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
0x99, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x67, 0x65, 0x74, 0x3a, 0x34, 0x0a, 0x04,
0x70, 0x6f, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9a, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f,
0x73, 0x74, 0x3a, 0x32, 0x0a, 0x03, 0x70, 0x75, 0x74, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68,
0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9b, 0x88, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x03, 0x70, 0x75, 0x74, 0x3a, 0x38, 0x0a, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x18, 0x9c, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x3a, 0x36, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68,
0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9d, 0x88, 0x03, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x3a, 0x3a, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x18, 0x9e, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x34, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x1e, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d,
0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x9f, 0x88, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x61, 0x64, 0x3a, 0x32, 0x0a, 0x03, 0x61, 0x6e,
0x79, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x18, 0xa0, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x6e, 0x79, 0x3a, 0x3b,
0x0a, 0x08, 0x67, 0x65, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74,
0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xfd, 0x88, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x67, 0x65, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x3a, 0x41, 0x0a, 0x0b, 0x61,
0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74,
0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xfe, 0x88, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x32,
0x0a, 0x03, 0x74, 0x61, 0x67, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xff, 0x88, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74,
0x61, 0x67, 0x3a, 0x34, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74,
0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x80, 0x89, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x3d, 0x0a, 0x09, 0x61, 0x70, 0x69, 0x5f,
0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x81, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61,
0x70, 0x69, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x40, 0x0a, 0x0a, 0x73, 0x65, 0x72, 0x69, 0x61,
0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x82, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73,
0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x3a, 0x36, 0x0a, 0x05, 0x70, 0x61, 0x72,
0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x18, 0x83, 0x89, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x72, 0x61,
0x6d, 0x3a, 0x3a, 0x0a, 0x07, 0x62, 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x12, 0x1e, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d,
0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x84, 0x89, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x75, 0x72, 0x6c, 0x3a, 0x40, 0x0a,
0x09, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75,
0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xe1, 0x89,
0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x68, 0x74, 0x74, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x42,
0x43, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x72,
0x65, 0x6d, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x56, 0x35, 0x2f, 0x44, 0x6f, 0x75, 0x54, 0x6f, 0x6b,
0x2f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x62, 0x69, 0x7a, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x5f,
0x79, 0x61, 0x6d, 0x6c,
}
var file_hz_proto_goTypes = []interface{}{
(*descriptorpb.FieldOptions)(nil), // 0: google.protobuf.FieldOptions
(*descriptorpb.MethodOptions)(nil), // 1: google.protobuf.MethodOptions
(*descriptorpb.EnumValueOptions)(nil), // 2: google.protobuf.EnumValueOptions
}
var file_hz_proto_depIdxs = []int32{
0, // 0: api.raw_body:extendee -> google.protobuf.FieldOptions
0, // 1: api.query:extendee -> google.protobuf.FieldOptions
0, // 2: api.header:extendee -> google.protobuf.FieldOptions
0, // 3: api.cookie:extendee -> google.protobuf.FieldOptions
0, // 4: api.body:extendee -> google.protobuf.FieldOptions
0, // 5: api.path:extendee -> google.protobuf.FieldOptions
0, // 6: api.vd:extendee -> google.protobuf.FieldOptions
0, // 7: api.form:extendee -> google.protobuf.FieldOptions
0, // 8: api.go_tag:extendee -> google.protobuf.FieldOptions
0, // 9: api.js_conv:extendee -> google.protobuf.FieldOptions
1, // 10: api.get:extendee -> google.protobuf.MethodOptions
1, // 11: api.post:extendee -> google.protobuf.MethodOptions
1, // 12: api.put:extendee -> google.protobuf.MethodOptions
1, // 13: api.delete:extendee -> google.protobuf.MethodOptions
1, // 14: api.patch:extendee -> google.protobuf.MethodOptions
1, // 15: api.options:extendee -> google.protobuf.MethodOptions
1, // 16: api.head:extendee -> google.protobuf.MethodOptions
1, // 17: api.any:extendee -> google.protobuf.MethodOptions
1, // 18: api.gen_path:extendee -> google.protobuf.MethodOptions
1, // 19: api.api_version:extendee -> google.protobuf.MethodOptions
1, // 20: api.tag:extendee -> google.protobuf.MethodOptions
1, // 21: api.name:extendee -> google.protobuf.MethodOptions
1, // 22: api.api_level:extendee -> google.protobuf.MethodOptions
1, // 23: api.serializer:extendee -> google.protobuf.MethodOptions
1, // 24: api.param:extendee -> google.protobuf.MethodOptions
1, // 25: api.baseurl:extendee -> google.protobuf.MethodOptions
2, // 26: api.http_code:extendee -> google.protobuf.EnumValueOptions
27, // [27:27] is the sub-list for method output_type
27, // [27:27] is the sub-list for method input_type
27, // [27:27] is the sub-list for extension type_name
0, // [0:27] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_hz_proto_init() }
func file_hz_proto_init() {
if File_hz_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_hz_proto_rawDesc,
NumEnums: 0,
NumMessages: 0,
NumExtensions: 27,
NumServices: 0,
},
GoTypes: file_hz_proto_goTypes,
DependencyIndexes: file_hz_proto_depIdxs,
ExtensionInfos: file_hz_proto_extTypes,
}.Build()
File_hz_proto = out.File
file_hz_proto_rawDesc = nil
file_hz_proto_goTypes = nil
file_hz_proto_depIdxs = nil
}

67
applications/api/biz/router/api/api.go

@ -0,0 +1,67 @@
// Code generated by hertz generator. DO NOT EDIT.
package Api
import (
api "github.com/TremblingV5/DouTok/applications/api/biz/handler/api"
"github.com/TremblingV5/DouTok/applications/api/initialize"
"github.com/cloudwego/hertz/pkg/app/server"
)
/*
This file will register all the routes of the services in the master idl.
And it will update automatically when you use the "update" command for the idl.
So don't modify the contents of the file, or your code will be deleted when it is updated.
*/
// Register register routes based on the IDL 'api.yaml.${HTTP Method}' annotation.
func Register(r *server.Hertz) {
root := r.Group("/", rootMw()...)
{
_douyin := root.Group("/douyin", _douyinMw()...)
_douyin.GET("/feed/", append(_getuserfeedMw(), api.GetUserFeed)...)
_douyin.GET("/user/", append(_getuserbyidMw(), api.GetUserById)...)
{
_comment := _douyin.Group("/comment", _commentMw()...)
_comment.POST("/action/", append(_comment_ctionMw(), api.CommentAction)...)
_comment.GET("/list/", append(_commentlistMw(), api.CommentList)...)
}
{
_favorite := _douyin.Group("/favorite", _favoriteMw()...)
_favorite.POST("/action/", append(_favorite_ctionMw(), api.FavoriteAction)...)
_favorite.GET("/list/", append(_favoritelistMw(), api.FavoriteList)...)
}
{
_message := _douyin.Group("/message", _messageMw()...)
_message.POST("/action/", append(_message_ctionMw(), api.MessageAction)...)
_message.GET("/chat/", append(_messagechatMw(), api.MessageChat)...)
}
{
_publish := _douyin.Group("/publish", _publishMw()...)
_publish.POST("/action/", append(_publish_ctionMw(), api.PublishAction)...)
_publish.GET("/list/", append(_publishlistMw(), api.PublishList)...)
}
{
_relation := _douyin.Group("/relation", _relationMw()...)
_relation.POST("/action/", append(_relation_ctionMw(), api.RelationAction)...)
{
_follow := _relation.Group("/follow", _followMw()...)
_follow.GET("/list/", append(_relationfollowlistMw(), api.RelationFollowList)...)
}
{
_follower := _relation.Group("/follower", _followerMw()...)
_follower.GET("/list/", append(_relationfollowerlistMw(), api.RelationFollowerList)...)
}
{
_friend := _relation.Group("/friend", _friendMw()...)
_friend.GET("/list/", append(_relationfriendlistMw(), api.RelationFriendList)...)
}
}
{
_user := _douyin.Group("/user", _userMw()...)
_user.POST("/login/", append(_loginMw(), initialize.AuthMiddleware.LoginHandler)...)
_user.POST("/register/", append(_registerMw(), api.Register)...)
}
}
}

160
applications/api/biz/router/api/middleware.go

@ -0,0 +1,160 @@
// Code generated by hertz generator.
package Api
import (
middleware2 "github.com/TremblingV5/DouTok/applications/api/initialize"
"github.com/TremblingV5/DouTok/pkg/middleware"
"github.com/cloudwego/hertz/pkg/app"
)
func rootMw() []app.HandlerFunc {
// your code...
return []app.HandlerFunc{
middleware.Recovery(),
middleware.CacheAPIRequest(),
}
}
func _douyinMw() []app.HandlerFunc {
// your code...
return nil
}
func _getuserfeedMw() []app.HandlerFunc {
// your code...
return []app.HandlerFunc{
middleware2.AuthMiddleware.MiddlewareFunc(),
}
}
func _getuserbyidMw() []app.HandlerFunc {
// your code...
return nil
}
func _commentMw() []app.HandlerFunc {
// your code...
return []app.HandlerFunc{
middleware2.AuthMiddleware.MiddlewareFunc(),
}
}
func _comment_ctionMw() []app.HandlerFunc {
// your code...
return nil
}
func _commentlistMw() []app.HandlerFunc {
// your code...
return nil
}
func _favoriteMw() []app.HandlerFunc {
// your code...
return []app.HandlerFunc{
middleware2.AuthMiddleware.MiddlewareFunc(),
}
}
func _favorite_ctionMw() []app.HandlerFunc {
// your code...
return nil
}
func _favoritelistMw() []app.HandlerFunc {
// your code...
return nil
}
func _messageMw() []app.HandlerFunc {
//your code...
return []app.HandlerFunc{
middleware2.AuthMiddleware.MiddlewareFunc(),
}
return nil
}
func _message_ctionMw() []app.HandlerFunc {
// your code...
return nil
}
func _messagechatMw() []app.HandlerFunc {
// your code...
return nil
}
func _publishMw() []app.HandlerFunc {
// your code...
return []app.HandlerFunc{
middleware2.AuthMiddleware.MiddlewareFunc(),
}
}
func _publish_ctionMw() []app.HandlerFunc {
// your code...
return nil
}
func _publishlistMw() []app.HandlerFunc {
// your code...
return nil
}
func _relationMw() []app.HandlerFunc {
// your code...
return []app.HandlerFunc{
middleware2.AuthMiddleware.MiddlewareFunc(),
}
}
func _relation_ctionMw() []app.HandlerFunc {
// your code...
return nil
}
func _followMw() []app.HandlerFunc {
// your code...
return nil
}
func _relationfollowlistMw() []app.HandlerFunc {
// your code...
return nil
}
func _followerMw() []app.HandlerFunc {
// your code...
return nil
}
func _relationfollowerlistMw() []app.HandlerFunc {
// your code...
return nil
}
func _friendMw() []app.HandlerFunc {
// your code...
return nil
}
func _relationfriendlistMw() []app.HandlerFunc {
// your code...
return nil
}
func _userMw() []app.HandlerFunc {
// your code...
return nil
}
func _loginMw() []app.HandlerFunc {
// your code...
return nil
}
func _registerMw() []app.HandlerFunc {
// your code...
return nil
}

14
applications/api/biz/router/register.go

@ -0,0 +1,14 @@
// Code generated by hertz generator. DO NOT EDIT.
package router
import (
api "github.com/TremblingV5/DouTok/applications/api/biz/router/api"
"github.com/cloudwego/hertz/pkg/app/server"
)
// GeneratedRegister registers routers generated by IDL.
func GeneratedRegister(r *server.Hertz) {
//INSERT_POINT: DO NOT DELETE THIS LINE!
api.Register(r)
}

164
applications/api/chat/client.go

@ -0,0 +1,164 @@
package chat
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/common/hlog"
"github.com/hertz-contrib/websocket"
"github.com/jellydator/ttlcache/v2"
"github.com/TremblingV5/DouTok/applications/api/biz/handler"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/TremblingV5/DouTok/kitex_gen/message"
"github.com/TremblingV5/DouTok/pkg/errno"
)
const (
// Time allowed to write a message to the peer.
// writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
// pongWait = 60 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
// pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
// maxMessageSize = 512
)
var (
// newline = []byte{'\n'}
// space = []byte{' '}
upgrader = websocket.HertzUpgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(ctx *app.RequestContext) bool {
return true
},
}
)
// Client is a middleman between the websocket connection and the hub.
type Client struct {
// The websocket connection.
conn *websocket.Conn
// to user id
userId string
}
type ClientMsg struct {
UserId int64 `json:"user_id"`
ToUserId int64 `json:"to_user_id"`
MsgContent string `json:"msg_content"`
}
type ServerMsg struct {
FromUserId int64 `json:"from_user_id"`
MsgContent string `json:"msg_content"`
}
// websocket 服务端的实现
// serveWs handles websocket requests from the peer.
func ServeWs(ctx context.Context, c *app.RequestContext) {
err := upgrader.Upgrade(c, func(conn *websocket.Conn) {
// 注册 client
for {
_, msg, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
// TODO 这里消息编解码可能有问题,需要考虑到客户端的处理方式
clientMsg := ClientMsg{}
if err := json.Unmarshal(msg, &clientMsg); err != nil {
continue
}
clientFrom, err := hub.clients.Get(fmt.Sprint(clientMsg.UserId))
if errors.Is(err, ttlcache.ErrNotFound) {
// 注册 client
client := &Client{conn: conn, userId: fmt.Sprint(clientMsg.UserId)}
hub.register <- client
} else {
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
}
// 向 message 模块发送消息
resp, err := rpc.MessageAction(ctx, rpc.MessageClient, &message.DouyinMessageActionRequest{
ToUserId: clientMsg.ToUserId,
ActionType: 1,
Content: clientMsg.MsgContent,
})
if err != nil {
handler.SendResponse(c, handler.BuildMessageActionResp(errno.ConvertErr(err)))
return
}
// 获取 B 用户的连接并发送消息
clientTo, err := hub.clients.Get(fmt.Sprint(clientMsg.ToUserId))
if errors.Is(err, ttlcache.ErrNotFound) {
// B 不在线
handler.SendResponse(c, handler.BuildMessageActionResp(errno.Success))
return
} else {
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
hlog.Info("error: %v", err)
}
break
} else {
// B 在线
serverMsg := ServerMsg{
FromUserId: clientMsg.UserId,
MsgContent: clientMsg.MsgContent,
}
data, err := json.Marshal(serverMsg)
if err != nil {
handler.SendResponse(c, handler.BuildMessageActionResp(errno.ConvertErr(err)))
return
}
if err := clientTo.(Client).conn.WriteMessage(websocket.TextMessage, data); err != nil {
return
}
}
}
// 返回 websocket 响应
data, err := json.Marshal(resp)
if err != nil {
handler.SendResponse(c, handler.BuildMessageActionResp(errno.ConvertErr(err)))
return
}
if err := clientFrom.(Client).conn.WriteMessage(websocket.TextMessage, data); err != nil {
return
}
}
})
if err != nil {
handler.SendResponse(c, handler.BuildMessageActionResp(errno.ConvertErr(err)))
}
}
// 实现 socket 消息推送中心
func SocketChat(ctx context.Context, c *app.RequestContext) {
/**
conn 等价于最初 hertz-server 握手之后创建的 conn
如果能够维护一个map在保证对应conn在keep-alive期间是可以实现消息的转发操作的
是否有必要
*/
//conn := c.GetConn()
}

66
applications/api/chat/hub.go

@ -0,0 +1,66 @@
package chat
import (
"sync"
"time"
"github.com/jellydator/ttlcache/v2"
)
// Hub maintains the set of active clients and send messages to the client
type Hub struct {
// Registered clients.
clients *ttlcache.Cache
clientsLock sync.RWMutex
// Register requests from the clients.
register chan *Client
// Unregister requests from clients.
unregister chan *Client
}
var hub = newHub()
func newHub() *Hub {
return &Hub{
clients: ttlcache.NewCache(),
register: make(chan *Client),
unregister: make(chan *Client),
}
}
func (h *Hub) run() { //nolint
for {
select {
case client := <-h.register:
if err := h.Register(client.userId, client); err != nil {
continue
}
case client := <-h.unregister:
if err := h.Unregister(client.userId); err != nil {
continue
}
}
}
}
func (h *Hub) Register(key string, client *Client) error {
return h.AddClient(key, client)
}
func (h *Hub) AddClient(key string, client *Client) error {
h.clientsLock.Lock()
defer h.clientsLock.Unlock()
return h.clients.SetWithTTL(key, client, time.Hour)
}
func (h *Hub) Unregister(key string) error {
return h.DelClient(key)
}
func (h *Hub) DelClient(key string) error {
h.clientsLock.Lock()
defer h.clientsLock.Unlock()
return h.clients.Remove(key)
}

1537
applications/api/docs/docs.go

File diff suppressed because it is too large

1517
applications/api/docs/swagger.json

File diff suppressed because it is too large

192
applications/api/initialize/init_hertz.go

@ -0,0 +1,192 @@
package initialize
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"github.com/TremblingV5/DouTok/pkg/constants"
"github.com/TremblingV5/DouTok/pkg/dtnetwork"
"time"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/app/server/registry"
"github.com/cloudwego/hertz/pkg/common/config"
"github.com/cloudwego/hertz/pkg/common/hlog"
"github.com/cloudwego/hertz/pkg/common/utils"
"github.com/hertz-contrib/gzip"
h2config "github.com/hertz-contrib/http2/config"
"github.com/hertz-contrib/http2/factory"
"github.com/hertz-contrib/obs-opentelemetry/provider"
hertztracing "github.com/hertz-contrib/obs-opentelemetry/tracing"
"github.com/hertz-contrib/registry/etcd"
)
var (
ServiceAddr string
EtcdAddress string
hertzCfg HertzCfg
)
type HertzCfg struct {
UseNetpoll bool `json:"UseNetpoll" yaml:"UseNetpoll"`
Http2 Http2 `json:"Http2" yaml:"Http2"`
Tls Tls `json:"Tls" yaml:"Tls"`
}
type Tls struct {
Enable bool `json:"Enable" yaml:"Enable"`
Cfg tls.Config
Cert string `json:"CertFile" yaml:"CertFile"`
Key string `json:"KeyFile" yaml:"KeyFile"`
ALPN bool `json:"ALPN" yaml:"ALPN"`
}
type Http2 struct {
Enable bool `json:"Enable" yaml:"Enable"`
DisableKeepalive bool `json:"DisableKeepalive" yaml:"DisableKeepalive"`
ReadTimeout Duration `json:"ReadTimeout" yaml:"ReadTimeout"`
}
type Duration struct {
time.Duration
}
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
d.Duration = time.Duration(value)
return nil
case string:
var err error
d.Duration, err = time.ParseDuration(value)
if err != nil {
return err
}
return nil
default:
return errors.New("invalid duration")
}
}
// 初始化 API 配置
func Init() {
InitViper()
rpc.InitRPC()
InitJwt()
InitRedisClient()
}
func InitHertzCfg() {
ServiceAddr = fmt.Sprintf("%s:%d", ViperConfig.Viper.GetString("Server.Address"), ViperConfig.Viper.GetInt("Server.Port"))
EtcdAddress = fmt.Sprintf("%s:%d", ViperConfig.Viper.GetString("Etcd.Address"), ViperConfig.Viper.GetInt("Etcd.Port"))
hertzV, err := json.Marshal(ViperConfig.Viper.Sub("Hertz").AllSettings())
if err != nil {
hlog.Fatalf("Error marshalling Hertz config %s", err)
}
if err := json.Unmarshal(hertzV, &hertzCfg); err != nil {
hlog.Fatalf("Error unmarshalling Hertz config %s", err)
}
}
// 初始化 Hertz
func InitHertz() (*server.Hertz, func()) {
InitHertzCfg()
opts := []config.Option{server.WithHostPorts(ServiceAddr)}
opts = append(opts, server.WithMaxRequestBodySize(1024*1024*1024))
// 服务注册
if ViperConfig.Viper.GetBool("Etcd.Enable") {
r, err := etcd.NewEtcdRegistry([]string{EtcdAddress})
if err != nil {
hlog.Fatal(err)
}
opts = append(opts, server.WithRegistry(r, &registry.Info{
ServiceName: constants.ApiServerName,
Addr: utils.NewNetAddr("tcp", ServiceAddr),
Weight: 10,
Tags: nil,
}))
}
var p provider.OtelProvider
if ViperConfig.Viper.GetBool("Otel.Enable") {
//链路追踪
p = provider.NewOpenTelemetryProvider(
provider.WithServiceName(constants.ApiServerName),
provider.WithExportEndpoint(fmt.Sprintf("%s:%s", ViperConfig.Viper.GetString("Otel.Host"), ViperConfig.Viper.GetString("Otel.Port"))),
provider.WithInsecure(),
)
}
tracer, tracerCfg := hertztracing.NewServerTracer()
opts = append(opts, tracer)
// 网络库
hertzNet := dtnetwork.GetTransporter(hertzCfg.UseNetpoll)
opts = append(opts, server.WithTransport(hertzNet))
// TLS & Http2
tlsEnable := hertzCfg.Tls.Enable
h2Enable := hertzCfg.Http2.Enable
hertzCfg.Tls.Cfg = tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
},
}
if tlsEnable {
cert, err := tls.LoadX509KeyPair(hertzCfg.Tls.Cert, hertzCfg.Tls.Key)
if err != nil {
hlog.Error(err)
}
hertzCfg.Tls.Cfg.Certificates = append(hertzCfg.Tls.Cfg.Certificates, cert)
opts = append(opts, server.WithTLS(&hertzCfg.Tls.Cfg))
if alpn := hertzCfg.Tls.ALPN; alpn {
opts = append(opts, server.WithALPN(alpn))
}
} else if h2Enable {
opts = append(opts, server.WithH2C(h2Enable))
}
// Hertz
h := server.Default(opts...)
h.Use(gzip.Gzip(gzip.DefaultCompression),
hertztracing.ServerMiddleware(tracerCfg))
// Protocol
if h2Enable {
h.AddProtocol("h2", factory.NewServerFactory(
h2config.WithReadTimeout(hertzCfg.Http2.ReadTimeout.Duration),
h2config.WithDisableKeepAlive(hertzCfg.Http2.DisableKeepalive)))
if tlsEnable {
hertzCfg.Tls.Cfg.NextProtos = append(hertzCfg.Tls.Cfg.NextProtos, "h2")
}
}
if ViperConfig.Viper.GetBool("Otel.Enable") {
return h, func() {
}
}
return h, func() {
_ = p.Shutdown(context.Background()) //nolint
}
}

121
applications/api/initialize/jwt.go

@ -0,0 +1,121 @@
package initialize
import (
"context"
"github.com/cloudwego/hertz/pkg/protocol"
"strings"
"time"
"github.com/hertz-contrib/jwt"
"github.com/TremblingV5/DouTok/applications/api/biz/model/api"
"github.com/TremblingV5/DouTok/applications/api/initialize/rpc"
"github.com/TremblingV5/DouTok/kitex_gen/user"
"github.com/TremblingV5/DouTok/pkg/constants"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
var AuthMiddleware *jwt.HertzJWTMiddleware
type LoginResp struct {
StatusCode int `json:"status_code"`
StatusMsg string `json:"status_msg"`
UserId int64 `json:"user_id,string"`
Token string `json:"token"`
}
func InitJwt() {
AuthMiddleware, _ = jwt.New(&jwt.HertzJWTMiddleware{
Key: []byte(ViperConfig.Viper.GetString("JWT.signingKey")),
Timeout: 12 * time.Hour,
MaxRefresh: time.Hour,
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(int64); ok {
return jwt.MapClaims{
constants.IdentityKey: v,
}
}
return jwt.MapClaims{}
},
HTTPStatusMessageFunc: func(e error, ctx context.Context, c *app.RequestContext) string {
switch e.(type) { //nolint
case errno.ErrNo:
return e.(errno.ErrNo).ErrMsg //nolint
default:
return e.Error()
}
},
LoginResponse: func(ctx context.Context, c *app.RequestContext, code int, token string, expire time.Time) {
claims := jwt.ExtractClaims(ctx, c)
// Long 型数据在返回前端时会失真,需使用string类型
//userId := strconv.FormatInt(claims[constants.IdentityKey].(int64), 10)
c.SetCookie("token", token, 24*60*60, "/", "", protocol.CookieSameSiteDefaultMode, false, true)
userId := claims[constants.IdentityKey].(int64)
c.JSON(consts.StatusOK, LoginResp{
StatusCode: errno.SuccessCode,
StatusMsg: errno.Success.ErrMsg,
UserId: userId,
Token: token,
})
},
WithNext: func(ctx context.Context, c *app.RequestContext) bool {
if strings.Contains(string(c.Request.Path()), "feed") {
var req api.DouyinFeedRequest
err := c.BindAndValidate(&req)
if err == nil && req.Token == "" {
return true
}
return false
}
//if strings.Contains(string(c.Request.Path()), "publish") {
// tokenInForm := string(c.FormValue("token"))
//
// if tokenInForm != "" {
// jwt1.Parse(tokenInForm, func(token *jwt1.Token) (interface{}, error) {
// if _, ok := token.Method.(*jwt1.SigningMethodRSA); !ok {
// return nil, errors.New("unsupported signing method")
// }
// return
// })
// return true
// }
//
// return false
//}
return false
},
IdentityKey: constants.IdentityKey,
IdentityHandler: func(ctx context.Context, c *app.RequestContext) interface{} {
claims := jwt.ExtractClaims(ctx, c)
return claims[constants.IdentityKey].(float64)
},
Unauthorized: func(ctx context.Context, c *app.RequestContext, code int, message string) {
c.JSON(code, map[string]interface{}{
"status_code": errno.AuthorizationFailedErrCode,
"status_msg": message,
})
},
Authenticator: func(ctx context.Context, c *app.RequestContext) (interface{}, error) {
var loginVar api.DouyinUserLoginRequest
if err := c.Bind(&loginVar); err != nil {
return "", jwt.ErrMissingLoginValues
}
if len(loginVar.Username) == 0 || len(loginVar.Password) == 0 {
return "", jwt.ErrMissingLoginValues
}
userId, err := rpc.Login(context.Background(), rpc.UserClient, &user.DouyinUserLoginRequest{Username: loginVar.Username, Password: loginVar.Password})
if err == nil && userId != 0 {
c.Set("JWT_PAYLOAD", jwt.MapClaims{
constants.IdentityKey: userId,
})
}
return userId, err
},
TokenLookup: "query: token, form: token, param: token, cookie: token",
TimeFunc: time.Now,
})
}

24
applications/api/initialize/redis.go

@ -0,0 +1,24 @@
package initialize
import (
"fmt"
redishandle "github.com/TremblingV5/DouTok/pkg/redisHandle"
"github.com/go-redis/redis/v8"
)
var (
RedisClient *redis.Client
)
func InitRedisClient() {
Client, err := redishandle.InitRedisClient(
fmt.Sprintf("%s:%d", ViperConfig.Viper.GetString("Redis.Host"), ViperConfig.Viper.GetInt("Redis.Port")),
ViperConfig.Viper.GetString("Redis.Password"),
ViperConfig.Viper.GetInt("Redis.Databases.Default"),
)
if err != nil {
panic(err)
}
RedisClient = Client
}

87
applications/api/initialize/rpc/comment.go

@ -0,0 +1,87 @@
package rpc
import (
"context"
"fmt"
"runtime"
"time"
"github.com/TremblingV5/DouTok/kitex_gen/comment"
"github.com/TremblingV5/DouTok/kitex_gen/comment/commentservice"
"github.com/TremblingV5/DouTok/pkg/dtviper"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/TremblingV5/DouTok/pkg/middleware"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/pkg/remote/trans/gonet"
"github.com/cloudwego/kitex/pkg/retry"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/kitex-contrib/obs-opentelemetry/tracing"
etcd "github.com/kitex-contrib/registry-etcd"
)
var CommentClient commentservice.Client
// Comment RPC 客户端初始化
func initCommentRpc(Config *dtviper.Config) {
EtcdAddress := fmt.Sprintf("%s:%d", Config.Viper.GetString("Etcd.Address"), Config.Viper.GetInt("Etcd.Port"))
r, err := etcd.NewEtcdResolver([]string{EtcdAddress})
if err != nil {
panic(err)
}
ServiceName := Config.Viper.GetString("Server.Name")
//p := provider.NewOpenTelemetryProvider(
// provider.WithServiceName(ServiceName),
// provider.WithExportEndpoint("localhost:4317"),
// provider.WithInsecure(),
//)
//defer p.Shutdown(context.Background())
options := []client.Option{
client.WithMiddleware(middleware.CommonMiddleware),
client.WithInstanceMW(middleware.ClientMiddleware),
client.WithRPCTimeout(30 * time.Second), // rpc timeout
client.WithConnectTimeout(30000 * time.Millisecond), // conn timeout
client.WithFailureRetry(retry.NewFailurePolicy()), // retry
client.WithSuite(tracing.NewClientSuite()), // tracer
client.WithResolver(r), // resolver
// Please keep the same as provider.WithServiceName
client.WithClientBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: ServiceName}),
}
if runtime.GOOS == "windows" {
options = append(options, client.WithTransHandlerFactory(gonet.NewCliTransHandlerFactory()))
} else {
options = append(options, client.WithMuxConnection(1)) // mux
}
c, err := commentservice.NewClient(
ServiceName,
options...,
)
if err != nil {
panic(err)
}
CommentClient = c
}
// 传递 评论操作 的上下文, 并获取 RPC Server 端的响应.
func CommentAction(ctx context.Context, commentClient commentservice.Client, req *comment.DouyinCommentActionRequest) (resp *comment.DouyinCommentActionResponse, err error) {
resp, err = commentClient.CommentAction(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}
// 传递 获取评论列表操作 的上下文, 并获取 RPC Server 端的响应.
func CommentList(ctx context.Context, commentClient commentservice.Client, req *comment.DouyinCommentListRequest) (resp *comment.DouyinCommentListResponse, err error) {
resp, err = commentClient.CommentList(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}

87
applications/api/initialize/rpc/favorite.go

@ -0,0 +1,87 @@
package rpc
import (
"context"
"fmt"
"runtime"
"time"
"github.com/TremblingV5/DouTok/kitex_gen/favorite"
"github.com/TremblingV5/DouTok/kitex_gen/favorite/favoriteservice"
"github.com/TremblingV5/DouTok/pkg/dtviper"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/TremblingV5/DouTok/pkg/middleware"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/pkg/remote/trans/gonet"
"github.com/cloudwego/kitex/pkg/retry"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/kitex-contrib/obs-opentelemetry/tracing"
etcd "github.com/kitex-contrib/registry-etcd"
)
var FavoriteClient favoriteservice.Client
// Favorite RPC 客户端初始化
func initFavoriteRpc(Config *dtviper.Config) {
EtcdAddress := fmt.Sprintf("%s:%d", Config.Viper.GetString("Etcd.Address"), Config.Viper.GetInt("Etcd.Port"))
r, err := etcd.NewEtcdResolver([]string{EtcdAddress})
if err != nil {
panic(err)
}
ServiceName := Config.Viper.GetString("Server.Name")
//p := provider.NewOpenTelemetryProvider(
// provider.WithServiceName(ServiceName),
// provider.WithExportEndpoint("localhost:4317"),
// provider.WithInsecure(),
//)
//defer p.Shutdown(context.Background())
options := []client.Option{
client.WithMiddleware(middleware.CommonMiddleware),
client.WithInstanceMW(middleware.ClientMiddleware),
client.WithRPCTimeout(30 * time.Second), // rpc timeout
client.WithConnectTimeout(30000 * time.Millisecond), // conn timeout
client.WithFailureRetry(retry.NewFailurePolicy()), // retry
client.WithSuite(tracing.NewClientSuite()), // tracer
client.WithResolver(r), // resolver
// Please keep the same as provider.WithServiceName
client.WithClientBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: ServiceName}),
}
if runtime.GOOS == "windows" {
options = append(options, client.WithTransHandlerFactory(gonet.NewCliTransHandlerFactory()))
} else {
options = append(options, client.WithMuxConnection(1)) // mux
}
c, err := favoriteservice.NewClient(
ServiceName,
options...,
)
if err != nil {
panic(err)
}
FavoriteClient = c
}
// 传递 点赞操作 的上下文, 并获取 RPC Server 端的响应.
func FavoriteAction(ctx context.Context, favoriteClient favoriteservice.Client, req *favorite.DouyinFavoriteActionRequest) (resp *favorite.DouyinFavoriteActionResponse, err error) {
resp, err = favoriteClient.FavoriteAction(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}
// 传递 获取点赞列表操作 的上下文, 并获取 RPC Server 端的响应.
func FavoriteList(ctx context.Context, favoriteClient favoriteservice.Client, req *favorite.DouyinFavoriteListRequest) (resp *favorite.DouyinFavoriteListResponse, err error) {
resp, err = favoriteClient.FavoriteList(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}

75
applications/api/initialize/rpc/feed.go

@ -0,0 +1,75 @@
package rpc
import (
"context"
"fmt"
"runtime"
"time"
"github.com/TremblingV5/DouTok/kitex_gen/feed"
"github.com/TremblingV5/DouTok/kitex_gen/feed/feedservice"
"github.com/TremblingV5/DouTok/pkg/dtviper"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/TremblingV5/DouTok/pkg/middleware"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/pkg/remote/trans/gonet"
"github.com/cloudwego/kitex/pkg/retry"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/kitex-contrib/obs-opentelemetry/tracing"
etcd "github.com/kitex-contrib/registry-etcd"
)
var FeedClient feedservice.Client
// Feed RPC 客户端初始化
func initFeedRpc(Config *dtviper.Config) {
EtcdAddress := fmt.Sprintf("%s:%d", Config.Viper.GetString("Etcd.Address"), Config.Viper.GetInt("Etcd.Port"))
r, err := etcd.NewEtcdResolver([]string{EtcdAddress})
if err != nil {
panic(err)
}
ServiceName := Config.Viper.GetString("Server.Name")
//p := provider.NewOpenTelemetryProvider(
// provider.WithServiceName(ServiceName),
// provider.WithExportEndpoint("localhost:4317"),
// provider.WithInsecure(),
//)
//defer p.Shutdown(context.Background())
options := []client.Option{
client.WithMiddleware(middleware.CommonMiddleware),
client.WithInstanceMW(middleware.ClientMiddleware),
client.WithRPCTimeout(30 * time.Second), // rpc timeout
client.WithConnectTimeout(30000 * time.Millisecond), // conn timeout
client.WithFailureRetry(retry.NewFailurePolicy()), // retry
client.WithSuite(tracing.NewClientSuite()), // tracer
client.WithResolver(r), // resolver
// Please keep the same as provider.WithServiceName
client.WithClientBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: ServiceName}),
}
if runtime.GOOS == "windows" {
options = append(options, client.WithTransHandlerFactory(gonet.NewCliTransHandlerFactory()))
} else {
options = append(options, client.WithMuxConnection(1)) // mux
}
c, err := feedservice.NewClient(
ServiceName,
options...,
)
if err != nil {
panic(err)
}
FeedClient = c
}
// 传递 获取视频流操作 的上下文, 并获取 RPC Server 端的响应.
func GetUserFeed(ctx context.Context, feedClient feedservice.Client, req *feed.DouyinFeedRequest) (resp *feed.DouyinFeedResponse, err error) {
resp, err = feedClient.GetUserFeed(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}

29
applications/api/initialize/rpc/init.go

@ -0,0 +1,29 @@
package rpc
import (
"github.com/TremblingV5/DouTok/pkg/dtviper"
)
// InitRPC init rpc client
func InitRPC() {
UserConfig := dtviper.ConfigInit("DOUTOK_USER", "user")
initUserRpc(UserConfig)
FeedConfig := dtviper.ConfigInit("DOUTOK_FEED", "feed")
initFeedRpc(FeedConfig)
PublishConfig := dtviper.ConfigInit("DOUTOK_PUBLISH", "publish")
initPublishRpc(PublishConfig)
FavoriteConfig := dtviper.ConfigInit("DOUTOK_FAVORITE", "favorite")
initFavoriteRpc(FavoriteConfig)
CommentConfig := dtviper.ConfigInit("DOUTOK_COMMENT", "comment")
initCommentRpc(CommentConfig)
RelationConfig := dtviper.ConfigInit("DOUTOK_RELATION", "relation")
initRelationRpc(RelationConfig)
MessageConfig := dtviper.ConfigInit("DOUTOK_MESSAGE", "message")
initMessageRpc(MessageConfig)
}

87
applications/api/initialize/rpc/message.go

@ -0,0 +1,87 @@
package rpc
import (
"context"
"fmt"
"runtime"
"time"
"github.com/TremblingV5/DouTok/kitex_gen/message"
"github.com/TremblingV5/DouTok/kitex_gen/message/messageservice"
"github.com/TremblingV5/DouTok/pkg/dtviper"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/TremblingV5/DouTok/pkg/middleware"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/pkg/remote/trans/gonet"
"github.com/cloudwego/kitex/pkg/retry"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/kitex-contrib/obs-opentelemetry/tracing"
etcd "github.com/kitex-contrib/registry-etcd"
)
var MessageClient messageservice.Client
// Comment RPC 客户端初始化
func initMessageRpc(Config *dtviper.Config) {
EtcdAddress := fmt.Sprintf("%s:%d", Config.Viper.GetString("Etcd.Address"), Config.Viper.GetInt("Etcd.Port"))
r, err := etcd.NewEtcdResolver([]string{EtcdAddress})
if err != nil {
panic(err)
}
ServiceName := Config.Viper.GetString("Server.Name")
//p := provider.NewOpenTelemetryProvider(
// provider.WithServiceName(ServiceName),
// provider.WithExportEndpoint("localhost:4317"),
// provider.WithInsecure(),
//)
//defer p.Shutdown(context.Background())
options := []client.Option{
client.WithMiddleware(middleware.CommonMiddleware),
client.WithInstanceMW(middleware.ClientMiddleware),
client.WithRPCTimeout(30 * time.Second), // rpc timeout
client.WithConnectTimeout(30000 * time.Millisecond), // conn timeout
client.WithFailureRetry(retry.NewFailurePolicy()), // retry
client.WithSuite(tracing.NewClientSuite()), // tracer
client.WithResolver(r), // resolver
// Please keep the same as provider.WithServiceName
client.WithClientBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: ServiceName}),
}
if runtime.GOOS == "windows" {
options = append(options, client.WithTransHandlerFactory(gonet.NewCliTransHandlerFactory()))
} else {
options = append(options, client.WithMuxConnection(1)) // mux
}
c, err := messageservice.NewClient(
ServiceName,
options...,
)
if err != nil {
panic(err)
}
MessageClient = c
}
// 传递 发布视频操作 的上下文, 并获取 RPC Server 端的响应.
func MessageAction(ctx context.Context, messageClient messageservice.Client, req *message.DouyinMessageActionRequest) (resp *message.DouyinMessageActionResponse, err error) {
resp, err = messageClient.MessageAction(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}
// 传递 获取用户发布视频列表操作 的上下文, 并获取 RPC Server 端的响应.
func MessageChat(ctx context.Context, messageClient messageservice.Client, req *message.DouyinMessageChatRequest) (resp *message.DouyinMessageChatResponse, err error) {
resp, err = messageClient.MessageChat(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}

88
applications/api/initialize/rpc/publish.go

@ -0,0 +1,88 @@
package rpc
import (
"context"
"fmt"
"runtime"
"time"
"github.com/TremblingV5/DouTok/kitex_gen/publish"
"github.com/TremblingV5/DouTok/kitex_gen/publish/publishservice"
"github.com/TremblingV5/DouTok/pkg/dtviper"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/TremblingV5/DouTok/pkg/middleware"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/pkg/remote/trans/gonet"
"github.com/cloudwego/kitex/pkg/retry"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/kitex-contrib/obs-opentelemetry/tracing"
etcd "github.com/kitex-contrib/registry-etcd"
)
var PublishClient publishservice.Client
// Publish RPC 客户端初始化
func initPublishRpc(Config *dtviper.Config) {
EtcdAddress := fmt.Sprintf("%s:%d", Config.Viper.GetString("Etcd.Address"), Config.Viper.GetInt("Etcd.Port"))
r, err := etcd.NewEtcdResolver([]string{EtcdAddress})
if err != nil {
panic(err)
}
ServiceName := Config.Viper.GetString("Server.Name")
//p := provider.NewOpenTelemetryProvider(
// provider.WithServiceName(ServiceName),
// provider.WithExportEndpoint("localhost:4317"),
// provider.WithInsecure(),
//)
//defer p.Shutdown(context.Background())
options := []client.Option{
client.WithMiddleware(middleware.CommonMiddleware),
client.WithInstanceMW(middleware.ClientMiddleware),
client.WithRPCTimeout(30 * time.Second), // rpc timeout
client.WithConnectTimeout(30000 * time.Millisecond), // conn timeout
client.WithFailureRetry(retry.NewFailurePolicy()), // retry
client.WithSuite(tracing.NewClientSuite()), // tracer
client.WithResolver(r), // resolver
// Please keep the same as provider.WithServiceName
client.WithClientBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: ServiceName}),
}
if runtime.GOOS == "windows" {
options = append(options, client.WithTransHandlerFactory(gonet.NewCliTransHandlerFactory()))
} else {
options = append(options, client.WithMuxConnection(1)) // mux
}
c, err := publishservice.NewClient(
ServiceName,
options...,
)
if err != nil {
panic(err)
}
PublishClient = c
}
// 传递 发布视频操作 的上下文, 并获取 RPC Server 端的响应.
func PublishAction(ctx context.Context, publishClient publishservice.Client, req *publish.DouyinPublishActionRequest) (resp *publish.DouyinPublishActionResponse, err error) {
resp, err = publishClient.PublishAction(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}
// 传递 获取用户发布视频列表操作 的上下文, 并获取 RPC Server 端的响应.
func PublishList(ctx context.Context, publishClient publishservice.Client, req *publish.DouyinPublishListRequest) (resp *publish.DouyinPublishListResponse, err error) {
resp, err = publishClient.PublishList(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}

111
applications/api/initialize/rpc/relation.go

@ -0,0 +1,111 @@
package rpc
import (
"context"
"fmt"
"runtime"
"time"
"github.com/TremblingV5/DouTok/kitex_gen/relation"
"github.com/TremblingV5/DouTok/kitex_gen/relation/relationservice"
"github.com/TremblingV5/DouTok/pkg/dtviper"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/TremblingV5/DouTok/pkg/middleware"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/pkg/remote/trans/gonet"
"github.com/cloudwego/kitex/pkg/retry"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/kitex-contrib/obs-opentelemetry/tracing"
etcd "github.com/kitex-contrib/registry-etcd"
)
var RelationClient relationservice.Client
// Relation RPC 客户端初始化
func initRelationRpc(Config *dtviper.Config) {
EtcdAddress := fmt.Sprintf("%s:%d", Config.Viper.GetString("Etcd.Address"), Config.Viper.GetInt("Etcd.Port"))
r, err := etcd.NewEtcdResolver([]string{EtcdAddress})
if err != nil {
panic(err)
}
ServiceName := Config.Viper.GetString("Server.Name")
//p := provider.NewOpenTelemetryProvider(
// provider.WithServiceName(ServiceName),
// provider.WithExportEndpoint("localhost:4317"),
// provider.WithInsecure(),
//)
//defer p.Shutdown(context.Background())
options := []client.Option{
client.WithMiddleware(middleware.CommonMiddleware),
client.WithInstanceMW(middleware.ClientMiddleware),
client.WithRPCTimeout(30 * time.Second), // rpc timeout
client.WithConnectTimeout(30000 * time.Millisecond), // conn timeout
client.WithFailureRetry(retry.NewFailurePolicy()), // retry
client.WithSuite(tracing.NewClientSuite()), // tracer
client.WithResolver(r), // resolver
// Please keep the same as provider.WithServiceName
client.WithClientBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: ServiceName}),
}
if runtime.GOOS == "windows" {
options = append(options, client.WithTransHandlerFactory(gonet.NewCliTransHandlerFactory()))
} else {
options = append(options, client.WithMuxConnection(1)) // mux
}
c, err := relationservice.NewClient(
ServiceName,
options...,
)
if err != nil {
panic(err)
}
RelationClient = c
}
// 传递 关注操作 的上下文, 并获取 RPC Server 端的响应.
func RelationAction(ctx context.Context, relationClient relationservice.Client, req *relation.DouyinRelationActionRequest) (resp *relation.DouyinRelationActionResponse, err error) {
resp, err = relationClient.RelationAction(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}
// 传递 获取正在关注列表操作 的上下文, 并获取 RPC Server 端的响应.
func RelationFollowList(ctx context.Context, relationClient relationservice.Client, req *relation.DouyinRelationFollowListRequest) (resp *relation.DouyinRelationFollowListResponse, err error) {
resp, err = relationClient.RelationFollowList(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}
// 传递 获取粉丝列表操作 的上下文, 并获取 RPC Server 端的响应.
func RelationFollowerList(ctx context.Context, relationClient relationservice.Client, req *relation.DouyinRelationFollowerListRequest) (resp *relation.DouyinRelationFollowerListResponse, err error) {
resp, err = relationClient.RelationFollowerList(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}
// 传递 获取好友列表操作 的上下文, 并获取 RPC Server 端的响应.
func RelationFriendList(ctx context.Context, relationClient relationservice.Client, req *relation.DouyinRelationFriendListRequest) (resp *relation.DouyinRelationFriendListResponse, err error) {
resp, err = relationClient.RelationFriendList(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}

99
applications/api/initialize/rpc/user.go

@ -0,0 +1,99 @@
package rpc
import (
"context"
"fmt"
"runtime"
"time"
"github.com/TremblingV5/DouTok/kitex_gen/user"
"github.com/TremblingV5/DouTok/kitex_gen/user/userservice"
"github.com/TremblingV5/DouTok/pkg/dtviper"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/TremblingV5/DouTok/pkg/middleware"
"github.com/cloudwego/kitex/client"
"github.com/cloudwego/kitex/pkg/remote/trans/gonet"
"github.com/cloudwego/kitex/pkg/retry"
"github.com/cloudwego/kitex/pkg/rpcinfo"
"github.com/kitex-contrib/obs-opentelemetry/tracing"
etcd "github.com/kitex-contrib/registry-etcd"
)
var UserClient userservice.Client
// User RPC 客户端初始化
func initUserRpc(Config *dtviper.Config) {
EtcdAddress := fmt.Sprintf("%s:%d", Config.Viper.GetString("Etcd.Address"), Config.Viper.GetInt("Etcd.Port"))
r, err := etcd.NewEtcdResolver([]string{EtcdAddress})
if err != nil {
panic(err)
}
ServiceName := Config.Viper.GetString("Server.Name")
//p := provider.NewOpenTelemetryProvider(
// provider.WithServiceName(ServiceName),
// provider.WithExportEndpoint("localhost:4317"),
// provider.WithInsecure(),
//)
//defer p.Shutdown(context.Background())
options := []client.Option{
client.WithMiddleware(middleware.CommonMiddleware),
client.WithInstanceMW(middleware.ClientMiddleware),
client.WithRPCTimeout(30 * time.Second), // rpc timeout
client.WithConnectTimeout(30000 * time.Millisecond), // conn timeout
client.WithFailureRetry(retry.NewFailurePolicy()), // retry
client.WithSuite(tracing.NewClientSuite()), // tracer
client.WithResolver(r), // resolver
// Please keep the same as provider.WithServiceName
client.WithClientBasicInfo(&rpcinfo.EndpointBasicInfo{ServiceName: ServiceName}),
}
if runtime.GOOS == "windows" {
options = append(options, client.WithTransHandlerFactory(gonet.NewCliTransHandlerFactory()))
} else {
options = append(options, client.WithMuxConnection(1)) // mux
}
c, err := userservice.NewClient(
ServiceName,
options...,
)
if err != nil {
panic(err)
}
UserClient = c
}
// 传递 注册操作 的上下文, 并获取 RPC Server 端的响应.
func Register(ctx context.Context, userClient userservice.Client, req *user.DouyinUserRegisterRequest) (resp *user.DouyinUserRegisterResponse, err error) {
resp, err = userClient.Register(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}
// 传递 登录操作 的上下文, 并获取 RPC Server 端的响应.
func Login(ctx context.Context, userClient userservice.Client, req *user.DouyinUserLoginRequest) (int64, error) {
resp, err := userClient.Login(ctx, req)
if err != nil {
return 0, err
}
if resp.StatusCode != 0 {
return 0, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp.UserId, nil
}
// 传递 获取用户信息操作 的上下文, 并获取 RPC Server 端的响应.
func GetUserById(ctx context.Context, userClient userservice.Client, req *user.DouyinUserRequest) (resp *user.DouyinUserResponse, err error) {
resp, err = userClient.GetUserById(ctx, req)
if err != nil {
return nil, err
}
if resp.StatusCode != 0 {
return nil, errno.NewErrNo(int(resp.StatusCode), resp.StatusMsg)
}
return resp, nil
}

11
applications/api/initialize/viper.go

@ -0,0 +1,11 @@
package initialize
import "github.com/TremblingV5/DouTok/pkg/dtviper"
var (
ViperConfig *dtviper.Config
)
func InitViper() {
ViperConfig = dtviper.ConfigInit("DOUTOK_API", "api")
}

53
applications/api/main.go

@ -0,0 +1,53 @@
// Code generated by hertz generator.
package main
import (
"fmt"
_ "github.com/TremblingV5/DouTok/applications/api/docs"
"github.com/TremblingV5/DouTok/applications/api/initialize"
"github.com/TremblingV5/DouTok/pkg/dlog"
"github.com/cloudwego/hertz/pkg/common/hlog"
"github.com/hertz-contrib/pprof"
"github.com/hertz-contrib/swagger"
swaggerFiles "github.com/swaggo/files"
)
// @title DouTokApi
// @version 1.0
// @description DouTok 项目后端
// @contact.name DouTok
// @contact.url https://github.com/TremblingV5/DouTok
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8088
// @BasePath /
// @schemes http
//
// 初始化 Hertz API 及 Router
func main() {
logger := dlog.InitHertzLog(3)
defer logger.Sync()
hlog.SetLogger(logger)
initialize.Init()
h, shutdown := initialize.InitHertz()
defer shutdown()
pprof.Register(h)
register(h)
url := swagger.URL("http://localhost:8088/swagger/doc.json") // The url pointing to API definition
h.GET("/swagger/*any", swagger.WrapHandler(swaggerFiles.Handler, url))
fmt.Println("http://localhost:8088/swagger/index.html")
h.Spin()
}

24
applications/api/router.go

@ -0,0 +1,24 @@
// Code generated by hertz generator.
package main
import (
"context"
handler "github.com/TremblingV5/DouTok/applications/api/biz/handler"
"github.com/cloudwego/hertz/pkg/app"
"github.com/cloudwego/hertz/pkg/app/server"
"github.com/cloudwego/hertz/pkg/protocol/consts"
)
// customizeRegister registers customize routers.
func customizedRegister(r *server.Hertz) {
r.GET("/ping", handler.Ping)
// your code ...
r.NoRoute(func(ctx context.Context, c *app.RequestContext) { // used for HTTP 404
c.String(consts.StatusOK, "no route")
})
r.NoMethod(func(ctx context.Context, c *app.RequestContext) { // used for HTTP 405
c.String(consts.StatusOK, "no method")
})
}

16
applications/api/router_gen.go

@ -0,0 +1,16 @@
// Code generated by hertz generator. DO NOT EDIT.
package main
import (
router "github.com/TremblingV5/DouTok/applications/api/biz/router"
"github.com/cloudwego/hertz/pkg/app/server"
)
// register registers all routers.
func register(r *server.Hertz) {
router.GeneratedRegister(r)
customizedRegister(r)
}

2
applications/comment/Makefile

@ -0,0 +1,2 @@
server:
kitex -module github.com/TremblingV5/DouTok -type protobuf -I ../../proto/ -service comment ../../proto/comment.proto

5
applications/comment/README.md

@ -0,0 +1,5 @@
# Comment
Different with other modules, this module is used to as an experimental unit of DDD.
Because all contributors of DouTok now are not familiar with DDD, so we need to practice it in a small module.

75
applications/comment/api/comment_api/api.go

@ -0,0 +1,75 @@
package comment_api
import (
"context"
"github.com/TremblingV5/DouTok/applications/comment/infra/misc"
"github.com/TremblingV5/DouTok/applications/comment/infra/rpc"
"github.com/TremblingV5/DouTok/applications/comment/services/comment_service"
"github.com/TremblingV5/DouTok/kitex_gen/comment"
"github.com/TremblingV5/DouTok/pkg/utils"
)
type CommentServiceImpl struct {
commentService *comment_service.Service
clients *rpc.Clients
}
func New(commentService *comment_service.Service, clients *rpc.Clients) *CommentServiceImpl {
return &CommentServiceImpl{
commentService: commentService,
clients: clients,
}
}
func (s *CommentServiceImpl) CommentAction(ctx context.Context, req *comment.DouyinCommentActionRequest) (resp *comment.DouyinCommentActionResponse, err error) {
if result := CheckCommentActionArgs(req); !result {
return s.packCommentActionResp(int32(misc.ParamsErr.ErrCode), misc.ParamsErr.ErrMsg, nil, req.UserId)
}
// 判断请求的动作,1为新加评论,2为删除评论
if req.ActionType == 1 {
result, err := s.commentService.AddComment(
ctx, req.VideoId, req.UserId, utils.GetSnowFlakeId().Int64(), 0, 0, req.CommentText,
)
if err != nil {
return s.packCommentActionResp(int32(misc.SystemErr.ErrCode), misc.SystemErr.ErrMsg, nil, req.UserId)
}
return s.packCommentActionResp(int32(misc.Success.ErrCode), misc.Success.ErrMsg, result, req.UserId)
} else if req.ActionType == 2 {
errNo, err := s.commentService.RmComment(ctx, req.UserId, req.CommentId)
if err != nil {
return s.packCommentActionResp(int32(errNo.ErrCode), errNo.ErrMsg, nil, req.UserId)
}
return s.packCommentActionResp(int32(misc.Success.ErrCode), misc.Success.ErrMsg, nil, req.UserId)
} else {
return s.packCommentActionResp(int32(misc.BindingErr.ErrCode), misc.BindingErr.ErrMsg, nil, req.UserId)
}
}
func (s *CommentServiceImpl) CommentCount(ctx context.Context, req *comment.DouyinCommentCountRequest) (resp *comment.DouyinCommentCountResponse, err error) {
if len(req.VideoIdList) == 0 {
return s.packCommentCountResp(int32(misc.ListEmptyErr.ErrCode), misc.ListEmptyErr.ErrMsg, nil)
}
res, errNo, err := s.commentService.CountComment(ctx, req.VideoIdList...)
if err != nil {
return s.packCommentCountResp(int32(errNo.ErrCode), errNo.ErrMsg, nil)
}
return s.packCommentCountResp(int32(errNo.ErrCode), errNo.ErrMsg, res)
}
func (s *CommentServiceImpl) CommentList(ctx context.Context, req *comment.DouyinCommentListRequest) (resp *comment.DouyinCommentListResponse, err error) {
if !CheckCommentListArgs(req) {
return s.packCommentListResp(int32(misc.VideoIdErr.ErrCode), misc.VideoIdErr.ErrMsg, nil)
}
res, errNo, err := s.commentService.ListComment(ctx, req.VideoId)
if err != nil {
return s.packCommentListResp(int32(errNo.ErrCode), errNo.ErrMsg, nil)
}
return s.packCommentListResp(int32(misc.Success.ErrCode), misc.Success.ErrMsg, res)
}

97
applications/comment/api/comment_api/pack.go

@ -0,0 +1,97 @@
package comment_api
import (
"context"
commententity "github.com/TremblingV5/DouTok/applications/comment/domain/entity/comment"
"github.com/TremblingV5/DouTok/applications/comment/infra/misc"
"github.com/TremblingV5/DouTok/applications/comment/services/comment_service"
"github.com/TremblingV5/DouTok/kitex_gen/comment"
"github.com/TremblingV5/DouTok/kitex_gen/user"
"time"
)
func (s *CommentServiceImpl) packCommentActionResp(code int32, msg string, cmt *commententity.Entity, userId int64) (resp *comment.DouyinCommentActionResponse, err error) {
var result *comment.Comment
result.Id = cmt.Id
result.Content = cmt.Content
result.CreateDate = cmt.Timestamp
if code == int32(misc.Success.ErrCode) {
reqUser, err := s.clients.User.Client.GetUserById(context.Background(), &user.DouyinUserRequest{
UserId: userId,
})
if err != nil {
return nil, err
}
user := user.User{
Id: reqUser.User.Id,
Name: reqUser.User.Name,
Avatar: reqUser.User.Avatar,
BackgroundImage: reqUser.User.BackgroundImage,
Signature: reqUser.User.Signature,
FollowCount: reqUser.User.FollowCount,
FollowerCount: reqUser.User.FollowerCount,
}
result.User = &user
}
return &comment.DouyinCommentActionResponse{
StatusCode: code,
StatusMsg: msg,
Comment: result,
}, nil
}
func (s *CommentServiceImpl) packCommentCountResp(code int32, msg string, countList map[int64]int64) (*comment.DouyinCommentCountResponse, error) {
return &comment.DouyinCommentCountResponse{
StatusCode: code,
StatusMsg: msg,
Result: countList,
}, nil
}
func (s *CommentServiceImpl) packCommentListResp(code int32, msg string, comments []*commententity.Entity) (resp *comment.DouyinCommentListResponse, err error) {
resp = &comment.DouyinCommentListResponse{
StatusCode: code,
StatusMsg: msg,
}
var commentList []*comment.Comment
currentTime := time.Now().Unix()
for _, v := range comments {
temp := comment.Comment{
Id: v.Id,
Content: v.Content,
CreateDate: comment_service.GetTimeRecall(v.Timestamp, currentTime),
}
reqUser, err := s.clients.User.Client.GetUserById(context.Background(), &user.DouyinUserRequest{
UserId: v.UserId,
})
if err != nil {
continue
}
tempUser := user.User{
Id: reqUser.User.Id,
Name: reqUser.User.Name,
FollowCount: reqUser.User.FollowCount,
FollowerCount: reqUser.User.FollowerCount,
Avatar: reqUser.User.Avatar,
BackgroundImage: reqUser.User.BackgroundImage,
Signature: reqUser.User.Signature,
}
temp.User = &tempUser
commentList = append(commentList, &temp)
}
resp.CommentList = commentList
return resp, nil
}

11
applications/comment/api/comment_api/validate.go

@ -0,0 +1,11 @@
package comment_api
import "github.com/TremblingV5/DouTok/kitex_gen/comment"
func CheckCommentActionArgs(req *comment.DouyinCommentActionRequest) bool {
return req.CommentText != ""
}
func CheckCommentListArgs(req *comment.DouyinCommentListRequest) bool {
return req.VideoId >= 0
}

12
applications/comment/build.sh

@ -0,0 +1,12 @@
#!/usr/bin/env bash
RUN_NAME="comment"
mkdir -p output/bin
cp script/* output/
chmod +x output/bootstrap.sh
if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
go build -o output/bin/${RUN_NAME}
else
go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
fi

45
applications/comment/domain/entity/comment/check_options.go

@ -0,0 +1,45 @@
package comment
import "errors"
type EntityCheckOption func(*Entity) error
func IsIdValid() EntityCheckOption {
return func(c *Entity) error {
if c.Id == 0 {
return errors.New("comment id can't be 0")
}
return nil
}
}
func IsVideoIdValid() EntityCheckOption {
return func(c *Entity) error {
if c.VideoId == 0 {
return errors.New("comment video id can't be 0")
}
return nil
}
}
func IsUserIdValid() EntityCheckOption {
return func(c *Entity) error {
if c.UserId == 0 {
return errors.New("comment user id can't be 0")
}
return nil
}
}
func IsBelongToUser(userId int64) EntityCheckOption {
return func(c *Entity) error {
if c.UserId != userId {
return errors.New("comment is not belong to user")
}
return nil
}
}

101
applications/comment/domain/entity/comment/entity.go

@ -0,0 +1,101 @@
package comment
import (
"fmt"
"time"
"github.com/TremblingV5/DouTok/applications/comment/infra/model"
)
type Entity struct {
Id int64
VideoId int64
UserId int64
ConversationId int64
LastId int64
ToUserId int64
Content string
Timestamp string
Status bool
CreatedAt time.Time
UpdatedAt time.Time
}
func (c *Entity) check(options ...EntityCheckOption) error {
for _, option := range options {
if err := option(c); err != nil {
return err
}
}
return nil
}
func (c *Entity) isValid() error {
return c.check(
IsVideoIdValid(),
IsUserIdValid(),
)
}
func New(options ...EntityOption) (*Entity, error) {
comment := &Entity{}
for _, option := range options {
option(comment)
}
return comment, comment.isValid()
}
func (c *Entity) ToModel() *model.Comment {
return &model.Comment{
Id: c.Id,
VideoId: c.VideoId,
UserId: c.UserId,
ConversationId: c.ConversationId,
LastId: c.LastId,
ToUserId: c.ToUserId,
Content: c.Content,
Timestamp: c.Timestamp,
Status: c.Status,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
}
func (c *Entity) ToHBModel() *model.CommentInHB {
return &model.CommentInHB{
Id: []byte(fmt.Sprint(c.Id)),
VideoId: []byte(fmt.Sprint(c.VideoId)),
UserId: []byte(fmt.Sprint(c.UserId)),
ConversationId: []byte(fmt.Sprint(c.ConversationId)),
LastId: []byte(fmt.Sprint(c.LastId)),
ToUserId: []byte(fmt.Sprint(c.ToUserId)),
Content: []byte(c.Content),
Timestamp: []byte(c.Timestamp),
}
}
func (c *Entity) TransformFromModel(comment *model.Comment) {
c.Id = comment.Id
c.VideoId = comment.VideoId
c.UserId = comment.UserId
c.ConversationId = comment.ConversationId
c.LastId = comment.LastId
c.ToUserId = comment.ToUserId
c.Content = comment.Content
c.Timestamp = comment.Timestamp
c.Status = comment.Status
c.CreatedAt = comment.CreatedAt
c.UpdatedAt = comment.UpdatedAt
}
func (c *Entity) MarkAsDelete() {
c.Status = false
}
func (c *Entity) IsBelongUser(userId int64) (bool, error) {
err := c.check(IsBelongToUser(userId))
return err == nil, err
}

25
applications/comment/domain/entity/comment/list.go

@ -0,0 +1,25 @@
package comment
import (
"github.com/TremblingV5/DouTok/applications/comment/infra/model"
)
type List []*Entity
func NewListFromHBModels(models []*model.CommentInHB) List {
var result List
for _, model := range models {
result = append(result, &Entity{
Id: model.GetId(),
VideoId: model.GetVideoId(),
UserId: model.GetUserId(),
ConversationId: model.GetConversationId(),
LastId: model.GetLastId(),
ToUserId: model.GetToUserId(),
Content: model.GetContent(),
Timestamp: model.GetTimestamp(),
})
}
return result
}

71
applications/comment/domain/entity/comment/new_options.go

@ -0,0 +1,71 @@
package comment
import "time"
type EntityOption func(*Entity)
func WithId(id int64) EntityOption {
return func(c *Entity) {
c.Id = id
}
}
func WithVideoId(videoId int64) EntityOption {
return func(c *Entity) {
c.VideoId = videoId
}
}
func WithUserId(userId int64) EntityOption {
return func(c *Entity) {
c.UserId = userId
}
}
func WithConversationId(conversationId int64) EntityOption {
return func(c *Entity) {
c.ConversationId = conversationId
}
}
func WithLastId(lastId int64) EntityOption {
return func(c *Entity) {
c.LastId = lastId
}
}
func WithToUserId(toUserId int64) EntityOption {
return func(c *Entity) {
c.ToUserId = toUserId
}
}
func WithContent(content string) EntityOption {
return func(c *Entity) {
c.Content = content
}
}
func WithTimestamp(timestamp string) EntityOption {
return func(c *Entity) {
c.Timestamp = timestamp
}
}
func WithStatus(status bool) EntityOption {
return func(c *Entity) {
c.Status = status
}
}
func WithCreatedAt(createdAt time.Time) EntityOption {
return func(c *Entity) {
c.CreatedAt = createdAt
}
}
func WithUpdatedAt(updatedAt time.Time) EntityOption {
return func(c *Entity) {
c.UpdatedAt = updatedAt
}
}

15
applications/comment/domain/entity/comment_count/check_options.go

@ -0,0 +1,15 @@
package comment_count
import "errors"
type EntityCheckOption func(*Entity) error
func IsIdValid() EntityCheckOption {
return func(c *Entity) error {
if c.Id == 0 {
return errors.New("video id of comment count can't be 0")
}
return nil
}
}

55
applications/comment/domain/entity/comment_count/entity.go

@ -0,0 +1,55 @@
package comment_count
import (
"time"
"github.com/TremblingV5/DouTok/applications/comment/infra/model"
)
type Entity struct {
Id int64
Number int64
CreatedAt time.Time
UpdatedAt time.Time
}
func New(options ...Option) *Entity {
e := &Entity{}
for _, option := range options {
option(e)
}
return e
}
func TransformFromModel(model *model.CommentCount) *Entity {
return &Entity{
Id: model.Id,
Number: model.Number,
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
}
}
func (c *Entity) ToModel() *model.CommentCount {
return &model.CommentCount{
Id: c.Id,
Number: c.Number,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}
}
func (c *Entity) Check(options ...EntityCheckOption) error {
for _, option := range options {
if err := option(c); err != nil {
return err
}
}
return nil
}
func (c *Entity) UpdateNumber(number int64) {
c.Number = number
}

19
applications/comment/domain/entity/comment_count/list.go

@ -0,0 +1,19 @@
package comment_count
import "github.com/TremblingV5/DouTok/applications/comment/infra/model"
type CommentCountList []*Entity
func NewListFromModels(models []*model.CommentCount) CommentCountList {
var result CommentCountList
for _, model := range models {
result = append(result, &Entity{
Id: model.Id,
Number: model.Number,
CreatedAt: model.CreatedAt,
UpdatedAt: model.UpdatedAt,
})
}
return result
}

15
applications/comment/domain/entity/comment_count/option.go

@ -0,0 +1,15 @@
package comment_count
type Option func(*Entity)
func WithId(id int64) Option {
return func(e *Entity) {
e.Id = id
}
}
func WithNumber(number int64) Option {
return func(e *Entity) {
e.Number = number
}
}

18
applications/comment/infra/misc/config.go

@ -0,0 +1,18 @@
package misc
import "github.com/TremblingV5/DouTok/pkg/dtviper"
var Config *dtviper.Config
func InitViperConfig() {
config := dtviper.ConfigInit(ViperConfigEnvPrefix, ViperConfigEnvFilename)
Config = config
}
func GetConfig(key string) string {
return Config.Viper.GetString(key)
}
func GetConfigNum(key string) int64 {
return Config.Viper.GetInt64(key)
}

9
applications/comment/infra/misc/const.go

@ -0,0 +1,9 @@
package misc
const (
ViperConfigEnvPrefix = "DOUTOK_COMMENT"
ViperConfigEnvFilename = "comment"
ComCntCache = "comcntcache"
ComTotalCntCache = "comtotalcntcache"
)

33
applications/comment/infra/misc/err.go

@ -0,0 +1,33 @@
package misc
import "github.com/TremblingV5/DouTok/pkg/errno"
var (
NilErrCode = -1
SuccessCode = 0
ParamsErrCode = 26001
SystemErrCode = 26002
BindingErrCode = 26003
ListEmptyErrCode = 26004
VideoIdErrCode = 26005
QueryCommentCountErrCode = 26006
QueryCommentListInHBErrCode = 26007
CommentNotBelongToUserErrCode = 26008
RmDataFromHBErrCode = 26009
HBDataNotFoundErrCode = 26010
)
var (
NilErr = errno.NewErrNo(NilErrCode, "Don't care")
Success = errno.NewErrNo(SuccessCode, "Success")
ParamsErr = errno.NewErrNo(ParamsErrCode, "Invalid parameters")
SystemErr = errno.NewErrNo(SystemErrCode, "System error")
BindingErr = errno.NewErrNo(BindingErrCode, "Action type invalid")
ListEmptyErr = errno.NewErrNo(ListEmptyErrCode, "List is empty")
VideoIdErr = errno.NewErrNo(VideoIdErrCode, "Video id invalid")
QueryCommentCountErr = errno.NewErrNo(QueryCommentCountErrCode, "Query comment count error")
QueryCommentListInHBErr = errno.NewErrNo(QueryCommentListInHBErrCode, "Query comment list in HBase error")
CommentNotBelongToUserErr = errno.NewErrNo(CommentNotBelongToUserErrCode, "This comment not belong to the user")
RmDataFromHBErr = errno.NewErrNo(RmDataFromHBErrCode, "Deleta data in HBase error")
HBDataNotFoundErr = errno.NewErrNo(HBDataNotFoundErrCode, "HBase comment data not found")
)

19
applications/comment/infra/misc/row_key.go

@ -0,0 +1,19 @@
package misc
import (
"fmt"
globalMisc "github.com/TremblingV5/DouTok/pkg/misc"
)
func EnsureIdLength(id int64) string {
return globalMisc.LFill(fmt.Sprint(id), 20)
}
func GetCommentRowKey(video_id int64, is_deleted string, conversation_id int64, timestamp string) string {
return EnsureIdLength(video_id) + is_deleted + EnsureIdLength(conversation_id) + timestamp
}
func GetCommentQueryPrefix(video_id int64) string {
return EnsureIdLength(video_id) + "0"
}

17
applications/comment/infra/model/comment.go

@ -0,0 +1,17 @@
package model
import "time"
type Comment struct {
Id int64 `gorm:"primarykey"`
VideoId int64 `gorm:"index:video_id"`
UserId int64 `gorm:"index:user_id"` // 留下评论的id
ConversationId int64 `gorm:"index:con_id"`
LastId int64 // 二级回复所使用到的上一条回复的id
ToUserId int64 // 二级回复的用户id
Content string
Timestamp string
Status bool
CreatedAt time.Time
UpdatedAt time.Time
}

10
applications/comment/infra/model/comment_count.go

@ -0,0 +1,10 @@
package model
import "time"
type CommentCount struct {
Id int64 `gorm:"index:com_count_id"` // 与Comment表的Id相同
Number int64
CreatedAt time.Time
UpdatedAt time.Time
}

65
applications/comment/infra/model/comment_hb.go

@ -0,0 +1,65 @@
package model
import (
"encoding/binary"
"strconv"
)
type CommentInHB struct {
Id []byte `json:"id"`
VideoId []byte `json:"video_id"`
UserId []byte `json:"user_id"`
ConversationId []byte `json:"conversation_id"`
LastId []byte `json:"last_id"`
ToUserId []byte `json:"to_user_id"`
Content []byte `json:"content"`
Timestamp []byte `json:"timestamp"`
}
func ToInt64(data []byte) int64 {
return int64(binary.BigEndian.Uint64(data))
}
func (c *CommentInHB) GetId() int64 {
str := string(c.Id)
i, _ := strconv.ParseInt(str, 10, 64)
return i
}
func (c *CommentInHB) GetVideoId() int64 {
str := string(c.VideoId)
i, _ := strconv.ParseInt(str, 10, 64)
return i
}
func (c *CommentInHB) GetUserId() int64 {
str := string(c.UserId)
i, _ := strconv.ParseInt(str, 10, 64)
return i
}
func (c *CommentInHB) GetConversationId() int64 {
str := string(c.ConversationId)
i, _ := strconv.ParseInt(str, 10, 64)
return i
}
func (c *CommentInHB) GetLastId() int64 {
str := string(c.LastId)
i, _ := strconv.ParseInt(str, 10, 64)
return i
}
func (c *CommentInHB) GetToUserId() int64 {
str := string(c.ToUserId)
i, _ := strconv.ParseInt(str, 10, 64)
return i
}
func (c *CommentInHB) GetContent() string {
return string(c.Content)
}
func (c *CommentInHB) GetTimestamp() string {
return string(c.Timestamp)
}

395
applications/comment/infra/query/comment_counts.gen.go

@ -0,0 +1,395 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package query
import (
"context"
"github.com/TremblingV5/DouTok/applications/comment/infra/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/plugin/dbresolver"
)
func newCommentCount(db *gorm.DB, opts ...gen.DOOption) commentCount {
_commentCount := commentCount{}
_commentCount.commentCountDo.UseDB(db, opts...)
_commentCount.commentCountDo.UseModel(&model.CommentCount{})
tableName := _commentCount.commentCountDo.TableName()
_commentCount.ALL = field.NewAsterisk(tableName)
_commentCount.Id = field.NewInt64(tableName, "id")
_commentCount.Number = field.NewInt64(tableName, "number")
_commentCount.CreatedAt = field.NewTime(tableName, "created_at")
_commentCount.UpdatedAt = field.NewTime(tableName, "updated_at")
_commentCount.fillFieldMap()
return _commentCount
}
type commentCount struct {
commentCountDo
ALL field.Asterisk
Id field.Int64
Number field.Int64
CreatedAt field.Time
UpdatedAt field.Time
fieldMap map[string]field.Expr
}
func (c commentCount) Table(newTableName string) *commentCount {
c.commentCountDo.UseTable(newTableName)
return c.updateTableName(newTableName)
}
func (c commentCount) As(alias string) *commentCount {
c.commentCountDo.DO = *(c.commentCountDo.As(alias).(*gen.DO))
return c.updateTableName(alias)
}
func (c *commentCount) updateTableName(table string) *commentCount {
c.ALL = field.NewAsterisk(table)
c.Id = field.NewInt64(table, "id")
c.Number = field.NewInt64(table, "number")
c.CreatedAt = field.NewTime(table, "created_at")
c.UpdatedAt = field.NewTime(table, "updated_at")
c.fillFieldMap()
return c
}
func (c *commentCount) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
_f, ok := c.fieldMap[fieldName]
if !ok || _f == nil {
return nil, false
}
_oe, ok := _f.(field.OrderExpr)
return _oe, ok
}
func (c *commentCount) fillFieldMap() {
c.fieldMap = make(map[string]field.Expr, 4)
c.fieldMap["id"] = c.Id
c.fieldMap["number"] = c.Number
c.fieldMap["created_at"] = c.CreatedAt
c.fieldMap["updated_at"] = c.UpdatedAt
}
func (c commentCount) clone(db *gorm.DB) commentCount {
c.commentCountDo.ReplaceConnPool(db.Statement.ConnPool)
return c
}
func (c commentCount) replaceDB(db *gorm.DB) commentCount {
c.commentCountDo.ReplaceDB(db)
return c
}
type commentCountDo struct{ gen.DO }
type ICommentCountDo interface {
gen.SubQuery
Debug() ICommentCountDo
WithContext(ctx context.Context) ICommentCountDo
WithResult(fc func(tx gen.Dao)) gen.ResultInfo
ReplaceDB(db *gorm.DB)
ReadDB() ICommentCountDo
WriteDB() ICommentCountDo
As(alias string) gen.Dao
Session(config *gorm.Session) ICommentCountDo
Columns(cols ...field.Expr) gen.Columns
Clauses(conds ...clause.Expression) ICommentCountDo
Not(conds ...gen.Condition) ICommentCountDo
Or(conds ...gen.Condition) ICommentCountDo
Select(conds ...field.Expr) ICommentCountDo
Where(conds ...gen.Condition) ICommentCountDo
Order(conds ...field.Expr) ICommentCountDo
Distinct(cols ...field.Expr) ICommentCountDo
Omit(cols ...field.Expr) ICommentCountDo
Join(table schema.Tabler, on ...field.Expr) ICommentCountDo
LeftJoin(table schema.Tabler, on ...field.Expr) ICommentCountDo
RightJoin(table schema.Tabler, on ...field.Expr) ICommentCountDo
Group(cols ...field.Expr) ICommentCountDo
Having(conds ...gen.Condition) ICommentCountDo
Limit(limit int) ICommentCountDo
Offset(offset int) ICommentCountDo
Count() (count int64, err error)
Scopes(funcs ...func(gen.Dao) gen.Dao) ICommentCountDo
Unscoped() ICommentCountDo
Create(values ...*model.CommentCount) error
CreateInBatches(values []*model.CommentCount, batchSize int) error
Save(values ...*model.CommentCount) error
First() (*model.CommentCount, error)
Take() (*model.CommentCount, error)
Last() (*model.CommentCount, error)
Find() ([]*model.CommentCount, error)
FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.CommentCount, err error)
FindInBatches(result *[]*model.CommentCount, batchSize int, fc func(tx gen.Dao, batch int) error) error
Pluck(column field.Expr, dest interface{}) error
Delete(...*model.CommentCount) (info gen.ResultInfo, err error)
Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
Updates(value interface{}) (info gen.ResultInfo, err error)
UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
UpdateFrom(q gen.SubQuery) gen.Dao
Attrs(attrs ...field.AssignExpr) ICommentCountDo
Assign(attrs ...field.AssignExpr) ICommentCountDo
Joins(fields ...field.RelationField) ICommentCountDo
Preload(fields ...field.RelationField) ICommentCountDo
FirstOrInit() (*model.CommentCount, error)
FirstOrCreate() (*model.CommentCount, error)
FindByPage(offset int, limit int) (result []*model.CommentCount, count int64, err error)
ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
Scan(result interface{}) (err error)
Returning(value interface{}, columns ...string) ICommentCountDo
UnderlyingDB() *gorm.DB
schema.Tabler
}
func (c commentCountDo) Debug() ICommentCountDo {
return c.withDO(c.DO.Debug())
}
func (c commentCountDo) WithContext(ctx context.Context) ICommentCountDo {
return c.withDO(c.DO.WithContext(ctx))
}
func (c commentCountDo) ReadDB() ICommentCountDo {
return c.Clauses(dbresolver.Read)
}
func (c commentCountDo) WriteDB() ICommentCountDo {
return c.Clauses(dbresolver.Write)
}
func (c commentCountDo) Session(config *gorm.Session) ICommentCountDo {
return c.withDO(c.DO.Session(config))
}
func (c commentCountDo) Clauses(conds ...clause.Expression) ICommentCountDo {
return c.withDO(c.DO.Clauses(conds...))
}
func (c commentCountDo) Returning(value interface{}, columns ...string) ICommentCountDo {
return c.withDO(c.DO.Returning(value, columns...))
}
func (c commentCountDo) Not(conds ...gen.Condition) ICommentCountDo {
return c.withDO(c.DO.Not(conds...))
}
func (c commentCountDo) Or(conds ...gen.Condition) ICommentCountDo {
return c.withDO(c.DO.Or(conds...))
}
func (c commentCountDo) Select(conds ...field.Expr) ICommentCountDo {
return c.withDO(c.DO.Select(conds...))
}
func (c commentCountDo) Where(conds ...gen.Condition) ICommentCountDo {
return c.withDO(c.DO.Where(conds...))
}
func (c commentCountDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) ICommentCountDo {
return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
}
func (c commentCountDo) Order(conds ...field.Expr) ICommentCountDo {
return c.withDO(c.DO.Order(conds...))
}
func (c commentCountDo) Distinct(cols ...field.Expr) ICommentCountDo {
return c.withDO(c.DO.Distinct(cols...))
}
func (c commentCountDo) Omit(cols ...field.Expr) ICommentCountDo {
return c.withDO(c.DO.Omit(cols...))
}
func (c commentCountDo) Join(table schema.Tabler, on ...field.Expr) ICommentCountDo {
return c.withDO(c.DO.Join(table, on...))
}
func (c commentCountDo) LeftJoin(table schema.Tabler, on ...field.Expr) ICommentCountDo {
return c.withDO(c.DO.LeftJoin(table, on...))
}
func (c commentCountDo) RightJoin(table schema.Tabler, on ...field.Expr) ICommentCountDo {
return c.withDO(c.DO.RightJoin(table, on...))
}
func (c commentCountDo) Group(cols ...field.Expr) ICommentCountDo {
return c.withDO(c.DO.Group(cols...))
}
func (c commentCountDo) Having(conds ...gen.Condition) ICommentCountDo {
return c.withDO(c.DO.Having(conds...))
}
func (c commentCountDo) Limit(limit int) ICommentCountDo {
return c.withDO(c.DO.Limit(limit))
}
func (c commentCountDo) Offset(offset int) ICommentCountDo {
return c.withDO(c.DO.Offset(offset))
}
func (c commentCountDo) Scopes(funcs ...func(gen.Dao) gen.Dao) ICommentCountDo {
return c.withDO(c.DO.Scopes(funcs...))
}
func (c commentCountDo) Unscoped() ICommentCountDo {
return c.withDO(c.DO.Unscoped())
}
func (c commentCountDo) Create(values ...*model.CommentCount) error {
if len(values) == 0 {
return nil
}
return c.DO.Create(values)
}
func (c commentCountDo) CreateInBatches(values []*model.CommentCount, batchSize int) error {
return c.DO.CreateInBatches(values, batchSize)
}
// Save : !!! underlying implementation is different with GORM
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
func (c commentCountDo) Save(values ...*model.CommentCount) error {
if len(values) == 0 {
return nil
}
return c.DO.Save(values)
}
func (c commentCountDo) First() (*model.CommentCount, error) {
if result, err := c.DO.First(); err != nil {
return nil, err
} else {
return result.(*model.CommentCount), nil
}
}
func (c commentCountDo) Take() (*model.CommentCount, error) {
if result, err := c.DO.Take(); err != nil {
return nil, err
} else {
return result.(*model.CommentCount), nil
}
}
func (c commentCountDo) Last() (*model.CommentCount, error) {
if result, err := c.DO.Last(); err != nil {
return nil, err
} else {
return result.(*model.CommentCount), nil
}
}
func (c commentCountDo) Find() ([]*model.CommentCount, error) {
result, err := c.DO.Find()
return result.([]*model.CommentCount), err
}
func (c commentCountDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.CommentCount, err error) {
buf := make([]*model.CommentCount, 0, batchSize)
err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
defer func() { results = append(results, buf...) }()
return fc(tx, batch)
})
return results, err
}
func (c commentCountDo) FindInBatches(result *[]*model.CommentCount, batchSize int, fc func(tx gen.Dao, batch int) error) error {
return c.DO.FindInBatches(result, batchSize, fc)
}
func (c commentCountDo) Attrs(attrs ...field.AssignExpr) ICommentCountDo {
return c.withDO(c.DO.Attrs(attrs...))
}
func (c commentCountDo) Assign(attrs ...field.AssignExpr) ICommentCountDo {
return c.withDO(c.DO.Assign(attrs...))
}
func (c commentCountDo) Joins(fields ...field.RelationField) ICommentCountDo {
for _, _f := range fields {
c = *c.withDO(c.DO.Joins(_f))
}
return &c
}
func (c commentCountDo) Preload(fields ...field.RelationField) ICommentCountDo {
for _, _f := range fields {
c = *c.withDO(c.DO.Preload(_f))
}
return &c
}
func (c commentCountDo) FirstOrInit() (*model.CommentCount, error) {
if result, err := c.DO.FirstOrInit(); err != nil {
return nil, err
} else {
return result.(*model.CommentCount), nil
}
}
func (c commentCountDo) FirstOrCreate() (*model.CommentCount, error) {
if result, err := c.DO.FirstOrCreate(); err != nil {
return nil, err
} else {
return result.(*model.CommentCount), nil
}
}
func (c commentCountDo) FindByPage(offset int, limit int) (result []*model.CommentCount, count int64, err error) {
result, err = c.Offset(offset).Limit(limit).Find()
if err != nil {
return
}
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
count, err = c.Offset(-1).Limit(-1).Count()
return
}
func (c commentCountDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
count, err = c.Count()
if err != nil {
return
}
err = c.Offset(offset).Limit(limit).Scan(result)
return
}
func (c commentCountDo) Scan(result interface{}) (err error) {
return c.DO.Scan(result)
}
func (c commentCountDo) Delete(models ...*model.CommentCount) (result gen.ResultInfo, err error) {
return c.DO.Delete(models)
}
func (c *commentCountDo) withDO(do gen.Dao) *commentCountDo {
c.DO = *do.(*gen.DO)
return c
}

423
applications/comment/infra/query/comments.gen.go

@ -0,0 +1,423 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package query
import (
"context"
"github.com/TremblingV5/DouTok/applications/comment/infra/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/plugin/dbresolver"
)
func newComment(db *gorm.DB, opts ...gen.DOOption) comment {
_comment := comment{}
_comment.commentDo.UseDB(db, opts...)
_comment.commentDo.UseModel(&model.Comment{})
tableName := _comment.commentDo.TableName()
_comment.ALL = field.NewAsterisk(tableName)
_comment.Id = field.NewInt64(tableName, "id")
_comment.VideoId = field.NewInt64(tableName, "video_id")
_comment.UserId = field.NewInt64(tableName, "user_id")
_comment.ConversationId = field.NewInt64(tableName, "conversation_id")
_comment.LastId = field.NewInt64(tableName, "last_id")
_comment.ToUserId = field.NewInt64(tableName, "to_user_id")
_comment.Content = field.NewString(tableName, "content")
_comment.Timestamp = field.NewString(tableName, "timestamp")
_comment.Status = field.NewBool(tableName, "status")
_comment.CreatedAt = field.NewTime(tableName, "created_at")
_comment.UpdatedAt = field.NewTime(tableName, "updated_at")
_comment.fillFieldMap()
return _comment
}
type comment struct {
commentDo
ALL field.Asterisk
Id field.Int64
VideoId field.Int64
UserId field.Int64
ConversationId field.Int64
LastId field.Int64
ToUserId field.Int64
Content field.String
Timestamp field.String
Status field.Bool
CreatedAt field.Time
UpdatedAt field.Time
fieldMap map[string]field.Expr
}
func (c comment) Table(newTableName string) *comment {
c.commentDo.UseTable(newTableName)
return c.updateTableName(newTableName)
}
func (c comment) As(alias string) *comment {
c.commentDo.DO = *(c.commentDo.As(alias).(*gen.DO))
return c.updateTableName(alias)
}
func (c *comment) updateTableName(table string) *comment {
c.ALL = field.NewAsterisk(table)
c.Id = field.NewInt64(table, "id")
c.VideoId = field.NewInt64(table, "video_id")
c.UserId = field.NewInt64(table, "user_id")
c.ConversationId = field.NewInt64(table, "conversation_id")
c.LastId = field.NewInt64(table, "last_id")
c.ToUserId = field.NewInt64(table, "to_user_id")
c.Content = field.NewString(table, "content")
c.Timestamp = field.NewString(table, "timestamp")
c.Status = field.NewBool(table, "status")
c.CreatedAt = field.NewTime(table, "created_at")
c.UpdatedAt = field.NewTime(table, "updated_at")
c.fillFieldMap()
return c
}
func (c *comment) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
_f, ok := c.fieldMap[fieldName]
if !ok || _f == nil {
return nil, false
}
_oe, ok := _f.(field.OrderExpr)
return _oe, ok
}
func (c *comment) fillFieldMap() {
c.fieldMap = make(map[string]field.Expr, 11)
c.fieldMap["id"] = c.Id
c.fieldMap["video_id"] = c.VideoId
c.fieldMap["user_id"] = c.UserId
c.fieldMap["conversation_id"] = c.ConversationId
c.fieldMap["last_id"] = c.LastId
c.fieldMap["to_user_id"] = c.ToUserId
c.fieldMap["content"] = c.Content
c.fieldMap["timestamp"] = c.Timestamp
c.fieldMap["status"] = c.Status
c.fieldMap["created_at"] = c.CreatedAt
c.fieldMap["updated_at"] = c.UpdatedAt
}
func (c comment) clone(db *gorm.DB) comment {
c.commentDo.ReplaceConnPool(db.Statement.ConnPool)
return c
}
func (c comment) replaceDB(db *gorm.DB) comment {
c.commentDo.ReplaceDB(db)
return c
}
type commentDo struct{ gen.DO }
type ICommentDo interface {
gen.SubQuery
Debug() ICommentDo
WithContext(ctx context.Context) ICommentDo
WithResult(fc func(tx gen.Dao)) gen.ResultInfo
ReplaceDB(db *gorm.DB)
ReadDB() ICommentDo
WriteDB() ICommentDo
As(alias string) gen.Dao
Session(config *gorm.Session) ICommentDo
Columns(cols ...field.Expr) gen.Columns
Clauses(conds ...clause.Expression) ICommentDo
Not(conds ...gen.Condition) ICommentDo
Or(conds ...gen.Condition) ICommentDo
Select(conds ...field.Expr) ICommentDo
Where(conds ...gen.Condition) ICommentDo
Order(conds ...field.Expr) ICommentDo
Distinct(cols ...field.Expr) ICommentDo
Omit(cols ...field.Expr) ICommentDo
Join(table schema.Tabler, on ...field.Expr) ICommentDo
LeftJoin(table schema.Tabler, on ...field.Expr) ICommentDo
RightJoin(table schema.Tabler, on ...field.Expr) ICommentDo
Group(cols ...field.Expr) ICommentDo
Having(conds ...gen.Condition) ICommentDo
Limit(limit int) ICommentDo
Offset(offset int) ICommentDo
Count() (count int64, err error)
Scopes(funcs ...func(gen.Dao) gen.Dao) ICommentDo
Unscoped() ICommentDo
Create(values ...*model.Comment) error
CreateInBatches(values []*model.Comment, batchSize int) error
Save(values ...*model.Comment) error
First() (*model.Comment, error)
Take() (*model.Comment, error)
Last() (*model.Comment, error)
Find() ([]*model.Comment, error)
FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Comment, err error)
FindInBatches(result *[]*model.Comment, batchSize int, fc func(tx gen.Dao, batch int) error) error
Pluck(column field.Expr, dest interface{}) error
Delete(...*model.Comment) (info gen.ResultInfo, err error)
Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
Updates(value interface{}) (info gen.ResultInfo, err error)
UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
UpdateFrom(q gen.SubQuery) gen.Dao
Attrs(attrs ...field.AssignExpr) ICommentDo
Assign(attrs ...field.AssignExpr) ICommentDo
Joins(fields ...field.RelationField) ICommentDo
Preload(fields ...field.RelationField) ICommentDo
FirstOrInit() (*model.Comment, error)
FirstOrCreate() (*model.Comment, error)
FindByPage(offset int, limit int) (result []*model.Comment, count int64, err error)
ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
Scan(result interface{}) (err error)
Returning(value interface{}, columns ...string) ICommentDo
UnderlyingDB() *gorm.DB
schema.Tabler
}
func (c commentDo) Debug() ICommentDo {
return c.withDO(c.DO.Debug())
}
func (c commentDo) WithContext(ctx context.Context) ICommentDo {
return c.withDO(c.DO.WithContext(ctx))
}
func (c commentDo) ReadDB() ICommentDo {
return c.Clauses(dbresolver.Read)
}
func (c commentDo) WriteDB() ICommentDo {
return c.Clauses(dbresolver.Write)
}
func (c commentDo) Session(config *gorm.Session) ICommentDo {
return c.withDO(c.DO.Session(config))
}
func (c commentDo) Clauses(conds ...clause.Expression) ICommentDo {
return c.withDO(c.DO.Clauses(conds...))
}
func (c commentDo) Returning(value interface{}, columns ...string) ICommentDo {
return c.withDO(c.DO.Returning(value, columns...))
}
func (c commentDo) Not(conds ...gen.Condition) ICommentDo {
return c.withDO(c.DO.Not(conds...))
}
func (c commentDo) Or(conds ...gen.Condition) ICommentDo {
return c.withDO(c.DO.Or(conds...))
}
func (c commentDo) Select(conds ...field.Expr) ICommentDo {
return c.withDO(c.DO.Select(conds...))
}
func (c commentDo) Where(conds ...gen.Condition) ICommentDo {
return c.withDO(c.DO.Where(conds...))
}
func (c commentDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) ICommentDo {
return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
}
func (c commentDo) Order(conds ...field.Expr) ICommentDo {
return c.withDO(c.DO.Order(conds...))
}
func (c commentDo) Distinct(cols ...field.Expr) ICommentDo {
return c.withDO(c.DO.Distinct(cols...))
}
func (c commentDo) Omit(cols ...field.Expr) ICommentDo {
return c.withDO(c.DO.Omit(cols...))
}
func (c commentDo) Join(table schema.Tabler, on ...field.Expr) ICommentDo {
return c.withDO(c.DO.Join(table, on...))
}
func (c commentDo) LeftJoin(table schema.Tabler, on ...field.Expr) ICommentDo {
return c.withDO(c.DO.LeftJoin(table, on...))
}
func (c commentDo) RightJoin(table schema.Tabler, on ...field.Expr) ICommentDo {
return c.withDO(c.DO.RightJoin(table, on...))
}
func (c commentDo) Group(cols ...field.Expr) ICommentDo {
return c.withDO(c.DO.Group(cols...))
}
func (c commentDo) Having(conds ...gen.Condition) ICommentDo {
return c.withDO(c.DO.Having(conds...))
}
func (c commentDo) Limit(limit int) ICommentDo {
return c.withDO(c.DO.Limit(limit))
}
func (c commentDo) Offset(offset int) ICommentDo {
return c.withDO(c.DO.Offset(offset))
}
func (c commentDo) Scopes(funcs ...func(gen.Dao) gen.Dao) ICommentDo {
return c.withDO(c.DO.Scopes(funcs...))
}
func (c commentDo) Unscoped() ICommentDo {
return c.withDO(c.DO.Unscoped())
}
func (c commentDo) Create(values ...*model.Comment) error {
if len(values) == 0 {
return nil
}
return c.DO.Create(values)
}
func (c commentDo) CreateInBatches(values []*model.Comment, batchSize int) error {
return c.DO.CreateInBatches(values, batchSize)
}
// Save : !!! underlying implementation is different with GORM
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
func (c commentDo) Save(values ...*model.Comment) error {
if len(values) == 0 {
return nil
}
return c.DO.Save(values)
}
func (c commentDo) First() (*model.Comment, error) {
if result, err := c.DO.First(); err != nil {
return nil, err
} else {
return result.(*model.Comment), nil
}
}
func (c commentDo) Take() (*model.Comment, error) {
if result, err := c.DO.Take(); err != nil {
return nil, err
} else {
return result.(*model.Comment), nil
}
}
func (c commentDo) Last() (*model.Comment, error) {
if result, err := c.DO.Last(); err != nil {
return nil, err
} else {
return result.(*model.Comment), nil
}
}
func (c commentDo) Find() ([]*model.Comment, error) {
result, err := c.DO.Find()
return result.([]*model.Comment), err
}
func (c commentDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Comment, err error) {
buf := make([]*model.Comment, 0, batchSize)
err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
defer func() { results = append(results, buf...) }()
return fc(tx, batch)
})
return results, err
}
func (c commentDo) FindInBatches(result *[]*model.Comment, batchSize int, fc func(tx gen.Dao, batch int) error) error {
return c.DO.FindInBatches(result, batchSize, fc)
}
func (c commentDo) Attrs(attrs ...field.AssignExpr) ICommentDo {
return c.withDO(c.DO.Attrs(attrs...))
}
func (c commentDo) Assign(attrs ...field.AssignExpr) ICommentDo {
return c.withDO(c.DO.Assign(attrs...))
}
func (c commentDo) Joins(fields ...field.RelationField) ICommentDo {
for _, _f := range fields {
c = *c.withDO(c.DO.Joins(_f))
}
return &c
}
func (c commentDo) Preload(fields ...field.RelationField) ICommentDo {
for _, _f := range fields {
c = *c.withDO(c.DO.Preload(_f))
}
return &c
}
func (c commentDo) FirstOrInit() (*model.Comment, error) {
if result, err := c.DO.FirstOrInit(); err != nil {
return nil, err
} else {
return result.(*model.Comment), nil
}
}
func (c commentDo) FirstOrCreate() (*model.Comment, error) {
if result, err := c.DO.FirstOrCreate(); err != nil {
return nil, err
} else {
return result.(*model.Comment), nil
}
}
func (c commentDo) FindByPage(offset int, limit int) (result []*model.Comment, count int64, err error) {
result, err = c.Offset(offset).Limit(limit).Find()
if err != nil {
return
}
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
count, err = c.Offset(-1).Limit(-1).Count()
return
}
func (c commentDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
count, err = c.Count()
if err != nil {
return
}
err = c.Offset(offset).Limit(limit).Scan(result)
return
}
func (c commentDo) Scan(result interface{}) (err error) {
return c.DO.Scan(result)
}
func (c commentDo) Delete(models ...*model.Comment) (result gen.ResultInfo, err error) {
return c.DO.Delete(models)
}
func (c *commentDo) withDO(do gen.Dao) *commentDo {
c.DO = *do.(*gen.DO)
return c
}

107
applications/comment/infra/query/gen.go

@ -0,0 +1,107 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package query
import (
"context"
"database/sql"
"gorm.io/gorm"
"gorm.io/gen"
"gorm.io/plugin/dbresolver"
)
var (
Q = new(Query)
Comment *comment
CommentCount *commentCount
)
func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
*Q = *Use(db, opts...)
Comment = &Q.Comment
CommentCount = &Q.CommentCount
}
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
return &Query{
db: db,
Comment: newComment(db, opts...),
CommentCount: newCommentCount(db, opts...),
}
}
type Query struct {
db *gorm.DB
Comment comment
CommentCount commentCount
}
func (q *Query) Available() bool { return q.db != nil }
func (q *Query) clone(db *gorm.DB) *Query {
return &Query{
db: db,
Comment: q.Comment.clone(db),
CommentCount: q.CommentCount.clone(db),
}
}
func (q *Query) ReadDB() *Query {
return q.ReplaceDB(q.db.Clauses(dbresolver.Read))
}
func (q *Query) WriteDB() *Query {
return q.ReplaceDB(q.db.Clauses(dbresolver.Write))
}
func (q *Query) ReplaceDB(db *gorm.DB) *Query {
return &Query{
db: db,
Comment: q.Comment.replaceDB(db),
CommentCount: q.CommentCount.replaceDB(db),
}
}
type queryCtx struct {
Comment ICommentDo
CommentCount ICommentCountDo
}
func (q *Query) WithContext(ctx context.Context) *queryCtx {
return &queryCtx{
Comment: q.Comment.WithContext(ctx),
CommentCount: q.CommentCount.WithContext(ctx),
}
}
func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error {
return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...)
}
func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx {
return &QueryTx{q.clone(q.db.Begin(opts...))}
}
type QueryTx struct{ *Query }
func (q *QueryTx) Commit() error {
return q.db.Commit().Error
}
func (q *QueryTx) Rollback() error {
return q.db.Rollback().Error
}
func (q *QueryTx) SavePoint(name string) error {
return q.db.SavePoint(name).Error
}
func (q *QueryTx) RollbackTo(name string) error {
return q.db.RollbackTo(name).Error
}

4
applications/comment/infra/query/var.go

@ -0,0 +1,4 @@
package query
var CommentStruct = &comment{}
var CommentCountStruct = &commentCount{}

78
applications/comment/infra/repository/comment_count_repo/repository.go

@ -0,0 +1,78 @@
package comment_count_repo
import (
"context"
"github.com/TremblingV5/DouTok/applications/comment/infra/model"
"github.com/TremblingV5/DouTok/applications/comment/infra/query"
"github.com/TremblingV5/box/dbtx"
)
type Repository interface {
Save(ctx context.Context, commentCount *model.CommentCount) error
Update(ctx context.Context, commentCount *model.CommentCount) error
LoadById(ctx context.Context, id int64) (*model.CommentCount, error)
LoadByIdList(ctx context.Context, ids ...int64) ([]*model.CommentCount, error)
UpdateNumber(ctx context.Context, id, number int64) error
}
type PersistRepository struct{}
func New() *PersistRepository {
return &PersistRepository{}
}
func (r *PersistRepository) Save(ctx context.Context, commentCount *model.CommentCount) error {
return dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
return tx.CommentCount.WithContext(ctx).Save(commentCount)
})
}
func (r *PersistRepository) Update(ctx context.Context, commentCount *model.CommentCount) error {
return dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
_, err := query.CommentCount.WithContext(ctx).Where(
query.CommentCount.Id.Eq(commentCount.Id),
).Updates(
commentCount,
)
return err
})
}
func (r *PersistRepository) LoadById(ctx context.Context, id int64) (commentCount *model.CommentCount, err error) {
err = dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
c, err := tx.CommentCount.WithContext(ctx).Where(
query.CommentCount.Id.Eq(id),
).First()
commentCount = c
return err
})
return commentCount, err
}
func (r *PersistRepository) LoadByIdList(ctx context.Context, ids ...int64) (commentCountList []*model.CommentCount, err error) {
err = dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
c, err := tx.CommentCount.WithContext(ctx).Where(
query.CommentCount.Id.In(ids...),
).Find()
commentCountList = append(commentCountList, c...)
return err
})
return commentCountList, err
}
func (r *PersistRepository) UpdateNumber(ctx context.Context, id, number int64) error {
return dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
_, err := query.CommentCount.WithContext(ctx).Where(
query.CommentCount.Id.Eq(id),
).Update(
query.CommentCount.Number, number,
)
return err
})
}

94
applications/comment/infra/repository/comment_hb_repo/repository.go

@ -0,0 +1,94 @@
package comment_hb_repo
import (
"context"
"github.com/TremblingV5/DouTok/applications/comment/infra/misc"
"github.com/TremblingV5/DouTok/applications/comment/infra/model"
"github.com/TremblingV5/DouTok/pkg/hbaseHandle"
tools "github.com/TremblingV5/DouTok/pkg/misc"
"github.com/tsuna/gohbase"
"github.com/tsuna/gohbase/hrpc"
)
type Repository interface {
Save(ctx context.Context, comment *model.CommentInHB) error
ScanByPrefix(ctx context.Context, videoId int64) ([]*model.CommentInHB, error)
Delete(ctx context.Context, rowKey string) error
}
type PersistRepository struct {
hbClient gohbase.Client
}
func New(hbClient gohbase.Client) *PersistRepository {
return &PersistRepository{
hbClient: hbClient,
}
}
func (r *PersistRepository) Save(ctx context.Context, comment *model.CommentInHB) error {
hbData := map[string]map[string][]byte{
"data": {
"id": comment.Id,
"video_id": comment.VideoId,
"user_id": comment.UserId,
"conversation_id": comment.ConversationId,
"last_id": comment.LastId,
"to_user_id": comment.ToUserId,
"content": comment.Content,
"timestamp": comment.Timestamp,
},
}
if _, err := r.hbClient.Put(
"comment", misc.GetCommentRowKey(
comment.GetVideoId(), "0",
comment.GetConversationId(),
comment.GetTimestamp(),
), hbData,
); err != nil {
return err
}
return nil
}
func (r *PersistRepository) ScanByPrefix(ctx context.Context, videoId int64) ([]*model.CommentInHB, error) {
result, err := r.hbClient.Scan(
"comment",
hbaseHandle.GetFilterByRowKeyPrefix(misc.GetCommentQueryPrefix(videoId))...,
)
if err != nil {
return nil, err
}
var commentList []*model.CommentInHB
for _, v := range result {
temp := model.CommentInHB{}
err := tools.Map2Struct4HB(v, &temp)
if err != nil {
continue
}
commentList = append(commentList, &temp)
}
return commentList, nil
}
func (r *PersistRepository) Delete(ctx context.Context, rowKey string) error {
rmReq, err := hrpc.NewDelStr(
context.Background(),
"comment", rowKey, make(map[string]map[string][]byte),
)
if err != nil {
return nil
}
if _, err := r.hbClient.Client.Delete(rmReq); err != nil {
return err
}
return nil
}

61
applications/comment/infra/repository/comment_repo/repository.go

@ -0,0 +1,61 @@
package comment_repo
import (
"context"
"github.com/TremblingV5/DouTok/applications/comment/infra/model"
"github.com/TremblingV5/DouTok/applications/comment/infra/query"
"github.com/TremblingV5/box/dbtx"
)
type Repository interface {
Save(ctx context.Context, comment *model.Comment) error
Update(ctx context.Context, comment *model.Comment) error
MarkAsDeleted(ctx context.Context, id int64) error
LoadById(ctx context.Context, id int64) (*model.Comment, error)
}
type PersistRepository struct {
}
func New() *PersistRepository {
return &PersistRepository{}
}
func (r *PersistRepository) Save(ctx context.Context, comment *model.Comment) error {
return dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
return tx.Comment.WithContext(ctx).Save(comment)
})
}
func (r *PersistRepository) Update(ctx context.Context, comment *model.Comment) error {
return dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
_, err := tx.Comment.WithContext(ctx).Where(
query.Comment.Id.Eq(comment.Id),
).Updates(comment)
return err
})
}
func (r *PersistRepository) MarkAsDeleted(ctx context.Context, id int64) error {
return dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
_, err := tx.Comment.WithContext(ctx).Where(
query.Comment.Id.Eq(id),
).Update(
query.Comment.Status, false,
)
return err
})
}
func (r *PersistRepository) LoadById(ctx context.Context, id int64) (comment *model.Comment, err error) {
err = dbtx.TxDo(ctx, func(tx *query.QueryTx) error {
c, err := tx.Comment.WithContext(ctx).Where(query.Comment.Id.Eq(id)).First()
comment = c
return err
})
return comment, err
}

18
applications/comment/infra/rpc/rpc.go

@ -0,0 +1,18 @@
package rpc
import (
"github.com/TremblingV5/DouTok/kitex_gen/user/userservice"
"github.com/TremblingV5/DouTok/pkg/constants"
"github.com/TremblingV5/DouTok/pkg/services"
"github.com/cloudwego/kitex/client"
)
type Clients struct {
User *services.Service[userservice.Client]
}
func New(options []client.Option) *Clients {
return &Clients{
User: services.New[userservice.Client](constants.UserServerName, userservice.NewClient, options),
}
}

98
applications/comment/main.go

@ -0,0 +1,98 @@
package main
import (
"context"
"github.com/TremblingV5/DouTok/applications/comment/api/comment_api"
"github.com/TremblingV5/DouTok/applications/comment/infra/misc"
"github.com/TremblingV5/DouTok/applications/comment/infra/query"
"github.com/TremblingV5/DouTok/applications/comment/infra/repository/comment_hb_repo"
"github.com/TremblingV5/DouTok/applications/comment/infra/rpc"
"github.com/TremblingV5/DouTok/applications/comment/services/comment_service"
"github.com/TremblingV5/DouTok/kitex_gen/comment"
"github.com/TremblingV5/DouTok/kitex_gen/comment/commentservice"
"github.com/TremblingV5/DouTok/pkg/cache"
"github.com/TremblingV5/DouTok/pkg/dlog"
"github.com/TremblingV5/DouTok/pkg/hbaseHandle"
"github.com/TremblingV5/DouTok/pkg/initHelper"
"github.com/TremblingV5/box/components/hbasex"
"github.com/TremblingV5/box/components/mysqlx"
"github.com/TremblingV5/box/components/redisx"
"github.com/TremblingV5/box/configx"
"github.com/TremblingV5/box/dbtx"
"github.com/TremblingV5/box/launcher"
"github.com/TremblingV5/box/rpcserver/kitexx"
)
var (
Logger = dlog.InitLog(3)
)
func initHb(host string) hbaseHandle.HBaseClient {
return hbaseHandle.InitHB(host)
}
func Init() comment.CommentService {
ctx := context.Background()
misc.InitViperConfig()
query.SetDefault(
mysqlx.GetDBClient(context.Background(), "default"),
)
dbtx.Init(func() dbtx.TX {
return query.Q.Begin()
})
hbaseClient := initHb(misc.GetConfig("HBase.Host"))
service := comment_service.New(
cache.NewCountMapCache(),
cache.NewCountMapCache(),
comment_hb_repo.New(hbasex.GetClient(ctx, "default")),
redisx.GetClient(ctx, "default", misc.ComCntCache),
redisx.GetClient(ctx, "default", misc.ComTotalCntCache),
)
go service.UpdateComCountMap()
go service.UpdateComTotalCntMap()
handle := comment_api.New(service, rpc.New(initHelper.InitRPCClientArgs(misc.Config)))
return handle
}
func oldMain() {
Init()
options, shutdown := initHelper.InitRPCServerArgs(misc.Config)
defer shutdown()
svr := commentservice.NewServer(
Init(),
options...,
)
if err := svr.Run(); err != nil {
Logger.Fatal(err)
}
}
func main() {
l := launcher.New()
l.AddBeforeConfigInitHandler(func() {
configx.SetRootConfigFilename("comment")
})
options, shutdown := initHelper.InitRPCServerArgs(misc.Config)
defer shutdown()
l.AddBeforeServerStartHandler(func() {
l.AddServer(
kitexx.NewKitexServer(
commentservice.NewServer, Init(), options...,
),
)
})
l.Run()
}

21
applications/comment/script/bootstrap.sh

@ -0,0 +1,21 @@
#! /usr/bin/env bash
CURDIR=$(cd $(dirname $0); pwd)
if [ "X$1" != "X" ]; then
RUNTIME_ROOT=$1
else
RUNTIME_ROOT=${CURDIR}
fi
export KITEX_RUNTIME_ROOT=$RUNTIME_ROOT
export KITEX_LOG_DIR="$RUNTIME_ROOT/log"
if [ ! -d "$KITEX_LOG_DIR/app" ]; then
mkdir -p "$KITEX_LOG_DIR/app"
fi
if [ ! -d "$KITEX_LOG_DIR/rpc" ]; then
mkdir -p "$KITEX_LOG_DIR/rpc"
fi
exec "$CURDIR/bin/comment"

234
applications/comment/services/comment_service/service.go

@ -0,0 +1,234 @@
package comment_service
import (
"context"
"errors"
"fmt"
"strconv"
"time"
misc "github.com/TremblingV5/DouTok/applications/comment/infra/misc"
"github.com/TremblingV5/box/dbtx"
"github.com/TremblingV5/DouTok/applications/comment/domain/entity/comment"
"github.com/TremblingV5/DouTok/applications/comment/domain/entity/comment_count"
"github.com/TremblingV5/DouTok/applications/comment/infra/repository/comment_count_repo"
"github.com/TremblingV5/DouTok/applications/comment/infra/repository/comment_hb_repo"
"github.com/TremblingV5/DouTok/applications/comment/infra/repository/comment_repo"
"github.com/TremblingV5/DouTok/pkg/cache"
"github.com/TremblingV5/DouTok/pkg/errno"
"github.com/go-redis/redis/v8"
"github.com/TremblingV5/DouTok/pkg/utils"
)
type Service struct {
commentCountCache *cache.CountMapCache
commentTotalCountCache *cache.CountMapCache
commentHBRepository comment_hb_repo.Repository
commentCountRedis *redis.Client
commentTotalCountRedis *redis.Client
commentRepository comment_repo.Repository
commentCountRepository comment_count_repo.Repository
}
func New(
commentCountCache *cache.CountMapCache,
commentTotalCountCache *cache.CountMapCache,
commentHBRepository comment_hb_repo.Repository,
commentCountRedis *redis.Client,
commentTotalCountRedis *redis.Client,
) *Service {
return &Service{
commentCountCache: commentCountCache,
commentTotalCountCache: commentTotalCountCache,
commentHBRepository: commentHBRepository,
commentCountRedis: commentCountRedis,
commentTotalCountRedis: commentTotalCountRedis,
commentRepository: comment_repo.New(),
commentCountRepository: comment_count_repo.New(),
}
}
func (s *Service) AddComment(
ctx context.Context,
videoId int64, userId int64, conId int64,
lastId int64, toUserId int64, content string,
) (c *comment.Entity, err error) {
ctx, persist := dbtx.WithTXPersist(ctx)
defer func() {
persist(err)
}()
timestamp := fmt.Sprint(time.Now().Unix())
id := utils.GetSnowFlakeId().Int64()
comment, err := comment.New(
comment.WithId(id),
comment.WithVideoId(videoId),
comment.WithUserId(userId),
comment.WithConversationId(conId),
comment.WithLastId(lastId),
comment.WithToUserId(toUserId),
comment.WithContent(content),
comment.WithTimestamp(timestamp),
)
if err != nil {
return nil, err
}
if err := s.commentRepository.Save(ctx, comment.ToModel()); err != nil {
return nil, err
}
// TODO: 通过消息队列异步写
if err := s.commentHBRepository.Save(ctx, comment.ToHBModel()); err != nil {
return nil, err
}
s.updateCacheComCount(videoId, true)
return comment, nil
}
func (s *Service) CountComment(ctx context.Context, videoId ...int64) (result map[int64]int64, e *errno.ErrNo, err error) {
ctx, persist := dbtx.WithTXPersist(ctx)
defer func() {
persist(err)
}()
resMap := make(map[int64]int64)
// 1. 从内存中查找喜欢数
var findAgain []int64
for _, v := range videoId {
cnt, ok := s.readComTotalCount(v)
if !ok {
findAgain = append(findAgain, v)
continue
}
resMap[v] = cnt
}
// 2. 从Redis中查找喜欢数
var findAgainAgain []int64
for _, v := range findAgain {
cnt, ok, _ := s.readComTotalCountFromCache(fmt.Sprint(v))
if !ok {
findAgainAgain = append(findAgainAgain, v)
continue
}
resMap[v] = cnt
s.commentTotalCountCache.Set(v, cnt)
}
// 3. 从MySQL中查找喜欢数
commentCountModels, err := s.commentCountRepository.LoadByIdList(ctx, findAgainAgain...)
if err != nil {
return nil, &misc.QueryCommentCountErr, err
}
commentCounts := comment_count.NewListFromModels(commentCountModels)
for _, v := range commentCounts {
resMap[v.Id] = v.Number
}
// 4. 如果仍然没有查找到该记录,则置0
for _, v := range videoId {
if _, ok := resMap[v]; !ok {
resMap[v] = 0
}
}
return resMap, &misc.Success, nil
}
func (s *Service) readComTotalCount(videoId int64) (int64, bool) {
return s.commentTotalCountCache.Get(videoId)
}
func (s *Service) readComTotalCountFromCache(videoId string) (int64, bool, error) {
data, err := s.commentTotalCountRedis.Get(context.Background(), videoId).Result()
if errors.Is(err, redis.Nil) {
return 0, false, nil
} else if err != nil {
return 0, false, err
}
num, _ := strconv.ParseInt(data, 10, 64)
return num, true, nil
}
func (s *Service) updateCacheComCount(videoId int64, isAdd bool) {
if isAdd {
s.commentCountCache.Add(videoId, 1)
return
}
s.commentCountCache.Add(videoId, -1)
}
func (s *Service) ListComment(ctx context.Context, videoId int64) (comment.List, errno.ErrNo, error) {
commentHBModels, err := s.commentHBRepository.ScanByPrefix(ctx, videoId)
if err != nil {
return nil, misc.QueryCommentListInHBErr, err
}
commentList := comment.NewListFromHBModels(commentHBModels)
return commentList, misc.Success, nil
}
func (s *Service) RmComment(ctx context.Context, userId, commentId int64) (e errno.ErrNo, err error) {
ctx, persist := dbtx.WithTXPersist(ctx)
defer func() {
persist(err)
}()
commentModel, err := s.commentRepository.LoadById(ctx, commentId)
if err != nil {
return misc.SystemErr, err
}
comment, err := comment.New(
comment.WithId(commentModel.Id),
comment.WithVideoId(commentModel.VideoId),
comment.WithUserId(commentModel.UserId),
comment.WithConversationId(commentModel.ConversationId),
comment.WithLastId(commentModel.LastId),
comment.WithToUserId(commentModel.ToUserId),
comment.WithContent(commentModel.Content),
comment.WithTimestamp(commentModel.Timestamp),
comment.WithStatus(commentModel.Status),
comment.WithCreatedAt(commentModel.CreatedAt),
comment.WithUpdatedAt(commentModel.UpdatedAt),
)
if err != nil {
return misc.SystemErr, err
}
_, err = comment.IsBelongUser(userId)
if err != nil {
return misc.SystemErr, err
}
comment.MarkAsDelete()
if err := s.commentRepository.Update(ctx, comment.ToModel()); err != nil {
return misc.SystemErr, err
}
if err := s.commentHBRepository.Save(ctx, comment.ToHBModel()); err != nil {
return misc.SystemErr, err
}
s.updateCacheComCount(comment.VideoId, false)
return misc.Success, nil
}

101
applications/comment/services/comment_service/timer.go

@ -0,0 +1,101 @@
package comment_service
import (
"context"
"fmt"
"strconv"
"time"
"github.com/TremblingV5/DouTok/applications/comment/domain/entity/comment_count"
"github.com/TremblingV5/DouTok/pkg/dlog"
)
var logger = dlog.InitLog(3)
/*
定时将内存中的局部评论数更新到数据库中并删除Redis中的评论总数
*/
func (s *Service) UpdateComCountMap() {
for {
time.Sleep(time.Duration(5) * time.Second)
logger.Info("Start iter comment cnt map and update on " + fmt.Sprint(time.Now().Unix()))
var keyList []string
s.commentCountCache.Iter(func(key string, v interface{}) {
keyList = append(keyList, key)
keyI64, _ := strconv.ParseInt(key, 10, 64)
err := s.updateCount(context.Background(), keyI64, int64(v.(int)))
if err != nil {
dlog.Warn("Write comment count to RDB defeat: " + key + " with count: " + fmt.Sprint(v.(int)))
}
err = s.delCount2Cache(key)
if err != nil {
dlog.Warn("Delete comment count from third party cache defeat: " + key)
}
})
for _, v := range keyList {
i64, _ := strconv.ParseInt(v, 10, 64)
s.commentCountCache.Set(i64, 0)
}
}
}
/*
定时将Redis中每个Video的Comment总数更新到内存中的Map
Redis不存在的视频评论数由单独查询时再添加到Redis中
*/
func (s *Service) UpdateComTotalCntMap() {
for {
time.Sleep(time.Duration(5) * time.Second)
logger.Info("Start iter comment total cnt map and update on " + fmt.Sprint(time.Now().Unix()))
keyList := []string{}
s.commentTotalCountCache.Iter(func(key string, v interface{}) {
keyList = append(keyList, key)
})
for _, v := range keyList {
res, err := s.commentTotalCountRedis.Get(context.Background(), v).Result()
if err != nil {
continue
}
i, _ := strconv.ParseInt(res, 10, 64)
iKey, _ := strconv.ParseInt(v, 10, 64)
s.commentTotalCountCache.Set(iKey, i)
}
}
}
func (s *Service) updateCount(ctx context.Context, videoId int64, cnt int64) error {
commentModel, err := s.commentCountRepository.LoadById(ctx, videoId)
if err != nil {
return err
}
commentCount := comment_count.TransformFromModel(commentModel)
if err := commentCount.Check(comment_count.IsIdValid()); err != nil {
return err
}
commentCount.UpdateNumber(cnt)
if err := s.commentCountRepository.Update(ctx, commentCount.ToModel()); err != nil {
return err
}
return nil
}
/*
删除Redis中存储的视频的完整的评论数量
*/
func (s *Service) delCount2Cache(videoId string) error {
return s.commentTotalCountRedis.Del(context.Background(), videoId).Err()
}

29
applications/comment/services/comment_service/utils.go

@ -0,0 +1,29 @@
package comment_service
import (
"fmt"
"strconv"
"time"
)
func GetTimeRecall(timestamp string, current int64) string {
timestampI64, _ := strconv.ParseInt(timestamp, 10, 64)
diff := current - timestampI64
if diff < 60 {
return "刚刚"
} else if diff < 60*60 {
minutes := diff / 60
return fmt.Sprint(minutes) + "分钟前"
} else if diff < 60*60*24 {
hours := diff / (60 * 60)
return fmt.Sprint(hours) + "小时前"
} else if diff < 60*60*24*14 {
days := diff / (60 * 60 * 24)
return fmt.Sprint(days) + "天前"
} else {
t := time.Unix(timestampI64, 0)
return t.Format("2006/01/02")
}
}

2
applications/favorite/Makefile

@ -0,0 +1,2 @@
server:
kitex -module github.com/TremblingV5/DouTok -type protobuf -I ../../proto/ -service favorite ../../proto/favorite.proto

12
applications/favorite/build.sh

@ -0,0 +1,12 @@
#!/usr/bin/env bash
RUN_NAME="favorite"
mkdir -p output/bin
cp script/* output/
chmod +x output/bootstrap.sh
if [ "$IS_SYSTEM_TEST_ENV" != "1" ]; then
go build -o output/bin/${RUN_NAME}
else
go test -c -covermode=set -o output/bin/${RUN_NAME} -coverpkg=./...
fi

39
applications/favorite/dal/gen.go

@ -0,0 +1,39 @@
package main
import (
"github.com/TremblingV5/DouTok/applications/favorite/dal/model"
"github.com/TremblingV5/DouTok/config/configStruct"
"github.com/TremblingV5/DouTok/pkg/configurator"
"github.com/TremblingV5/DouTok/pkg/mysqlIniter"
"gorm.io/gen"
)
func main() {
g := gen.NewGenerator(gen.Config{
OutPath: "../query",
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,
})
var config configStruct.MySQLConfig
configurator.InitConfig(
&config, "mysql.yaml",
)
db, err := mysqlIniter.InitDb(
config.Username,
config.Password,
config.Host,
config.Port,
config.Database,
)
if err != nil {
panic(err)
}
g.UseDB(db)
g.ApplyBasic(model.Favorite{}, model.FavoriteCount{})
g.ApplyInterface(func() {}, model.Favorite{}, model.FavoriteCount{})
g.Execute()
}

21
applications/favorite/dal/migrate/main.go

@ -0,0 +1,21 @@
package main
import (
"github.com/TremblingV5/DouTok/applications/favorite/dal/model"
"github.com/TremblingV5/DouTok/applications/favorite/misc"
"github.com/TremblingV5/DouTok/applications/favorite/service"
)
func main() {
misc.InitViperConfig()
service.InitDb(
misc.GetConfig("MySQL.Username"),
misc.GetConfig("MySQL.Password"),
misc.GetConfig("MySQL.Host"),
misc.GetConfig("MySQL.Port"),
misc.GetConfig("MySQL.Database"),
)
service.DB.AutoMigrate(&model.Favorite{}, &model.FavoriteCount{})
}

10
applications/favorite/dal/model/count.go

@ -0,0 +1,10 @@
package model
import "time"
type FavoriteCount struct {
VideoId int64 `gorm:"index:video_id"`
Number int64
CreatedAt time.Time
UpdatedAt time.Time
}

12
applications/favorite/dal/model/favorite.go

@ -0,0 +1,12 @@
package model
import "time"
type Favorite struct {
ID int64 `gorm:"primarykey"`
UserId int64 `gorm:"index:user_id"`
VideoId int64 `gorm:"index:video_id"`
Status int
CreatedAt time.Time
UpdatedAt time.Time
}

396
applications/favorite/dal/query/favorite_counts.gen.go

@ -0,0 +1,396 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package query
import (
"context"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/plugin/dbresolver"
"github.com/TremblingV5/DouTok/applications/favorite/dal/model"
)
func newFavoriteCount(db *gorm.DB, opts ...gen.DOOption) favoriteCount {
_favoriteCount := favoriteCount{}
_favoriteCount.favoriteCountDo.UseDB(db, opts...)
_favoriteCount.favoriteCountDo.UseModel(&model.FavoriteCount{})
tableName := _favoriteCount.favoriteCountDo.TableName()
_favoriteCount.ALL = field.NewAsterisk(tableName)
_favoriteCount.VideoId = field.NewInt64(tableName, "video_id")
_favoriteCount.Number = field.NewInt64(tableName, "number")
_favoriteCount.CreatedAt = field.NewTime(tableName, "created_at")
_favoriteCount.UpdatedAt = field.NewTime(tableName, "updated_at")
_favoriteCount.fillFieldMap()
return _favoriteCount
}
type favoriteCount struct {
favoriteCountDo
ALL field.Asterisk
VideoId field.Int64
Number field.Int64
CreatedAt field.Time
UpdatedAt field.Time
fieldMap map[string]field.Expr
}
func (f favoriteCount) Table(newTableName string) *favoriteCount {
f.favoriteCountDo.UseTable(newTableName)
return f.updateTableName(newTableName)
}
func (f favoriteCount) As(alias string) *favoriteCount {
f.favoriteCountDo.DO = *(f.favoriteCountDo.As(alias).(*gen.DO))
return f.updateTableName(alias)
}
func (f *favoriteCount) updateTableName(table string) *favoriteCount {
f.ALL = field.NewAsterisk(table)
f.VideoId = field.NewInt64(table, "video_id")
f.Number = field.NewInt64(table, "number")
f.CreatedAt = field.NewTime(table, "created_at")
f.UpdatedAt = field.NewTime(table, "updated_at")
f.fillFieldMap()
return f
}
func (f *favoriteCount) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
_f, ok := f.fieldMap[fieldName]
if !ok || _f == nil {
return nil, false
}
_oe, ok := _f.(field.OrderExpr)
return _oe, ok
}
func (f *favoriteCount) fillFieldMap() {
f.fieldMap = make(map[string]field.Expr, 4)
f.fieldMap["video_id"] = f.VideoId
f.fieldMap["number"] = f.Number
f.fieldMap["created_at"] = f.CreatedAt
f.fieldMap["updated_at"] = f.UpdatedAt
}
func (f favoriteCount) clone(db *gorm.DB) favoriteCount {
f.favoriteCountDo.ReplaceConnPool(db.Statement.ConnPool)
return f
}
func (f favoriteCount) replaceDB(db *gorm.DB) favoriteCount {
f.favoriteCountDo.ReplaceDB(db)
return f
}
type favoriteCountDo struct{ gen.DO }
type IFavoriteCountDo interface {
gen.SubQuery
Debug() IFavoriteCountDo
WithContext(ctx context.Context) IFavoriteCountDo
WithResult(fc func(tx gen.Dao)) gen.ResultInfo
ReplaceDB(db *gorm.DB)
ReadDB() IFavoriteCountDo
WriteDB() IFavoriteCountDo
As(alias string) gen.Dao
Session(config *gorm.Session) IFavoriteCountDo
Columns(cols ...field.Expr) gen.Columns
Clauses(conds ...clause.Expression) IFavoriteCountDo
Not(conds ...gen.Condition) IFavoriteCountDo
Or(conds ...gen.Condition) IFavoriteCountDo
Select(conds ...field.Expr) IFavoriteCountDo
Where(conds ...gen.Condition) IFavoriteCountDo
Order(conds ...field.Expr) IFavoriteCountDo
Distinct(cols ...field.Expr) IFavoriteCountDo
Omit(cols ...field.Expr) IFavoriteCountDo
Join(table schema.Tabler, on ...field.Expr) IFavoriteCountDo
LeftJoin(table schema.Tabler, on ...field.Expr) IFavoriteCountDo
RightJoin(table schema.Tabler, on ...field.Expr) IFavoriteCountDo
Group(cols ...field.Expr) IFavoriteCountDo
Having(conds ...gen.Condition) IFavoriteCountDo
Limit(limit int) IFavoriteCountDo
Offset(offset int) IFavoriteCountDo
Count() (count int64, err error)
Scopes(funcs ...func(gen.Dao) gen.Dao) IFavoriteCountDo
Unscoped() IFavoriteCountDo
Create(values ...*model.FavoriteCount) error
CreateInBatches(values []*model.FavoriteCount, batchSize int) error
Save(values ...*model.FavoriteCount) error
First() (*model.FavoriteCount, error)
Take() (*model.FavoriteCount, error)
Last() (*model.FavoriteCount, error)
Find() ([]*model.FavoriteCount, error)
FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.FavoriteCount, err error)
FindInBatches(result *[]*model.FavoriteCount, batchSize int, fc func(tx gen.Dao, batch int) error) error
Pluck(column field.Expr, dest interface{}) error
Delete(...*model.FavoriteCount) (info gen.ResultInfo, err error)
Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
Updates(value interface{}) (info gen.ResultInfo, err error)
UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
UpdateFrom(q gen.SubQuery) gen.Dao
Attrs(attrs ...field.AssignExpr) IFavoriteCountDo
Assign(attrs ...field.AssignExpr) IFavoriteCountDo
Joins(fields ...field.RelationField) IFavoriteCountDo
Preload(fields ...field.RelationField) IFavoriteCountDo
FirstOrInit() (*model.FavoriteCount, error)
FirstOrCreate() (*model.FavoriteCount, error)
FindByPage(offset int, limit int) (result []*model.FavoriteCount, count int64, err error)
ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
Scan(result interface{}) (err error)
Returning(value interface{}, columns ...string) IFavoriteCountDo
UnderlyingDB() *gorm.DB
schema.Tabler
}
func (f favoriteCountDo) Debug() IFavoriteCountDo {
return f.withDO(f.DO.Debug())
}
func (f favoriteCountDo) WithContext(ctx context.Context) IFavoriteCountDo {
return f.withDO(f.DO.WithContext(ctx))
}
func (f favoriteCountDo) ReadDB() IFavoriteCountDo {
return f.Clauses(dbresolver.Read)
}
func (f favoriteCountDo) WriteDB() IFavoriteCountDo {
return f.Clauses(dbresolver.Write)
}
func (f favoriteCountDo) Session(config *gorm.Session) IFavoriteCountDo {
return f.withDO(f.DO.Session(config))
}
func (f favoriteCountDo) Clauses(conds ...clause.Expression) IFavoriteCountDo {
return f.withDO(f.DO.Clauses(conds...))
}
func (f favoriteCountDo) Returning(value interface{}, columns ...string) IFavoriteCountDo {
return f.withDO(f.DO.Returning(value, columns...))
}
func (f favoriteCountDo) Not(conds ...gen.Condition) IFavoriteCountDo {
return f.withDO(f.DO.Not(conds...))
}
func (f favoriteCountDo) Or(conds ...gen.Condition) IFavoriteCountDo {
return f.withDO(f.DO.Or(conds...))
}
func (f favoriteCountDo) Select(conds ...field.Expr) IFavoriteCountDo {
return f.withDO(f.DO.Select(conds...))
}
func (f favoriteCountDo) Where(conds ...gen.Condition) IFavoriteCountDo {
return f.withDO(f.DO.Where(conds...))
}
func (f favoriteCountDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) IFavoriteCountDo {
return f.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
}
func (f favoriteCountDo) Order(conds ...field.Expr) IFavoriteCountDo {
return f.withDO(f.DO.Order(conds...))
}
func (f favoriteCountDo) Distinct(cols ...field.Expr) IFavoriteCountDo {
return f.withDO(f.DO.Distinct(cols...))
}
func (f favoriteCountDo) Omit(cols ...field.Expr) IFavoriteCountDo {
return f.withDO(f.DO.Omit(cols...))
}
func (f favoriteCountDo) Join(table schema.Tabler, on ...field.Expr) IFavoriteCountDo {
return f.withDO(f.DO.Join(table, on...))
}
func (f favoriteCountDo) LeftJoin(table schema.Tabler, on ...field.Expr) IFavoriteCountDo {
return f.withDO(f.DO.LeftJoin(table, on...))
}
func (f favoriteCountDo) RightJoin(table schema.Tabler, on ...field.Expr) IFavoriteCountDo {
return f.withDO(f.DO.RightJoin(table, on...))
}
func (f favoriteCountDo) Group(cols ...field.Expr) IFavoriteCountDo {
return f.withDO(f.DO.Group(cols...))
}
func (f favoriteCountDo) Having(conds ...gen.Condition) IFavoriteCountDo {
return f.withDO(f.DO.Having(conds...))
}
func (f favoriteCountDo) Limit(limit int) IFavoriteCountDo {
return f.withDO(f.DO.Limit(limit))
}
func (f favoriteCountDo) Offset(offset int) IFavoriteCountDo {
return f.withDO(f.DO.Offset(offset))
}
func (f favoriteCountDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IFavoriteCountDo {
return f.withDO(f.DO.Scopes(funcs...))
}
func (f favoriteCountDo) Unscoped() IFavoriteCountDo {
return f.withDO(f.DO.Unscoped())
}
func (f favoriteCountDo) Create(values ...*model.FavoriteCount) error {
if len(values) == 0 {
return nil
}
return f.DO.Create(values)
}
func (f favoriteCountDo) CreateInBatches(values []*model.FavoriteCount, batchSize int) error {
return f.DO.CreateInBatches(values, batchSize)
}
// Save : !!! underlying implementation is different with GORM
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
func (f favoriteCountDo) Save(values ...*model.FavoriteCount) error {
if len(values) == 0 {
return nil
}
return f.DO.Save(values)
}
func (f favoriteCountDo) First() (*model.FavoriteCount, error) {
if result, err := f.DO.First(); err != nil {
return nil, err
} else {
return result.(*model.FavoriteCount), nil
}
}
func (f favoriteCountDo) Take() (*model.FavoriteCount, error) {
if result, err := f.DO.Take(); err != nil {
return nil, err
} else {
return result.(*model.FavoriteCount), nil
}
}
func (f favoriteCountDo) Last() (*model.FavoriteCount, error) {
if result, err := f.DO.Last(); err != nil {
return nil, err
} else {
return result.(*model.FavoriteCount), nil
}
}
func (f favoriteCountDo) Find() ([]*model.FavoriteCount, error) {
result, err := f.DO.Find()
return result.([]*model.FavoriteCount), err
}
func (f favoriteCountDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.FavoriteCount, err error) {
buf := make([]*model.FavoriteCount, 0, batchSize)
err = f.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
defer func() { results = append(results, buf...) }()
return fc(tx, batch)
})
return results, err
}
func (f favoriteCountDo) FindInBatches(result *[]*model.FavoriteCount, batchSize int, fc func(tx gen.Dao, batch int) error) error {
return f.DO.FindInBatches(result, batchSize, fc)
}
func (f favoriteCountDo) Attrs(attrs ...field.AssignExpr) IFavoriteCountDo {
return f.withDO(f.DO.Attrs(attrs...))
}
func (f favoriteCountDo) Assign(attrs ...field.AssignExpr) IFavoriteCountDo {
return f.withDO(f.DO.Assign(attrs...))
}
func (f favoriteCountDo) Joins(fields ...field.RelationField) IFavoriteCountDo {
for _, _f := range fields {
f = *f.withDO(f.DO.Joins(_f))
}
return &f
}
func (f favoriteCountDo) Preload(fields ...field.RelationField) IFavoriteCountDo {
for _, _f := range fields {
f = *f.withDO(f.DO.Preload(_f))
}
return &f
}
func (f favoriteCountDo) FirstOrInit() (*model.FavoriteCount, error) {
if result, err := f.DO.FirstOrInit(); err != nil {
return nil, err
} else {
return result.(*model.FavoriteCount), nil
}
}
func (f favoriteCountDo) FirstOrCreate() (*model.FavoriteCount, error) {
if result, err := f.DO.FirstOrCreate(); err != nil {
return nil, err
} else {
return result.(*model.FavoriteCount), nil
}
}
func (f favoriteCountDo) FindByPage(offset int, limit int) (result []*model.FavoriteCount, count int64, err error) {
result, err = f.Offset(offset).Limit(limit).Find()
if err != nil {
return
}
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
count, err = f.Offset(-1).Limit(-1).Count()
return
}
func (f favoriteCountDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
count, err = f.Count()
if err != nil {
return
}
err = f.Offset(offset).Limit(limit).Scan(result)
return
}
func (f favoriteCountDo) Scan(result interface{}) (err error) {
return f.DO.Scan(result)
}
func (f favoriteCountDo) Delete(models ...*model.FavoriteCount) (result gen.ResultInfo, err error) {
return f.DO.Delete(models)
}
func (f *favoriteCountDo) withDO(do gen.Dao) *favoriteCountDo {
f.DO = *do.(*gen.DO)
return f
}

404
applications/favorite/dal/query/favorites.gen.go

@ -0,0 +1,404 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package query
import (
"context"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/plugin/dbresolver"
"github.com/TremblingV5/DouTok/applications/favorite/dal/model"
)
func newFavorite(db *gorm.DB, opts ...gen.DOOption) favorite {
_favorite := favorite{}
_favorite.favoriteDo.UseDB(db, opts...)
_favorite.favoriteDo.UseModel(&model.Favorite{})
tableName := _favorite.favoriteDo.TableName()
_favorite.ALL = field.NewAsterisk(tableName)
_favorite.ID = field.NewInt64(tableName, "id")
_favorite.UserId = field.NewInt64(tableName, "user_id")
_favorite.VideoId = field.NewInt64(tableName, "video_id")
_favorite.Status = field.NewInt(tableName, "status")
_favorite.CreatedAt = field.NewTime(tableName, "created_at")
_favorite.UpdatedAt = field.NewTime(tableName, "updated_at")
_favorite.fillFieldMap()
return _favorite
}
type favorite struct {
favoriteDo
ALL field.Asterisk
ID field.Int64
UserId field.Int64
VideoId field.Int64
Status field.Int
CreatedAt field.Time
UpdatedAt field.Time
fieldMap map[string]field.Expr
}
func (f favorite) Table(newTableName string) *favorite {
f.favoriteDo.UseTable(newTableName)
return f.updateTableName(newTableName)
}
func (f favorite) As(alias string) *favorite {
f.favoriteDo.DO = *(f.favoriteDo.As(alias).(*gen.DO))
return f.updateTableName(alias)
}
func (f *favorite) updateTableName(table string) *favorite {
f.ALL = field.NewAsterisk(table)
f.ID = field.NewInt64(table, "id")
f.UserId = field.NewInt64(table, "user_id")
f.VideoId = field.NewInt64(table, "video_id")
f.Status = field.NewInt(table, "status")
f.CreatedAt = field.NewTime(table, "created_at")
f.UpdatedAt = field.NewTime(table, "updated_at")
f.fillFieldMap()
return f
}
func (f *favorite) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
_f, ok := f.fieldMap[fieldName]
if !ok || _f == nil {
return nil, false
}
_oe, ok := _f.(field.OrderExpr)
return _oe, ok
}
func (f *favorite) fillFieldMap() {
f.fieldMap = make(map[string]field.Expr, 6)
f.fieldMap["id"] = f.ID
f.fieldMap["user_id"] = f.UserId
f.fieldMap["video_id"] = f.VideoId
f.fieldMap["status"] = f.Status
f.fieldMap["created_at"] = f.CreatedAt
f.fieldMap["updated_at"] = f.UpdatedAt
}
func (f favorite) clone(db *gorm.DB) favorite {
f.favoriteDo.ReplaceConnPool(db.Statement.ConnPool)
return f
}
func (f favorite) replaceDB(db *gorm.DB) favorite {
f.favoriteDo.ReplaceDB(db)
return f
}
type favoriteDo struct{ gen.DO }
type IFavoriteDo interface {
gen.SubQuery
Debug() IFavoriteDo
WithContext(ctx context.Context) IFavoriteDo
WithResult(fc func(tx gen.Dao)) gen.ResultInfo
ReplaceDB(db *gorm.DB)
ReadDB() IFavoriteDo
WriteDB() IFavoriteDo
As(alias string) gen.Dao
Session(config *gorm.Session) IFavoriteDo
Columns(cols ...field.Expr) gen.Columns
Clauses(conds ...clause.Expression) IFavoriteDo
Not(conds ...gen.Condition) IFavoriteDo
Or(conds ...gen.Condition) IFavoriteDo
Select(conds ...field.Expr) IFavoriteDo
Where(conds ...gen.Condition) IFavoriteDo
Order(conds ...field.Expr) IFavoriteDo
Distinct(cols ...field.Expr) IFavoriteDo
Omit(cols ...field.Expr) IFavoriteDo
Join(table schema.Tabler, on ...field.Expr) IFavoriteDo
LeftJoin(table schema.Tabler, on ...field.Expr) IFavoriteDo
RightJoin(table schema.Tabler, on ...field.Expr) IFavoriteDo
Group(cols ...field.Expr) IFavoriteDo
Having(conds ...gen.Condition) IFavoriteDo
Limit(limit int) IFavoriteDo
Offset(offset int) IFavoriteDo
Count() (count int64, err error)
Scopes(funcs ...func(gen.Dao) gen.Dao) IFavoriteDo
Unscoped() IFavoriteDo
Create(values ...*model.Favorite) error
CreateInBatches(values []*model.Favorite, batchSize int) error
Save(values ...*model.Favorite) error
First() (*model.Favorite, error)
Take() (*model.Favorite, error)
Last() (*model.Favorite, error)
Find() ([]*model.Favorite, error)
FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Favorite, err error)
FindInBatches(result *[]*model.Favorite, batchSize int, fc func(tx gen.Dao, batch int) error) error
Pluck(column field.Expr, dest interface{}) error
Delete(...*model.Favorite) (info gen.ResultInfo, err error)
Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
Updates(value interface{}) (info gen.ResultInfo, err error)
UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
UpdateFrom(q gen.SubQuery) gen.Dao
Attrs(attrs ...field.AssignExpr) IFavoriteDo
Assign(attrs ...field.AssignExpr) IFavoriteDo
Joins(fields ...field.RelationField) IFavoriteDo
Preload(fields ...field.RelationField) IFavoriteDo
FirstOrInit() (*model.Favorite, error)
FirstOrCreate() (*model.Favorite, error)
FindByPage(offset int, limit int) (result []*model.Favorite, count int64, err error)
ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
Scan(result interface{}) (err error)
Returning(value interface{}, columns ...string) IFavoriteDo
UnderlyingDB() *gorm.DB
schema.Tabler
}
func (f favoriteDo) Debug() IFavoriteDo {
return f.withDO(f.DO.Debug())
}
func (f favoriteDo) WithContext(ctx context.Context) IFavoriteDo {
return f.withDO(f.DO.WithContext(ctx))
}
func (f favoriteDo) ReadDB() IFavoriteDo {
return f.Clauses(dbresolver.Read)
}
func (f favoriteDo) WriteDB() IFavoriteDo {
return f.Clauses(dbresolver.Write)
}
func (f favoriteDo) Session(config *gorm.Session) IFavoriteDo {
return f.withDO(f.DO.Session(config))
}
func (f favoriteDo) Clauses(conds ...clause.Expression) IFavoriteDo {
return f.withDO(f.DO.Clauses(conds...))
}
func (f favoriteDo) Returning(value interface{}, columns ...string) IFavoriteDo {
return f.withDO(f.DO.Returning(value, columns...))
}
func (f favoriteDo) Not(conds ...gen.Condition) IFavoriteDo {
return f.withDO(f.DO.Not(conds...))
}
func (f favoriteDo) Or(conds ...gen.Condition) IFavoriteDo {
return f.withDO(f.DO.Or(conds...))
}
func (f favoriteDo) Select(conds ...field.Expr) IFavoriteDo {
return f.withDO(f.DO.Select(conds...))
}
func (f favoriteDo) Where(conds ...gen.Condition) IFavoriteDo {
return f.withDO(f.DO.Where(conds...))
}
func (f favoriteDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) IFavoriteDo {
return f.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
}
func (f favoriteDo) Order(conds ...field.Expr) IFavoriteDo {
return f.withDO(f.DO.Order(conds...))
}
func (f favoriteDo) Distinct(cols ...field.Expr) IFavoriteDo {
return f.withDO(f.DO.Distinct(cols...))
}
func (f favoriteDo) Omit(cols ...field.Expr) IFavoriteDo {
return f.withDO(f.DO.Omit(cols...))
}
func (f favoriteDo) Join(table schema.Tabler, on ...field.Expr) IFavoriteDo {
return f.withDO(f.DO.Join(table, on...))
}
func (f favoriteDo) LeftJoin(table schema.Tabler, on ...field.Expr) IFavoriteDo {
return f.withDO(f.DO.LeftJoin(table, on...))
}
func (f favoriteDo) RightJoin(table schema.Tabler, on ...field.Expr) IFavoriteDo {
return f.withDO(f.DO.RightJoin(table, on...))
}
func (f favoriteDo) Group(cols ...field.Expr) IFavoriteDo {
return f.withDO(f.DO.Group(cols...))
}
func (f favoriteDo) Having(conds ...gen.Condition) IFavoriteDo {
return f.withDO(f.DO.Having(conds...))
}
func (f favoriteDo) Limit(limit int) IFavoriteDo {
return f.withDO(f.DO.Limit(limit))
}
func (f favoriteDo) Offset(offset int) IFavoriteDo {
return f.withDO(f.DO.Offset(offset))
}
func (f favoriteDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IFavoriteDo {
return f.withDO(f.DO.Scopes(funcs...))
}
func (f favoriteDo) Unscoped() IFavoriteDo {
return f.withDO(f.DO.Unscoped())
}
func (f favoriteDo) Create(values ...*model.Favorite) error {
if len(values) == 0 {
return nil
}
return f.DO.Create(values)
}
func (f favoriteDo) CreateInBatches(values []*model.Favorite, batchSize int) error {
return f.DO.CreateInBatches(values, batchSize)
}
// Save : !!! underlying implementation is different with GORM
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
func (f favoriteDo) Save(values ...*model.Favorite) error {
if len(values) == 0 {
return nil
}
return f.DO.Save(values)
}
func (f favoriteDo) First() (*model.Favorite, error) {
if result, err := f.DO.First(); err != nil {
return nil, err
} else {
return result.(*model.Favorite), nil
}
}
func (f favoriteDo) Take() (*model.Favorite, error) {
if result, err := f.DO.Take(); err != nil {
return nil, err
} else {
return result.(*model.Favorite), nil
}
}
func (f favoriteDo) Last() (*model.Favorite, error) {
if result, err := f.DO.Last(); err != nil {
return nil, err
} else {
return result.(*model.Favorite), nil
}
}
func (f favoriteDo) Find() ([]*model.Favorite, error) {
result, err := f.DO.Find()
return result.([]*model.Favorite), err
}
func (f favoriteDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Favorite, err error) {
buf := make([]*model.Favorite, 0, batchSize)
err = f.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
defer func() { results = append(results, buf...) }()
return fc(tx, batch)
})
return results, err
}
func (f favoriteDo) FindInBatches(result *[]*model.Favorite, batchSize int, fc func(tx gen.Dao, batch int) error) error {
return f.DO.FindInBatches(result, batchSize, fc)
}
func (f favoriteDo) Attrs(attrs ...field.AssignExpr) IFavoriteDo {
return f.withDO(f.DO.Attrs(attrs...))
}
func (f favoriteDo) Assign(attrs ...field.AssignExpr) IFavoriteDo {
return f.withDO(f.DO.Assign(attrs...))
}
func (f favoriteDo) Joins(fields ...field.RelationField) IFavoriteDo {
for _, _f := range fields {
f = *f.withDO(f.DO.Joins(_f))
}
return &f
}
func (f favoriteDo) Preload(fields ...field.RelationField) IFavoriteDo {
for _, _f := range fields {
f = *f.withDO(f.DO.Preload(_f))
}
return &f
}
func (f favoriteDo) FirstOrInit() (*model.Favorite, error) {
if result, err := f.DO.FirstOrInit(); err != nil {
return nil, err
} else {
return result.(*model.Favorite), nil
}
}
func (f favoriteDo) FirstOrCreate() (*model.Favorite, error) {
if result, err := f.DO.FirstOrCreate(); err != nil {
return nil, err
} else {
return result.(*model.Favorite), nil
}
}
func (f favoriteDo) FindByPage(offset int, limit int) (result []*model.Favorite, count int64, err error) {
result, err = f.Offset(offset).Limit(limit).Find()
if err != nil {
return
}
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
count, err = f.Offset(-1).Limit(-1).Count()
return
}
func (f favoriteDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
count, err = f.Count()
if err != nil {
return
}
err = f.Offset(offset).Limit(limit).Scan(result)
return
}
func (f favoriteDo) Scan(result interface{}) (err error) {
return f.DO.Scan(result)
}
func (f favoriteDo) Delete(models ...*model.Favorite) (result gen.ResultInfo, err error) {
return f.DO.Delete(models)
}
func (f *favoriteDo) withDO(do gen.Dao) *favoriteDo {
f.DO = *do.(*gen.DO)
return f
}

107
applications/favorite/dal/query/gen.go

@ -0,0 +1,107 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package query
import (
"context"
"database/sql"
"gorm.io/gorm"
"gorm.io/gen"
"gorm.io/plugin/dbresolver"
)
var (
Q = new(Query)
Favorite *favorite
FavoriteCount *favoriteCount
)
func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
*Q = *Use(db, opts...)
Favorite = &Q.Favorite
FavoriteCount = &Q.FavoriteCount
}
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
return &Query{
db: db,
Favorite: newFavorite(db, opts...),
FavoriteCount: newFavoriteCount(db, opts...),
}
}
type Query struct {
db *gorm.DB
Favorite favorite
FavoriteCount favoriteCount
}
func (q *Query) Available() bool { return q.db != nil }
func (q *Query) clone(db *gorm.DB) *Query {
return &Query{
db: db,
Favorite: q.Favorite.clone(db),
FavoriteCount: q.FavoriteCount.clone(db),
}
}
func (q *Query) ReadDB() *Query {
return q.ReplaceDB(q.db.Clauses(dbresolver.Read))
}
func (q *Query) WriteDB() *Query {
return q.ReplaceDB(q.db.Clauses(dbresolver.Write))
}
func (q *Query) ReplaceDB(db *gorm.DB) *Query {
return &Query{
db: db,
Favorite: q.Favorite.replaceDB(db),
FavoriteCount: q.FavoriteCount.replaceDB(db),
}
}
type queryCtx struct {
Favorite IFavoriteDo
FavoriteCount IFavoriteCountDo
}
func (q *Query) WithContext(ctx context.Context) *queryCtx {
return &queryCtx{
Favorite: q.Favorite.WithContext(ctx),
FavoriteCount: q.FavoriteCount.WithContext(ctx),
}
}
func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error {
return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...)
}
func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx {
return &QueryTx{q.clone(q.db.Begin(opts...))}
}
type QueryTx struct{ *Query }
func (q *QueryTx) Commit() error {
return q.db.Commit().Error
}
func (q *QueryTx) Rollback() error {
return q.db.Rollback().Error
}
func (q *QueryTx) SavePoint(name string) error {
return q.db.SavePoint(name).Error
}
func (q *QueryTx) RollbackTo(name string) error {
return q.db.RollbackTo(name).Error
}

4
applications/favorite/dal/query/var.go

@ -0,0 +1,4 @@
package query
var FavoriteStruct = &favorite{}
var FavoriteCountStruct = &favoriteCount{}

24
applications/favorite/handler/favorite_action.go

@ -0,0 +1,24 @@
package handler
import (
"context"
"github.com/TremblingV5/DouTok/applications/favorite/misc"
"github.com/TremblingV5/DouTok/applications/favorite/pack"
"github.com/TremblingV5/DouTok/applications/favorite/service"
"github.com/TremblingV5/DouTok/kitex_gen/favorite"
)
func (s *FavoriteServiceImpl) FavoriteAction(ctx context.Context, req *favorite.DouyinFavoriteActionRequest) (resp *favorite.DouyinFavoriteActionResponse, err error) {
if req.ActionType == 1 {
// 点赞
errNo, _ := service.ActionFavorite(req.UserId, req.VideoId, true)
return pack.PackFavoriteActionResp(int32(errNo.ErrCode), errNo.ErrMsg)
} else if req.ActionType == 2 {
// 取消点赞
errNo, _ := service.ActionFavorite(req.UserId, req.VideoId, false)
return pack.PackFavoriteActionResp(int32(errNo.ErrCode), errNo.ErrMsg)
} else {
return pack.PackFavoriteActionResp(int32(misc.BindingInvalidErr.ErrCode), misc.BindingInvalidErr.ErrMsg)
}
}

23
applications/favorite/handler/favorite_count.go

@ -0,0 +1,23 @@
package handler
import (
"context"
"github.com/TremblingV5/DouTok/applications/favorite/misc"
"github.com/TremblingV5/DouTok/applications/favorite/pack"
"github.com/TremblingV5/DouTok/applications/favorite/service"
"github.com/TremblingV5/DouTok/kitex_gen/favorite"
)
func (s *FavoriteServiceImpl) FavoriteCount(ctx context.Context, req *favorite.DouyinFavoriteCountRequest) (resp *favorite.DouyinFavoriteCountResponse, err error) {
if len(req.VideoIdList) == 0 {
return pack.PackFavoriteCountResp(int32(misc.EmptyVideoIdListErr.ErrCode), misc.EmptyVideoIdListErr.ErrMsg, nil)
}
res, err := service.QueryFavoriteCount(req.VideoIdList)
if err != nil {
return pack.PackFavoriteCountResp(int32(misc.SystemErr.ErrCode), misc.SystemErr.ErrMsg, nil)
}
return pack.PackFavoriteCountResp(int32(misc.Success.ErrCode), misc.Success.ErrMsg, res)
}

35
applications/favorite/handler/favorite_list.go

@ -0,0 +1,35 @@
package handler
import (
"context"
"github.com/TremblingV5/DouTok/applications/favorite/misc"
"github.com/TremblingV5/DouTok/applications/favorite/pack"
"github.com/TremblingV5/DouTok/applications/favorite/service"
"github.com/TremblingV5/DouTok/kitex_gen/favorite"
)
func (s *FavoriteServiceImpl) FavoriteList(ctx context.Context, req *favorite.DouyinFavoriteListRequest) (resp *favorite.DouyinFavoriteListResponse, err error) {
// 1. 查缓存
res, _ := service.QueryFavListInCache(req.UserId)
// 2. 如果缓存有则直接返回
if len(res) > 0 {
return pack.PackFavoriteListResp(int32(misc.Success.ErrCode), misc.Success.ErrMsg, res)
}
// 3. 如果缓存没有则查库
res, err = service.QueryFavListInRDB(req.UserId)
// 4. 将从RDB查询到的数据读入缓存
for _, v := range res {
if err := service.WriteFavoriteInCache(req.UserId, v, true); err != nil {
continue
}
}
if err != nil {
pack.PackFavoriteListResp(int32(misc.SystemErr.ErrCode), misc.SystemErr.ErrMsg, nil)
}
return pack.PackFavoriteListResp(int32(misc.Success.ErrCode), misc.Success.ErrMsg, res)
}

20
applications/favorite/handler/is_favorite.go

@ -0,0 +1,20 @@
package handler
import (
"context"
"github.com/TremblingV5/DouTok/applications/favorite/misc"
"github.com/TremblingV5/DouTok/applications/favorite/pack"
"github.com/TremblingV5/DouTok/applications/favorite/service"
"github.com/TremblingV5/DouTok/kitex_gen/favorite"
)
func (s *FavoriteServiceImpl) IsFavorite(ctx context.Context, req *favorite.DouyinIsFavoriteRequest) (resp *favorite.DouyinIsFavoriteResponse, err error) {
res, err := service.QueryIsFavorite(req.UserId, req.VideoIdList)
if err != nil {
return pack.PackIsFavoriteResp(int32(misc.SystemErr.ErrCode), misc.SystemErr.ErrMsg, nil)
}
return pack.PackIsFavoriteResp(int32(misc.Success.ErrCode), misc.Success.ErrMsg, res)
}

3
applications/favorite/handler/typedef.go

@ -0,0 +1,3 @@
package handler
type FavoriteServiceImpl struct{}

43
applications/favorite/main.go

@ -0,0 +1,43 @@
package main
import (
"github.com/TremblingV5/DouTok/applications/favorite/handler"
"github.com/TremblingV5/DouTok/applications/favorite/misc"
"github.com/TremblingV5/DouTok/applications/favorite/rpc"
"github.com/TremblingV5/DouTok/applications/favorite/service"
"github.com/TremblingV5/DouTok/kitex_gen/favorite/favoriteservice"
"github.com/TremblingV5/DouTok/pkg/dlog"
"github.com/TremblingV5/DouTok/pkg/initHelper"
)
var (
Logger = dlog.InitLog(3)
)
func Init() {
misc.InitViperConfig()
service.Init()
rpc.InitPRCClient()
go service.UpdateFavMap()
go service.UpdateFavCntMap()
go service.Consumer4UpdateCount()
}
func main() {
Init()
options, shutdown := initHelper.InitRPCServerArgs(misc.Config)
defer shutdown()
svr := favoriteservice.NewServer(
new(handler.FavoriteServiceImpl),
options...,
)
if err := svr.Run(); err != nil {
Logger.Fatal(err)
}
}

18
applications/favorite/misc/config.go

@ -0,0 +1,18 @@
package misc
import "github.com/TremblingV5/DouTok/pkg/dtviper"
var Config *dtviper.Config
func InitViperConfig() {
config := dtviper.ConfigInit(ViperConfigEnvPrefix, ViperConfigEnvFilename)
Config = config
}
func GetConfig(key string) string {
return Config.Viper.GetString(key)
}
func GetConfigNum(key string) int64 {
return Config.Viper.GetInt64(key)
}

22
applications/favorite/misc/const.go

@ -0,0 +1,22 @@
package misc
var (
FavCountTopicName = "fav_count"
FavCountGroupName = "fav_count01"
ViperConfigEnvPrefix = "DOUTOK_FAVORITE"
ViperConfigEnvFilename = "favorite"
ConfigIndex_MySQLUsername = "MySQL.Username"
ConfigIndex_MySQLPassword = "MySQL.Password"
ConfigIndex_MySQLHost = "MySQL.Host"
ConfigIndex_MySQLPort = "MySQL.Port"
ConfigIndex_MySQLDb = "MySQL.Database"
ConfigIndex_RedisDest = "Redis.Dest"
ConfigIndex_RedisPassword = "Redis.Password"
ConfigIndex_RedisFavCacheDbNum = "Redis.FavCache.Num"
ConfigIndex_RedisFavCntCacheDbNum = "Redis.FavCntCache.Num"
ConfigIndex_SnowFlake = "Snowflake.Node"
)

27
applications/favorite/misc/err.go

@ -0,0 +1,27 @@
package misc
import "github.com/TremblingV5/DouTok/pkg/errno"
var (
NilErrCode = -1
SuccessCode = 0
ParamsErrCode = 27001
SystemErrCode = 27002
EmptyVideoIdListErrCode = 27003
BindingInvalidErrCode = 27004
QueryCacheErrCode = 27005
CreateFavoriteInRDBErrCode = 27006
WriteRDBErrCode = 27007
)
var (
NilErr = errno.NewErrNo(NilErrCode, "Don't care")
Success = errno.NewErrNo(SuccessCode, "Success")
ParamsErr = errno.NewErrNo(ParamsErrCode, "Invalid parameters")
SystemErr = errno.NewErrNo(SystemErrCode, "System error")
EmptyVideoIdListErr = errno.NewErrNo(EmptyVideoIdListErrCode, "Empty video id list")
BindingInvalidErr = errno.NewErrNo(BindingInvalidErrCode, "Action type is invalid")
QueryCacheErr = errno.NewErrNo(QueryCacheErrCode, "Query cache defeat")
CreateFavoriteInRDBErr = errno.NewErrNo(CreateFavoriteInRDBErrCode, "Create new favorite relation in rdb defeat")
WriteRDBErr = errno.NewErrNo(WriteRDBErrCode, "Write rdb defeat")
)

5
applications/favorite/misc/var.go

@ -0,0 +1,5 @@
package misc
var FavCache = "favcache"
var FavCntCache = "favcountcache"
var FavTotalCntCache = "favtotalcountcache"

10
applications/favorite/pack/favorite_action.go

@ -0,0 +1,10 @@
package pack
import "github.com/TremblingV5/DouTok/kitex_gen/favorite"
func PackFavoriteActionResp(code int32, msg string) (resp *favorite.DouyinFavoriteActionResponse, err error) {
return &favorite.DouyinFavoriteActionResponse{
StatusCode: code,
StatusMsg: msg,
}, nil
}

13
applications/favorite/pack/favorite_count.go

@ -0,0 +1,13 @@
package pack
import "github.com/TremblingV5/DouTok/kitex_gen/favorite"
func PackFavoriteCountResp(code int32, msg string, list map[int64]int64) (resp *favorite.DouyinFavoriteCountResponse, err error) {
resp = &favorite.DouyinFavoriteCountResponse{
StatusCode: code,
StatusMsg: msg,
Result: list,
}
return resp, nil
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save