├── .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 | ![image-20230113125129881](https://image.nickxu.me/202301131251144.png) 6 | 7 | image-20230113130844275 8 | 9 | > 我们的目标是一等奖,重振h68u荣光 10 | 11 | 有第三届的项目可供参考:https://github.com/h68u/h68u-tiktok-app 12 | 13 | ## DDL 14 | 15 | ![1280X1280](https://image.nickxu.me/202301131254144.PNG) 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 | ![image-20230214002205723](https://image.nickxu.me/202302140022813.png) 10 | 11 | # 准备工作 12 | 13 | ## 准备配置文件 14 | 15 | 来到各个服务的配置目录,将示例配置文件复制一份,去掉example后缀 16 | 17 | 当然,OSS配置我不能告诉你( 18 | 19 | ![image-20230214003609781](https://image.nickxu.me/202302140036810.png) 20 | 21 | ## 创建消息队列 22 | 23 | 先启动里面的卡夫卡 24 | 25 | ![image-20230213232725379](https://image.nickxu.me/202302132327650.png) 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 | ![image-20230213233046685](https://image.nickxu.me/202302132330710.png) 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 | ![image-20230213233805227](https://image.nickxu.me/202302132338255.png) 54 | 55 | 测试一下能否连接到数据库 56 | 57 | ![image-20230213233846140](https://image.nickxu.me/202302132338164.png) 58 | 59 | 创建项目会访问到的库 60 | 61 | ![image-20230216223030583](https://image.nickxu.me/202302162230942.png) 62 | 63 | ## 启动所有服务 64 | 65 | ![image-20230213234727364](https://image.nickxu.me/202302132347411.png) 66 | 67 | 等待13个镜像全部拉取完成并启动 68 | 69 | ![image-20230213234242298](https://image.nickxu.me/202302132342332.png) 70 | 71 | 项目本体使用 modd 热编译,任何修改都会自动重新编译并启动 72 | 73 | ![image-20230213234229785](https://image.nickxu.me/202302132342841.png) 74 | 75 | 等待项目完全启动 76 | 77 | ![image-20230213234752966](https://image.nickxu.me/202302132347005.png) 78 | 79 | # 服务监控 80 | 81 | [9090](http://127.0.0.1:9090/) 端口访问普罗米修斯,来到target页面,稍等片刻等待服务全部亮起 82 | 83 | ![image-20230213235050066](https://image.nickxu.me/202302132350103.png) 84 | 85 | ![image-20230213235110737](https://image.nickxu.me/202302132351768.png) 86 | 87 | ![image-20230213235157072](https://image.nickxu.me/202302132351112.png) 88 | 89 | # 链接追踪 90 | 91 | 去 apifox 上请求一下,然后来到 [16686](http://127.0.0.1:16686/) 端口访问 Jaeger,就能查看链路追踪 92 | 93 | ![image-20230214001801336](https://image.nickxu.me/202302140018455.png) 94 | 95 | ![b696e8bb719a6d19fee3e168e9a76cc3](https://image.nickxu.me/202302140025172.png) 96 | 97 | # 日志收集 98 | 99 | 通过 [5601](http://127.0.0.1:5601/) 端口访问 Kibana 100 | 101 | 102 | 103 | ![image-20230214001900850](https://image.nickxu.me/202302140019908.png) 104 | 105 | ![image-20230214001929216](https://image.nickxu.me/202302140019273.png) 106 | 107 | ![image-20230214001950254](https://image.nickxu.me/202302140019297.png) 108 | 109 | ![image-20230214001958514](https://image.nickxu.me/202302140019553.png) 110 | 111 | ![image-20230214002030719](https://image.nickxu.me/202302140020772.png) 112 | 113 | ![image-20230214002634676](https://image.nickxu.me/202302140026725.png) 114 | 115 | 演示:根据请求ID查询日志排查错误 116 | 117 | 可以尝试把 mysql 关掉再请求 118 | 119 | ![ed2c40b507cf0fda52187cd346096a03](https://image.nickxu.me/202302140025057.png) 120 | 121 | ![f09a295e995d99a8b00ff9b25d031a71](https://image.nickxu.me/202302140025942.png) -------------------------------------------------------------------------------- /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 | } --------------------------------------------------------------------------------