├── .idea ├── .gitignore ├── modules.xml ├── tiktok.iml └── workspace.xml ├── .vscode └── launch.json ├── go.mod ├── go.sum ├── go ├── config │ ├── DataSource.go │ ├── SecretKeyForJwt.go │ ├── datasource_test.go │ ├── parameter.go │ ├── readme.md │ ├── redis.go │ ├── redis_test.go │ └── tencent_cos.go ├── controller │ ├── commentController.go │ ├── followController.go │ ├── likeController.go │ ├── messageController.go │ ├── readme.md │ ├── userController.go │ └── videoController.go ├── main.go ├── middle │ ├── base.go │ └── jwt │ │ ├── Verify.go │ │ ├── jwt_test.go │ │ └── token.go ├── model │ ├── InitDb.go │ ├── base.go │ ├── comment.go │ ├── comment_test.go │ ├── follow.go │ ├── follow_test.go │ ├── like.go │ ├── like_test.go │ ├── message.go │ ├── message_test.go │ ├── readme.md │ ├── user.go │ ├── video.go │ └── video_test.go ├── route │ └── load.go ├── service │ ├── baseService.go │ ├── commentService.go │ ├── followService.go │ ├── likeService.go │ ├── messageService.go │ ├── readme.md │ ├── userService.go │ └── videoService.go └── util │ ├── checkFile.go │ ├── checkFile_test.go │ ├── filter.go │ ├── filter_test.go │ ├── key.txt │ ├── log.go │ └── log_test.go ├── readme.md └── resources ├── initial.sql └── insertData.sql /.idea/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WenTesla/tiktok/140f0d6625698620d47a6d6badced4e425377a14/.idea/.gitignore -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/tiktok.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 21 | 22 | 23 | 24 | 29 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 40 | 41 | 42 | 43 | 46 | { 47 | "keyToString": { 48 | "DefaultGoTemplateProperty": "Go File", 49 | "RunOnceActivity.OpenProjectViewOnStart": "true", 50 | "RunOnceActivity.ShowReadmeOnStart": "true", 51 | "RunOnceActivity.go.formatter.settings.were.checked": "true", 52 | "RunOnceActivity.go.migrated.go.modules.settings": "true", 53 | "RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true", 54 | "WebServerToolWindowFactoryState": "false", 55 | "configurable..is.expanded": "false", 56 | "configurable.GoLibrariesConfigurable.is.expanded": "true", 57 | "go.import.settings.migrated": "true", 58 | "last_opened_file_path": "H:/redis_course", 59 | "node.js.detected.package.eslint": "true", 60 | "node.js.selected.package.eslint": "(autodetect)", 61 | "nodejs_package_manager_path": "npm", 62 | "settings.editor.selected.configurable": "preferences.intentionPowerPack" 63 | }, 64 | "keyToStringList": { 65 | "DatabaseDriversLRU": [ 66 | "mysql", 67 | "redis" 68 | ], 69 | "RunConfigurationTargetLRU": [ 70 | "f8ed14ed-78ef-4118-87b7-1ecf6c80c7b5" 71 | ], 72 | "com.intellij.ide.scratch.ScratchImplUtil$2/新建临时文件": [ 73 | "go" 74 | ] 75 | } 76 | } 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 168 | 169 | 190 | 191 | 192 | 215 | 216 | true 217 | 218 | 219 | 220 | 221 | 222 | file://$PROJECT_DIR$/go/util/filter.go 223 | 61 224 | 226 | 227 | file://$PROJECT_DIR$/go/model/follow.go 228 | 92 229 | 231 | 232 | file://$PROJECT_DIR$/go/util/checkFile_test.go 233 | 16 234 | 236 | 237 | file://$PROJECT_DIR$/go/util/checkFile.go 238 | 95 239 | 241 | 242 | file://$PROJECT_DIR$/go/middle/jwt/jwt_test.go 243 | 33 244 | 246 | 247 | file://$APPLICATION_CONFIG_DIR$/scratches/scratch_1.go 248 | 8 249 | 251 | 252 | file://$PROJECT_DIR$/go/controller/videoController.go 253 | 63 254 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}" 13 | }, 14 | { 15 | "name": "Attach to Process", 16 | "type": "go", 17 | "request": "attach", 18 | "mode": "local", 19 | "processId": 0 20 | }, 21 | { 22 | "name": "Launch Chrome", 23 | "request": "launch", 24 | "type": "chrome", 25 | "url": "http://localhost:8080", 26 | "webRoot": "${workspaceFolder}" 27 | }, 28 | { 29 | "name": "Launch Package", 30 | "type": "go", 31 | "request": "launch", 32 | "mode": "auto", 33 | "program": "${fileDirname}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module tiktok 2 | 3 | go 1.19 4 | 5 | // 引入gin框架 6 | require ( 7 | github.com/gin-contrib/sse v0.1.0 // indirect 8 | github.com/gin-gonic/gin v1.8.2 9 | github.com/go-playground/locales v0.14.1 // indirect 10 | github.com/go-playground/universal-translator v0.18.0 // indirect 11 | github.com/go-playground/validator/v10 v10.11.1 // indirect 12 | github.com/goccy/go-json v0.10.0 // indirect 13 | github.com/json-iterator/go v1.1.12 // indirect 14 | github.com/leodido/go-urn v1.2.1 // indirect 15 | github.com/mattn/go-isatty v0.0.17 // indirect 16 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 17 | github.com/modern-go/reflect2 v1.0.2 // indirect 18 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 19 | github.com/ugorji/go/codec v1.2.8 // indirect 20 | golang.org/x/crypto v0.5.0 // indirect 21 | golang.org/x/net v0.5.0 // indirect 22 | golang.org/x/sys v0.4.0 // indirect 23 | golang.org/x/text v0.6.0 // indirect 24 | google.golang.org/protobuf v1.28.1 // indirect 25 | gopkg.in/yaml.v2 v2.4.0 // indirect 26 | ) 27 | 28 | // 引入数据库连接驱动和gorm 29 | require ( 30 | gorm.io/driver/mysql v1.4.5 31 | gorm.io/gorm v1.24.3 32 | ) 33 | 34 | // 引入 35 | require ( 36 | github.com/go-sql-driver/mysql v1.7.0 // indirect 37 | github.com/jinzhu/inflection v1.0.0 // indirect 38 | github.com/jinzhu/now v1.1.5 // indirect 39 | ) 40 | 41 | // 引入go的jwt 42 | require github.com/golang-jwt/jwt/v4 v4.4.3 43 | 44 | // 引入腾讯云的依赖 45 | require ( 46 | github.com/clbanning/mxj v1.8.4 // indirect 47 | github.com/google/go-querystring v1.1.0 // indirect 48 | github.com/mitchellh/mapstructure v1.5.0 // indirect 49 | github.com/mozillazg/go-httpheader v0.3.1 // indirect 50 | github.com/tencentyun/cos-go-sdk-v5 v0.7.41 // indirect 51 | ) 52 | 53 | // 引入redis 54 | require github.com/go-redis/redis v6.15.9+incompatible // indirect 55 | 56 | require golang.org/x/time v0.3.0 // indirect 57 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= 2 | github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= 3 | github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= 4 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 9 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 10 | github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY= 11 | github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398= 12 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 13 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 14 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 15 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 16 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 17 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 18 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 19 | github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= 20 | github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 21 | github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= 22 | github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 23 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 24 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 25 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= 26 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 27 | github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU= 28 | github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 29 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 30 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 31 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 32 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 34 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 35 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 36 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 37 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 38 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 39 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 40 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 41 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 42 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 43 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 44 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 45 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 46 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 47 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 48 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 49 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 50 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 51 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 52 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 53 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 54 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 55 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 56 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 57 | github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 58 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 59 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 60 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 61 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 62 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 63 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 64 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 65 | github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= 66 | github.com/mozillazg/go-httpheader v0.3.1 h1:IRP+HFrMX2SlwY9riuio7raffXUpzAosHtZu25BSJok= 67 | github.com/mozillazg/go-httpheader v0.3.1/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK08bt6gKSReGMqtdA= 68 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 69 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 70 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 71 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 72 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 73 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 74 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 75 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 76 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 77 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 78 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 79 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 80 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 81 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 82 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 83 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 84 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 85 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 86 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= 87 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4= 88 | github.com/tencentyun/cos-go-sdk-v5 v0.7.41 h1:iU0Li/Np78H4SBna0ECQoF3mpgi6ImLXU+doGzPFXGc= 89 | github.com/tencentyun/cos-go-sdk-v5 v0.7.41/go.mod h1:4dCEtLHGh8QPxHEkgq+nFaky7yZxQuYwgSJM87icDaw= 90 | github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0= 91 | github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 92 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 93 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= 94 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 95 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 96 | golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= 97 | golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 98 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 102 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= 104 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 106 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 107 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 108 | golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= 109 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 110 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 111 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 112 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 113 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 114 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 115 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 116 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 117 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 118 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 119 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 120 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 121 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 122 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 123 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 124 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 125 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 126 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 127 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 128 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 129 | gorm.io/driver/mysql v1.4.5 h1:u1lytId4+o9dDaNcPCFzNv7h6wvmc92UjNk3z8enSBU= 130 | gorm.io/driver/mysql v1.4.5/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc= 131 | gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 132 | gorm.io/gorm v1.24.3 h1:WL2ifUmzR/SLp85CSURAfybcHnGZ+yLSGSxgYXlFBHg= 133 | gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= 134 | -------------------------------------------------------------------------------- /go/config/DataSource.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "gorm.io/driver/mysql" 5 | "gorm.io/gorm" 6 | "tiktok/go/util" 7 | ) 8 | 9 | //var Db *gorm.DB 10 | 11 | // 初始化并返回数据链接 12 | func InitDataSource() *gorm.DB { 13 | // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情 14 | // 本地数据库-推荐-自己建表 -自己替换 15 | dsn := "root:zhang134679@tcp(127.0.0.1:3306)/tiktok?charset=utf8mb4&parseTime=True&loc=Local" 16 | // 阿里云数据库 非常慢 17 | //dsn := "tiktok:tiktok@tcp(rm-2ze62585lf96k7285mo.mysql.rds.aliyuncs.com)/tiktok?charset=utf8mb4&parseTime=True&loc=Local" 18 | Db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ 19 | PrepareStmt: true, //缓存预编译语句 20 | }) 21 | if err != nil { 22 | util.LogFatal(err.Error()) 23 | panic(err) 24 | return nil 25 | } 26 | println("连接成功") 27 | return Db 28 | } 29 | -------------------------------------------------------------------------------- /go/config/SecretKeyForJwt.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const Secret = "this_is_a_secret_key" 4 | -------------------------------------------------------------------------------- /go/config/datasource_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "testing" 4 | 5 | func TestInitDataSource(t *testing.T) { 6 | InitDataSource() 7 | } 8 | -------------------------------------------------------------------------------- /go/config/parameter.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // 假数据 4 | 5 | const MockAvatarUrl = "https://xingqiu-tuchuang-1256524210.cos.ap-shanghai.myqcloud.com/12640/20230206133334.png" 6 | 7 | const VideoMinCount = 5 8 | 9 | const VideoCount = 10 10 | 11 | const VideoMaxCount = 30 12 | 13 | const CommentCount = 20 14 | 15 | const MessageCount = 10 16 | 17 | const TokenIsNotExist = "Token不存在-用户未登录" 18 | 19 | const TokenIsNotMatchUserId = "Token与用户不匹配" 20 | 21 | const TokenIsExpire = "Token过期" 22 | 23 | const TokenParseErr = "Token解析错误" 24 | 25 | const Success = "成功!" 26 | 27 | const Fail = "失败" 28 | 29 | const RequestFail = "请求参数错误" 30 | 31 | const RequestTooFast = "请求过于频繁" 32 | 33 | const RequestParameterIsNull = "请求参数为空" 34 | 35 | const DatabaseError = "内部数据库错误" 36 | 37 | const SuccessWithEnglish = "Success" 38 | 39 | const FailWithEnglish = "Fail" 40 | 41 | const RequestError = "请求错误" 42 | -------------------------------------------------------------------------------- /go/config/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WenTesla/tiktok/140f0d6625698620d47a6d6badced4e425377a14/go/config/readme.md -------------------------------------------------------------------------------- /go/config/redis.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/go-redis/redis" 5 | "tiktok/go/util" 6 | ) 7 | 8 | // 声明一个全局的redisDb变量 9 | //var redisDb *redis.Client 10 | 11 | // 根据redis配置初始化一个客户端 12 | 13 | func InitRedisClient() (redisDb *redis.Client, err error) { 14 | // 替换你的账号密码 15 | redisDb = redis.NewClient(&redis.Options{ 16 | Addr: "43.138.126.75:6388", // redis地址 17 | Password: "redis", // redis密码,没有则留空 18 | DB: 0, // 默认数据库,默认是0 19 | }) 20 | 21 | //通过 *redis.Client.Ping() 来检查是否成功连接到了redis服务器 22 | _, err = redisDb.Ping().Result() 23 | if err != nil { 24 | util.LogFatal(err.Error()) 25 | panic(err) 26 | return nil, err 27 | } 28 | util.Log("redis 初始化成功") 29 | return redisDb, nil 30 | } 31 | 32 | // 选择redis的数据库 -> i 33 | 34 | func RedisClient(i int) (*redis.Client, error) { 35 | redisDb := redis.NewClient(&redis.Options{ 36 | Addr: "43.138.126.75:6388", // redis地址 37 | Password: "redis", // redis密码,没有则留空 38 | DB: i, // 默认数据库,默认是0 39 | }) 40 | //通过 *redis.Client.Ping() 来检查是否成功连接到了redis服务器 41 | _, err := redisDb.Ping().Result() 42 | if err != nil { 43 | util.LogFatal(err.Error()) 44 | panic(err) 45 | return nil, err 46 | } 47 | return redisDb, nil 48 | } 49 | -------------------------------------------------------------------------------- /go/config/redis_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | type Comment struct { 11 | ID int64 // 评论id 12 | UserId int64 // 用户Id 13 | VideoId int64 //视频Id 14 | Text string // 评论内容 15 | IsCancel int64 // 是否取消 16 | CreateTime time.Time `gorm:"column:createTime"` // 创建时间 17 | } 18 | 19 | // CommentInfo 评论详细信息 20 | type CommentInfo struct { 21 | Content string `json:"content"` // 评论内容 22 | CreateDate string `json:"create_date"` // 评论发布日期,格式 mm-dd 23 | ID int64 `json:"id"` // 评论id 24 | UserInfo UserInfo `json:"user"` // 评论用户的具体信息 25 | } 26 | 27 | // UserInfo 最详细的信息 不与数据库模型对应 28 | type UserInfo struct { 29 | Id int64 `json:"id,omitempty"` //主键 30 | Name string `json:"name,omitempty"` //昵称 31 | FollowCount int64 `json:"follow_count"` //关注总数 32 | FollowerCount int64 `json:"follower_count"` //粉丝总数 33 | IsFollow bool `json:"is_follow"` //是否关注 34 | AvatarUrl string `json:"avatar,omitempty"` //用户的url 35 | TotalFavorited int64 `json:"total_favorited,omitempty"` //获赞数量 36 | WorkCount int64 `json:"work_count,omitempty"` //作品数量 37 | FavoriteCount int64 `json:"favorite_count,omitempty"` //点赞数量 38 | } 39 | 40 | func TestRedis(t *testing.T) { 41 | 42 | _, err := InitRedisClient() 43 | if err != nil { 44 | fmt.Println(err) 45 | } 46 | } 47 | 48 | type guo struct { 49 | Name string 50 | Age int 51 | } 52 | 53 | //func (g *guo) MarshalBinary() (data []byte, err error) { 54 | // return json.Marshal(g) 55 | //} 56 | // 57 | //func (g *guo) UnmarshalBinary(data []byte) (err error) { 58 | // return json.Unmarshal(data, g) 59 | //} 60 | 61 | func TestAddRedis(t *testing.T) { 62 | db, _ := InitRedisClient() 63 | comment := CommentInfo{ 64 | Content: "111", 65 | CreateDate: time.Now().String(), 66 | ID: 1, 67 | UserInfo: UserInfo{ 68 | Id: 2, 69 | Name: "22", 70 | FollowCount: 3, 71 | FollowerCount: 4, 72 | IsFollow: false, 73 | AvatarUrl: "132", 74 | TotalFavorited: 1, 75 | WorkCount: 2, 76 | FavoriteCount: 3, 77 | }, 78 | } 79 | bytes, err := json.Marshal(comment) 80 | if err != nil { 81 | fmt.Println(err) 82 | } 83 | db.LPush("commentInfo", bytes) 84 | 85 | } 86 | 87 | func TestQueryRedis(t *testing.T) { 88 | db, _ := InitRedisClient() 89 | push := db.LRange("commentInfo", 0, -1) 90 | result, err := push.Result() 91 | if err != nil { 92 | fmt.Println(err) 93 | } 94 | s := result[3] 95 | bytes := []byte(s) 96 | g := CommentInfo{} 97 | json.Unmarshal(bytes, &g) 98 | fmt.Printf("%v", g) 99 | } 100 | 101 | func TestTimeRedis(t *testing.T) { 102 | db, _ := RedisClient(10) 103 | //i, _ := db.Get("111").Int() 104 | db.Incr("111") 105 | //fmt.Println(i) 106 | } 107 | 108 | func TestIs(t *testing.T) { 109 | client, _ := RedisClient(1) 110 | client.SAdd("1", "2", "3", "4") 111 | member, _ := client.SIsMember("1", "1").Result() 112 | fmt.Println(member) 113 | } 114 | 115 | func TestCount(t *testing.T) { 116 | client, _ := RedisClient(4) 117 | card, _ := client.SCard("423").Result() 118 | fmt.Printf("%T", card) 119 | } 120 | -------------------------------------------------------------------------------- /go/config/tencent_cos.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // 替换Id 4 | 5 | const SecretId = "xxxxxxxxxxxxxxxxxxx" 6 | 7 | // 替换Key 8 | 9 | const SecretKey = "xxxxxxxxxxxxxxxxxxx" 10 | 11 | // 替换你的oss的域名 12 | 13 | const CosUrl = "https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com" 14 | 15 | const ReplaceSuffix = ".jpg" 16 | 17 | const CosUrlAnd = "https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/" 18 | -------------------------------------------------------------------------------- /go/controller/commentController.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "log" 6 | "net/http" 7 | "strconv" 8 | "tiktok/go/config" 9 | "tiktok/go/model" 10 | "tiktok/go/service" 11 | ) 12 | 13 | type CommentListResponse struct { 14 | model.BaseResponse 15 | CommentList []model.CommentInfo `json:"comment_list"` // 评论列表 16 | } 17 | type CommentResponse struct { 18 | model.BaseResponse 19 | CommentInfo model.CommentInfo `json:"comment"` // 评论成功返回评论内容,不需要重新拉取整个列表 20 | } 21 | 22 | // 用户评论 登录用户对视频进行评论 23 | 24 | func CommentVideo(c *gin.Context) { 25 | // 获取登录用户 26 | user_id, exists := c.Get("Id") 27 | if !exists { 28 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.TokenIsNotExist)) 29 | return 30 | } 31 | // 32 | userId := int64(user_id.(float64)) 33 | video_id := c.Query("video_id") 34 | videoId, _ := strconv.ParseInt(video_id, 10, 64) 35 | action_type := c.Query("action_type") 36 | 37 | switch action_type { 38 | case "1": 39 | log.Printf("在%d的视频上发布评论", video_id) 40 | content := c.Query("comment_text") 41 | commentInfo, err := service.CreateCommentService(userId, videoId, content) 42 | if err != nil { 43 | c.JSON(http.StatusBadRequest, CommentResponse{ 44 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 45 | CommentInfo: model.CommentInfo{}, 46 | }) 47 | return 48 | } else { 49 | c.JSON(http.StatusOK, CommentResponse{ 50 | BaseResponse: model.BaseResponseInstance.Success(), 51 | CommentInfo: commentInfo, 52 | }) 53 | return 54 | } 55 | case "2": 56 | log.Printf("在%d的视频上删除评论", video_id) 57 | comment_id := c.Query("comment_id") 58 | commentId, _ := strconv.ParseInt(comment_id, 10, 64) 59 | isDelete, err := service.CancelCommentService(commentId, userId, videoId) 60 | if err != nil { 61 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(err.Error())) 62 | return 63 | } else { 64 | if !isDelete { 65 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.Fail()) 66 | return 67 | } else { 68 | c.JSON(http.StatusOK, model.BaseResponseInstance.Success()) 69 | return 70 | } 71 | } 72 | } 73 | 74 | println(userId) 75 | } 76 | 77 | // 评论列表 78 | 79 | func CommentList(c *gin.Context) { 80 | // 获取视频的id 81 | video_id := c.Query("video_id") 82 | // 判空 83 | if video_id == "" { 84 | c.JSON(http.StatusOK, CommentListResponse{ 85 | BaseResponse: model.BaseResponseInstance.FailMsg(config.RequestParameterIsNull), 86 | CommentList: nil, 87 | }) 88 | return 89 | } 90 | // 转换 91 | videoId, err := strconv.ParseInt(video_id, 10, 64) 92 | if err != nil { 93 | c.JSON(http.StatusBadRequest, CommentListResponse{ 94 | BaseResponse: model.BaseResponseInstance.Fail(), 95 | CommentList: nil, 96 | }) 97 | return 98 | } 99 | // 调用服务 100 | commentInfos, err := service.CommentListService(videoId) 101 | if err != nil { 102 | c.JSON(http.StatusBadRequest, CommentListResponse{ 103 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 104 | CommentList: nil, 105 | }) 106 | return 107 | } 108 | c.JSON(http.StatusOK, CommentListResponse{ 109 | BaseResponse: model.BaseResponseInstance.Success(), 110 | CommentList: commentInfos, 111 | }) 112 | return 113 | } 114 | -------------------------------------------------------------------------------- /go/controller/followController.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "strconv" 7 | "tiktok/go/config" 8 | "tiktok/go/model" 9 | "tiktok/go/service" 10 | ) 11 | 12 | type FollowListResponse struct { 13 | model.BaseResponse 14 | UserList []model.UserInfo `json:"user_list"` // 用户信息列表 15 | } 16 | 17 | type FollowerListResponse struct { 18 | model.BaseResponse 19 | UserList []model.UserInfo `json:"user_list"` // 用户信息列表 20 | } 21 | 22 | // 关注操作 23 | 24 | func FollowUser(c *gin.Context) { 25 | // 对方用户的id 26 | to_user_id := c.Query("to_user_id") 27 | // 取token 28 | user_id, exists := c.Get("Id") 29 | if !exists { 30 | c.JSON(http.StatusBadRequest, model.BaseResponse{ 31 | StatusCode: -1, 32 | StatusMsg: config.TokenIsNotExist, 33 | }) 34 | return 35 | } 36 | // 取类型 37 | action_type := c.Query("action_type") 38 | // 判断为空 39 | if to_user_id == "" || user_id == "" || action_type == "" { 40 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestParameterIsNull)) 41 | return 42 | } 43 | // 转换 44 | toUserId, _ := strconv.ParseInt(to_user_id, 10, 64) 45 | userId := int64(user_id.(float64)) 46 | if userId == toUserId { 47 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg("不能自己关注自己")) 48 | return 49 | } 50 | var actionType bool 51 | // 关注 52 | if action_type == "1" { 53 | actionType = true 54 | } else if action_type == "2" { 55 | actionType = false 56 | } else { 57 | c.JSON(http.StatusNotFound, model.BaseResponseInstance.FailMsg(config.RequestFail)) 58 | return 59 | } 60 | pass, err := service.FollowUserService(userId, toUserId, actionType) 61 | if err != nil { 62 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(err.Error())) 63 | return 64 | } 65 | if !pass { 66 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.Fail()) 67 | return 68 | } else { 69 | c.JSON(http.StatusOK, model.BaseResponseInstance.Success()) 70 | return 71 | } 72 | } 73 | 74 | // 关注列表 75 | 76 | func FollowList(c *gin.Context) { 77 | user_id := c.Query("user_id") 78 | userId, _ := strconv.ParseInt(user_id, 10, 64) 79 | // 取token 80 | loginUserId, exists := c.Get("Id") 81 | // 判空 82 | if user_id == "" || loginUserId == "" { 83 | c.JSON(http.StatusBadRequest, FollowListResponse{ 84 | BaseResponse: model.BaseResponseInstance.FailMsg(config.RequestParameterIsNull), 85 | UserList: nil, 86 | }) 87 | return 88 | } 89 | if !exists { 90 | // 不存在即未登录 91 | userInfos, err := service.FollowListService(userId) 92 | if err != nil { 93 | c.JSON(http.StatusBadRequest, FollowListResponse{ 94 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 95 | UserList: nil, 96 | }) 97 | return 98 | } 99 | c.JSON(http.StatusOK, FollowListResponse{ 100 | BaseResponse: model.BaseResponseInstance.Success(), 101 | UserList: userInfos, 102 | }) 103 | return 104 | } else { 105 | loginUserId := int64(loginUserId.(float64)) 106 | userInfos, err := service.FollowListServiceWithUserId(userId, loginUserId) 107 | if err != nil { 108 | c.JSON(http.StatusBadRequest, FollowListResponse{ 109 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 110 | UserList: nil, 111 | }) 112 | return 113 | } 114 | c.JSON(http.StatusOK, FollowListResponse{ 115 | BaseResponse: model.BaseResponseInstance.Success(), 116 | UserList: userInfos, 117 | }) 118 | return 119 | } 120 | 121 | } 122 | 123 | // 粉丝列表 124 | 125 | func FollowerList(c *gin.Context) { 126 | user_id := c.Query("user_id") 127 | userId, _ := strconv.ParseInt(user_id, 10, 64) 128 | if user_id == "" { 129 | c.JSON(http.StatusBadRequest, FollowerListResponse{ 130 | BaseResponse: model.BaseResponseInstance.FailMsg(config.RequestParameterIsNull), 131 | UserList: nil, 132 | }) 133 | return 134 | } 135 | // 取token 136 | loginUserId, exists := c.Get("Id") 137 | if !exists { 138 | userInfos, err := service.FollowerListService(userId) 139 | if err != nil { 140 | c.JSON(http.StatusBadRequest, FollowerListResponse{ 141 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 142 | UserList: nil, 143 | }) 144 | return 145 | } 146 | c.JSON(http.StatusOK, FollowerListResponse{ 147 | BaseResponse: model.BaseResponseInstance.Success(), 148 | UserList: userInfos, 149 | }) 150 | return 151 | } else { 152 | loginUserId := int64(loginUserId.(float64)) 153 | userInfos, err := service.FollowerListServiceWithUserId(userId, loginUserId) 154 | if err != nil { 155 | c.JSON(http.StatusBadRequest, FollowerListResponse{ 156 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 157 | UserList: nil, 158 | }) 159 | return 160 | } 161 | c.JSON(http.StatusOK, FollowerListResponse{ 162 | BaseResponse: model.BaseResponseInstance.Success(), 163 | UserList: userInfos, 164 | }) 165 | return 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /go/controller/likeController.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "strconv" 7 | "tiktok/go/config" 8 | "tiktok/go/model" 9 | "tiktok/go/service" 10 | ) 11 | 12 | type userFavoriteListResponse struct { 13 | model.BaseResponse 14 | VideoList []model.Video `json:"video_list"` 15 | } 16 | 17 | // 点赞行为: 1-点赞,2-取消点赞 18 | 19 | func LikeVideoByUserID(c *gin.Context) { 20 | video_id := c.Query("video_id") 21 | action_type := c.Query("action_type") 22 | // 判断是否为空 23 | if video_id == "" || action_type == "" { 24 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestParameterIsNull)) 25 | return 26 | } 27 | // 转类型 28 | id, _ := strconv.ParseInt(video_id, 10, 64) 29 | Type, _ := strconv.ParseInt(action_type, 10, 64) 30 | // 用于同步数据库 31 | switch Type { 32 | case 1: 33 | //设置为0 34 | Type = 0 35 | case 2: 36 | Type = 1 37 | default: 38 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestFail)) 39 | return 40 | } 41 | // 提取用户Id 42 | user_id, exists := c.Get("Id") 43 | if !exists { 44 | c.JSON(http.StatusNotFound, model.BaseResponseInstance.FailMsg(config.TokenIsNotExist)) 45 | return 46 | } 47 | userId := int64(user_id.(float64)) 48 | // 点赞Type为1 取消为2 49 | flag, err := service.LikeVideoByUserIDService(userId, id, Type) 50 | if err != nil { 51 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(err.Error())) 52 | return 53 | } 54 | if flag { 55 | c.JSON(http.StatusOK, model.BaseResponseInstance.Success()) 56 | return 57 | } 58 | } 59 | 60 | // 用户点赞列表 61 | 62 | func UserFavoriteList(c *gin.Context) { 63 | user_id := c.Query("user_id") 64 | // 判空 65 | if user_id == "" { 66 | c.JSON(http.StatusBadRequest, userFavoriteListResponse{ 67 | BaseResponse: model.BaseResponseInstance.FailMsg(config.RequestParameterIsNull), 68 | VideoList: []model.Video{}, 69 | }) 70 | return 71 | } 72 | userId, err := strconv.ParseInt(user_id, 10, 64) 73 | if err != nil { 74 | c.JSON(http.StatusBadRequest, userFavoriteListResponse{ 75 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 76 | VideoList: []model.Video{}, 77 | }) 78 | return 79 | } 80 | userFavoriteList, err := service.UserFavoriteListService(userId) 81 | if err != nil { 82 | c.JSON(http.StatusBadRequest, userFavoriteListResponse{ 83 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 84 | VideoList: []model.Video{}, 85 | }) 86 | return 87 | } else { 88 | c.JSON(http.StatusOK, userFavoriteListResponse{ 89 | BaseResponse: model.BaseResponseInstance.Success(), 90 | VideoList: userFavoriteList, 91 | }) 92 | return 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /go/controller/messageController.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "log" 6 | "net/http" 7 | "strconv" 8 | "tiktok/go/config" 9 | "tiktok/go/model" 10 | "tiktok/go/service" 11 | ) 12 | 13 | type FriendListResponse struct { 14 | model.BaseResponse 15 | UserList []model.FriendUser `json:"user_list"` // 用户列表 16 | } 17 | 18 | type MessageListResponse struct { 19 | model.BaseResponse 20 | MessageList []model.Message `json:"message_list"` 21 | } 22 | 23 | // 好友列表 24 | 25 | func FriendList(c *gin.Context) { 26 | user_id := c.Query("user_id") 27 | //userId, _ := strconv.ParseInt(user_id, 10, 64) 28 | if user_id == "" { 29 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestParameterIsNull)) 30 | return 31 | } 32 | log.Println(user_id) 33 | // 提取用户Id 34 | userid, exists := c.Get("Id") 35 | if !exists { 36 | c.JSON(http.StatusNotFound, model.BaseResponseInstance.FailMsg(config.TokenIsNotExist)) 37 | return 38 | } 39 | userId := int64(userid.(float64)) 40 | // 41 | log.Printf("%v", userId) 42 | friendUsers, err := service.FriendListService(userId) 43 | if err != nil { 44 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(err.Error())) 45 | return 46 | } 47 | c.JSON(http.StatusOK, FriendListResponse{ 48 | BaseResponse: model.BaseResponseInstance.Success(), 49 | UserList: friendUsers, 50 | }) 51 | return 52 | } 53 | 54 | // 聊天记录 点进去才能看到 55 | 56 | func MessageChat(c *gin.Context) { 57 | to_user_id := c.Query("to_user_id") 58 | if to_user_id == "" { 59 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestParameterIsNull)) 60 | return 61 | } 62 | toUserId, _ := strconv.ParseInt(to_user_id, 10, 64) 63 | log.Println(to_user_id) 64 | // 提取用户Id 65 | userid, exists := c.Get("Id") 66 | if !exists { 67 | c.JSON(http.StatusNotFound, model.BaseResponseInstance.FailMsg(config.TokenIsNotExist)) 68 | return 69 | } 70 | userId := int64(userid.(float64)) 71 | // 提取上次最新消息的时间 72 | pre_msg_time := c.Query("pre_msg_time") 73 | preMsgTime, err := strconv.ParseInt(pre_msg_time, 10, 64) 74 | if err != nil { 75 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(err.Error())) 76 | return 77 | } 78 | 79 | messages, err := service.MessageChatService(userId, toUserId, preMsgTime) 80 | if err != nil { 81 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(err.Error())) 82 | return 83 | } 84 | c.JSON(http.StatusOK, MessageListResponse{ 85 | BaseResponse: model.BaseResponseInstance.Success(), 86 | MessageList: messages, 87 | }) 88 | return 89 | } 90 | 91 | // MessageAction 发送消息 92 | func MessageAction(c *gin.Context) { 93 | // 提取用户Id 94 | userid, exists := c.Get("Id") 95 | if !exists { 96 | c.JSON(http.StatusNotFound, model.BaseResponseInstance.FailMsg(config.TokenIsNotExist)) 97 | return 98 | } 99 | userId := int64(userid.(float64)) 100 | // 获取toUser 101 | to_user_id := c.Query("to_user_id") 102 | toUserId, err := strconv.ParseInt(to_user_id, 10, 64) 103 | content := c.Query("content") 104 | actionType := c.Query("action_type") 105 | // 参数错误 106 | if actionType != "1" { 107 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestFail)) 108 | return 109 | } 110 | pass, err := service.MessageActionService(userId, toUserId, content) 111 | if err != nil { 112 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(err.Error())) 113 | return 114 | } 115 | if !pass { 116 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.Fail()) 117 | return 118 | } else { 119 | c.JSON(http.StatusOK, model.BaseResponseInstance.Success()) 120 | return 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /go/controller/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WenTesla/tiktok/140f0d6625698620d47a6d6badced4e425377a14/go/controller/readme.md -------------------------------------------------------------------------------- /go/controller/userController.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "tiktok/go/config" 7 | "tiktok/go/model" 8 | 9 | "github.com/gin-gonic/gin" 10 | 11 | "net/http" 12 | "tiktok/go/service" 13 | ) 14 | 15 | // 要大写,不然无法传入json成功 16 | type userRegisterResponse struct { 17 | model.BaseResponse 18 | UserId int64 `json:"user_id"` 19 | Token string `json:"token"` 20 | } 21 | type userLoginResponse struct { 22 | model.BaseResponse 23 | UserId int64 `json:"user_id"` 24 | Token string `json:"token"` 25 | } 26 | type douyinUserResponse struct { 27 | model.BaseResponse 28 | UserInfo *model.UserInfo `json:"user"` 29 | } 30 | 31 | // 用户注册 32 | 33 | func Register(c *gin.Context) { 34 | username := c.Query("username") 35 | password := c.Query("password") 36 | // 先判空 37 | if username == "" || password == "" { 38 | c.JSON(http.StatusBadRequest, userRegisterResponse{ 39 | BaseResponse: model.BaseResponseInstance.FailMsg("账号密码为空"), 40 | UserId: -1, 41 | Token: "", 42 | }) 43 | return 44 | } 45 | // 先校验参数长度 46 | if len(password) > 32 || len(password) <= 5 || len(username) > 32 { 47 | c.JSON(http.StatusBadRequest, userRegisterResponse{ 48 | BaseResponse: model.BaseResponseInstance.FailMsg("参数长度不正确"), 49 | UserId: -1, 50 | Token: "", 51 | }) 52 | return 53 | } 54 | password = service.Encryption(password) 55 | //先校验合法性 56 | Id, err := service.RegisterService(username, password) 57 | if err != nil { 58 | fmt.Println(err) 59 | c.JSON(http.StatusBadRequest, userRegisterResponse{ 60 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 61 | UserId: -1, 62 | Token: "", 63 | }) 64 | return 65 | } else { 66 | // 颁发token 67 | token, _ := service.GenerateTokenByName(username) 68 | // 成功返回 69 | c.JSON(http.StatusOK, userRegisterResponse{ 70 | BaseResponse: model.BaseResponseInstance.Success(), 71 | UserId: Id, 72 | Token: token, 73 | }) 74 | return 75 | } 76 | } 77 | 78 | // 用户登录 79 | 80 | func Login(c *gin.Context) { 81 | username := c.Query("username") 82 | password := c.Query("password") 83 | // 先判空 84 | if username == "" || password == "" { 85 | c.JSON(http.StatusBadRequest, userRegisterResponse{ 86 | BaseResponse: model.BaseResponseInstance.FailMsg("账号密码为空"), 87 | UserId: -1, 88 | Token: "", 89 | }) 90 | return 91 | } 92 | 93 | // 先校验参数 94 | if len(username) > 32 || len(password) > 32 || len(password) <= 5 { 95 | c.JSON(http.StatusBadRequest, userLoginResponse{ 96 | BaseResponse: model.BaseResponseInstance.FailMsg("参数长度不正确"), 97 | UserId: -1, 98 | Token: "", 99 | }) 100 | return 101 | } 102 | password = service.Encryption(password) 103 | fmt.Println(username, password) 104 | Id, err := service.LoginService(username, password) 105 | if err != nil { 106 | c.JSON(http.StatusBadRequest, userLoginResponse{ 107 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 108 | UserId: -1, 109 | Token: "", 110 | }) 111 | } else { 112 | // 颁发token 113 | token, _ := service.GenerateTokenByName(username) 114 | c.JSON(http.StatusOK, userLoginResponse{ 115 | BaseResponse: model.BaseResponseInstance.Success(), 116 | UserId: Id, 117 | Token: token, 118 | }) 119 | } 120 | 121 | } 122 | 123 | // 用户信息 124 | 125 | func UserInfo(c *gin.Context) { 126 | user_Id := c.Query("user_id") 127 | // 转换Id的类型 128 | userId, _ := strconv.ParseInt(user_Id, 10, 64) 129 | Id, exists := c.Get("Id") 130 | if exists != true { 131 | c.JSON(http.StatusNotFound, douyinUserResponse{ 132 | BaseResponse: model.BaseResponseInstance.FailMsg(config.TokenIsNotExist), 133 | }) 134 | return 135 | } 136 | if userId != int64(Id.(float64)) { 137 | c.JSON(http.StatusNotFound, douyinUserResponse{ 138 | BaseResponse: model.BaseResponseInstance.FailMsg(config.TokenIsNotMatchUserId), 139 | }) 140 | return 141 | } 142 | userInfo, err := service.UserService(userId) 143 | if err != nil { 144 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(err.Error())) 145 | return 146 | } else { 147 | c.JSON(http.StatusOK, douyinUserResponse{ 148 | BaseResponse: model.BaseResponseInstance.Success(), 149 | UserInfo: &userInfo, 150 | }) 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /go/controller/videoController.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "log" 6 | "net/http" 7 | "strconv" 8 | "tiktok/go/config" 9 | "tiktok/go/middle/jwt" 10 | "tiktok/go/model" 11 | "tiktok/go/service" 12 | "time" 13 | ) 14 | 15 | type VideoStreamModel struct { 16 | model.BaseResponse 17 | NextTime int64 `json:"next_time,omitempty"` // 本次返回的视频中,发布最早的时间,作为下次请求时的latest_time 18 | VideoList []model.Video `json:"video_list"` // 视频列表 19 | } 20 | 21 | type VideoPublishListResponse struct { 22 | model.BaseResponse 23 | VideoList []model.Video `json:"video_list"` // 用户发布的视频列表 24 | } 25 | 26 | type Stream struct { 27 | model.BaseResponse 28 | } 29 | 30 | // 定义变量 31 | 32 | // 视频流接口 33 | 34 | func VideoStream(c *gin.Context) { 35 | // 传入的参数 36 | input_time := c.Query("latest_time") 37 | log.Printf("获取的参数 %s", input_time) 38 | var last_time time.Time 39 | if len(input_time) != 0 { 40 | // 处理传入的时间戳(这里是毫秒的) 41 | temp, _ := strconv.ParseInt(input_time, 10, 64) 42 | temp /= 1000 43 | last_time = time.Unix(temp, 0) 44 | } else { 45 | last_time = time.Now() 46 | } 47 | log.Printf("获取的时间戳 %v", last_time) 48 | // 定义变量 49 | var err error 50 | var videos []model.Video 51 | //userId := 20053 52 | // 获取token的数据 53 | token := c.Query("token") 54 | // 未登录的情况下 55 | if len(token) == 0 { 56 | videos, err = service.VideoStreamService(last_time, -1) 57 | if err != nil { 58 | c.JSON(http.StatusBadRequest, VideoStreamModel{ 59 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 60 | VideoList: []model.Video{}, 61 | }) 62 | } 63 | // 获取发布最早的时间 作为下一条next参数 64 | nextTime, err := model.QueryNextTimeByVideoId(videos[len(videos)-1].ID) 65 | if err != nil { 66 | c.JSON(http.StatusBadRequest, VideoStreamModel{ 67 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 68 | VideoList: []model.Video{}, 69 | }) 70 | return 71 | } 72 | log.Printf("%v", videos) 73 | c.JSON(http.StatusOK, VideoStreamModel{ 74 | BaseResponse: model.BaseResponseInstance.Success(), 75 | NextTime: nextTime.UnixNano() / 1e6, 76 | VideoList: videos, 77 | }) 78 | return 79 | } 80 | // 解析token 81 | parseToken, _ := jwt.ParseToken(token) 82 | userId := int64(parseToken.(float64)) 83 | videos, err = service.VideoStreamService(last_time, userId) 84 | if err != nil { 85 | c.JSON(http.StatusBadRequest, VideoStreamModel{ 86 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 87 | VideoList: []model.Video{}, 88 | }) 89 | return 90 | } 91 | // 获取发布最早的时间 作为下一条next参数 这里有问题 92 | nextTime, err := model.QueryNextTimeByVideoId(videos[len(videos)-1].ID) 93 | if err != nil { 94 | c.JSON(http.StatusBadRequest, VideoStreamModel{ 95 | BaseResponse: model.BaseResponseInstance.FailMsg(err.Error()), 96 | VideoList: []model.Video{}, 97 | }) 98 | return 99 | } 100 | c.JSON(http.StatusOK, VideoStreamModel{ 101 | NextTime: nextTime.UnixNano() / 1e6, 102 | BaseResponse: model.BaseResponseInstance.Success(), 103 | VideoList: videos, 104 | }) 105 | } 106 | 107 | // 登录用户选择视频上传 108 | 109 | func VideoPublish(c *gin.Context) { 110 | file, err := c.FormFile("data") 111 | if err != nil { 112 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestFail)) 113 | return 114 | } 115 | // 参数判断空 116 | if file.Size == 0 { 117 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestParameterIsNull)) 118 | return 119 | } 120 | // 获取登录用户的id 121 | user_id, exists := c.Get("Id") 122 | if !exists { 123 | c.JSON(http.StatusNotFound, model.BaseResponseInstance.FailMsg(config.RequestFail)) 124 | return 125 | } 126 | // 转换 127 | //user_id.(float64) 128 | // 获取标题 129 | title := c.PostForm("title") 130 | // 上传视频 131 | err = service.PublishVideoService(file, int64(user_id.(float64)), title) 132 | if err != nil { 133 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(err.Error())) 134 | return 135 | } else { 136 | c.JSON(http.StatusOK, model.BaseResponseInstance.Success()) 137 | return 138 | } 139 | 140 | } 141 | 142 | // 发布列表 用户的视频发布列表,直接列出用户所有投稿过的视频 143 | 144 | func VideoList(c *gin.Context) { 145 | user_id := c.Query("user_id") 146 | Id, err := strconv.ParseInt(user_id, 10, 64) 147 | if err != nil { 148 | c.JSON(http.StatusBadRequest, VideoPublishListResponse{ 149 | BaseResponse: model.BaseResponseInstance.FailMsg(config.RequestFail), 150 | VideoList: nil, 151 | }) 152 | return 153 | } 154 | videos, err := service.VideoInfoByUserId(int(Id)) 155 | if err != nil { 156 | c.JSON(http.StatusNotFound, VideoPublishListResponse{ 157 | BaseResponse: model.BaseResponseInstance.FailMsg(config.DatabaseError), 158 | VideoList: nil, 159 | }) 160 | return 161 | } 162 | c.JSON(http.StatusOK, VideoPublishListResponse{ 163 | BaseResponse: model.BaseResponseInstance.Success(), 164 | VideoList: videos, 165 | }) 166 | } 167 | -------------------------------------------------------------------------------- /go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "os" 7 | "tiktok/go/config" 8 | "tiktok/go/util" 9 | 10 | "github.com/gin-gonic/gin" 11 | 12 | // "tiktok/go/config" 13 | "tiktok/go/route" 14 | ) 15 | 16 | /* 17 | 启动类 18 | */ 19 | func main() { 20 | //初始化项目 21 | initProject() 22 | // 1.创建路由 23 | r := gin.Default() 24 | // 2.绑定路由规则,执行的函数 25 | route.LoadRouter(r) 26 | // 3.监听端口,默认在8080 27 | // Run("里面不指定端口号默认为8080") 28 | err := r.Run(":8000") 29 | if err != nil { 30 | panic(errors.New("项目启动失败")) 31 | } 32 | } 33 | 34 | func initProject() { 35 | // mysql 初始化 36 | config.InitDataSource() 37 | // redis 初始化 38 | config.InitRedisClient() 39 | // 过滤器 40 | util.InitSensitiveFilter() 41 | 42 | // 设置日志 --取消注释即可创建日志文件 43 | f, _ := os.Create("resources/gin.log") // // 如果文件已存在,会将文件清空。 44 | gin.DefaultWriter = io.MultiWriter(f) 45 | //gin.DebugPrintRouteFunc() 46 | 47 | // 如果需要同时将日志写入文件和控制台,请使用以下代码。 48 | //gin.DefaultWriter = io.MultiWriter(f, os.Stdout) 49 | util.Log("服务器开启成功!") 50 | } 51 | -------------------------------------------------------------------------------- /go/middle/base.go: -------------------------------------------------------------------------------- 1 | package middle 2 | 3 | import "tiktok/go/config" 4 | 5 | // 用于限速和防止大量请求 6 | var FlowLimitRedisDbByIp, _ = config.RedisClient(10) 7 | 8 | var FlowLimitRedisDbByUserId, _ = config.RedisClient(11) 9 | -------------------------------------------------------------------------------- /go/middle/jwt/Verify.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "strconv" 8 | "tiktok/go/config" 9 | "tiktok/go/middle" 10 | "tiktok/go/model" 11 | "time" 12 | ) 13 | 14 | // 核实token,用于中间件的验证 15 | func VerifyToken(c *gin.Context) { 16 | token := c.Query("token") 17 | fmt.Printf("%v \t \n", token) 18 | if len(token) == 0 { 19 | ////错误 直接 20 | //c.Abort() 21 | ////返回json 22 | //c.JSON(http.StatusBadRequest, BasicResponse{ 23 | // StatusCode: -1, 24 | // StatusMsg: "未携带token", 25 | //}) 26 | 27 | // 未登录开启IP限速器 28 | remoteIP := c.RemoteIP() 29 | result, _ := middle.FlowLimitRedisDbByIp.Get(remoteIP).Int() 30 | if result == 0 { 31 | // 设置 32 | middle.FlowLimitRedisDbByIp.Set(remoteIP, 1, time.Minute) 33 | } else if result <= 60 { 34 | // +1 35 | middle.FlowLimitRedisDbByIp.Incr(remoteIP) 36 | } else { 37 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestTooFast)) 38 | c.Abort() 39 | } 40 | c.Next() 41 | return 42 | } 43 | Id, err := ParseToken(token) 44 | if err != nil { 45 | // 解析错误 46 | c.Abort() 47 | // 返回json 48 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.TokenIsNotExist)) 49 | } else { 50 | // 开启用户限速器 51 | //temp := int64(Id.(float64)) 52 | userId := strconv.FormatFloat(Id.(float64), 'f', 0, 64) 53 | // 登录开启Id限速器 54 | result, _ := middle.FlowLimitRedisDbByIp.Get(userId).Int() 55 | if result == 0 { 56 | // 设置 57 | middle.FlowLimitRedisDbByIp.Set(userId, 1, time.Minute) 58 | } else if result <= 60 { 59 | // +1 60 | middle.FlowLimitRedisDbByIp.Incr(userId) 61 | } else { 62 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.RequestTooFast)) 63 | c.Abort() 64 | } 65 | 66 | // 解析签发时间 67 | tokenTime, err := ParseTokenTime(token) 68 | if err != nil { 69 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.TokenParseErr)) 70 | } 71 | // 判断时间 72 | if GetDays(int64(tokenTime.(float64)), time.Now().Unix()) > 30 { 73 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.TokenIsExpire)) 74 | } 75 | // 解析正确 76 | //str := strconv.FormatFloat(Id, 'E', -1, 64) 77 | //strconv.ParseInt(str, 10, 64) 78 | c.Set("Id", Id) 79 | c.Next() 80 | } 81 | } 82 | 83 | // 通过post请求获取token 84 | 85 | func VerifyTokenByPost(c *gin.Context) { 86 | token := c.PostForm("token") 87 | fmt.Printf("%v \t \n", token) 88 | if len(token) == 0 { 89 | //错误 直接 90 | c.Abort() 91 | //返回json 92 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.TokenIsNotExist)) 93 | return 94 | } 95 | Id, err := ParseToken(token) 96 | if err != nil { 97 | // 解析错误 98 | c.Abort() 99 | // 返回json 100 | c.JSON(http.StatusBadRequest, model.BaseResponseInstance.FailMsg(config.TokenParseErr)) 101 | } else { 102 | // 解析正确 103 | //str := strconv.FormatFloat(Id, 'E', -1, 64) 104 | //strconv.ParseInt(str, 10, 64) 105 | c.Set("Id", Id) 106 | c.Next() 107 | } 108 | } 109 | 110 | func GetDays(start, end int64) int { 111 | startTime := time.Unix(start, 0) 112 | endTime := time.Unix(end, 0) 113 | sub := int(endTime.Sub(startTime).Hours()) 114 | days := sub / 24 115 | if (sub % 24) > 0 { 116 | days = days + 1 117 | } 118 | return days 119 | } 120 | -------------------------------------------------------------------------------- /go/middle/jwt/jwt_test.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/time/rate" 6 | "log" 7 | "net/http" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // 测试token的生成 13 | func TestSignToken(t *testing.T) { 14 | token := TestSignTokenFunction("1111") 15 | log.Println(token) 16 | } 17 | 18 | //func TestParseToken(t *testing.T) { 19 | // 20 | //} 21 | 22 | func TestToken(t *testing.T) { 23 | token := TestSignTokenFunction("1111") 24 | log.Println(token) 25 | parseToken, err := TestParseToken(token) 26 | fmt.Println(parseToken, err) 27 | } 28 | 29 | func TestParseTokenTime(t *testing.T) { 30 | time, err := ParseTokenTime("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaXNzIjoidGlrdG9rIiwibmJmIjoxNjc1MTU5ODM4LCJzdWIiOiLnlKjmiLdJZCJ9.sPKBddA3foAeVVH19QFNoDNIckUIIipfZOrGvKLHxQk") 31 | if err != nil { 32 | t.Fail() 33 | } 34 | fmt.Println(time) 35 | } 36 | 37 | func TestGetDays(t *testing.T) { 38 | days := GetDays(1676775698, 1675998098) 39 | fmt.Println(days) 40 | } 41 | 42 | func TestLimit(t *testing.T) { 43 | r := rate.Every(1 * time.Millisecond) 44 | limit := rate.NewLimiter(r, 10) 45 | http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { 46 | if limit.Allow() { 47 | fmt.Printf("请求成功,当前时间:%s\n", time.Now().Format("2006-01-02 15:04:05")) 48 | } else { 49 | fmt.Printf("请求成功,但是被限流了。。。\n") 50 | } 51 | }) 52 | 53 | _ = http.ListenAndServe(":8081", nil) 54 | } 55 | 56 | func GetApi() { 57 | api := "http://localhost:8081/" 58 | res, err := http.Get(api) 59 | if err != nil { 60 | panic(err) 61 | } 62 | defer res.Body.Close() 63 | 64 | if res.StatusCode == http.StatusOK { 65 | fmt.Printf("get api success\n") 66 | } 67 | } 68 | 69 | func Benchmark_Main(b *testing.B) { 70 | for i := 0; i < b.N; i++ { 71 | GetApi() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /go/middle/jwt/token.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang-jwt/jwt/v4" 6 | "tiktok/go/config" 7 | "tiktok/go/model" 8 | "time" 9 | ) 10 | 11 | // 提取密钥 12 | var jwtSecret = []byte(config.Secret) 13 | 14 | // 测试签名token 15 | func TestSignTokenFunction(Id string) string { 16 | // test 17 | 18 | // Create a new token object, specifying signing method and the claims 19 | // you would like it to contain. 20 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 21 | // sub (subject): 主题 22 | "sub": "用户Id来签发token", 23 | // 24 | "foo": "bar", 25 | //nbf (Not Before): 生效时间 26 | //"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(), 27 | "nbf": time.Now().Unix(), 28 | }) 29 | // 提取密钥 30 | var jwtSecret = []byte(config.Secret) 31 | // Sign and get the complete encoded token as a string using the secret 32 | tokenString, err := token.SignedString(jwtSecret) 33 | // 如果签名失败 34 | if err != nil { 35 | return "" 36 | } 37 | fmt.Println(tokenString, err) 38 | return tokenString 39 | } 40 | 41 | // 签名token 42 | func SignToken(user model.User) string { 43 | // dev 44 | Id := user.Id 45 | // Create a new token object, specifying signing method and the claims 46 | // you would like it to contain. 47 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 48 | // iss (issuer): 签发人 49 | "iss": "tiktok", 50 | // sub (subject): 主题 51 | "sub": "用户Id", 52 | // Id 53 | "id": Id, 54 | //nbf (Not Before): 生效时间 55 | "nbf": time.Now().Unix(), 56 | }) 57 | 58 | // Sign and get the complete encoded token as a string using the secret 59 | tokenString, err := token.SignedString(jwtSecret) 60 | // 如果签名失败 61 | if err != nil { 62 | return "签名失败" 63 | } 64 | fmt.Println(tokenString, err) 65 | return tokenString 66 | } 67 | 68 | // 测试解析token 69 | func TestParseToken(tokenString string) (interface{}, error) { 70 | // sample token string taken from the New example 71 | //tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU" 72 | 73 | // Parse takes the token string and a function for looking up the key. The latter is especially 74 | // useful if you use multiple keys for your application. The standard is to use 'kid' in the 75 | // head of the token to identify which key to use, but the parsed token (head and claims) is provided 76 | // to the callback, providing flexibility. 77 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 78 | // Don't forget to validate the alg is what you expect: 79 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 80 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 81 | } 82 | 83 | // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") 84 | return jwtSecret, nil 85 | }) 86 | 87 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 88 | fmt.Println(claims["foo"], claims["nbf"]) 89 | } else { 90 | fmt.Println(err) 91 | return false, err 92 | } 93 | return true, err 94 | } 95 | 96 | // 解析token 返回用户Id 97 | 98 | func ParseToken(tokenString string) (interface{}, error) { 99 | 100 | // sample token string taken from the New example 101 | //tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU" 102 | 103 | // Parse takes the token string and a function for looking up the key. The latter is especially 104 | // useful if you use multiple keys for your application. The standard is to use 'kid' in the 105 | // head of the token to identify which key to use, but the parsed token (head and claims) is provided 106 | // to the callback, providing flexibility. 107 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 108 | // Don't forget to validate the alg is what you expect: 109 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 110 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 111 | } 112 | 113 | // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") 114 | return jwtSecret, nil 115 | }) 116 | 117 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 118 | //fmt.Println(claims["foo"], claims["nbf"]) 119 | return claims["id"], nil 120 | } else { 121 | fmt.Println(err) 122 | return nil, err 123 | } 124 | //return true, err 125 | } 126 | 127 | // 返回token中的签发日期 128 | 129 | func ParseTokenTime(tokenString string) (any, error) { 130 | // sample token string taken from the New example 131 | //tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU" 132 | 133 | // Parse takes the token string and a function for looking up the key. The latter is especially 134 | // useful if you use multiple keys for your application. The standard is to use 'kid' in the 135 | // head of the token to identify which key to use, but the parsed token (head and claims) is provided 136 | // to the callback, providing flexibility. 137 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 138 | // Don't forget to validate the alg is what you expect: 139 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 140 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 141 | } 142 | 143 | // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") 144 | return jwtSecret, nil 145 | }) 146 | 147 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 148 | //fmt.Println(claims["foo"], claims["nbf"]) 149 | return claims["nbf"], nil 150 | } else { 151 | fmt.Println(err) 152 | return nil, err 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /go/model/InitDb.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gorm.io/driver/mysql" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | //var db *gorm.DB 9 | 10 | // 初始化并返回数据链接 11 | func Init() *gorm.DB { 12 | // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情 13 | dsn := "root:zhang134679@tcp(127.0.0.1:3306)/tiktok?charset=utf8mb4&parseTime=True&loc=Local" 14 | //dsn := "tiktok:tiktok@tcp(47.115.218.216:3306)/tiktok?charset=utf8mb4&parseTime=True&loc=Local" 15 | Db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ 16 | PrepareStmt: true, //缓存预编译语句 17 | }) 18 | if err != nil { 19 | panic(err) 20 | return nil 21 | } 22 | println("连接成功") 23 | return Db 24 | } 25 | -------------------------------------------------------------------------------- /go/model/base.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "tiktok/go/config" 4 | 5 | // 数据库连接池 6 | var db = config.InitDataSource() 7 | 8 | type BaseResponse struct { 9 | StatusCode int32 `json:"status_code"` 10 | StatusMsg string `json:"status_msg"` 11 | } 12 | 13 | var BaseResponseInstance = BaseResponse{} 14 | 15 | func (baseResponse *BaseResponse) Success() BaseResponse { 16 | baseResponse.StatusCode = 0 17 | baseResponse.StatusMsg = config.Success 18 | return BaseResponseInstance 19 | } 20 | 21 | func (baseResponse *BaseResponse) Fail() BaseResponse { 22 | baseResponse.StatusCode = -1 23 | baseResponse.StatusMsg = config.Fail 24 | return BaseResponseInstance 25 | } 26 | 27 | func (baseResponse *BaseResponse) SuccessMsg(msg string) BaseResponse { 28 | baseResponse.StatusCode = 0 29 | baseResponse.StatusMsg = msg 30 | return BaseResponseInstance 31 | } 32 | func (baseResponse *BaseResponse) FailMsg(msg string) BaseResponse { 33 | baseResponse.StatusCode = -1 34 | baseResponse.StatusMsg = msg 35 | return BaseResponseInstance 36 | } 37 | -------------------------------------------------------------------------------- /go/model/comment.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "errors" 5 | "tiktok/go/util" 6 | "time" 7 | ) 8 | 9 | type Comment struct { 10 | ID int64 // 评论id 11 | UserId int64 // 用户Id 12 | VideoId int64 //视频Id 13 | Text string // 评论内容 14 | IsCancel int64 // 是否取消 15 | CreateTime time.Time `gorm:"column:createTime"` // 创建时间 16 | } 17 | 18 | // CommentInfo 评论详细信息 19 | type CommentInfo struct { 20 | Content string `json:"content"` // 评论内容 21 | CreateDate string `json:"create_date"` // 评论发布日期,格式 mm-dd 22 | ID int64 `json:"id"` // 评论id 23 | UserInfo UserInfo `json:"user"` // 评论用户的具体信息 24 | } 25 | 26 | // QueryCommentByUserId 根据用户id获取评论 27 | func QueryCommentByUserId(userId int64) ([]Comment, error) { 28 | var comments []Comment 29 | // 获取 30 | result := db.Debug().Where("user_id = ? AND is_cancel = ?", userId, 0).Order("createTime desc").Find(&comments) 31 | if result.Error != nil { 32 | util.LogError(result.Error.Error()) 33 | return nil, result.Error 34 | } 35 | return comments, nil 36 | } 37 | 38 | // QueryCommentByVideoId 根据视频id获取评论 39 | func QueryCommentByVideoId(videoId int64) ([]Comment, error) { 40 | var comments []Comment 41 | // 获取 42 | // SELECT * FROM `comments` WHERE video_id = 43 AND is_cancel = 0 ORDER BY createTime desc 43 | result := db.Where("video_id = ? AND is_cancel = ?", videoId, 0).Order("createTime desc").Find(&comments) 44 | if result.Error != nil { 45 | return nil, result.Error 46 | } 47 | return comments, nil 48 | 49 | } 50 | 51 | // QueryCommentCountByVideoId 根据视频的id获取视频的评论数 52 | func QueryCommentCountByVideoId(videoId int64) (int64, error) { 53 | var count int64 54 | // SELECT count(*) FROM `comments` WHERE video_id = 43 AND is_cancel = 0 55 | result := db.Model(&Comment{}).Where("video_id = ? AND is_cancel = ?", videoId, 0).Count(&count) 56 | if result.Error != nil { 57 | util.LogError(result.Error.Error()) 58 | return -1, result.Error 59 | } 60 | return count, nil 61 | } 62 | 63 | // InsertComment 插入评论 这有问题 传入的参数 64 | func InsertComment(userId int64, videoId int64, content string) (Comment, error) { 65 | comment := Comment{ 66 | UserId: userId, 67 | VideoId: videoId, 68 | Text: content, 69 | CreateTime: time.Now(), 70 | } 71 | result := db.Debug().Select("user_id", "video_id", "text").Create(&comment) 72 | if result.Error != nil { 73 | util.LogError(result.Error.Error()) 74 | return comment, result.Error 75 | } 76 | return comment, nil 77 | } 78 | 79 | // DeleteComment 删除评论 80 | func DeleteComment(id int64) (bool, error) { 81 | result := db.Debug().Delete(&Comment{}, id) 82 | if result.Error != nil { 83 | util.LogError(result.Error.Error()) 84 | return false, result.Error 85 | } 86 | if result.RowsAffected == 0 { 87 | return false, errors.New("该评论不存在") 88 | } 89 | return true, nil 90 | } 91 | 92 | // 取消评论 gorm有bug还是设计问题,不能通过主键直接更新,必须先查询数据 93 | 94 | func CancelComment(id int64, userId int64, videoId int64) (bool, error) { 95 | comment := Comment{ 96 | ID: id, 97 | } 98 | //UPDATE `comments` SET `is_cancel`=1 WHERE (user_id = 1 AND video_id = 15) AND `id` = 48 99 | result := db.Debug().Model(&comment).Where("user_id = ? AND video_id = ?", userId, videoId).Update("is_cancel", 1) 100 | //// SELECT * FROM `comments` WHERE `comments`.`id` = 10 ORDER BY `comments`.`id` LIMIT 1 101 | //db.First(&comment, id) 102 | //comment.IsCancel = 1 103 | //result := db.Debug().Save(&comment) 104 | //if result.Error != nil { 105 | // util.LogError(result.Error.Error()) 106 | // return false, result.Error 107 | //} 108 | //if result.RowsAffected == 0 { 109 | // return false, errors.New("该评论不存在") 110 | //} 111 | if result.Error != nil { 112 | util.LogError(result.Error.Error()) 113 | return false, result.Error 114 | } 115 | return true, nil 116 | } 117 | -------------------------------------------------------------------------------- /go/model/comment_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestQueryCommentByUserId(t *testing.T) { 9 | comments, err := QueryCommentByUserId(2) 10 | fmt.Printf("%v", comments) 11 | fmt.Printf("%v", err) 12 | } 13 | func TestQueryCommentByVideoId(t *testing.T) { 14 | comments, err := QueryCommentByVideoId(2) 15 | fmt.Printf("%v", comments) 16 | fmt.Printf("%v", err) 17 | } 18 | func TestCancelComment(t *testing.T) { 19 | CancelComment(48, 1, 15) 20 | } 21 | -------------------------------------------------------------------------------- /go/model/follow.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "tiktok/go/util" 5 | "time" 6 | ) 7 | 8 | // Follow 用户关系结构,对应用户关系表。 9 | type Follow struct { 10 | Id int64 11 | UserId int64 12 | FollowerId int64 13 | Cancel int8 14 | CreatedAt time.Time `gorm:"-"` 15 | } 16 | 17 | // GetFollowingById 通过id获取自己关注的数目 同时筛选出cancel 18 | func GetFollowingById(id int64) (int64, error) { 19 | var count int64 20 | // SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) 21 | result := db.Model(&Follow{}).Where("user_id = ? AND cancel = ?", id, 0).Count(&count) 22 | if result.Error != nil { 23 | util.LogError(result.Error.Error()) 24 | return 0, result.Error 25 | } 26 | return count, nil 27 | } 28 | 29 | // GetFansById 通过id获取自己的粉丝数 同时筛选出cancel 30 | func GetFansById(id int64) (int64, error) { 31 | var count int64 32 | // SELECT count(1) FROM users WHERE name = 'jinzhu'; (count) 33 | result := db.Model(&Follow{}).Where("follower_id = ? AND cancel = ?", id, 0).Count(&count) 34 | if result.Error != nil { 35 | util.LogError(result.Error.Error()) 36 | return 0, result.Error 37 | } 38 | return count, nil 39 | } 40 | 41 | // IsFollowingById 通过id查看是否关注 42 | func IsFollowingById(id int64) (bool, error) { 43 | //var count int64 44 | 45 | return true, nil 46 | } 47 | 48 | // InsertFollow 插入关注 49 | func InsertFollow(userId int64, toUserID int64) (bool, error) { 50 | follow := Follow{ 51 | UserId: userId, 52 | FollowerId: toUserID, 53 | } 54 | // INSERT INTO `follows` (`user_id`,`follower_id`,`cancel`) VALUES (7,9,0) 55 | result := db.Create(&follow) 56 | if result.Error != nil { 57 | util.LogError(result.Error.Error()) 58 | return false, result.Error 59 | } 60 | return true, nil 61 | } 62 | 63 | // QueryFollowByUserIdAndToUserID 根据用户id和关注的id查询是否存在 64 | func QueryFollowByUserIdAndToUserID(userId int64, toUserID int64) (bool, error) { 65 | //SELECT * FROM `follows` WHERE user_id = 1 AND follower_id = 5 66 | result := db.Where("user_id = ? AND follower_id = ?", userId, toUserID).Find(&Follow{}) 67 | if result.Error != nil { 68 | util.LogError(result.Error.Error()) 69 | return false, result.Error 70 | } 71 | if result.RowsAffected == 0 { 72 | return false, nil 73 | } 74 | return true, nil 75 | } 76 | 77 | // CancelFollow 取消关注 78 | func CancelFollow(userId int64, toUserID int64) error { 79 | // 更新单列 80 | //UPDATE `follows` SET `cancel`=1 WHERE user_id = 1 AND follower_id = 5 81 | result := db.Debug().Model(&Follow{}).Where("user_id = ? AND follower_id = ?", userId, toUserID).Update("cancel", 1) 82 | if result.Error != nil { 83 | util.LogError(result.Error.Error()) 84 | return result.Error 85 | } 86 | return nil 87 | } 88 | 89 | // RefocusUser 重新关注用户 90 | func RefocusUser(userId int64, toUserID int64) error { 91 | // 更新单列 92 | // UPDATE `follows` SET `cancel`=0 WHERE user_id = 1 AND follower_id = 5 93 | result := db.Model(&Follow{}).Where("user_id = ? AND follower_id = ?", userId, toUserID).Update("cancel", 0) 94 | if result.Error != nil { 95 | util.LogError(result.Error.Error()) 96 | return result.Error 97 | } 98 | return nil 99 | } 100 | 101 | // 根据用户id查询用户关注的用户id切片 比较绕 102 | 103 | func QueryFollowUsersByUserId(userId int64) ([]User, error) { 104 | var users []User 105 | //SELECT * FROM `users` WHERE id IN (SELECT `follower_id` FROM `follows` WHERE user_id = 1 AND cancel = 0) 106 | result := db.Where("id IN (?)", db.Where("user_id = ? AND cancel = ?", userId, 0).Select("follower_id").Find(&Follow{})).Find(&users) 107 | if result.Error != nil { 108 | util.LogError(result.Error.Error()) 109 | return nil, result.Error 110 | } 111 | return users, nil 112 | } 113 | 114 | // 根据用户id查询当前用户的粉丝id切片 比较绕 115 | 116 | func QueryFansUsersByUserId(userId int64) ([]User, error) { 117 | var users []User 118 | //SELECT * FROM `users` WHERE id IN (SELECT `user_id` FROM `follows` WHERE follower_id = 1 AND cancel = 0) 119 | result := db.Where("id IN (?)", db.Where("follower_id = ? AND cancel = ?", userId, 0).Select("user_id").Find(&Follow{})).Find(&users) 120 | if result.Error != nil { 121 | util.LogError(result.Error.Error()) 122 | return nil, result.Error 123 | } 124 | return users, nil 125 | } 126 | 127 | /* 128 | 查询互相关注的用户列表 129 | */ 130 | 131 | func QueryMutualFollowListByUserId(userId int64) ([]User, error) { 132 | 133 | // inner join 134 | //db.InnerJoins("Company").Find(&users) 135 | // SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` INNER JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id` 136 | 137 | var MutualFollowList []User 138 | // SELECT a.follower_id FROM follows a join follows b on a.user_id=b.follower_id AND a.follower_id = b.user_id WHERE a.cancel = 0 AND b.cancel = 0 AND a.user_id = ? AND b.follower_id = ? 139 | //result := db.Debug().Table("follows a").Select("a.follower_id").Joins("join follows b on a.user_id=b.follower_id AND a.follower_id = b.user_id").Where("a.cancel = ? AND b.cancel = ? AND a.user_id = ? AND b.follower_id = ?", 0, 0, userId, userId).Find(&MutualFollowList) 140 | // SELECT `id` `name` FROM `users` WHERE id in (SELECT a.follower_id FROM follows a join follows b on a.user_id=b.follower_id AND a.follower_id = b.user_id WHERE a.cancel = 0 AND b.cancel = 0 AND a.user_id = 1 AND b.follower_id = 1) 141 | result := db.Where("id in (?)", db.Table("follows a").Select("a.follower_id").Joins("join follows b on a.user_id=b.follower_id AND a.follower_id = b.user_id").Where("a.cancel = ? AND b.cancel = ? AND a.user_id = ? AND b.follower_id = ?", 0, 0, userId, userId).Find(&Follow{})).Select("id", "name").Find(&MutualFollowList) 142 | if result.Error != nil { 143 | util.LogError(result.Error.Error()) 144 | return nil, result.Error 145 | } 146 | return MutualFollowList, nil 147 | } 148 | 149 | // 查询是否关注 第一个参数为当前用户的id,第二个参数为要关注的用户Id 150 | 151 | func QueryIsFollow(userId int64, toUserId int64) (bool, error) { 152 | // 自己不能关注自己 153 | if userId == toUserId { 154 | return true, nil 155 | } 156 | var count int64 157 | //SELECT count(*) FROM `follows` WHERE user_id = ? AND follower_id = ? AND cancel = 0 158 | result := db.Model(&Follow{}).Where("user_id = ? AND follower_id = ? AND cancel = ?", userId, toUserId, 0).Count(&count) 159 | if result.Error != nil { 160 | util.LogError(result.Error.Error()) 161 | return false, result.Error 162 | } 163 | if count != 0 { 164 | return true, nil 165 | } 166 | return false, nil 167 | } 168 | 169 | // 查询粉丝的Id 170 | 171 | func QueryFansId(userId int64) ([]int64, error) { 172 | 173 | return nil, nil 174 | } 175 | -------------------------------------------------------------------------------- /go/model/follow_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | ) 8 | 9 | func TestGetFollowingById(t *testing.T) { 10 | id, err := GetFollowingById(2) 11 | log.Println(err) 12 | log.Println(id) 13 | } 14 | 15 | func TestGetFansById(t *testing.T) { 16 | id, err := GetFansById(2) 17 | log.Println(id) 18 | log.Println(err) 19 | } 20 | func TestInsertFollow(t *testing.T) { 21 | InsertFollow(4, 2) 22 | } 23 | func TestCancelFollow(t *testing.T) { 24 | CancelFollow(1, 2) 25 | } 26 | func TestQueryFollowUsersByUserId(t *testing.T) { 27 | users, err := QueryFollowUsersByUserId(1) 28 | if err != nil { 29 | log.Println(err) 30 | } 31 | log.Println(users) 32 | } 33 | func TestQueryIsFollow(t *testing.T) { 34 | isFollow, _ := QueryIsFollow(1, 50) 35 | fmt.Println(isFollow) 36 | } 37 | 38 | func TestQueryMutualFollowListByUserId(t *testing.T) { 39 | QueryMutualFollowListByUserId(1) 40 | } 41 | -------------------------------------------------------------------------------- /go/model/like.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "tiktok/go/config" 5 | "tiktok/go/util" 6 | ) 7 | 8 | // Like 表的结构,不需要json化 9 | type Like struct { 10 | Id int64 //自增主键 11 | UserId int64 //点赞用户的id 12 | VideoId int64 //视频的id 13 | IsCancel int8 //是否点赞,0为点赞,1为取消赞 14 | } 15 | 16 | // 更新点赞数据 17 | func UpdateLikeVideoByUserId(userId int64, videoId int64, action int64) error { 18 | //更新当前用户观看视频的点赞状态“cancel”,返回错误结果 19 | // UPDATE `likes` SET `is_cancel`=0 WHERE `user_id` = 9 AND `video_id` = 39 20 | result := db.Model(Like{}).Where(map[string]interface{}{"user_id": userId, "video_id": videoId}). 21 | Update("is_cancel", action) 22 | if result.Error != nil { 23 | util.LogError(result.Error.Error()) 24 | return result.Error 25 | } 26 | return nil 27 | } 28 | 29 | // 插入数据 30 | func InsertLikeData(userId int64, videoId int64) (bool, error) { 31 | like := Like{ 32 | UserId: userId, 33 | VideoId: videoId, 34 | } 35 | result := db.Create(&like) 36 | if result.Error != nil { 37 | util.LogError(result.Error.Error()) 38 | return false, result.Error 39 | } 40 | return true, nil 41 | } 42 | 43 | // QueryDuplicateLikeData 查询是否有重复数据 44 | func QueryDuplicateLikeData(userId int64, videoId int64) (bool, error) { 45 | like := Like{} 46 | // SELECT * FROM `likes` WHERE `user_id` = 9 AND `video_id` = 39 47 | result := db.Where(map[string]interface{}{"user_id": userId, "video_id": videoId}).Find(&like) 48 | if result.Error != nil { 49 | util.LogError(result.Error.Error()) 50 | // 查询错误 51 | return false, result.Error 52 | } 53 | if like.Id == 0 { 54 | return false, nil 55 | } 56 | return true, nil 57 | } 58 | 59 | // QueryVideoByUserId 根据用户id查询视频的信息 60 | func QueryVideoByUserId(userId int64) ([]TableVideo, error) { 61 | tableVideos := make([]TableVideo, config.VideoMaxCount) // 62 | // SELECT 63 | // * 64 | //FROM 65 | // videos 66 | //WHERE 67 | // id IN ( SELECT video_id FROM likes WHERE user_id =? AND is_cancel = 0); 68 | result := db.Where("id IN (?)", db.Where("user_id = ? AND is_cancel = ?", userId, 0).Select("video_id").Find(&Like{})).Find(&tableVideos) 69 | //log.Printf("%v", tableVideos) 70 | if result.Error != nil { 71 | util.LogError(result.Error.Error()) 72 | return nil, result.Error 73 | } 74 | return tableVideos, nil 75 | } 76 | 77 | // QueryLikeByVideoId 根据id获取视频被点赞的总数 78 | func QueryLikeByVideoId(videoId int64) (int64, error) { 79 | var count int64 80 | // SELECT count(*) FROM `likes` WHERE video_id = ? AND is_cancel = 0 81 | result := db.Model(&Like{}).Where("video_id = ? AND is_cancel = ?", videoId, 0).Count(&count) 82 | if result.Error != nil { 83 | util.LogError(result.Error.Error()) 84 | return -1, result.Error 85 | } 86 | return count, nil 87 | } 88 | 89 | // 查询是否有点赞 90 | 91 | func QueryIsLike(userId int64, videoId int64) (bool, error) { 92 | like := Like{} 93 | // SELECT * FROM `likes` WHERE `user_id` = 9 AND `video_id` = 39 AND is_cancel = 0 94 | result := db.Where(map[string]interface{}{"user_id": userId, "video_id": videoId, "is_cancel": 0}).Find(&like) 95 | if result.Error != nil { 96 | util.LogError(result.Error.Error()) 97 | // 查询错误 98 | return false, result.Error 99 | } 100 | if like.Id == 0 { 101 | return false, nil 102 | } 103 | return true, nil 104 | } 105 | 106 | // 查询点赞数量 107 | 108 | func QueryFavoriteCountByUserId(userId int64) (int64, error) { 109 | var count int64 110 | // SELECT count(*) FROM `likes` WHERE user_id = 1 AND is_cancel = 0 111 | result := db.Model(&Like{}).Where("user_id = ? AND is_cancel = ?", userId, 0).Count(&count) 112 | if result.Error != nil { 113 | util.LogError(result.Error.Error()) 114 | return -1, result.Error 115 | } 116 | return count, nil 117 | } 118 | 119 | // 获取获赞数量 120 | 121 | func QueryTotalFavorited(userId int64) (int64, error) { 122 | var count int64 123 | // SELECT count(*) FROM `likes` WHERE video_id in (SELECT `id` FROM `videos` WHERE author_id = 1) AND is_cancel = 0 124 | db.Model(&Like{}).Where("video_id in (?) AND is_cancel = ?", db.Model(&TableVideo{}).Where("author_id = ?", userId).Select("id"), 0).Count(&count) 125 | return count, nil 126 | } 127 | -------------------------------------------------------------------------------- /go/model/like_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestLikeVideoByUserId(t *testing.T) { 9 | 10 | } 11 | 12 | func TestUnLikeVideoByUserId(t *testing.T) { 13 | err := UpdateLikeVideoByUserId(2, 1, 1) 14 | fmt.Printf("%v", err) 15 | } 16 | func TestQueryDuplicateLikeData(t *testing.T) { 17 | data, err := QueryDuplicateLikeData(1, 10) 18 | fmt.Printf("%v %v", data, err) 19 | } 20 | func TestQueryVideoByUserId(t *testing.T) { 21 | QueryVideoByUserId(2) 22 | } 23 | func TestQueryLikeByVideoId(t *testing.T) { 24 | count, _ := QueryLikeByVideoId(3) 25 | fmt.Print(count) 26 | } 27 | func TestQueryTotalFavorited(t *testing.T) { 28 | QueryTotalFavorited(1) 29 | } 30 | -------------------------------------------------------------------------------- /go/model/message.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "tiktok/go/config" 5 | "tiktok/go/util" 6 | "time" 7 | ) 8 | 9 | type Message struct { 10 | ID int64 `json:"id"` 11 | UserId int64 `json:"from_user_id"` 12 | ToUserId int64 `json:"to_user_id"` 13 | Content string `json:"content"` 14 | IsWithdraw int8 `json:"is_withdraw,omitempty"` 15 | CreateTime int64 `json:"create_time" gorm:"column:createTime"` // 创建时间 16 | } 17 | 18 | // 插入数据 19 | 20 | func InsertMessage(userId int64, toUserId int64, content string) (bool, error) { 21 | messageInfo := Message{ 22 | UserId: userId, 23 | ToUserId: toUserId, 24 | Content: content, 25 | CreateTime: time.Now().Unix(), 26 | } 27 | // INSERT INTO `messages` (`user_id`,`to_user_id`,`content`,`is_withdraw`,`createTime`) VALUES (5,1,'111',0,'2023-02-08 19:21:15.017') 28 | result := db.Create(&messageInfo) 29 | if result.Error != nil { 30 | util.LogError(result.Error.Error()) 31 | return false, result.Error 32 | } 33 | return true, nil 34 | } 35 | 36 | // 根据用户Id查询聊天记录 37 | 38 | func QueryMessageByUserId(userId int64) ([]Message, error) { 39 | var messages []Message 40 | //SELECT * FROM `messages` WHERE user_id = 1 AND is_withdraw = 0 LIMIT 10 41 | result := db.Where("user_id = ? AND is_withdraw = ?", userId, 0).Limit(config.MessageCount).Find(&messages) 42 | if result.Error != nil { 43 | util.LogError(result.Error.Error()) 44 | return nil, result.Error 45 | } 46 | return messages, nil 47 | } 48 | 49 | // 通过用户Id查询最新的聊天记录 0-接受 1-发送 有点问题 50 | 51 | func QueryNewestMessageByUserId(userId int64) (string, int8, error) { 52 | message := Message{} 53 | // SELECT * FROM `messages` WHERE (user_id = 1 Or to_user_id = 1) AND is_withdraw = 0 ORDER BY createTime desc LIMIT 1 54 | result := db.Where("(user_id = ? Or to_user_id = ?) AND is_withdraw = ?", userId, userId, 0).Order("createTime desc").Limit(1).Find(&message) 55 | if result.Error != nil { 56 | util.LogError(result.Error.Error()) 57 | return "", -1, result.Error 58 | } 59 | if userId == message.UserId { 60 | return message.Content, 1, nil 61 | } else { 62 | return message.Content, 0, nil 63 | } 64 | } 65 | 66 | // 通过两者的用户Id查询最新最新的两者之间的聊天记录 0-接受 1-发送 67 | 68 | func QueryNewestMessageByUserIdAndToUserID(userId int64, toUserId int64) (string, int8, error) { 69 | message := Message{} 70 | // SELECT `content`,`createTime`,`user_id`,`to_user_id` FROM `messages` WHERE (user_id = 2 AND to_user_id = 7 AND is_withdraw = 0) OR (user_id = 7 AND to_user_id = 2 AND is_withdraw = 0) ORDER BY createTime desc LIMIT 1 71 | result := db.Where("user_id = ? AND to_user_id = ? AND is_withdraw = ?", userId, toUserId, 0).Or("user_id = ? AND to_user_id = ? AND is_withdraw = ?", toUserId, userId, 0).Order("createTime desc").Limit(1).Select("content", "createTime", "user_id", "to_user_id").Find(&message) 72 | if result.Error != nil { 73 | util.LogError(result.Error.Error()) 74 | return "", -1, result.Error 75 | } 76 | if userId == message.UserId { 77 | return message.Content, 1, nil 78 | } else { 79 | return message.Content, 0, nil 80 | } 81 | } 82 | 83 | // 查询两者的全部聊天记录 84 | 85 | func QueryMessageByUserIdAndToUserId(userId int64, toUserId int64) ([]Message, error) { 86 | var messages []Message 87 | // SELECT * FROM `messages` WHERE (user_id = 1 AND to_user_id = 2 AND is_withdraw = 0) OR (user_id = 2 AND to_user_id = 1 AND is_withdraw = 0) ORDER BY createTime asc 88 | result := db.Where("user_id = ? AND to_user_id = ? AND is_withdraw = ?", userId, toUserId, 0).Or("user_id = ? AND to_user_id = ? AND is_withdraw = ?", toUserId, userId, 0).Order("createTime asc").Find(&messages) 89 | if result.Error != nil { 90 | util.LogError(result.Error.Error()) 91 | return messages, result.Error 92 | } 93 | //for _, message := range messages { 94 | // message.CreateTime = message.CreateTime.Unix() 95 | //} 96 | return messages, nil 97 | } 98 | 99 | // 查询消息记录的最大值 100 | 101 | func QueryMessageMaxCount(userId int64, toUserId int64) (int64, error) { 102 | var count int64 103 | // SELECT count(*) FROM `messages` WHERE (user_id = 1 AND to_user_id = 2 ) OR (user_id = 2 AND to_user_id = 1 ) 104 | result := db.Model(&Message{}).Where("user_id = ? AND to_user_id = ? ", userId, toUserId).Or("user_id = ? AND to_user_id = ? ", toUserId, userId).Count(&count) 105 | if result.Error != nil { 106 | util.LogError(result.Error.Error()) 107 | return -1, result.Error 108 | } 109 | return count, nil 110 | } 111 | -------------------------------------------------------------------------------- /go/model/message_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestInsertMessage(t *testing.T) { 9 | InsertMessage(3, 2, "1311") 10 | } 11 | 12 | func TestQueryMessageByUserId(t *testing.T) { 13 | QueryMessageByUserId(1) 14 | } 15 | func TestQert(t *testing.T) { 16 | _, i, _ := QueryNewestMessageByUserId(1) 17 | fmt.Println(i) 18 | } 19 | func TestQueryNewestMessageByUserIdAndToUserID(t *testing.T) { 20 | QueryNewestMessageByUserIdAndToUserID(1, 2) 21 | } 22 | func TestQueryMessageByUserIdAndToUserId(t *testing.T) { 23 | QueryMessageByUserIdAndToUserId(1, 2) 24 | } 25 | 26 | func TestQueryMessageMaxCount(t *testing.T) { 27 | count, _ := QueryMessageMaxCount(1, 2) 28 | fmt.Println(count) 29 | } 30 | -------------------------------------------------------------------------------- /go/model/readme.md: -------------------------------------------------------------------------------- 1 | ### 定义数据类型与数据库交互接口 2 | -------------------------------------------------------------------------------- /go/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "tiktok/go/util" 5 | ) 6 | 7 | // User 对应数据库User表结构的结构体 8 | type User struct { 9 | Id int64 //主键 10 | Name string //昵称 11 | Password string //密码 12 | } 13 | 14 | // UserInfo 最详细的信息 不与数据库模型对应 15 | type UserInfo struct { 16 | Id int64 `json:"id,omitempty"` //主键 17 | Name string `json:"name,omitempty"` //昵称 18 | FollowCount int64 `json:"follow_count"` //关注总数 19 | FollowerCount int64 `json:"follower_count"` //粉丝总数 20 | IsFollow bool `json:"is_follow"` //是否关注 21 | AvatarUrl string `json:"avatar,omitempty"` //用户的url 22 | TotalFavorited int64 `json:"total_favorited,omitempty"` //获赞数量 23 | WorkCount int64 `json:"work_count,omitempty"` //作品数量 24 | FavoriteCount int64 `json:"favorite_count,omitempty"` //点赞数量 25 | } 26 | 27 | type FriendUser struct { 28 | UserInfo 29 | Message string `json:"message"` //聊天信息 30 | MsgType int8 `json:"msgType"` //message信息的类型,0=>请求用户接受信息,1=>当前请求用户发送的信息 31 | } 32 | 33 | // InsertUser 插入用户 34 | func InsertUser(name string, password string) (User, error) { 35 | //CreateUser() 36 | user := User{ 37 | Name: name, 38 | Password: password, 39 | } 40 | result := db.Create(&user) 41 | return user, result.Error 42 | // return true, nil 43 | } 44 | 45 | // GetUserById 根据id(主键)获取用户 46 | func GetUserById(id int64) (User, error) { 47 | user := User{} 48 | // SELECT * FROM `users` WHERE Id = 9 ORDER BY `users`.`id` LIMIT 1 49 | if err := db.Where("Id = ?", id).First(&user).Error; err != nil { 50 | return user, err 51 | } 52 | //Db.Close() 53 | return user, nil 54 | } 55 | 56 | // GetUserByName 根据用户名(唯一)查询用户 57 | func GetUserByName(name string) (User, error) { 58 | user := User{} 59 | //Db := config.InitDataSource() 60 | // 查数据表 61 | // SELECT * FROM `users` WHERE name = '周子豪' LIMIT 1 62 | if err := db.Where("name = ?", name).Limit(1).Find(&user).Error; err != nil { 63 | //log.Println(err.Error()) 64 | util.LogError(err.Error()) 65 | return user, err 66 | } 67 | return user, nil 68 | } 69 | 70 | // PackageUserToUserInfo 71 | // 72 | // FollowCount 73 | // FollowerCount 74 | // IsFollow 75 | func PackageUserToUserInfo(user User) (UserInfo, error) { 76 | userInfo := UserInfo{} 77 | //var err error 78 | // 查询关注总数 79 | followingsCount, err := GetFollowingById(user.Id) 80 | if err != nil { 81 | util.LogError(err.Error()) 82 | 83 | return userInfo, err 84 | } 85 | // 查询粉丝总数 86 | fansCount, err := GetFansById(user.Id) 87 | if err != nil { 88 | util.LogError(err.Error()) 89 | return userInfo, err 90 | } 91 | // to-do 查询是否关注 92 | 93 | // 合并 94 | userInfo.Id = user.Id 95 | userInfo.Name = user.Name 96 | userInfo.FollowCount = followingsCount 97 | userInfo.FollowerCount = fansCount 98 | userInfo.IsFollow = false 99 | 100 | return userInfo, nil 101 | } 102 | 103 | // 104 | // 105 | // IsFollow -todo 点赞和作品总数 106 | 107 | func PackageUserToSimpleUserInfo(user User, userId int64) (UserInfo, error) { 108 | userInfo := UserInfo{} 109 | // 查询是否关注 110 | isFollow, err := QueryIsFollow(userId, user.Id) 111 | if err != nil { 112 | util.LogError(err.Error()) 113 | return userInfo, err 114 | } 115 | // 合并 116 | userInfo.Id = user.Id 117 | userInfo.Name = user.Name 118 | userInfo.FollowCount = 0 119 | userInfo.FollowerCount = 0 120 | userInfo.IsFollow = isFollow 121 | return userInfo, nil 122 | } 123 | 124 | // PackageUserToUserInfoByUserId 根据id将user包装成userInfo 125 | func PackageUserToUserInfoByUserId(id int64) (UserInfo, error) { 126 | userInfo := UserInfo{} 127 | 128 | return userInfo, nil 129 | } 130 | 131 | // 直接将user转换为userInfo,不查询数据库,只是包装 132 | 133 | func PackageUserToDirectUserInfo(user User) (UserInfo, error) { 134 | userInfo := UserInfo{} 135 | userInfo.Id = user.Id 136 | userInfo.Name = user.Name 137 | userInfo.IsFollow = true 138 | return userInfo, nil 139 | } 140 | -------------------------------------------------------------------------------- /go/model/video.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "tiktok/go/config" 5 | "time" 6 | ) 7 | 8 | type ApifoxModel struct { 9 | NextTime *int64 `json:"next_time"` // 本次返回的视频中,发布最早的时间,作为下次请求时的latest_time 10 | StatusCode int64 `json:"status_code"` // 状态码,0-成功,其他值-失败 11 | StatusMsg *string `json:"status_msg"` // 返回状态描述 12 | VideoList []Video `json:"video_list"` // 视频列表 13 | } 14 | 15 | // 对应sql表格的实体 更加简单的配置 16 | type TableVideo struct { 17 | Id int64 `json:"id"` 18 | AuthorId int64 19 | PlayUrl string `json:"play_url"` 20 | CoverUrl string `json:"cover_url"` 21 | PublishTime time.Time 22 | Title string `json:"title"` //视频名,5.23添加 23 | } 24 | 25 | // Video 26 | type Video struct { 27 | Author UserInfo `json:"author"` // 视频作者信息 28 | CommentCount int64 `json:"comment_count"` // 视频的评论总数 29 | CoverURL string `json:"cover_url"` // 视频封面地址 30 | FavoriteCount int64 `json:"favorite_count"` // 视频的点赞总数 31 | ID int64 `json:"id"` // 视频唯一标识 32 | IsFavorite bool `json:"is_favorite"` // true-已点赞,false-未点赞 33 | PlayURL string `json:"play_url"` // 视频播放地址 34 | Title string `json:"title"` // 视频标题 35 | } 36 | 37 | func (TableVideo) TableName() string { 38 | return "videos" 39 | } 40 | 41 | //var db = config.InitDataSource() 42 | 43 | // 获取时间戳之前的视频 44 | 45 | func GetVideoByLastTime(lastTime time.Time) ([]TableVideo, error) { 46 | tableVideos := make([]TableVideo, config.VideoCount) 47 | // SELECT * FROM `videos` WHERE publish_time <= '2023-02-11 18:37:18.326' ORDER BY publish_time desc LIMIT 10 48 | result := db.Where("publish_time <= ?", lastTime).Order("publish_time desc").Limit(config.VideoCount).Find(&tableVideos) 49 | if result.Error != nil { 50 | return nil, result.Error 51 | } 52 | return tableVideos, nil 53 | } 54 | 55 | // 通过用户id获取视频 56 | 57 | func GetVideoByUserId(userId int) ([]TableVideo, error) { 58 | tableVideos := make([]TableVideo, config.VideoMaxCount) 59 | // SELECT * FROM `videos` WHERE author_id = 13 ORDER BY publish_time desc LIMIT 30 60 | db.Where("author_id = ?", userId).Order("publish_time desc").Limit(config.VideoMaxCount).Find(&tableVideos) 61 | return tableVideos, nil 62 | } 63 | 64 | // 获取发布最早的视频的时间戳,作为下次请求的时间戳 废弃 65 | 66 | func GetVideoNextTime(lastTime time.Time) (time.Time, error) { 67 | tableVideo := TableVideo{} 68 | result := db.Debug().Where("publish_time <= ?", lastTime).Order("publish_time asc").Limit(1).Select("publish_time").Find(&tableVideo) 69 | if result.Error != nil { 70 | return time.Time{}, result.Error 71 | } 72 | return tableVideo.PublishTime, nil 73 | } 74 | 75 | // 获取 76 | 77 | func QueryNextTimeByVideoId(videoId int64) (time.Time, error) { 78 | tableVideo := TableVideo{} 79 | // SELECT `publish_time` FROM `videos` WHERE id = 6 LIMIT 1 80 | result := db.Where("id = ? ", videoId).Limit(1).Select("publish_time").Find(&tableVideo) 81 | if result.Error != nil { 82 | return time.Time{}, result.Error 83 | } 84 | return tableVideo.PublishTime, nil 85 | } 86 | 87 | // 插入数据库 88 | 89 | func InsertVideo(userId int64, play_url string, cover_url string, title string) error { 90 | tableVideo := TableVideo{ 91 | AuthorId: userId, 92 | PlayUrl: play_url, 93 | CoverUrl: cover_url, 94 | Title: title, 95 | PublishTime: time.Now(), 96 | } 97 | result := db.Debug().Create(&tableVideo) 98 | if result.Error != nil { 99 | return result.Error 100 | } 101 | return nil 102 | } 103 | 104 | // 查询作品的数量 105 | 106 | func QueryWorkCountByUserId(userId int64) (int64, error) { 107 | var count int64 108 | // SELECT count(*) FROM `videos` WHERE author_id = 1 109 | //result := db.Debug().Table("videos").Where("author_id = ?", userId).Count(&count) 110 | result := db.Model(&TableVideo{}).Where("author_id = ?", userId).Count(&count) 111 | if result.Error != nil { 112 | return -1, result.Error 113 | } 114 | return count, nil 115 | } 116 | 117 | // 查询视频的Id是否存在 118 | 119 | func QueryIsExistVideoId(Id int64) (bool, error) { 120 | result := db.Find(&TableVideo{}, Id) 121 | if result.Error != nil { 122 | return false, result.Error 123 | } 124 | if result.RowsAffected == 0 { 125 | return false, nil 126 | } 127 | return true, nil 128 | } 129 | -------------------------------------------------------------------------------- /go/model/video_test.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestGetVideoByLastTime(t *testing.T) { 11 | now := time.Now() 12 | videos, err := GetVideoByLastTime(now) 13 | log.Printf("%v", videos) 14 | log.Printf("%v", err) 15 | } 16 | func TestGetVideoNextTime(t *testing.T) { 17 | now := time.Now() 18 | lastTime, err := GetVideoNextTime(now) 19 | log.Printf("%v", lastTime) 20 | log.Printf("%v", err) 21 | } 22 | 23 | func TestQueryIsExitsVideoId(t *testing.T) { 24 | id, _ := QueryIsExistVideoId(11) 25 | fmt.Println(id) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /go/route/load.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "tiktok/go/controller" 7 | "tiktok/go/middle/jwt" 8 | "tiktok/go/model" 9 | ) 10 | 11 | // LoadRouter 12 | // 路由分组/* 13 | func LoadRouter(r *gin.Engine) { 14 | apiRouter := r.Group("/douyin") 15 | // basic apis 16 | // 注册接口 17 | apiRouter.POST("/user/register/", controller.Register) 18 | // 登录接口 19 | apiRouter.POST("/user/login/", controller.Login) 20 | // 用户信息接口 21 | apiRouter.GET("/user/", jwt.VerifyToken, controller.UserInfo) 22 | // 视频流接口 23 | apiRouter.GET("/feed/", controller.VideoStream) 24 | // 发布接口(视频上传) 25 | apiRouter.POST("/publish/action/", jwt.VerifyTokenByPost, controller.VideoPublish) 26 | // 发布列表接口 27 | apiRouter.GET("/publish/list/", controller.VideoList) 28 | 29 | // extra apis - I 30 | // 用户点赞接口 31 | apiRouter.POST("/favorite/action/", jwt.VerifyToken, controller.LikeVideoByUserID) 32 | // 喜欢列表接口 33 | apiRouter.GET("/favorite/list/", controller.UserFavoriteList) 34 | // 用户评论接口 35 | apiRouter.POST("/comment/action/", jwt.VerifyToken, controller.CommentVideo) 36 | // 评论列表接口 37 | apiRouter.GET("/comment/list/", controller.CommentList) 38 | // extra apis - II 39 | // 关注操作 40 | apiRouter.POST("/relation/action/", jwt.VerifyToken, controller.FollowUser) 41 | // 关注列表 42 | apiRouter.GET("/relation/follow/list/", jwt.VerifyToken, controller.FollowList) 43 | // 粉丝列表 44 | apiRouter.GET("/relation/follower/list/", jwt.VerifyToken, controller.FollowerList) 45 | // 用户好友列表 46 | apiRouter.GET("/relation/friend/list/", jwt.VerifyToken, controller.FriendList) 47 | // 聊天记录 48 | apiRouter.GET("/message/chat/", jwt.VerifyToken, controller.MessageChat) 49 | // 发送消息 50 | apiRouter.POST("/message/action/", jwt.VerifyToken, controller.MessageAction) 51 | // test 52 | //apiRouter.GET("/test/token",jwt.SignToken) 53 | 54 | // other 55 | r.NoRoute(func(context *gin.Context) { 56 | context.JSON(http.StatusNotFound, model.BaseResponseInstance.FailMsg("页面不存在")) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /go/service/baseService.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "tiktok/go/config" 6 | ) 7 | 8 | // 自定义数据库错误 用于返回上层 9 | var dataSourceErr = errors.New(config.DatabaseError) 10 | 11 | // 提供基本服务 12 | 13 | var redisDb, _ = config.InitRedisClient() 14 | 15 | var userRedisDb, _ = config.RedisClient(1) 16 | 17 | var videoRedisDb, _ = config.RedisClient(2) 18 | 19 | var followRedisDb, _ = config.RedisClient(3) 20 | 21 | var likeRedisDb, _ = config.RedisClient(4) 22 | 23 | var commentRedisDb, _ = config.RedisClient(5) 24 | 25 | var messageRedisDb, _ = config.RedisClient(6) 26 | -------------------------------------------------------------------------------- /go/service/commentService.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "tiktok/go/model" 6 | "tiktok/go/util" 7 | ) 8 | 9 | func CommentListService(videoId int64) ([]model.CommentInfo, error) { 10 | comments, err := model.QueryCommentByVideoId(videoId) 11 | if err != nil { 12 | return nil, dataSourceErr 13 | } 14 | commentInfos, err := PackageComments(comments) 15 | return commentInfos, nil 16 | } 17 | 18 | // 包装切片 19 | 20 | func PackageComments(comments []model.Comment) ([]model.CommentInfo, error) { 21 | // 提前定义切片 22 | var commentInfos []model.CommentInfo 23 | for _, comment := range comments { 24 | commentInfo, err := PackageComment(comment) 25 | if err != nil { 26 | return nil, err 27 | } 28 | commentInfos = append(commentInfos, commentInfo) 29 | } 30 | return commentInfos, nil 31 | } 32 | 33 | // 包装一个结构体 传入userInfo值 34 | 35 | func PackageComment(comment model.Comment) (model.CommentInfo, error) { 36 | // 定义 37 | commentInfo := model.CommentInfo{} 38 | // 填入Id 39 | commentInfo.ID = comment.ID 40 | // 填入评论内容 41 | commentInfo.Content = comment.Text 42 | // 填入创建时间 "2006-01-02 15:04:05.999999999 -0700 MST" 43 | commentInfo.CreateDate = comment.CreateTime.Format("01-02") 44 | // 根据用户id查询 45 | user, err := model.GetUserById(comment.UserId) 46 | if err != nil { 47 | return commentInfo, dataSourceErr 48 | } 49 | userInfo, err := model.PackageUserToUserInfo(user) 50 | if err != nil { 51 | return commentInfo, err 52 | } 53 | commentInfo.UserInfo = userInfo 54 | return commentInfo, nil 55 | } 56 | 57 | // 创建评论 58 | 59 | func CreateCommentService(userId int64, videoId int64, content string) (model.CommentInfo, error) { 60 | // 敏感词处理 61 | content, err := replaceSensitive(content) 62 | commentInfo := model.CommentInfo{} 63 | isExistVideoId, err := model.QueryIsExistVideoId(videoId) 64 | if isExistVideoId != true { 65 | return commentInfo, errors.New("视频不存在") 66 | } 67 | // 插入评论 68 | comment, err := model.InsertComment(userId, videoId, content) 69 | if err != nil { 70 | return commentInfo, dataSourceErr 71 | } 72 | commentInfo, err = PackageComment(comment) 73 | if err != nil { 74 | return commentInfo, dataSourceErr 75 | } 76 | return commentInfo, nil 77 | } 78 | 79 | // 删除评论 80 | 81 | func CancelCommentService(id int64, userId int64, videoId int64) (bool, error) { 82 | 83 | isDelete, err := model.CancelComment(id, userId, videoId) 84 | if err != nil { 85 | return false, dataSourceErr 86 | } 87 | return isDelete, nil 88 | } 89 | 90 | // checkSensitive 检验敏感词 91 | func checkSensitive(content string) bool { 92 | 93 | return false 94 | } 95 | 96 | // 替换敏感词 优化-》传递指针 97 | 98 | func replaceSensitive(content string) (string, error) { 99 | content, err := util.SensitiveWordsFilter(content) 100 | return content, err 101 | } 102 | -------------------------------------------------------------------------------- /go/service/followService.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "tiktok/go/model" 6 | ) 7 | 8 | // 关注用户服务 9 | 10 | func FollowUserService(userId int64, toUserId int64, actionType bool) (bool, error) { 11 | if actionType { 12 | // 先查询数据 13 | isExist, err := model.QueryFollowByUserIdAndToUserID(userId, toUserId) 14 | if err != nil { 15 | return false, dataSourceErr 16 | } 17 | if isExist { 18 | // 修改数据 19 | err = model.RefocusUser(userId, toUserId) 20 | if err != nil { 21 | return false, dataSourceErr 22 | } 23 | return true, nil 24 | } else { 25 | // 插入数据 26 | pass, err := model.InsertFollow(userId, toUserId) 27 | if err != nil { 28 | return false, dataSourceErr 29 | } 30 | if !pass { 31 | return false, errors.New("关注失败") 32 | } 33 | return true, nil 34 | } 35 | } else { 36 | // 取消关注 37 | err := model.CancelFollow(userId, toUserId) 38 | if err != nil { 39 | return false, dataSourceErr 40 | } 41 | return true, nil 42 | } 43 | return false, nil 44 | } 45 | 46 | // 未登录状态下的关注列表服务 47 | 48 | func FollowListService(userId int64) ([]model.UserInfo, error) { 49 | // 先根据用户Id取用户关注 50 | users, err := model.QueryFollowUsersByUserId(userId) 51 | if err != nil { 52 | return nil, dataSourceErr 53 | } 54 | // 定义userInfos 切片 55 | var userInfos []model.UserInfo 56 | // 循环 57 | for _, user := range users { 58 | userInfo, err := model.PackageUserToUserInfo(user) 59 | if err != nil { 60 | return nil, err 61 | } 62 | userInfos = append(userInfos, userInfo) 63 | } 64 | 65 | return userInfos, nil 66 | } 67 | 68 | // 未登录状态下的粉丝列表服务 69 | 70 | func FollowerListService(userId int64) ([]model.UserInfo, error) { 71 | // 先根据用户Id取用户关注 72 | users, err := model.QueryFansUsersByUserId(userId) 73 | if err != nil { 74 | return nil, dataSourceErr 75 | } 76 | // 定义userInfos 切片 77 | var userInfos []model.UserInfo 78 | // 循环 79 | for _, user := range users { 80 | userInfo, err := model.PackageUserToUserInfo(user) 81 | userInfo.IsFollow = false 82 | if err != nil { 83 | return nil, err 84 | } 85 | userInfos = append(userInfos, userInfo) 86 | } 87 | 88 | return userInfos, nil 89 | } 90 | 91 | // 登录状态下的关注列表 第一个参数为 第二个参数为登录用户的Id 92 | 93 | func FollowListServiceWithUserId(userId int64, loginUserId int64) ([]model.UserInfo, error) { 94 | // 先根据用户Id取用户关注 95 | users, err := model.QueryFollowUsersByUserId(userId) 96 | if err != nil { 97 | return nil, dataSourceErr 98 | } 99 | // 定义userInfos 切片 100 | var userInfos []model.UserInfo 101 | // 循环 102 | for _, user := range users { 103 | // 判断对方是否关注 104 | 105 | userInfo, err := model.PackageUserToSimpleUserInfo(user, loginUserId) 106 | if err != nil { 107 | return nil, err 108 | } 109 | userInfos = append(userInfos, userInfo) 110 | } 111 | 112 | return userInfos, nil 113 | } 114 | 115 | // 登录状态下的粉丝列表 116 | 117 | func FollowerListServiceWithUserId(userId int64, loginUserId int64) ([]model.UserInfo, error) { 118 | // 先根据用户Id取用户关注 119 | users, err := model.QueryFansUsersByUserId(userId) 120 | if err != nil { 121 | return nil, dataSourceErr 122 | } 123 | // 定义userInfos 切片 124 | var userInfos []model.UserInfo 125 | // 循环 126 | for _, user := range users { 127 | userInfo, err := model.PackageUserToSimpleUserInfo(user, loginUserId) 128 | if err != nil { 129 | return nil, err 130 | } 131 | userInfos = append(userInfos, userInfo) 132 | } 133 | 134 | return userInfos, nil 135 | } 136 | 137 | // 互相关注的好友列表 138 | 139 | func MutualFollowListService(userId int64) ([]model.UserInfo, error) { 140 | // 先根据用户Id取用户关注 141 | users, err := model.QueryMutualFollowListByUserId(userId) 142 | if err != nil { 143 | return nil, dataSourceErr 144 | } 145 | // 定义userInfos 切片 146 | var userInfos []model.UserInfo 147 | // 循环 148 | for _, user := range users { 149 | userInfo, _ := model.PackageUserToDirectUserInfo(user) 150 | userInfos = append(userInfos, userInfo) 151 | } 152 | return userInfos, nil 153 | } 154 | -------------------------------------------------------------------------------- /go/service/likeService.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "tiktok/go/model" 7 | ) 8 | 9 | // 用户点赞服务 10 | 11 | func LikeVideoByUserIDService(userId int64, videoId int64, actionType int64) (bool, error) { 12 | var err error = nil 13 | var isDuplicate bool 14 | videoIdString := strconv.FormatInt(videoId, 10) 15 | //// 点赞 -- 没有数据的话创建数据,存在数据的话插入数据 16 | 17 | //// 从redis判断是否点过赞 18 | //isMember, err := likeRedisDb.SIsMember(videoIdString, userId).Result() 19 | //// redis 正常 20 | //if isMember { 21 | // // 查询到数据 22 | // // 如果是点赞,直接返回 23 | // if actionType == 0 { 24 | // return true, nil 25 | // } else if actionType == 1 { 26 | // // 如果是取消点赞 27 | // // 删除redis的操作 28 | // likeRedisDb.SRem(videoIdString, userId) 29 | // // 操作数据库 30 | // go model.UpdateLikeVideoByUserId(userId, videoId, actionType) 31 | // return true, nil 32 | // } 33 | //} else { 34 | // // 未查询到数据 35 | // // 如果是点赞 36 | // if actionType == 0 { 37 | // // 添加数据到redis 38 | // likeRedisDb.SAdd(videoIdString, userId) 39 | // // 添加数据到数据库 40 | // go model.InsertLikeData(userId, videoId) 41 | // return true, nil 42 | // } else if actionType == 1 { 43 | // // 如果是取消点赞 44 | // go model.UpdateLikeVideoByUserId(userId, videoId, actionType) 45 | // return true, nil 46 | // } 47 | //} 48 | // redis 操作异常 只操作数据库的做法 49 | // 检查是否存在当前的重复值 50 | isDuplicate, err = model.QueryDuplicateLikeData(userId, videoId) 51 | // 为查询相关数据或者数据查询错误 52 | if err != nil { 53 | return false, dataSourceErr 54 | } 55 | // 包含相关数据 56 | if isDuplicate { 57 | // 包含相关数据-更新数据 58 | err = model.UpdateLikeVideoByUserId(userId, videoId, actionType) 59 | if err != nil { 60 | return false, dataSourceErr 61 | } 62 | if actionType == 0 { 63 | // 添加redis 64 | likeRedisDb.SAdd(videoIdString, userId) 65 | } else if actionType == 1 { 66 | // 移除redis 67 | likeRedisDb.SRem(videoIdString, userId) 68 | } 69 | } else { 70 | if actionType == 0 { 71 | // 插入数据 72 | // 先检查视频的id是否存在 73 | IsExist, err := model.QueryIsExistVideoId(videoId) 74 | if IsExist != true { 75 | return false, errors.New("视频不存在") 76 | } 77 | _, err = model.InsertLikeData(userId, videoId) 78 | if err != nil { 79 | return false, dataSourceErr 80 | } 81 | // 添加到redis 82 | likeRedisDb.SAdd(videoIdString, userId) 83 | } 84 | } 85 | return true, err 86 | } 87 | 88 | // 用户喜欢列表服务 89 | 90 | func UserFavoriteListService(userId int64) ([]model.Video, error) { 91 | // 根据用户查询点赞的视频 92 | tableVideos, err := model.QueryVideoByUserId(userId) 93 | if err != nil { 94 | return nil, dataSourceErr 95 | } 96 | videos, err := packageVideos(tableVideos, -1) 97 | if err != nil { 98 | return nil, err 99 | } 100 | return videos, nil 101 | } 102 | -------------------------------------------------------------------------------- /go/service/messageService.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "github.com/go-redis/redis" 7 | "strconv" 8 | "sync" 9 | "tiktok/go/config" 10 | "tiktok/go/model" 11 | "tiktok/go/util" 12 | "time" 13 | ) 14 | 15 | // 定义键值对维护消息记录 用户的Id->用户目前的消息记录索引 16 | // var userCommentIndex = make(map[int64]int64) 17 | var userCommentIndex sync.Map 18 | 19 | // 定义键值对维护消息记录 用户的Id->用户目前的消息记录最大值 20 | // var userMessageMaxIndex = make(map[int64]int64) 21 | var userMessageMaxIndex sync.Map 22 | 23 | var NowTime string 24 | 25 | func FriendListService(userId int64) ([]model.FriendUser, error) { 26 | var FriendUsers []model.FriendUser 27 | var err error 28 | FriendUsers, err = PackageFriendLists(userId) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return FriendUsers, nil 33 | } 34 | 35 | func MessageService() { 36 | 37 | } 38 | 39 | // 通过userId查询粉丝的数据,再包装加入消息 40 | 41 | func PackageFriendLists(userId int64) ([]model.FriendUser, error) { 42 | var FriendLists []model.FriendUser 43 | var message string 44 | var msgType int8 45 | userInfos, err := MutualFollowListService(userId) 46 | if err != nil { 47 | return nil, err 48 | } 49 | for _, userInfo := range userInfos { 50 | // 查询Message和MsgType 51 | message, msgType, err = model.QueryNewestMessageByUserIdAndToUserID(userId, userInfo.Id) 52 | if err != nil { 53 | return nil, err 54 | } 55 | userInfo.AvatarUrl = config.MockAvatarUrl 56 | FriendLists = append(FriendLists, model.FriendUser{ 57 | UserInfo: userInfo, 58 | Message: message, 59 | MsgType: msgType, 60 | }) 61 | } 62 | 63 | return FriendLists, nil 64 | } 65 | 66 | // 包装单个请求 67 | 68 | func PackageFriendList(userInfo model.FriendUser) (model.UserInfo, error) { 69 | // test 70 | userInfo.AvatarUrl = "https://xingqiu-tuchuang-1256524210.cos.ap-shanghai.myqcloud.com/12640/20230206171653.png" 71 | return model.UserInfo{}, nil 72 | } 73 | 74 | func MessageChatService(userId int64, toUserId int64, preMsgTime int64) ([]model.Message, error) { 75 | var messages []model.Message 76 | var err error 77 | // 第一次查询 78 | if preMsgTime == 0 { 79 | // 第一次使用 80 | // 查询userid和toUserId的表将全部内容返回 81 | messages, err = model.QueryMessageByUserIdAndToUserId(userId, toUserId) 82 | if err != nil { 83 | return nil, err 84 | } 85 | // 添加所有数据进入redis 86 | //AllMessageListAddRedis(userId, toUserId, messages) 87 | return messages, err 88 | } 89 | // 不是第一次查询 查询redis 90 | messages, err = ParseAllMessageListFromRedis(userId, toUserId, preMsgTime) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return messages, nil 96 | } 97 | 98 | func MessageActionService(userId int64, toUserId int64, content string) (bool, error) { 99 | // 敏感词替换 100 | content, _ = util.SensitiveWordsFilter(content) 101 | 102 | // 添加数据进入redis 103 | MessageActionRedis(userId, toUserId, content) 104 | // 添加数据库 105 | pass, err := model.InsertMessage(userId, toUserId, content) 106 | if err != nil { 107 | return false, dataSourceErr 108 | } 109 | if !pass { 110 | return false, errors.New("发送消息失败") 111 | } 112 | return true, nil 113 | } 114 | 115 | func MessageActionRedis(userId int64, toUserId int64, content string) error { 116 | timeUnix := time.Now().Unix() 117 | message := model.Message{ 118 | UserId: userId, 119 | ToUserId: toUserId, 120 | Content: content, 121 | IsWithdraw: 0, 122 | CreateTime: timeUnix, 123 | } 124 | // 序列化 125 | bytes, err := json.Marshal(message) 126 | if err != nil { 127 | return err 128 | } 129 | var messageRedisName = strconv.FormatInt(userId, 10) + "-" + strconv.FormatInt(toUserId, 10) 130 | // 添加到redis中hash 131 | messageRedisDb.RPush(messageRedisName, bytes) 132 | return nil 133 | } 134 | 135 | func AllMessageListAddRedis(userId int64, toUserId int64, messages []model.Message) { 136 | var messageRedisName = strconv.FormatInt(userId, 10) + "-" + strconv.FormatInt(toUserId, 10) 137 | // 添加到redis中hash 138 | for _, message := range messages { 139 | bytes, _ := json.Marshal(message) 140 | messageRedisDb.ZAdd(messageRedisName, redis.Z{ 141 | Score: float64(message.CreateTime), 142 | Member: bytes, 143 | }) 144 | } 145 | } 146 | 147 | // 查询时间戳之前的记录 并删除 148 | 149 | func ParseAllMessageListFromRedis(userId int64, toUserId int64, msgTime int64) ([]model.Message, error) { 150 | var messages []model.Message 151 | var messageRedisName = strconv.FormatInt(userId, 10) + "-" + strconv.FormatInt(toUserId, 10) 152 | for { 153 | bytes, _ := messageRedisDb.LPop(messageRedisName).Result() 154 | if bytes == "" { 155 | break 156 | } 157 | message := model.Message{} 158 | json.Unmarshal([]byte(bytes), &message) 159 | if message.IsWithdraw != 0 || message.CreateTime >= msgTime { 160 | continue 161 | } 162 | messages = append(messages, message) 163 | } 164 | return messages, nil 165 | } 166 | -------------------------------------------------------------------------------- /go/service/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WenTesla/tiktok/140f0d6625698620d47a6d6badced4e425377a14/go/service/readme.md -------------------------------------------------------------------------------- /go/service/userService.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "errors" 7 | "log" 8 | "tiktok/go/config" 9 | "tiktok/go/middle/jwt" 10 | 11 | // "log" 12 | "regexp" 13 | "tiktok/go/model" 14 | ) 15 | 16 | // SALT 盐值 17 | const SALT = "TikTok" 18 | 19 | // email verify 20 | func VerifyEmailFormat(email string) bool { 21 | //pattern := `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` //匹配电子邮箱 22 | pattern := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$` 23 | reg := regexp.MustCompile(pattern) 24 | return reg.MatchString(email) 25 | } 26 | 27 | // mobile verify 28 | func VerifyMobileFormat(mobileNum string) bool { 29 | regular := "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$" 30 | reg := regexp.MustCompile(regular) 31 | return reg.MatchString(mobileNum) 32 | } 33 | 34 | // GenerateTokenByName 根据name生成token 35 | func GenerateTokenByName(username string) (string, error) { 36 | user, _ := model.GetUserByName(username) 37 | token := jwt.SignToken(user) 38 | return token, nil 39 | } 40 | 41 | // Encryption md5加盐加密 42 | func Encryption(password string) string { 43 | password += SALT 44 | hash := md5.New() 45 | hash.Write([]byte(password)) 46 | hash_password := hex.EncodeToString(hash.Sum(nil)) 47 | return hash_password 48 | } 49 | 50 | // RegisterService 注册服务 51 | func RegisterService(username string, password string) (int64, error) { 52 | log.Println(username, "---", password) 53 | 54 | // 查表,是否存在id 55 | user, err := model.GetUserByName(username) 56 | if err != nil { 57 | return 0, errors.New("数据库查询错误") 58 | } 59 | if username == user.Name { 60 | return 0, errors.New("用户名已经存在") 61 | } 62 | // 插入 63 | user, _ = model.InsertUser(username, password) 64 | return user.Id, nil 65 | } 66 | 67 | // LoginService 登录服务 68 | func LoginService(username string, password string) (int64, error) { 69 | 70 | user, err := model.GetUserByName(username) 71 | if err != nil { 72 | return 0, dataSourceErr 73 | } 74 | if username != user.Name { 75 | return 0, errors.New("用户名不存在") 76 | } 77 | if password != user.Password { 78 | return 0, errors.New("用户或密码不正确") 79 | } 80 | 81 | return user.Id, nil 82 | } 83 | 84 | // 用户服务 先封装小的,再封装大的 根据用户Id查询用户的具体信息 85 | 86 | func UserService(Id int64) (model.UserInfo, error) { 87 | user, err := model.GetUserById(Id) 88 | if err != nil { 89 | return model.UserInfo{}, dataSourceErr 90 | } 91 | // 脱密 92 | user.Password = "" 93 | // 查询自己的关注数目 94 | followingCount, _ := model.GetFollowingById(Id) 95 | // 查询自己的粉丝 96 | fanCount, err := model.GetFansById(Id) 97 | // 查询点赞数量 98 | favoriteCount, err := model.QueryFavoriteCountByUserId(Id) 99 | if err != nil { 100 | return model.UserInfo{}, dataSourceErr 101 | } 102 | // 查询作品的数量 103 | workCount, err := model.QueryWorkCountByUserId(Id) 104 | if err != nil { 105 | return model.UserInfo{}, dataSourceErr 106 | } 107 | // 查询获赞数量 108 | totalFavorited, err := model.QueryTotalFavorited(Id) 109 | if err != nil { 110 | return model.UserInfo{}, dataSourceErr 111 | } 112 | // 以下为假数据 -avator 113 | 114 | // 关注一定为false 115 | userInfo := model.UserInfo{ 116 | Id: user.Id, 117 | Name: user.Name, 118 | FollowCount: followingCount, 119 | FollowerCount: fanCount, 120 | IsFollow: false, 121 | AvatarUrl: config.MockAvatarUrl, 122 | TotalFavorited: totalFavorited, 123 | WorkCount: workCount, 124 | FavoriteCount: favoriteCount, 125 | } 126 | return userInfo, nil 127 | } 128 | 129 | // 用户服务 先封装小的,再封装大的 此时为登录状态,需要查询是否登录 130 | 131 | func UserInfoService(Id int64, userId int64) (model.UserInfo, error) { 132 | user, err := model.GetUserById(Id) 133 | if err != nil { 134 | return model.UserInfo{}, err 135 | } 136 | user.Password = "" 137 | // 查询关注数目 138 | followingCount, _ := model.GetFollowingById(Id) 139 | // 查询粉丝 140 | fanCount, err := model.GetFansById(Id) 141 | if err != nil { 142 | return model.UserInfo{}, dataSourceErr 143 | } 144 | // 查询是否关注 145 | isFollow, err := model.QueryIsFollow(userId, Id) 146 | if err != nil { 147 | return model.UserInfo{}, dataSourceErr 148 | } 149 | // 查询作品数量 150 | favoriteCount, err := model.QueryFavoriteCountByUserId(Id) 151 | if err != nil { 152 | return model.UserInfo{}, dataSourceErr 153 | } 154 | // 查询点赞数量 155 | workCount, err := model.QueryWorkCountByUserId(Id) 156 | if err != nil { 157 | return model.UserInfo{}, dataSourceErr 158 | } 159 | // 查询获赞数量 160 | totalFavorited, err := model.QueryTotalFavorited(Id) 161 | if err != nil { 162 | return model.UserInfo{}, dataSourceErr 163 | } 164 | userInfo := model.UserInfo{ 165 | Id: user.Id, 166 | Name: user.Name, 167 | FollowCount: followingCount, 168 | FollowerCount: fanCount, 169 | IsFollow: isFollow, 170 | AvatarUrl: config.CosUrl, 171 | TotalFavorited: totalFavorited, 172 | WorkCount: workCount, 173 | FavoriteCount: favoriteCount, 174 | } 175 | return userInfo, nil 176 | } 177 | 178 | // 脱密后的信息 179 | 180 | func SimpleUserService(Id int64, userId int64) (model.UserInfo, error) { 181 | user, err := model.GetUserById(Id) 182 | if err != nil { 183 | return model.UserInfo{}, dataSourceErr 184 | } 185 | user.Password = "" 186 | // 关注一定为true 187 | userInfo := model.UserInfo{ 188 | Id: user.Id, 189 | Name: user.Name, 190 | FollowCount: 0, 191 | FollowerCount: 0, 192 | IsFollow: false, 193 | } 194 | return userInfo, nil 195 | } 196 | -------------------------------------------------------------------------------- /go/service/videoService.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "errors" 5 | "github.com/tencentyun/cos-go-sdk-v5" 6 | "golang.org/x/net/context" 7 | "log" 8 | "mime/multipart" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "strconv" 13 | "strings" 14 | "tiktok/go/config" 15 | "tiktok/go/model" 16 | "tiktok/go/util" 17 | "time" 18 | ) 19 | 20 | var nowTime string 21 | 22 | var newFileName string 23 | 24 | // 通过传入时间戳,当前用户的id,返回对应的视频数组,以及视频数组中最早的发布时间 25 | 26 | func VideoStreamService(lastTime time.Time, userId int64) ([]model.Video, error) { 27 | tableVideos, err := model.GetVideoByLastTime(lastTime) 28 | if err != nil { 29 | log.Printf("失败 %v", err) 30 | util.LogError(err.Error()) 31 | return nil, dataSourceErr 32 | } 33 | log.Printf("获取成功") 34 | videos, err := packageVideos(tableVideos, userId) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | return videos, nil 40 | } 41 | func VideoInfoByUserId(id int) ([]model.Video, error) { 42 | tableVideos, err := model.GetVideoByUserId(id) 43 | if err != nil { 44 | log.Printf("失败%v", err) 45 | util.LogError(err.Error()) 46 | return nil, dataSourceErr 47 | } 48 | videos, err := packageVideos(tableVideos, -1) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return videos, nil 53 | } 54 | 55 | // 包装视频流,填入内容如下 56 | // 57 | // author 58 | // "favorite_count": 0, 59 | // "comment_count": 0, 60 | // "is_favorite": true, 61 | // 62 | // user 63 | func packageVideos(tableVideos []model.TableVideo, userId int64) ([]model.Video, error) { 64 | // 创建video模型 65 | videos := make([]model.Video, 0, config.VideoCount) 66 | if userId == -1 { 67 | // 填入author 68 | for _, tableVideo := range tableVideos { 69 | video, err := packageVideo(&tableVideo) 70 | if err != nil { 71 | return nil, err 72 | } 73 | videos = append(videos, video) 74 | } 75 | } else { 76 | // 填入author 77 | for _, tableVideo := range tableVideos { 78 | video, err := packageVideoWithUserId(&tableVideo, userId) 79 | if err != nil { 80 | return nil, err 81 | } 82 | videos = append(videos, video) 83 | } 84 | } 85 | 86 | return videos, nil 87 | } 88 | 89 | // 包装简单的视频列表 90 | 91 | func packageSimpleVideos(tableVideos []model.TableVideo, userId int64) ([]model.Video, error) { 92 | // 创建video模型 93 | videos := make([]model.Video, 0, config.VideoCount) 94 | if userId == -1 { 95 | // 填入author 96 | for _, tableVideo := range tableVideos { 97 | video, err := PackSimpleVideoService(&tableVideo) 98 | if err != nil { 99 | return nil, err 100 | } 101 | videos = append(videos, video) 102 | } 103 | } else { 104 | // 填入author 105 | for _, tableVideo := range tableVideos { 106 | video, err := packageVideoWithUserId(&tableVideo, userId) 107 | if err != nil { 108 | return nil, err 109 | } 110 | videos = append(videos, video) 111 | } 112 | } 113 | 114 | return videos, nil 115 | } 116 | 117 | // 包装单个视频,不返回是否关注的信息-即未登录状态的信息 118 | 119 | func packageVideo(tableVideo *model.TableVideo) (model.Video, error) { 120 | // 创建video单例 121 | video := model.Video{} 122 | // 获取作者信息 123 | userInfo, err := UserService(tableVideo.AuthorId) 124 | if err != nil { 125 | return model.Video{}, err 126 | } 127 | log.Printf("%v", userInfo) 128 | //video.Author=user 129 | video.Author = userInfo 130 | // 填充Videos的 131 | video.ID = tableVideo.Id 132 | video.PlayURL = tableVideo.PlayUrl 133 | video.CoverURL = tableVideo.CoverUrl 134 | video.Title = tableVideo.Title 135 | // 获取 favorite_count 136 | // 先查询redis 137 | favoriteCount, err := likeRedisDb.SCard(strconv.FormatInt(video.ID, 10)).Result() 138 | if err != nil { 139 | //// 出错查询数据库 140 | favoriteCount, err = model.QueryLikeByVideoId(tableVideo.Id) 141 | if err != nil { 142 | return video, dataSourceErr 143 | } 144 | } 145 | video.FavoriteCount = favoriteCount 146 | // 获取"commentCount" 147 | commentCount, err := model.QueryCommentCountByVideoId(tableVideo.Id) 148 | if err != nil { 149 | return video, dataSourceErr 150 | } 151 | video.CommentCount = commentCount 152 | video.IsFavorite = false 153 | return video, nil 154 | } 155 | 156 | // 包装单个视频,返回是否关注的信息 157 | 158 | func packageVideoWithUserId(tableVideo *model.TableVideo, id int64) (model.Video, error) { 159 | // 创建video单例 160 | video := model.Video{} 161 | // 获取作者信息 162 | userInfo, err := UserInfoService(tableVideo.AuthorId, id) 163 | if err != nil { 164 | return model.Video{}, err 165 | } 166 | log.Printf("%v", userInfo) 167 | //video.Author=user 168 | video.Author = userInfo 169 | // 填充Videos的 170 | video.ID = tableVideo.Id 171 | video.PlayURL = tableVideo.PlayUrl 172 | video.CoverURL = tableVideo.CoverUrl 173 | video.Title = tableVideo.Title 174 | // 获取 favorite_count 175 | // 先查询redis 176 | favoriteCount, err := likeRedisDb.SCard(strconv.FormatInt(video.ID, 10)).Result() 177 | if err != nil { 178 | //// 出错查询数据库 179 | favoriteCount, err = model.QueryLikeByVideoId(tableVideo.Id) 180 | if err != nil { 181 | return video, dataSourceErr 182 | } 183 | } 184 | video.FavoriteCount = favoriteCount 185 | // 获取"commentCount" 186 | commentCount, err := model.QueryCommentCountByVideoId(tableVideo.Id) 187 | if err != nil { 188 | return video, dataSourceErr 189 | } 190 | video.CommentCount = commentCount 191 | // 获取是否点赞 先查询redis 192 | is_favorite, err := likeRedisDb.SIsMember(strconv.FormatInt(video.ID, 10), id).Result() 193 | // redis查询失败查询数据库 194 | if err != nil { 195 | is_favorite, err = model.QueryIsLike(id, tableVideo.Id) 196 | if err != nil { 197 | return video, err 198 | } 199 | } 200 | video.IsFavorite = is_favorite 201 | return video, nil 202 | } 203 | 204 | // 包装最简单的视频信息 作者只包含 205 | 206 | func PackSimpleVideoService(tableVideo *model.TableVideo) (model.Video, error) { 207 | // 创建video单例 208 | video := model.Video{} 209 | // 获取作者信息 210 | userInfo, err := SimpleUserService(tableVideo.AuthorId, -1) 211 | if err != nil { 212 | return model.Video{}, err 213 | } 214 | log.Printf("%v", userInfo) 215 | //video.Author=user 216 | video.Author = userInfo 217 | // 填充Videos的 218 | video.ID = tableVideo.Id 219 | video.PlayURL = tableVideo.PlayUrl 220 | video.CoverURL = tableVideo.CoverUrl 221 | video.Title = tableVideo.Title 222 | video.IsFavorite = false 223 | return video, nil 224 | } 225 | 226 | // PublishVideo 可以优化 227 | 228 | func PublishVideoService(file *multipart.FileHeader, userId int64, title string) error { 229 | src, err := file.Open() 230 | if err != nil { 231 | return errors.New("不能打开文件") 232 | } 233 | defer src.Close() 234 | // 获取视频文件名称 235 | nowTime = strconv.FormatInt(time.Now().Unix(), 10) 236 | // 转换视频名称 237 | newFileName, err = fileNameToTimeCurrentFileName(file.Filename, nowTime) 238 | // 上传到cos 239 | err = publishVideoByTencentCos(src, newFileName) 240 | if err != nil { 241 | return err 242 | } 243 | // 提取封面url 244 | play_cover, err := parseFileName(newFileName) 245 | if err != nil { 246 | return err 247 | } 248 | // 添加数据库 249 | err = model.InsertVideo(userId, config.CosUrl+"/"+newFileName, config.CosUrl+"/"+play_cover, title) 250 | if err != nil { 251 | return dataSourceErr 252 | } 253 | return nil 254 | } 255 | 256 | // publishVideoByTencentCos 257 | func publishVideoByTencentCos(file multipart.File, fileName string) error { 258 | // 将 examplebucket-1250000000 和 COS_REGION 修改为真实的信息 259 | // 存储桶名称,由 bucketname-appid 组成,appid 必须填入,可以在 COS 控制台查看存储桶名称。https://console.cloud.tencent.com/cos5/bucket 260 | // COS_REGION 可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket, 关于地域的详情见 https://cloud.tencent.com/document/product/436/6224 261 | u, _ := url.Parse(config.CosUrl) 262 | b := &cos.BaseURL{BucketURL: u} 263 | c := cos.NewClient(b, &http.Client{ 264 | Transport: &cos.AuthorizationTransport{ 265 | SecretID: os.Getenv(config.SecretId), // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140 266 | SecretKey: os.Getenv(config.SecretKey), // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考 https://cloud.tencent.com/document/product/598/37140 267 | }, 268 | }) 269 | // 对象键(Key)是对象在存储桶中的唯一标识。 270 | // 例如,在对象的访问域名 `examplebucket-1250000000.cos.COS_REGION.myqcloud.com/test/objectPut.go` 中,对象键为 test/objectPut.go 271 | _, err := c.Object.Put(context.Background(), fileName, file, nil) 272 | if err != nil { 273 | return errors.New("文件上传失败") 274 | } 275 | //os.Open() 276 | return nil 277 | } 278 | 279 | // parseFileName parseFileName 解析文件名称,去除文件后缀并加上文件格式jpg 280 | func parseFileName(fileName string) (string, error) { 281 | // 282 | lastIndex := strings.LastIndex(fileName, ".") 283 | if lastIndex == -1 { 284 | return "", errors.New("解析错误") 285 | } 286 | replaced := fileName[lastIndex:] 287 | // 判断文件后缀是否为要求的后缀 288 | if replaced != ".mp4" { 289 | return "", errors.New("文件格式不符合要求") 290 | } 291 | return strings.Replace(fileName, replaced, config.ReplaceSuffix, 1), nil 292 | } 293 | 294 | // fileNameToTimeCurrentFileName 将文件名称转化为时间戳并返回 295 | func fileNameToTimeCurrentFileName(oldFileName string, newFileName string) (string, error) { 296 | // 提取文件后缀 297 | lastIndex := strings.LastIndex(oldFileName, ".") 298 | if lastIndex == -1 { 299 | return "", errors.New("文件名称转换错误") 300 | } 301 | replaced := oldFileName[:lastIndex] 302 | return strings.Replace(oldFileName, replaced, newFileName, 1), nil 303 | } 304 | 305 | // CheckFile -todo 检查文件内容的合法性 306 | func CheckFile() { 307 | // 检查文件后缀 308 | //path.Ext() 309 | } 310 | -------------------------------------------------------------------------------- /go/util/checkFile.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "strconv" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | var fileTypeMap sync.Map 12 | 13 | func init() { 14 | fileTypeMap.Store("ffd8ffe000104a464946", "jpg") //JPEG (jpg) 15 | fileTypeMap.Store("89504e470d0a1a0a0000", "png") //PNG (png) 16 | fileTypeMap.Store("47494638396126026f01", "gif") //GIF (gif) 17 | fileTypeMap.Store("49492a00227105008037", "tif") //TIFF (tif) 18 | fileTypeMap.Store("424d228c010000000000", "bmp") //16色位图(bmp) 19 | fileTypeMap.Store("424d8240090000000000", "bmp") //24位位图(bmp) 20 | fileTypeMap.Store("424d8e1b030000000000", "bmp") //256色位图(bmp) 21 | fileTypeMap.Store("41433130313500000000", "dwg") //CAD (dwg) 22 | fileTypeMap.Store("3c21444f435459504520", "html") //HTML (html) 3c68746d6c3e0 3c68746d6c3e0 23 | fileTypeMap.Store("3c68746d6c3e0", "html") //HTML (html) 3c68746d6c3e0 3c68746d6c3e0 24 | fileTypeMap.Store("3c21646f637479706520", "htm") //HTM (htm) 25 | fileTypeMap.Store("48544d4c207b0d0a0942", "css") //css 26 | fileTypeMap.Store("696b2e71623d696b2e71", "js") //js 27 | fileTypeMap.Store("7b5c727466315c616e73", "rtf") //Rich Text Format (rtf) 28 | fileTypeMap.Store("38425053000100000000", "psd") //Photoshop (psd) 29 | fileTypeMap.Store("46726f6d3a203d3f6762", "eml") //Email [Outlook Express 6] (eml) 30 | fileTypeMap.Store("d0cf11e0a1b11ae10000", "doc") //MS Excel 注意:word、msi 和 excel的文件头一样 31 | fileTypeMap.Store("d0cf11e0a1b11ae10000", "vsd") //Visio 绘图 32 | fileTypeMap.Store("5374616E64617264204A", "mdb") //MS Access (mdb) 33 | fileTypeMap.Store("252150532D41646F6265", "ps") 34 | fileTypeMap.Store("255044462d312e350d0a", "pdf") //Adobe Acrobat (pdf) 35 | fileTypeMap.Store("2e524d46000000120001", "rmvb") //rmvb/rm相同 36 | fileTypeMap.Store("464c5601050000000900", "flv") //flv与f4v相同 37 | fileTypeMap.Store("00000020667479706d70", "mp4") // 0000001c667479706d70 00 00 00 18 66 74 79 70 69 73 6F 6D 38 | fileTypeMap.Store("49443303000000002176", "mp3") 39 | fileTypeMap.Store("000001ba210001000180", "mpg") // 40 | fileTypeMap.Store("3026b2758e66cf11a6d9", "wmv") //wmv与asf相同 41 | fileTypeMap.Store("52494646e27807005741", "wav") //Wave (wav) 42 | fileTypeMap.Store("52494646d07d60074156", "avi") 43 | fileTypeMap.Store("4d546864000000060001", "mid") //MIDI (mid) 44 | fileTypeMap.Store("504b0304140000000800", "zip") 45 | fileTypeMap.Store("526172211a0700cf9073", "rar") 46 | fileTypeMap.Store("235468697320636f6e66", "ini") 47 | fileTypeMap.Store("504b03040a0000000000", "jar") 48 | fileTypeMap.Store("4d5a9000030000000400", "exe") //可执行文件 49 | fileTypeMap.Store("3c25402070616765206c", "jsp") //jsp文件 50 | fileTypeMap.Store("4d616e69666573742d56", "mf") //MF文件 51 | fileTypeMap.Store("3c3f786d6c2076657273", "xml") //xml文件 52 | fileTypeMap.Store("494e5345525420494e54", "sql") //xml文件 53 | fileTypeMap.Store("7061636b616765207765", "java") //java文件 54 | fileTypeMap.Store("406563686f206f66660d", "bat") //bat文件 55 | fileTypeMap.Store("1f8b0800000000000000", "gz") //gz文件 56 | fileTypeMap.Store("6c6f67346a2e726f6f74", "properties") //bat文件 57 | fileTypeMap.Store("cafebabe0000002e0041", "class") //bat文件 58 | fileTypeMap.Store("49545346030000006000", "chm") //bat文件 59 | fileTypeMap.Store("04000000010000001300", "mxp") //bat文件 60 | fileTypeMap.Store("504b0304140006000800", "docx") //docx文件 61 | fileTypeMap.Store("d0cf11e0a1b11ae10000", "wps") //WPS文字wps、表格et、演示dps都是一样的 62 | fileTypeMap.Store("6431303a637265617465", "torrent") 63 | fileTypeMap.Store("6D6F6F76", "mov") //Quicktime (mov) 64 | fileTypeMap.Store("FF575043", "wpd") //WordPerfect (wpd) 65 | fileTypeMap.Store("CFAD12FEC5FD746F", "dbx") //Outlook Express (dbx) 66 | fileTypeMap.Store("2142444E", "pst") //Outlook (pst) 67 | fileTypeMap.Store("AC9EBD8F", "qdf") //Quicken (qdf) 68 | fileTypeMap.Store("E3828596", "pwl") //Windows Password (pwl) 69 | fileTypeMap.Store("2E7261FD", "ram") //Real Audio (ram) 70 | } 71 | 72 | // 获取前面结果字节的二进制 73 | func bytesToHexString(src []byte) string { 74 | res := bytes.Buffer{} 75 | if src == nil || len(src) <= 0 { 76 | return "" 77 | } 78 | temp := make([]byte, 0) 79 | for _, v := range src { 80 | sub := v & 0xFF 81 | hv := hex.EncodeToString(append(temp, sub)) 82 | if len(hv) < 2 { 83 | res.WriteString(strconv.FormatInt(int64(0), 10)) 84 | } 85 | res.WriteString(hv) 86 | } 87 | return res.String() 88 | } 89 | 90 | // 用文件前面几个字节来判断 91 | // fSrc: 文件字节流(就用前面几个字节) 92 | func GetFileType(fSrc []byte) string { 93 | var fileType string 94 | fileCode := bytesToHexString(fSrc) 95 | // 0000001c667479706d70 96 | fileTypeMap.Range(func(key, value interface{}) bool { 97 | k := key.(string) 98 | v := value.(string) 99 | // 如果包含前后缀 0000001c667479706d70 100 | if strings.HasPrefix(fileCode, strings.ToLower(k)) || 101 | strings.HasPrefix(k, strings.ToLower(fileCode)) { 102 | fileType = v 103 | return false 104 | } 105 | return true 106 | }) 107 | return fileType 108 | } 109 | -------------------------------------------------------------------------------- /go/util/checkFile_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestGetFileType(t *testing.T) { 11 | //f, err := os.Open("C:\\Users\\Administrator\\Desktop\\api.html") 12 | f, err := os.Open("C:\\Users\\WenTe\\Desktop\\video\\664bc4e86cfae46338056e7ec016555e.mp4") 13 | if err != nil { 14 | t.Logf("open error: %v", err) 15 | } 16 | fSrc, err := ioutil.ReadAll(f) 17 | fileType := GetFileType(fSrc[:10]) 18 | log.Println(fileType) 19 | } 20 | -------------------------------------------------------------------------------- /go/util/filter.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | const SensitiveWordFilePath = "go/util/key.txt" 11 | 12 | var sensitiveWords []string 13 | 14 | //用于过滤 15 | 16 | var wordReg *regexp.Regexp 17 | 18 | func InitSensitiveFilter() { 19 | //sensitiveWords := []string{ 20 | // "傻逼", 21 | // "傻叉", 22 | // "垃圾", 23 | // "妈的", 24 | // "sb", 25 | //} 26 | //text := "什么垃圾打野,傻逼一样,叫你来开龙不来,sb" 27 | // 28 | //// 构造正则匹配字符 29 | //regStr := strings.Join(sensitiveWords, "|") 30 | //println("regStr -> ", regStr) 31 | //傻逼|傻叉|垃圾|妈的|sb 32 | //wordReg := regexp.MustCompile(regStr) 33 | //text = wordReg.ReplaceAllString(text, "*") 34 | // 35 | //println("text -> ", text) 36 | //func ReadFile(name string) ([]byte, error) {} 37 | content, err := os.ReadFile(SensitiveWordFilePath) 38 | if err != nil { 39 | LogError(err.Error()) 40 | panic(err) 41 | } 42 | s := string(content) 43 | wordReg = regexp.MustCompile(s) 44 | } 45 | 46 | func SensitiveWordsFilter(context string) (string, error) { 47 | test := wordReg.ReplaceAllString(context, "*") 48 | fmt.Printf("%s", test) 49 | return test, nil 50 | } 51 | 52 | func Test() { 53 | sensitiveWords := []string{ 54 | "傻逼", 55 | "傻叉", 56 | "垃圾", 57 | "妈的", 58 | "sb", 59 | } 60 | text := "什么垃圾打野,傻逼一样,叫你来开龙不来,sb" 61 | // 构造正则匹配字符 62 | regStr := strings.Join(sensitiveWords, "|") 63 | println("regStr -> ", regStr) 64 | wordReg := regexp.MustCompile(regStr) 65 | text = wordReg.ReplaceAllString(text, "*") 66 | 67 | println("text -> ", text) 68 | 69 | } 70 | -------------------------------------------------------------------------------- /go/util/filter_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestInitSensitiveFilter(t *testing.T) { 9 | InitSensitiveFilter() 10 | } 11 | 12 | func TestInitSensitiveFilter2(t *testing.T) { 13 | InitSensitiveFilter() 14 | filter, _ := SensitiveWordsFilter("傻逼") 15 | fmt.Printf("%s", filter) 16 | } 17 | 18 | func TestTest(t *testing.T) { 19 | Test() 20 | } 21 | -------------------------------------------------------------------------------- /go/util/log.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "time" 7 | ) 8 | 9 | // 日志插件,封装了一层gin的日志 10 | 11 | var now string 12 | 13 | // Log 普通log 14 | func Log(format string, values ...any) { 15 | now = time.Now().Format("2006/01/02 - 15:04:05") 16 | f := fmt.Sprintf("[DEV] %s %s\n", now, format) 17 | fmt.Fprintf(gin.DefaultWriter, f, values...) 18 | } 19 | 20 | // LogError 带错误信息的log(服务器的错误) 21 | func LogError(ErrorInfo string, values ...any) { 22 | now = time.Now().Format("2006/01/02 - 15:04:05") 23 | f := fmt.Sprintf("[DEV] [Error] %s %s %v \n", now, ErrorInfo, values) 24 | fmt.Fprintf(gin.DefaultWriter, f) 25 | } 26 | 27 | // LogFatal 严重的错误 28 | func LogFatal(ErrorInfo string, values ...any) { 29 | now = time.Now().Format("2006/01/02 - 15:04:05") 30 | f := fmt.Sprintf("[DEV] [Fatal] %s %s %v\n", now, ErrorInfo, values) 31 | fmt.Fprintf(gin.DefaultWriter, f) 32 | } 33 | -------------------------------------------------------------------------------- /go/util/log_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func TestLog(t *testing.T) { 6 | LogFatal("111") 7 | } 8 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 简易版抖音项目 2 | 3 | ## 项目答辩文档与App地址 4 | [飞书文档](https://xp8kgipb5a.feishu.cn/docx/RPAvdVcqpoc6DzxRPlnc5f3VnMb) 5 | ## 项目演示视频 6 | [青训营演示视频](https://www.bilibili.com/video/BV1uT411S79U/?share_source=copy_web&vd_source=fe55b12bbf1a3c973a095834d9f2ba6d) 7 | 8 | ## 项目启动 9 | ### 建表 10 | resources/initial.sql 11 | resources/insertData.sql 12 | 13 | ### 替换redis地址 14 | config/redis.go 15 | 16 | ### 替换腾讯云oss服务密钥 17 | config/tencent_oss 18 | 19 | 20 | ### 直接启动 21 | ```shell 22 | cd go 23 | ``` 24 | ```shell 25 | go run main.go 26 | ``` 27 | 28 | ## APP 操作 29 | 设置服务端地址 30 | 为方便测试登录和注册,及修改网络请求的服务器地址,提供了退出登录和高级设置两个能力。 31 | 1. 点击退出登录会自动重启 32 | 2. 在高级设置中可以配置自己的服务端项目的前缀地址,如下配置的http://192.168.1.7:8080 33 | 在app中访问上述某个接口时就会拼接该前缀地址,例如访问 http://192.168.1.7:8080/douyin/feed/ 拉取视频列表 34 | ![](https://image-bed-1313520634.cos.ap-beijing.myqcloud.com/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-03-07%20162902.png) 35 | 36 | ## 表数据 37 | 38 | ## 使用mvc分层结构 39 | 参考文献 (不懂的可以参考这篇文章) 40 | https://juejin.cn/post/7152299022017888286 41 | 42 | 43 | 44 | ## 版本 45 | * go版本 1.19 46 | * mysql 8.0+ 47 | * redis驱动 48 | ## 使用到的框架与依赖 49 | + gin框架 50 | + gorm框架 51 | + mysql驱动 52 | + golang的jwt框架 53 | + 腾讯云的oss存储(设置了工作流用于截取视频的第一帧(.jpg)并储存在相同的桶中)文献: https://juejin.cn/post/7195857732846567485 54 | + redis驱动 55 | ## 已知错误 56 | 57 | 58 | ### 日志(resources/gin.log) 59 | ### 注意 60 | * 启动服务会自动生成日志文件 61 | * 每次重启会覆盖日志 62 | * 同时封装了log日志 63 | 64 | 65 | ## 待优化地方: 66 | * 建表为了省事使用自增Id,安全性缺乏 (懒得优化了) 67 | * 上传文件相同文件名称的处理(目前将文件名改为时间戳后处理,好像也可以) 68 | * 未设置读写分离 69 | * 一些地方可以用到指针(javer 的问题) 70 | * 服务更加细致,只返回对应的必要的json数据 71 | * 定时任务 72 | 73 | 74 | ## 注意 75 | * **目前上传文件接口只支持mp4格式** 76 | 77 | ## to-do 78 | * 使用docker部署 79 | * 自动执行sql语句 80 | 81 | ### 获奖证书 82 | ![Vfit13.jpeg](https://i.328888.xyz/2023/05/18/Vfit13.jpeg) 83 | 84 | 一个人完成,只拿到了结营证书,拿到优秀学员证书都是使用微服务 85 | 86 | ### 作者: 87 | bowen https://www.github.com/WenTesla 88 | ### 最后修改时间 89 | 2023/5/18 90 | -------------------------------------------------------------------------------- /resources/initial.sql: -------------------------------------------------------------------------------- 1 | -- create 2 | -- database tiktok; 3 | 4 | use 5 | tiktok; 6 | -- ---------------------------- 7 | -- Table structure for users 8 | -- ---------------------------- 9 | DROP TABLE IF EXISTS `users`; 10 | CREATE TABLE `users` 11 | ( 12 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户id,自增主键', 13 | `name` varchar(255) NOT NULL COMMENT '用户名', 14 | `password` varchar(255) NOT NULL COMMENT '用户密码', 15 | PRIMARY KEY (`id`), 16 | KEY `name-password` (`name`,`password`) USING BTREE COMMENT '用户名-密码索引\r\n' 17 | ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb3 COMMENT='用户表'; 18 | 19 | -- ---------------------------- 20 | -- Table structure for videos 21 | -- ---------------------------- 22 | DROP TABLE IF EXISTS `videos`; 23 | CREATE TABLE `videos` 24 | ( 25 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键,视频唯一id', 26 | `author_id` bigint NOT NULL COMMENT '视频作者id', 27 | `play_url` varchar(255) NOT NULL COMMENT '播放url', 28 | `cover_url` varchar(255) NOT NULL COMMENT '封面的url', 29 | `title` varchar(255) DEFAULT NULL COMMENT '视频名称', 30 | `publish_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发布时间戳', 31 | PRIMARY KEY (`id`) USING BTREE, 32 | KEY `publish_time` (`publish_time`) COMMENT '发布时间索引' 33 | ) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8mb3 COMMENT='\r\n视频表'; 34 | 35 | DROP TABLE IF EXISTS `likes`; 36 | CREATE TABLE `likes` 37 | ( 38 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键', 39 | `user_id` int DEFAULT NULL COMMENT '点赞用户的id', 40 | `video_id` int DEFAULT NULL COMMENT '视频作者的id', 41 | `is_cancel` tinyint DEFAULT '0' COMMENT '是否点赞 0-点赞 1-未点赞', 42 | `createTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 43 | `updateTime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', 44 | PRIMARY KEY (`id`), 45 | KEY `user_id` (`user_id`), 46 | KEY `video_id` (`video_id`), 47 | KEY `user_id_video_id` (`user_id`,`video_id`) 48 | ) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='点赞列表'; 49 | 50 | 51 | DROP TABLE IF EXISTS `comments`; 52 | CREATE TABLE `comments` 53 | ( 54 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增id', 55 | `user_id` int DEFAULT NULL COMMENT '用户的id', 56 | `video_id` int DEFAULT NULL COMMENT '视频的id', 57 | `text` varchar(255) DEFAULT NULL COMMENT '评论的内容', 58 | `is_cancel` int DEFAULT '0' COMMENT '是否取消评论', 59 | `createTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 60 | PRIMARY KEY (`id`), 61 | KEY `user_id` (`user_id`), 62 | KEY `video_id` (`video_id`), 63 | KEY `user_id_video_id` (`user_id`,`video_id`) 64 | ) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='评论表-用户评论视频'; 65 | 66 | DROP TABLE IF EXISTS `follows`; 67 | CREATE TABLE `follows` 68 | ( 69 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增id', 70 | `user_id` int DEFAULT NULL COMMENT '用户的id', 71 | `follower_id` int DEFAULT NULL COMMENT '关注的用户', 72 | `cancel` tinyint DEFAULT '0' COMMENT '是否关注', 73 | `createTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 74 | PRIMARY KEY (`id`), 75 | KEY `user_id` (`user_id`), 76 | KEY `follower_id` (`follower_id`), 77 | KEY `user_id_follower_id` (`user_id`,`follower_id`) 78 | ) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户关注列表'; 79 | 80 | DROP TABLE IF EXISTS `messages`; 81 | CREATE TABLE `messages` 82 | ( 83 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增id', 84 | `user_id` bigint NOT NULL COMMENT '用户的Id', 85 | `to_user_id` bigint NOT NULL COMMENT '接受消息的用户Id', 86 | `content` varchar(256) NOT NULL COMMENT '消息内容', 87 | `is_withdraw` tinyint DEFAULT '0' COMMENT '是否撤回 0-不撤回,1-撤回', 88 | `createTime` bigint DEFAULT NULL COMMENT '时间戳', 89 | PRIMARY KEY (`id`), 90 | KEY `用户索引` (`user_id`) USING BTREE COMMENT '发送信息的用户Id索引', 91 | KEY `接受信息的用户索引` (`to_user_id`) USING BTREE COMMENT '接受用户的用户Id索引' 92 | ) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='消息表'; 93 | -------------------------------------------------------------------------------- /resources/insertData.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 2 | VALUES (3, 'bowenzhang', '77a90868207689664f244ad398a871fc'); 3 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 4 | VALUES (4, 'lichangyuan', '77a90868207689664f244ad398a871fc'); 5 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 6 | VALUES (11, 'rantong', '77a90868207689664f244ad398a871fc'); 7 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 8 | VALUES (5, 'sunshixin', 'de9ae573b41776f624526219666336d2'); 9 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 10 | VALUES (6, 'tandonghang', '77a90868207689664f244ad398a871fc'); 11 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 12 | VALUES (14, 'test', '77a90868207689664f244ad398a871fc'); 13 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 14 | VALUES (15, 'test2', '77a90868207689664f244ad398a871fc'); 15 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 16 | VALUES (7, 'tuzhuangzhuang', '77a90868207689664f244ad398a871fc'); 17 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 18 | VALUES (1, 'zhangbowen', '77a90868207689664f244ad398a871fc'); 19 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 20 | VALUES (2, 'zhangbowen1', '77a90868207689664f244ad398a871fc'); 21 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 22 | VALUES (8, 'zhangchangyueyan', '77a90868207689664f244ad398a871fc'); 23 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 24 | VALUES (12, '周子豪', '77a90868207689664f244ad398a871fc'); 25 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 26 | VALUES (10, '张博文', '77a90868207689664f244ad398a871fc'); 27 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 28 | VALUES (17, '张常越岩', '77a90868207689664f244ad398a871fc'); 29 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 30 | VALUES (9, '李常远', '77a90868207689664f244ad398a871fc'); 31 | INSERT INTO `tiktok`.`users` (`id`, `name`, `password`) 32 | VALUES (13, '邢政', '77a90868207689664f244ad398a871fc'); 33 | 34 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 35 | VALUES (1, 1, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/36924f94fdf64cb3e3cfca3956fa6d9c.mp4', 36 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/36924f94fdf64cb3e3cfca3956fa6d9c.jpg', 'test', 37 | '2023-02-02 19:49:36'); 38 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 39 | VALUES (2, 2, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/664bc4e86cfae46338056e7ec016555e.mp4', 40 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/664bc4e86cfae46338056e7ec016555e.jpg', 'test', 41 | '2023-02-02 19:50:58'); 42 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 43 | VALUES (3, 3, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/aefa322953ccc87ffe59525ad1b4e1c0.mp4', 44 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/aefa322953ccc87ffe59525ad1b4e1c0.jpg', 'test', 45 | '2023-02-02 19:51:42'); 46 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 47 | VALUES (4, 4, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/b31512aa2916f3ab484f8a4be569a0fa.mp4', 48 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/b31512aa2916f3ab484f8a4be569a0fa.jpg', 'test', 49 | '2023-02-02 19:51:49'); 50 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 51 | VALUES (5, 5, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/a1da2b17a124aa88d0134d75d3983b5f.mp4', 52 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/a1da2b17a124aa88d0134d75d3983b5f.jpg', 'test', 53 | '2023-02-02 19:51:56'); 54 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 55 | VALUES (6, 2, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/dad18708c59804da1f3abb996cb56770.mp4', 56 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/dad18708c59804da1f3abb996cb56770.jpg', 'test', 57 | '2023-02-02 19:52:00'); 58 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 59 | VALUES (7, 1, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/1675682358.mp4', 60 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/1675682358.jpg', 'test', '2023-02-02 19:52:04'); 61 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 62 | VALUES (8, 2, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/bea309f6840bee5d95c233616b3f1f34.mp4', 63 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/bea309f6840bee5d95c233616b3f1f34.jpg', 'test', 64 | '2023-02-02 19:52:09'); 65 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 66 | VALUES (9, 3, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/dbd19a6cba6bcf02027613b4caefdce8.mp4', 67 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/dbd19a6cba6bcf02027613b4caefdce8.jpg', 'test', 68 | '2023-02-02 19:52:38'); 69 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 70 | VALUES (10, 5, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/b29672e25430abc8eb31daecda52b8cb.mp4', 71 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/b29672e25430abc8eb31daecda52b8cb.jpg', 'test', 72 | '2023-02-02 19:52:43'); 73 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 74 | VALUES (11, 1, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/1671fadf2ac23fed56996c3dc935ce92.mp4', 75 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/1671fadf2ac23fed56996c3dc935ce92.jpg', 'test', 76 | '2023-02-02 19:52:47'); 77 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 78 | VALUES (12, 11, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/V30203-123620.mp4', 79 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/V30203-123620.jpg', '124578', 80 | '2023-02-03 12:45:58'); 81 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 82 | VALUES (13, 4, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/安欣霸凌高启强-哔哩哔哩_302651035.mp4', 83 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/安欣霸凌高启强-哔哩哔哩_302651035.jpg', '高启强', 84 | '2023-02-04 18:36:05'); 85 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 86 | VALUES (14, 9, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/1675658873.mp4', 87 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/1675658873.jpg', '维利维亚', 88 | '2023-02-06 12:47:55'); 89 | INSERT INTO `tiktok`.`videos` (`id`, `author_id`, `play_url`, `cover_url`, `title`, `publish_time`) 90 | VALUES (15, 9, 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/1675685888.mp4', 91 | 'https://tiktok-video-1313520634.cos.ap-beijing.myqcloud.com/1675685888.jpg', 'girl', '2023-02-06 20:18:09'); 92 | 93 | -- like 表与 redis 缓存交互 这里不插入数据 94 | 95 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 96 | VALUES (34, 1, 9, 0, '2023-02-21 17:16:26'); 97 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 98 | VALUES (35, 1, 2, 0, '2023-02-21 17:44:27'); 99 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 100 | VALUES (36, 9, 1, 0, '2023-02-21 17:45:20'); 101 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 102 | VALUES (37, 9, 5, 0, '2023-02-21 17:50:01'); 103 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 104 | VALUES (38, 9, 4, 0, '2023-02-21 17:50:21'); 105 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 106 | VALUES (39, 11, 9, 0, '2023-02-21 17:52:01'); 107 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 108 | VALUES (40, 11, 5, 0, '2023-02-21 17:52:13'); 109 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 110 | VALUES (41, 11, 4, 0, '2023-02-21 17:52:13'); 111 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 112 | VALUES (42, 14, 2, 0, '2023-02-21 17:53:19'); 113 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 114 | VALUES (43, 14, 1, 0, '2023-02-21 17:53:32'); 115 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 116 | VALUES (44, 14, 3, 0, '2023-02-21 17:53:38'); 117 | INSERT INTO `tiktok`.`follows` (`id`, `user_id`, `follower_id`, `cancel`, `createTime`) 118 | VALUES (45, 10, 9, 0, '2023-02-21 18:42:49'); 119 | 120 | INSERT INTO `tiktok`.`comments` (`id`, `user_id`, `video_id`, `text`, `is_cancel`, `createTime`) 121 | VALUES (48, 1, 15, '太美了哈哈', 0, '2023-02-21 17:19:31'); 122 | INSERT INTO `tiktok`.`comments` (`id`, `user_id`, `video_id`, `text`, `is_cancel`, `createTime`) 123 | VALUES (49, 9, 15, '自己发的*哈哈', 0, '2023-02-21 17:48:08'); 124 | 125 | INSERT INTO `tiktok`.`messages` (`id`, `user_id`, `to_user_id`, `content`, `is_withdraw`, `createTime`) 126 | VALUES (22, 1, 9, '测试', 0, 1676976129); 127 | --------------------------------------------------------------------------------