├── .gitignore ├── LICENSE ├── README.md ├── client ├── .gitignore ├── Dockerfile ├── Dockerfile-build ├── apis │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── api │ │ │ ├── ai-mange.ts │ │ │ ├── archive.ts │ │ │ ├── carousel.ts │ │ │ ├── code.ts │ │ │ ├── collect.ts │ │ │ ├── collection.ts │ │ │ ├── comment.ts │ │ │ ├── danmuku.ts │ │ │ ├── follow.ts │ │ │ ├── history.ts │ │ │ ├── msg-announce.ts │ │ │ ├── msg-at.ts │ │ │ ├── msg-like.ts │ │ │ ├── msg-reply.ts │ │ │ ├── msg-whisper.ts │ │ │ ├── partition.ts │ │ │ ├── resource.ts │ │ │ ├── token.ts │ │ │ ├── upload.ts │ │ │ ├── user.ts │ │ │ ├── video.ts │ │ │ └── watchroom.ts │ │ ├── request.ts │ │ └── types │ │ │ ├── ai-mange-type.ts │ │ │ ├── carousel-type.ts │ │ │ ├── collect-type.ts │ │ │ ├── collection-type.ts │ │ │ ├── comment-type.ts │ │ │ ├── danmuku-type.ts │ │ │ ├── history-type.ts │ │ │ ├── msg-announce-type.ts │ │ │ ├── msg-at-type.ts │ │ │ ├── msg-like-type.ts │ │ │ ├── msg-reply-type.ts │ │ │ ├── msg-whisper-type.ts │ │ │ ├── partition-type.ts │ │ │ ├── resource-type.ts │ │ │ ├── upload-options-type.ts │ │ │ ├── user-type.ts │ │ │ ├── video-type.ts │ │ │ └── watchroom-type.ts │ └── tsconfig.json ├── components │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── common-avatar │ │ │ └── Index.vue │ │ ├── search-box │ │ │ └── Index.vue │ │ ├── types │ │ │ └── player.d.ts │ │ ├── video-artplayer │ │ │ ├── ArtplayerCom.vue │ │ │ ├── ArtplayerComNoDanmu.vue │ │ │ └── ArtplayerComRoom.vue │ │ ├── video-player │ │ │ ├── Index.vue │ │ │ └── watchroomPlayer.vue │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── custom.conf ├── icons │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── Add.vue │ │ ├── ArrowDown.vue │ │ ├── ArrowLeft.vue │ │ ├── ArrowRight.vue │ │ ├── ArrowUp.vue │ │ ├── Avatar.vue │ │ ├── Close.vue │ │ ├── Collect.vue │ │ ├── Collection.vue │ │ ├── Comment.vue │ │ ├── Delete.vue │ │ ├── Edit.vue │ │ ├── Female.vue │ │ ├── Forbid.vue │ │ ├── History.vue │ │ ├── Kans.vue │ │ ├── Like.vue │ │ ├── Link.vue │ │ ├── Male.vue │ │ ├── Me.vue │ │ ├── MenuFold.vue │ │ ├── MenuOutline.vue │ │ ├── MenuUnfold.vue │ │ ├── Message.vue │ │ ├── Refresh.vue │ │ ├── Search.vue │ │ ├── Setting.vue │ │ ├── Upload.vue │ │ └── Video.vue │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite-env.d.ts │ └── vite.config.ts ├── package.json ├── packages │ ├── manage-client │ │ ├── .eslintrc.cjs │ │ ├── env.d.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── public │ │ │ └── favicon.ico │ │ ├── src │ │ │ ├── App.vue │ │ │ ├── assets │ │ │ │ ├── login-illustration.png │ │ │ │ └── not-found-page.png │ │ │ ├── components │ │ │ │ ├── carousel-uploader │ │ │ │ │ └── Index.vue │ │ │ │ └── header-bar │ │ │ │ │ └── Index.vue │ │ │ ├── hooks │ │ │ │ └── send-code-hooks.ts │ │ │ ├── main.ts │ │ │ ├── router │ │ │ │ └── index.ts │ │ │ ├── stores │ │ │ │ └── index.ts │ │ │ ├── theme │ │ │ │ └── index.ts │ │ │ ├── typings │ │ │ │ └── theme.d.ts │ │ │ └── views │ │ │ │ ├── aiMange │ │ │ │ └── Index.vue │ │ │ │ ├── announce │ │ │ │ └── Index.vue │ │ │ │ ├── carousel │ │ │ │ └── Index.vue │ │ │ │ ├── home │ │ │ │ └── Index.vue │ │ │ │ ├── login │ │ │ │ ├── Index.vue │ │ │ │ └── component │ │ │ │ │ └── LoginForm.vue │ │ │ │ ├── partition │ │ │ │ └── Index.vue │ │ │ │ ├── result │ │ │ │ └── page-not-found │ │ │ │ │ └── Index.vue │ │ │ │ ├── review │ │ │ │ ├── Index.vue │ │ │ │ └── component │ │ │ │ │ └── VideoList.vue │ │ │ │ ├── user │ │ │ │ └── Index.vue │ │ │ │ └── video │ │ │ │ ├── Index.vue │ │ │ │ └── component │ │ │ │ └── PartitionSelector.vue │ │ ├── tsconfig.config.json │ │ ├── tsconfig.json │ │ └── vite.config.ts │ └── web-client │ │ ├── .eslintrc.cjs │ │ ├── env.d.ts │ │ ├── index.html │ │ ├── package.json │ │ ├── public │ │ └── favicon.ico │ │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ ├── collection.png │ │ │ ├── filing.png │ │ │ ├── login-bg.svg │ │ │ └── not-found-page.png │ │ ├── components │ │ │ ├── follow-list │ │ │ │ └── Index.vue │ │ │ ├── header-bar │ │ │ │ └── Index.vue │ │ │ ├── leaf-cropper │ │ │ │ ├── Index.vue │ │ │ │ └── component │ │ │ │ │ ├── AvatarCropper.vue │ │ │ │ │ ├── CoverCropper.vue │ │ │ │ │ ├── PicCropper.vue │ │ │ │ │ └── SpaceCoverCropper.vue │ │ │ ├── login-card │ │ │ │ ├── Index.vue │ │ │ │ └── component │ │ │ │ │ ├── LoginForm.vue │ │ │ │ │ ├── LoginIllustration.vue │ │ │ │ │ └── RegisterForm.vue │ │ │ ├── login-dialog │ │ │ │ └── Index.vue │ │ │ ├── steps │ │ │ │ └── Index.vue │ │ │ └── video-item │ │ │ │ └── Index.vue │ │ ├── hooks │ │ │ ├── collection-hooks.ts │ │ │ ├── comment-hooks.ts │ │ │ ├── mention-hooks.ts │ │ │ ├── render-icon-hooks.ts │ │ │ ├── send-code-hooks.ts │ │ │ └── user-follow-hooks.ts │ │ ├── locale │ │ │ ├── en-US.ts │ │ │ ├── index.ts │ │ │ ├── ja-JP.ts │ │ │ └── zh-CN.ts │ │ ├── main.ts │ │ ├── router │ │ │ ├── index.ts │ │ │ ├── message-routes.ts │ │ │ ├── space-routes.ts │ │ │ ├── upload-routes.ts │ │ │ └── user-routes.ts │ │ ├── stores │ │ │ ├── index.ts │ │ │ ├── login-store.ts │ │ │ └── video-count-store.ts │ │ ├── theme │ │ │ └── index.ts │ │ ├── typings │ │ │ └── theme.d.ts │ │ └── views │ │ │ ├── collection │ │ │ └── Index.vue │ │ │ ├── find-password │ │ │ └── Index.vue │ │ │ ├── history │ │ │ └── Index.vue │ │ │ ├── home │ │ │ ├── Index.vue │ │ │ └── component │ │ │ │ ├── HomeCarousel.vue │ │ │ │ ├── HomeHeader.vue │ │ │ │ └── HomeSidebar.vue │ │ │ ├── login │ │ │ └── Index.vue │ │ │ ├── message │ │ │ ├── Index.vue │ │ │ ├── announce │ │ │ │ └── Index.vue │ │ │ ├── at │ │ │ │ └── Index.vue │ │ │ ├── like │ │ │ │ └── Index.vue │ │ │ ├── reply │ │ │ │ └── Index.vue │ │ │ └── whisper │ │ │ │ └── Index.vue │ │ │ ├── result │ │ │ └── page-not-found │ │ │ │ └── Index.vue │ │ │ ├── search │ │ │ └── Index.vue │ │ │ ├── space │ │ │ ├── Index.vue │ │ │ ├── collection │ │ │ │ ├── Index.vue │ │ │ │ └── component │ │ │ │ │ └── CoverUploader.vue │ │ │ ├── follow │ │ │ │ └── Index.vue │ │ │ ├── setting │ │ │ │ ├── Index.vue │ │ │ │ ├── info │ │ │ │ │ └── Index.vue │ │ │ │ └── security │ │ │ │ │ └── Index.vue │ │ │ └── video │ │ │ │ └── Index.vue │ │ │ ├── upload │ │ │ ├── Index.vue │ │ │ ├── comment-manage │ │ │ │ └── Index.vue │ │ │ ├── upload-video │ │ │ │ ├── Index.vue │ │ │ │ └── component │ │ │ │ │ ├── CoverUploader.vue │ │ │ │ │ ├── PartitionSelector.vue │ │ │ │ │ ├── UploadVideo.vue │ │ │ │ │ ├── UploadVideoInfo.vue │ │ │ │ │ └── VideoUploader.vue │ │ │ └── video-manage │ │ │ │ └── Index.vue │ │ │ ├── user │ │ │ ├── Index.vue │ │ │ └── component │ │ │ │ ├── UserFollow.vue │ │ │ │ └── UserVideo.vue │ │ │ ├── video-list │ │ │ └── Index.vue │ │ │ ├── video │ │ │ ├── Index.vue │ │ │ └── component │ │ │ │ ├── ArchiveInfo.vue │ │ │ │ ├── AuthorCard.vue │ │ │ │ ├── AuthorVideo.vue │ │ │ │ ├── CollectionList.vue │ │ │ │ ├── CommentList.vue │ │ │ │ ├── PartList.vue │ │ │ │ └── addRoom.vue │ │ │ └── watchroom │ │ │ ├── Index.vue │ │ │ ├── component │ │ │ ├── ArchiveInfo.vue │ │ │ ├── AuthorCard.vue │ │ │ ├── AuthorVideo.vue │ │ │ ├── CollectionList.vue │ │ │ ├── CommentList.vue │ │ │ ├── PartList.vue │ │ │ └── msg.vue │ │ │ └── goinRoom.vue │ │ ├── tsconfig.config.json │ │ ├── tsconfig.json │ │ └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── utils │ ├── .env │ ├── index.ts │ ├── package.json │ ├── src │ ├── color.ts │ ├── config.ts │ ├── format.ts │ ├── resource.ts │ ├── review-code.ts │ ├── status-code.ts │ ├── stored-data.ts │ ├── time.ts │ ├── util.ts │ ├── uuid.ts │ └── watchroom-code.ts │ └── tsconfig.json ├── data └── mysql │ └── init │ └── init.sql ├── docker-compose.yml ├── docs ├── .gitignore ├── .vitepress │ └── config.ts ├── data │ └── tb_plooks(仅结构).sql ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── src │ ├── api │ │ ├── archive.md │ │ ├── captcha.md │ │ ├── carousel.md │ │ ├── collection.md │ │ ├── comment.md │ │ ├── danmaku.md │ │ ├── follow.md │ │ ├── history.md │ │ ├── index.md │ │ ├── message.md │ │ ├── partition.md │ │ ├── redis.md │ │ ├── resource.md │ │ ├── upload.md │ │ ├── user.md │ │ └── video.md │ ├── guide │ │ ├── api.md │ │ ├── index.md │ │ ├── qa.md │ │ ├── screenshot.md │ │ └── web.md │ ├── image │ │ ├── alipay.png │ │ ├── manage_gpt.png │ │ ├── manage_login.png │ │ ├── manage_user.png │ │ ├── manage_videoup.png │ │ ├── token.png │ │ ├── web_home.png │ │ ├── web_login.png │ │ ├── web_plook.png │ │ ├── web_space.png │ │ ├── web_upload.png │ │ ├── web_video.png │ │ └── wechat.png │ ├── index.md │ └── other │ │ └── donate.md ├── tsconfig.json └── vite.config.ts └── server ├── Dockerfile ├── Dockerfile-build ├── pom.xml └── src └── main ├── java └── com │ └── schuanhe │ └── plooks │ ├── Application.java │ ├── config │ ├── CorsConfig.java │ ├── QiniuFileConfig.java │ ├── RedisConfig.java │ ├── SecurityConfig.java │ └── WebSocketConfig.java │ ├── controller │ ├── Admin │ │ ├── AiMangeController.java │ │ ├── AnnouncesAdminController.java │ │ ├── CarouselAdminController.java │ │ ├── PartitionAdminController.java │ │ ├── UserAdminController.java │ │ └── VideoAdminController.java │ ├── User │ │ ├── ArchiveController.java │ │ ├── CarouselController.java │ │ ├── CollectionController.java │ │ ├── CommentController.java │ │ ├── DanmukusController.java │ │ ├── FollowsController.java │ │ ├── HistoriesController.java │ │ ├── MessageController.java │ │ ├── PartitionController.java │ │ ├── ResourceController.java │ │ ├── UploadController.java │ │ ├── UserController.java │ │ ├── VideoController.java │ │ ├── WatchroomController.java │ │ └── WhispersController.java │ └── WebSocket │ │ ├── VideoOnlineWebSocket.java │ │ ├── WatchRoomWebSocket.java │ │ └── WhisperWebSocket.java │ ├── domain │ ├── Archive.java │ ├── Carousels.java │ ├── Collections.java │ ├── Comments.java │ ├── Danmukus.java │ ├── Follows.java │ ├── Histories.java │ ├── Message.java │ ├── Partition.java │ ├── Resources.java │ ├── User.java │ ├── Video.java │ ├── Whispers.java │ ├── form │ │ ├── LoginForm.java │ │ ├── ReplyForm.java │ │ └── RoomFrom.java │ └── model │ │ └── UserDetailsImpl.java │ ├── filter │ └── JwtAuthenticationTokenFilter.java │ ├── handler.impl │ ├── AccessDeniedHandlerImpl.java │ ├── AuthenticationEntryPointImpl.java │ └── MyDaoAuthenticationProviderImpl.java │ ├── mapper │ ├── AiMangeMapper.java │ ├── ArchiveMapper.java │ ├── CarouselsMapper.java │ ├── CollectionsMapper.java │ ├── CommentsMapper.java │ ├── DanmukusMapper.java │ ├── FollowsMapper.java │ ├── HistoriesMapper.java │ ├── MessageMapper.java │ ├── PartitionsMapper.java │ ├── ResourcesMapper.java │ ├── UserMapper.java │ ├── VideoMapper.java │ └── WhispersMapper.java │ ├── service │ ├── Admin │ │ ├── AiMangeService.java │ │ ├── AnnouncesAdminService.java │ │ ├── CarouselAdminService.java │ │ ├── PartitionAdminService.java │ │ ├── UserAdminService.java │ │ ├── VideoAdminService.java │ │ └── impl │ │ │ ├── AiMangeServiceImpl.java │ │ │ ├── AnnouncesAdminServiceImpl.java │ │ │ ├── CarouselAdminServiceImpl.java │ │ │ ├── PartitionAdminServiceImpl.java │ │ │ ├── UserAdminServiceImpl.java │ │ │ └── VideoAdminServiceImpl.java │ └── User │ │ ├── ArchiveService.java │ │ ├── CarouselsService.java │ │ ├── CollectionsService.java │ │ ├── CommentsService.java │ │ ├── DanmukusService.java │ │ ├── FollowsService.java │ │ ├── HistoriesService.java │ │ ├── MessageService.java │ │ ├── PartitionService.java │ │ ├── ResourcesService.java │ │ ├── UploadService.java │ │ ├── UserService.java │ │ ├── VideoService.java │ │ ├── WatchroomService.java │ │ ├── WhispersService.java │ │ └── impl │ │ ├── ArchiveServiceImpl.java │ │ ├── CarouselsServiceImpl.java │ │ ├── CollectionsServiceImpl.java │ │ ├── CommentsServiceImpl.java │ │ ├── DanmukusServiceImpl.java │ │ ├── FollowsServiceImpl.java │ │ ├── HistoriesServiceImpl.java │ │ ├── MessageServiceImpl.java │ │ ├── PartitionServiceImpl.java │ │ ├── QiniuServiceImpl.java │ │ ├── ResourcesServiceImpl.java │ │ ├── UploadServiceImpl.java │ │ ├── UserDetailsServiceImpl.java │ │ ├── UserServiceImpl.java │ │ ├── VideoServiceImpl.java │ │ ├── WatchroomServiceImpl.java │ │ └── WhispersServiceImpl.java │ └── utils │ ├── CoreUtils.java │ ├── FastJsonRedisSerializer.java │ ├── JwtUtil.java │ ├── MailUtil.java │ ├── RedisCache.java │ ├── ResponseResult.java │ ├── SqlProvider.java │ ├── WebSocketManager.java │ └── WebUtils.java └── resources ├── application.yml └── mapper ├── ArchiveMapper.xml ├── CarouselsMapper.xml ├── CollectionsMapper.xml ├── CommentsMapper.xml ├── DanmukusMapper.xml ├── FollowsMapper.xml ├── HistoriesMapper.xml ├── MessageMapper.xml ├── PartitionsMapper.xml ├── ResourcesMapper.xml ├── UserMapper.xml ├── VideoMapper.xml └── WhispersMapper.xml /.gitignore: -------------------------------------------------------------------------------- 1 | ### Java ### 2 | 3 | # Java编译生成的class文件 4 | *.class 5 | # Java Web应用生成的war、ear包 6 | *.war 7 | *.ear 8 | # Java虚拟机生成的错误日志 9 | hs_err_pid* 10 | 11 | ### SpringBoot ### 12 | 13 | 14 | # 忽略target目录下的所有文件 15 | target/ 16 | 17 | # 忽略IntelliJ IDEA工程文件 18 | .idea/ 19 | *.iml 20 | *.iws 21 | *.ipr 22 | 23 | # 忽略SpringBoot的日志文件 24 | logs/ 25 | 26 | # 暂时忽略部分docs目录下的文件 27 | docs/apis/ 28 | 29 | 30 | # 本地配置文件 31 | application-local.yml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plooks 2 | 3 | plooks是一款用户自由上传视频,并且可以使用一起看功能的视频网站。 4 | 5 | 本项目采用前后端分离的开发模式,前端使用vue.js,后端使用springboot。 6 | 7 | 项目预览地址 8 | http://plooks.schuanhe.com 9 | 10 | ## 最近计划 11 | 12 | - [x] 部署文档与官网 13 | - [x] docker部署 14 | 15 | 16 | 17 | ## 目录结构 18 | 19 | ### client 20 | 21 | 该目录为前端vue项目,其中packages目录下分为用户端和管理端 22 | 23 | ### server 24 | 25 | 该目录为后端(api)使用springboot构建 26 | 27 | ### docs 28 | 文档目录 29 | 30 | 存放sql文件,注意数据库版本必须大于7.0,因为使用了json字段 31 | 账号:admin 32 | 密码:123456 33 | 34 | ## 打包教程 35 | 36 | ### 后端(server) 37 | 38 | 请先将**server/src/main/resources/application.yml**填写完整 39 | 40 | 完整后直接使用maven打包 41 | 42 | ```bash 43 | mvn package 44 | ``` 45 | 46 | ### 前端 47 | 48 | 前端分为manage-client和web-client 49 | 50 | 请先对 **client\utils\src\config.ts** 进行配置 51 | 52 | ```bash 53 | # 进入前端 54 | ```bash 55 | cd client 56 | 57 | # 使用pnpm安装依赖,也可以使用npm 58 | 59 | pnpm install 60 | 61 | # 打包manage-client 62 | cd packages\manage-client 63 | 64 | pnpm run build 65 | 66 | # 打包web-client 67 | cd ..\web-client 68 | 69 | pnpm run build 70 | ``` 71 | ## 部署 72 | 73 | ### 后端 74 | 75 | 后端使用springboot打包后,直接使用java -jar运行即可 76 | 77 | 端口为2023 78 | 79 | ### 前端 80 | 81 | 前端打包后,将会有两个文件夹,manage和web 82 | 83 | manage为管理端,web为用户端 84 | 85 | 使用nginx部署即可 86 | 87 | nginx配置文件如下 88 | 89 | ```nginx 90 | server { 91 | listen 80; 92 | server_name localhost; 93 | 94 | location / { 95 | root /usr/share/nginx/html/web; 96 | index index.html index.htm; 97 | } 98 | 99 | location /manage { 100 | root /usr/share/nginx/html/manage; 101 | index index.html index.htm; 102 | } 103 | } 104 | ``` 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | web 14 | manage 15 | mobile 16 | *.local 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | 29 | 30 | # 自定义 31 | /packages/web-client/src/test/ 32 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | # nginx镜像 2 | FROM nginx:1.23.3-alpine as production-stage 3 | 4 | # 移除nginx容器的default.conf文件 5 | RUN rm /etc/nginx/conf.d/default.conf 6 | # 把主机的nginx.conf文件复制到nginx容器的/etc/nginx文件夹下 7 | COPY ./custom.conf /etc/nginx/conf.d/ 8 | # 拷贝前端vue项目打包后生成的文件到nginx下运行 9 | COPY dist /usr/share/nginx/html 10 | 11 | # 检查是否存在 index.html 文件 12 | RUN if [ ! -f /usr/share/nginx/html/web/index.html ]; then \ 13 | echo "Error: 请先打包前端,或者使用Dockerfile-build 运行:docker build -f Dockerfile-build -t \"plooks-web\" ."; \ 14 | exit 1; \ 15 | fi 16 | 17 | # 暴露2024端口 18 | EXPOSE 2024 19 | # 使用daemon off的方式将nginx运行在前台保证镜像不至于退出 20 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /client/Dockerfile-build: -------------------------------------------------------------------------------- 1 | # node镜像 2 | FROM node:latest as build-stage 3 | 4 | WORKDIR /web 5 | COPY . . 6 | 7 | # 设置淘宝npm镜像 8 | RUN npm config set registry https://registry.npmmirror.com 9 | # 安装pnpm 10 | RUN npm install -g pnpm 11 | # 安装依赖 12 | RUN pnpm install 13 | 14 | # 打包PC端网页 15 | WORKDIR /web/packages/web-client 16 | RUN pnpm run build 17 | 18 | # 打包后台管理端网页 19 | WORKDIR /web/packages/manage-client 20 | RUN pnpm run build 21 | 22 | # nginx镜像 23 | FROM nginx:1.23.3-alpine as production-stage 24 | 25 | # 移除nginx容器的default.conf文件 26 | RUN rm /etc/nginx/conf.d/default.conf 27 | 28 | # 把主机的nginx.conf文件复制到nginx容器的/etc/nginx文件夹下 29 | COPY ./custom.conf /etc/nginx/ 30 | conf.d/ 31 | 32 | # 拷贝前端vue项目打包后生成的文件到nginx下运行 33 | COPY --from=build-stage /web/packages/web-client/web /usr/share/nginx/html/web 34 | COPY --from=build-stage /web/packages/manage-client/manage /usr/share/nginx/html/manage 35 | 36 | # 暴露2024端口 37 | EXPOSE 2024 38 | 39 | # 使用daemon off的方式将nginx运行在前台保证镜像不至于退出 40 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /client/apis/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/api/user'; 2 | export * from './src/types/user-type'; 3 | 4 | export * from './src/api/token'; 5 | 6 | export * from './src/api/code'; 7 | 8 | export * from "./src/api/upload"; 9 | export * from "./src/types/upload-options-type"; 10 | 11 | export * from "./src/api/partition"; 12 | export * from "./src/types/partition-type"; 13 | 14 | export * from "./src/api/video"; 15 | export * from "./src/types/video-type"; 16 | 17 | export * from "./src/api/resource"; 18 | export * from "./src/types/resource-type"; 19 | 20 | export * from "./src/api/archive"; 21 | 22 | export * from "./src/api/collection"; 23 | export * from "./src/types/collection-type"; 24 | 25 | export * from "./src/api/collect"; 26 | export * from "./src/types/collect-type"; 27 | 28 | export * from "./src/api/comment"; 29 | export * from "./src/types/comment-type"; 30 | 31 | export * from "./src/api/follow"; 32 | 33 | export * from "./src/api/msg-announce"; 34 | export * from "./src/types/msg-announce-type"; 35 | 36 | export * from "./src/api/msg-whisper"; 37 | export * from "./src/types/msg-whisper-type"; 38 | 39 | export * from "./src/api/msg-like"; 40 | export * from "./src/types/msg-like-type"; 41 | 42 | export * from "./src/api/msg-at"; 43 | export * from "./src/types/msg-at-type"; 44 | 45 | export * from "./src/api/msg-reply"; 46 | export * from "./src/types/msg-reply-type"; 47 | 48 | export * from "./src/api/history"; 49 | export * from "./src/types/history-type"; 50 | 51 | export * from "./src/api/danmuku"; 52 | export * from "./src/types/danmuku-type"; 53 | 54 | export * from "./src/api/carousel"; 55 | export * from "./src/types/carousel-type"; 56 | 57 | export * from "./src/api/watchroom"; 58 | export * from "./src/types/watchroom-type" 59 | 60 | export * from "./src/api/ai-mange"; 61 | export * from "./src/types/ai-mange-type" 62 | -------------------------------------------------------------------------------- /client/apis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plooks/apis", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "main": "index.ts", 7 | "devDependencies": { 8 | "typescript": "^4.6.4", 9 | "vite": "^3.2.0" 10 | }, 11 | "dependencies": { 12 | "@plooks/utils": "workspace:^1.0.0", 13 | "axios": "^1.1.3" 14 | } 15 | } -------------------------------------------------------------------------------- /client/apis/src/api/ai-mange.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | 3 | // 请求数据 4 | export const getAiMngeData = (q: string) => { 5 | return request.post('v1/aiMange/getAiData' , {q:q},{ 6 | // 超时时间设置30s 7 | timeout: 30000, 8 | }) 9 | } -------------------------------------------------------------------------------- /client/apis/src/api/archive.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | 3 | // 获取点赞收藏数据 4 | export const getArchiveStatAPI = (vid: number) => { 5 | return request.get('v1/archive/' + vid); 6 | } 7 | 8 | // 是否点赞 9 | export const getLikeStatusAPI = (vid: number) => { 10 | return request.get('v1/archive/has/like/' + vid); 11 | } 12 | 13 | // 是否收藏 14 | export const getCollectStatusAPI = (vid: number) => { 15 | return request.get('v1/archive/has/collect/' + vid); 16 | } 17 | 18 | //点赞 19 | export const likeAPI = (id: number) => { 20 | return request.post('v1/archive/like/' + id ); 21 | } 22 | 23 | //取消赞 24 | export const cancelLikeAPI = (id: number) => { 25 | return request.delete('v1/archive/like/' + id ) 26 | } 27 | -------------------------------------------------------------------------------- /client/apis/src/api/carousel.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { AddCarouselType } from '../types/carousel-type'; 3 | 4 | // 添加轮播图 5 | export const addCarouselAPI = (carousel: AddCarouselType) => { 6 | return request.post('v1/carousel/admin', carousel); 7 | } 8 | 9 | // 获取轮播图 10 | export const getCarouselAPI = () => { 11 | return request.get(`v1/carousel`); 12 | } 13 | 14 | // 删除轮播图 15 | export const deleteCarouselAPI = (id: number) => { 16 | return request.delete(`v1/carousel/admin/${id}` ); 17 | } 18 | -------------------------------------------------------------------------------- /client/apis/src/api/code.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { EmailCodeType,UserLoginType } from '../types/user-type'; 3 | 4 | // 发送验证码 5 | export const sendEmailCodeAPI = (email: EmailCodeType) => { 6 | // 将EmailCodeType,转化为UserLoginType 7 | const userLogin: UserLoginType = { 8 | user: { 9 | email: email.email, 10 | // 有userneme的场景,才传username 11 | username: email.username ? email.username : undefined 12 | }, 13 | code: email.code, 14 | uuid: email.uuid 15 | } 16 | 17 | switch (email.scene) { 18 | case 1: 19 | return request.post("v1/user/register/email", userLogin); // 注册验证码 20 | case 2: 21 | return request.post("v1/user/login/email/code", userLogin); // 登录验证码 22 | case 3: 23 | return request.post("v1/user/modify/email/code", userLogin); // 修改密码 24 | default: 25 | return request.post("v1/user/register/email", userLogin); // 默认为注册 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/apis/src/api/collect.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { CollectType } from '../types/collect-type'; 3 | 4 | //收藏 5 | export const collectAPI = (collect: CollectType) => { 6 | return request.post('v1/archive/collect', collect); 7 | } 8 | 9 | // 获取该收藏视频的收藏夹 10 | export const getCollectedCollection = (vid: number) => { 11 | return request.get(`v1/archive/collect/${vid}`); 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /client/apis/src/api/collection.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { ModifyCollectionType } from "../types/collection-type"; 3 | 4 | //获取收藏夹列表 5 | export const getCollectionListAPI = () => { 6 | return request.get('v1/collection'); 7 | } 8 | 9 | //创建收藏夹 10 | export const addCollectionAPI = (name: string) => { 11 | return request.post('v1/collection', { name }); 12 | } 13 | 14 | //修改收藏夹 15 | export const modifyCollectionAPI = (collect: ModifyCollectionType) => { 16 | return request.put('v1/collection', collect); 17 | } 18 | 19 | //获取收藏夹信息(非所有者仅获取公开的) 20 | export const getCollectionInfoAPI = (id: number) => { 21 | return request.get(`v1/collection/${id}`); 22 | } 23 | 24 | //删除收藏夹 25 | export const deleteCollectionAPI = (id: number) => { 26 | return request.delete('v1/collection/' + id ); 27 | } -------------------------------------------------------------------------------- /client/apis/src/api/comment.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { AddCommentType } from '../types/comment-type'; 3 | 4 | // 评论 5 | export const addCommentAPI = (postComment: AddCommentType) => { 6 | return request.post('v1/comment', postComment); 7 | } 8 | 9 | // 回复 10 | export const addReplyAPI = (postComment: AddCommentType) => { 11 | return request.post('v1/comment/reply', postComment); 12 | } 13 | 14 | //获取评论 15 | export const getCommentListAPI = (vid: number, page: number, page_size: number) => { 16 | return request.get(`v1/comment/${vid}/${page_size}/${page}`); 17 | } 18 | 19 | //获取回复 20 | export const getReplyListAPI = (cid: string, page: number, page_size: number) => { 21 | return request.get(`v1/comment/reply/${cid}/${page_size}/${page}`); 22 | } 23 | 24 | //删除评论 25 | export const deleteCommentAPI = (id: string,vid: number) => { 26 | return request.delete(`v1/comment/${vid}/${id}`); 27 | } 28 | 29 | //删除回复 30 | export const deleteReplyAPI = (commentId: string, replyId: string, vid: number) => { 31 | console.log(vid); 32 | 33 | return request.delete(`v1/comment/reply/${vid}/${commentId}/${replyId}`); 34 | } 35 | 36 | 37 | // //获取管理评论 38 | // export const getManageCommentListAPI = (vid: number, page: number, page_size: number) => { 39 | // return request.get(`v1/comment/list/manage?vid=${vid}&page=${page}&page_size=${page_size}`); 40 | // } -------------------------------------------------------------------------------- /client/apis/src/api/danmuku.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { AddDanmukuType } from '../types/danmuku-type'; 3 | 4 | //发送弹幕 5 | export const sendDanmukuAPI = (Danmuku: AddDanmukuType) => { 6 | return request.post('v1/danmuku', Danmuku); 7 | } 8 | 9 | //获取弹幕 10 | export const getDanmukuAPI = (vid: number, part: number) => { 11 | return request.get(`v1/danmuku/${vid}/${part}`); 12 | } 13 | -------------------------------------------------------------------------------- /client/apis/src/api/follow.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | 3 | //关注 4 | export const followAPI = (id: number) => { 5 | return request.post('v1/follow/'+ id); 6 | } 7 | 8 | //取消关注 9 | export const unfollowAPI = (id: number) => { 10 | return request.delete('v1/follow/' + id ) 11 | } 12 | 13 | //获取关注状态 14 | export const getFollowStatusAPI = (fid: number) => { 15 | return request.get(`v1/follow/status/${fid}`) 16 | } 17 | 18 | //获取关注数据 19 | export const getFollowDataAPI = (uid: number) => { 20 | return request.get(`v1/follow/count/${uid}`) 21 | } 22 | 23 | //获取关注列表 24 | export const getFollowingAPI = (uid: number, page: number, page_size: number) => { 25 | return request.get(`v1/follow/following/${uid}/${page_size}/${page}`) 26 | } 27 | 28 | //获取粉丝列表 29 | export const getFollowersAPI = (uid: number, page: number, page_size: number) => { 30 | return request.get(`v1/follow/follower/${uid}/${page_size}/${page}`) 31 | } -------------------------------------------------------------------------------- /client/apis/src/api/history.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { AddHistoryType } from '../types/history-type'; 3 | 4 | // 上传历史记录 5 | export const addHistoryAPI = (addHistory: AddHistoryType) => { 6 | return request.post('v1/history', addHistory); 7 | } 8 | 9 | // 获取播放进度 10 | export const getHistoryProgressAPI = (vid: number) => { 11 | return request.get(`v1/history/progress/${vid}`); 12 | } 13 | 14 | // 获取历史记录 15 | export const getHistoryVideoAPI = (page: number,pageSize: number) => { 16 | return request.get(`v1/history/video/list/${pageSize}/${page}`); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /client/apis/src/api/msg-announce.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { AddAnnounceType } from '../types/msg-announce-type'; 3 | 4 | //获取公告 5 | export const getAnnounceAPI = (page: number, page_size: number) => { 6 | return request.get(`v1/message/announce/${page_size}/${page}`); 7 | } 8 | 9 | //获取最新重要公告 10 | export const getImportantAnnounceAPI = () => { 11 | return request.get('v1/message/announce/important'); 12 | } 13 | 14 | //添加公告 15 | export const addAnnounceAPI = (addAnnounce: AddAnnounceType) => { 16 | return request.post('v1/announce/admin', addAnnounce); 17 | } 18 | 19 | //删除公告 20 | export const deleteAnnounceAPI = (id: number) => { 21 | return request.delete(`v1/announce/admin/${id}`); 22 | } 23 | -------------------------------------------------------------------------------- /client/apis/src/api/msg-at.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | 3 | //获取@通知 4 | export const getAtMessageAPI = (page: number, page_size: number) => { 5 | return request.get(`v1/message/at/${page_size}/${page}`); 6 | } 7 | -------------------------------------------------------------------------------- /client/apis/src/api/msg-like.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | 3 | //获取点赞通知 4 | export const getLikeMessageAPI = (page: number, page_size: number) => { 5 | return request.get(`v1/message/like/${page_size}/${page}`); 6 | } 7 | -------------------------------------------------------------------------------- /client/apis/src/api/msg-reply.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | 3 | //获取回复通知 4 | export const getReplyMessageAPI = (page: number, page_size: number) => { 5 | return request.get(`v1/message/reply/${page_size}/${page}`); 6 | } 7 | -------------------------------------------------------------------------------- /client/apis/src/api/msg-whisper.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { WhisperType } from '../types/msg-whisper-type'; 3 | 4 | //获取私信列表 5 | export const getWhisperListAPI = () => { 6 | return request.get('v1/whisper') 7 | } 8 | 9 | //获取私信列表 10 | export const getWhisperDetailsAPI = (fid: number, page: number, page_size: number) => { 11 | return request.get(`v1/whisper/${fid}/${page_size}/${page}`) 12 | } 13 | 14 | //发送私信 15 | export const sendWhisperAPI = (msg: WhisperType) => { 16 | return request.post('v1/whisper', msg) 17 | } 18 | 19 | //已读私信 20 | export const readWhisperAPI = (id: number) => { 21 | return request.put(`v1/whisper/${id}` ) 22 | } -------------------------------------------------------------------------------- /client/apis/src/api/partition.ts: -------------------------------------------------------------------------------- 1 | import request from '../request' 2 | import type { AddPartitionType } from '../types/partition-type'; 3 | 4 | //获取分区 5 | export const getPartitionAPI = () => { 6 | return request.get(`v1/partition`); 7 | } 8 | 9 | //新增分区 10 | export const addPartitionAPI = (addPartition: AddPartitionType) => { 11 | return request.post(`v1/partition/admin`, addPartition); 12 | } 13 | 14 | 15 | //删除分区 16 | export const deletePartitionAPI = (id: number) => { 17 | return request.delete(`v1/partition/admin/${id}`); 18 | } 19 | -------------------------------------------------------------------------------- /client/apis/src/api/resource.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { BaseResourceType } from '../types/resource-type'; 3 | 4 | //修改资源标题 5 | export const modifyTitleAPI = (resourceTitle: BaseResourceType) => { 6 | return request.put('v1/resource', resourceTitle); 7 | } 8 | 9 | //删除资源 10 | export const deleteResourceAPI = (id: number) => { 11 | return request.delete('v1/resource/' + id); 12 | } -------------------------------------------------------------------------------- /client/apis/src/api/token.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import { storageData } from "@plooks/utils" 3 | // import { storageData } from "@plooks/utils"; 4 | 5 | // 获取access_token 6 | export const getAccessToken = async () => { 7 | const res = await request.get('v1/user/token/refresh', { 8 | headers: { 9 | Authorization: `${storageData.get('refresh_token')}` 10 | } 11 | }); 12 | 13 | return res 14 | } -------------------------------------------------------------------------------- /client/apis/src/api/upload.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { AxiosProgressEvent } from 'axios'; 3 | import type { UploadOptions } from "../types/upload-options-type"; 4 | import { statusCode } from '@plooks/utils'; 5 | 6 | // 上传图片 7 | export const uploadFileAPI = ({ 8 | name, 9 | file, 10 | action, 11 | onProgress, 12 | onFinish, 13 | onError, 14 | }: UploadOptions) => { 15 | const formData = new FormData(); 16 | formData.append(name, file) 17 | request.post(action, formData, { 18 | // 文件上传60分钟超时 3600000 = 1000*60*60 19 | timeout: 3600000, 20 | headers: { 21 | 'Content-Type': 'multipart/form-data' 22 | }, 23 | onUploadProgress: (progressEvent: AxiosProgressEvent) => { 24 | if (!progressEvent.total) { 25 | onProgress(0); 26 | return; 27 | } 28 | onProgress(Math.floor(progressEvent.loaded / progressEvent.total * 100)); 29 | } 30 | }).then((res) => { 31 | if (res.data.code === statusCode.OK) { 32 | onFinish(res.data); 33 | } else { 34 | onError(res.data); 35 | } 36 | }).catch((err) => { 37 | onError(err); 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /client/apis/src/api/watchroom.ts: -------------------------------------------------------------------------------- 1 | import request from '../request'; 2 | import type { sendWebSocketType, roomInfoType } from '../types/watchroom-type'; 3 | 4 | // 发送webSocket消息 5 | export const sendWebSocketAPI = (sendWebSocket:sendWebSocketType) => { 6 | return request.post('v1/watchroom/msg',sendWebSocket) 7 | } 8 | // 创建房间 9 | export const addRoomApi = (addRoom:roomInfoType) => { 10 | return request.post('v1/watchroom',addRoom) 11 | } 12 | // 进入房间 13 | export const inRoomApi = (rid:number, password:String|null) => { 14 | return request.post(`v1/watchroom/${rid}`, { password } ) 15 | } 16 | // 获取房间信息 17 | export const getRoomInfoApi = (rid:number) => { 18 | return request.get(`v1/watchroom/${rid}`) 19 | } 20 | // 更新房间信息 21 | export const updateRoomApi = (rid:number, roomInfo:roomInfoType) => { 22 | return request.put(`v1/watchroom/${rid}`, roomInfo) 23 | } -------------------------------------------------------------------------------- /client/apis/src/types/ai-mange-type.ts: -------------------------------------------------------------------------------- 1 | export interface columnType { 2 | title: string; 3 | key: string; 4 | } 5 | -------------------------------------------------------------------------------- /client/apis/src/types/carousel-type.ts: -------------------------------------------------------------------------------- 1 | export interface AddCarouselType { 2 | img: string, 3 | title: string, 4 | url?: string, 5 | color: string, 6 | } 7 | 8 | export interface CarouselType { 9 | id: number, 10 | img: string, 11 | title: string, 12 | url?: string, 13 | color: string, 14 | createdAt: string 15 | } -------------------------------------------------------------------------------- /client/apis/src/types/collect-type.ts: -------------------------------------------------------------------------------- 1 | export interface CollectType { 2 | vid: number, 3 | addList: Array, 4 | cancelList: Array, 5 | } -------------------------------------------------------------------------------- /client/apis/src/types/collection-type.ts: -------------------------------------------------------------------------------- 1 | export interface CollectionType { 2 | id: number, 3 | name?: string, 4 | cover?: string, 5 | desc?: string, 6 | open?: boolean, 7 | createdAt?: string 8 | } 9 | 10 | export interface ModifyCollectionType { 11 | id: number, 12 | name: string, 13 | cover: string, 14 | desc: string, 15 | open: boolean, 16 | } 17 | 18 | export interface CollectionInfoType { 19 | id: number, 20 | name: string, 21 | cover: string, 22 | desc: string, 23 | open: boolean, 24 | createdAt: string 25 | } -------------------------------------------------------------------------------- /client/apis/src/types/comment-type.ts: -------------------------------------------------------------------------------- 1 | import type { UserInfoType } from "./user-type" 2 | 3 | export interface AddCommentType { 4 | vid: number, //视频id 5 | content: string, //评论内容 6 | parentId?: string, //父级评论id 7 | replyUserId?: number, //回复用户id 8 | replyContent?: string, //回复内容 9 | rootComment?: string, //根评论 10 | at: Array //被@的用户id 11 | } 12 | 13 | 14 | export interface CommentType { 15 | id: string, //评论id 16 | content: string, //评论内容 17 | author: UserInfoType, //评论作者 18 | reply: Array, //回复 19 | createdAt: number, //评论时间 20 | page: number,//回复页码 21 | noMore: boolean //是否还有更多回复 22 | } 23 | 24 | export interface ReplyType { 25 | id: string, //回复id 26 | content: string, //回复内容 27 | author: UserInfoType, //回复作者 28 | createdAt: number, //回复时间 29 | } 30 | 31 | -------------------------------------------------------------------------------- /client/apis/src/types/danmuku-type.ts: -------------------------------------------------------------------------------- 1 | export interface AddDanmukuType { 2 | vid: number, 3 | part: number, 4 | text?: string, 5 | mode?: 0 | 1, 6 | color?: string, 7 | time?: number, 8 | border?: boolean, 9 | } 10 | export interface DanmukuType{ 11 | 12 | /** 13 | * 弹幕文本 14 | */ 15 | text?: string; 16 | 17 | /** 18 | * 弹幕发送模式,0为滚动,1为静止 19 | */ 20 | mode?: 0 | 1; 21 | 22 | /** 23 | * 弹幕颜色 24 | */ 25 | color?: string; 26 | 27 | /** 28 | * 弹幕出现的时间,单位为秒 29 | */ 30 | time?: number; 31 | 32 | /** 33 | * 弹幕是否有描边 34 | */ 35 | border?: boolean; 36 | } 37 | -------------------------------------------------------------------------------- /client/apis/src/types/history-type.ts: -------------------------------------------------------------------------------- 1 | import type { BaseVideoType } from "./video-type" 2 | 3 | export interface AddHistoryType { 4 | vid: number, 5 | part: number, 6 | time: number 7 | } 8 | 9 | export interface HistoryVideoType { 10 | id: number, 11 | part: number, 12 | time: number, 13 | video: BaseVideoType, 14 | createdAt: string 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/apis/src/types/msg-announce-type.ts: -------------------------------------------------------------------------------- 1 | export interface AnnounceType { 2 | id: number, 3 | title: string, 4 | content: string, 5 | createdAt: string, 6 | url: string, 7 | important: boolean 8 | } 9 | 10 | 11 | export interface AddAnnounceType { 12 | title: string, 13 | content: string, 14 | url: string, 15 | important: boolean 16 | } -------------------------------------------------------------------------------- /client/apis/src/types/msg-at-type.ts: -------------------------------------------------------------------------------- 1 | import type { UserInfoType } from "./user-type"; 2 | import type { BaseVideoType } from "./video-type"; 3 | 4 | export interface AtMessageType { 5 | video: BaseVideoType, 6 | user: UserInfoType, 7 | createdAt: string, 8 | } -------------------------------------------------------------------------------- /client/apis/src/types/msg-like-type.ts: -------------------------------------------------------------------------------- 1 | import type { UserInfoType } from "./user-type"; 2 | import type { BaseVideoType } from "./video-type"; 3 | 4 | export interface LikeMessageType { 5 | video: BaseVideoType, 6 | user: UserInfoType, 7 | createdAt: string, 8 | } -------------------------------------------------------------------------------- /client/apis/src/types/msg-reply-type.ts: -------------------------------------------------------------------------------- 1 | import type { UserInfoType } from "./user-type"; 2 | import type { BaseVideoType } from "./video-type"; 3 | 4 | export interface ReplyMessageType { 5 | video: BaseVideoType, 6 | user: UserInfoType, 7 | createdAt: string, 8 | content: string, 9 | targetReplyContent: string, 10 | rootContent: string, 11 | commentId: number 12 | } 13 | -------------------------------------------------------------------------------- /client/apis/src/types/msg-whisper-type.ts: -------------------------------------------------------------------------------- 1 | import type { UserInfoType } from "./user-type"; 2 | 3 | export interface WhisperType { 4 | fid: number, 5 | content: string, 6 | } 7 | 8 | export interface WhisperListType { 9 | user: UserInfoType, 10 | createdAt: string | Date, 11 | status: boolean 12 | } 13 | 14 | export interface WhisperDetailsType { 15 | fid: number, 16 | fromId: number, 17 | content: string, 18 | createdAt: string 19 | } -------------------------------------------------------------------------------- /client/apis/src/types/partition-type.ts: -------------------------------------------------------------------------------- 1 | export interface PartitionType { 2 | id: number, 3 | content: string 4 | parentId: number 5 | } 6 | 7 | export interface AddPartitionType { 8 | content: string 9 | parentId: number 10 | } -------------------------------------------------------------------------------- /client/apis/src/types/resource-type.ts: -------------------------------------------------------------------------------- 1 | export interface BaseResourceType { 2 | id: number 3 | title: string, 4 | } 5 | 6 | // 分P 7 | export interface ResourceType extends BaseResourceType { 8 | url: string, 9 | duration: number, 10 | status: number, 11 | quality: number, 12 | } 13 | -------------------------------------------------------------------------------- /client/apis/src/types/upload-options-type.ts: -------------------------------------------------------------------------------- 1 | export interface UploadOptions { 2 | action: string 3 | name: string 4 | file: File 5 | onProgress: (percent: number) => void 6 | onFinish: (data?: any) => void 7 | onError: (error?: any) => void 8 | } 9 | -------------------------------------------------------------------------------- /client/apis/src/types/user-type.ts: -------------------------------------------------------------------------------- 1 | // User 类 2 | export interface UserType { 3 | id?: number 4 | username?: string 5 | email?: string 6 | password?: string 7 | } 8 | 9 | export interface UserLoginType { 10 | user: UserType 11 | code?: string //验证码 12 | uuid?: string //uuid 13 | } 14 | 15 | 16 | export interface UserInfoType { 17 | uid: number 18 | nickname: string 19 | avatar: string 20 | spaceCover?: string 21 | email?: string 22 | gender?: number 23 | sign?: string 24 | birthday?: string 25 | createdAt?: string 26 | role?: number 27 | } 28 | 29 | export interface ModifyUserInfoType { 30 | nickname: string, 31 | avatar: string, 32 | gender: number, 33 | sign: string, 34 | birthday: string 35 | } 36 | 37 | export interface AdminModifyUserInfoType { 38 | uid: number, 39 | email: string, 40 | nickname: string, 41 | sign: string, 42 | } 43 | 44 | // 邮箱验证码发送实体 45 | export interface EmailCodeType { 46 | email: string, 47 | code: string, 48 | uuid: string, 49 | username?: string, 50 | scene: Number // 1: 注册 2: 登录 3: 修改密码 51 | } -------------------------------------------------------------------------------- /client/apis/src/types/video-type.ts: -------------------------------------------------------------------------------- 1 | import type { ResourceType } from "./resource-type" 2 | import type { UserInfoType } from "./user-type" 3 | 4 | export interface BaseVideoType { 5 | vid: number, 6 | title: string, 7 | cover: string, 8 | desc: string, 9 | createdAt: string 10 | } 11 | 12 | // 上传视频 13 | export interface UploadVideoType { 14 | title: string, 15 | cover: string, 16 | desc: string, 17 | copyright: boolean, 18 | partitionId: number 19 | } 20 | 21 | // 修改视频信息 22 | export interface ModifyVideoType extends BaseVideoType { 23 | copyright: boolean, 24 | } 25 | 26 | // 视频状态 27 | export interface VideoStatusType extends BaseVideoType { 28 | status: number, 29 | copyright: boolean, 30 | partitionId: number, 31 | resources: ResourceType[] 32 | } 33 | 34 | // 视频 35 | export interface VideoType extends BaseVideoType { 36 | copyright: boolean, 37 | clicks: number, 38 | author: UserInfoType, 39 | partition?: number 40 | } 41 | 42 | // 视频 43 | export interface UserUploadVideoType extends BaseVideoType { 44 | clicks: number, 45 | status: number 46 | } 47 | 48 | -------------------------------------------------------------------------------- /client/apis/src/types/watchroom-type.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface sendWebSocketType { 3 | uid:number, 4 | roomId:number, 5 | cood:number, 6 | vid?:number, 7 | piv?:number 8 | msg?:String 9 | seek?:number 10 | } 11 | 12 | 13 | export interface roomInfoType { 14 | id?:number, 15 | isPublic:boolean, 16 | password?:String | null, 17 | adminId?:number, 18 | userIds?:Array, 19 | vid:number, 20 | pid:number, 21 | time?:number, 22 | isPaly?:boolean 23 | } 24 | 25 | export interface webSendMesgType { 26 | type: number, 27 | data?: any 28 | } 29 | 30 | 31 | // 房间聊天 32 | export interface roomChatType { 33 | uid: number, //消息所属用户id 34 | // 消息所属用户头像 35 | avatar: string, 36 | // 消息所属用户昵称 37 | nickname: string, 38 | content: string, // 内容 39 | createdAt: string // 时间 40 | } -------------------------------------------------------------------------------- /client/apis/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "skipLibCheck": true 17 | }, 18 | "include": ["src"] 19 | } 20 | -------------------------------------------------------------------------------- /client/components/index.ts: -------------------------------------------------------------------------------- 1 | import CommonAvatar from "./src/common-avatar/Index.vue"; 2 | import ArtplayerComNoDanmu from "./src/video-artplayer/ArtplayerComNoDanmu.vue"; 3 | import watchroomPlayer from "./src/video-player/watchroomPlayer.vue"; 4 | import VideoPlayer from "./src/video-player/Index.vue"; 5 | import SearchBox from "./src/search-box/Index.vue"; 6 | 7 | 8 | export { 9 | CommonAvatar, 10 | ArtplayerComNoDanmu, 11 | watchroomPlayer, 12 | VideoPlayer, 13 | SearchBox, 14 | } -------------------------------------------------------------------------------- /client/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plooks/components", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "main": "index.ts", 7 | "dependencies": { 8 | "@plooks/apis": "workspace:^1.0.0", 9 | "@plooks/icons": "workspace:^1.0.0", 10 | "@plooks/utils": "workspace:^1.0.0", 11 | "artplayer": "^5.0.9", 12 | "artplayer-plugin-danmuku": "^5.0.1", 13 | "artplayer-plugin-hls-quality": "^2.0.0", 14 | "dashjs": "^4.5.1", 15 | "hls.js": "^1.4.12", 16 | "swarmcloud-hls": "^2.8.1", 17 | "vue": "^3.2.41" 18 | }, 19 | "devDependencies": { 20 | "@vitejs/plugin-vue": "^3.2.0", 21 | "less": "^4.1.3", 22 | "naive-ui": "^2.33.5", 23 | "typescript": "^4.6.4", 24 | "vite": "^3.2.0", 25 | "vue-tsc": "^1.0.9" 26 | } 27 | } -------------------------------------------------------------------------------- /client/components/src/common-avatar/Index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /client/components/src/search-box/Index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 27 | -------------------------------------------------------------------------------- /client/components/src/types/player.d.ts: -------------------------------------------------------------------------------- 1 | import type { AddDanmukuType, DanmukuType } from "@plooks/apis"; 2 | 3 | export interface QualityType { 4 | [key: number]: { 5 | name?: string, 6 | url: string, 7 | type?: string 8 | } 9 | } 10 | 11 | export interface DanmukuOptionsType { 12 | open: boolean, 13 | placeholder?: string, 14 | data?: Array, 15 | send?: (Danmuku: AddDanmukuType) => void 16 | } 17 | 18 | 19 | export interface OptionsType { 20 | resource: string | qualityType, // 资源 21 | cover?: string, // 封面 22 | type?: string,//视频类型 23 | mobile?: boolean,//移动端 24 | blob?: boolean,//mp4视频是否使用blob 25 | customType?: (player: HTMLVideoElement, src: string) => void, 26 | customQualityChange?: (quality: string) => void, // 自定义清晰度切换 27 | theme?: string,//主题色, 28 | Danmuku?: DanmukuOptionsType, 29 | playbackSpeed?: Array,// 播放速度 30 | } 31 | 32 | 33 | export interface OptionType { 34 | type?: string,//视频类型 35 | currentTime: number,//视频进度 36 | resource: string | qualityType 37 | cover?: string, // 封面 38 | part: number, // 分P 39 | Danmuku: Array, // 弹幕 40 | } 41 | -------------------------------------------------------------------------------- /client/components/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /client/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "lib": [ 13 | "ESNext", 14 | "DOM" 15 | ], 16 | "skipLibCheck": true, 17 | "noEmit": true 18 | }, 19 | "include": [ 20 | "src/**/*.ts", 21 | "src/**/*.d.ts", 22 | "src/**/*.tsx", 23 | "src/**/*.vue", 24 | "index.ts" 25 | ], 26 | "references": [ 27 | { 28 | "path": "./tsconfig.node.json" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /client/components/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /client/components/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /client/custom.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 2024; 3 | server_name localhost; 4 | client_max_body_size 2048M; 5 | 6 | location / { 7 | root /usr/share/nginx/html/web; 8 | index index.html index.htm; 9 | try_files $uri $uri/ @web; 10 | } 11 | 12 | # 解决history路由问题 13 | location @web { 14 | rewrite ^.*$ /index.html; 15 | } 16 | 17 | # 后台管理 18 | location /manage/ { 19 | root /usr/share/nginx/html; 20 | index index.html index.htm; 21 | try_files $uri $uri/ @manage; 22 | } 23 | 24 | # 解决后台管理history路由问题 25 | location @manage { 26 | rewrite ^.*$ /manage/index.html; 27 | } 28 | 29 | error_page 500 502 503 504 /50x.html; 30 | location = /50x.html { 31 | root html; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/icons/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plooks/icons", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "main": "index.ts", 7 | "dependencies": { 8 | "vue": "^3.2.41" 9 | }, 10 | "devDependencies": { 11 | "@vitejs/plugin-vue": "^3.2.0", 12 | "typescript": "^4.6.4", 13 | "vite": "^3.2.0", 14 | "vue-tsc": "^1.0.9" 15 | } 16 | } -------------------------------------------------------------------------------- /client/icons/src/Add.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/ArrowDown.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/ArrowLeft.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/ArrowRight.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/ArrowUp.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Close.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Collect.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Collection.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Comment.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Delete.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Edit.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Female.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Forbid.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/History.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Kans.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Like.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Link.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Male.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Me.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/MenuFold.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/MenuOutline.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/MenuUnfold.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Message.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Refresh.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Search.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Setting.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Upload.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/src/Video.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/icons/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "lib": [ 13 | "ESNext", 14 | "DOM" 15 | ], 16 | "skipLibCheck": true, 17 | "noEmit": true 18 | }, 19 | "include": [ 20 | "index.ts", 21 | "src/*.vue", 22 | "vite-env.d.ts" 23 | ], 24 | "references": [ 25 | { 26 | "path": "./tsconfig.node.json" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /client/icons/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /client/icons/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /client/icons/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plooks-client", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^3.2.0" 13 | } 14 | } -------------------------------------------------------------------------------- /client/packages/manage-client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 'latest' 13 | }, 14 | rules: { 15 | "vue/multi-word-component-names":"off", 16 | "prefer-const": "off" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/packages/manage-client/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } -------------------------------------------------------------------------------- /client/packages/manage-client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 后台管理系统 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/packages/manage-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manage-client", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "run-p type-check build-only", 8 | "preview": "vite preview", 9 | "build-only": "vite build", 10 | "type-check": "vue-tsc --noEmit", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" 12 | }, 13 | "dependencies": { 14 | "@plooks/apis": "workspace:^1.0.0", 15 | "@plooks/components": "workspace:^1.0.0", 16 | "@plooks/icons": "workspace:^1.0.0", 17 | "@plooks/utils": "workspace:^1.0.0", 18 | "dashjs": "^4.5.1", 19 | "naive-ui": "^2.33.5", 20 | "pinia": "^2.0.28", 21 | "terser": "^5.16.1", 22 | "vue": "^3.2.45", 23 | "vue-router": "^4.1.6" 24 | }, 25 | "devDependencies": { 26 | "@rushstack/eslint-patch": "^1.1.4", 27 | "@types/node": "^18.11.12", 28 | "@vitejs/plugin-vue": "^4.0.0", 29 | "@vue/eslint-config-typescript": "^11.0.0", 30 | "@vue/tsconfig": "^0.1.3", 31 | "eslint": "^8.22.0", 32 | "eslint-plugin-vue": "^9.3.0", 33 | "npm-run-all": "^4.1.5", 34 | "typescript": "~4.7.4", 35 | "vite": "^4.0.0", 36 | "vue-tsc": "^1.0.12" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/packages/manage-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/client/packages/manage-client/public/favicon.ico -------------------------------------------------------------------------------- /client/packages/manage-client/src/assets/login-illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/client/packages/manage-client/src/assets/login-illustration.png -------------------------------------------------------------------------------- /client/packages/manage-client/src/assets/not-found-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/client/packages/manage-client/src/assets/not-found-page.png -------------------------------------------------------------------------------- /client/packages/manage-client/src/hooks/send-code-hooks.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { useMessage } from 'naive-ui'; 3 | import { sendEmailCodeAPI } from '@plooks/apis'; 4 | import { statusCode } from '@plooks/utils'; 5 | import type { EmailCodeType } from '@plooks/apis'; 6 | 7 | export default function useSendCode() { 8 | 9 | const disabledSend = ref(false);//禁用发送按钮 10 | const sendBtnText = ref("发送验证码");//发送按钮文字 11 | const message = useMessage();//通知 12 | 13 | const sendEmailCodeAsync = async (emailCode: EmailCodeType) => { 14 | //禁用发送按钮 15 | disabledSend.value = true; 16 | try { 17 | const res = await sendEmailCodeAPI(emailCode); 18 | 19 | if (res.data.code === statusCode.OK) { 20 | message.success("邮箱验证码发送成功"); 21 | //开启倒计时 22 | let count = 0; 23 | const tag = setInterval(() => { 24 | if (++count >= 60) { 25 | clearInterval(tag); 26 | disabledSend.value = false; 27 | sendBtnText.value = "发送验证码"; 28 | return; 29 | } 30 | sendBtnText.value = `${60 - count}秒后重发`; 31 | }, 1000); 32 | } else { 33 | disabledSend.value = false; 34 | message.error(res.data.message); 35 | } 36 | return res.data.code; 37 | } catch { 38 | disabledSend.value = false; 39 | sendBtnText.value = "发送验证码"; 40 | message.error("发送验证码失败"); 41 | } 42 | } 43 | 44 | return { 45 | disabledSend, 46 | sendBtnText, 47 | sendEmailCodeAsync 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /client/packages/manage-client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | 3 | import App from './App.vue'; 4 | import store from './stores'; 5 | import router from './router'; 6 | 7 | const app = createApp(App); 8 | 9 | //动态标题 10 | app.directive('title', { 11 | mounted(el) { 12 | document.title = el.dataset.title 13 | } 14 | }) 15 | 16 | app.use(router); 17 | app.use(store); 18 | 19 | app.mount('#app'); 20 | -------------------------------------------------------------------------------- /client/packages/manage-client/src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | 3 | const store = createPinia(); 4 | 5 | export default store; -------------------------------------------------------------------------------- /client/packages/manage-client/src/theme/index.ts: -------------------------------------------------------------------------------- 1 | export const getTheme = () => { 2 | return defaultTheme; 3 | } 4 | 5 | const defaultTheme: Theme = { 6 | primaryColor: "#4d70ff", 7 | primaryHoverColor: "#0d84ff" 8 | } 9 | -------------------------------------------------------------------------------- /client/packages/manage-client/src/typings/theme.d.ts: -------------------------------------------------------------------------------- 1 | interface Theme { 2 | primaryColor: string 3 | primaryHoverColor: string 4 | } -------------------------------------------------------------------------------- /client/packages/manage-client/src/views/result/page-not-found/Index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | -------------------------------------------------------------------------------- /client/packages/manage-client/tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/packages/manage-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | }, 10 | 11 | "references": [ 12 | { 13 | "path": "./tsconfig.config.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /client/packages/manage-client/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue()], 9 | base: "/manage", 10 | build: { 11 | chunkSizeWarningLimit: 2048, 12 | outDir: 'manage', //指定输出路径 13 | minify: 'terser' // 混淆器,terser构建后文件体积更小 14 | }, 15 | resolve: { 16 | alias: { 17 | '@': fileURLToPath(new URL('./src', import.meta.url)) 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /client/packages/web-client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 'latest' 13 | }, 14 | rules: { 15 | "vue/multi-word-component-names":"off", 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/packages/web-client/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } -------------------------------------------------------------------------------- /client/packages/web-client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/packages/web-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "run-p type-check build-only", 7 | "preview": "vite preview", 8 | "build-only": "vite build", 9 | "type-check": "vue-tsc --noEmit", 10 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" 11 | }, 12 | "dependencies": { 13 | "@plooks/apis": "workspace:^1.0.0", 14 | "@plooks/components": "workspace:^1.0.0", 15 | "@plooks/icons": "workspace:^1.0.0", 16 | "@plooks/utils": "workspace:^1.0.0", 17 | "artplayer": "^5.0.9", 18 | "artplayer-plugin-danmuku": "^5.0.1", 19 | "artplayer-plugin-hls-quality": "^2.0.0", 20 | "hls.js": "^1.4.12", 21 | "js-base64": "^3.7.3", 22 | "pinia": "^2.0.23", 23 | "swarmcloud-hls": "^2.8.1", 24 | "terser": "^5.16.1", 25 | "vue": "^3.2.41", 26 | "vue-dompurify-html": "^3.1.2", 27 | "vue-i18n": "9.3.0-beta.16", 28 | "vue-router": "^4.1.5" 29 | }, 30 | "devDependencies": { 31 | "@rushstack/eslint-patch": "^1.1.4", 32 | "@types/node": "^16.11.68", 33 | "@vitejs/plugin-vue": "^3.1.2", 34 | "@vue/eslint-config-typescript": "^11.0.0", 35 | "@vue/tsconfig": "^0.1.3", 36 | "eslint": "^8.22.0", 37 | "eslint-plugin-vue": "^9.3.0", 38 | "less": "^4.1.3", 39 | "naive-ui": "^2.33.5", 40 | "npm-run-all": "^4.1.5", 41 | "typescript": "~4.7.4", 42 | "vite": "^3.1.8", 43 | "vue-picture-cropper": "^0.5.1", 44 | "vue-tsc": "^1.0.8" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /client/packages/web-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/client/packages/web-client/public/favicon.ico -------------------------------------------------------------------------------- /client/packages/web-client/src/assets/collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/client/packages/web-client/src/assets/collection.png -------------------------------------------------------------------------------- /client/packages/web-client/src/assets/filing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/client/packages/web-client/src/assets/filing.png -------------------------------------------------------------------------------- /client/packages/web-client/src/assets/not-found-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/client/packages/web-client/src/assets/not-found-page.png -------------------------------------------------------------------------------- /client/packages/web-client/src/components/login-dialog/Index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /client/packages/web-client/src/hooks/collection-hooks.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { useNotification } from 'naive-ui'; 3 | import { statusCode } from "@plooks/utils"; 4 | import type { CollectionType } from '@plooks/apis'; 5 | import { getCollectionListAPI, addCollectionAPI } from '@plooks/apis'; 6 | 7 | 8 | export default function useCollection() { 9 | const collections = ref>([]); 10 | const notification = useNotification();//通知 11 | 12 | //获取收藏夹列表 13 | const getCollectionList = () => { 14 | getCollectionListAPI().then((res) => { 15 | if (res.data.code === statusCode.OK) { 16 | collections.value = res.data.data.collections; 17 | } 18 | }) 19 | } 20 | 21 | //创建收藏夹 22 | const createCollection = (name: string) => { 23 | addCollectionAPI(name).then((res) => { 24 | if (res.data.code === statusCode.OK) { 25 | collections.value.push({ 26 | id: res.data.data.id, 27 | cover: "", 28 | name: name, 29 | desc: "", 30 | open: false, 31 | }) 32 | } else { 33 | notification.error({ 34 | title: '收藏夹创建失败', 35 | duration: 5000, 36 | }) 37 | } 38 | }) 39 | } 40 | 41 | return { 42 | collections, 43 | getCollectionList, 44 | createCollection 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /client/packages/web-client/src/hooks/mention-hooks.ts: -------------------------------------------------------------------------------- 1 | export default function useMention() { 2 | 3 | const handleMention = (content: string) => { 4 | let res = []; 5 | const state = { 6 | ReadContent: 'ReadContent', 7 | ReadUser: 'ReadUser' 8 | } 9 | let currentRead = '';//当前读取的内容 10 | let currentState = state.ReadContent;//当前状态 11 | 12 | content += ' ';//如果@用户结尾没有空格将无法识别 13 | for (let i = 0; i < content.length; i++) { 14 | if (currentState === state.ReadContent && content[i] === '@') { 15 | currentState = state.ReadUser; 16 | res.push({ 17 | class: '', 18 | value: currentRead, 19 | key: null 20 | }); 21 | currentRead = ''; 22 | continue; 23 | } 24 | 25 | if (currentState === state.ReadUser && content[i] === ' ') { 26 | currentState = state.ReadContent; 27 | res.push({ 28 | class: 'mention-user', 29 | value: `@${currentRead}`, 30 | key: currentRead 31 | }); 32 | currentRead = ''; 33 | continue; 34 | } 35 | 36 | currentRead += content[i]; 37 | } 38 | res.push({ 39 | class: '', 40 | value: currentRead, 41 | key: null 42 | }) 43 | return res 44 | } 45 | 46 | return { 47 | handleMention 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /client/packages/web-client/src/hooks/render-icon-hooks.ts: -------------------------------------------------------------------------------- 1 | import { h } from "vue"; 2 | import { NIcon } from "naive-ui"; 3 | 4 | export default function useRenderIcon() { 5 | function renderIcon(icon: any, color?: string) { 6 | return () => h(NIcon, { color: color }, { default: () => h(icon) }); 7 | } 8 | 9 | return { 10 | renderIcon 11 | } 12 | } -------------------------------------------------------------------------------- /client/packages/web-client/src/hooks/user-follow-hooks.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | import { useMessage } from 'naive-ui'; 3 | import { statusCode } from '@plooks/utils'; 4 | import { getFollowStatusAPI, followAPI, unfollowAPI } from "@plooks/apis"; 5 | 6 | export default function useUserFollow() { 7 | const isFollow = ref(false);//是否关注 8 | 9 | const message = useMessage();//通知 10 | 11 | const getFollowStatus = (fid: number) => { 12 | getFollowStatusAPI(fid).then((res) => { 13 | if (res.data.code === statusCode.OK) { 14 | isFollow.value = res.data.data.follow; 15 | } 16 | }) 17 | } 18 | 19 | //关注 20 | const follow = (id: number) => { 21 | followAPI(id).then((res) => { 22 | if (res.data.code === statusCode.OK) { 23 | message.success('关注成功'); 24 | isFollow.value = true; 25 | } else { 26 | message.error(res.data.message); 27 | } 28 | }) 29 | } 30 | 31 | //取关 32 | const unfollow = (id: number) => { 33 | unfollowAPI(id).then((res) => { 34 | if (res.data.code === statusCode.OK) { 35 | message.success('取关成功'); 36 | isFollow.value = false; 37 | } else { 38 | message.error(res.data.message); 39 | } 40 | }) 41 | } 42 | 43 | return { 44 | isFollow, 45 | follow, 46 | unfollow, 47 | getFollowStatus, 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /client/packages/web-client/src/locale/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from "vue-i18n"; 2 | import en from './en-US'; 3 | import zh from './zh-CN'; 4 | // import ja from './ja-JP'; 5 | 6 | // 默认读取本地存储语言设置 7 | const defaultLocale = localStorage.getItem('locale') || 'zh-CN' 8 | 9 | const i18n = createI18n({ 10 | locale: defaultLocale,// 默认语言 11 | fallbackLocale: 'en-US',// 不存在默认则为英文 12 | allowComposition: true,// 允许组合式api 13 | messages: { 14 | 'en-US': en, // 标识:配置对象 15 | 'zh-CN': zh 16 | // 'ja-JP': ja 17 | }, 18 | }) 19 | export default i18n; -------------------------------------------------------------------------------- /client/packages/web-client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import VueDOMPurifyHTML from 'vue-dompurify-html'; 3 | 4 | import App from './App.vue'; 5 | import store from './stores'; 6 | import router from './router'; 7 | import i18n from './locale'; //引入国际化 8 | 9 | const app = createApp(App); 10 | 11 | //动态标题 12 | app.directive('title', { 13 | mounted(el) { 14 | document.title = el.dataset.title 15 | } 16 | }) 17 | 18 | app.use(router); 19 | app.use(store); 20 | app.use(i18n); 21 | app.use(VueDOMPurifyHTML); 22 | 23 | app.mount('#app'); 24 | -------------------------------------------------------------------------------- /client/packages/web-client/src/router/message-routes.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from "vue-router"; 2 | 3 | export const messageRoutes: RouteRecordRaw = { 4 | path: '/message', 5 | name: 'Message', 6 | meta: { auth: true }, 7 | component: () => import("../views/message/Index.vue"), 8 | redirect: '/message/announce', 9 | children: [ 10 | { 11 | path: '/message/announce', 12 | name: 'Announce', 13 | meta: { auth: true }, 14 | component: () => import("../views/message/announce/Index.vue"), 15 | }, 16 | { 17 | path: '/message/like', 18 | name: 'Like', 19 | meta: { auth: true }, 20 | component: () => import("../views/message/like/Index.vue"), 21 | }, 22 | { 23 | path: '/message/at', 24 | name: 'At', 25 | meta: { auth: true }, 26 | component: () => import("../views/message/at/Index.vue"), 27 | }, 28 | { 29 | path: '/message/reply', 30 | name: 'Reply', 31 | meta: { auth: true }, 32 | component: () => import("../views/message/reply/Index.vue"), 33 | }, 34 | { 35 | path: '/message/whisper', 36 | name: 'Whisper', 37 | meta: { auth: true }, 38 | component: () => import("../views/message/whisper/Index.vue"), 39 | }, 40 | ] 41 | } -------------------------------------------------------------------------------- /client/packages/web-client/src/router/upload-routes.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from "vue-router"; 2 | 3 | export const uploadRoutes: RouteRecordRaw = { 4 | path: '/upload', 5 | name: 'Upload', 6 | component: () => import("../views/upload/Index.vue"), 7 | redirect: "/upload/video", 8 | children: [ 9 | { 10 | path: '/upload/video/:vid?', 11 | name: 'UploadVideo', 12 | meta: { auth: true }, 13 | component: () => import("../views/upload/upload-video/Index.vue"), 14 | }, 15 | { 16 | path: '/upload/video/manage', 17 | name: 'VideoManage', 18 | meta: { auth: true }, 19 | component: () => import("../views/upload/video-manage/Index.vue"), 20 | }, 21 | { 22 | path: '/upload/comment/manage', 23 | name: 'CommentManage', 24 | meta: { auth: true }, 25 | component: () => import("../views/upload/comment-manage/Index.vue"), 26 | }, 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /client/packages/web-client/src/router/user-routes.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from "vue-router"; 2 | 3 | export const userRoutes: Array = [ 4 | { 5 | path: "/user/:uid", 6 | name: "User", 7 | component: () => import("../views/user/Index.vue"), 8 | redirect: { name: 'UserVideo' }, 9 | children: [ 10 | { 11 | path: '/user/:uid/video', 12 | name: 'UserVideo', 13 | component: () => import("../views/user/component/UserVideo.vue"), 14 | }, 15 | { 16 | path: '/user/:uid/following', 17 | name: 'UserFollowing', 18 | component: () => import("../views/user/component/UserFollow.vue"), 19 | }, 20 | { 21 | path: '/user/:uid/follower', 22 | name: 'UserFollower', 23 | component: () => import("../views/user/component/UserFollow.vue"), 24 | }, 25 | ] 26 | }, 27 | ] -------------------------------------------------------------------------------- /client/packages/web-client/src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | 3 | const store = createPinia(); 4 | 5 | export default store; -------------------------------------------------------------------------------- /client/packages/web-client/src/stores/login-store.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import type { UserType } from '@plooks/apis'; 3 | 4 | export const useLoginStore = defineStore({ 5 | id: "showLogin", 6 | state: () => { 7 | return { 8 | showLogin: false, // 是否显示登录框 9 | // 登录注册默认数据(UserType)类型 10 | loginData: { 11 | email: "", 12 | username: "", 13 | password: "", 14 | } 15 | 16 | } 17 | }, 18 | actions: { 19 | setLoginState(val: boolean) { 20 | this.showLogin = val; 21 | }, 22 | setLoginData(val: UserType) { 23 | this.loginData.email = val.email ? val.email : ""; 24 | this.loginData.username = val.username ? val.username : ""; 25 | this.loginData.password = val.password ? val.password : ""; 26 | }, 27 | //清除setLoginData数据 28 | clearLoginData() { 29 | this.loginData.email = ""; 30 | this.loginData.username = ""; 31 | this.loginData.password = ""; 32 | } 33 | 34 | } 35 | 36 | }) 37 | -------------------------------------------------------------------------------- /client/packages/web-client/src/stores/video-count-store.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | 3 | export const useVideoCountStore = defineStore({ 4 | id: "videoCount", 5 | state: () => { 6 | return { 7 | videoCount: 0, 8 | } 9 | }, 10 | actions: { 11 | setVideoCountState(val: number) { 12 | this.videoCount = val; 13 | } 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /client/packages/web-client/src/theme/index.ts: -------------------------------------------------------------------------------- 1 | // 获取主题的函数 2 | export const getTheme = () => { 3 | return defaultTheme; 4 | } 5 | 6 | // 默认主题 7 | const defaultTheme: Theme = { 8 | primaryColor: "#ffc90c", // 主要颜色 9 | primaryHoverColor: "#f8df72" // 主要颜色的悬停颜色 10 | } -------------------------------------------------------------------------------- /client/packages/web-client/src/typings/theme.d.ts: -------------------------------------------------------------------------------- 1 | interface Theme { 2 | primaryColor: string 3 | primaryHoverColor: string 4 | } -------------------------------------------------------------------------------- /client/packages/web-client/src/views/login/Index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | -------------------------------------------------------------------------------- /client/packages/web-client/src/views/result/page-not-found/Index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | -------------------------------------------------------------------------------- /client/packages/web-client/src/views/space/follow/Index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 35 | 36 | -------------------------------------------------------------------------------- /client/packages/web-client/src/views/space/setting/security/Index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 50 | 51 | -------------------------------------------------------------------------------- /client/packages/web-client/src/views/user/component/UserFollow.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | 31 | -------------------------------------------------------------------------------- /client/packages/web-client/tsconfig.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/packages/web-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | }, 10 | 11 | "references": [ 12 | { 13 | "path": "./tsconfig.config.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /client/packages/web-client/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue()], 9 | build: { 10 | chunkSizeWarningLimit: 2048, 11 | outDir: 'web', //指定输出路径 12 | minify: 'terser' // 混淆器,terser构建后文件体积更小 13 | }, 14 | resolve: { 15 | alias: { 16 | '@': fileURLToPath(new URL('./src', import.meta.url)) 17 | } 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /client/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | # 项目 3 | - "packages/*" 4 | # 接口相关 5 | - "apis/**" 6 | # 一些工具 7 | - "utils/**" 8 | # 公共组件 9 | - "components/**" 10 | # 图标 11 | - "icons/**" 12 | -------------------------------------------------------------------------------- /client/utils/.env: -------------------------------------------------------------------------------- 1 | VITE_BASE_DOMAIN="localhost:8080" -------------------------------------------------------------------------------- /client/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/util"; 2 | export * from "./src/format"; 3 | export * from "./src/status-code"; 4 | export * from "./src/config"; 5 | export * from './src/stored-data'; 6 | export * from "./src/review-code"; 7 | export * from "./src/time"; 8 | export * from "./src/color"; 9 | export * from "./src/resource"; 10 | export * from "./src/uuid"; 11 | export * from "./src/watchroom-code"; -------------------------------------------------------------------------------- /client/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plooks/utils", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "typescript": "^4.6.4", 13 | "vite": "^3.2.0" 14 | } 15 | } -------------------------------------------------------------------------------- /client/utils/src/color.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取图片主色调 3 | * @param src 图片地址 4 | * @returns 主色调 5 | */ 6 | export const getMainColor = (src: string) => { 7 | const img = document.createElement("img"); 8 | img.src = src; 9 | img.crossOrigin = "anonymous"; 10 | 11 | return new Promise((resolve, reject) => { 12 | img.onload = () => { 13 | const canvas = document.createElement("canvas"); 14 | const ctx = canvas.getContext && canvas.getContext('2d'); 15 | 16 | if (ctx) { 17 | ctx.drawImage(img, 0, 0); 18 | const data = ctx.getImageData(0, 0, img.width, img.height).data; 19 | let r = 0, g = 0, b = 0; 20 | 21 | //计算平均值 22 | for (let row = 0; row < img.height; row++) { 23 | for (let col = 0; col < img.width; col++) { 24 | r += data[(img.width * row + col) * 4]; 25 | g += data[(img.width * row + col) * 4 + 1]; 26 | b += data[(img.width * row + col) * 4 + 2]; 27 | } 28 | } 29 | 30 | //计算平均值 31 | r /= img.width * img.height; 32 | g /= img.width * img.height; 33 | b /= img.width * img.height; 34 | 35 | r = Math.round(r); 36 | g = Math.round(g); 37 | b = Math.round(b); 38 | console.log(r, g, b) 39 | if (r === 0 && g === 0 && b === 0) { 40 | resolve("#9499a0"); 41 | } 42 | resolve(`#${r.toString(16)}${g.toString(16)}${b.toString(16)}`); 43 | } 44 | 45 | reject("#9499a0"); 46 | } 47 | }) 48 | 49 | 50 | 51 | } -------------------------------------------------------------------------------- /client/utils/src/config.ts: -------------------------------------------------------------------------------- 1 | // 获取环境变量或设置默认值 2 | const title = "Plooks 一起看!"; // 网站标题 3 | const https = false; // 是否启用https 4 | // 去除警告 5 | // @ts-ignore 6 | // const domain = import.meta.env.VITE_BASE_DOMAIN; // 域名 7 | // const domain = "localhost:2023"; // 域名 8 | const domain = "plooks-api:2023"; // 域名 9 | const icp = "icp备案信息"; // 备案信息 10 | const security = "公网安备信息"; // 公网安备信息 11 | 12 | //上传文件大小限制,需要先修改后端大小限制 13 | const maxImgSize = 5;//上传图片最大大小(单位M) 14 | 15 | export const globalConfig = { 16 | title: title, 17 | https: https, 18 | domain: domain, 19 | icp: icp, 20 | security: security, 21 | maxImgSize: maxImgSize, 22 | } 23 | -------------------------------------------------------------------------------- /client/utils/src/format.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断是否是邮箱 3 | * @param str 邮箱 4 | */ 5 | export const isEmail = (str: string) => { 6 | const reg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/; 7 | return reg.test(str); 8 | } 9 | 10 | 11 | /** 12 | * 判断是否是手机号 13 | * @param str 手机号 14 | */ 15 | export const isPhone = (str: string) => { 16 | const reg = /^1[3|4|5|6|7|8|9][0-9]{9}$/; 17 | return reg.test(str); 18 | } -------------------------------------------------------------------------------- /client/utils/src/resource.ts: -------------------------------------------------------------------------------- 1 | import { globalConfig } from "./config" 2 | import { isURL } from "./util" 3 | 4 | // 获取资源url 5 | export const getResourceUrl = (originalUrl: string) => { 6 | // 如果本身就是一个url,或者前后端同源,直接返回 7 | if (isURL(originalUrl) || !globalConfig.domain) { 8 | return originalUrl; 9 | } 10 | 11 | // 前后端不同源 12 | return `http${globalConfig.https ? 's' : ''}://${globalConfig.domain}${originalUrl}`; 13 | } -------------------------------------------------------------------------------- /client/utils/src/review-code.ts: -------------------------------------------------------------------------------- 1 | export const reviewCode = { 2 | AUDIT_APPROVED: 0, //审核通过 3 | 4 | CREATED_VIDEO: 100, //成功创建视频 5 | VIDEO_PROCESSING: 200, //视频转码中 6 | SUBMIT_REVIEW: 300, // 提交审核 7 | WAITING_REVIEW: 500, //等待审核 8 | 9 | //审核不通过 10 | WRONG_VIDEO_INFO: 2100, //视频信息存在问题 11 | WRONG_VIDEO_CONTENT: 2200, //视频内容存在问题 12 | PROCESSING_FAIL: 2300, //视频处理失败 13 | } -------------------------------------------------------------------------------- /client/utils/src/status-code.ts: -------------------------------------------------------------------------------- 1 | export const statusCode = { 2 | OK: 200, 3 | 4 | CAPTCHA_REQUIRED: -1, // 需要人机验证 5 | 6 | TOKEN_EXPRIED: 3000, // token过期 7 | 8 | // 需要密码 9 | IS_PASSWORD: 5002 10 | } -------------------------------------------------------------------------------- /client/utils/src/time.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将时间字符串转换为格式为“yyyy-mm-dd”的日期字符串。 3 | * @param time 要转换的时间字符串。 4 | * @returns 格式为“yyyy-mm-dd”的日期字符串。 5 | */ 6 | export const timeStrToDateStr = (time: string) => { 7 | const date = new Date(time); 8 | const year = date.getFullYear() 9 | // 由于 getMonth 返回值会比正常月份小 1 10 | const month = date.getMonth() + 1; 11 | const day = date.getDate(); 12 | 13 | return `${year}-${month}-${day}` 14 | } 15 | 16 | 17 | /** 18 | * 获取给定时间与当前时间之间的相对时间。 19 | * @param time 要获取相对时间的时间。 20 | * @returns 表示给定时间与当前时间之间的相对时间的字符串。 21 | */ 22 | export const relativeDate = (time: string) => { 23 | const originalTime = new Date(time); 24 | const currentTime = new Date(); 25 | 26 | const originalYear = originalTime.getFullYear() 27 | const originalMonth = originalTime.getMonth() + 1; 28 | const originalDay = originalTime.getDay(); 29 | 30 | if (currentTime.getFullYear() !== originalYear || currentTime.getMonth() + 1 != originalMonth) { 31 | return `${originalYear}-${originalMonth}-${originalDay}`; 32 | } 33 | 34 | switch (currentTime.getDay() - originalDay) { 35 | case 0: 36 | return "今天"; 37 | case 1: 38 | return "昨天"; 39 | case 2: 40 | return "前天"; 41 | default: 42 | return `${originalYear}-${originalMonth}-${originalDay}`; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/utils/src/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成一个指定范围内的随机整数 3 | * @param min - 最小值 4 | * @param max - 最大值 5 | * @returns 一个随机整数 6 | */ 7 | export const getRandom = (min: number, max: number) => { 8 | return Math.ceil(Math.random() * (max - min) + min); 9 | } 10 | 11 | /** 12 | * 防抖函数,用于减少函数的执行次数 13 | * @param fn - 需要执行的函数 14 | * @param delay - 延迟时间,默认为1000ms 15 | * @returns 一个新的函数 16 | */ 17 | export const debounce = (fn: Function, delay = 1000) => { 18 | let timer: number | null = null; 19 | return function () { 20 | if (timer) { 21 | clearTimeout(timer) 22 | } 23 | timer = window.setTimeout(() => { 24 | fn(arguments); 25 | timer = null 26 | }, delay); 27 | } 28 | } 29 | 30 | /** 31 | * 节流函数,用于减少函数的执行次数 32 | * @param fn - 需要执行的函数 33 | * @param delay - 延迟时间,默认为1000ms 34 | * @returns 一个新的函数 35 | */ 36 | export const thorttle = (fn: Function, delay = 1000) => { 37 | let timer: number | null = null; 38 | return function () { 39 | if (!timer) { 40 | timer = window.setTimeout(function () { 41 | fn(arguments); 42 | 43 | timer = null; 44 | }, delay); 45 | } 46 | }; 47 | }; 48 | 49 | /** 50 | * 判断一个字符串是否为URL 51 | * @param url - 需要判断的字符串 52 | * @returns 如果是URL则返回true,否则返回false 53 | */ 54 | export const isURL = (url: string) => { 55 | const reg = /http(s)?:\/\/((([\w-]+\.)+[\w-]+)|(localhost))(:\d+)?(\/[\w- ./?%&=#]*)?/; 56 | return reg.test(url)} 57 | 58 | -------------------------------------------------------------------------------- /client/utils/src/uuid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成一个唯一的 UUID 字符串。 3 | * 4 | * @returns 一个 UUID 字符串。 5 | */ 6 | export const createUuid = () =>{ 7 | const tmp_url = URL.createObjectURL(new Blob()); 8 | const uuid = tmp_url.split('/').pop() as string; 9 | URL.revokeObjectURL(tmp_url); 10 | return uuid; 11 | } 12 | -------------------------------------------------------------------------------- /client/utils/src/watchroom-code.ts: -------------------------------------------------------------------------------- 1 | export const watchroomCode = { 2 | // 视频初始化完成 3 | ready:200, 4 | // 视频初始话完成后接受 5 | readyAck:201, 6 | // 播放 7 | play: 1000, 8 | // 暂停 9 | pause: 1001, 10 | // 进度 11 | seek: 2000, 12 | // 切换 piv 13 | switchPiv:3000, 14 | // 切换vid and pid 15 | switchVid:3001, 16 | // 发送消息 17 | sendMsg:4000 18 | } -------------------------------------------------------------------------------- /client/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "skipLibCheck": true 17 | }, 18 | "include": ["src"] 19 | } 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | #MySQL 5 | mysql: 6 | image: mysql:8.0.28 7 | container_name: plooks-mysql 8 | environment: 9 | MYSQL_ROOT_PASSWORD: 123456 # root密码 10 | MYSQL_DATABASE: tb_plooks 11 | restart: on-failure 12 | volumes: 13 | - ./data/mysql/db:/var/lib/mysql 14 | - ./data/mysql/init:/docker-entrypoint-initdb.d/ 15 | ports: 16 | - '3307:3306' 17 | #Redis 18 | redis: 19 | image: redis 20 | container_name: plooks-redis 21 | ports: 22 | - "6380:6379" 23 | volumes: 24 | - ./data/redis:/data 25 | 26 | 27 | # 后端 28 | api: 29 | container_name: plooks-api 30 | build: 31 | context: ./server 32 | # 如果已经打包好jar包可以将“Dockerfile-build”替换“Dockerfile” 33 | dockerfile: Dockerfile-build 34 | ports: 35 | # 映射端口 36 | - "2023:2023" 37 | depends_on: 38 | - mysql 39 | - redis -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .vitepress/cache 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaf-docs", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "docs:dev": "vitepress dev", 8 | "docs:build": "vitepress build", 9 | "docs:serve": "vitepress serve" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "^20.8.4", 13 | "typescript": "^4.9.3", 14 | "vite": "4.0.0-alpha.0", 15 | "vitepress": "^1.0.0-alpha.40" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/src/api/captcha.md: -------------------------------------------------------------------------------- 1 | # 人机验证相关接口 2 | 3 | ## 获取图片验证码 4 | 5 | #### 请求URL 6 | - ` http://域名/api/v1/user/captchaImage/{uuid} ` 7 | 8 | #### 请求方式 9 | - GET 10 | 11 | #### 返回示例 12 | 13 | 直接返回图片 14 | 15 | #### 备注 16 | 无 17 | 18 | -------------------------------------------------------------------------------- /docs/src/api/danmaku.md: -------------------------------------------------------------------------------- 1 | # 弹幕接口 2 | 3 | 4 | ## 发送弹幕 5 | 6 | #### 请求URL 7 | - ` http://域名/api/v1/danmaku ` 8 | 9 | #### 请求方式 10 | - POST 11 | 12 | #### 请求头 13 | - `"content-type": "application/json",` 14 | - `Authorization': access_token ` 15 | 16 | 17 | #### 参数 18 | 19 | | 参数名 | 必选 | 类型 | 说明 | 20 | | :----- | :--- | :----- | ---------------------- | 21 | | vid | 是 | int | 视频id | 22 | | part | 否 | int | 分P (默认1) | 23 | | time | 是 | int | 时间 | 24 | | type | 是 | int | 类型 0滚动 1顶部 2底部 | 25 | | color | 是 | string | 颜色 | 26 | | text | 是 | string | 内容 | 27 | 28 | #### 返回示例 29 | 30 | ``` json 31 | { 32 | "code": 200, 33 | "data": null, 34 | "mssage":"success" 35 | } 36 | ``` 37 | 38 | #### 备注 39 | 无 40 | 41 | 42 | ## 获取弹幕 43 | 44 | #### 请求URL 45 | - ` http://域名/api/v1/danmaku/{视频ID}/{分P} ` 46 | 47 | #### 请求方式 48 | - GET 49 | 50 | #### 请求参数 51 | - 见url 52 | 53 | #### 返回示例 54 | 55 | ``` json 56 | { 57 | "code": 200, 58 | "data": { 59 | "danmaku": [ 60 | { 61 | "time": 10, 62 | "type": 0, 63 | "color": "#fff", 64 | "text": "私信测试" 65 | } 66 | ] 67 | }, 68 | "mssage": "success" 69 | } 70 | ``` 71 | 72 | #### 返回参数说明 73 | 74 | | 参数名 | 类型 | 说明 | 75 | | :----- | :----- | -------- | 76 | | time | int | 进度 | 77 | | type | int | 弹幕类型 | 78 | | color | string | 弹幕颜色 | 79 | | text | string | 弹幕内容 | 80 | 81 | #### 备注 82 | 无 83 | -------------------------------------------------------------------------------- /docs/src/api/index.md: -------------------------------------------------------------------------------- 1 | # 开始 2 | 3 | ## 双token登录方案 4 | 本项目登录采用双token登录 5 | 6 | access_token 在登陆后会存在redis中`user:token:token 为key`有效时间为12小时 7 | 8 | refresh_token 则不会存在redis中, 会存在cookie中, 有效时间为一周 9 | ![双token登录方案](../image/token.png) 10 | 11 | ## 稿件状态码 12 | | 状态 | 状态码 | 含义 | 13 | | ------------------- | ------ | ---------------- | 14 | | AUDIT_APPROVED | 0 | 审核通过 | 15 | | CREATED_VIDEO | 100 | 成功创建视频 | 16 | | VIDEO_PROCESSING | 200 | 视频转码中 | 17 | | SUBMIT_REVIEW | 300 | 用户提交审核 | 18 | | WAITING_REVIEW | 500 | 等待审核 | 19 | | WRONG_VIDEO_INFO | 2100 | 视频信息存在问题 | 20 | | WRONG_VIDEO_CONTENT | 2200 | 视频内容存在问题 | 21 | | PROCESSING_FAIL | 2300 | 视频处理失败 | 22 | 23 | 24 | ## 用户身份 25 | | 角色 | 角色编号 | 含义 | 26 | | ------- | -------- | -------- | 27 | | root | 3 | 超级管理 | 28 | | admin | 2 | 管理 | 29 | | auditor | 1 | 审核 | 30 | | user | 0 | 普通用户 | 31 | 32 | 角色权限向下兼容 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/src/api/partition.md: -------------------------------------------------------------------------------- 1 | # 分区相关接口 2 | 3 | ## 获取分区列表 4 | 5 | #### 请求URL 6 | - ` http://域名/api/v1/partition ` 7 | 8 | #### 请求方式 9 | - GET 10 | 11 | #### 返回示例 12 | 13 | ``` json 14 | { 15 | "code": 200, 16 | "message": "success", 17 | "data": [ 18 | { 19 | "id": 1, 20 | "content": "测试大分区", 21 | "parentId": 0 22 | }, 23 | { 24 | "id": 2, 25 | "content": "测试小分区", 26 | "parentId": 1 27 | } 28 | ] 29 | } 30 | ``` 31 | 32 | ## 新增分区 33 | 34 | #### 请求URL 35 | - ` http://域名/api/v1/partition ` 36 | 37 | #### 请求方式 38 | - POST 39 | 40 | #### 请求头 41 | - `"content-type": "application/json",` 42 | - `Authorization': access_token` 43 | 44 | #### 参数 45 | 46 | | 参数名 | 必选 | 类型 | 说明 | 47 | | :------- | :--- | :----- | ---------- | 48 | | content | 是 | string | 分区名 | 49 | | parentId | 否 | int | 所属分区id | 50 | 51 | #### 返回示例 52 | 53 | ```json 54 | { 55 | "code": 200, 56 | "data": null, 57 | "mssage": "success" 58 | } 59 | ``` 60 | 61 | #### 备注 62 | 需要超级管理员权限 63 | 64 | 65 | ## 删除分区 66 | 67 | #### 请求URL 68 | - ` http://域名/api/v1/partition/{id} ` 69 | 70 | #### 请求方式 71 | - DELETE 72 | 73 | #### 请求头 74 | - `"content-type": "application/json",` 75 | - `Authorization': access_token` 76 | 77 | #### 参数 78 | 79 | | 参数名 | 必选 | 类型 | 说明 | 80 | | :----- | :--- | :--- | ------ | 81 | | id | 是 | int | 分区id | 82 | 83 | #### 返回示例 84 | 85 | ```json 86 | { 87 | "code": 200, 88 | "data": null, 89 | "msgsage": "success" 90 | } 91 | ``` 92 | 93 | #### 备注 94 | 需要超级管理员权限 95 | -------------------------------------------------------------------------------- /docs/src/api/resource.md: -------------------------------------------------------------------------------- 1 | # 视频资源接口 2 | 3 | ## 修改资源标题 4 | 5 | #### 请求URL 6 | - ` http://域名/api/v1/resource ` 7 | 8 | #### 请求方式 9 | - POST 10 | 11 | #### 请求头 12 | - `"content-type": "application/json",` 13 | - `Authorization': access_token` 14 | 15 | #### 参数 16 | 17 | | 参数名 | 必选 | 类型 | 说明 | 18 | | :----- | :--- | :----- | -------- | 19 | | id | 是 | int | 资源ID | 20 | | title | 是 | string | 资源标题 | 21 | 22 | #### 返回示例 23 | 24 | ``` json 25 | { 26 | "code": 200, 27 | "data": null, 28 | "mssage": "success" 29 | } 30 | ``` 31 | 32 | #### 备注 33 | 无 34 | 35 | 36 | ## 删除资源 37 | 38 | #### 请求URL 39 | - ` http://域名/api/v1/resource/{id} ` 40 | 41 | #### 请求方式 42 | - POST 43 | 44 | #### 请求头 45 | - `"content-type": "application/json",` 46 | - `Authorization': access_token` 47 | 48 | #### 参数 49 | 50 | | 参数名 | 必选 | 类型 | 说明 | 51 | | :----- | :--- | :--- | ------ | 52 | | id | 否 | int | 视频id | 53 | 54 | #### 返回示例 55 | 56 | ``` json 57 | { 58 | "code": 200, 59 | "data": null, 60 | "mssage":"success" 61 | } 62 | ``` 63 | 64 | #### 备注 65 | 无 -------------------------------------------------------------------------------- /docs/src/api/upload.md: -------------------------------------------------------------------------------- 1 | # 文件上传相关接口 2 | 3 | ## 视频封面上传接口 4 | 5 | #### 请求URL 6 | - ` http://域名/api/v1/upload/image ` 7 | 8 | #### 请求方式 9 | - POST 10 | 11 | #### 请求头 12 | - `Authorization': access_token ` 13 | 14 | #### 参数 15 | | 参数名 | 必选 | 类型 | 说明 | 16 | | :----- | :--- | :--- | -------- | 17 | | image | 否 | file | 图片文件 | 18 | 19 | - 在postman中使用form-data类型进行测试 20 | 21 | #### 返回示例 22 | 23 | ``` json 24 | { 25 | "code": 200, 26 | "data": { 27 | "url":"", 28 | }, 29 | "mssage":"success" 30 | } 31 | ``` 32 | 33 | #### 返回参数说明 34 | 35 | | 参数名 | 类型 | 说明 | 36 | | :----- | :----- | ------- | 37 | | url | string | 图片url | 38 | 39 | #### 备注 40 | 无 41 | 42 | 43 | ## 视频上传接口 44 | 45 | #### 请求URL 46 | - ` http://域名/api/v1/upload/video/{vid} ` 47 | 48 | #### 请求方式 49 | - POST 50 | 51 | #### 请求头 52 | - `Authorization': access_token ` 53 | 54 | #### 参数 55 | 56 | | 参数名 | 必选 | 类型 | 说明 | 57 | | :----- | :--- | :----- | -------- | 58 | | vid | 否 | string | 视频id(在URL中) | 59 | | video | 否 | file | 视频文件 | 60 | 61 | - 在postman中使用form-data类型进行测试 62 | 63 | #### 返回示例 64 | 65 | ``` json 66 | { 67 | "code": 200, 68 | "data": null, 69 | "mssage":"success" 70 | } 71 | ``` 72 | 73 | #### 备注 74 | 无 -------------------------------------------------------------------------------- /docs/src/guide/api.md: -------------------------------------------------------------------------------- 1 | # 后端部署 2 | 3 | :::tip 4 | 后端使用springboot开发,需要安装好java,Maven环境,数据库使用mysql,redis需要安装好mysql和redis。其中mysql的sql文件位于`docs/data/sql`中 5 | ::: 6 | 7 | ### 部署前准备 8 | 9 | 1. 该项目使用了七牛云的对象存储,需要在七牛云上创建一个对象存储空间 10 | 2. 该项目使用了邮箱验证码功能,需要在邮箱中开启smtp服务,获取邮箱的授权码 11 | 12 | ### 配置后端文件 13 | 打开 `sercver/src/main/resources/application.yml`文件,根据其中的注释修改配置文件并保存 14 | 15 | ### 打包 16 | 17 | ```bash 18 | # 进入server目录 19 | cd server 20 | # 打包 21 | mvn package 22 | ``` 23 | ### 运行 24 | 直接运行jar包 25 | 其中后端默认端口为2023,如需修改请修改`application.yml`中的`server.port`属性 26 | ```bash 27 | # 进入server目录 28 | cd server 29 | # 运行jar包 30 | java -jar target/server-1.0-SNAPSHOT.jar 31 | ``` 32 | 33 | ### Docker部署 34 | :::tip 35 | 在使用docker安装之前,需要安装好docker 36 | docker镜像可能会更新慢 37 | ::: 38 | 39 | 直接使用docker镜像部署 40 | ```bash 41 | docker run -e MY_MAIL_HOST=smtp.exmail.qq.com \ 42 | -e MY_MAIL_USERNAME=xxx@qq.com \ 43 | -e MY_MAIL_PASSWORD=xxxxxxxx \ 44 | -e MY_MAIL_PORT=465 \ 45 | -e MY_QINIU_ACCESS=xxxxxxxxxxxxxx \ 46 | -e MY_QINIU_SECRET=xxxxxxxxxxxxxxxxxx \ 47 | -e MY_QINIU_BUCKET=xxxx \ 48 | -e MY_QINIU_PATH=http://xxx.xxx/ \ 49 | -p 2023:2023 -d schuanhe/plooks-api 50 | ``` 51 | 环境变量说明 52 | 53 | | 环境变量 | 说明 | 54 | |------------------|--------------| 55 | | MY_MAIL_HOST | 邮箱smtp地址 | 56 | | MY_MAIL_USERNAME | 邮箱用户名 | 57 | | MY_MAIL_PASSWORD | 邮箱授权码 | 58 | | MY_MAIL_PORT | 邮箱端口 | 59 | | MY_QINIU_ACCESS | 七牛云accessKey | 60 | | MY_QINIU_SECRET | 七牛云secretKey | 61 | | MY_QINIU_BUCKET | 七牛云存储空间名称 | 62 | | MY_QINIU_PATH | 七牛云存储空间域名 | -------------------------------------------------------------------------------- /docs/src/guide/index.md: -------------------------------------------------------------------------------- 1 | # 概要 2 | 3 | ## 开发背景 4 | 5 | Plooks的名称灵感来自于"people + look",强调了人们一起观看视频的理念。 6 | 我们的目标是满足那些分隔两地、异地恋情侣或任何需要一起观看电影、视频的用户的需求。跨越地理障碍,一起看视频,一起聊天,一起分享快乐。 7 | 8 | ## 项目介绍 9 | 10 | 在前段时间,因为想和异地的朋友一起看电影,但是发现有电影的地方没有一起看功能,有一起看功能的视频网站没有电影,所以就想自己做一个一起看网站,于是就有了Plooks。 11 | 12 | Plooks是一个完整的视频网站,其中包括了视频网站基本的功能和一起看功能,旨在公用,功能齐全 13 | 14 | 我的另一个项目[Plook](https://github.com/schuanhe/plook)是一个纯粹的一起看网站,只有通过提供视频链接实现一起看功能。旨在私用,方便快捷 15 | 16 | ## 技术栈 17 | Plooks 一起看网站是一个基于Spring Boot+Vue的前后端分离项目。 18 | 19 | Web 端使用 : 20 | Vue + Vue Router + Pinia + Axios + Vite + TypeScript + Naive UI + Vue-i18n + WebSocket 21 | 22 | 后端使用 Spring Boot + Spring Security + MyBatis-Plus + Redis + Qiniu云存储 + JJWT + Druid连接池 + EasyCaptcha + WebSocket 23 | 24 | ## 其他 25 | QQ交流群:[486601640](https://qm.qq.com/cgi-bin/qm/qr?k=upwuvNRXfdnArlXNm98suFCpxJ8Gv-66&jump_from=webapi&authKey=07Ba7hMXblM5e+J92re2LIdhU/+mUCLx8jYziz10/hZYxX3bT1d95FGAvBxZAbhb) 26 | 27 | -------------------------------------------------------------------------------- /docs/src/guide/qa.md: -------------------------------------------------------------------------------- 1 | # 常见问题解答 2 | 3 | ## 后台管理404 4 | 后台管理地址为 `域名/manage/`,最后以`/`结尾,没有`/`可能会出现404 5 | 6 | ## sql文件导入失败 7 | sql文件中有字段使用json格式,需要版本大于5.7.8,如果版本低于5.7.8,可以将json字段改为text 8 | 9 | ## 修改默认管理员密码 10 | 使用默认账号(账号:schuanhe 密码: 123456)登录后台管理, 11 | 修改邮箱为自己的邮箱,进入到前端登录页面,点击找回密码修改密码 12 | 13 | ## 修改网站配色 14 | 找到主题文件 15 | - web端 `web\packages\web-client\src\theme\index.ts` 16 | - 后台管理端 `web\packages\manage-client\src\theme\index.ts` 17 | 18 | 修改以下颜色并重新打包 19 | ```js 20 | const defaultTheme: Theme = { 21 | primaryColor: "#ffc90c", 22 | primaryHoverColor: "#f8df72" 23 | } 24 | ``` 25 | ## 修改后台管理路径 26 | 后台管理默认`/manage/`,如需修改须按照以下步骤: 27 | 1. 打开`web\packages\manage-client\vite.config.ts`,修改`base`为想要的路径, 28 | (像`/manage`前面有`/`后面没有`/`),修改`build.outDir`,需要与`base`相对应(没有`/`) 29 | 2. 配置nginx时,找到以下部分内容,并将`manage`替换掉。 30 | ``` 31 | location /manage/ { 32 | root /usr/share/nginx/html; 33 | index index.html index.htm; 34 | try_files $uri $uri/ @manage; 35 | } 36 | 37 | # 解决后台管理history路由问题 38 | location @manage { 39 | rewrite ^.*$ /manage/index.html; 40 | } 41 | ``` 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/src/guide/screenshot.md: -------------------------------------------------------------------------------- 1 | # 相关截图 2 | 3 | | Web端 | | 4 | |:----------------------------------------:|:-------------------------------------:| 5 | | ![Web端登录](../image/web_login.png) | ![Web端首页](../image/web_home.png) | 6 | | ![Web端上传](../image/web_upload.png) | ![Web端视频](../image/web_video.png) | 7 | | ![Web端个人中心](../image/web_space.png) | ![Web端一起看](../image/web_plook.png) | 8 | | 后台管理端 | | 9 | | ![后台管理端登录](../image/manage_login.png) | ![后台管理用户管理](../image/manage_user.png) | 10 | | ![后台管理视频审核](../image/manage_videoup.png) | ![智能后台](../image/manage_gpt.png) | -------------------------------------------------------------------------------- /docs/src/guide/web.md: -------------------------------------------------------------------------------- 1 | # 前端部署 2 | 3 | :::tip 4 | 前端使用vue3开发,需要安装好nodejs和pnpm环境 5 | ::: 6 | 7 | ### 配置前端文件 8 | 前端文件位于`client\utils\src\config.ts`,修改`domain`为后端域名+端口,如`xxx.xxx.xxx.xxx:2023`,如果是本地部署可以使用`localhost:2023` 9 | 10 | ### 打包 11 | 前端分为web-client、manage-client两个项目,分别对应web端、后台管理端: 12 | 13 | 先安装依赖 14 | ``` 15 | # 进入client目录 16 | cd client 17 | # 安装依赖 18 | pnpm i 19 | ``` 20 | 如果没有安装pnpm,可以使用npm代替或者安装pnpm 21 | ``` 22 | # 安装pnpm 23 | npm i -g pnpm 24 | ``` 25 | 打包web-client 26 | ``` 27 | # 进入web-client目录 28 | cd packages/web-client 29 | # 打包 30 | pnpm run build 31 | ``` 32 | 打包manage-client 33 | ``` 34 | # 进入manage-client目录 35 | cd packages/manage-client 36 | # 打包 37 | pnpm run build 38 | ``` 39 | 40 | ### 部署 41 | 42 | 项目使用可以使用nginx部署,需要先将打包好的文件复制到nginx的html目录下,然后配置nginx 43 | nginx配置文件如下: 44 | ``` 45 | server { 46 | listen 80; 47 | server_name localhost; #有域名可以把localhost替换为域名 48 | client_max_body_size 1024M; 49 | 50 | location / { 51 | root /usr/share/nginx/html/web; 52 | index index.html index.htm; 53 | try_files $uri $uri/ @web; 54 | } 55 | 56 | # 解决history路由问题 57 | location @web { 58 | rewrite ^.*$ /index.html; 59 | } 60 | 61 | #后台管理 62 | location /manage/ { 63 | root /usr/share/nginx/html; 64 | index index.html index.htm; 65 | try_files $uri $uri/ @manage; 66 | } 67 | 68 | # 解决后台管理history路由问题 69 | location @manage { 70 | rewrite ^.*$ /manage/index.html; 71 | } 72 | 73 | } 74 | ``` -------------------------------------------------------------------------------- /docs/src/image/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/alipay.png -------------------------------------------------------------------------------- /docs/src/image/manage_gpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/manage_gpt.png -------------------------------------------------------------------------------- /docs/src/image/manage_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/manage_login.png -------------------------------------------------------------------------------- /docs/src/image/manage_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/manage_user.png -------------------------------------------------------------------------------- /docs/src/image/manage_videoup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/manage_videoup.png -------------------------------------------------------------------------------- /docs/src/image/token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/token.png -------------------------------------------------------------------------------- /docs/src/image/web_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/web_home.png -------------------------------------------------------------------------------- /docs/src/image/web_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/web_login.png -------------------------------------------------------------------------------- /docs/src/image/web_plook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/web_plook.png -------------------------------------------------------------------------------- /docs/src/image/web_space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/web_space.png -------------------------------------------------------------------------------- /docs/src/image/web_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/web_upload.png -------------------------------------------------------------------------------- /docs/src/image/web_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/web_video.png -------------------------------------------------------------------------------- /docs/src/image/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schuanhe/Plooks/e9a623205ca4e05a6bc547dde78aa403eef3c1b6/docs/src/image/wechat.png -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | title: Plooks 一起看网站 5 | 6 | hero: 7 | name: Plooks 一起看 8 | text: 基于Spring Boot + Vue的一起看网站 9 | tagline: 前后端分离 \ 完全开源 \ 部署简单 10 | actions: 11 | - theme: brand 12 | text: 开始 13 | link: /guide/ 14 | - theme: alt 15 | text: 在 GitHub 上查看 16 | link: https://github.com/schuanhe/Plooks/ 17 | 18 | features: 19 | - title: 开源 20 | details: 基于AGPL-3.0协议,源代码完全开源 21 | - title: 文档丰富 22 | details: 提供了部署文档、后端接口文档以及视频教程,方便用户进行修改和二次开发。 23 | - title: 前后端分离 24 | details: 前后端分离,前端使用Vue,后端使用Spring Boot 25 | - title: 一起看功能 26 | details: 通过websocket实现了一起看功能,可以和所有人一起看视频,房间聊天 27 | --- -------------------------------------------------------------------------------- /docs/src/other/donate.md: -------------------------------------------------------------------------------- 1 | # 赞助 2 | 3 | 如果您觉得这个项目对您有帮助,可以帮作者买杯饮料鼓励鼓励! 4 | 5 | | WeChat | Alipay | 6 | | :--------------------------: | :-------------------------: | 7 | | ![微信](../image/wechat.png) | ![支付宝](../image/alipay.png) | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": [ 7 | "ESNext", 8 | "DOM" 9 | ], 10 | "moduleResolution": "Node", 11 | "strict": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "noEmit": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "skipLibCheck": true 20 | }, 21 | } -------------------------------------------------------------------------------- /docs/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | server: { 5 | host: '0.0.0.0', 6 | open: true 7 | } 8 | }); -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17-slim 2 | 3 | WORKDIR /app 4 | 5 | 6 | # 复制JAR文件 7 | COPY target/plooks-api-1.0-SNAPSHOT.jar . 8 | 9 | # 检查是否存在plooks-api-1.0-SNAPSHOT.jar文件 10 | RUN if [ ! -e plooks-api-1.0-SNAPSHOT.jar ]; then \ 11 | echo "Error: 请先打包jar文件,或者使用Dockerfile-build"; \ 12 | exit 1; \ 13 | fi 14 | 15 | EXPOSE 2023 16 | 17 | CMD ["java", "-jar", "plooks-api-1.0-SNAPSHOT.jar"] 18 | 19 | 20 | -------------------------------------------------------------------------------- /server/Dockerfile-build: -------------------------------------------------------------------------------- 1 | # 构建阶段 2 | FROM maven:3.8.4-openjdk-17-slim AS build 3 | 4 | WORKDIR /app 5 | 6 | COPY . . 7 | 8 | 9 | # 打开 Maven 配置文件,添加阿里云镜像配置 10 | RUN sed -i '/<\/mirrors>/i \ 11 | \ 12 | aliyunmaven<\/id> \ 13 | * \ 14 | 阿里云公共仓库<\/name> \ 15 | https:\/\/maven.aliyun.com\/repository\/public<\/url> \ 16 | <\/mirror>' /usr/share/maven/conf/settings.xml 17 | 18 | RUN mvn clean install 19 | 20 | # 运行阶段 21 | FROM openjdk:17-slim 22 | 23 | WORKDIR /app 24 | 25 | # 复制构建阶段产生的 JAR 文件 26 | COPY --from=build /app/target/plooks-api-1.0-SNAPSHOT.jar . 27 | 28 | CMD ["java", "-jar", "plooks-api-1.0-SNAPSHOT.jar"] -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/Application.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks; 2 | 3 | 4 | import org.mybatis.spring.annotation.MapperScan; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.scheduling.annotation.EnableAsync; 8 | 9 | @MapperScan("com.schuanhe.plooks.mapper") 10 | @SpringBootApplication 11 | @EnableAsync // 开启异步任务 12 | public class Application { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(Application.class, args); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | /** 8 | * 解决跨域问题 9 | */ 10 | @Configuration 11 | public class CorsConfig implements WebMvcConfigurer { 12 | 13 | @Override 14 | public void addCorsMappings(CorsRegistry registry) { 15 | // 设置允许跨域的路径 16 | registry.addMapping("/**") 17 | // 设置允许跨域请求的域名 18 | .allowedOriginPatterns("*") 19 | // 是否允许cookie 20 | .allowCredentials(true) 21 | // 设置允许的请求方式 22 | .allowedMethods("GET", "POST", "DELETE", "PUT") 23 | // 设置允许的header属性 24 | .allowedHeaders("*") 25 | // 跨域允许时间 26 | .maxAge(3600); 27 | 28 | } 29 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/config/QiniuFileConfig.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.config; 2 | 3 | 4 | import com.qiniu.http.Client; 5 | import com.qiniu.processing.OperationManager; 6 | import com.qiniu.storage.BucketManager; 7 | import com.qiniu.storage.Region; 8 | import com.qiniu.storage.UploadManager; 9 | import com.qiniu.util.Auth; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | 15 | @Configuration 16 | public class QiniuFileConfig { 17 | 18 | @Value("${qiniu.access}") 19 | private String access; 20 | 21 | @Value("${qiniu.secret}") 22 | private String secret; 23 | 24 | 25 | 26 | @Bean 27 | public com.qiniu.storage.Configuration qiniuConfiguration() { 28 | //七牛云的配置类,和注解有点冲突 29 | return new com.qiniu.storage.Configuration(Region.huanan()); 30 | } 31 | 32 | /** * 构建一个七牛云上传工具实例 */ 33 | @Bean 34 | public UploadManager uploadManager() { 35 | return new UploadManager(qiniuConfiguration()); 36 | } 37 | 38 | 39 | /** * 认证信息实例 * @return */ 40 | @Bean 41 | public Auth auth() { 42 | return Auth.create(access, secret); 43 | } 44 | 45 | /** * 构建七牛空间管理实例 */ 46 | @Bean 47 | public BucketManager bucketManager() { 48 | return new BucketManager(auth(), qiniuConfiguration()); 49 | } 50 | 51 | @Bean 52 | public Client client() { 53 | return new Client(qiniuConfiguration()); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.config; 2 | 3 | 4 | 5 | import com.schuanhe.plooks.utils.FastJsonRedisSerializer; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.data.redis.connection.RedisConnectionFactory; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.data.redis.serializer.StringRedisSerializer; 11 | 12 | @Configuration 13 | public class RedisConfig { 14 | 15 | @Bean 16 | @SuppressWarnings(value = { "unchecked", "rawtypes" }) 17 | public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) 18 | { 19 | RedisTemplate template = new RedisTemplate<>(); 20 | template.setConnectionFactory(connectionFactory); 21 | 22 | FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class); 23 | 24 | // 使用StringRedisSerializer来序列化和反序列化redis的key值 25 | template.setKeySerializer(new StringRedisSerializer()); 26 | template.setValueSerializer(serializer); 27 | 28 | // Hash的key也采用StringRedisSerializer的序列化方式 29 | template.setHashKeySerializer(new StringRedisSerializer()); 30 | template.setHashValueSerializer(serializer); 31 | 32 | template.afterPropertiesSet(); 33 | return template; 34 | } 35 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.socket.server.standard.ServerEndpointExporter; 6 | 7 | /** 8 | * webSocket 配置 9 | */ 10 | @Configuration 11 | public class WebSocketConfig { 12 | @Bean 13 | public ServerEndpointExporter serverEndpointExporter() { 14 | return new ServerEndpointExporter(); 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/controller/Admin/AiMangeController.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.controller.Admin; 2 | 3 | 4 | import com.schuanhe.plooks.service.Admin.AiMangeService; 5 | import com.schuanhe.plooks.utils.ResponseResult; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.Map; 13 | 14 | @RestController 15 | @RequestMapping("${base-url}/aiMange") 16 | public class AiMangeController { 17 | 18 | @Autowired 19 | private AiMangeService aiMangeService; 20 | 21 | /** 22 | * 通过问题获取数据 23 | */ 24 | @PostMapping("/getAiData") 25 | public ResponseResult getAiData(@RequestBody Map data) { 26 | String q = data.get("q"); 27 | if (q == null || "".equals(q)) { 28 | return ResponseResult.error("参数错误"); 29 | } 30 | try { 31 | Map aiData = aiMangeService.getAiData(q); 32 | return ResponseResult.success(aiData); 33 | }catch (Exception e){ 34 | return ResponseResult.error(e.getMessage()); 35 | } 36 | 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/controller/Admin/AnnouncesAdminController.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.controller.Admin; 2 | 3 | 4 | import com.schuanhe.plooks.domain.Message; 5 | import com.schuanhe.plooks.service.Admin.AnnouncesAdminService; 6 | import com.schuanhe.plooks.utils.ResponseResult; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | @RestController 11 | @RequestMapping("${base-url}/announce/admin") 12 | public class AnnouncesAdminController { 13 | 14 | @Autowired 15 | private AnnouncesAdminService announcesAdminService; 16 | 17 | /** 18 | * 新增公告 19 | */ 20 | @PostMapping 21 | public ResponseResult addAnnounce(@RequestBody Message.Announces announces) { 22 | try { 23 | announcesAdminService.addAnnounce(announces); 24 | } catch (Exception e) { 25 | return ResponseResult.error(e.getMessage()); 26 | } 27 | return ResponseResult.success(); 28 | } 29 | 30 | /** 31 | * 删除公告 32 | */ 33 | @DeleteMapping("/{id}") 34 | public ResponseResult deleteAnnounce(@PathVariable Integer id) { 35 | announcesAdminService.deleteAnnounce(id); 36 | return ResponseResult.success(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/controller/Admin/CarouselAdminController.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.controller.Admin; 2 | 3 | 4 | import com.schuanhe.plooks.domain.Carousels; 5 | import com.schuanhe.plooks.service.Admin.CarouselAdminService; 6 | import com.schuanhe.plooks.utils.ResponseResult; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | /** 11 | * 轮播图 12 | */ 13 | @RestController 14 | @RequestMapping("${base-url}/carousel/admin") 15 | public class CarouselAdminController { 16 | 17 | @Autowired 18 | private CarouselAdminService carouselAdminService; 19 | 20 | /** 21 | * 新增轮播图 22 | */ 23 | @PostMapping 24 | public ResponseResult addCarousel(@RequestBody Carousels carousels) { 25 | carouselAdminService.addCarousel(carousels); 26 | return ResponseResult.success(); 27 | } 28 | 29 | /** 30 | * 删除轮播图 31 | */ 32 | @DeleteMapping("/{id}") 33 | public ResponseResult deleteCarousel(@PathVariable Integer id) { 34 | carouselAdminService.deleteCarousel(id); 35 | return ResponseResult.success(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/controller/Admin/PartitionAdminController.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.controller.Admin; 2 | 3 | 4 | import com.schuanhe.plooks.domain.Partition; 5 | import com.schuanhe.plooks.service.Admin.PartitionAdminService; 6 | import com.schuanhe.plooks.utils.ResponseResult; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | @RestController 11 | @RequestMapping("${base-url}/partition/admin") 12 | public class PartitionAdminController { 13 | 14 | @Autowired 15 | private PartitionAdminService partitionAdminService; 16 | 17 | /** 18 | * 新增分区 19 | */ 20 | @PostMapping 21 | public ResponseResult addPartition(@RequestBody Partition partition) { 22 | try { 23 | partitionAdminService.addPartition(partition); 24 | }catch (Exception e) { 25 | return ResponseResult.error(e.getMessage()); 26 | } 27 | 28 | return ResponseResult.success(); 29 | } 30 | 31 | /** 32 | * 删除分区 33 | */ 34 | @DeleteMapping("/{id}") 35 | public ResponseResult deletePartition(@PathVariable Integer id) { 36 | partitionAdminService.deletePartition(id); 37 | return ResponseResult.success(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/controller/User/CarouselController.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.controller.User; 2 | 3 | 4 | import com.schuanhe.plooks.domain.Carousels; 5 | import com.schuanhe.plooks.service.User.CarouselsService; 6 | import com.schuanhe.plooks.utils.ResponseResult; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * 轮播图相关接口 18 | */ 19 | @RestController 20 | @RequestMapping("${base-url}/carousel") 21 | public class CarouselController { 22 | 23 | @Autowired 24 | private CarouselsService carouselsService; 25 | /** 26 | * 获取轮播图 27 | * @return 轮播图 28 | */ 29 | @GetMapping 30 | public ResponseResult getCarousel() { 31 | List carousels = carouselsService.getCarousels(); 32 | 33 | // 返回轮播图 34 | Map data = new HashMap<>(); 35 | data.put("carousels", carousels); 36 | return ResponseResult.success(data); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/controller/User/DanmukusController.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.controller.User; 2 | 3 | 4 | import com.schuanhe.plooks.domain.Danmukus; 5 | import com.schuanhe.plooks.service.User.DanmukusService; 6 | import com.schuanhe.plooks.utils.ResponseResult; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | @RestController 15 | @RequestMapping("${base-url}/danmuku") 16 | public class DanmukusController { 17 | 18 | @Autowired 19 | private DanmukusService danmukusService; 20 | 21 | /** 22 | * 获取弹幕 23 | */ 24 | @GetMapping("/{vid}/{part}") 25 | public ResponseResult getDanmukus(@PathVariable Integer vid, @PathVariable Integer part) { 26 | 27 | List danmukusList = danmukusService.getDanmukus(vid,part); 28 | 29 | Map> data = new HashMap<>(); 30 | data.put("Danmuku",danmukusList); 31 | 32 | return ResponseResult.success(data); 33 | 34 | } 35 | 36 | /** 37 | * 添加弹幕 38 | */ 39 | @PostMapping 40 | public ResponseResult addDanmukus(@RequestBody Danmukus danmukus) { 41 | 42 | danmukusService.addDanmukus(danmukus); 43 | 44 | return ResponseResult.success(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/controller/User/PartitionController.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.controller.User; 2 | 3 | 4 | import com.schuanhe.plooks.domain.Partition; 5 | import com.schuanhe.plooks.service.User.PartitionService; 6 | import com.schuanhe.plooks.utils.ResponseResult; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | 17 | /** 18 | * 分区 19 | */ 20 | @RestController 21 | @RequestMapping("${base-url}/partition") 22 | public class PartitionController { 23 | 24 | @Autowired 25 | private PartitionService partitionService; 26 | 27 | /** 28 | * 获取分区列表 29 | */ 30 | @GetMapping 31 | public ResponseResult>> getPartitionList() { 32 | // 获取分区列表(去除时间字段和已删除字段) 33 | List partitionList = partitionService.partitionList(); 34 | 35 | Map> data = new HashMap<>(); 36 | data.put("partitions",partitionList); 37 | 38 | return ResponseResult.success(data); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/controller/User/ResourceController.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.controller.User; 2 | 3 | 4 | import com.schuanhe.plooks.domain.Resources; 5 | import com.schuanhe.plooks.service.User.ResourcesService; 6 | import com.schuanhe.plooks.utils.ResponseResult; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | @RestController 11 | @RequestMapping("${base-url}/resource") 12 | public class ResourceController { 13 | 14 | 15 | @Autowired 16 | private ResourcesService resourcesService; 17 | 18 | /** 19 | * 删除资源 20 | * @param id 资源id 21 | */ 22 | @DeleteMapping("/{id}") 23 | public ResponseResult deleteResource(@PathVariable Integer id){ 24 | // 判断id是否合法 25 | if (id == null) { 26 | ResponseResult.error("资源不存在"); 27 | } 28 | 29 | boolean deleteResource = resourcesService.deleteResource(id); 30 | 31 | if (deleteResource) { 32 | return ResponseResult.success("删除成功"); 33 | } else { 34 | return ResponseResult.error("删除失败"); 35 | } 36 | } 37 | 38 | /** 39 | * 修改资源标题 40 | */ 41 | @PutMapping 42 | public ResponseResult updateResourceTitle(@RequestBody Resources resources){ 43 | // 判断id是否合法 44 | if ( resources == null) { 45 | ResponseResult.error("资源不存在"); 46 | } 47 | 48 | boolean updateResourceTitle = resourcesService.updateResourceTitle(resources); 49 | 50 | if (updateResourceTitle) { 51 | return ResponseResult.success("修改成功"); 52 | } else { 53 | return ResponseResult.error("修改失败"); 54 | } 55 | } 56 | 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/Archive.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.fasterxml.jackson.annotation.JsonInclude; 7 | import lombok.Data; 8 | 9 | import java.util.Date; 10 | 11 | 12 | public class Archive { 13 | 14 | @Data 15 | @JsonInclude(JsonInclude.Include.NON_NULL) 16 | public static class Collect{ 17 | 18 | @TableId(type = IdType.AUTO) 19 | private Integer id; 20 | 21 | private Date createdAt; 22 | 23 | private Date updatedAt; 24 | 25 | private Date deletedAt; 26 | 27 | private Integer vid; 28 | 29 | private Integer cid; 30 | 31 | @TableField(exist = false) 32 | private static final long serialVersionUID = 1L; 33 | } 34 | 35 | @Data 36 | @JsonInclude(JsonInclude.Include.NON_NULL) 37 | public static class Like{ 38 | 39 | @TableId(type = IdType.AUTO) 40 | private Integer id; 41 | 42 | private Date createdAt; 43 | 44 | private Date updatedAt; 45 | 46 | private Date deletedAt; 47 | 48 | private Integer vid; 49 | 50 | private Integer uid; 51 | 52 | @TableField(exist = false) 53 | private static final long serialVersionUID = 1L; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/Carousels.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | import lombok.Data; 10 | 11 | /** 12 | * 轮播图表 13 | * @TableName carousels 14 | */ 15 | @TableName(value ="carousels") 16 | @Data 17 | public class Carousels implements Serializable { 18 | /** 19 | * 20 | */ 21 | @TableId(type = IdType.AUTO) 22 | private Integer id; 23 | 24 | /** 25 | * 26 | */ 27 | private Date createdAt; 28 | 29 | /** 30 | * 31 | */ 32 | private Date updatedAt; 33 | 34 | /** 35 | * 36 | */ 37 | private Date deletedAt; 38 | 39 | /** 40 | * 图片链接 41 | */ 42 | private String img; 43 | 44 | /** 45 | * 标题 46 | */ 47 | private String title; 48 | 49 | /** 50 | * 指向的链接 51 | */ 52 | private String url; 53 | 54 | /** 55 | * 主题色 56 | */ 57 | private String color; 58 | 59 | @TableField(exist = false) 60 | private static final long serialVersionUID = 1L; 61 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/Collections.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | 10 | import com.fasterxml.jackson.annotation.JsonInclude; 11 | import lombok.Data; 12 | 13 | /** 14 | * 收藏夹表 15 | * @TableName collections 16 | */ 17 | @JsonInclude(JsonInclude.Include.NON_NULL) 18 | @TableName(value ="collections") 19 | @Data 20 | public class Collections implements Serializable { 21 | /** 22 | * 23 | */ 24 | @TableId(type = IdType.AUTO) 25 | private Integer id; 26 | 27 | /** 28 | * 29 | */ 30 | private Date createdAt; 31 | 32 | /** 33 | * 34 | */ 35 | private Date updatedAt; 36 | 37 | /** 38 | * 39 | */ 40 | private Date deletedAt; 41 | 42 | /** 43 | * 所属用户 44 | */ 45 | private Integer uid; 46 | 47 | /** 48 | * 收藏夹名称 49 | */ 50 | @TableField(value = "`name`") 51 | private String name; 52 | 53 | /** 54 | * 简介 55 | */ 56 | @TableField(value = "`desc`") 57 | private String desc; 58 | 59 | /** 60 | * 封面 61 | */ 62 | private String cover; 63 | 64 | /** 65 | * 是否公开 66 | */ 67 | @TableField(value = "`open`") 68 | private boolean open; 69 | 70 | @TableField(exist = false) 71 | private static final long serialVersionUID = 1L; 72 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/Danmukus.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | import lombok.Data; 10 | 11 | /** 12 | * 弹幕表 13 | * @TableName danmukus 14 | */ 15 | @TableName(value ="danmukus") 16 | @Data 17 | public class Danmukus implements Serializable { 18 | /** 19 | * 20 | */ 21 | @TableId(type = IdType.AUTO) 22 | private Integer id; 23 | 24 | /** 25 | * 26 | */ 27 | private Date createdAt; 28 | 29 | /** 30 | * 31 | */ 32 | private Date updatedAt; 33 | 34 | /** 35 | * 36 | */ 37 | private Date deletedAt; 38 | 39 | /** 40 | * 视频ID 41 | */ 42 | private Integer vid; 43 | 44 | /** 45 | * 分集ID 46 | */ 47 | private Integer part; 48 | 49 | /** 50 | * 内容 51 | */ 52 | private String text; 53 | 54 | /** 55 | * 时间 56 | */ 57 | private Double time; 58 | 59 | /** 60 | * 类型0滚动;1顶部;2底部 61 | */ 62 | private Integer mode; 63 | 64 | /** 65 | * 颜色 66 | */ 67 | private String color; 68 | 69 | /** 70 | * 用户ID 71 | */ 72 | private Integer uid; 73 | 74 | @TableField(exist = false) 75 | private static final long serialVersionUID = 1L; 76 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/Follows.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | import lombok.Data; 10 | 11 | /** 12 | * 关注表 13 | * @TableName follows 14 | */ 15 | @TableName(value ="follows") 16 | @Data 17 | public class Follows implements Serializable { 18 | /** 19 | * 20 | */ 21 | @TableId(type = IdType.AUTO) 22 | private Integer id; 23 | 24 | /** 25 | * 26 | */ 27 | private Date createdAt; 28 | 29 | /** 30 | * 31 | */ 32 | private Date updatedAt; 33 | 34 | /** 35 | * 36 | */ 37 | private Date deletedAt; 38 | 39 | /** 40 | * 用户ID 41 | */ 42 | private Integer uid; 43 | 44 | /** 45 | * 关注的用户ID 46 | */ 47 | private Integer fid; 48 | 49 | @TableField(exist = false) 50 | private static final long serialVersionUID = 1L; 51 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/Histories.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | 10 | import com.fasterxml.jackson.annotation.JsonInclude; 11 | import lombok.Data; 12 | 13 | /** 14 | * 历史记录表 15 | * @TableName histories 16 | */ 17 | @TableName(value ="histories") 18 | @Data 19 | @JsonInclude(JsonInclude.Include.NON_NULL) 20 | public class Histories implements Serializable { 21 | /** 22 | * 23 | */ 24 | @TableId(type = IdType.AUTO) 25 | private Integer id; 26 | 27 | /** 28 | * 29 | */ 30 | private Date createdAt; 31 | 32 | /** 33 | * 34 | */ 35 | private Date updatedAt; 36 | 37 | /** 38 | * 39 | */ 40 | private Date deletedAt; 41 | 42 | /** 43 | * 所在视频id 44 | */ 45 | private Integer vid; 46 | 47 | /** 48 | * 所属用户ID 49 | */ 50 | private Integer uid; 51 | 52 | /** 53 | * 分集 54 | */ 55 | private Integer part; 56 | 57 | /** 58 | * 进度 59 | */ 60 | private Double time; 61 | 62 | /** 63 | * 视频信息 64 | */ 65 | @TableField(exist = false) 66 | private Video video; 67 | 68 | @TableField(exist = false) 69 | private static final long serialVersionUID = 1L; 70 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/Partition.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | 10 | import com.fasterxml.jackson.annotation.JsonInclude; 11 | import lombok.Data; 12 | 13 | /** 14 | * 分区表 15 | * @TableName partitions 16 | */ 17 | @TableName(value ="partitions") 18 | @Data 19 | @JsonInclude(JsonInclude.Include.NON_NULL) 20 | public class Partition implements Serializable { 21 | /** 22 | * 23 | */ 24 | @TableId(type = IdType.AUTO) 25 | private Integer id; 26 | 27 | /** 28 | * 29 | */ 30 | private Date createdAt; 31 | 32 | /** 33 | * 34 | */ 35 | private Date updatedAt; 36 | 37 | /** 38 | * 39 | */ 40 | private Date deletedAt; 41 | 42 | /** 43 | * 分区名称 44 | */ 45 | private String content; 46 | 47 | /** 48 | * 所属分区ID 49 | */ 50 | private Integer parentId; 51 | 52 | @TableField(exist = false) 53 | private static final long serialVersionUID = 1L; 54 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/Resources.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | 10 | import lombok.AllArgsConstructor; 11 | import lombok.Data; 12 | import lombok.NoArgsConstructor; 13 | 14 | /** 15 | * 资源表 16 | * @TableName resources 17 | */ 18 | @TableName(value ="resources") 19 | @Data 20 | public class Resources implements Serializable { 21 | /** 22 | * 23 | */ 24 | @TableId(type = IdType.AUTO) 25 | private Integer id; 26 | 27 | /** 28 | * 上传时间 29 | */ 30 | private Date createdAt; 31 | 32 | /** 33 | * 更新时间 34 | */ 35 | private Date updatedAt; 36 | 37 | /** 38 | * 删除时间 39 | */ 40 | private Date deletedAt; 41 | 42 | /** 43 | * 所属视频 44 | */ 45 | private Integer vid; 46 | 47 | /** 48 | * 所属用户 49 | */ 50 | private Integer uid; 51 | 52 | /** 53 | * 分P使用的标题 54 | */ 55 | private String title; 56 | 57 | /** 58 | * 视频链接 59 | */ 60 | private String url; 61 | 62 | /** 63 | * 视频类型 64 | */ 65 | private String type; 66 | 67 | /** 68 | * 原始链接 69 | */ 70 | private String originalUrl; 71 | 72 | /** 73 | * 视频时长 74 | */ 75 | private Double duration; 76 | 77 | /** 78 | * 审核状态 79 | */ 80 | private Integer status; 81 | 82 | /** 83 | * 视频最大质量 84 | */ 85 | private Integer quality; 86 | 87 | @TableField(exist = false) 88 | private static final long serialVersionUID = 1L; 89 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/Whispers.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | import lombok.Data; 10 | 11 | /** 12 | * 私信表 13 | * @TableName whispers 14 | */ 15 | @TableName(value ="whispers") 16 | @Data 17 | public class Whispers implements Serializable { 18 | /** 19 | * 20 | */ 21 | @TableId(type = IdType.AUTO) 22 | private Integer id; 23 | 24 | /** 25 | * 26 | */ 27 | private Date createdAt; 28 | 29 | /** 30 | * 31 | */ 32 | private Date updatedAt; 33 | 34 | /** 35 | * 36 | */ 37 | private Date deletedAt; 38 | 39 | /** 40 | * 用户ID 41 | */ 42 | private Integer uid; 43 | 44 | /** 45 | * 关联ID 46 | */ 47 | private Integer fid; 48 | 49 | /** 50 | * 发送者 51 | */ 52 | private Integer fromId; 53 | 54 | /** 55 | * 接受者 56 | */ 57 | private Integer toId; 58 | 59 | /** 60 | * 内容 61 | */ 62 | private String content; 63 | 64 | /** 65 | * 已读状态 66 | */ 67 | private Boolean status; 68 | 69 | /** 70 | * 发送者的信息 71 | */ 72 | @TableField(exist = false) 73 | private User user; 74 | 75 | @TableField(exist = false) 76 | private static final long serialVersionUID = 1L; 77 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/form/LoginForm.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain.form; 2 | 3 | import com.schuanhe.plooks.domain.User; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class LoginForm { 12 | private User user; //用户信息 13 | private String code; //验证码 14 | private String uuid; //验证码唯一标识 15 | } 16 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/form/ReplyForm.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain.form; 2 | 3 | 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 接受回复参数 10 | */ 11 | @Data 12 | public class ReplyForm { 13 | 14 | private Integer vid; //视频id 15 | private String content; //回复内容 16 | private Integer parentId; //父评论id 17 | 18 | private Integer replyUserId; //回复对象id 19 | 20 | private String replyContent; // 回复评论 21 | 22 | private String rootComment; // 根评论 23 | private List at; // @用户昵称列表 24 | 25 | } 26 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/domain/form/RoomFrom.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.domain.form; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.annotation.JsonInclude; 6 | import lombok.Data; 7 | 8 | import javax.websocket.Session; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * 房间配置实体类 14 | */ 15 | @Data 16 | public class RoomFrom { 17 | private Integer id; // 房间id 18 | // 是否不公开 19 | private Boolean isPublic; 20 | // 密码 21 | private String password; 22 | // 房间 管理员 23 | private Integer adminId; 24 | // 在房间的用户id 25 | private List userIds; 26 | // 房间播放视频id 27 | private Integer vid; 28 | // 分p 29 | private Integer pid; 30 | // 视频进度 31 | private Double time; 32 | // 视频是否在播放 33 | private Boolean isPlay; 34 | 35 | // 用户id与其对应的session(不序列化) 36 | @JsonIgnore 37 | private Map userSession; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/handler.impl/AccessDeniedHandlerImpl.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.handler.impl; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.schuanhe.plooks.utils.ResponseResult; 5 | import com.schuanhe.plooks.utils.WebUtils; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.security.access.AccessDeniedException; 8 | import org.springframework.security.web.access.AccessDeniedHandler; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.servlet.ServletException; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.io.IOException; 15 | 16 | @Component 17 | public class AccessDeniedHandlerImpl implements AccessDeniedHandler { 18 | @Override 19 | public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { 20 | //授权处理异常 21 | ResponseResult result = ResponseResult.error(HttpStatus.FORBIDDEN.value(), accessDeniedException.getMessage()); 22 | // 23 | String json = JSON.toJSONString(result); 24 | WebUtils.renderString(response,json); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/handler.impl/AuthenticationEntryPointImpl.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.handler.impl; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.schuanhe.plooks.utils.RedisCache; 5 | import com.schuanhe.plooks.utils.ResponseResult; 6 | import com.schuanhe.plooks.utils.WebUtils; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.core.userdetails.UserDetailsService; 11 | import org.springframework.security.web.AuthenticationEntryPoint; 12 | import org.springframework.stereotype.Component; 13 | 14 | import javax.servlet.ServletException; 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.io.IOException; 18 | 19 | @Component 20 | public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { 21 | 22 | 23 | @Override 24 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { 25 | //认证异常处理 26 | ResponseResult result =ResponseResult.error(HttpStatus.UNAUTHORIZED.value(), authException.getMessage()); 27 | 28 | String json = JSON.toJSONString(result); 29 | WebUtils.renderString(response,json); 30 | } 31 | } -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/AiMangeMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.utils.SqlProvider; 4 | import org.apache.ibatis.annotations.SelectProvider; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | 12 | @Component 13 | public interface AiMangeMapper { 14 | 15 | @SelectProvider(type = SqlProvider.class, method = "dynamicSql") 16 | List> findByCriteria(String sql); 17 | } 18 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/ArchiveMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.schuanhe.plooks.domain.Archive; 5 | import com.schuanhe.plooks.domain.Message; 6 | 7 | import java.util.List; 8 | 9 | public interface ArchiveMapper extends BaseMapper{ 10 | 11 | /** 12 | * 获取收藏数 13 | * @param vid 视频id 14 | * @return 收藏数 15 | */ 16 | int getCollectCount(int vid); 17 | 18 | /** 19 | * 获取点赞数 20 | * @param vid 视频id 21 | * @return 点赞数 22 | */ 23 | int getLikeCount(int vid); 24 | 25 | int hasLike(int vid, int uid); 26 | 27 | int hasCollect(int vid, Integer uid); 28 | 29 | Archive.Like hasLikeNoDelete(int vid, Integer uid); 30 | 31 | void updateLikeById(Archive.Like like); 32 | 33 | void insertLike(Archive.Like like); 34 | 35 | List selectCollect(Integer uid, int vid); 36 | 37 | void cancelCollect(Integer uid, int vid, List cancelList); 38 | 39 | void addCollect(Integer uid, int vid, List addList); 40 | 41 | List getCollectVideoIds(Integer cid, Integer uid, int i, Integer size); 42 | 43 | Integer getCollectVideoCount(Integer cid, Integer uid); 44 | 45 | // 防止依赖注入时出错 46 | Integer selectUidByVid(int vid); 47 | 48 | void insertLikeMessage(Message.LikeMessages likeMessages); 49 | } 50 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/CarouselsMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.domain.Carousels; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author ASUS 10 | * @description 针对表【carousels(轮播图表)】的数据库操作Mapper 11 | * @createDate 2023-08-31 19:32:07 12 | * @Entity com.schuanhe.plooks.domain.Carousels 13 | */ 14 | public interface CarouselsMapper extends BaseMapper { 15 | 16 | } 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/CollectionsMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.domain.Collections; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author ASUS 10 | * @description 针对表【collections(收藏夹表)】的数据库操作Mapper 11 | * @createDate 2023-09-06 18:27:04 12 | * @Entity com.schuanhe.plooks.domain.Collections 13 | */ 14 | public interface CollectionsMapper extends BaseMapper { 15 | 16 | List selectByName(String name, Integer uid); 17 | 18 | /** 19 | * 根据主键删除 20 | * 只是设置了deletedAt字段 21 | * @param id 主键 22 | */ 23 | void deleteById(Integer id); 24 | 25 | } 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/CommentsMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.schuanhe.plooks.domain.Comments; 5 | 6 | import java.util.List; 7 | 8 | public interface CommentsMapper extends BaseMapper { 9 | List getComment(Integer vid); 10 | 11 | void insertComment(Comments.Comment comment); 12 | 13 | void insertReply(Comments.Reply reply); 14 | 15 | void updateNoMore(Integer id); 16 | 17 | List getReply(Integer fid); 18 | 19 | void deleteCommentById(Integer id); 20 | 21 | void deleteReplyByFid(Integer id); 22 | 23 | void deleteReplyById(Integer id); 24 | } 25 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/DanmukusMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.domain.Danmukus; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * @author ASUS 8 | * @description 针对表【danmukus(弹幕表)】的数据库操作Mapper 9 | * @createDate 2023-09-05 13:01:36 10 | * @Entity com.schuanhe.plooks.domain.Danmukus 11 | */ 12 | public interface DanmukusMapper extends BaseMapper { 13 | 14 | } 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/FollowsMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.domain.Follows; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author ASUS 10 | * @description 针对表【follows(关注表)】的数据库操作Mapper 11 | * @createDate 2023-09-01 19:47:34 12 | * @Entity com.schuanhe.plooks.domain.Follows 13 | */ 14 | public interface FollowsMapper extends BaseMapper { 15 | 16 | } 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/HistoriesMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.domain.Histories; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author ASUS 10 | * @description 针对表【histories(历史记录表)】的数据库操作Mapper 11 | * @createDate 2023-08-29 19:37:08 12 | * @Entity com.schuanhe.plooks.domain.Histories 13 | */ 14 | public interface HistoriesMapper extends BaseMapper { 15 | 16 | List getHistoryList(int i, int size, Integer uid); 17 | } 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/MessageMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.schuanhe.plooks.domain.Message; 5 | 6 | import java.util.List; 7 | 8 | public interface MessageMapper extends BaseMapper { 9 | 10 | List getAnnouncement(); 11 | 12 | List getImportantAnnouncement(); 13 | 14 | List getReplyMessage(Integer uid); 15 | 16 | void insertReply(Message.ReplyMessages replyMessages); 17 | 18 | List getAtMessage(Integer uid); 19 | 20 | List getLikeMessage(Integer uid); 21 | 22 | void insertAt(Message.AtMessages atMessages); 23 | 24 | void insertLike(Message.LikeMessages likeMessages); 25 | 26 | void addAnnounce(Message.Announces announces); 27 | 28 | void deleteAnnounce(Integer id); 29 | 30 | Integer getAnnouncementContent(); 31 | } 32 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/PartitionsMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.domain.Partition; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * @author ASUS 8 | * @description 针对表【partitions(分区表)】的数据库操作Mapper 9 | * @createDate 2023-08-16 16:21:11 10 | * @Entity com.schuanhe.plooks.domain.Partition 11 | */ 12 | public interface PartitionsMapper extends BaseMapper { 13 | 14 | 15 | } 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/ResourcesMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.domain.Resources; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | /** 7 | * @author ASUS 8 | * @description 针对表【resources(资源表)】的数据库操作Mapper 9 | * @createDate 2023-08-17 18:32:11 10 | * @Entity com.schuanhe.plooks.domain.Resources 11 | */ 12 | public interface ResourcesMapper extends BaseMapper { 13 | 14 | } 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.domain.User; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | 8 | /** 9 | * @author ASUS 10 | * @description 针对表【user(用户表)】的数据库操作Mapper 11 | * @createDate 2023-07-27 08:14:23 12 | * @Entity com.schuanhe.plooks.domain.User 13 | */ 14 | @Mapper 15 | public interface UserMapper extends BaseMapper { 16 | 17 | // 根据用户名查询用户 18 | User selectByUsername(String username); 19 | 20 | // 根据邮箱查询用户 21 | User selectByEmail(String email); 22 | 23 | // 通过邮箱修改密码 24 | Integer updatePasswordByEmail(User user); 25 | 26 | // 通过id修改用户背景图 27 | Integer updateCoverById(Integer id, String cover); 28 | 29 | Integer updateUserInfoById(User user); 30 | } 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /server/src/main/java/com/schuanhe/plooks/mapper/VideoMapper.java: -------------------------------------------------------------------------------- 1 | package com.schuanhe.plooks.mapper; 2 | 3 | import com.schuanhe.plooks.domain.Video; 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author ASUS 10 | * @description 针对表【videos(视频表)】的数据库操作Mapper 11 | * @createDate 2023-08-16 17:25:40 12 | * @Entity com.schuanhe.plooks.domain.Video 13 | */ 14 | public interface VideoMapper extends BaseMapper