├── .github
└── workflows
│ ├── build-cron.yml
│ ├── build-http-app.yml
│ ├── build-mq.yml
│ ├── build-rpc-contact.yml
│ ├── build-rpc-user.yml
│ ├── build-rpc-video.yml
│ ├── golangci-lint.yml
│ └── reviewdog.yml
├── .gitignore
├── common
├── cron
│ ├── syncUserInfoCache.go
│ └── syncVideoInfoCache.go
├── error
│ ├── apiErr
│ │ ├── base.go
│ │ └── code.go
│ └── rpcErr
│ │ └── rpcErr.go
├── model
│ ├── contact.go
│ ├── user.go
│ └── video.go
├── mq
│ ├── addCacheValue.go
│ ├── delCache.go
│ ├── loseFriends.go
│ └── tryMakeFriends.go
├── oss
│ └── aliyun.go
└── utils
│ ├── convert.go
│ ├── genCacheKey.go
│ ├── jwt.go
│ └── uuid.go
├── deploy
└── config
│ ├── filebeat.yml
│ ├── go-stash.yml
│ ├── modd.DockerFile
│ └── prometheus.yml
├── docker-compose-arm64.yml
├── docker-compose.yml
├── docs
├── 0113第一次会议.md
├── docker-compose.md
├── 业务架构图.drawio
└── 项目架构图.drawio
├── go.mod
├── go.sum
├── modd.conf
├── readme.md
├── script
├── genCode.bat
└── genCode.sh
└── service
├── cron
├── Dockerfile
├── cron.go
├── etc
│ └── cron.yaml.example
└── internal
│ ├── config
│ └── config.go
│ ├── scheduler
│ └── asynq.go
│ └── svc
│ └── serviceContext.go
├── http
├── Dockerfile
├── apis
│ ├── contact.api
│ ├── dto
│ │ └── dto.api
│ ├── user.api
│ └── video.api
├── app.api
├── app.go
├── etc
│ └── app.yaml.example
└── internal
│ ├── config
│ └── config.go
│ ├── handler
│ ├── contact
│ │ ├── getFriendListHandler.go
│ │ ├── getHistoryMessageHandler.go
│ │ └── sendMessageHandler.go
│ ├── routes.go
│ ├── user
│ │ ├── fansListHandler.go
│ │ ├── followHandler.go
│ │ ├── followListHandler.go
│ │ ├── getUserInfoHandler.go
│ │ ├── loginHandler.go
│ │ └── registerHandler.go
│ └── video
│ │ ├── commentListHandler.go
│ │ ├── commentVideoHandler.go
│ │ ├── favoriteListHandler.go
│ │ ├── favoriteVideoHandler.go
│ │ ├── getVideoListHandler.go
│ │ ├── publishVideoHandler.go
│ │ └── publishedListHandler.go
│ ├── logic
│ ├── contact
│ │ ├── getFriendListLogic.go
│ │ ├── getHistoryMessageLogic.go
│ │ └── sendMessageLogic.go
│ ├── user
│ │ ├── fansListLogic.go
│ │ ├── followListLogic.go
│ │ ├── followLogic.go
│ │ ├── getUserInfoLogic.go
│ │ ├── loginLogic.go
│ │ └── registerLogic.go
│ └── video
│ │ ├── commentListLogic.go
│ │ ├── commentVideoLogic.go
│ │ ├── favoriteListLogic.go
│ │ ├── favoriteVideoLogic.go
│ │ ├── getVideoListLogic.go
│ │ ├── publishVideoLogic.go
│ │ └── publishedListLogic.go
│ ├── middleware
│ └── authMiddleware.go
│ ├── svc
│ └── serviceContext.go
│ └── types
│ └── types.go
├── mq
├── Dockerfile
├── etc
│ └── mq.yaml.example
├── internal
│ ├── config
│ │ └── config.go
│ ├── handler
│ │ ├── addCacheValue.go
│ │ ├── asynq.go
│ │ ├── delCache.go
│ │ ├── loseFriends.go
│ │ ├── syncUserInfoCache.go
│ │ ├── syncVideoInfoCache.go
│ │ └── tryMakeFriends.go
│ └── svc
│ │ └── serviceContext.go
└── mq.go
└── rpc
├── contact
├── Dockerfile
├── contact.go
├── contact.proto
├── contactclient
│ └── contact.go
├── etc
│ └── contact.yaml.example
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── createMessageLogic.go
│ │ ├── getFriendsListLogic.go
│ │ ├── getLatestMessageLogic.go
│ │ ├── getMessageListLogic.go
│ │ ├── loseFriendsLogic.go
│ │ └── makeFriendsLogic.go
│ ├── server
│ │ └── contactServer.go
│ └── svc
│ │ └── serviceContext.go
└── types
│ └── contact
│ ├── contact.pb.go
│ └── contact_grpc.pb.go
├── user
├── Dockerfile
├── etc
│ └── user.yaml.example
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── createUserLogic.go
│ │ ├── followUserLogic.go
│ │ ├── getFansListLogic.go
│ │ ├── getFollowListLogic.go
│ │ ├── getUserByIdLogic.go
│ │ ├── getUserByNameLogic.go
│ │ ├── isFollowLogic.go
│ │ ├── unFollowUserLogic.go
│ │ └── updateUserLogic.go
│ ├── server
│ │ └── userServer.go
│ └── svc
│ │ └── serviceContext.go
├── types
│ └── user
│ │ ├── user.pb.go
│ │ └── user_grpc.pb.go
├── user.go
├── user.proto
└── userclient
│ └── user.go
└── video
├── Dockerfile
├── etc
└── video.yaml.example
├── internal
├── config
│ └── config.go
├── logic
│ ├── commentVideoLogic.go
│ ├── deleteVideoCommentLogic.go
│ ├── favoriteVideoLogic.go
│ ├── getCommentInfoLogic.go
│ ├── getCommentListLogic.go
│ ├── getFavoriteVideoListLogic.go
│ ├── getVideoListByAuthorLogic.go
│ ├── getVideoListLogic.go
│ ├── isFavoriteVideoLogic.go
│ ├── publishVideoLogic.go
│ ├── unFavoriteVideoLogic.go
│ └── updateVideoLogic.go
├── server
│ └── videoServer.go
└── svc
│ └── serviceContext.go
├── types
└── video
│ ├── video.pb.go
│ └── video_grpc.pb.go
├── video.go
├── video.proto
└── videoclient
└── video.go
/.github/workflows/build-cron.yml:
--------------------------------------------------------------------------------
1 | name: build-cron
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths:
7 | - '.github/workflows/build-cron.yml'
8 | - 'service/cron/**'
9 | - 'common/**'
10 | # pull_request:
11 | # branches: [ main ]
12 | # paths:
13 | # - '.github/workflows/cron.yml'
14 | # - 'service/cron/**'
15 | # - 'common/**'
16 | # types: [closed]
17 |
18 | env:
19 | CONTAINER: cron
20 |
21 | jobs:
22 | build-base:
23 | name: Build Base
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: checkout
27 | uses: actions/checkout@v3.3.0
28 |
29 | - name: Docker meta
30 | id: meta
31 | uses: docker/metadata-action@v4.3.0
32 | with:
33 | # list of Docker images to use as base name for tags
34 | images: registry.cn-hongkong.aliyuncs.com/h68u-tiktok-microservice/${{ env.CONTAINER }}
35 | # generate Docker tags based on the following events/attributes
36 | tags: |
37 | type=schedule
38 | type=ref,event=branch
39 | type=ref,event=pr
40 | type=semver,pattern={{version}}
41 | type=semver,pattern={{major}}.{{minor}}
42 | type=semver,pattern={{major}}
43 | type=sha
44 |
45 | - name: Set up QEMU
46 | uses: docker/setup-qemu-action@v2.1.0
47 |
48 | - name: Set up Docker Buildx
49 | uses: docker/setup-buildx-action@v2.4.1
50 |
51 | - name: Login to Aliyun Registry
52 | uses: docker/login-action@v2.1.0
53 | with:
54 | registry: registry.cn-hongkong.aliyuncs.com
55 | username: ${{ secrets.ALIYUN_REGISTRY_USERNAME }}
56 | password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}
57 |
58 | - name: Build and Push
59 | uses: docker/build-push-action@v4.0.0
60 | with:
61 | context: .
62 | file: service/cron/Dockerfile
63 | push: true
64 | tags: ${{ steps.meta.outputs.tags }}
65 |
66 | - name: Webhook
67 | run: |
68 | curl '${{ secrets.WEBHOOK_URL }}&target=${{ env.CONTAINER }}'
--------------------------------------------------------------------------------
/.github/workflows/build-http-app.yml:
--------------------------------------------------------------------------------
1 | name: build-http-app
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths:
7 | - '.github/workflows/build-http-app.yml'
8 | - 'service/http/**'
9 | - 'common/**'
10 | # pull_request:
11 | # branches: [ main ]
12 | # paths:
13 | # - '.github/workflows/build-http-app.yml'
14 | # - 'service/http/**'
15 | # - 'common/**'
16 | # types: [closed]
17 |
18 | env:
19 | CONTAINER: http-app
20 |
21 | jobs:
22 | build-base:
23 | name: Build Base
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v3.3.0
28 |
29 | - name: Docker meta
30 | id: meta
31 | uses: docker/metadata-action@v4.3.0
32 | with:
33 | # list of Docker images to use as base name for tags
34 | images: registry.cn-hongkong.aliyuncs.com/h68u-tiktok-microservice/${{ env.CONTAINER }}
35 | # generate Docker tags based on the following events/attributes
36 | tags: |
37 | type=schedule
38 | type=ref,event=branch
39 | type=ref,event=pr
40 | type=semver,pattern={{version}}
41 | type=semver,pattern={{major}}.{{minor}}
42 | type=semver,pattern={{major}}
43 | type=sha
44 |
45 | - name: Docker Setup QEMU
46 | uses: docker/setup-qemu-action@v2.1.0
47 |
48 | - name: Set up Docker Buildx
49 | uses: docker/setup-buildx-action@v2.4.1
50 |
51 | - name: Login to Aliyun Registry
52 | uses: docker/login-action@v2.1.0
53 | with:
54 | registry: registry.cn-hongkong.aliyuncs.com
55 | username: ${{ secrets.ALIYUN_REGISTRY_USERNAME }}
56 | password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}
57 |
58 | - name: Build and Push
59 | uses: docker/build-push-action@v4.0.0
60 | with:
61 | context: .
62 | file: service/http/Dockerfile
63 | push: true
64 | tags: ${{ steps.meta.outputs.tags }}
65 |
66 | - name: Webhook
67 | run: |
68 | curl '${{ secrets.WEBHOOK_URL }}&target=${{ env.CONTAINER }}'
--------------------------------------------------------------------------------
/.github/workflows/build-mq.yml:
--------------------------------------------------------------------------------
1 | name: build-mq
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths:
7 | - '.github/workflows/build-mq.yml'
8 | - 'service/mq/**'
9 | - 'common/**'
10 | # pull_request:
11 | # branches: [ main ]
12 | # paths:
13 | # - '.github/workflows/mq.yml'
14 | # - 'service/mq/**'
15 | # - 'common/**'
16 | # types: [closed]
17 |
18 | env:
19 | CONTAINER: mq
20 |
21 | jobs:
22 | build-base:
23 | name: Build Base
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: checkout
27 | uses: actions/checkout@v3.3.0
28 |
29 | - name: Docker meta
30 | id: meta
31 | uses: docker/metadata-action@v4.3.0
32 | with:
33 | # list of Docker images to use as base name for tags
34 | images: registry.cn-hongkong.aliyuncs.com/h68u-tiktok-microservice/${{ env.CONTAINER }}
35 | # generate Docker tags based on the following events/attributes
36 | tags: |
37 | type=schedule
38 | type=ref,event=branch
39 | type=ref,event=pr
40 | type=semver,pattern={{version}}
41 | type=semver,pattern={{major}}.{{minor}}
42 | type=semver,pattern={{major}}
43 | type=sha
44 |
45 | - name: Set up QEMU
46 | uses: docker/setup-qemu-action@v2.1.0
47 |
48 | - name: Set up Docker Buildx
49 | uses: docker/setup-buildx-action@v2.4.1
50 |
51 | - name: Login to Aliyun Registry
52 | uses: docker/login-action@v2.1.0
53 | with:
54 | registry: registry.cn-hongkong.aliyuncs.com
55 | username: ${{ secrets.ALIYUN_REGISTRY_USERNAME }}
56 | password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}
57 |
58 | - name: Build and Push
59 | uses: docker/build-push-action@v4.0.0
60 | with:
61 | context: .
62 | file: service/mq/Dockerfile
63 | push: true
64 | tags: ${{ steps.meta.outputs.tags }}
65 |
66 | - name: Webhook
67 | run: |
68 | curl '${{ secrets.WEBHOOK_URL }}&target=${{ env.CONTAINER }}'
--------------------------------------------------------------------------------
/.github/workflows/build-rpc-contact.yml:
--------------------------------------------------------------------------------
1 | name: build-rpc-contact
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths:
7 | - '.github/workflows/build-rpc-contact.yml'
8 | - 'service/rpc/contact/**'
9 | - 'common/**'
10 | # pull_request:
11 | # branches: [ main ]
12 | # paths:
13 | # - '.github/workflows/build-rpc-contact.yml'
14 | # - 'service/rpc/contact/**'
15 | # - 'common/**'
16 | # types: [closed]
17 |
18 | env:
19 | CONTAINER: rpc-contact
20 |
21 | jobs:
22 | build-base:
23 | name: Build Base
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: checkout
27 | uses: actions/checkout@v3.3.0
28 |
29 | - name: Docker meta
30 | id: meta
31 | uses: docker/metadata-action@v4.3.0
32 | with:
33 | # list of Docker images to use as base name for tags
34 | images: registry.cn-hongkong.aliyuncs.com/h68u-tiktok-microservice/${{ env.CONTAINER }}
35 | # generate Docker tags based on the following events/attributes
36 | tags: |
37 | type=schedule
38 | type=ref,event=branch
39 | type=ref,event=pr
40 | type=semver,pattern={{version}}
41 | type=semver,pattern={{major}}.{{minor}}
42 | type=semver,pattern={{major}}
43 | type=sha
44 |
45 | - name: Set up QEMU
46 | uses: docker/setup-qemu-action@v2.1.0
47 |
48 | - name: Set up Docker Buildx
49 | uses: docker/setup-buildx-action@v2.4.1
50 |
51 | - name: Login to Aliyun Registry
52 | uses: docker/login-action@v2.1.0
53 | with:
54 | registry: registry.cn-hongkong.aliyuncs.com
55 | username: ${{ secrets.ALIYUN_REGISTRY_USERNAME }}
56 | password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}
57 |
58 | - name: Build and Push
59 | uses: docker/build-push-action@v4.0.0
60 | with:
61 | context: .
62 | file: service/rpc/contact/Dockerfile
63 | push: true
64 | tags: ${{ steps.meta.outputs.tags }}
65 |
66 | - name: Webhook
67 | run: |
68 | curl '${{ secrets.WEBHOOK_URL }}&target=${{ env.CONTAINER }}'
--------------------------------------------------------------------------------
/.github/workflows/build-rpc-user.yml:
--------------------------------------------------------------------------------
1 | name: build-rpc-user
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths:
7 | - '.github/workflows/build-rpc-user.yml'
8 | - 'service/rpc/user/**'
9 | - 'common/**'
10 | # pull_request:
11 | # branches: [ main ]
12 | # paths:
13 | # - '.github/workflows/build-rpc-user.yml'
14 | # - 'service/rpc/user/**'
15 | # - 'common/**'
16 | # types: [closed]
17 |
18 | env:
19 | CONTAINER: rpc-user
20 |
21 | jobs:
22 | build-base:
23 | name: Build Base
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: checkout
27 | uses: actions/checkout@v3.3.0
28 |
29 | - name: Docker meta
30 | id: meta
31 | uses: docker/metadata-action@v4.3.0
32 | with:
33 | # list of Docker images to use as base name for tags
34 | images: registry.cn-hongkong.aliyuncs.com/h68u-tiktok-microservice/${{ env.CONTAINER }}
35 | # generate Docker tags based on the following events/attributes
36 | tags: |
37 | type=schedule
38 | type=ref,event=branch
39 | type=ref,event=pr
40 | type=semver,pattern={{version}}
41 | type=semver,pattern={{major}}.{{minor}}
42 | type=semver,pattern={{major}}
43 | type=sha
44 |
45 | - name: Set up QEMU
46 | uses: docker/setup-qemu-action@v2.1.0
47 |
48 | - name: Set up Docker Buildx
49 | uses: docker/setup-buildx-action@v2.4.1
50 |
51 | - name: Login to Aliyun Registry
52 | uses: docker/login-action@v2.1.0
53 | with:
54 | registry: registry.cn-hongkong.aliyuncs.com
55 | username: ${{ secrets.ALIYUN_REGISTRY_USERNAME }}
56 | password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}
57 |
58 | - name: Build and Push
59 | uses: docker/build-push-action@v4.0.0
60 | with:
61 | context: .
62 | file: service/rpc/user/Dockerfile
63 | push: true
64 | tags: ${{ steps.meta.outputs.tags }}
65 |
66 | - name: Webhook
67 | run: |
68 | curl '${{ secrets.WEBHOOK_URL }}&target=${{ env.CONTAINER }}'
--------------------------------------------------------------------------------
/.github/workflows/build-rpc-video.yml:
--------------------------------------------------------------------------------
1 | name: build-rpc-video
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | paths:
7 | - '.github/workflows/build-rpc-video.yml'
8 | - 'service/rpc/video/**'
9 | - 'common/**'
10 | # pull_request:
11 | # branches: [ main ]
12 | # paths:
13 | # - '.github/workflows/build-rpc-video.yml'
14 | # - 'service/rpc/video/**'
15 | # - 'common/**'
16 | # types: [closed]
17 |
18 | env:
19 | CONTAINER: rpc-video
20 |
21 | jobs:
22 | build-base:
23 | name: Build Base
24 | runs-on: ubuntu-latest
25 | steps:
26 | - name: checkout
27 | uses: actions/checkout@v3.3.0
28 |
29 | - name: Docker meta
30 | id: meta
31 | uses: docker/metadata-action@v4.3.0
32 | with:
33 | # list of Docker images to use as base name for tags
34 | images: registry.cn-hongkong.aliyuncs.com/h68u-tiktok-microservice/${{ env.CONTAINER }}
35 | # generate Docker tags based on the following events/attributes
36 | tags: |
37 | type=schedule
38 | type=ref,event=branch
39 | type=ref,event=pr
40 | type=semver,pattern={{version}}
41 | type=semver,pattern={{major}}.{{minor}}
42 | type=semver,pattern={{major}}
43 | type=sha
44 |
45 | - name: Set up QEMU
46 | uses: docker/setup-qemu-action@v2.1.0
47 |
48 | - name: Set up Docker Buildx
49 | uses: docker/setup-buildx-action@v2.4.1
50 |
51 | - name: Login to Aliyun Registry
52 | uses: docker/login-action@v2.1.0
53 | with:
54 | registry: registry.cn-hongkong.aliyuncs.com
55 | username: ${{ secrets.ALIYUN_REGISTRY_USERNAME }}
56 | password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}
57 |
58 | - name: Build and Push
59 | uses: docker/build-push-action@v4.0.0
60 | with:
61 | context: .
62 | file: service/rpc/video/Dockerfile
63 | push: true
64 | tags: ${{ steps.meta.outputs.tags }}
65 |
66 | - name: Webhook
67 | run: |
68 | curl '${{ secrets.WEBHOOK_URL }}&target=${{ env.CONTAINER }}'
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yml:
--------------------------------------------------------------------------------
1 | name: golangci-lint
2 | on:
3 | push:
4 | branches:
5 | - master
6 | - main
7 |
8 | jobs:
9 | golangci:
10 | name: lint
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/setup-go@v3
14 | with:
15 | go-version: '1.19'
16 | - uses: actions/checkout@v3
17 | - name: golangci-lint
18 | uses: golangci/golangci-lint-action@v3
19 | with:
20 | version: latest
21 | working-directory: ./
--------------------------------------------------------------------------------
/.github/workflows/reviewdog.yml:
--------------------------------------------------------------------------------
1 | name: reviewdog
2 | on: [pull_request]
3 | jobs:
4 | golangci-lint:
5 | name: runner /golangci-lint
6 | runs-on: ubuntu-latest
7 | steps:
8 | - name: Check out code into the Go module directory
9 | uses: actions/checkout@v3
10 |
11 | - name: golangci-lint
12 | uses: reviewdog/action-golangci-lint@v2
13 | with:
14 | workdir: ./
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
17 | *.yaml
18 | .idea/
19 | deploy/data/*
20 | deploy/build/*
--------------------------------------------------------------------------------
/common/cron/syncUserInfoCache.go:
--------------------------------------------------------------------------------
1 | package cron
2 |
3 | import (
4 | "github.com/hibiken/asynq"
5 | )
6 |
7 | const TypeSyncUserInfoCache = "cache:userInfo:sync"
8 |
9 | func NewSyncUserInfoCacheTask() *asynq.Task {
10 | return asynq.NewTask(TypeSyncUserInfoCache, nil)
11 | }
12 |
--------------------------------------------------------------------------------
/common/cron/syncVideoInfoCache.go:
--------------------------------------------------------------------------------
1 | package cron
2 |
3 | import (
4 | "github.com/hibiken/asynq"
5 | )
6 |
7 | const TypeSyncVideoInfoCache = "cache:videoInfo:sync"
8 |
9 | func NewSyncVideoInfoCacheTask() *asynq.Task {
10 | return asynq.NewTask(TypeSyncVideoInfoCache, nil)
11 | }
12 |
--------------------------------------------------------------------------------
/common/error/apiErr/base.go:
--------------------------------------------------------------------------------
1 | package apiErr
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/zeromicro/go-zero/core/service"
7 | "github.com/zeromicro/go-zero/core/trace"
8 | "github.com/zeromicro/go-zero/rest"
9 | )
10 |
11 | type ApiErr struct {
12 | Code int64
13 | Msg string
14 | }
15 |
16 | type ErrInternal struct {
17 | ApiErr
18 | Details string
19 | TraceId string
20 | }
21 |
22 | type ErrResponse struct {
23 | Code int64 `json:"status_code"`
24 | Msg string `json:"status_msg"`
25 | }
26 |
27 | func newError(code int64, msg string) ApiErr {
28 | return ApiErr{
29 | Code: code,
30 | Msg: msg,
31 | }
32 | }
33 |
34 | func (e ApiErr) Error() string {
35 | return e.Msg
36 | }
37 |
38 | // WithDetails 在基础错误上追加详细信息,例如:密码错误,密码长度不足6位
39 | func (e ApiErr) WithDetails(detail string) ApiErr {
40 | return ApiErr{
41 | Code: e.Code,
42 | Msg: e.Msg + ": " + detail,
43 | }
44 | }
45 |
46 | // InternalError 用于返回内部错误,例如RPC连接故障,数据库报错等,
47 | // 该方法会自动从上下文中获取 traceId,
48 | // debugDetail 用于调试,生成环境不会返回给客户端,
49 | // 当 Mode 为 service.ProMode 仅返回 traceId,
50 | // 当 Mode 为 service.DevMode 同时返回 debugDetail 和 traceId,帮助调试
51 | func InternalError(ctx context.Context, debugDetail string) ErrInternal {
52 | return ErrInternal{
53 | ApiErr: ServerInternal,
54 | Details: debugDetail,
55 | TraceId: trace.TraceIDFromContext(ctx),
56 | }
57 | }
58 |
59 | func (e ApiErr) Response() *ErrResponse {
60 | return &ErrResponse{
61 | Code: e.Code,
62 | Msg: e.Msg,
63 | }
64 | }
65 |
66 | func (e ErrInternal) Response(cfg rest.RestConf) *ErrResponse {
67 | switch cfg.Mode {
68 | case service.ProMode:
69 | return &ErrResponse{
70 | Code: e.Code,
71 | Msg: fmt.Sprintf("%s,请求 ID: %s", e.Msg, e.TraceId),
72 | }
73 | default:
74 | return &ErrResponse{
75 | Code: e.Code,
76 | Msg: fmt.Sprintf("%s, Details: %s, TraceId: %s", e.Msg, e.Details, e.TraceId),
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/common/error/apiErr/code.go:
--------------------------------------------------------------------------------
1 | package apiErr
2 |
3 | // 本错误码参照了HTTP状态码的语义,方便识别错误类型
4 |
5 | //分类 分类描述
6 | //1** 信息,服务器收到请求,需要请求者继续执行操作
7 | //2** 成功,操作被成功接收并处理
8 | //3** 重定向,需要进一步的操作以完成请求
9 | //4** 客户端错误,请求包含语法错误或无法完成请求
10 | //5** 服务器错误,服务器在处理请求的过程中发生了错误
11 |
12 | // Success 根据官方文档 0 代表成功
13 | var Success = newError(0, "Success")
14 |
15 | //// 200 OK
16 | //var (
17 | // Success = newError(200, "Success")
18 | //)
19 |
20 | // 400 BAD REQUEST
21 | var (
22 | InvalidParams = newError(40001, "参数错误")
23 | PasswordIncorrect = newError(40002, "密码错误")
24 | CommentActionUnknown = newError(40003, "未知的评论操作")
25 | FavouriteActionUnknown = newError(40004, "未知的收藏操作")
26 | FileUploadFailed = newError(40005, "文件上传失败")
27 | FileIsNotVideo = newError(40006, "文件不是视频")
28 | MessageActionUnknown = newError(40007, "未知的消息操作")
29 | )
30 |
31 | // 401 WITHOUT PERMISSION
32 | var (
33 | NotLogin = newError(40101, "用户未登录")
34 | InvalidToken = newError(40102, "无效的Token")
35 | )
36 |
37 | // 403 ILLEGAL OPERATION
38 | var (
39 | PermissionDenied = newError(40301, "权限不足")
40 | IllegalOperation = newError(40302, "非法操作")
41 | )
42 |
43 | // 404 NOT FOUND
44 | var (
45 | UserNotFound = newError(40401, "用户不存在")
46 | )
47 |
48 | // 409 CONFLICT
49 | var (
50 | UserAlreadyExist = newError(40901, "用户已存在")
51 | AlreadyFollowed = newError(40904, "当前已关注")
52 | NotFollowed = newError(40905, "当前未关注")
53 | )
54 |
55 | // 500 INTERNAL ERROR
56 | var (
57 | ServerInternal = newError(50001, "服务器内部错误")
58 | GenerateTokenFailed = newError(50002, "生成Token失败")
59 | )
60 |
--------------------------------------------------------------------------------
/common/error/rpcErr/rpcErr.go:
--------------------------------------------------------------------------------
1 | package rpcErr
2 |
3 | import (
4 | "google.golang.org/grpc/codes"
5 | "google.golang.org/grpc/status"
6 | )
7 |
8 | const (
9 | DataBaseErrorCode codes.Code = iota + 1000
10 | CacheErrorCode
11 | PasswordEncryptFailedCode
12 | MQErrorCode
13 | )
14 |
15 | const (
16 | UserAlreadyExistCode codes.Code = iota + 2000
17 | UserNotExistCode
18 | )
19 |
20 | const (
21 | CommentNotExistCode codes.Code = iota + 3000
22 | )
23 |
24 | var errCodeMap = map[codes.Code]string{
25 | DataBaseErrorCode: "数据库错误",
26 | CacheErrorCode: "缓存错误",
27 | PasswordEncryptFailedCode: "密码加密失败",
28 | MQErrorCode: "消息队列错误",
29 |
30 | UserAlreadyExistCode: "用户已存在",
31 | UserNotExistCode: "用户不存在",
32 |
33 | CommentNotExistCode: "评论不存在",
34 | }
35 |
36 | var (
37 | UserAlreadyExist = NewRpcError(UserAlreadyExistCode)
38 | DataBaseError = NewRpcError(DataBaseErrorCode)
39 | CacheError = NewRpcError(CacheErrorCode)
40 | MQError = NewRpcError(MQErrorCode)
41 | PassWordEncryptFailed = NewRpcError(PasswordEncryptFailedCode)
42 | UserNotExist = NewRpcError(UserNotExistCode)
43 | CommentNotExist = NewRpcError(CommentNotExistCode)
44 | )
45 |
46 | type RpcError struct {
47 | Code codes.Code `json:"code"`
48 | Message string `json:"message"`
49 | }
50 |
51 | func NewRpcError(code codes.Code) *RpcError {
52 | return &RpcError{
53 | Code: code,
54 | Message: errCodeMap[code],
55 | }
56 | }
57 |
58 | func Is(err error, target *RpcError) bool {
59 |
60 | if err == nil {
61 | return false
62 | }
63 | s, _ := status.FromError(err)
64 |
65 | return s.Code() == target.Code
66 | }
67 |
68 | func (e *RpcError) Error() string {
69 | return e.Message
70 | }
71 |
--------------------------------------------------------------------------------
/common/model/contact.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "gorm.io/gorm"
4 |
5 | type Message struct {
6 | gorm.Model
7 | FromId int64 `gorm:"index"`
8 | ToUserId int64 `gorm:"index"`
9 | Content string `gorm:"not null"`
10 | }
11 |
12 | type Friend struct {
13 | gorm.Model
14 | UserId int64 `gorm:"index"`
15 | FriendId int64 `gorm:"index"`
16 | }
17 |
--------------------------------------------------------------------------------
/common/model/user.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "gorm.io/gorm"
4 |
5 | type User struct {
6 | gorm.Model
7 | Username string `gorm:"not null;unique;index"`
8 | Password string `gorm:"not null"`
9 | FollowCount int64
10 | FanCount int64
11 |
12 | // many to many
13 | Follows []*User `gorm:"many2many:follows;"` // 关注列表
14 | Fans []*User `gorm:"many2many:follows;joinForeignKey:follow_id"` // 粉丝列表
15 | }
16 |
17 | const (
18 | PopularUserStandard = 1000 // 拥有超过 1000 个粉丝的用户成为大V,有特殊处理
19 | )
20 |
21 | func IsPopularUser(fanCount int64) bool {
22 | return fanCount >= PopularUserStandard
23 | }
24 |
--------------------------------------------------------------------------------
/common/model/video.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "gorm.io/gorm"
4 |
5 | type Video struct {
6 | gorm.Model
7 | AuthorId int64 `gorm:"not null;index"`
8 | Title string `gorm:"not null;index"`
9 | PlayUrl string `gorm:"not null"`
10 | CoverUrl string `gorm:"not null"`
11 | FavoriteCount int64 `gorm:"column:favorite_count;"`
12 | CommentCount int64
13 |
14 | // has many
15 | Comments []Comment
16 | Favorites []Favorite
17 | }
18 |
19 | const (
20 | PopularVideoStandard = 1000 // 拥有超过 1000 个赞或 1000 个评论的视频成为热门视频,有特殊处理
21 | )
22 |
23 | func IsPopularVideo(favoriteCount, commentCount int64) bool {
24 | return favoriteCount >= PopularVideoStandard || commentCount >= PopularVideoStandard
25 | }
26 |
27 | type Comment struct {
28 | gorm.Model
29 | UserId int64
30 | VideoId int64
31 | Content string `gorm:"not null"`
32 | }
33 |
34 | type Favorite struct {
35 | gorm.Model
36 | UserId int64 `gorm:"column:user_id;"`
37 | VideoId int64 `gorm:"column:video_id;"`
38 |
39 | // belongs to
40 | Video Video
41 | }
42 |
--------------------------------------------------------------------------------
/common/mq/addCacheValue.go:
--------------------------------------------------------------------------------
1 | package mq
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/hibiken/asynq"
6 | "github.com/pkg/errors"
7 | )
8 |
9 | const TypeAddCacheValue = "cache:add"
10 |
11 | type AddCacheValuePayload struct {
12 | Key string
13 | Field string
14 | Add int64
15 | }
16 |
17 | func NewAddCacheValueTask(key string, field string, add int64) (*asynq.Task, error) {
18 | payload, err := json.Marshal(AddCacheValuePayload{Key: key, Field: field, Add: add})
19 | if err != nil {
20 | return nil, errors.Wrapf(err, "failed to marshal payload for task %q", TypeAddCacheValue)
21 | }
22 | return asynq.NewTask(TypeAddCacheValue, payload), nil
23 | }
24 |
--------------------------------------------------------------------------------
/common/mq/delCache.go:
--------------------------------------------------------------------------------
1 | package mq
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/hibiken/asynq"
6 | "github.com/pkg/errors"
7 | )
8 |
9 | const TypeDelCache = "cache:del"
10 |
11 | type DelCachePayload struct {
12 | Key string
13 | }
14 |
15 | func NewDelCacheTask(key string) (*asynq.Task, error) {
16 | payload, err := json.Marshal(DelCachePayload{Key: key})
17 | if err != nil {
18 | return nil, errors.Wrapf(err, "failed to marshal payload for task %q", TypeDelCache)
19 | }
20 | return asynq.NewTask(TypeDelCache, payload), nil
21 | }
22 |
--------------------------------------------------------------------------------
/common/mq/loseFriends.go:
--------------------------------------------------------------------------------
1 | package mq
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/hibiken/asynq"
6 | "github.com/pkg/errors"
7 | )
8 |
9 | const TypeLoseFriends = "friend:lose"
10 |
11 | type LoseFriendsPayload struct {
12 | UserAId int64
13 | UserBId int64
14 | }
15 |
16 | func NewLoseFriendsTask(userAId int64, userBId int64) (*asynq.Task, error) {
17 | payload, err := json.Marshal(LoseFriendsPayload{UserAId: userAId, UserBId: userBId})
18 | if err != nil {
19 | return nil, errors.Wrapf(err, "failed to marshal payload for task %q", TypeLoseFriends)
20 | }
21 | return asynq.NewTask(TypeLoseFriends, payload), nil
22 | }
23 |
--------------------------------------------------------------------------------
/common/mq/tryMakeFriends.go:
--------------------------------------------------------------------------------
1 | package mq
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/hibiken/asynq"
6 | "github.com/pkg/errors"
7 | )
8 |
9 | const TypeTryMakeFriends = "friend:make"
10 |
11 | type TryMakeFriendsPayload struct {
12 | UserAId int64
13 | UserBId int64
14 | }
15 |
16 | func NewTryMakeFriendsTask(userAId int64, userBId int64) (*asynq.Task, error) {
17 | payload, err := json.Marshal(TryMakeFriendsPayload{UserAId: userAId, UserBId: userBId})
18 | if err != nil {
19 | return nil, errors.Wrapf(err, "failed to marshal payload for task %q", TypeTryMakeFriends)
20 | }
21 | return asynq.NewTask(TypeTryMakeFriends, payload), nil
22 | }
23 |
--------------------------------------------------------------------------------
/common/oss/aliyun.go:
--------------------------------------------------------------------------------
1 | package oss
2 |
3 | import (
4 | "fmt"
5 | "github.com/aliyun/aliyun-oss-go-sdk/oss"
6 | "mime/multipart"
7 | )
8 |
9 | //TODO:重构成接口类型,每种存储介质只需实现对应的方法
10 |
11 | type AliyunCfg struct {
12 | Endpoint string
13 | AccessKeyID string
14 | AccessKeySecret string
15 | BucketName string
16 | }
17 |
18 | func AliyunInit(AliyunCfg AliyunCfg) *oss.Client {
19 | client, err := oss.New(AliyunCfg.Endpoint, AliyunCfg.AccessKeyID, AliyunCfg.AccessKeySecret)
20 | if err != nil {
21 | panic(err)
22 | }
23 | return client
24 | }
25 |
26 | func UploadVideoToOss(AliyunClient *oss.Client, bucketName string, objectName string, reader multipart.File) (bool, error) {
27 | bucket, err := AliyunClient.Bucket(bucketName)
28 | if err != nil {
29 | return false, err
30 | }
31 | err = bucket.PutObject(objectName, reader)
32 | if err != nil {
33 | fmt.Println(err)
34 | return false, err
35 | }
36 | return true, nil
37 | }
38 |
39 | func GetOssVideoUrlAndImgUrl(AliyunCfg AliyunCfg, objectName string) (string, string) {
40 | url := "https://" + AliyunCfg.BucketName + "." + AliyunCfg.Endpoint + "/" + objectName
41 | return url, url + "?x-oss-process=video/snapshot,t_0,f_jpg,w_0,h_0,m_fast,ar_auto"
42 | }
43 |
--------------------------------------------------------------------------------
/common/utils/convert.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "github.com/zeromicro/go-zero/core/logx"
5 | "strconv"
6 | )
7 |
8 | func Str2Int64(str string) int64 {
9 | i, err := strconv.ParseInt(str, 10, 64)
10 | if err != nil {
11 | logx.Error(err)
12 | }
13 | return i
14 | }
15 |
16 | func Int642Str(i int64) string {
17 | return strconv.FormatInt(i, 10)
18 | }
19 |
--------------------------------------------------------------------------------
/common/utils/genCacheKey.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | func GenFavoriteVideoCacheKey(userId, videoId int64) string {
4 | return "favorite_video_" + Int642Str(userId) + "_" + Int642Str(videoId)
5 | }
6 |
7 | func GenFollowUserCacheKey(userId, followUserId int64) string {
8 | return "follow_user_" + Int642Str(userId) + "_" + Int642Str(followUserId)
9 | }
10 |
11 | func GenPopUserListCacheKey() string {
12 | return "pop_user_list"
13 | }
14 |
15 | func GenUserInfoCacheKey(userId int64) string {
16 | return "user_info_" + Int642Str(userId)
17 | }
18 |
19 | func GenPopVideoListCacheKey() string {
20 | return "pop_video_list"
21 | }
22 |
23 | func GenVideoInfoCacheKey(videoId int64) string {
24 | return "video_info_" + Int642Str(videoId)
25 | }
26 |
--------------------------------------------------------------------------------
/common/utils/jwt.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "github.com/golang-jwt/jwt"
5 | "time"
6 | )
7 |
8 | type Claims struct {
9 | UserId int64 `json:"userId"`
10 | jwt.StandardClaims
11 | }
12 |
13 | // CreateToken 签发用户Token
14 | func CreateToken(userId int64, AccessSecret string, AccessExpire int64) (string, error) {
15 | claims := Claims{
16 | UserId: userId,
17 | StandardClaims: jwt.StandardClaims{
18 | ExpiresAt: time.Now().Unix() + AccessExpire,
19 | IssuedAt: time.Now().Unix(),
20 | Issuer: "tiktok-app",
21 | },
22 | }
23 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
24 | token, err := tokenClaims.SignedString([]byte(AccessSecret))
25 | return token, err
26 | }
27 |
28 | // ValidToken 验证用户token
29 | // bool: 是否过期 default: true 过期
30 | // error: 解析是否成功 default: nil
31 | func ValidToken(token string, AccessSecret string) (bool, error) {
32 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
33 | return []byte(AccessSecret), nil
34 | })
35 | if tokenClaims != nil {
36 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
37 | expiresTime := claims.ExpiresAt
38 | now := time.Now().Unix()
39 | if now > expiresTime {
40 | //token过期了
41 | return true, nil
42 | } else {
43 | return false, nil
44 | }
45 | }
46 | }
47 | return true, err
48 | }
49 |
50 | // GetUserIDFormToken 从token中获取用户id
51 | func GetUserIDFormToken(token string, AccessSecret string) (int64, error) {
52 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
53 | return []byte(AccessSecret), nil
54 | })
55 | if tokenClaims != nil {
56 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
57 | return claims.UserId, nil
58 | }
59 | }
60 | return -1, err
61 | }
62 |
--------------------------------------------------------------------------------
/common/utils/uuid.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "github.com/google/uuid"
4 |
5 | func GetUUID() string {
6 | return uuid.New().String()
7 | }
8 |
--------------------------------------------------------------------------------
/deploy/config/filebeat.yml:
--------------------------------------------------------------------------------
1 | filebeat.inputs:
2 | - type: log
3 | enabled: true
4 | paths:
5 | - /var/lib/docker/containers/*/*-json.log
6 |
7 | filebeat.config:
8 | modules:
9 | path: ${path.config}/modules.d/*.yml
10 | reload.enabled: false
11 |
12 | processors:
13 | - add_cloud_metadata: ~
14 | - add_docker_metadata: ~
15 |
16 | output.kafka:
17 | enabled: true
18 | hosts: ["kafka:9092"]
19 | #要提前创建topic
20 | topic: "h68u-tiktok-log"
21 | partition.hash:
22 | reachable_only: true
23 | compression: gzip
24 | max_message_bytes: 1000000
25 | required_acks: 1
26 |
--------------------------------------------------------------------------------
/deploy/config/go-stash.yml:
--------------------------------------------------------------------------------
1 | Clusters:
2 | - Input:
3 | Kafka:
4 | Name: gostash
5 | Brokers:
6 | - "kafka:9092"
7 | Topics:
8 | - h68u-tiktok-log
9 | Group: pro
10 | Consumers: 16
11 | Filters:
12 | - Action: drop
13 | Conditions:
14 | - Key: k8s_container_name
15 | Value: "-rpc"
16 | Type: contains
17 | - Key: level
18 | Value: info
19 | Type: match
20 | Op: and
21 | - Action: remove_field
22 | Fields:
23 | # - message
24 | - _source
25 | - _type
26 | - _score
27 | - _id
28 | - "@version"
29 | - topic
30 | - index
31 | - beat
32 | - docker_container
33 | - offset
34 | - prospector
35 | - source
36 | - stream
37 | - "@metadata"
38 | - Action: transfer
39 | Field: message
40 | Target: data
41 | Output:
42 | ElasticSearch:
43 | Hosts:
44 | - "http://elasticsearch:9200"
45 | Index: "h68u-tiktok-{{yyyy-MM-dd}}"
46 |
--------------------------------------------------------------------------------
/deploy/config/modd.DockerFile:
--------------------------------------------------------------------------------
1 | FROM golang:1.20 AS builder
2 |
3 | ENV GOPROXY=https://goproxy.cn,direct
4 | ENV GO111MODULE=on
5 |
6 | RUN go version
7 | RUN go install github.com/cortesi/modd/cmd/modd@latest
8 |
9 | CMD [ "modd" ]
--------------------------------------------------------------------------------
/deploy/config/prometheus.yml:
--------------------------------------------------------------------------------
1 | global:
2 | scrape_interval:
3 | external_labels:
4 | monitor: 'codelab-monitor'
5 |
6 | scrape_configs:
7 | - job_name: 'prometheus'
8 | scrape_interval: 5s #global catch time
9 | static_configs:
10 | - targets: ['127.0.0.1:9090']
11 |
12 |
13 | - job_name: 'http-app'
14 | static_configs:
15 | - targets: [ 'h68u-tiktok:4000' ]
16 | labels:
17 | job: http-app
18 | app: http-app
19 | env: dev
20 |
21 | - job_name: 'rpc-user'
22 | static_configs:
23 | - targets: [ 'h68u-tiktok:4001' ]
24 | labels:
25 | job: rpc-user
26 | app: rpc-user
27 | env: dev
28 |
29 | - job_name: 'rpc-video'
30 | static_configs:
31 | - targets: [ 'h68u-tiktok:4002' ]
32 | labels:
33 | job: rpc-video
34 | app: rpc-video
35 | env: dev
36 |
37 | - job_name: 'rpc-contact'
38 | static_configs:
39 | - targets: [ 'h68u-tiktok:4003' ]
40 | labels:
41 | job: rpc-contact
42 | app: rpc-contact
43 | env: dev
44 |
45 | - job_name: 'mq'
46 | static_configs:
47 | - targets: [ 'h68u-tiktok:4004' ]
48 | labels:
49 | job: mq
50 | app: mq
51 | env: dev
52 |
53 | - job_name: 'cron'
54 | static_configs:
55 | - targets: [ 'h68u-tiktok:4005' ]
56 | labels:
57 | job: cron
58 | app: cron
59 | env: dev
--------------------------------------------------------------------------------
/docs/0113第一次会议.md:
--------------------------------------------------------------------------------
1 | # 明确目标
2 |
3 | ## 项目质量
4 |
5 | 
6 |
7 |
8 |
9 | > 我们的目标是一等奖,重振h68u荣光
10 |
11 | 有第三届的项目可供参考:https://github.com/h68u/h68u-tiktok-app
12 |
13 | ## DDL
14 |
15 | 
16 |
17 | > 约寒假结束,但肯定要提前完成
18 |
19 | ## 个人要求
20 |
21 | > 不要当死人
22 |
23 | 当你遇到问题时:
24 |
25 | - 自己折腾
26 | - 搜索引擎
27 | - B站
28 | - copilot
29 | - ChatGPT
30 | - 群里问
31 |
32 | 必备技能:
33 |
34 | - gorm
35 | - redis
36 | - 熟悉 go-zero
37 |
38 | ---
39 |
40 | # 项目规划
41 |
42 | ## 框架
43 |
44 | go-zero
45 |
46 | ### 原因/优点
47 |
48 | - 有现成模板,鄙人的项目:https://github.com/hdu-packaging-design/hdu-packaging-design-be
49 | - 微服务
50 | - 及其清晰的项目结构
51 | - 方便结合 docker、k8s 部署
52 | - 易于搭配各种监控组件
53 | - 日后参加面试系统开发
54 |
55 | ## 时间规划
56 |
57 | 最晚过年前冲出功能完善的demo,再继续改进
58 |
59 | ## 可改进问题
60 |
61 | ### 缓存一致性
62 |
63 | 暂时先更新数据库后删除缓存值,后面再考虑要不要上消息队列等
64 |
65 | 可参考资料:
66 |
67 | - [聊一聊缓存和数据库不一致性问题的产生及主流解决方案以及扩展的思考](https://juejin.cn/post/7156237969202872350)
68 | - [万字图文讲透数据库缓存一致性问题](https://juejin.cn/post/7168291215753314311)
69 |
70 | ### 热点数据
71 |
72 | 分库分表
73 |
74 | 可参考资料:
75 |
76 | [好好的系统,为什么要分库分表?](https://juejin.cn/post/7155784807702593572)
77 |
78 | [Mysql大数据表处理方案](https://juejin.cn/post/7146016771936354312)
79 |
80 | B站也有相关的视频
81 |
82 | 其他解决方法后面再思考
83 |
84 | ### 监控组件
85 |
86 | - prometheus+grafan 服务监控
87 | - jaeger 链路追踪
88 | - kafka+Elasticsearch 日志分析
89 |
90 | https://www.waterflow.link/articles/1661696351405 连着3篇
91 |
92 | B 站 go-zero官方视频也有这方面内容
93 |
94 | ## 团队分工
95 |
96 | 自由分工
97 |
98 |
--------------------------------------------------------------------------------
/docs/docker-compose.md:
--------------------------------------------------------------------------------
1 | 本节将讲述如何使用 docker-compose 在本地启动整个项目
2 |
3 | 本功能模仿 [go–zero-looklook](https://github.com/Mikaelemmmm/go-zero-looklook) ,晚上状态不佳,有疏漏可参考那个项目的文档,真的非常详细
4 |
5 | 首先请根据你的指令集选择对应的 `docker-compose` 文件!
6 |
7 | (根目录的 `docker-compose` 仅用于本地部署测试,服务端部署是拉取 workflows 中自动编译打包上传的各服务的镜像)
8 |
9 | 
10 |
11 | # 准备工作
12 |
13 | ## 准备配置文件
14 |
15 | 来到各个服务的配置目录,将示例配置文件复制一份,去掉example后缀
16 |
17 | 当然,OSS配置我不能告诉你(
18 |
19 | 
20 |
21 | ## 创建消息队列
22 |
23 | 先启动里面的卡夫卡
24 |
25 | 
26 |
27 | 卡夫卡启动后,进去创建用于传输日志的消息队列
28 |
29 | ```bash
30 | $ docker exec -it kafka /bin/sh
31 | $ cd /opt/kafka/bin/
32 | $ ./kafka-topics.sh --create --zookeeper zookeeper:2181 --replication-factor 1 -partitions 1 --topic h68u-tiktok-log
33 | ```
34 |
35 | 或者你也可以直接在 Docker Desktop 启动它的终端
36 |
37 | 
38 |
39 | ## 初始化 MySQL
40 |
41 | 接着往下翻,找到 mysql 并启动
42 |
43 | 进入mysql,准备一下 root 用户
44 |
45 | ```bash
46 | $ docker exec -it mysql mysql -uroot -p
47 | ##输入密码:PXDN93VRKUm8TeE7
48 | $ use mysql;
49 | $ update user set host='%' where user='root';
50 | $ FLUSH PRIVILEGES;
51 | ```
52 |
53 | 
54 |
55 | 测试一下能否连接到数据库
56 |
57 | 
58 |
59 | 创建项目会访问到的库
60 |
61 | 
62 |
63 | ## 启动所有服务
64 |
65 | 
66 |
67 | 等待13个镜像全部拉取完成并启动
68 |
69 | 
70 |
71 | 项目本体使用 modd 热编译,任何修改都会自动重新编译并启动
72 |
73 | 
74 |
75 | 等待项目完全启动
76 |
77 | 
78 |
79 | # 服务监控
80 |
81 | [9090](http://127.0.0.1:9090/) 端口访问普罗米修斯,来到target页面,稍等片刻等待服务全部亮起
82 |
83 | 
84 |
85 | 
86 |
87 | 
88 |
89 | # 链接追踪
90 |
91 | 去 apifox 上请求一下,然后来到 [16686](http://127.0.0.1:16686/) 端口访问 Jaeger,就能查看链路追踪
92 |
93 | 
94 |
95 | 
96 |
97 | # 日志收集
98 |
99 | 通过 [5601](http://127.0.0.1:5601/) 端口访问 Kibana
100 |
101 |
102 |
103 | 
104 |
105 | 
106 |
107 | 
108 |
109 | 
110 |
111 | 
112 |
113 | 
114 |
115 | 演示:根据请求ID查询日志排查错误
116 |
117 | 可以尝试把 mysql 关掉再请求
118 |
119 | 
120 |
121 | 
--------------------------------------------------------------------------------
/docs/项目架构图.drawio:
--------------------------------------------------------------------------------
1 | 5VtLc5s6FP41nuld2APCPLxsk7Rd5M5kJtO5aXcylkE1ICpEbOfXXwlLtkEkJbUNSppFjA56wHl85yExcq7SzRcK8/hfskDJCFiLzci5HgEwc6f8vyBsdwR3BnaEiOLFjmQfCPf4CUmiJaklXqCi1pERkjCc14khyTIUshoNUkrW9W5LktRXzWGENMJ9CBOd+h9esFhSPfVe4sZXhKNYLW17s92dFKre8lWKGC7I+ojk3IycK0oI212lmyuUCOYpxuzGfX7m7v7JKMpYlwFPzH+4/ZpvcYDiH/f0/hv7Fo2BfNqCbdUrowXngGxmJOM/nygpswUS81i8RSiLSUQymNwSknOizYk/EWNbKT9YMsJJMUsTeRdtMHsQwyeubH0/unO9kTNXja1qZIxuH1Q30fh+mEE0D8Oqlhqn80WyqiAlDdELzFAKBmmE2EtMk8IXnDpaQbL9CyIp4g/EO1CUQIYf67oEpUpG+34HofELKbdXyNCRCvYIk1IulZCoVa63cM7tsyYamOAo49chZxeinPCIKMPcAj7KGyleLJJKC1CBn+C8mk8wOic4Y9XLuJ9G7nUr619UOrES2rTZsVylZil/xlk59E48K+8iVwP1AWS5LLjEm4LYr/fnspk6vdqXfWRd1sR3uxmYZZqBTYFRBjbtFyTrQgRvVoiBUUK0NZBcBYVAnKQsBPCBzwsSrhAdhyTNSYE0kRdrnCawEvU6xgzd57Di05oHPnVBPstbDfB0Zsi7UwXqMm6yZ7K9PkQhji9p8VEAAgLrQgz0NY5cSON/o/C8cYco5i8l3NVJRlC1mpN1dGJdzQV0NBfHKGsJNGuJaB4aHFP4f01IATTRwByPC0QfcchTJQ22GCWrffIC6oLiOUku+qWbSKRvk2VC1mEMKZukJU+yxhwSy7RifCvmvc5UuqNfE/wsHfxmLdjnXQr6HI3l3BjeF8uBVee5PzDLh8xLrRMc0MHn7FxQEBjgg6Zv0gfZel5ruBMaPK8dN0ZcMLHVZJP+Gt04Ix5+BnZISfYuUNEOGp4oGBgWbdArLr4qML9sEN45Pe1cxTMK7Fw9rIsZyw0GO6WKJ6CdQi1r4vC/mqUpWz0REN3WSXsI0t0B45eOdfVmrHK5itEbLawDT7NJwwvr7mAByOVNSmnRkTSyCGebcQQZWkP+UF4i4oM55VeRuPqwxtxJAw+mIkbI5oX4WUKcgDnM/tHkeLBGu5ca335nUKITaKnx2aDP6ALoiW4vMHYCtAQdoUVpjyHQYusFtrIQVehm0Czj4jJNPoaMHMNIBTl3pMAMEwEnc8IYSVtwhpGG7pKSJThDV/s98xfh/RXRsvV7hXZ6rdvoQZXh+gw6p+pm6TPQ88ElTtAcQWYczrpgaJx1dFe2gssVNI5V3nRwVumKZbgJq0TmrZmw42qczstqmNlB715DTs5DrQmo+6/ROVJQu25Squ5w+YDZmWkCfTemY9bZgqmO53lZxOabzukF60MJZ+p5fqumn7qxClpn7cF+9GwoIuOCQSHZgR2165rmqF1f45bpaHN2EHnGAWhRld8Qwg7v5LALKLKebPI3Kji4FAjScHhtDtw6crRpc7/7LOoB6vkMa8nQ+SuyywG50Pdip/rVNlm1h3ZFElEJUCbEHyxpkM6BMI2tL8/WZeK2iMS5mEi8XvDkxJI66Kmmro7IvrlQSXcTBSNUP25pTpSkFO8sUZI1Uzt9Z4qSetjqB5rIfkIUtWBh737Dr2OU03JqqecoyNKYYiBI+X2BVNfqvGEg5er5nOkgpRTvHFUQ21Jf6CiMck7DqL6PyLp6KasXs+zBUlyztsinemqRUzF3jEr9YGzf/sG27PoBkH10OpyD0IsKf1liAdyGTLyBEwtX32KIKFzCbPj9GLt5FN4ABdYtfoXnZjCrx90r3jx8ur3zW4cP4J2b/wE=
--------------------------------------------------------------------------------
/modd.conf:
--------------------------------------------------------------------------------
1 | # http-app
2 | service/http/**/*.go {
3 | prep: go build -o deploy/build/http-app -v service/http/app.go
4 | daemon +sigkill: deploy/build/http-app -f service/http/etc/app.yaml
5 | }
6 |
7 | # rpc-user
8 | service/rpc/user/**/*.go {
9 | prep: go build -o deploy/build/rpc-user -v service/rpc/user/user.go
10 | daemon +sigkill: deploy/build/rpc-user -f service/rpc/user/etc/user.yaml
11 | }
12 |
13 | # rpc-video
14 | service/rpc/video/**/*.go {
15 | prep: go build -o deploy/build/rpc-video -v service/rpc/video/video.go
16 | daemon +sigkill: deploy/build/rpc-video -f service/rpc/video/etc/video.yaml
17 | }
18 |
19 | # rpc-contact
20 | service/rpc/contact/**/*.go {
21 | prep: go build -o deploy/build/rpc-contact -v service/rpc/contact/contact.go
22 | daemon +sigkill: deploy/build/rpc-contact -f service/rpc/contact/etc/contact.yaml
23 | }
24 |
25 | # mq
26 | service/mq/**/*.go {
27 | prep: go build -o deploy/build/mq -v service/mq/mq.go
28 | daemon +sigkill: deploy/build/mq -f service/mq/etc/mq.yaml
29 | }
30 |
31 | # cron
32 | service/cron/**/*.go {
33 | prep: go build -o deploy/build/cron -v service/cron/cron.go
34 | daemon +sigkill: deploy/build/cron -f service/cron/etc/cron.yaml
35 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # h68u-tiktok-app-microservice
2 |
3 | 第五届字节跳动青训营 抖音APP项目 h68u组
4 |
5 | ## 项目汇报文档地址
6 |
7 | [飞书文档](https://hdu-help.feishu.cn/docx/KuGidErAIogvWDxcn2VcTrfUntb)
8 |
9 | [后记](https://www.nickxu.top/2023/03/13/%E7%AC%AC%E4%BA%94%E5%B1%8A%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8%E9%9D%92%E8%AE%AD%E8%90%A5%E9%A1%B9%E7%9B%AE%E6%80%BB%E7%BB%93/)
10 |
11 | ---
12 | 本地如何如何部署启动请参考 [./docs/docker-compose.md](./docs/docker-compose.md)
--------------------------------------------------------------------------------
/script/genCode.bat:
--------------------------------------------------------------------------------
1 | cd ../
2 | :: api
3 | goctl api go -api ./service/http/app.api -dir ./service/http -style goZero
4 |
5 | :: rpc
6 | :: user
7 | goctl rpc protoc ./service/rpc/user/user.proto --go_out=./service/rpc/user/types --go-grpc_out=./service/rpc/user/types --zrpc_out=./service/rpc/user -style goZero
8 |
9 | :: video
10 | goctl rpc protoc ./service/rpc/video/video.proto --go_out=./service/rpc/video/types --go-grpc_out=./service/rpc/video/types --zrpc_out=./service/rpc/video -style goZero
11 |
12 | :: contact
13 | goctl rpc protoc ./service/rpc/contact/contact.proto --go_out=./service/rpc/contact/types --go-grpc_out=./service/rpc/contact/types --zrpc_out=./service/rpc/contact -style goZero
14 |
--------------------------------------------------------------------------------
/script/genCode.sh:
--------------------------------------------------------------------------------
1 | cd ../
2 | # api
3 | goctl api go -api service/http/app.api -dir service/http -style goZero
4 |
5 | # rpc
6 | # user
7 | goctl rpc protoc service/rpc/user/user.proto --go_out=service/rpc/user/types --go-grpc_out=service/rpc/user/types --zrpc_out=service/rpc/user -style goZero
8 |
9 | # video
10 | goctl rpc protoc service/rpc/video/video.proto --go_out=service/rpc/video/types --go-grpc_out=service/rpc/video/types --zrpc_out=service/rpc/video -style goZero
11 |
12 | # contact
13 | goctl rpc protoc service/rpc/contact/contact.proto --go_out=service/rpc/contact/types --go-grpc_out=service/rpc/contact/types --zrpc_out=service/rpc/contact -style goZero
14 |
--------------------------------------------------------------------------------
/service/cron/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine AS builder
2 |
3 | LABEL stage=gobuilder
4 |
5 | ENV CGO_ENABLED 0
6 | ENV GOPROXY https://goproxy.cn,direct
7 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
8 |
9 | RUN apk update --no-cache && apk add --no-cache tzdata
10 |
11 | WORKDIR /build
12 |
13 | ADD go.mod .
14 | ADD go.sum .
15 | RUN go mod download
16 | COPY . .
17 | #COPY service/http/etc /app/etc
18 | RUN go build -ldflags="-s -w" -o /app/cron service/cron/cron.go
19 |
20 |
21 | FROM scratch
22 |
23 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
24 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
25 | ENV TZ Asia/Shanghai
26 |
27 | WORKDIR /app
28 | COPY --from=builder /app/cron /app/cron
29 | #COPY --from=builder /app/etc /app/etc
30 |
31 | CMD ["./cron", "-f", "etc/cron.yaml"]
32 |
--------------------------------------------------------------------------------
/service/cron/cron.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "github.com/zeromicro/go-zero/core/conf"
7 | "github.com/zeromicro/go-zero/core/logx"
8 | "github.com/zeromicro/go-zero/core/prometheus"
9 | "github.com/zeromicro/go-zero/core/service"
10 | "github.com/zeromicro/go-zero/core/trace"
11 | "h68u-tiktok-app-microservice/service/cron/internal/config"
12 | "h68u-tiktok-app-microservice/service/cron/internal/scheduler"
13 | "h68u-tiktok-app-microservice/service/cron/internal/svc"
14 | )
15 |
16 | var configFile = flag.String("f", "etc/cron.yaml", "Specify the config file")
17 |
18 | func main() {
19 | flag.Parse()
20 | var c config.Config
21 |
22 | conf.MustLoad(*configFile, &c)
23 | // nolint:staticcheck
24 | prometheus.StartAgent(c.Prometheus)
25 | trace.StartAgent(c.Telemetry)
26 |
27 | svcContext := svc.NewServiceContext(c)
28 | ctx := context.Background()
29 | logx.DisableStat()
30 |
31 | serviceGroup := service.NewServiceGroup()
32 | defer serviceGroup.Stop()
33 |
34 | serviceGroup.Add(scheduler.NewAsynqServer(ctx, svcContext))
35 | serviceGroup.Start()
36 | }
37 |
--------------------------------------------------------------------------------
/service/cron/etc/cron.yaml.example:
--------------------------------------------------------------------------------
1 | Name: cron
2 | Host: 0.0.0.0
3 | Port: 7002
4 |
5 | #监控
6 | Prometheus:
7 | Host: 0.0.0.0
8 | Port: 4005
9 | Path: /metrics
10 |
11 | #链路追踪
12 | Telemetry:
13 | Name: cron
14 | Endpoint: http://jaeger:14268/api/traces
15 | Sampler: 1.0
16 | Batcher: jaeger
17 |
18 | # RPC 配置
19 | ContactRpc:
20 | Endpoints:
21 | - 127.0.0.1:9003
22 | NonBlock: true
23 |
24 | VideoRpc:
25 | Endpoints:
26 | - 127.0.0.1:9002
27 | NonBlock: true
28 |
29 | UserRpc:
30 | Endpoints:
31 | - 127.0.0.1:9001
32 | NonBlock: true
33 |
34 | Redis:
35 | Address: redis:6379
36 | Password: G62m50oigInC30sf
37 |
38 |
--------------------------------------------------------------------------------
/service/cron/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/zeromicro/go-zero/core/service"
5 | "github.com/zeromicro/go-zero/zrpc"
6 | )
7 |
8 | type Config struct {
9 | service.ServiceConf
10 |
11 | Redis RedisConf
12 |
13 | VideoRpc zrpc.RpcClientConf
14 | UserRpc zrpc.RpcClientConf
15 | ContactRpc zrpc.RpcClientConf
16 | }
17 |
18 | type RedisConf struct {
19 | Address string
20 | Password string
21 | //DB int
22 | }
23 |
--------------------------------------------------------------------------------
/service/cron/internal/scheduler/asynq.go:
--------------------------------------------------------------------------------
1 | package scheduler
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/hibiken/asynq"
7 | "github.com/zeromicro/go-zero/core/logx"
8 | "h68u-tiktok-app-microservice/common/cron"
9 | "h68u-tiktok-app-microservice/service/cron/internal/svc"
10 | "log"
11 | )
12 |
13 | type AsynqServer struct {
14 | ctx context.Context
15 | svcCtx *svc.ServiceContext
16 | logx.Logger
17 | }
18 |
19 | func NewAsynqServer(ctx context.Context, svcCtx *svc.ServiceContext) *AsynqServer {
20 | return &AsynqServer{
21 | ctx: ctx,
22 | svcCtx: svcCtx,
23 | Logger: logx.WithContext(ctx),
24 | }
25 | }
26 |
27 | func (l *AsynqServer) Start() {
28 | fmt.Println("AsynqTask start")
29 |
30 | scheduler := asynq.NewScheduler(
31 | asynq.RedisClientOpt{
32 | Addr: l.svcCtx.Config.Redis.Address,
33 | Password: l.svcCtx.Config.Redis.Password},
34 | nil,
35 | )
36 |
37 | syncUserInfoCacheTask := cron.NewSyncUserInfoCacheTask()
38 | entryID, err := scheduler.Register("@every 1h", syncUserInfoCacheTask)
39 | if err != nil {
40 | log.Fatal(err)
41 | }
42 | log.Printf("registered an entry: %q\n", entryID)
43 |
44 | syncVideoInfoCacheTask := cron.NewSyncVideoInfoCacheTask()
45 | entryID, err = scheduler.Register("@every 301s", syncVideoInfoCacheTask)
46 | if err != nil {
47 | log.Fatal(err)
48 | }
49 | log.Printf("registered an entry: %q\n", entryID)
50 |
51 | if err := scheduler.Run(); err != nil {
52 | log.Fatal(err)
53 | }
54 | }
55 |
56 | func (l *AsynqServer) Stop() {
57 | fmt.Println("AsynqTask stop")
58 | }
59 |
--------------------------------------------------------------------------------
/service/cron/internal/svc/serviceContext.go:
--------------------------------------------------------------------------------
1 | package svc
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/go-redis/redis/v8"
7 | "github.com/zeromicro/go-zero/zrpc"
8 | "h68u-tiktok-app-microservice/service/cron/internal/config"
9 | "h68u-tiktok-app-microservice/service/rpc/contact/contactclient"
10 | "h68u-tiktok-app-microservice/service/rpc/user/userclient"
11 | "h68u-tiktok-app-microservice/service/rpc/video/videoclient"
12 | "time"
13 | )
14 |
15 | type ServiceContext struct {
16 | Config config.Config
17 | Redis *redis.Client
18 | VideoRpc videoclient.Video
19 | UserRpc userclient.User
20 | ContactRpc contactclient.Contact
21 | }
22 |
23 | func NewServiceContext(c config.Config) *ServiceContext {
24 | return &ServiceContext{
25 | Config: c,
26 | Redis: initRedis(c),
27 | VideoRpc: videoclient.NewVideo(zrpc.MustNewClient(c.VideoRpc)),
28 | UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
29 | ContactRpc: contactclient.NewContact(zrpc.MustNewClient(c.ContactRpc)),
30 | }
31 | }
32 |
33 | func initRedis(c config.Config) *redis.Client {
34 | fmt.Println("connect Redis ...")
35 | db := redis.NewClient(&redis.Options{
36 | Addr: c.Redis.Address,
37 | Password: c.Redis.Password,
38 | //DB: c.DBList.Redis.DB,
39 | //超时
40 | ReadTimeout: 2 * time.Second,
41 | WriteTimeout: 2 * time.Second,
42 | PoolTimeout: 3 * time.Second,
43 | })
44 | _, err := db.Ping(context.Background()).Result()
45 | if err != nil {
46 | fmt.Println("connect Redis failed")
47 | panic(err)
48 | }
49 | fmt.Println("connect Redis success")
50 | return db
51 | }
52 |
--------------------------------------------------------------------------------
/service/http/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine AS builder
2 |
3 | LABEL stage=gobuilder
4 |
5 | ENV CGO_ENABLED 0
6 | ENV GOPROXY https://goproxy.cn,direct
7 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
8 |
9 | RUN apk update --no-cache && apk add --no-cache tzdata
10 |
11 | WORKDIR /build
12 |
13 | ADD go.mod .
14 | ADD go.sum .
15 | RUN go mod download
16 | COPY . .
17 | #COPY service/http/etc /app/etc
18 | RUN go build -ldflags="-s -w" -o /app/app service/http/app.go
19 |
20 |
21 | FROM scratch
22 |
23 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
24 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
25 | ENV TZ Asia/Shanghai
26 |
27 | WORKDIR /app
28 | COPY --from=builder /app/app /app/app
29 | #COPY --from=builder /app/etc /app/etc
30 |
31 | CMD ["./app", "-f", "etc/app.yaml"]
32 |
--------------------------------------------------------------------------------
/service/http/apis/contact.api:
--------------------------------------------------------------------------------
1 | syntax = "v1"
2 |
3 | import "apis/dto/dto.api"
4 |
5 | @server(
6 | middleware: Auth
7 | group: contact
8 | )
9 | service app {
10 | @handler GetFriendList // 获取好友列表
11 | get /douyin/relation/friend/list (GetFriendListRequest) returns (GetFriendListReply)
12 |
13 | @handler SendMessage // 发送消息
14 | post /douyin/message/action (SendMessageRequest) returns (SendMessageReply)
15 |
16 | @handler GetHistoryMessage // 获取聊天记录
17 | get /douyin/message/chat (GetHistoryMessageRequest) returns (GetHistoryMessageReply)
18 | }
19 |
20 | // 获取好友列表
21 | type (
22 | GetFriendListRequest {
23 | UserId int64 `form:"user_id"`
24 | Token string `form:"token"`
25 | }
26 | GetFriendListReply {
27 | BasicReply
28 | FriendList []Friend `json:"user_list"` // 没错就是这个名字
29 | }
30 | Friend {
31 | Id int64 `json:"id"`
32 | Name string `json:"name"`
33 | // Avatar string `json:"avatar"`
34 | FollowCount int64 `json:"follow_count"`
35 | FollowerCount int64 `json:"follower_count"`
36 | IsFollow bool `json:"is_follow"`
37 | Message string `json:"message"`
38 | MsgType int64 `json:"msgType"`
39 | }
40 | )
41 |
42 | // 发送消息
43 | type (
44 | SendMessageRequest {
45 | Token string `from:"token"`
46 | ToUserId int64 `from:"to_user_id"`
47 | Content string `from:"content"`
48 | ActionType int64 `from:"action_type"`
49 | }
50 | SendMessageReply {
51 | BasicReply
52 | }
53 | )
54 |
55 | // 获取聊天记录
56 | type (
57 | GetHistoryMessageRequest {
58 | Token string `form:"token"`
59 | ToUserId int64 `form:"to_user_id"`
60 | }
61 | GetHistoryMessageReply {
62 | BasicReply
63 | MessageList []Message `json:"message_list"`
64 | }
65 | )
--------------------------------------------------------------------------------
/service/http/apis/dto/dto.api:
--------------------------------------------------------------------------------
1 | syntax = "v1"
2 |
3 | type BasicReply {
4 | Code int64 `json:"status_code"`
5 | Msg string `json:"status_msg"`
6 | }
7 |
8 | // 用户信息
9 | type User {
10 | Id int64 `json:"id"`
11 | Name string `json:"name"`
12 | FollowCount int64 `json:"follow_count"`
13 | FollowerCount int64 `json:"follower_count"`
14 | IsFollow bool `json:"is_follow"`
15 | }
16 |
17 | // 视频信息
18 | type Video {
19 | Id int64 `json:"id"`
20 | Title string `json:"title"`
21 | Author User `json:"author"`
22 | PlayUrl string `json:"play_url"`
23 | CoverUrl string `json:"cover_url"`
24 | FavoriteCount int64 `json:"favorite_count"`
25 | CommentCount int64 `json:"comment_count"`
26 | IsFavorite bool `json:"is_favorite"`
27 | }
28 |
29 | // 评论信息
30 | type Comment {
31 | Id int64 `json:"id"`
32 | Content string `json:"content"`
33 | User User `json:"user"`
34 | CreateDate string `json:"create_date"`
35 | // CreateTime int64 `json:"create_time"`
36 | }
37 |
38 | // 聊天消息
39 | type Message {
40 | Id int64 `json:"id"`
41 | Content string `json:"content"`
42 | CreateTime int64 `json:"create_time"`
43 | FromUserId int64 `json:"from_user_id"`
44 | ToUserId int64 `json:"to_user_id"`
45 | }
--------------------------------------------------------------------------------
/service/http/apis/user.api:
--------------------------------------------------------------------------------
1 | syntax = "v1"
2 |
3 | import "apis/dto/dto.api"
4 |
5 | // 不需要鉴权的接口
6 | @server(
7 | group: user
8 | )
9 | service app {
10 | @handler Register // 用户注册
11 | post /douyin/user/register (RegisterRequest) returns (RegisterReply)
12 |
13 | @handler Login // 用户登录
14 | post /douyin/user/login (LoginRequest) returns (LoginReply)
15 | }
16 |
17 | // 用户注册
18 | type (
19 | RegisterRequest {
20 | Username string `form:"username"`
21 | Password string `form:"password"`
22 | }
23 | RegisterReply {
24 | BasicReply
25 | UserId int64 `json:"user_id"`
26 | Token string `json:"token"`
27 | }
28 | )
29 |
30 | // 用户登录
31 | type (
32 | LoginRequest {
33 | Username string `form:"username"`
34 | Password string `form:"password"`
35 | }
36 | LoginReply {
37 | BasicReply
38 | UserId int64 `json:"user_id"`
39 | Token string `json:"token"`
40 | }
41 | )
42 |
43 | // 需要鉴权的接口
44 | @server(
45 | group: user
46 | middleware: Auth
47 | )
48 | service app {
49 | @handler GetUserInfo // 获取用户信息
50 | get /douyin/user (GetUserInfoRequest) returns (GetUserInfoReply)
51 |
52 | @handler Follow // 关注用户
53 | post /douyin/relation/action (FollowRequest) returns (FollowReply)
54 |
55 | @handler FollowList // 获取关注列表
56 | get /douyin/relation/follow/list (FollowListRequest) returns (FollowListReply)
57 |
58 | @handler FansList // 获取粉丝列表
59 | get /douyin/relation/follower/list (FansListRequest) returns (FansListReply)
60 | }
61 |
62 | // 获取用户信息
63 | type (
64 | GetUserInfoRequest {
65 | UserId int64 `form:"user_id"`
66 | Token string `form:"token"`
67 | }
68 | GetUserInfoReply {
69 | BasicReply
70 | User User `json:"user"`
71 | }
72 | )
73 |
74 | // 关注用户
75 | type (
76 | FollowRequest {
77 | // UserId int64 `form:"user_id"`
78 | Token string `form:"token"`
79 | ToUserId int64 `form:"to_user_id"`
80 | ActionType int64 `form:"action_type"`
81 | }
82 | FollowReply {
83 | BasicReply
84 | }
85 | )
86 |
87 | // 获取关注列表
88 | type (
89 | FollowListRequest {
90 | UserId int64 `form:"user_id"`
91 | Token string `form:"token"`
92 | }
93 | FollowListReply {
94 | BasicReply
95 | Users []User `json:"user_list"`
96 | }
97 | )
98 |
99 | // 获取粉丝列表
100 | type (
101 | FansListRequest {
102 | UserId int64 `form:"user_id"`
103 | Token string `form:"token"`
104 | }
105 | FansListReply {
106 | BasicReply
107 | Users []User `json:"user_list"`
108 | }
109 | )
--------------------------------------------------------------------------------
/service/http/apis/video.api:
--------------------------------------------------------------------------------
1 | syntax = "v1"
2 |
3 | import "apis/dto/dto.api"
4 |
5 | // 不需要鉴权的接口
6 | @server(
7 | group: video
8 | )
9 | service app {
10 | @handler GetVideoList // 视频流接口
11 | get /douyin/feed (GetVideoListRequest) returns (GetVideoListReply)
12 | }
13 |
14 | // 视频流接口
15 | type (
16 | GetVideoListRequest {
17 | LatestTime int64 `form:"latest_time,optional"`
18 | Token string `form:"token,optional"`
19 | }
20 | GetVideoListReply {
21 | BasicReply
22 | NextTime int64 `json:"next_time"`
23 | VideoList []Video `json:"video_list"`
24 | }
25 | )
26 |
27 | // 需要鉴权的接口
28 | @server(
29 | middleware: Auth
30 | group: video
31 | )
32 | service app {
33 | @handler PublishVideo // 发布视频
34 | post /douyin/publish/action (PublishVideoRequest) returns (PublishVideoReply)
35 |
36 | @handler PublishedList // 已发布的视频列表
37 | get /douyin/publish/list (PublishedListRequest) returns (PublishedListReply)
38 |
39 | @handler FavoriteVideo // 点赞视频
40 | post /douyin/favorite/action (FavoriteVideoRequest) returns (FavoriteVideoReply)
41 |
42 | @handler FavoriteList // 点赞列表
43 | get /douyin/favorite/list (FavoriteListRequest) returns (FavoriteListReply)
44 |
45 | @handler CommentVideo // 评论视频
46 | post /douyin/comment/action (CommentVideoRequest) returns (CommentVideoReply)
47 |
48 | @handler CommentList // 评论列表
49 | get /douyin/comment/list (CommentListRequest) returns (CommentListReply)
50 | }
51 |
52 | // 发布视频
53 | type (
54 | PublishVideoRequest {
55 | Title string `form:"title"`
56 | Token string `form:"token"`
57 | }
58 | PublishVideoReply {
59 | BasicReply
60 | }
61 | )
62 |
63 | // 已发布的视频列表
64 | type (
65 | PublishedListRequest {
66 | Token string `form:"token"`
67 | UserId int64 `form:"user_id"`
68 | }
69 | PublishedListReply {
70 | BasicReply
71 | VideoList []Video `json:"video_list"`
72 | }
73 | )
74 |
75 | // 点赞视频
76 | type (
77 | FavoriteVideoRequest {
78 | VideoId int64 `form:"video_id"`
79 | Token string `form:"token"`
80 | ActionType int64 `form:"action_type"`
81 | }
82 | FavoriteVideoReply {
83 | BasicReply
84 | }
85 | )
86 |
87 | // 点赞列表
88 | type (
89 | FavoriteListRequest {
90 | Token string `form:"token"`
91 | UserId int64 `form:"user_id"`
92 | }
93 | FavoriteListReply {
94 | BasicReply
95 | VideoList []Video `json:"video_list"`
96 | }
97 | )
98 |
99 | // 评论视频
100 | type (
101 | CommentVideoRequest {
102 | VideoId int64 `form:"video_id"`
103 | Token string `form:"token"`
104 | ActionType int64 `form:"action_type"`
105 | Content string `form:"comment_text"`
106 | CommentId int64 `form:"comment_id,optional"`
107 | }
108 | CommentVideoReply {
109 | BasicReply
110 | Comment Comment `json:"comment"`
111 | }
112 | )
113 |
114 | // 评论列表
115 | type (
116 | CommentListRequest {
117 | Token string `form:"token"`
118 | VideoId int64 `form:"video_id"`
119 | }
120 | CommentListReply {
121 | BasicReply
122 | CommentList []Comment `json:"comment_list"`
123 | }
124 | )
--------------------------------------------------------------------------------
/service/http/app.api:
--------------------------------------------------------------------------------
1 | syntax = "v1"
2 |
3 | import "apis/user.api"
4 | import "apis/video.api"
5 | import "apis/contact.api"
--------------------------------------------------------------------------------
/service/http/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/zeromicro/go-zero/core/logx"
7 | "github.com/zeromicro/go-zero/rest/httpx"
8 | "h68u-tiktok-app-microservice/common/error/apiErr"
9 | "net/http"
10 |
11 | "h68u-tiktok-app-microservice/service/http/internal/config"
12 | "h68u-tiktok-app-microservice/service/http/internal/handler"
13 | "h68u-tiktok-app-microservice/service/http/internal/svc"
14 |
15 | "github.com/zeromicro/go-zero/core/conf"
16 | "github.com/zeromicro/go-zero/rest"
17 | )
18 |
19 | var configFile = flag.String("f", "etc/app.yaml", "the config file")
20 |
21 | func main() {
22 | flag.Parse()
23 |
24 | var c config.Config
25 | conf.MustLoad(*configFile, &c)
26 |
27 | server := rest.MustNewServer(c.RestConf)
28 | defer server.Stop()
29 |
30 | ctx := svc.NewServiceContext(c)
31 | handler.RegisterHandlers(server, ctx)
32 |
33 | httpx.SetErrorHandler(func(err error) (int, interface{}) {
34 | switch e := err.(type) {
35 | case apiErr.ApiErr:
36 | return http.StatusOK, e.Response()
37 | case apiErr.ErrInternal:
38 | return http.StatusOK, e.Response(c.RestConf)
39 | default:
40 | return http.StatusInternalServerError, err
41 | }
42 | })
43 |
44 | logx.DisableStat()
45 |
46 | fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
47 | server.Start()
48 | }
49 |
--------------------------------------------------------------------------------
/service/http/etc/app.yaml.example:
--------------------------------------------------------------------------------
1 | # 启动配置
2 | Name: h68u-tiktok
3 | Host: 0.0.0.0
4 | Port: 8080
5 | #Mode: dev
6 | MaxBytes: 104857600 # 100MB
7 | Timeout: 100000 # 100s
8 |
9 | # RPC 配置
10 | ContactRpc:
11 | Endpoints:
12 | - 127.0.0.1:9003
13 | NonBlock: true
14 |
15 | VideoRpc:
16 | Endpoints:
17 | - 127.0.0.1:9002
18 | NonBlock: true
19 |
20 | UserRpc:
21 | Endpoints:
22 | - 127.0.0.1:9001
23 | NonBlock: true
24 |
25 | # JWT 配置
26 | Auth:
27 | AccessSecret: ThisIsMySecret
28 | AccessExpire: 1296000 # 15 天
29 |
30 | OSS:
31 | Endpoint: oss-cn-hongkong.aliyuncs.com
32 | AccessKeyID:
33 | AccessKeySecret:
34 | BucketName: h68u-tiktok
35 |
36 |
37 | #监控
38 | Prometheus:
39 | Host: 0.0.0.0
40 | Port: 4000
41 | Path: /metrics
42 |
43 | #链路追踪
44 | Telemetry:
45 | Name: http-app
46 | Endpoint: http://jaeger:14268/api/traces
47 | Sampler: 1.0
48 | Batcher: jaeger
49 |
50 | Redis:
51 | Address: redis:6379
52 | Password: G62m50oigInC30sf
--------------------------------------------------------------------------------
/service/http/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/zeromicro/go-zero/rest"
5 | "github.com/zeromicro/go-zero/zrpc"
6 | "h68u-tiktok-app-microservice/common/oss"
7 | )
8 |
9 | type Config struct {
10 | // 启动配置
11 | rest.RestConf
12 |
13 | // RPC 配置
14 | VideoRpc zrpc.RpcClientConf
15 | UserRpc zrpc.RpcClientConf
16 | ContactRpc zrpc.RpcClientConf
17 |
18 | // JWT 配置
19 | Auth struct {
20 | AccessSecret string
21 | AccessExpire int64
22 | }
23 |
24 | OSS oss.AliyunCfg
25 | Redis RedisConf
26 | }
27 |
28 | type RedisConf struct {
29 | Address string
30 | Password string
31 | //DB int
32 | }
33 |
--------------------------------------------------------------------------------
/service/http/internal/handler/contact/getFriendListHandler.go:
--------------------------------------------------------------------------------
1 | package contact
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/contact"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func GetFriendListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.GetFriendListRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := contact.NewGetFriendListLogic(r.Context(), svcCtx)
21 | resp, err := l.GetFriendList(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/contact/getHistoryMessageHandler.go:
--------------------------------------------------------------------------------
1 | package contact
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/contact"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func GetHistoryMessageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.GetHistoryMessageRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := contact.NewGetHistoryMessageLogic(r.Context(), svcCtx)
21 | resp, err := l.GetHistoryMessage(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/contact/sendMessageHandler.go:
--------------------------------------------------------------------------------
1 | package contact
2 |
3 | import (
4 | "h68u-tiktok-app-microservice/common/error/apiErr"
5 | "net/http"
6 | "strconv"
7 |
8 | "github.com/zeromicro/go-zero/rest/httpx"
9 | "h68u-tiktok-app-microservice/service/http/internal/logic/contact"
10 | "h68u-tiktok-app-microservice/service/http/internal/svc"
11 | "h68u-tiktok-app-microservice/service/http/internal/types"
12 | )
13 |
14 | func SendMessageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
15 | return func(w http.ResponseWriter, r *http.Request) {
16 | var req types.SendMessageRequest
17 | //if err := httpx.Parse(r, &req); err != nil {
18 | // httpx.Error(w, err)
19 | // return
20 | //}
21 |
22 | // post 居然用query传参
23 | req.Token = r.URL.Query().Get("token")
24 | req.Content = r.URL.Query().Get("content")
25 |
26 | ToUserId, err := strconv.ParseInt(r.URL.Query().Get("to_user_id"), 10, 64)
27 | if err != nil {
28 | httpx.OkJson(w, apiErr.InvalidParams)
29 | return
30 | }
31 | req.ToUserId = ToUserId
32 |
33 | ActionType, err := strconv.ParseInt(r.URL.Query().Get("action_type"), 10, 64)
34 | if err != nil {
35 | httpx.OkJson(w, apiErr.InvalidParams)
36 | return
37 | }
38 | req.ActionType = ActionType
39 |
40 | l := contact.NewSendMessageLogic(r.Context(), svcCtx)
41 | resp, err := l.SendMessage(&req)
42 | if err != nil {
43 | httpx.Error(w, err)
44 | } else {
45 | httpx.OkJson(w, resp)
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/service/http/internal/handler/routes.go:
--------------------------------------------------------------------------------
1 | // Code generated by goctl. DO NOT EDIT.
2 | package handler
3 |
4 | import (
5 | "net/http"
6 |
7 | contact "h68u-tiktok-app-microservice/service/http/internal/handler/contact"
8 | user "h68u-tiktok-app-microservice/service/http/internal/handler/user"
9 | video "h68u-tiktok-app-microservice/service/http/internal/handler/video"
10 | "h68u-tiktok-app-microservice/service/http/internal/svc"
11 |
12 | "github.com/zeromicro/go-zero/rest"
13 | )
14 |
15 | func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
16 | server.AddRoutes(
17 | []rest.Route{
18 | {
19 | Method: http.MethodPost,
20 | Path: "/douyin/user/register",
21 | Handler: user.RegisterHandler(serverCtx),
22 | },
23 | {
24 | Method: http.MethodPost,
25 | Path: "/douyin/user/login",
26 | Handler: user.LoginHandler(serverCtx),
27 | },
28 | },
29 | )
30 |
31 | server.AddRoutes(
32 | rest.WithMiddlewares(
33 | []rest.Middleware{serverCtx.Auth},
34 | []rest.Route{
35 | {
36 | Method: http.MethodGet,
37 | Path: "/douyin/user",
38 | Handler: user.GetUserInfoHandler(serverCtx),
39 | },
40 | {
41 | Method: http.MethodPost,
42 | Path: "/douyin/relation/action",
43 | Handler: user.FollowHandler(serverCtx),
44 | },
45 | {
46 | Method: http.MethodGet,
47 | Path: "/douyin/relation/follow/list",
48 | Handler: user.FollowListHandler(serverCtx),
49 | },
50 | {
51 | Method: http.MethodGet,
52 | Path: "/douyin/relation/follower/list",
53 | Handler: user.FansListHandler(serverCtx),
54 | },
55 | }...,
56 | ),
57 | )
58 |
59 | server.AddRoutes(
60 | []rest.Route{
61 | {
62 | Method: http.MethodGet,
63 | Path: "/douyin/feed",
64 | Handler: video.GetVideoListHandler(serverCtx),
65 | },
66 | },
67 | )
68 |
69 | server.AddRoutes(
70 | rest.WithMiddlewares(
71 | []rest.Middleware{serverCtx.Auth},
72 | []rest.Route{
73 | {
74 | Method: http.MethodPost,
75 | Path: "/douyin/publish/action",
76 | Handler: video.PublishVideoHandler(serverCtx),
77 | },
78 | {
79 | Method: http.MethodGet,
80 | Path: "/douyin/publish/list",
81 | Handler: video.PublishedListHandler(serverCtx),
82 | },
83 | {
84 | Method: http.MethodPost,
85 | Path: "/douyin/favorite/action",
86 | Handler: video.FavoriteVideoHandler(serverCtx),
87 | },
88 | {
89 | Method: http.MethodGet,
90 | Path: "/douyin/favorite/list",
91 | Handler: video.FavoriteListHandler(serverCtx),
92 | },
93 | {
94 | Method: http.MethodPost,
95 | Path: "/douyin/comment/action",
96 | Handler: video.CommentVideoHandler(serverCtx),
97 | },
98 | {
99 | Method: http.MethodGet,
100 | Path: "/douyin/comment/list",
101 | Handler: video.CommentListHandler(serverCtx),
102 | },
103 | }...,
104 | ),
105 | )
106 |
107 | server.AddRoutes(
108 | rest.WithMiddlewares(
109 | []rest.Middleware{serverCtx.Auth},
110 | []rest.Route{
111 | {
112 | Method: http.MethodGet,
113 | Path: "/douyin/relation/friend/list",
114 | Handler: contact.GetFriendListHandler(serverCtx),
115 | },
116 | {
117 | Method: http.MethodPost,
118 | Path: "/douyin/message/action",
119 | Handler: contact.SendMessageHandler(serverCtx),
120 | },
121 | {
122 | Method: http.MethodGet,
123 | Path: "/douyin/message/chat",
124 | Handler: contact.GetHistoryMessageHandler(serverCtx),
125 | },
126 | }...,
127 | ),
128 | )
129 | }
130 |
--------------------------------------------------------------------------------
/service/http/internal/handler/user/fansListHandler.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/user"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func FansListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.FansListRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := user.NewFansListLogic(r.Context(), svcCtx)
21 | resp, err := l.FansList(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/user/followHandler.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/user"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func FollowHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.FollowRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := user.NewFollowLogic(r.Context(), svcCtx)
21 | resp, err := l.Follow(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/user/followListHandler.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/user"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func FollowListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.FollowListRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := user.NewFollowListLogic(r.Context(), svcCtx)
21 | resp, err := l.FollowList(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/user/getUserInfoHandler.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/user"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func GetUserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.GetUserInfoRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := user.NewGetUserInfoLogic(r.Context(), svcCtx)
21 | resp, err := l.GetUserInfo(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/user/loginHandler.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/user"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.LoginRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := user.NewLoginLogic(r.Context(), svcCtx)
21 | resp, err := l.Login(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/user/registerHandler.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/user"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.RegisterRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := user.NewRegisterLogic(r.Context(), svcCtx)
21 | resp, err := l.Register(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/video/commentListHandler.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/video"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func CommentListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.CommentListRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := video.NewCommentListLogic(r.Context(), svcCtx)
21 | resp, err := l.CommentList(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/video/commentVideoHandler.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/video"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func CommentVideoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.CommentVideoRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := video.NewCommentVideoLogic(r.Context(), svcCtx)
21 | resp, err := l.CommentVideo(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/video/favoriteListHandler.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/video"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func FavoriteListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.FavoriteListRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := video.NewFavoriteListLogic(r.Context(), svcCtx)
21 | resp, err := l.FavoriteList(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/video/favoriteVideoHandler.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/video"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func FavoriteVideoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.FavoriteVideoRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := video.NewFavoriteVideoLogic(r.Context(), svcCtx)
21 | resp, err := l.FavoriteVideo(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/video/getVideoListHandler.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/video"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func GetVideoListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.GetVideoListRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := video.NewGetVideoListLogic(r.Context(), svcCtx)
21 | resp, err := l.GetVideoList(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/handler/video/publishVideoHandler.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "bytes"
5 | "github.com/h2non/filetype"
6 | "github.com/zeromicro/go-zero/core/logx"
7 | "github.com/zeromicro/go-zero/rest/httpx"
8 | "h68u-tiktok-app-microservice/common/error/apiErr"
9 | "h68u-tiktok-app-microservice/common/oss"
10 | "h68u-tiktok-app-microservice/common/utils"
11 | logic "h68u-tiktok-app-microservice/service/http/internal/logic/video"
12 | "h68u-tiktok-app-microservice/service/http/internal/svc"
13 | "h68u-tiktok-app-microservice/service/http/internal/types"
14 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
15 | "io"
16 | "mime/multipart"
17 | "net/http"
18 | "path/filepath"
19 | )
20 |
21 | func PublishVideoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
22 | return func(w http.ResponseWriter, r *http.Request) {
23 | var req types.PublishVideoRequest
24 | if err := httpx.Parse(r, &req); err != nil {
25 | httpx.Error(w, err)
26 | return
27 | }
28 |
29 | // 获取用户id
30 | userId, err := utils.GetUserIDFormToken(req.Token, svcCtx.Config.Auth.AccessSecret)
31 | if err != nil {
32 | httpx.Error(w, apiErr.InvalidToken)
33 | return
34 | }
35 |
36 | // 获取文件
37 | file, fileHeader, err := r.FormFile("data")
38 | if err != nil {
39 | httpx.Error(w, apiErr.FileUploadFailed.WithDetails(err.Error()))
40 | return
41 | }
42 | defer func(file multipart.File) {
43 | _ = file.Close()
44 | }(file)
45 |
46 | // 判断是否为视频
47 | tmpFile, err := fileHeader.Open()
48 | if err != nil {
49 | httpx.Error(w, apiErr.FileUploadFailed.WithDetails(err.Error()))
50 | return
51 | }
52 | buf := bytes.NewBuffer(nil)
53 | if _, err := io.Copy(buf, tmpFile); err != nil {
54 | httpx.Error(w, apiErr.FileUploadFailed.WithDetails(err.Error()))
55 | return
56 | }
57 | if !filetype.IsVideo(buf.Bytes()) {
58 | httpx.Error(w, apiErr.FileIsNotVideo)
59 | return
60 | }
61 | if err = tmpFile.Close(); err != nil {
62 | httpx.Error(w, apiErr.FileUploadFailed.WithDetails(err.Error()))
63 | return
64 | }
65 |
66 | // 使用uuid重新生成文件名
67 | fileName := utils.GetUUID() + filepath.Ext(fileHeader.Filename)
68 |
69 | // 存储到oss
70 | ok, err := oss.UploadVideoToOss(svcCtx.AliyunClient, svcCtx.Config.OSS.BucketName, fileName, file)
71 | if err != nil {
72 | httpx.Error(w, apiErr.FileUploadFailed.WithDetails(err.Error()))
73 | return
74 | } else if !ok {
75 | httpx.Error(w, apiErr.FileUploadFailed.WithDetails("upload video to oss error"))
76 | return
77 | }
78 |
79 | videoUrl, imgUrl := oss.GetOssVideoUrlAndImgUrl(svcCtx.Config.OSS, fileName)
80 |
81 | // 调用rpc服务
82 | _, err = svcCtx.VideoRpc.PublishVideo(r.Context(), &video.PublishVideoRequest{
83 | Video: &video.VideoInfo{
84 | AuthorId: userId,
85 | Title: req.Title,
86 | PlayUrl: videoUrl,
87 | CoverUrl: imgUrl,
88 | },
89 | })
90 |
91 | if err != nil {
92 | logx.WithContext(r.Context()).Errorf("PublishVideo rpc error: %v", err)
93 | httpx.Error(w, apiErr.InternalError(r.Context(), err.Error()))
94 | return
95 | }
96 |
97 | //httpx.OkJson(w, apiErr.Success)
98 |
99 | l := logic.NewPublishVideoLogic(r.Context(), svcCtx)
100 | resp, err := l.PublishVideo(&req)
101 | if err != nil {
102 | httpx.Error(w, err)
103 | } else {
104 | httpx.OkJson(w, resp)
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/service/http/internal/handler/video/publishedListHandler.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/zeromicro/go-zero/rest/httpx"
7 | "h68u-tiktok-app-microservice/service/http/internal/logic/video"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | )
11 |
12 | func PublishedListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
13 | return func(w http.ResponseWriter, r *http.Request) {
14 | var req types.PublishedListRequest
15 | if err := httpx.Parse(r, &req); err != nil {
16 | httpx.Error(w, err)
17 | return
18 | }
19 |
20 | l := video.NewPublishedListLogic(r.Context(), svcCtx)
21 | resp, err := l.PublishedList(&req)
22 | if err != nil {
23 | httpx.Error(w, err)
24 | } else {
25 | httpx.OkJson(w, resp)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/service/http/internal/logic/contact/getHistoryMessageLogic.go:
--------------------------------------------------------------------------------
1 | package contact
2 |
3 | import (
4 | "context"
5 | "github.com/zeromicro/go-zero/core/logx"
6 | "h68u-tiktok-app-microservice/common/error/apiErr"
7 | "h68u-tiktok-app-microservice/common/utils"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | "h68u-tiktok-app-microservice/service/rpc/contact/contactclient"
11 | )
12 |
13 | type GetHistoryMessageLogic struct {
14 | logx.Logger
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | }
18 |
19 | func NewGetHistoryMessageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetHistoryMessageLogic {
20 | return &GetHistoryMessageLogic{
21 | Logger: logx.WithContext(ctx),
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | }
25 | }
26 |
27 | func (l *GetHistoryMessageLogic) GetHistoryMessage(req *types.GetHistoryMessageRequest) (resp *types.GetHistoryMessageReply, err error) {
28 | logx.WithContext(l.ctx).Infof("获取历史消息列表请求参数: %v", req)
29 |
30 | // 获取登录用户id
31 | UserId, err := utils.GetUserIDFormToken(req.Token, l.svcCtx.Config.Auth.AccessSecret)
32 | if err != nil {
33 | return nil, apiErr.InvalidToken
34 | }
35 |
36 | // 获取我的发送的消息
37 | MessageSent, err := l.svcCtx.ContactRpc.GetMessageList(l.ctx, &contactclient.GetMessageListRequest{
38 | FromId: UserId,
39 | ToId: req.ToUserId,
40 | })
41 |
42 | if err != nil {
43 | logx.WithContext(l.ctx).Errorf("获取历史消息列表失败: %v", err)
44 | return nil, apiErr.InternalError(l.ctx, err.Error())
45 | }
46 |
47 | // 获取我的接收的消息
48 | MessageReceived, err := l.svcCtx.ContactRpc.GetMessageList(l.ctx, &contactclient.GetMessageListRequest{
49 | FromId: req.ToUserId,
50 | ToId: UserId,
51 | })
52 |
53 | if err != nil {
54 | logx.WithContext(l.ctx).Errorf("获取历史消息列表失败: %v", err)
55 | return nil, apiErr.InternalError(l.ctx, err.Error())
56 | }
57 |
58 | var messageList []types.Message
59 |
60 | for _, message := range MessageSent.Messages {
61 | messageList = append(messageList, types.Message{
62 | Id: message.Id,
63 | Content: message.Content,
64 | CreateTime: message.CreateTime,
65 | FromUserId: message.FromId,
66 | ToUserId: message.ToId,
67 | })
68 | }
69 |
70 | for _, message := range MessageReceived.Messages {
71 | messageList = append(messageList, types.Message{
72 | Id: message.Id,
73 | Content: message.Content,
74 | CreateTime: message.CreateTime,
75 | FromUserId: message.FromId,
76 | ToUserId: message.ToId,
77 | })
78 | }
79 |
80 | return &types.GetHistoryMessageReply{
81 | BasicReply: types.BasicReply(apiErr.Success),
82 | MessageList: messageList,
83 | }, nil
84 | }
85 |
--------------------------------------------------------------------------------
/service/http/internal/logic/contact/sendMessageLogic.go:
--------------------------------------------------------------------------------
1 | package contact
2 |
3 | import (
4 | "context"
5 | "github.com/zeromicro/go-zero/core/logx"
6 | "h68u-tiktok-app-microservice/common/error/apiErr"
7 | "h68u-tiktok-app-microservice/common/error/rpcErr"
8 | "h68u-tiktok-app-microservice/common/utils"
9 | "h68u-tiktok-app-microservice/service/http/internal/svc"
10 | "h68u-tiktok-app-microservice/service/http/internal/types"
11 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
12 | )
13 |
14 | type SendMessageLogic struct {
15 | logx.Logger
16 | ctx context.Context
17 | svcCtx *svc.ServiceContext
18 | }
19 |
20 | func NewSendMessageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SendMessageLogic {
21 | return &SendMessageLogic{
22 | Logger: logx.WithContext(ctx),
23 | ctx: ctx,
24 | svcCtx: svcCtx,
25 | }
26 | }
27 |
28 | func (l *SendMessageLogic) SendMessage(req *types.SendMessageRequest) (resp *types.SendMessageReply, err error) {
29 | // 参数检查
30 | UserId, err := utils.GetUserIDFormToken(req.Token, l.svcCtx.Config.Auth.AccessSecret)
31 | if err != nil {
32 | return nil, apiErr.InvalidToken
33 | }
34 | if UserId == req.ToUserId {
35 | return nil, apiErr.InvalidParams.WithDetails("不能发消息给自己")
36 | }
37 | //发送
38 | if req.ActionType == 1 {
39 | _, err = l.svcCtx.ContactRpc.CreateMessage(l.ctx, &contact.CreateMessageRequest{
40 | FromId: UserId,
41 | ToId: req.ToUserId,
42 | Content: req.Content,
43 | })
44 | if rpcErr.Is(err, rpcErr.UserNotExist) {
45 | return nil, apiErr.UserNotFound
46 | } else if err != nil {
47 | return nil, apiErr.InternalError(l.ctx, err.Error())
48 | }
49 | } else {
50 | return nil, apiErr.MessageActionUnknown.WithDetails(utils.Int642Str(req.ActionType))
51 | }
52 | return &types.SendMessageReply{
53 | BasicReply: types.BasicReply(apiErr.Success),
54 | }, nil
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/service/http/internal/logic/user/fansListLogic.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/error/apiErr"
6 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
7 |
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 |
11 | "github.com/zeromicro/go-zero/core/logx"
12 | )
13 |
14 | type FansListLogic struct {
15 | logx.Logger
16 | ctx context.Context
17 | svcCtx *svc.ServiceContext
18 | }
19 |
20 | func NewFansListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FansListLogic {
21 | return &FansListLogic{
22 | Logger: logx.WithContext(ctx),
23 | ctx: ctx,
24 | svcCtx: svcCtx,
25 | }
26 | }
27 |
28 | func (l *FansListLogic) FansList(req *types.FansListRequest) (resp *types.FansListReply, err error) {
29 | logx.WithContext(l.ctx).Infof("获取粉丝列表: %v", req)
30 |
31 | //拿到粉丝列表的信息
32 | GetFansListReply, err := l.svcCtx.UserRpc.GetFansList(l.ctx, &user.GetFansListRequest{
33 | UserId: req.UserId,
34 | })
35 | if err != nil {
36 | logx.WithContext(l.ctx).Errorf("FansListLogic.FansList GetFansList err: %+v", err)
37 | return nil, apiErr.InternalError(l.ctx, err.Error())
38 | }
39 | var fansList []types.User
40 | for _, fans := range GetFansListReply.FansList {
41 | //先判断你是否关注你的粉丝
42 | isFollowReply, err := l.svcCtx.UserRpc.IsFollow(l.ctx, &user.IsFollowRequest{
43 | UserId: req.UserId,
44 | FollowUserId: fans.Id,
45 | })
46 | if err != nil {
47 | logx.WithContext(l.ctx).Errorf("FansListLogic.FansList IsFollow err: %v", err)
48 | return nil, apiErr.InternalError(l.ctx, err.Error())
49 | }
50 | fansList = append(fansList, types.User{
51 | Id: fans.Id,
52 | Name: fans.Name,
53 | FollowCount: fans.FollowCount,
54 | FollowerCount: fans.FansCount,
55 | IsFollow: isFollowReply.IsFollow,
56 | })
57 | }
58 |
59 | return &types.FansListReply{
60 | BasicReply: types.BasicReply(apiErr.Success),
61 | Users: fansList,
62 | }, nil
63 | }
64 |
--------------------------------------------------------------------------------
/service/http/internal/logic/user/followListLogic.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/error/apiErr"
6 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
7 |
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 |
11 | "github.com/zeromicro/go-zero/core/logx"
12 | )
13 |
14 | type FollowListLogic struct {
15 | logx.Logger
16 | ctx context.Context
17 | svcCtx *svc.ServiceContext
18 | }
19 |
20 | func NewFollowListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FollowListLogic {
21 | return &FollowListLogic{
22 | Logger: logx.WithContext(ctx),
23 | ctx: ctx,
24 | svcCtx: svcCtx,
25 | }
26 | }
27 |
28 | func (l *FollowListLogic) FollowList(req *types.FollowListRequest) (resp *types.FollowListReply, err error) {
29 | logx.WithContext(l.ctx).Infof("获取关注列表: %v", req)
30 |
31 | //拿到关注列表的数据
32 | GetFollowListReply, err := l.svcCtx.UserRpc.GetFollowList(l.ctx, &user.GetFollowListRequest{
33 | UserId: req.UserId,
34 | })
35 | if err != nil {
36 | logx.WithContext(l.ctx).Errorf("GetFollowList failed, err:%v", err)
37 | return nil, apiErr.InternalError(l.ctx, err.Error())
38 | }
39 |
40 | var followList []types.User
41 | for _, follow := range GetFollowListReply.FollowList {
42 | ////判断关注者是否关注了你
43 | //isFollowReply, err := l.svcCtx.UserRpc.IsFollow(l.ctx, &user.IsFollowRequest{
44 | // UserId: follow.Id,
45 | // FollowUserId: int64(req.UserId),
46 | //})
47 | if err != nil {
48 | logx.WithContext(l.ctx).Errorf("IsFollow failed, err:%v", err)
49 | return nil, apiErr.InternalError(l.ctx, err.Error())
50 | }
51 | followList = append(followList, types.User{
52 | Id: follow.Id,
53 | Name: follow.Name,
54 | FollowCount: follow.FollowCount,
55 | FollowerCount: follow.FansCount,
56 | //IsFollow: isFollowReply.IsFollow,
57 | IsFollow: true, // 这个字段的含义就是这么奇怪(
58 | })
59 | }
60 | return &types.FollowListReply{
61 | BasicReply: types.BasicReply(apiErr.Success),
62 | Users: followList,
63 | }, nil
64 | }
65 |
--------------------------------------------------------------------------------
/service/http/internal/logic/user/getUserInfoLogic.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/error/apiErr"
6 | "h68u-tiktok-app-microservice/common/error/rpcErr"
7 | "h68u-tiktok-app-microservice/common/utils"
8 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
9 |
10 | "h68u-tiktok-app-microservice/service/http/internal/svc"
11 | "h68u-tiktok-app-microservice/service/http/internal/types"
12 |
13 | "github.com/zeromicro/go-zero/core/logx"
14 | )
15 |
16 | type GetUserInfoLogic struct {
17 | logx.Logger
18 | ctx context.Context
19 | svcCtx *svc.ServiceContext
20 | }
21 |
22 | func NewGetUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserInfoLogic {
23 | return &GetUserInfoLogic{
24 | Logger: logx.WithContext(ctx),
25 | ctx: ctx,
26 | svcCtx: svcCtx,
27 | }
28 | }
29 |
30 | func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserInfoRequest) (resp *types.GetUserInfoReply, err error) {
31 | logx.WithContext(l.ctx).Infof("获取用户信息: %v", req)
32 |
33 | //从token获取自己的id
34 | id, err := utils.GetUserIDFormToken(req.Token, l.svcCtx.Config.Auth.AccessSecret)
35 | if err != nil {
36 | return nil, apiErr.InvalidToken
37 | }
38 |
39 | //获取用户信息(名字与id)
40 | getUserByIdReply, err := l.svcCtx.UserRpc.GetUserById(l.ctx, &user.GetUserByIdRequest{
41 | Id: req.UserId,
42 | })
43 | if rpcErr.Is(err, rpcErr.UserNotExist) {
44 | return nil, apiErr.UserNotFound
45 | } else if err != nil {
46 | logx.WithContext(l.ctx).Errorf("获取用户信息失败: %v", err)
47 | return nil, apiErr.InternalError(l.ctx, err.Error())
48 | }
49 |
50 | //判断是否关注了该用户
51 | isFollowReply, err := l.svcCtx.UserRpc.IsFollow(l.ctx, &user.IsFollowRequest{
52 | UserId: id,
53 | FollowUserId: getUserByIdReply.Id,
54 | })
55 | if err != nil {
56 | logx.WithContext(l.ctx).Errorf("获取是否关注失败: %v", err)
57 | return nil, apiErr.InternalError(l.ctx, err.Error())
58 | }
59 |
60 | return &types.GetUserInfoReply{
61 | BasicReply: types.BasicReply(apiErr.Success),
62 | User: types.User{
63 | Id: getUserByIdReply.Id,
64 | Name: getUserByIdReply.Name,
65 | FollowCount: getUserByIdReply.FollowCount,
66 | FollowerCount: getUserByIdReply.FanCount,
67 | IsFollow: isFollowReply.IsFollow,
68 | },
69 | }, nil
70 | }
71 |
--------------------------------------------------------------------------------
/service/http/internal/logic/user/loginLogic.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "context"
5 | "golang.org/x/crypto/bcrypt"
6 | "h68u-tiktok-app-microservice/common/error/apiErr"
7 | "h68u-tiktok-app-microservice/common/error/rpcErr"
8 | "h68u-tiktok-app-microservice/common/utils"
9 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
10 |
11 | "h68u-tiktok-app-microservice/service/http/internal/svc"
12 | "h68u-tiktok-app-microservice/service/http/internal/types"
13 |
14 | "github.com/zeromicro/go-zero/core/logx"
15 | )
16 |
17 | type LoginLogic struct {
18 | logx.Logger
19 | ctx context.Context
20 | svcCtx *svc.ServiceContext
21 | }
22 |
23 | func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {
24 | return &LoginLogic{
25 | Logger: logx.WithContext(ctx),
26 | ctx: ctx,
27 | svcCtx: svcCtx,
28 | }
29 | }
30 |
31 | func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginReply, err error) {
32 |
33 | // 调用rpc
34 | GetUserByNameReply, err := l.svcCtx.UserRpc.GetUserByName(l.ctx, &user.GetUserByNameRequest{
35 | Name: req.Username,
36 | })
37 | if rpcErr.Is(err, rpcErr.UserNotExist) {
38 | return nil, apiErr.UserNotFound
39 | } else if err != nil {
40 | logx.WithContext(l.ctx).Errorf("LoginLogic.Login GetUserByName err: %v", err)
41 | return nil, apiErr.InternalError(l.ctx, err.Error())
42 | }
43 |
44 | // 校验密码
45 | err = bcrypt.CompareHashAndPassword([]byte(GetUserByNameReply.Password), []byte(req.Password))
46 | if err != nil {
47 | return nil, apiErr.PasswordIncorrect
48 | }
49 |
50 | // 生成 token
51 | jwtToken, err := utils.CreateToken(
52 | GetUserByNameReply.Id,
53 | l.svcCtx.Config.Auth.AccessSecret,
54 | l.svcCtx.Config.Auth.AccessExpire,
55 | )
56 |
57 | if err != nil {
58 | logx.WithContext(l.ctx).Errorf("LoginLogic.Login CreateToken err: %v", err)
59 | return nil, apiErr.GenerateTokenFailed
60 | }
61 |
62 | return &types.LoginReply{
63 | BasicReply: types.BasicReply(apiErr.Success),
64 | UserId: GetUserByNameReply.Id,
65 | Token: jwtToken,
66 | }, nil
67 | }
68 |
--------------------------------------------------------------------------------
/service/http/internal/logic/user/registerLogic.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/error/apiErr"
6 | "h68u-tiktok-app-microservice/common/error/rpcErr"
7 | "h68u-tiktok-app-microservice/common/utils"
8 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
9 |
10 | "h68u-tiktok-app-microservice/service/http/internal/svc"
11 | "h68u-tiktok-app-microservice/service/http/internal/types"
12 |
13 | "github.com/zeromicro/go-zero/core/logx"
14 | )
15 |
16 | type RegisterLogic struct {
17 | logx.Logger
18 | ctx context.Context
19 | svcCtx *svc.ServiceContext
20 | }
21 |
22 | func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
23 | return &RegisterLogic{
24 | Logger: logx.WithContext(ctx),
25 | ctx: ctx,
26 | svcCtx: svcCtx,
27 | }
28 | }
29 |
30 | func (l *RegisterLogic) Register(req *types.RegisterRequest) (resp *types.RegisterReply, err error) {
31 | logx.WithContext(l.ctx).Infof("注册: %v", req)
32 |
33 | // 参数检查
34 | if len(req.Username) > 32 {
35 | return nil, apiErr.InvalidParams.WithDetails("用户名最长32个字符")
36 | } else if len(req.Password) > 32 {
37 | return nil, apiErr.InvalidParams.WithDetails("密码最长32个字符")
38 | }
39 |
40 | // 调用rpc
41 | CreateUserReply, err := l.svcCtx.UserRpc.CreateUser(l.ctx, &user.CreateUserRequest{
42 | Name: req.Username,
43 | Password: req.Password,
44 | })
45 |
46 | if rpcErr.Is(err, rpcErr.UserAlreadyExist) {
47 | return nil, apiErr.UserAlreadyExist
48 | } else if err != nil {
49 | logx.WithContext(l.ctx).Errorf("调用rpc CreateUser 失败: %v", err)
50 | return nil, apiErr.InternalError(l.ctx, err.Error())
51 | }
52 |
53 | // 生成 token
54 | jwtToken, err := utils.CreateToken(
55 | CreateUserReply.Id,
56 | l.svcCtx.Config.Auth.AccessSecret,
57 | l.svcCtx.Config.Auth.AccessExpire,
58 | )
59 |
60 | if err != nil {
61 | return nil, apiErr.GenerateTokenFailed
62 | }
63 |
64 | return &types.RegisterReply{
65 | BasicReply: types.BasicReply(apiErr.Success),
66 | UserId: CreateUserReply.Id,
67 | Token: jwtToken,
68 | }, nil
69 | }
70 |
--------------------------------------------------------------------------------
/service/http/internal/logic/video/favoriteVideoLogic.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/error/apiErr"
6 | "h68u-tiktok-app-microservice/common/utils"
7 | "h68u-tiktok-app-microservice/service/rpc/video/videoclient"
8 |
9 | "h68u-tiktok-app-microservice/service/http/internal/svc"
10 | "h68u-tiktok-app-microservice/service/http/internal/types"
11 |
12 | "github.com/zeromicro/go-zero/core/logx"
13 | )
14 |
15 | const (
16 | FavoriteVideoAction = 1
17 | UnFavoriteVideoAction = 2
18 | )
19 |
20 | type FavoriteVideoLogic struct {
21 | logx.Logger
22 | ctx context.Context
23 | svcCtx *svc.ServiceContext
24 | }
25 |
26 | func NewFavoriteVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FavoriteVideoLogic {
27 | return &FavoriteVideoLogic{
28 | Logger: logx.WithContext(ctx),
29 | ctx: ctx,
30 | svcCtx: svcCtx,
31 | }
32 | }
33 |
34 | func (l *FavoriteVideoLogic) FavoriteVideo(req *types.FavoriteVideoRequest) (resp *types.FavoriteVideoReply, err error) { // 获取登录用户id
35 | logx.WithContext(l.ctx).Infof("收藏视频: %v", req)
36 |
37 | // 获取登录用户id
38 | UserId, err := utils.GetUserIDFormToken(req.Token, l.svcCtx.Config.Auth.AccessSecret)
39 | if err != nil {
40 | return nil, apiErr.InvalidToken
41 | }
42 |
43 | switch req.ActionType {
44 | case FavoriteVideoAction:
45 | if _, err = l.svcCtx.VideoRpc.FavoriteVideo(l.ctx, &videoclient.FavoriteVideoRequest{
46 | UserId: UserId,
47 | VideoId: req.VideoId,
48 | }); err != nil {
49 | logx.WithContext(l.ctx).Errorf("收藏视频失败: %v", err)
50 | return nil, apiErr.InternalError(l.ctx, err.Error())
51 | }
52 |
53 | case UnFavoriteVideoAction:
54 | if _, err = l.svcCtx.VideoRpc.UnFavoriteVideo(l.ctx, &videoclient.UnFavoriteVideoRequest{
55 | UserId: UserId,
56 | VideoId: req.VideoId,
57 | }); err != nil {
58 | logx.WithContext(l.ctx).Errorf("取消收藏视频失败: %v", err)
59 | return nil, apiErr.InternalError(l.ctx, err.Error())
60 | }
61 |
62 | default:
63 | return nil, apiErr.FavouriteActionUnknown
64 | }
65 |
66 | return &types.FavoriteVideoReply{
67 | BasicReply: types.BasicReply(apiErr.Success),
68 | }, nil
69 | }
70 |
--------------------------------------------------------------------------------
/service/http/internal/logic/video/getVideoListLogic.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/error/apiErr"
6 | "h68u-tiktok-app-microservice/common/utils"
7 | "h68u-tiktok-app-microservice/service/rpc/user/userclient"
8 | "h68u-tiktok-app-microservice/service/rpc/video/videoclient"
9 | "time"
10 |
11 | "h68u-tiktok-app-microservice/service/http/internal/svc"
12 | "h68u-tiktok-app-microservice/service/http/internal/types"
13 |
14 | "github.com/zeromicro/go-zero/core/logx"
15 | )
16 |
17 | type GetVideoListLogic struct {
18 | logx.Logger
19 | ctx context.Context
20 | svcCtx *svc.ServiceContext
21 | }
22 |
23 | func NewGetVideoListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetVideoListLogic {
24 | return &GetVideoListLogic{
25 | Logger: logx.WithContext(ctx),
26 | ctx: ctx,
27 | svcCtx: svcCtx,
28 | }
29 | }
30 |
31 | // lastTime 值0时不限制;限制返回视频的最新投稿时间戳,精确到秒,不填表示当前时间
32 | // nextTime 本次返回的视频中,发布最早的时间,作为下次请求时的latest_time
33 |
34 | func (l *GetVideoListLogic) GetVideoList(req *types.GetVideoListRequest) (resp *types.GetVideoListReply, err error) {
35 | logx.WithContext(l.ctx).Infof("GetVideoList req: %+v", req)
36 |
37 | // 获取登录用户id
38 | UserId, err := utils.GetUserIDFormToken(req.Token, l.svcCtx.Config.Auth.AccessSecret)
39 | if err != nil {
40 | UserId = 0
41 | }
42 |
43 | // 获取视频列表
44 | GetVideoListReply, err := l.svcCtx.VideoRpc.GetVideoList(l.ctx, &videoclient.GetVideoListRequest{
45 | Num: 20,
46 | LatestTime: func() int64 {
47 | if req.LatestTime == 0 {
48 | return time.Now().Unix()
49 | } else {
50 | return req.LatestTime / 1000 // 前端传入的时间戳精确到毫秒,转换为秒
51 | }
52 | }(),
53 | })
54 | if err != nil {
55 | logx.WithContext(l.ctx).Errorf("GetVideoList err: %+v", err)
56 | return nil, apiErr.InternalError(l.ctx, err.Error())
57 | }
58 |
59 | // 封装返回体
60 | resp = &types.GetVideoListReply{}
61 | resp.BasicReply = types.BasicReply(apiErr.Success)
62 | if len(GetVideoListReply.VideoList) != 0 {
63 | resp.NextTime = GetVideoListReply.VideoList[len(GetVideoListReply.VideoList)-1].CreateTime
64 | }
65 |
66 | for _, v := range GetVideoListReply.VideoList {
67 |
68 | // 获取视频作者信息
69 | GetUserInfoReply, err := l.svcCtx.UserRpc.GetUserById(l.ctx, &userclient.GetUserByIdRequest{
70 | Id: v.AuthorId,
71 | })
72 | if err != nil {
73 | logx.WithContext(l.ctx).Errorf("GetUserById err: %+v", err)
74 | return nil, apiErr.InternalError(l.ctx, err.Error())
75 | }
76 |
77 | // 获取用户关注状态
78 | isFollowed := false
79 | if UserId != 0 {
80 | IsFollowedReply, err := l.svcCtx.UserRpc.IsFollow(l.ctx, &userclient.IsFollowRequest{
81 | UserId: UserId,
82 | FollowUserId: v.AuthorId,
83 | })
84 | if err != nil {
85 | logx.WithContext(l.ctx).Errorf("IsFollow err: %+v", err)
86 | return nil, apiErr.InternalError(l.ctx, err.Error())
87 | }
88 | isFollowed = IsFollowedReply.IsFollow
89 | }
90 |
91 | // 获取视频收藏状态
92 | isFavorite := false
93 | if UserId != 0 {
94 | IsFavoriteVideoReply, err := l.svcCtx.VideoRpc.IsFavoriteVideo(l.ctx, &videoclient.IsFavoriteVideoRequest{
95 | UserId: UserId,
96 | VideoId: v.Id,
97 | })
98 | if err != nil {
99 | logx.WithContext(l.ctx).Errorf("IsFavoriteVideo err: %+v", err)
100 | return nil, apiErr.InternalError(l.ctx, err.Error())
101 | }
102 | isFavorite = IsFavoriteVideoReply.IsFavorite
103 | }
104 |
105 | // 封装返回体
106 | resp.VideoList = append(resp.VideoList, types.Video{
107 | Id: v.Id,
108 | Title: v.Title,
109 | Author: types.User{
110 | Id: v.AuthorId,
111 | Name: GetUserInfoReply.Name,
112 | FollowCount: GetUserInfoReply.FollowCount,
113 | FollowerCount: GetUserInfoReply.FanCount,
114 | IsFollow: isFollowed,
115 | },
116 | PlayUrl: v.PlayUrl,
117 | CoverUrl: v.CoverUrl,
118 | FavoriteCount: v.FavoriteCount,
119 | CommentCount: v.CommentCount,
120 | IsFavorite: isFavorite,
121 | })
122 |
123 | }
124 |
125 | return resp, nil
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/service/http/internal/logic/video/publishVideoLogic.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/error/apiErr"
6 | "h68u-tiktok-app-microservice/service/http/internal/svc"
7 | "h68u-tiktok-app-microservice/service/http/internal/types"
8 |
9 | "github.com/zeromicro/go-zero/core/logx"
10 | )
11 |
12 | type PublishVideoLogic struct {
13 | logx.Logger
14 | ctx context.Context
15 | svcCtx *svc.ServiceContext
16 | }
17 |
18 | func NewPublishVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PublishVideoLogic {
19 | return &PublishVideoLogic{
20 | Logger: logx.WithContext(ctx),
21 | ctx: ctx,
22 | svcCtx: svcCtx,
23 | }
24 | }
25 |
26 | func (l *PublishVideoLogic) PublishVideo(req *types.PublishVideoRequest) (resp *types.PublishVideoReply, err error) {
27 | logx.WithContext(l.ctx).Infof("发布视频: %v", req)
28 |
29 | // 因为要处理文件,业务逻辑在handler中
30 |
31 | return &types.PublishVideoReply{
32 | BasicReply: types.BasicReply(apiErr.Success),
33 | }, nil
34 | }
35 |
--------------------------------------------------------------------------------
/service/http/internal/logic/video/publishedListLogic.go:
--------------------------------------------------------------------------------
1 | package video
2 |
3 | import (
4 | "context"
5 | "github.com/zeromicro/go-zero/core/logx"
6 | "h68u-tiktok-app-microservice/common/error/apiErr"
7 | "h68u-tiktok-app-microservice/common/utils"
8 | "h68u-tiktok-app-microservice/service/http/internal/svc"
9 | "h68u-tiktok-app-microservice/service/http/internal/types"
10 | "h68u-tiktok-app-microservice/service/rpc/user/userclient"
11 | "h68u-tiktok-app-microservice/service/rpc/video/videoclient"
12 | )
13 |
14 | type PublishedListLogic struct {
15 | logx.Logger
16 | ctx context.Context
17 | svcCtx *svc.ServiceContext
18 | }
19 |
20 | func NewPublishedListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PublishedListLogic {
21 | return &PublishedListLogic{
22 | Logger: logx.WithContext(ctx),
23 | ctx: ctx,
24 | svcCtx: svcCtx,
25 | }
26 | }
27 |
28 | func (l *PublishedListLogic) PublishedList(req *types.PublishedListRequest) (resp *types.PublishedListReply, err error) {
29 | logx.WithContext(l.ctx).Infof("获取发布列表: %v", req)
30 |
31 | // 获取登录用户id
32 | UserId, err := utils.GetUserIDFormToken(req.Token, l.svcCtx.Config.Auth.AccessSecret)
33 | if err != nil {
34 | return nil, apiErr.InvalidToken
35 | }
36 | //获取发布列表数据
37 | publishedList, err := l.svcCtx.VideoRpc.GetVideoListByAuthor(l.ctx, &videoclient.GetVideoListByAuthorRequest{
38 | AuthorId: req.UserId,
39 | })
40 | if err != nil {
41 | logx.WithContext(l.ctx).Errorf("获取发布列表失败: %v", err)
42 | return nil, apiErr.InternalError(l.ctx, err.Error())
43 | }
44 | //获取用户信息
45 | authorInfo, err := l.svcCtx.UserRpc.GetUserById(l.ctx, &userclient.GetUserByIdRequest{
46 | Id: req.UserId,
47 | })
48 | if err != nil {
49 | logx.WithContext(l.ctx).Errorf("获取用户信息失败: %v", err)
50 | return nil, apiErr.InternalError(l.ctx, err.Error())
51 | }
52 | //封装数据
53 | videoList := make([]types.Video, 0, len(publishedList.VideoList))
54 | if UserId == req.UserId {
55 | for _, video := range publishedList.VideoList {
56 |
57 | videoList = append(videoList, types.Video{
58 | Id: video.Id,
59 | Title: video.Title,
60 | Author: types.User{
61 | Id: authorInfo.Id,
62 | Name: authorInfo.Name,
63 | FollowCount: authorInfo.FollowCount,
64 | FollowerCount: authorInfo.FanCount,
65 | // 这里查询的是用户自己的发布列表,无需获取用户是否关注
66 | IsFollow: false,
67 | },
68 | PlayUrl: video.PlayUrl,
69 | CoverUrl: video.CoverUrl,
70 | FavoriteCount: video.FavoriteCount,
71 | CommentCount: video.CommentCount,
72 | // 这里查询的是用户自己的发布列表,无需获取用户是否点赞
73 | IsFavorite: true,
74 | })
75 | }
76 | } else {
77 | //是否关注作者
78 | isFollowRes, err := l.svcCtx.UserRpc.IsFollow(l.ctx, &userclient.IsFollowRequest{
79 | UserId: UserId,
80 | FollowUserId: req.UserId,
81 | })
82 | if err != nil {
83 | logx.WithContext(l.ctx).Errorf("获取用户是否关注失败: %v", err)
84 | return nil, apiErr.InternalError(l.ctx, err.Error())
85 | }
86 |
87 | author := types.User{
88 | Id: authorInfo.Id,
89 | Name: authorInfo.Name,
90 | FollowCount: authorInfo.FollowCount,
91 | FollowerCount: authorInfo.FanCount,
92 | IsFollow: isFollowRes.IsFollow,
93 | }
94 | for _, video := range publishedList.VideoList {
95 | //是否点赞
96 | isFavoriteRes, err := l.svcCtx.VideoRpc.IsFavoriteVideo(l.ctx, &videoclient.IsFavoriteVideoRequest{
97 | UserId: UserId,
98 | VideoId: video.Id,
99 | })
100 | if err != nil {
101 | logx.WithContext(l.ctx).Errorf("获取用户是否点赞失败: %v", err)
102 | return nil, apiErr.InternalError(l.ctx, err.Error())
103 | }
104 | videoList = append(videoList, types.Video{
105 | Id: video.Id,
106 | Title: video.Title,
107 | Author: author,
108 | PlayUrl: video.PlayUrl,
109 | CoverUrl: video.CoverUrl,
110 | FavoriteCount: video.FavoriteCount,
111 | CommentCount: video.CommentCount,
112 | IsFavorite: isFavoriteRes.IsFavorite,
113 | })
114 | }
115 |
116 | }
117 |
118 | return &types.PublishedListReply{
119 | BasicReply: types.BasicReply(apiErr.Success),
120 | VideoList: videoList,
121 | }, nil
122 | }
123 |
--------------------------------------------------------------------------------
/service/http/internal/middleware/authMiddleware.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/zeromicro/go-zero/rest/httpx"
5 | "h68u-tiktok-app-microservice/common/error/apiErr"
6 | "h68u-tiktok-app-microservice/common/utils"
7 | "h68u-tiktok-app-microservice/service/http/internal/config"
8 | "net/http"
9 | )
10 |
11 | type AuthMiddleware struct {
12 | Config config.Config
13 | }
14 |
15 | func NewAuthMiddleware(c config.Config) *AuthMiddleware {
16 | return &AuthMiddleware{
17 | Config: c,
18 | }
19 | }
20 |
21 | func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
22 | return func(w http.ResponseWriter, r *http.Request) {
23 |
24 | var token string
25 | if token = r.URL.Query().Get("token"); token == "" {
26 | token = r.PostFormValue("token")
27 | }
28 | if token == "" {
29 | httpx.OkJson(w, apiErr.NotLogin)
30 | return
31 | }
32 | isTimeOut, err := utils.ValidToken(token, m.Config.Auth.AccessSecret)
33 | if err != nil || isTimeOut {
34 | httpx.OkJson(w, apiErr.InvalidToken)
35 | return
36 | }
37 |
38 | next(w, r)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/service/http/internal/svc/serviceContext.go:
--------------------------------------------------------------------------------
1 | package svc
2 |
3 | import (
4 | "github.com/aliyun/aliyun-oss-go-sdk/oss"
5 | "github.com/hibiken/asynq"
6 | "github.com/zeromicro/go-zero/rest"
7 | "github.com/zeromicro/go-zero/zrpc"
8 | oss2 "h68u-tiktok-app-microservice/common/oss"
9 | "h68u-tiktok-app-microservice/service/http/internal/config"
10 | "h68u-tiktok-app-microservice/service/http/internal/middleware"
11 | "h68u-tiktok-app-microservice/service/rpc/contact/contactclient"
12 | "h68u-tiktok-app-microservice/service/rpc/user/userclient"
13 | "h68u-tiktok-app-microservice/service/rpc/video/videoclient"
14 | )
15 |
16 | type ServiceContext struct {
17 | Config config.Config
18 | Auth rest.Middleware
19 | AliyunClient *oss.Client
20 | AsynqClient *asynq.Client
21 | VideoRpc videoclient.Video
22 | UserRpc userclient.User
23 | ContactRpc contactclient.Contact
24 | }
25 |
26 | func NewServiceContext(c config.Config) *ServiceContext {
27 | return &ServiceContext{
28 | Config: c,
29 | Auth: middleware.NewAuthMiddleware(c).Handle,
30 | AliyunClient: oss2.AliyunInit(c.OSS),
31 | AsynqClient: asynq.NewClient(asynq.RedisClientOpt{Addr: c.Redis.Address, Password: c.Redis.Password}),
32 | VideoRpc: videoclient.NewVideo(zrpc.MustNewClient(c.VideoRpc)),
33 | UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
34 | ContactRpc: contactclient.NewContact(zrpc.MustNewClient(c.ContactRpc)),
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/service/mq/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine AS builder
2 |
3 | LABEL stage=gobuilder
4 |
5 | ENV CGO_ENABLED 0
6 | ENV GOPROXY https://goproxy.cn,direct
7 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
8 |
9 | RUN apk update --no-cache && apk add --no-cache tzdata
10 |
11 | WORKDIR /build
12 |
13 | ADD go.mod .
14 | ADD go.sum .
15 | RUN go mod download
16 | COPY . .
17 | #COPY service/http/etc /app/etc
18 | RUN go build -ldflags="-s -w" -o /app/mq service/mq/mq.go
19 |
20 |
21 | FROM scratch
22 |
23 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
24 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
25 | ENV TZ Asia/Shanghai
26 |
27 | WORKDIR /app
28 | COPY --from=builder /app/mq /app/mq
29 | #COPY --from=builder /app/etc /app/etc
30 |
31 | CMD ["./mq", "-f", "etc/mq.yaml"]
32 |
--------------------------------------------------------------------------------
/service/mq/etc/mq.yaml.example:
--------------------------------------------------------------------------------
1 | Name: mq
2 | Host: 0.0.0.0
3 | Port: 7001
4 |
5 | #监控
6 | Prometheus:
7 | Host: 0.0.0.0
8 | Port: 4004
9 | Path: /metrics
10 |
11 | #链路追踪
12 | Telemetry:
13 | Name: mq
14 | Endpoint: http://jaeger:14268/api/traces
15 | Sampler: 1.0
16 | Batcher: jaeger
17 |
18 | # RPC 配置
19 | ContactRpc:
20 | Endpoints:
21 | - 127.0.0.1:9003
22 | NonBlock: true
23 |
24 | VideoRpc:
25 | Endpoints:
26 | - 127.0.0.1:9002
27 | NonBlock: true
28 |
29 | UserRpc:
30 | Endpoints:
31 | - 127.0.0.1:9001
32 | NonBlock: true
33 |
34 | Redis:
35 | Address: redis:6379
36 | Password: G62m50oigInC30sf
37 |
38 |
--------------------------------------------------------------------------------
/service/mq/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "github.com/zeromicro/go-zero/core/service"
5 | "github.com/zeromicro/go-zero/zrpc"
6 | )
7 |
8 | type Config struct {
9 | service.ServiceConf
10 |
11 | Redis RedisConf
12 |
13 | VideoRpc zrpc.RpcClientConf
14 | UserRpc zrpc.RpcClientConf
15 | ContactRpc zrpc.RpcClientConf
16 | }
17 |
18 | type RedisConf struct {
19 | Address string
20 | Password string
21 | //DB int
22 | }
23 |
--------------------------------------------------------------------------------
/service/mq/internal/handler/addCacheValue.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/hibiken/asynq"
8 | "h68u-tiktok-app-microservice/common/mq"
9 | )
10 |
11 | func (l *AsynqServer) addCacheValueHandler(ctx context.Context, t *asynq.Task) error {
12 | var p mq.AddCacheValuePayload
13 | if err := json.Unmarshal(t.Payload(), &p); err != nil {
14 | l.Logger.Errorf("json.Unmarshal failed: %v", err)
15 | return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
16 | }
17 | err := l.svcCtx.Redis.HIncrBy(ctx, p.Key, p.Field, p.Add).Err()
18 | if err != nil {
19 | l.Logger.Errorf("redis.HIncrBy failed: %v", err)
20 | return err
21 | }
22 | return nil
23 | }
24 |
--------------------------------------------------------------------------------
/service/mq/internal/handler/asynq.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/hibiken/asynq"
7 | "github.com/zeromicro/go-zero/core/logx"
8 | "h68u-tiktok-app-microservice/common/cron"
9 | "h68u-tiktok-app-microservice/common/mq"
10 | "h68u-tiktok-app-microservice/service/mq/internal/svc"
11 | "log"
12 | )
13 |
14 | type AsynqServer struct {
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | logx.Logger
18 | }
19 |
20 | func NewAsynqServer(ctx context.Context, svcCtx *svc.ServiceContext) *AsynqServer {
21 | return &AsynqServer{
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | Logger: logx.WithContext(ctx),
25 | }
26 | }
27 |
28 | func (l *AsynqServer) Start() {
29 | fmt.Println("AsynqTask start")
30 |
31 | srv := asynq.NewServer(
32 | asynq.RedisClientOpt{Addr: l.svcCtx.Config.Redis.Address, Password: l.svcCtx.Config.Redis.Password},
33 | asynq.Config{
34 | // Specify how many concurrent workers to use
35 | Concurrency: 10,
36 | // Optionally specify multiple queues with different priority.
37 | Queues: map[string]int{
38 | "critical": 6,
39 | "default": 3,
40 | "low": 1,
41 | },
42 | // See the godoc for other configuration options
43 | },
44 | )
45 |
46 | // mux maps a type to a handler
47 | mux := asynq.NewServeMux()
48 | mux.HandleFunc(mq.TypeTryMakeFriends, l.tryMakeFriendsHandler)
49 | mux.HandleFunc(mq.TypeLoseFriends, l.loseFriendsHandler)
50 | mux.HandleFunc(mq.TypeDelCache, l.delCacheHandler)
51 | mux.HandleFunc(mq.TypeAddCacheValue, l.addCacheValueHandler)
52 |
53 | mux.HandleFunc(cron.TypeSyncUserInfoCache, l.syncUserInfoCacheHandler)
54 | mux.HandleFunc(cron.TypeSyncVideoInfoCache, l.syncVideoInfoCacheHandler)
55 | // ...register other handlers...
56 |
57 | if err := srv.Run(mux); err != nil {
58 | log.Fatalf("could not run server: %v", err)
59 | }
60 | }
61 |
62 | func (l *AsynqServer) Stop() {
63 | fmt.Println("AsynqTask stop")
64 | }
65 |
--------------------------------------------------------------------------------
/service/mq/internal/handler/delCache.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/hibiken/asynq"
8 | "h68u-tiktok-app-microservice/common/mq"
9 | "time"
10 | )
11 |
12 | // 延迟双删
13 | func (l *AsynqServer) delCacheHandler(ctx context.Context, t *asynq.Task) error {
14 | var p mq.DelCachePayload
15 | if err := json.Unmarshal(t.Payload(), &p); err != nil {
16 | l.Logger.Errorf("json.Unmarshal failed: %v", err)
17 | return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
18 | }
19 |
20 | if err := l.svcCtx.Redis.Del(ctx, p.Key).Err(); err != nil {
21 | l.Logger.Errorf("redis.Del failed: %v", err)
22 | return err
23 | }
24 |
25 | time.Sleep(1 * time.Second)
26 |
27 | if err := l.svcCtx.Redis.Del(ctx, p.Key).Err(); err != nil {
28 | l.Logger.Errorf("redis.Del failed: %v", err)
29 | return err
30 | }
31 |
32 | return nil
33 | }
34 |
--------------------------------------------------------------------------------
/service/mq/internal/handler/loseFriends.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/hibiken/asynq"
8 | "h68u-tiktok-app-microservice/common/mq"
9 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
10 | )
11 |
12 | func (l *AsynqServer) loseFriendsHandler(ctx context.Context, t *asynq.Task) error {
13 | var p mq.LoseFriendsPayload
14 | if err := json.Unmarshal(t.Payload(), &p); err != nil {
15 | l.Logger.Errorf("json.Unmarshal failed: %v", err)
16 | return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
17 | }
18 |
19 | _, err := l.svcCtx.ContactRpc.LoseFriends(ctx, &contact.LoseFriendsRequest{
20 | UserAId: p.UserAId,
21 | UserBId: p.UserBId,
22 | })
23 | if err != nil {
24 | fmt.Printf("解除好友关系失败: %v\n", err)
25 | l.Logger.Errorf("解除好友关系失败: %v", err)
26 | return err
27 | }
28 |
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/service/mq/internal/handler/syncUserInfoCache.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "github.com/hibiken/asynq"
6 | "h68u-tiktok-app-microservice/common/utils"
7 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
8 | )
9 |
10 | func (l *AsynqServer) syncUserInfoCacheHandler(ctx context.Context, t *asynq.Task) error {
11 |
12 | res, err := l.svcCtx.Redis.LRange(ctx, utils.GenPopUserListCacheKey(), 0, -1).Result()
13 | if err != nil {
14 | l.Logger.Error(err.Error())
15 | return err
16 | }
17 |
18 | for _, v := range res {
19 | userId := utils.Str2Int64(v)
20 | // 读取缓存
21 | userInfo, err := l.svcCtx.Redis.HGetAll(ctx, utils.GenUserInfoCacheKey(userId)).Result()
22 | if err != nil {
23 | l.Logger.Error(err.Error())
24 | return err
25 | }
26 | // 更新用户信息
27 | _, err = l.svcCtx.UserRpc.UpdateUser(ctx, &user.UpdateUserRequest{
28 | Id: userId,
29 | Name: userInfo["Name"],
30 | Password: userInfo["Password"],
31 | FollowCount: utils.Str2Int64(userInfo["FollowCount"]),
32 | FanCount: utils.Str2Int64(userInfo["FanCount"]),
33 | })
34 | if err != nil {
35 | l.Logger.Error(err.Error())
36 | return err
37 | }
38 | }
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/service/mq/internal/handler/syncVideoInfoCache.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "github.com/hibiken/asynq"
6 | "h68u-tiktok-app-microservice/common/utils"
7 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
8 | )
9 |
10 | func (l *AsynqServer) syncVideoInfoCacheHandler(ctx context.Context, t *asynq.Task) error {
11 | res, err := l.svcCtx.Redis.LRange(ctx, utils.GenPopVideoListCacheKey(), 0, -1).Result()
12 | if err != nil {
13 | l.Logger.Error(err.Error())
14 | return err
15 | }
16 |
17 | for _, v := range res {
18 | videoId := utils.Str2Int64(v)
19 | // 读取缓存
20 | videoInfo, err := l.svcCtx.Redis.HGetAll(ctx, utils.GenVideoInfoCacheKey(videoId)).Result()
21 | if err != nil {
22 | l.Logger.Error(err.Error())
23 | return err
24 | }
25 | // 更新视频信息
26 | _, err = l.svcCtx.VideoRpc.UpdateVideo(ctx, &video.UpdateVideoRequest{
27 | Video: &video.VideoInfo{
28 | Id: videoId,
29 | AuthorId: utils.Str2Int64(videoInfo["AuthorId"]),
30 | Title: videoInfo["Title"],
31 | PlayUrl: videoInfo["PlayUrl"],
32 | CoverUrl: videoInfo["CoverUrl"],
33 | FavoriteCount: utils.Str2Int64(videoInfo["FavoriteCount"]),
34 | CommentCount: utils.Str2Int64(videoInfo["CommentCount"]),
35 | CreateTime: utils.Str2Int64(videoInfo["CreateTime"]),
36 | },
37 | })
38 | if err != nil {
39 | l.Logger.Error(err.Error())
40 | return err
41 | }
42 | }
43 | return nil
44 | }
45 |
--------------------------------------------------------------------------------
/service/mq/internal/handler/tryMakeFriends.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/hibiken/asynq"
8 | "github.com/zeromicro/go-zero/core/logx"
9 | "h68u-tiktok-app-microservice/common/mq"
10 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
11 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
12 | )
13 |
14 | func (l *AsynqServer) tryMakeFriendsHandler(ctx context.Context, t *asynq.Task) error {
15 | var p mq.TryMakeFriendsPayload
16 | if err := json.Unmarshal(t.Payload(), &p); err != nil {
17 | l.Logger.Errorf("json.Unmarshal failed: %v", err)
18 | return fmt.Errorf("json.Unmarshal failed: %v: %w", err, asynq.SkipRetry)
19 | }
20 |
21 | isAFollowBReply, err := l.svcCtx.UserRpc.IsFollow(ctx, &user.IsFollowRequest{
22 | UserId: p.UserAId,
23 | FollowUserId: p.UserBId,
24 | })
25 | if err != nil {
26 | l.Logger.Errorf("查询是否关注失败: %v", err)
27 | return err
28 | }
29 |
30 | isBFollowAReply, err := l.svcCtx.UserRpc.IsFollow(ctx, &user.IsFollowRequest{
31 | UserId: p.UserBId,
32 | FollowUserId: p.UserAId,
33 | })
34 | if err != nil {
35 | l.Logger.Errorf("查询是否关注失败: %v", err)
36 | return err
37 | }
38 |
39 | // 如果相互关注了就加好友
40 | if isAFollowBReply.IsFollow && isBFollowAReply.IsFollow {
41 | _, err := l.svcCtx.ContactRpc.MakeFriends(ctx, &contact.MakeFriendsRequest{
42 | UserAId: p.UserBId,
43 | UserBId: p.UserAId,
44 | })
45 | if err != nil {
46 | logx.WithContext(l.ctx).Errorf("加好友失败: %v", err)
47 | return err
48 | }
49 | }
50 |
51 | return nil
52 | }
53 |
--------------------------------------------------------------------------------
/service/mq/internal/svc/serviceContext.go:
--------------------------------------------------------------------------------
1 | package svc
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/go-redis/redis/v8"
7 | "github.com/zeromicro/go-zero/zrpc"
8 | "h68u-tiktok-app-microservice/service/mq/internal/config"
9 | "h68u-tiktok-app-microservice/service/rpc/contact/contactclient"
10 | "h68u-tiktok-app-microservice/service/rpc/user/userclient"
11 | "h68u-tiktok-app-microservice/service/rpc/video/videoclient"
12 | "time"
13 | )
14 |
15 | type ServiceContext struct {
16 | Config config.Config
17 | Redis *redis.Client
18 | VideoRpc videoclient.Video
19 | UserRpc userclient.User
20 | ContactRpc contactclient.Contact
21 | }
22 |
23 | func NewServiceContext(c config.Config) *ServiceContext {
24 | return &ServiceContext{
25 | Config: c,
26 | Redis: initRedis(c),
27 | VideoRpc: videoclient.NewVideo(zrpc.MustNewClient(c.VideoRpc)),
28 | UserRpc: userclient.NewUser(zrpc.MustNewClient(c.UserRpc)),
29 | ContactRpc: contactclient.NewContact(zrpc.MustNewClient(c.ContactRpc)),
30 | }
31 | }
32 |
33 | func initRedis(c config.Config) *redis.Client {
34 | fmt.Println("connect Redis ...")
35 | db := redis.NewClient(&redis.Options{
36 | Addr: c.Redis.Address,
37 | Password: c.Redis.Password,
38 | //DB: c.DBList.Redis.DB,
39 | //超时
40 | ReadTimeout: 2 * time.Second,
41 | WriteTimeout: 2 * time.Second,
42 | PoolTimeout: 3 * time.Second,
43 | })
44 | _, err := db.Ping(context.Background()).Result()
45 | if err != nil {
46 | fmt.Println("connect Redis failed")
47 | panic(err)
48 | }
49 | fmt.Println("connect Redis success")
50 | return db
51 | }
52 |
--------------------------------------------------------------------------------
/service/mq/mq.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "flag"
6 | "github.com/zeromicro/go-zero/core/conf"
7 | "github.com/zeromicro/go-zero/core/logx"
8 | "github.com/zeromicro/go-zero/core/prometheus"
9 | "github.com/zeromicro/go-zero/core/service"
10 | "github.com/zeromicro/go-zero/core/trace"
11 | "h68u-tiktok-app-microservice/service/mq/internal/config"
12 | "h68u-tiktok-app-microservice/service/mq/internal/handler"
13 | "h68u-tiktok-app-microservice/service/mq/internal/svc"
14 | )
15 |
16 | var configFile = flag.String("f", "etc/mq.yaml", "Specify the config file")
17 |
18 | func main() {
19 | flag.Parse()
20 | var c config.Config
21 |
22 | conf.MustLoad(*configFile, &c)
23 | // nolint:staticcheck
24 | prometheus.StartAgent(c.Prometheus)
25 | trace.StartAgent(c.Telemetry)
26 |
27 | svcContext := svc.NewServiceContext(c)
28 | ctx := context.Background()
29 | logx.DisableStat()
30 |
31 | serviceGroup := service.NewServiceGroup()
32 | defer serviceGroup.Stop()
33 |
34 | serviceGroup.Add(handler.NewAsynqServer(ctx, svcContext))
35 | serviceGroup.Start()
36 | }
37 |
--------------------------------------------------------------------------------
/service/rpc/contact/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine AS builder
2 |
3 | LABEL stage=gobuilder
4 |
5 | ENV CGO_ENABLED 0
6 | ENV GOPROXY https://goproxy.cn,direct
7 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
8 |
9 | RUN apk update --no-cache && apk add --no-cache tzdata
10 |
11 | WORKDIR /build
12 |
13 | ADD go.mod .
14 | ADD go.sum .
15 | RUN go mod download
16 | COPY . .
17 | #COPY service/rpc/contact/etc /app/etc
18 | RUN go build -ldflags="-s -w" -o /app/contact service/rpc/contact/contact.go
19 |
20 |
21 | FROM scratch
22 |
23 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
24 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
25 | ENV TZ Asia/Shanghai
26 |
27 | WORKDIR /app
28 | COPY --from=builder /app/contact /app/contact
29 | #COPY --from=builder /app/etc /app/etc
30 |
31 | CMD ["./contact", "-f", "etc/contact.yaml"]
32 |
--------------------------------------------------------------------------------
/service/rpc/contact/contact.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/zeromicro/go-zero/core/logx"
7 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/config"
8 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/server"
9 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/svc"
10 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
11 |
12 | "github.com/zeromicro/go-zero/core/conf"
13 | "github.com/zeromicro/go-zero/core/service"
14 | "github.com/zeromicro/go-zero/zrpc"
15 | "google.golang.org/grpc"
16 | "google.golang.org/grpc/reflection"
17 | )
18 |
19 | var configFile = flag.String("f", "etc/contact.yaml", "the config file")
20 |
21 | func main() {
22 | flag.Parse()
23 |
24 | var c config.Config
25 | conf.MustLoad(*configFile, &c)
26 | ctx := svc.NewServiceContext(c)
27 |
28 | s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
29 | contact.RegisterContactServer(grpcServer, server.NewContactServer(ctx))
30 |
31 | if c.Mode == service.DevMode || c.Mode == service.TestMode {
32 | reflection.Register(grpcServer)
33 | }
34 | })
35 | defer s.Stop()
36 |
37 | logx.DisableStat()
38 | fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
39 | s.Start()
40 | }
41 |
--------------------------------------------------------------------------------
/service/rpc/contact/contact.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package contact;
4 |
5 | option go_package = "./contact";
6 |
7 | // 空消息
8 | message Empty {}
9 |
10 | // 消息结构体
11 | message Message {
12 | int64 Id = 1;
13 | string Content = 2;
14 | int64 CreateTime = 3;
15 | int64 FromId = 4;
16 | int64 ToId = 5;
17 | }
18 |
19 | service Contact {
20 | rpc CreateMessage (CreateMessageRequest) returns (Empty);
21 | rpc GetLatestMessage (GetLatestMessageRequest) returns (GetLatestMessageResponse);
22 | rpc GetMessageList (GetMessageListRequest) returns (GetMessageListResponse);
23 | rpc MakeFriends (MakeFriendsRequest) returns (Empty);
24 | rpc LoseFriends (LoseFriendsRequest) returns (Empty);
25 | rpc GetFriendsList (GetFriendsListRequest) returns (GetFriendsListResponse);
26 | }
27 |
28 | message GetLatestMessageRequest {
29 | int64 UserAId = 1;
30 | int64 UserBId = 2;
31 | }
32 |
33 | message GetLatestMessageResponse {
34 | Message Message = 1;
35 | }
36 |
37 | message CreateMessageRequest {
38 | int64 FromId = 1;
39 | int64 ToId = 2;
40 | string content = 3;
41 | }
42 |
43 | message GetMessageListRequest {
44 | int64 FromId = 1;
45 | int64 ToId = 2;
46 | }
47 |
48 | message GetMessageListResponse {
49 | repeated Message Messages = 1;
50 | }
51 |
52 | message MakeFriendsRequest {
53 | int64 UserAId = 1;
54 | int64 UserBId = 2;
55 | }
56 |
57 | message LoseFriendsRequest {
58 | int64 UserAId = 1;
59 | int64 UserBId = 2;
60 | }
61 |
62 | message GetFriendsListRequest {
63 | int64 UserId = 1;
64 | }
65 |
66 | message GetFriendsListResponse {
67 | repeated int64 FriendsId = 1;
68 | }
--------------------------------------------------------------------------------
/service/rpc/contact/contactclient/contact.go:
--------------------------------------------------------------------------------
1 | // Code generated by goctl. DO NOT EDIT!
2 | // Source: contact.proto
3 |
4 | package contactclient
5 |
6 | import (
7 | "context"
8 |
9 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
10 |
11 | "github.com/zeromicro/go-zero/zrpc"
12 | "google.golang.org/grpc"
13 | )
14 |
15 | type (
16 | CreateMessageRequest = contact.CreateMessageRequest
17 | Empty = contact.Empty
18 | GetFriendsListRequest = contact.GetFriendsListRequest
19 | GetFriendsListResponse = contact.GetFriendsListResponse
20 | GetLatestMessageRequest = contact.GetLatestMessageRequest
21 | GetLatestMessageResponse = contact.GetLatestMessageResponse
22 | GetMessageListRequest = contact.GetMessageListRequest
23 | GetMessageListResponse = contact.GetMessageListResponse
24 | LoseFriendsRequest = contact.LoseFriendsRequest
25 | MakeFriendsRequest = contact.MakeFriendsRequest
26 | Message = contact.Message
27 |
28 | Contact interface {
29 | CreateMessage(ctx context.Context, in *CreateMessageRequest, opts ...grpc.CallOption) (*Empty, error)
30 | GetLatestMessage(ctx context.Context, in *GetLatestMessageRequest, opts ...grpc.CallOption) (*GetLatestMessageResponse, error)
31 | GetMessageList(ctx context.Context, in *GetMessageListRequest, opts ...grpc.CallOption) (*GetMessageListResponse, error)
32 | MakeFriends(ctx context.Context, in *MakeFriendsRequest, opts ...grpc.CallOption) (*Empty, error)
33 | LoseFriends(ctx context.Context, in *LoseFriendsRequest, opts ...grpc.CallOption) (*Empty, error)
34 | GetFriendsList(ctx context.Context, in *GetFriendsListRequest, opts ...grpc.CallOption) (*GetFriendsListResponse, error)
35 | }
36 |
37 | defaultContact struct {
38 | cli zrpc.Client
39 | }
40 | )
41 |
42 | func NewContact(cli zrpc.Client) Contact {
43 | return &defaultContact{
44 | cli: cli,
45 | }
46 | }
47 |
48 | func (m *defaultContact) CreateMessage(ctx context.Context, in *CreateMessageRequest, opts ...grpc.CallOption) (*Empty, error) {
49 | client := contact.NewContactClient(m.cli.Conn())
50 | return client.CreateMessage(ctx, in, opts...)
51 | }
52 |
53 | func (m *defaultContact) GetLatestMessage(ctx context.Context, in *GetLatestMessageRequest, opts ...grpc.CallOption) (*GetLatestMessageResponse, error) {
54 | client := contact.NewContactClient(m.cli.Conn())
55 | return client.GetLatestMessage(ctx, in, opts...)
56 | }
57 |
58 | func (m *defaultContact) GetMessageList(ctx context.Context, in *GetMessageListRequest, opts ...grpc.CallOption) (*GetMessageListResponse, error) {
59 | client := contact.NewContactClient(m.cli.Conn())
60 | return client.GetMessageList(ctx, in, opts...)
61 | }
62 |
63 | func (m *defaultContact) MakeFriends(ctx context.Context, in *MakeFriendsRequest, opts ...grpc.CallOption) (*Empty, error) {
64 | client := contact.NewContactClient(m.cli.Conn())
65 | return client.MakeFriends(ctx, in, opts...)
66 | }
67 |
68 | func (m *defaultContact) LoseFriends(ctx context.Context, in *LoseFriendsRequest, opts ...grpc.CallOption) (*Empty, error) {
69 | client := contact.NewContactClient(m.cli.Conn())
70 | return client.LoseFriends(ctx, in, opts...)
71 | }
72 |
73 | func (m *defaultContact) GetFriendsList(ctx context.Context, in *GetFriendsListRequest, opts ...grpc.CallOption) (*GetFriendsListResponse, error) {
74 | client := contact.NewContactClient(m.cli.Conn())
75 | return client.GetFriendsList(ctx, in, opts...)
76 | }
77 |
--------------------------------------------------------------------------------
/service/rpc/contact/etc/contact.yaml.example:
--------------------------------------------------------------------------------
1 | Name: contact-rpc
2 | ListenOn: 0.0.0.0:9003
3 |
4 |
5 | DBList:
6 | Mysql:
7 | Address: mysql:3306
8 | Username: root
9 | Password: "PXDN93VRKUm8TeE7"
10 | DBName: tiktok-contact
11 | TablePrefix: ""
12 | Redis:
13 | Address: redis:6379
14 | Password: G62m50oigInC30sf
15 |
16 | #监控
17 | Prometheus:
18 | Host: 0.0.0.0
19 | Port: 4003
20 | Path: /metrics
21 |
22 | #链路追踪
23 | Telemetry:
24 | Name: rpc-contact
25 | Endpoint: http://jaeger:14268/api/traces
26 | Sampler: 1.0
27 | Batcher: jaeger
--------------------------------------------------------------------------------
/service/rpc/contact/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/zeromicro/go-zero/zrpc"
4 |
5 | type Config struct {
6 | zrpc.RpcServerConf
7 |
8 | // 数据库配置
9 | DBList DBListConf
10 | }
11 |
12 | type DBListConf struct {
13 | Mysql MysqlConf
14 | Redis RedisConf
15 | }
16 |
17 | type MysqlConf struct {
18 | Address string
19 | Username string
20 | Password string
21 | DBName string
22 | TablePrefix string
23 | }
24 |
25 | type RedisConf struct {
26 | Address string
27 | Password string
28 | //DB int
29 | }
30 |
--------------------------------------------------------------------------------
/service/rpc/contact/internal/logic/createMessageLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "github.com/zeromicro/go-zero/core/logx"
6 | "google.golang.org/grpc/status"
7 | "gorm.io/gorm"
8 | "h68u-tiktok-app-microservice/common/error/rpcErr"
9 | "h68u-tiktok-app-microservice/common/model"
10 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/svc"
11 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
12 | )
13 |
14 | type CreateMessageLogic struct {
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | logx.Logger
18 | }
19 |
20 | func NewCreateMessageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateMessageLogic {
21 | return &CreateMessageLogic{
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | Logger: logx.WithContext(ctx),
25 | }
26 | }
27 |
28 | func (l *CreateMessageLogic) CreateMessage(in *contact.CreateMessageRequest) (*contact.Empty, error) {
29 | err := l.svcCtx.DBList.Mysql.Transaction(func(tx *gorm.DB) error {
30 | //创建并增加消息记录
31 | message := model.Message{
32 | FromId: in.FromId,
33 | ToUserId: in.ToId,
34 | Content: in.Content,
35 | }
36 |
37 | if err := l.svcCtx.DBList.Mysql.Create(&message).Error; err != nil {
38 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
39 | }
40 |
41 | return nil
42 | })
43 | if err != nil {
44 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
45 | }
46 | return &contact.Empty{}, nil
47 | }
48 |
--------------------------------------------------------------------------------
/service/rpc/contact/internal/logic/getFriendsListLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/model"
6 |
7 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/svc"
8 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
9 |
10 | "github.com/zeromicro/go-zero/core/logx"
11 | )
12 |
13 | type GetFriendsListLogic struct {
14 | ctx context.Context
15 | svcCtx *svc.ServiceContext
16 | logx.Logger
17 | }
18 |
19 | func NewGetFriendsListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFriendsListLogic {
20 | return &GetFriendsListLogic{
21 | ctx: ctx,
22 | svcCtx: svcCtx,
23 | Logger: logx.WithContext(ctx),
24 | }
25 | }
26 |
27 | func (l *GetFriendsListLogic) GetFriendsList(in *contact.GetFriendsListRequest) (*contact.GetFriendsListResponse, error) {
28 | var result []model.Friend
29 |
30 | err := l.svcCtx.DBList.Mysql.Where("user_id = ?", in.UserId).Select("friend_id").Find(&result).Error
31 | if err != nil {
32 | return nil, err
33 | }
34 |
35 | return &contact.GetFriendsListResponse{
36 | FriendsId: func() []int64 {
37 | var friendsId []int64
38 | for _, v := range result {
39 | friendsId = append(friendsId, v.FriendId)
40 | }
41 | return friendsId
42 | }(),
43 | }, nil
44 | }
45 |
--------------------------------------------------------------------------------
/service/rpc/contact/internal/logic/getLatestMessageLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/model"
6 |
7 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/svc"
8 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
9 |
10 | "github.com/zeromicro/go-zero/core/logx"
11 | )
12 |
13 | type GetLatestMessageLogic struct {
14 | ctx context.Context
15 | svcCtx *svc.ServiceContext
16 | logx.Logger
17 | }
18 |
19 | func NewGetLatestMessageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLatestMessageLogic {
20 | return &GetLatestMessageLogic{
21 | ctx: ctx,
22 | svcCtx: svcCtx,
23 | Logger: logx.WithContext(ctx),
24 | }
25 | }
26 |
27 | func (l *GetLatestMessageLogic) GetLatestMessage(in *contact.GetLatestMessageRequest) (*contact.GetLatestMessageResponse, error) {
28 | result := model.Message{}
29 |
30 | l.svcCtx.DBList.Mysql.
31 | Where("from_id = ? and to_user_id = ?", in.UserAId, in.UserBId).
32 | Or("from_id = ? and to_user_id = ?", in.UserBId, in.UserAId).
33 | Order("created_at desc").
34 | First(&result)
35 |
36 | l.Logger.Info("GetLatestMessage", result)
37 |
38 | return &contact.GetLatestMessageResponse{
39 | Message: &contact.Message{
40 | Id: int64(result.ID),
41 | Content: result.Content,
42 | CreateTime: result.CreatedAt.Unix(),
43 | FromId: result.FromId,
44 | ToId: result.ToUserId,
45 | },
46 | }, nil
47 | }
48 |
--------------------------------------------------------------------------------
/service/rpc/contact/internal/logic/getMessageListLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "github.com/zeromicro/go-zero/core/logx"
6 | "google.golang.org/grpc/status"
7 | "h68u-tiktok-app-microservice/common/error/rpcErr"
8 | "h68u-tiktok-app-microservice/common/model"
9 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/svc"
10 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
11 | )
12 |
13 | type GetMessageListLogic struct {
14 | ctx context.Context
15 | svcCtx *svc.ServiceContext
16 | logx.Logger
17 | }
18 |
19 | func NewGetMessageListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMessageListLogic {
20 | return &GetMessageListLogic{
21 | ctx: ctx,
22 | svcCtx: svcCtx,
23 | Logger: logx.WithContext(ctx),
24 | }
25 | }
26 |
27 | func (l *GetMessageListLogic) GetMessageList(in *contact.GetMessageListRequest) (*contact.GetMessageListResponse, error) {
28 | var messages []model.Message
29 | err := l.svcCtx.DBList.Mysql.Where("from_id = ?", in.FromId).Where("to_user_id = ?", in.ToId).Find(&messages).Error
30 | if err != nil {
31 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
32 | }
33 |
34 | var messageList []*contact.Message
35 | for _, message := range messages {
36 | messageList = append(messageList, &contact.Message{
37 | Id: int64(message.ID),
38 | Content: message.Content,
39 | CreateTime: message.CreatedAt.Unix(),
40 | FromId: message.FromId,
41 | ToId: message.ToUserId,
42 | })
43 | }
44 | return &contact.GetMessageListResponse{
45 | Messages: messageList,
46 | }, nil
47 | }
48 |
--------------------------------------------------------------------------------
/service/rpc/contact/internal/logic/loseFriendsLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/model"
6 |
7 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/svc"
8 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
9 |
10 | "github.com/zeromicro/go-zero/core/logx"
11 | )
12 |
13 | type LoseFriendsLogic struct {
14 | ctx context.Context
15 | svcCtx *svc.ServiceContext
16 | logx.Logger
17 | }
18 |
19 | func NewLoseFriendsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoseFriendsLogic {
20 | return &LoseFriendsLogic{
21 | ctx: ctx,
22 | svcCtx: svcCtx,
23 | Logger: logx.WithContext(ctx),
24 | }
25 | }
26 |
27 | func (l *LoseFriendsLogic) LoseFriends(in *contact.LoseFriendsRequest) (*contact.Empty, error) {
28 | //friendsA := model.Friend{
29 | // UserId: int64(in.UserAId),
30 | // FriendId: int64(in.UserBId),
31 | //}
32 | //
33 | //friendsB := model.Friend{
34 | // UserId: int64(in.UserBId),
35 | // FriendId: int64(in.UserAId),
36 | //}
37 |
38 | tx := l.svcCtx.DBList.Mysql.Begin()
39 |
40 | if err := tx.Where("user_id = ? AND friend_id = ?", in.UserAId, in.UserBId).Delete(&model.Friend{}).Error; err != nil {
41 | tx.Rollback()
42 | return nil, err
43 | }
44 |
45 | if err := tx.Where("user_id = ? AND friend_id = ?", in.UserBId, in.UserAId).Delete(&model.Friend{}).Error; err != nil {
46 | tx.Rollback()
47 | return nil, err
48 | }
49 |
50 | tx.Commit()
51 |
52 | return &contact.Empty{}, nil
53 | }
54 |
--------------------------------------------------------------------------------
/service/rpc/contact/internal/logic/makeFriendsLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "h68u-tiktok-app-microservice/common/model"
6 |
7 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/svc"
8 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
9 |
10 | "github.com/zeromicro/go-zero/core/logx"
11 | )
12 |
13 | type MakeFriendsLogic struct {
14 | ctx context.Context
15 | svcCtx *svc.ServiceContext
16 | logx.Logger
17 | }
18 |
19 | func NewMakeFriendsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MakeFriendsLogic {
20 | return &MakeFriendsLogic{
21 | ctx: ctx,
22 | svcCtx: svcCtx,
23 | Logger: logx.WithContext(ctx),
24 | }
25 | }
26 |
27 | func (l *MakeFriendsLogic) MakeFriends(in *contact.MakeFriendsRequest) (*contact.Empty, error) {
28 |
29 | newFriendsA := model.Friend{
30 | UserId: in.UserAId,
31 | FriendId: in.UserBId,
32 | }
33 |
34 | newFriendsB := model.Friend{
35 | UserId: in.UserBId,
36 | FriendId: in.UserAId,
37 | }
38 |
39 | tx := l.svcCtx.DBList.Mysql.Begin()
40 |
41 | if err := tx.Create(&newFriendsA).Error; err != nil {
42 | tx.Rollback()
43 | return nil, err
44 | }
45 |
46 | if err := tx.Create(&newFriendsB).Error; err != nil {
47 | tx.Rollback()
48 | return nil, err
49 | }
50 |
51 | tx.Commit()
52 |
53 | return &contact.Empty{}, nil
54 | }
55 |
--------------------------------------------------------------------------------
/service/rpc/contact/internal/server/contactServer.go:
--------------------------------------------------------------------------------
1 | // Code generated by goctl. DO NOT EDIT!
2 | // Source: contact.proto
3 |
4 | package server
5 |
6 | import (
7 | "context"
8 |
9 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/logic"
10 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/svc"
11 | "h68u-tiktok-app-microservice/service/rpc/contact/types/contact"
12 | )
13 |
14 | type ContactServer struct {
15 | svcCtx *svc.ServiceContext
16 | contact.UnimplementedContactServer
17 | }
18 |
19 | func NewContactServer(svcCtx *svc.ServiceContext) *ContactServer {
20 | return &ContactServer{
21 | svcCtx: svcCtx,
22 | }
23 | }
24 |
25 | func (s *ContactServer) CreateMessage(ctx context.Context, in *contact.CreateMessageRequest) (*contact.Empty, error) {
26 | l := logic.NewCreateMessageLogic(ctx, s.svcCtx)
27 | return l.CreateMessage(in)
28 | }
29 |
30 | func (s *ContactServer) GetLatestMessage(ctx context.Context, in *contact.GetLatestMessageRequest) (*contact.GetLatestMessageResponse, error) {
31 | l := logic.NewGetLatestMessageLogic(ctx, s.svcCtx)
32 | return l.GetLatestMessage(in)
33 | }
34 |
35 | func (s *ContactServer) GetMessageList(ctx context.Context, in *contact.GetMessageListRequest) (*contact.GetMessageListResponse, error) {
36 | l := logic.NewGetMessageListLogic(ctx, s.svcCtx)
37 | return l.GetMessageList(in)
38 | }
39 |
40 | func (s *ContactServer) MakeFriends(ctx context.Context, in *contact.MakeFriendsRequest) (*contact.Empty, error) {
41 | l := logic.NewMakeFriendsLogic(ctx, s.svcCtx)
42 | return l.MakeFriends(in)
43 | }
44 |
45 | func (s *ContactServer) LoseFriends(ctx context.Context, in *contact.LoseFriendsRequest) (*contact.Empty, error) {
46 | l := logic.NewLoseFriendsLogic(ctx, s.svcCtx)
47 | return l.LoseFriends(in)
48 | }
49 |
50 | func (s *ContactServer) GetFriendsList(ctx context.Context, in *contact.GetFriendsListRequest) (*contact.GetFriendsListResponse, error) {
51 | l := logic.NewGetFriendsListLogic(ctx, s.svcCtx)
52 | return l.GetFriendsList(in)
53 | }
54 |
--------------------------------------------------------------------------------
/service/rpc/contact/internal/svc/serviceContext.go:
--------------------------------------------------------------------------------
1 | package svc
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/go-redis/redis/v8"
7 | "gorm.io/driver/mysql"
8 | "gorm.io/gorm"
9 | "gorm.io/gorm/schema"
10 | "h68u-tiktok-app-microservice/common/model"
11 | "h68u-tiktok-app-microservice/service/rpc/contact/internal/config"
12 | "time"
13 | )
14 |
15 | type ServiceContext struct {
16 | Config config.Config
17 | DBList *DBList
18 | }
19 |
20 | type DBList struct {
21 | Mysql *gorm.DB
22 | Redis *redis.Client
23 | }
24 |
25 | func NewServiceContext(c config.Config) *ServiceContext {
26 | return &ServiceContext{
27 | Config: c,
28 | DBList: initDB(c),
29 | }
30 | }
31 |
32 | func initDB(c config.Config) *DBList {
33 | dbList := new(DBList)
34 | dbList.Mysql = initMysql(c)
35 | dbList.Redis = initRedis(c)
36 |
37 | return dbList
38 | }
39 |
40 | func initMysql(c config.Config) *gorm.DB {
41 | dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
42 | c.DBList.Mysql.Username,
43 | c.DBList.Mysql.Password,
44 | c.DBList.Mysql.Address,
45 | c.DBList.Mysql.DBName,
46 | )
47 |
48 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
49 | NamingStrategy: schema.NamingStrategy{
50 | TablePrefix: c.DBList.Mysql.TablePrefix, // 表名前缀
51 | SingularTable: true, // 使用单数表名
52 | },
53 | DisableForeignKeyConstraintWhenMigrating: true,
54 | })
55 | if err != nil {
56 | panic(err)
57 | }
58 |
59 | // 自动建表
60 | err = db.AutoMigrate(&model.Message{}, &model.Friend{})
61 | if err != nil {
62 | panic(err)
63 | }
64 |
65 | return db
66 | }
67 |
68 | func initRedis(c config.Config) *redis.Client {
69 | fmt.Println("connect Redis ...")
70 | db := redis.NewClient(&redis.Options{
71 | Addr: c.DBList.Redis.Address,
72 | Password: c.DBList.Redis.Password,
73 | //DB: c.DBList.Redis.DB,
74 | //超时
75 | ReadTimeout: 2 * time.Second,
76 | WriteTimeout: 2 * time.Second,
77 | PoolTimeout: 3 * time.Second,
78 | })
79 | _, err := db.Ping(context.Background()).Result()
80 | if err != nil {
81 | fmt.Println("connect Redis failed")
82 | panic(err)
83 | }
84 | fmt.Println("connect Redis success")
85 | return db
86 | }
87 |
--------------------------------------------------------------------------------
/service/rpc/user/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine AS builder
2 |
3 | LABEL stage=gobuilder
4 |
5 | ENV CGO_ENABLED 0
6 | ENV GOPROXY https://goproxy.cn,direct
7 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
8 |
9 | RUN apk update --no-cache && apk add --no-cache tzdata
10 |
11 | WORKDIR /build
12 |
13 | ADD go.mod .
14 | ADD go.sum .
15 | RUN go mod download
16 | COPY . .
17 | #COPY service/rpc/user/etc /app/etc
18 | RUN go build -ldflags="-s -w" -o /app/user service/rpc/user/user.go
19 |
20 |
21 | FROM scratch
22 |
23 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
24 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
25 | ENV TZ Asia/Shanghai
26 |
27 | WORKDIR /app
28 | COPY --from=builder /app/user /app/user
29 | #COPY --from=builder /app/etc /app/etc
30 |
31 | CMD ["./user", "-f", "etc/user.yaml"]
32 |
--------------------------------------------------------------------------------
/service/rpc/user/etc/user.yaml.example:
--------------------------------------------------------------------------------
1 | # 启动配置
2 | Name: user-rpc
3 | ListenOn: 0.0.0.0:9001
4 |
5 |
6 | DBList:
7 | Mysql:
8 | Address: mysql:3306
9 | Username: root
10 | Password: "PXDN93VRKUm8TeE7"
11 | DBName: tiktok-user
12 | TablePrefix: ""
13 | Redis:
14 | Address: redis:6379
15 | Password: G62m50oigInC30sf
16 |
17 | #监控
18 | Prometheus:
19 | Host: 0.0.0.0
20 | Port: 4001
21 | Path: /metrics
22 |
23 | #链路追踪
24 | Telemetry:
25 | Name: rpc-user
26 | Endpoint: http://jaeger:14268/api/traces
27 | Sampler: 1.0
28 | Batcher: jaeger
--------------------------------------------------------------------------------
/service/rpc/user/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/zeromicro/go-zero/zrpc"
4 |
5 | type Config struct {
6 | zrpc.RpcServerConf
7 |
8 | // 数据库配置
9 | DBList DBListConf
10 | }
11 |
12 | type DBListConf struct {
13 | Mysql MysqlConf
14 | Redis RedisConf
15 | }
16 |
17 | type MysqlConf struct {
18 | Address string
19 | Username string
20 | Password string
21 | DBName string
22 | TablePrefix string
23 | }
24 |
25 | type RedisConf struct {
26 | Address string
27 | Password string
28 | //DB int
29 | }
30 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/logic/createUserLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "golang.org/x/crypto/bcrypt"
6 | "google.golang.org/grpc/status"
7 | "h68u-tiktok-app-microservice/common/error/rpcErr"
8 | "h68u-tiktok-app-microservice/common/model"
9 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
10 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
11 |
12 | "github.com/zeromicro/go-zero/core/logx"
13 | )
14 |
15 | type CreateUserLogic struct {
16 | ctx context.Context
17 | svcCtx *svc.ServiceContext
18 | logx.Logger
19 | }
20 |
21 | func NewCreateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateUserLogic {
22 | return &CreateUserLogic{
23 | ctx: ctx,
24 | svcCtx: svcCtx,
25 | Logger: logx.WithContext(ctx),
26 | }
27 | }
28 |
29 | func (l *CreateUserLogic) CreateUser(in *user.CreateUserRequest) (*user.CreatUserReply, error) {
30 | tx := l.svcCtx.DBList.Mysql.Begin()
31 |
32 | // 检查是否已经存在
33 | var count int64
34 | if err := tx.Model(&model.User{}).Where("username = ?", in.Name).Count(&count).Error; err != nil {
35 | tx.Rollback()
36 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
37 | }
38 | if count > 0 {
39 | tx.Rollback()
40 | return nil, status.Error(rpcErr.UserAlreadyExist.Code, rpcErr.UserAlreadyExist.Message)
41 | }
42 |
43 | // 密码加密
44 | password, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
45 | if err != nil {
46 | tx.Rollback()
47 | return nil, status.Error(rpcErr.PassWordEncryptFailed.Code, err.Error())
48 | }
49 |
50 | // 准备数据
51 | newUser := &model.User{
52 | Username: in.Name,
53 | Password: string(password),
54 | }
55 |
56 | // 插入数据
57 | if err := tx.Create(newUser).Error; err != nil {
58 | tx.Rollback()
59 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
60 | }
61 |
62 | if err := tx.Commit().Error; err != nil {
63 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
64 | }
65 |
66 | return &user.CreatUserReply{
67 | Id: int64(newUser.ID),
68 | }, nil
69 | }
70 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/logic/followUserLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "github.com/zeromicro/go-zero/core/logx"
6 | "google.golang.org/grpc/status"
7 | "gorm.io/gorm"
8 | "gorm.io/gorm/clause"
9 | "h68u-tiktok-app-microservice/common/error/rpcErr"
10 | "h68u-tiktok-app-microservice/common/model"
11 | "h68u-tiktok-app-microservice/common/mq"
12 | "h68u-tiktok-app-microservice/common/utils"
13 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
14 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
15 | )
16 |
17 | type FollowUserLogic struct {
18 | ctx context.Context
19 | svcCtx *svc.ServiceContext
20 | logx.Logger
21 | }
22 |
23 | func NewFollowUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FollowUserLogic {
24 | return &FollowUserLogic{
25 | ctx: ctx,
26 | svcCtx: svcCtx,
27 | Logger: logx.WithContext(ctx),
28 | }
29 | }
30 |
31 | func (l *FollowUserLogic) FollowUser(in *user.FollowUserRequest) (*user.Empty, error) {
32 | err := l.svcCtx.DBList.Mysql.Transaction(func(tx *gorm.DB) error {
33 | var users *model.User
34 | var followUser *model.User
35 | tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", in.UserId).First(&users)
36 | tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", in.FollowUserId).First(&followUser)
37 |
38 | //处理关注用户
39 | result, err := l.svcCtx.DBList.Redis.Exists(l.ctx, utils.GenUserInfoCacheKey(in.UserId)).Result()
40 | if result == 1 {
41 | // 如果是大V(在redis中有缓存),就只更新缓存,交给定时任务更新数据库
42 | task, err := mq.NewAddCacheValueTask(utils.GenUserInfoCacheKey(in.UserId), "FollowCount", 1)
43 | if err != nil {
44 | logx.WithContext(l.ctx).Errorf("创建任务失败: %v", err)
45 | return err
46 | }
47 | if _, err := l.svcCtx.AsynqClient.Enqueue(task); err != nil {
48 | logx.WithContext(l.ctx).Errorf("发送任务失败: %v", err)
49 | return err
50 | }
51 | } else {
52 | if err != nil {
53 | l.Logger.Error(rpcErr.CacheError.Code, err.Error())
54 | }
55 | // 如果是普通用户,就直接更新数据库
56 | users.FollowCount++
57 | err := tx.Save(&users).Error
58 | if err != nil {
59 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
60 | }
61 | }
62 |
63 | //处理被关注用户
64 | result, err = l.svcCtx.DBList.Redis.Exists(l.ctx, utils.GenUserInfoCacheKey(in.FollowUserId)).Result()
65 | if result == 1 {
66 | // 如果是大V(在redis中有缓存),就只更新缓存,交给定时任务更新数据库
67 | task, err := mq.NewAddCacheValueTask(utils.GenUserInfoCacheKey(in.FollowUserId), "FanCount", 1)
68 | if err != nil {
69 | logx.WithContext(l.ctx).Errorf("创建任务失败: %v", err)
70 | return err
71 | }
72 | if _, err := l.svcCtx.AsynqClient.Enqueue(task); err != nil {
73 | logx.WithContext(l.ctx).Errorf("发送任务失败: %v", err)
74 | return err
75 | }
76 | } else {
77 | if err != nil {
78 | l.Logger.Error(rpcErr.CacheError.Code, err.Error())
79 | }
80 | // 如果是普通用户,就直接更新数据库
81 |
82 | followUser.FanCount++
83 | err = tx.Save(&followUser).Error
84 | if err != nil {
85 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
86 | }
87 | }
88 | //建立关注关系
89 | err = tx.Clauses(clause.Locking{Strength: "UPDATE"}).Model(users).Association("Follows").Append(followUser)
90 | if err != nil {
91 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
92 | }
93 | return nil
94 | })
95 |
96 | if err != nil {
97 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
98 | }
99 | return &user.Empty{}, nil
100 | }
101 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/logic/getFansListLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "gorm.io/gorm"
7 | "h68u-tiktok-app-microservice/common/error/rpcErr"
8 | "h68u-tiktok-app-microservice/common/model"
9 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
10 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
11 |
12 | "github.com/zeromicro/go-zero/core/logx"
13 | )
14 |
15 | type GetFansListLogic struct {
16 | ctx context.Context
17 | svcCtx *svc.ServiceContext
18 | logx.Logger
19 | }
20 |
21 | func NewGetFansListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFansListLogic {
22 | return &GetFansListLogic{
23 | ctx: ctx,
24 | svcCtx: svcCtx,
25 | Logger: logx.WithContext(ctx),
26 | }
27 | }
28 |
29 | func (l *GetFansListLogic) GetFansList(in *user.GetFansListRequest) (*user.GetFansListReply, error) {
30 | var fans model.User
31 | //获取粉丝列表
32 | err := l.svcCtx.DBList.Mysql.
33 | Where("id = ?", in.UserId).
34 | Preload("Fans", func(db *gorm.DB) *gorm.DB {
35 | return db.Limit(model.PopularUserStandard)
36 | }).
37 | Find(&fans).Error
38 | if err != nil {
39 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
40 | }
41 | var fanList []*user.UserInfo
42 | for _, fan := range fans.Fans {
43 | fanList = append(fanList, &user.UserInfo{
44 | Id: int64(fan.ID),
45 | Name: fan.Username,
46 | FollowCount: fan.FollowCount,
47 | FansCount: fan.FanCount,
48 | })
49 | }
50 | return &user.GetFansListReply{
51 | FansList: fanList,
52 | }, nil
53 | }
54 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/logic/getFollowListLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "h68u-tiktok-app-microservice/common/error/rpcErr"
7 | "h68u-tiktok-app-microservice/common/model"
8 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
9 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
10 |
11 | "github.com/zeromicro/go-zero/core/logx"
12 | )
13 |
14 | type GetFollowListLogic struct {
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | logx.Logger
18 | }
19 |
20 | func NewGetFollowListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFollowListLogic {
21 | return &GetFollowListLogic{
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | Logger: logx.WithContext(ctx),
25 | }
26 | }
27 |
28 | func (l *GetFollowListLogic) GetFollowList(in *user.GetFollowListRequest) (*user.GetFollowListReply, error) {
29 | var follows model.User
30 | err := l.svcCtx.DBList.Mysql.Where("id = ?", in.UserId).Preload("Follows").Find(&follows).Error
31 | if err != nil {
32 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
33 | }
34 | var followList []*user.UserInfo
35 | for _, follow := range follows.Follows {
36 | followList = append(followList, &user.UserInfo{
37 | Id: int64(follow.ID),
38 | Name: follow.Username,
39 | FollowCount: follow.FollowCount,
40 | FansCount: follow.FanCount,
41 | })
42 | }
43 |
44 | return &user.GetFollowListReply{
45 | FollowList: followList,
46 | }, nil
47 | }
48 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/logic/getUserByIdLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "github.com/go-redis/redis/v8"
6 | "google.golang.org/grpc/status"
7 | "gorm.io/gorm"
8 | "h68u-tiktok-app-microservice/common/error/rpcErr"
9 | "h68u-tiktok-app-microservice/common/model"
10 | "h68u-tiktok-app-microservice/common/utils"
11 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
12 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
13 |
14 | "github.com/zeromicro/go-zero/core/logx"
15 | )
16 |
17 | type GetUserByIdLogic struct {
18 | ctx context.Context
19 | svcCtx *svc.ServiceContext
20 | logx.Logger
21 | }
22 |
23 | func NewGetUserByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserByIdLogic {
24 | return &GetUserByIdLogic{
25 | ctx: ctx,
26 | svcCtx: svcCtx,
27 | Logger: logx.WithContext(ctx),
28 | }
29 | }
30 |
31 | func (l *GetUserByIdLogic) GetUserById(in *user.GetUserByIdRequest) (*user.GetUserReply, error) {
32 | // 查询缓存
33 | cacheData, err := l.svcCtx.DBList.Redis.HGetAll(l.ctx, utils.GenUserInfoCacheKey(in.Id)).Result()
34 | if err == nil && len(cacheData) != 0 {
35 | l.Logger.Info("Get user info from cache")
36 | return &user.GetUserReply{
37 | Id: utils.Str2Int64(cacheData["Id"]),
38 | Name: cacheData["Name"],
39 | Password: cacheData["Password"],
40 | FollowCount: utils.Str2Int64(cacheData["FollowCount"]),
41 | FanCount: utils.Str2Int64(cacheData["FanCount"]),
42 | }, nil
43 | } else if err != nil && err != redis.Nil {
44 | l.Logger.Error(rpcErr.CacheError.Code, err.Error())
45 | }
46 |
47 | // 准备模型
48 | result := &model.User{}
49 |
50 | // 查询数据
51 | err = l.svcCtx.DBList.Mysql.Where("id = ?", in.Id).First(result).Error
52 |
53 | if err == gorm.ErrRecordNotFound {
54 | return nil, status.Error(rpcErr.UserNotExist.Code, rpcErr.UserNotExist.Message)
55 | } else if err != nil {
56 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
57 | }
58 |
59 | // 如果是大V,缓存个人信息
60 | if model.IsPopularUser(result.FanCount) {
61 | err := l.svcCtx.DBList.Redis.HSet(l.ctx, utils.GenUserInfoCacheKey(int64(result.ID)), map[string]interface{}{
62 | "Id": result.ID,
63 | "Name": result.Username,
64 | "Password": result.Password,
65 | "FollowCount": result.FollowCount,
66 | "FanCount": result.FanCount,
67 | }).Err()
68 | if err != nil {
69 | return nil, status.Error(rpcErr.CacheError.Code, err.Error())
70 | }
71 |
72 | err = l.svcCtx.DBList.Redis.LPush(l.ctx, utils.GenPopUserListCacheKey(), result.ID).Err()
73 | if err != nil {
74 | return nil, status.Error(rpcErr.CacheError.Code, err.Error())
75 | }
76 | }
77 |
78 | return &user.GetUserReply{
79 | Id: int64(result.ID),
80 | Name: result.Username,
81 | Password: result.Password,
82 | FollowCount: result.FollowCount,
83 | FanCount: result.FanCount,
84 | }, nil
85 | }
86 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/logic/getUserByNameLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "gorm.io/gorm"
7 | "h68u-tiktok-app-microservice/common/error/rpcErr"
8 | "h68u-tiktok-app-microservice/common/model"
9 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
10 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
11 |
12 | "github.com/zeromicro/go-zero/core/logx"
13 | )
14 |
15 | type GetUserByNameLogic struct {
16 | ctx context.Context
17 | svcCtx *svc.ServiceContext
18 | logx.Logger
19 | }
20 |
21 | func NewGetUserByNameLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserByNameLogic {
22 | return &GetUserByNameLogic{
23 | ctx: ctx,
24 | svcCtx: svcCtx,
25 | Logger: logx.WithContext(ctx),
26 | }
27 | }
28 |
29 | func (l *GetUserByNameLogic) GetUserByName(in *user.GetUserByNameRequest) (*user.GetUserReply, error) {
30 | // 准备数据
31 | result := &model.User{}
32 |
33 | // 查询数据
34 | err := l.svcCtx.DBList.Mysql.Where("username = ?", in.Name).First(result).Error
35 |
36 | if err == gorm.ErrRecordNotFound {
37 | return nil, status.Error(rpcErr.UserNotExist.Code, rpcErr.UserNotExist.Message)
38 | } else if err != nil {
39 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
40 | }
41 |
42 | return &user.GetUserReply{
43 | Id: int64(result.ID),
44 | Name: result.Username,
45 | Password: result.Password,
46 | FollowCount: result.FollowCount,
47 | FanCount: result.FanCount,
48 | }, nil
49 | }
50 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/logic/isFollowLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "h68u-tiktok-app-microservice/common/error/rpcErr"
7 | "h68u-tiktok-app-microservice/common/model"
8 | "h68u-tiktok-app-microservice/common/utils"
9 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
10 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
11 | "time"
12 |
13 | "github.com/zeromicro/go-zero/core/logx"
14 | )
15 |
16 | type IsFollowLogic struct {
17 | ctx context.Context
18 | svcCtx *svc.ServiceContext
19 | logx.Logger
20 | }
21 |
22 | func NewIsFollowLogic(ctx context.Context, svcCtx *svc.ServiceContext) *IsFollowLogic {
23 | return &IsFollowLogic{
24 | ctx: ctx,
25 | svcCtx: svcCtx,
26 | Logger: logx.WithContext(ctx),
27 | }
28 | }
29 |
30 | func (l *IsFollowLogic) IsFollow(in *user.IsFollowRequest) (*user.IsFollowReply, error) {
31 | // 查询缓存是否存在
32 | if l.svcCtx.DBList.Redis.
33 | Exists(l.ctx, utils.GenFollowUserCacheKey(in.UserId, in.FollowUserId)).
34 | Val() == 1 {
35 | return &user.IsFollowReply{
36 | IsFollow: true,
37 | }, nil
38 | }
39 |
40 | //判断你是否关注了这个人
41 | var aUser model.User
42 | l.svcCtx.DBList.Mysql.Where("id = ?", in.UserId).Preload("Follows", "id = ?", in.FollowUserId).Find(&aUser)
43 |
44 | if len(aUser.Follows) > 0 {
45 | // 记录存在,设置缓存
46 | err := l.svcCtx.DBList.Redis.
47 | Set(l.ctx, utils.GenFollowUserCacheKey(in.UserId, in.FollowUserId), 1, time.Hour).Err()
48 | if err != nil {
49 | return nil, status.Error(rpcErr.CacheError.Code, err.Error())
50 | }
51 | return &user.IsFollowReply{
52 | IsFollow: true,
53 | }, nil
54 | } else {
55 | return &user.IsFollowReply{
56 | IsFollow: false,
57 | }, nil
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/logic/unFollowUserLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/clause"
8 | "h68u-tiktok-app-microservice/common/error/rpcErr"
9 | "h68u-tiktok-app-microservice/common/model"
10 | "h68u-tiktok-app-microservice/common/mq"
11 | "h68u-tiktok-app-microservice/common/utils"
12 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
13 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
14 |
15 | "github.com/zeromicro/go-zero/core/logx"
16 | )
17 |
18 | type UnFollowUserLogic struct {
19 | ctx context.Context
20 | svcCtx *svc.ServiceContext
21 | logx.Logger
22 | }
23 |
24 | func NewUnFollowUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnFollowUserLogic {
25 | return &UnFollowUserLogic{
26 | ctx: ctx,
27 | svcCtx: svcCtx,
28 | Logger: logx.WithContext(ctx),
29 | }
30 | }
31 |
32 | func (l *UnFollowUserLogic) UnFollowUser(in *user.UnFollowUserRequest) (*user.Empty, error) {
33 | err := l.svcCtx.DBList.Mysql.Transaction(func(tx *gorm.DB) error {
34 | var users *model.User
35 | var unFollowUser *model.User
36 | tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", in.UserId).First(&users)
37 | tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", in.UnFollowUserId).First(&unFollowUser)
38 |
39 | //处理关注用户
40 | result, err := l.svcCtx.DBList.Redis.Exists(l.ctx, utils.GenUserInfoCacheKey(in.UserId)).Result()
41 | if result == 1 {
42 | // 如果是大V(在redis中有缓存),就只更新缓存,交给定时任务更新数据库
43 | task, err := mq.NewAddCacheValueTask(utils.GenUserInfoCacheKey(in.UserId), "FollowCount", -1)
44 | if err != nil {
45 | logx.WithContext(l.ctx).Errorf("创建任务失败: %v", err)
46 | return err
47 | }
48 | if _, err := l.svcCtx.AsynqClient.Enqueue(task); err != nil {
49 | logx.WithContext(l.ctx).Errorf("发送任务失败: %v", err)
50 | return err
51 | }
52 | } else {
53 | if err != nil {
54 | l.Logger.Error(rpcErr.CacheError.Code, err.Error())
55 | }
56 | // 如果是普通用户,就直接更新数据库
57 | users.FollowCount--
58 | err := tx.Save(&users).Error
59 | if err != nil {
60 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
61 | }
62 | }
63 | //处理被关注用户
64 | result, err = l.svcCtx.DBList.Redis.Exists(l.ctx, utils.GenUserInfoCacheKey(in.UnFollowUserId)).Result()
65 | if result == 1 {
66 | // 如果是大V(在redis中有缓存),就只更新缓存,交给定时任务更新数据库
67 | task, err := mq.NewAddCacheValueTask(utils.GenUserInfoCacheKey(in.UnFollowUserId), "FanCount", -1)
68 | if err != nil {
69 | logx.WithContext(l.ctx).Errorf("创建任务失败: %v", err)
70 | return err
71 | }
72 | if _, err := l.svcCtx.AsynqClient.Enqueue(task); err != nil {
73 | logx.WithContext(l.ctx).Errorf("发送任务失败: %v", err)
74 | return err
75 | }
76 | } else {
77 | if err != nil {
78 | l.Logger.Error(rpcErr.CacheError.Code, err.Error())
79 | }
80 | // 如果是普通用户,就直接更新数据库
81 | unFollowUser.FanCount--
82 | err = tx.Save(&unFollowUser).Error
83 | if err != nil {
84 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
85 | }
86 | }
87 | //解除关注关系
88 | err = tx.Model(users).Association("Follows").Delete(unFollowUser)
89 | if err != nil {
90 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
91 | }
92 | return nil
93 | })
94 |
95 | if err != nil {
96 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
97 | }
98 |
99 | // 异步删除缓存
100 | task, err := mq.NewDelCacheTask(utils.GenFollowUserCacheKey(in.UserId, in.UnFollowUserId))
101 | if err != nil {
102 | logx.WithContext(l.ctx).Errorf("创建任务失败: %v", err)
103 | return nil, status.Error(rpcErr.MQError.Code, err.Error())
104 | }
105 | if _, err := l.svcCtx.AsynqClient.Enqueue(task); err != nil {
106 | logx.WithContext(l.ctx).Errorf("发送任务失败: %v", err)
107 | return nil, status.Error(rpcErr.MQError.Code, err.Error())
108 | }
109 |
110 | return &user.Empty{}, nil
111 | }
112 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/logic/updateUserLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "gorm.io/gorm/clause"
6 | "h68u-tiktok-app-microservice/common/model"
7 |
8 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
9 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
10 |
11 | "github.com/zeromicro/go-zero/core/logx"
12 | )
13 |
14 | type UpdateUserLogic struct {
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | logx.Logger
18 | }
19 |
20 | func NewUpdateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserLogic {
21 | return &UpdateUserLogic{
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | Logger: logx.WithContext(ctx),
25 | }
26 | }
27 |
28 | func (l *UpdateUserLogic) UpdateUser(in *user.UpdateUserRequest) (*user.Empty, error) {
29 |
30 | // 开启事务
31 | tx := l.svcCtx.DBList.Mysql.Begin()
32 |
33 | var newUser *model.User
34 | err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", in.Id).First(&newUser).Error
35 | if err != nil {
36 | tx.Rollback()
37 | return nil, err
38 | }
39 |
40 | //newUser.Username = in.Name
41 | //newUser.Password = in.Password
42 | newUser.FollowCount = in.FollowCount
43 | newUser.FanCount = in.FanCount
44 |
45 | err = tx.Clauses(clause.Locking{Strength: "UPDATE"}).Save(&newUser).Error
46 | if err != nil {
47 | tx.Rollback()
48 | return nil, err
49 | }
50 |
51 | tx.Commit()
52 |
53 | return &user.Empty{}, nil
54 | }
55 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/server/userServer.go:
--------------------------------------------------------------------------------
1 | // Code generated by goctl. DO NOT EDIT!
2 | // Source: user.proto
3 |
4 | package server
5 |
6 | import (
7 | "context"
8 |
9 | "h68u-tiktok-app-microservice/service/rpc/user/internal/logic"
10 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
11 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
12 | )
13 |
14 | type UserServer struct {
15 | svcCtx *svc.ServiceContext
16 | user.UnimplementedUserServer
17 | }
18 |
19 | func NewUserServer(svcCtx *svc.ServiceContext) *UserServer {
20 | return &UserServer{
21 | svcCtx: svcCtx,
22 | }
23 | }
24 |
25 | func (s *UserServer) GetUserByName(ctx context.Context, in *user.GetUserByNameRequest) (*user.GetUserReply, error) {
26 | l := logic.NewGetUserByNameLogic(ctx, s.svcCtx)
27 | return l.GetUserByName(in)
28 | }
29 |
30 | func (s *UserServer) GetUserById(ctx context.Context, in *user.GetUserByIdRequest) (*user.GetUserReply, error) {
31 | l := logic.NewGetUserByIdLogic(ctx, s.svcCtx)
32 | return l.GetUserById(in)
33 | }
34 |
35 | func (s *UserServer) CreateUser(ctx context.Context, in *user.CreateUserRequest) (*user.CreatUserReply, error) {
36 | l := logic.NewCreateUserLogic(ctx, s.svcCtx)
37 | return l.CreateUser(in)
38 | }
39 |
40 | func (s *UserServer) UpdateUser(ctx context.Context, in *user.UpdateUserRequest) (*user.Empty, error) {
41 | l := logic.NewUpdateUserLogic(ctx, s.svcCtx)
42 | return l.UpdateUser(in)
43 | }
44 |
45 | func (s *UserServer) FollowUser(ctx context.Context, in *user.FollowUserRequest) (*user.Empty, error) {
46 | l := logic.NewFollowUserLogic(ctx, s.svcCtx)
47 | return l.FollowUser(in)
48 | }
49 |
50 | func (s *UserServer) UnFollowUser(ctx context.Context, in *user.UnFollowUserRequest) (*user.Empty, error) {
51 | l := logic.NewUnFollowUserLogic(ctx, s.svcCtx)
52 | return l.UnFollowUser(in)
53 | }
54 |
55 | func (s *UserServer) GetFollowList(ctx context.Context, in *user.GetFollowListRequest) (*user.GetFollowListReply, error) {
56 | l := logic.NewGetFollowListLogic(ctx, s.svcCtx)
57 | return l.GetFollowList(in)
58 | }
59 |
60 | func (s *UserServer) GetFansList(ctx context.Context, in *user.GetFansListRequest) (*user.GetFansListReply, error) {
61 | l := logic.NewGetFansListLogic(ctx, s.svcCtx)
62 | return l.GetFansList(in)
63 | }
64 |
65 | func (s *UserServer) IsFollow(ctx context.Context, in *user.IsFollowRequest) (*user.IsFollowReply, error) {
66 | l := logic.NewIsFollowLogic(ctx, s.svcCtx)
67 | return l.IsFollow(in)
68 | }
69 |
--------------------------------------------------------------------------------
/service/rpc/user/internal/svc/serviceContext.go:
--------------------------------------------------------------------------------
1 | package svc
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/go-redis/redis/v8"
7 | "github.com/hibiken/asynq"
8 | "gorm.io/driver/mysql"
9 | "gorm.io/gorm"
10 | "gorm.io/gorm/schema"
11 | "h68u-tiktok-app-microservice/common/model"
12 | "h68u-tiktok-app-microservice/service/rpc/user/internal/config"
13 | "time"
14 | )
15 |
16 | type ServiceContext struct {
17 | Config config.Config
18 | DBList *DBList
19 | AsynqClient *asynq.Client
20 | }
21 |
22 | type DBList struct {
23 | Mysql *gorm.DB
24 | Redis *redis.Client
25 | }
26 |
27 | func NewServiceContext(c config.Config) *ServiceContext {
28 | return &ServiceContext{
29 | Config: c,
30 | DBList: initDB(c),
31 | AsynqClient: asynq.NewClient(asynq.RedisClientOpt{Addr: c.DBList.Redis.Address, Password: c.DBList.Redis.Password}),
32 | }
33 | }
34 |
35 | func initDB(c config.Config) *DBList {
36 | dbList := new(DBList)
37 | dbList.Mysql = initMysql(c)
38 | dbList.Redis = initRedis(c)
39 |
40 | return dbList
41 | }
42 |
43 | func initMysql(c config.Config) *gorm.DB {
44 | dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
45 | c.DBList.Mysql.Username,
46 | c.DBList.Mysql.Password,
47 | c.DBList.Mysql.Address,
48 | c.DBList.Mysql.DBName,
49 | )
50 |
51 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
52 | NamingStrategy: schema.NamingStrategy{
53 | TablePrefix: c.DBList.Mysql.TablePrefix, // 表名前缀
54 | SingularTable: true, // 使用单数表名
55 | },
56 | DisableForeignKeyConstraintWhenMigrating: true,
57 | })
58 | if err != nil {
59 | panic(err)
60 | }
61 |
62 | // 自动建表
63 | err = db.AutoMigrate(&model.User{})
64 | if err != nil {
65 | panic(err)
66 | }
67 |
68 | return db
69 | }
70 |
71 | func initRedis(c config.Config) *redis.Client {
72 | fmt.Println("connect Redis ...")
73 | db := redis.NewClient(&redis.Options{
74 | Addr: c.DBList.Redis.Address,
75 | Password: c.DBList.Redis.Password,
76 | //DB: c.DBList.Redis.DB,
77 | //超时
78 | ReadTimeout: 2 * time.Second,
79 | WriteTimeout: 2 * time.Second,
80 | PoolTimeout: 3 * time.Second,
81 | })
82 | _, err := db.Ping(context.Background()).Result()
83 | if err != nil {
84 | fmt.Println("connect Redis failed")
85 | panic(err)
86 | }
87 | fmt.Println("connect Redis success")
88 | return db
89 | }
90 |
--------------------------------------------------------------------------------
/service/rpc/user/user.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/zeromicro/go-zero/core/logx"
7 | "h68u-tiktok-app-microservice/service/rpc/user/internal/config"
8 | "h68u-tiktok-app-microservice/service/rpc/user/internal/server"
9 | "h68u-tiktok-app-microservice/service/rpc/user/internal/svc"
10 | "h68u-tiktok-app-microservice/service/rpc/user/types/user"
11 |
12 | "github.com/zeromicro/go-zero/core/conf"
13 | "github.com/zeromicro/go-zero/core/service"
14 | "github.com/zeromicro/go-zero/zrpc"
15 | "google.golang.org/grpc"
16 | "google.golang.org/grpc/reflection"
17 | )
18 |
19 | var configFile = flag.String("f", "etc/user.yaml", "the config file")
20 |
21 | func main() {
22 | flag.Parse()
23 |
24 | var c config.Config
25 | conf.MustLoad(*configFile, &c)
26 | ctx := svc.NewServiceContext(c)
27 |
28 | s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
29 | user.RegisterUserServer(grpcServer, server.NewUserServer(ctx))
30 |
31 | if c.Mode == service.DevMode || c.Mode == service.TestMode {
32 | reflection.Register(grpcServer)
33 | }
34 | })
35 | defer s.Stop()
36 |
37 | logx.DisableStat()
38 | fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
39 | s.Start()
40 | }
41 |
--------------------------------------------------------------------------------
/service/rpc/user/user.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package user;
4 |
5 | option go_package = "./user";
6 |
7 | // 空消息
8 | message Empty {}
9 |
10 | // 用户信息结构体
11 | message UserInfo {
12 | int64 Id = 1;
13 | string Name = 2;
14 | int64 FollowCount = 3;
15 | int64 FansCount = 4;
16 | }
17 |
18 | service User {
19 | rpc GetUserByName(GetUserByNameRequest) returns (GetUserReply);
20 | rpc GetUserById(GetUserByIdRequest) returns (GetUserReply);
21 | rpc CreateUser(CreateUserRequest) returns (CreatUserReply);
22 | rpc UpdateUser(UpdateUserRequest) returns (Empty);
23 |
24 | rpc FollowUser(FollowUserRequest) returns (Empty);
25 | rpc UnFollowUser(UnFollowUserRequest) returns (Empty);
26 |
27 | rpc GetFollowList(GetFollowListRequest) returns (GetFollowListReply);
28 | rpc GetFansList(GetFansListRequest) returns (GetFansListReply);
29 |
30 | rpc IsFollow(IsFollowRequest) returns (IsFollowReply);
31 | }
32 |
33 | message GetUserByNameRequest {
34 | string Name = 1;
35 | }
36 |
37 | message GetUserByIdRequest {
38 | int64 Id = 1;
39 | }
40 |
41 | message GetUserReply {
42 | int64 Id = 1;
43 | string Name = 2;
44 | string Password = 3;
45 | int64 FollowCount = 4;
46 | int64 FanCount = 5;
47 | }
48 |
49 | message CreateUserRequest {
50 | string Name = 1;
51 | string Password = 2;
52 | }
53 |
54 | message CreatUserReply {
55 | int64 Id = 1;
56 | }
57 |
58 | message UpdateUserRequest{
59 | int64 Id = 1;
60 | string Name = 2;
61 | string Password = 3;
62 | int64 FollowCount = 4;
63 | int64 FanCount = 5;
64 | }
65 |
66 | message FollowUserRequest {
67 | int64 UserId = 1;
68 | int64 FollowUserId = 2;
69 | }
70 |
71 | message UnFollowUserRequest {
72 | int64 UserId = 1;
73 | int64 UnFollowUserId = 2;
74 | }
75 |
76 | message GetFollowListRequest {
77 | int64 UserId = 1;
78 | }
79 |
80 | message GetFollowListReply {
81 | repeated UserInfo FollowList = 1;
82 | }
83 |
84 | message GetFansListRequest {
85 | int64 UserId = 1;
86 | }
87 |
88 | message GetFansListReply {
89 | repeated UserInfo FansList = 1;
90 | }
91 |
92 | message IsFollowRequest {
93 | int64 UserId = 1;
94 | int64 FollowUserId = 2;
95 | }
96 |
97 | message IsFollowReply {
98 | bool IsFollow = 1;
99 | }
--------------------------------------------------------------------------------
/service/rpc/video/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine AS builder
2 |
3 | LABEL stage=gobuilder
4 |
5 | ENV CGO_ENABLED 0
6 | ENV GOPROXY https://goproxy.cn,direct
7 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
8 |
9 | RUN apk update --no-cache && apk add --no-cache tzdata
10 |
11 | WORKDIR /build
12 |
13 | ADD go.mod .
14 | ADD go.sum .
15 | RUN go mod download
16 | COPY . .
17 | COPY service/rpc/video/etc /app/etc
18 | RUN go build -ldflags="-s -w" -o /app/video service/rpc/video/video.go
19 |
20 |
21 | FROM scratch
22 |
23 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
24 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
25 | ENV TZ Asia/Shanghai
26 |
27 | WORKDIR /app
28 | COPY --from=builder /app/video /app/video
29 | COPY --from=builder /app/etc /app/etc
30 |
31 | CMD ["./video", "-f", "etc/video.yaml"]
32 |
--------------------------------------------------------------------------------
/service/rpc/video/etc/video.yaml.example:
--------------------------------------------------------------------------------
1 | # 启动配置
2 | Name: video-rpc
3 | ListenOn: 0.0.0.0:9002
4 |
5 | DBList:
6 | Mysql:
7 | Address: mysql:3306
8 | Username: root
9 | Password: "PXDN93VRKUm8TeE7"
10 | DBName: tiktok-video
11 | TablePrefix: ""
12 | Redis:
13 | Address: redis:6379
14 | Password: G62m50oigInC30sf
15 |
16 | #监控
17 | Prometheus:
18 | Host: 0.0.0.0
19 | Port: 4002
20 | Path: /metrics
21 |
22 | #链路追踪
23 | Telemetry:
24 | Name: rpc-video
25 | Endpoint: http://jaeger:14268/api/traces
26 | Sampler: 1.0
27 | Batcher: jaeger
--------------------------------------------------------------------------------
/service/rpc/video/internal/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "github.com/zeromicro/go-zero/zrpc"
4 |
5 | type Config struct {
6 | zrpc.RpcServerConf
7 |
8 | // 数据库配置
9 | DBList DBListConf
10 | }
11 |
12 | type DBListConf struct {
13 | Mysql MysqlConf
14 | Redis RedisConf
15 | }
16 |
17 | type MysqlConf struct {
18 | Address string
19 | Username string
20 | Password string
21 | DBName string
22 | TablePrefix string
23 | }
24 |
25 | type RedisConf struct {
26 | Address string
27 | Password string
28 | //DB int
29 | }
30 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/commentVideoLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/clause"
8 | "h68u-tiktok-app-microservice/common/error/rpcErr"
9 | "h68u-tiktok-app-microservice/common/model"
10 | "h68u-tiktok-app-microservice/common/mq"
11 | "h68u-tiktok-app-microservice/common/utils"
12 |
13 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
14 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
15 |
16 | "github.com/zeromicro/go-zero/core/logx"
17 | )
18 |
19 | type CommentVideoLogic struct {
20 | ctx context.Context
21 | svcCtx *svc.ServiceContext
22 | logx.Logger
23 | }
24 |
25 | func NewCommentVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CommentVideoLogic {
26 | return &CommentVideoLogic{
27 | ctx: ctx,
28 | svcCtx: svcCtx,
29 | Logger: logx.WithContext(ctx),
30 | }
31 | }
32 |
33 | func (l *CommentVideoLogic) CommentVideo(in *video.CommentVideoRequest) (*video.CommentVideoResponse, error) {
34 | // 创建评论记录
35 | comment := model.Comment{
36 | VideoId: in.VideoId,
37 | UserId: in.UserId,
38 | Content: in.Content,
39 | }
40 |
41 | tx := l.svcCtx.DBList.Mysql.Begin()
42 |
43 | if err := tx.Create(&comment).Error; err != nil {
44 | tx.Rollback()
45 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
46 | }
47 |
48 | // 更新视频评论数
49 | // 如果是热门视频(在缓存中),就只更新缓存,交给定时任务更新数据库
50 | result, err := l.svcCtx.DBList.Redis.Exists(l.ctx, utils.GenUserInfoCacheKey(in.UserId)).Result()
51 | if result == 1 {
52 | task, err := mq.NewAddCacheValueTask(utils.GenVideoInfoCacheKey(in.VideoId), "CommentCount", 1)
53 | if err != nil {
54 | logx.WithContext(l.ctx).Errorf("创建任务失败: %v", err)
55 | tx.Rollback()
56 | return nil, err
57 | }
58 | if _, err := l.svcCtx.AsynqClient.Enqueue(task); err != nil {
59 | logx.WithContext(l.ctx).Errorf("发送任务失败: %v", err)
60 | tx.Rollback()
61 | return nil, err
62 | }
63 | } else {
64 | if err != nil {
65 | l.Logger.Error(rpcErr.CacheError.Code, err.Error())
66 | }
67 | if err := tx.Model(&model.Video{}).
68 | Where("id = ?", in.VideoId).
69 | Clauses(clause.Locking{Strength: "UPDATE"}).
70 | UpdateColumn("comment_count", gorm.Expr("comment_count + ?", 1)).
71 | Error; err != nil {
72 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
73 | }
74 | }
75 | tx.Commit()
76 |
77 | return &video.CommentVideoResponse{
78 | Id: int64(comment.ID),
79 | UserId: comment.UserId,
80 | Content: comment.Content,
81 | CreatedTime: comment.CreatedAt.Unix(),
82 | }, nil
83 | }
84 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/deleteVideoCommentLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "h68u-tiktok-app-microservice/common/error/rpcErr"
7 | "h68u-tiktok-app-microservice/common/model"
8 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
9 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
10 |
11 | "github.com/zeromicro/go-zero/core/logx"
12 | )
13 |
14 | type DeleteVideoCommentLogic struct {
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | logx.Logger
18 | }
19 |
20 | func NewDeleteVideoCommentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteVideoCommentLogic {
21 | return &DeleteVideoCommentLogic{
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | Logger: logx.WithContext(ctx),
25 | }
26 | }
27 |
28 | func (l *DeleteVideoCommentLogic) DeleteVideoComment(in *video.DeleteVideoCommentRequest) (*video.Empty, error) {
29 | if err := l.svcCtx.DBList.Mysql.
30 | Where("id = ?", in.CommentId).
31 | Delete(&model.Comment{}).Error; err != nil {
32 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
33 | }
34 |
35 | return &video.Empty{}, nil
36 | }
37 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/favoriteVideoLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/clause"
8 | "h68u-tiktok-app-microservice/common/error/rpcErr"
9 | "h68u-tiktok-app-microservice/common/model"
10 | "h68u-tiktok-app-microservice/common/mq"
11 | "h68u-tiktok-app-microservice/common/utils"
12 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
13 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
14 |
15 | "github.com/zeromicro/go-zero/core/logx"
16 | )
17 |
18 | type FavoriteVideoLogic struct {
19 | ctx context.Context
20 | svcCtx *svc.ServiceContext
21 | logx.Logger
22 | }
23 |
24 | func NewFavoriteVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FavoriteVideoLogic {
25 | return &FavoriteVideoLogic{
26 | ctx: ctx,
27 | svcCtx: svcCtx,
28 | Logger: logx.WithContext(ctx),
29 | }
30 | }
31 |
32 | func (l *FavoriteVideoLogic) FavoriteVideo(in *video.FavoriteVideoRequest) (*video.Empty, error) {
33 | err := l.svcCtx.DBList.Mysql.Transaction(func(tx *gorm.DB) error {
34 | // 先查询用户是否点赞过该视频
35 | f := model.Favorite{}
36 | err := tx.
37 | Clauses(clause.Locking{Strength: "UPDATE"}).
38 | Where("user_id = ? And video_id = ?", in.UserId, in.VideoId).
39 | First(&f).Error
40 |
41 | // 点赞记录已存在
42 | if err == nil {
43 | return nil
44 | }
45 |
46 | // 数据库查询错误
47 | if err != gorm.ErrRecordNotFound {
48 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
49 | }
50 |
51 | // 未点赞,创建记录
52 | f.VideoId = in.VideoId
53 | f.UserId = in.UserId
54 | if err := tx.Create(&f).Error; err != nil {
55 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
56 | }
57 |
58 | // 视频点赞量加一
59 | // 如果是热门视频(在缓存中),就只更新缓存,交给定时任务更新数据库
60 | result, err := l.svcCtx.DBList.Redis.Exists(l.ctx, utils.GenUserInfoCacheKey(in.UserId)).Result()
61 | if result == 1 {
62 | task, err := mq.NewAddCacheValueTask(utils.GenVideoInfoCacheKey(in.VideoId), "FavoriteCount", 1)
63 | if err != nil {
64 | logx.WithContext(l.ctx).Errorf("创建任务失败: %v", err)
65 | return err
66 | }
67 | if _, err := l.svcCtx.AsynqClient.Enqueue(task); err != nil {
68 | logx.WithContext(l.ctx).Errorf("发送任务失败: %v", err)
69 | return err
70 | }
71 | } else {
72 | if err != nil {
73 | l.Logger.Error(rpcErr.CacheError.Code, err.Error())
74 | }
75 | if err := tx.Model(&model.Video{}).
76 | Where("id = ?", in.VideoId).
77 | Clauses(clause.Locking{Strength: "UPDATE"}).
78 | UpdateColumn("favorite_count", gorm.Expr("favorite_count + ?", 1)).
79 | Error; err != nil {
80 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
81 | }
82 | }
83 | return nil
84 | })
85 |
86 | return &video.Empty{}, err
87 | }
88 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/getCommentInfoLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "gorm.io/gorm"
7 | "h68u-tiktok-app-microservice/common/error/rpcErr"
8 | "h68u-tiktok-app-microservice/common/model"
9 |
10 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
11 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
12 |
13 | "github.com/zeromicro/go-zero/core/logx"
14 | )
15 |
16 | type GetCommentInfoLogic struct {
17 | ctx context.Context
18 | svcCtx *svc.ServiceContext
19 | logx.Logger
20 | }
21 |
22 | func NewGetCommentInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCommentInfoLogic {
23 | return &GetCommentInfoLogic{
24 | ctx: ctx,
25 | svcCtx: svcCtx,
26 | Logger: logx.WithContext(ctx),
27 | }
28 | }
29 |
30 | func (l *GetCommentInfoLogic) GetCommentInfo(in *video.GetCommentInfoRequest) (*video.GetCommentInfoResponse, error) {
31 | var comment model.Comment
32 | err := l.svcCtx.DBList.Mysql.Where("id = ?", in.CommentId).First(&comment).Error
33 | if err == gorm.ErrRecordNotFound {
34 | return nil, status.Error(rpcErr.CommentNotExist.Code, err.Error())
35 | }
36 |
37 | if err != nil {
38 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
39 | }
40 |
41 | return &video.GetCommentInfoResponse{
42 | Id: int64(comment.ID),
43 | UserId: comment.UserId,
44 | Content: comment.Content,
45 | CreatedTime: comment.CreatedAt.Unix(),
46 | }, nil
47 | }
48 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/getCommentListLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "h68u-tiktok-app-microservice/common/error/rpcErr"
7 | "h68u-tiktok-app-microservice/common/model"
8 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
9 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
10 |
11 | "github.com/zeromicro/go-zero/core/logx"
12 | )
13 |
14 | type GetCommentListLogic struct {
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | logx.Logger
18 | }
19 |
20 | func NewGetCommentListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCommentListLogic {
21 | return &GetCommentListLogic{
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | Logger: logx.WithContext(ctx),
25 | }
26 | }
27 |
28 | func (l *GetCommentListLogic) GetCommentList(in *video.GetCommentListRequest) (*video.GetCommentListResponse, error) {
29 | // 按倒序获取视频的评论列表
30 | var comments []model.Comment
31 | if err := l.svcCtx.DBList.Mysql.
32 | Where("video_id = ?", in.VideoId).
33 | Limit(model.PopularVideoStandard).
34 | Order("created_at").
35 | Find(&comments).Error; err != nil {
36 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
37 | }
38 |
39 | // 封装评论数据
40 | commentList := make([]*video.Comment, 0, len(comments))
41 | for _, v := range comments {
42 | commentList = append(commentList, &video.Comment{
43 | Id: int64(v.ID),
44 | AuthorId: v.UserId,
45 | CreateTime: v.CreatedAt.Unix(),
46 | Content: v.Content,
47 | })
48 | }
49 |
50 | return &video.GetCommentListResponse{
51 | CommentList: commentList,
52 | }, nil
53 | }
54 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/getFavoriteVideoListLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "h68u-tiktok-app-microservice/common/error/rpcErr"
7 | "h68u-tiktok-app-microservice/common/model"
8 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
9 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
10 | video2 "h68u-tiktok-app-microservice/service/rpc/video/types/video"
11 |
12 | "github.com/zeromicro/go-zero/core/logx"
13 | )
14 |
15 | type GetFavoriteVideoListLogic struct {
16 | ctx context.Context
17 | svcCtx *svc.ServiceContext
18 | logx.Logger
19 | }
20 |
21 | func NewGetFavoriteVideoListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFavoriteVideoListLogic {
22 | return &GetFavoriteVideoListLogic{
23 | ctx: ctx,
24 | svcCtx: svcCtx,
25 | Logger: logx.WithContext(ctx),
26 | }
27 | }
28 |
29 | func (l *GetFavoriteVideoListLogic) GetFavoriteVideoList(in *video2.GetFavoriteVideoListRequest) (*video2.GetFavoriteVideoListResponse, error) {
30 | // 获取用户点赞的视频id (要根据时间排序,从新到旧)
31 | var favorites []model.Favorite
32 |
33 | if err := l.svcCtx.DBList.Mysql.
34 | Where("user_id = ?", in.UserId).
35 | Preload("Video").
36 | Order("created_at desc").
37 | Find(&favorites).Error; err != nil {
38 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
39 | }
40 |
41 | // 封装查询结果
42 | videoList := make([]*video.VideoInfo, 0, len(favorites))
43 | for _, v := range favorites {
44 | // 可能存在脏数据,需要判断视频是否存在
45 | if v.Video.ID == 0 {
46 | // TODO:异步删除脏数据
47 | continue
48 | }
49 |
50 | videoInfo := &video.VideoInfo{
51 | Id: int64(v.Video.ID),
52 | AuthorId: v.Video.AuthorId,
53 | Title: v.Video.Title,
54 | PlayUrl: v.Video.PlayUrl,
55 | CoverUrl: v.Video.CoverUrl,
56 | FavoriteCount: v.Video.FavoriteCount,
57 | CommentCount: v.Video.CommentCount,
58 | }
59 |
60 | videoList = append(videoList, videoInfo)
61 | }
62 |
63 | return &video2.GetFavoriteVideoListResponse{
64 | VideoList: videoList,
65 | }, nil
66 | }
67 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/getVideoListByAuthorLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "h68u-tiktok-app-microservice/common/error/rpcErr"
7 | "h68u-tiktok-app-microservice/common/model"
8 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
9 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
10 |
11 | "github.com/zeromicro/go-zero/core/logx"
12 | )
13 |
14 | type GetVideoListByAuthorLogic struct {
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | logx.Logger
18 | }
19 |
20 | func NewGetVideoListByAuthorLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetVideoListByAuthorLogic {
21 | return &GetVideoListByAuthorLogic{
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | Logger: logx.WithContext(ctx),
25 | }
26 | }
27 |
28 | func (l *GetVideoListByAuthorLogic) GetVideoListByAuthor(in *video.GetVideoListByAuthorRequest) (*video.GetVideoListByAuthorResponse, error) {
29 | //获得作者的视频列表(由新到旧)
30 | var videos []model.Video
31 | err := l.svcCtx.DBList.Mysql.Where("author_id = ?", in.AuthorId).Order("created_at desc").Find(&videos).Error
32 | if err != nil {
33 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
34 | }
35 | // 封装查询结果
36 | videoList := make([]*video.VideoInfo, 0, len(videos))
37 | for _, v := range videos {
38 | videoInfo := &video.VideoInfo{
39 | Id: int64(v.ID),
40 | AuthorId: v.AuthorId,
41 | Title: v.Title,
42 | PlayUrl: v.PlayUrl,
43 | CoverUrl: v.CoverUrl,
44 | FavoriteCount: v.FavoriteCount,
45 | CommentCount: v.CommentCount,
46 | }
47 | videoList = append(videoList, videoInfo)
48 | }
49 |
50 | return &video.GetVideoListByAuthorResponse{
51 | VideoList: videoList,
52 | }, nil
53 | }
54 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/getVideoListLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "github.com/zeromicro/go-zero/core/logx"
6 | "google.golang.org/grpc/status"
7 | "h68u-tiktok-app-microservice/common/error/rpcErr"
8 | "h68u-tiktok-app-microservice/common/model"
9 | "h68u-tiktok-app-microservice/common/utils"
10 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
11 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
12 | "time"
13 | )
14 |
15 | type GetVideoListLogic struct {
16 | ctx context.Context
17 | svcCtx *svc.ServiceContext
18 | logx.Logger
19 | }
20 |
21 | func NewGetVideoListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetVideoListLogic {
22 | return &GetVideoListLogic{
23 | ctx: ctx,
24 | svcCtx: svcCtx,
25 | Logger: logx.WithContext(ctx),
26 | }
27 | }
28 |
29 | func (l *GetVideoListLogic) GetVideoList(in *video.GetVideoListRequest) (*video.GetVideoListResponse, error) {
30 | var videos []model.Video
31 |
32 | err := l.svcCtx.DBList.Mysql.
33 | Model(&model.Video{}).
34 | Where("created_at < ?", time.Unix(in.LatestTime, 0)).
35 | Order("created_at desc"). // 按照创建时间倒序,最新的在前面
36 | Limit(int(in.Num)).
37 | Find(&videos).Error
38 | if err != nil {
39 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
40 | }
41 |
42 | var videoList []*video.VideoInfo
43 | for _, v := range videos {
44 | //把热门视频放缓存里
45 | if model.IsPopularVideo(v.FavoriteCount, v.CommentCount) {
46 | if l.svcCtx.DBList.Redis.Exists(l.ctx, utils.GenVideoInfoCacheKey(int64(v.ID))).Val() == 0 {
47 | // 缓存不存在,写入缓存
48 | if err := l.svcCtx.DBList.Redis.HSet(l.ctx, utils.GenVideoInfoCacheKey(int64(v.ID)), map[string]interface{}{
49 | "AuthorId": v.AuthorId,
50 | "Title": v.Title,
51 | "PlayUrl": v.PlayUrl,
52 | "CoverUrl": v.CoverUrl,
53 | "FavoriteCount": v.FavoriteCount,
54 | "CommentCount": v.CommentCount,
55 | "CreatedAt": v.CreatedAt.Unix(),
56 | }).Err(); err != nil {
57 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
58 | }
59 | // 放进热门视频列表
60 | err = l.svcCtx.DBList.Redis.LPush(l.ctx, utils.GenPopVideoListCacheKey(), v.ID).Err()
61 | if err != nil {
62 | return nil, status.Error(rpcErr.CacheError.Code, err.Error())
63 | }
64 | }
65 |
66 | }
67 |
68 | videoList = append(videoList, &video.VideoInfo{
69 | Id: int64(v.ID),
70 | AuthorId: v.AuthorId,
71 | Title: v.Title,
72 | PlayUrl: v.PlayUrl,
73 | CoverUrl: v.CoverUrl,
74 | FavoriteCount: v.FavoriteCount,
75 | CommentCount: v.CommentCount,
76 | CreateTime: v.CreatedAt.Unix(),
77 | })
78 | }
79 |
80 | return &video.GetVideoListResponse{
81 | VideoList: videoList,
82 | }, nil
83 | }
84 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/isFavoriteVideoLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "github.com/zeromicro/go-zero/core/logx"
6 | "google.golang.org/grpc/status"
7 | "gorm.io/gorm"
8 | "h68u-tiktok-app-microservice/common/error/rpcErr"
9 | "h68u-tiktok-app-microservice/common/model"
10 | "h68u-tiktok-app-microservice/common/utils"
11 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
12 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
13 | "time"
14 | )
15 |
16 | type IsFavoriteVideoLogic struct {
17 | ctx context.Context
18 | svcCtx *svc.ServiceContext
19 | logx.Logger
20 | }
21 |
22 | func NewIsFavoriteVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *IsFavoriteVideoLogic {
23 | return &IsFavoriteVideoLogic{
24 | ctx: ctx,
25 | svcCtx: svcCtx,
26 | Logger: logx.WithContext(ctx),
27 | }
28 | }
29 |
30 | func (l *IsFavoriteVideoLogic) IsFavoriteVideo(in *video.IsFavoriteVideoRequest) (*video.IsFavoriteVideoResponse, error) {
31 | // 查询缓存是否存在
32 | if l.svcCtx.DBList.Redis.
33 | Exists(l.ctx, utils.GenFavoriteVideoCacheKey(in.UserId, in.VideoId)).
34 | Val() == 1 {
35 | return &video.IsFavoriteVideoResponse{
36 | IsFavorite: true,
37 | }, nil
38 | }
39 |
40 | // 查询记录是否存在
41 | err := l.svcCtx.DBList.Mysql.
42 | Where("user_id = ? And video_id = ?", in.UserId, in.VideoId).
43 | First(&model.Favorite{}).Error
44 |
45 | // 记录不存在
46 | if err == gorm.ErrRecordNotFound {
47 | return &video.IsFavoriteVideoResponse{
48 | IsFavorite: false,
49 | }, nil
50 | }
51 |
52 | // 数据库查询错误
53 | if err != nil {
54 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
55 | }
56 |
57 | // 记录存在,设置缓存
58 | err = l.svcCtx.DBList.Redis.
59 | Set(l.ctx, utils.GenFavoriteVideoCacheKey(in.UserId, in.VideoId), 1, time.Hour).Err()
60 | if err != nil {
61 | return nil, status.Error(rpcErr.CacheError.Code, err.Error())
62 | }
63 |
64 | return &video.IsFavoriteVideoResponse{
65 | IsFavorite: true,
66 | }, nil
67 | }
68 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/publishVideoLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "h68u-tiktok-app-microservice/common/error/rpcErr"
7 | "h68u-tiktok-app-microservice/common/model"
8 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
9 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
10 |
11 | "github.com/zeromicro/go-zero/core/logx"
12 | )
13 |
14 | type PublishVideoLogic struct {
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | logx.Logger
18 | }
19 |
20 | func NewPublishVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PublishVideoLogic {
21 | return &PublishVideoLogic{
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | Logger: logx.WithContext(ctx),
25 | }
26 | }
27 |
28 | func (l *PublishVideoLogic) PublishVideo(in *video.PublishVideoRequest) (*video.Empty, error) {
29 | newVideo := &model.Video{
30 | AuthorId: in.Video.AuthorId,
31 | Title: in.Video.Title,
32 | PlayUrl: in.Video.PlayUrl,
33 | CoverUrl: in.Video.CoverUrl,
34 | }
35 |
36 | if err := l.svcCtx.DBList.Mysql.Create(newVideo).Error; err != nil {
37 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
38 | }
39 |
40 | return &video.Empty{}, nil
41 | }
42 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/unFavoriteVideoLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "google.golang.org/grpc/status"
6 | "gorm.io/gorm"
7 | "gorm.io/gorm/clause"
8 | "h68u-tiktok-app-microservice/common/error/rpcErr"
9 | "h68u-tiktok-app-microservice/common/model"
10 | "h68u-tiktok-app-microservice/common/mq"
11 | "h68u-tiktok-app-microservice/common/utils"
12 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
13 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
14 |
15 | "github.com/zeromicro/go-zero/core/logx"
16 | )
17 |
18 | type UnFavoriteVideoLogic struct {
19 | ctx context.Context
20 | svcCtx *svc.ServiceContext
21 | logx.Logger
22 | }
23 |
24 | func NewUnFavoriteVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UnFavoriteVideoLogic {
25 | return &UnFavoriteVideoLogic{
26 | ctx: ctx,
27 | svcCtx: svcCtx,
28 | Logger: logx.WithContext(ctx),
29 | }
30 | }
31 |
32 | func (l *UnFavoriteVideoLogic) UnFavoriteVideo(in *video.UnFavoriteVideoRequest) (*video.Empty, error) {
33 | err := l.svcCtx.DBList.Mysql.Transaction(func(tx *gorm.DB) error {
34 | // 查询用户喜欢记录是否存在
35 | f := model.Favorite{}
36 | err := tx.
37 | Where("user_id = ? And video_id = ?", in.UserId, in.VideoId).
38 | Clauses(clause.Locking{Strength: "UPDATE"}).
39 | First(&f).Error
40 |
41 | // 点赞记录不存在
42 | if err == gorm.ErrRecordNotFound {
43 | return nil
44 | }
45 |
46 | if err != nil {
47 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
48 | }
49 |
50 | // 删除记录
51 | if err := tx.Where("user_id = ? And video_id = ?", in.UserId, in.VideoId).Delete(&f).Error; err != nil {
52 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
53 | }
54 |
55 | // 视频点赞数减一
56 | if err := tx.Model(&model.Video{}).
57 | Where("id = ?", in.VideoId).
58 | Clauses(clause.Locking{Strength: "UPDATE"}).
59 | UpdateColumn("favorite_count", gorm.Expr("favorite_count - ?", 1)).
60 | Error; err != nil {
61 |
62 | return status.Error(rpcErr.DataBaseError.Code, err.Error())
63 | }
64 |
65 | return nil
66 | })
67 |
68 | if err != nil {
69 | return nil, status.Error(rpcErr.DataBaseError.Code, err.Error())
70 | }
71 |
72 | // 异步删除缓存
73 | task, err := mq.NewDelCacheTask(utils.GenFavoriteVideoCacheKey(in.UserId, in.VideoId))
74 | if err != nil {
75 | logx.WithContext(l.ctx).Errorf("创建任务失败: %v", err)
76 | return nil, status.Error(rpcErr.MQError.Code, err.Error())
77 | }
78 | if _, err := l.svcCtx.AsynqClient.Enqueue(task); err != nil {
79 | logx.WithContext(l.ctx).Errorf("发送任务失败: %v", err)
80 | return nil, status.Error(rpcErr.MQError.Code, err.Error())
81 | }
82 |
83 | return &video.Empty{}, nil
84 | }
85 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/logic/updateVideoLogic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "context"
5 | "gorm.io/gorm/clause"
6 | "h68u-tiktok-app-microservice/common/model"
7 |
8 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
9 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
10 |
11 | "github.com/zeromicro/go-zero/core/logx"
12 | )
13 |
14 | type UpdateVideoLogic struct {
15 | ctx context.Context
16 | svcCtx *svc.ServiceContext
17 | logx.Logger
18 | }
19 |
20 | func NewUpdateVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateVideoLogic {
21 | return &UpdateVideoLogic{
22 | ctx: ctx,
23 | svcCtx: svcCtx,
24 | Logger: logx.WithContext(ctx),
25 | }
26 | }
27 |
28 | func (l *UpdateVideoLogic) UpdateVideo(in *video.UpdateVideoRequest) (*video.Empty, error) {
29 | // 开启事务
30 | tx := l.svcCtx.DBList.Mysql.Begin()
31 |
32 | var newVideo model.Video
33 | err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", in.Video.Id).First(&newVideo).Error
34 | if err != nil {
35 | tx.Rollback()
36 | return nil, err
37 | }
38 |
39 | newVideo.CommentCount = in.Video.CommentCount
40 | newVideo.FavoriteCount = in.Video.FavoriteCount
41 |
42 | err = tx.Clauses(clause.Locking{Strength: "UPDATE"}).Save(&newVideo).Error
43 | if err != nil {
44 | tx.Rollback()
45 | return nil, err
46 | }
47 |
48 | tx.Commit()
49 |
50 | return &video.Empty{}, nil
51 | }
52 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/server/videoServer.go:
--------------------------------------------------------------------------------
1 | // Code generated by goctl. DO NOT EDIT!
2 | // Source: video.proto
3 |
4 | package server
5 |
6 | import (
7 | "context"
8 |
9 | "h68u-tiktok-app-microservice/service/rpc/video/internal/logic"
10 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
11 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
12 | )
13 |
14 | type VideoServer struct {
15 | svcCtx *svc.ServiceContext
16 | video.UnimplementedVideoServer
17 | }
18 |
19 | func NewVideoServer(svcCtx *svc.ServiceContext) *VideoServer {
20 | return &VideoServer{
21 | svcCtx: svcCtx,
22 | }
23 | }
24 |
25 | func (s *VideoServer) GetVideoList(ctx context.Context, in *video.GetVideoListRequest) (*video.GetVideoListResponse, error) {
26 | l := logic.NewGetVideoListLogic(ctx, s.svcCtx)
27 | return l.GetVideoList(in)
28 | }
29 |
30 | func (s *VideoServer) PublishVideo(ctx context.Context, in *video.PublishVideoRequest) (*video.Empty, error) {
31 | l := logic.NewPublishVideoLogic(ctx, s.svcCtx)
32 | return l.PublishVideo(in)
33 | }
34 |
35 | func (s *VideoServer) UpdateVideo(ctx context.Context, in *video.UpdateVideoRequest) (*video.Empty, error) {
36 | l := logic.NewUpdateVideoLogic(ctx, s.svcCtx)
37 | return l.UpdateVideo(in)
38 | }
39 |
40 | func (s *VideoServer) GetVideoListByAuthor(ctx context.Context, in *video.GetVideoListByAuthorRequest) (*video.GetVideoListByAuthorResponse, error) {
41 | l := logic.NewGetVideoListByAuthorLogic(ctx, s.svcCtx)
42 | return l.GetVideoListByAuthor(in)
43 | }
44 |
45 | func (s *VideoServer) FavoriteVideo(ctx context.Context, in *video.FavoriteVideoRequest) (*video.Empty, error) {
46 | l := logic.NewFavoriteVideoLogic(ctx, s.svcCtx)
47 | return l.FavoriteVideo(in)
48 | }
49 |
50 | func (s *VideoServer) UnFavoriteVideo(ctx context.Context, in *video.UnFavoriteVideoRequest) (*video.Empty, error) {
51 | l := logic.NewUnFavoriteVideoLogic(ctx, s.svcCtx)
52 | return l.UnFavoriteVideo(in)
53 | }
54 |
55 | func (s *VideoServer) GetFavoriteVideoList(ctx context.Context, in *video.GetFavoriteVideoListRequest) (*video.GetFavoriteVideoListResponse, error) {
56 | l := logic.NewGetFavoriteVideoListLogic(ctx, s.svcCtx)
57 | return l.GetFavoriteVideoList(in)
58 | }
59 |
60 | func (s *VideoServer) IsFavoriteVideo(ctx context.Context, in *video.IsFavoriteVideoRequest) (*video.IsFavoriteVideoResponse, error) {
61 | l := logic.NewIsFavoriteVideoLogic(ctx, s.svcCtx)
62 | return l.IsFavoriteVideo(in)
63 | }
64 |
65 | func (s *VideoServer) CommentVideo(ctx context.Context, in *video.CommentVideoRequest) (*video.CommentVideoResponse, error) {
66 | l := logic.NewCommentVideoLogic(ctx, s.svcCtx)
67 | return l.CommentVideo(in)
68 | }
69 |
70 | func (s *VideoServer) GetCommentList(ctx context.Context, in *video.GetCommentListRequest) (*video.GetCommentListResponse, error) {
71 | l := logic.NewGetCommentListLogic(ctx, s.svcCtx)
72 | return l.GetCommentList(in)
73 | }
74 |
75 | func (s *VideoServer) DeleteVideoComment(ctx context.Context, in *video.DeleteVideoCommentRequest) (*video.Empty, error) {
76 | l := logic.NewDeleteVideoCommentLogic(ctx, s.svcCtx)
77 | return l.DeleteVideoComment(in)
78 | }
79 |
80 | func (s *VideoServer) GetCommentInfo(ctx context.Context, in *video.GetCommentInfoRequest) (*video.GetCommentInfoResponse, error) {
81 | l := logic.NewGetCommentInfoLogic(ctx, s.svcCtx)
82 | return l.GetCommentInfo(in)
83 | }
84 |
--------------------------------------------------------------------------------
/service/rpc/video/internal/svc/serviceContext.go:
--------------------------------------------------------------------------------
1 | package svc
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/go-redis/redis/v8"
7 | "github.com/hibiken/asynq"
8 | "gorm.io/driver/mysql"
9 | "gorm.io/gorm"
10 | "gorm.io/gorm/schema"
11 | "h68u-tiktok-app-microservice/common/model"
12 | "h68u-tiktok-app-microservice/service/rpc/video/internal/config"
13 | "time"
14 | )
15 |
16 | type ServiceContext struct {
17 | Config config.Config
18 | DBList *DBList
19 | AsynqClient *asynq.Client
20 | }
21 |
22 | type DBList struct {
23 | Mysql *gorm.DB
24 | Redis *redis.Client
25 | }
26 |
27 | func NewServiceContext(c config.Config) *ServiceContext {
28 | return &ServiceContext{
29 | Config: c,
30 | DBList: initDB(c),
31 | AsynqClient: asynq.NewClient(asynq.RedisClientOpt{Addr: c.DBList.Redis.Address, Password: c.DBList.Redis.Password}),
32 | }
33 | }
34 |
35 | func initDB(c config.Config) *DBList {
36 | dbList := new(DBList)
37 | dbList.Mysql = initMysql(c)
38 | dbList.Redis = initRedis(c)
39 |
40 | return dbList
41 | }
42 |
43 | func initMysql(c config.Config) *gorm.DB {
44 | dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
45 | c.DBList.Mysql.Username,
46 | c.DBList.Mysql.Password,
47 | c.DBList.Mysql.Address,
48 | c.DBList.Mysql.DBName,
49 | )
50 |
51 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
52 | NamingStrategy: schema.NamingStrategy{
53 | TablePrefix: c.DBList.Mysql.TablePrefix, // 表名前缀
54 | SingularTable: true, // 使用单数表名
55 | },
56 | DisableForeignKeyConstraintWhenMigrating: true,
57 | })
58 | if err != nil {
59 | panic(err)
60 | }
61 |
62 | // 自动建表
63 | err = db.AutoMigrate(&model.Video{}, &model.Favorite{}, &model.Comment{})
64 | if err != nil {
65 | panic(err)
66 | }
67 |
68 | return db
69 | }
70 |
71 | func initRedis(c config.Config) *redis.Client {
72 | fmt.Println("connect Redis ...")
73 | db := redis.NewClient(&redis.Options{
74 | Addr: c.DBList.Redis.Address,
75 | Password: c.DBList.Redis.Password,
76 | //DB: c.DBList.Redis.DB,
77 | //超时
78 | ReadTimeout: 2 * time.Second,
79 | WriteTimeout: 2 * time.Second,
80 | PoolTimeout: 3 * time.Second,
81 | })
82 | _, err := db.Ping(context.Background()).Result()
83 | if err != nil {
84 | fmt.Println("connect Redis failed")
85 | panic(err)
86 | }
87 | fmt.Println("connect Redis success")
88 | return db
89 | }
90 |
--------------------------------------------------------------------------------
/service/rpc/video/video.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/zeromicro/go-zero/core/logx"
7 | "h68u-tiktok-app-microservice/service/rpc/video/internal/config"
8 | s1 "h68u-tiktok-app-microservice/service/rpc/video/internal/server"
9 | "h68u-tiktok-app-microservice/service/rpc/video/internal/svc"
10 | "h68u-tiktok-app-microservice/service/rpc/video/types/video"
11 |
12 | "github.com/zeromicro/go-zero/core/conf"
13 | "github.com/zeromicro/go-zero/core/service"
14 | "github.com/zeromicro/go-zero/zrpc"
15 | "google.golang.org/grpc"
16 | "google.golang.org/grpc/reflection"
17 | )
18 |
19 | var configFile = flag.String("f", "etc/video.yaml", "the config file")
20 |
21 | func main() {
22 | flag.Parse()
23 |
24 | var c config.Config
25 | conf.MustLoad(*configFile, &c)
26 | ctx := svc.NewServiceContext(c)
27 |
28 | s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
29 | video.RegisterVideoServer(grpcServer, s1.NewVideoServer(ctx))
30 |
31 | if c.Mode == service.DevMode || c.Mode == service.TestMode {
32 | reflection.Register(grpcServer)
33 | }
34 | })
35 | defer s.Stop()
36 |
37 | logx.DisableStat()
38 | fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
39 | s.Start()
40 | }
41 |
--------------------------------------------------------------------------------
/service/rpc/video/video.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package video;
4 |
5 | option go_package = "./video";
6 |
7 | // 空消息
8 | message Empty {}
9 |
10 | // 视频信息结构体
11 | message VideoInfo {
12 | int64 Id = 1;
13 | int64 AuthorId = 2;
14 | string Title = 3;
15 | string PlayUrl = 4;
16 | string CoverUrl = 5;
17 | int64 FavoriteCount = 6;
18 | int64 CommentCount = 7;
19 | int64 CreateTime = 8;
20 | }
21 |
22 | // 评论信息结构体
23 | message Comment {
24 | int64 Id = 1;
25 | int64 AuthorId = 3;
26 | int64 CreateTime =4;
27 | string Content = 5;
28 | }
29 |
30 | service Video {
31 | rpc GetVideoList(GetVideoListRequest) returns (GetVideoListResponse);
32 | rpc PublishVideo(PublishVideoRequest) returns (Empty);
33 | rpc UpdateVideo(UpdateVideoRequest) returns (Empty);
34 | rpc GetVideoListByAuthor(GetVideoListByAuthorRequest) returns (GetVideoListByAuthorResponse);
35 |
36 | rpc FavoriteVideo(FavoriteVideoRequest) returns (Empty);
37 | rpc UnFavoriteVideo(UnFavoriteVideoRequest) returns (Empty);
38 | rpc GetFavoriteVideoList(GetFavoriteVideoListRequest) returns (GetFavoriteVideoListResponse);
39 | rpc IsFavoriteVideo(IsFavoriteVideoRequest) returns (IsFavoriteVideoResponse);
40 |
41 | rpc CommentVideo(CommentVideoRequest) returns (CommentVideoResponse);
42 | rpc GetCommentList(GetCommentListRequest) returns (GetCommentListResponse);
43 | rpc DeleteVideoComment(DeleteVideoCommentRequest) returns(Empty);
44 | rpc GetCommentInfo(GetCommentInfoRequest) returns (GetCommentInfoResponse);
45 | }
46 |
47 | message GetVideoListRequest {
48 | int64 Num = 1;
49 | int64 LatestTime = 2;
50 | }
51 |
52 | message GetVideoListResponse {
53 | repeated VideoInfo VideoList = 1;
54 | }
55 |
56 | message PublishVideoRequest {
57 | VideoInfo Video = 1;
58 | }
59 |
60 | message UpdateVideoRequest {
61 | VideoInfo Video = 1;
62 | }
63 |
64 | message GetVideoListByAuthorRequest {
65 | int64 AuthorId = 1;
66 | }
67 |
68 | message GetVideoListByAuthorResponse {
69 | repeated VideoInfo VideoList = 1;
70 | }
71 |
72 | message FavoriteVideoRequest {
73 | int64 UserId = 1;
74 | int64 VideoId = 2;
75 | }
76 |
77 | message UnFavoriteVideoRequest {
78 | int64 UserId = 1;
79 | int64 VideoId = 2;
80 | }
81 |
82 | message GetFavoriteVideoListRequest {
83 | int64 UserId = 1;
84 | }
85 |
86 | message GetFavoriteVideoListResponse {
87 | repeated VideoInfo VideoList = 1;
88 | }
89 |
90 | message IsFavoriteVideoRequest {
91 | int64 UserId = 1;
92 | int64 VideoId = 2;
93 | }
94 |
95 | message IsFavoriteVideoResponse {
96 | bool IsFavorite = 1;
97 | }
98 |
99 | message CommentVideoRequest {
100 | int64 UserId = 1;
101 | int64 VideoId = 2;
102 | string Content = 3;
103 | }
104 |
105 | message CommentVideoResponse {
106 | int64 Id = 1;
107 | int64 UserId = 2;
108 | string Content = 3;
109 | int64 CreatedTime = 4;
110 | }
111 |
112 | message GetCommentListRequest {
113 | int64 VideoId = 1;
114 | }
115 |
116 | message GetCommentListResponse {
117 | repeated Comment CommentList = 1;
118 | }
119 |
120 | message DeleteVideoCommentRequest {
121 | int64 CommentId = 1;
122 | }
123 |
124 | message GetCommentInfoRequest {
125 | int64 CommentId = 1;
126 | }
127 |
128 | message GetCommentInfoResponse {
129 | int64 Id = 1;
130 | int64 UserId = 2;
131 | string Content = 3;
132 | int64 CreatedTime = 4;
133 | }
--------------------------------------------------------------------------------