├── .env.docker.compose ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature_request.md └── workflows │ ├── devcheck.yml │ ├── docker-push.yml │ ├── lint.yml │ ├── maincheck.yml │ └── qodana.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTING_CN.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── config └── logs │ ├── fluent-bit.conf │ ├── parsers.conf │ └── readme.md ├── docker-compose.yaml ├── docker ├── basic │ ├── Dockerfile │ └── readme.md ├── clash │ ├── Dockerfile │ └── readme.md └── rabbitmq │ └── Dockerfile ├── docs └── README-CN.md ├── go.mod ├── go.sum ├── gorse-config.docker.compose.toml ├── manifests-endymx ├── configmap.yaml ├── deployment-auth-service.yaml ├── deployment-comment-service.yaml ├── deployment-event-service.yaml ├── deployment-favorite-service.yaml ├── deployment-feed-service.yaml ├── deployment-http-service.yaml ├── deployment-message-service.yaml ├── deployment-msg-consumer-service.yaml ├── deployment-publish-service.yaml ├── deployment-recommend-service.yaml ├── deployment-relation-service.yaml ├── deployment-user-service.yaml ├── deployment-video-processor-service.yaml └── sevice-http-api.yaml ├── promethus.docker.compose.yml ├── scripts ├── build-all.bat ├── build-all.ps1 ├── build-all.sh └── run-all.sh ├── src ├── constant │ ├── config │ │ ├── .env.example │ │ ├── env.go │ │ └── service.go │ └── strings │ │ ├── common.go │ │ ├── err.go │ │ └── service.go ├── extra │ ├── gorse │ │ ├── client.go │ │ └── model.go │ ├── profiling │ │ └── analyzer.go │ └── tracing │ │ └── otel.go ├── idl │ ├── auth.proto │ ├── chat.proto │ ├── check.proto │ ├── comment.proto │ ├── favorite.proto │ ├── feed.proto │ ├── publish.proto │ ├── recommand.proto │ ├── relation.proto │ └── user.proto ├── models │ ├── action.go │ ├── comment.go │ ├── event.go │ ├── message.go │ ├── rawvideo.go │ ├── relation.go │ ├── user.go │ └── video.go ├── rpc │ ├── auth │ │ ├── auth.pb.go │ │ └── auth_grpc.pb.go │ ├── chat │ │ ├── chat.pb.go │ │ └── chat_grpc.pb.go │ ├── comment │ │ ├── comment.pb.go │ │ └── comment_grpc.pb.go │ ├── favorite │ │ ├── favorite.pb.go │ │ └── favorite_grpc.pb.go │ ├── feed │ │ ├── feed.pb.go │ │ └── feed_grpc.pb.go │ ├── health │ │ ├── check.pb.go │ │ └── check_grpc.pb.go │ ├── publish │ │ ├── publish.pb.go │ │ └── publish_grpc.pb.go │ ├── recommend │ │ ├── recommand.pb.go │ │ └── recommand_grpc.pb.go │ ├── relation │ │ ├── relation.pb.go │ │ └── relation_grpc.pb.go │ └── user │ │ ├── user.pb.go │ │ └── user_grpc.pb.go ├── services │ ├── auth │ │ ├── handler.go │ │ └── main.go │ ├── comment │ │ ├── handler.go │ │ ├── main.go │ │ └── moderation.go │ ├── event │ │ └── main.go │ ├── favorite │ │ ├── handler.go │ │ └── main.go │ ├── feed │ │ ├── handler.go │ │ └── main.go │ ├── message │ │ ├── handler.go │ │ └── main.go │ ├── msgconsumer │ │ ├── esexchange.go │ │ └── main.go │ ├── publish │ │ ├── handler.go │ │ └── main.go │ ├── recommend │ │ ├── handler.go │ │ └── main.go │ ├── relation │ │ ├── handler.go │ │ └── main.go │ ├── user │ │ ├── handler.go │ │ └── main.go │ └── videoprocessor │ │ ├── main.go │ │ └── summary.go ├── storage │ ├── cached │ │ ├── cache.go │ │ └── ticker.go │ ├── database │ │ └── gorm.go │ ├── es │ │ └── Elasticsearch.go │ ├── file │ │ ├── fs.go │ │ └── provider.go │ ├── mq │ │ └── rabbitmq.go │ └── redis │ │ └── redis.go ├── utils │ ├── audit │ │ └── publish.go │ ├── consul │ │ └── register.go │ ├── grpc │ │ └── connection.go │ ├── logging │ │ ├── gorm.go │ │ └── logging.go │ ├── pathgen │ │ └── video.go │ ├── prom │ │ ├── interceptor.go │ │ └── pack.go │ ├── ptr │ │ └── ptr.go │ └── rabbitmq │ │ ├── mq.go │ │ └── otel.go └── web │ ├── about │ └── handler.go │ ├── auth │ └── handler.go │ ├── comment │ └── handler.go │ ├── favorite │ └── handler.go │ ├── feed │ └── handler.go │ ├── main.go │ ├── message │ └── handler.go │ ├── middleware │ ├── authenticate.go │ └── limiter.go │ ├── models │ ├── About.go │ ├── Comment.go │ ├── Favorite.go │ ├── Feed.go │ ├── Login.go │ ├── Message.go │ ├── Publish.go │ ├── Relation.go │ └── User.go │ ├── publish │ └── handler.go │ ├── relation │ └── handler.go │ ├── user │ └── handler.go │ └── utils │ └── json.go └── test ├── k6 ├── comment.js ├── comment_get.js ├── favorite.js ├── favorite_random.js └── feed.js ├── rpc ├── authrpc_test.go ├── commentrpc_test.go ├── feedrpc_test.go ├── like_test.go ├── messagerpc_test.go ├── publishrpc_test.go ├── relationrpc_test.go └── userrpc_test.go └── web ├── auth_test.go ├── comment_test.go ├── favorite_test.go ├── feed_test.go ├── message_test.go ├── publish_test.go ├── relation_test.go └── value.go /.env.docker.compose: -------------------------------------------------------------------------------- 1 | # Configure Consul address, the default address is `localhost:8500` 2 | # TIPS: If you provide `CONSUL_ANONYMITY_NAME`, all services will register with `CONSUL_ANONYMITY_NAME` as prefix 3 | CONSUL_ADDR=consul:8500 4 | CONSUL_ANONYMITY_NAME=paraparty. 5 | # Configure logger level, support: DEBUG, INFO, WARN (WARNING), ERROR, FATAL 6 | LOGGER_LEVEL=INFO 7 | # Cofigure logger integrated with otel, support: enable, disable 8 | # If this setting is enable, you will see log in the OTEL Export with possible runtime waste 9 | LOGGER_OUT_TRACING=enable 10 | # Configure Tied information, which will be bound with every log print 11 | TIED= 12 | # Configure PostgreSQL connection information 13 | # You can just provide conn, and the program will auto migrate data 14 | # If you do not provide PostgreSQL schema, this field would not take effect without any error 15 | POSTGRESQL_HOST=rdb 16 | POSTGRESQL_PORT=5432 17 | POSTGRESQL_USER=gugotik 18 | POSTGRESQL_PASSWORD=gugotik123 19 | POSTGRESQL_DATABASE=gugodb 20 | POSTGRESQL_SCHEMA=public 21 | POSTGRESQL_PREFIX=gugotik_ 22 | # Configure storage mode, support: fs, s3 23 | # fs: stoarge binary files in the local machine, use this should provide `FS_PATH` config, or will output at /tmp. Aslo, 24 | # you should provide `FS_BASEURL`, the default is `http://localhost/` 25 | # s3: I do not know what is s3, do not ask me plz. 26 | STORAGE_TYPE=fs 27 | FS_PATH=/usr/share/nginx/html/ 28 | FS_BASEURL=http://192.168.124.33:8066/ 29 | # Configure redis host 30 | # `REDIS_PASSWORD` has a default value '' 31 | # `REDIS_DB` has a default value '0' 32 | # `REDIS_PREFIX` will make field `PREFIX-KEYNAME` style 33 | # TIPS: There is a Auto choose mode for Redis 34 | # TIPS: You can opt to use `Single Redis Node` with providing a single ip 35 | # TIPS: You can opt to use `Redis Cluster` with providing multi redis using ';' to split 36 | # TIPS: When you trying to use Redis Cluster, you should ensure they have the same password or have no password 37 | # TIPS: If you do not provide the name of REDIS_MASTER, the Redis client will use normal way get addr of REDIS SERVER 38 | REDIS_PREFIX=GuGoTik 39 | REDIS_ADDR=redis:6379 40 | REDIS_PASSWORD= 41 | REDIS_DB= 42 | REDIS_MASTER= 43 | # Config Tracing EndPoint, support Jaeger 44 | # Config state, if use `disable` the sampler will be closed. use `enable` to enable 45 | TRACING_STATE=enable 46 | # Config tracing sampler, suggest 0.01 47 | TRACING_SAMPLER=0.1 48 | TRACING_ENDPOINT=jaeger:4318 49 | # Optional: Config Pyroscope 50 | # Decide whether to enable the service, support : enable, disable. 51 | # If you enable this service, you must provide Pyroscope server environment 52 | # This profiling is ONLY designed for DEBUGGING 53 | # SO, PLEASE DO NOT ENABLE THIS SERVICE IN YOUR PRODUCTION ENVIRONMENT, OR IT MAY TAKE MUCH RUNTIME COST. 54 | PYROSCOPE_STATE=enable 55 | PYROSCOPE_ADDR=http://pyroscope:4040/ 56 | # Configure RabbitMQ 57 | # Optional: `RABBITMQ_VHOST_PREFIX`: If you provide this config, the service will use value as the rabbit mq vhost prefix. 58 | # The default value of `RABBITMQ_VHOST_PREFIX` is empty, so if the service use `/post`, the real host is `/post` also. 59 | # ATTENTION: The value of `RABBITMQ_VHOST_PREFIX` is "path/to/your/host" like, such as `gugotik`, but not `/gugotik` 60 | RABBITMQ_USERNAME=guest 61 | RABBITMQ_PASSWORD=guest 62 | RABBITMQ_ADDRESS=rabbitmq 63 | RABBITMQ_PORT=5672 64 | RABBITMQ_VHOST_PREFIX= 65 | # ChatGPT API secret key 66 | CHATGPT_API_KEYS= 67 | # Gorse provides recommend service for GuGoTik. 68 | GORSE_ADDR=http://gorse-server:8087 69 | GORSE_APIKEY=5105502fc46a411c896aa5b50c31e951 70 | ANONYMITY_USER=50 71 | # Configure your Elastic Search Address 72 | ES_ADDR=http://elasticsearch:9200 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug of GuGoTik 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: liaosunny123 7 | 8 | --- 9 | 10 | **Component** 11 | The component of bug is: xxx 12 | 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior. 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for GuGoTik 4 | title: "[Feature]" 5 | labels: enhancement 6 | assignees: liaosunny123 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | 22 | **Do you want to PR?** 23 | Yes ! / No ! 24 | -------------------------------------------------------------------------------- /.github/workflows/devcheck.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: DevBuildCheck 5 | 6 | on: 7 | push: 8 | branches: [ "dev" ] 9 | pull_request: 10 | branches: [ "dev" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.20' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | -------------------------------------------------------------------------------- /.github/workflows/docker-push.yml: -------------------------------------------------------------------------------- 1 | name: Docker Push 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: [ "main" ] 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | environment: Docker 12 | steps: 13 | - 14 | name: Set up QEMU 15 | uses: docker/setup-qemu-action@v2 16 | - 17 | name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v2 19 | - 20 | name: Login to Docker Hub 21 | uses: docker/login-action@v2 22 | with: 23 | username: ${{ secrets.DOCKERHUB_USERNAME }} 24 | password: ${{ secrets.DOCKERHUB_TOKEN }} 25 | - 26 | name: Build and push 27 | uses: docker/build-push-action@v4 28 | with: 29 | push: true 30 | tags: | 31 | ${{ secrets.DOCKERHUB_USERNAME }}/gugotik:latest 32 | ${{ secrets.DOCKERHUB_USERNAME }}/gugotik:${{ github.sha }} 33 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: 5 | - dev 6 | - main 7 | pull_request: 8 | branches: 9 | - dev 10 | - main 11 | 12 | permissions: 13 | contents: read 14 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 15 | # pull-requests: read 16 | 17 | jobs: 18 | golangci: 19 | name: lint 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: actions/setup-go@v4 24 | with: 25 | go-version: '1.20' 26 | cache: false 27 | - name: golangci-lint 28 | uses: golangci/golangci-lint-action@v3 29 | with: 30 | # Require: The version of golangci-lint to use. 31 | # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. 32 | # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. 33 | version: v1.53 -------------------------------------------------------------------------------- /.github/workflows/maincheck.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: MainBuildCheck 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.20' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | -------------------------------------------------------------------------------- /.github/workflows/qodana.yml: -------------------------------------------------------------------------------- 1 | name: Code Scan 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | branches: [ "dev" ] 6 | 7 | jobs: 8 | qodana: 9 | runs-on: ubuntu-latest 10 | environment: Analysis 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 0 15 | - name: 'Qodana Scan' 16 | uses: JetBrains/qodana-action@v2023.2 17 | with: 18 | pr-mode: false 19 | args: --apply-fixes 20 | push-fixes: pull-request 21 | upload-result: true 22 | env: 23 | QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} 24 | - uses: github/codeql-action/upload-sarif@v2 25 | with: 26 | sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | debug/ 3 | output/ 4 | log/ 5 | .env 6 | docker/basic/static 7 | docker/clash/static 8 | docker/rabbitmq/static 9 | static/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # contribute 2 | We welcome anyone to contribute to GuGoTik, and look forward to your contributions to GuGoTik, whether it is bug fixes or feature contributions! 3 | [中文简体](CONTRIBUTING_CN.md) 4 | # Contribution process 5 | - Before you start contributing to something, please submit an Issue and describe in detail what you are doing 6 | For example: if you want to fix a bug, you need to raise the bug issue first, and then comment that you are working on this, so that we can Assign the task to you. 7 | - When you are contributing, please make sure to follow GuGoTik's original project code style, and complete GoLint operations and single-test operations locally. 8 | - When you have completed your code modification, please submit it to the repository in the form of a PR and mention your Issue for us to track. 9 | -------------------------------------------------------------------------------- /CONTRIBUTING_CN.md: -------------------------------------------------------------------------------- 1 | # 贡献 2 | 我们欢迎任何人参与到 GuGoTik 的贡献中,并且期待你们对于 GuGoTik 无论是 Bug 修复,还是 Feature 贡献等任何 Contribution ! 3 | # 贡献流程 4 | - 在你开始参与某方面的贡献前,请先提交一个 Issue,并且详细描述你正在进行的行为 5 | 例如:如果你要修复某个 Bug,需要先提出 Bug Issue,然后评论你正在进行这方面的工作,以供我们 Assign 任务到你。 6 | - 当你正在贡献的时候,请确保遵守了 GuGoTik 原有的项目代码风格,并且在本地完成 GoLint 操作和单测操作。 7 | - 当你完成的你的代码修改后,请以 PR 的形式提交到仓库,并提及你的 Issue,以供我们追踪。 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | 3 | WORKDIR /build 4 | 5 | ENV CGO_ENABLED 0 6 | ENV GOPROXY https://goproxy.cn/,direct 7 | 8 | RUN apk update --no-cache \ 9 | && apk upgrade \ 10 | && apk add --no-cache bash \ 11 | bash-doc \ 12 | bash-completion \ 13 | && apk add --no-cache tzdata \ 14 | && rm -rf /var/cache/apk/* 15 | 16 | COPY . . 17 | 18 | RUN go mod download \ 19 | && bash ./scripts/build-all.sh 20 | 21 | FROM docker.io/epicmo/gugotik-basic:1.3 as prod 22 | 23 | ENV TZ Asia/Shanghai 24 | 25 | WORKDIR /data/apps/gugotik-service-bundle 26 | 27 | RUN apk update --no-cache \ 28 | && apk upgrade 29 | 30 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 31 | COPY --from=builder /build/output . -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 2.0.x | :white_check_mark: | 8 | | 1.0.x | :white_check_mark: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | If you find a vulnerability, please report us using a issue. 13 | -------------------------------------------------------------------------------- /config/logs/fluent-bit.conf: -------------------------------------------------------------------------------- 1 | [SERVICE] 2 | Parsers_File parsers.conf 3 | Daemon Off 4 | Log_Level info 5 | HTTP_Server off 6 | HTTP_Listen 0.0.0.0 7 | HTTP_Port 24224 8 | [INPUT] 9 | Name tail 10 | Tag gugotik.* 11 | Path /var/log/gugotik/*.log 12 | Mem_Buf_Limit 10MB 13 | DB /var/log/flt_logs.db 14 | Refresh_Interval 5 15 | Ignore_Older 10s 16 | Rotate_Wait 5 17 | [FILTER] 18 | Name record_modifier 19 | Match * 20 | Key_name message 21 | Record hostname ${HOSTNAME} 22 | Record namespace gugotik 23 | Record environment prod 24 | [OUTPUT] 25 | Name es 26 | Match * 27 | Host [YOUR HOST] 28 | Port 9200 29 | Logstash_Format On 30 | Retry_Limit False 31 | Time_Key @timestamp 32 | Logstash_Prefix gugotik-log -------------------------------------------------------------------------------- /config/logs/parsers.conf: -------------------------------------------------------------------------------- 1 | [PARSER] 2 | Name docker 3 | Format json 4 | Time_Key time 5 | Time_Format %Y-%m-%dT%H:%M:%SZ 6 | Time_Keep On -------------------------------------------------------------------------------- /config/logs/readme.md: -------------------------------------------------------------------------------- 1 | # Log Solution 2 | 3 | GuGoTik 支持以 Fluent-bit 以 Sidecar 模式收集日志,目录中为推荐配置方案,开箱即用。 4 | 5 | # Use 6 | 7 | 请根据需要自行更改 ES 的用户账号和密码等 -------------------------------------------------------------------------------- /docker/basic/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | WORKDIR /data/apps/gugotik-service-bundle/static 4 | 5 | RUN apk update --no-cache \ 6 | && apk upgrade \ 7 | && apk add yasm \ 8 | && apk add ffmpeg \ 9 | && rm -rf /var/cache/apk/* 10 | 11 | COPY ./static . -------------------------------------------------------------------------------- /docker/basic/readme.md: -------------------------------------------------------------------------------- 1 | # 说明 2 | 本镜像为 GuGoTik 提供基础镜像层服务 3 | # 功能 4 | - 提供 FFmpeg 环境 5 | - 将 static 目录下的 font.ttf 作为 GuGoTik 的水印标记 6 | # 集成 7 | 本镜像推送后,将作为主 Dockerfile 的 prod 基础镜像,如果修改了请同步修改主 Dockerfile -------------------------------------------------------------------------------- /docker/clash/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.18.2 2 | 3 | WORKDIR /clash 4 | COPY ./static/clash /clash 5 | COPY ./static/Country.mmdb /root/.config/clash 6 | COPY ./static/config.yaml /root/.config/clash -------------------------------------------------------------------------------- /docker/clash/readme.md: -------------------------------------------------------------------------------- 1 | # 说明 2 | 本镜像为 GuGoTik 提供透明网络代理功能 3 | # 功能 4 | - 提供 OpenAI 访问 5 | # 集成 6 | 本镜像推送后,将作为 VideoProcess / Comment / MessageConsumer / Message 的透明网络镜像,请自行作为透明网络代理 7 | -------------------------------------------------------------------------------- /docker/rabbitmq/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rabbitmq:3.12.3-management 2 | 3 | COPY ./static /plugins 4 | 5 | RUN rabbitmq-plugins enable rabbitmq_delayed_message_exchange -------------------------------------------------------------------------------- /docs/README-CN.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | GuGoTik 4 | 5 |

6 | 7 |
8 | 9 | # GuGoTik 10 | 11 | _✨ 第六届字节跳动青训营进阶班后端实战项目第一名,迷你抖音后端 GuGoTik ✨_ 12 | 13 | 14 |
15 | 16 |

17 | 18 | license 19 | 20 | 21 | release 22 | 23 | 24 | action 25 | 26 | 27 |

28 | 下载 29 | · 30 | 参与贡献 31 | · 32 | 文档 33 |

34 | 35 |

36 | 37 |

GIVE US A STAR PLEASE MY SIR !!! | 请给我们一个 Star 求求了 !!!

38 |

39 | 40 | GuGoTik是 第六届字节跳动青训营后端进阶 实战项目,题目为编写一个小型的抖音后端。 41 | 42 | 如果你想了解更多信息,请等待 青训营结束后 ,GuGoTik 会提供完整的项目开发文档,请给本项目一个 Star ~ 43 | # 贡献者 44 | 项目开发者:这是一群来自五湖四海的 Contributors,来自于 WHU,HNU,NJUPT。 45 | - [EpicMo](https://github.com/liaosunny123) 46 | - [Maples](https://github.com/Maple-pro) 47 | - [Attacks](https://github.com/Attack825) 48 | - [amazing-compass](https://github.com/amazing-compass) 49 | - [XFFFCCCC](https://github.com/XFFFCCCC) 50 | 51 | 特别感谢: 52 | - [Eric](https://github.com/ExerciseBook) 53 | - [Huang Yongliang](https://github.com/956237586) 54 | - [nicognaW](https://github.com/nicognaW) 55 | 56 | 以及有事而无法参与项目的小伙伴: 57 | - [Chuanwise](https://github.com/Chuanwise) 58 | 59 | # 项目结构 60 | - docker: 基础镜像,为项目的Dockerfile提供基础镜像,或者是为 K8S 技术设施提供基础镜像 61 | - scripts: 构建脚本 62 | - src: 项目源代码 63 | - constant: 项目常量 64 | - extra: 外部服务依赖 65 | - idl: idl文件 66 | - models: 数据模型 67 | - rpc: Rpc 代码 68 | - services: 微服务实例 69 | - storage: 存储相关 70 | - utils: 辅助代码 71 | - web: 网关代码 72 | - test: 项目测试 73 | - 其他单文件:Docker Compose 文件和使用的demo环境变量 74 | 75 | # 外部服务依赖 76 | - Redis (Cluster) 77 | - PostgreSQL 78 | - Consul 79 | - OpenTelemetry Collector 80 | - FFMpeg 81 | - Go 82 | 83 | 项目推荐使用以下可观测性基础设施: 84 | - Jaeger 85 | - Victoria Metrics 86 | - Grafana 87 | 88 | Profile 性能分析: 89 | - Pyroscope 90 | 91 | # 自部署流程 92 | 由 梦想珈 RyzeBot 提供自动推送至K8S集群构建流程。 93 | PR 至 Dev 分支,经过基于 Action 的 UnitTest + Code Analysis + Lint + BuildCheck 后,可合并至 endymx 分支。 94 | endymx 分支会自动触发 CD,构建镜像并推送,由 RyzeBot 完成向 K8S 的推送,自动部署。 95 | 96 | # 配置 97 | GuGoTik可以自动捕获环境变量,也可以以 .env 文件的方式手动提供,覆盖顺序为: 98 | .env > 环境变量 > DefaultEnv > EmptyEnv(即默认提供空值,由GuGoTik提供运行时特判) 99 | 100 | # 构建 101 | ## 基于 Standalone 102 | 运行 scripts 文件夹下 build-all 脚本,然后运行 run-all 脚本即可,请选择自己平台支持的脚本。 103 | ## 基于 Docker 104 | ```bash 105 | docker pull epicmo/gugotik:latest 106 | ``` 107 | 通过交互式终端进入容器后自行运行 GateWay 文件夹下和 Services 文件夹下程序 108 | ## 基于 Docker-Compose 109 | 在项目根目录运行: 110 | 注:相关的账号密码设置在 .env.docker.compose 文件查看 111 | ```bash 112 | docker compose up -d 113 | ``` 114 | -------------------------------------------------------------------------------- /manifests-endymx/configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: env-config 5 | namespace: gugotik-service-bundle -------------------------------------------------------------------------------- /manifests-endymx/deployment-auth-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-auth-service 8 | name: gugotik-auth-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-auth-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-auth-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-auth-service 21 | dream-app: gugotik-auth-service 22 | dream-unit: gugotik-auth-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-auth-service 30 | command: 31 | - ./services/auth/AuthService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | ports: 40 | - name: grpc-37001 41 | containerPort: 37001 42 | protocol: TCP 43 | - name: metrics-37099 44 | containerPort: 37099 45 | protocol: TCP 46 | volumeMounts: 47 | - mountPath: /var/log/gugotik 48 | name: log-volume 49 | resources: 50 | limits: 51 | cpu: 2000m 52 | memory: 2048Mi 53 | requests: 54 | cpu: 100m 55 | memory: 128Mi 56 | - name: logger 57 | image: fluent/fluent-bit:1.8.4 58 | imagePullPolicy: IfNotPresent 59 | resources: 60 | requests: 61 | cpu: 20m 62 | memory: 100Mi 63 | limits: 64 | cpu: 100m 65 | memory: 200Mi 66 | volumeMounts: 67 | - mountPath: /fluent-bit/etc 68 | name: config 69 | - mountPath: /var/log/gugotik 70 | name: log-volume 71 | volumes: 72 | - name: config 73 | configMap: 74 | name: gugotik-log-config 75 | - name: log-volume 76 | emptyDir: { } 77 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-comment-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-comment-service 8 | name: gugotik-comment-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-comment-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-comment-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-comment-service 21 | dream-app: gugotik-comment-service 22 | dream-unit: gugotik-comment-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-comment-service 30 | command: 31 | - ./services/comment/CommentService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: grpc-37003 44 | containerPort: 37003 45 | protocol: TCP 46 | - name: metrics-37099 47 | containerPort: 37099 48 | protocol: TCP 49 | resources: 50 | limits: 51 | cpu: 2000m 52 | memory: 2048Mi 53 | requests: 54 | cpu: 100m 55 | memory: 128Mi 56 | - name: logger 57 | image: fluent/fluent-bit:1.8.4 58 | imagePullPolicy: IfNotPresent 59 | resources: 60 | requests: 61 | cpu: 20m 62 | memory: 100Mi 63 | limits: 64 | cpu: 100m 65 | memory: 200Mi 66 | volumeMounts: 67 | - mountPath: /fluent-bit/etc 68 | name: config 69 | - mountPath: /var/log/gugotik 70 | name: log-volume 71 | volumes: 72 | - name: config 73 | configMap: 74 | name: gugotik-log-config 75 | - name: log-volume 76 | emptyDir: { } 77 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-event-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-event-service 8 | name: gugotik-event-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-event-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-event-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-event-service 21 | dream-app: gugotik-event-service 22 | dream-unit: gugotik-event-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-event-service 30 | command: 31 | - ./services/event/EventService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: metrics-37099 44 | containerPort: 37099 45 | protocol: TCP 46 | resources: 47 | limits: 48 | cpu: 2000m 49 | memory: 2048Mi 50 | requests: 51 | cpu: 100m 52 | memory: 128Mi 53 | - name: logger 54 | image: fluent/fluent-bit:1.8.4 55 | imagePullPolicy: IfNotPresent 56 | resources: 57 | requests: 58 | cpu: 20m 59 | memory: 100Mi 60 | limits: 61 | cpu: 100m 62 | memory: 200Mi 63 | volumeMounts: 64 | - mountPath: /fluent-bit/etc 65 | name: config 66 | - mountPath: /var/log/gugotik 67 | name: log-volume 68 | volumes: 69 | - name: config 70 | configMap: 71 | name: gugotik-log-config 72 | - name: log-volume 73 | emptyDir: { } 74 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-favorite-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-favorite-service 8 | name: gugotik-favorite-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-favorite-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-favorite-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-favorite-service 21 | dream-app: gugotik-favorite-service 22 | dream-unit: gugotik-favorite-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-favorite-service 30 | command: 31 | - ./services/favorite/FavoriteService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: grpc-37006 44 | containerPort: 37006 45 | protocol: TCP 46 | - name: metrics-37099 47 | containerPort: 37099 48 | protocol: TCP 49 | resources: 50 | limits: 51 | cpu: 2000m 52 | memory: 2048Mi 53 | requests: 54 | cpu: 100m 55 | memory: 128Mi 56 | - name: logger 57 | image: fluent/fluent-bit:1.8.4 58 | imagePullPolicy: IfNotPresent 59 | resources: 60 | requests: 61 | cpu: 20m 62 | memory: 100Mi 63 | limits: 64 | cpu: 100m 65 | memory: 200Mi 66 | volumeMounts: 67 | - mountPath: /fluent-bit/etc 68 | name: config 69 | - mountPath: /var/log/gugotik 70 | name: log-volume 71 | volumes: 72 | - name: config 73 | configMap: 74 | name: gugotik-log-config 75 | - name: log-volume 76 | emptyDir: { } 77 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-feed-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-feed-service 8 | name: gugotik-feed-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-feed-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-feed-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-feed-service 21 | dream-app: gugotik-feed-service 22 | dream-unit: gugotik-feed-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-feed-service 30 | command: 31 | - ./services/feed/FeedService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: grpc-37004 44 | containerPort: 37004 45 | protocol: TCP 46 | - name: metrics-37099 47 | containerPort: 37099 48 | protocol: TCP 49 | resources: 50 | limits: 51 | cpu: 2000m 52 | memory: 2048Mi 53 | requests: 54 | cpu: 100m 55 | memory: 128Mi 56 | - name: logger 57 | image: fluent/fluent-bit:1.8.4 58 | imagePullPolicy: IfNotPresent 59 | resources: 60 | requests: 61 | cpu: 20m 62 | memory: 100Mi 63 | limits: 64 | cpu: 100m 65 | memory: 200Mi 66 | volumeMounts: 67 | - mountPath: /fluent-bit/etc 68 | name: config 69 | - mountPath: /var/log/gugotik 70 | name: log-volume 71 | volumes: 72 | - name: config 73 | configMap: 74 | name: gugotik-log-config 75 | - name: log-volume 76 | emptyDir: { } 77 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-http-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-http-service 8 | name: gugotik-http-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-http-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-http-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-http-service 21 | dream-app: gugotik-http-service 22 | dream-unit: gugotik-http-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-http-service 30 | command: 31 | - ./gateway/Gateway 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: http-37000 44 | containerPort: 37000 45 | protocol: TCP 46 | resources: 47 | limits: 48 | cpu: 2000m 49 | memory: 2048Mi 50 | requests: 51 | cpu: 100m 52 | memory: 128Mi 53 | - name: logger 54 | image: fluent/fluent-bit:1.8.4 55 | imagePullPolicy: IfNotPresent 56 | resources: 57 | requests: 58 | cpu: 20m 59 | memory: 100Mi 60 | limits: 61 | cpu: 100m 62 | memory: 200Mi 63 | volumeMounts: 64 | - mountPath: /fluent-bit/etc 65 | name: config 66 | - mountPath: /var/log/gugotik 67 | name: log-volume 68 | volumes: 69 | - name: config 70 | configMap: 71 | name: gugotik-log-config 72 | - name: log-volume 73 | emptyDir: { } 74 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-message-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-message-service 8 | name: gugotik-message-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-message-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-message-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-message-service 21 | dream-app: gugotik-message-service 22 | dream-unit: gugotik-message-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-message-service 30 | command: 31 | - ./services/message/MessageService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: grpc-37007 44 | containerPort: 37007 45 | protocol: TCP 46 | - name: metrics-37099 47 | containerPort: 37099 48 | protocol: TCP 49 | resources: 50 | limits: 51 | cpu: 2000m 52 | memory: 2048Mi 53 | requests: 54 | cpu: 100m 55 | memory: 128Mi 56 | - name: logger 57 | image: fluent/fluent-bit:1.8.4 58 | imagePullPolicy: IfNotPresent 59 | resources: 60 | requests: 61 | cpu: 20m 62 | memory: 100Mi 63 | limits: 64 | cpu: 100m 65 | memory: 200Mi 66 | volumeMounts: 67 | - mountPath: /fluent-bit/etc 68 | name: config 69 | - mountPath: /var/log/gugotik 70 | name: log-volume 71 | volumes: 72 | - name: config 73 | configMap: 74 | name: gugotik-log-config 75 | - name: log-volume 76 | emptyDir: { } 77 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-msg-consumer-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-msgconsumer-service 8 | name: gugotik-msgconsumer-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-msgconsumer-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-msgconsumer-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-msgconsumer-service 21 | dream-app: gugotik-msgconsumer-service 22 | dream-unit: gugotik-msgconsumer-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-msgconsumer-service 30 | command: 31 | - ./services/msgconsumer/MsgconsumerService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: metrics-37099 44 | containerPort: 37099 45 | protocol: TCP 46 | resources: 47 | limits: 48 | cpu: 2000m 49 | memory: 2048Mi 50 | requests: 51 | cpu: 100m 52 | memory: 128Mi 53 | - name: logger 54 | image: fluent/fluent-bit:1.8.4 55 | imagePullPolicy: IfNotPresent 56 | resources: 57 | requests: 58 | cpu: 20m 59 | memory: 100Mi 60 | limits: 61 | cpu: 100m 62 | memory: 200Mi 63 | volumeMounts: 64 | - mountPath: /fluent-bit/etc 65 | name: config 66 | - mountPath: /var/log/gugotik 67 | name: log-volume 68 | volumes: 69 | - name: config 70 | configMap: 71 | name: gugotik-log-config 72 | - name: log-volume 73 | emptyDir: { } 74 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-publish-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-publish-service 8 | name: gugotik-publish-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-publish-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-publish-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-publish-service 21 | dream-app: gugotik-publish-service 22 | dream-unit: gugotik-publish-service 23 | spec: 24 | volumes: 25 | - name: volume 26 | persistentVolumeClaim: 27 | claimName: storage 28 | - name: config 29 | configMap: 30 | name: gugotik-log-config 31 | - name: log-volume 32 | emptyDir: { } 33 | imagePullSecrets: 34 | - name: regcred 35 | containers: 36 | - image: ${IMAGE} 37 | imagePullPolicy: IfNotPresent 38 | name: gugotik-publish-service 39 | command: 40 | - ./services/publish/PublishService 41 | envFrom: 42 | - configMapRef: 43 | name: env-config 44 | - configMapRef: 45 | name: gugotik-env 46 | - secretRef: 47 | name: gugotik-secret 48 | ports: 49 | - name: grpc-37005 50 | containerPort: 37005 51 | protocol: TCP 52 | - name: metrics-37099 53 | containerPort: 37099 54 | protocol: TCP 55 | resources: 56 | limits: 57 | cpu: 2000m 58 | memory: 2048Mi 59 | requests: 60 | cpu: 100m 61 | memory: 128Mi 62 | volumeMounts: 63 | - mountPath: /data/apps/gugotik-service-bundle/data 64 | name: volume 65 | - mountPath: /var/log/gugotik 66 | name: log-volume 67 | - name: logger 68 | image: fluent/fluent-bit:1.8.4 69 | imagePullPolicy: IfNotPresent 70 | resources: 71 | requests: 72 | cpu: 20m 73 | memory: 100Mi 74 | limits: 75 | cpu: 100m 76 | memory: 200Mi 77 | volumeMounts: 78 | - mountPath: /fluent-bit/etc 79 | name: config 80 | - mountPath: /var/log/gugotik 81 | name: log-volume 82 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-recommend-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-recommend-service 8 | name: gugotik-recommend-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-recommend-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-recommend-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-recommend-service 21 | dream-app: gugotik-recommend-service 22 | dream-unit: gugotik-recommend-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-recommend-service 30 | command: 31 | - ./services/recommend/RecommendService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: grpc-37009 44 | containerPort: 37009 45 | protocol: TCP 46 | - name: metrics-37099 47 | containerPort: 37099 48 | protocol: TCP 49 | resources: 50 | limits: 51 | cpu: 2000m 52 | memory: 2048Mi 53 | requests: 54 | cpu: 100m 55 | memory: 128Mi 56 | - name: logger 57 | image: fluent/fluent-bit:1.8.4 58 | imagePullPolicy: IfNotPresent 59 | resources: 60 | requests: 61 | cpu: 20m 62 | memory: 100Mi 63 | limits: 64 | cpu: 100m 65 | memory: 200Mi 66 | volumeMounts: 67 | - mountPath: /fluent-bit/etc 68 | name: config 69 | - mountPath: /var/log/gugotik 70 | name: log-volume 71 | volumes: 72 | - name: config 73 | configMap: 74 | name: gugotik-log-config 75 | - name: log-volume 76 | emptyDir: { } 77 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-relation-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-relation-service 8 | name: gugotik-relation-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-relation-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-relation-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-relation-service 21 | dream-app: gugotik-relation-service 22 | dream-unit: gugotik-relation-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-relation-service 30 | command: 31 | - ./services/relation/RelationService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: grpc-37008 44 | containerPort: 37008 45 | protocol: TCP 46 | - name: metrics-37099 47 | containerPort: 37099 48 | protocol: TCP 49 | resources: 50 | limits: 51 | cpu: 2000m 52 | memory: 2048Mi 53 | requests: 54 | cpu: 100m 55 | memory: 128Mi 56 | - name: logger 57 | image: fluent/fluent-bit:1.8.4 58 | imagePullPolicy: IfNotPresent 59 | resources: 60 | requests: 61 | cpu: 20m 62 | memory: 100Mi 63 | limits: 64 | cpu: 100m 65 | memory: 200Mi 66 | volumeMounts: 67 | - mountPath: /fluent-bit/etc 68 | name: config 69 | - mountPath: /var/log/gugotik 70 | name: log-volume 71 | volumes: 72 | - name: config 73 | configMap: 74 | name: gugotik-log-config 75 | - name: log-volume 76 | emptyDir: { } 77 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-user-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-user-service 8 | name: gugotik-user-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-user-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-user-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-user-service 21 | dream-app: gugotik-user-service 22 | dream-unit: gugotik-user-service 23 | spec: 24 | imagePullSecrets: 25 | - name: regcred 26 | containers: 27 | - image: ${IMAGE} 28 | imagePullPolicy: IfNotPresent 29 | name: gugotik-user-service 30 | command: 31 | - ./services/user/UserService 32 | envFrom: 33 | - configMapRef: 34 | name: env-config 35 | - configMapRef: 36 | name: gugotik-env 37 | - secretRef: 38 | name: gugotik-secret 39 | volumeMounts: 40 | - mountPath: /var/log/gugotik 41 | name: log-volume 42 | ports: 43 | - name: grpc-37002 44 | containerPort: 37002 45 | protocol: TCP 46 | - name: metrics-37099 47 | containerPort: 37099 48 | protocol: TCP 49 | resources: 50 | limits: 51 | cpu: 2000m 52 | memory: 2048Mi 53 | requests: 54 | cpu: 100m 55 | memory: 128Mi 56 | - name: logger 57 | image: fluent/fluent-bit:1.8.4 58 | imagePullPolicy: IfNotPresent 59 | resources: 60 | requests: 61 | cpu: 20m 62 | memory: 100Mi 63 | limits: 64 | cpu: 100m 65 | memory: 200Mi 66 | volumeMounts: 67 | - mountPath: /fluent-bit/etc 68 | name: config 69 | - mountPath: /var/log/gugotik 70 | name: log-volume 71 | volumes: 72 | - name: config 73 | configMap: 74 | name: gugotik-log-config 75 | - name: log-volume 76 | emptyDir: { } 77 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/deployment-video-processor-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | annotations: 5 | sidecar.jaegertracing.io/inject: 'false' 6 | labels: 7 | app: gugotik-video-service 8 | name: gugotik-video-service 9 | namespace: gugotik-service-bundle 10 | spec: 11 | selector: 12 | matchLabels: 13 | name: gugotik-video-service 14 | template: 15 | metadata: 16 | labels: 17 | app: gugotik-video-service 18 | branch: master 19 | version: ${BUILD_NUMBER}-${CI_COMMIT_ID} 20 | name: gugotik-video-service 21 | dream-app: gugotik-video-service 22 | dream-unit: gugotik-video-service 23 | spec: 24 | volumes: 25 | - name: volume 26 | persistentVolumeClaim: 27 | claimName: storage 28 | - name: config 29 | configMap: 30 | name: gugotik-log-config 31 | - name: log-volume 32 | emptyDir: { } 33 | imagePullSecrets: 34 | - name: regcred 35 | containers: 36 | - image: ${IMAGE} 37 | imagePullPolicy: IfNotPresent 38 | name: gugotik-video-service 39 | command: 40 | - ./services/videoprocessor/VideoprocessorService 41 | envFrom: 42 | - configMapRef: 43 | name: env-config 44 | - configMapRef: 45 | name: gugotik-env 46 | - secretRef: 47 | name: gugotik-secret 48 | ports: 49 | - name: metrics-37099 50 | containerPort: 37099 51 | protocol: TCP 52 | resources: 53 | limits: 54 | cpu: 4000m 55 | memory: 8Gi 56 | requests: 57 | cpu: 100m 58 | memory: 128Mi 59 | volumeMounts: 60 | - mountPath: /data/apps/gugotik-service-bundle/data 61 | name: volume 62 | - mountPath: /var/log/gugotik 63 | name: log-volume 64 | - name: logger 65 | image: fluent/fluent-bit:1.8.4 66 | imagePullPolicy: IfNotPresent 67 | resources: 68 | requests: 69 | cpu: 20m 70 | memory: 100Mi 71 | limits: 72 | cpu: 100m 73 | memory: 200Mi 74 | volumeMounts: 75 | - mountPath: /fluent-bit/etc 76 | name: config 77 | - mountPath: /var/log/gugotik 78 | name: log-volume 79 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /manifests-endymx/sevice-http-api.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: gugotik-http-service 6 | name: gugotik-http-service 7 | namespace: gugotik-service-bundle 8 | spec: 9 | ports: 10 | - name: http 11 | port: 37000 12 | protocol: TCP 13 | targetPort: 37000 14 | selector: 15 | name: gugotik-http-service 16 | branch: master 17 | type: ClusterIP -------------------------------------------------------------------------------- /promethus.docker.compose.yml: -------------------------------------------------------------------------------- 1 | # my global config 2 | global: 3 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 4 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. 5 | # scrape_timeout is set to the global default (10s). 6 | 7 | # Alertmanager configuration 8 | alerting: 9 | alertmanagers: 10 | - static_configs: 11 | - targets: 12 | # - alertmanager:9093 13 | 14 | # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. 15 | rule_files: 16 | # - "first_rules.yml" 17 | # - "second_rules.yml" 18 | 19 | # A scrape configuration containing exactly one endpoint to scrape: 20 | # Here it's Prometheus itself. 21 | scrape_configs: 22 | # The job name is added as a label `job=` to any timeseries scraped from this config. 23 | - job_name: "prometheus" 24 | # metrics_path defaults to '/metrics' 25 | # scheme defaults to 'http'. 26 | static_configs: 27 | - targets: [ "localhost:9090" ] 28 | - targets: [ "gateway:37000" ] 29 | - targets: [ "auth:37099" ] 30 | - targets: [ "user:37099" ] 31 | - targets: [ "comment:37099" ] 32 | - targets: [ "favorite:37099" ] 33 | - targets: [ "feed:37099" ] 34 | - targets: [ "message:37099" ] 35 | - targets: [ "publish:37099" ] 36 | - targets: [ "recommend:37099" ] 37 | - targets: [ "relation:37099" ] -------------------------------------------------------------------------------- /scripts/build-all.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo Please Run Me on the root dir, not in scripts dir. 4 | 5 | IF EXIST output ( 6 | echo "Output dir existed, deleting and recreating..." 7 | rd /s /q output 8 | ) 9 | mkdir output\services 10 | pushd src\services 11 | for /D %%i in (*) do ( 12 | if not "%%i"=="health" ( 13 | set "name=%%i" 14 | setlocal enabledelayedexpansion 15 | set "capName=!name:~0,1!" 16 | set "capName=!capName:a=A!" 17 | set "capName=!capName:b=B!" 18 | set "capName=!capName:c=C!" 19 | set "capName=!capName:d=D!" 20 | set "capName=!capName:e=E!" 21 | set "capName=!capName:f=F!" 22 | set "capName=!capName:g=G!" 23 | set "capName=!capName:h=H!" 24 | set "capName=!capName:i=I!" 25 | set "capName=!capName:j=J!" 26 | set "capName=!capName:k=K!" 27 | set "capName=!capName:l=L!" 28 | set "capName=!capName:m=M!" 29 | set "capName=!capName:n=N!" 30 | set "capName=!capName:o=O!" 31 | set "capName=!capName:p=P!" 32 | set "capName=!capName:q=Q!" 33 | set "capName=!capName:r=R!" 34 | set "capName=!capName:s=S!" 35 | set "capName=!capName:t=T!" 36 | set "capName=!capName:u=U!" 37 | set "capName=!capName:v=V!" 38 | set "capName=!capName:w=W!" 39 | set "capName=!capName:x=X!" 40 | set "capName=!capName:y=Y!" 41 | set "capName=!capName:z=Z!" 42 | set "capName=!capName!!name:~1!" 43 | cd %%i 44 | go build -o ../../../output/services/%%i/!capName!Service.exe 45 | cd .. 46 | endlocal 47 | ) 48 | ) 49 | 50 | 51 | popd 52 | mkdir output\gateway 53 | 54 | cd src\web 55 | go build -o ../../output/gateway/Gateway.exe 56 | echo OK! 57 | -------------------------------------------------------------------------------- /scripts/build-all.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "Please Run Me on the root dir, not in scripts dir." 2 | 3 | if (Test-Path output) { 4 | Write-Host "Output dir existed, deleting and recreating..." 5 | Remove-Item -Recurse -Force output 6 | } 7 | New-Item -ItemType Directory -Path output\services | Out-Null 8 | 9 | Set-Location src\services 10 | 11 | $directories = Get-ChildItem -Directory | Where-Object { $_.Name -ne 'health' } 12 | 13 | foreach ($dir in $directories) { 14 | $capitalizedName = $dir.Name.Substring(0, 1).ToUpper() + $dir.Name.Substring(1) 15 | 16 | Set-Location -Path $dir.FullName 17 | & go build -o "../../../output/services/$($dir.Name)/$($capitalizedName)Service.exe" 18 | Set-Location -Path ".." 19 | } 20 | 21 | Set-Location "..\.." 22 | 23 | New-Item -ItemType Directory -Path output\gateway | Out-Null 24 | 25 | Set-Location src\web 26 | & go build -o "../../output/gateway/GateWay.exe" 27 | Set-Location "..\.." 28 | 29 | Write-Host "OK!" 30 | -------------------------------------------------------------------------------- /scripts/build-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Please Run Me on the root dir, not in scripts dir." 4 | 5 | if [ -d "output" ]; then 6 | echo "Output dir existed, deleting and recreating..." 7 | rm -rf output 8 | fi 9 | mkdir -p output/services 10 | 11 | pushd src/services || exit 12 | 13 | for i in *; do 14 | if [ "$i" != "health" ]; then 15 | name="$i" 16 | capName="${name^}" 17 | cd "$i" || exit 18 | go build -o "../../../output/services/$i/${capName}Service" 19 | cd .. 20 | fi 21 | done 22 | 23 | popd || exit 24 | 25 | mkdir -p output/gateway 26 | 27 | cd src/web || exit 28 | 29 | go build -o "../../output/gateway/Gateway" 30 | 31 | echo "OK!" 32 | -------------------------------------------------------------------------------- /scripts/run-all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Please Run Me on the root dir, not in scripts dir." 4 | 5 | gateway_directory="output/gateway" 6 | service_directory="output/services" 7 | 8 | log_directory="log" 9 | 10 | mkdir -p "$log_directory" 11 | 12 | if [ ! -d "output" ]; then 13 | echo "Output dir does not exist, please run build script first." 14 | fi 15 | 16 | for gateway_file in "$gateway_directory"/*; do 17 | if [[ -x "$gateway_file" && -f "$gateway_file" ]]; then 18 | echo "Running $gateway_file" 19 | gateway_log_file="$log_directory"/"$(basename gateway_file)".log 20 | ./"$gateway_file" >> "$gateway_log_file" 2>&1 & 21 | fi 22 | done 23 | 24 | for service in "$service_directory"/*; do 25 | for service_file in "$service"/*; do 26 | if [[ -x "$service_file" && -f "$service_file" ]]; then 27 | echo "Running $service_file" 28 | service_log_file="$log_directory"/"$(basename service_file)".log 29 | ./"$service_file" >> "$service_log_file" 2>&1 & 30 | fi 31 | done 32 | done 33 | -------------------------------------------------------------------------------- /src/constant/config/env.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/caarlos0/env/v6" 5 | "github.com/joho/godotenv" 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | var EnvCfg envConfig 10 | 11 | type envConfig struct { 12 | ConsulAddr string `env:"CONSUL_ADDR" envDefault:"localhost:8500"` 13 | LoggerLevel string `env:"LOGGER_LEVEL" envDefault:"INFO"` 14 | LoggerWithTraceState string `env:"LOGGER_OUT_TRACING" envDefault:"disable"` 15 | TiedLogging string `env:"TIED" envDefault:"NONE"` 16 | PostgreSQLHost string `env:"POSTGRESQL_HOST"` 17 | PostgreSQLPort string `env:"POSTGRESQL_PORT"` 18 | PostgreSQLUser string `env:"POSTGRESQL_USER"` 19 | PostgreSQLPassword string `env:"POSTGRESQL_PASSWORD"` 20 | PostgreSQLDataBase string `env:"POSTGRESQL_DATABASE"` 21 | StorageType string `env:"STORAGE_TYPE" envDefault:"fs"` 22 | FileSystemStartPath string `env:"FS_PATH" envDefault:"/tmp"` 23 | FileSystemBaseUrl string `env:"FS_BASEURL" envDefault:"http://localhost/"` 24 | RedisAddr string `env:"REDIS_ADDR"` 25 | RedisPassword string `env:"REDIS_PASSWORD" envDefault:""` 26 | RedisDB int `env:"REDIS_DB" envDefault:"0"` 27 | TracingEndPoint string `env:"TRACING_ENDPOINT"` 28 | PyroscopeState string `env:"PYROSCOPE_STATE" envDefault:"false"` 29 | PyroscopeAddr string `env:"PYROSCOPE_ADDR"` 30 | RedisPrefix string `env:"REDIS_PREFIX" envDefault:"GUGUTIK"` 31 | PostgreSQLSchema string `env:"POSTGRESQL_SCHEMA" envDefault:""` 32 | RedisMaster string `env:"REDIS_MASTER"` 33 | ConsulAnonymityPrefix string `env:"CONSUL_ANONYMITY_NAME" envDefault:""` 34 | RabbitMQUsername string `env:"RABBITMQ_USERNAME" envDefault:"guest"` 35 | RabbitMQPassword string `env:"RABBITMQ_PASSWORD" envDefault:"guest"` 36 | RabbitMQAddr string `env:"RABBITMQ_ADDRESS" envDefault:"localhost"` 37 | RabbitMQPort string `env:"RABBITMQ_PORT" envDefault:"5672"` 38 | RabbitMQVhostPrefix string `env:"RABBITMQ_VHOST_PREFIX" envDefault:""` 39 | ChatGPTAPIKEYS string `env:"CHATGPT_API_KEYS"` 40 | PodIpAddr string `env:"POD_IP" envDefault:"localhost"` 41 | GorseAddr string `env:"GORSE_ADDR"` 42 | GorseApiKey string `env:"GORSE_APIKEY"` 43 | MagicUserId uint32 `env:"MAGIC_USER_ID" envDefault:"1"` 44 | ChatGptProxy string `env:"CHATGPT_PROXY"` 45 | PostgreSQLPrefix string `env:"POSTGRESQL_PREFIX" envDefault:""` 46 | PostgreSQLReplicaState string `env:"POSTGRESQL_REPLICA"` 47 | PostgreSQLReplicaAddress string `env:"POSTGRESQL_REPLICA_ADDR"` 48 | PostgreSQLReplicaUsername string `env:"POSTGRESQL_REPLICA_USER"` 49 | PostgreSQLReplicaPassword string `env:"POSTGRESQL_REPLICA_PASSWORD"` 50 | OtelState string `env:"TRACING_STATE" envDefault:"enable"` 51 | OtelSampler float64 `env:"TRACING_SAMPLER" envDefault:"0.01"` 52 | AnonymityUser string `env:"ANONYMITY_USER" envDefault:"114514"` 53 | ElasticsearchUrl string `env:"ES_ADDR"` 54 | } 55 | 56 | func init() { 57 | if err := godotenv.Load(); err != nil { 58 | log.Errorf("Can not read env from file system, please check the right this program owned.") 59 | } 60 | 61 | EnvCfg = envConfig{} 62 | 63 | if err := env.Parse(&EnvCfg); err != nil { 64 | panic("Can not parse env from file system, please check the env.") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/constant/config/service.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const WebServiceName = "GuGoTik-GateWay" 4 | const WebServiceAddr = ":37000" 5 | 6 | const AuthRpcServerName = "GuGoTik-AuthService" 7 | const AuthRpcServerPort = ":37001" 8 | 9 | const UserRpcServerName = "GuGoTik-UserService" 10 | const UserRpcServerPort = ":37002" 11 | 12 | const CommentRpcServerName = "GuGoTik-CommentService" 13 | const CommentRpcServerPort = ":37003" 14 | 15 | const FeedRpcServerName = "GuGoTik-FeedService" 16 | const FeedRpcServerPort = ":37004" 17 | 18 | const PublishRpcServerName = "GuGoTik-PublisherService" 19 | const PublishRpcServerPort = ":37005" 20 | 21 | const FavoriteRpcServerName = "GuGoTik-FavoriteService" 22 | const FavoriteRpcServerPort = ":37006" 23 | 24 | const MessageRpcServerName = "GuGoTik-MessageService" 25 | const MessageRpcServerPort = ":37007" 26 | 27 | const RelationRpcServerName = "GuGoTik-RelationService" 28 | const RelationRpcServerPort = ":37008" 29 | 30 | const RecommendRpcServiceName = "GuGoTik-Recommend" 31 | const RecommendRpcServicePort = ":37009" 32 | 33 | const Metrics = ":37099" 34 | const VideoProcessorRpcServiceName = "GuGoTik-VideoProcessorService" 35 | 36 | const VideoPicker = "GuGoTik-VideoPicker" 37 | const Event = "GuGoTik-Recommend" 38 | const MsgConsumer = "GuGoTik-MgsConsumer" 39 | 40 | const BloomRedisChannel = "GuGoTik-Bloom" 41 | 42 | const MaxVideoSize = 200 * 1024 * 1024 43 | -------------------------------------------------------------------------------- /src/constant/strings/common.go: -------------------------------------------------------------------------------- 1 | package strings 2 | 3 | const ( 4 | ServiceOKCode = 0 5 | ServiceOK = "success" 6 | ) 7 | -------------------------------------------------------------------------------- /src/constant/strings/err.go: -------------------------------------------------------------------------------- 1 | package strings 2 | 3 | // Bad Request 4 | const ( 5 | GateWayErrorCode = 40001 6 | GateWayError = "GuGoTik Gateway 暂时不能处理您的请求,请稍后重试!" 7 | GateWayParamsErrorCode = 40002 8 | GateWayParamsError = "GuGoTik Gateway 无法响应您的请求,请重启 APP 或稍后再试!" 9 | ) 10 | 11 | // Server Inner Error 12 | const ( 13 | AuthServiceInnerErrorCode = 50001 14 | AuthServiceInnerError = "登录服务出现内部错误,请稍后重试!" 15 | VideoServiceInnerErrorCode = 50002 16 | VideoServiceInnerError = "视频发布服务出现内部错误,请稍后重试!" 17 | UnableToQueryUserErrorCode = 50003 18 | UnableToQueryUserError = "无法查询到对应用户" 19 | UnableToQueryCommentErrorCode = 50004 20 | UnableToQueryCommentError = "无法查询到视频评论" 21 | UnableToCreateCommentErrorCode = 50005 22 | UnableToCreateCommentError = "无法创建评论" 23 | FeedServiceInnerErrorCode = 50006 24 | FeedServiceInnerError = "视频服务出现内部错误,请稍后重试!" 25 | ActorIDNotMatchErrorCode = 50007 26 | ActorIDNotMatchError = "用户不匹配" 27 | UnableToDeleteCommentErrorCode = 50008 28 | UnableToDeleteCommentError = "无法删除视频评论" 29 | UnableToAddMessageErrorCode = 50009 30 | UnableToAddMessageError = "发送消息出错" 31 | UnableToQueryMessageErrorCode = 50010 32 | UnableToQueryMessageError = "查消息出错" 33 | PublishServiceInnerErrorCode = 50011 34 | PublishServiceInnerError = "发布服务出现内部错误,请稍后重试!" 35 | UnableToFollowErrorCode = 50012 36 | UnableToFollowError = "关注该用户失败" 37 | UnableToUnFollowErrorCode = 50013 38 | UnableToUnFollowError = "取消关注失败" 39 | UnableToGetFollowListErrorCode = 50014 40 | UnableToGetFollowListError = "无法查询到关注列表" 41 | UnableToGetFollowerListErrorCode = 50015 42 | UnableToGetFollowerListError = "无法查询到粉丝列表" 43 | UnableToRelateYourselfErrorCode = 50016 44 | UnableToRelateYourselfError = "无法关注自己" 45 | RelationNotFoundErrorCode = 50017 46 | RelationNotFoundError = "未关注该用户" 47 | StringToIntErrorCode = 50018 48 | StringToIntError = "字符串转数字失败" 49 | RelationServiceIntErrorCode = 50019 50 | RelationServiceIntError = "关系服务出现内部错误" 51 | FavoriteServiceErrorCode = 50020 52 | FavoriteServiceError = "点赞服务内部出错" 53 | UserServiceInnerErrorCode = 50021 54 | UserServiceInnerError = "登录服务出现内部错误,请稍后重试!" 55 | UnableToQueryVideoErrorCode = 50022 56 | UnableToQueryVideoError = "无法查询到该视频" 57 | AlreadyFollowingErrorCode = 50023 58 | AlreadyFollowingError = "无法关注已关注的人" 59 | UnableToGetFriendListErrorCode = 50024 60 | UnableToGetFriendListError = "无法查询到好友列表" 61 | RecommendServiceInnerErrorCode = 50025 62 | RecommendServiceInnerError = "推荐系统内部错误" 63 | ) 64 | 65 | // Expected Error 66 | const ( 67 | AuthUserExistedCode = 10001 68 | AuthUserExisted = "用户已存在,请更换用户名或尝试登录!" 69 | UserNotExistedCode = 10002 70 | UserNotExisted = "用户不存在,请先注册或检查你的用户名是否正确!" 71 | AuthUserLoginFailedCode = 10003 72 | AuthUserLoginFailed = "用户信息错误,请检查账号密码是否正确" 73 | AuthUserNeededCode = 10004 74 | AuthUserNeeded = "用户无权限操作,请登陆后重试!" 75 | ActionCommentTypeInvalidCode = 10005 76 | ActionCommentTypeInvalid = "不合法的评论类型" 77 | ActionCommentLimitedCode = 10006 78 | ActionCommentLimited = "评论频繁,请稍后再试!" 79 | InvalidContentTypeCode = 10007 80 | InvalidContentType = "不合法的内容类型" 81 | FavoriteServiceDuplicateCode = 10008 82 | FavoriteServiceDuplicateError = "不能重复点赞" 83 | FavoriteServiceCancelCode = 10009 84 | FavoriteServiceCancelError = "没有点赞,不能取消点赞" 85 | PublishVideoLimitedCode = 10010 86 | PublishVideoLimited = "视频发布频繁,请稍后再试!" 87 | ChatActionLimitedCode = 10011 88 | ChatActionLimitedError = "发送消息频繁,请稍后再试!" 89 | FollowLimitedCode = 10012 90 | FollowLimited = "关注频繁,请稍后再试!" 91 | UserDoNotExistedCode = 10013 92 | UserDoNotExisted = "查询用户不存在!" 93 | OversizeVideoCode = 10014 94 | OversizeVideo = "上传视频超过了200MB" 95 | ) 96 | -------------------------------------------------------------------------------- /src/constant/strings/service.go: -------------------------------------------------------------------------------- 1 | package strings 2 | 3 | // Exchange name 4 | const ( 5 | VideoExchange = "video_exchange" 6 | EventExchange = "event" 7 | MessageExchange = "message_exchange" 8 | AuditExchange = "audit_exchange" 9 | ) 10 | 11 | // Queue name 12 | const ( 13 | VideoPicker = "video_picker" 14 | VideoSummary = "video_summary" 15 | MessageCommon = "message_common" 16 | MessageGPT = "message_gpt" 17 | MessageES = "message_es" 18 | AuditPicker = "audit_picker" 19 | ) 20 | 21 | // Routing key 22 | const ( 23 | FavoriteActionEvent = "video.favorite.action" 24 | VideoGetEvent = "video.get.action" 25 | VideoCommentEvent = "video.comment.action" 26 | VideoPublishEvent = "video.publish.action" 27 | 28 | MessageActionEvent = "message.common" 29 | MessageGptActionEvent = "message.gpt" 30 | AuditPublishEvent = "audit" 31 | ) 32 | 33 | // Action Type 34 | const ( 35 | FavoriteIdActionLog = 1 // 用户点赞相关操作 36 | FollowIdActionLog = 2 // 用户关注相关操作 37 | ) 38 | 39 | // Action Name 40 | const ( 41 | FavoriteNameActionLog = "favorite.action" // 用户点赞操作名称 42 | FavoriteUpActionSubLog = "up" 43 | FavoriteDownActionSubLog = "down" 44 | 45 | FollowNameActionLog = "follow.action" // 用户关注操作名称 46 | FollowUpActionSubLog = "up" 47 | FollowDownActionSubLog = "down" 48 | ) 49 | 50 | // Action Service Name 51 | const ( 52 | FavoriteServiceName = "FavoriteService" 53 | FollowServiceName = "FollowService" 54 | ) 55 | -------------------------------------------------------------------------------- /src/extra/gorse/model.go: -------------------------------------------------------------------------------- 1 | package gorse 2 | 3 | import "time" 4 | 5 | type Feedback struct { 6 | FeedbackType string `json:"FeedbackType"` 7 | UserId string `json:"UserId"` 8 | ItemId string `json:"ItemId"` 9 | Timestamp string `json:"Timestamp"` 10 | } 11 | 12 | type Feedbacks struct { 13 | Cursor string `json:"Cursor"` 14 | Feedback []Feedback `json:"Feedback"` 15 | } 16 | 17 | type ErrorMessage string 18 | 19 | func (e ErrorMessage) Error() string { 20 | return string(e) 21 | } 22 | 23 | type RowAffected struct { 24 | RowAffected int `json:"RowAffected"` 25 | } 26 | 27 | type Score struct { 28 | Id string `json:"Id"` 29 | Score float64 `json:"Score"` 30 | } 31 | 32 | type User struct { 33 | UserId string `json:"UserId"` 34 | Labels []string `json:"Labels"` 35 | Subscribe []string `json:"Subscribe"` 36 | Comment string `json:"Comment"` 37 | } 38 | 39 | type Users struct { 40 | Cursor string `json:"Cursor"` 41 | Users []User `json:"Users"` 42 | } 43 | 44 | type UserPatch struct { 45 | Labels []string 46 | Subscribe []string 47 | Comment *string 48 | } 49 | 50 | type Item struct { 51 | ItemId string `json:"ItemId"` 52 | IsHidden bool `json:"IsHidden"` 53 | Labels []string `json:"Labels"` 54 | Categories []string `json:"Categories"` 55 | Timestamp string `json:"Timestamp"` 56 | Comment string `json:"Comment"` 57 | } 58 | 59 | type Items struct { 60 | Cursor string `json:"Cursor"` 61 | Items []Item `json:"Items"` 62 | } 63 | 64 | type ItemPatch struct { 65 | IsHidden *bool 66 | Categories []string 67 | Timestamp *time.Time 68 | Labels []string 69 | Comment *string 70 | } 71 | 72 | type Health struct { 73 | CacheStoreConnected bool `json:"CacheStoreConnected"` 74 | CacheStoreError string `json:"CacheStoreError"` 75 | DataStoreConnected bool `json:"DataStoreConnected"` 76 | DataStoreError string `json:"DataStoreError"` 77 | Ready bool `json:"Ready"` 78 | } 79 | -------------------------------------------------------------------------------- /src/extra/profiling/analyzer.go: -------------------------------------------------------------------------------- 1 | package profiling 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/utils/logging" 6 | "github.com/pyroscope-io/client/pyroscope" 7 | log "github.com/sirupsen/logrus" 8 | "gorm.io/plugin/opentelemetry/logging/logrus" 9 | "os" 10 | "runtime" 11 | ) 12 | 13 | func InitPyroscope(appName string) { 14 | if config.EnvCfg.PyroscopeState != "enable" { 15 | logging.Logger.WithFields(log.Fields{ 16 | "appName": appName, 17 | }).Infof("User close Pyroscope, the service would not run.") 18 | return 19 | } 20 | 21 | runtime.SetMutexProfileFraction(5) 22 | runtime.SetBlockProfileRate(5) 23 | 24 | _, err := pyroscope.Start(pyroscope.Config{ 25 | ApplicationName: appName, 26 | ServerAddress: config.EnvCfg.PyroscopeAddr, 27 | Logger: logrus.NewWriter(), 28 | Tags: map[string]string{"hostname": os.Getenv("HOSTNAME")}, 29 | ProfileTypes: []pyroscope.ProfileType{ 30 | pyroscope.ProfileCPU, 31 | pyroscope.ProfileAllocObjects, 32 | pyroscope.ProfileAllocSpace, 33 | pyroscope.ProfileInuseObjects, 34 | pyroscope.ProfileInuseSpace, 35 | pyroscope.ProfileGoroutines, 36 | pyroscope.ProfileMutexCount, 37 | pyroscope.ProfileMutexDuration, 38 | pyroscope.ProfileBlockCount, 39 | pyroscope.ProfileBlockDuration, 40 | }, 41 | }) 42 | 43 | if err != nil { 44 | logging.Logger.WithFields(log.Fields{ 45 | "appName": appName, 46 | "err": err, 47 | }).Warnf("Pyroscope failed to run.") 48 | return 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/extra/tracing/otel.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/utils/logging" 6 | "context" 7 | "github.com/sirupsen/logrus" 8 | "go.opentelemetry.io/otel" 9 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace" 10 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" 11 | "go.opentelemetry.io/otel/propagation" 12 | "go.opentelemetry.io/otel/sdk/resource" 13 | "go.opentelemetry.io/otel/sdk/trace" 14 | semconv "go.opentelemetry.io/otel/semconv/v1.20.0" 15 | trace2 "go.opentelemetry.io/otel/trace" 16 | ) 17 | 18 | var Tracer trace2.Tracer 19 | 20 | func SetTraceProvider(name string) (*trace.TracerProvider, error) { 21 | client := otlptracehttp.NewClient( 22 | otlptracehttp.WithEndpoint(config.EnvCfg.TracingEndPoint), 23 | otlptracehttp.WithInsecure(), 24 | ) 25 | exporter, err := otlptrace.New(context.Background(), client) 26 | if err != nil { 27 | logging.Logger.WithFields(logrus.Fields{ 28 | "err": err, 29 | }).Errorf("Can not init otel !") 30 | return nil, err 31 | } 32 | 33 | var sampler trace.Sampler 34 | if config.EnvCfg.OtelState == "disable" { 35 | sampler = trace.NeverSample() 36 | } else { 37 | sampler = trace.TraceIDRatioBased(config.EnvCfg.OtelSampler) 38 | } 39 | 40 | tp := trace.NewTracerProvider( 41 | trace.WithBatcher(exporter), 42 | trace.WithResource( 43 | resource.NewWithAttributes( 44 | semconv.SchemaURL, 45 | semconv.ServiceNameKey.String(name), 46 | ), 47 | ), 48 | trace.WithSampler(sampler), 49 | ) 50 | otel.SetTracerProvider(tp) 51 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 52 | Tracer = otel.Tracer(name) 53 | return tp, nil 54 | } 55 | -------------------------------------------------------------------------------- /src/idl/auth.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rpc.auth; 3 | option go_package = "GuGoTik/src/rpc/auth"; 4 | 5 | message LoginRequest { 6 | string username = 1; // 登录用户名 7 | string password = 2; // 登录密码 8 | } 9 | 10 | message LoginResponse { 11 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 12 | string status_msg = 2; // 返回状态描述 13 | uint32 user_id = 3; // 用户id 14 | string token = 4; // 用户鉴权token 15 | } 16 | 17 | message RegisterRequest { 18 | string username = 1; // 注册用户名,最长32个字符 19 | string password = 2; // 密码,最长32个字符 20 | } 21 | 22 | message RegisterResponse { 23 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 24 | string status_msg = 2; // 返回状态描述 25 | uint32 user_id = 3; // 用户id 26 | string token = 4; // 用户鉴权token 27 | } 28 | 29 | message AuthenticateRequest { 30 | string token = 1; // 用户鉴权token 31 | } 32 | 33 | message AuthenticateResponse { 34 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 35 | string status_msg = 2; // 返回状态描述 36 | uint32 user_id = 3; // 用户id 37 | } 38 | 39 | service AuthService { 40 | rpc Authenticate (AuthenticateRequest) returns (AuthenticateResponse); 41 | 42 | rpc Register (RegisterRequest) returns (RegisterResponse); 43 | 44 | rpc Login (LoginRequest) returns (LoginResponse); 45 | } -------------------------------------------------------------------------------- /src/idl/chat.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rpc.chat; 3 | option go_package = "GuGoTik/src/rpc/chat"; 4 | 5 | message ChatRequest { 6 | uint32 actor_id = 1; 7 | uint32 user_id = 2; 8 | uint64 pre_msg_time = 3; 9 | } 10 | 11 | message Message { 12 | uint32 id = 1; 13 | string content = 2; 14 | uint64 create_time = 3; 15 | optional uint32 from_user_id = 4; 16 | optional uint32 to_user_id = 5; 17 | } 18 | 19 | message ChatResponse { 20 | int32 status_code = 1; 21 | string status_msg = 2; 22 | repeated Message message_list = 3; 23 | } 24 | 25 | message ActionRequest { 26 | uint32 actor_id = 1; 27 | uint32 user_id = 2; 28 | uint32 action_type = 3; // 1-发送消息 29 | string content = 4; 30 | } 31 | 32 | message ActionResponse { 33 | int32 status_code = 1; 34 | string status_msg = 2; 35 | } 36 | 37 | service ChatService { 38 | rpc Chat(ChatRequest) returns (ChatResponse); 39 | 40 | rpc ChatAction(ActionRequest) returns (ActionResponse); 41 | } 42 | -------------------------------------------------------------------------------- /src/idl/check.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grpc.health.v1; 4 | option go_package = "GuGoTik/src/rpc/health"; 5 | message HealthCheckRequest { 6 | string service = 1; 7 | } 8 | 9 | message HealthCheckResponse { 10 | enum ServingStatus { 11 | UNKNOWN = 0; 12 | SERVING = 1; 13 | NOT_SERVING = 2; 14 | SERVICE_UNKNOWN = 3; // Used only by the Watch method. 15 | } 16 | ServingStatus status = 1; 17 | } 18 | 19 | service Health { 20 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 21 | 22 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse); 23 | } -------------------------------------------------------------------------------- /src/idl/comment.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "user.proto"; 3 | package rpc.comment; 4 | option go_package = "GuGoTik/src/rpc/comment"; 5 | 6 | message Comment { 7 | uint32 id = 1; 8 | user.User user = 2; 9 | string content = 3; 10 | string create_date = 4; 11 | } 12 | 13 | enum ActionCommentType { 14 | ACTION_COMMENT_TYPE_UNSPECIFIED = 0; 15 | ACTION_COMMENT_TYPE_ADD = 1; 16 | ACTION_COMMENT_TYPE_DELETE = 2; 17 | } 18 | 19 | message ActionCommentRequest { 20 | uint32 actor_id = 1; 21 | uint32 video_id = 2; 22 | ActionCommentType action_type = 3; 23 | oneof action { 24 | string comment_text = 4; 25 | uint32 comment_id = 5; 26 | } 27 | } 28 | 29 | message ActionCommentResponse { 30 | int32 status_code = 1; 31 | string status_msg = 2; 32 | optional Comment comment = 3; 33 | } 34 | 35 | message ListCommentRequest { 36 | uint32 actor_id = 1; 37 | uint32 video_id = 2; 38 | } 39 | 40 | message ListCommentResponse { 41 | int32 status_code = 1; 42 | string status_msg = 2; 43 | repeated Comment comment_list = 3; 44 | } 45 | 46 | message CountCommentRequest { 47 | uint32 actor_id = 1; 48 | uint32 video_id = 2; 49 | } 50 | 51 | message CountCommentResponse { 52 | int32 status_code = 1; 53 | string status_msg = 2; 54 | uint32 comment_count = 3; 55 | } 56 | 57 | service CommentService { 58 | rpc ActionComment(ActionCommentRequest) returns (ActionCommentResponse); 59 | rpc ListComment(ListCommentRequest) returns (ListCommentResponse); 60 | rpc CountComment(CountCommentRequest) returns (CountCommentResponse); 61 | } -------------------------------------------------------------------------------- /src/idl/favorite.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "feed.proto"; 3 | package rpc.favorite; 4 | option go_package = "GuGoTik/src/rpc/favorite"; 5 | 6 | message FavoriteRequest { 7 | uint32 actor_id = 1; // 用户id 8 | uint32 video_id = 2; // 视频id 9 | uint32 action_type = 3; // 1-点赞,2-取消点赞 10 | } 11 | 12 | message FavoriteResponse { 13 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 14 | string status_msg = 2; // 返回状态描述 15 | } 16 | 17 | message FavoriteListRequest { 18 | uint32 actor_id = 1; // 发出请求的用户的id 19 | uint32 user_id = 2; // 用户id 20 | } 21 | 22 | message FavoriteListResponse { 23 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 24 | string status_msg = 2; // 返回状态描述 25 | repeated feed.Video video_list = 3; // 用户点赞视频列表 26 | } 27 | 28 | message IsFavoriteRequest { 29 | uint32 actor_id = 1; // 用户id 30 | uint32 video_id = 2; // 视频id 31 | } 32 | 33 | message IsFavoriteResponse { 34 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 35 | string status_msg = 2; // 返回状态描述 36 | bool result = 3; // 结果 37 | } 38 | 39 | message CountFavoriteRequest { 40 | uint32 video_id = 1; // 视频id 41 | } 42 | 43 | message CountFavoriteResponse { 44 | int32 status_code = 1; 45 | string status_msg = 2; 46 | uint32 count = 3; // 点赞数 47 | } 48 | 49 | message CountUserFavoriteRequest { 50 | uint32 user_id = 1; // 用户id 51 | } 52 | 53 | message CountUserFavoriteResponse { 54 | int32 status_code = 1; 55 | string status_msg = 2; 56 | uint32 count = 3; // 点赞数 57 | } 58 | 59 | message CountUserTotalFavoritedRequest { 60 | uint32 actor_id = 1; 61 | uint32 user_id = 2; 62 | } 63 | 64 | message CountUserTotalFavoritedResponse { 65 | int32 status_code = 1; 66 | string status_msg = 2; 67 | uint32 count = 3; // 点赞数 68 | } 69 | 70 | service FavoriteService { 71 | rpc FavoriteAction (FavoriteRequest) returns (FavoriteResponse); 72 | 73 | rpc FavoriteList (FavoriteListRequest) returns (FavoriteListResponse); 74 | 75 | rpc IsFavorite (IsFavoriteRequest) returns (IsFavoriteResponse); 76 | 77 | rpc CountFavorite (CountFavoriteRequest) returns (CountFavoriteResponse); 78 | 79 | rpc CountUserFavorite (CountUserFavoriteRequest) returns (CountUserFavoriteResponse); 80 | 81 | rpc CountUserTotalFavorited (CountUserTotalFavoritedRequest) returns (CountUserTotalFavoritedResponse); 82 | } 83 | -------------------------------------------------------------------------------- /src/idl/feed.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "user.proto"; 3 | package rpc.feed; 4 | option go_package = "GuGoTik/src/rpc/feed"; 5 | 6 | message Video { 7 | uint32 id = 1; 8 | user.User author = 2; 9 | string play_url = 3; 10 | string cover_url = 4; 11 | uint32 favorite_count = 5; 12 | uint32 comment_count = 6; 13 | bool is_favorite = 7; 14 | string title = 8; 15 | } 16 | 17 | message ListFeedRequest { 18 | optional string latest_time = 1; 19 | optional uint32 actor_id = 2; 20 | } 21 | 22 | message ListFeedResponse { 23 | int32 status_code = 1; 24 | string status_msg = 2; 25 | optional uint64 next_time = 3; 26 | repeated Video video_list = 4; 27 | } 28 | 29 | message QueryVideosRequest { 30 | uint32 actor_id = 1; 31 | repeated uint32 video_ids = 2; 32 | } 33 | 34 | message QueryVideosResponse { 35 | int32 status_code = 1; 36 | string status_msg = 2; 37 | repeated Video video_list = 3; 38 | } 39 | 40 | message VideoExistRequest { 41 | uint32 video_id = 1; // 视频id 42 | } 43 | 44 | message VideoExistResponse { 45 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 46 | string status_msg = 2; // 返回状态描述 47 | bool existed = 3; 48 | } 49 | 50 | message QueryVideoSummaryAndKeywordsRequest { 51 | uint32 actor_id = 1; 52 | uint32 video_id = 2; 53 | } 54 | 55 | message QueryVideoSummaryAndKeywordsResponse { 56 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 57 | string status_msg = 2; // 返回状态描述 58 | string summary = 3; 59 | string keywords = 4; 60 | } 61 | 62 | service FeedService { 63 | rpc ListVideosByRecommend(ListFeedRequest) returns (ListFeedResponse); 64 | rpc ListVideos(ListFeedRequest) returns (ListFeedResponse); 65 | rpc QueryVideos(QueryVideosRequest) returns (QueryVideosResponse); 66 | rpc QueryVideoExisted(VideoExistRequest) returns (VideoExistResponse); 67 | rpc QueryVideoSummaryAndKeywords(QueryVideoSummaryAndKeywordsRequest) returns (QueryVideoSummaryAndKeywordsResponse); 68 | } 69 | -------------------------------------------------------------------------------- /src/idl/publish.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "feed.proto"; 3 | package rpc.publish; 4 | option go_package = "GuGoTik/src/rpc/publish"; 5 | 6 | message CreateVideoRequest { 7 | uint32 actor_id = 1; // 用户id 8 | bytes data = 2; // 视频数据 9 | string title = 3; // 视频标题 10 | } 11 | 12 | message CreateVideoResponse { 13 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 14 | string status_msg = 2; // 返回状态描述 15 | } 16 | 17 | message ListVideoRequest{ 18 | uint32 actor_id = 1; // 用户id 19 | uint32 user_id = 2; // 被请求查询的用户id 20 | } 21 | 22 | message ListVideoResponse{ 23 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 24 | string status_msg = 2; // 返回状态描述 25 | repeated feed.Video video_list = 3; // 视频列表 26 | } 27 | 28 | message CountVideoRequest{ 29 | uint32 user_id = 1; // 用户id 30 | } 31 | 32 | message CountVideoResponse{ 33 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 34 | string status_msg = 2; // 返回状态描述 35 | uint32 count = 3; // 视频数量 36 | } 37 | 38 | service PublishService { 39 | rpc CreateVideo(CreateVideoRequest) returns (CreateVideoResponse) {} 40 | rpc ListVideo(ListVideoRequest) returns (ListVideoResponse) {} 41 | rpc CountVideo(CountVideoRequest) returns (CountVideoResponse) {} 42 | } 43 | -------------------------------------------------------------------------------- /src/idl/recommand.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rpc.recommend; 3 | option go_package = "GuGoTik/src/rpc/recommend"; 4 | 5 | message RecommendRequest { 6 | uint32 user_id = 1; 7 | int32 offset = 2;// 用户id 8 | int32 number = 3; 9 | } 10 | 11 | message RecommendResponse { 12 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 13 | string status_msg = 2; // 返回状态描述 14 | repeated uint32 video_list = 3; // 视频 Id 列表 15 | } 16 | 17 | message RecommendRegisterRequest { 18 | uint32 user_id = 1; // 用户id 19 | string username = 2; 20 | } 21 | 22 | message RecommendRegisterResponse { 23 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 24 | string status_msg = 2; // 返回状态描述 25 | } 26 | 27 | service RecommendService { 28 | rpc GetRecommendInformation(RecommendRequest) returns (RecommendResponse) {} 29 | rpc RegisterRecommendUser(RecommendRegisterRequest) returns (RecommendRegisterResponse) {} 30 | } -------------------------------------------------------------------------------- /src/idl/relation.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "user.proto"; 3 | package rpc.Relation; 4 | option go_package = "GuGoTik/src/rpc/relation"; 5 | 6 | 7 | message RelationActionRequest { 8 | uint32 actor_id = 1; // 当前登录用户 9 | uint32 user_id = 2; // 对方用户id 10 | } 11 | 12 | message RelationActionResponse { 13 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 14 | string status_msg = 2; // 返回状态描述 15 | } 16 | 17 | message FollowListRequest { 18 | uint32 actor_id = 1; // 当前登录用户id 19 | uint32 user_id = 2; // 对方用户id 20 | } 21 | 22 | message FollowListResponse { 23 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 24 | string status_msg = 2; // 返回状态描述 25 | repeated user.User user_list = 3; // 用户信息列表 26 | } 27 | 28 | message CountFollowListRequest { 29 | uint32 user_id = 1; // 用户id 30 | } 31 | 32 | message CountFollowListResponse { 33 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 34 | string status_msg = 2; // 返回状态描述 35 | uint32 count = 3; // 关注数 36 | } 37 | 38 | message FollowerListRequest { 39 | uint32 actor_id = 1; // 当前登录用户id 40 | uint32 user_id = 2; // 对方用户id 41 | } 42 | 43 | message FollowerListResponse { 44 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 45 | string status_msg = 2; // 返回状态描述 46 | repeated user.User user_list = 3; // 用户列表 47 | } 48 | 49 | message CountFollowerListRequest { 50 | uint32 user_id = 1; // 用户id 51 | } 52 | 53 | message CountFollowerListResponse { 54 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 55 | string status_msg = 2; // 返回状态描述 56 | uint32 count = 3; // 粉丝数 57 | } 58 | 59 | message FriendListRequest { 60 | uint32 actor_id = 1; // 当前登录用户id 61 | uint32 user_id = 2; // 对方用户id 62 | } 63 | 64 | message FriendListResponse { 65 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 66 | string status_msg = 2; // 返回状态描述 67 | repeated user.User user_list = 3; // 用户列表 68 | } 69 | 70 | message IsFollowRequest { 71 | uint32 actor_id = 1; 72 | uint32 user_id = 2; 73 | } 74 | 75 | message IsFollowResponse { 76 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 77 | string status_msg = 2; // 返回状态描述 78 | bool result = 3; // 结果 79 | } 80 | 81 | service RelationService { 82 | rpc Follow (RelationActionRequest) returns (RelationActionResponse); 83 | 84 | rpc Unfollow (RelationActionRequest) returns (RelationActionResponse); 85 | 86 | rpc GetFollowList (FollowListRequest) returns (FollowListResponse); 87 | 88 | rpc CountFollowList (CountFollowListRequest) returns (CountFollowListResponse); 89 | 90 | rpc GetFollowerList (FollowerListRequest) returns (FollowerListResponse); 91 | 92 | rpc CountFollowerList (CountFollowerListRequest) returns (CountFollowerListResponse); 93 | 94 | rpc GetFriendList (FriendListRequest) returns (FriendListResponse); 95 | 96 | rpc IsFollow (IsFollowRequest) returns (IsFollowResponse); 97 | } 98 | -------------------------------------------------------------------------------- /src/idl/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rpc.user; 3 | option go_package = "GuGoTik/src/rpc/user"; 4 | 5 | message UserRequest { 6 | uint32 user_id = 1; // 用户id 7 | uint32 actor_id = 2; // 发送请求的用户的id 8 | } 9 | 10 | message UserResponse { 11 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 12 | string status_msg = 2; // 返回状态描述 13 | User user = 3; // 用户信息 14 | } 15 | 16 | message UserExistRequest { 17 | uint32 user_id = 1; // 用户id 18 | } 19 | 20 | message UserExistResponse { 21 | int32 status_code = 1; // 状态码,0-成功,其他值-失败 22 | string status_msg = 2; // 返回状态描述 23 | bool existed = 3; 24 | } 25 | 26 | message User { 27 | uint32 id = 1; // 用户id 28 | string name = 2; // 用户名称 29 | optional uint32 follow_count = 3; // 关注总数 30 | optional uint32 follower_count = 4; // 粉丝总数 31 | bool is_follow = 5; // true-已关注,false-未关注 32 | optional string avatar = 6; //用户头像 33 | optional string background_image = 7; //用户个人页顶部大图 34 | optional string signature = 8; //个人简介 35 | optional uint32 total_favorited = 9; //获赞数量 36 | optional uint32 work_count = 10; //作品数量 37 | optional uint32 favorite_count = 11; //点赞数量 38 | } 39 | 40 | service UserService{ 41 | rpc GetUserInfo(UserRequest) returns(UserResponse); 42 | 43 | rpc GetUserExistInformation(UserExistRequest) returns(UserExistResponse); 44 | } -------------------------------------------------------------------------------- /src/models/action.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/storage/database" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Action struct { 9 | Type uint // 用户操作的行为类型,如:1表示点赞相关 10 | Name string // 用户操作的动作名称,如:FavoriteNameActionLog 表示点赞相关操作 11 | SubName string // 用户操作动作的子名称,如:FavoriteUpActionLog 表示给视频增加赞操作 12 | ServiceName string // 服务来源,添加服务的名称,如 FavoriteService 13 | Attached string // 附带信息,当 Name - SubName 无法说明时,添加一个额外的信息 14 | ActorId uint32 // 操作者 Id 15 | VideoId uint32 // 附属的视频 Id,没有填写为0 16 | AffectUserId uint32 // 操作的用户 Id,如:被关注的用户 Id 17 | AffectAction uint // 操作的类型,如:1. 自增/自减某个数据,2. 直接修改某个数据 18 | AffectedData string // 操作的数值是什么,如果是自增,填 1,如果是修改为某个数据,那么填这个数据的值 19 | EventId string // 如果这个操作是一个大操作的子类型,那么需要具有相同的 UUID 20 | TraceId string // 这个操作的 TraceId 21 | SpanId string // 这个操作的 SpanId 22 | gorm.Model //数据库模型 23 | } 24 | 25 | func init() { 26 | if err := database.Client.AutoMigrate(&Action{}); err != nil { 27 | panic(err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/models/comment.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/storage/database" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Comment struct { 9 | ID uint32 `gorm:"not null;primaryKey;autoIncrement"` // 评论 ID 10 | VideoId uint32 `json:"video_id" column:"video_id" gorm:"not null;index:comment_video"` // 视频 ID 11 | UserId uint32 `json:"user_id" column:"user_id" gorm:"not null"` // 用户 ID 12 | Content string `json:"content" column:"content"` // 评论内容 13 | Rate uint32 `gorm:"index:comment_video"` 14 | Reason string 15 | ModerationFlagged bool 16 | ModerationHate bool 17 | ModerationHateThreatening bool 18 | ModerationSelfHarm bool 19 | ModerationSexual bool 20 | ModerationSexualMinors bool 21 | ModerationViolence bool 22 | ModerationViolenceGraphic bool 23 | gorm.Model 24 | } 25 | 26 | func init() { 27 | if err := database.Client.AutoMigrate(&Comment{}); err != nil { 28 | panic(err) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/models/event.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type RecommendEvent struct { 4 | Type int // 1. 已读 2. 喜欢 3. 插入新数据 5 | Source string // 来源 6 | Slice string // 附带信息 7 | ActorId uint32 8 | VideoId []uint32 // 代表视频 Id,可以批量操作,但是仅对于某一个唯一的用户 9 | Tag []string // 插入时使用 10 | Category []string // 插入时使用 11 | Title string 12 | } 13 | -------------------------------------------------------------------------------- /src/models/message.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/storage/database" 5 | "time" 6 | 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type Message struct { 11 | ID uint32 `gorm:"not null;primarykey;autoIncrement"` 12 | ToUserId uint32 `gorm:"not null"` 13 | FromUserId uint32 `gorm:"not null"` 14 | ConversationId string `gorm:"not null" index:"conversationid"` 15 | Content string `gorm:"not null"` 16 | 17 | // Create_time time.Time `gorm:"not null"` 18 | //Updatetime deleteTime 19 | gorm.Model 20 | } 21 | 22 | // es 使用 23 | type EsMessage struct { 24 | ToUserId uint32 `json:"toUserid"` 25 | FromUserId uint32 `json:"fromUserId"` 26 | ConversationId string `json:"conversationId"` 27 | Content string `json:"content"` 28 | CreateTime time.Time `json:"createTime"` 29 | } 30 | 31 | func init() { 32 | if err := database.Client.AutoMigrate(&Message{}); err != nil { 33 | panic(err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/models/rawvideo.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/storage/database" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type RawVideo struct { 9 | ActorId uint32 10 | VideoId uint32 `gorm:"not null;primaryKey"` 11 | Title string 12 | FileName string 13 | CoverName string 14 | gorm.Model 15 | } 16 | 17 | func init() { 18 | if err := database.Client.AutoMigrate(&RawVideo{}); err != nil { 19 | panic(err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/models/relation.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/storage/database" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type Relation struct { 9 | ID uint32 `gorm:"not null;primarykey;autoIncrement"` // relation ID 10 | ActorId uint32 `json:"actor_id" column:"actor_id" gorm:"not null;index:actor_list"` // 粉丝 ID 11 | UserId uint32 `json:"user_id" column:"user_id" gorm:"not null;index:user_list"` // 被关注用户 ID 12 | gorm.Model 13 | } 14 | 15 | func init() { 16 | if err := database.Client.AutoMigrate(&Relation{}); err != nil { 17 | panic(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/storage/database" 5 | "gorm.io/gorm" 6 | "regexp" 7 | ) 8 | 9 | type User struct { 10 | ID uint32 `gorm:"not null;primarykey;autoIncrement"` //用户 Id 11 | UserName string `gorm:"not null;unique;size: 32;index" redis:"UserName"` // 用户名 12 | Password string `gorm:"not null" redis:"Password"` // 密码 13 | Role int `gorm:"default:1" redis:"Role"` // 角色 14 | Avatar string `redis:"Avatar"` // 头像 15 | BackgroundImage string `redis:"BackGroundImage"` // 背景图片 16 | Signature string `redis:"Signature"` // 个人简介 17 | gorm.Model 18 | } 19 | 20 | // IsNameEmail 判断用户的名称是否为邮箱。 21 | func (u *User) IsNameEmail() bool { 22 | pattern := `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` 23 | reg := regexp.MustCompile(pattern) 24 | return reg.MatchString(u.UserName) 25 | } 26 | 27 | func (u *User) IsDirty() bool { 28 | return u.UserName != "" 29 | } 30 | 31 | func (u *User) GetID() uint32 { 32 | return u.ID 33 | } 34 | 35 | func init() { 36 | if err := database.Client.AutoMigrate(&User{}); err != nil { 37 | panic(err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/models/video.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/storage/database" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | // Video 视频表 9 | type Video struct { 10 | ID uint32 `gorm:"not null;primaryKey;"` 11 | UserId uint32 `json:"user_id" gorm:"not null;"` 12 | Title string `json:"title" gorm:"not null;"` 13 | FileName string `json:"play_name" gorm:"not null;"` 14 | CoverName string `json:"cover_name" gorm:"not null;"` 15 | AudioFileName string 16 | Transcript string 17 | Summary string 18 | Keywords string // e.g., "keywords1 | keywords2 | keywords3" 19 | gorm.Model 20 | } 21 | 22 | func init() { 23 | if err := database.Client.AutoMigrate(&Video{}); err != nil { 24 | panic(err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/services/comment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/extra/profiling" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/comment" 8 | "GuGoTik/src/utils/consul" 9 | "GuGoTik/src/utils/logging" 10 | "GuGoTik/src/utils/prom" 11 | "context" 12 | grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" 13 | "github.com/oklog/run" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | "github.com/sirupsen/logrus" 16 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 17 | "google.golang.org/grpc" 18 | "google.golang.org/grpc/health" 19 | "google.golang.org/grpc/health/grpc_health_v1" 20 | "net" 21 | "net/http" 22 | "os" 23 | "syscall" 24 | ) 25 | 26 | func main() { 27 | tp, err := tracing.SetTraceProvider(config.CommentRpcServerName) 28 | 29 | if err != nil { 30 | logging.Logger.WithFields(logrus.Fields{ 31 | "err": err, 32 | }).Panicf("Error to set the trace") 33 | } 34 | defer func() { 35 | if err := tp.Shutdown(context.Background()); err != nil { 36 | logging.Logger.WithFields(logrus.Fields{ 37 | "err": err, 38 | }).Errorf("Error to set the trace") 39 | } 40 | }() 41 | 42 | // Configure Pyroscope 43 | profiling.InitPyroscope("GuGoTik.CommentService") 44 | 45 | log := logging.LogService(config.CommentRpcServerName) 46 | lis, err := net.Listen("tcp", config.EnvCfg.PodIpAddr+config.CommentRpcServerPort) 47 | 48 | if err != nil { 49 | log.Panicf("Rpc %s listen happens error: %v", config.CommentRpcServerName, err) 50 | } 51 | 52 | srvMetrics := grpcprom.NewServerMetrics( 53 | grpcprom.WithServerHandlingTimeHistogram( 54 | grpcprom.WithHistogramBuckets([]float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120}), 55 | ), 56 | ) 57 | reg := prom.Client 58 | reg.MustRegister(srvMetrics) 59 | 60 | s := grpc.NewServer( 61 | grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), 62 | grpc.ChainUnaryInterceptor(srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 63 | grpc.ChainStreamInterceptor(srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 64 | ) 65 | 66 | if err := consul.RegisterConsul(config.CommentRpcServerName, config.CommentRpcServerPort); err != nil { 67 | log.Panicf("Rpc %s register consul happens error for: %v", config.CommentRpcServerName, err) 68 | } 69 | log.Infof("Rpc %s is running at %s now", config.CommentRpcServerName, config.CommentRpcServerPort) 70 | 71 | var srv CommentServiceImpl 72 | comment.RegisterCommentServiceServer(s, srv) 73 | grpc_health_v1.RegisterHealthServer(s, health.NewServer()) 74 | defer CloseMQConn() 75 | if err := consul.RegisterConsul(config.CommentRpcServerName, config.CommentRpcServerPort); err != nil { 76 | log.Panicf("Rpc %s register consul happens error for: %v", config.CommentRpcServerName, err) 77 | } 78 | srv.New() 79 | srvMetrics.InitializeMetrics(s) 80 | 81 | g := &run.Group{} 82 | g.Add(func() error { 83 | return s.Serve(lis) 84 | }, func(err error) { 85 | s.GracefulStop() 86 | s.Stop() 87 | log.Errorf("Rpc %s listen happens error for: %v", config.CommentRpcServerName, err) 88 | }) 89 | 90 | httpSrv := &http.Server{Addr: config.EnvCfg.PodIpAddr + config.Metrics} 91 | g.Add(func() error { 92 | m := http.NewServeMux() 93 | m.Handle("/metrics", promhttp.HandlerFor( 94 | reg, 95 | promhttp.HandlerOpts{ 96 | EnableOpenMetrics: true, 97 | }, 98 | )) 99 | httpSrv.Handler = m 100 | log.Infof("Promethus now running") 101 | return httpSrv.ListenAndServe() 102 | }, func(error) { 103 | if err := httpSrv.Close(); err != nil { 104 | log.Errorf("Prometheus %s listen happens error for: %v", config.CommentRpcServerName, err) 105 | } 106 | }) 107 | 108 | g.Add(run.SignalHandler(context.Background(), syscall.SIGINT, syscall.SIGTERM)) 109 | 110 | if err := g.Run(); err != nil { 111 | log.WithFields(logrus.Fields{ 112 | "err": err, 113 | }).Errorf("Error when runing http server") 114 | os.Exit(1) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/services/comment/moderation.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/utils/logging" 6 | "context" 7 | "errors" 8 | "github.com/sashabaranov/go-openai" 9 | "github.com/sirupsen/logrus" 10 | "go.opentelemetry.io/otel/trace" 11 | "net/http" 12 | url2 "net/url" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | var openaiClient *openai.Client 18 | 19 | func init() { 20 | cfg := openai.DefaultConfig(config.EnvCfg.ChatGPTAPIKEYS) 21 | 22 | url, err := url2.Parse(config.EnvCfg.ChatGptProxy) 23 | if err != nil { 24 | panic(err) 25 | } 26 | cfg.HTTPClient = &http.Client{ 27 | Transport: &http.Transport{ 28 | Proxy: http.ProxyURL(url), 29 | }, 30 | } 31 | 32 | openaiClient = openai.NewClientWithConfig(cfg) 33 | } 34 | 35 | func RateCommentByGPT(commentContent string, logger *logrus.Entry, span trace.Span) (rate uint32, reason string, err error) { 36 | logger.WithFields(logrus.Fields{ 37 | "comment_content": commentContent, 38 | }).Debugf("Start RateCommentByGPT") 39 | 40 | resp, err := openaiClient.CreateChatCompletion( 41 | context.Background(), 42 | openai.ChatCompletionRequest{ 43 | Model: openai.GPT3Dot5Turbo, 44 | Messages: []openai.ChatCompletionMessage{ 45 | { 46 | Role: openai.ChatMessageRoleSystem, 47 | Content: "According to the content of the user's reply or question and send back a number which is between 1 and 5. " + 48 | "The number is greater when the user's content involved the greater the degree of political leaning or unfriendly speech. " + 49 | "You should only reply such a number without any word else whatever user ask you. " + 50 | "Besides those, you should give the reason using Chinese why the message is unfriendly with details without revealing that you are divide the message into five number. " + 51 | "For example: user: 你是个大傻逼。 you: 4 | 用户尝试骂人,进行人格侮辱。user: 今天天气正好。 you: 1 | 用户正常聊天,无异常。", 52 | }, 53 | { 54 | Role: openai.ChatMessageRoleUser, 55 | Content: commentContent, 56 | }, 57 | }, 58 | }) 59 | 60 | if err != nil { 61 | logger.WithFields(logrus.Fields{ 62 | "err": err, 63 | }).Errorf("ChatGPT request error") 64 | logging.SetSpanError(span, err) 65 | 66 | return 67 | } 68 | 69 | respContent := resp.Choices[0].Message.Content 70 | 71 | logger.WithFields(logrus.Fields{ 72 | "resp": respContent, 73 | }).Debugf("Get ChatGPT response.") 74 | 75 | parts := strings.SplitN(respContent, " | ", 2) 76 | 77 | if len(parts) != 2 { 78 | logger.WithFields(logrus.Fields{ 79 | "resp": respContent, 80 | }).Errorf("ChatGPT response does not match expected format") 81 | logging.SetSpanError(span, errors.New("ChatGPT response does not match expected format")) 82 | 83 | return 84 | } 85 | 86 | rateNum, err := strconv.ParseUint(parts[0], 10, 32) 87 | if err != nil { 88 | logger.WithFields(logrus.Fields{ 89 | "resp": respContent, 90 | }).Errorf("ChatGPT response does not match expected format") 91 | logging.SetSpanError(span, errors.New("ChatGPT response does not match expected format")) 92 | 93 | return 94 | } 95 | 96 | rate = uint32(rateNum) 97 | reason = parts[1] 98 | 99 | return 100 | } 101 | 102 | func ModerationCommentByGPT(commentContent string, logger *logrus.Entry, span trace.Span) (moderationRes openai.Result) { 103 | logger.WithFields(logrus.Fields{ 104 | "comment_content": commentContent, 105 | }).Debugf("Start ModerationCommentByGPT") 106 | 107 | resp, err := openaiClient.Moderations( 108 | context.Background(), 109 | openai.ModerationRequest{ 110 | Model: openai.ModerationTextLatest, 111 | Input: commentContent, 112 | }, 113 | ) 114 | 115 | if err != nil { 116 | logger.WithFields(logrus.Fields{ 117 | "err": err, 118 | }).Errorf("ChatGPT request error") 119 | logging.SetSpanError(span, err) 120 | 121 | return 122 | } 123 | 124 | moderationRes = resp.Results[0] 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /src/services/favorite/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/extra/profiling" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/favorite" 8 | "GuGoTik/src/utils/audit" 9 | "GuGoTik/src/utils/consul" 10 | "GuGoTik/src/utils/logging" 11 | "GuGoTik/src/utils/prom" 12 | "context" 13 | grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" 14 | "github.com/oklog/run" 15 | "github.com/prometheus/client_golang/prometheus/promhttp" 16 | "google.golang.org/grpc/health" 17 | "google.golang.org/grpc/health/grpc_health_v1" 18 | "net" 19 | "net/http" 20 | "os" 21 | "syscall" 22 | 23 | "github.com/sirupsen/logrus" 24 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 25 | "google.golang.org/grpc" 26 | ) 27 | 28 | func main() { 29 | tp, err := tracing.SetTraceProvider(config.FavoriteRpcServerName) 30 | 31 | if err != nil { 32 | logging.Logger.WithFields(logrus.Fields{ 33 | "err": err, 34 | }).Panicf("Error to set the trace") 35 | } 36 | defer func() { 37 | if err := tp.Shutdown(context.Background()); err != nil { 38 | logging.Logger.WithFields(logrus.Fields{ 39 | "err": err, 40 | }).Errorf("Error to set the trace") 41 | } 42 | }() 43 | 44 | // Configure Pyroscope 45 | profiling.InitPyroscope("GuGoTik.FavoriteService") 46 | 47 | log := logging.LogService(config.FavoriteRpcServerName) 48 | lis, err := net.Listen("tcp", config.EnvCfg.PodIpAddr+config.FavoriteRpcServerPort) 49 | 50 | if err != nil { 51 | log.Panicf("Rpc %s listen happens error: %v", config.FavoriteRpcServerName, err) 52 | } 53 | 54 | srvMetrics := grpcprom.NewServerMetrics( 55 | grpcprom.WithServerHandlingTimeHistogram( 56 | grpcprom.WithHistogramBuckets([]float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120}), 57 | ), 58 | ) 59 | reg := prom.Client 60 | reg.MustRegister(srvMetrics) 61 | 62 | s := grpc.NewServer( 63 | grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), 64 | grpc.ChainUnaryInterceptor(srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 65 | grpc.ChainStreamInterceptor(srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 66 | ) 67 | 68 | if err := consul.RegisterConsul(config.FavoriteRpcServerName, config.FavoriteRpcServerPort); err != nil { 69 | log.Panicf("Rpc %s register consul happens error for: %v", config.FavoriteRpcServerName, err) 70 | } 71 | log.Infof("Rpc %s is running at %s now", config.FavoriteRpcServerName, config.FavoriteRpcServerPort) 72 | 73 | var srv FavoriteServiceServerImpl 74 | favorite.RegisterFavoriteServiceServer(s, srv) 75 | grpc_health_v1.RegisterHealthServer(s, health.NewServer()) 76 | defer CloseMQConn() 77 | if err := consul.RegisterConsul(config.FavoriteRpcServerName, config.FavoriteRpcServerPort); err != nil { 78 | log.Panicf("Rpc %s register consul happens error for: %v", config.FavoriteRpcServerName, err) 79 | } 80 | srv.New() 81 | 82 | // Initialize the audit_exchange 83 | audit.DeclareAuditExchange(channel) 84 | 85 | srvMetrics.InitializeMetrics(s) 86 | 87 | g := &run.Group{} 88 | g.Add(func() error { 89 | return s.Serve(lis) 90 | }, func(err error) { 91 | s.GracefulStop() 92 | s.Stop() 93 | log.Errorf("Rpc %s listen happens error for: %v", config.FavoriteRpcServerName, err) 94 | }) 95 | 96 | httpSrv := &http.Server{Addr: config.EnvCfg.PodIpAddr + config.Metrics} 97 | g.Add(func() error { 98 | m := http.NewServeMux() 99 | m.Handle("/metrics", promhttp.HandlerFor( 100 | reg, 101 | promhttp.HandlerOpts{ 102 | EnableOpenMetrics: true, 103 | }, 104 | )) 105 | httpSrv.Handler = m 106 | log.Infof("Promethus now running") 107 | return httpSrv.ListenAndServe() 108 | }, func(error) { 109 | if err := httpSrv.Close(); err != nil { 110 | log.Errorf("Prometheus %s listen happens error for: %v", config.FavoriteRpcServerName, err) 111 | } 112 | }) 113 | 114 | g.Add(run.SignalHandler(context.Background(), syscall.SIGINT, syscall.SIGTERM)) 115 | 116 | if err := g.Run(); err != nil { 117 | log.WithFields(logrus.Fields{ 118 | "err": err, 119 | }).Errorf("Error when runing http server") 120 | os.Exit(1) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/services/feed/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/extra/profiling" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/feed" 8 | "GuGoTik/src/utils/consul" 9 | "GuGoTik/src/utils/logging" 10 | "GuGoTik/src/utils/prom" 11 | "context" 12 | grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" 13 | "github.com/oklog/run" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | "github.com/sirupsen/logrus" 16 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 17 | "google.golang.org/grpc" 18 | "google.golang.org/grpc/health" 19 | "google.golang.org/grpc/health/grpc_health_v1" 20 | "net" 21 | "net/http" 22 | "os" 23 | "syscall" 24 | ) 25 | 26 | func main() { 27 | tp, err := tracing.SetTraceProvider(config.FeedRpcServerName) 28 | 29 | if err != nil { 30 | logging.Logger.WithFields(logrus.Fields{ 31 | "err": err, 32 | }).Panicf("Error to set the trace") 33 | } 34 | defer func() { 35 | if err := tp.Shutdown(context.Background()); err != nil { 36 | logging.Logger.WithFields(logrus.Fields{ 37 | "err": err, 38 | }).Errorf("Error to set the trace") 39 | } 40 | }() 41 | 42 | // Configure Pyroscope 43 | profiling.InitPyroscope("GuGoTik.FeedService") 44 | 45 | log := logging.LogService(config.FeedRpcServerName) 46 | lis, err := net.Listen("tcp", config.EnvCfg.PodIpAddr+config.FeedRpcServerPort) 47 | 48 | if err != nil { 49 | log.Panicf("Rpc %s listen happens error: %v", config.FeedRpcServerName, err) 50 | } 51 | 52 | srvMetrics := grpcprom.NewServerMetrics( 53 | grpcprom.WithServerHandlingTimeHistogram( 54 | grpcprom.WithHistogramBuckets([]float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120}), 55 | ), 56 | ) 57 | reg := prom.Client 58 | reg.MustRegister(srvMetrics) 59 | 60 | s := grpc.NewServer( 61 | grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), 62 | grpc.ChainUnaryInterceptor(srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 63 | grpc.ChainStreamInterceptor(srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 64 | ) 65 | 66 | if err := consul.RegisterConsul(config.FeedRpcServerName, config.FeedRpcServerPort); err != nil { 67 | log.Panicf("Rpc %s register consul happens error for: %v", config.FeedRpcServerName, err) 68 | } 69 | log.Infof("Rpc %s is running at %s now", config.FeedRpcServerName, config.FeedRpcServerPort) 70 | 71 | var srv FeedServiceImpl 72 | feed.RegisterFeedServiceServer(s, srv) 73 | grpc_health_v1.RegisterHealthServer(s, health.NewServer()) 74 | defer CloseMQConn() 75 | if err := consul.RegisterConsul(config.FeedRpcServerName, config.FeedRpcServerPort); err != nil { 76 | log.Panicf("Rpc %s register consul happens error for: %v", config.FeedRpcServerName, err) 77 | } 78 | srv.New() 79 | srvMetrics.InitializeMetrics(s) 80 | 81 | g := &run.Group{} 82 | g.Add(func() error { 83 | return s.Serve(lis) 84 | }, func(err error) { 85 | s.GracefulStop() 86 | s.Stop() 87 | log.Errorf("Rpc %s listen happens error for: %v", config.FeedRpcServerName, err) 88 | }) 89 | 90 | httpSrv := &http.Server{Addr: config.EnvCfg.PodIpAddr + config.Metrics} 91 | g.Add(func() error { 92 | m := http.NewServeMux() 93 | m.Handle("/metrics", promhttp.HandlerFor( 94 | reg, 95 | promhttp.HandlerOpts{ 96 | EnableOpenMetrics: true, 97 | }, 98 | )) 99 | httpSrv.Handler = m 100 | log.Infof("Promethus now running") 101 | return httpSrv.ListenAndServe() 102 | }, func(error) { 103 | if err := httpSrv.Close(); err != nil { 104 | log.Errorf("Prometheus %s listen happens error for: %v", config.FeedRpcServerName, err) 105 | } 106 | }) 107 | 108 | g.Add(run.SignalHandler(context.Background(), syscall.SIGINT, syscall.SIGTERM)) 109 | 110 | if err := g.Run(); err != nil { 111 | log.WithFields(logrus.Fields{ 112 | "err": err, 113 | }).Errorf("Error when runing http server") 114 | os.Exit(1) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/services/message/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/extra/profiling" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/chat" 8 | "GuGoTik/src/utils/consul" 9 | "GuGoTik/src/utils/logging" 10 | "GuGoTik/src/utils/prom" 11 | "context" 12 | grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" 13 | "github.com/oklog/run" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | "google.golang.org/grpc/health" 16 | "google.golang.org/grpc/health/grpc_health_v1" 17 | "net" 18 | "net/http" 19 | "os" 20 | "syscall" 21 | 22 | "github.com/sirupsen/logrus" 23 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 24 | "google.golang.org/grpc" 25 | ) 26 | 27 | func main() { 28 | tp, err := tracing.SetTraceProvider(config.MessageRpcServerName) 29 | 30 | if err != nil { 31 | logging.Logger.WithFields(logrus.Fields{ 32 | "err": err, 33 | }).Panicf("Error to set the trace") 34 | } 35 | defer func() { 36 | if err := tp.Shutdown(context.Background()); err != nil { 37 | logging.Logger.WithFields(logrus.Fields{ 38 | "err": err, 39 | }).Errorf("Error to set the trace") 40 | } 41 | }() 42 | 43 | // Configure Pyroscope 44 | profiling.InitPyroscope("GuGoTik.ChatService") 45 | 46 | log := logging.LogService(config.MessageRpcServerName) 47 | lis, err := net.Listen("tcp", config.EnvCfg.PodIpAddr+config.MessageRpcServerPort) 48 | 49 | if err != nil { 50 | log.Panicf("Rpc %s listen happens error: %v", config.MessageRpcServerName, err) 51 | } 52 | 53 | srvMetrics := grpcprom.NewServerMetrics( 54 | grpcprom.WithServerHandlingTimeHistogram( 55 | grpcprom.WithHistogramBuckets([]float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120}), 56 | ), 57 | ) 58 | 59 | reg := prom.Client 60 | reg.MustRegister(srvMetrics) 61 | defer CloseMQConn() 62 | s := grpc.NewServer( 63 | grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), 64 | grpc.ChainUnaryInterceptor(srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 65 | grpc.ChainStreamInterceptor(srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 66 | ) 67 | 68 | if err := consul.RegisterConsul(config.MessageRpcServerName, config.MessageRpcServerPort); err != nil { 69 | log.Panicf("Rpc %s register consul happens error for: %v", config.MessageRpcServerName, err) 70 | } 71 | log.Infof("Rpc %s is running at %s now", config.MessageRpcServerName, config.MessageRpcServerPort) 72 | 73 | var srv MessageServiceImpl 74 | chat.RegisterChatServiceServer(s, srv) 75 | grpc_health_v1.RegisterHealthServer(s, health.NewServer()) 76 | defer CloseMQConn() 77 | srv.New() 78 | srvMetrics.InitializeMetrics(s) 79 | 80 | g := &run.Group{} 81 | g.Add(func() error { 82 | return s.Serve(lis) 83 | }, func(err error) { 84 | s.GracefulStop() 85 | s.Stop() 86 | log.Errorf("Rpc %s listen happens error for: %v", config.MessageRpcServerName, err) 87 | }) 88 | 89 | httpSrv := &http.Server{Addr: config.EnvCfg.PodIpAddr + config.Metrics} 90 | g.Add(func() error { 91 | m := http.NewServeMux() 92 | m.Handle("/metrics", promhttp.HandlerFor( 93 | reg, 94 | promhttp.HandlerOpts{ 95 | EnableOpenMetrics: true, 96 | }, 97 | )) 98 | httpSrv.Handler = m 99 | log.Infof("Promethus now running") 100 | return httpSrv.ListenAndServe() 101 | }, func(error) { 102 | if err := httpSrv.Close(); err != nil { 103 | log.Errorf("Prometheus %s listen happens error for: %v", config.MessageRpcServerName, err) 104 | } 105 | }) 106 | 107 | g.Add(run.SignalHandler(context.Background(), syscall.SIGINT, syscall.SIGTERM)) 108 | 109 | if err := g.Run(); err != nil { 110 | log.WithFields(logrus.Fields{ 111 | "err": err, 112 | }).Errorf("Error when runing http server") 113 | os.Exit(1) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/services/msgconsumer/esexchange.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/strings" 5 | "GuGoTik/src/extra/tracing" 6 | "GuGoTik/src/models" 7 | "GuGoTik/src/storage/es" 8 | "GuGoTik/src/utils/logging" 9 | "GuGoTik/src/utils/rabbitmq" 10 | "bytes" 11 | "context" 12 | "encoding/json" 13 | 14 | "github.com/elastic/go-elasticsearch/esapi" 15 | amqp "github.com/rabbitmq/amqp091-go" 16 | "github.com/sirupsen/logrus" 17 | ) 18 | 19 | func esSaveMessage(channel *amqp.Channel) { 20 | 21 | msg, err := channel.Consume(strings.MessageES, "", 22 | false, false, false, false, nil, 23 | ) 24 | failOnError(err, "Failed to Consume") 25 | 26 | var message models.Message 27 | for body := range msg { 28 | ctx := rabbitmq.ExtractAMQPHeaders(context.Background(), body.Headers) 29 | ctx, span := tracing.Tracer.Start(ctx, "MessageSendService") 30 | logger := logging.LogService("MessageSend").WithContext(ctx) 31 | 32 | if err := json.Unmarshal(body.Body, &message); err != nil { 33 | logger.WithFields(logrus.Fields{ 34 | "from_id": message.FromUserId, 35 | "to_id": message.ToUserId, 36 | "content": message.Content, 37 | "err": err, 38 | }).Errorf("Error when unmarshaling the prepare json body.") 39 | logging.SetSpanError(span, err) 40 | err = body.Nack(false, true) 41 | if err != nil { 42 | logger.WithFields( 43 | logrus.Fields{ 44 | "from_id": message.FromUserId, 45 | "to_id": message.ToUserId, 46 | "content": message.Content, 47 | "err": err, 48 | }, 49 | ).Errorf("Error when nack the message") 50 | logging.SetSpanError(span, err) 51 | } 52 | span.End() 53 | continue 54 | } 55 | 56 | EsMessage := models.EsMessage{ 57 | ToUserId: message.ToUserId, 58 | FromUserId: message.FromUserId, 59 | ConversationId: message.ConversationId, 60 | Content: message.Content, 61 | CreateTime: message.CreatedAt, 62 | } 63 | data, _ := json.Marshal(EsMessage) 64 | 65 | req := esapi.IndexRequest{ 66 | Index: "message", 67 | Refresh: "true", 68 | Body: bytes.NewReader(data), 69 | } 70 | //返回值close 71 | res, err := req.Do(ctx, es.EsClient) 72 | 73 | if err != nil { 74 | logger.WithFields(logrus.Fields{ 75 | "from_id": message.FromUserId, 76 | "to_id": message.ToUserId, 77 | "content": message.Content, 78 | "err": err, 79 | }).Errorf("Error when insert message to database.") 80 | logging.SetSpanError(span, err) 81 | 82 | span.End() 83 | continue 84 | } 85 | res.Body.Close() 86 | 87 | err = body.Ack(false) 88 | 89 | if err != nil { 90 | logger.WithFields(logrus.Fields{ 91 | "err": err, 92 | }).Errorf("Error when dealing with the message...3") 93 | logging.SetSpanError(span, err) 94 | } 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/services/publish/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/extra/profiling" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/publish" 8 | "GuGoTik/src/utils/consul" 9 | "GuGoTik/src/utils/logging" 10 | "GuGoTik/src/utils/prom" 11 | "context" 12 | grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" 13 | "github.com/oklog/run" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | "github.com/sirupsen/logrus" 16 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 17 | "google.golang.org/grpc" 18 | "google.golang.org/grpc/health" 19 | "google.golang.org/grpc/health/grpc_health_v1" 20 | "net" 21 | "net/http" 22 | "os" 23 | "syscall" 24 | ) 25 | 26 | func main() { 27 | tp, err := tracing.SetTraceProvider(config.PublishRpcServerName) 28 | 29 | if err != nil { 30 | logging.Logger.WithFields(logrus.Fields{ 31 | "err": err, 32 | }).Panicf("Error to set the trace") 33 | } 34 | defer func() { 35 | if err := tp.Shutdown(context.Background()); err != nil { 36 | logging.Logger.WithFields(logrus.Fields{ 37 | "err": err, 38 | }).Errorf("Error to set the trace") 39 | } 40 | }() 41 | 42 | // Configure Pyroscope 43 | profiling.InitPyroscope("GuGoTik.PublishService") 44 | 45 | log := logging.LogService(config.PublishRpcServerName) 46 | lis, err := net.Listen("tcp", config.EnvCfg.PodIpAddr+config.PublishRpcServerPort) 47 | 48 | if err != nil { 49 | log.Panicf("Rpc %s listen happens error: %v", config.PublishRpcServerName, err) 50 | } 51 | 52 | srvMetrics := grpcprom.NewServerMetrics( 53 | grpcprom.WithServerHandlingTimeHistogram( 54 | grpcprom.WithHistogramBuckets([]float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120}), 55 | ), 56 | ) 57 | reg := prom.Client 58 | reg.MustRegister(srvMetrics) 59 | maxSize := config.MaxVideoSize 60 | 61 | s := grpc.NewServer( 62 | grpc.MaxRecvMsgSize(maxSize), 63 | grpc.MaxSendMsgSize(maxSize), 64 | grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), 65 | grpc.ChainUnaryInterceptor(srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 66 | grpc.ChainStreamInterceptor(srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 67 | ) 68 | 69 | if err := consul.RegisterConsul(config.PublishRpcServerName, config.PublishRpcServerPort); err != nil { 70 | log.Panicf("Rpc %s register consul happens error for: %v", config.PublishRpcServerName, err) 71 | } 72 | log.Infof("Rpc %s is running at %s now", config.PublishRpcServerName, config.PublishRpcServerPort) 73 | 74 | var srv PublishServiceImpl 75 | publish.RegisterPublishServiceServer(s, srv) 76 | grpc_health_v1.RegisterHealthServer(s, health.NewServer()) 77 | defer CloseMQConn() 78 | if err := consul.RegisterConsul(config.PublishRpcServerName, config.PublishRpcServerPort); err != nil { 79 | log.Panicf("Rpc %s register consul happens error for: %v", config.PublishRpcServerName, err) 80 | } 81 | srv.New() 82 | srvMetrics.InitializeMetrics(s) 83 | 84 | g := &run.Group{} 85 | g.Add(func() error { 86 | return s.Serve(lis) 87 | }, func(err error) { 88 | s.GracefulStop() 89 | s.Stop() 90 | log.Errorf("Rpc %s listen happens error for: %v", config.PublishRpcServerName, err) 91 | }) 92 | 93 | httpSrv := &http.Server{Addr: config.EnvCfg.PodIpAddr + config.Metrics} 94 | g.Add(func() error { 95 | m := http.NewServeMux() 96 | m.Handle("/metrics", promhttp.HandlerFor( 97 | reg, 98 | promhttp.HandlerOpts{ 99 | EnableOpenMetrics: true, 100 | }, 101 | )) 102 | httpSrv.Handler = m 103 | log.Infof("Promethus now running") 104 | return httpSrv.ListenAndServe() 105 | }, func(error) { 106 | if err := httpSrv.Close(); err != nil { 107 | log.Errorf("Prometheus %s listen happens error for: %v", config.PublishRpcServerName, err) 108 | } 109 | }) 110 | 111 | g.Add(run.SignalHandler(context.Background(), syscall.SIGINT, syscall.SIGTERM)) 112 | 113 | if err := g.Run(); err != nil { 114 | log.WithFields(logrus.Fields{ 115 | "err": err, 116 | }).Errorf("Error when runing http server") 117 | os.Exit(1) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/services/recommend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/extra/profiling" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/recommend" 8 | "GuGoTik/src/utils/consul" 9 | "GuGoTik/src/utils/logging" 10 | "GuGoTik/src/utils/prom" 11 | "context" 12 | grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" 13 | "github.com/oklog/run" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | "github.com/sirupsen/logrus" 16 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 17 | "google.golang.org/grpc" 18 | "google.golang.org/grpc/health" 19 | "google.golang.org/grpc/health/grpc_health_v1" 20 | "net" 21 | "net/http" 22 | "os" 23 | "syscall" 24 | ) 25 | 26 | func main() { 27 | tp, err := tracing.SetTraceProvider(config.RecommendRpcServiceName) 28 | 29 | if err != nil { 30 | logging.Logger.WithFields(logrus.Fields{ 31 | "err": err, 32 | }).Panicf("Error to set the trace") 33 | } 34 | defer func() { 35 | if err := tp.Shutdown(context.Background()); err != nil { 36 | logging.Logger.WithFields(logrus.Fields{ 37 | "err": err, 38 | }).Errorf("Error to set the trace") 39 | } 40 | }() 41 | 42 | // Configure Pyroscope 43 | profiling.InitPyroscope("GuGoTik.RecommendService") 44 | 45 | log := logging.LogService(config.RecommendRpcServiceName) 46 | lis, err := net.Listen("tcp", config.EnvCfg.PodIpAddr+config.RecommendRpcServicePort) 47 | 48 | if err != nil { 49 | log.Panicf("Rpc %s listen happens error: %v", config.RecommendRpcServiceName, err) 50 | } 51 | 52 | srvMetrics := grpcprom.NewServerMetrics( 53 | grpcprom.WithServerHandlingTimeHistogram( 54 | grpcprom.WithHistogramBuckets([]float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120}), 55 | ), 56 | ) 57 | 58 | reg := prom.Client 59 | reg.MustRegister(srvMetrics) 60 | 61 | s := grpc.NewServer( 62 | grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), 63 | grpc.ChainUnaryInterceptor(srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 64 | grpc.ChainStreamInterceptor(srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 65 | ) 66 | 67 | if err := consul.RegisterConsul(config.RecommendRpcServiceName, config.RecommendRpcServicePort); err != nil { 68 | log.Panicf("Rpc %s register consul happens error for: %v", config.RecommendRpcServiceName, err) 69 | } 70 | log.Infof("Rpc %s is running at %s now", config.RecommendRpcServiceName, config.RecommendRpcServicePort) 71 | 72 | var srv RecommendServiceImpl 73 | recommend.RegisterRecommendServiceServer(s, srv) 74 | grpc_health_v1.RegisterHealthServer(s, health.NewServer()) 75 | 76 | srv.New() 77 | srvMetrics.InitializeMetrics(s) 78 | 79 | g := &run.Group{} 80 | g.Add(func() error { 81 | return s.Serve(lis) 82 | }, func(err error) { 83 | s.GracefulStop() 84 | s.Stop() 85 | log.Errorf("Rpc %s listen happens error for: %v", config.RecommendRpcServiceName, err) 86 | }) 87 | 88 | httpSrv := &http.Server{Addr: config.EnvCfg.PodIpAddr + config.Metrics} 89 | g.Add(func() error { 90 | m := http.NewServeMux() 91 | m.Handle("/metrics", promhttp.HandlerFor( 92 | reg, 93 | promhttp.HandlerOpts{ 94 | EnableOpenMetrics: true, 95 | }, 96 | )) 97 | httpSrv.Handler = m 98 | log.Infof("Promethus now running") 99 | return httpSrv.ListenAndServe() 100 | }, func(error) { 101 | if err := httpSrv.Close(); err != nil { 102 | log.Errorf("Prometheus %s listen happens error for: %v", config.RecommendRpcServiceName, err) 103 | } 104 | }) 105 | 106 | g.Add(run.SignalHandler(context.Background(), syscall.SIGINT, syscall.SIGTERM)) 107 | 108 | if err := g.Run(); err != nil { 109 | log.WithFields(logrus.Fields{ 110 | "err": err, 111 | }).Errorf("Error when runing http server") 112 | os.Exit(1) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/services/relation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/extra/profiling" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/relation" 8 | "GuGoTik/src/utils/audit" 9 | "GuGoTik/src/utils/consul" 10 | "GuGoTik/src/utils/logging" 11 | "GuGoTik/src/utils/prom" 12 | "context" 13 | grpcprom "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus" 14 | "github.com/oklog/run" 15 | "github.com/prometheus/client_golang/prometheus/promhttp" 16 | amqp "github.com/rabbitmq/amqp091-go" 17 | "github.com/sirupsen/logrus" 18 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 19 | "google.golang.org/grpc" 20 | "google.golang.org/grpc/health" 21 | "google.golang.org/grpc/health/grpc_health_v1" 22 | "net" 23 | "net/http" 24 | "os" 25 | "syscall" 26 | ) 27 | 28 | var conn = &amqp.Connection{} 29 | var channel = &amqp.Channel{} 30 | 31 | func main() { 32 | tp, err := tracing.SetTraceProvider(config.RelationRpcServerName) 33 | 34 | if err != nil { 35 | logging.Logger.WithFields(logrus.Fields{ 36 | "err": err, 37 | }).Panicf("Error to set the trace") 38 | } 39 | defer func() { 40 | if err := tp.Shutdown(context.Background()); err != nil { 41 | logging.Logger.WithFields(logrus.Fields{ 42 | "err": err, 43 | }).Errorf("Error to set the trace") 44 | } 45 | }() 46 | 47 | // Configure Pyroscope 48 | profiling.InitPyroscope("GuGoTik.RelationService") 49 | 50 | log := logging.LogService(config.RelationRpcServerName) 51 | lis, err := net.Listen("tcp", config.EnvCfg.PodIpAddr+config.RelationRpcServerPort) 52 | 53 | if err != nil { 54 | log.Panicf("Rpc %s listen happens error: %v", config.RelationRpcServerName, err) 55 | } 56 | 57 | srvMetrics := grpcprom.NewServerMetrics( 58 | grpcprom.WithServerHandlingTimeHistogram( 59 | grpcprom.WithHistogramBuckets([]float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120}), 60 | ), 61 | ) 62 | 63 | reg := prom.Client 64 | reg.MustRegister(srvMetrics) 65 | 66 | s := grpc.NewServer( 67 | grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), 68 | grpc.ChainUnaryInterceptor(srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 69 | grpc.ChainStreamInterceptor(srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(prom.ExtractContext))), 70 | ) 71 | 72 | if err := consul.RegisterConsul(config.RelationRpcServerName, config.RelationRpcServerPort); err != nil { 73 | log.Panicf("Rpc %s register consul happens error for: %v", config.RelationRpcServerName, err) 74 | } 75 | log.Infof("Rpc %s is running at %s now", config.RelationRpcServerName, config.RelationRpcServerPort) 76 | 77 | var srv RelationServiceImpl 78 | relation.RegisterRelationServiceServer(s, srv) 79 | grpc_health_v1.RegisterHealthServer(s, health.NewServer()) 80 | 81 | srv.New() 82 | 83 | // Initialize the audit_exchange 84 | audit.DeclareAuditExchange(channel) 85 | defer CloseMQConn() 86 | 87 | srvMetrics.InitializeMetrics(s) 88 | 89 | g := &run.Group{} 90 | g.Add(func() error { 91 | return s.Serve(lis) 92 | }, func(err error) { 93 | s.GracefulStop() 94 | s.Stop() 95 | log.Errorf("Rpc %s listen happens error for: %v", config.RelationRpcServerName, err) 96 | }) 97 | 98 | httpSrv := &http.Server{Addr: config.EnvCfg.PodIpAddr + config.Metrics} 99 | g.Add(func() error { 100 | m := http.NewServeMux() 101 | m.Handle("/metrics", promhttp.HandlerFor( 102 | reg, 103 | promhttp.HandlerOpts{ 104 | EnableOpenMetrics: true, 105 | }, 106 | )) 107 | httpSrv.Handler = m 108 | log.Infof("Promethus now running") 109 | return httpSrv.ListenAndServe() 110 | }, func(error) { 111 | if err := httpSrv.Close(); err != nil { 112 | log.Errorf("Prometheus %s listen happens error for: %v", config.RelationRpcServerName, err) 113 | } 114 | }) 115 | 116 | g.Add(run.SignalHandler(context.Background(), syscall.SIGINT, syscall.SIGTERM)) 117 | 118 | if err := g.Run(); err != nil { 119 | log.WithFields(logrus.Fields{ 120 | "err": err, 121 | }).Errorf("Error when runing http server") 122 | os.Exit(1) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/storage/cached/ticker.go: -------------------------------------------------------------------------------- 1 | package cached 2 | 3 | import ( 4 | "GuGoTik/src/storage/redis" 5 | "GuGoTik/src/utils/logging" 6 | redis2 "github.com/redis/go-redis/v9" 7 | "github.com/sirupsen/logrus" 8 | "time" 9 | ) 10 | 11 | type TimeTicker struct { 12 | Tick *time.Ticker 13 | Work func(client redis2.UniversalClient) error 14 | } 15 | 16 | func (t *TimeTicker) Start() { 17 | for range t.Tick.C { 18 | err := t.Work(redis.Client) 19 | if err != nil { 20 | logging.Logger.WithFields(logrus.Fields{ 21 | "err": err, 22 | }).Errorf("Error happens when dealing with cron job") 23 | continue 24 | } 25 | } 26 | } 27 | 28 | func NewTick(interval time.Duration, f func(client redis2.UniversalClient) error) *TimeTicker { 29 | return &TimeTicker{ 30 | Tick: time.NewTicker(interval), 31 | Work: f, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/storage/database/gorm.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/utils/logging" 6 | "fmt" 7 | "gorm.io/driver/postgres" 8 | "gorm.io/gorm" 9 | "gorm.io/gorm/schema" 10 | "gorm.io/plugin/dbresolver" 11 | "gorm.io/plugin/opentelemetry/tracing" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | var Client *gorm.DB 17 | 18 | func init() { 19 | var err error 20 | 21 | gormLogrus := logging.GetGormLogger() 22 | 23 | var cfg gorm.Config 24 | if config.EnvCfg.PostgreSQLSchema == "" { 25 | cfg = gorm.Config{ 26 | PrepareStmt: true, 27 | Logger: gormLogrus, 28 | NamingStrategy: schema.NamingStrategy{ 29 | TablePrefix: config.EnvCfg.PostgreSQLSchema + "." + config.EnvCfg.PostgreSQLPrefix, 30 | }, 31 | } 32 | } else { 33 | cfg = gorm.Config{ 34 | PrepareStmt: true, 35 | Logger: gormLogrus, 36 | NamingStrategy: schema.NamingStrategy{ 37 | TablePrefix: config.EnvCfg.PostgreSQLSchema + "." + config.EnvCfg.PostgreSQLPrefix, 38 | }, 39 | } 40 | } 41 | 42 | if Client, err = gorm.Open( 43 | postgres.Open( 44 | fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s", 45 | config.EnvCfg.PostgreSQLHost, 46 | config.EnvCfg.PostgreSQLUser, 47 | config.EnvCfg.PostgreSQLPassword, 48 | config.EnvCfg.PostgreSQLDataBase, 49 | config.EnvCfg.PostgreSQLPort)), 50 | &cfg, 51 | ); err != nil { 52 | panic(err) 53 | } 54 | 55 | if config.EnvCfg.PostgreSQLReplicaState == "enable" { 56 | var replicas []gorm.Dialector 57 | for _, addr := range strings.Split(config.EnvCfg.PostgreSQLReplicaAddress, ",") { 58 | pair := strings.Split(addr, ":") 59 | if len(pair) != 2 { 60 | continue 61 | } 62 | 63 | replicas = append(replicas, postgres.Open( 64 | fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s", 65 | pair[0], 66 | config.EnvCfg.PostgreSQLReplicaUsername, 67 | config.EnvCfg.PostgreSQLReplicaPassword, 68 | config.EnvCfg.PostgreSQLDataBase, 69 | pair[1]))) 70 | } 71 | 72 | err := Client.Use(dbresolver.Register(dbresolver.Config{ 73 | Replicas: replicas, 74 | Policy: dbresolver.RandomPolicy{}, 75 | })) 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | 81 | sqlDB, err := Client.DB() 82 | if err != nil { 83 | panic(err) 84 | } 85 | 86 | sqlDB.SetMaxIdleConns(100) 87 | sqlDB.SetMaxOpenConns(200) 88 | sqlDB.SetConnMaxLifetime(24 * time.Hour) 89 | sqlDB.SetConnMaxIdleTime(time.Hour) 90 | 91 | if err := Client.Use(tracing.NewPlugin()); err != nil { 92 | panic(err) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/storage/es/Elasticsearch.go: -------------------------------------------------------------------------------- 1 | package es 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "log" 6 | 7 | es "github.com/elastic/go-elasticsearch/v7" 8 | ) 9 | 10 | var EsClient *es.Client 11 | 12 | func init() { 13 | cfg := es.Config{ 14 | Addresses: []string{ 15 | config.EnvCfg.ElasticsearchUrl, 16 | }, 17 | } 18 | var err error 19 | EsClient, err = es.NewClient(cfg) 20 | if err != nil { 21 | log.Fatalf("elasticsearch.NewClient: %v", err) 22 | } 23 | 24 | _, err = EsClient.Info() 25 | if err != nil { 26 | log.Fatalf("Error getting response: %s", err) 27 | } 28 | 29 | _, err = EsClient.API.Indices.Create("Message") 30 | 31 | if err != nil { 32 | log.Fatalf("create index error: %s", err) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/storage/file/fs.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/extra/tracing" 6 | "GuGoTik/src/utils/logging" 7 | "context" 8 | "github.com/sirupsen/logrus" 9 | "io" 10 | "net/url" 11 | "os" 12 | "path" 13 | ) 14 | 15 | type FSStorage struct { 16 | } 17 | 18 | func (f FSStorage) GetLocalPath(ctx context.Context, fileName string) string { 19 | _, span := tracing.Tracer.Start(ctx, "FSStorage-GetLocalPath") 20 | defer span.End() 21 | logging.SetSpanWithHostname(span) 22 | return path.Join(config.EnvCfg.FileSystemStartPath, fileName) 23 | } 24 | 25 | func (f FSStorage) Upload(ctx context.Context, fileName string, content io.Reader) (output *PutObjectOutput, err error) { 26 | ctx, span := tracing.Tracer.Start(ctx, "FSStorage-Upload") 27 | defer span.End() 28 | logging.SetSpanWithHostname(span) 29 | logger := logging.LogService("FSStorage.Upload").WithContext(ctx) 30 | 31 | logger = logger.WithFields(logrus.Fields{ 32 | "file_name": fileName, 33 | }) 34 | logger.Debugf("Process start") 35 | 36 | all, err := io.ReadAll(content) 37 | if err != nil { 38 | logger.WithFields(logrus.Fields{ 39 | "err": err, 40 | }).Debug("Failed reading content") 41 | return nil, err 42 | } 43 | 44 | filePath := path.Join(config.EnvCfg.FileSystemStartPath, fileName) 45 | 46 | dir := path.Dir(filePath) 47 | err = os.MkdirAll(dir, os.FileMode(0755)) 48 | 49 | if err != nil { 50 | logger.WithFields(logrus.Fields{ 51 | "err": err, 52 | }).Debug("Failed creating directory before writing file") 53 | return nil, err 54 | } 55 | 56 | err = os.WriteFile(filePath, all, os.FileMode(0755)) 57 | 58 | if err != nil { 59 | logger.WithFields(logrus.Fields{ 60 | "err": err, 61 | }).Debug("Failed writing content to file") 62 | return nil, err 63 | } 64 | 65 | return &PutObjectOutput{}, nil 66 | } 67 | 68 | func (f FSStorage) GetLink(ctx context.Context, fileName string) (string, error) { 69 | _, span := tracing.Tracer.Start(ctx, "FSStorage-GetLink") 70 | defer span.End() 71 | logging.SetSpanWithHostname(span) 72 | return url.JoinPath(config.EnvCfg.FileSystemBaseUrl, fileName) 73 | } 74 | 75 | func (f FSStorage) IsFileExist(ctx context.Context, fileName string) (bool, error) { 76 | _, span := tracing.Tracer.Start(ctx, "FSStorage-IsFileExist") 77 | defer span.End() 78 | logging.SetSpanWithHostname(span) 79 | filePath := path.Join(config.EnvCfg.FileSystemStartPath, fileName) 80 | _, err := os.Stat(filePath) 81 | if err == nil { 82 | return true, nil 83 | } 84 | if os.IsNotExist(err) { 85 | return false, nil 86 | } 87 | 88 | return false, err 89 | } 90 | -------------------------------------------------------------------------------- /src/storage/file/provider.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "context" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | var client storageProvider 11 | 12 | type storageProvider interface { 13 | Upload(ctx context.Context, fileName string, content io.Reader) (*PutObjectOutput, error) 14 | GetLink(ctx context.Context, fileName string) (string, error) 15 | GetLocalPath(ctx context.Context, fileName string) string 16 | IsFileExist(ctx context.Context, fileName string) (bool, error) 17 | } 18 | 19 | type PutObjectOutput struct{} 20 | 21 | func init() { 22 | switch config.EnvCfg.StorageType { // Append more type here to provide more file action ability 23 | case "fs": 24 | client = FSStorage{} 25 | } 26 | } 27 | 28 | func Upload(ctx context.Context, fileName string, content io.Reader) (*PutObjectOutput, error) { 29 | return client.Upload(ctx, fileName, content) 30 | } 31 | 32 | func GetLocalPath(ctx context.Context, fileName string) string { 33 | return client.GetLocalPath(ctx, fileName) 34 | } 35 | 36 | func GetLink(ctx context.Context, fileName string, userId uint32) (link string, err error) { 37 | originLink, err := client.GetLink(ctx, fileName) 38 | link = fmt.Sprintf("%s?user_id=%d", originLink, userId) 39 | return 40 | } 41 | 42 | func IsFileExist(ctx context.Context, fileName string) (bool, error) { 43 | return client.IsFileExist(ctx, fileName) 44 | } 45 | -------------------------------------------------------------------------------- /src/storage/mq/rabbitmq.go: -------------------------------------------------------------------------------- 1 | package mq 2 | -------------------------------------------------------------------------------- /src/storage/redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "github.com/redis/go-redis/extra/redisotel/v9" 6 | "github.com/redis/go-redis/v9" 7 | "strings" 8 | ) 9 | 10 | var Client redis.UniversalClient 11 | 12 | func init() { 13 | addrs := strings.Split(config.EnvCfg.RedisAddr, ";") 14 | Client = redis.NewUniversalClient(&redis.UniversalOptions{ 15 | Addrs: addrs, 16 | Password: config.EnvCfg.RedisPassword, 17 | DB: config.EnvCfg.RedisDB, 18 | MasterName: config.EnvCfg.RedisMaster, 19 | }) 20 | 21 | if err := redisotel.InstrumentTracing(Client); err != nil { 22 | panic(err) 23 | } 24 | 25 | if err := redisotel.InstrumentMetrics(Client); err != nil { 26 | panic(err) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/audit/publish.go: -------------------------------------------------------------------------------- 1 | package audit 2 | 3 | import ( 4 | "GuGoTik/src/constant/strings" 5 | "GuGoTik/src/extra/tracing" 6 | models2 "GuGoTik/src/models" 7 | "GuGoTik/src/utils/logging" 8 | "GuGoTik/src/utils/rabbitmq" 9 | "context" 10 | "encoding/json" 11 | amqp "github.com/rabbitmq/amqp091-go" 12 | "github.com/sirupsen/logrus" 13 | ) 14 | 15 | func exitOnError(err error) { 16 | if err != nil { 17 | panic(err) 18 | } 19 | } 20 | 21 | func DeclareAuditExchange(channel *amqp.Channel) { 22 | err := channel.ExchangeDeclare( 23 | strings.AuditExchange, 24 | "direct", 25 | true, 26 | false, 27 | false, 28 | false, 29 | nil, 30 | ) 31 | exitOnError(err) 32 | } 33 | 34 | func PublishAuditEvent(ctx context.Context, action *models2.Action, channel *amqp.Channel) { 35 | ctx, span := tracing.Tracer.Start(ctx, "AuditEventPublisher") 36 | defer span.End() 37 | logging.SetSpanWithHostname(span) 38 | logger := logging.LogService("AuditEventPublisher").WithContext(ctx) 39 | 40 | data, err := json.Marshal(action) 41 | if err != nil { 42 | logger.WithFields(logrus.Fields{ 43 | "err": err, 44 | }).Errorf("Error when marshal the action model") 45 | logging.SetSpanError(span, err) 46 | return 47 | } 48 | 49 | headers := rabbitmq.InjectAMQPHeaders(ctx) 50 | 51 | err = channel.PublishWithContext(ctx, 52 | strings.AuditExchange, 53 | strings.AuditPublishEvent, 54 | false, 55 | false, 56 | amqp.Publishing{ 57 | ContentType: "text/plain", 58 | Body: data, 59 | Headers: headers, 60 | }, 61 | ) 62 | 63 | if err != nil { 64 | logger.WithFields(logrus.Fields{ 65 | "err": err, 66 | }).Errorf("Error when publishing the action model") 67 | logging.SetSpanError(span, err) 68 | return 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/utils/consul/register.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/utils/logging" 6 | "fmt" 7 | "github.com/google/uuid" 8 | capi "github.com/hashicorp/consul/api" 9 | log "github.com/sirupsen/logrus" 10 | "strconv" 11 | ) 12 | 13 | var consulClient *capi.Client 14 | 15 | func init() { 16 | cfg := capi.DefaultConfig() 17 | cfg.Address = config.EnvCfg.ConsulAddr 18 | if c, err := capi.NewClient(cfg); err == nil { 19 | consulClient = c 20 | return 21 | } else { 22 | logging.Logger.Panicf("Connect Consul happens error: %v", err) 23 | } 24 | } 25 | 26 | func RegisterConsul(name string, port string) error { 27 | parsedPort, err := strconv.Atoi(port[1:]) // port start with ':' which like ':37001' 28 | logging.Logger.WithFields(log.Fields{ 29 | "name": name, 30 | "port": parsedPort, 31 | }).Infof("Services Register Consul") 32 | name = config.EnvCfg.ConsulAnonymityPrefix + name 33 | 34 | if err != nil { 35 | return err 36 | } 37 | reg := &capi.AgentServiceRegistration{ 38 | ID: fmt.Sprintf("%s-%s", name, uuid.New().String()[:5]), 39 | Name: name, 40 | Address: config.EnvCfg.PodIpAddr, 41 | Port: parsedPort, 42 | Check: &capi.AgentServiceCheck{ 43 | Interval: "5s", 44 | Timeout: "5s", 45 | GRPC: fmt.Sprintf("%s:%d", config.EnvCfg.PodIpAddr, parsedPort), 46 | GRPCUseTLS: false, 47 | DeregisterCriticalServiceAfter: "30s", 48 | }, 49 | } 50 | if err := consulClient.Agent().ServiceRegister(reg); err != nil { 51 | return err 52 | } 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/grpc/connection.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/utils/logging" 6 | "fmt" 7 | _ "github.com/mbobakov/grpc-consul-resolver" 8 | "github.com/sirupsen/logrus" 9 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/credentials/insecure" 12 | "google.golang.org/grpc/keepalive" 13 | "time" 14 | ) 15 | 16 | func Connect(serviceName string) (conn *grpc.ClientConn) { 17 | kacp := keepalive.ClientParameters{ 18 | Time: 10 * time.Second, // send pings every 10 seconds if there is no activity 19 | Timeout: time.Second, // wait 1 second for ping ack before considering the connection dead 20 | PermitWithoutStream: false, // send pings even without active streams 21 | } 22 | 23 | conn, err := grpc.Dial( 24 | fmt.Sprintf("consul://%s/%s?wait=15s", config.EnvCfg.ConsulAddr, config.EnvCfg.ConsulAnonymityPrefix+serviceName), 25 | grpc.WithTransportCredentials(insecure.NewCredentials()), 26 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), 27 | grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), 28 | grpc.WithKeepaliveParams(kacp), 29 | ) 30 | 31 | logging.Logger.Debugf("connect") 32 | 33 | if err != nil { 34 | logging.Logger.WithFields(logrus.Fields{ 35 | "service": config.EnvCfg.ConsulAnonymityPrefix + serviceName, 36 | "err": err, 37 | }).Errorf("Cannot connect to %v service", config.EnvCfg.ConsulAnonymityPrefix+serviceName) 38 | } 39 | return 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/logging/gorm.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/sirupsen/logrus" 7 | "gorm.io/gorm/logger" 8 | "gorm.io/gorm/utils" 9 | "time" 10 | ) 11 | 12 | var errRecordNotFound = errors.New("record not found") 13 | 14 | type GormLogger struct{} 15 | 16 | func (g GormLogger) LogMode(_ logger.LogLevel) logger.Interface { 17 | // We do not use this because Gorm will print different log according to log set. 18 | // However, we just print to TRACE. 19 | return g 20 | } 21 | 22 | func (g GormLogger) Info(ctx context.Context, s string, i ...interface{}) { 23 | Logger.WithContext(ctx).WithFields(logrus.Fields{ 24 | "component": "gorm", 25 | }).Infof(s, i...) 26 | } 27 | 28 | func (g GormLogger) Warn(ctx context.Context, s string, i ...interface{}) { 29 | Logger.WithContext(ctx).WithFields(logrus.Fields{ 30 | "component": "gorm", 31 | }).Warnf(s, i...) 32 | } 33 | 34 | func (g GormLogger) Error(ctx context.Context, s string, i ...interface{}) { 35 | Logger.WithContext(ctx).WithFields(logrus.Fields{ 36 | "component": "gorm", 37 | }).Errorf(s, i...) 38 | } 39 | 40 | func (g GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) { 41 | const traceStr = "File: %s, Cost: %v, Rows: %v, SQL: %s" 42 | elapsed := time.Since(begin) 43 | sql, rows := fc() 44 | fields := logrus.Fields{ 45 | "component": "gorm", 46 | } 47 | if err != nil && !errors.Is(err, errRecordNotFound) { 48 | fields = logrus.Fields{ 49 | "err": err, 50 | } 51 | } 52 | 53 | if rows == -1 { 54 | Logger.WithContext(ctx).WithFields(fields).Tracef(traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql) 55 | } else { 56 | Logger.WithContext(ctx).WithFields(fields).Tracef(traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql) 57 | } 58 | } 59 | 60 | func GetGormLogger() *GormLogger { 61 | return &GormLogger{} 62 | } 63 | -------------------------------------------------------------------------------- /src/utils/logging/logging.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "fmt" 6 | log "github.com/sirupsen/logrus" 7 | "go.opentelemetry.io/otel/attribute" 8 | "go.opentelemetry.io/otel/codes" 9 | "go.opentelemetry.io/otel/trace" 10 | "io" 11 | "os" 12 | "path" 13 | ) 14 | 15 | var hostname string 16 | 17 | func init() { 18 | hostname, _ = os.Hostname() 19 | 20 | switch config.EnvCfg.LoggerLevel { 21 | case "DEBUG": 22 | log.SetLevel(log.DebugLevel) 23 | case "INFO": 24 | log.SetLevel(log.InfoLevel) 25 | case "WARN", "WARNING": 26 | log.SetLevel(log.WarnLevel) 27 | case "ERROR": 28 | log.SetLevel(log.ErrorLevel) 29 | case "FATAL": 30 | log.SetLevel(log.FatalLevel) 31 | case "TRACE": 32 | log.SetLevel(log.TraceLevel) 33 | } 34 | 35 | filePath := path.Join("/var", "log", "gugotik", "gugotik.log") 36 | dir := path.Dir(filePath) 37 | if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil { 38 | panic(err) 39 | } 40 | 41 | f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | log.SetFormatter(&log.JSONFormatter{}) 47 | log.AddHook(logTraceHook{}) 48 | log.SetOutput(io.MultiWriter(f, os.Stdout)) 49 | 50 | Logger = log.WithFields(log.Fields{ 51 | "Tied": config.EnvCfg.TiedLogging, 52 | "Hostname": hostname, 53 | "PodIP": config.EnvCfg.PodIpAddr, 54 | }) 55 | } 56 | 57 | type logTraceHook struct{} 58 | 59 | func (t logTraceHook) Levels() []log.Level { return log.AllLevels } 60 | 61 | func (t logTraceHook) Fire(entry *log.Entry) error { 62 | ctx := entry.Context 63 | if ctx == nil { 64 | return nil 65 | } 66 | 67 | span := trace.SpanFromContext(ctx) 68 | //if !span.IsRecording() { 69 | // return nil 70 | //} 71 | 72 | sCtx := span.SpanContext() 73 | if sCtx.HasTraceID() { 74 | entry.Data["trace_id"] = sCtx.TraceID().String() 75 | } 76 | if sCtx.HasSpanID() { 77 | entry.Data["span_id"] = sCtx.SpanID().String() 78 | } 79 | 80 | if config.EnvCfg.LoggerWithTraceState == "enable" { 81 | attrs := make([]attribute.KeyValue, 0) 82 | logSeverityKey := attribute.Key("log.severity") 83 | logMessageKey := attribute.Key("log.message") 84 | attrs = append(attrs, logSeverityKey.String(entry.Level.String())) 85 | attrs = append(attrs, logMessageKey.String(entry.Message)) 86 | for key, value := range entry.Data { 87 | fields := attribute.Key(fmt.Sprintf("log.fields.%s", key)) 88 | attrs = append(attrs, fields.String(fmt.Sprintf("%v", value))) 89 | } 90 | span.AddEvent("log", trace.WithAttributes(attrs...)) 91 | if entry.Level <= log.ErrorLevel { 92 | span.SetStatus(codes.Error, entry.Message) 93 | } 94 | } 95 | return nil 96 | } 97 | 98 | var Logger *log.Entry 99 | 100 | func LogService(name string) *log.Entry { 101 | return Logger.WithFields(log.Fields{ 102 | "Service": name, 103 | }) 104 | } 105 | 106 | func SetSpanError(span trace.Span, err error) { 107 | span.RecordError(err) 108 | span.SetStatus(codes.Error, "Internal Error") 109 | } 110 | 111 | func SetSpanErrorWithDesc(span trace.Span, err error, desc string) { 112 | span.RecordError(err) 113 | span.SetStatus(codes.Error, desc) 114 | } 115 | 116 | func SetSpanWithHostname(span trace.Span) { 117 | span.SetAttributes(attribute.String("hostname", hostname)) 118 | span.SetAttributes(attribute.String("podIP", config.EnvCfg.PodIpAddr)) 119 | } 120 | -------------------------------------------------------------------------------- /src/utils/pathgen/video.go: -------------------------------------------------------------------------------- 1 | package pathgen 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "strconv" 7 | ) 8 | 9 | // GenerateRawVideoName 生成初始视频名称,此链接仅用于内部使用,暴露给用户的视频名称 10 | func GenerateRawVideoName(actorId uint32, title string, videoId uint32) string { 11 | hash := sha256.Sum256([]byte("RAW" + strconv.FormatUint(uint64(actorId), 10) + title + strconv.FormatUint(uint64(videoId), 10))) 12 | return hex.EncodeToString(hash[:]) + ".mp4" 13 | } 14 | 15 | // GenerateFinalVideoName 最终暴露给用户的视频名称 16 | func GenerateFinalVideoName(actorId uint32, title string, videoId uint32) string { 17 | hash := sha256.Sum256([]byte(strconv.FormatUint(uint64(actorId), 10) + title + strconv.FormatUint(uint64(videoId), 10))) 18 | return hex.EncodeToString(hash[:]) + ".mp4" 19 | } 20 | 21 | // GenerateCoverName 生成视频封面名称 22 | func GenerateCoverName(actorId uint32, title string, videoId uint32) string { 23 | hash := sha256.Sum256([]byte(strconv.FormatUint(uint64(actorId), 10) + title + strconv.FormatUint(uint64(videoId), 10))) 24 | return hex.EncodeToString(hash[:]) + ".png" 25 | } 26 | 27 | // GenerateAudioName 生成音频链接,此链接仅用于内部使用,不暴露给用户 28 | func GenerateAudioName(videoFileName string) string { 29 | hash := sha256.Sum256([]byte("AUDIO_" + videoFileName)) 30 | return hex.EncodeToString(hash[:]) + ".mp3" 31 | } 32 | 33 | // GenerateNameWatermark 生成用户名水印图片 34 | func GenerateNameWatermark(actorId uint32, Name string) string { 35 | hash := sha256.Sum256([]byte("Watermark" + strconv.FormatUint(uint64(actorId), 10) + Name)) 36 | return hex.EncodeToString(hash[:]) + ".png" 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/prom/interceptor.go: -------------------------------------------------------------------------------- 1 | package prom 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "context" 6 | "github.com/prometheus/client_golang/prometheus" 7 | "go.opentelemetry.io/otel/trace" 8 | ) 9 | 10 | func ExtractContext(ctx context.Context) prometheus.Labels { 11 | if span := trace.SpanContextFromContext(ctx); span.IsSampled() { 12 | return prometheus.Labels{ 13 | "traceID": span.TraceID().String(), 14 | "spanID": span.SpanID().String(), 15 | "podId": config.EnvCfg.PodIpAddr, 16 | } 17 | } 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/prom/pack.go: -------------------------------------------------------------------------------- 1 | package prom 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | var Client = prometheus.NewRegistry() 6 | -------------------------------------------------------------------------------- /src/utils/ptr/ptr.go: -------------------------------------------------------------------------------- 1 | package ptr 2 | 3 | func Ptr[T any](a T) *T { 4 | return &a 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/rabbitmq/mq.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "fmt" 6 | ) 7 | 8 | func BuildMQConnAddr() string { 9 | return fmt.Sprintf("amqp://%s:%s@%s:%s/%s", config.EnvCfg.RabbitMQUsername, config.EnvCfg.RabbitMQPassword, 10 | config.EnvCfg.RabbitMQAddr, config.EnvCfg.RabbitMQPort, config.EnvCfg.RabbitMQVhostPrefix) 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/rabbitmq/otel.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | import ( 4 | "context" 5 | "go.opentelemetry.io/otel" 6 | ) 7 | 8 | type AmqpHeadersCarrier map[string]interface{} 9 | 10 | func (a AmqpHeadersCarrier) Get(key string) string { 11 | v, ok := a[key] 12 | if !ok { 13 | return "" 14 | } 15 | return v.(string) 16 | } 17 | 18 | func (a AmqpHeadersCarrier) Set(key string, value string) { 19 | a[key] = value 20 | } 21 | 22 | func (a AmqpHeadersCarrier) Keys() []string { 23 | i := 0 24 | r := make([]string, len(a)) 25 | 26 | for k := range a { 27 | r[i] = k 28 | i++ 29 | } 30 | 31 | return r 32 | } 33 | 34 | func InjectAMQPHeaders(ctx context.Context) map[string]interface{} { 35 | h := make(AmqpHeadersCarrier) 36 | otel.GetTextMapPropagator().Inject(ctx, h) 37 | return h 38 | } 39 | 40 | func ExtractAMQPHeaders(ctx context.Context, headers map[string]interface{}) context.Context { 41 | return otel.GetTextMapPropagator().Extract(ctx, AmqpHeadersCarrier(headers)) 42 | } 43 | -------------------------------------------------------------------------------- /src/web/about/handler.go: -------------------------------------------------------------------------------- 1 | package about 2 | 3 | import ( 4 | "GuGoTik/src/constant/strings" 5 | "GuGoTik/src/extra/tracing" 6 | "GuGoTik/src/utils/logging" 7 | "GuGoTik/src/web/models" 8 | "github.com/gin-gonic/gin" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | func Handle(c *gin.Context) { 14 | _, span := tracing.Tracer.Start(c.Request.Context(), "AboutHandler") 15 | defer span.End() 16 | logging.SetSpanWithHostname(span) 17 | 18 | var req models.AboutReq 19 | if err := c.ShouldBind(&req); err != nil { 20 | c.JSON(http.StatusBadRequest, gin.H{ 21 | "status_code": strings.GateWayErrorCode, 22 | "status_msg": strings.GateWayError, 23 | }) 24 | } 25 | res := models.AboutRes{ 26 | Echo: req.Echo, 27 | TimeStamp: time.Now().Unix(), 28 | } 29 | c.JSON(http.StatusOK, res) 30 | } 31 | -------------------------------------------------------------------------------- /src/web/auth/handler.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/constant/strings" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/auth" 8 | grpc2 "GuGoTik/src/utils/grpc" 9 | "GuGoTik/src/utils/logging" 10 | "GuGoTik/src/web/models" 11 | "GuGoTik/src/web/utils" 12 | "github.com/gin-gonic/gin" 13 | _ "github.com/mbobakov/grpc-consul-resolver" 14 | "github.com/sirupsen/logrus" 15 | "net/http" 16 | ) 17 | 18 | var Client auth.AuthServiceClient 19 | 20 | func LoginHandle(c *gin.Context) { 21 | var req models.LoginReq 22 | _, span := tracing.Tracer.Start(c.Request.Context(), "LoginHandler") 23 | defer span.End() 24 | logging.SetSpanWithHostname(span) 25 | logger := logging.LogService("GateWay.Login").WithContext(c.Request.Context()) 26 | 27 | if err := c.ShouldBindQuery(&req); err != nil { 28 | c.JSON(http.StatusOK, models.LoginRes{ 29 | StatusCode: strings.GateWayParamsErrorCode, 30 | StatusMsg: strings.GateWayParamsError, 31 | UserId: 0, 32 | Token: "", 33 | }) 34 | return 35 | } 36 | 37 | res, err := Client.Login(c.Request.Context(), &auth.LoginRequest{ 38 | Username: req.UserName, 39 | Password: req.Password, 40 | }) 41 | if err != nil { 42 | logger.WithFields(logrus.Fields{ 43 | "Username": req.UserName, 44 | }).Warnf("Error when trying to connect with AuthService") 45 | c.Render(http.StatusOK, utils.CustomJSON{Data: res, Context: c}) 46 | return 47 | } 48 | 49 | logger.WithFields(logrus.Fields{ 50 | "Username": req.UserName, 51 | "Token": res.Token, 52 | "UserId": res.UserId, 53 | }).Infof("User log in") 54 | 55 | c.Render(http.StatusOK, utils.CustomJSON{Data: res, Context: c}) 56 | } 57 | 58 | func RegisterHandle(c *gin.Context) { 59 | var req models.RegisterReq 60 | _, span := tracing.Tracer.Start(c.Request.Context(), "LoginHandler") 61 | defer span.End() 62 | logger := logging.LogService("GateWay.Register").WithContext(c.Request.Context()) 63 | 64 | if err := c.ShouldBindQuery(&req); err != nil { 65 | c.JSON(http.StatusOK, models.RegisterRes{ 66 | StatusCode: strings.GateWayParamsErrorCode, 67 | StatusMsg: strings.GateWayParamsError, 68 | UserId: 0, 69 | Token: "", 70 | }) 71 | return 72 | } 73 | 74 | res, err := Client.Register(c.Request.Context(), &auth.RegisterRequest{ 75 | Username: req.UserName, 76 | Password: req.Password, 77 | }) 78 | 79 | if err != nil { 80 | logger.WithFields(logrus.Fields{ 81 | "Username": req.UserName, 82 | }).Warnf("Error when trying to connect with AuthService") 83 | c.Render(http.StatusOK, utils.CustomJSON{Data: res, Context: c}) 84 | return 85 | } 86 | 87 | logger.WithFields(logrus.Fields{ 88 | "Username": req.UserName, 89 | "Token": res.Token, 90 | "UserId": res.UserId, 91 | }).Infof("User register in") 92 | 93 | c.Render(http.StatusOK, utils.CustomJSON{Data: res, Context: c}) 94 | } 95 | 96 | func init() { 97 | conn := grpc2.Connect(config.AuthRpcServerName) 98 | Client = auth.NewAuthServiceClient(conn) 99 | } 100 | -------------------------------------------------------------------------------- /src/web/favorite/handler.go: -------------------------------------------------------------------------------- 1 | package favorite 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/constant/strings" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/favorite" 8 | grpc2 "GuGoTik/src/utils/grpc" 9 | "GuGoTik/src/utils/logging" 10 | "GuGoTik/src/web/models" 11 | "GuGoTik/src/web/utils" 12 | "github.com/gin-gonic/gin" 13 | "github.com/sirupsen/logrus" 14 | "net/http" 15 | ) 16 | 17 | var Client favorite.FavoriteServiceClient 18 | 19 | func init() { 20 | conn := grpc2.Connect(config.FavoriteRpcServerName) 21 | Client = favorite.NewFavoriteServiceClient(conn) 22 | } 23 | 24 | func ActionFavoriteHandler(c *gin.Context) { 25 | var req models.ActionFavoriteReq 26 | _, span := tracing.Tracer.Start(c.Request.Context(), "ActionFavoriteHandler") 27 | defer span.End() 28 | logging.SetSpanWithHostname(span) 29 | logger := logging.LogService("GateWay.ActionFavorite").WithContext(c.Request.Context()) 30 | 31 | if err := c.ShouldBindQuery(&req); err != nil { 32 | c.JSON(http.StatusOK, models.ActionCommentRes{ 33 | StatusCode: strings.GateWayParamsErrorCode, 34 | StatusMsg: strings.GateWayParamsError, 35 | }) 36 | return 37 | } 38 | 39 | actionType := uint32(req.ActionType) 40 | if actionType != uint32(1) && actionType != uint32(2) { 41 | c.JSON(http.StatusOK, models.ActionCommentRes{ 42 | StatusCode: strings.GateWayParamsErrorCode, 43 | StatusMsg: strings.GateWayParamsError, 44 | }) 45 | return 46 | } 47 | res, err := Client.FavoriteAction(c.Request.Context(), &favorite.FavoriteRequest{ 48 | ActorId: uint32(req.ActorId), 49 | VideoId: uint32(req.VideoId), 50 | ActionType: actionType, 51 | }) 52 | 53 | if err != nil { 54 | logger.WithFields(logrus.Fields{ 55 | "ActorId": req.ActorId, 56 | "VideoId": req.VideoId, 57 | "ActionType": req.ActionType, 58 | }).Warnf("Error when trying to connect with ActionFavoriteService") 59 | c.Render(http.StatusOK, utils.CustomJSON{Data: res, Context: c}) 60 | return 61 | } 62 | 63 | logger.WithFields(logrus.Fields{ 64 | "ActorId": req.ActorId, 65 | "VideoId": req.VideoId, 66 | "ActionType": req.ActionType, 67 | }).Infof("Action favorite success") 68 | 69 | c.Render(http.StatusOK, utils.CustomJSON{Data: res, Context: c}) 70 | } 71 | 72 | func ListFavoriteHandler(c *gin.Context) { 73 | var req models.ListFavoriteReq 74 | _, span := tracing.Tracer.Start(c.Request.Context(), "ListFavoriteHandler") 75 | defer span.End() 76 | logger := logging.LogService("GateWay.ListFavorite").WithContext(c.Request.Context()) 77 | 78 | if err := c.ShouldBindQuery(&req); err != nil { 79 | c.JSON(http.StatusOK, models.ListCommentRes{ 80 | StatusCode: strings.GateWayParamsErrorCode, 81 | StatusMsg: strings.GateWayParamsError, 82 | }) 83 | return 84 | } 85 | 86 | res, err := Client.FavoriteList(c.Request.Context(), &favorite.FavoriteListRequest{ 87 | ActorId: uint32(req.ActorId), 88 | UserId: uint32(req.UserId), 89 | }) 90 | if err != nil { 91 | logger.WithFields(logrus.Fields{ 92 | "ActorId": req.ActorId, 93 | "UserId": req.UserId, 94 | }).Warnf("Error when trying to connect with ListFavoriteHandler") 95 | c.Render(http.StatusOK, utils.CustomJSON{Data: res, Context: c}) 96 | return 97 | } 98 | 99 | logger.WithFields(logrus.Fields{ 100 | "ActorId": req.ActorId, 101 | "UserId": req.UserId, 102 | }).Infof("List favorite videos success") 103 | 104 | c.Render(http.StatusOK, utils.CustomJSON{Data: res, Context: c}) 105 | } 106 | -------------------------------------------------------------------------------- /src/web/feed/handler.go: -------------------------------------------------------------------------------- 1 | package feed 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/constant/strings" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/feed" 8 | grpc2 "GuGoTik/src/utils/grpc" 9 | "GuGoTik/src/utils/logging" 10 | "GuGoTik/src/web/models" 11 | "GuGoTik/src/web/utils" 12 | "github.com/gin-gonic/gin" 13 | _ "github.com/mbobakov/grpc-consul-resolver" 14 | "github.com/sirupsen/logrus" 15 | "net/http" 16 | "strconv" 17 | ) 18 | 19 | var Client feed.FeedServiceClient 20 | 21 | func ListVideosByRecommendHandle(c *gin.Context) { 22 | var req models.ListVideosReq 23 | _, span := tracing.Tracer.Start(c.Request.Context(), "Feed-ListVideosByRecommendHandle") 24 | defer span.End() 25 | logging.SetSpanWithHostname(span) 26 | logger := logging.LogService("GateWay.Videos").WithContext(c.Request.Context()) 27 | 28 | if err := c.ShouldBindQuery(&req); err != nil { 29 | logger.WithFields(logrus.Fields{ 30 | "latestTime": req.LatestTime, 31 | "err": err, 32 | }).Warnf("Error when trying to bind query") 33 | c.JSON(http.StatusOK, models.ListVideosRes{ 34 | StatusCode: strings.GateWayParamsErrorCode, 35 | StatusMsg: strings.GateWayParamsError, 36 | NextTime: nil, 37 | VideoList: nil, 38 | }) 39 | return 40 | } 41 | 42 | latestTime := req.LatestTime 43 | actorId := uint32(req.ActorId) 44 | var res *feed.ListFeedResponse 45 | var err error 46 | anonymity, err := strconv.ParseUint(config.EnvCfg.AnonymityUser, 10, 32) 47 | if err != nil { 48 | c.JSON(http.StatusOK, models.ListVideosRes{ 49 | StatusCode: strings.FeedServiceInnerErrorCode, 50 | StatusMsg: strings.FeedServiceInnerError, 51 | NextTime: nil, 52 | VideoList: nil, 53 | }) 54 | return 55 | } 56 | if actorId == uint32(anonymity) { 57 | res, err = Client.ListVideos(c.Request.Context(), &feed.ListFeedRequest{ 58 | LatestTime: &latestTime, 59 | ActorId: &actorId, 60 | }) 61 | } else { 62 | res, err = Client.ListVideosByRecommend(c.Request.Context(), &feed.ListFeedRequest{ 63 | LatestTime: &latestTime, 64 | ActorId: &actorId, 65 | }) 66 | } 67 | if err != nil { 68 | logger.WithFields(logrus.Fields{ 69 | "LatestTime": latestTime, 70 | "Err": err, 71 | }).Warnf("Error when trying to connect with FeedService") 72 | c.JSON(http.StatusOK, models.ListVideosRes{ 73 | StatusCode: strings.FeedServiceInnerErrorCode, 74 | StatusMsg: strings.FeedServiceInnerError, 75 | NextTime: nil, 76 | VideoList: nil, 77 | }) 78 | return 79 | } 80 | c.Render(http.StatusOK, utils.CustomJSON{Data: res, Context: c}) 81 | } 82 | 83 | func init() { 84 | conn := grpc2.Connect(config.FeedRpcServerName) 85 | Client = feed.NewFeedServiceClient(conn) 86 | } 87 | -------------------------------------------------------------------------------- /src/web/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/extra/profiling" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/utils/logging" 8 | "GuGoTik/src/web/about" 9 | "GuGoTik/src/web/auth" 10 | comment2 "GuGoTik/src/web/comment" 11 | favorite2 "GuGoTik/src/web/favorite" 12 | feed2 "GuGoTik/src/web/feed" 13 | message2 "GuGoTik/src/web/message" 14 | "GuGoTik/src/web/middleware" 15 | publish2 "GuGoTik/src/web/publish" 16 | relation2 "GuGoTik/src/web/relation" 17 | user2 "GuGoTik/src/web/user" 18 | "context" 19 | "github.com/gin-contrib/gzip" 20 | "github.com/gin-gonic/gin" 21 | "github.com/sirupsen/logrus" 22 | ginprometheus "github.com/zsais/go-gin-prometheus" 23 | "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" 24 | "time" 25 | ) 26 | 27 | func main() { 28 | // Set Trace Provider 29 | tp, err := tracing.SetTraceProvider(config.WebServiceName) 30 | 31 | if err != nil { 32 | logging.Logger.WithFields(logrus.Fields{ 33 | "err": err, 34 | }).Panicf("Error to set the trace") 35 | } 36 | defer func() { 37 | if err := tp.Shutdown(context.Background()); err != nil { 38 | logging.Logger.WithFields(logrus.Fields{ 39 | "err": err, 40 | }).Errorf("Error to set the trace") 41 | } 42 | }() 43 | 44 | g := gin.Default() 45 | // Configure Prometheus 46 | p := ginprometheus.NewPrometheus("GuGoTik-WebGateway") 47 | p.Use(g) 48 | // Configure Gzip 49 | g.Use(gzip.Gzip(gzip.DefaultCompression)) 50 | // Configure Tracing 51 | g.Use(otelgin.Middleware(config.WebServiceName)) 52 | g.Use(middleware.TokenAuthMiddleware()) 53 | g.Use(middleware.RateLimiterMiddleWare(time.Second, 1000, 1000)) 54 | 55 | // Configure Pyroscope 56 | profiling.InitPyroscope("GuGoTik.GateWay") 57 | 58 | // Register Service 59 | // Test Service 60 | g.GET("/about", about.Handle) 61 | // Production Service 62 | rootPath := g.Group("/douyin") 63 | user := rootPath.Group("/user") 64 | { 65 | user.GET("/", user2.UserHandler) 66 | user.POST("/login/", auth.LoginHandle) 67 | user.POST("/register/", auth.RegisterHandle) 68 | } 69 | feed := rootPath.Group("/feed") 70 | { 71 | feed.GET("/", feed2.ListVideosByRecommendHandle) 72 | } 73 | comment := rootPath.Group("/comment") 74 | { 75 | comment.POST("/action/", comment2.ActionCommentHandler) 76 | comment.GET("/list/", comment2.ListCommentHandler) 77 | comment.GET("/count/", comment2.CountCommentHandler) 78 | } 79 | relation := rootPath.Group("/relation") 80 | { 81 | //todo: frontend 82 | relation.POST("/action/", relation2.ActionRelationHandler) 83 | relation.POST("/follow/", relation2.FollowHandler) 84 | relation.POST("/unfollow/", relation2.UnfollowHandler) 85 | relation.GET("/follow/list/", relation2.GetFollowListHandler) 86 | relation.GET("/follower/list/", relation2.GetFollowerListHandler) 87 | relation.GET("/friend/list/", relation2.GetFriendListHandler) 88 | relation.GET("/follow/count/", relation2.CountFollowHandler) 89 | relation.GET("/follower/count/", relation2.CountFollowerHandler) 90 | relation.GET("/isFollow/", relation2.IsFollowHandler) 91 | } 92 | 93 | publish := rootPath.Group("/publish") 94 | { 95 | publish.POST("/action/", publish2.ActionPublishHandle) 96 | publish.GET("/list/", publish2.ListPublishHandle) 97 | } 98 | //todo 99 | message := rootPath.Group("/message") 100 | { 101 | message.GET("/chat/", message2.ListMessageHandler) 102 | message.POST("/action/", message2.ActionMessageHandler) 103 | } 104 | favorite := rootPath.Group("/favorite") 105 | { 106 | favorite.POST("/action/", favorite2.ActionFavoriteHandler) 107 | favorite.GET("/list/", favorite2.ListFavoriteHandler) 108 | } 109 | // Run Server 110 | if err := g.Run(config.WebServiceAddr); err != nil { 111 | panic("Can not run GuGoTik Gateway, binding port: " + config.WebServiceAddr) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/web/middleware/authenticate.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/constant/strings" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/auth" 8 | grpc2 "GuGoTik/src/utils/grpc" 9 | "GuGoTik/src/utils/logging" 10 | "github.com/gin-gonic/gin" 11 | "github.com/sirupsen/logrus" 12 | "go.opentelemetry.io/otel/attribute" 13 | "net/http" 14 | "strconv" 15 | ) 16 | 17 | var client auth.AuthServiceClient 18 | 19 | func TokenAuthMiddleware() gin.HandlerFunc { 20 | return func(c *gin.Context) { 21 | ctx, span := tracing.Tracer.Start(c.Request.Context(), "AuthMiddleWare") 22 | defer span.End() 23 | logging.SetSpanWithHostname(span) 24 | logger := logging.LogService("GateWay.AuthMiddleWare").WithContext(ctx) 25 | span.SetAttributes(attribute.String("url", c.Request.URL.Path)) 26 | if c.Request.URL.Path == "/douyin/user/login/" || 27 | c.Request.URL.Path == "/douyin/user/register/" || 28 | c.Request.URL.Path == "/douyin/comment/list/" || 29 | c.Request.URL.Path == "/douyin/publish/list/" || 30 | c.Request.URL.Path == "/douyin/favorite/list/" { 31 | c.Request.URL.RawQuery += "&actor_id=" + config.EnvCfg.AnonymityUser 32 | span.SetAttributes(attribute.String("mark_url", c.Request.URL.String())) 33 | logger.WithFields(logrus.Fields{ 34 | "Path": c.Request.URL.Path, 35 | }).Debugf("Skip Auth with targeted url") 36 | c.Next() 37 | return 38 | } 39 | 40 | var token string 41 | if c.Request.URL.Path == "/douyin/publish/action/" { 42 | token = c.PostForm("token") 43 | } else { 44 | token = c.Query("token") 45 | } 46 | 47 | if token == "" && (c.Request.URL.Path == "/douyin/feed/" || 48 | c.Request.URL.Path == "/douyin/relation/follow/list/" || 49 | c.Request.URL.Path == "/douyin/relation/follower/list/") { 50 | c.Request.URL.RawQuery += "&actor_id=" + config.EnvCfg.AnonymityUser 51 | span.SetAttributes(attribute.String("mark_url", c.Request.URL.String())) 52 | logger.WithFields(logrus.Fields{ 53 | "Path": c.Request.URL.Path, 54 | }).Debugf("Skip Auth with targeted url") 55 | c.Next() 56 | return 57 | } 58 | span.SetAttributes(attribute.String("token", token)) 59 | // Verify User Token 60 | authenticate, err := client.Authenticate(c.Request.Context(), &auth.AuthenticateRequest{Token: token}) 61 | if err != nil { 62 | logger.WithFields(logrus.Fields{ 63 | "err": err, 64 | }).Errorf("Gateway Auth meet trouble") 65 | span.RecordError(err) 66 | c.JSON(http.StatusOK, gin.H{ 67 | "status_code": strings.GateWayErrorCode, 68 | "status_msg": strings.GateWayError, 69 | }) 70 | c.Abort() 71 | return 72 | } 73 | 74 | if authenticate.StatusCode != 0 { 75 | c.JSON(http.StatusUnauthorized, gin.H{ 76 | "status_code": strings.AuthUserNeededCode, 77 | "status_msg": strings.AuthUserNeeded, 78 | }) 79 | c.Abort() 80 | return 81 | } 82 | 83 | c.Request.URL.RawQuery += "&actor_id=" + strconv.FormatUint(uint64(authenticate.UserId), 10) 84 | c.Next() 85 | } 86 | } 87 | 88 | func init() { 89 | authConn := grpc2.Connect(config.AuthRpcServerName) 90 | client = auth.NewAuthServiceClient(authConn) 91 | } 92 | -------------------------------------------------------------------------------- /src/web/middleware/limiter.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/juju/ratelimit" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func RateLimiterMiddleWare(fillInterval time.Duration, cap, quantum int64) gin.HandlerFunc { 11 | bucket := ratelimit.NewBucketWithQuantum(fillInterval, cap, quantum) 12 | return func(c *gin.Context) { 13 | if bucket.TakeAvailable(1) < 1 { 14 | c.String(http.StatusForbidden, "rate limit...") 15 | c.Abort() 16 | return 17 | } 18 | c.Next() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/web/models/About.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type AboutReq struct { 4 | Echo string `json:"echo" uri:"echo" form:"echo"` 5 | } 6 | 7 | type AboutRes struct { 8 | TimeStamp int64 `json:"time_stamp"` 9 | Echo string `json:"echo,omitempty"` 10 | } 11 | -------------------------------------------------------------------------------- /src/web/models/Comment.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "GuGoTik/src/rpc/comment" 4 | 5 | type ActionCommentReq struct { 6 | Token string `form:"token" binding:"required"` 7 | ActorId int `form:"actor_id"` 8 | VideoId int `form:"video_id" binding:"-"` 9 | ActionType int `form:"action_type" binding:"required"` 10 | CommentText string `form:"comment_text"` 11 | CommentId int `form:"comment_id"` 12 | } 13 | 14 | type ActionCommentRes struct { 15 | StatusCode int `json:"status_code"` 16 | StatusMsg string `json:"status_msg"` 17 | Comment comment.Comment `json:"comment"` 18 | } 19 | 20 | type ListCommentReq struct { 21 | Token string `form:"token"` 22 | ActorId int `form:"actor_id"` 23 | VideoId int `form:"video_id" binding:"-"` 24 | } 25 | 26 | type ListCommentRes struct { 27 | StatusCode int `json:"status_code"` 28 | StatusMsg string `json:"status_msg"` 29 | CommentList []*comment.Comment `json:"comment_list"` 30 | } 31 | 32 | type CountCommentReq struct { 33 | Token string `form:"token"` 34 | ActorId int `form:"actor_id"` 35 | VideoId int `form:"video_id" binding:"-"` 36 | } 37 | 38 | type CountCommentRes struct { 39 | StatusCode int `json:"status_code"` 40 | StatusMsg string `json:"status_msg"` 41 | CommentCount int `json:"comment_count"` 42 | } 43 | -------------------------------------------------------------------------------- /src/web/models/Favorite.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/rpc/feed" 5 | ) 6 | 7 | type ActionFavoriteReq struct { 8 | Token string `form:"token" binding:"required"` 9 | ActorId int `form:"actor_id" binding:"required"` 10 | VideoId int `form:"video_id" binding:"required"` 11 | ActionType int `form:"action_type" binding:"required"` 12 | } 13 | 14 | type ActionFavoriteRes struct { 15 | StatusCode int `json:"status_code"` 16 | StatusMsg string `json:"status_msg"` 17 | } 18 | 19 | type ListFavoriteReq struct { 20 | Token string `form:"token"` 21 | ActorId int `form:"actor_id"` 22 | UserId int `form:"user_id" binding:"required"` 23 | } 24 | 25 | type ListFavoriteRes struct { 26 | StatusCode int `json:"status_code"` 27 | StatusMsg string `json:"status_msg"` 28 | VideoList []*feed.Video `json:"video_list"` 29 | } 30 | -------------------------------------------------------------------------------- /src/web/models/Feed.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/rpc/feed" 5 | ) 6 | 7 | type ListVideosReq struct { 8 | LatestTime string `form:"latest_time"` 9 | ActorId int `form:"actor_id"` 10 | } 11 | 12 | type ListVideosRes struct { 13 | StatusCode int `json:"status_code"` 14 | StatusMsg string `json:"status_msg"` 15 | NextTime *int64 `json:"next_time,omitempty"` 16 | VideoList []*feed.Video `json:"video_list,omitempty"` 17 | } 18 | -------------------------------------------------------------------------------- /src/web/models/Login.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type LoginReq struct { 4 | UserName string `form:"username" binding:"required"` 5 | Password string `form:"password" binding:"required"` 6 | } 7 | 8 | type LoginRes struct { 9 | StatusCode int `json:"status_code"` 10 | StatusMsg string `json:"status_msg"` 11 | UserId uint32 `json:"user_id"` 12 | Token string `json:"token"` 13 | } 14 | 15 | type RegisterReq struct { 16 | UserName string `form:"username" binding:"required"` 17 | Password string `form:"password" binding:"required"` 18 | } 19 | 20 | type RegisterRes struct { 21 | StatusCode int `json:"status_code"` 22 | StatusMsg string `json:"status_msg"` 23 | UserId int `json:"user_id"` 24 | Token string `json:"token"` 25 | } 26 | -------------------------------------------------------------------------------- /src/web/models/Message.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "GuGoTik/src/rpc/chat" 5 | ) 6 | 7 | // SMessageReq 这个是发数据的数据结构 8 | type SMessageReq struct { 9 | ActorId int `form:"actor_id" binding:"required"` 10 | ToUserId int `form:"to_user_id" binding:"required"` 11 | Content string `form:"content" binding:"required"` 12 | ActionType int `form:"action_type" binding:"required"` // send message 13 | //Create_time string //time maybe have some question 14 | } 15 | 16 | // SMessageRes 收的状态 17 | // status_code 状态码 0- 成功 其他值 -失败 18 | // status_msg 返回状态描述 19 | type SMessageRes struct { 20 | StatusCode int `json:"status_code"` 21 | StatusMsg string `json:"status_msg"` 22 | } 23 | 24 | type ListMessageReq struct { 25 | ActorId uint32 `form:"actor_id" binding:"required"` 26 | ToUserId uint32 `form:"to_user_id" binding:"required"` 27 | PreMsgTime uint64 `form:"pre_msg_time"` 28 | } 29 | 30 | type ListMessageRes struct { 31 | StatusCode int `json:"status_code"` 32 | StatusMsg string `json:"status_msg"` 33 | MessageList []*chat.Message `json:"message_list"` 34 | } 35 | -------------------------------------------------------------------------------- /src/web/models/Publish.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "GuGoTik/src/rpc/feed" 4 | 5 | type ListPublishReq struct { 6 | Token string `form:"token" binding:"required"` 7 | ActorId uint32 `form:"actor_id" binding:"required"` 8 | UserId uint32 `form:"user_id" binding:"required"` 9 | } 10 | 11 | type ListPublishRes struct { 12 | StatusCode int `json:"status_code"` 13 | StatusMsg string `json:"status_msg"` 14 | VideoList []*feed.Video `json:"video_list"` 15 | } 16 | 17 | type ActionPublishReq struct { 18 | ActorId uint32 `form:"actor_id" binding:"required"` 19 | } 20 | 21 | type ActionPublishRes struct { 22 | StatusCode int `json:"status_code"` 23 | StatusMsg string `json:"status_msg"` 24 | } 25 | -------------------------------------------------------------------------------- /src/web/models/Relation.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "GuGoTik/src/rpc/user" 4 | 5 | // 6 | //type RelationActionReq struct { 7 | // Token string `form:"token" binding:"required"` 8 | // ActorId int `form:"actor_id"` 9 | // UserId int `form:"user_id"` 10 | //} 11 | 12 | type RelationActionReq struct { 13 | Token string `form:"token" binding:"required"` 14 | ActorId int `form:"actor_id"` 15 | UserId int `form:"to_user_id"` 16 | ActionType int `form:"action_type" binding:"required"` 17 | } 18 | 19 | type RelationActionRes struct { 20 | StatusCode int `json:"status_code"` 21 | StatusMsg string `json:"status_msg"` 22 | } 23 | 24 | type FollowListReq struct { 25 | Token string `form:"token"` 26 | ActorId int `form:"actor_id"` 27 | UserId int `form:"user_id"` 28 | } 29 | 30 | type FollowListRes struct { 31 | StatusCode int `json:"status_code"` 32 | StatusMsg string `json:"status_msg"` 33 | UserList []*user.User `json:"user_list"` 34 | } 35 | 36 | type CountFollowListReq struct { 37 | Token string `form:"token" binding:"required"` 38 | UserId int `form:"user_id"` 39 | } 40 | 41 | type CountFollowListRes struct { 42 | StatusCode int `json:"status_code"` 43 | StatusMsg string `json:"status_msg"` 44 | Count int `json:"count"` 45 | } 46 | 47 | type FollowerListReq struct { 48 | Token string `form:"token"` 49 | ActorId int `form:"actor_id"` 50 | UserId int `form:"user_id"` 51 | } 52 | 53 | type FollowerListRes struct { 54 | StatusCode int `json:"status_code"` 55 | StatusMsg string `json:"status_msg"` 56 | UserList []*user.User `json:"user_list"` 57 | } 58 | 59 | type CountFollowerListReq struct { 60 | Token string `form:"token"` 61 | UserId int `form:"user_id"` 62 | } 63 | 64 | type CountFollowerListRes struct { 65 | StatusCode int `json:"status_code"` 66 | StatusMsg string `json:"status_msg"` 67 | Count int `json:"count"` 68 | } 69 | 70 | type FriendListReq struct { 71 | Token string `form:"token"` 72 | ActorId int `form:"actor_id"` 73 | UserId int `form:"user_id"` 74 | } 75 | 76 | type FriendListRes struct { 77 | StatusCode int `json:"status_code"` 78 | StatusMsg string `json:"status_msg"` 79 | UserList []*user.User `json:"user_list"` 80 | } 81 | 82 | type IsFollowReq struct { 83 | Token string `form:"token" binding:"required"` 84 | ActorId int `form:"actor_id"` 85 | UserId int `form:"user_id"` 86 | } 87 | 88 | type IsFollowRes struct { 89 | Result bool `json:"result"` 90 | } 91 | -------------------------------------------------------------------------------- /src/web/models/User.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type UserReq struct { 4 | UserId uint32 `form:"user_id" binding:"required"` 5 | ActorId uint32 `form:"actor_id" binding:"required"` 6 | } 7 | 8 | type UserRes struct { 9 | StatusCode int32 `json:"status_code"` // 状态码,0-成功,其他值-失败 10 | StatusMsg string `json:"status_msg"` // 返回状态描述 11 | } 12 | -------------------------------------------------------------------------------- /src/web/user/handler.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/constant/strings" 6 | "GuGoTik/src/extra/tracing" 7 | "GuGoTik/src/rpc/user" 8 | grpc2 "GuGoTik/src/utils/grpc" 9 | "GuGoTik/src/utils/logging" 10 | "GuGoTik/src/web/models" 11 | "GuGoTik/src/web/utils" 12 | "net/http" 13 | 14 | "github.com/gin-gonic/gin" 15 | "github.com/sirupsen/logrus" 16 | ) 17 | 18 | var userClient user.UserServiceClient 19 | 20 | func init() { 21 | userConn := grpc2.Connect(config.UserRpcServerName) 22 | userClient = user.NewUserServiceClient(userConn) 23 | } 24 | 25 | func UserHandler(c *gin.Context) { 26 | var req models.UserReq 27 | _, span := tracing.Tracer.Start(c.Request.Context(), "UserInfoHandler") 28 | defer span.End() 29 | logging.SetSpanWithHostname(span) 30 | logger := logging.LogService("GateWay.UserInfo").WithContext(c.Request.Context()) 31 | 32 | if err := c.ShouldBindQuery(&req); err != nil { 33 | c.JSON(http.StatusOK, models.UserRes{ 34 | StatusCode: strings.GateWayParamsErrorCode, 35 | StatusMsg: strings.GateWayParamsError, 36 | }) 37 | logging.SetSpanError(span, err) 38 | return 39 | } 40 | 41 | resp, err := userClient.GetUserInfo(c.Request.Context(), &user.UserRequest{ 42 | UserId: req.UserId, 43 | ActorId: req.ActorId, 44 | }) 45 | 46 | if err != nil { 47 | logger.WithFields(logrus.Fields{ 48 | "err": err, 49 | }).Errorf("Error when gateway get info from UserInfo Service") 50 | logging.SetSpanError(span, err) 51 | c.Render(http.StatusOK, utils.CustomJSON{Data: resp, Context: c}) 52 | return 53 | } 54 | 55 | c.Render(http.StatusOK, utils.CustomJSON{Data: resp, Context: c}) 56 | } 57 | -------------------------------------------------------------------------------- /src/web/utils/json.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "google.golang.org/protobuf/encoding/protojson" 6 | "google.golang.org/protobuf/proto" 7 | "net/http" 8 | ) 9 | 10 | type CustomJSON struct { 11 | Data proto.Message 12 | Context *gin.Context 13 | } 14 | 15 | var m = protojson.MarshalOptions{ 16 | EmitUnpopulated: true, 17 | UseProtoNames: true, 18 | } 19 | 20 | func (r CustomJSON) Render(w http.ResponseWriter) (err error) { 21 | r.WriteContentType(w) 22 | res, _ := m.Marshal(r.Data) 23 | _, err = w.Write(res) 24 | return 25 | } 26 | 27 | func (r CustomJSON) WriteContentType(w http.ResponseWriter) { 28 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 29 | } 30 | -------------------------------------------------------------------------------- /test/k6/comment.js: -------------------------------------------------------------------------------- 1 | import { sleep, check } from 'k6'; 2 | import http from 'k6/http'; 3 | 4 | export const options = { 5 | scenarios: { 6 | Scenario_1: { 7 | executor: 'ramping-vus', 8 | gracefulStop: '30s', 9 | stages: [ 10 | { target: 1000, duration: '15s' }, 11 | { target: 1500, duration: '30s' }, 12 | { target: 1000, duration: '15s' }, 13 | ], 14 | gracefulRampDown: '30s', 15 | exec: 'comment', 16 | }, 17 | }, 18 | } 19 | 20 | export function comment() { 21 | let res = http.post('http://127.0.0.1:37000/douyin/comment/action/?token=e75fae76-6a4e-4fa8-9b60-230e5d4f6b29&video_id=3048003698&action_type=1&comment_text=好好好') 22 | 23 | let jsonResponse = JSON.parse(res.body); 24 | check(jsonResponse, { 25 | 'status_code is 0': (json) => json.status_code === 0, 26 | }); 27 | sleep(3) 28 | } 29 | -------------------------------------------------------------------------------- /test/k6/comment_get.js: -------------------------------------------------------------------------------- 1 | import { sleep, check } from 'k6'; 2 | import http from 'k6/http'; 3 | 4 | export const options = { 5 | scenarios: { 6 | Scenario_1: { 7 | executor: 'ramping-vus', 8 | gracefulStop: '30s', 9 | stages: [ 10 | { target: 1000, duration: '15s' }, 11 | { target: 1500, duration: '30s' }, 12 | { target: 1000, duration: '15s' }, 13 | ], 14 | gracefulRampDown: '30s', 15 | exec: 'comment_get', 16 | }, 17 | }, 18 | } 19 | 20 | export function comment_get() { 21 | let res = http.get('http://127.0.0.1:37000/douyin/comment/list/?video_id=3048003698') 22 | 23 | let jsonResponse = JSON.parse(res.body); 24 | check(jsonResponse, { 25 | 'status_code is 0': (json) => json.status_code === 0, 26 | }); 27 | sleep(3) 28 | } 29 | 30 | -------------------------------------------------------------------------------- /test/k6/favorite.js: -------------------------------------------------------------------------------- 1 | import { sleep, check } from 'k6'; 2 | import http from 'k6/http'; 3 | 4 | export const options = { 5 | scenarios: { 6 | Scenario_1: { 7 | executor: 'ramping-vus', 8 | gracefulStop: '30s', 9 | stages: [ 10 | { target: 1000, duration: '15s' }, 11 | { target: 1500, duration: '30s' }, 12 | { target: 1000, duration: '15s' }, 13 | ], 14 | gracefulRampDown: '30s', 15 | exec: 'favorite', 16 | }, 17 | }, 18 | } 19 | 20 | export function favorite() { 21 | let res = http.post('http://127.0.0.1:37000/douyin/favorite/action/?token=e75fae76-6a4e-4fa8-9b60-230e5d4f6b29&video_id=3048003698&action_type=1') 22 | 23 | let jsonResponse = JSON.parse(res.body); 24 | check(jsonResponse, { 25 | 'status_code is 0': (json) => json.status_code === 10008, 26 | }); 27 | sleep(3) 28 | } 29 | -------------------------------------------------------------------------------- /test/k6/favorite_random.js: -------------------------------------------------------------------------------- 1 | import { sleep } from 'k6'; 2 | import http from 'k6/http'; 3 | 4 | export const options = { 5 | scenarios: { 6 | Scenario_1: { 7 | executor: 'ramping-vus', 8 | gracefulStop: '30s', 9 | stages: [ 10 | { target: 1000, duration: '15s' }, 11 | { target: 1500, duration: '30s' }, 12 | { target: 1000, duration: '15s' }, 13 | ], 14 | gracefulRampDown: '30s', 15 | exec: 'favorite', 16 | }, 17 | }, 18 | } 19 | 20 | export function favorite() { 21 | let actionType = Math.random() < 0.5 ? 1 : 2; 22 | http.post(`http://127.0.0.1:37000/douyin/favorite/action/?token=e75fae76-6a4e-4fa8-9b60-230e5d4f6b29&video_id=3048003698&action_type=${actionType}`) 23 | 24 | sleep(3) 25 | } 26 | -------------------------------------------------------------------------------- /test/k6/feed.js: -------------------------------------------------------------------------------- 1 | import { sleep, check } from 'k6'; 2 | import http from 'k6/http'; 3 | 4 | export const options = { 5 | scenarios: { 6 | feed: { 7 | executor: 'ramping-vus', 8 | startVUs: 0, 9 | gracefulStop: '30s', 10 | stages: [ 11 | { target: 1000, duration: '20s' }, 12 | { target: 2000, duration: '20s' }, 13 | { target: 1000, duration: '20s' }, 14 | ], 15 | gracefulRampDown: '30s', 16 | exec: 'feed', 17 | }, 18 | }, 19 | } 20 | 21 | export function feed() { 22 | let res = http.get('http://127.0.0.1:37000/douyin/feed?'); 23 | 24 | let jsonResponse = JSON.parse(res.body); 25 | check(jsonResponse, { 26 | 'status_code is 0': (json) => json.status_code === 0, 27 | }); 28 | 29 | sleep(3); 30 | } 31 | -------------------------------------------------------------------------------- /test/rpc/authrpc_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/rpc/auth" 6 | "GuGoTik/src/rpc/health" 7 | "context" 8 | "fmt" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/credentials/insecure" 14 | ) 15 | 16 | func TestHealth(t *testing.T) { 17 | var Client health.HealthClient 18 | req := health.HealthCheckRequest{} 19 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.AuthRpcServerPort), 20 | grpc.WithTransportCredentials(insecure.NewCredentials()), 21 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 22 | assert.Empty(t, err) 23 | Client = health.NewHealthClient(conn) 24 | check, err := Client.Check(context.Background(), &req) 25 | assert.Empty(t, err) 26 | assert.Equal(t, "SERVING", check.Status.String()) 27 | } 28 | 29 | func TestRegister(t *testing.T) { 30 | var Client auth.AuthServiceClient 31 | req := auth.RegisterRequest{ 32 | Username: "epicmo12312", 33 | Password: "epicmo12312312", 34 | } 35 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.AuthRpcServerPort), 36 | grpc.WithTransportCredentials(insecure.NewCredentials()), 37 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 38 | assert.Empty(t, err) 39 | Client = auth.NewAuthServiceClient(conn) 40 | res, err := Client.Register(context.Background(), &req) 41 | assert.Empty(t, err) 42 | assert.Equal(t, int32(0), res.StatusCode) 43 | } 44 | 45 | func TestLogin(t *testing.T) { 46 | var Client auth.AuthServiceClient 47 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.AuthRpcServerPort), 48 | grpc.WithTransportCredentials(insecure.NewCredentials()), 49 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 50 | assert.Empty(t, err) 51 | Client = auth.NewAuthServiceClient(conn) 52 | res, err := Client.Login(context.Background(), &auth.LoginRequest{ 53 | Username: "epicmo", 54 | Password: "epicmo", 55 | }) 56 | assert.Empty(t, err) 57 | assert.Equal(t, int32(0), res.StatusCode) 58 | } 59 | -------------------------------------------------------------------------------- /test/rpc/commentrpc_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/rpc/comment" 6 | "context" 7 | "fmt" 8 | "github.com/stretchr/testify/assert" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/credentials/insecure" 11 | "os" 12 | "sync" 13 | "testing" 14 | ) 15 | 16 | var Client comment.CommentServiceClient 17 | 18 | func setup() { 19 | conn, _ := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.CommentRpcServerPort), 20 | grpc.WithTransportCredentials(insecure.NewCredentials()), 21 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 22 | 23 | Client = comment.NewCommentServiceClient(conn) 24 | } 25 | 26 | func TestActionComment_Add(t *testing.T) { 27 | res, err := Client.ActionComment(context.Background(), &comment.ActionCommentRequest{ 28 | ActorId: 1, 29 | VideoId: 0, 30 | ActionType: comment.ActionCommentType_ACTION_COMMENT_TYPE_ADD, 31 | Action: &comment.ActionCommentRequest_CommentText{CommentText: "I want to kill them all"}, 32 | }) 33 | assert.Empty(t, err) 34 | assert.Equal(t, int32(0), res.StatusCode) 35 | } 36 | 37 | func TestActionComment_Limiter(t *testing.T) { 38 | wg := &sync.WaitGroup{} 39 | for i := 0; i < 10; i++ { 40 | wg.Add(1) 41 | go func() { 42 | defer wg.Done() 43 | _, _ = Client.ActionComment(context.Background(), &comment.ActionCommentRequest{ 44 | ActorId: 1, 45 | VideoId: 1, 46 | ActionType: comment.ActionCommentType_ACTION_COMMENT_TYPE_ADD, 47 | Action: &comment.ActionCommentRequest_CommentText{CommentText: "富强民主文明和谐"}, 48 | }) 49 | _, _ = Client.ActionComment(context.Background(), &comment.ActionCommentRequest{ 50 | ActorId: 2, 51 | VideoId: 1, 52 | ActionType: comment.ActionCommentType_ACTION_COMMENT_TYPE_ADD, 53 | Action: &comment.ActionCommentRequest_CommentText{CommentText: "自由平等公正法治"}, 54 | }) 55 | }() 56 | } 57 | wg.Wait() 58 | } 59 | 60 | func TestActionComment_Delete(t *testing.T) { 61 | res, err := Client.ActionComment(context.Background(), &comment.ActionCommentRequest{ 62 | ActorId: 1, 63 | VideoId: 0, 64 | ActionType: comment.ActionCommentType_ACTION_COMMENT_TYPE_DELETE, 65 | Action: &comment.ActionCommentRequest_CommentId{CommentId: 1}, 66 | }) 67 | assert.Empty(t, err) 68 | assert.Equal(t, int32(0), res.StatusCode) 69 | } 70 | 71 | func TestListComment(t *testing.T) { 72 | res, err := Client.ListComment(context.Background(), &comment.ListCommentRequest{ 73 | ActorId: 1, 74 | VideoId: 0, 75 | }) 76 | assert.Empty(t, err) 77 | assert.Equal(t, int32(0), res.StatusCode) 78 | } 79 | 80 | func TestCountComment(t *testing.T) { 81 | res, err := Client.CountComment(context.Background(), &comment.CountCommentRequest{ 82 | ActorId: 1, 83 | VideoId: 0, 84 | }) 85 | assert.Empty(t, err) 86 | assert.Equal(t, int32(0), res.StatusCode) 87 | } 88 | 89 | func TestMain(m *testing.M) { 90 | setup() 91 | code := m.Run() 92 | os.Exit(code) 93 | } 94 | -------------------------------------------------------------------------------- /test/rpc/feedrpc_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/rpc/feed" 6 | "context" 7 | "fmt" 8 | "github.com/stretchr/testify/assert" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/credentials/insecure" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | func TestListVideos(t *testing.T) { 16 | 17 | var Client feed.FeedServiceClient 18 | currentTime := time.Now().Unix() 19 | latestTime := fmt.Sprintf("%d", currentTime) 20 | actorId := uint32(1) 21 | req := feed.ListFeedRequest{ 22 | LatestTime: &latestTime, 23 | ActorId: &actorId, 24 | } 25 | 26 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.FeedRpcServerPort), 27 | grpc.WithTransportCredentials(insecure.NewCredentials()), 28 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 29 | assert.Empty(t, err) 30 | Client = feed.NewFeedServiceClient(conn) 31 | 32 | res, err := Client.ListVideos(context.Background(), &req) 33 | assert.Empty(t, err) 34 | assert.Equal(t, int32(0), res.StatusCode) 35 | } 36 | 37 | func TestQueryVideoExisted(t *testing.T) { 38 | 39 | var Client feed.FeedServiceClient 40 | req := feed.VideoExistRequest{ 41 | VideoId: 1, 42 | } 43 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.FeedRpcServerPort), 44 | grpc.WithTransportCredentials(insecure.NewCredentials()), 45 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 46 | assert.Empty(t, err) 47 | Client = feed.NewFeedServiceClient(conn) 48 | 49 | res, err := Client.QueryVideoExisted(context.Background(), &req) 50 | assert.Empty(t, err) 51 | assert.Equal(t, int32(0), res.StatusCode) 52 | } 53 | -------------------------------------------------------------------------------- /test/rpc/like_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/rpc/favorite" 6 | "context" 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/credentials/insecure" 13 | ) 14 | 15 | var likeClient favorite.FavoriteServiceClient 16 | 17 | func setups1() { 18 | conn, _ := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.FavoriteRpcServerPort), 19 | grpc.WithTransportCredentials(insecure.NewCredentials()), 20 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 21 | likeClient = favorite.NewFavoriteServiceClient(conn) 22 | } 23 | func TestFavoriteAction(t *testing.T) { 24 | setups1() 25 | res, err := likeClient.FavoriteAction(context.Background(), &favorite.FavoriteRequest{ 26 | ActorId: 2, 27 | VideoId: 20, 28 | ActionType: 1, 29 | }) 30 | assert.Empty(t, err) 31 | assert.Equal(t, int32(0), res.StatusCode) 32 | } 33 | 34 | func TestFavoriteList(t *testing.T) { 35 | setups1() 36 | res, err := likeClient.FavoriteList(context.Background(), &favorite.FavoriteListRequest{ 37 | ActorId: 2, 38 | UserId: 1, 39 | }) 40 | 41 | assert.Empty(t, err) 42 | assert.Equal(t, int32(0), res.StatusCode) 43 | assert.Nil(t, res.VideoList) 44 | } 45 | 46 | func TestIsFavorite(t *testing.T) { 47 | setups1() 48 | res, err := likeClient.IsFavorite(context.Background(), &favorite.IsFavoriteRequest{ 49 | ActorId: 1, 50 | VideoId: 1, 51 | }) 52 | assert.Empty(t, err) 53 | assert.Equal(t, int32(0), res.StatusCode) 54 | assert.Equal(t, true, res.Result) 55 | } 56 | 57 | func TestCountFavorite(t *testing.T) { 58 | setups1() 59 | res, err := likeClient.CountFavorite(context.Background(), &favorite.CountFavoriteRequest{ 60 | VideoId: 88, 61 | }) 62 | assert.Empty(t, err) 63 | assert.Equal(t, int32(0), res.StatusCode) 64 | assert.Equal(t, uint32(1), res.Count) 65 | } 66 | 67 | func TestCountUserFavorite(t *testing.T) { 68 | setups1() 69 | res, err := likeClient.CountUserFavorite(context.Background(), &favorite.CountUserFavoriteRequest{ 70 | UserId: 2, 71 | }) 72 | assert.Empty(t, err) 73 | assert.Equal(t, int32(0), res.StatusCode) 74 | assert.Equal(t, uint32(1), res.Count) 75 | } 76 | 77 | func TestCountUserTotalFavorited(t *testing.T) { 78 | setups1() 79 | res, err := likeClient.CountUserTotalFavorited(context.Background(), &favorite.CountUserTotalFavoritedRequest{ 80 | ActorId: 100, 81 | UserId: 3, 82 | }) 83 | assert.Empty(t, err) 84 | assert.Equal(t, int32(0), res.StatusCode) 85 | assert.Equal(t, uint32(0), res.Count) 86 | } 87 | -------------------------------------------------------------------------------- /test/rpc/messagerpc_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/rpc/chat" 6 | "context" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/credentials/insecure" 12 | ) 13 | 14 | var chatClient chat.ChatServiceClient 15 | 16 | func setups() { 17 | conn, _ := grpc.Dial(config.MessageRpcServerPort, 18 | grpc.WithTransportCredentials(insecure.NewCredentials()), 19 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 20 | chatClient = chat.NewChatServiceClient(conn) 21 | } 22 | 23 | func TestActionMessage_Add(t *testing.T) { 24 | setups() 25 | res, err := chatClient.ChatAction(context.Background(), &chat.ActionRequest{ 26 | ActorId: 1, 27 | UserId: 2, 28 | ActionType: 1, 29 | Content: "Test message1", 30 | }) 31 | 32 | assert.Empty(t, err) 33 | assert.Equal(t, int32(0), res.StatusCode) 34 | 35 | } 36 | 37 | func TestChat(t *testing.T) { 38 | setups() 39 | res, err := chatClient.Chat(context.Background(), &chat.ChatRequest{ 40 | ActorId: 1, 41 | UserId: 2, 42 | PreMsgTime: 0, 43 | }) 44 | 45 | assert.Empty(t, err) 46 | assert.Equal(t, int32(0), res.StatusCode) 47 | } 48 | -------------------------------------------------------------------------------- /test/rpc/publishrpc_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/rpc/publish" 6 | "context" 7 | "fmt" 8 | "github.com/stretchr/testify/assert" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/credentials/insecure" 11 | "io" 12 | "os" 13 | "sync" 14 | "testing" 15 | ) 16 | 17 | var publishClient publish.PublishServiceClient 18 | 19 | func TestListVideo(t *testing.T) { 20 | req := publish.ListVideoRequest{ 21 | UserId: 123, 22 | ActorId: 123, 23 | } 24 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.PublishRpcServerPort), 25 | grpc.WithTransportCredentials(insecure.NewCredentials()), 26 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 27 | assert.Empty(t, err) 28 | publishClient = publish.NewPublishServiceClient(conn) 29 | //调用服务端方法 30 | res, err := publishClient.ListVideo(context.Background(), &req) 31 | assert.Empty(t, err) 32 | assert.Equal(t, int32(0), res.StatusCode) 33 | } 34 | 35 | func TestCountVideo(t *testing.T) { 36 | req := publish.CountVideoRequest{ 37 | UserId: 1, 38 | } 39 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.PublishRpcServerPort), 40 | grpc.WithTransportCredentials(insecure.NewCredentials()), 41 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 42 | assert.Empty(t, err) 43 | publishClient = publish.NewPublishServiceClient(conn) 44 | res, err := publishClient.CountVideo(context.Background(), &req) 45 | assert.Empty(t, err) 46 | assert.Equal(t, int32(0), res.StatusCode) 47 | } 48 | 49 | func TestPublishVideo(t *testing.T) { 50 | reader, err := os.Open("/home/yangfeng/Repos/youthcamp/videos/upload_video_2_1080p.mp4") 51 | assert.Empty(t, err) 52 | bytes, err := io.ReadAll(reader) 53 | assert.Empty(t, err) 54 | req := publish.CreateVideoRequest{ 55 | ActorId: 2, 56 | Data: bytes, 57 | Title: "原神,启动!", 58 | } 59 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.PublishRpcServerPort), 60 | grpc.WithTransportCredentials(insecure.NewCredentials()), 61 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 62 | assert.Empty(t, err) 63 | publishClient = publish.NewPublishServiceClient(conn) 64 | res, err := publishClient.CreateVideo(context.Background(), &req) 65 | assert.Empty(t, err) 66 | assert.Equal(t, int32(0), res.StatusCode) 67 | } 68 | 69 | func TestPublishVideo_Limiter(t *testing.T) { 70 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.PublishRpcServerPort), 71 | grpc.WithTransportCredentials(insecure.NewCredentials()), 72 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 73 | assert.Empty(t, err) 74 | publishClient = publish.NewPublishServiceClient(conn) 75 | 76 | reader, err := os.Open("/home/yangfeng/Repos/youthcamp/videos/upload_video_4.mp4") 77 | assert.Empty(t, err) 78 | bytes, err := io.ReadAll(reader) 79 | assert.Empty(t, err) 80 | req := publish.CreateVideoRequest{ 81 | ActorId: 1, 82 | Data: bytes, 83 | Title: "原神,启动!", 84 | } 85 | 86 | wg := &sync.WaitGroup{} 87 | for i := 0; i < 10; i++ { 88 | wg.Add(1) 89 | go func() { 90 | defer wg.Done() 91 | _, _ = publishClient.CreateVideo(context.Background(), &req) 92 | }() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /test/rpc/userrpc_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "GuGoTik/src/constant/config" 5 | "GuGoTik/src/rpc/user" 6 | "context" 7 | "fmt" 8 | "github.com/stretchr/testify/assert" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/credentials/insecure" 11 | "testing" 12 | ) 13 | 14 | func TestGetUserInfo(t *testing.T) { 15 | var Client user.UserServiceClient 16 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.UserRpcServerPort), 17 | grpc.WithTransportCredentials(insecure.NewCredentials()), 18 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 19 | assert.Empty(t, err) 20 | Client = user.NewUserServiceClient(conn) 21 | res, err := Client.GetUserInfo(context.Background(), &user.UserRequest{ 22 | UserId: 2, 23 | ActorId: 1, 24 | }) 25 | assert.Empty(t, err) 26 | assert.Equal(t, int32(0), res.StatusCode) 27 | } 28 | 29 | func TestGetUserExistedInfo(t *testing.T) { 30 | var Client user.UserServiceClient 31 | conn, err := grpc.Dial(fmt.Sprintf("127.0.0.1%s", config.UserRpcServerPort), 32 | grpc.WithTransportCredentials(insecure.NewCredentials()), 33 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)) 34 | assert.Empty(t, err) 35 | Client = user.NewUserServiceClient(conn) 36 | res, err := Client.GetUserExistInformation(context.Background(), &user.UserExistRequest{ 37 | UserId: 1, 38 | }) 39 | assert.Empty(t, err) 40 | assert.Equal(t, int32(0), res.StatusCode) 41 | } 42 | -------------------------------------------------------------------------------- /test/web/auth_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "GuGoTik/src/web/models" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | "testing" 9 | 10 | "github.com/google/uuid" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestRegister(t *testing.T) { 15 | url := "http://127.0.0.1:37000/douyin/user/register?username=" + uuid.New().String() + "&password=epicmo" 16 | method := "POST" 17 | client := &http.Client{} 18 | req, err := http.NewRequest(method, url, nil) 19 | 20 | assert.Empty(t, err) 21 | 22 | res, err := client.Do(req) 23 | assert.Empty(t, err) 24 | defer func(Body io.ReadCloser) { 25 | err := Body.Close() 26 | assert.Empty(t, err) 27 | }(res.Body) 28 | 29 | body, err := io.ReadAll(res.Body) 30 | assert.Empty(t, err) 31 | user := &models.LoginRes{} 32 | err = json.Unmarshal(body, &user) 33 | assert.Empty(t, err) 34 | assert.Equal(t, 0, user.StatusCode) 35 | } 36 | 37 | // This Test can only run once. 38 | func TestDisplayRegister(t *testing.T) { 39 | url := "http://127.0.0.1:37000/douyin/user/register?username=epicmo4&password=epicmo" 40 | method := "POST" 41 | client := &http.Client{} 42 | req, err := http.NewRequest(method, url, nil) 43 | 44 | assert.Empty(t, err) 45 | 46 | res, err := client.Do(req) 47 | assert.Empty(t, err) 48 | defer func(Body io.ReadCloser) { 49 | err := Body.Close() 50 | assert.Empty(t, err) 51 | }(res.Body) 52 | 53 | body, err := io.ReadAll(res.Body) 54 | assert.Empty(t, err) 55 | user := &models.LoginRes{} 56 | err = json.Unmarshal(body, &user) 57 | assert.Empty(t, err) 58 | assert.Equal(t, 0, user.StatusCode) 59 | } 60 | 61 | // This test must run after `TestDisplayRegister` 62 | func TestLogin(t *testing.T) { 63 | 64 | url := "http://127.0.0.1:37000/douyin/user/login?username=epicmo&password=epicmo" 65 | method := "POST" 66 | client := &http.Client{} 67 | req, err := http.NewRequest(method, url, nil) 68 | 69 | assert.Empty(t, err) 70 | 71 | res, err := client.Do(req) 72 | assert.Empty(t, err) 73 | defer func(Body io.ReadCloser) { 74 | err := Body.Close() 75 | assert.Empty(t, err) 76 | }(res.Body) 77 | 78 | body, err := io.ReadAll(res.Body) 79 | assert.Empty(t, err) 80 | user := &models.LoginRes{} 81 | err = json.Unmarshal(body, &user) 82 | assert.Empty(t, err) 83 | assert.Equal(t, 0, user.StatusCode) 84 | } 85 | -------------------------------------------------------------------------------- /test/web/comment_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "GuGoTik/src/web/models" 5 | "encoding/json" 6 | "github.com/stretchr/testify/assert" 7 | "io" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | var client = &http.Client{} 13 | var commentBaseUrl = "http://127.0.0.1:37000/douyin/comment" 14 | 15 | func TestActionComment_Add(t *testing.T) { 16 | url := commentBaseUrl + "/action" 17 | method := "POST" 18 | req, err := http.NewRequest(method, url, nil) 19 | q := req.URL.Query() 20 | q.Add("token", token) 21 | q.Add("actor_id", "1") 22 | q.Add("video_id", "0") 23 | q.Add("action_type", "1") 24 | q.Add("comment_text", "test comment") 25 | req.URL.RawQuery = q.Encode() 26 | 27 | assert.Empty(t, err) 28 | 29 | res, err := client.Do(req) 30 | assert.Empty(t, err) 31 | defer func(Body io.ReadCloser) { 32 | err := Body.Close() 33 | assert.Empty(t, err) 34 | }(res.Body) 35 | 36 | body, err := io.ReadAll(res.Body) 37 | assert.Empty(t, err) 38 | comment := &models.ActionCommentRes{} 39 | err = json.Unmarshal(body, &comment) 40 | assert.Empty(t, err) 41 | assert.Equal(t, 0, comment.StatusCode) 42 | } 43 | 44 | func TestActionComment_Delete(t *testing.T) { 45 | url := commentBaseUrl + "/action" 46 | method := "POST" 47 | req, err := http.NewRequest(method, url, nil) 48 | q := req.URL.Query() 49 | q.Add("token", token) 50 | q.Add("actor_id", "1") 51 | q.Add("video_id", "0") 52 | q.Add("action_type", "2") 53 | q.Add("comment_id", "2") 54 | req.URL.RawQuery = q.Encode() 55 | 56 | assert.Empty(t, err) 57 | 58 | res, err := client.Do(req) 59 | assert.Empty(t, err) 60 | defer func(Body io.ReadCloser) { 61 | err := Body.Close() 62 | assert.Empty(t, err) 63 | }(res.Body) 64 | 65 | body, err := io.ReadAll(res.Body) 66 | assert.Empty(t, err) 67 | actionCommentRes := &models.ActionCommentRes{} 68 | err = json.Unmarshal(body, &actionCommentRes) 69 | assert.Empty(t, err) 70 | assert.Equal(t, 0, actionCommentRes.StatusCode) 71 | } 72 | 73 | func TestListComment(t *testing.T) { 74 | url := commentBaseUrl + "/list" 75 | method := "GET" 76 | req, err := http.NewRequest(method, url, nil) 77 | q := req.URL.Query() 78 | q.Add("token", token) 79 | q.Add("actor_id", "1") 80 | q.Add("video_id", "0") 81 | req.URL.RawQuery = q.Encode() 82 | 83 | assert.Empty(t, err) 84 | 85 | res, err := client.Do(req) 86 | assert.Empty(t, err) 87 | defer func(Body io.ReadCloser) { 88 | err := Body.Close() 89 | assert.Empty(t, err) 90 | }(res.Body) 91 | 92 | body, err := io.ReadAll(res.Body) 93 | assert.Empty(t, err) 94 | listCommentRes := &models.ListCommentRes{} 95 | err = json.Unmarshal(body, &listCommentRes) 96 | assert.Empty(t, err) 97 | assert.Equal(t, 0, listCommentRes.StatusCode) 98 | } 99 | 100 | func TestCountComment(t *testing.T) { 101 | url := commentBaseUrl + "/count" 102 | method := "GET" 103 | req, err := http.NewRequest(method, url, nil) 104 | q := req.URL.Query() 105 | q.Add("token", token) 106 | q.Add("actor_id", "1") 107 | q.Add("video_id", "0") 108 | req.URL.RawQuery = q.Encode() 109 | 110 | assert.Empty(t, err) 111 | 112 | res, err := client.Do(req) 113 | assert.Empty(t, err) 114 | defer func(Body io.ReadCloser) { 115 | err := Body.Close() 116 | assert.Empty(t, err) 117 | }(res.Body) 118 | 119 | body, err := io.ReadAll(res.Body) 120 | assert.Empty(t, err) 121 | countCommentRes := &models.CountCommentRes{} 122 | err = json.Unmarshal(body, &countCommentRes) 123 | assert.Empty(t, err) 124 | assert.Equal(t, 0, countCommentRes.StatusCode) 125 | } 126 | -------------------------------------------------------------------------------- /test/web/favorite_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "GuGoTik/src/web/models" 5 | "encoding/json" 6 | "github.com/stretchr/testify/assert" 7 | "io" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | var favoriteBaseUrl = "http://127.0.0.1:37000/douyin/favorite" 13 | 14 | func TestActionFavorite_Do(t *testing.T) { 15 | url := favoriteBaseUrl + "/action" 16 | method := "POST" 17 | req, err := http.NewRequest(method, url, nil) 18 | q := req.URL.Query() 19 | q.Add("token", "") // replace token, video_id 20 | q.Add("video_id", "1948195853") 21 | q.Add("action_type", "1") 22 | req.URL.RawQuery = q.Encode() 23 | 24 | assert.Empty(t, err) 25 | 26 | res, err := client.Do(req) 27 | assert.Empty(t, err) 28 | defer func(Body io.ReadCloser) { 29 | err := Body.Close() 30 | assert.Empty(t, err) 31 | }(res.Body) 32 | 33 | body, err := io.ReadAll(res.Body) 34 | assert.Empty(t, err) 35 | actionFavoriteRes := &models.ActionFavoriteRes{} 36 | err = json.Unmarshal(body, &actionFavoriteRes) 37 | assert.Empty(t, err) 38 | assert.Equal(t, 0, actionFavoriteRes.StatusCode) 39 | } 40 | 41 | func TestActionFavorite_Cancel(t *testing.T) { 42 | url := favoriteBaseUrl + "/action" 43 | method := "POST" 44 | req, err := http.NewRequest(method, url, nil) 45 | q := req.URL.Query() 46 | q.Add("token", "") // replace token, video_id 47 | q.Add("video_id", "1948195853") 48 | q.Add("action_type", "2") 49 | req.URL.RawQuery = q.Encode() 50 | 51 | assert.Empty(t, err) 52 | 53 | res, err := client.Do(req) 54 | assert.Empty(t, err) 55 | defer func(Body io.ReadCloser) { 56 | err := Body.Close() 57 | assert.Empty(t, err) 58 | }(res.Body) 59 | 60 | body, err := io.ReadAll(res.Body) 61 | assert.Empty(t, err) 62 | actionFavoriteRes := &models.ActionFavoriteRes{} 63 | err = json.Unmarshal(body, &actionFavoriteRes) 64 | assert.Empty(t, err) 65 | assert.Equal(t, 0, actionFavoriteRes.StatusCode) 66 | } 67 | 68 | func TestListFavorite(t *testing.T) { 69 | url := favoriteBaseUrl + "/list" 70 | method := "POST" 71 | req, err := http.NewRequest(method, url, nil) 72 | q := req.URL.Query() 73 | q.Add("token", "") // replace token, user_id 74 | q.Add("user_id", "1") 75 | req.URL.RawQuery = q.Encode() 76 | 77 | assert.Empty(t, err) 78 | 79 | res, err := client.Do(req) 80 | assert.Empty(t, err) 81 | defer func(Body io.ReadCloser) { 82 | err := Body.Close() 83 | assert.Empty(t, err) 84 | }(res.Body) 85 | 86 | body, err := io.ReadAll(res.Body) 87 | assert.Empty(t, err) 88 | listFavoriteRes := &models.ListFavoriteRes{} 89 | err = json.Unmarshal(body, &listFavoriteRes) 90 | assert.Empty(t, err) 91 | assert.Equal(t, 0, listFavoriteRes.StatusCode) 92 | } 93 | -------------------------------------------------------------------------------- /test/web/feed_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "GuGoTik/src/web/models" 5 | "encoding/json" 6 | "github.com/stretchr/testify/assert" 7 | "io" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | func TestListVideos(t *testing.T) { 13 | //url := "http://127.0.0.1:37000/douyin/feed/?token=90aee89f-43c0-4e90-a440-cf4e47c9b790" 14 | url := "http://127.0.0.1:37000/douyin/feed/?latest_time=2006-01-02T15:04:05.999Z&token=90aee89f-43c0-4e90-a440-cf4e47c9b790" 15 | 16 | method := "GET" 17 | client := &http.Client{} 18 | req, err := http.NewRequest(method, url, nil) 19 | assert.Empty(t, err) 20 | 21 | res, err := client.Do(req) 22 | assert.Empty(t, err) 23 | defer func(Body io.ReadCloser) { 24 | err := Body.Close() 25 | assert.Empty(t, err) 26 | }(res.Body) 27 | 28 | body, err := io.ReadAll(res.Body) 29 | assert.Empty(t, err) 30 | feed := &models.ListVideosRes{} 31 | err = json.Unmarshal(body, &feed) 32 | assert.Empty(t, err) 33 | assert.Equal(t, 0, feed.StatusCode) 34 | } 35 | -------------------------------------------------------------------------------- /test/web/message_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "GuGoTik/src/web/models" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestActionMessage_Add(t *testing.T) { 15 | 16 | var client = &http.Client{} 17 | var baseUrl = "http://127.0.0.1:37000/douyin/message" 18 | url := baseUrl + "/action" 19 | method := "POST" 20 | req, err := http.NewRequest(method, url, nil) 21 | q := req.URL.Query() 22 | q.Add("token", token) 23 | q.Add("to_user_id", "2") 24 | q.Add("action_type", "1") 25 | q.Add("content", "test comment in gateway") 26 | req.URL.RawQuery = q.Encode() 27 | 28 | assert.Empty(t, err) 29 | 30 | res, err := client.Do(req) 31 | assert.Empty(t, err) 32 | defer func(Body io.ReadCloser) { 33 | err := Body.Close() 34 | assert.Empty(t, err) 35 | }(res.Body) 36 | 37 | body, err := io.ReadAll(res.Body) 38 | assert.Empty(t, err) 39 | message := &models.ListMessageRes{} 40 | err = json.Unmarshal(body, &message) 41 | assert.Empty(t, err) 42 | assert.Equal(t, 0, message.StatusCode) 43 | } 44 | 45 | func TestChat(t *testing.T) { 46 | var client = &http.Client{} 47 | var baseUrl = "http://127.0.0.1:37000/douyin/message" 48 | url := baseUrl + "/chat" 49 | method := "GET" 50 | req, err := http.NewRequest(method, url, nil) 51 | 52 | q := req.URL.Query() 53 | q.Add("token", "token") 54 | q.Add("to_user_id", "2") 55 | q.Add("perMsgTime", "0") 56 | req.URL.RawQuery = q.Encode() 57 | assert.Empty(t, err) 58 | 59 | res, err := client.Do(req) 60 | assert.Empty(t, err) 61 | defer func(Body io.ReadCloser) { 62 | err := Body.Close() 63 | assert.Empty(t, err) 64 | }(res.Body) 65 | 66 | body, err := io.ReadAll(res.Body) 67 | assert.Empty(t, err) 68 | listMessage := &models.ListMessageRes{} 69 | fmt.Println(listMessage) 70 | err = json.Unmarshal(body, &listMessage) 71 | assert.Empty(t, err) 72 | assert.Equal(t, 0, listMessage.StatusCode) 73 | } 74 | -------------------------------------------------------------------------------- /test/web/publish_test.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "GuGoTik/src/web/models" 5 | "bytes" 6 | "encoding/json" 7 | "github.com/stretchr/testify/assert" 8 | "io" 9 | "mime/multipart" 10 | "net/http" 11 | "os" 12 | "testing" 13 | ) 14 | 15 | func TestListVideo(t *testing.T) { 16 | url := "http://127.0.0.1:37000/douyin/publish/list" 17 | method := "GET" 18 | req, err := http.NewRequest(method, url, nil) 19 | q := req.URL.Query() 20 | q.Add("token", token) 21 | q.Add("user_id", "1") 22 | req.URL.RawQuery = q.Encode() 23 | 24 | assert.Empty(t, err) 25 | 26 | res, err := client.Do(req) 27 | assert.Empty(t, err) 28 | defer func(Body io.ReadCloser) { 29 | err := Body.Close() 30 | assert.Empty(t, err) 31 | }(res.Body) 32 | 33 | body, err := io.ReadAll(res.Body) 34 | assert.Empty(t, err) 35 | ListPublishRes := &models.ListPublishRes{} 36 | err = json.Unmarshal(body, &ListPublishRes) 37 | assert.Empty(t, err) 38 | assert.Equal(t, 0, ListPublishRes.StatusCode) 39 | } 40 | 41 | func TestPublishVideo(t *testing.T) { 42 | url := "http://127.0.0.1:37000/douyin/publish/action" 43 | method := "POST" 44 | filePath := "E:\\Administrator\\Videos\\1.mp4" 45 | 46 | payload := &bytes.Buffer{} 47 | writer := multipart.NewWriter(payload) 48 | 49 | file, err := os.Open(filePath) 50 | assert.Empty(t, err) 51 | defer func(file *os.File) { 52 | err := file.Close() 53 | assert.Empty(t, err) 54 | }(file) 55 | 56 | fileWriter, err := writer.CreateFormFile("data", file.Name()) 57 | assert.Empty(t, err) 58 | 59 | _, err = io.Copy(fileWriter, file) 60 | assert.Empty(t, err) 61 | 62 | _ = writer.WriteField("token", token) 63 | _ = writer.WriteField("title", "10个报错,但是我代码只有9行啊???") 64 | 65 | err = writer.Close() 66 | assert.Empty(t, err) 67 | 68 | req, err := http.NewRequest(method, url, payload) 69 | assert.Empty(t, err) 70 | 71 | req.Header.Set("Content-Type", writer.FormDataContentType()) 72 | 73 | res, err := client.Do(req) 74 | assert.Empty(t, err) 75 | defer func(Body io.ReadCloser) { 76 | err := Body.Close() 77 | assert.Empty(t, err) 78 | }(res.Body) 79 | 80 | body, err := io.ReadAll(res.Body) 81 | assert.Empty(t, err) 82 | actionPublishRes := &models.ActionPublishRes{} 83 | err = json.Unmarshal(body, &actionPublishRes) 84 | assert.Empty(t, err) 85 | assert.Equal(t, 0, actionPublishRes.StatusCode) 86 | } 87 | -------------------------------------------------------------------------------- /test/web/value.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | const token = "e75fae76-6a4e-4fa8-9b60-230e5d4f6b29" 4 | --------------------------------------------------------------------------------