├── .github └── workflows │ └── docs.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── LICENSE ├── README.md ├── commitlint.config.js ├── data ├── config │ └── application.prod.yaml ├── mysql │ └── init │ │ └── init.sql └── upload │ ├── image │ └── .gitkeep │ └── video │ └── .gitkeep ├── docker-compose.yml ├── docs ├── .gitignore ├── .vitepress │ ├── config.mts │ └── theme │ │ ├── index.ts │ │ └── style.css ├── package.json ├── src │ ├── api │ │ ├── api.md │ │ ├── archive_article.md │ │ ├── archive_video.md │ │ ├── article.md │ │ ├── auth.md │ │ ├── carousel.md │ │ ├── collection.md │ │ ├── comment_article.md │ │ ├── comment_video.md │ │ ├── config.md │ │ ├── danmaku.md │ │ ├── history.md │ │ ├── menu.md │ │ ├── message.md │ │ ├── online.md │ │ ├── partition.md │ │ ├── relation.md │ │ ├── resource.md │ │ ├── review_article.md │ │ ├── review_video.md │ │ ├── role.md │ │ ├── upload.md │ │ ├── user.md │ │ ├── verify.md │ │ └── video.md │ ├── guide │ │ ├── deploy │ │ │ ├── docker.md │ │ │ ├── domain.md │ │ │ └── manual.md │ │ ├── guide │ │ │ ├── captcha.md │ │ │ ├── code.md │ │ │ ├── login.md │ │ │ ├── player.md │ │ │ └── transcoding.md │ │ ├── introduce │ │ │ └── index.md │ │ ├── other │ │ │ ├── contribution.md │ │ │ ├── qa.md │ │ │ ├── screenshot.md │ │ │ └── update.md │ │ └── start │ │ │ ├── config.md │ │ │ ├── env.md │ │ │ └── start.md │ ├── index.md │ ├── other │ │ └── donate.md │ └── public │ │ ├── CNAME │ │ ├── alipay.png │ │ ├── css │ │ └── fancybox.css │ │ ├── favicon.ico │ │ ├── js │ │ └── fancybox.umd.js │ │ ├── logo.png │ │ ├── manage_login.png │ │ ├── manage_user.png │ │ ├── manage_video.png │ │ ├── mobile_home.png │ │ ├── mobile_video.png │ │ ├── token.png │ │ ├── transcoding.png │ │ ├── web_home.png │ │ ├── web_login.png │ │ ├── web_message.png │ │ ├── web_space.png │ │ ├── web_upload.png │ │ ├── web_video.png │ │ └── wechat.png └── yarn.lock ├── package-lock.json ├── package.json ├── server ├── .gitignore ├── Dockerfile ├── cmd │ └── main.go ├── conf │ └── .gitkeep ├── go.mod ├── go.sum ├── internal │ ├── api │ │ └── v1 │ │ │ ├── api.go │ │ │ ├── archive.go │ │ │ ├── article.go │ │ │ ├── auth.go │ │ │ ├── captcha.go │ │ │ ├── carousel.go │ │ │ ├── collect_article.go │ │ │ ├── collect_video.go │ │ │ ├── collection.go │ │ │ ├── comment_article.go │ │ │ ├── comment_video.go │ │ │ ├── config.go │ │ │ ├── danmaku.go │ │ │ ├── email.go │ │ │ ├── file.go │ │ │ ├── history.go │ │ │ ├── like_article.go │ │ │ ├── like_video.go │ │ │ ├── menu.go │ │ │ ├── msg_announce.go │ │ │ ├── msg_at.go │ │ │ ├── msg_like.go │ │ │ ├── msg_reply.go │ │ │ ├── msg_whisper.go │ │ │ ├── online.go │ │ │ ├── partition.go │ │ │ ├── relation.go │ │ │ ├── resource.go │ │ │ ├── review.go │ │ │ ├── role.go │ │ │ ├── upload.go │ │ │ ├── user.go │ │ │ └── video.go │ ├── cache │ │ ├── article.go │ │ ├── captcha.go │ │ ├── clicks_article.go │ │ ├── clicks_video.go │ │ ├── constant.go │ │ ├── email.go │ │ ├── jwt.go │ │ ├── login.go │ │ ├── partition.go │ │ ├── resetpwd.go │ │ ├── slice.go │ │ ├── slider.go │ │ ├── upload.go │ │ ├── user.go │ │ └── video.go │ ├── config │ │ ├── config.go │ │ ├── cors.go │ │ ├── file.go │ │ ├── log.go │ │ ├── mail.go │ │ ├── mysql.go │ │ ├── redis.go │ │ ├── security.go │ │ ├── storage.go │ │ ├── transcoding.go │ │ └── user.go │ ├── cron │ │ ├── click.go │ │ ├── cron.go │ │ └── popular.go │ ├── domain │ │ ├── dto │ │ │ ├── api.go │ │ │ ├── article.go │ │ │ ├── captcha.go │ │ │ ├── carousel.go │ │ │ ├── collect.go │ │ │ ├── collection.go │ │ │ ├── comment.go │ │ │ ├── common.go │ │ │ ├── config.go │ │ │ ├── danmaku.go │ │ │ ├── email.go │ │ │ ├── history.go │ │ │ ├── like.go │ │ │ ├── menu.go │ │ │ ├── msg_announce.go │ │ │ ├── msg_whisper.go │ │ │ ├── partition.go │ │ │ ├── resource.go │ │ │ ├── review.go │ │ │ ├── role.go │ │ │ ├── transcoding.go │ │ │ ├── user.go │ │ │ └── video.go │ │ ├── model │ │ │ ├── api.go │ │ │ ├── article.go │ │ │ ├── carousel.go │ │ │ ├── casbin.go │ │ │ ├── collect_article.go │ │ │ ├── collect_video.go │ │ │ ├── collection.go │ │ │ ├── comment.go │ │ │ ├── danmaku.go │ │ │ ├── history.go │ │ │ ├── like_article.go │ │ │ ├── like_video.go │ │ │ ├── menu.go │ │ │ ├── msg_announce.go │ │ │ ├── msg_at.go │ │ │ ├── msg_like.go │ │ │ ├── msg_reply.go │ │ │ ├── msg_whisper.go │ │ │ ├── operate.go │ │ │ ├── partition.go │ │ │ ├── relation.go │ │ │ ├── resource.go │ │ │ ├── review.go │ │ │ ├── role.go │ │ │ ├── user.go │ │ │ ├── video.go │ │ │ ├── video_file.go │ │ │ └── video_index_file.go │ │ └── vo │ │ │ ├── api.go │ │ │ ├── archive.go │ │ │ ├── article.go │ │ │ ├── captcha.go │ │ │ ├── carousel.go │ │ │ ├── collection.go │ │ │ ├── comment.go │ │ │ ├── config.go │ │ │ ├── danmaku.go │ │ │ ├── history.go │ │ │ ├── menu.go │ │ │ ├── msg_announce.go │ │ │ ├── msg_at.go │ │ │ ├── msg_like.go │ │ │ ├── msg_reply.go │ │ │ ├── msg_whisper.go │ │ │ ├── online.go │ │ │ ├── partition.go │ │ │ ├── relation.go │ │ │ ├── resource.go │ │ │ ├── review.go │ │ │ ├── role.go │ │ │ ├── user.go │ │ │ └── video.go │ ├── global │ │ ├── constant.go │ │ ├── global.go │ │ └── transcoding.go │ ├── initialize │ │ ├── cache.go │ │ ├── config.go │ │ ├── data.go │ │ ├── snowflake.go │ │ └── tables.go │ ├── middleware │ │ ├── auth.go │ │ ├── cors.go │ │ ├── logger.go │ │ └── operation.go │ ├── resp │ │ └── response.go │ ├── routes │ │ ├── api_router.go │ │ ├── archive_router.go │ │ ├── article_router.go │ │ ├── auth_router.go │ │ ├── carousel_router.go │ │ ├── collection_router.go │ │ ├── comment_router.go │ │ ├── config_router.go │ │ ├── danmaku_router.go │ │ ├── history_router.go │ │ ├── menu_router.go │ │ ├── message_router.go │ │ ├── online_router.go │ │ ├── partition_router.go │ │ ├── relation_router.go │ │ ├── resource_router.go │ │ ├── review_router.go │ │ ├── role_router.go │ │ ├── router.go │ │ ├── upload_router.go │ │ ├── user_router.go │ │ ├── verify_router.go │ │ └── video_router.go │ └── service │ │ ├── api.go │ │ ├── archive.go │ │ ├── article.go │ │ ├── carousel.go │ │ ├── clicks.go │ │ ├── collect_article.go │ │ ├── collect_video.go │ │ ├── collection.go │ │ ├── comment.go │ │ ├── comment_article.go │ │ ├── comment_video.go │ │ ├── config.go │ │ ├── danmaku.go │ │ ├── history.go │ │ ├── like_article.go │ │ ├── like_video.go │ │ ├── menu.go │ │ ├── msg_announce.go │ │ ├── msg_at.go │ │ ├── msg_like.go │ │ ├── msg_reply.go │ │ ├── msg_whisper.go │ │ ├── online.go │ │ ├── operate.go │ │ ├── partition.go │ │ ├── relation.go │ │ ├── resource.go │ │ ├── review.go │ │ ├── role.go │ │ ├── transcoding.go │ │ ├── upload.go │ │ ├── user.go │ │ └── video.go ├── logs │ └── .gitkeep ├── pkg │ ├── casbin │ │ └── casbin.go │ ├── jigsaw │ │ └── jigsaw.go │ ├── jwt │ │ └── jwt.go │ ├── logger │ │ └── logger.go │ ├── mail │ │ └── mail.go │ ├── mysql │ │ └── mysql.go │ ├── oss │ │ ├── aliyun.go │ │ ├── cloudflare.go │ │ ├── config.go │ │ ├── index.go │ │ ├── minio.go │ │ └── tencent.go │ ├── redis │ │ └── redis.go │ └── ws │ │ └── ws.go ├── static │ ├── casbin │ │ └── model.conf │ └── jigsaw │ │ ├── bg │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ │ └── mask.png ├── upload │ ├── image │ │ └── .gitkeep │ └── video │ │ └── .gitkeep └── utils │ ├── cmd.go │ ├── convert.go │ ├── file.go │ ├── log.go │ ├── math.go │ ├── md5.go │ ├── random.go │ ├── slice.go │ ├── strings.go │ └── verify.go ├── sql └── structure.sql ├── version └── web ├── admin-client ├── .eslintrc.cjs ├── .gitignore ├── Dockerfile ├── index.html ├── nginx.conf ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── logo.png ├── src │ ├── App.vue │ ├── api │ │ ├── announce.ts │ │ ├── api.ts │ │ ├── article.ts │ │ ├── auth.ts │ │ ├── captcha.ts │ │ ├── carousel.ts │ │ ├── code.ts │ │ ├── config.ts │ │ ├── menu.ts │ │ ├── partition.ts │ │ ├── review.ts │ │ ├── role.ts │ │ ├── upload.ts │ │ ├── user.ts │ │ └── video.ts │ ├── assets │ │ ├── images │ │ │ └── login-illustration.png │ │ └── styles │ │ │ └── nprogress.scss │ ├── components │ │ ├── common-avatar │ │ │ └── index.vue │ │ ├── icon-selector │ │ │ └── index.vue │ │ ├── multistage-router-layout │ │ │ └── index.vue │ │ ├── slider-captcha │ │ │ └── index.vue │ │ └── video-player │ │ │ └── index.vue │ ├── hooks │ │ ├── loading-hooks.ts │ │ ├── partition-hooks.ts │ │ ├── resize-hooks.ts │ │ ├── send-code-hooks.ts │ │ └── theme-hooks.ts │ ├── layout │ │ ├── components │ │ │ ├── header-breadcrumb │ │ │ │ └── index.vue │ │ │ ├── header-controller │ │ │ │ ├── fullscreen-btn.vue │ │ │ │ ├── index.vue │ │ │ │ └── theme-switch.vue │ │ │ ├── header-user │ │ │ │ └── index.vue │ │ │ └── view-tags │ │ │ │ └── index.vue │ │ ├── content │ │ │ ├── header.vue │ │ │ └── sider.vue │ │ ├── global.vue │ │ └── index.vue │ ├── main.ts │ ├── router │ │ ├── guards.ts │ │ └── index.ts │ ├── stores │ │ ├── index.ts │ │ └── modules │ │ │ ├── history-store.ts │ │ │ ├── menu-store.ts │ │ │ ├── system-store.ts │ │ │ ├── theme-store.ts │ │ │ └── user-store.ts │ ├── theme │ │ ├── dark.ts │ │ └── default.ts │ ├── typings │ │ ├── announce.d.ts │ │ ├── api.d.ts │ │ ├── article.d.ts │ │ ├── auth.d.ts │ │ ├── carousel.d.ts │ │ ├── code.d.ts │ │ ├── config.d.ts │ │ ├── env.d.ts │ │ ├── global.d.ts │ │ ├── menu.d.ts │ │ ├── partition.d.ts │ │ ├── player.d.ts │ │ ├── resource.d.ts │ │ ├── review.d.ts │ │ ├── role.d.ts │ │ ├── route.d.ts │ │ ├── theme.d.ts │ │ ├── upload.d.ts │ │ ├── user.d.ts │ │ └── video.d.ts │ ├── utils │ │ ├── async-router.ts │ │ ├── color.ts │ │ ├── constant.ts │ │ ├── format.ts │ │ ├── global-config.ts │ │ ├── render.ts │ │ ├── request.ts │ │ ├── resource.ts │ │ ├── review-code.ts │ │ ├── status-code.ts │ │ └── storage-data.ts │ └── views │ │ ├── content │ │ ├── announce │ │ │ ├── components │ │ │ │ └── table-action-modal.vue │ │ │ └── index.vue │ │ ├── article │ │ │ ├── components │ │ │ │ ├── table-action-drawer.vue │ │ │ │ └── text-editor.vue │ │ │ └── index.vue │ │ ├── carousel │ │ │ ├── components │ │ │ │ ├── carousel-uploader.vue │ │ │ │ └── table-action-drawer.vue │ │ │ └── index.vue │ │ ├── partition │ │ │ ├── components │ │ │ │ └── table-action-modal.vue │ │ │ └── index.vue │ │ └── video │ │ │ ├── components │ │ │ ├── table-action-drawer.vue │ │ │ └── video-modal.vue │ │ │ └── index.vue │ │ ├── errors │ │ ├── 403.vue │ │ └── 404.vue │ │ ├── login │ │ ├── component │ │ │ └── LoginForm.vue │ │ └── index.vue │ │ ├── review │ │ ├── article │ │ │ ├── components │ │ │ │ ├── review-modal.vue │ │ │ │ ├── table-action-drawer.vue │ │ │ │ └── text-editor.vue │ │ │ └── index.vue │ │ └── video │ │ │ ├── components │ │ │ ├── review-modal.vue │ │ │ ├── table-action-drawer.vue │ │ │ └── video-modal.vue │ │ │ └── index.vue │ │ └── system │ │ ├── api │ │ ├── components │ │ │ └── table-action-modal.vue │ │ └── index.vue │ │ ├── config │ │ ├── components │ │ │ ├── EmailConfig.vue │ │ │ ├── OtherConfig.vue │ │ │ └── StorageConfig.vue │ │ └── index.vue │ │ ├── menu │ │ ├── components │ │ │ └── table-action-modal.vue │ │ └── index.vue │ │ ├── role │ │ ├── components │ │ │ ├── drawer-api.vue │ │ │ ├── drawer-menu.vue │ │ │ └── table-action-modal.vue │ │ └── index.vue │ │ └── user │ │ ├── components │ │ └── table-action-modal.vue │ │ └── index.vue ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock ├── mobile-client ├── .eslintrc.cjs ├── .gitignore ├── Dockerfile ├── README.md ├── env.d.ts ├── index.html ├── nginx.conf ├── package-lock.json ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.vue │ ├── api │ │ ├── archive.ts │ │ ├── auth.ts │ │ ├── captcha.ts │ │ ├── carousel.ts │ │ ├── code.ts │ │ ├── collect.ts │ │ ├── collection.ts │ │ ├── comment.ts │ │ ├── danmaku.ts │ │ ├── history.ts │ │ ├── like.ts │ │ ├── msg-announce.ts │ │ ├── msg-whisper.ts │ │ ├── partition.ts │ │ ├── relation.ts │ │ ├── user.ts │ │ └── video.ts │ ├── assets │ │ ├── images │ │ │ └── common │ │ │ │ ├── announce.png │ │ │ │ └── filing.png │ │ └── styles │ │ │ └── global.scss │ ├── components │ │ ├── base-slider │ │ │ └── index.vue │ │ ├── common-avatar │ │ │ └── index.vue │ │ ├── footer-bar │ │ │ └── index.vue │ │ ├── header-bar │ │ │ └── index.vue │ │ ├── icons │ │ │ ├── ArrowDownIcon.vue │ │ │ ├── ArrowLeftIcon.vue │ │ │ ├── ArrowRightIcon.vue │ │ │ ├── ArrowUpIcon.vue │ │ │ ├── CollectIcon.vue │ │ │ ├── LikeIcon.vue │ │ │ ├── MessageIcon.vue │ │ │ ├── SearchIcon.vue │ │ │ └── TextIcon.vue │ │ ├── slider-captcha │ │ │ └── index.vue │ │ ├── video-list │ │ │ └── index.vue │ │ └── video-player │ │ │ ├── components │ │ │ └── DanmakuSend.vue │ │ │ └── index.vue │ ├── hooks │ │ └── theme-hooks.ts │ ├── main.ts │ ├── router │ │ └── index.ts │ ├── stores │ │ └── index.ts │ ├── theme │ │ ├── dark.ts │ │ └── default.ts │ ├── typings │ │ ├── auth.d.ts │ │ ├── carousel.d.ts │ │ ├── code.d.ts │ │ ├── collect.d.ts │ │ ├── collection.d.ts │ │ ├── comment.d.ts │ │ ├── danmaku.d.ts │ │ ├── global.d.ts │ │ ├── history.d.ts │ │ ├── msg-announce.d.ts │ │ ├── msg-whisper.d.ts │ │ ├── partition.d.ts │ │ ├── player.d.ts │ │ ├── resource.d.ts │ │ ├── theme.d.ts │ │ ├── user.d.ts │ │ └── video.d.ts │ ├── utils │ │ ├── format.ts │ │ ├── global-config.ts │ │ ├── mention.ts │ │ ├── request.ts │ │ ├── resource.ts │ │ ├── review-code.ts │ │ ├── scroll.ts │ │ ├── status-code.ts │ │ └── storage-data.ts │ └── views │ │ ├── home │ │ ├── components │ │ │ ├── HomeCarousel.vue │ │ │ └── NavBar.vue │ │ └── index.vue │ │ ├── login │ │ └── index.vue │ │ ├── message │ │ ├── announce │ │ │ └── index.vue │ │ ├── index.vue │ │ └── whisper │ │ │ └── index.vue │ │ ├── register │ │ └── index.vue │ │ ├── search │ │ └── index.vue │ │ ├── space │ │ ├── edit │ │ │ └── index.vue │ │ └── info │ │ │ └── index.vue │ │ └── video │ │ ├── components │ │ ├── AddCollection.vue │ │ ├── ArchiveInfo.vue │ │ ├── CollectionList.vue │ │ ├── CommentList.vue │ │ └── PartList.vue │ │ └── index.vue ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock └── web-client ├── .gitignore ├── Dockerfile ├── ecosystem.config.js ├── nuxt.config.ts ├── package-lock.json ├── package.json ├── server └── tsconfig.json ├── src ├── api │ ├── archive.ts │ ├── article.ts │ ├── auth.ts │ ├── captcha.ts │ ├── carousel.ts │ ├── code.ts │ ├── collect.ts │ ├── collection.ts │ ├── comment.ts │ ├── danmaku.ts │ ├── history.ts │ ├── like.ts │ ├── msg-announce.ts │ ├── msg-at.ts │ ├── msg-like.ts │ ├── msg-reply.ts │ ├── msg-whisper.ts │ ├── partition.ts │ ├── relation.ts │ ├── resource.ts │ ├── revies.ts │ ├── upload.ts │ ├── user.ts │ └── video.ts ├── app.vue ├── assets │ ├── fonts │ │ └── HarmonyOS_Sans_SC_Regular.ttf │ ├── images │ │ ├── common │ │ │ └── filing.png │ │ └── login │ │ │ └── login-bg.png │ └── styles │ │ ├── article-html.scss │ │ ├── element.scss │ │ └── global.scss ├── components │ ├── alnitak-carousel │ │ └── index.vue │ ├── alnitak-editor │ │ └── index.vue │ ├── alnitak-steps │ │ └── index.vue │ ├── base-slider │ │ └── index.vue │ ├── base-tabs │ │ └── index.vue │ ├── common-avatar │ │ └── index.vue │ ├── cover-uploader │ │ └── index.vue │ ├── follow-list │ │ └── index.vue │ ├── form-skeleton │ │ └── index.vue │ ├── header-bar │ │ └── index.vue │ ├── home-header │ │ └── index.vue │ ├── home-sidebar │ │ └── index.vue │ ├── home-video-item │ │ └── index.vue │ ├── icons │ │ ├── ArrowLeftIcon.vue │ │ ├── ArrowRightIcon.vue │ │ ├── ArticleIcon.vue │ │ ├── CloseIcon.vue │ │ ├── CollectIcon.vue │ │ ├── CommentFillIcon.vue │ │ ├── CommentIcon.vue │ │ ├── HomeIcon.vue │ │ ├── LikeIcon.vue │ │ ├── MonitorIcon.vue │ │ ├── PlayCountIcon.vue │ │ ├── TextIcon.vue │ │ ├── UpIcon.vue │ │ ├── UploadIcon.vue │ │ └── VideoIcon.vue │ ├── image-cropper │ │ ├── components │ │ │ ├── AvatarCropper.vue │ │ │ ├── CoverCropper.vue │ │ │ ├── PictureCropper.vue │ │ │ └── SpaceCoverCropper.vue │ │ └── index.vue │ ├── login-card │ │ ├── components │ │ │ ├── LoginForm.vue │ │ │ ├── LoginIllustration.vue │ │ │ └── RegisterForm.vue │ │ └── index.vue │ ├── login-dialog │ │ └── index.vue │ ├── slider-captcha │ │ └── index.vue │ └── video-player │ │ ├── components │ │ └── DanmakuSend.vue │ │ └── index.vue ├── composables │ └── video-count-store.ts ├── error.vue ├── middleware │ ├── article.ts │ └── auth.ts ├── pages │ ├── 404.vue │ ├── article │ │ ├── [id].vue │ │ ├── components │ │ │ ├── ArchiveInfo.vue │ │ │ ├── AuthorCard.vue │ │ │ ├── CommentList.vue │ │ │ └── TextEditor.vue │ │ └── list.vue │ ├── collection │ │ └── [id].vue │ ├── index.vue │ ├── login.vue │ ├── message.vue │ ├── message │ │ ├── announce.vue │ │ ├── at.vue │ │ ├── like.vue │ │ ├── reply.vue │ │ └── whisper.vue │ ├── partition │ │ └── [partition].vue │ ├── search │ │ ├── [keywords].vue │ │ └── components │ │ │ └── VideoItem.vue │ ├── setpassword.vue │ ├── space.vue │ ├── space │ │ ├── collection.vue │ │ ├── components │ │ │ ├── CollectionModal.vue │ │ │ └── HistoryVideoList.vue │ │ ├── follower.vue │ │ ├── following.vue │ │ ├── history.vue │ │ ├── manuscript.vue │ │ ├── setting.vue │ │ └── setting │ │ │ ├── info.vue │ │ │ └── security.vue │ ├── upload.vue │ ├── upload │ │ ├── article-manage.vue │ │ ├── article.vue │ │ ├── comment-manage.vue │ │ ├── components │ │ │ ├── CoverUploader.vue │ │ │ ├── PartitionSelector.vue │ │ │ ├── UploadVideoFile.vue │ │ │ ├── UploadVideoInfo.vue │ │ │ └── VideoUploader.vue │ │ ├── video-manage.vue │ │ └── video.vue │ ├── user │ │ ├── [id].vue │ │ ├── [id] │ │ │ ├── follower.vue │ │ │ ├── following.vue │ │ │ └── manuscript.vue │ │ └── index.vue │ └── video │ │ ├── [id].vue │ │ └── components │ │ ├── ArchiveInfo.vue │ │ ├── AuthorCard.vue │ │ ├── CollectionList.vue │ │ ├── CommentList.vue │ │ ├── DanmakuList.vue │ │ ├── PartList.vue │ │ └── RecommendList.vue ├── plugins │ └── wang-editor.ts ├── public │ └── favicon.ico ├── typings │ ├── article.d.ts │ ├── auth.d.ts │ ├── carousel.d.ts │ ├── code.d.ts │ ├── collect.d.ts │ ├── collection.d.ts │ ├── comment.d.ts │ ├── danmaku.d.ts │ ├── global.d.ts │ ├── history.d.ts │ ├── msg-announce.d.ts │ ├── msg-at.d.ts │ ├── msg-like.d.ts │ ├── msg-reply.d.ts │ ├── msg-whisper.d.ts │ ├── partition.d.ts │ ├── player.d.ts │ ├── relation.d.ts │ ├── resource.d.ts │ ├── upload.d.ts │ ├── user.d.ts │ └── video.d.ts └── utils │ ├── format.ts │ ├── global-config.ts │ ├── md5.ts │ ├── mention.ts │ ├── relation-code.ts │ ├── request.ts │ ├── resource.ts │ ├── review-code.ts │ ├── scroll.ts │ ├── status-code.ts │ ├── storage-data.ts │ ├── uuid.ts │ └── verify.ts ├── tsconfig.json └── yarn.lock /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | push: 5 | branches: [doc] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | docs: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [16.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | # 生成静态文件 22 | - name: Build 23 | run: cd docs && npm install && npm run docs:build 24 | 25 | - name: Deploy 26 | uses: crazy-max/ghaction-github-pages@v3 27 | with: 28 | target_branch: gh-pages 29 | build_dir: docs/.vitepress/dist 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | .idea 4 | .DS_Store 5 | *.suo 6 | *.ntvs* 7 | *.njsproj 8 | *.sln 9 | *.sw? 10 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "@commitlint/config-conventional" 4 | ], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | 'feat', //新功能(feature) 8 | 'fix', //修补bug 9 | 'docs', //文档(documentation) 10 | 'style', //格式(不影响代码运行的变动) 11 | 'refactor', //重构(即不是新增功能,也不是修改bug的代码变动) 12 | 'perf', //性能提升(提高性能的代码改动) 13 | 'test', //测试 14 | 'chore', // 不修改src或测试文件的其他更改 15 | 'revert', //撤退之前的commit 16 | 'build' //构建过程或辅助工具的变动(webpack等) 17 | ]], 18 | 'type-case': [0], 19 | 'type-empty': [0], 20 | 'scope-empty': [0], 21 | 'scope-case': [0], 22 | 'subject-full-stop': [0, 'never'], 23 | 'subject-case': [0, 'never'], 24 | 'header-max-length': [0, 'always', 72] 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /data/mysql/init/init.sql: -------------------------------------------------------------------------------- 1 | -- 授权 root 用户可以远程链接 2 | ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'AlnitakRoot@123'; 3 | flush privileges; -------------------------------------------------------------------------------- /data/upload/image/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/data/upload/image/.gitkeep -------------------------------------------------------------------------------- /data/upload/video/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/data/upload/video/.gitkeep -------------------------------------------------------------------------------- /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/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // https://vitepress.dev/guide/custom-theme 2 | import { h } from 'vue' 3 | import type { Theme } from 'vitepress' 4 | import DefaultTheme from 'vitepress/theme' 5 | import './style.css' 6 | 7 | export default { 8 | extends: DefaultTheme, 9 | Layout: () => { 10 | return h(DefaultTheme.Layout, null, { 11 | // https://vitepress.dev/guide/extending-default-theme#layout-slots 12 | }) 13 | }, 14 | enhanceApp({ app, router, siteData }) { 15 | // ... 16 | } 17 | } satisfies Theme 18 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alnitak-doc", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "docs:dev": "vitepress dev", 8 | "docs:build": "vitepress build", 9 | "docs:preview": "vitepress preview" 10 | }, 11 | "keywords": [], 12 | "author": "wzm", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "vitepress": "^1.2.2" 16 | }, 17 | "dependencies": { 18 | "markdown-it-custom-attrs": "^1.0.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/src/api/online.md: -------------------------------------------------------------------------------- 1 | # 在线人数相关接口 2 | 3 | ## 视频在线人数连接 4 | 5 | #### 请求URL 6 | - `/api/v1/online/video?vid=视频ID&clientId=随机生成的客户端ID ` 7 | 8 | #### 返回示例 9 | ```json 10 | { 11 | "number": 1 12 | } 13 | ``` 14 | 15 | #### 备注 16 | 请求时使用ws或者wss协议 -------------------------------------------------------------------------------- /docs/src/api/resource.md: -------------------------------------------------------------------------------- 1 | # 视频资源接口 2 | 3 | ## 修改资源标题 4 | 5 | #### 请求URL 6 | - `/api/v1/resource/modifyTitle ` 7 | 8 | #### 请求方式 9 | - PUT 10 | 11 | #### 请求头 12 | - `Authorization': token` 13 | - `"content-type": "application/json",` 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 | "msg":"ok" 29 | } 30 | ``` 31 | 32 | #### 备注 33 | 无 34 | 35 | 36 | 37 | ## 删除资源 38 | 39 | #### 请求URL 40 | - `/api/v1/resource/deleteResource/资源ID ` 41 | 42 | #### 请求方式 43 | - DELETE 44 | 45 | #### 请求头 46 | - `Authorization': token` 47 | 48 | #### 返回示例 49 | 50 | ``` json 51 | { 52 | "code": 200, 53 | "data": null, 54 | "msg":"ok" 55 | } 56 | ``` 57 | 58 | #### 备注 59 | 无 -------------------------------------------------------------------------------- /docs/src/guide/guide/login.md: -------------------------------------------------------------------------------- 1 | # 登录流程 2 | 3 | ## 整体流程 4 |  5 | 6 | ## 刷新token 7 | 8 | 调用[刷新TOKEN接口](/api/auth#刷新TOKEN)时会返回`RefreshToken`、`AccessToken`,同时会在Cookie中添加`user_id`和`user_id_md5`。 9 | 其中Cookie中的内容暂时没有在后端使用,如需使用请自行修改相关代码。 10 | -------------------------------------------------------------------------------- /docs/src/guide/guide/transcoding.md: -------------------------------------------------------------------------------- 1 | # 视频处理流程 2 | 3 | ## 整体流程 4 |  5 | 6 | ## 视频输出参数 7 | | 清晰度 | 视频分辨率 | 视频帧率 | 视频平均码率 | 备注 | 8 | | ------- | ---------- | -------- | ------------ | -------------------------------------- | 9 | | 1080p60 | 1920x1080 | 60 | 6000kbps | 需在配置文件中开启(v1.2.0及之后版本) | 10 | | 1080p | 1920x1080 | 30 | 3000kbps | | 11 | | 720p | 1080x720 | 30 | 2000kbps | | 12 | | 480p | 854x480 | 30 | 800kbps | | 13 | | 360p | 640x360 | 30 | 500kbps | | 14 | 15 | 视频处理相关代码位于server/internal/service/transcoding.go。如果上述输出参数不符合您的要求,您可以自行修改相关代码。 -------------------------------------------------------------------------------- /docs/src/guide/introduce/index.md: -------------------------------------------------------------------------------- 1 | # 项目介绍 2 | 3 | [Alnitak](https://github.com/wangzmgit/alnitak)是一个基于nuxt和gin开发的前后端分离的弹幕视频网站。 4 | 项目实现了视频、专栏、弹幕、评论、点赞、收藏等功能。 5 | 如有任何的意见或建议,欢迎您通过创建Issue或PR的方式告知我们。 6 | 7 | QQ交流群:909847398 8 | 9 | 10 | ## 特性 11 | * **弹幕播放器**: 基于DPlayer二次开发出弹幕播放器WPlayer并上传npm。 12 | * **视频转码**:实现了视频码率和分辨率的调整,并将视频转码为HLS格式,以提高播放效果 13 | * **实时通信**:基于WebSocket实现了私信的实时收发功能,同时实现了视频在线人数的实时更新 14 | * **鉴权**:基于JWT实现双Token方案,通过对axios二次封装实现Token过期重发请求功能 15 | * **滑块拼图库**:使用Go语言开发了滑块拼图生成库,并将其发布到GitHub上作为项目依赖使用 16 | * **权限管理**:后台管理系统基于JWT和Casbin实现了基于角色的访问控制(RBAC),保障了系统的安全性和灵活性 17 | 18 | ## 如何贡献 19 | 20 | 非常欢迎您参与项目的维护。如有任何意见或建议,请通过创建 Issue 或提交 Pull Request 的方式告知我们。 21 | 在提交之前,请务必阅读[贡献指南](/guide/other/contribution)。 22 | -------------------------------------------------------------------------------- /docs/src/guide/other/contribution.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | 欢迎您考虑为我们的项目做出贡献!您的参与对我们来说非常宝贵。 4 | 在开始之前,请确保您已经阅读并理解了本指南的内容,以便您的贡献能够顺利进行。 5 | 6 | ## Issues 规范 7 | * 搜索现有问题:在提交新问题之前,请先搜索现有的问题列表,以确保您的问题尚未被其他人提出。 8 | 9 | * 清晰明确的描述:提交问题时,请尽量提供清晰、明确的描述,包括具体的现象、复现步骤、预期行为和实际结果,如果可能的话,请提供相关的截图或最小可复现的代码示例。 10 | 11 | * 版本信息:如果您的问题与特定版本相关,请提供相关的版本信息,包括项目版本号、操作系统版本、浏览器版本等。 12 | 13 | ## Pull Request 规范 14 | 15 | * 请先fork一份到自己的项目下,不要直接在仓库下建分支。 16 | * commit信息要以`type:描述信息`的形式填写,例如`fix: fix xx bug`。 17 | * type:必须是feat, fix, docs, style. refactor, perf, test, chore, revert, build其中的一个。 18 | * header:描述信息不要超过72个字符。 19 | * 确保PR是提交到对应的分支,而不是master分支。 20 | * 前端项目提交到`web`分支 21 | * 后端项目提交到`server`分支 22 | * 文档提交到`doc`分支 23 | * 如果是修复bug,请在PR中给出描述信息。 -------------------------------------------------------------------------------- /docs/src/guide/other/qa.md: -------------------------------------------------------------------------------- 1 | # 常见问题解答 2 | 3 | ## 端口修改 4 | 为了方便一键部署,项目没有提供配置端口的功能。如果出于某些原因需要修改端口号, 5 | 您可以前往 `server/internal/routes/router.go `文件中修改`port = "9000"`中的端口号为您想要的值。 6 | **在修改任何与端口相关的内容之前,请确保您已经熟悉了部署流程,并清楚了解端口变更可能会对哪些内容造成影响。** 7 | 8 | ## 修改默认管理员密码 9 | 使用默认账号(账号:admin@admin.com 密码: 123456)登录后台管理, 10 | 修改邮箱为自己的邮箱,进入到前端登录页面,点击找回密码修改密码 11 | 12 | ## 轮播图不能正常显示 13 | 系统的轮播图是按照分区进行上传的。若某一分区的轮播图数量少于 3 张,则可能无法正常展示。 14 | 15 | ## 首页视频空白 16 | 首页的视频列表数据并非实时数据,系统每隔 3 小时会统计近期热门视频并展示在首页。若需立即将视频展示在首页,可以重启后端项目,每次重启时也会重新计算首页数据。 17 | 18 | ## 视频显示审核中但后台管理中不存在该视频 19 | 用户上传视频后,需要对视频进行转码等操作,视频转码需要一定的时间。在视频转码完成前,后台管理系统中不会显示该视频。 -------------------------------------------------------------------------------- /docs/src/guide/other/screenshot.md: -------------------------------------------------------------------------------- 1 | # 相关截图 2 | 3 | | Web端 | Web端 | 4 | | :------------------------------: | :----------------------------: | 5 | |  |  | 6 | |  |  | 7 | |  |  | 8 | 9 | | 后台管理端 | 后台管理端 | 10 | | :-------------------------------------: | :--------------------------------------: | 11 | |  |  | 12 | |  | | 13 | 14 | | 移动端 | 移动端 | 15 | | :-----------------------------: | :------------------------------: | 16 | |  |  | 17 | -------------------------------------------------------------------------------- /docs/src/guide/start/env.md: -------------------------------------------------------------------------------- 1 | # 本地开发环境配置 2 | 3 | :::tip 相关信息 4 | 以下内容将带领您逐步从零开始搭建和启动项目。如果您只想部署该项目,请转至[一键部署](/guide/deploy/docker)。 5 | ::: 6 | 7 | ## 后端环境 8 | 9 | 1. 前往 https://golang.google.cn/dl/ 下载对应系统的Go语言安装包。`推荐版本:1.20 +` 10 | 2. 命令行输入`go version`若控制台输出版本信息则安装成功 11 | 3. 使用命令 `go env -w GOPROXY=https://goproxy.cn,direct` 设置go代理 12 | 13 | 14 | ## 前端环境 15 | 1. 前往 https://nodejs.org/zh-cn/ 下载对应系统的Go语言安装包。`推荐版本:18.16 +` 16 | 2. 命令行输入`node -v`若控制台输出版本信息则安装成功 17 | 18 | ## 其他环境 19 | :::tip 确保你的环境满足以下要求: 20 | * Git: 推荐最新版本 21 | * Mysql: >= 8.0,推荐使用8.0,其他版本未进行测试 22 | * Redis: 推荐最新版本 23 | * Nginx: 推荐最新版本 24 | * FFmpeg: 推荐最新版本 25 | ::: 26 | -------------------------------------------------------------------------------- /docs/src/guide/start/start.md: -------------------------------------------------------------------------------- 1 | # 项目启动 2 | 3 | ## 启动后端项目 4 | ::: tip 相关信息 5 | 在启动之前,需先创建一个数据库,其字符集设置为 utf8mb4。 6 | 系统在启动时会自动进行表的创建,不需要导入完整的表结构。 7 | ::: 8 | 9 | ### 运行项目 10 | 1. 进入到`server`目录 11 | 2. 使命令行输入`go run cmd\main.go -env dev`启动项目 12 | 3. 访问`localhost:9000`,如果页面中出现`404 not found`则说明项目运行成功 13 | 14 | 15 | ## 启动前端项目 16 | ::: tip 相关信息 17 | 前端需要使用yarn,如果尚未安装yarn,可以使用命令 `npm install -g yarn` 来安装yarn。 18 | ::: 19 | 20 | ### 运行用户端 21 | 1. 进入到`web/web-client`目录 22 | 2. 首次运行需要执行`yarn install` 安装项目依赖 23 | 3. 使命令行输入`yarn dev`启动项目 24 | 25 | ### 运行管理端 26 | 1. 进入到`web/admin-client`目录 27 | 2. 首次运行需要执行`yarn install` 安装项目依赖 28 | 3. 使命令行输入`yarn dev`启动项目 -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | title: Alnitak弹幕视频网站 5 | 6 | hero: 7 | name: Alnitak 8 | text: 弹幕视频网站 9 | tagline: 前后端分离 \ 完全开源 \ 部署简单 10 | image: 11 | src: logo.png 12 | alt: Alnitak 13 | actions: 14 | - theme: brand 15 | text: 开始 16 | link: /guide/introduce 17 | - theme: alt 18 | text: 在 GitHub 上查看 19 | link: https://github.com/wangzmgit/alnitak 20 | 21 | features: 22 | - title: 开源 23 | details: 基于MIT协议,源代码完全开源 24 | - title: 文档丰富 25 | details: 提供了部署文档、后端接口文档以及视频教程,方便用户进行修改和二次开发。 26 | - title: 视频处理 27 | details: 实现了视频码率和分辨率的调整,并将视频转码为HLS格式,以提高播放效果 28 | - title: 权限管理 29 | details: 实现了基于角色的访问控制(RBAC),保障了系统的安全性和灵活性 30 | --- -------------------------------------------------------------------------------- /docs/src/other/donate.md: -------------------------------------------------------------------------------- 1 | # 赞助 2 | 3 | 如果您觉得这个项目对您有帮助,可以帮作者买杯饮料鼓励鼓励! 4 | | WeChat | Alipay | 5 | | :--------------------------: | :-------------------------: | 6 | |  |  | -------------------------------------------------------------------------------- /docs/src/public/CNAME: -------------------------------------------------------------------------------- 1 | alnitak.interastral-peace.com -------------------------------------------------------------------------------- /docs/src/public/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/alipay.png -------------------------------------------------------------------------------- /docs/src/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/favicon.ico -------------------------------------------------------------------------------- /docs/src/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/logo.png -------------------------------------------------------------------------------- /docs/src/public/manage_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/manage_login.png -------------------------------------------------------------------------------- /docs/src/public/manage_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/manage_user.png -------------------------------------------------------------------------------- /docs/src/public/manage_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/manage_video.png -------------------------------------------------------------------------------- /docs/src/public/mobile_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/mobile_home.png -------------------------------------------------------------------------------- /docs/src/public/mobile_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/mobile_video.png -------------------------------------------------------------------------------- /docs/src/public/token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/token.png -------------------------------------------------------------------------------- /docs/src/public/transcoding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/transcoding.png -------------------------------------------------------------------------------- /docs/src/public/web_home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/web_home.png -------------------------------------------------------------------------------- /docs/src/public/web_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/web_login.png -------------------------------------------------------------------------------- /docs/src/public/web_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/web_message.png -------------------------------------------------------------------------------- /docs/src/public/web_space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/web_space.png -------------------------------------------------------------------------------- /docs/src/public/web_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/web_upload.png -------------------------------------------------------------------------------- /docs/src/public/web_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/web_video.png -------------------------------------------------------------------------------- /docs/src/public/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/docs/src/public/wechat.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alnitak", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "prepare": "husky install" 6 | }, 7 | "husky": { 8 | "hooks": { 9 | "commit-msg": "commitlint -e HUSKY_GIT_PARAMS" 10 | } 11 | }, 12 | "devDependencies": { 13 | "@commitlint/cli": "^17.1.2", 14 | "@commitlint/config-conventional": "^17.1.0", 15 | "husky": "^8.0.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /conf/*.yaml 2 | /logs/*.log 3 | /upload/video/* 4 | /upload/image/* 5 | !/upload/video/.gitkeep 6 | !/upload/image/.gitkeep -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22.12-alpine 2 | WORKDIR /server 3 | COPY . . 4 | 5 | # #构建后端和安装环境 6 | RUN go env -w GOPROXY=https://goproxy.cn,direct \ 7 | && go mod tidy \ 8 | && go build -o app cmd/main.go 9 | 10 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ 11 | && apk update --no-cache \ 12 | && apk add ffmpeg 13 | 14 | EXPOSE 9000 15 | 16 | CMD ./app 17 | -------------------------------------------------------------------------------- /server/conf/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/conf/.gitkeep -------------------------------------------------------------------------------- /server/internal/api/v1/archive.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/resp" 6 | "interastral-peace.com/alnitak/internal/service" 7 | "interastral-peace.com/alnitak/utils" 8 | ) 9 | 10 | // 通过视频ID获取点赞收藏数据 11 | func GetVideoArchiveStat(ctx *gin.Context) { 12 | vid := utils.StringToUint(ctx.Query("vid")) 13 | 14 | stat, err := service.GetVideoArchiveStat(ctx, vid) 15 | if err != nil { 16 | resp.FailWithMessage(ctx, err.Error()) 17 | return 18 | } 19 | 20 | // 返回给前端 21 | resp.OkWithData(ctx, gin.H{"stat": stat}) 22 | } 23 | 24 | // 通过文章ID获取点赞收藏数据 25 | func GetArticleArchiveStat(ctx *gin.Context) { 26 | aid := utils.StringToUint(ctx.Query("aid")) 27 | 28 | stat, err := service.GetArticleArchiveStat(ctx, aid) 29 | if err != nil { 30 | resp.FailWithMessage(ctx, err.Error()) 31 | return 32 | } 33 | 34 | // 返回给前端 35 | resp.OkWithData(ctx, gin.H{"stat": stat}) 36 | } 37 | -------------------------------------------------------------------------------- /server/internal/api/v1/msg_at.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/resp" 6 | "interastral-peace.com/alnitak/internal/service" 7 | "interastral-peace.com/alnitak/utils" 8 | ) 9 | 10 | func GetAtMessage(ctx *gin.Context) { 11 | page := utils.StringToInt(ctx.Query("page")) 12 | pageSize := utils.StringToInt(ctx.Query("pageSize")) 13 | 14 | if pageSize > 30 { 15 | resp.FailWithMessage(ctx, "请求数量过多") 16 | return 17 | } 18 | 19 | total, messages := service.GetAtMessage(ctx, page, pageSize) 20 | 21 | // 返回给前端 22 | resp.OkWithData(ctx, gin.H{"total": total, "messages": messages}) 23 | } 24 | -------------------------------------------------------------------------------- /server/internal/api/v1/msg_like.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/resp" 6 | "interastral-peace.com/alnitak/internal/service" 7 | "interastral-peace.com/alnitak/utils" 8 | ) 9 | 10 | func GetLikeMessage(ctx *gin.Context) { 11 | page := utils.StringToInt(ctx.Query("page")) 12 | pageSize := utils.StringToInt(ctx.Query("pageSize")) 13 | 14 | if pageSize > 30 { 15 | resp.FailWithMessage(ctx, "请求数量过多") 16 | return 17 | } 18 | 19 | total, messages := service.GetLikeMessage(ctx, page, pageSize) 20 | 21 | // 返回给前端 22 | resp.OkWithData(ctx, gin.H{"total": total, "messages": messages}) 23 | } 24 | -------------------------------------------------------------------------------- /server/internal/api/v1/msg_reply.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/resp" 6 | "interastral-peace.com/alnitak/internal/service" 7 | "interastral-peace.com/alnitak/utils" 8 | ) 9 | 10 | func GetReplyMessage(ctx *gin.Context) { 11 | page := utils.StringToInt(ctx.Query("page")) 12 | pageSize := utils.StringToInt(ctx.Query("pageSize")) 13 | 14 | if pageSize > 30 { 15 | resp.FailWithMessage(ctx, "请求数量过多") 16 | return 17 | } 18 | 19 | total, messages := service.GetReplyMessage(ctx, page, pageSize) 20 | 21 | // 返回给前端 22 | resp.OkWithData(ctx, gin.H{"total": total, "messages": messages}) 23 | } 24 | -------------------------------------------------------------------------------- /server/internal/api/v1/online.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/service" 6 | "interastral-peace.com/alnitak/utils" 7 | ) 8 | 9 | // 视频Websocket连接(统计在线人数) 10 | func GetVideoOnlineConnect(ctx *gin.Context) { 11 | videoId := utils.StringToUint(ctx.Query("vid")) 12 | clientId := ctx.Query("clientId") 13 | if videoId == 0 || clientId == "" { 14 | return 15 | } 16 | 17 | // 升级为websocket长链接 18 | service.GetVideoOnlineConnect(ctx, videoId, clientId) 19 | } 20 | -------------------------------------------------------------------------------- /server/internal/cache/article.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "interastral-peace.com/alnitak/internal/global" 5 | ) 6 | 7 | func SetArticleId(articleId uint) { 8 | global.Redis.SAdd(ALL_ARTICLE_KEY, articleId) 9 | } 10 | 11 | func DelArticleId(articleId uint) { 12 | global.Redis.SRem(ALL_ARTICLE_KEY, articleId) 13 | } 14 | 15 | func DelAllArticleId() { 16 | global.Redis.Del(ALL_ARTICLE_KEY) 17 | } 18 | 19 | func GetRandomArticleIds(count int64) []string { 20 | return global.Redis.SRandMemberN(ALL_ARTICLE_KEY, count) 21 | } 22 | -------------------------------------------------------------------------------- /server/internal/cache/email.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "interastral-peace.com/alnitak/internal/global" 5 | ) 6 | 7 | // 获取邮箱验证码 8 | func GetEmailCode(email string) string { 9 | return global.Redis.Get(EMAIL_CODE_KEY + email) 10 | } 11 | 12 | // 保存邮箱验证码 13 | func SetEmailCode(email string, code string) { 14 | global.Redis.Set(EMAIL_CODE_KEY+email, code, EMIAL_CODE_EXPIRATION_TIME) 15 | } 16 | 17 | // 删除邮箱验证码 18 | func DelEmailCode(email string) { 19 | global.Redis.Del(EMAIL_CODE_KEY + email) 20 | } 21 | -------------------------------------------------------------------------------- /server/internal/cache/jwt.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "time" 5 | 6 | "interastral-peace.com/alnitak/internal/global" 7 | "interastral-peace.com/alnitak/utils" 8 | ) 9 | 10 | func IsRefreshTokenExist(userId uint, token string) bool { 11 | return global.Redis.ZScore(REFRESH_TOKEN_KEY+utils.UintToString(userId), token) != 0 12 | } 13 | 14 | func SetRefreshToken(id uint, token string) { 15 | key := REFRESH_TOKEN_KEY + utils.UintToString(id) 16 | if global.Redis.ZCard(key) >= MAX_LOGIN_LIMIT { 17 | // 保留MAX_LOGIN_LIMIT - 1个token 18 | global.Redis.ZRemRangeByRank(key, 0, 1-MAX_LOGIN_LIMIT) 19 | } 20 | 21 | global.Redis.ZAdd(key, float64(time.Now().Add(REFRESH_TOKEN_EXPRIRATION_TIME).Unix()), token) 22 | } 23 | 24 | func DelRefreshToken(id uint, token string) { 25 | global.Redis.ZRem(REFRESH_TOKEN_KEY+utils.UintToString(id), token) 26 | } 27 | -------------------------------------------------------------------------------- /server/internal/cache/login.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "strconv" 5 | 6 | "interastral-peace.com/alnitak/internal/global" 7 | "interastral-peace.com/alnitak/utils" 8 | ) 9 | 10 | func GetLoginTryCount(username string) int { 11 | s := global.Redis.Get(LOGIN_TRY_COUNT_KEY + username) 12 | if s == "" { 13 | return 0 14 | } 15 | 16 | count, err := strconv.Atoi(s) 17 | if err != nil { 18 | utils.ErrorLog("用户登录次数转换int类型失败", "cache", err.Error()) 19 | } 20 | return count 21 | } 22 | 23 | func SetLoginTryCount(username string, count int) { 24 | global.Redis.Set(LOGIN_TRY_COUNT_KEY+username, count, LOGIN_TRY_COUNT_EXPRIRATION_TIME) 25 | } 26 | 27 | func IncrLoginTryCount(username string) { 28 | global.Redis.Incr(LOGIN_TRY_COUNT_KEY + username) 29 | global.Redis.Expire(LOGIN_TRY_COUNT_KEY+username, LOGIN_TRY_COUNT_EXPRIRATION_TIME) 30 | } 31 | 32 | func DelLoginTryCount(username string) { 33 | global.Redis.Del(LOGIN_TRY_COUNT_KEY + username) 34 | } 35 | -------------------------------------------------------------------------------- /server/internal/cache/resetpwd.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "strconv" 5 | 6 | "interastral-peace.com/alnitak/internal/global" 7 | "interastral-peace.com/alnitak/utils" 8 | ) 9 | 10 | // 状态1: 验证成功 11 | func GetResetPwdCheckStatus(email string) int { 12 | s := global.Redis.Get(RESET_PWD_CHECK_KEY + email) 13 | if s == "" { 14 | return 0 15 | } 16 | 17 | status, err := strconv.Atoi(s) 18 | if err != nil { 19 | utils.ErrorLog("重置密码状态转换为int类型失败", "cache", err.Error()) 20 | } 21 | return status 22 | } 23 | 24 | func SetResetPwdCheckStatus(email string, status int) { 25 | global.Redis.Set(RESET_PWD_CHECK_KEY+email, status, RESET_PWD_CHECK_EXPRIRATION_TIME) 26 | } 27 | 28 | func DelResetPwdCheckStatus(email string) { 29 | global.Redis.Del(RESET_PWD_CHECK_KEY + email) 30 | } 31 | -------------------------------------------------------------------------------- /server/internal/cache/slice.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import "interastral-peace.com/alnitak/internal/global" 4 | 5 | func GetVideoSlice(key string) string { 6 | return global.Redis.Get(VIDEO_SLICE_STATUS + key) 7 | } 8 | 9 | func SetVideoSlice(key, value string) { 10 | global.Redis.Set(VIDEO_SLICE_STATUS+key, value, VIDEO_SLICE_EXPRIRATION_TIME) 11 | } 12 | 13 | func DelVideoSlice(key string) { 14 | global.Redis.Del(VIDEO_SLICE_STATUS + key) 15 | } 16 | -------------------------------------------------------------------------------- /server/internal/cache/slider.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "strconv" 5 | 6 | "interastral-peace.com/alnitak/internal/global" 7 | "interastral-peace.com/alnitak/utils" 8 | ) 9 | 10 | func GetSliderX(captchaId string) int { 11 | s := global.Redis.Get(SLIDER_X_KEY + captchaId) 12 | if s == "" { 13 | return -1 14 | } 15 | 16 | x, err := strconv.Atoi(s) 17 | if err != nil { 18 | utils.ErrorLog("滑块x坐标转换为int类型失败", "cache", err.Error()) 19 | } 20 | return x 21 | } 22 | 23 | func SetSliderX(captchaId string, x int) { 24 | global.Redis.Set(SLIDER_X_KEY+captchaId, x, SLIDER_X_EXPRIRATION_TIME) 25 | } 26 | 27 | func DelSlider(captchaId string) { 28 | global.Redis.Del(SLIDER_X_KEY + captchaId) 29 | } 30 | -------------------------------------------------------------------------------- /server/internal/cache/upload.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "strconv" 5 | 6 | "interastral-peace.com/alnitak/internal/global" 7 | "interastral-peace.com/alnitak/utils" 8 | ) 9 | 10 | func GetUploadImage(url string) uint { 11 | s := global.Redis.Get(UPLOAD_IMAGE_KEY + url) 12 | if s == "" { 13 | return 0 14 | } 15 | 16 | userId, err := strconv.Atoi(s) 17 | if err != nil { 18 | utils.ErrorLog("用户id转换为uint类型失败", "cache", err.Error()) 19 | } 20 | return uint(userId) 21 | } 22 | 23 | func SetUploadImage(url string, userID uint) { 24 | global.Redis.Set(UPLOAD_IMAGE_KEY+url, userID, UPLOAD_IMAGE_EXPRIRATION_TIME) 25 | } 26 | 27 | func DelUploadImage(url string) { 28 | global.Redis.Del(UPLOAD_IMAGE_KEY + url) 29 | } 30 | -------------------------------------------------------------------------------- /server/internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config struct { 4 | Cors Cors `mapstructure:"cors" json:"cors" yaml:"cors"` 5 | File File `mapstructure:"file" json:"file" yaml:"file"` 6 | Log Log `mapstructure:"log" json:"log" yaml:"log"` 7 | Mail Mail `mapstructure:"mail" json:"mail" yaml:"mail"` 8 | Mysql Mysql `mapstructure:"mysql" json:"mysql" yaml:"mysql"` 9 | Redis Redis `mapstructure:"redis" json:"redis" yaml:"redis"` 10 | Security Security `mapstructure:"security" json:"security" yaml:"security"` 11 | Storage Storage `mapstructure:"storage" json:"storage" yaml:"storage"` 12 | Transcoding Transcoding `mapstructure:"transcoding" json:"transcoding" yaml:"transcoding"` 13 | User User `mapstructure:"user" json:"user" yaml:"user"` 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/config/cors.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type User struct { 4 | Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` 5 | } 6 | -------------------------------------------------------------------------------- /server/internal/config/file.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type File struct { 4 | MaxImgSize int64 `mapstructure:"max_img_size" json:"max_img_size" yaml:"max_img_size"` 5 | MaxVideoSize int64 `mapstructure:"max_video_size" json:"max_video_size" yaml:"max_video_size"` 6 | } 7 | -------------------------------------------------------------------------------- /server/internal/config/log.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Log struct { 4 | FileName string `mapstructure:"filename" json:"filename" yaml:"filename"` 5 | MaxAge int `mapstructure:"max-age" json:"max-age" yaml:"max-age"` 6 | MaxBackups int `mapstructure:"max_backups" json:"max_backups" yaml:"max_backups"` 7 | MaxSize int `mapstructure:"max-size" json:"max-size" yaml:"max-size"` 8 | Mode string `mapstructure:"mode" json:"mode" yaml:"mode"` 9 | } 10 | -------------------------------------------------------------------------------- /server/internal/config/mail.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Mail struct { 4 | Addresser string `mapstructure:"addresser" json:"addresser" yaml:"addresser"` 5 | Host string `mapstructure:"host" json:"host" yaml:"host"` 6 | Port int `mapstructure:"port" json:"port" yaml:"port"` 7 | User string `mapstructure:"user" json:"user" yaml:"user"` 8 | Pass string `mapstructure:"pass" json:"pass" yaml:"pass"` 9 | Debug bool `mapstructure:"debug" json:"debug" yaml:"debug"` 10 | } 11 | -------------------------------------------------------------------------------- /server/internal/config/mysql.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Mysql struct { 4 | Datasource string `mapstructure:"datasource" json:"datasource" yaml:"datasource"` 5 | Host string `mapstructure:"host" json:"host" yaml:"host"` 6 | Port int `mapstructure:"port" json:"port" yaml:"port"` 7 | Username string `mapstructure:"username" json:"username" yaml:"username"` 8 | Password string `mapstructure:"password" json:"password" yaml:"password"` 9 | Param string `mapstructure:"param" json:"param" yaml:"param"` 10 | } 11 | -------------------------------------------------------------------------------- /server/internal/config/redis.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Redis struct { 4 | Host string `mapstructure:"host" json:"host" yaml:"host"` 5 | Port int `mapstructure:"port" json:"port" yaml:"port"` 6 | Password string `mapstructure:"password" json:"password" yaml:"password"` 7 | } 8 | -------------------------------------------------------------------------------- /server/internal/config/security.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Security struct { 4 | AccessJwtSecret string `mapstructure:"access_jwt_secret" json:"access_jwt_secret" yaml:"access_jwt_secret"` 5 | RefreshJwtSecret string `mapstructure:"refresh_jwt_secret" json:"refresh_jwt_secret" yaml:"refresh_jwt_secret"` 6 | UserIdSalt string `mapstructure:"user_id_salt" json:"user_id_salt" yaml:"user_id_salt"` 7 | CloseRecordUserOperation bool `mapstructure:"close_record_user_operation" json:"close_record_user_operation" yaml:"close_record_user_operation"` 8 | } 9 | -------------------------------------------------------------------------------- /server/internal/config/storage.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Storage struct { 4 | Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"` 5 | Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"` 6 | KeyId string `mapstructure:"key_id" json:"key_id" yaml:"key_id"` 7 | AppId string `mapstructure:"app_id" json:"app_id" yaml:"app_id"` 8 | KeySecret string `mapstructure:"key_secret" json:"key_secret" yaml:"key_secret"` 9 | OssType string `mapstructure:"oss_type" json:"oss_type" yaml:"oss_type"` 10 | Region string `mapstructure:"region" json:"region" yaml:"region"` 11 | Domain string `mapstructure:"domain" json:"domain" yaml:"domain"` 12 | Private bool `mapstructure:"private" json:"private" yaml:"private"` 13 | UploadMp4File bool `mapstructure:"upload_mp4_file" json:"upload_mp4_file" yaml:"upload_mp4_file"` 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/config/transcoding.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Transcoding struct { 4 | UseGpu bool `mapstructure:"use_gpu" json:"use_gpu" yaml:"use_gpu"` 5 | Generate1080p60 bool `mapstructure:"generate_1080p60" json:"generate_1080p60" yaml:"generate_1080p60"` 6 | } 7 | -------------------------------------------------------------------------------- /server/internal/config/user.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Cors struct { 4 | AllowOrigin string `mapstructure:"allow_origin" json:"allow_origin" yaml:"allow_origin"` 5 | } 6 | -------------------------------------------------------------------------------- /server/internal/cron/cron.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import "github.com/jasonlvhit/gocron" 4 | 5 | func StartCronTask() { 6 | c := gocron.NewScheduler() 7 | 8 | // 每3小时刷新同步播放量数据 9 | c.Every(3).Hours().Do(SyncClicks) 10 | 11 | // 每3小时刷新一次热点 12 | c.Every(3).Hours().Do(RefreshPopular) 13 | 14 | <-c.Start() 15 | } 16 | -------------------------------------------------------------------------------- /server/internal/domain/dto/api.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type ApiListReq struct { 4 | Page int 5 | PageSize int 6 | } 7 | 8 | type AddApiReq struct { 9 | Path string 10 | Category string 11 | Method string 12 | Desc string 13 | } 14 | 15 | type EditApiReq struct { 16 | ID uint 17 | Path string 18 | Category string 19 | Method string 20 | Desc string 21 | } 22 | 23 | // 编辑角色Api 24 | type EditRoleApiReq struct { 25 | Id uint //角色ID 26 | AddIds []uint //添加API ID数组 27 | RemoveIds []uint //移除API ID数组 28 | } 29 | -------------------------------------------------------------------------------- /server/internal/domain/dto/article.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type ArticleListReq struct { 4 | Page int 5 | PageSize int 6 | } 7 | 8 | type UploadArticleReq struct { 9 | Title string 10 | Cover string 11 | Copyright bool 12 | Tags string 13 | Content string 14 | PartitionId uint //分区ID 15 | } 16 | 17 | type EditArticleReq struct { 18 | Aid uint 19 | Title string 20 | Cover string 21 | Tags string 22 | Content string 23 | } 24 | 25 | type ReviewArticleListReq struct { 26 | Page int 27 | PageSize int 28 | } 29 | -------------------------------------------------------------------------------- /server/internal/domain/dto/captcha.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type SliderReq struct { 4 | // 邮箱 5 | CaptchaId string 6 | // x坐标 7 | X int 8 | } 9 | -------------------------------------------------------------------------------- /server/internal/domain/dto/carousel.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type AddCarouselReq struct { 4 | Img string 5 | Url string 6 | Title string 7 | Color string 8 | Use bool 9 | PartitionId uint 10 | } 11 | 12 | type EditCarouselReq struct { 13 | ID uint 14 | Img string 15 | Url string 16 | Title string 17 | Color string 18 | Use bool 19 | PartitionId uint 20 | } 21 | 22 | type CarouselListReq struct { 23 | Page int 24 | PageSize int 25 | } 26 | -------------------------------------------------------------------------------- /server/internal/domain/dto/collect.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type AddCollectReq struct { 4 | Vid uint //视频ID 5 | AddList []uint //添加的收藏夹数组 6 | CancelList []uint //取消的收藏夹数组 7 | } 8 | 9 | type CollectArticleReq struct { 10 | Aid uint 11 | } 12 | -------------------------------------------------------------------------------- /server/internal/domain/dto/collection.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type AddCollectionReq struct { 4 | Name string //收藏夹名称 5 | } 6 | 7 | type EditCollectionReq struct { 8 | ID uint 9 | Cover string //封面图 10 | Name string //收藏夹名称 11 | Desc string //简介 12 | Open bool //是否公开 13 | } 14 | -------------------------------------------------------------------------------- /server/internal/domain/dto/comment.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type AddCommentReq struct { 4 | Cid uint 5 | Content string 6 | At []string 7 | ParentID uint 8 | ReplyUserID uint 9 | ReplyUserName string 10 | ReplyContent string 11 | } 12 | -------------------------------------------------------------------------------- /server/internal/domain/dto/common.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type EmailReq struct { 4 | // 邮箱 5 | Email string 6 | } 7 | 8 | type IdReq struct { 9 | ID uint 10 | } 11 | -------------------------------------------------------------------------------- /server/internal/domain/dto/config.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type EmailConfigReq struct { 4 | Debug bool 5 | User string 6 | Pass string 7 | Host string 8 | Port int 9 | Addresser string 10 | } 11 | 12 | type StorageConfigReq struct { 13 | MaxImgSize int64 14 | MaxVideoSize int64 15 | 16 | Type string 17 | KeyID string 18 | KeySecret string 19 | Bucket string 20 | Endpoint string 21 | AppID string 22 | Region string 23 | Domain string 24 | Private bool 25 | 26 | UploadMp4File bool 27 | } 28 | 29 | type OtherConfigReq struct { 30 | AllowOrigin string 31 | Prefix string 32 | Generate1080p60 bool 33 | UseGpu bool 34 | } 35 | -------------------------------------------------------------------------------- /server/internal/domain/dto/danmaku.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type DanmakuReq struct { 4 | Vid uint 5 | Part uint 6 | Time float32 //时间 7 | Type int //类型0滚动;1顶部;2底部 8 | Color string 9 | Text string 10 | } 11 | -------------------------------------------------------------------------------- /server/internal/domain/dto/email.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type SendEmailReq struct { 4 | // 邮箱 5 | Email string 6 | // 验证ID 7 | CaptchaId string 8 | } 9 | -------------------------------------------------------------------------------- /server/internal/domain/dto/history.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type HistoryReq struct { 4 | Vid uint 5 | Part uint 6 | Time float64 7 | } 8 | -------------------------------------------------------------------------------- /server/internal/domain/dto/like.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type LikeVideoReq struct { 4 | Vid uint 5 | } 6 | 7 | type LikeArticleReq struct { 8 | Aid uint 9 | } 10 | -------------------------------------------------------------------------------- /server/internal/domain/dto/menu.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type AddMenuReq struct { 4 | Name string 5 | Path string 6 | Component string 7 | Desc string 8 | Sort uint 9 | ParentId uint 10 | // Meta内容 11 | Title string 12 | Icon string 13 | Hidden bool 14 | KeepAlive bool 15 | } 16 | 17 | type EditMenuReq struct { 18 | ID uint 19 | Name string 20 | Path string 21 | Component string 22 | Desc string 23 | Sort uint 24 | ParentId uint 25 | // Meta内容 26 | Title string 27 | Icon string 28 | Hidden bool 29 | KeepAlive bool 30 | } 31 | 32 | // 修改角色菜单 33 | type EditRoleMenuReq struct { 34 | Id uint //角色ID 35 | MenuIds []uint //菜单ID数组 36 | } 37 | -------------------------------------------------------------------------------- /server/internal/domain/dto/msg_announce.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type AddAnnounceReq struct { 4 | Title string 5 | Content string 6 | Url string 7 | } 8 | -------------------------------------------------------------------------------- /server/internal/domain/dto/msg_whisper.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type WhisperReq struct { 4 | Fid uint // 接收者ID 5 | Content string 6 | } 7 | -------------------------------------------------------------------------------- /server/internal/domain/dto/partition.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type AddPartitionReq struct { 4 | Name string 5 | ParentId uint //所属分区ID 6 | Type int 7 | } 8 | -------------------------------------------------------------------------------- /server/internal/domain/dto/resource.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | // 修改资源标题 4 | type ModifyResourceTitleReq struct { 5 | ID uint 6 | Title string 7 | } 8 | -------------------------------------------------------------------------------- /server/internal/domain/dto/review.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type ReviewVideoReq struct { 4 | Vid uint 5 | Status int 6 | Remark string 7 | } 8 | 9 | type ReviewArticleReq struct { 10 | Aid uint 11 | Status int 12 | Remark string 13 | } 14 | -------------------------------------------------------------------------------- /server/internal/domain/dto/role.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type RoleListReq struct { 4 | Page int 5 | PageSize int 6 | } 7 | 8 | // 新增角色 9 | type AddRoleReq struct { 10 | Name string 11 | Code string 12 | Desc string 13 | } 14 | 15 | // 编辑角色 16 | type EditRoleReq struct { 17 | ID uint 18 | Name string 19 | Desc string 20 | } 21 | 22 | // 编辑角色首页 23 | type EditRoleHomeReq struct { 24 | ID uint 25 | Home string 26 | } 27 | 28 | // 设置用户角色 29 | type EditUserRoleReq struct { 30 | Uid uint 31 | Code string 32 | } 33 | -------------------------------------------------------------------------------- /server/internal/domain/dto/transcoding.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type TranscodingInfo struct { 4 | Width int // 视频宽度 5 | Height int // 视频高度 6 | Duration float64 // 视频时长 7 | DirName string // 目录名称 8 | OutputDir string // 输出位置 9 | InputFile string // 输入文件 10 | ResourceID uint // 资源ID 11 | VideoID uint // 视频ID 12 | CodecName string // 视频编码名称 13 | FPS string // 视频帧率 14 | FPS30 string // 30帧实际帧率 15 | FPS60 string // 60帧实际帧率 16 | } 17 | -------------------------------------------------------------------------------- /server/internal/domain/dto/video.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type VideoListReq struct { 4 | Page int 5 | PageSize int 6 | } 7 | 8 | type VideoFileReq struct { 9 | Hash string 10 | } 11 | 12 | type ReviewListReq struct { 13 | Page int 14 | PageSize int 15 | } 16 | 17 | type UploadVideoReq struct { 18 | Vid uint 19 | Title string 20 | Cover string 21 | Desc string 22 | Copyright bool 23 | Tags string 24 | PartitionId uint //分区ID 25 | } 26 | 27 | type EditVideoReq struct { 28 | Vid uint 29 | Title string 30 | Cover string 31 | Desc string 32 | Tags string 33 | } 34 | 35 | type SearchVideoReq struct { 36 | Page int 37 | PageSize int 38 | KeyWords string 39 | } 40 | -------------------------------------------------------------------------------- /server/internal/domain/model/api.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Api struct { 6 | gorm.Model 7 | Method string `gorm:"type:varchar(20);comment:请求方式"` 8 | Path string `gorm:"type:varchar(100);comment:访问路径"` 9 | Category string `gorm:"type:varchar(50);comment:所属类别"` 10 | Desc string `gorm:"type:varchar(100);comment:说明"` 11 | } 12 | 13 | func (table *Api) TableName() string { 14 | return "api" 15 | } 16 | -------------------------------------------------------------------------------- /server/internal/domain/model/article.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Article struct { 6 | gorm.Model 7 | Title string `gorm:"type:varchar(50);comment:标题;not null;index"` 8 | Cover string `gorm:"type:varchar(255);comment:封面图;not null"` 9 | Content string `gorm:"type:text;comment:内容;not null"` 10 | ContentDesc string `gorm:"type:varchar(300);comment:内容简介"` 11 | Uid uint `gorm:"comment:用户ID;not null;index"` 12 | Copyright bool `gorm:"comment:是否为原创;not null"` 13 | Clicks int64 `gorm:"comment:点击量;default:0"` 14 | Status int `gorm:"comment:审核状态;not null"` 15 | PartitionId uint `gorm:"comment:分区ID;deult:0"` 16 | Tags string `gorm:"type:varchar(100);comment:标签;"` 17 | 18 | Author User `gorm:"-"` 19 | } 20 | 21 | func (table *Article) TableName() string { 22 | return "article" 23 | } 24 | -------------------------------------------------------------------------------- /server/internal/domain/model/carousel.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Carousel struct { 6 | gorm.Model 7 | Uid uint `gorm:"comment:创建者"` 8 | Img string `gorm:"type:varchar(255);comment:图片链接"` 9 | Title string `gorm:"type:varchar(30);comment:标题"` 10 | Url string `gorm:"type:varchar(100);comment:指向的链接"` 11 | Color string `gorm:"type:varchar(20);comment:主题色"` 12 | Use bool `gorm:"comment:是否启用"` 13 | PartitionId uint `gorm:"default:0;comment:分区ID"` 14 | } 15 | 16 | func (table *Carousel) TableName() string { 17 | return "carousel" 18 | } 19 | -------------------------------------------------------------------------------- /server/internal/domain/model/casbin.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // 角色权限规则 4 | type CasbinRule struct { 5 | ID uint `gorm:"primarykey"` 6 | Ptype string `gorm:"type:varchar(100);"` 7 | V0 string `gorm:"type:varchar(100);"` 8 | V1 string `gorm:"type:varchar(100);"` 9 | V2 string `gorm:"type:varchar(100);"` 10 | V3 string `gorm:"type:varchar(100);"` 11 | V4 string `gorm:"type:varchar(100);"` 12 | V5 string `gorm:"type:varchar(100);"` 13 | } 14 | 15 | func (table *CasbinRule) TableName() string { 16 | return "casbin_rule" 17 | } 18 | -------------------------------------------------------------------------------- /server/internal/domain/model/collect_article.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type CollectArticle struct { 6 | gorm.Model 7 | Uid uint `gorm:"comment:用户ID;not null;index"` 8 | Aid uint `gorm:"comment:内容ID;not null"` 9 | IsCollect bool `gorm:"comment:是否收藏;default:false"` //是否点赞 10 | } 11 | 12 | func (table *CollectArticle) TableName() string { 13 | return "collect_article" 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/domain/model/collect_video.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type CollectVideo struct { 6 | gorm.Model 7 | Uid uint `gorm:"comment:用户ID;not null"` 8 | Vid uint `gorm:"comment:视频ID;not null"` 9 | CollectionID uint `gorm:"comment:所属收藏夹ID;default:0"` 10 | } 11 | 12 | func (table *CollectVideo) TableName() string { 13 | return "collect_video" 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/domain/model/collection.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | //收藏夹 6 | type Collection struct { 7 | gorm.Model 8 | Uid uint `gorm:"comment:所属用户;not null"` 9 | Name string `gorm:"comment:收藏夹名称;type:varchar(20);"` 10 | Desc string `gorm:"comment:简介;type:varchar(150);"` 11 | Cover string `gorm:"comment:封面;size:255;"` 12 | Open bool `gorm:"comment:是否公开;default:false"` 13 | } 14 | 15 | func (table *Collection) TableName() string { 16 | return "collection" 17 | } 18 | -------------------------------------------------------------------------------- /server/internal/domain/model/comment.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | ) 6 | 7 | // 评论 8 | type Comment struct { 9 | gorm.Model 10 | Cid uint `gorm:"comment:内容ID;index"` 11 | Uid uint `gorm:"comment:所属用户;index"` 12 | Content string `gorm:"type:varchar(200);comment:评论内容"` 13 | AtUsernames string `gorm:"type:varchar(100);comment:提及的用户名"` 14 | AtUserIds string `gorm:"type:varchar(100);comment:提及的用户的ID"` 15 | ReplyUserID uint `gorm:"comment:回复的用户的ID"` 16 | ReplyUserName string `gorm:"type:varchar(20);comment:回复用户的用户名"` 17 | ParentId uint `gorm:"default:0;comment:所属评论ID"` 18 | Type int `gorm:"size:1;default:0;comment:类型:0视频、1文章"` 19 | } 20 | 21 | func (table *Comment) TableName() string { 22 | return "comment" 23 | } 24 | -------------------------------------------------------------------------------- /server/internal/domain/model/danmaku.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Danmaku struct { 6 | gorm.Model 7 | Vid uint `gorm:"comment:视频ID;not null;index"` 8 | Part uint `gorm:"comment:分集ID;default:1;index"` 9 | Time float32 `gorm:"comment:时间;not null"` 10 | Type int `gorm:"comment:类型:0滚动;1顶部;2底部;default:0"` 11 | Color string `gorm:"type:varchar(10);comment:颜色;default:'#fff'"` 12 | Text string `gorm:"type:varchar(100);comment:内容;not null"` 13 | Uid uint `gorm:"comment:用户ID;not null"` 14 | } 15 | 16 | func (table *Danmaku) TableName() string { 17 | return "danmaku" 18 | } 19 | -------------------------------------------------------------------------------- /server/internal/domain/model/history.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type History struct { 6 | gorm.Model 7 | Vid uint `gorm:"comment:所在视频id;not null"` 8 | Uid uint `gorm:"comment:所属用户ID;not null"` 9 | Part uint `gorm:"comment:分集;default:1"` 10 | Time float64 `gorm:"comment:进度;not null"` 11 | } 12 | 13 | func (table *History) TableName() string { 14 | return "history" 15 | } 16 | -------------------------------------------------------------------------------- /server/internal/domain/model/like_article.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type LikeArticle struct { 6 | gorm.Model 7 | Uid uint `gorm:"comment:用户ID;not null;index"` 8 | Aid uint `gorm:"comment:内容ID;not null"` 9 | IsLike bool `gorm:"comment:是否点赞;default:false"` //是否点赞 10 | } 11 | 12 | func (table *LikeArticle) TableName() string { 13 | return "like_article" 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/domain/model/like_video.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type LikeVideo struct { 6 | gorm.Model 7 | Uid uint `gorm:"comment:用户ID;not null;index"` 8 | Vid uint `gorm:"comment:视频ID;not null"` 9 | IsLike bool `gorm:"comment:是否点赞;default:false"` //是否点赞 10 | } 11 | 12 | func (table *LikeVideo) TableName() string { 13 | return "like_video" 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/domain/model/menu.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "gorm.io/gorm" 5 | ) 6 | 7 | type Menu struct { 8 | gorm.Model 9 | Name string `gorm:"type:varchar(50);comment:菜单名称"` 10 | Path string `gorm:"type:varchar(100);comment:菜单访问路径"` 11 | Component string `gorm:"type:varchar(100);comment:前端组件路径" ` 12 | Desc string `gorm:"type:varchar(100);comment:介绍" ` 13 | Sort uint `gorm:"type:int(3);default:1;comment:菜单排序"` 14 | Children []Menu `gorm:"-" json:"children"` // 子菜单集合 15 | Roles []Role `gorm:"many2many:role_menu;"` // 角色菜单多对多关系 16 | ParentId uint `gorm:"default:0;comment:父菜单ID"` 17 | // Meta内容 18 | Title string `gorm:"type:varchar(50);comment:菜单标题"` 19 | Icon string `gorm:"type:varchar(50);comment:菜单图标"` 20 | Hidden bool `gorm:"default:false;comment:在菜单中隐藏"` 21 | KeepAlive bool `gorm:"default:false;comment:缓存页面"` 22 | } 23 | 24 | func (table *Menu) TableName() string { 25 | return "menu" 26 | } 27 | -------------------------------------------------------------------------------- /server/internal/domain/model/msg_announce.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Announce struct { 6 | gorm.Model 7 | Title string `gorm:"type:varchar(50);comment:标题;not null"` 8 | Content string `gorm:"type:varchar(200);comment:内容;"` 9 | Url string `gorm:"type:varchar(100);comment:链接;"` 10 | } 11 | 12 | func (table *Announce) TableName() string { 13 | return "msg_announce" 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/domain/model/msg_at.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type AtMessage struct { 6 | gorm.Model 7 | Cid uint `gorm:"comment:内容ID;not null"` 8 | Uid uint `gorm:"comment:所属用户ID;not null"` 9 | Sid uint `gorm:"comment:发送用户ID;not null"` 10 | Type int `gorm:"size:1;default:0;comment:类型:0视频、1文章"` 11 | } 12 | 13 | func (table *AtMessage) TableName() string { 14 | return "msg_at" 15 | } 16 | -------------------------------------------------------------------------------- /server/internal/domain/model/msg_like.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type LikeMessage struct { 6 | gorm.Model 7 | Cid uint `gorm:"comment:内容ID;not null"` 8 | Uid uint `gorm:"comment:所属用户ID;not null"` 9 | Sid uint `gorm:"comment:发送用户ID;not null"` 10 | Type int `gorm:"size:1;default:0;comment:类型:0视频、1文章"` 11 | } 12 | 13 | func (table *LikeMessage) TableName() string { 14 | return "msg_like" 15 | } 16 | -------------------------------------------------------------------------------- /server/internal/domain/model/msg_reply.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type ReplyMessage struct { 6 | gorm.Model 7 | Cid uint `gorm:"comment:内容ID;not null"` 8 | Uid uint `gorm:"comment:所属用户ID;not null"` 9 | Sid uint `gorm:"comment:关联用户ID;not null"` 10 | Content string `gorm:"comment:内容;not null"` 11 | TargetReplyContent string `gorm:"comment:上级回复内容"` 12 | RootContent string `gorm:"comment:根评论内容"` 13 | CommentId uint `gorm:"comment:评论ID;"` 14 | Type int `gorm:"size:1;default:0;comment:类型:0视频、1文章"` 15 | } 16 | 17 | func (table *ReplyMessage) TableName() string { 18 | return "msg_reply" 19 | } 20 | -------------------------------------------------------------------------------- /server/internal/domain/model/msg_whisper.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Whisper struct { 6 | gorm.Model 7 | Uid uint `gorm:"comment:用户ID;not null;"` 8 | Fid uint `gorm:"comment:好友ID;not null;"` 9 | FromId uint `gorm:"comment:发送者;not null;"` 10 | ToId uint `gorm:"comment:接受者;not null;"` 11 | Content string `gorm:"size:255;comment:内容;"` 12 | Status bool `gorm:"comment:已读状态;default:false"` 13 | } 14 | 15 | func (table *Whisper) TableName() string { 16 | return "msg_whisper" 17 | } 18 | -------------------------------------------------------------------------------- /server/internal/domain/model/partition.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Partition struct { 6 | gorm.Model 7 | Name string `gorm:"varchar(20);comment:分区名称"` 8 | Type int `gorm:"size:1;default:0;comment:类型:0视频、1文章"` 9 | ParentId uint `gorm:"default:0;comment:所属分区ID"` 10 | } 11 | 12 | func (table *Partition) TableName() string { 13 | return "partition" 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/domain/model/relation.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Relation struct { 6 | gorm.Model 7 | Uid uint `gorm:"comment:用户ID;not null;index"` 8 | TargetUid uint `gorm:"comment:目标用户ID;not null;index"` 9 | Relation int `gorm:"comment:关系:0未关注、1关注、2互相关注;default:1"` 10 | } 11 | 12 | func (table *Relation) TableName() string { 13 | return "relation" 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/domain/model/resource.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Resource struct { 6 | gorm.Model 7 | Vid uint `gorm:"comment:所属视频;index"` 8 | Uid uint `gorm:"comment:所属用户;index"` 9 | Title string `gorm:"type:varchar(50);comment:分P使用的标题"` 10 | CodecName string `gorm:"type:varchar(10);comment:视频编码名称"` 11 | Duration float64 `gorm:"comment:视频时长;default:0"` 12 | Status int `gorm:"comment:审核状态;not null;index"` 13 | } 14 | 15 | func (table *Resource) TableName() string { 16 | return "resource" 17 | } 18 | -------------------------------------------------------------------------------- /server/internal/domain/model/review.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Review struct { 6 | gorm.Model 7 | Cid uint `gorm:"comment:内容ID;not null;index"` 8 | Status int `gorm:"comment:审核状态;not null"` 9 | Remark string `gorm:"type:varchar(200);comment:备注;not null;index"` 10 | Uid uint `gorm:"comment:审核人;"` 11 | Type int `gorm:"size:1;default:0;comment:类型:0视频、1文章"` 12 | } 13 | 14 | func (table *Review) TableName() string { 15 | return "review" 16 | } 17 | -------------------------------------------------------------------------------- /server/internal/domain/model/role.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Role struct { 6 | gorm.Model 7 | Name string `gorm:"type:varchar(20);not null;unique;comment:角色名"` 8 | Code string `gorm:"type:varchar(20);not null;unique;comment:角色代码"` 9 | Desc string `gorm:"type:varchar(100);comment:介绍"` 10 | HomePage string `gorm:"type:varchar(20);comment:角色首页"` 11 | Menus []Menu `gorm:"many2many:role_menu;"` // 角色菜单多对多关系 12 | } 13 | 14 | func (table *Role) TableName() string { 15 | return "role" 16 | } 17 | -------------------------------------------------------------------------------- /server/internal/domain/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type User struct { 10 | gorm.Model 11 | Username string `gorm:"type:varchar(30);comment:用户名;not null;index"` 12 | Email string `gorm:"type:varchar(30);comment:邮箱;not null;index"` 13 | Password string `gorm:"type:varchar(128);comment:密码;not null"` 14 | Avatar string `gorm:"type:varchar(255);comment:头像"` 15 | SpaceCover string `gorm:"type:varchar(255);comment:空间封面"` 16 | Gender int `gorm:"size:1;default:0;comment:性别:0未知、1男、3女"` 17 | Birthday time.Time `gorm:"default:'1970-01-01';comment:生日"` 18 | Sign string `gorm:"type:varchar(50);comment:个性签名;default:'这个人很懒,什么都没有留下'"` 19 | ClientIp string `gorm:"type:varchar(20);comment:客户端IP"` 20 | Role string `gorm:"default:'001';comment:角色ID"` 21 | } 22 | 23 | func (table *User) TableName() string { 24 | return "user" 25 | } 26 | -------------------------------------------------------------------------------- /server/internal/domain/model/video.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type Video struct { 6 | gorm.Model 7 | Title string `gorm:"type:varchar(50);comment:标题;not null;index"` 8 | Cover string `gorm:"type:varchar(255);cmment:封面图;not null"` 9 | Desc string `gorm:"type:varchar(200);comment:视频简介;default:什么都没有~"` 10 | Uid uint `gorm:"comment:用户ID;not null;index"` 11 | Copyright bool `gorm:"comment:是否为原创;not null"` 12 | Clicks int64 `gorm:"comment:点击量;default:0"` 13 | Status int `gorm:"comment:审核状态;not null"` 14 | PartitionId uint `gorm:"comment:分区ID;deult:0"` 15 | Tags string `gorm:"type:varchar(100);comment:标签;"` 16 | Duration float64 `gorm:"comment:视频时长;default:0"` 17 | 18 | Author User `gorm:"-"` 19 | } 20 | 21 | func (table *Video) TableName() string { 22 | return "video" 23 | } 24 | -------------------------------------------------------------------------------- /server/internal/domain/model/video_file.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type VideoFile struct { 6 | gorm.Model 7 | Uid uint `gorm:"comment:用户ID;not null;index"` 8 | OriginalName string `gorm:"type:varchar(100);comment:原始文件名;"` 9 | DirName string `gorm:"type:varchar(20);comment:目录名称;index"` 10 | Hash string `gorm:"type:varchar(64);comment:文件hash;"` 11 | ChunksCount int `gorm:"comment:分片数量;"` 12 | } 13 | 14 | func (table *VideoFile) TableName() string { 15 | return "video_file" 16 | } 17 | -------------------------------------------------------------------------------- /server/internal/domain/model/video_index_file.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | type VideoIndexFile struct { 6 | gorm.Model 7 | ResourceID uint `gorm:"index;comment:视频资源ID;"` 8 | Quality string `gorm:"comment:视频质量;"` 9 | DirName string `gorm:"type:varchar(20);comment:目录名称;"` 10 | Content string `gorm:"type:text;comment:文件内容;"` 11 | } 12 | 13 | func (table *VideoIndexFile) TableName() string { 14 | return "video_index_file" 15 | } 16 | -------------------------------------------------------------------------------- /server/internal/domain/vo/api.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import "time" 4 | 5 | type ApiResp struct { 6 | ID uint `json:"id"` 7 | Method string `json:"method"` 8 | Path string `json:"path"` 9 | Category string `json:"category"` 10 | Desc string `json:"desc"` 11 | CreatedAt time.Time `json:"createdAt"` 12 | } 13 | -------------------------------------------------------------------------------- /server/internal/domain/vo/archive.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | // 视频点赞收藏数据 4 | type ArchiveStatResp struct { 5 | Collect int64 `json:"collect"` 6 | Like int64 `json:"like"` 7 | } 8 | -------------------------------------------------------------------------------- /server/internal/domain/vo/captcha.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | type CaptchaResp struct { 4 | SliderImg string `json:"slider_img"` 5 | BgImg string `json:"bg_img"` 6 | Y int `json:"y"` 7 | } 8 | -------------------------------------------------------------------------------- /server/internal/domain/vo/carousel.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | CAROUSEL_FIELD = "`id`,`img`,`url`,`title`,`color`,`use`,`partition_id`,`created_at`" 9 | ) 10 | 11 | type CarouselResp struct { 12 | ID uint `json:"id"` 13 | Img string `json:"img"` 14 | Url string `json:"url"` 15 | Title string `json:"title"` 16 | Color string `json:"color"` 17 | Use bool `json:"use"` 18 | PartitionId uint `json:"partitionId"` 19 | CreatedAt time.Time `json:"createdAt"` 20 | } 21 | -------------------------------------------------------------------------------- /server/internal/domain/vo/collection.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | const ( 8 | COLLECTION_LIST_FIELD = "`id`,`cover`,`name`,`desc`,`open`,`created_at`" 9 | COLLECTION_FIELD = "`id`,`uid`,`cover`,`name`,`desc`,`open`,`created_at`" 10 | ) 11 | 12 | // 收藏夹 13 | type CollectionResp struct { 14 | ID uint `json:"id"` 15 | Uid uint `json:"uid"` 16 | Cover string `json:"cover"` 17 | Name string `json:"name"` //收藏夹名称 18 | Desc string `json:"desc"` //简介 19 | Open bool `json:"open"` //是否公开 20 | CreatedAt time.Time `json:"createdAt"` 21 | Author UserInfoResp `json:"author" gorm:"-"` 22 | } 23 | 24 | type CollectionListResp struct { 25 | ID uint `json:"id"` 26 | Cover string `json:"cover"` 27 | Name string `json:"name"` //收藏夹名称 28 | Desc string `json:"desc"` //简介 29 | Open bool `json:"open"` //是否公开 30 | CreatedAt time.Time `json:"createdAt"` 31 | } 32 | -------------------------------------------------------------------------------- /server/internal/domain/vo/danmaku.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import "time" 4 | 5 | const ( 6 | DANMAKU_FIELD = "`id`, `time`, `type`, `color`, `text`, `created_at`" 7 | ) 8 | 9 | type DanmakuResp struct { 10 | ID uint `json:"id"` 11 | Time float32 `json:"time"` 12 | Type int `json:"type"` 13 | Color string `json:"color"` 14 | Text string `json:"text"` 15 | CreatedAt time.Time `json:"createdAt"` 16 | } 17 | -------------------------------------------------------------------------------- /server/internal/domain/vo/history.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import "time" 4 | 5 | const ( 6 | HISTORY_VIDEO_FIELD = "`video`.`id`,`video`.`uid`,`title`,`cover`,`desc`,`history`.`updated_at`,`time`" 7 | ) 8 | 9 | type HistoryVideoResp struct { 10 | ID uint `json:"vid"` 11 | Uid uint `json:"uid"` 12 | Title string `json:"title"` 13 | Cover string `json:"cover"` 14 | Desc string `json:"desc"` 15 | Time float64 `json:"time"` 16 | UpdatedAt time.Time `json:"updatedAt"` 17 | } 18 | -------------------------------------------------------------------------------- /server/internal/domain/vo/msg_announce.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type AnnounceResp struct { 8 | ID uint `json:"id"` 9 | Title string `json:"title"` 10 | Content string `json:"content"` 11 | Url string `json:"url"` 12 | CreatedAt time.Time `json:"createdAt"` 13 | } 14 | -------------------------------------------------------------------------------- /server/internal/domain/vo/msg_at.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type AtMessageResp struct { 8 | ID uint `json:"id"` 9 | Cid uint `json:"cid"` 10 | Sid uint `json:"sid"` 11 | CreatedAt time.Time `json:"createdAt"` 12 | User UserInfoResp `json:"user" gorm:"-"` 13 | Video VideoResp `json:"video" gorm:"-"` 14 | Article ArticleResp `json:"article" gorm:"-"` 15 | Type int `json:"type"` 16 | } 17 | -------------------------------------------------------------------------------- /server/internal/domain/vo/msg_like.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type LikeMessageResp struct { 8 | ID uint `json:"id"` 9 | Cid uint `json:"cid"` 10 | Sid uint `json:"sid"` 11 | CreatedAt time.Time `json:"createdAt"` 12 | User UserInfoResp `json:"user" gorm:"-"` 13 | Video VideoResp `json:"video" gorm:"-"` 14 | Article ArticleResp `json:"article" gorm:"-"` 15 | Type int `json:"type"` 16 | } 17 | -------------------------------------------------------------------------------- /server/internal/domain/vo/msg_reply.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type ReplyMessageResp struct { 8 | ID uint `json:"id"` 9 | Cid uint `json:"cid"` 10 | Sid uint `json:"sid"` 11 | CreatedAt time.Time `json:"createdAt"` 12 | Content string `json:"content"` 13 | TargetReplyContent string `json:"targetReplyContent"` 14 | RootContent string `json:"rootContent"` 15 | CommentId string `json:"commentId"` 16 | User UserInfoResp `json:"user" gorm:"-"` 17 | Video VideoResp `json:"video" gorm:"-"` 18 | Article ArticleResp `json:"article" gorm:"-"` 19 | Type int `json:"type"` 20 | } 21 | -------------------------------------------------------------------------------- /server/internal/domain/vo/msg_whisper.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import "time" 4 | 5 | const ( 6 | WHISPER_GROUP_FIELD = "`fid`,`created_at`,`content`,`status`" 7 | WHISPER_DETAILS_FIELD = "`fid`,`from_id`,`content`,`created_at`" 8 | ) 9 | 10 | // 消息列表 11 | type WhisperGroupResp struct { 12 | CreatedAt time.Time `json:"createdAt"` 13 | Content string `json:"content"` 14 | Fid uint `json:"fid"` 15 | Status bool `json:"status" ` //已读状态 16 | User UserInfoResp `json:"user" gorm:"-"` 17 | } 18 | 19 | // 消息详情 20 | type WhisperDetailsResp struct { 21 | Fid uint `json:"fid"` 22 | FromId uint `json:"fromId"` 23 | Content string `json:"content"` 24 | CreatedAt time.Time `json:"createdAt"` 25 | } 26 | 27 | // 消息 28 | type WhisperResp struct { 29 | Fid uint `json:"fid"` 30 | Content string `json:"content"` 31 | } 32 | -------------------------------------------------------------------------------- /server/internal/domain/vo/online.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | type OnlineCountResp struct { 4 | Number int `json:"number"` 5 | } 6 | -------------------------------------------------------------------------------- /server/internal/domain/vo/partition.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import "time" 4 | 5 | const ( 6 | PARTITION_FIELD = "`id`,`name`,`type`,`parent_id`,`created_at`" 7 | ) 8 | 9 | // 分区 10 | type PartitionResp struct { 11 | ID uint `json:"id"` 12 | Name string `json:"name"` 13 | Type int `json:"type"` 14 | ParentID uint `json:"parentId"` 15 | CreatedAt time.Time `json:"createdAt"` 16 | } 17 | -------------------------------------------------------------------------------- /server/internal/domain/vo/relation.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | type RelationResp struct { 4 | Uid uint `json:"-"` 5 | TargetUid uint `json:"-"` 6 | Relation int `json:"relation"` 7 | User UserInfoResp `json:"user" gorm:"-"` 8 | } 9 | -------------------------------------------------------------------------------- /server/internal/domain/vo/resource.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import ( 4 | "time" 5 | 6 | "interastral-peace.com/alnitak/internal/domain/model" 7 | ) 8 | 9 | type ResourceResp struct { 10 | ID uint `json:"id"` 11 | CreatedAt time.Time `json:"createdAt"` 12 | Vid uint `json:"vid"` 13 | Title string `json:"title"` 14 | Duration float64 `json:"duration"` 15 | Status int `json:"status"` 16 | } 17 | 18 | func ResourceToResourceResp(resource model.Resource) ResourceResp { 19 | return ResourceResp{ 20 | ID: resource.ID, 21 | CreatedAt: resource.CreatedAt, 22 | Vid: resource.Vid, 23 | Title: resource.Title, 24 | Duration: resource.Duration, 25 | Status: resource.Status, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/internal/domain/vo/review.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import "time" 4 | 5 | const ( 6 | REVIEW_FIELD = "`status`,`remark`,`created_at`" 7 | ) 8 | 9 | // 消息列表 10 | type ReviewResp struct { 11 | Status int `json:"status"` 12 | Remark string `json:"remark"` 13 | CreatedAt time.Time `json:"createdAt"` 14 | } 15 | -------------------------------------------------------------------------------- /server/internal/domain/vo/role.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import "time" 4 | 5 | type RoleResp struct { 6 | ID uint `json:"id"` 7 | Name string `json:"name"` 8 | Code string `json:"code"` 9 | Desc string `json:"desc"` 10 | HomePage string `json:"homePage"` 11 | CreatedAt time.Time `json:"createdAt"` 12 | } 13 | 14 | type AllRoleResp struct { 15 | Code string `json:"code"` 16 | Name string `json:"name"` 17 | } 18 | -------------------------------------------------------------------------------- /server/internal/global/constant.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | const CAPTCHA_STATUS_ABSENT = 0 // 人机验证状态不存在 4 | const CAPTCHA_STATUS_NOT_USED = 1 // 人机验证状态未使用 5 | const CAPTCHA_STATUS_PASS = 2 // 人机验证状态已通过 6 | 7 | // 视频审核状态 8 | const ( 9 | // 审核通过 10 | AUDIT_APPROVED = 0 11 | // 成功创建视频 12 | CREATED_VIDEO = 100 13 | // 视频转码中 14 | VIDEO_PROCESSING = 200 15 | // 提交审核 16 | SUBMIT_REVIEW = 300 17 | // 等待审核 18 | WAITING_REVIEW = 500 19 | // 审核不通过 20 | REVIEW_FAILED = 2000 21 | // 视频处理失败 22 | PROCESSING_FAIL = 3000 23 | ) 24 | 25 | // 用户关系 26 | const ( 27 | // 未关注 28 | NOT_FOLLOWING = 0 29 | // 已关注 30 | FOLLOWED = 1 31 | // 互粉 32 | MUTUAL_FANS = 2 33 | ) 34 | 35 | const ( 36 | CONTENT_TYPE_VIDEO = 0 37 | CONTENT_TYPE_ARTICLE = 1 38 | ) 39 | -------------------------------------------------------------------------------- /server/internal/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "github.com/bwmarrin/snowflake" 5 | "gorm.io/gorm" 6 | "interastral-peace.com/alnitak/internal/config" 7 | "interastral-peace.com/alnitak/pkg/casbin" 8 | "interastral-peace.com/alnitak/pkg/oss" 9 | "interastral-peace.com/alnitak/pkg/redis" 10 | ) 11 | 12 | var ( 13 | Config *config.Config 14 | Mysql *gorm.DB 15 | Redis *redis.Redis 16 | Casbin *casbin.Casbin 17 | Storage oss.Storage 18 | SnowflakeNode *snowflake.Node 19 | VideoPartitionMap map[uint]uint 20 | ) 21 | -------------------------------------------------------------------------------- /server/internal/global/transcoding.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | type VideoInfo struct { 4 | Stream []Streams `json:"streams"` 5 | Format Format `json:"format"` 6 | } 7 | 8 | type Streams struct { 9 | CodecName string `json:"codec_name"` 10 | Width int `json:"width,omitempty"` 11 | Height int `json:"height,omitempty"` 12 | PixFmt string `json:"pix_fmt,omitempty"` 13 | Duration string `json:"duration"` 14 | RFrameRate string `json:"r_frame_rate,omitempty"` // 原始帧率 (如 "30/1") 15 | AvgFrameRate string `json:"avg_frame_rate,omitempty"` // 平均帧率 (如 "30/1") 16 | } 17 | 18 | type Format struct { 19 | BitRate string `json:"bit_rate"` 20 | } 21 | -------------------------------------------------------------------------------- /server/internal/initialize/snowflake.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "github.com/bwmarrin/snowflake" 5 | "interastral-peace.com/alnitak/internal/global" 6 | "interastral-peace.com/alnitak/utils" 7 | ) 8 | 9 | func InitSnowflake() { 10 | node, err := snowflake.NewNode(1) 11 | if err != nil { 12 | utils.ErrorLog("雪花ID初始化失败", "snowflake", err.Error()) 13 | } 14 | 15 | global.SnowflakeNode = node 16 | } 17 | -------------------------------------------------------------------------------- /server/internal/routes/api_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectApiRoutes(r *gin.RouterGroup) { 10 | 11 | apiGroup := r.Group("api") 12 | apiGroup.Use(middleware.Auth()) 13 | { 14 | // 获取API列表 15 | apiGroup.POST("getApiList", api.GetApiList) 16 | // 获取全部API列表 17 | apiGroup.GET("getAllApiList", api.GetAllApiList) 18 | // 新增API 19 | apiGroup.POST("addApi", api.AddApi) 20 | // 编辑API 21 | apiGroup.PUT("editApi", api.EditApi) 22 | // 删除API 23 | apiGroup.DELETE("deleteApi/:id", api.DeleteApi) 24 | // 获取角色API 25 | apiGroup.GET("getRoleApi", api.GetRoleApi) 26 | // 编辑角色API 27 | apiGroup.PUT("editRoleApi", api.EditRoleApi) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/internal/routes/auth_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectAuthRoutes(r *gin.RouterGroup) { 10 | 11 | authGroup := r.Group("auth") 12 | 13 | authAuth := authGroup.Group("") 14 | authAuth.Use(middleware.Auth()) 15 | { 16 | authAuth.POST("logout", api.Logout) 17 | } 18 | 19 | // 用户注册 20 | authGroup.POST("register", api.Register) 21 | // 用户登录(密码) 22 | authGroup.POST("login", api.Login) 23 | // 用户登录(邮箱) 24 | authGroup.POST("login/email", api.EmailLogin) 25 | // 更新token 26 | authGroup.POST("updateToken", api.UpdateToken) 27 | // 清除Cookie 28 | authGroup.POST("clearCookie", api.ClearCookie) 29 | // 修改密码检查 30 | authGroup.POST("resetpwdCheck", api.ResetPwdCheck) 31 | // 修改密码 32 | authGroup.POST("modifyPwd", api.ModifyPwd) 33 | } 34 | -------------------------------------------------------------------------------- /server/internal/routes/carousel_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectCarouselRoutes(r *gin.RouterGroup) { 10 | carouselGroup := r.Group("carousel") 11 | 12 | carouselAuth := carouselGroup.Group("") 13 | carouselAuth.Use(middleware.Auth()) 14 | { 15 | // 新增轮播图 16 | carouselAuth.POST("addCarousel", api.AddCarousel) 17 | // 获取轮播图列表 18 | carouselAuth.POST("getCarouselList", api.GetCarouselList) 19 | // 编辑轮播图信息 20 | carouselAuth.PUT("editCarousel", api.EditCarousel) 21 | // 删除轮播图 22 | carouselAuth.DELETE("deleteCarousel/:id", api.DeleteCarousel) 23 | } 24 | 25 | carouselGroup.GET("getCarousel", api.GetCarousel) 26 | } 27 | -------------------------------------------------------------------------------- /server/internal/routes/collection_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectCollectionRoutes(r *gin.RouterGroup) { 10 | collectionGroup := r.Group("collection") 11 | 12 | collectionAuth := collectionGroup.Group("") 13 | collectionAuth.Use(middleware.Auth()) 14 | { 15 | // 获取收藏夹列表 16 | collectionAuth.GET("getCollectionList", api.GetCollectionList) 17 | // 获取收藏夹信息 18 | collectionAuth.GET("getCollectionInfo", api.GetCollectionInfo) 19 | // 添加收藏夹 20 | collectionAuth.POST("addCollection", api.AddCollection) 21 | // 获取收藏夹的视频列表 22 | collectionAuth.GET("getVideoList", api.GetCollectVideo) 23 | // 修改收藏夹 24 | collectionAuth.PUT("editCollection", api.EditCollection) 25 | // 删除收藏夹 26 | collectionAuth.DELETE("deleteCollection/:id", api.DeleteCollection) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/internal/routes/config_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectConfigRoutes(r *gin.RouterGroup) { 10 | configGroup := r.Group("config") 11 | 12 | configAuth := configGroup.Group("") 13 | configAuth.Use(middleware.Auth()) 14 | { 15 | // 获取邮箱配置 16 | configAuth.GET("getEmailConfig", api.GetEmailConfig) 17 | // 修改邮箱配置 18 | configAuth.POST("setEmailConfig", api.SetEmailConfig) 19 | // 获取存储配置 20 | configAuth.GET("getStorageConfig", api.GetStorageConfig) 21 | // 修改存储配置 22 | configAuth.POST("setStorageConfig", api.SetStorageConfig) 23 | // 获取其他配置 24 | configAuth.GET("getOtherConfig", api.GetOtherConfig) 25 | // 修改其他配置 26 | configAuth.POST("setOtherConfig", api.SetOtherConfig) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /server/internal/routes/danmaku_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectDanmakuRoutes(r *gin.RouterGroup) { 10 | danmakuGroup := r.Group("danmaku") 11 | 12 | danmakuAuth := danmakuGroup.Group("") 13 | danmakuAuth.Use(middleware.Auth()) 14 | { 15 | // 发送弹幕 16 | danmakuAuth.POST("sendDanmaku", api.SendDanmaku) 17 | } 18 | 19 | // 获取弹幕列表 20 | danmakuGroup.GET("getDanmaku", api.GetDanmaku) 21 | } 22 | -------------------------------------------------------------------------------- /server/internal/routes/history_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectHistoryRoutes(r *gin.RouterGroup) { 10 | historyGroup := r.Group("history") 11 | 12 | historyAuth := historyGroup.Group("") 13 | historyAuth.Use(middleware.Auth()) 14 | { 15 | // 记录历史记录 16 | historyAuth.POST("video/addHistory", api.AddHistory) 17 | // 获取历史记录 18 | historyAuth.GET("video/getHistory", api.GetHistoryList) 19 | // 获取播放进度 20 | historyAuth.GET("video/getProgress", api.GetHistoryProgress) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/internal/routes/menu_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectMenuRoutes(r *gin.RouterGroup) { 10 | 11 | menuGroup := r.Group("menu") 12 | menuGroup.Use(middleware.Auth()) 13 | { 14 | // 获取菜单树 15 | menuGroup.GET("getMenuTree", api.GetMenuTree) 16 | // 获取用户菜单树 17 | menuGroup.GET("getUserMenu", api.GetUserMenuTree) 18 | // 新增菜单 19 | menuGroup.POST("addMenu", api.AddMenu) 20 | // 编辑菜单 21 | menuGroup.PUT("editMenu", api.EditMenu) 22 | // 删除菜单 23 | menuGroup.DELETE("deleteMenu/:id", api.DeleteMenu) 24 | // 获取角色菜单 25 | menuGroup.GET("getRoleMenu", api.GetRoleMenu) 26 | // 编辑角色菜单 27 | menuGroup.PUT("editRoleMenu", api.EditRoleMenu) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/internal/routes/online_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | ) 7 | 8 | func CollectOnlineRoutes(r *gin.RouterGroup) { 9 | onlineGroup := r.Group("online") 10 | 11 | // 视频Websocket连接(统计在线人数) 12 | onlineGroup.GET("video", api.GetVideoOnlineConnect) 13 | } 14 | -------------------------------------------------------------------------------- /server/internal/routes/partition_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectPartitionRoutes(route *gin.RouterGroup) { 10 | partitionGroup := route.Group("partition") 11 | 12 | //获取分区列表 13 | partitionGroup.GET("getPartitionList", api.GetPartitionList) 14 | 15 | partitionAuth := partitionGroup.Use(middleware.Auth()) 16 | { 17 | //添加分区 18 | partitionAuth.POST("addPartition", api.AddPartition) 19 | //删除分区 20 | partitionAuth.DELETE("deletePartition/:id", api.DeletePartition) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /server/internal/routes/relation_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectRelationRoutes(r *gin.RouterGroup) { 10 | relationGroup := r.Group("relation") 11 | 12 | relationAuth := relationGroup.Group("") 13 | relationAuth.Use(middleware.Auth()) 14 | { 15 | // 关注 16 | relationAuth.POST("follow", api.Follow) 17 | // 取消关注 18 | relationAuth.POST("unfollow", api.Unfollow) 19 | // 获取用户关系 20 | relationAuth.GET("getUserRelation", api.GetUserRelation) 21 | } 22 | 23 | // 获取关注列表 24 | relationGroup.GET("getFollowings", api.GetFollowings) 25 | // 获取粉丝列表 26 | relationGroup.GET("getFollowers", api.GetFollowers) 27 | // 获取关注和粉丝数 28 | relationGroup.GET("getFollowCount", api.GetFollowCount) 29 | } 30 | -------------------------------------------------------------------------------- /server/internal/routes/resource_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectResourceRoutes(r *gin.RouterGroup) { 10 | resourceGroup := r.Group("resource") 11 | 12 | resourceAuth := resourceGroup.Group("") 13 | resourceAuth.Use(middleware.Auth()) 14 | { 15 | resourceAuth.PUT("modifyTitle", api.ModifyResourceTitle) 16 | resourceAuth.DELETE("deleteResource/:id", api.DeleteResource) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/internal/routes/review_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectReviewRoutes(r *gin.RouterGroup) { 10 | reviewGroup := r.Group("review") 11 | 12 | reviewAuth := reviewGroup.Group("") 13 | reviewAuth.Use(middleware.Auth()) 14 | { 15 | // 视频审核通过 16 | reviewAuth.POST("reviewVideoApproved", api.ReviewVideoApproved) 17 | // 视频审核不通过 18 | reviewAuth.POST("reviewVideoFailed", api.ReviewVideoFailed) 19 | // 获取视频审核记录 20 | reviewAuth.GET("getVideoReviewRecord", api.GetVideoReviewRecord) 21 | 22 | // 文章审核通过 23 | reviewAuth.POST("reviewArticleApproved", api.ReviewArticleApproved) 24 | // 文章审核不通过 25 | reviewAuth.POST("reviewArticleFailed", api.ReviewArticleFailed) 26 | // 获取文章审核记录 27 | reviewAuth.GET("getArticleReviewRecord", api.GetArticleReviewRecord) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/internal/routes/role_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectRoleRoutes(r *gin.RouterGroup) { 10 | 11 | roleGroup := r.Group("role") 12 | roleGroup.Use(middleware.Auth()) 13 | { 14 | // 获取个人角色信息 15 | roleGroup.GET("getRoleInfo", api.GetRoleInfo) 16 | // 新增角色 17 | roleGroup.POST("addRole", api.AddRole) 18 | // 获取角色列表 19 | roleGroup.POST("getRoleList", api.GetRoleList) 20 | // 获取全部角色 21 | roleGroup.GET("getAllRoleList", api.GetAllRoleList) 22 | // 编辑角色 23 | roleGroup.PUT("editRole", api.EditRole) 24 | // 删除角色 25 | roleGroup.DELETE("deleteRole/:id", api.DeleteRole) 26 | // 编辑角色首页 27 | roleGroup.PUT("editRoleHome", api.EditRoleHome) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/internal/routes/upload_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectUploadRoutes(r *gin.RouterGroup) { 10 | 11 | uploadGroup := r.Group("upload") 12 | uploadGroup.Use(middleware.Auth()) 13 | { 14 | uploadGroup.POST("image", api.UploadImg) 15 | uploadGroup.POST("video/:vid", api.UploadVideoAdd) 16 | uploadGroup.POST("video", api.UploadVideoCreate) 17 | uploadGroup.POST("checkVideo", api.UploadVideoCheck) 18 | uploadGroup.POST("chunkVideo", api.UploadVideoChunk) // 分片上传视频 19 | uploadGroup.POST("mergeVideo", api.UploadVideoMerge) // 合并视频分片 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/internal/routes/user_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | "interastral-peace.com/alnitak/internal/middleware" 7 | ) 8 | 9 | func CollectUserRoutes(r *gin.RouterGroup) { 10 | userGroup := r.Group("user") 11 | // 获取用户基本信息 12 | userGroup.GET("getUserBaseInfo", api.GetUserBaseInfo) 13 | 14 | userAuth := userGroup.Use(middleware.Auth()) 15 | { 16 | // 用户获取个人信息 17 | userAuth.GET("getUserInfo", api.GetUserInfo) 18 | // 用户编辑个人信息 19 | userAuth.PUT("editUserInfo", api.EditUserInfo) 20 | // 获取用户列表(后台管理) 21 | userAuth.POST("getUserListManage", api.GetUserListManage) 22 | // 编辑用户信息(后台管理) 23 | userAuth.PUT("editUserInfoManage", api.EditUserInfoManage) 24 | // 设置用户角色 25 | userAuth.PUT("editUserRole", api.EditUserRole) 26 | // 删除用户 27 | userAuth.DELETE("deleteUser/:id", api.DeleteUser) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/internal/routes/verify_router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "interastral-peace.com/alnitak/internal/api/v1" 6 | ) 7 | 8 | func CollectVerifyRoutes(r *gin.RouterGroup) { 9 | 10 | verifyGroup := r.Group("verify") 11 | { 12 | // 获取滑块验证 13 | verifyGroup.GET("captcha/get", api.GetSliderCaptcha) 14 | // 验证滑块 15 | verifyGroup.POST("captcha/validate", api.ValidateSlider) 16 | // 获取邮箱验证码 17 | verifyGroup.POST("getEmailCode", api.SendRegisterEmailCode) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /server/internal/service/comment.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "interastral-peace.com/alnitak/internal/domain/model" 5 | "interastral-peace.com/alnitak/internal/global" 6 | ) 7 | 8 | func FindCommentById(id uint) (comment model.Comment, err error) { 9 | err = global.Mysql.First(&comment, id).Error 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /server/internal/service/operate.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "interastral-peace.com/alnitak/internal/domain/model" 5 | "interastral-peace.com/alnitak/internal/global" 6 | ) 7 | 8 | func AddOperate(operate *model.Operate) error { 9 | return global.Mysql.Create(&operate).Error 10 | } 11 | -------------------------------------------------------------------------------- /server/logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/logs/.gitkeep -------------------------------------------------------------------------------- /server/pkg/jigsaw/jigsaw.go: -------------------------------------------------------------------------------- 1 | package jigsaw 2 | 3 | import "github.com/wangzmgit/jigsaw" 4 | 5 | func Jigsaw() { 6 | j := jigsaw.New() 7 | j.SetBgDir("./static/jigsaw/bg/") 8 | j.SetMaskPath("./static/jigsaw/mask.png") 9 | } 10 | -------------------------------------------------------------------------------- /server/pkg/mysql/mysql.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "fmt" 5 | 6 | "go.uber.org/zap" 7 | "gorm.io/driver/mysql" 8 | "gorm.io/gorm" 9 | "interastral-peace.com/alnitak/internal/config" 10 | "interastral-peace.com/alnitak/utils" 11 | "moul.io/zapgorm2" 12 | ) 13 | 14 | var db *gorm.DB 15 | 16 | func Init(c config.Mysql) *gorm.DB { 17 | dns := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s", c.Username, c.Password, c.Host, c.Port, c.Datasource, c.Param) 18 | logger := zapgorm2.New(zap.L()) 19 | logger.SetAsDefault() 20 | if mysqlClient, err := gorm.Open(mysql.Open(dns), &gorm.Config{Logger: logger}); err != nil { 21 | utils.ErrorLog("mysql连接失败", "db", err.Error()) 22 | panic(err) 23 | } else { 24 | zap.L().Info("mysql连接成功", zap.String("module", "db")) 25 | db = mysqlClient 26 | return mysqlClient 27 | } 28 | } 29 | 30 | func GetMysqlClient() *gorm.DB { 31 | return db 32 | } 33 | -------------------------------------------------------------------------------- /server/pkg/oss/config.go: -------------------------------------------------------------------------------- 1 | package oss 2 | 3 | type Config struct { 4 | KeyID string //(必填,对应阿里云access_key_id、腾讯云secret_id,七牛云accessKey) 5 | KeySecret string //(必填,对应阿里云access_key_secret、腾讯云secret_key,七牛云secretKey) 6 | Bucket string 7 | 8 | Endpoint string //阿里云必填 9 | 10 | AppID string //腾讯云必填 11 | Region string //腾讯云必填 12 | 13 | Domain string //域名 14 | 15 | Private bool //是否私有 (仅七牛云) 16 | } 17 | -------------------------------------------------------------------------------- /server/static/casbin/model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [role_definition] 8 | g = _, _ 9 | 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | [matchers] 14 | m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && (r.act == p.act || p.act == "*") -------------------------------------------------------------------------------- /server/static/jigsaw/bg/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/0.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/1.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/10.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/2.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/3.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/4.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/5.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/6.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/7.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/8.png -------------------------------------------------------------------------------- /server/static/jigsaw/bg/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/bg/9.png -------------------------------------------------------------------------------- /server/static/jigsaw/mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/static/jigsaw/mask.png -------------------------------------------------------------------------------- /server/upload/image/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/upload/image/.gitkeep -------------------------------------------------------------------------------- /server/upload/video/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wangzmgit/alnitak/b2981fa5d93bab5d1efd85df0425c3b2d20637d1/server/upload/video/.gitkeep -------------------------------------------------------------------------------- /server/utils/cmd.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "os/exec" 7 | ) 8 | 9 | func RunCmd(cmd *exec.Cmd) (bytes.Buffer, error) { 10 | var out bytes.Buffer 11 | var stderr bytes.Buffer 12 | cmd.Stdout = &out 13 | cmd.Stderr = &stderr 14 | if err := cmd.Run(); err != nil { 15 | return out, errors.New(stderr.String()) 16 | } 17 | 18 | return out, nil 19 | } 20 | -------------------------------------------------------------------------------- /server/utils/convert.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | ) 7 | 8 | func UintToString(n uint) string { 9 | return strconv.FormatUint(uint64(n), 10) 10 | } 11 | 12 | func StringToInt(v string) int { 13 | res, err := strconv.Atoi(v) 14 | if err != nil { 15 | return 0 16 | } 17 | return res 18 | } 19 | 20 | func StringToUint(v string) uint { 21 | return uint(StringToInt(v)) 22 | } 23 | 24 | func MapToJson(param map[string]interface{}) string { 25 | dataType, _ := json.Marshal(param) 26 | dataString := string(dataType) 27 | return dataString 28 | } 29 | -------------------------------------------------------------------------------- /server/utils/file.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "os" 4 | 5 | // 判断文件是否存在 6 | func IsFileExists(path string) bool { 7 | if _, err := os.Stat(path); os.IsNotExist(err) { 8 | return false 9 | } 10 | return true 11 | } 12 | -------------------------------------------------------------------------------- /server/utils/log.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "go.uber.org/zap" 4 | 5 | func ErrorLog(msg, module, err string) { 6 | zap.L().Error(msg, zap.String("module", module), zap.String("err", err)) 7 | } 8 | 9 | func InfoLog(msg, module string) { 10 | zap.L().Info(msg, zap.String("module", module)) 11 | } 12 | -------------------------------------------------------------------------------- /server/utils/math.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | type OrderedType interface { 4 | int | float32 | float64 5 | } 6 | 7 | func Max[T OrderedType](x, y T) T { 8 | if x > y { 9 | return x 10 | } 11 | 12 | return y 13 | } 14 | 15 | func Min[T OrderedType](x, y T) T { 16 | if x > y { 17 | return y 18 | } 19 | 20 | return x 21 | } 22 | 23 | // 取差集 24 | func DifferenceSet(a []uint, b []uint) []uint { 25 | var c []uint 26 | temp := map[uint]struct{}{} 27 | 28 | for _, val := range b { 29 | if _, ok := temp[val]; !ok { 30 | temp[val] = struct{}{} 31 | } 32 | } 33 | 34 | for _, val := range a { 35 | if _, ok := temp[val]; !ok { 36 | c = append(c, val) 37 | } 38 | } 39 | 40 | return c 41 | } 42 | -------------------------------------------------------------------------------- /server/utils/md5.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "io" 7 | ) 8 | 9 | func GenerateSaltedMD5(input string, salt string) string { 10 | saltedInput := input + salt 11 | hasher := md5.New() 12 | io.WriteString(hasher, saltedInput) 13 | return hex.EncodeToString(hasher.Sum(nil)) 14 | } 15 | -------------------------------------------------------------------------------- /server/utils/random.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/rand" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | // 生成n位数字随机码 10 | func GenerateNumberCode(length int) string { 11 | res := "" 12 | rand.Seed(time.Now().UnixNano()) 13 | // 生成 4 个 [0, 9) 范围的真随机数。 14 | for i := 0; i < length; i++ { 15 | num := rand.Intn(10) 16 | res += strconv.Itoa(num) 17 | } 18 | return res 19 | } 20 | -------------------------------------------------------------------------------- /server/utils/slice.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func SlicePagingStr(s []string, page, pageSize int) []string { 4 | startIdx := (page - 1) * pageSize 5 | endIdx := startIdx + pageSize 6 | if startIdx >= len(s) { 7 | return nil 8 | } 9 | if endIdx > len(s) { 10 | endIdx = len(s) 11 | } 12 | 13 | return s[startIdx:endIdx] 14 | } 15 | 16 | func IsUintInSlice(nums []uint, target uint) bool { 17 | for _, num := range nums { 18 | if num == target { 19 | return true 20 | } 21 | } 22 | return false 23 | } 24 | -------------------------------------------------------------------------------- /server/utils/strings.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func IntJoin(elems []int, sep string) string { 9 | str := make([]string, len(elems)) 10 | for i, v := range elems { 11 | str[i] = fmt.Sprint(v) 12 | } 13 | 14 | return strings.Join(str, ",") 15 | } 16 | 17 | func UintJoin(elems []uint, sep string) string { 18 | str := make([]string, len(elems)) 19 | for i, v := range elems { 20 | str[i] = fmt.Sprint(v) 21 | } 22 | 23 | return strings.Join(str, ",") 24 | } 25 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 1.2.2 -------------------------------------------------------------------------------- /web/admin-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 | } 15 | -------------------------------------------------------------------------------- /web/admin-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 | .vitepress/cache 12 | dist 13 | dist-ssr 14 | admin 15 | *.local 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | 28 | -------------------------------------------------------------------------------- /web/admin-client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.23.3-alpine 2 | 3 | WORKDIR /web 4 | COPY . . 5 | 6 | # 移除nginx容器的default.conf文件、nginx配置文件 7 | RUN rm /etc/nginx/conf.d/default.conf \ 8 | && rm /etc/nginx/nginx.conf \ 9 | && mkdir /etc/nginx/logs \ 10 | && touch /etc/nginx/logs/error.log 11 | 12 | # 把主机的nginx.conf文件复制到nginx容器的/etc/nginx文件夹下 13 | COPY ./nginx.conf /etc/nginx/ 14 | 15 | # 拷贝前端vue项目打包后生成的文件到nginx下运行 16 | COPY ./admin /usr/share/nginx/html/admin 17 | 18 | # 暴露端口 19 | EXPOSE 9030 20 | 21 | # 使用daemon off的方式将nginx运行在前台保证镜像不至于退出 22 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /web/admin-client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |