├── How We Built Whimsical@1.100000023841858x.webp ├── LICENSE ├── README.md ├── common ├── globalkey │ ├── constantKey.go │ └── redisCacheKey.go ├── help │ ├── cos │ │ ├── download.go │ │ ├── snowAlg.go │ │ ├── updateComment.go │ │ └── uploadVideo.go │ └── token │ │ ├── common.go │ │ ├── genToken.go │ │ └── parseToken.go ├── messageTypes │ └── message.go └── xerr │ ├── errCode.go │ ├── errMsg.go │ └── errors.go ├── deploy ├── README.md ├── api │ └── Dockerfile ├── asynqJob │ ├── client-scheduler │ │ └── Dockerfile │ └── server │ │ └── Dockerfile ├── asynqUI.sh ├── docker-compose.yml ├── mq │ └── Dockerfile ├── rpc-user-info │ └── Dockerfile ├── rpc-user-operate │ └── Dockerfile └── rpc-video-service │ └── Dockerfile ├── go.mod ├── go.sum ├── service ├── api │ ├── api │ │ ├── build.sh │ │ ├── user.api │ │ ├── userOpt.api │ │ └── video.api │ ├── etc │ │ └── user-api.yaml │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── handler │ │ │ ├── feed │ │ │ │ └── feedVideoListHandler.go │ │ │ ├── publish │ │ │ │ ├── getPublishVideoListHandler.go │ │ │ │ └── publishVideoHandler.go │ │ │ ├── routes.go │ │ │ ├── user │ │ │ │ ├── userInfoHandler.go │ │ │ │ ├── userLoginHandler.go │ │ │ │ └── userRegisterHandler.go │ │ │ └── userOpt │ │ │ │ ├── commentOptHandler.go │ │ │ │ ├── favoriteOptHandler.go │ │ │ │ ├── followOptHandler.go │ │ │ │ ├── getCommentListHandler.go │ │ │ │ ├── getFavoriteListHandler.go │ │ │ │ ├── getFollowListHandler.go │ │ │ │ └── getFollowerListHandler.go │ │ ├── logic │ │ │ ├── feed │ │ │ │ └── feedVideoListLogic.go │ │ │ ├── publish │ │ │ │ ├── getPublishVideoListLogic.go │ │ │ │ └── publishVideoLogic.go │ │ │ ├── user │ │ │ │ ├── userInfoLogic.go │ │ │ │ ├── userLoginLogic.go │ │ │ │ └── userRegisterLogic.go │ │ │ └── userOpt │ │ │ │ ├── commentOptLogic.go │ │ │ │ ├── favoriteOptLogic.go │ │ │ │ ├── followOptLogic.go │ │ │ │ ├── getCommentListLogic.go │ │ │ │ ├── getFavoriteListLogic.go │ │ │ │ ├── getFollowListLogic.go │ │ │ │ └── getFollowerListLogic.go │ │ ├── middleware │ │ │ ├── authjwtMiddleware.go │ │ │ └── isloginMiddleware.go │ │ ├── svc │ │ │ └── serviceContext.go │ │ └── types │ │ │ └── types.go │ └── user.go ├── asynqJob │ ├── README.md │ ├── client-scheduler │ │ ├── etc │ │ │ └── scheduler.yaml │ │ ├── internal │ │ │ ├── config │ │ │ │ └── config.go │ │ │ ├── logic │ │ │ │ ├── register.go │ │ │ │ └── setScheduler.go │ │ │ └── svc │ │ │ │ ├── scheduler.go │ │ │ │ └── serviceContext.go │ │ └── scheduler.go │ └── server │ │ ├── etc │ │ └── server.yaml │ │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── logic │ │ │ ├── jobs │ │ │ │ ├── GetUserFavoriteStatus.go │ │ │ │ └── GetUserFollowStatus.go │ │ │ └── routes.go │ │ └── svc │ │ │ ├── asynqServer.go │ │ │ └── serviceContext.go │ │ ├── jobtype │ │ └── jobtype.go │ │ └── server.go ├── mq │ ├── etc │ │ └── mq.yaml │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── listen │ │ │ ├── kqMqs.go │ │ │ └── listen.go │ │ ├── mqs │ │ │ └── kq │ │ │ │ ├── userCommentUpdate.go │ │ │ │ ├── userFavoriteUpdate.go │ │ │ │ └── userFollowUpdate.go │ │ └── svc │ │ │ └── serviceContext.go │ └── mq.go ├── rpc-user-info │ ├── etc │ │ └── userInfoService.yaml │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── logic │ │ │ ├── authsInfoLogic.go │ │ │ ├── infoLogic.go │ │ │ ├── loginLogic.go │ │ │ └── registerLogic.go │ │ ├── server │ │ │ └── userInfoServiceServer.go │ │ └── svc │ │ │ └── serviceContext.go │ ├── model │ │ ├── userModel.go │ │ ├── userModel_gen.go │ │ └── vars.go │ ├── userInfoPb │ │ ├── GenPb │ │ │ ├── UserInfoService.proto │ │ │ └── build.sh │ │ ├── UserInfoService.pb.go │ │ └── UserInfoService_grpc.pb.go │ ├── userInfoService.go │ └── userinfoservice │ │ └── userInfoService.go ├── rpc-user-operate │ ├── etc │ │ └── userOptService.yaml │ ├── internal │ │ ├── config │ │ │ └── config.go │ │ ├── logic │ │ │ ├── getUserFavoriteLogic.go │ │ │ ├── getUserFollowLogic.go │ │ │ ├── getUserFollowerLogic.go │ │ │ ├── getVideoCommentLogic.go │ │ │ ├── updateCommentStatusLogic.go │ │ │ ├── updateFavoriteStatusLogic.go │ │ │ └── updateFollowStatusLogic.go │ │ ├── server │ │ │ └── userOptServiceServer.go │ │ └── svc │ │ │ └── serviceContext.go │ ├── model │ │ ├── userCommentListModel.go │ │ ├── userCommentListModel_gen.go │ │ ├── userFavoriteListModel.go │ │ ├── userFavoriteListModel_gen.go │ │ ├── userFollowListModel.go │ │ ├── userFollowListModel_gen.go │ │ └── vars.go │ ├── userOptPb │ │ ├── GenPb │ │ │ ├── UserOptService.proto │ │ │ └── build.sh │ │ ├── UserOptService.pb.go │ │ └── UserOptService_grpc.pb.go │ ├── userOptService.go │ └── useroptservice │ │ └── userOptService.go └── rpc-video-service │ ├── etc │ └── videoService.yaml │ ├── internal │ ├── config │ │ └── config.go │ ├── logic │ │ ├── feedVideosLogic.go │ │ ├── getMyFavoriteVideosLogic.go │ │ ├── getVideoListLogic.go │ │ └── pubVideoLogic.go │ ├── server │ │ └── videoServiceServer.go │ └── svc │ │ └── serviceContext.go │ ├── model │ ├── vars.go │ ├── videoModel.go │ └── videoModel_gen.go │ ├── videoService.go │ ├── videoSvcPb │ ├── GenPb │ │ ├── VideoService.proto │ │ └── build.sh │ ├── VideoService.pb.go │ └── VideoService_grpc.pb.go │ └── videoservice │ └── videoService.go └── tpl └── 1.3.5 ├── api ├── config.tpl ├── context.tpl ├── etc.tpl ├── handler.tpl ├── logic.tpl ├── main.tpl ├── middleware.tpl ├── route-addition.tpl ├── routes.tpl ├── template.tpl └── types.tpl ├── docker └── docker.tpl ├── kube ├── deployment.tpl └── job.tpl ├── model ├── delete.tpl ├── err.tpl ├── field.tpl ├── find-one-by-field-extra-method.tpl ├── find-one-by-field.tpl ├── find-one.tpl ├── import-no-cache.tpl ├── import.tpl ├── insert.tpl ├── interface-delete.tpl ├── interface-find-one-by-field.tpl ├── interface-find-one.tpl ├── interface-insert.tpl ├── interface-update.tpl ├── model-gen.tpl ├── model-new.tpl ├── model.tpl ├── table-name.tpl ├── tag.tpl ├── types.tpl ├── update.tpl └── var.tpl ├── mongo ├── err.tpl └── model.tpl ├── newapi └── newtemplate.tpl └── rpc ├── call-func.tpl ├── call-interface-func.tpl ├── call.tpl ├── config.tpl ├── etc.tpl ├── logic-func.tpl ├── logic.tpl ├── main.tpl ├── server-func.tpl ├── server.tpl ├── svc.tpl └── template.tpl /How We Built Whimsical@1.100000023841858x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/healerwhy/douyin/267d2fba80ddbafb46dedc734c4eb6d3fefd25f7/How We Built Whimsical@1.100000023841858x.webp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 healerwhy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 字节跳动青训营 - douyin [项目文档](https://ljxltr3g7w.feishu.cn/docs/doccnLberlBxkQjylBal5I6Tg6g) 2 | 3 | # 业务架构 4 | ![](https://raw.githubusercontent.com/healerwhy/douyin/main/How%20We%20Built%20Whimsical%401.100000023841858x.webp) 5 | # common 6 | 存放公共代码:主要有对象存储的上传、下载、删除,token的生成与解析,Kafka所需的消息结构体,公共错误代码等。 7 | 8 | # deploy 9 | 通过docker-compose.yml部署Kafka、ZooKeeper,Dockerfile是服务的镜像生成文件,具体使用在deploy下的README文件 10 | 11 | # service 12 | ## api 13 | 暴露RESTful API,主要是对接口的访问控制、接口的参数校验、接口的返回结构体等。 14 | ## asynqJob 15 | 存放异步任务,client-scheduler 调度server执行任务(设置的每10s一次) 16 | ## mq 17 | 存放消息队列的消费者,将api放在redis中的数据取出后写入mysql中 18 | ## rpc-user-info 19 | 存放用户信息的rpc接口,完成用户注册、登陆、查看用户信息,主要是对user表的CURD 20 | ## rpc-user-operate 21 | 存放用户操作的rpc接口,完成点赞、评论、关注操作,主要是对user_favorite_list、user_follow_list、user_comment_list表的CURD 22 | ## rpc-video-service 23 | 存放对视频的操作的rpc接口,完成视频的发布、拉取、视频流操作,主要是对video表进行CURD 24 | 25 | # tpl 26 | 是goctl脚手架工具所需的模板文件,因为对原本的模板文件进行了修改,所以在生成时需要指定为该目录下的tpl文件 27 | 28 | # 写在最后 29 | 感谢以下项目让我学到了很多东西:
30 | [go-zero](https://github.com/zeromicro/go-zero) [万总b站账号](https://space.bilibili.com/434639159?spm_id_from=333.337.search-card.all.click)
31 | go-zero真的是超棒的项目!一定要加万总的社群,疯狂安利!大佬云集! 32 | 向万总和安前松前辈学到了很多东西!谢谢~!每次深夜去找他们请教问题,两位大佬也会很快回复我的消息,真的超感动! 33 | 34 | [go-zero-looklook](https://github.com/Mikaelemmmm/go-zero-looklook) [B站up主~](https://space.bilibili.com/389552232)
35 | Mikaelemmmm大佬人超级棒~! 谢谢~!正如他的签名:“做人但求无愧于心,做事但求无愧于人”,很佩服! 36 | 37 | [cos-go-sdk-v5](https://github.com/tencentyun/cos-go-sdk-v5)
38 | 腾讯云的技术客服超级耐心~!谢谢~! 39 | 40 | ``` 41 | 最后的最后: 42 | 感谢字节跳动青训营的所有工作人员无私的辛苦付出,祝青训营越办越好~ 43 | 这段时间接触到了很多的大佬!为你们的开源精神致敬!salute~ 44 | 我会继续加油,努力追赶你们的脚步 45 | 欢迎一起学习讨论~ 对该项目有任何建议或者意见,请发邮件至:645582687@qq.com,谢谢! 46 | 47 | 2022.7.3 48 | ``` 49 | -------------------------------------------------------------------------------- /common/globalkey/constantKey.go: -------------------------------------------------------------------------------- 1 | package globalkey 2 | 3 | // 软删除只是在数据库表中添加量该字段 但是项目中并未用到 4 | 5 | var DelStateNo int64 = 0 //未删除 6 | var DelStateYes int64 = 1 //已删除 7 | -------------------------------------------------------------------------------- /common/globalkey/redisCacheKey.go: -------------------------------------------------------------------------------- 1 | package globalkey 2 | 3 | // 存放的是api记录点赞和关注放在redis的key与valTpl 4 | 5 | const FavoriteSetKey = "FavoriteKey" 6 | const FavoriteSetValTpl = "Favorite&Video&Id:%d" 7 | 8 | const FollowSetKey = "FollowKey" 9 | const FollowSetValTpl = "Follow&Follow&Id:%d" 10 | 11 | const ExistDataValTpl = "%d:%d" 12 | -------------------------------------------------------------------------------- /common/help/cos/download.go: -------------------------------------------------------------------------------- 1 | package cos 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/tencentyun/cos-go-sdk-v5" 8 | "github.com/zeromicro/go-zero/core/logx" 9 | "io/ioutil" 10 | "net/http" 11 | "net/url" 12 | "sync" 13 | ) 14 | 15 | type DownloadComment struct { 16 | Key string 17 | CommentBucket string 18 | SecretID string 19 | SecretKey string 20 | } 21 | 22 | func (l *DownloadComment) DownloadComment(ctx context.Context) ([]*CommentFile, error) { 23 | 24 | u, _ := url.Parse(l.CommentBucket) 25 | b := &cos.BaseURL{BucketURL: u} 26 | c := cos.NewClient(b, &http.Client{ 27 | Transport: &cos.AuthorizationTransport{ 28 | SecretID: l.SecretID, 29 | SecretKey: l.SecretKey, 30 | }, 31 | }) 32 | 33 | // 多goroutine同时读取文件 34 | keysCh := make(chan []string, 3) 35 | var wg sync.WaitGroup 36 | threadpool := 3 37 | var allComment []*CommentFile 38 | for i := 0; i < threadpool; i++ { 39 | wg.Add(1) 40 | 41 | go func() { 42 | defer wg.Done() 43 | for keys := range keysCh { 44 | key := keys[0] 45 | resp, err := c.Object.Get(ctx, key, nil) 46 | if err != nil { 47 | panic(err) 48 | } 49 | all, err := ioutil.ReadAll(resp.Body) 50 | _ = resp.Body.Close() 51 | 52 | if err != nil { 53 | return 54 | } 55 | var ret CommentFile 56 | _ = json.Unmarshal(all, &ret) 57 | 58 | allComment = append(allComment, &ret) 59 | 60 | if err != nil { 61 | fmt.Println(err) 62 | } 63 | } 64 | }() 65 | } 66 | 67 | isTruncated := true 68 | prefix := l.Key // 下载 dir 目录下所有文件 69 | 70 | logx.Infof("-------------%+v", prefix) 71 | 72 | for isTruncated { 73 | opt := &cos.BucketGetOptions{ 74 | Prefix: prefix, 75 | EncodingType: "url", // url编码 76 | } 77 | // 列出目录 78 | v, _, err := c.Bucket.Get(ctx, opt) 79 | if err != nil { 80 | fmt.Println(err) 81 | break 82 | } 83 | for _, c := range v.Contents { 84 | key, _ := cos.DecodeURIComponent(c.Key) //EncodingType: "url",先对 Key 进行 url decode 85 | 86 | keysCh <- []string{key} 87 | } 88 | isTruncated = v.IsTruncated 89 | } 90 | close(keysCh) 91 | wg.Wait() 92 | 93 | return allComment, nil 94 | } 95 | -------------------------------------------------------------------------------- /common/help/cos/snowAlg.go: -------------------------------------------------------------------------------- 1 | package cos 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "github.com/sony/sonyflake" 6 | ) 7 | 8 | type GenSnowFlake struct{} 9 | 10 | var sf *sonyflake.Sonyflake 11 | 12 | func (*GenSnowFlake) GenSnowFlake(machineId uint16) (uint64, error) { 13 | var st sonyflake.Settings 14 | st.MachineID = func() (uint16, error) { 15 | return machineId, nil 16 | } 17 | sf = sonyflake.NewSonyflake(st) 18 | 19 | id, err := sf.NextID() 20 | if err != nil { 21 | return 0, errors.Wrap(err, " + sonyflake not created") 22 | } 23 | return id, nil 24 | } 25 | -------------------------------------------------------------------------------- /common/help/cos/updateComment.go: -------------------------------------------------------------------------------- 1 | package cos 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/jinzhu/copier" 9 | "github.com/tencentyun/cos-go-sdk-v5" 10 | "github.com/zeromicro/go-zero/core/logx" 11 | "net/http" 12 | "net/url" 13 | ) 14 | 15 | type UpdateComment struct { 16 | VideoId int64 `json:"video_id"` 17 | CommentId int64 `json:"id"` 18 | UserId int64 `json:"user_id"` 19 | 20 | Content string `json:"content,omitempty" copier:"CommentText"` 21 | CreateDate string `json:"create_date,omitempty"` 22 | } 23 | 24 | type CommentFile struct { 25 | UserId int64 `json:"user_id"` 26 | CommentId int64 `json:"comment_id"` 27 | CreateDate string `json:"create_date"` 28 | Content string `json:"content" copier:"CommentText"` 29 | } 30 | 31 | func (l *UpdateComment) UploadComment(ctx context.Context, CommentBucket, SecretID, SecretKey string) (string, error) { 32 | u, _ := url.Parse(CommentBucket) 33 | b := &cos.BaseURL{BucketURL: u} 34 | c := cos.NewClient(b, &http.Client{ 35 | Transport: &cos.AuthorizationTransport{ 36 | SecretID: SecretID, 37 | SecretKey: SecretKey, 38 | }, 39 | }) 40 | 41 | var commentFile CommentFile 42 | _ = copier.Copy(&commentFile, l) 43 | 44 | ret, _ := json.Marshal(commentFile) 45 | buf := bytes.NewBuffer(ret) 46 | key := "video_id_" + fmt.Sprintf("%d/", l.VideoId) + fmt.Sprintf("user_id_%d_comment_id_%d", l.UserId, l.CommentId) + ".json" 47 | 48 | _, err := c.Object.Put(ctx, key, buf, nil) 49 | if err != nil { 50 | logx.Errorf("UploadComment error: %v", err) 51 | return "", err 52 | } 53 | 54 | return "", nil 55 | } 56 | 57 | func (l *UpdateComment) DeleteComment(ctx context.Context, CommentBucket, SecretID, SecretKey string) (string, error) { 58 | u, _ := url.Parse(CommentBucket) 59 | b := &cos.BaseURL{BucketURL: u} 60 | c := cos.NewClient(b, &http.Client{ 61 | Transport: &cos.AuthorizationTransport{ 62 | SecretID: SecretID, 63 | SecretKey: SecretKey, 64 | }, 65 | }) 66 | 67 | key := "video_id_" + fmt.Sprintf("%d/", l.VideoId) + fmt.Sprintf("user_id_%d_comment_id_%d", l.UserId, l.CommentId) + ".json" 68 | 69 | _, err := c.Object.Delete(ctx, key) 70 | if err != nil { 71 | logx.Errorf("UploadComment error: %v", err) 72 | return "", err 73 | } 74 | 75 | return "", nil 76 | } 77 | -------------------------------------------------------------------------------- /common/help/cos/uploadVideo.go: -------------------------------------------------------------------------------- 1 | package cos 2 | 3 | import ( 4 | "context" 5 | "github.com/tencentyun/cos-go-sdk-v5" 6 | "github.com/zeromicro/go-zero/core/logx" 7 | "mime/multipart" 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | ) 12 | 13 | type UploaderVideo struct { 14 | UserId int64 15 | MachineId uint16 16 | VideoBucket string 17 | SecretID string 18 | SecretKey string 19 | } 20 | 21 | func (l *UploaderVideo) UploadVideo(ctx context.Context, file multipart.File) (string, error) { 22 | u, _ := url.Parse(l.VideoBucket) 23 | b := &cos.BaseURL{BucketURL: u} 24 | c := cos.NewClient(b, &http.Client{ 25 | Transport: &cos.AuthorizationTransport{ 26 | SecretID: l.SecretID, 27 | SecretKey: l.SecretKey, 28 | }, 29 | }) 30 | genSnowFlake := new(GenSnowFlake) 31 | id, err := genSnowFlake.GenSnowFlake(l.MachineId) 32 | if err != nil { 33 | logx.Errorf("UploadVideo--->GenSnowFlake err : %v", err) 34 | return "", err 35 | } 36 | // 生成useId/id.mp4 37 | key := strconv.FormatInt(l.UserId, 10) + "/" + strconv.FormatInt(int64(id), 10) 38 | // 上传视频文件 39 | _, err = c.Object.Put(ctx, key+".mp4", file, nil) 40 | if err != nil { 41 | logx.Errorf("UploadVideo--->Put err : %v", err) 42 | return "", err 43 | } 44 | 45 | // 上传成功 返回key 46 | return key, nil 47 | } 48 | -------------------------------------------------------------------------------- /common/help/token/common.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import "github.com/golang-jwt/jwt/v4" 4 | 5 | type Claims struct { 6 | UserId int64 `json:"user_id"` 7 | ExpireAt int64 `json:"expire_at"` 8 | Else jwt.MapClaims 9 | jwt.RegisteredClaims 10 | } 11 | 12 | type CurrentUserId string 13 | 14 | const ( 15 | AccessSecret = "healerrrrr" 16 | AccessExpire = 86400 17 | ) 18 | -------------------------------------------------------------------------------- /common/help/token/genToken.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "github.com/golang-jwt/jwt/v4" 5 | "time" 6 | ) 7 | 8 | type GenToken struct{} 9 | 10 | func (g *GenToken) GenToken(iat time.Time, userId int64, payloads map[string]interface{}) (string, error) { 11 | claims := Claims{ 12 | UserId: userId, 13 | ExpireAt: iat.Add(time.Second * AccessExpire).Unix(), 14 | RegisteredClaims: jwt.RegisteredClaims{ 15 | Issuer: "healer", 16 | }, 17 | } 18 | for k, v := range payloads { 19 | claims.Else[k] = v 20 | } 21 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 22 | return token.SignedString([]byte(AccessSecret)) 23 | } 24 | -------------------------------------------------------------------------------- /common/help/token/parseToken.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import "github.com/golang-jwt/jwt/v4" 4 | 5 | type ParseToken struct{} 6 | 7 | func (*ParseToken) ParseToken(tokenString string) (*Claims, error) { 8 | // 解码token 9 | tokenClaims, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { 10 | return []byte(AccessSecret), nil 11 | }) 12 | // 如果不为空,解码成功 13 | if tokenClaims != nil { 14 | // 断言转换 15 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 16 | return claims, nil 17 | } 18 | } 19 | return nil, err 20 | } 21 | -------------------------------------------------------------------------------- /common/messageTypes/message.go: -------------------------------------------------------------------------------- 1 | package messageTypes 2 | 3 | const ( 4 | ActionADD int64 = 1 5 | ActionCancel int64 = 2 6 | ActionErr int64 = -99 7 | ) 8 | 9 | // UserFavoriteOptMessage 点赞 / 取消点赞 10 | type UserFavoriteOptMessage struct { 11 | ActionType int64 `json:"action_type"` 12 | VideoId int64 `json:"video_id"` 13 | UserId int64 `json:"user_id"` 14 | } 15 | 16 | // UserFollowOptMessage 关注 / 取消关注 17 | type UserFollowOptMessage struct { 18 | ActionType int64 `json:"action_type"` 19 | FollowId int64 `json:"follow_id"` 20 | UserId int64 `json:"user_id"` 21 | } 22 | 23 | // UserCommentOptMessage 评论 / 删除评论 24 | type UserCommentOptMessage struct { 25 | VideoId int64 `json:"video_id"` 26 | CommentId int64 `json:"comment_id"` 27 | UserId int64 `json:"user_id"` 28 | ActionType int64 `json:"action_type"` 29 | CommentText string `json:"comment_text,omitempty"` 30 | CreateDate string `json:"create_date,omitempty"` 31 | } 32 | -------------------------------------------------------------------------------- /common/xerr/errCode.go: -------------------------------------------------------------------------------- 1 | package xerr 2 | 3 | // 通用错误码成功返回 4 | const ( 5 | OK int64 = 0 6 | ERR int64 = 1 7 | ) 8 | 9 | // 全局错误码 10 | const ( 11 | SERVER_COMMON_ERROR int64 = 100001 12 | REUQEST_PARAM_ERROR int64 = 100002 13 | TOKEN_EXPIRE_ERROR int64 = 100003 14 | TOKEN_GENERATE_ERROR int64 = 100004 15 | DB_ERROR int64 = 100005 16 | DB_UPDATE_AFFECTED_ZERO_ERROR int64 = 100006 17 | SECRET_ERROR int64 = 100007 18 | ) 19 | -------------------------------------------------------------------------------- /common/xerr/errMsg.go: -------------------------------------------------------------------------------- 1 | package xerr 2 | 3 | var message map[int64]string 4 | 5 | func init() { 6 | message = make(map[int64]string) 7 | message[OK] = "SUCCESS" 8 | message[REUQEST_PARAM_ERROR] = "参数错误" 9 | message[TOKEN_EXPIRE_ERROR] = "token失效,请重新登陆" 10 | message[TOKEN_GENERATE_ERROR] = "生成token失败" 11 | message[DB_ERROR] = "数据库繁忙,请稍后再试" 12 | message[SECRET_ERROR] = "密码错误" 13 | } 14 | 15 | func MapErrMsg(errcode int64) string { 16 | if msg, ok := message[errcode]; ok { 17 | return msg 18 | } else { 19 | return "服务器开小差啦,稍后再来试一试" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/xerr/errors.go: -------------------------------------------------------------------------------- 1 | package xerr 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | /** 8 | 常用通用固定错误 9 | */ 10 | 11 | type CodeError struct { 12 | errCode int64 13 | errMsg string 14 | } 15 | 16 | // GetErrCode 返回给前端的错误码 17 | func (e *CodeError) GetErrCode() int64 { 18 | return e.errCode 19 | } 20 | 21 | // GetErrMsg 返回给前端显示端错误信息 22 | func (e *CodeError) GetErrMsg() string { 23 | return e.errMsg 24 | } 25 | 26 | func (e *CodeError) Error() string { 27 | return fmt.Sprintf("ErrCode:%d,ErrMsg:%s", e.errCode, e.errMsg) 28 | } 29 | 30 | func NewErrCode(errCode int64) *CodeError { 31 | return &CodeError{errCode: errCode, errMsg: MapErrMsg(errCode)} 32 | } 33 | 34 | func NewErrMsg(errMsg string) *CodeError { 35 | return &CodeError{errCode: SERVER_COMMON_ERROR, errMsg: errMsg} 36 | } 37 | -------------------------------------------------------------------------------- /deploy/README.md: -------------------------------------------------------------------------------- 1 | 1. docker-compose.yml 部署的是Zookeeper和Kafka ```docker-compose up -d```启动 2 | 3 | 2. asynqUI 是Asynq配套的UI,可以在浏览器中查看任务的执行状态,他的镜像通过```docker pull hibiken/asynqmon```下载 4 | 5 | 3. 其他的Dockerfile都是service下的各个文件的镜像生成文件,使用时需要将对应的Dockerfile在项目根目录下进行```docker build .```构建镜像 -------------------------------------------------------------------------------- /deploy/api/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 | 8 | RUN apk update --no-cache && apk add --no-cache tzdata 9 | 10 | WORKDIR /build 11 | 12 | ADD go.mod . 13 | ADD go.sum . 14 | RUN go mod download 15 | COPY ../../service/api . 16 | COPY service/api/etc /app/etc 17 | RUN go build -ldflags="-s -w" -o /app/user service/api/user.go 18 | 19 | 20 | FROM scratch 21 | 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai 24 | ENV TZ Asia/Shanghai 25 | 26 | WORKDIR /app 27 | COPY --from=builder /app/user /app/user 28 | COPY --from=builder /app/etc /app/etc 29 | 30 | CMD ["./user", "-f", "etc/user-api.yaml"] 31 | -------------------------------------------------------------------------------- /deploy/asynqJob/client-scheduler/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 | 8 | RUN apk update --no-cache && apk add --no-cache tzdata 9 | 10 | WORKDIR /build 11 | 12 | ADD go.mod . 13 | ADD go.sum . 14 | RUN go mod download 15 | COPY ../../../service/asynqJob/client-scheduler . 16 | COPY service/asynqJob/client-scheduler/etc /app/etc 17 | RUN go build -ldflags="-s -w" -o /app/scheduler service/asynqJob/client-scheduler/scheduler.go 18 | 19 | 20 | FROM scratch 21 | 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai 24 | ENV TZ Asia/Shanghai 25 | 26 | WORKDIR /app 27 | COPY --from=builder /app/scheduler /app/scheduler 28 | COPY --from=builder /app/etc /app/etc 29 | 30 | CMD ["./scheduler", "-f", "etc/scheduler.yaml"] 31 | -------------------------------------------------------------------------------- /deploy/asynqJob/server/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 | 8 | RUN apk update --no-cache && apk add --no-cache tzdata 9 | 10 | WORKDIR /build 11 | 12 | ADD go.mod . 13 | ADD go.sum . 14 | RUN go mod download 15 | COPY ../../../service/asynqJob/server . 16 | COPY service/asynqJob/server/etc /app/etc 17 | RUN go build -ldflags="-s -w" -o /app/server service/asynqJob/server/server.go 18 | 19 | 20 | FROM scratch 21 | 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai 24 | ENV TZ Asia/Shanghai 25 | 26 | WORKDIR /app 27 | COPY --from=builder /app/server /app/server 28 | COPY --from=builder /app/etc /app/etc 29 | 30 | CMD ["./server", "-f", "etc/server.yaml"] 31 | -------------------------------------------------------------------------------- /deploy/asynqUI.sh: -------------------------------------------------------------------------------- 1 | docker run --rm --name asynqmon \ 2 | --network test-net \ 3 | -p 8080:8080 \ 4 | hibiken/asynqmon --redis-addr=localhost:6379 --redis-password=password 5 | -------------------------------------------------------------------------------- /deploy/docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3' 3 | services: 4 | zookeeper: 5 | image: 3f43f72cb283 6 | container_name: zookeeper 7 | environment: 8 | ZOOKEEPER_CLIENT_PORT: 2181 9 | ZOOKEEPER_TICK_TIME: 2000 10 | networks: 11 | - test-net 12 | 13 | broker: 14 | image: 2dd91ce2efe1 15 | container_name: kafka 16 | ports: 17 | - "29092:9092" 18 | depends_on: 19 | - zookeeper 20 | environment: 21 | KAFKA_BROKER_ID: 1 22 | KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181' 23 | KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092 24 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://172.18.85.218:29092 25 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 26 | KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 27 | KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 28 | networks: 29 | - test-net 30 | networks: 31 | test-net: 32 | external: 33 | name: test-net 34 | -------------------------------------------------------------------------------- /deploy/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 | 8 | RUN apk update --no-cache && apk add --no-cache tzdata 9 | 10 | WORKDIR /build 11 | 12 | ADD go.mod . 13 | ADD go.sum . 14 | RUN go mod download 15 | COPY ../../service/mq . 16 | COPY service/mq/etc /app/etc 17 | RUN go build -ldflags="-s -w" -o /app/mq service/mq/mq.go 18 | 19 | 20 | FROM scratch 21 | 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai 24 | ENV TZ Asia/Shanghai 25 | 26 | WORKDIR /app 27 | COPY --from=builder /app/mq /app/mq 28 | COPY --from=builder /app/etc /app/etc 29 | 30 | CMD ["./mq", "-f", "etc/mq.yaml"] 31 | -------------------------------------------------------------------------------- /deploy/rpc-user-info/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 | 8 | RUN apk update --no-cache && apk add --no-cache tzdata 9 | 10 | WORKDIR /build 11 | 12 | ADD go.mod . 13 | ADD go.sum . 14 | RUN go mod download 15 | COPY ../../service/rpc-user-info . 16 | COPY service/rpc-user-info/etc /app/etc 17 | RUN go build -ldflags="-s -w" -o /app/userInfoService service/rpc-user-info/userInfoService.go 18 | 19 | 20 | FROM scratch 21 | 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai 24 | ENV TZ Asia/Shanghai 25 | 26 | WORKDIR /app 27 | COPY --from=builder /app/userInfoService /app/userInfoService 28 | COPY --from=builder /app/etc /app/etc 29 | 30 | CMD ["./userInfoService", "-f", "etc/userInfoService.yaml"] 31 | -------------------------------------------------------------------------------- /deploy/rpc-user-operate/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 | 8 | RUN apk update --no-cache && apk add --no-cache tzdata 9 | 10 | WORKDIR /build 11 | 12 | ADD go.mod . 13 | ADD go.sum . 14 | RUN go mod download 15 | COPY ../../service/rpc-user-operate . 16 | COPY service/rpc-user-operate/etc /app/etc 17 | RUN go build -ldflags="-s -w" -o /app/userOptService service/rpc-user-operate/userOptService.go 18 | 19 | 20 | FROM scratch 21 | 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai 24 | ENV TZ Asia/Shanghai 25 | 26 | WORKDIR /app 27 | COPY --from=builder /app/userOptService /app/userOptService 28 | COPY --from=builder /app/etc /app/etc 29 | 30 | CMD ["./userOptService", "-f", "etc/userOptService.yaml"] 31 | -------------------------------------------------------------------------------- /deploy/rpc-video-service/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 | 8 | RUN apk update --no-cache && apk add --no-cache tzdata 9 | 10 | WORKDIR /build 11 | 12 | ADD go.mod . 13 | ADD go.sum . 14 | RUN go mod download 15 | COPY ../../service/rpc-video-service . 16 | COPY service/rpc-video-service/etc /app/etc 17 | RUN go build -ldflags="-s -w" -o /app/videoService service/rpc-video-service/videoService.go 18 | 19 | 20 | FROM scratch 21 | 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai 24 | ENV TZ Asia/Shanghai 25 | 26 | WORKDIR /app 27 | COPY --from=builder /app/videoService /app/videoService 28 | COPY --from=builder /app/etc /app/etc 29 | 30 | CMD ["./videoService", "-f", "etc/videoService.yaml"] 31 | -------------------------------------------------------------------------------- /service/api/api/build.sh: -------------------------------------------------------------------------------- 1 | # 通过goctl生成脚手架 --home 是你的模板(如果未对goctl的模板进行修改就不写,我这里修改了一些模板) 2 | # userOpt.api中 import了多个api文件 所以最后只需要这一个文件 3 | goctl api go -api userOpt.api -dir ../ --style=goZero --home=../../../tpl -------------------------------------------------------------------------------- /service/api/api/user.api: -------------------------------------------------------------------------------- 1 | // api语法版本 2 | syntax = "v1" 3 | 4 | info( 5 | author: "healer" 6 | date: "2022-6-7" 7 | ) 8 | 9 | // common 请求 10 | type UserReq { 11 | UserName string `form:"username"` 12 | Password string `form:"password"` 13 | } 14 | 15 | type IdWithTokenReq { 16 | UserId int64 `form:"user_id"` 17 | Token string `form:"token"` 18 | } 19 | 20 | // common 返回 21 | type Status { 22 | Code int64 `json:"status_code"` 23 | Msg string `json:"status_msg,omitempty"` 24 | } 25 | 26 | type IdWithTokenRes { 27 | UserId int64 `json:"user_id,omitempty"` 28 | Token string `json:"token,omitempty"` 29 | } 30 | 31 | // 每个成员的tag只能绑定一种 json,form,path header 32 | // 注册 33 | type ( 34 | UserRegisterReq { 35 | UserReq 36 | } 37 | UserRegisterRes { 38 | Status 39 | IdWithTokenRes 40 | } 41 | ) 42 | 43 | // 登录 44 | type ( 45 | UserLoginReq { 46 | UserReq 47 | } 48 | UserLoginRes { 49 | Status 50 | IdWithTokenRes 51 | } 52 | ) 53 | 54 | // 用户信息 55 | type User { 56 | UserId int64 `json:"id"` 57 | UserName string `json:"name"` 58 | FollowCount int64 `json:"follow_count"` 59 | FollowerCount int64 `json:"follower_count"` 60 | IsFollow bool `json:"is_follow"` 61 | } 62 | 63 | type ( 64 | UserInfoReq { 65 | IdWithTokenReq 66 | } 67 | UserInfoRes { 68 | Status 69 | User *User `json:"user,omitempty"` 70 | } 71 | ) 72 | @server ( 73 | group : user 74 | prefix : /douyin/user 75 | ) 76 | service user-api { 77 | @doc "用户注册" 78 | @handler UserRegister 79 | post /register (UserRegisterReq) returns (UserRegisterRes) 80 | @doc "用户登录" 81 | @handler UserLogin 82 | post /login (UserLoginReq) returns (UserLoginRes) 83 | } 84 | 85 | @server ( 86 | group : user 87 | prefix : /douyin/user 88 | middleware: AuthJWT 89 | ) 90 | service user-api { 91 | @doc "用户信息" 92 | @handler UserInfo 93 | get / (UserInfoReq) returns (UserInfoRes) 94 | } -------------------------------------------------------------------------------- /service/api/api/userOpt.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | import "user.api" 4 | import "video.api" 5 | 6 | type FavoriteOptReq { 7 | Token string `form:"token"` 8 | VideoId int64 `form:"video_id"` 9 | ActionType int64 `form:"action_type"` 10 | } 11 | 12 | type FavoriteOptRes { 13 | Status 14 | } 15 | 16 | type FollowOptReq { 17 | Token string `form:"token"` 18 | FollowId int64 `form:"to_user_id"` 19 | ActionType int64 `form:"action_type"` 20 | } 21 | 22 | type FollowOptRes { 23 | Status 24 | } 25 | 26 | type CommentOptReq { 27 | Token string `form:"token"` 28 | VideoId int64 `form:"video_id"` 29 | ActionType int64 `form:"action_type"` 30 | CommentText string `form:"comment_text,omitempty, optional"` 31 | CommentId int64 `form:"comment_id,omitempty, optional"` 32 | } 33 | 34 | type Comment { 35 | CommentId int64 `json:"id" copyier:"id"` 36 | User User `json:"user"` 37 | Content string `json:"content"` 38 | CreateTime string `json:"create_date"` 39 | } 40 | 41 | type CommentOptRes { 42 | Status 43 | Comment *Comment `json:"comment,omitempty"` 44 | } 45 | 46 | @server( 47 | group : userOpt 48 | prefix : /douyin 49 | middleware: AuthJWT 50 | ) 51 | 52 | service user-api { 53 | @doc "用户点赞" 54 | @handler FavoriteOpt 55 | post /favorite/action (FavoriteOptReq) returns (FavoriteOptRes) 56 | 57 | @doc "用户评论" 58 | @handler CommentOpt 59 | post /comment/action (CommentOptReq) returns (CommentOptRes) 60 | 61 | @doc "用户关注" 62 | @handler FollowOpt 63 | post /relation/action (FollowOptReq) returns (FollowOptRes) 64 | } 65 | 66 | type FavoriteListReq { 67 | IdWithTokenReq 68 | } 69 | 70 | type FavoriteListRes { 71 | Status 72 | FavoriteList []*PubVideo `json:"video_list,omitempty"` 73 | } 74 | 75 | type FollowListReq { 76 | IdWithTokenReq 77 | } 78 | 79 | type FollowListRes { 80 | Status 81 | UserFollowlist []*User `json:"user_list,omitempty"` 82 | } 83 | 84 | type FollowerListReq { 85 | IdWithTokenReq 86 | } 87 | 88 | type FollowerListRes { 89 | Status 90 | UserFollowerlist []*User `json:"user_list,omitempty"` 91 | } 92 | 93 | type CommentListReq { 94 | Token string `form:"token"` 95 | VideoId int64 `form:"video_id"` 96 | } 97 | 98 | type CommentListRes { 99 | Status 100 | CommentList []*Comment `json:"comment_list,omitempty"` 101 | } 102 | 103 | @server( 104 | group : userOpt 105 | prefix : /douyin 106 | middleware: AuthJWT 107 | ) 108 | service user-api { 109 | @doc "用户点赞列表" 110 | @handler GetFavoriteList 111 | get /favorite/list (FavoriteListReq) returns (FavoriteListRes) 112 | 113 | @doc "视频评论列表" 114 | @handler GetCommentList 115 | get /comment/list (CommentListReq) returns (CommentListRes) 116 | 117 | @doc "用户关注列表" 118 | @handler GetFollowList 119 | get /relation/follow/list (FollowListReq) returns (FollowListRes) 120 | 121 | @doc "用户粉丝列表" 122 | @handler GetFollowerList 123 | get /relation/follower/list (FollowerListReq) returns (FollowerListRes) 124 | } -------------------------------------------------------------------------------- /service/api/api/video.api: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | info( 4 | title: "video" 5 | author: "healer" 6 | ) 7 | 8 | import "user.api" 9 | 10 | type PubVideoReq { 11 | Token string `form:"token"` 12 | Title string `form:"title"` 13 | } 14 | 15 | type PubVideoRes { 16 | Status 17 | } 18 | 19 | type GetPubVideoListReq { 20 | Token string `form:"token"` 21 | UserId int64 `form:"user_id"` 22 | } 23 | 24 | type PubVideo { 25 | Id int64 `json:"id"` 26 | User User `json:"author"` 27 | PlayURL string `json:"play_url"` 28 | CoverURL string `json:"cover_url"` 29 | FavoriteCount int `json:"favorite_count"` 30 | CommentCount int `json:"comment_count"` 31 | IsFavorite bool `json:"is_favorite"` 32 | Title string `json:"title"` 33 | } 34 | 35 | type GetPubVideoListRes { 36 | Status 37 | VideoPubList []*PubVideo `json:"video_list,omitempty"` 38 | } 39 | 40 | @server( 41 | group: publish 42 | prefix: /douyin/publish 43 | middleware: AuthJWT 44 | ) 45 | service user-api { 46 | @doc "发布视频" 47 | @handler PublishVideo 48 | post /action (PubVideoReq) returns (PubVideoRes) 49 | 50 | @doc "获取发布视频列表" 51 | @handler GetPublishVideoList 52 | get /list (GetPubVideoListReq) returns (GetPubVideoListRes) 53 | } 54 | 55 | type FeedVideoListReq { 56 | Token string `form:"token,optional"` 57 | LastTime int64 `form:"last_time,optional"` 58 | } 59 | 60 | type FeedVideoListRes { 61 | Status 62 | NextTime int64 `json:"next_time,omitempty"` 63 | VideoList []*PubVideo `json:"video_list,omitempty"` 64 | } 65 | 66 | @server( 67 | group: feed 68 | prefix: /douyin/feed 69 | middleware: IsLogin 70 | ) 71 | service user-api { 72 | @doc "获取视频流" 73 | @handler FeedVideoList 74 | get / (FeedVideoListReq) returns (FeedVideoListRes) 75 | } -------------------------------------------------------------------------------- /service/api/etc/user-api.yaml: -------------------------------------------------------------------------------- 1 | Name: user-api 2 | Host: 0.0.0.0 3 | Port: 22222 4 | 5 | Mode: test 6 | 7 | Log: 8 | Encoding: plain 9 | 10 | # 腾讯云 11 | COSConf: 12 | SecretId: SecretId 13 | SecretKey: SecretKey 14 | MachineId: 1 15 | VideoBucket: https://xxxx.cos.ap-guangzhou.myqcloud.com 16 | CoverBucket: https://xxxx.cos.ap-guangzhou.myqcloud.com 17 | CommentBucket: https://xxxx.cos.ap-guangzhou.myqcloud.com 18 | 19 | # rpc 20 | UserInfoService: 21 | Etcd: 22 | Hosts: 23 | - 127.0.0.1:33333 24 | Key: userinfoservice.rpc 25 | NonBlock: true 26 | 27 | VideoService: 28 | Etcd: 29 | Hosts: 30 | - 127.0.0.1:33333 31 | Key: videoservice.rpc 32 | NonBlock: true 33 | 34 | UserOptService: 35 | Etcd: 36 | Hosts: 37 | - 127.0.0.1:33333 38 | Key: useroptservice.rpc 39 | NonBlock: true 40 | 41 | Cache: 42 | - Host: localhost:6379 43 | Pass: password 44 | 45 | # kafka 46 | UserFavoriteOptServiceConf: 47 | Brokers: 48 | - 127.0.0.1:9092 49 | Topic: UserFavoriteOptService-topic 50 | 51 | UserCommentOptServiceConf: 52 | Brokers: 53 | - 127.0.0.1:9092 54 | Topic: UserCommentOptService-topic 55 | 56 | UserFollowOptServiceConf: 57 | Brokers: 58 | - 127.0.0.1:9092 59 | Topic: UserFollowOptService-topic -------------------------------------------------------------------------------- /service/api/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/core/stores/cache" 5 | "github.com/zeromicro/go-zero/rest" 6 | "github.com/zeromicro/go-zero/zrpc" 7 | ) 8 | 9 | // KqConfig 用于发现kafka的Topic 10 | type KqConfig struct { 11 | Brokers []string 12 | Topic string 13 | } 14 | 15 | type Config struct { 16 | rest.RestConf 17 | 18 | Cache cache.CacheConf 19 | // rpc 20 | UserInfoService zrpc.RpcClientConf 21 | VideoService zrpc.RpcClientConf 22 | UserOptService zrpc.RpcClientConf 23 | // 腾讯云 24 | COSConf struct { 25 | SecretId string 26 | SecretKey string 27 | MachineId uint16 28 | VideoBucket string 29 | CoverBucket string 30 | } 31 | // kafka 32 | UserFavoriteOptServiceConf KqConfig 33 | UserCommentOptServiceConf KqConfig 34 | UserFollowOptServiceConf KqConfig 35 | } 36 | -------------------------------------------------------------------------------- /service/api/internal/handler/feed/feedVideoListHandler.go: -------------------------------------------------------------------------------- 1 | package feed 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/feed" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func FeedVideoListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.FeedVideoListReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := feed.NewFeedVideoListLogic(r.Context(), svcCtx) 21 | 22 | resp, err := l.FeedVideoList(&req) 23 | 24 | if err != nil { 25 | httpx.Error(w, err) 26 | } else { 27 | httpx.OkJson(w, resp) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /service/api/internal/handler/publish/getPublishVideoListHandler.go: -------------------------------------------------------------------------------- 1 | package publish 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/publish" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func GetPublishVideoListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.GetPubVideoListReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := publish.NewGetPublishVideoListLogic(r.Context(), svcCtx) 21 | resp, err := l.GetPublishVideoList(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/publish/publishVideoHandler.go: -------------------------------------------------------------------------------- 1 | package publish 2 | 3 | import ( 4 | "douyin/service/api/internal/logic/publish" 5 | "douyin/service/api/internal/svc" 6 | "douyin/service/api/internal/types" 7 | "github.com/zeromicro/go-zero/rest/httpx" 8 | "net/http" 9 | ) 10 | 11 | func PublishVideoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 12 | return func(w http.ResponseWriter, r *http.Request) { 13 | var req types.PubVideoReq 14 | if err := httpx.Parse(r, &req); err != nil { 15 | httpx.Error(w, err) 16 | return 17 | } 18 | 19 | l := publish.NewPublishVideoLogic(r.Context(), svcCtx, r) 20 | resp, err := l.PublishVideo(&req) 21 | if err != nil { 22 | httpx.Error(w, err) 23 | } else { 24 | httpx.OkJson(w, resp) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /service/api/internal/handler/routes.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT. 2 | package handler 3 | 4 | import ( 5 | "net/http" 6 | 7 | feed "douyin/service/api/internal/handler/feed" 8 | publish "douyin/service/api/internal/handler/publish" 9 | user "douyin/service/api/internal/handler/user" 10 | userOpt "douyin/service/api/internal/handler/userOpt" 11 | "douyin/service/api/internal/svc" 12 | 13 | "github.com/zeromicro/go-zero/rest" 14 | ) 15 | 16 | func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { 17 | server.AddRoutes( 18 | rest.WithMiddlewares( 19 | []rest.Middleware{serverCtx.AuthJWT}, 20 | []rest.Route{ 21 | { 22 | Method: http.MethodPost, 23 | Path: "/favorite/action", 24 | Handler: userOpt.FavoriteOptHandler(serverCtx), 25 | }, 26 | { 27 | Method: http.MethodPost, 28 | Path: "/comment/action", 29 | Handler: userOpt.CommentOptHandler(serverCtx), 30 | }, 31 | { 32 | Method: http.MethodPost, 33 | Path: "/relation/action", 34 | Handler: userOpt.FollowOptHandler(serverCtx), 35 | }, 36 | }..., 37 | ), 38 | rest.WithPrefix("/douyin"), 39 | ) 40 | 41 | server.AddRoutes( 42 | rest.WithMiddlewares( 43 | []rest.Middleware{serverCtx.AuthJWT}, 44 | []rest.Route{ 45 | { 46 | Method: http.MethodGet, 47 | Path: "/favorite/list", 48 | Handler: userOpt.GetFavoriteListHandler(serverCtx), 49 | }, 50 | { 51 | Method: http.MethodGet, 52 | Path: "/comment/list", 53 | Handler: userOpt.GetCommentListHandler(serverCtx), 54 | }, 55 | { 56 | Method: http.MethodGet, 57 | Path: "/relation/follow/list", 58 | Handler: userOpt.GetFollowListHandler(serverCtx), 59 | }, 60 | { 61 | Method: http.MethodGet, 62 | Path: "/relation/follower/list", 63 | Handler: userOpt.GetFollowerListHandler(serverCtx), 64 | }, 65 | }..., 66 | ), 67 | rest.WithPrefix("/douyin"), 68 | ) 69 | 70 | server.AddRoutes( 71 | []rest.Route{ 72 | { 73 | Method: http.MethodPost, 74 | Path: "/register", 75 | Handler: user.UserRegisterHandler(serverCtx), 76 | }, 77 | { 78 | Method: http.MethodPost, 79 | Path: "/login", 80 | Handler: user.UserLoginHandler(serverCtx), 81 | }, 82 | }, 83 | rest.WithPrefix("/douyin/user"), 84 | ) 85 | 86 | server.AddRoutes( 87 | rest.WithMiddlewares( 88 | []rest.Middleware{serverCtx.AuthJWT}, 89 | []rest.Route{ 90 | { 91 | Method: http.MethodGet, 92 | Path: "/", 93 | Handler: user.UserInfoHandler(serverCtx), 94 | }, 95 | }..., 96 | ), 97 | rest.WithPrefix("/douyin/user"), 98 | ) 99 | 100 | server.AddRoutes( 101 | rest.WithMiddlewares( 102 | []rest.Middleware{serverCtx.AuthJWT}, 103 | []rest.Route{ 104 | { 105 | Method: http.MethodPost, 106 | Path: "/action", 107 | Handler: publish.PublishVideoHandler(serverCtx), 108 | }, 109 | { 110 | Method: http.MethodGet, 111 | Path: "/list", 112 | Handler: publish.GetPublishVideoListHandler(serverCtx), 113 | }, 114 | }..., 115 | ), 116 | rest.WithPrefix("/douyin/publish"), 117 | ) 118 | 119 | server.AddRoutes( 120 | rest.WithMiddlewares( 121 | []rest.Middleware{serverCtx.IsLogin}, 122 | []rest.Route{ 123 | { 124 | Method: http.MethodGet, 125 | Path: "/", 126 | Handler: feed.FeedVideoListHandler(serverCtx), 127 | }, 128 | }..., 129 | ), 130 | rest.WithPrefix("/douyin/feed"), 131 | ) 132 | } 133 | -------------------------------------------------------------------------------- /service/api/internal/handler/user/userInfoHandler.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/user" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func UserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.UserInfoReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := user.NewUserInfoLogic(r.Context(), svcCtx) 21 | resp, err := l.UserInfo(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/user/userLoginHandler.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/user" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func UserLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.UserLoginReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := user.NewUserLoginLogic(r.Context(), svcCtx) 21 | resp, err := l.UserLogin(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/user/userRegisterHandler.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/user" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func UserRegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.UserRegisterReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := user.NewUserRegisterLogic(r.Context(), svcCtx) 21 | resp, err := l.UserRegister(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/userOpt/commentOptHandler.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/userOpt" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func CommentOptHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.CommentOptReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := userOpt.NewCommentOptLogic(r.Context(), svcCtx) 21 | resp, err := l.CommentOpt(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/userOpt/favoriteOptHandler.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/userOpt" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func FavoriteOptHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.FavoriteOptReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := userOpt.NewFavoriteOptLogic(r.Context(), svcCtx) 21 | resp, err := l.FavoriteOpt(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/userOpt/followOptHandler.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/userOpt" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func FollowOptHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.FollowOptReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := userOpt.NewFollowOptLogic(r.Context(), svcCtx) 21 | resp, err := l.FollowOpt(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/userOpt/getCommentListHandler.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/userOpt" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func GetCommentListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.CommentListReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := userOpt.NewGetCommentListLogic(r.Context(), svcCtx) 21 | resp, err := l.GetCommentList(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/userOpt/getFavoriteListHandler.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/userOpt" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func GetFavoriteListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.FavoriteListReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := userOpt.NewGetFavoriteListLogic(r.Context(), svcCtx) 21 | resp, err := l.GetFavoriteList(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/userOpt/getFollowListHandler.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/userOpt" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func GetFollowListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.FollowListReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := userOpt.NewGetFollowListLogic(r.Context(), svcCtx) 21 | resp, err := l.GetFollowList(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/handler/userOpt/getFollowerListHandler.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "net/http" 5 | 6 | "douyin/service/api/internal/logic/userOpt" 7 | "douyin/service/api/internal/svc" 8 | "douyin/service/api/internal/types" 9 | "github.com/zeromicro/go-zero/rest/httpx" 10 | ) 11 | 12 | func GetFollowerListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | var req types.FollowerListReq 15 | if err := httpx.Parse(r, &req); err != nil { 16 | httpx.Error(w, err) 17 | return 18 | } 19 | 20 | l := userOpt.NewGetFollowerListLogic(r.Context(), svcCtx) 21 | resp, err := l.GetFollowerList(&req) 22 | if err != nil { 23 | httpx.Error(w, err) 24 | } else { 25 | httpx.OkJson(w, resp) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /service/api/internal/logic/publish/getPublishVideoListLogic.go: -------------------------------------------------------------------------------- 1 | package publish 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/api/internal/svc" 7 | "douyin/service/api/internal/types" 8 | "douyin/service/rpc-user-info/userInfoPb" 9 | "douyin/service/rpc-video-service/videoservice" 10 | "github.com/jinzhu/copier" 11 | "github.com/zeromicro/go-zero/core/logx" 12 | ) 13 | 14 | type GetPublishVideoListLogic struct { 15 | logx.Logger 16 | ctx context.Context 17 | svcCtx *svc.ServiceContext 18 | } 19 | 20 | func NewGetPublishVideoListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPublishVideoListLogic { 21 | return &GetPublishVideoListLogic{ 22 | Logger: logx.WithContext(ctx), 23 | ctx: ctx, 24 | svcCtx: svcCtx, 25 | } 26 | } 27 | 28 | func (l *GetPublishVideoListLogic) GetPublishVideoList(req *types.GetPubVideoListReq) (resp *types.GetPubVideoListRes, err error) { 29 | // 获得本人发布列表 30 | res, err := l.svcCtx.VideoSvcRpcClient.GetVideoList(l.ctx, &videoservice.GetVideoListReq{ 31 | AuthId: req.UserId, 32 | }) 33 | if err != nil { 34 | logx.Errorf("get publish video list fail %s", err.Error()) 35 | return &types.GetPubVideoListRes{ 36 | Status: types.Status{ 37 | Code: xerr.ERR, 38 | Msg: "get publish video list failed", 39 | }, 40 | }, nil 41 | } 42 | 43 | // 如果没有视频直接返回 44 | if len(res.VideoPubList) == 0 { 45 | return &types.GetPubVideoListRes{ 46 | Status: types.Status{ 47 | Code: xerr.OK, 48 | }, 49 | }, nil 50 | } 51 | 52 | // 获得本人的信息 53 | authInfo, err := l.svcCtx.UserInfoRpcClient.Info(l.ctx, &userInfoPb.UserInfoReq{ 54 | UserId: req.UserId, 55 | }) 56 | if err != nil { 57 | return &types.GetPubVideoListRes{ 58 | Status: types.Status{ 59 | Code: xerr.ERR, 60 | Msg: "get user info failed", 61 | }, 62 | }, nil 63 | } 64 | 65 | // 整合数据 66 | // 将rpc返回的videoPubList转换为api返回的videoPubList 67 | var videos []*types.PubVideo 68 | for _, v := range res.VideoPubList { 69 | var video types.PubVideo 70 | _ = copier.Copy(&video, v) 71 | _ = copier.Copy(&video.Author, authInfo.User) 72 | 73 | videos = append(videos, &video) 74 | } 75 | 76 | return &types.GetPubVideoListRes{ 77 | Status: types.Status{ 78 | Code: xerr.OK, 79 | }, 80 | VideoPubList: videos, 81 | }, nil 82 | } 83 | -------------------------------------------------------------------------------- /service/api/internal/logic/publish/publishVideoLogic.go: -------------------------------------------------------------------------------- 1 | package publish 2 | 3 | import ( 4 | "context" 5 | "douyin/common/help/cos" 6 | myToken "douyin/common/help/token" 7 | "douyin/common/xerr" 8 | "douyin/service/api/internal/svc" 9 | "douyin/service/api/internal/types" 10 | "douyin/service/rpc-video-service/videoservice" 11 | "github.com/zeromicro/go-zero/core/logx" 12 | "net/http" 13 | ) 14 | 15 | type PublishVideoLogic struct { 16 | logx.Logger 17 | ctx context.Context 18 | svcCtx *svc.ServiceContext 19 | r *http.Request 20 | } 21 | 22 | func NewPublishVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext, r *http.Request) *PublishVideoLogic { 23 | return &PublishVideoLogic{ 24 | Logger: logx.WithContext(ctx), 25 | ctx: ctx, 26 | svcCtx: svcCtx, 27 | r: r, 28 | } 29 | } 30 | 31 | func (l *PublishVideoLogic) PublishVideo(req *types.PubVideoReq) (resp *types.PubVideoRes, err error) { 32 | // 从前端获取视频 33 | file, _, err := l.r.FormFile("data") 34 | authId := l.ctx.Value(myToken.CurrentUserId("CurrentUserId")).(int64) 35 | 36 | upLoader := cos.UploaderVideo{ 37 | UserId: authId, 38 | MachineId: l.svcCtx.Config.COSConf.MachineId, 39 | VideoBucket: l.svcCtx.Config.COSConf.VideoBucket, 40 | SecretID: l.svcCtx.Config.COSConf.SecretId, 41 | SecretKey: l.svcCtx.Config.COSConf.SecretKey, 42 | } 43 | key, err := upLoader.UploadVideo(l.ctx, file) 44 | if err != nil { 45 | logx.Errorf("upload video failed: %s", err.Error()) 46 | return &types.PubVideoRes{ 47 | Status: types.Status{ 48 | Code: xerr.ERR, 49 | Msg: "upload video failed", 50 | }, 51 | }, nil 52 | } 53 | 54 | // 将视频信息存到数据库 55 | _, err = l.svcCtx.VideoSvcRpcClient.PubVideo(l.ctx, &videoservice.PubVideoReq{ 56 | AuthId: authId, 57 | Title: req.Title, 58 | PlayURL: l.svcCtx.Config.COSConf.VideoBucket + "/" + key + ".mp4", 59 | CoverURL: l.svcCtx.Config.COSConf.CoverBucket + "/" + key + "_0.jpg", 60 | }) 61 | if err != nil { 62 | logx.Errorf("publish video failed: %s", err.Error()) 63 | return &types.PubVideoRes{ 64 | Status: types.Status{ 65 | Code: xerr.ERR, 66 | Msg: "server database err", 67 | }, 68 | }, nil 69 | } 70 | 71 | return &types.PubVideoRes{ 72 | Status: types.Status{ 73 | Code: xerr.OK, 74 | }, 75 | }, nil 76 | } 77 | -------------------------------------------------------------------------------- /service/api/internal/logic/user/userInfoLogic.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-user-info/userinfoservice" 7 | 8 | "douyin/service/api/internal/svc" 9 | "douyin/service/api/internal/types" 10 | 11 | "github.com/zeromicro/go-zero/core/logx" 12 | ) 13 | 14 | type UserInfoLogic struct { 15 | logx.Logger 16 | ctx context.Context 17 | svcCtx *svc.ServiceContext 18 | } 19 | 20 | func NewUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserInfoLogic { 21 | return &UserInfoLogic{ 22 | Logger: logx.WithContext(ctx), 23 | ctx: ctx, 24 | svcCtx: svcCtx, 25 | } 26 | } 27 | 28 | // UserInfo 登陆后调用此接口拉取个人信息 29 | func (l *UserInfoLogic) UserInfo(req *types.UserInfoReq) (resp *types.UserInfoRes, err error) { 30 | info, err := l.svcCtx.UserInfoRpcClient.Info(l.ctx, &userinfoservice.UserInfoReq{ 31 | UserId: req.UserId, 32 | }) 33 | if err != nil { 34 | logx.Errorf("get user info failed: %v", err.Error()) 35 | return &types.UserInfoRes{ 36 | Status: types.Status{ 37 | Code: xerr.ERR, 38 | Msg: "get user info failed", 39 | }, 40 | }, nil 41 | } 42 | 43 | return &types.UserInfoRes{ 44 | Status: types.Status{ 45 | Code: xerr.OK, 46 | }, 47 | User: &types.User{ 48 | UserId: info.User.UserId, 49 | UserName: info.User.UserName, 50 | FollowCount: info.User.FollowCount, 51 | FollowerCount: info.User.FollowerCount, 52 | IsFollow: false, 53 | }, 54 | }, nil 55 | } 56 | -------------------------------------------------------------------------------- /service/api/internal/logic/user/userLoginLogic.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/api/internal/svc" 7 | "douyin/service/api/internal/types" 8 | "douyin/service/rpc-user-info/userinfoservice" 9 | 10 | "github.com/zeromicro/go-zero/core/logx" 11 | ) 12 | 13 | type UserLoginLogic struct { 14 | logx.Logger 15 | ctx context.Context 16 | svcCtx *svc.ServiceContext 17 | } 18 | 19 | func NewUserLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserLoginLogic { 20 | return &UserLoginLogic{ 21 | Logger: logx.WithContext(ctx), 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | } 25 | } 26 | 27 | // UserLogin 用户登陆 28 | // 通过username获得用户密码,然后比对密码 29 | // token 先查redis 是否存在,如果存在,则直接返回,如果不存在,则生成token,并存入redis 30 | // 并返回userId,token 31 | func (l *UserLoginLogic) UserLogin(req *types.UserLoginReq) (resp *types.UserLoginRes, err error) { 32 | res, err := l.svcCtx.UserInfoRpcClient.Login(l.ctx, &userinfoservice.LoginReq{ 33 | UserName: req.UserName, 34 | Password: req.Password, 35 | }) 36 | if err != nil { 37 | logx.Errorf("login failed: %v", err.Error()) 38 | return &types.UserLoginRes{ 39 | Status: types.Status{ 40 | Code: xerr.ERR, 41 | Msg: "login failed", 42 | }, 43 | }, nil 44 | } 45 | 46 | return &types.UserLoginRes{ 47 | Status: types.Status{ 48 | Code: xerr.OK, 49 | }, 50 | IdWithTokenRes: types.IdWithTokenRes{ 51 | UserId: res.UserId, 52 | Token: res.Token, 53 | }, 54 | }, nil 55 | } 56 | -------------------------------------------------------------------------------- /service/api/internal/logic/user/userRegisterLogic.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/api/internal/svc" 7 | "douyin/service/api/internal/types" 8 | "douyin/service/rpc-user-info/userinfoservice" 9 | "github.com/zeromicro/go-zero/core/logx" 10 | ) 11 | 12 | type UserRegisterLogic struct { 13 | logx.Logger 14 | ctx context.Context 15 | svcCtx *svc.ServiceContext 16 | } 17 | 18 | func NewUserRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserRegisterLogic { 19 | return &UserRegisterLogic{ 20 | Logger: logx.WithContext(ctx), 21 | ctx: ctx, 22 | svcCtx: svcCtx, 23 | } 24 | } 25 | 26 | // UserRegister 对密码使用bcrypt加密 27 | // 生成token,并将token和userId存入redis 28 | // rpc调用Register服务保存用户信息 29 | func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterReq) (resp *types.UserRegisterRes, err error) { 30 | res, err := l.svcCtx.UserInfoRpcClient.Register(l.ctx, &userinfoservice.RegisterReq{ 31 | UserName: req.UserName, 32 | Password: req.Password, 33 | }) 34 | if err != nil { 35 | logx.Errorf("register failed: %s", err.Error()) 36 | return &types.UserRegisterRes{ 37 | Status: types.Status{ 38 | Code: xerr.SECRET_ERROR, 39 | Msg: "register failed" + err.Error(), 40 | }, 41 | }, nil 42 | } 43 | 44 | return &types.UserRegisterRes{ 45 | Status: types.Status{ 46 | Code: xerr.OK, 47 | }, 48 | IdWithTokenRes: types.IdWithTokenRes{ 49 | UserId: res.UserId, 50 | Token: res.Token, 51 | }, 52 | }, nil 53 | } 54 | -------------------------------------------------------------------------------- /service/api/internal/logic/userOpt/commentOptLogic.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "context" 5 | "douyin/common/help/cos" 6 | myToken "douyin/common/help/token" 7 | "douyin/common/messageTypes" 8 | "douyin/common/xerr" 9 | "douyin/service/api/internal/svc" 10 | "douyin/service/api/internal/types" 11 | "douyin/service/rpc-user-info/userInfoPb" 12 | "encoding/json" 13 | "github.com/jinzhu/copier" 14 | "github.com/pkg/errors" 15 | "time" 16 | 17 | "github.com/zeromicro/go-zero/core/logx" 18 | ) 19 | 20 | type CommentOptLogic struct { 21 | logx.Logger 22 | ctx context.Context 23 | svcCtx *svc.ServiceContext 24 | } 25 | 26 | func NewCommentOptLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CommentOptLogic { 27 | return &CommentOptLogic{ 28 | Logger: logx.WithContext(ctx), 29 | ctx: ctx, 30 | svcCtx: svcCtx, 31 | } 32 | } 33 | 34 | func (l *CommentOptLogic) CommentOpt(req *types.CommentOptReq) (resp *types.CommentOptRes, err error) { 35 | 36 | // 前端传入的是1,2表示评论取消评论,入口这里就将它转换成1,0表示评论取消评论 37 | msgTemp, status, err := l.getActionType(req) 38 | 39 | if msgTemp.ActionType == messageTypes.ActionErr || err != nil { 40 | logx.Errorf("CommentOptLogic CommentOpt err: %s", err.Error()) 41 | return status, nil 42 | } 43 | 44 | if msgTemp.ActionType == 1 { 45 | // 拉取发布消息的用户信息 46 | userInfo, err := l.svcCtx.UserInfoRpcClient.Info(l.ctx, &userInfoPb.UserInfoReq{ 47 | UserId: msgTemp.UserId, 48 | }) 49 | if err != nil { 50 | logx.Errorf("CommentOptLogic CommentOpt err: %s", err.Error()) 51 | return &types.CommentOptRes{ 52 | Status: types.Status{ 53 | Code: xerr.ERR, 54 | Msg: "get user info err", 55 | }, 56 | }, nil 57 | } 58 | return &types.CommentOptRes{ 59 | Status: types.Status{ 60 | Code: xerr.OK, 61 | }, 62 | Comment: &types.Comment{ 63 | CommentId: msgTemp.CommentId, 64 | User: types.User{ 65 | UserId: userInfo.User.UserId, 66 | UserName: userInfo.User.UserName, 67 | FollowCount: userInfo.User.FollowCount, 68 | FollowerCount: userInfo.User.FollowerCount, 69 | IsFollow: false, 70 | }, 71 | Content: msgTemp.CommentText, 72 | CreateTime: msgTemp.CreateDate, 73 | }, 74 | }, nil 75 | } 76 | 77 | if msgTemp.ActionType == 0 { 78 | return &types.CommentOptRes{ 79 | Status: types.Status{ 80 | Code: xerr.OK, 81 | }, 82 | }, nil 83 | } 84 | 85 | return nil, nil 86 | } 87 | 88 | func (l *CommentOptLogic) getActionType(req *types.CommentOptReq) (*messageTypes.UserCommentOptMessage, *types.CommentOptRes, error) { 89 | var msgTemp messageTypes.UserCommentOptMessage 90 | _ = copier.Copy(&msgTemp, req) 91 | 92 | switch req.ActionType { // 方便扩展 93 | case messageTypes.ActionADD: 94 | msgTemp.UserId = l.ctx.Value(myToken.CurrentUserId("CurrentUserId")).(int64) 95 | msgTemp.CreateDate = time.Now().Format("01-01") 96 | msgTemp.ActionType = 1 97 | var genId cos.GenSnowFlake 98 | id, _ := genId.GenSnowFlake(1) 99 | msgTemp.CommentId = int64(id) 100 | 101 | case messageTypes.ActionCancel: 102 | msgTemp.UserId = l.ctx.Value(myToken.CurrentUserId("CurrentUserId")).(int64) 103 | msgTemp.ActionType = 0 104 | default: 105 | msgTemp.ActionType = -99 106 | return nil, &types.CommentOptRes{ 107 | Status: types.Status{ 108 | Code: xerr.ERR, 109 | Msg: "send message to CommentOptMsgConsumer ActionType err", 110 | }, 111 | }, errors.New("operate error") 112 | } 113 | 114 | // 序列化 115 | msg, err := json.Marshal(msgTemp) 116 | if err != nil { 117 | return nil, &types.CommentOptRes{ 118 | Status: types.Status{ 119 | Code: xerr.ERR, 120 | Msg: "send message to CommentOptMsgConsumer json.Marshal err", 121 | }, 122 | }, errors.Wrapf(err, " json.Marshal err") 123 | } 124 | 125 | // 向消息队列发送消息 126 | err = l.svcCtx.CommentOptMsgProducer.Push(string(msg)) 127 | if err != nil { 128 | return nil, &types.CommentOptRes{ 129 | Status: types.Status{ 130 | Code: xerr.ERR, 131 | Msg: "send message to CommentOptMsgConsumer err", 132 | }, 133 | }, errors.Wrapf(err, " json.Marshal err") 134 | } 135 | return &msgTemp, nil, nil 136 | } 137 | -------------------------------------------------------------------------------- /service/api/internal/logic/userOpt/favoriteOptLogic.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "context" 5 | myToken "douyin/common/help/token" 6 | "douyin/common/messageTypes" 7 | "douyin/common/xerr" 8 | "douyin/service/api/internal/svc" 9 | "douyin/service/api/internal/types" 10 | "encoding/json" 11 | "github.com/jinzhu/copier" 12 | "github.com/pkg/errors" 13 | "github.com/zeromicro/go-zero/core/logx" 14 | ) 15 | 16 | type FavoriteOptLogic struct { 17 | logx.Logger 18 | ctx context.Context 19 | svcCtx *svc.ServiceContext 20 | } 21 | 22 | func NewFavoriteOptLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FavoriteOptLogic { 23 | return &FavoriteOptLogic{ 24 | Logger: logx.WithContext(ctx), 25 | ctx: ctx, 26 | svcCtx: svcCtx, 27 | } 28 | } 29 | 30 | func (l *FavoriteOptLogic) FavoriteOpt(req *types.FavoriteOptReq) (resp *types.FavoriteOptRes, err error) { 31 | 32 | var msgTemp messageTypes.UserFavoriteOptMessage 33 | _ = copier.Copy(&msgTemp, req) 34 | 35 | // 前端传入的是1,2表示点赞与取消点赞,入口这里就将它转换成1,0表示点赞与取消点赞 36 | msgTemp.ActionType = l.getActionType(req.ActionType) 37 | if msgTemp.ActionType == -99 { 38 | logx.Errorf("error actionType : %d", req.ActionType) 39 | return &types.FavoriteOptRes{ 40 | Status: types.Status{ 41 | Code: xerr.ERR, 42 | Msg: "operate error", 43 | }, 44 | }, nil 45 | } 46 | 47 | msgTemp.UserId = l.ctx.Value(myToken.CurrentUserId("CurrentUserId")).(int64) 48 | 49 | // 序列化 50 | msg, err := json.Marshal(msgTemp) 51 | if err != nil { 52 | logx.Errorf("FavoriteOpt json.Marshal err : %s", err.Error()) 53 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), " json.Marshal err") 54 | } 55 | 56 | // 向消息队列发送消息 57 | err = l.svcCtx.FavoriteOptMsgProducer.Push(string(msg)) 58 | if err != nil { 59 | logx.Errorf("FavoriteOpt msgProducer.Push err : %s", err.Error()) 60 | return &types.FavoriteOptRes{ 61 | Status: types.Status{ 62 | Code: xerr.ERR, 63 | Msg: "send message to FavoriteOptMsgConsumer err", 64 | }, 65 | }, nil 66 | } 67 | 68 | return &types.FavoriteOptRes{ 69 | Status: types.Status{ 70 | Code: xerr.OK, 71 | }, 72 | }, nil 73 | } 74 | 75 | func (l *FavoriteOptLogic) getActionType(actionType int64) int64 { 76 | 77 | switch actionType { // 方便扩展 78 | case messageTypes.ActionADD: 79 | return 1 80 | case messageTypes.ActionCancel: 81 | return 0 82 | default: 83 | return -99 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /service/api/internal/logic/userOpt/followOptLogic.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "context" 5 | myToken "douyin/common/help/token" 6 | "douyin/common/messageTypes" 7 | "douyin/common/xerr" 8 | "encoding/json" 9 | "github.com/jinzhu/copier" 10 | "github.com/pkg/errors" 11 | 12 | "douyin/service/api/internal/svc" 13 | "douyin/service/api/internal/types" 14 | 15 | "github.com/zeromicro/go-zero/core/logx" 16 | ) 17 | 18 | type FollowOptLogic struct { 19 | logx.Logger 20 | ctx context.Context 21 | svcCtx *svc.ServiceContext 22 | } 23 | 24 | func NewFollowOptLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FollowOptLogic { 25 | return &FollowOptLogic{ 26 | Logger: logx.WithContext(ctx), 27 | ctx: ctx, 28 | svcCtx: svcCtx, 29 | } 30 | } 31 | 32 | func (l *FollowOptLogic) FollowOpt(req *types.FollowOptReq) (resp *types.FollowOptRes, err error) { 33 | var msgTemp messageTypes.UserFollowOptMessage 34 | _ = copier.Copy(&msgTemp, req) 35 | 36 | msgTemp.UserId = l.ctx.Value(myToken.CurrentUserId("CurrentUserId")).(int64) 37 | 38 | // 前端传入的是1,2表示关注与取消关注,入口这里就将它转换成1,0表示点赞与取消关注 39 | msgTemp.ActionType = l.getActionType(req.ActionType) 40 | 41 | if msgTemp.ActionType == -99 { 42 | logx.Errorf("error actionType : %+v", req.ActionType) 43 | return &types.FollowOptRes{ 44 | Status: types.Status{ 45 | Code: xerr.ERR, 46 | Msg: "operate error", 47 | }, 48 | }, nil 49 | } 50 | 51 | // 序列化 52 | msg, err := json.Marshal(msgTemp) 53 | if err != nil { 54 | logx.Errorf("json.Marshal err : %s", err.Error()) 55 | return nil, errors.Wrapf(err, " json.Marshal err") 56 | } 57 | 58 | // 向消息队列发送消息 59 | err = l.svcCtx.FollowOptMsgProducer.Push(string(msg)) 60 | if err != nil { 61 | logx.Errorf("FollowOptMsgProducer.Push err : %s", err.Error()) 62 | return &types.FollowOptRes{ 63 | Status: types.Status{ 64 | Code: xerr.ERR, 65 | Msg: "send message to FollowOptMsgProducer err", 66 | }, 67 | }, nil 68 | } 69 | 70 | return &types.FollowOptRes{ 71 | Status: types.Status{ 72 | Code: xerr.OK, 73 | Msg: "operate success", 74 | }, 75 | }, nil 76 | } 77 | 78 | func (l *FollowOptLogic) getActionType(actionType int64) int64 { 79 | 80 | switch actionType { // 方便扩展 81 | case messageTypes.ActionADD: 82 | return 1 83 | case messageTypes.ActionCancel: 84 | return 0 85 | default: 86 | return -99 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /service/api/internal/logic/userOpt/getCommentListLogic.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "context" 5 | myToken "douyin/common/help/token" 6 | "douyin/common/xerr" 7 | "douyin/service/rpc-user-info/userInfoPb" 8 | "douyin/service/rpc-user-operate/useroptservice" 9 | "github.com/jinzhu/copier" 10 | "github.com/zeromicro/go-zero/core/mr" 11 | 12 | "douyin/service/api/internal/svc" 13 | "douyin/service/api/internal/types" 14 | 15 | "github.com/zeromicro/go-zero/core/logx" 16 | ) 17 | 18 | type GetCommentListLogic struct { 19 | logx.Logger 20 | ctx context.Context 21 | svcCtx *svc.ServiceContext 22 | } 23 | 24 | func NewGetCommentListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCommentListLogic { 25 | return &GetCommentListLogic{ 26 | Logger: logx.WithContext(ctx), 27 | ctx: ctx, 28 | svcCtx: svcCtx, 29 | } 30 | } 31 | 32 | func (l *GetCommentListLogic) GetCommentList(req *types.CommentListReq) (resp *types.CommentListRes, err error) { 33 | // 1.获得视频里所有的点赞内容 34 | // 2.获得点赞用的用户信息,follow情况 35 | comments, err := l.svcCtx.UserOptSvcRpcClient.GetVideoComment(l.ctx, &useroptservice.GetVideoCommentReq{ 36 | VideoId: req.VideoId, 37 | }) 38 | if err != nil { 39 | logx.Errorf("get video comment list fail %s", err.Error()) 40 | return &types.CommentListRes{ 41 | Status: types.Status{ 42 | Code: xerr.ERR, 43 | Msg: "get video comment list fail", 44 | }, 45 | }, nil 46 | } 47 | 48 | var commentList []*types.Comment // 最终返回的视频列表 49 | 50 | // 当前的用户id 51 | userId := l.ctx.Value(myToken.CurrentUserId("CurrentUserId")).(int64) 52 | if comments != nil { 53 | // 把视频里的作者的信息找出来 并去重 54 | var authIds = make([]int64, 0, len(comments.CommentList)) 55 | for _, v := range comments.CommentList { 56 | var authIdsTemp = make(map[int64]interface{}, len(comments.CommentList)) 57 | 58 | if _, ok := authIdsTemp[v.UserId]; !ok { 59 | authIdsTemp[v.UserId] = nil 60 | authIds = append(authIds, v.UserId) 61 | } 62 | } 63 | 64 | var authsInfo *userInfoPb.AuthsInfoResp 65 | var userFollowList *useroptservice.GetUserFollowResp 66 | err = mr.Finish(func() error { 67 | // 查询所有视频的的作者信息 68 | authsInfo, err = l.svcCtx.UserInfoRpcClient.AuthsInfo(l.ctx, &userInfoPb.AuthsInfoReq{ // 返回作者信息 按照作者id升序排列 69 | AuthIds: authIds, 70 | }) 71 | if err != nil { 72 | resp = &types.CommentListRes{ 73 | Status: types.Status{ 74 | Code: xerr.ERR, 75 | Msg: "get author list fail", 76 | }, 77 | } 78 | return err 79 | } 80 | return nil 81 | }, func() error { 82 | // 查找当前用户对作者的关注状态 83 | userFollowList, err = l.svcCtx.UserOptSvcRpcClient.GetUserFollow(l.ctx, &useroptservice.GetUserFollowReq{ 84 | UserId: userId, 85 | AuthIds: authIds, 86 | }) 87 | if err != nil { 88 | resp = &types.CommentListRes{ 89 | Status: types.Status{ 90 | Code: xerr.ERR, 91 | Msg: "get user follow list fail", 92 | }, 93 | } 94 | return err 95 | } 96 | return nil 97 | }) 98 | if err != nil { 99 | logx.Errorf("get user follow list fail %s", err.Error()) 100 | return resp, nil 101 | } 102 | 103 | for _, v := range comments.CommentList { 104 | var comment types.Comment 105 | _ = copier.Copy(&comment, v) 106 | _ = copier.Copy(&comment.User, authsInfo.Auths[v.UserId]) 107 | // 用户对该视频的作者是否关注 108 | comment.User.IsFollow = userFollowList.UserFollowList[v.UserId] 109 | comment.CreateTime = v.CreateDate 110 | 111 | commentList = append(commentList, &comment) 112 | } 113 | 114 | return &types.CommentListRes{ 115 | Status: types.Status{ 116 | Code: xerr.OK, 117 | }, 118 | CommentList: commentList, 119 | }, nil 120 | 121 | } else { // 没有点赞视频 122 | return &types.CommentListRes{ 123 | Status: types.Status{ 124 | Code: xerr.OK, 125 | }, 126 | }, nil 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /service/api/internal/logic/userOpt/getFavoriteListLogic.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-user-info/userInfoPb" 7 | "douyin/service/rpc-user-operate/useroptservice" 8 | "douyin/service/rpc-video-service/videoSvcPb" 9 | "github.com/jinzhu/copier" 10 | "github.com/zeromicro/go-zero/core/mr" 11 | 12 | "douyin/service/api/internal/svc" 13 | "douyin/service/api/internal/types" 14 | 15 | "github.com/zeromicro/go-zero/core/logx" 16 | ) 17 | 18 | type GetFavoriteListLogic struct { 19 | logx.Logger 20 | ctx context.Context 21 | svcCtx *svc.ServiceContext 22 | } 23 | 24 | func NewGetFavoriteListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFavoriteListLogic { 25 | return &GetFavoriteListLogic{ 26 | Logger: logx.WithContext(ctx), 27 | ctx: ctx, 28 | svcCtx: svcCtx, 29 | } 30 | } 31 | 32 | func (l *GetFavoriteListLogic) GetFavoriteList(req *types.FavoriteListReq) (resp *types.FavoriteListRes, err error) { 33 | videosId, err := l.svcCtx.UserOptSvcRpcClient.GetUserFavorite(l.ctx, &useroptservice.GetUserFavoriteReq{ 34 | UserId: req.UserId, 35 | }) 36 | if err != nil { 37 | logx.Errorf("GetFavoriteListLogic.GetFavoriteList error: %s", err.Error()) 38 | return &types.FavoriteListRes{ 39 | Status: types.Status{ 40 | Code: xerr.ERR, 41 | Msg: "get user favorite list fail ", 42 | }, 43 | }, nil 44 | } 45 | 46 | var videoList []*types.PubVideo // 最终返回的视频列表 47 | 48 | // 存在点赞的视频 49 | if videosId != nil { 50 | // 拿着这些视频id去查询视频信息 51 | videoArr, err := l.svcCtx.VideoSvcRpcClient.GetMyFavoriteVideos(l.ctx, &videoSvcPb.MyFavoriteVideosReq{ 52 | VideoIdArr: videosId.UserFavoriteArr, 53 | }) 54 | if err != nil { 55 | logx.Errorf("GetFavoriteListLogic.GetFavoriteList error: %s", err.Error()) 56 | return &types.FavoriteListRes{ 57 | Status: types.Status{ 58 | Code: xerr.ERR, 59 | Msg: "get video list fail ", 60 | }, 61 | }, nil 62 | } 63 | 64 | // 把视频里的作者的信息找出来 并去重 65 | var authIds = make([]int64, 0, len(videoArr.VideoPubList)) 66 | for _, v := range videoArr.VideoPubList { 67 | var authIdsTemp = make(map[int64]interface{}, len(videoArr.VideoPubList)) 68 | 69 | if _, ok := authIdsTemp[v.AuthId]; !ok { 70 | authIdsTemp[v.AuthId] = nil 71 | authIds = append(authIds, v.AuthId) 72 | } 73 | } 74 | 75 | var authsInfo *userInfoPb.AuthsInfoResp 76 | var userFollowList *useroptservice.GetUserFollowResp 77 | 78 | err = mr.Finish(func() error { 79 | // 查询所有视频的的作者信息 80 | authsInfo, err = l.svcCtx.UserInfoRpcClient.AuthsInfo(l.ctx, &userInfoPb.AuthsInfoReq{ // 返回作者信息 按照作者id升序排列 81 | AuthIds: authIds, 82 | }) 83 | if err != nil { 84 | resp = &types.FavoriteListRes{ 85 | Status: types.Status{ 86 | Code: xerr.ERR, 87 | Msg: "get author list fail " + err.Error(), 88 | }, 89 | } 90 | return err 91 | } 92 | return nil 93 | }, func() error { 94 | // 查找当前用户对作者的关注状态 95 | userFollowList, err = l.svcCtx.UserOptSvcRpcClient.GetUserFollow(l.ctx, &useroptservice.GetUserFollowReq{ 96 | UserId: req.UserId, 97 | AuthIds: authIds, 98 | }) 99 | if err != nil { 100 | resp = &types.FavoriteListRes{ 101 | Status: types.Status{ 102 | Code: xerr.ERR, 103 | Msg: "get user follow list fail " + err.Error(), 104 | }, 105 | } 106 | return err 107 | } 108 | return nil 109 | }) 110 | if err != nil { 111 | logx.Errorf("GetFavoriteListLogic error: %s", err.Error()) 112 | return resp, err 113 | } 114 | 115 | for _, v := range videoArr.VideoPubList { 116 | var video types.PubVideo 117 | _ = copier.Copy(&video, v) 118 | _ = copier.Copy(&video.Author, authsInfo.Auths[v.AuthId]) 119 | // 点赞列表里肯定点赞了 120 | video.IsFavorite = true 121 | // 用户对该视频的作者是否关注 122 | video.Author.IsFollow = userFollowList.UserFollowList[v.AuthId] 123 | 124 | videoList = append(videoList, &video) 125 | } 126 | 127 | return &types.FavoriteListRes{ 128 | Status: types.Status{ 129 | Code: xerr.OK, 130 | }, 131 | FavoriteList: videoList, 132 | }, nil 133 | 134 | } else { // 没有点赞视频 135 | return &types.FavoriteListRes{ 136 | Status: types.Status{ 137 | Code: xerr.OK, 138 | }, 139 | }, nil 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /service/api/internal/logic/userOpt/getFollowListLogic.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/api/internal/svc" 7 | "douyin/service/api/internal/types" 8 | "douyin/service/rpc-user-info/userInfoPb" 9 | "douyin/service/rpc-user-operate/useroptservice" 10 | "github.com/jinzhu/copier" 11 | 12 | "github.com/zeromicro/go-zero/core/logx" 13 | ) 14 | 15 | type GetFollowListLogic struct { 16 | logx.Logger 17 | ctx context.Context 18 | svcCtx *svc.ServiceContext 19 | } 20 | 21 | func NewGetFollowListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFollowListLogic { 22 | return &GetFollowListLogic{ 23 | Logger: logx.WithContext(ctx), 24 | ctx: ctx, 25 | svcCtx: svcCtx, 26 | } 27 | } 28 | 29 | func (l *GetFollowListLogic) GetFollowList(req *types.FollowListReq) (resp *types.FollowListRes, err error) { 30 | 31 | followsIdMap, err := l.svcCtx.UserOptSvcRpcClient.GetUserFollow(l.ctx, &useroptservice.GetUserFollowReq{ 32 | UserId: req.UserId, 33 | }) 34 | if err != nil { 35 | logx.Errorf("get user follow list fail %s", err.Error()) 36 | return &types.FollowListRes{ 37 | Status: types.Status{ 38 | Code: xerr.ERR, 39 | Msg: "get user follow list fail ", 40 | }, 41 | }, nil 42 | } 43 | 44 | var followsIdArr []int64 45 | for k := range followsIdMap.UserFollowList { 46 | followsIdArr = append(followsIdArr, k) 47 | } 48 | 49 | var userList []*types.User // 最终返回的关注者列表 50 | 51 | if followsIdMap != nil { 52 | 53 | followsInfo, err := l.svcCtx.UserInfoRpcClient.AuthsInfo(l.ctx, &userInfoPb.AuthsInfoReq{ 54 | AuthIds: followsIdArr, 55 | }) 56 | if err != nil { 57 | logx.Errorf("get user follow list fail %s", err.Error()) 58 | return &types.FollowListRes{ 59 | Status: types.Status{ 60 | Code: xerr.ERR, 61 | Msg: "get AuthsInfo list fail " + err.Error(), 62 | }, 63 | }, nil 64 | } 65 | 66 | for _, v := range followsInfo.Auths { 67 | var user types.User 68 | _ = copier.Copy(&user, v) 69 | user.IsFollow = true 70 | 71 | userList = append(userList, &user) 72 | } 73 | 74 | return &types.FollowListRes{ 75 | Status: types.Status{ 76 | Code: xerr.OK, 77 | }, 78 | UserFollowlist: userList, 79 | }, nil 80 | 81 | } else { // 没有关注任何人 82 | return &types.FollowListRes{ 83 | Status: types.Status{ 84 | Code: xerr.OK, 85 | }, 86 | }, nil 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /service/api/internal/logic/userOpt/getFollowerListLogic.go: -------------------------------------------------------------------------------- 1 | package userOpt 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-user-info/userInfoPb" 7 | "douyin/service/rpc-user-operate/useroptservice" 8 | "github.com/jinzhu/copier" 9 | 10 | "douyin/service/api/internal/svc" 11 | "douyin/service/api/internal/types" 12 | 13 | "github.com/zeromicro/go-zero/core/logx" 14 | ) 15 | 16 | type GetFollowerListLogic struct { 17 | logx.Logger 18 | ctx context.Context 19 | svcCtx *svc.ServiceContext 20 | } 21 | 22 | func NewGetFollowerListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFollowerListLogic { 23 | return &GetFollowerListLogic{ 24 | Logger: logx.WithContext(ctx), 25 | ctx: ctx, 26 | svcCtx: svcCtx, 27 | } 28 | } 29 | 30 | func (l *GetFollowerListLogic) GetFollowerList(req *types.FollowerListReq) (resp *types.FollowerListRes, err error) { 31 | 32 | followersIdMap, err := l.svcCtx.UserOptSvcRpcClient.GetUserFollower(l.ctx, &useroptservice.GetUserFollowerReq{ 33 | UserId: req.UserId, 34 | }) 35 | if err != nil { 36 | return &types.FollowerListRes{ 37 | Status: types.Status{ 38 | Code: xerr.ERR, 39 | Msg: "get user follower list fail ", 40 | }, 41 | }, nil 42 | } 43 | 44 | var followersIdArr []int64 45 | for k := range followersIdMap.UserFollowerList { 46 | followersIdArr = append(followersIdArr, k) 47 | } 48 | 49 | var userList []*types.User // 最终返回的关注者列表 50 | 51 | if followersIdMap != nil { 52 | // 查看我是否关注了粉丝 53 | followersInfo, err := l.svcCtx.UserInfoRpcClient.AuthsInfo(l.ctx, &userInfoPb.AuthsInfoReq{ 54 | AuthIds: followersIdArr, 55 | }) 56 | if err != nil { 57 | logx.Errorf("get AuthsInfo list fail %s", err.Error()) 58 | return &types.FollowerListRes{ 59 | Status: types.Status{ 60 | Code: xerr.ERR, 61 | Msg: "get AuthsInfo list fail", 62 | }, 63 | }, nil 64 | } 65 | 66 | allFollowersMap, err := l.svcCtx.UserOptSvcRpcClient.GetUserFollow(l.ctx, &useroptservice.GetUserFollowReq{ 67 | UserId: req.UserId, 68 | AuthIds: followersIdArr, 69 | }) 70 | if err != nil { 71 | logx.Errorf("get user follow list fail %s", err.Error()) 72 | return &types.FollowerListRes{ 73 | Status: types.Status{ 74 | Code: xerr.ERR, 75 | Msg: "get user follow list fail", 76 | }, 77 | }, nil 78 | } 79 | 80 | for _, v := range followersInfo.Auths { 81 | var user types.User 82 | _ = copier.Copy(&user, v) 83 | user.IsFollow = allFollowersMap.UserFollowList[v.UserId] 84 | 85 | userList = append(userList, &user) 86 | } 87 | 88 | return &types.FollowerListRes{ 89 | Status: types.Status{ 90 | Code: xerr.OK, 91 | }, 92 | UserFollowerlist: userList, 93 | }, nil 94 | 95 | } else { // 没有关注任何人 96 | return &types.FollowerListRes{ 97 | Status: types.Status{ 98 | Code: xerr.OK, 99 | }, 100 | }, nil 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /service/api/internal/middleware/authjwtMiddleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | myToken "douyin/common/help/token" 6 | "douyin/common/xerr" 7 | "douyin/service/api/internal/types" 8 | "encoding/json" 9 | "fmt" 10 | "net/http" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | type AuthJWTMiddleware struct { 16 | } 17 | 18 | func NewAuthJWTMiddleware() *AuthJWTMiddleware { 19 | return &AuthJWTMiddleware{} 20 | } 21 | 22 | /* 23 | 这里前端有bug 用户信息、投稿接口、发布列表应该都带有user_id 才能对比token解析出来的user_id是否一致 24 | 但是前端的投稿接口没有user_id 只有token 所以这里没有办法判断 25 | */ 26 | 27 | func (m *AuthJWTMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { 28 | return func(w http.ResponseWriter, r *http.Request) { 29 | status := new(types.Status) 30 | token := r.FormValue("token") 31 | 32 | if token == "" { 33 | status.Code = xerr.REUQEST_PARAM_ERROR 34 | status.Msg = "no token" 35 | res, _ := json.Marshal(status) 36 | _, _ = w.Write(res) 37 | return 38 | } 39 | // 解析token 判断是否有效 40 | var parseClaims myToken.ParseToken 41 | claims, err := parseClaims.ParseToken(token) 42 | if err != nil { 43 | status.Code = xerr.REUQEST_PARAM_ERROR 44 | status.Msg = "param error " + err.Error() 45 | res, _ := json.Marshal(status) 46 | _, _ = w.Write(res) 47 | return 48 | } 49 | 50 | id := r.FormValue("user_id") 51 | var userId int64 52 | if id != "" { 53 | res, err := strconv.ParseInt(id, 10, 64) 54 | if err != nil { 55 | status.Code = xerr.REUQEST_PARAM_ERROR 56 | status.Msg = fmt.Sprintf("param error user_id : %d", res) 57 | res, _ := json.Marshal(status) 58 | _, _ = w.Write(res) 59 | return 60 | } 61 | userId = res 62 | } 63 | 64 | if userId != 0 && userId != claims.UserId { 65 | status.Code = xerr.REUQEST_PARAM_ERROR 66 | status.Msg = "user_id not match" 67 | res, _ := json.Marshal(status) 68 | _, _ = w.Write(res) 69 | return 70 | } 71 | 72 | // 过期时间点 小于当前时间 表示过期 73 | if claims.ExpireAt < time.Now().Unix() { 74 | status.Code = xerr.REUQEST_PARAM_ERROR 75 | status.Msg = "please login again" 76 | res, _ := json.Marshal(status) 77 | _, _ = w.Write(res) 78 | return 79 | } 80 | r = r.Clone(context.WithValue(r.Context(), myToken.CurrentUserId("CurrentUserId"), claims.UserId)) 81 | 82 | next(w, r) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /service/api/internal/middleware/isloginMiddleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | myToken "douyin/common/help/token" 6 | "douyin/common/xerr" 7 | "douyin/service/api/internal/types" 8 | "encoding/json" 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | type IsLoginMiddleware struct { 14 | } 15 | 16 | func NewIsLoginMiddleware() *IsLoginMiddleware { 17 | return &IsLoginMiddleware{} 18 | } 19 | 20 | // Handle 没有登录也是可以调用feed接口的 21 | func (m *IsLoginMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { 22 | return func(w http.ResponseWriter, r *http.Request) { 23 | status := new(types.Status) 24 | token := r.FormValue("token") 25 | if token != "" { 26 | // 解析token 判断是否有效 27 | var parseClaims myToken.ParseToken 28 | claims, err := parseClaims.ParseToken(token) 29 | if err != nil { 30 | status.Code = xerr.REUQEST_PARAM_ERROR 31 | status.Msg = "param error " + err.Error() 32 | res, _ := json.Marshal(status) 33 | _, _ = w.Write(res) 34 | return 35 | } 36 | 37 | // 过期时间点 小于当前时间 表示过期 38 | if claims.ExpireAt < time.Now().Unix() { 39 | status.Code = xerr.REUQEST_PARAM_ERROR 40 | status.Msg = "please login again" 41 | res, _ := json.Marshal(status) 42 | _, _ = w.Write(res) 43 | return 44 | } 45 | 46 | r = r.Clone(context.WithValue(r.Context(), myToken.CurrentUserId("LoginUserId"), claims.UserId)) 47 | 48 | // 把r传递给下一个handler 49 | next(w, r) 50 | } else { 51 | next(w, r) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /service/api/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "douyin/service/api/internal/config" 5 | "douyin/service/api/internal/middleware" 6 | "douyin/service/rpc-user-info/userinfoservice" 7 | "douyin/service/rpc-user-operate/useroptservice" 8 | "douyin/service/rpc-video-service/videoservice" 9 | "github.com/zeromicro/go-queue/kq" 10 | "github.com/zeromicro/go-zero/rest" 11 | "github.com/zeromicro/go-zero/zrpc" 12 | ) 13 | 14 | type ServiceContext struct { 15 | Config config.Config 16 | 17 | UserInfoRpcClient userinfoservice.UserInfoService 18 | VideoSvcRpcClient videoservice.VideoService 19 | UserOptSvcRpcClient useroptservice.UserOptService 20 | 21 | AuthJWT rest.Middleware 22 | IsLogin rest.Middleware 23 | 24 | FavoriteOptMsgProducer *kq.Pusher 25 | CommentOptMsgProducer *kq.Pusher 26 | FollowOptMsgProducer *kq.Pusher 27 | } 28 | 29 | func NewServiceContext(c config.Config) *ServiceContext { 30 | return &ServiceContext{ 31 | Config: c, 32 | 33 | UserInfoRpcClient: userinfoservice.NewUserInfoService(zrpc.MustNewClient(c.UserInfoService)), 34 | VideoSvcRpcClient: videoservice.NewVideoService(zrpc.MustNewClient(c.VideoService)), 35 | UserOptSvcRpcClient: useroptservice.NewUserOptService(zrpc.MustNewClient(c.UserOptService)), 36 | 37 | AuthJWT: middleware.NewAuthJWTMiddleware().Handle, 38 | IsLogin: middleware.NewIsLoginMiddleware().Handle, 39 | 40 | FavoriteOptMsgProducer: kq.NewPusher(c.UserFavoriteOptServiceConf.Brokers, c.UserFavoriteOptServiceConf.Topic), 41 | CommentOptMsgProducer: kq.NewPusher(c.UserCommentOptServiceConf.Brokers, c.UserCommentOptServiceConf.Topic), 42 | FollowOptMsgProducer: kq.NewPusher(c.UserFollowOptServiceConf.Brokers, c.UserFollowOptServiceConf.Topic), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /service/api/internal/types/types.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT. 2 | package types 3 | 4 | type FavoriteOptReq struct { 5 | Token string `form:"token"` 6 | VideoId int64 `form:"video_id"` 7 | ActionType int64 `form:"action_type"` 8 | } 9 | 10 | type FavoriteOptRes struct { 11 | Status 12 | } 13 | 14 | type FollowOptReq struct { 15 | Token string `form:"token"` 16 | FollowId int64 `form:"to_user_id"` 17 | ActionType int64 `form:"action_type"` 18 | } 19 | 20 | type FollowOptRes struct { 21 | Status 22 | } 23 | 24 | type CommentOptReq struct { 25 | Token string `form:"token"` 26 | VideoId int64 `form:"video_id"` 27 | ActionType int64 `form:"action_type"` 28 | CommentText string `form:"comment_text,omitempty, optional"` 29 | CommentId int64 `form:"comment_id,omitempty, optional"` 30 | } 31 | 32 | type Comment struct { 33 | CommentId int64 `json:"id" copyier:"id"` 34 | User User `json:"user"` 35 | Content string `json:"content"` 36 | CreateTime string `json:"create_date"` 37 | } 38 | 39 | type CommentOptRes struct { 40 | Status 41 | Comment *Comment `json:"comment,omitempty"` 42 | } 43 | 44 | type FavoriteListReq struct { 45 | IdWithTokenReq 46 | } 47 | 48 | type FavoriteListRes struct { 49 | Status 50 | FavoriteList []*PubVideo `json:"video_list,omitempty"` 51 | } 52 | 53 | type FollowListReq struct { 54 | IdWithTokenReq 55 | } 56 | 57 | type FollowListRes struct { 58 | Status 59 | UserFollowlist []*User `json:"user_list,omitempty"` 60 | } 61 | 62 | type FollowerListReq struct { 63 | IdWithTokenReq 64 | } 65 | 66 | type FollowerListRes struct { 67 | Status 68 | UserFollowerlist []*User `json:"user_list,omitempty"` 69 | } 70 | 71 | type CommentListReq struct { 72 | Token string `form:"token"` 73 | VideoId int64 `form:"video_id"` 74 | } 75 | 76 | type CommentListRes struct { 77 | Status 78 | CommentList []*Comment `json:"comment_list,omitempty"` 79 | } 80 | 81 | type UserReq struct { 82 | UserName string `form:"username"` 83 | Password string `form:"password"` 84 | } 85 | 86 | type IdWithTokenReq struct { 87 | UserId int64 `form:"user_id"` 88 | Token string `form:"token"` 89 | } 90 | 91 | type Status struct { 92 | Code int64 `json:"status_code"` 93 | Msg string `json:"status_msg,omitempty"` 94 | } 95 | 96 | type IdWithTokenRes struct { 97 | UserId int64 `json:"user_id,omitempty"` 98 | Token string `json:"token,omitempty"` 99 | } 100 | 101 | type UserRegisterReq struct { 102 | UserReq 103 | } 104 | 105 | type UserRegisterRes struct { 106 | Status 107 | IdWithTokenRes 108 | } 109 | 110 | type UserLoginReq struct { 111 | UserReq 112 | } 113 | 114 | type UserLoginRes struct { 115 | Status 116 | IdWithTokenRes 117 | } 118 | 119 | type User struct { 120 | UserId int64 `json:"id"` 121 | UserName string `json:"name"` 122 | FollowCount int64 `json:"follow_count"` 123 | FollowerCount int64 `json:"follower_count"` 124 | IsFollow bool `json:"is_follow"` 125 | } 126 | 127 | type UserInfoReq struct { 128 | IdWithTokenReq 129 | } 130 | 131 | type UserInfoRes struct { 132 | Status 133 | User *User `json:"user,omitempty"` 134 | } 135 | 136 | type PubVideoReq struct { 137 | Token string `form:"token"` 138 | Title string `form:"title"` 139 | } 140 | 141 | type PubVideoRes struct { 142 | Status 143 | } 144 | 145 | type GetPubVideoListReq struct { 146 | Token string `form:"token"` 147 | UserId int64 `form:"user_id"` 148 | } 149 | 150 | type PubVideo struct { 151 | Id int64 `json:"id"` 152 | Author User `json:"author"` 153 | PlayURL string `json:"play_url"` 154 | CoverURL string `json:"cover_url"` 155 | FavoriteCount int `json:"favorite_count"` 156 | CommentCount int `json:"comment_count"` 157 | IsFavorite bool `json:"is_favorite"` 158 | Title string `json:"title"` 159 | } 160 | 161 | type GetPubVideoListRes struct { 162 | Status 163 | VideoPubList []*PubVideo `json:"video_list,omitempty"` 164 | } 165 | 166 | type FeedVideoListReq struct { 167 | Token string `form:"token,optional"` 168 | LastTime int64 `form:"last_time,optional"` 169 | } 170 | 171 | type FeedVideoListRes struct { 172 | Status 173 | NextTime int64 `json:"next_time,omitempty"` 174 | VideoList []*PubVideo `json:"video_list,omitempty"` 175 | } 176 | -------------------------------------------------------------------------------- /service/api/user.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "douyin/service/api/internal/config" 8 | "douyin/service/api/internal/handler" 9 | "douyin/service/api/internal/svc" 10 | 11 | "github.com/zeromicro/go-zero/core/conf" 12 | "github.com/zeromicro/go-zero/rest" 13 | ) 14 | 15 | var configFile = flag.String("f", "etc/user-api.yaml", "the config file") 16 | 17 | func main() { 18 | flag.Parse() 19 | 20 | var c config.Config 21 | conf.MustLoad(*configFile, &c) 22 | 23 | ctx := svc.NewServiceContext(c) 24 | server := rest.MustNewServer(c.RestConf) 25 | defer server.Stop() 26 | 27 | handler.RegisterHandlers(server, ctx) 28 | 29 | fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) 30 | server.Start() 31 | } 32 | -------------------------------------------------------------------------------- /service/asynqJob/README.md: -------------------------------------------------------------------------------- 1 | 文件夹下分为client与server 2 | 3 | client用来定义调度时间 4 | 5 | server是到了时间接受client的消息触发来执行我们写的业务的 6 | 7 | 实际业务我们应该写在server,client用来定义业务调度时间的 -------------------------------------------------------------------------------- /service/asynqJob/client-scheduler/etc/scheduler.yaml: -------------------------------------------------------------------------------- 1 | Name: client-scheduler 2 | 3 | Mode: dev 4 | 5 | Log: 6 | ServiceName: client-scheduler 7 | Encoding: plain 8 | 9 | Redis: 10 | Host: localhost:6379 11 | Type: node 12 | Pass: password 13 | -------------------------------------------------------------------------------- /service/asynqJob/client-scheduler/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/core/service" 5 | "github.com/zeromicro/go-zero/core/stores/redis" 6 | ) 7 | 8 | type Config struct { 9 | service.ServiceConf 10 | 11 | Redis redis.RedisConf 12 | } 13 | -------------------------------------------------------------------------------- /service/asynqJob/client-scheduler/internal/logic/register.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/service/asynqJob/client-scheduler/internal/svc" 6 | ) 7 | 8 | type MqueueScheduler struct { 9 | ctx context.Context 10 | svcCtx *svc.ServiceContext 11 | } 12 | 13 | func NewCronScheduler(ctx context.Context, svcCtx *svc.ServiceContext) *MqueueScheduler { 14 | return &MqueueScheduler{ 15 | ctx: ctx, 16 | svcCtx: svcCtx, 17 | } 18 | } 19 | 20 | func (l *MqueueScheduler) Register() { 21 | 22 | l.GetUserFavoriteStatusScheduler() 23 | 24 | l.GetUserFollowStatusScheduler() 25 | } 26 | -------------------------------------------------------------------------------- /service/asynqJob/client-scheduler/internal/logic/setScheduler.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "douyin/service/asynqJob/server/jobtype" 5 | "github.com/hibiken/asynq" 6 | "github.com/zeromicro/go-zero/core/logx" 7 | ) 8 | 9 | // GetUserFavoriteStatusScheduler 向Redis发送定时消息调用worker进行工作 10 | func (l *MqueueScheduler) GetUserFavoriteStatusScheduler() { 11 | 12 | task := asynq.NewTask(jobtype.ScheduleGetUserFavoriteStatus, nil) 13 | // every one minute exec 14 | entryID, err := l.svcCtx.Scheduler.Register("@every 10s", task) 15 | if err != nil { 16 | logx.WithContext(l.ctx).Errorf("!!!MqueueSchedulerErr!!! ====> 【ScheduleGetUserFavoriteStatus】 registered err:%+v , task:%+v", err, task) 17 | } 18 | logx.Infof("【ScheduleGetUserFavoriteStatus】 registered an entry: %q \n", entryID) 19 | } 20 | 21 | func (l *MqueueScheduler) GetUserFollowStatusScheduler() { 22 | 23 | task := asynq.NewTask(jobtype.ScheduleGetUserFollowStatus, nil) 24 | // every one minute exec 25 | entryID, err := l.svcCtx.Scheduler.Register("@every 10s", task) 26 | if err != nil { 27 | logx.WithContext(l.ctx).Errorf("!!!MqueueSchedulerErr!!! ====> 【ScheduleGetUserFollowStatus】 registered err:%+v , task:%+v", err, task) 28 | } 29 | logx.Infof("【ScheduleGetUserFollowStatus】 registered an entry: %q \n", entryID) 30 | } 31 | -------------------------------------------------------------------------------- /service/asynqJob/client-scheduler/internal/svc/scheduler.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "douyin/service/asynqJob/client-scheduler/internal/config" 5 | "github.com/hibiken/asynq" 6 | "github.com/zeromicro/go-zero/core/logx" 7 | "time" 8 | ) 9 | 10 | // create client-scheduler 11 | func newScheduler(c config.Config) *asynq.Scheduler { 12 | 13 | location, _ := time.LoadLocation("Asia/Shanghai") 14 | return asynq.NewScheduler( 15 | asynq.RedisClientOpt{ 16 | Addr: c.Redis.Host, 17 | Password: c.Redis.Pass, 18 | }, &asynq.SchedulerOpts{ 19 | Location: location, 20 | EnqueueErrorHandler: func(task *asynq.Task, opts []asynq.Option, err error) { 21 | logx.Infof("Scheduler EnqueueErrorHandler <<<<<<<===>>>>> err : %+v , task : %+v", err, task) 22 | }, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /service/asynqJob/client-scheduler/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "douyin/service/asynqJob/client-scheduler/internal/config" 5 | "github.com/hibiken/asynq" 6 | ) 7 | 8 | type ServiceContext struct { 9 | Config config.Config 10 | 11 | Scheduler *asynq.Scheduler 12 | } 13 | 14 | func NewServiceContext(c config.Config) *ServiceContext { 15 | return &ServiceContext{ 16 | Config: c, 17 | Scheduler: newScheduler(c), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /service/asynqJob/client-scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "douyin/service/asynqJob/client-scheduler/internal/config" 6 | "douyin/service/asynqJob/client-scheduler/internal/logic" 7 | "douyin/service/asynqJob/client-scheduler/internal/svc" 8 | "flag" 9 | "github.com/zeromicro/go-zero/core/logx" 10 | "os" 11 | 12 | "github.com/zeromicro/go-zero/core/conf" 13 | ) 14 | 15 | var configFile = flag.String("f", "etc/scheduler.yaml", "Specify the config file") 16 | 17 | func main() { 18 | flag.Parse() 19 | var c config.Config 20 | 21 | conf.MustLoad(*configFile, &c) 22 | 23 | logx.DisableStat() 24 | // log、prometheus、trace、metricsUrl. 25 | if err := c.SetUp(); err != nil { 26 | panic(err) 27 | } 28 | 29 | svcContext := svc.NewServiceContext(c) 30 | ctx := context.Background() 31 | mqueueScheduler := logic.NewCronScheduler(ctx, svcContext) 32 | mqueueScheduler.Register() 33 | 34 | if err := svcContext.Scheduler.Run(); err != nil { 35 | logx.Errorf("!!!MqueueSchedulerErr!!! run err:%+v", err) 36 | os.Exit(1) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /service/asynqJob/server/etc/server.yaml: -------------------------------------------------------------------------------- 1 | Name: asynq-server 2 | Mode: dev 3 | Log: 4 | ServiceName: asynq-server 5 | Encoding: plain 6 | 7 | Redis: 8 | Host: localhost:6379 9 | Type: node 10 | Pass: password 11 | 12 | UserOptServiceConf: 13 | Etcd: 14 | Hosts: 15 | - 127.0.0.1:33333 16 | Key: useroptservice.rpc 17 | NonBlock: true 18 | 19 | RedisCacheConf: 20 | Host: localhost:6379 21 | Pass: password -------------------------------------------------------------------------------- /service/asynqJob/server/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/core/service" 5 | "github.com/zeromicro/go-zero/core/stores/redis" 6 | "github.com/zeromicro/go-zero/zrpc" 7 | ) 8 | 9 | type Config struct { 10 | service.ServiceConf 11 | Redis redis.RedisConf 12 | 13 | RedisCacheConf redis.RedisConf 14 | 15 | UserOptServiceConf zrpc.RpcClientConf 16 | } 17 | -------------------------------------------------------------------------------- /service/asynqJob/server/internal/logic/jobs/GetUserFavoriteStatus.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import ( 4 | "context" 5 | "douyin/common/globalkey" 6 | "douyin/service/asynqJob/server/internal/svc" 7 | "douyin/service/rpc-user-operate/userOptPb" 8 | "github.com/hibiken/asynq" 9 | "github.com/zeromicro/go-zero/core/logx" 10 | "github.com/zeromicro/go-zero/core/mr" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type GetUserFavoriteStatusHandler struct { 16 | svcCtx *svc.ServiceContext 17 | } 18 | 19 | func NewGetUserFavoriteStatusHandler(svcCtx *svc.ServiceContext) *GetUserFavoriteStatusHandler { 20 | return &GetUserFavoriteStatusHandler{ 21 | svcCtx: svcCtx, 22 | } 23 | } 24 | 25 | // ProcessTask every one minute exec : if return err != nil , asynq will retry 26 | func (l *GetUserFavoriteStatusHandler) ProcessTask(ctx context.Context, _ *asynq.Task) error { 27 | 28 | vals, err := l.svcCtx.RedisCache.SmembersCtx(ctx, globalkey.FavoriteSetKey) 29 | if err != nil { 30 | logx.Errorf("RedisCache.SmembersCtx error -----> %s", err.Error()) 31 | return err 32 | } 33 | if len(vals) == 0 { 34 | logx.Infof("every 10s exec But not exist data in redis cache") 35 | return nil 36 | } 37 | 38 | // 持久化数据 39 | mr.ForEach(func(source chan<- interface{}) { 40 | for _, videoIdKey := range vals { 41 | source <- videoIdKey 42 | } 43 | }, func(item interface{}) { 44 | videoIdKey := item.(string) 45 | 46 | usersInfoTemp, err := l.svcCtx.RedisCache.EvalShaCtx(ctx, l.svcCtx.ScriptREMTag, []string{videoIdKey}) 47 | 48 | if err != nil { // 获取赞了这个视频的所有的用户Id 49 | logx.Errorf("RedisCache.SmembersCtx error -----> %s", err.Error()) 50 | return 51 | } 52 | 53 | // 切分出视频的Id 54 | _, videoIdStr, _ := strings.Cut(videoIdKey, ":") 55 | videoId, _ := strconv.ParseInt(videoIdStr, 10, 64) 56 | 57 | var usersInfo []interface{} 58 | usersInfo = usersInfoTemp.([]interface{}) 59 | 60 | mr.ForEach(func(source chan<- interface{}) { 61 | for _, userId := range usersInfo { 62 | source <- userId 63 | } 64 | }, func(item interface{}) { 65 | // 切分出用户的Id 和 点赞状态 "%d:%d" 0 是未写入数据库 第二个是user_id 第三个是操作 点赞与未点赞 66 | members := strings.Split(item.(string), ":") 67 | userid, _ := strconv.ParseInt(members[0], 10, 64) 68 | actType, _ := strconv.ParseInt(members[1], 10, 64) 69 | _, _ = l.svcCtx.UserOptSvcRpcClient.UpdateFavoriteStatus(ctx, &userOptPb.UpdateFavoriteStatusReq{ 70 | VideoId: videoId, 71 | UserId: userid, 72 | ActionType: actType, 73 | }) 74 | }) 75 | }, mr.WithWorkers(10)) 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /service/asynqJob/server/internal/logic/jobs/GetUserFollowStatus.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import ( 4 | "context" 5 | "douyin/common/globalkey" 6 | "douyin/service/asynqJob/server/internal/svc" 7 | "douyin/service/rpc-user-operate/userOptPb" 8 | "github.com/hibiken/asynq" 9 | "github.com/zeromicro/go-zero/core/logx" 10 | "github.com/zeromicro/go-zero/core/mr" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | type GetUserFollowStatusHandler struct { 16 | svcCtx *svc.ServiceContext 17 | } 18 | 19 | func NewGetUserFollowStatusHandler(svcCtx *svc.ServiceContext) *GetUserFollowStatusHandler { 20 | return &GetUserFollowStatusHandler{ 21 | svcCtx: svcCtx, 22 | } 23 | } 24 | 25 | // ProcessTask if return err != nil , asynq will retry 26 | func (l *GetUserFollowStatusHandler) ProcessTask(ctx context.Context, _ *asynq.Task) error { 27 | 28 | vals, err := l.svcCtx.RedisCache.SmembersCtx(ctx, globalkey.FollowSetKey) 29 | if err != nil { 30 | logx.Errorf("RedisCache.SmembersCtx error -----> %s", err.Error()) 31 | return err 32 | } 33 | if len(vals) == 0 { 34 | logx.Infof("every 10s exec But not exist data in redis cache") 35 | return nil 36 | } 37 | 38 | // 持久化数据 39 | mr.ForEach(func(source chan<- interface{}) { 40 | for _, followKey := range vals { 41 | source <- followKey 42 | } 43 | }, func(item interface{}) { 44 | followIdKey := item.(string) 45 | usersInfoTemp, err := l.svcCtx.RedisCache.EvalShaCtx(ctx, l.svcCtx.ScriptREMTag, []string{followIdKey}) 46 | 47 | if err != nil { // 获取赞了这个视频的所有的用户Id 48 | logx.Errorf("RedisCache.SmembersCtx error -----> %s", err.Error()) 49 | return 50 | } 51 | 52 | // 切分出视频的Id 53 | _, followIdStr, _ := strings.Cut(followIdKey, ":") 54 | followId, _ := strconv.ParseInt(followIdStr, 10, 64) 55 | 56 | var usersInfo []interface{} 57 | usersInfo = usersInfoTemp.([]interface{}) 58 | 59 | mr.ForEach(func(source chan<- interface{}) { 60 | for _, userId := range usersInfo { 61 | source <- userId 62 | } 63 | }, func(item interface{}) { 64 | // 切分出用户的Id 和 关注状态 "%d:%d" 0 是未写入数据库 第二个是user_id 第三个是操作 关注与未关注 65 | members := strings.Split(item.(string), ":") 66 | userid, _ := strconv.ParseInt(members[0], 10, 64) 67 | actType, _ := strconv.ParseInt(members[1], 10, 64) 68 | 69 | _, err = l.svcCtx.UserOptSvcRpcClient.UpdateFollowStatus(ctx, &userOptPb.UpdateFollowStatusReq{ 70 | FollowId: followId, 71 | UserId: userid, 72 | ActionType: actType, 73 | }) 74 | if err != nil { 75 | logx.Errorf("UserOptSvcRpcClient.UpdateFollowStatus error -----> %s", err.Error()) 76 | return 77 | } 78 | }) 79 | }, mr.WithWorkers(10)) 80 | 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /service/asynqJob/server/internal/logic/routes.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/service/asynqJob/server/internal/logic/jobs" 6 | "douyin/service/asynqJob/server/internal/svc" 7 | "douyin/service/asynqJob/server/jobtype" 8 | "github.com/hibiken/asynq" 9 | ) 10 | 11 | type CronJob struct { 12 | ctx context.Context 13 | svcCtx *svc.ServiceContext 14 | } 15 | 16 | func NewCronJob(ctx context.Context, svcCtx *svc.ServiceContext) *CronJob { 17 | return &CronJob{ 18 | ctx: ctx, 19 | svcCtx: svcCtx, 20 | } 21 | } 22 | 23 | // register server 24 | func (l *CronJob) Register() *asynq.ServeMux { 25 | 26 | mux := asynq.NewServeMux() 27 | 28 | // handle 29 | mux.Handle(jobtype.ScheduleGetUserFavoriteStatus, jobs.NewGetUserFavoriteStatusHandler(l.svcCtx)) 30 | mux.Handle(jobtype.ScheduleGetUserFollowStatus, jobs.NewGetUserFollowStatusHandler(l.svcCtx)) 31 | 32 | return mux 33 | } 34 | -------------------------------------------------------------------------------- /service/asynqJob/server/internal/svc/asynqServer.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "douyin/service/asynqJob/server/internal/config" 5 | "github.com/hibiken/asynq" 6 | "github.com/zeromicro/go-zero/core/logx" 7 | ) 8 | 9 | func newAsynqServer(c config.Config) *asynq.Server { 10 | 11 | return asynq.NewServer( 12 | asynq.RedisClientOpt{Addr: c.Redis.Host, Password: c.Redis.Pass}, 13 | asynq.Config{ 14 | IsFailure: func(err error) bool { 15 | logx.Infof("asynq server exec task IsFailure ======== >>>>>>>>>>> err : %s", err.Error()) 16 | return true 17 | }, 18 | Concurrency: 10, //max concurrent process server task num 19 | }, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /service/asynqJob/server/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "douyin/service/asynqJob/server/internal/config" 5 | "douyin/service/rpc-user-operate/useroptservice" 6 | "github.com/hibiken/asynq" 7 | "github.com/zeromicro/go-zero/core/stores/redis" 8 | "github.com/zeromicro/go-zero/zrpc" 9 | ) 10 | 11 | type ServiceContext struct { 12 | Config config.Config 13 | AsynqServer *asynq.Server 14 | RedisCache *redis.Redis 15 | 16 | UserOptSvcRpcClient useroptservice.UserOptService 17 | 18 | ScriptREMTag string 19 | } 20 | 21 | const scriptLoadREM = "local arr = redis.call('SMEMBERS', KEYS[1]) for i=1, #arr do redis.call('SREM', KEYS[1], arr[i] ) end return arr" 22 | 23 | func NewServiceContext(c config.Config) *ServiceContext { 24 | ServiceContext := &ServiceContext{ 25 | Config: c, 26 | AsynqServer: newAsynqServer(c), 27 | RedisCache: c.RedisCacheConf.NewRedis(), 28 | 29 | UserOptSvcRpcClient: useroptservice.NewUserOptService(zrpc.MustNewClient(c.UserOptServiceConf)), 30 | } 31 | 32 | ServiceContext.ScriptREMTag, _ = ServiceContext.RedisCache.ScriptLoad(scriptLoadREM) 33 | 34 | return ServiceContext 35 | } 36 | -------------------------------------------------------------------------------- /service/asynqJob/server/jobtype/jobtype.go: -------------------------------------------------------------------------------- 1 | package jobtype 2 | 3 | // ScheduleGetUserFavoriteStatus 结算给商家的结算记录 4 | const ScheduleGetUserFavoriteStatus = "schedule:get_user_favorite_status" 5 | 6 | const ScheduleGetUserFollowStatus = "schedule:get_user_follow_status" 7 | -------------------------------------------------------------------------------- /service/asynqJob/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "douyin/service/asynqJob/server/internal/config" 6 | "douyin/service/asynqJob/server/internal/logic" 7 | "douyin/service/asynqJob/server/internal/svc" 8 | "flag" 9 | "github.com/zeromicro/go-zero/core/logx" 10 | "os" 11 | 12 | "github.com/zeromicro/go-zero/core/conf" 13 | ) 14 | 15 | var configFile = flag.String("f", "etc/server.yaml", "Specify the config file") 16 | 17 | func main() { 18 | flag.Parse() 19 | var c config.Config 20 | 21 | conf.MustLoad(*configFile, &c) 22 | 23 | // log、prometheus、trace、metricsUrl 24 | if err := c.SetUp(); err != nil { 25 | panic(err) 26 | } 27 | 28 | //logx.DisableStat() 29 | 30 | svcContext := svc.NewServiceContext(c) 31 | ctx := context.Background() 32 | cronJob := logic.NewCronJob(ctx, svcContext) 33 | mux := cronJob.Register() 34 | 35 | if err := svcContext.AsynqServer.Run(mux); err != nil { 36 | logx.WithContext(ctx).Errorf("!!!CronJobErr!!! run err:%+v", err) 37 | os.Exit(1) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /service/mq/etc/mq.yaml: -------------------------------------------------------------------------------- 1 | Name: user-opt-mq 2 | Log: 3 | Encoding: plain 4 | Mode: dev 5 | 6 | UserOptServiceConf: 7 | Etcd: 8 | Hosts: 9 | - 127.0.0.1:33333 10 | Key: useroptservice.rpc 11 | NonBlock: true 12 | 13 | 14 | VideoService: 15 | Etcd: 16 | Hosts: 17 | - 127.0.0.1:33333 18 | Key: videoservice.rpc 19 | NonBlock: true 20 | 21 | 22 | #kq 23 | UserFavoriteOptServiceConf: 24 | Name: kq 25 | Brokers: 26 | - 127.0.0.1:9092 27 | Group: UserFavoriteOptService-group 28 | Topic: UserFavoriteOptService-topic 29 | Offset: first 30 | Consumers: 1 31 | Processors: 1 32 | 33 | UserCommentOptServiceConf: 34 | Name: UserCommentOptService 35 | Brokers: 36 | - 127.0.0.1:9092 37 | Group: UserCommentOptService-group 38 | Topic: UserCommentOptService-topic 39 | Offset: first 40 | Consumers: 1 41 | Processors: 1 42 | 43 | UserFollowOptServiceConf: 44 | Name: UserFollowOptService 45 | Brokers: 46 | - 127.0.0.1:9092 47 | Group: UserFollowOptService-group 48 | Topic: UserFollowOptService-topic 49 | Offset: first 50 | Consumers: 1 51 | Processors: 1 52 | 53 | RedisCacheConf: 54 | Host: localhost:6379 55 | Pass: password 56 | 57 | COSConf: 58 | SecretId: SecretId 59 | SecretKey: SecretKey 60 | CommentBucket: https://xxxx.cos.ap-guangzhou.myqcloud.com -------------------------------------------------------------------------------- /service/mq/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/zeromicro/go-queue/kq" 5 | "github.com/zeromicro/go-zero/core/service" 6 | "github.com/zeromicro/go-zero/core/stores/redis" 7 | "github.com/zeromicro/go-zero/zrpc" 8 | ) 9 | 10 | type Config struct { 11 | service.ServiceConf 12 | 13 | // kq : pub sub 14 | // 点赞 评论 关注 15 | UserFavoriteOptServiceConf kq.KqConf 16 | UserCommentOptServiceConf kq.KqConf 17 | UserFollowOptServiceConf kq.KqConf 18 | 19 | // 操作rpc 20 | UserOptServiceConf zrpc.RpcClientConf 21 | VideoService zrpc.RpcClientConf 22 | 23 | RedisCacheConf redis.RedisConf 24 | 25 | COSConf struct { 26 | SecretId string 27 | SecretKey string 28 | CommentBucket string 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /service/mq/internal/listen/kqMqs.go: -------------------------------------------------------------------------------- 1 | package listen 2 | 3 | import ( 4 | "context" 5 | "douyin/service/mq/internal/config" 6 | "douyin/service/mq/internal/svc" 7 | 8 | kqMq "douyin/service/mq/internal/mqs/kq" 9 | 10 | "github.com/zeromicro/go-queue/kq" 11 | "github.com/zeromicro/go-zero/core/service" 12 | ) 13 | 14 | // KqMqs pub sub use kq (kafka) 15 | func KqMqs(c config.Config, ctx context.Context, svcContext *svc.ServiceContext) []service.Service { 16 | 17 | return []service.Service{ 18 | //Listening for changes in consumption flow status 19 | kq.MustNewQueue(c.UserFavoriteOptServiceConf, kqMq.NewUserFavoriteUpdateMq(ctx, svcContext)), 20 | kq.MustNewQueue(c.UserFollowOptServiceConf, kqMq.NewUserFollowUpdateMq(ctx, svcContext)), 21 | kq.MustNewQueue(c.UserCommentOptServiceConf, kqMq.NewUserCommentUpdateMq(ctx, svcContext)), 22 | // 配置 23 | //..... 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /service/mq/internal/listen/listen.go: -------------------------------------------------------------------------------- 1 | package listen 2 | 3 | import ( 4 | "context" 5 | "douyin/service/mq/internal/config" 6 | "douyin/service/mq/internal/svc" 7 | "github.com/zeromicro/go-zero/core/logx" 8 | 9 | "github.com/zeromicro/go-zero/core/service" 10 | ) 11 | 12 | const scriptLoadSADD = "redis.call('SADD', KEYS[1], ARGV[1]) redis.call('SADD', KEYS[2], ARGV[2]) redis.call('EXPIRE', KEYS[1], 60) redis.call('EXPIRE', KEYS[2], 60)" 13 | 14 | // Mqs back to all consumers 15 | func Mqs(c config.Config) []service.Service { 16 | 17 | svcContext := svc.NewServiceContext(c) 18 | ctx := context.Background() 19 | 20 | // 加载脚本 21 | tmp, err := svcContext.RedisCache.ScriptLoadCtx(ctx, scriptLoadSADD) 22 | if err != nil { 23 | logx.Errorf("load script err:%+v", err) 24 | return nil 25 | } 26 | svcContext.ScriptADD = tmp 27 | 28 | var services []service.Service 29 | 30 | //kq :pub sub 31 | services = append(services, KqMqs(c, ctx, svcContext)...) 32 | 33 | return services 34 | } 35 | -------------------------------------------------------------------------------- /service/mq/internal/mqs/kq/userCommentUpdate.go: -------------------------------------------------------------------------------- 1 | package kq 2 | 3 | import ( 4 | "context" 5 | "douyin/common/help/cos" 6 | "douyin/common/messageTypes" 7 | "douyin/service/mq/internal/svc" 8 | "douyin/service/rpc-user-operate/model" 9 | "douyin/service/rpc-user-operate/useroptservice" 10 | "encoding/json" 11 | "github.com/jinzhu/copier" 12 | "github.com/pkg/errors" 13 | "github.com/zeromicro/go-zero/core/logx" 14 | ) 15 | 16 | /* 17 | Listening to the payment flow status change notification message queue 18 | */ 19 | type UserCommentOpt struct { 20 | ctx context.Context 21 | svcCtx *svc.ServiceContext 22 | 23 | CommentOpt cos.UpdateComment 24 | } 25 | 26 | func NewUserCommentUpdateMq(ctx context.Context, svcCtx *svc.ServiceContext) *UserCommentOpt { 27 | return &UserCommentOpt{ 28 | ctx: ctx, 29 | svcCtx: svcCtx, 30 | CommentOpt: cos.UpdateComment{}, 31 | } 32 | } 33 | 34 | func (l *UserCommentOpt) Consume(_, val string) error { 35 | var message messageTypes.UserCommentOptMessage 36 | if err := json.Unmarshal([]byte(val), &message); err != nil { 37 | logx.WithContext(l.ctx).Error("UserCommentOptMessage->Consume Unmarshal err : %v , val : %s", err, val) 38 | return err 39 | } 40 | 41 | if err := l.execService(message); err != nil { 42 | logx.WithContext(l.ctx).Error("UserCommentOptMessage->execService err : %v , val : %s , message:%+v", err, val, message) 43 | return err 44 | } 45 | 46 | return nil 47 | } 48 | 49 | // 处理逻辑 50 | func (l *UserCommentOpt) execService(message messageTypes.UserCommentOptMessage) error { 51 | 52 | actionType, err := l.getActionType(message.ActionType, message) 53 | if actionType == -99 || err != nil { 54 | return errors.Wrap(err, "UserCommentOptMessage->execService getActionType err") 55 | } 56 | 57 | // 调用rpc 更新user_comment表 58 | _, err = l.svcCtx.UserOptSvcRpcClient.UpdateCommentStatus(l.ctx, &useroptservice.UpdateCommentStatusReq{ 59 | VideoId: message.VideoId, 60 | UserId: message.UserId, 61 | CommentId: message.CommentId, 62 | ActionType: actionType, 63 | }) 64 | 65 | logx.Error("UserCommentOptMessage->execService xxxxxxxxxxx") 66 | 67 | if err != nil { 68 | logx.Errorf("UserCommentOptMessage->execService err : %v , val : %s , message:%+v", err, message) 69 | return err 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func (l *UserCommentOpt) getActionType(actionType int64, message messageTypes.UserCommentOptMessage) (int64, error) { 76 | 77 | _ = copier.Copy(&l.CommentOpt, &message) 78 | 79 | switch actionType { // 方便扩展 80 | case model.ActionADD: 81 | // 新增评论 82 | _, err := l.CommentOpt.UploadComment(l.ctx, l.svcCtx.Config.COSConf.CommentBucket, 83 | l.svcCtx.Config.COSConf.SecretId, l.svcCtx.Config.COSConf.SecretKey) 84 | 85 | if err != nil { 86 | logx.Errorf("UserCommentOptMessage->getActionType err : %v , val : %s , message:%+v", err, message) 87 | return 0, err 88 | } 89 | 90 | return model.ActionADD, nil 91 | 92 | case model.ActionCancel: 93 | // 取消评论 94 | _, err := l.CommentOpt.DeleteComment(l.ctx, l.svcCtx.Config.COSConf.CommentBucket, 95 | l.svcCtx.Config.COSConf.SecretId, l.svcCtx.Config.COSConf.SecretKey) 96 | 97 | if err != nil { 98 | logx.Errorf("UserCommentOptMessage->getActionType err : %v , val : %s , message:%+v", err, message) 99 | return 0, err 100 | } 101 | 102 | return model.ActionCancel, nil 103 | 104 | default: 105 | return -99, errors.New("UserCommentOptMessage->execService ActionType err") 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /service/mq/internal/mqs/kq/userFavoriteUpdate.go: -------------------------------------------------------------------------------- 1 | package kq 2 | 3 | import ( 4 | "context" 5 | "douyin/common/globalkey" 6 | "douyin/common/messageTypes" 7 | "douyin/service/mq/internal/svc" 8 | "douyin/service/rpc-user-operate/userOptPb" 9 | "encoding/json" 10 | "fmt" 11 | "github.com/jinzhu/copier" 12 | "github.com/zeromicro/go-zero/core/logx" 13 | "github.com/zeromicro/go-zero/core/stores/redis" 14 | ) 15 | 16 | /* 17 | Listening to the payment flow status change notification message queue 18 | */ 19 | type UserFavoriteOpt struct { 20 | ctx context.Context 21 | svcCtx *svc.ServiceContext 22 | } 23 | 24 | func NewUserFavoriteUpdateMq(ctx context.Context, svcCtx *svc.ServiceContext) *UserFavoriteOpt { 25 | return &UserFavoriteOpt{ 26 | ctx: ctx, 27 | svcCtx: svcCtx, 28 | } 29 | } 30 | 31 | func (l *UserFavoriteOpt) Consume(_, val string) error { 32 | var message messageTypes.UserFavoriteOptMessage 33 | 34 | if err := json.Unmarshal([]byte(val), &message); err != nil { 35 | logx.WithContext(l.ctx).Error("UserFavoriteOptMessage->Consume Unmarshal err : %v , val : %s", err, val) 36 | return err 37 | } 38 | 39 | if err := l.execService(message); err != nil { 40 | logx.WithContext(l.ctx).Error("UserFavoriteOptMessage->execService err : %v , val : %s , message:%+v", err, val, message) 41 | logx.Errorf("UserFavoriteOptMessage->execService err : %v , val : %s , message:%+v", err, val, message) 42 | return err 43 | } 44 | return nil 45 | } 46 | 47 | // 处理逻辑 48 | func (l *UserFavoriteOpt) execService(message messageTypes.UserFavoriteOptMessage) error { 49 | 50 | logx.Infof("UserFavoriteOptMessage message : %+v\n", message) 51 | 52 | var req userOptPb.UpdateFavoriteStatusReq 53 | _ = copier.Copy(&req, &message) 54 | 55 | // 构造redis的数据 56 | dataKey := fmt.Sprintf(globalkey.FavoriteSetValTpl, message.VideoId) 57 | favoriteSetVal := fmt.Sprintf(globalkey.FavoriteSetValTpl, message.VideoId) 58 | dataVal := fmt.Sprintf(globalkey.ExistDataValTpl, message.UserId, message.ActionType) 59 | 60 | // 消息取出来之后无非是点赞或者取消点赞 0,1,那么打到redis也是0,1 61 | _, err := l.svcCtx.RedisCache.EvalShaCtx(l.ctx, l.svcCtx.ScriptADD, []string{globalkey.FavoriteSetKey, dataKey}, []string{favoriteSetVal, dataVal}) 62 | if err != redis.Nil { 63 | logx.Errorf("script exec err : %v", err) 64 | return err 65 | } 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /service/mq/internal/mqs/kq/userFollowUpdate.go: -------------------------------------------------------------------------------- 1 | package kq 2 | 3 | import ( 4 | "context" 5 | "douyin/common/globalkey" 6 | "douyin/common/messageTypes" 7 | "douyin/service/mq/internal/svc" 8 | "douyin/service/rpc-user-operate/userOptPb" 9 | "encoding/json" 10 | "fmt" 11 | "github.com/jinzhu/copier" 12 | "github.com/zeromicro/go-zero/core/logx" 13 | "github.com/zeromicro/go-zero/core/stores/redis" 14 | ) 15 | 16 | /* 17 | Listening to the payment flow status change notification message queue 18 | */ 19 | type UserFollowOpt struct { 20 | ctx context.Context 21 | svcCtx *svc.ServiceContext 22 | } 23 | 24 | func NewUserFollowUpdateMq(ctx context.Context, svcCtx *svc.ServiceContext) *UserFollowOpt { 25 | return &UserFollowOpt{ 26 | ctx: ctx, 27 | svcCtx: svcCtx, 28 | } 29 | } 30 | 31 | func (l *UserFollowOpt) Consume(_, val string) error { 32 | var message messageTypes.UserFollowOptMessage 33 | if err := json.Unmarshal([]byte(val), &message); err != nil { 34 | logx.WithContext(l.ctx).Error("UserFollowOptMessage->Consume Unmarshal err : %v , val : %s", err, val) 35 | return err 36 | } 37 | 38 | if err := l.execService(message); err != nil { 39 | logx.WithContext(l.ctx).Error("UserFollowOptMessage->execService err : %v , val : %s , message:%+v", err, val, message) 40 | return err 41 | } 42 | 43 | return nil 44 | } 45 | 46 | // 处理逻辑 47 | func (l *UserFollowOpt) execService(message messageTypes.UserFollowOptMessage) error { 48 | 49 | logx.Infof("UserFollowOptMessage message : %+v\n", message) 50 | 51 | var req userOptPb.UpdateFollowStatusReq 52 | _ = copier.Copy(&req, &message) 53 | 54 | // 构造redis的数据 55 | dataKey := fmt.Sprintf(globalkey.FollowSetValTpl, message.FollowId) 56 | followSetVal := fmt.Sprintf(globalkey.FollowSetValTpl, message.FollowId) 57 | dataVal := fmt.Sprintf(globalkey.ExistDataValTpl, message.UserId, message.ActionType) 58 | 59 | // 消息取出来之后无非是点赞或者取消点赞 0,1,那么打到redis也是0,1 60 | _, err := l.svcCtx.RedisCache.EvalShaCtx(l.ctx, l.svcCtx.ScriptADD, []string{globalkey.FollowSetKey, dataKey}, []string{followSetVal, dataVal}) 61 | if err != redis.Nil { 62 | logx.Errorf("script exec err : %v", err) 63 | return err 64 | } 65 | 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /service/mq/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "douyin/service/mq/internal/config" 5 | "douyin/service/rpc-user-operate/useroptservice" 6 | "douyin/service/rpc-video-service/videoservice" 7 | "github.com/zeromicro/go-zero/core/stores/redis" 8 | "github.com/zeromicro/go-zero/zrpc" 9 | ) 10 | 11 | type ServiceContext struct { 12 | Config config.Config 13 | 14 | UserOptSvcRpcClient useroptservice.UserOptService 15 | VideoSvcRpcClient videoservice.VideoService 16 | 17 | RedisCache *redis.Redis 18 | ScriptADD string // 在Mqs中初始化 19 | } 20 | 21 | func NewServiceContext(c config.Config) *ServiceContext { 22 | return &ServiceContext{ 23 | Config: c, 24 | 25 | UserOptSvcRpcClient: useroptservice.NewUserOptService(zrpc.MustNewClient(c.UserOptServiceConf)), 26 | VideoSvcRpcClient: videoservice.NewVideoService(zrpc.MustNewClient(c.VideoService)), 27 | 28 | RedisCache: c.RedisCacheConf.NewRedis(), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /service/mq/mq.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "douyin/service/mq/internal/config" 5 | "douyin/service/mq/internal/listen" 6 | "flag" 7 | "fmt" 8 | 9 | "github.com/zeromicro/go-zero/core/conf" 10 | "github.com/zeromicro/go-zero/core/service" 11 | ) 12 | 13 | var configFile = flag.String("f", "etc/mq.yaml", "Specify the config file") 14 | 15 | func main() { 16 | flag.Parse() 17 | var c config.Config 18 | 19 | conf.MustLoad(*configFile, &c) 20 | 21 | fmt.Printf("%+v\n", c.UserFavoriteOptServiceConf.Brokers) 22 | 23 | // log、prometheus、trace、metricsUrl. 24 | if err := c.SetUp(); err != nil { 25 | panic(err) 26 | } 27 | 28 | serviceGroup := service.NewServiceGroup() 29 | defer serviceGroup.Stop() 30 | 31 | for _, mq := range listen.Mqs(c) { 32 | serviceGroup.Add(mq) 33 | } 34 | 35 | serviceGroup.Start() 36 | 37 | } 38 | -------------------------------------------------------------------------------- /service/rpc-user-info/etc/userInfoService.yaml: -------------------------------------------------------------------------------- 1 | Name: userinfoservice.rpc 2 | ListenOn: 0.0.0.0:9997 3 | # Mode 使用 dev可以通过 grpcui -plaintext localhost:9999 进行调试 4 | Mode: dev 5 | #注册中心 6 | Etcd: 7 | Hosts: 8 | - 127.0.0.1:33333 9 | Key: userinfoservice.rpc 10 | Log: 11 | Encoding: plain 12 | 13 | DB: 14 | DataSource: username:password@tcp(localhost:3306)/douyin2?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai 15 | 16 | CacheConf: 17 | - Host: localhost:6379 18 | Pass: password 19 | 20 | RedisCacheConf: 21 | Host: localhost:6379 22 | Pass: password 23 | -------------------------------------------------------------------------------- /service/rpc-user-info/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/core/stores/cache" 5 | "github.com/zeromicro/go-zero/core/stores/redis" 6 | "github.com/zeromicro/go-zero/zrpc" 7 | ) 8 | 9 | type Config struct { 10 | zrpc.RpcServerConf 11 | RedisCacheConf redis.RedisConf 12 | DB struct { 13 | DataSource string 14 | } 15 | CacheConf cache.CacheConf 16 | } 17 | -------------------------------------------------------------------------------- /service/rpc-user-info/internal/logic/authsInfoLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-user-info/internal/svc" 7 | "douyin/service/rpc-user-info/userInfoPb" 8 | "github.com/Masterminds/squirrel" 9 | "github.com/jinzhu/copier" 10 | "github.com/pkg/errors" 11 | "github.com/zeromicro/go-zero/core/logx" 12 | ) 13 | 14 | type AuthsInfoLogic struct { 15 | ctx context.Context 16 | svcCtx *svc.ServiceContext 17 | logx.Logger 18 | } 19 | 20 | func NewAuthsInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AuthsInfoLogic { 21 | return &AuthsInfoLogic{ 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | Logger: logx.WithContext(ctx), 25 | } 26 | } 27 | 28 | func (l *AuthsInfoLogic) AuthsInfo(in *userInfoPb.AuthsInfoReq) (*userInfoPb.AuthsInfoResp, error) { 29 | whereBuilder := l.svcCtx.UserModel.RowBuilder().Where(squirrel.Eq{"user_id": in.AuthIds}) 30 | /* 31 | FindAll FindAll 里的降序 需要是user_id 而默认是id 需要改一下源文件 32 | */ 33 | auths, err := l.svcCtx.UserModel.FindAll(l.ctx, whereBuilder, "user_id ASC") 34 | 35 | if err != nil { 36 | logx.Errorf("get auths info fail FindAll err : %v", err) 37 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "get auths info fail FindAll err : %v , authIds:%v", err, in.AuthIds) 38 | } 39 | 40 | var authsInfo map[int64]*userInfoPb.User 41 | if len(auths) > 0 { 42 | authsInfo = make(map[int64]*userInfoPb.User, len(auths)) 43 | for _, v := range auths { 44 | var authInfo userInfoPb.User 45 | _ = copier.Copy(&authInfo, v) 46 | 47 | authsInfo[v.UserId] = &authInfo 48 | } 49 | } 50 | 51 | return &userInfoPb.AuthsInfoResp{ 52 | Auths: authsInfo, 53 | }, nil 54 | } 55 | -------------------------------------------------------------------------------- /service/rpc-user-info/internal/logic/infoLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-user-info/userInfoPb" 7 | "github.com/jinzhu/copier" 8 | "github.com/pkg/errors" 9 | 10 | "douyin/service/rpc-user-info/internal/svc" 11 | "github.com/zeromicro/go-zero/core/logx" 12 | ) 13 | 14 | type InfoLogic struct { 15 | ctx context.Context 16 | svcCtx *svc.ServiceContext 17 | logx.Logger 18 | } 19 | 20 | func NewInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *InfoLogic { 21 | return &InfoLogic{ 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | Logger: logx.WithContext(ctx), 25 | } 26 | } 27 | 28 | func (l *InfoLogic) Info(in *userInfoPb.UserInfoReq) (*userInfoPb.UserInfoResp, error) { 29 | userInfo, err := l.svcCtx.UserModel.FindOne(l.ctx, in.UserId) 30 | 31 | if err != nil { 32 | logx.Errorf("get user info failed: %v", err.Error()) 33 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "req: %+v", in) 34 | } 35 | 36 | var user userInfoPb.User 37 | _ = copier.Copy(&user, userInfo) 38 | 39 | return &userInfoPb.UserInfoResp{ 40 | User: &user, 41 | }, nil 42 | } 43 | -------------------------------------------------------------------------------- /service/rpc-user-info/internal/logic/loginLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | myToken "douyin/common/help/token" 6 | "douyin/common/xerr" 7 | "douyin/service/rpc-user-info/internal/svc" 8 | "douyin/service/rpc-user-info/userInfoPb" 9 | "github.com/pkg/errors" 10 | "golang.org/x/crypto/bcrypt" 11 | "strconv" 12 | "time" 13 | 14 | "github.com/zeromicro/go-zero/core/logx" 15 | ) 16 | 17 | type LoginLogic struct { 18 | ctx context.Context 19 | svcCtx *svc.ServiceContext 20 | logx.Logger 21 | } 22 | 23 | func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic { 24 | return &LoginLogic{ 25 | ctx: ctx, 26 | svcCtx: svcCtx, 27 | Logger: logx.WithContext(ctx), 28 | } 29 | } 30 | 31 | // Login 登录 32 | // 然后通过username获得用户密码,然后比对密码 33 | // 通过userId查 redis 34 | // 如果存在,则直接返回,如果不存在,则生成token,并存入redis 35 | func (l *LoginLogic) Login(in *userInfoPb.LoginReq) (*userInfoPb.LoginResp, error) { 36 | user, err := l.svcCtx.UserModel.FindOneByUserName(l.ctx, in.UserName) 37 | if err != nil { 38 | logx.Errorf("find user failed, err: %s", err.Error()) 39 | return nil, errors.Wrap(err, "find user failed") 40 | } 41 | 42 | // 校验密码 43 | err = bcrypt.CompareHashAndPassword([]byte(user.PasswordDigest), []byte(in.Password)) 44 | if err != nil { 45 | logx.Errorf("password not match, err: %s", err.Error()) 46 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "password not match") 47 | } 48 | 49 | // 通过userId查 redis 是否有此token 50 | token, err := l.svcCtx.RedisCache.GetCtx(l.ctx, "token:"+strconv.FormatInt(user.UserId, 10)) 51 | if err != nil { 52 | logx.Errorf("get token from redis failed, err: %s", err.Error()) 53 | return nil, errors.Wrap(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "get token from redis failed") 54 | } 55 | // 如果存在,则直接返回 56 | if token != "" { 57 | return &userInfoPb.LoginResp{ 58 | UserId: user.UserId, 59 | Token: token, 60 | }, nil 61 | } 62 | 63 | //如果不存在,则生成token,并存入redis 64 | var genToken myToken.GenToken 65 | now := time.Now() 66 | token, err = genToken.GenToken(now, user.UserId, nil) 67 | _, err = l.svcCtx.RedisCache.SetnxExCtx(l.ctx, "token:"+strconv.FormatInt(user.UserId, 10), token, myToken.AccessExpire) 68 | if err != nil { 69 | logx.Errorf("set token to redis failed, err: %s", err.Error()) 70 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_GENERATE_ERROR), "set token to redis error") 71 | } 72 | 73 | return &userInfoPb.LoginResp{ 74 | UserId: user.UserId, 75 | Token: token, 76 | }, nil 77 | } 78 | -------------------------------------------------------------------------------- /service/rpc-user-info/internal/logic/registerLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/help/token" 6 | "douyin/common/xerr" 7 | "douyin/service/rpc-user-info/model" 8 | "douyin/service/rpc-user-info/userInfoPb" 9 | "github.com/pkg/errors" 10 | "golang.org/x/crypto/bcrypt" 11 | "strconv" 12 | "time" 13 | 14 | "douyin/service/rpc-user-info/internal/svc" 15 | "github.com/zeromicro/go-zero/core/logx" 16 | ) 17 | 18 | type RegisterLogic struct { 19 | ctx context.Context 20 | svcCtx *svc.ServiceContext 21 | logx.Logger 22 | } 23 | 24 | func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic { 25 | return &RegisterLogic{ 26 | ctx: ctx, 27 | svcCtx: svcCtx, 28 | Logger: logx.WithContext(ctx), 29 | } 30 | } 31 | 32 | // Register -----------------------user----------------------- 33 | func (l *RegisterLogic) Register(in *userInfoPb.RegisterReq) (*userInfoPb.RegisterResp, error) { 34 | 35 | bytes, err := bcrypt.GenerateFromPassword([]byte(in.Password), 12) 36 | if err != nil { 37 | logx.Errorf("generate password failed, err:%s", err.Error()) 38 | return nil, err 39 | } 40 | res, err := l.svcCtx.UserModel.Insert(l.ctx, nil, &model.User{ 41 | UserName: in.UserName, 42 | PasswordDigest: string(bytes), 43 | }) 44 | if err != nil { 45 | logx.Errorf("insert user failed, err: %s", err.Error()) 46 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "insert user failed, user_name: %s", in.UserName) 47 | } 48 | userId, _ := res.LastInsertId() 49 | 50 | var genToken *token.GenToken 51 | now := time.Now() 52 | tokenString, err := genToken.GenToken(now, userId, nil) 53 | if err != nil { 54 | logx.Errorf("gen token error: %s", err.Error()) 55 | return nil, errors.Wrapf(err, "genToken error") 56 | } 57 | 58 | _, err = l.svcCtx.RedisCache.SetnxExCtx(l.ctx, "token:"+strconv.FormatInt(userId, 10), tokenString, token.AccessExpire) 59 | if err != nil { 60 | logx.Errorf("set token to redis error: %s", err.Error()) 61 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_GENERATE_ERROR), "genToken error") 62 | } 63 | 64 | return &userInfoPb.RegisterResp{ 65 | UserId: userId, 66 | Token: tokenString, 67 | }, nil 68 | } 69 | -------------------------------------------------------------------------------- /service/rpc-user-info/internal/server/userInfoServiceServer.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT! 2 | // Source: UserInfoService.proto 3 | 4 | package server 5 | 6 | import ( 7 | "context" 8 | 9 | "douyin/service/rpc-user-info/internal/logic" 10 | "douyin/service/rpc-user-info/internal/svc" 11 | "douyin/service/rpc-user-info/userInfoPb" 12 | ) 13 | 14 | type UserInfoServiceServer struct { 15 | svcCtx *svc.ServiceContext 16 | userInfoPb.UnimplementedUserInfoServiceServer 17 | } 18 | 19 | func NewUserInfoServiceServer(svcCtx *svc.ServiceContext) *UserInfoServiceServer { 20 | return &UserInfoServiceServer{ 21 | svcCtx: svcCtx, 22 | } 23 | } 24 | 25 | // -----------------------user----------------------- 26 | func (s *UserInfoServiceServer) Register(ctx context.Context, in *userInfoPb.RegisterReq) (*userInfoPb.RegisterResp, error) { 27 | l := logic.NewRegisterLogic(ctx, s.svcCtx) 28 | return l.Register(in) 29 | } 30 | 31 | func (s *UserInfoServiceServer) Login(ctx context.Context, in *userInfoPb.LoginReq) (*userInfoPb.LoginResp, error) { 32 | l := logic.NewLoginLogic(ctx, s.svcCtx) 33 | return l.Login(in) 34 | } 35 | 36 | func (s *UserInfoServiceServer) Info(ctx context.Context, in *userInfoPb.UserInfoReq) (*userInfoPb.UserInfoResp, error) { 37 | l := logic.NewInfoLogic(ctx, s.svcCtx) 38 | return l.Info(in) 39 | } 40 | 41 | func (s *UserInfoServiceServer) AuthsInfo(ctx context.Context, in *userInfoPb.AuthsInfoReq) (*userInfoPb.AuthsInfoResp, error) { 42 | l := logic.NewAuthsInfoLogic(ctx, s.svcCtx) 43 | return l.AuthsInfo(in) 44 | } 45 | -------------------------------------------------------------------------------- /service/rpc-user-info/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "douyin/service/rpc-user-info/internal/config" 5 | "douyin/service/rpc-user-info/model" 6 | "github.com/zeromicro/go-zero/core/stores/redis" 7 | "github.com/zeromicro/go-zero/core/stores/sqlx" 8 | ) 9 | 10 | type ServiceContext struct { 11 | Config config.Config 12 | UserModel model.UserModel 13 | RedisCache *redis.Redis 14 | } 15 | 16 | func NewServiceContext(c config.Config) *ServiceContext { 17 | return &ServiceContext{ 18 | Config: c, 19 | RedisCache: c.RedisCacheConf.NewRedis(), 20 | UserModel: model.NewUserModel(sqlx.NewMysql(c.DB.DataSource), c.CacheConf), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /service/rpc-user-info/model/vars.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/zeromicro/go-zero/core/stores/sqlx" 4 | 5 | var ErrNotFound = sqlx.ErrNotFound 6 | -------------------------------------------------------------------------------- /service/rpc-user-info/userInfoPb/GenPb/UserInfoService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package ="./userInfoPb"; 4 | 5 | package userInfoPb; 6 | 7 | // ------------------------------------ 8 | // Messages 9 | // ------------------------------------ 10 | message RegisterReq { 11 | string userName = 1; //name 12 | string password = 2; //password 13 | } 14 | message RegisterResp { 15 | int64 userId = 1; //userId 16 | string token = 2; //token 17 | } 18 | 19 | message LoginReq { 20 | string userName = 1; //userName 21 | string password = 2; //password 22 | } 23 | message LoginResp { 24 | int64 user_id = 1; 25 | string token = 2; //token 26 | } 27 | 28 | message User { 29 | int64 userId = 1; //userId 30 | string userName = 2; //name 31 | int64 followCount = 3; //followCount 32 | int64 followerCount = 4; //followerCount 33 | } 34 | 35 | message UserInfoReq { 36 | int64 userId = 1; //userId 37 | } 38 | 39 | message UserInfoResp { 40 | User user = 1; 41 | } 42 | 43 | message AuthsInfoReq { 44 | repeated int64 authIds = 1; //authIds 45 | optional int64 curUserId = 2; //curUserId 46 | } 47 | 48 | message AuthsInfoResp { 49 | map auths = 1; //auths 50 | } 51 | 52 | service UserInfoService{ 53 | //-----------------------user----------------------- 54 | rpc Register(RegisterReq) returns (RegisterResp); 55 | rpc Login(LoginReq) returns (LoginResp); 56 | rpc Info(UserInfoReq) returns (UserInfoResp); 57 | rpc AuthsInfo(AuthsInfoReq) returns (AuthsInfoResp); 58 | } 59 | -------------------------------------------------------------------------------- /service/rpc-user-info/userInfoPb/GenPb/build.sh: -------------------------------------------------------------------------------- 1 | goctl rpc protoc UserInfoService.proto --go_out=../../ --go-grpc_out=../../ --zrpc_out=../../ --style=goZero --home=../../../../tpl 2 | # 当有多个proto文件时 需要先生成其他的文件 再生成主要的pb文件 -------------------------------------------------------------------------------- /service/rpc-user-info/userInfoService.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "douyin/service/rpc-user-info/userInfoPb" 5 | "flag" 6 | "fmt" 7 | 8 | "douyin/service/rpc-user-info/internal/config" 9 | "douyin/service/rpc-user-info/internal/server" 10 | "douyin/service/rpc-user-info/internal/svc" 11 | "github.com/zeromicro/go-zero/core/conf" 12 | "github.com/zeromicro/go-zero/core/service" 13 | "github.com/zeromicro/go-zero/zrpc" 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/reflection" 16 | ) 17 | 18 | var configFile = flag.String("f", "etc/userInfoService.yaml", "the config file") 19 | 20 | func main() { 21 | flag.Parse() 22 | 23 | var c config.Config 24 | conf.MustLoad(*configFile, &c) 25 | ctx := svc.NewServiceContext(c) 26 | svr := server.NewUserInfoServiceServer(ctx) 27 | 28 | s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { 29 | userInfoPb.RegisterUserInfoServiceServer(grpcServer, svr) 30 | 31 | if c.Mode == service.DevMode || c.Mode == service.TestMode { 32 | reflection.Register(grpcServer) 33 | } 34 | }) 35 | defer s.Stop() 36 | 37 | fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) 38 | s.Start() 39 | } 40 | -------------------------------------------------------------------------------- /service/rpc-user-info/userinfoservice/userInfoService.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT! 2 | // Source: UserInfoService.proto 3 | 4 | package userinfoservice 5 | 6 | import ( 7 | "context" 8 | 9 | "douyin/service/rpc-user-info/userInfoPb" 10 | 11 | "github.com/zeromicro/go-zero/zrpc" 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | type ( 16 | AuthsInfoReq = userInfoPb.AuthsInfoReq 17 | AuthsInfoResp = userInfoPb.AuthsInfoResp 18 | LoginReq = userInfoPb.LoginReq 19 | LoginResp = userInfoPb.LoginResp 20 | RegisterReq = userInfoPb.RegisterReq 21 | RegisterResp = userInfoPb.RegisterResp 22 | User = userInfoPb.User 23 | UserInfoReq = userInfoPb.UserInfoReq 24 | UserInfoResp = userInfoPb.UserInfoResp 25 | 26 | UserInfoService interface { 27 | // -----------------------user----------------------- 28 | Register(ctx context.Context, in *RegisterReq, opts ...grpc.CallOption) (*RegisterResp, error) 29 | Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginResp, error) 30 | Info(ctx context.Context, in *UserInfoReq, opts ...grpc.CallOption) (*UserInfoResp, error) 31 | AuthsInfo(ctx context.Context, in *AuthsInfoReq, opts ...grpc.CallOption) (*AuthsInfoResp, error) 32 | } 33 | 34 | defaultUserInfoService struct { 35 | cli zrpc.Client 36 | } 37 | ) 38 | 39 | func NewUserInfoService(cli zrpc.Client) UserInfoService { 40 | return &defaultUserInfoService{ 41 | cli: cli, 42 | } 43 | } 44 | 45 | // -----------------------user----------------------- 46 | func (m *defaultUserInfoService) Register(ctx context.Context, in *RegisterReq, opts ...grpc.CallOption) (*RegisterResp, error) { 47 | client := userInfoPb.NewUserInfoServiceClient(m.cli.Conn()) 48 | return client.Register(ctx, in, opts...) 49 | } 50 | 51 | func (m *defaultUserInfoService) Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginResp, error) { 52 | client := userInfoPb.NewUserInfoServiceClient(m.cli.Conn()) 53 | return client.Login(ctx, in, opts...) 54 | } 55 | 56 | func (m *defaultUserInfoService) Info(ctx context.Context, in *UserInfoReq, opts ...grpc.CallOption) (*UserInfoResp, error) { 57 | client := userInfoPb.NewUserInfoServiceClient(m.cli.Conn()) 58 | return client.Info(ctx, in, opts...) 59 | } 60 | 61 | func (m *defaultUserInfoService) AuthsInfo(ctx context.Context, in *AuthsInfoReq, opts ...grpc.CallOption) (*AuthsInfoResp, error) { 62 | client := userInfoPb.NewUserInfoServiceClient(m.cli.Conn()) 63 | return client.AuthsInfo(ctx, in, opts...) 64 | } 65 | -------------------------------------------------------------------------------- /service/rpc-user-operate/etc/userOptService.yaml: -------------------------------------------------------------------------------- 1 | Name: useroptservice.rpc 2 | ListenOn: 0.0.0.0:9998 3 | 4 | Mode: dev 5 | 6 | Etcd: 7 | Hosts: 8 | - 127.0.0.1:33333 9 | Key: useroptservice.rpc 10 | 11 | Log: 12 | Encoding: plain 13 | DB: 14 | DataSource: username:password@tcp(localhost:3306)/douyin2?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai 15 | 16 | CacheConf: 17 | - Host: localhost:6379 18 | Pass: password 19 | 20 | COSConf: 21 | SecretId: SecretId 22 | SecretKey: SecretKey 23 | CommentBucket: https://xxxx.cos.ap-guangzhou.myqcloud.com -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/core/stores/cache" 5 | "github.com/zeromicro/go-zero/zrpc" 6 | ) 7 | 8 | type Config struct { 9 | zrpc.RpcServerConf 10 | DB struct { 11 | DataSource string 12 | } 13 | CacheConf cache.CacheConf 14 | 15 | COSConf struct { 16 | SecretId string 17 | SecretKey string 18 | CommentBucket string 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/logic/getUserFavoriteLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-user-operate/internal/svc" 7 | "douyin/service/rpc-user-operate/userOptPb" 8 | "github.com/Masterminds/squirrel" 9 | "github.com/pkg/errors" 10 | 11 | "github.com/zeromicro/go-zero/core/logx" 12 | ) 13 | 14 | type GetUserFavoriteLogic struct { 15 | ctx context.Context 16 | svcCtx *svc.ServiceContext 17 | logx.Logger 18 | } 19 | 20 | func NewGetUserFavoriteLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserFavoriteLogic { 21 | return &GetUserFavoriteLogic{ 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | Logger: logx.WithContext(ctx), 25 | } 26 | } 27 | 28 | // GetUserFavorite 获取用户对视频的点赞 29 | func (l *GetUserFavoriteLogic) GetUserFavorite(in *userOptPb.GetUserFavoriteReq) (*userOptPb.GetUserFavoriteResp, error) { 30 | 31 | if in.VideoIds != nil { // 给 feed 接口使用 查看用户对以下videoids的点赞状态 32 | whereBuilder := l.svcCtx.UserFavoriteModel.RowBuilder().Where(squirrel.Eq{"user_id": in.UserId, "video_id": in.VideoIds}).Where(squirrel.NotEq{"is_favorite": 0}) 33 | 34 | res, err := l.svcCtx.UserFavoriteModel.FindAll(l.ctx, whereBuilder, "") 35 | if err != nil { 36 | logx.Errorf("GetUserFavorite err , id:%d , err:%s", in.UserId, err.Error()) 37 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "GetUserFavorite err") 38 | } 39 | 40 | // 该用户对视频的点赞映射 41 | var respUserFavoriteList map[int64]bool 42 | if len(res) > 0 { 43 | respUserFavoriteList = make(map[int64]bool, len(res)) 44 | for _, v := range res { 45 | respUserFavoriteList[v.VideoId] = true 46 | } 47 | } 48 | 49 | return &userOptPb.GetUserFavoriteResp{ 50 | UserFavoriteList: respUserFavoriteList, 51 | }, nil 52 | 53 | } else { // 给拉取点赞列表使用 查询的是用户对哪些视频进行了点赞 54 | whereBuilder := l.svcCtx.UserFavoriteModel.RowBuilder().Where("user_id = ? and is_favorite != 0 ", in.UserId) 55 | 56 | res, err := l.svcCtx.UserFavoriteModel.FindAll(l.ctx, whereBuilder, "user_id ASC") 57 | if err != nil { 58 | logx.Errorf("GetUserFavorite err , id:%d , err:%s", in.UserId, err.Error()) 59 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "GetUserFavorite err") 60 | } 61 | 62 | /* 63 | 这里用数组是因为拉取点赞列表的接口返回的是数组 可以直接返回 比较方便 64 | */ 65 | var respUserFavoriteArr []int64 66 | if len(res) > 0 { 67 | respUserFavoriteArr = make([]int64, len(res)) 68 | for _, v := range res { 69 | respUserFavoriteArr = append(respUserFavoriteArr, v.VideoId) 70 | } 71 | } 72 | return &userOptPb.GetUserFavoriteResp{ 73 | UserFavoriteArr: respUserFavoriteArr, 74 | }, nil 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/logic/getUserFollowLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-user-operate/internal/svc" 7 | "douyin/service/rpc-user-operate/userOptPb" 8 | "github.com/Masterminds/squirrel" 9 | "github.com/pkg/errors" 10 | "github.com/zeromicro/go-zero/core/logx" 11 | ) 12 | 13 | type GetUserFollowLogic struct { 14 | ctx context.Context 15 | svcCtx *svc.ServiceContext 16 | logx.Logger 17 | } 18 | 19 | func NewGetUserFollowLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserFollowLogic { 20 | return &GetUserFollowLogic{ 21 | ctx: ctx, 22 | svcCtx: svcCtx, 23 | Logger: logx.WithContext(ctx), 24 | } 25 | } 26 | 27 | func (l *GetUserFollowLogic) GetUserFollow(in *userOptPb.GetUserFollowReq) (*userOptPb.GetUserFollowResp, error) { 28 | 29 | if in.AuthIds != nil { // 查看用户是否关注了这些作者 30 | whereBuilder := l.svcCtx.UserFollowModel.RowBuilder().Where(squirrel.Eq{"user_id": in.UserId, "follow_id": in.AuthIds}).Where(squirrel.NotEq{"is_follow": 0}) 31 | res, err := l.svcCtx.UserFollowModel.FindAll(l.ctx, whereBuilder, "") 32 | if err != nil { 33 | logx.Errorf("GetUserFollow err:%s", err.Error()) 34 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "GetUserFollow err , id:%d , err:%v", in.UserId, err) 35 | } 36 | 37 | var userFollowList map[int64]bool 38 | if len(res) > 0 { 39 | userFollowList = make(map[int64]bool, len(res)) 40 | for _, v := range res { 41 | userFollowList[v.FollowId] = true 42 | } 43 | } 44 | 45 | return &userOptPb.GetUserFollowResp{ 46 | UserFollowList: userFollowList, 47 | }, nil 48 | 49 | } else { // 把该用户的所有关注者找出来 50 | whereBuilder := l.svcCtx.UserFollowModel.RowBuilder().Where("user_id = ? and is_follow != 0 ", in.UserId) 51 | 52 | res, err := l.svcCtx.UserFollowModel.FindAll(l.ctx, whereBuilder, "user_id ASC") 53 | if err != nil { 54 | logx.Errorf("GetUserFollowLogic GetUserFollow err: %s", err.Error()) 55 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "GetUserFollow err , id:%d , err:%v", in.UserId, err) 56 | } 57 | 58 | var userFollowList map[int64]bool 59 | if len(res) > 0 { 60 | userFollowList = make(map[int64]bool, len(res)) 61 | for _, v := range res { 62 | userFollowList[v.FollowId] = true 63 | } 64 | } 65 | 66 | return &userOptPb.GetUserFollowResp{ 67 | UserFollowList: userFollowList, 68 | }, nil 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/logic/getUserFollowerLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "github.com/pkg/errors" 7 | 8 | "douyin/service/rpc-user-operate/internal/svc" 9 | "douyin/service/rpc-user-operate/userOptPb" 10 | 11 | "github.com/zeromicro/go-zero/core/logx" 12 | ) 13 | 14 | type GetUserFollowerLogic struct { 15 | ctx context.Context 16 | svcCtx *svc.ServiceContext 17 | logx.Logger 18 | } 19 | 20 | func NewGetUserFollowerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserFollowerLogic { 21 | return &GetUserFollowerLogic{ 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | Logger: logx.WithContext(ctx), 25 | } 26 | } 27 | 28 | func (l *GetUserFollowerLogic) GetUserFollower(in *userOptPb.GetUserFollowerReq) (*userOptPb.GetUserFollowerResp, error) { 29 | whereBuilder := l.svcCtx.UserFollowModel.RowBuilder().Where("follow_id = ? and is_follow != 0 ", in.UserId) 30 | 31 | res, err := l.svcCtx.UserFollowModel.FindAll(l.ctx, whereBuilder, "user_id ASC") 32 | if err != nil { 33 | logx.Errorf("GetUserFollowerLogic GetUserFollower err: %s", err.Error()) 34 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "GetUserFollower err , id:%d , err:%v", in.UserId, err) 35 | } 36 | 37 | /* 38 | 这里用数组是因为拉取点赞列表的接口返回的是数组 可以直接返回 比较方便 39 | */ 40 | var userFollowerList map[int64]bool 41 | if len(res) > 0 { 42 | userFollowerList = make(map[int64]bool, len(res)) 43 | for _, v := range res { 44 | userFollowerList[v.UserId] = true 45 | } 46 | } 47 | 48 | return &userOptPb.GetUserFollowerResp{ 49 | UserFollowerList: userFollowerList, 50 | }, nil 51 | } 52 | -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/logic/getVideoCommentLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/help/cos" 6 | "douyin/service/rpc-user-operate/internal/svc" 7 | "douyin/service/rpc-user-operate/userOptPb" 8 | "fmt" 9 | "github.com/jinzhu/copier" 10 | "github.com/zeromicro/go-zero/core/logx" 11 | ) 12 | 13 | type GetVideoCommentLogic struct { 14 | ctx context.Context 15 | svcCtx *svc.ServiceContext 16 | logx.Logger 17 | } 18 | 19 | func NewGetVideoCommentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetVideoCommentLogic { 20 | return &GetVideoCommentLogic{ 21 | ctx: ctx, 22 | svcCtx: svcCtx, 23 | Logger: logx.WithContext(ctx), 24 | } 25 | } 26 | 27 | func (l *GetVideoCommentLogic) GetVideoComment(in *userOptPb.GetVideoCommentReq) (*userOptPb.GetVideoCommentReqResp, error) { 28 | 29 | key := fmt.Sprintf("video_id_%d/", in.VideoId) 30 | downloadHelper := cos.DownloadComment{ 31 | Key: key, 32 | CommentBucket: l.svcCtx.Config.COSConf.CommentBucket, 33 | SecretID: l.svcCtx.Config.COSConf.SecretId, 34 | SecretKey: l.svcCtx.Config.COSConf.SecretKey, 35 | } 36 | comments, err := downloadHelper.DownloadComment(l.ctx) 37 | 38 | var commentList []*userOptPb.Comment 39 | for _, v := range comments { 40 | var comment userOptPb.Comment 41 | _ = copier.Copy(&comment, v) 42 | comment.Content = v.Content 43 | commentList = append(commentList, &comment) 44 | } 45 | 46 | if err != nil { 47 | logx.Errorf("get video comment list fail %s", err.Error()) 48 | return nil, err 49 | } 50 | 51 | return &userOptPb.GetVideoCommentReqResp{ 52 | CommentList: commentList, 53 | }, nil 54 | } 55 | -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/logic/updateCommentStatusLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/service/rpc-user-operate/model" 6 | "github.com/zeromicro/go-zero/core/stores/sqlx" 7 | "strings" 8 | 9 | "douyin/service/rpc-user-operate/internal/svc" 10 | "douyin/service/rpc-user-operate/userOptPb" 11 | 12 | "github.com/zeromicro/go-zero/core/logx" 13 | ) 14 | 15 | type UpdateCommentStatusLogic struct { 16 | ctx context.Context 17 | svcCtx *svc.ServiceContext 18 | logx.Logger 19 | } 20 | 21 | func NewUpdateCommentStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateCommentStatusLogic { 22 | return &UpdateCommentStatusLogic{ 23 | ctx: ctx, 24 | svcCtx: svcCtx, 25 | Logger: logx.WithContext(ctx), 26 | } 27 | } 28 | 29 | func (l *UpdateCommentStatusLogic) UpdateCommentStatus(in *userOptPb.UpdateCommentStatusReq) (*userOptPb.UpdateCommentStatusResp, error) { 30 | tmp := []string{"video_id", "comment_id", "user_id", "del_state"} 31 | field := strings.Join(tmp, ",") 32 | var action int64 33 | if in.ActionType == model.ActionADD { // 1是添加评论 0是删除评论 34 | action = 0 // del_state 0 是存在 1是已删除 35 | } else if in.ActionType == model.ActionCancel { 36 | action = 1 37 | } 38 | 39 | err := l.svcCtx.UserCommentModel.Trans(l.ctx, func(context context.Context, session sqlx.Session) error { 40 | 41 | // 这里有点不一样 要是model文件里的内容变了 这里也要变 42 | // InsertOrUpdate(ctx context.Context, session sqlx.Session, field string, setStatus string, videoId, objId, userId, opt int64) 43 | _, err := l.svcCtx.UserCommentModel.InsertOrUpdate(l.ctx, session, field, "del_state", in.VideoId, in.CommentId, in.UserId, action) 44 | if err != nil { 45 | logx.Errorf("UpdateCommentStatus------->InsertOrUpdate err : %s", err.Error()) 46 | return err 47 | } 48 | 49 | // 消息中传来的 in.action是 0 1 写入video comment_count 就需要变成 -1 / +1 50 | actionType := l.getActionType(in.ActionType) 51 | _, err = l.svcCtx.VideoModel.UpdateStatus(l.ctx, session, "comment_count", "id", actionType, in.VideoId) 52 | if err != nil { 53 | logx.Errorf("UpdateCommentStatus------->UpdateStatus err : %s", err.Error()) 54 | return err 55 | } 56 | 57 | return nil 58 | }) 59 | 60 | if err != nil { 61 | logx.Error("UpdateCommentStatus-------> trans fail") 62 | return &userOptPb.UpdateCommentStatusResp{}, err 63 | } 64 | 65 | return &userOptPb.UpdateCommentStatusResp{}, nil 66 | } 67 | 68 | func (l *UpdateCommentStatusLogic) getActionType(actionType int64) int64 { 69 | 70 | switch actionType { // 方便扩展 71 | case model.ActionADD: 72 | return 1 73 | case model.ActionCancel: 74 | return -1 75 | default: 76 | return -99 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/logic/updateFavoriteStatusLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/service/rpc-user-operate/internal/svc" 6 | "douyin/service/rpc-user-operate/model" 7 | "douyin/service/rpc-user-operate/userOptPb" 8 | "github.com/zeromicro/go-zero/core/stores/sqlx" 9 | "strings" 10 | 11 | "github.com/zeromicro/go-zero/core/logx" 12 | ) 13 | 14 | type UpdateFavoriteStatusLogic struct { 15 | ctx context.Context 16 | svcCtx *svc.ServiceContext 17 | logx.Logger 18 | } 19 | 20 | func NewUpdateFavoriteStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateFavoriteStatusLogic { 21 | return &UpdateFavoriteStatusLogic{ 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | Logger: logx.WithContext(ctx), 25 | } 26 | } 27 | 28 | // export logic "INSERT INTO user_favorite_list(user_id,video_id,is_favorite) VALUES(1,6,?) ON DUPLICATE KEY UPDATE is_favorite=?" 29 | func (l *UpdateFavoriteStatusLogic) UpdateFavoriteStatus(in *userOptPb.UpdateFavoriteStatusReq) (*userOptPb.UpdateFavoriteStatusResp, error) { 30 | tmp := []string{"user_id", "video_id", "is_favorite"} 31 | field := strings.Join(tmp, ",") 32 | err := l.svcCtx.UserFavoriteModel.Trans(l.ctx, func(context context.Context, session sqlx.Session) error { 33 | _, err := l.svcCtx.UserFavoriteModel.InsertOrUpdate(l.ctx, session, field, "is_favorite", in.UserId, in.VideoId, in.ActionType) 34 | if err != nil { 35 | logx.Errorf("UpdateFavoriteStatusLogic------->InsertOrUpdate err : %v", err.Error()) 36 | return err 37 | } 38 | 39 | // 消息中传来的 in.action是 0 1 写入user favorite_count 就需要变成 -1 1 40 | action := l.getActionType(in.ActionType) 41 | _, err = l.svcCtx.VideoModel.UpdateStatus(l.ctx, session, "favorite_count", "id", action, in.VideoId) 42 | if err != nil { 43 | logx.Errorf("UpdateFavoriteStatusLogic------->UpdateStatus err : %v", err.Error()) 44 | return err 45 | } 46 | return nil 47 | }) 48 | if err != nil { 49 | logx.Error("UpdateFavoriteStatusLogic-------> trans fail") 50 | return &userOptPb.UpdateFavoriteStatusResp{}, err 51 | } 52 | 53 | return &userOptPb.UpdateFavoriteStatusResp{}, nil 54 | } 55 | func (l *UpdateFavoriteStatusLogic) getActionType(actionType int64) int64 { 56 | 57 | switch actionType { // 方便扩展 58 | case model.ActionADD: 59 | return 1 60 | case model.ActionCancel: 61 | return -1 62 | default: 63 | return -99 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/logic/updateFollowStatusLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/service/rpc-user-operate/model" 6 | "github.com/zeromicro/go-zero/core/stores/sqlx" 7 | "strings" 8 | 9 | "douyin/service/rpc-user-operate/internal/svc" 10 | "douyin/service/rpc-user-operate/userOptPb" 11 | 12 | "github.com/zeromicro/go-zero/core/logx" 13 | ) 14 | 15 | type UpdateFollowStatusLogic struct { 16 | ctx context.Context 17 | svcCtx *svc.ServiceContext 18 | logx.Logger 19 | } 20 | 21 | func NewUpdateFollowStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateFollowStatusLogic { 22 | return &UpdateFollowStatusLogic{ 23 | ctx: ctx, 24 | svcCtx: svcCtx, 25 | Logger: logx.WithContext(ctx), 26 | } 27 | } 28 | 29 | func (l *UpdateFollowStatusLogic) UpdateFollowStatus(in *userOptPb.UpdateFollowStatusReq) (*userOptPb.UpdateFollowStatusResp, error) { 30 | 31 | tmp := []string{"user_id", "follow_id", "is_follow"} 32 | field := strings.Join(tmp, ",") 33 | err := l.svcCtx.UserFollowModel.Trans(l.ctx, func(context context.Context, session sqlx.Session) error { 34 | _, err := l.svcCtx.UserFollowModel.InsertOrUpdate(l.ctx, session, field, "is_follow", in.UserId, in.FollowId, in.ActionType) 35 | if err != nil { 36 | logx.Errorf("UpdateFollowStatus------->InsertOrUpdate err : %s", err.Error()) 37 | return err 38 | } 39 | 40 | // 消息中传来的 in.action是 0 1 写入user follow_count 就需要变成 -1 / +1 41 | action := l.getActionType(in.ActionType) 42 | _, err = l.svcCtx.UserModel.UpdateStatus(l.ctx, session, "follow_count", "user_id", action, in.UserId) 43 | if err != nil { 44 | logx.Errorf("UpdateFollowStatus------->UpdateStatus err : %s", err.Error()) 45 | return err 46 | } 47 | 48 | // 更新被关注者的粉丝数 49 | _, err = l.svcCtx.UserModel.UpdateStatus(l.ctx, session, "follower_count", "user_id", action, in.FollowId) 50 | if err != nil { 51 | logx.Errorf("UpdateFollowStatus------->Update follower Status err : %v", err.Error()) 52 | return err 53 | } 54 | return nil 55 | }) 56 | if err != nil { 57 | logx.Error("UpdateFollowStatus-------> trans fail") 58 | return &userOptPb.UpdateFollowStatusResp{}, err 59 | } 60 | 61 | return &userOptPb.UpdateFollowStatusResp{}, nil 62 | } 63 | 64 | func (l *UpdateFollowStatusLogic) getActionType(actionType int64) int64 { 65 | 66 | switch actionType { // 方便扩展 67 | case model.ActionADD: 68 | return 1 69 | case model.ActionCancel: 70 | return -1 71 | default: 72 | return -99 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/server/userOptServiceServer.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT! 2 | // Source: UserOptService.proto 3 | 4 | package server 5 | 6 | import ( 7 | "context" 8 | 9 | "douyin/service/rpc-user-operate/internal/logic" 10 | "douyin/service/rpc-user-operate/internal/svc" 11 | "douyin/service/rpc-user-operate/userOptPb" 12 | ) 13 | 14 | type UserOptServiceServer struct { 15 | svcCtx *svc.ServiceContext 16 | userOptPb.UnimplementedUserOptServiceServer 17 | } 18 | 19 | func NewUserOptServiceServer(svcCtx *svc.ServiceContext) *UserOptServiceServer { 20 | return &UserOptServiceServer{ 21 | svcCtx: svcCtx, 22 | } 23 | } 24 | 25 | // -----------------------userFavoriteList----------------------- 26 | func (s *UserOptServiceServer) GetUserFavorite(ctx context.Context, in *userOptPb.GetUserFavoriteReq) (*userOptPb.GetUserFavoriteResp, error) { 27 | l := logic.NewGetUserFavoriteLogic(ctx, s.svcCtx) 28 | return l.GetUserFavorite(in) 29 | } 30 | 31 | func (s *UserOptServiceServer) GetUserFollow(ctx context.Context, in *userOptPb.GetUserFollowReq) (*userOptPb.GetUserFollowResp, error) { 32 | l := logic.NewGetUserFollowLogic(ctx, s.svcCtx) 33 | return l.GetUserFollow(in) 34 | } 35 | 36 | func (s *UserOptServiceServer) UpdateFavoriteStatus(ctx context.Context, in *userOptPb.UpdateFavoriteStatusReq) (*userOptPb.UpdateFavoriteStatusResp, error) { 37 | l := logic.NewUpdateFavoriteStatusLogic(ctx, s.svcCtx) 38 | return l.UpdateFavoriteStatus(in) 39 | } 40 | 41 | func (s *UserOptServiceServer) UpdateFollowStatus(ctx context.Context, in *userOptPb.UpdateFollowStatusReq) (*userOptPb.UpdateFollowStatusResp, error) { 42 | l := logic.NewUpdateFollowStatusLogic(ctx, s.svcCtx) 43 | return l.UpdateFollowStatus(in) 44 | } 45 | 46 | func (s *UserOptServiceServer) UpdateCommentStatus(ctx context.Context, in *userOptPb.UpdateCommentStatusReq) (*userOptPb.UpdateCommentStatusResp, error) { 47 | l := logic.NewUpdateCommentStatusLogic(ctx, s.svcCtx) 48 | return l.UpdateCommentStatus(in) 49 | } 50 | 51 | func (s *UserOptServiceServer) GetVideoComment(ctx context.Context, in *userOptPb.GetVideoCommentReq) (*userOptPb.GetVideoCommentReqResp, error) { 52 | l := logic.NewGetVideoCommentLogic(ctx, s.svcCtx) 53 | return l.GetVideoComment(in) 54 | } 55 | 56 | func (s *UserOptServiceServer) GetUserFollower(ctx context.Context, in *userOptPb.GetUserFollowerReq) (*userOptPb.GetUserFollowerResp, error) { 57 | l := logic.NewGetUserFollowerLogic(ctx, s.svcCtx) 58 | return l.GetUserFollower(in) 59 | } 60 | -------------------------------------------------------------------------------- /service/rpc-user-operate/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | usermodel "douyin/service/rpc-user-info/model" 5 | "douyin/service/rpc-user-operate/internal/config" 6 | "douyin/service/rpc-user-operate/model" 7 | videomodel "douyin/service/rpc-video-service/model" 8 | "github.com/zeromicro/go-zero/core/stores/sqlx" 9 | ) 10 | 11 | type ServiceContext struct { 12 | Config config.Config 13 | UserFavoriteModel model.UserFavoriteListModel 14 | UserFollowModel model.UserFollowListModel 15 | UserCommentModel model.UserCommentListModel 16 | UserModel usermodel.UserModel 17 | VideoModel videomodel.VideoModel 18 | } 19 | 20 | func NewServiceContext(c config.Config) *ServiceContext { 21 | return &ServiceContext{ 22 | Config: c, 23 | UserFavoriteModel: model.NewUserFavoriteListModel(sqlx.NewMysql(c.DB.DataSource), c.CacheConf), 24 | UserFollowModel: model.NewUserFollowListModel(sqlx.NewMysql(c.DB.DataSource), c.CacheConf), 25 | UserModel: usermodel.NewUserModel(sqlx.NewMysql(c.DB.DataSource), c.CacheConf), 26 | VideoModel: videomodel.NewVideoModel(sqlx.NewMysql(c.DB.DataSource), c.CacheConf), 27 | UserCommentModel: model.NewUserCommentListModel(sqlx.NewMysql(c.DB.DataSource), c.CacheConf), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /service/rpc-user-operate/model/vars.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/zeromicro/go-zero/core/stores/sqlx" 4 | 5 | var ErrNotFound = sqlx.ErrNotFound 6 | 7 | const ( // 增加是1 取消是0 8 | ActionADD int64 = 1 9 | ActionCancel int64 = 0 10 | ) 11 | -------------------------------------------------------------------------------- /service/rpc-user-operate/userOptPb/GenPb/UserOptService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package ="./userOptPb"; 4 | 5 | package pb; 6 | 7 | // ------------------------------------ 8 | // Messages 9 | // ------------------------------------ 10 | 11 | //--------------------------------userFavoriteList-------------------------------- 12 | 13 | // 用户给视频点赞 14 | message UpdateFavoriteStatusReq { 15 | int64 videoId = 1; //videoId 16 | int64 userId = 2; //userId 17 | int64 actionType = 3; //点赞 18 | } 19 | 20 | message UpdateFavoriteStatusResp { 21 | } 22 | 23 | // 用户给用户关注 24 | message UpdateFollowStatusReq { 25 | int64 followId = 1; //videoId 26 | int64 userId = 2; //userId 27 | int64 actionType = 3; //点赞 28 | } 29 | 30 | message UpdateFollowStatusResp { 31 | } 32 | 33 | // 用户对视频评论 34 | message UpdateCommentStatusReq { 35 | int64 videoId = 1; //videoId 36 | int64 userId = 2; //userId 37 | int64 commentId = 3; //commentId 38 | int64 actionType = 4; //评论 取消评论 39 | } 40 | 41 | message UpdateCommentStatusResp { 42 | } 43 | 44 | 45 | 46 | // 获得用户和视频的点赞关系 47 | message GetUserFavoriteReq { 48 | int64 userId = 1; //userId 49 | repeated int64 videoIds = 2; //videoId 50 | } 51 | 52 | message GetUserFavoriteResp { 53 | map userFavoriteList = 1; //userFavoriteList 54 | repeated int64 userFavoriteArr = 2; //userFavoriteArr 55 | } 56 | 57 | // 获得用户的关注列表 58 | message GetUserFollowReq { 59 | int64 userId = 1; //user_id 60 | repeated int64 authIds = 2; //user_id 61 | } 62 | message GetUserFollowResp { 63 | map userFollowList = 1; //用户和视频的关注关系 64 | } 65 | 66 | // 获得用户的粉丝列表 67 | message GetUserFollowerReq { 68 | int64 userId = 1; //user_id 69 | } 70 | message GetUserFollowerResp { 71 | map userFollowerList = 1; //用户和视频的关注关系 72 | } 73 | 74 | 75 | // 获得视频的评论列表 76 | message GetVideoCommentReq { 77 | int64 videoId = 1; //video_id 78 | } 79 | message Comment { 80 | int64 commentId = 1; 81 | int64 userId = 2; 82 | string content = 3; 83 | string createDate = 4; 84 | } 85 | 86 | message GetVideoCommentReqResp { 87 | repeated Comment commentList = 1;//comment_list 88 | } 89 | 90 | // ------------------------------------ 91 | // Rpc Func 92 | // ------------------------------------ 93 | 94 | service UserOptService{ 95 | //-----------------------userFavoriteList----------------------- 96 | rpc GetUserFavorite(GetUserFavoriteReq) returns (GetUserFavoriteResp); 97 | rpc GetUserFollow(GetUserFollowReq) returns (GetUserFollowResp); 98 | rpc UpdateFavoriteStatus(UpdateFavoriteStatusReq) returns (UpdateFavoriteStatusResp); 99 | rpc UpdateFollowStatus(UpdateFollowStatusReq) returns (UpdateFollowStatusResp); 100 | rpc UpdateCommentStatus(UpdateCommentStatusReq) returns (UpdateCommentStatusResp); 101 | rpc GetVideoComment(GetVideoCommentReq) returns (GetVideoCommentReqResp); 102 | rpc GetUserFollower(GetUserFollowerReq) returns (GetUserFollowerResp); 103 | } 104 | -------------------------------------------------------------------------------- /service/rpc-user-operate/userOptPb/GenPb/build.sh: -------------------------------------------------------------------------------- 1 | goctl rpc protoc UserOptService.proto --go_out=../../ --go-grpc_out=../../ --zrpc_out=../../ --style=goZero --home=../../../../tpl 2 | # 当有多个proto文件时 需要先生成其他的文件 再生成主要的pb文件 -------------------------------------------------------------------------------- /service/rpc-user-operate/userOptService.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "douyin/service/rpc-user-operate/internal/config" 8 | "douyin/service/rpc-user-operate/internal/server" 9 | "douyin/service/rpc-user-operate/internal/svc" 10 | "douyin/service/rpc-user-operate/userOptPb" 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/userOptService.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 | svr := server.NewUserOptServiceServer(ctx) 28 | 29 | s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { 30 | userOptPb.RegisterUserOptServiceServer(grpcServer, svr) 31 | 32 | if c.Mode == service.DevMode || c.Mode == service.TestMode { 33 | reflection.Register(grpcServer) 34 | } 35 | }) 36 | defer s.Stop() 37 | 38 | fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) 39 | s.Start() 40 | } 41 | -------------------------------------------------------------------------------- /service/rpc-user-operate/useroptservice/userOptService.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT! 2 | // Source: UserOptService.proto 3 | 4 | package useroptservice 5 | 6 | import ( 7 | "context" 8 | 9 | "douyin/service/rpc-user-operate/userOptPb" 10 | 11 | "github.com/zeromicro/go-zero/zrpc" 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | type ( 16 | Comment = userOptPb.Comment 17 | GetUserFavoriteReq = userOptPb.GetUserFavoriteReq 18 | GetUserFavoriteResp = userOptPb.GetUserFavoriteResp 19 | GetUserFollowReq = userOptPb.GetUserFollowReq 20 | GetUserFollowResp = userOptPb.GetUserFollowResp 21 | GetUserFollowerReq = userOptPb.GetUserFollowerReq 22 | GetUserFollowerResp = userOptPb.GetUserFollowerResp 23 | GetVideoCommentReq = userOptPb.GetVideoCommentReq 24 | GetVideoCommentReqResp = userOptPb.GetVideoCommentReqResp 25 | UpdateCommentStatusReq = userOptPb.UpdateCommentStatusReq 26 | UpdateCommentStatusResp = userOptPb.UpdateCommentStatusResp 27 | UpdateFavoriteStatusReq = userOptPb.UpdateFavoriteStatusReq 28 | UpdateFavoriteStatusResp = userOptPb.UpdateFavoriteStatusResp 29 | UpdateFollowStatusReq = userOptPb.UpdateFollowStatusReq 30 | UpdateFollowStatusResp = userOptPb.UpdateFollowStatusResp 31 | 32 | UserOptService interface { 33 | // -----------------------userFavoriteList----------------------- 34 | GetUserFavorite(ctx context.Context, in *GetUserFavoriteReq, opts ...grpc.CallOption) (*GetUserFavoriteResp, error) 35 | GetUserFollow(ctx context.Context, in *GetUserFollowReq, opts ...grpc.CallOption) (*GetUserFollowResp, error) 36 | UpdateFavoriteStatus(ctx context.Context, in *UpdateFavoriteStatusReq, opts ...grpc.CallOption) (*UpdateFavoriteStatusResp, error) 37 | UpdateFollowStatus(ctx context.Context, in *UpdateFollowStatusReq, opts ...grpc.CallOption) (*UpdateFollowStatusResp, error) 38 | UpdateCommentStatus(ctx context.Context, in *UpdateCommentStatusReq, opts ...grpc.CallOption) (*UpdateCommentStatusResp, error) 39 | GetVideoComment(ctx context.Context, in *GetVideoCommentReq, opts ...grpc.CallOption) (*GetVideoCommentReqResp, error) 40 | GetUserFollower(ctx context.Context, in *GetUserFollowerReq, opts ...grpc.CallOption) (*GetUserFollowerResp, error) 41 | } 42 | 43 | defaultUserOptService struct { 44 | cli zrpc.Client 45 | } 46 | ) 47 | 48 | func NewUserOptService(cli zrpc.Client) UserOptService { 49 | return &defaultUserOptService{ 50 | cli: cli, 51 | } 52 | } 53 | 54 | // -----------------------userFavoriteList----------------------- 55 | func (m *defaultUserOptService) GetUserFavorite(ctx context.Context, in *GetUserFavoriteReq, opts ...grpc.CallOption) (*GetUserFavoriteResp, error) { 56 | client := userOptPb.NewUserOptServiceClient(m.cli.Conn()) 57 | return client.GetUserFavorite(ctx, in, opts...) 58 | } 59 | 60 | func (m *defaultUserOptService) GetUserFollow(ctx context.Context, in *GetUserFollowReq, opts ...grpc.CallOption) (*GetUserFollowResp, error) { 61 | client := userOptPb.NewUserOptServiceClient(m.cli.Conn()) 62 | return client.GetUserFollow(ctx, in, opts...) 63 | } 64 | 65 | func (m *defaultUserOptService) UpdateFavoriteStatus(ctx context.Context, in *UpdateFavoriteStatusReq, opts ...grpc.CallOption) (*UpdateFavoriteStatusResp, error) { 66 | client := userOptPb.NewUserOptServiceClient(m.cli.Conn()) 67 | return client.UpdateFavoriteStatus(ctx, in, opts...) 68 | } 69 | 70 | func (m *defaultUserOptService) UpdateFollowStatus(ctx context.Context, in *UpdateFollowStatusReq, opts ...grpc.CallOption) (*UpdateFollowStatusResp, error) { 71 | client := userOptPb.NewUserOptServiceClient(m.cli.Conn()) 72 | return client.UpdateFollowStatus(ctx, in, opts...) 73 | } 74 | 75 | func (m *defaultUserOptService) UpdateCommentStatus(ctx context.Context, in *UpdateCommentStatusReq, opts ...grpc.CallOption) (*UpdateCommentStatusResp, error) { 76 | client := userOptPb.NewUserOptServiceClient(m.cli.Conn()) 77 | return client.UpdateCommentStatus(ctx, in, opts...) 78 | } 79 | 80 | func (m *defaultUserOptService) GetVideoComment(ctx context.Context, in *GetVideoCommentReq, opts ...grpc.CallOption) (*GetVideoCommentReqResp, error) { 81 | client := userOptPb.NewUserOptServiceClient(m.cli.Conn()) 82 | return client.GetVideoComment(ctx, in, opts...) 83 | } 84 | 85 | func (m *defaultUserOptService) GetUserFollower(ctx context.Context, in *GetUserFollowerReq, opts ...grpc.CallOption) (*GetUserFollowerResp, error) { 86 | client := userOptPb.NewUserOptServiceClient(m.cli.Conn()) 87 | return client.GetUserFollower(ctx, in, opts...) 88 | } 89 | -------------------------------------------------------------------------------- /service/rpc-video-service/etc/videoService.yaml: -------------------------------------------------------------------------------- 1 | Name: videoservice.rpc 2 | ListenOn: 0.0.0.0:9999 3 | 4 | Mode: dev 5 | 6 | Etcd: 7 | Hosts: 8 | - 127.0.0.1:33333 9 | Key: videoservice.rpc 10 | Log: 11 | Encoding: plain 12 | DB: 13 | DataSource: username:password@tcp(localhost:3306)/douyin2?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai 14 | 15 | CacheConf: 16 | - Host: localhost:6379 17 | Pass: password 18 | -------------------------------------------------------------------------------- /service/rpc-video-service/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/zeromicro/go-zero/core/stores/cache" 5 | "github.com/zeromicro/go-zero/zrpc" 6 | ) 7 | 8 | type Config struct { 9 | zrpc.RpcServerConf 10 | DB struct { 11 | DataSource string 12 | } 13 | CacheConf cache.CacheConf 14 | } 15 | -------------------------------------------------------------------------------- /service/rpc-video-service/internal/logic/feedVideosLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-video-service/internal/svc" 7 | "douyin/service/rpc-video-service/videoSvcPb" 8 | "github.com/jinzhu/copier" 9 | "github.com/pkg/errors" 10 | "github.com/zeromicro/go-zero/core/logx" 11 | "time" 12 | ) 13 | 14 | type FeedVideosLogic struct { 15 | ctx context.Context 16 | svcCtx *svc.ServiceContext 17 | logx.Logger 18 | } 19 | 20 | func NewFeedVideosLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FeedVideosLogic { 21 | return &FeedVideosLogic{ 22 | ctx: ctx, 23 | svcCtx: svcCtx, 24 | Logger: logx.WithContext(ctx), 25 | } 26 | } 27 | 28 | func (l *FeedVideosLogic) FeedVideos(in *videoSvcPb.FeedVideosReq) (*videoSvcPb.FeedVideosResp, error) { 29 | 30 | // 转换为数据库里的时间格式 31 | reqTime := time.Unix(in.LastTime, 0) 32 | 33 | logx.Errorf("小于这个时间的视频reqTime: %v", reqTime) 34 | 35 | whereBuilder := l.svcCtx.VideoModel.RowBuilder().Where("create_time < ?", reqTime) 36 | list, err := l.svcCtx.VideoModel.FindPageListByPage(l.ctx, whereBuilder, 1, 10, "create_time DESC") 37 | if err != nil { 38 | return nil, errors.Wrapf(xerr.NewErrMsg("get video list fail"), "get video list fail FindPageListByIdDESC err : %v , lastTime:%d", err, in.LastTime) 39 | } 40 | /* 41 | 如果想重构这里可以用泛型 42 | */ 43 | var videos []*videoSvcPb.Video 44 | videos = make([]*videoSvcPb.Video, 0, len(list)) 45 | var NextTime int64 46 | if len(list) > 0 { 47 | for _, v := range list { 48 | var video videoSvcPb.Video 49 | _ = copier.Copy(&video, v) 50 | videos = append(videos, &video) 51 | } 52 | // 最早的视频的时间 时间戳最小的 53 | NextTime = list[len(list)-1].CreateTime.Unix() 54 | } 55 | 56 | logx.Errorf("下次请求的视频小于这个时间的视频: %v", list[len(list)-1].CreateTime) 57 | 58 | return &videoSvcPb.FeedVideosResp{ 59 | VideoPubList: videos, 60 | NextTime: NextTime, 61 | }, nil 62 | } 63 | -------------------------------------------------------------------------------- /service/rpc-video-service/internal/logic/getMyFavoriteVideosLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-video-service/model" 7 | "github.com/Masterminds/squirrel" 8 | "github.com/jinzhu/copier" 9 | "github.com/pkg/errors" 10 | 11 | "douyin/service/rpc-video-service/internal/svc" 12 | "douyin/service/rpc-video-service/videoSvcPb" 13 | 14 | "github.com/zeromicro/go-zero/core/logx" 15 | ) 16 | 17 | type GetMyFavoriteVideosLogic struct { 18 | ctx context.Context 19 | svcCtx *svc.ServiceContext 20 | logx.Logger 21 | } 22 | 23 | func NewGetMyFavoriteVideosLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMyFavoriteVideosLogic { 24 | return &GetMyFavoriteVideosLogic{ 25 | ctx: ctx, 26 | svcCtx: svcCtx, 27 | Logger: logx.WithContext(ctx), 28 | } 29 | } 30 | 31 | func (l *GetMyFavoriteVideosLogic) GetMyFavoriteVideos(in *videoSvcPb.MyFavoriteVideosReq) (*videoSvcPb.MyFavoriteVideosResp, error) { 32 | whereBuilder := l.svcCtx.VideoModel.RowBuilder().Where(squirrel.Eq{"id": in.VideoIdArr}) 33 | list, err := l.svcCtx.VideoModel.FindAll(l.ctx, whereBuilder, "") 34 | if err != nil && err != model.ErrNotFound { 35 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Failed to get video list err : %v , in :%+v", err, in) 36 | } 37 | 38 | /* 39 | 如果想重构这里可以用泛型 40 | */ 41 | var resp []*videoSvcPb.Video 42 | if len(list) > 0 { 43 | for _, video := range list { 44 | var pbVideo videoSvcPb.Video 45 | _ = copier.Copy(&pbVideo, video) 46 | 47 | resp = append(resp, &pbVideo) 48 | } 49 | } 50 | 51 | return &videoSvcPb.MyFavoriteVideosResp{ 52 | VideoPubList: resp, 53 | }, nil 54 | } 55 | -------------------------------------------------------------------------------- /service/rpc-video-service/internal/logic/getVideoListLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/common/xerr" 6 | "douyin/service/rpc-video-service/internal/svc" 7 | "douyin/service/rpc-video-service/model" 8 | "douyin/service/rpc-video-service/videoSvcPb" 9 | "github.com/Masterminds/squirrel" 10 | "github.com/jinzhu/copier" 11 | "github.com/pkg/errors" 12 | "github.com/zeromicro/go-zero/core/logx" 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 *videoSvcPb.GetVideoListReq) (*videoSvcPb.GetVideoListResp, error) { 30 | whereBuilder := l.svcCtx.VideoModel.RowBuilder().Where(squirrel.Eq{"auth_id": in.AuthId}) 31 | list, err := l.svcCtx.VideoModel.FindAll(l.ctx, whereBuilder, "") 32 | if err != nil && err != model.ErrNotFound { 33 | return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "Failed to get video list err : %v , in :%+v", err, in) 34 | } 35 | 36 | /* 37 | 如果想重构这里可以用泛型 38 | */ 39 | var resp []*videoSvcPb.Video 40 | if len(list) > 0 { 41 | for _, video := range list { 42 | var pbVideo videoSvcPb.Video 43 | _ = copier.Copy(&pbVideo, video) 44 | resp = append(resp, &pbVideo) 45 | } 46 | } 47 | 48 | return &videoSvcPb.GetVideoListResp{ 49 | VideoPubList: resp, 50 | }, nil 51 | } 52 | -------------------------------------------------------------------------------- /service/rpc-video-service/internal/logic/pubVideoLogic.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | "douyin/service/rpc-video-service/internal/svc" 6 | "douyin/service/rpc-video-service/model" 7 | "douyin/service/rpc-video-service/videoSvcPb" 8 | "github.com/pkg/errors" 9 | "github.com/zeromicro/go-zero/core/logx" 10 | ) 11 | 12 | type PubVideoLogic struct { 13 | ctx context.Context 14 | svcCtx *svc.ServiceContext 15 | logx.Logger 16 | } 17 | 18 | func NewPubVideoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PubVideoLogic { 19 | return &PubVideoLogic{ 20 | ctx: ctx, 21 | svcCtx: svcCtx, 22 | Logger: logx.WithContext(ctx), 23 | } 24 | } 25 | 26 | // PubVideo -----------------------video----------------------- 27 | func (l *PubVideoLogic) PubVideo(in *videoSvcPb.PubVideoReq) (*videoSvcPb.PubVideoResp, error) { 28 | 29 | _, err := l.svcCtx.VideoModel.Insert(l.ctx, nil, &model.Video{ 30 | AuthId: in.AuthId, 31 | Title: in.Title, 32 | PlayURL: in.PlayURL, 33 | CoverURL: in.CoverURL, 34 | }) 35 | if err != nil { 36 | return nil, errors.Wrapf(err, " PubVideo insert video fail") 37 | } 38 | return &videoSvcPb.PubVideoResp{}, nil 39 | } 40 | -------------------------------------------------------------------------------- /service/rpc-video-service/internal/server/videoServiceServer.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT! 2 | // Source: VideoService.proto 3 | 4 | package server 5 | 6 | import ( 7 | "context" 8 | 9 | "douyin/service/rpc-video-service/internal/logic" 10 | "douyin/service/rpc-video-service/internal/svc" 11 | "douyin/service/rpc-video-service/videoSvcPb" 12 | ) 13 | 14 | type VideoServiceServer struct { 15 | svcCtx *svc.ServiceContext 16 | videoSvcPb.UnimplementedVideoServiceServer 17 | } 18 | 19 | func NewVideoServiceServer(svcCtx *svc.ServiceContext) *VideoServiceServer { 20 | return &VideoServiceServer{ 21 | svcCtx: svcCtx, 22 | } 23 | } 24 | 25 | // -----------------------video----------------------- 26 | func (s *VideoServiceServer) PubVideo(ctx context.Context, in *videoSvcPb.PubVideoReq) (*videoSvcPb.PubVideoResp, error) { 27 | l := logic.NewPubVideoLogic(ctx, s.svcCtx) 28 | return l.PubVideo(in) 29 | } 30 | 31 | func (s *VideoServiceServer) GetVideoList(ctx context.Context, in *videoSvcPb.GetVideoListReq) (*videoSvcPb.GetVideoListResp, error) { 32 | l := logic.NewGetVideoListLogic(ctx, s.svcCtx) 33 | return l.GetVideoList(in) 34 | } 35 | 36 | func (s *VideoServiceServer) FeedVideos(ctx context.Context, in *videoSvcPb.FeedVideosReq) (*videoSvcPb.FeedVideosResp, error) { 37 | l := logic.NewFeedVideosLogic(ctx, s.svcCtx) 38 | return l.FeedVideos(in) 39 | } 40 | 41 | func (s *VideoServiceServer) GetMyFavoriteVideos(ctx context.Context, in *videoSvcPb.MyFavoriteVideosReq) (*videoSvcPb.MyFavoriteVideosResp, error) { 42 | l := logic.NewGetMyFavoriteVideosLogic(ctx, s.svcCtx) 43 | return l.GetMyFavoriteVideos(in) 44 | } 45 | -------------------------------------------------------------------------------- /service/rpc-video-service/internal/svc/serviceContext.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "douyin/service/rpc-video-service/internal/config" 5 | videoModel "douyin/service/rpc-video-service/model" 6 | "github.com/zeromicro/go-zero/core/stores/sqlx" 7 | ) 8 | 9 | type ServiceContext struct { 10 | Config config.Config 11 | VideoModel videoModel.VideoModel 12 | } 13 | 14 | func NewServiceContext(c config.Config) *ServiceContext { 15 | return &ServiceContext{ 16 | Config: c, 17 | VideoModel: videoModel.NewVideoModel(sqlx.NewMysql(c.DB.DataSource), c.CacheConf), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /service/rpc-video-service/model/vars.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/zeromicro/go-zero/core/stores/sqlx" 4 | 5 | var ErrNotFound = sqlx.ErrNotFound 6 | -------------------------------------------------------------------------------- /service/rpc-video-service/videoService.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "douyin/service/rpc-video-service/internal/config" 8 | "douyin/service/rpc-video-service/internal/server" 9 | "douyin/service/rpc-video-service/internal/svc" 10 | "douyin/service/rpc-video-service/videoSvcPb" 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/videoService.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 | svr := server.NewVideoServiceServer(ctx) 28 | 29 | s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { 30 | videoSvcPb.RegisterVideoServiceServer(grpcServer, svr) 31 | 32 | if c.Mode == service.DevMode || c.Mode == service.TestMode { 33 | reflection.Register(grpcServer) 34 | } 35 | }) 36 | defer s.Stop() 37 | 38 | fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) 39 | s.Start() 40 | } 41 | -------------------------------------------------------------------------------- /service/rpc-video-service/videoSvcPb/GenPb/VideoService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package ="./videoSvcPb"; 4 | 5 | package pb; 6 | 7 | // ------------------------------------ 8 | // Messages 9 | // ------------------------------------ 10 | 11 | //--------------------------------video-------------------------------- 12 | message PubVideoReq { 13 | int64 AuthId = 1; //authId 14 | string Title = 2; //title 15 | string PlayURL = 3; //play_url 16 | string CoverURL = 4; //cover_url 17 | } 18 | 19 | message PubVideoResp { 20 | } 21 | 22 | message Video { 23 | int64 Id = 1; //id 24 | string Title = 2; //title 25 | string PlayURL = 3; //play_url 26 | string CoverURL = 4; //cover_url 27 | int64 FavoriteCount = 5; //favorite_count 28 | int64 CommentCount = 6; //comment_count 29 | bool IsFavorite = 7; //is_favorite 30 | int64 AuthId = 8; // auth_id 31 | } 32 | 33 | message GetVideoListReq { 34 | int64 AuthId = 1; //authId 35 | } 36 | 37 | message GetVideoListResp { 38 | repeated Video VideoPubList = 1; //video_list 39 | } 40 | 41 | message FeedVideosReq { 42 | int64 LastTime = 1; // last_time 43 | } 44 | message FeedVideosResp { 45 | repeated Video VideoPubList = 1; //video_list 46 | int64 NextTime = 2; // next_time 47 | } 48 | 49 | message MyFavoriteVideosReq { 50 | repeated int64 VideoIdArr = 1; //videos_id 51 | } 52 | message MyFavoriteVideosResp { 53 | repeated Video VideoPubList = 1; //video_list 54 | } 55 | 56 | // ------------------------------------ 57 | // Rpc Func 58 | // ------------------------------------ 59 | 60 | service VideoService{ 61 | //-----------------------video----------------------- 62 | rpc PubVideo(PubVideoReq) returns (PubVideoResp); 63 | rpc GetVideoList(GetVideoListReq) returns (GetVideoListResp); 64 | rpc FeedVideos(FeedVideosReq) returns (FeedVideosResp); 65 | rpc GetMyFavoriteVideos(MyFavoriteVideosReq) returns (MyFavoriteVideosResp); 66 | } 67 | -------------------------------------------------------------------------------- /service/rpc-video-service/videoSvcPb/GenPb/build.sh: -------------------------------------------------------------------------------- 1 | goctl rpc protoc VideoService.proto --go_out=../../ --go-grpc_out=../../ --zrpc_out=../../ --style=goZero --home=../../../../tpl 2 | # 当有多个proto文件时 需要先生成其他的文件 再生成主要的pb文件 -------------------------------------------------------------------------------- /service/rpc-video-service/videoservice/videoService.go: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT! 2 | // Source: VideoService.proto 3 | 4 | package videoservice 5 | 6 | import ( 7 | "context" 8 | 9 | "douyin/service/rpc-video-service/videoSvcPb" 10 | 11 | "github.com/zeromicro/go-zero/zrpc" 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | type ( 16 | FeedVideosReq = videoSvcPb.FeedVideosReq 17 | FeedVideosResp = videoSvcPb.FeedVideosResp 18 | GetVideoListReq = videoSvcPb.GetVideoListReq 19 | GetVideoListResp = videoSvcPb.GetVideoListResp 20 | MyFavoriteVideosReq = videoSvcPb.MyFavoriteVideosReq 21 | MyFavoriteVideosResp = videoSvcPb.MyFavoriteVideosResp 22 | PubVideoReq = videoSvcPb.PubVideoReq 23 | PubVideoResp = videoSvcPb.PubVideoResp 24 | Video = videoSvcPb.Video 25 | 26 | VideoService interface { 27 | // -----------------------video----------------------- 28 | PubVideo(ctx context.Context, in *PubVideoReq, opts ...grpc.CallOption) (*PubVideoResp, error) 29 | GetVideoList(ctx context.Context, in *GetVideoListReq, opts ...grpc.CallOption) (*GetVideoListResp, error) 30 | FeedVideos(ctx context.Context, in *FeedVideosReq, opts ...grpc.CallOption) (*FeedVideosResp, error) 31 | GetMyFavoriteVideos(ctx context.Context, in *MyFavoriteVideosReq, opts ...grpc.CallOption) (*MyFavoriteVideosResp, error) 32 | } 33 | 34 | defaultVideoService struct { 35 | cli zrpc.Client 36 | } 37 | ) 38 | 39 | func NewVideoService(cli zrpc.Client) VideoService { 40 | return &defaultVideoService{ 41 | cli: cli, 42 | } 43 | } 44 | 45 | // -----------------------video----------------------- 46 | func (m *defaultVideoService) PubVideo(ctx context.Context, in *PubVideoReq, opts ...grpc.CallOption) (*PubVideoResp, error) { 47 | client := videoSvcPb.NewVideoServiceClient(m.cli.Conn()) 48 | return client.PubVideo(ctx, in, opts...) 49 | } 50 | 51 | func (m *defaultVideoService) GetVideoList(ctx context.Context, in *GetVideoListReq, opts ...grpc.CallOption) (*GetVideoListResp, error) { 52 | client := videoSvcPb.NewVideoServiceClient(m.cli.Conn()) 53 | return client.GetVideoList(ctx, in, opts...) 54 | } 55 | 56 | func (m *defaultVideoService) FeedVideos(ctx context.Context, in *FeedVideosReq, opts ...grpc.CallOption) (*FeedVideosResp, error) { 57 | client := videoSvcPb.NewVideoServiceClient(m.cli.Conn()) 58 | return client.FeedVideos(ctx, in, opts...) 59 | } 60 | 61 | func (m *defaultVideoService) GetMyFavoriteVideos(ctx context.Context, in *MyFavoriteVideosReq, opts ...grpc.CallOption) (*MyFavoriteVideosResp, error) { 62 | client := videoSvcPb.NewVideoServiceClient(m.cli.Conn()) 63 | return client.GetMyFavoriteVideos(ctx, in, opts...) 64 | } 65 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/config.tpl: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import {{.authImport}} 4 | 5 | type Config struct { 6 | rest.RestConf 7 | {{.auth}} 8 | {{.jwtTrans}} 9 | } 10 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/context.tpl: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | {{.configImport}} 5 | ) 6 | 7 | type ServiceContext struct { 8 | Config {{.config}} 9 | {{.middleware}} 10 | } 11 | 12 | func NewServiceContext(c {{.config}}) *ServiceContext { 13 | return &ServiceContext{ 14 | Config: c, 15 | {{.middlewareAssignment}} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/etc.tpl: -------------------------------------------------------------------------------- 1 | Name: {{.serviceName}} 2 | Host: {{.host}} 3 | Port: {{.port}} 4 | 5 | Log: 6 | Encoding: plain 7 | 8 | DB: 9 | DataSource: username:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai 10 | CacheConf: 11 | - Host: localhost:6379 12 | Pass: Pass 13 | # ----- 一键生成proto文件 sql2pb----- 14 | #sql2pb -go_package ./pb -package pb -host localhost -user user -password password -port 3306 \ 15 | # -schema tablename -service_name xxx > xxx.proto 16 | # --- docker ---- 17 | # goctl docker -go user.go 18 | 19 | # ---- model ---- 20 | # modeldir=.. 21 | # # 数据库配置 22 | # host=localhost 23 | # port=3306 24 | # dbname=douyin 25 | # tablename1=user 26 | # tablename2=user_data 27 | # username=username 28 | # passwd=passwd 29 | # echo "开始连接库:$dbname 的表:$tablename1" 30 | # goctl model mysql datasource -url="${username}:${passwd}@tcp(${host}:${port})/${dbname}" -table="${tablename1}" -dir="${modeldir}" -------------------------------------------------------------------------------- /tpl/1.3.5/api/handler.tpl: -------------------------------------------------------------------------------- 1 | package {{.PkgName}} 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/zeromicro/go-zero/rest/httpx" 7 | {{.ImportPackages}} 8 | ) 9 | 10 | func {{.HandlerName}}(svcCtx *svc.ServiceContext) http.HandlerFunc { 11 | return func(w http.ResponseWriter, r *http.Request) { 12 | {{if .HasRequest}}var req types.{{.RequestType}} 13 | if err := httpx.Parse(r, &req); err != nil { 14 | httpx.Error(w, err) 15 | return 16 | } 17 | 18 | {{end}}l := {{.LogicName}}.New{{.LogicType}}(r.Context(), svcCtx) 19 | {{if .HasResp}}resp, {{end}}err := l.{{.Call}}({{if .HasRequest}}&req{{end}}) 20 | if err != nil { 21 | httpx.Error(w, err) 22 | } else { 23 | {{if .HasResp}}httpx.OkJson(w, resp){{else}}httpx.Ok(w){{end}} 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/logic.tpl: -------------------------------------------------------------------------------- 1 | package {{.pkgName}} 2 | 3 | import ( 4 | {{.imports}} 5 | ) 6 | 7 | type {{.logic}} struct { 8 | logx.Logger 9 | ctx context.Context 10 | svcCtx *svc.ServiceContext 11 | } 12 | 13 | func New{{.logic}}(ctx context.Context, svcCtx *svc.ServiceContext) *{{.logic}} { 14 | return &{{.logic}}{ 15 | Logger: logx.WithContext(ctx), 16 | ctx: ctx, 17 | svcCtx: svcCtx, 18 | } 19 | } 20 | 21 | func (l *{{.logic}}) {{.function}}({{.request}}) {{.responseType}} { 22 | // todo: add your logic here and delete this line 23 | 24 | {{.returnString}} 25 | } 26 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/main.tpl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | {{.importPackages}} 8 | ) 9 | 10 | var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file") 11 | 12 | func main() { 13 | flag.Parse() 14 | 15 | var c config.Config 16 | conf.MustLoad(*configFile, &c) 17 | 18 | ctx := svc.NewServiceContext(c) 19 | server := rest.MustNewServer(c.RestConf) 20 | defer server.Stop() 21 | 22 | handler.RegisterHandlers(server, ctx) 23 | 24 | fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) 25 | server.Start() 26 | } 27 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/middleware.tpl: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import "net/http" 4 | 5 | type {{.name}} struct { 6 | } 7 | 8 | func New{{.name}}() *{{.name}} { 9 | return &{{.name}}{} 10 | } 11 | 12 | func (m *{{.name}})Handle(next http.HandlerFunc) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | // TODO generate middleware implement function, delete after code implementation 15 | 16 | // Passthrough to next handler if need 17 | next(w, r) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/route-addition.tpl: -------------------------------------------------------------------------------- 1 | 2 | server.AddRoutes( 3 | {{.routes}} {{.jwt}}{{.signature}} {{.prefix}} {{.timeout}} 4 | ) 5 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/routes.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT. 2 | package handler 3 | 4 | import ( 5 | "net/http"{{if .hasTimeout}} 6 | "time"{{end}} 7 | 8 | {{.importPackages}} 9 | ) 10 | 11 | func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { 12 | {{.routesAdditions}} 13 | } 14 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/template.tpl: -------------------------------------------------------------------------------- 1 | syntax = "v1" 2 | 3 | info ( 4 | title: // TODO: add title 5 | desc: // TODO: add description 6 | author: "{{.gitUser}}" 7 | email: "{{.gitEmail}}" 8 | ) 9 | 10 | type request { 11 | // TODO: add members here and delete this comment 12 | } 13 | 14 | type response { 15 | // TODO: add members here and delete this comment 16 | } 17 | 18 | service {{.serviceName}} { 19 | @handler GetUser // TODO: set handler name and delete this comment 20 | get /usersInfo/id/:userId(request) returns(response) 21 | 22 | @handler CreateUser // TODO: set handler name and delete this comment 23 | post /usersInfo/create(request) 24 | } 25 | -------------------------------------------------------------------------------- /tpl/1.3.5/api/types.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT. 2 | package types{{if .containsTime}} 3 | import ( 4 | "time" 5 | ){{end}} 6 | {{.types}} 7 | -------------------------------------------------------------------------------- /tpl/1.3.5/docker/docker.tpl: -------------------------------------------------------------------------------- 1 | FROM golang:{{.Version}}alpine AS builder 2 | 3 | LABEL stage=gobuilder 4 | 5 | ENV CGO_ENABLED 0 6 | {{if .Chinese}}ENV GOPROXY https://goproxy.cn,direct 7 | {{end}}{{if .HasTimezone}} 8 | RUN apk update --no-cache && apk add --no-cache tzdata 9 | {{end}} 10 | WORKDIR /build 11 | 12 | ADD go.mod . 13 | ADD go.sum . 14 | RUN go mod download 15 | COPY . . 16 | {{if .Argument}}COPY {{.GoRelPath}}/etc /app/etc 17 | {{end}}RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoRelPath}}/{{.GoFile}} 18 | 19 | 20 | FROM {{.BaseImage}} 21 | 22 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 23 | {{if .HasTimezone}}COPY --from=builder /usr/share/zoneinfo/{{.Timezone}} /usr/share/zoneinfo/{{.Timezone}} 24 | ENV TZ {{.Timezone}} 25 | {{end}} 26 | WORKDIR /app 27 | COPY --from=builder /app/{{.ExeFile}} /app/{{.ExeFile}}{{if .Argument}} 28 | COPY --from=builder /app/etc /app/etc{{end}} 29 | {{if .HasPort}} 30 | EXPOSE {{.Port}} 31 | {{end}} 32 | CMD ["./{{.ExeFile}}"{{.Argument}}] 33 | -------------------------------------------------------------------------------- /tpl/1.3.5/kube/deployment.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{.Name}} 5 | namespace: {{.Namespace}} 6 | labels: 7 | app: {{.Name}} 8 | spec: 9 | replicas: {{.Replicas}} 10 | revisionHistoryLimit: {{.Revisions}} 11 | selector: 12 | matchLabels: 13 | app: {{.Name}} 14 | template: 15 | metadata: 16 | labels: 17 | app: {{.Name}} 18 | spec:{{if .ServiceAccount}} 19 | serviceAccountName: {{.ServiceAccount}}{{end}} 20 | containers: 21 | - name: {{.Name}} 22 | image: {{.Image}} 23 | lifecycle: 24 | preStop: 25 | exec: 26 | command: ["sh","-c","sleep 5"] 27 | ports: 28 | - containerPort: {{.Port}} 29 | readinessProbe: 30 | tcpSocket: 31 | port: {{.Port}} 32 | initialDelaySeconds: 5 33 | periodSeconds: 10 34 | livenessProbe: 35 | tcpSocket: 36 | port: {{.Port}} 37 | initialDelaySeconds: 15 38 | periodSeconds: 20 39 | resources: 40 | requests: 41 | cpu: {{.RequestCpu}}m 42 | memory: {{.RequestMem}}Mi 43 | limits: 44 | cpu: {{.LimitCpu}}m 45 | memory: {{.LimitMem}}Mi 46 | volumeMounts: 47 | - name: timezone 48 | mountPath: /etc/localtime 49 | {{if .Secret}}imagePullSecrets: 50 | - name: {{.Secret}} 51 | {{end}}volumes: 52 | - name: timezone 53 | hostPath: 54 | path: /usr/share/zoneinfo/Asia/Shanghai 55 | 56 | --- 57 | 58 | apiVersion: v1 59 | kind: Service 60 | metadata: 61 | name: {{.Name}}-svc 62 | namespace: {{.Namespace}} 63 | spec: 64 | ports: 65 | {{if .UseNodePort}}- nodePort: {{.NodePort}} 66 | port: {{.Port}} 67 | protocol: TCP 68 | targetPort: {{.Port}} 69 | type: NodePort{{else}}- port: {{.Port}}{{end}} 70 | selector: 71 | app: {{.Name}} 72 | 73 | --- 74 | 75 | apiVersion: autoscaling/v2beta1 76 | kind: HorizontalPodAutoscaler 77 | metadata: 78 | name: {{.Name}}-hpa-c 79 | namespace: {{.Namespace}} 80 | labels: 81 | app: {{.Name}}-hpa-c 82 | spec: 83 | scaleTargetRef: 84 | apiVersion: apps/v1 85 | kind: Deployment 86 | name: {{.Name}} 87 | minReplicas: {{.MinReplicas}} 88 | maxReplicas: {{.MaxReplicas}} 89 | metrics: 90 | - type: Resource 91 | resource: 92 | name: cpu 93 | targetAverageUtilization: 80 94 | 95 | --- 96 | 97 | apiVersion: autoscaling/v2beta1 98 | kind: HorizontalPodAutoscaler 99 | metadata: 100 | name: {{.Name}}-hpa-m 101 | namespace: {{.Namespace}} 102 | labels: 103 | app: {{.Name}}-hpa-m 104 | spec: 105 | scaleTargetRef: 106 | apiVersion: apps/v1 107 | kind: Deployment 108 | name: {{.Name}} 109 | minReplicas: {{.MinReplicas}} 110 | maxReplicas: {{.MaxReplicas}} 111 | metrics: 112 | - type: Resource 113 | resource: 114 | name: memory 115 | targetAverageUtilization: 80 116 | -------------------------------------------------------------------------------- /tpl/1.3.5/kube/job.tpl: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: {{.Name}} 5 | namespace: {{.Namespace}} 6 | spec: 7 | successfulJobsHistoryLimit: {{.SuccessfulJobsHistoryLimit}} 8 | schedule: "{{.Schedule}}" 9 | jobTemplate: 10 | spec: 11 | template: 12 | spec:{{if .ServiceAccount}} 13 | serviceAccountName: {{.ServiceAccount}}{{end}} 14 | {{end}}containers: 15 | - name: {{.Name}} 16 | image: # todo image url 17 | resources: 18 | requests: 19 | cpu: {{.RequestCpu}}m 20 | memory: {{.RequestMem}}Mi 21 | limits: 22 | cpu: {{.LimitCpu}}m 23 | memory: {{.LimitMem}}Mi 24 | command: 25 | - ./{{.ServiceName}} 26 | - -f 27 | - ./{{.Name}}.yaml 28 | volumeMounts: 29 | - name: timezone 30 | mountPath: /etc/localtime 31 | imagePullSecrets: 32 | - name: # registry secret, if no, remove this 33 | restartPolicy: OnFailure 34 | volumes: 35 | - name: timezone 36 | hostPath: 37 | path: /usr/share/zoneinfo/Asia/Shanghai 38 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/delete.tpl: -------------------------------------------------------------------------------- 1 | func (m *default{{.upperStartCamelObject}}Model) Delete(ctx context.Context, session sqlx.Session, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error { 2 | {{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne(ctx, {{.lowerStartCamelPrimaryKey}}) 3 | if err!=nil{ 4 | return err 5 | } 6 | 7 | {{end}} {{.keys}} 8 | _, err {{if .containsIndexCache}}={{else}}:={{end}} m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { 9 | query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table) 10 | if session!=nil{ 11 | return session.ExecCtx(ctx,query, {{.lowerStartCamelPrimaryKey}}) 12 | } 13 | return conn.ExecCtx(ctx, query, {{.lowerStartCamelPrimaryKey}}) 14 | }, {{.keyValues}}){{else}}query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table) 15 | if session!=nil{ 16 | _,err:= session.ExecCtx(ctx,query, {{.lowerStartCamelPrimaryKey}}) 17 | return err 18 | } 19 | _,err:=m.conn.ExecCtx(ctx, query, {{.lowerStartCamelPrimaryKey}}){{end}} 20 | return err 21 | } 22 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/err.tpl: -------------------------------------------------------------------------------- 1 | package {{.pkg}} 2 | 3 | import "github.com/zeromicro/go-zero/core/stores/sqlx" 4 | 5 | var ErrNotFound = sqlx.ErrNotFound 6 | 7 | const ( // 增加是1 取消是0 8 | ActionADD int64 = 1 9 | ActionCancel int64 = 0 10 | ) 11 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/field.tpl: -------------------------------------------------------------------------------- 1 | {{.name}} {{.type}} {{.tag}} {{if .hasComment}}// {{.comment}}{{end}} -------------------------------------------------------------------------------- /tpl/1.3.5/model/find-one-by-field-extra-method.tpl: -------------------------------------------------------------------------------- 1 | func (m *default{{.upperStartCamelObject}}Model) formatPrimary(primary interface{}) string { 2 | return fmt.Sprintf("%s%v", {{.primaryKeyLeft}}, primary) 3 | } 4 | func (m *default{{.upperStartCamelObject}}Model) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error { 5 | query := fmt.Sprintf("select %s from %s where {{.originalPrimaryField}} = {{if .postgreSql}}$1{{else}}?{{end}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table ) 6 | return conn.QueryRowCtx(ctx, v, query, primary,globalkey.DelStateNo) 7 | } 8 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/find-one-by-field.tpl: -------------------------------------------------------------------------------- 1 | 2 | func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) { 3 | {{if .withCache}}{{.cacheKey}} 4 | var resp {{.upperStartCamelObject}} 5 | err := m.QueryRowIndexCtx(ctx, &resp, {{.cacheKeyVariable}}, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { 6 | query := fmt.Sprintf("select %s from %s where {{.originalField}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table) 7 | if err := conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelField}},globalkey.DelStateNo); err != nil { 8 | return nil, err 9 | } 10 | return resp.{{.upperStartCamelPrimaryKey}}, nil 11 | }, m.queryPrimary) 12 | switch err { 13 | case nil: 14 | return &resp, nil 15 | case sqlc.ErrNotFound: 16 | return nil, ErrNotFound 17 | default: 18 | return nil, err 19 | } 20 | }{{else}}var resp {{.upperStartCamelObject}} 21 | query := fmt.Sprintf("select %s from %s where {{.originalField}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table ) 22 | err := m.conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelField}},globalkey.DelStateNo) 23 | switch err { 24 | case nil: 25 | return &resp, nil 26 | case sqlc.ErrNotFound: 27 | return nil, ErrNotFound 28 | default: 29 | return nil, err 30 | } 31 | }{{end}} 32 | 33 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/find-one.tpl: -------------------------------------------------------------------------------- 1 | func (m *default{{.upperStartCamelObject}}Model) FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) { 2 | {{if .withCache}}{{.cacheKey}} 3 | var resp {{.upperStartCamelObject}} 4 | err := m.QueryRowCtx(ctx, &resp, {{.cacheKeyVariable}}, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error { 5 | query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table) 6 | return conn.QueryRowCtx(ctx, v, query, {{.lowerStartCamelPrimaryKey}},globalkey.DelStateNo) 7 | }) 8 | switch err { 9 | case nil: 10 | return &resp, nil 11 | case sqlc.ErrNotFound: 12 | return nil, ErrNotFound 13 | default: 14 | return nil, err 15 | }{{else}}query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} and del_state = ? limit 1", {{.lowerStartCamelObject}}Rows, m.table) 16 | var resp {{.upperStartCamelObject}} 17 | err := m.conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelPrimaryKey}},globalkey.DelStateNo) 18 | switch err { 19 | case nil: 20 | return &resp, nil 21 | case sqlc.ErrNotFound: 22 | return nil, ErrNotFound 23 | default: 24 | return nil, err 25 | }{{end}} 26 | } 27 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/import-no-cache.tpl: -------------------------------------------------------------------------------- 1 | import ( 2 | "context" 3 | "database/sql" 4 | "fmt" 5 | "strings" 6 | {{if .time}}"time"{{end}} 7 | 8 | "douyin/common/globalkey" 9 | 10 | "github.com/zeromicro/go-zero/core/stores/builder" 11 | "github.com/zeromicro/go-zero/core/stores/sqlc" 12 | "github.com/zeromicro/go-zero/core/stores/sqlx" 13 | "github.com/zeromicro/go-zero/core/stringx" 14 | ) 15 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/import.tpl: -------------------------------------------------------------------------------- 1 | import ( 2 | "context" 3 | "database/sql" 4 | "fmt" 5 | "strings" 6 | {{if .time}}"time"{{end}} 7 | 8 | "douyin/common/globalkey" 9 | 10 | "github.com/zeromicro/go-zero/core/stores/builder" 11 | "github.com/zeromicro/go-zero/core/stores/cache" 12 | "github.com/zeromicro/go-zero/core/stores/sqlc" 13 | "github.com/zeromicro/go-zero/core/stores/sqlx" 14 | "github.com/zeromicro/go-zero/core/stringx" 15 | ) 16 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/insert.tpl: -------------------------------------------------------------------------------- 1 | 2 | func (m *default{{.upperStartCamelObject}}Model) Insert(ctx context.Context,session sqlx.Session, data *{{.upperStartCamelObject}}) (sql.Result,error) { 3 | // data.DeletedTime = time.Unix(0,0) 4 | {{if .withCache}}{{.keys}} 5 | return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { 6 | query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet) 7 | if session != nil{ 8 | return session.ExecCtx(ctx,query,{{.expressionValues}}) 9 | } 10 | return conn.ExecCtx(ctx, query, {{.expressionValues}}) 11 | }, {{.keyValues}}){{else}} 12 | query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet) 13 | if session != nil{ 14 | return session.ExecCtx(ctx,query,{{.expressionValues}}) 15 | } 16 | return m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}} 17 | } 18 | 19 | func (m *default{{.upperStartCamelObject}}Model) InsertOrUpdate(ctx context.Context, session sqlx.Session, field string, setStatus string, userId, objId, opt int64) (sql.Result, error) { 20 | return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { 21 | query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%d,%d,?) ON DUPLICATE KEY UPDATE %s=?", m.table, field, userId, objId, setStatus) 22 | if session != nil { 23 | return session.ExecCtx(ctx, query, opt, opt) 24 | } 25 | return conn.ExecCtx(ctx, query, opt, opt) 26 | }) 27 | } -------------------------------------------------------------------------------- /tpl/1.3.5/model/interface-delete.tpl: -------------------------------------------------------------------------------- 1 | Delete(ctx context.Context,session sqlx.Session, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error 2 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/interface-find-one-by-field.tpl: -------------------------------------------------------------------------------- 1 | FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) -------------------------------------------------------------------------------- /tpl/1.3.5/model/interface-find-one.tpl: -------------------------------------------------------------------------------- 1 | FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) -------------------------------------------------------------------------------- /tpl/1.3.5/model/interface-insert.tpl: -------------------------------------------------------------------------------- 1 | Insert(ctx context.Context, session sqlx.Session,data *{{.upperStartCamelObject}}) (sql.Result,error) 2 | InsertOrUpdate(ctx context.Context, session sqlx.Session, field string, setStatus string, userId, objId, opt int64) (sql.Result, error) -------------------------------------------------------------------------------- /tpl/1.3.5/model/interface-update.tpl: -------------------------------------------------------------------------------- 1 | Update(ctx context.Context,session sqlx.Session, data *{{.upperStartCamelObject}}) (sql.Result, error) 2 | UpdateStatus(ctx context.Context,session sqlx.Session, key string, idx string,actionType,id int64) (sql.Result,error) 3 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/model-gen.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by goctl. DO NOT EDIT! 2 | 3 | package {{.pkg}} 4 | {{.imports}} 5 | {{.vars}} 6 | {{.types}} 7 | {{.new}} 8 | {{.insert}} 9 | {{.find}} 10 | {{.update}} 11 | {{.delete}} 12 | {{.extraMethod}} 13 | {{.tableName}} 14 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/model-new.tpl: -------------------------------------------------------------------------------- 1 | 2 | func new{{.upperStartCamelObject}}Model(conn sqlx.SqlConn{{if .withCache}}, c cache.CacheConf{{end}}) *default{{.upperStartCamelObject}}Model { 3 | return &default{{.upperStartCamelObject}}Model{ 4 | {{if .withCache}}CachedConn: sqlc.NewConn(conn, c){{else}}conn:conn{{end}}, 5 | table: {{.table}}, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/table-name.tpl: -------------------------------------------------------------------------------- 1 | 2 | func (m *default{{.upperStartCamelObject}}Model) tableName() string { 3 | return m.table 4 | } 5 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/tag.tpl: -------------------------------------------------------------------------------- 1 | `db:"{{.field}}"` -------------------------------------------------------------------------------- /tpl/1.3.5/model/types.tpl: -------------------------------------------------------------------------------- 1 | 2 | type ( 3 | {{.lowerStartCamelObject}}Model interface{ 4 | {{.method}} 5 | } 6 | 7 | default{{.upperStartCamelObject}}Model struct { 8 | {{if .withCache}}sqlc.CachedConn{{else}}conn sqlx.SqlConn{{end}} 9 | table string 10 | } 11 | 12 | {{.upperStartCamelObject}} struct { 13 | {{.fields}} 14 | } 15 | ) 16 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/update.tpl: -------------------------------------------------------------------------------- 1 | 2 | func (m *default{{.upperStartCamelObject}}Model) Update(ctx context.Context,session sqlx.Session, data *{{.upperStartCamelObject}}) (sql.Result,error) { 3 | {{if .withCache}}{{.keys}} 4 | return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { 5 | query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder) 6 | if session != nil{ 7 | return session.ExecCtx(ctx,query, {{.expressionValues}}) 8 | } 9 | return conn.ExecCtx(ctx, query, {{.expressionValues}}) 10 | }, {{.keyValues}}){{else}}query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder) 11 | if session != nil{ 12 | return session.ExecCtx(ctx,query, {{.expressionValues}}) 13 | } 14 | return m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}} 15 | } 16 | 17 | func (m *default{{.upperStartCamelObject}}Model) UpdateStatus(ctx context.Context,session sqlx.Session, key string, idx string,actionType,id int64) (sql.Result,error) { 18 | 19 | return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { 20 | query := fmt.Sprintf("UPDATE %s SET %s=%s+? WHERE %s=?", m.table, key, key, idx) 21 | if session != nil { 22 | return session.ExecCtx(ctx, query, actionType, id) 23 | } 24 | return conn.ExecCtx(ctx, query, actionType, id) 25 | }) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tpl/1.3.5/model/var.tpl: -------------------------------------------------------------------------------- 1 | 2 | var ( 3 | {{.lowerStartCamelObject}}FieldNames = builder.RawFieldNames(&{{.upperStartCamelObject}}{}{{if .postgreSql}},true{{end}}) 4 | {{.lowerStartCamelObject}}Rows = strings.Join({{.lowerStartCamelObject}}FieldNames, ",") 5 | {{.lowerStartCamelObject}}RowsExpectAutoSet = {{if .postgreSql}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "create_time", "update_time"), ","){{else}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "`create_time`", "`update_time`"), ","){{end}} 6 | {{.lowerStartCamelObject}}RowsWithPlaceHolder = {{if .postgreSql}}builder.PostgreSqlJoin(stringx.Remove({{.lowerStartCamelObject}}FieldNames, "{{.originalPrimaryKey}}", "create_time", "update_time")){{else}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, "{{.originalPrimaryKey}}", "`create_time`", "`update_time`"), "=?,") + "=?"{{end}} 7 | 8 | {{if .withCache}}{{.cacheKeys}}{{end}} 9 | ) 10 | 11 | -------------------------------------------------------------------------------- /tpl/1.3.5/mongo/err.tpl: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "errors" 4 | 5 | var ErrNotFound = errors.New("not found") 6 | var ErrInvalidObjectId = errors.New("invalid objectId") 7 | -------------------------------------------------------------------------------- /tpl/1.3.5/mongo/model.tpl: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/globalsign/mgo/bson" 7 | {{if .CacheConf}}cachec "github.com/zeromicro/go-zero/core/stores/cache" 8 | "github.com/zeromicro/go-zero/core/stores/mongoc"{{else}}"github.com/zeromicro/go-zero/core/stores/mongo"{{end}} 9 | ) 10 | 11 | {{if .CacheConf}}var prefix{{.Type}}CacheKey = "cache:{{.Type}}:"{{end}} 12 | 13 | type {{.Type}}Model interface{ 14 | Insert(ctx context.Context,data *{{.Type}}) error 15 | FindOne(ctx context.Context,id string) (*{{.Type}}, error) 16 | Update(ctx context.Context,data *{{.Type}}) error 17 | Delete(ctx context.Context,id string) error 18 | } 19 | 20 | type default{{.Type}}Model struct { 21 | {{if .CacheConf}}*mongoc.Model{{else}}*mongo.Model{{end}} 22 | } 23 | 24 | func New{{.Type}}Model(url, collection string{{if .CacheConf}}, c cachec.CacheConf{{end}}) {{.Type}}Model { 25 | return &default{{.Type}}Model{ 26 | Model: {{if .CacheConf}}mongoc.MustNewModel(url, collection, c){{else}}mongo.MustNewModel(url, collection){{end}}, 27 | } 28 | } 29 | 30 | 31 | func (m *default{{.Type}}Model) Insert(ctx context.Context, data *{{.Type}}) error { 32 | if !data.ID.Valid() { 33 | data.ID = bson.NewObjectId() 34 | } 35 | 36 | session, err := m.TakeSession() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | defer m.PutSession(session) 42 | return m.GetCollection(session).Insert(data) 43 | } 44 | 45 | func (m *default{{.Type}}Model) FindOne(ctx context.Context, id string) (*{{.Type}}, error) { 46 | if !bson.IsObjectIdHex(id) { 47 | return nil, ErrInvalidObjectId 48 | } 49 | 50 | session, err := m.TakeSession() 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | defer m.PutSession(session) 56 | var data {{.Type}} 57 | {{if .CacheConf}}key := prefix{{.Type}}CacheKey + id 58 | err = m.GetCollection(session).FindOneId(&data, key, bson.ObjectIdHex(id)) 59 | {{- else}} 60 | err = m.GetCollection(session).FindId(bson.ObjectIdHex(id)).One(&data) 61 | {{- end}} 62 | switch err { 63 | case nil: 64 | return &data,nil 65 | case {{if .CacheConf}}mongoc.ErrNotFound{{else}}mongo.ErrNotFound{{end}}: 66 | return nil,ErrNotFound 67 | default: 68 | return nil,err 69 | } 70 | } 71 | 72 | func (m *default{{.Type}}Model) Update(ctx context.Context, data *{{.Type}}) error { 73 | session, err := m.TakeSession() 74 | if err != nil { 75 | return err 76 | } 77 | 78 | defer m.PutSession(session) 79 | {{if .CacheConf}}key := prefix{{.Type}}CacheKey + data.ID.Hex() 80 | return m.GetCollection(session).UpdateId(data.ID, data, key) 81 | {{- else}} 82 | return m.GetCollection(session).UpdateId(data.ID, data) 83 | {{- end}} 84 | } 85 | 86 | func (m *default{{.Type}}Model) Delete(ctx context.Context, id string) error { 87 | session, err := m.TakeSession() 88 | if err != nil { 89 | return err 90 | } 91 | 92 | defer m.PutSession(session) 93 | {{if .CacheConf}}key := prefix{{.Type}}CacheKey + id 94 | return m.GetCollection(session).RemoveId(bson.ObjectIdHex(id), key) 95 | {{- else}} 96 | return m.GetCollection(session).RemoveId(bson.ObjectIdHex(id)) 97 | {{- end}} 98 | } 99 | -------------------------------------------------------------------------------- /tpl/1.3.5/newapi/newtemplate.tpl: -------------------------------------------------------------------------------- 1 | type Request { 2 | Name string `path:"name,options=you|me"` 3 | } 4 | 5 | type Response { 6 | Message string `json:"message"` 7 | } 8 | 9 | service {{.name}}-api { 10 | @handler {{.handler}}Handler 11 | get /from/:name(Request) returns (Response) 12 | } 13 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/call-func.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{if .hasComment}}{{.comment}}{{end}} 3 | func (m *default{{.serviceName}}) {{.method}}(ctx context.Context{{if .hasReq}}, in *{{.pbRequest}}{{end}}, opts ...grpc.CallOption) ({{if .notStream}}*{{.pbResponse}}, {{else}}{{.streamBody}},{{end}} error) { 4 | client := {{if .isCallPkgSameToGrpcPkg}}{{else}}{{.package}}.{{end}}New{{.rpcServiceName}}Client(m.cli.Conn()) 5 | return client.{{.method}}(ctx{{if .hasReq}}, in{{end}}, opts...) 6 | } 7 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/call-interface-func.tpl: -------------------------------------------------------------------------------- 1 | {{if .hasComment}}{{.comment}} 2 | {{end}}{{.method}}(ctx context.Context{{if .hasReq}}, in *{{.pbRequest}}{{end}}, opts ...grpc.CallOption) ({{if .notStream}}*{{.pbResponse}}, {{else}}{{.streamBody}},{{end}} error) -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/call.tpl: -------------------------------------------------------------------------------- 1 | {{.head}} 2 | 3 | package {{.filePackage}} 4 | 5 | import ( 6 | "context" 7 | 8 | {{.pbPackage}} 9 | {{if ne .pbPackage .protoGoPackage}}{{.protoGoPackage}}{{end}} 10 | 11 | "github.com/zeromicro/go-zero/zrpc" 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | type ( 16 | {{.alias}} 17 | 18 | {{.serviceName}} interface { 19 | {{.interface}} 20 | } 21 | 22 | default{{.serviceName}} struct { 23 | cli zrpc.Client 24 | } 25 | ) 26 | 27 | func New{{.serviceName}}(cli zrpc.Client) {{.serviceName}} { 28 | return &default{{.serviceName}}{ 29 | cli: cli, 30 | } 31 | } 32 | 33 | {{.functions}} 34 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/config.tpl: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/zeromicro/go-zero/zrpc" 4 | 5 | type Config struct { 6 | zrpc.RpcServerConf 7 | } 8 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/etc.tpl: -------------------------------------------------------------------------------- 1 | Name: {{.serviceName}}.rpc 2 | ListenOn: 0.0.0.0:9999 3 | # Mode 使用 dev可以通过 grpcui -plaintext localhost:9999 进行调试 4 | Mode: dev 5 | #注册中心 6 | Etcd: 7 | Hosts: 8 | - 127.0.0.1:2379 9 | Key: {{.serviceName}}.rpc 10 | #如果api不通过etcd,而是直接连接rpc服务 11 | #注意是rpc客户端不是rpc服务端需要配置 Endpoints: 12 | # - 127.0.0.1:9999 13 | Log: 14 | Encoding: plain 15 | 16 | DB: 17 | DataSource: username:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai 18 | CacheConf: 19 | - Host: localhost:6379 20 | Pass: Pass 21 | # ----- 一键生成proto文件 sql2pb----- 22 | #sql2pb -go_package ./pb -package pb -host localhost -user user -password password -port 3306 \ 23 | # -schema tablename -service_name xxx > xxx.proto 24 | # --- docker ---- 25 | # goctl docker -go user.go 26 | 27 | # ---- model ---- 28 | # modeldir=.. 29 | # # 数据库配置 30 | # host=localhost 31 | # port=3306 32 | # dbname=douyin 33 | # tablename=user 34 | # username=username 35 | # passwd=passwd 36 | # echo "开始连接库:$dbname 的表:$tablename" 37 | # goctl model mysql datasource -url="${username}:${passwd}@tcp(${host}:${port})/${dbname}" -table="${tablename}" -dir="${modeldir}" 38 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/logic-func.tpl: -------------------------------------------------------------------------------- 1 | {{if .hasComment}}{{.comment}}{{end}} 2 | func (l *{{.logicName}}) {{.method}} ({{if .hasReq}}in {{.request}}{{if .stream}},stream {{.streamBody}}{{end}}{{else}}stream {{.streamBody}}{{end}}) ({{if .hasReply}}{{.response}},{{end}} error) { 3 | // todo: add your logic here and delete this line 4 | 5 | return {{if .hasReply}}&{{.responseType}}{},{{end}} nil 6 | } 7 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/logic.tpl: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "context" 5 | 6 | {{.imports}} 7 | 8 | "github.com/zeromicro/go-zero/core/logx" 9 | ) 10 | 11 | type {{.logicName}} struct { 12 | ctx context.Context 13 | svcCtx *svc.ServiceContext 14 | logx.Logger 15 | } 16 | 17 | func New{{.logicName}}(ctx context.Context,svcCtx *svc.ServiceContext) *{{.logicName}} { 18 | return &{{.logicName}}{ 19 | ctx: ctx, 20 | svcCtx: svcCtx, 21 | Logger: logx.WithContext(ctx), 22 | } 23 | } 24 | {{.functions}} 25 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/main.tpl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | {{.imports}} 8 | 9 | "github.com/zeromicro/go-zero/core/conf" 10 | "github.com/zeromicro/go-zero/core/service" 11 | "github.com/zeromicro/go-zero/zrpc" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/reflection" 14 | ) 15 | 16 | var configFile = flag.String("f", "etc/{{.serviceName}}.yaml", "the config file") 17 | 18 | func main() { 19 | flag.Parse() 20 | 21 | var c config.Config 22 | conf.MustLoad(*configFile, &c) 23 | ctx := svc.NewServiceContext(c) 24 | svr := server.New{{.serviceNew}}Server(ctx) 25 | 26 | s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) { 27 | {{.pkg}}.Register{{.service}}Server(grpcServer, svr) 28 | 29 | if c.Mode == service.DevMode || c.Mode == service.TestMode { 30 | reflection.Register(grpcServer) 31 | } 32 | }) 33 | defer s.Stop() 34 | 35 | fmt.Printf("Starting rpc server at %s...\n", c.ListenOn) 36 | s.Start() 37 | } 38 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/server-func.tpl: -------------------------------------------------------------------------------- 1 | 2 | {{if .hasComment}}{{.comment}}{{end}} 3 | func (s *{{.server}}Server) {{.method}} ({{if .notStream}}ctx context.Context,{{if .hasReq}} in {{.request}}{{end}}{{else}}{{if .hasReq}} in {{.request}},{{end}}stream {{.streamBody}}{{end}}) ({{if .notStream}}{{.response}},{{end}}error) { 4 | l := logic.New{{.logicName}}({{if .notStream}}ctx,{{else}}stream.Context(),{{end}}s.svcCtx) 5 | return l.{{.method}}({{if .hasReq}}in{{if .stream}} ,stream{{end}}{{else}}{{if .stream}}stream{{end}}{{end}}) 6 | } 7 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/server.tpl: -------------------------------------------------------------------------------- 1 | {{.head}} 2 | 3 | package server 4 | 5 | import ( 6 | {{if .notStream}}"context"{{end}} 7 | 8 | {{.imports}} 9 | ) 10 | 11 | type {{.server}}Server struct { 12 | svcCtx *svc.ServiceContext 13 | {{.unimplementedServer}} 14 | } 15 | 16 | func New{{.server}}Server(svcCtx *svc.ServiceContext) *{{.server}}Server { 17 | return &{{.server}}Server{ 18 | svcCtx: svcCtx, 19 | } 20 | } 21 | 22 | {{.funcs}} 23 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/svc.tpl: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import {{.imports}} 4 | 5 | type ServiceContext struct { 6 | Config config.Config 7 | } 8 | 9 | func NewServiceContext(c config.Config) *ServiceContext { 10 | return &ServiceContext{ 11 | Config:c, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tpl/1.3.5/rpc/template.tpl: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package {{.package}}; 4 | option go_package="./{{.package}}"; 5 | 6 | message Request { 7 | string ping = 1; 8 | } 9 | 10 | message Response { 11 | string pong = 1; 12 | } 13 | 14 | service {{.serviceName}} { 15 | rpc Ping(Request) returns(Response); 16 | } 17 | --------------------------------------------------------------------------------