├── frontend ├── .browserslistrc ├── .env ├── babel.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ ├── logo.png │ │ ├── image │ │ │ └── freefish.png │ │ ├── plugins │ │ │ └── font-awesome-4.7.0 │ │ │ │ └── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ └── app.css │ ├── store │ │ ├── getters.js │ │ ├── index.js │ │ └── modules │ │ │ └── user.js │ ├── api │ │ ├── tip.js │ │ ├── billboard.js │ │ ├── promote.js │ │ ├── tag.js │ │ ├── search.js │ │ ├── comment.js │ │ ├── follow.js │ │ ├── user.js │ │ ├── auth │ │ │ └── auth.js │ │ └── post.js │ ├── utils │ │ ├── get-page-title.js │ │ ├── auth.js │ │ ├── scroll-to.js │ │ └── request.js │ ├── views │ │ ├── card │ │ │ ├── CardBar.vue │ │ │ ├── Promotion.vue │ │ │ ├── Tip.vue │ │ │ └── LoginWelcome.vue │ │ ├── error │ │ │ └── 404.vue │ │ ├── Home.vue │ │ ├── post │ │ │ ├── Recommend.vue │ │ │ ├── Author.vue │ │ │ ├── Edit.vue │ │ │ ├── Create.vue │ │ │ └── Detail.vue │ │ ├── tag │ │ │ └── Tag.vue │ │ ├── auth │ │ │ ├── Login.vue │ │ │ └── Register.vue │ │ ├── Search.vue │ │ └── user │ │ │ ├── Profile.vue │ │ │ └── Setting.vue │ ├── user.js │ ├── components │ │ ├── Backtop │ │ │ └── BackTop.vue │ │ ├── Comment │ │ │ ├── CommentsItem.vue │ │ │ ├── Comments.vue │ │ │ └── CommentsForm.vue │ │ ├── Layout │ │ │ ├── Footer.vue │ │ │ └── Header.vue │ │ └── Pagination │ │ │ └── index.vue │ ├── App.vue │ ├── main.js │ ├── permission.js │ └── router │ │ └── index.js ├── vue.config.js ├── Dockerfile ├── package.json └── docker │ └── nginx.conf ├── backend ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.yml │ │ │ ├── mapper │ │ │ │ ├── BmsPostTagMapper.xml │ │ │ │ ├── BmsTipMapper.xml │ │ │ │ ├── BmsCommentMapper.xml │ │ │ │ └── BmsPostMapper.xml │ │ │ ├── application-prod.yml │ │ │ └── application-dev.yml │ │ └── java │ │ │ └── com │ │ │ └── which │ │ │ └── freefish │ │ │ ├── controller │ │ │ ├── BaseController.java │ │ │ ├── BmsTipController.java │ │ │ ├── BmsPromotionController.java │ │ │ ├── BmsBillboardController.java │ │ │ ├── BmsSearchController.java │ │ │ ├── BmsCommentController.java │ │ │ ├── BmsTagController.java │ │ │ ├── admin │ │ │ │ ├── PostManageController.java │ │ │ │ └── UserManageController.java │ │ │ ├── UmsUserController.java │ │ │ ├── BmsRelationshipController.java │ │ │ └── BmsPostController.java │ │ │ ├── service │ │ │ ├── IBmsFollowService.java │ │ │ ├── IBmsBillboardService.java │ │ │ ├── IBmsPromotionService.java │ │ │ ├── IBmsTipService.java │ │ │ ├── impl │ │ │ │ ├── IBmsFollowServiceImpl.java │ │ │ │ ├── IBmsPromotionServiceImpl.java │ │ │ │ ├── IBmsBillboardServiceImpl.java │ │ │ │ ├── IBmsTipServiceImpl.java │ │ │ │ ├── IBmsCommentServiceImpl.java │ │ │ │ ├── IBmsTopicTagServiceImpl.java │ │ │ │ ├── IBmsTagServiceImpl.java │ │ │ │ ├── IUmsUserServiceImpl.java │ │ │ │ └── IBmsPostServiceImpl.java │ │ │ ├── IBmsCommentService.java │ │ │ ├── IBmsTagService.java │ │ │ ├── IBmsTopicTagService.java │ │ │ ├── IUmsUserService.java │ │ │ └── IBmsPostService.java │ │ │ ├── mapper │ │ │ ├── BmsPostMapper.java │ │ │ ├── BmsFollowMapper.java │ │ │ ├── BmsTagMapper.java │ │ │ ├── BmsBillboardMapper.java │ │ │ ├── BmsPromotionMapper.java │ │ │ ├── BmsTipMapper.java │ │ │ ├── UmsUserMapper.java │ │ │ ├── BmsTopicTagMapper.java │ │ │ ├── BmsCommentMapper.java │ │ │ └── BmsTopicMapper.java │ │ │ ├── common │ │ │ ├── api │ │ │ │ ├── IErrorCode.java │ │ │ │ ├── ApiErrorCode.java │ │ │ │ └── ApiResult.java │ │ │ ├── exception │ │ │ │ ├── ApiAsserts.java │ │ │ │ ├── ApiException.java │ │ │ │ └── GlobalExceptionHandler.java │ │ │ └── mybatisplus │ │ │ │ └── MybatisPlusConfig.java │ │ │ ├── model │ │ │ ├── dto │ │ │ │ ├── CommentDTO.java │ │ │ │ ├── CreateTopicDTO.java │ │ │ │ ├── LoginDTO.java │ │ │ │ └── RegisterDTO.java │ │ │ ├── vo │ │ │ │ ├── CommentVO.java │ │ │ │ ├── ProfileVO.java │ │ │ │ ├── UserVO.java │ │ │ │ └── PostVO.java │ │ │ └── entity │ │ │ │ ├── BmsTip.java │ │ │ │ ├── BmsTopicTag.java │ │ │ │ ├── BmsFollow.java │ │ │ │ ├── BmsTag.java │ │ │ │ ├── BmsPromotion.java │ │ │ │ ├── BmsBillboard.java │ │ │ │ ├── BmsComment.java │ │ │ │ ├── UmsUser.java │ │ │ │ └── BmsPost.java │ │ │ ├── config │ │ │ ├── RedisConfiguration.java │ │ │ └── GlobalWebMvcConfiguration.java │ │ │ ├── MainApplication.java │ │ │ ├── utils │ │ │ └── MD5Utils.java │ │ │ └── jwt │ │ │ ├── JwtAuthenticationFilter.java │ │ │ └── JwtUtil.java │ └── test │ │ └── java │ │ └── com │ │ └── which │ │ └── freefish │ │ └── MainApplicationTests.java ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ ├── maven-wrapper.properties │ │ └── MavenWrapperDownloader.java ├── Dockerfile └── pom.xml ├── doc ├── pl.png ├── dark.png ├── home.png ├── login.png ├── logo.png ├── post.png ├── post_f.png ├── post_m.png ├── search.png ├── update.png ├── user_m.png ├── all_post.png └── register.png ├── .gitignore └── README.md /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | # 后端服务端地址 2 | VUE_APP_SERVER_URL = 'http://localhost:9001' -------------------------------------------------------------------------------- /backend/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev -------------------------------------------------------------------------------- /doc/pl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/pl.png -------------------------------------------------------------------------------- /doc/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/dark.png -------------------------------------------------------------------------------- /doc/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/home.png -------------------------------------------------------------------------------- /doc/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/login.png -------------------------------------------------------------------------------- /doc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/logo.png -------------------------------------------------------------------------------- /doc/post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/post.png -------------------------------------------------------------------------------- /doc/post_f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/post_f.png -------------------------------------------------------------------------------- /doc/post_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/post_m.png -------------------------------------------------------------------------------- /doc/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/search.png -------------------------------------------------------------------------------- /doc/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/update.png -------------------------------------------------------------------------------- /doc/user_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/user_m.png -------------------------------------------------------------------------------- /doc/all_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/all_post.png -------------------------------------------------------------------------------- /doc/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/doc/register.png -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | // 前端启动端口 4 | port: 3001 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /backend/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/backend/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /frontend/src/assets/image/freefish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/frontend/src/assets/image/freefish.png -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/BaseController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | public class BaseController { 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | token: state => state.user.token, // token 3 | user: state => state.user.user, // 用户对象 4 | } 5 | export default getters -------------------------------------------------------------------------------- /frontend/src/api/tip.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getTodayTip() { 4 | return request({ 5 | url: '/tip/today', 6 | method: 'get' 7 | }) 8 | } -------------------------------------------------------------------------------- /frontend/src/api/billboard.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getBillboard() { 4 | return request({ 5 | url: '/billboard/show', 6 | method: 'get' 7 | }) 8 | } -------------------------------------------------------------------------------- /frontend/src/assets/plugins/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/frontend/src/assets/plugins/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /frontend/src/api/promote.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 获取友情链接 4 | export function getList() { 5 | return request(({ 6 | url: '/promotion/all', 7 | method: 'get' 8 | })) 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/assets/plugins/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/frontend/src/assets/plugins/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /frontend/src/assets/plugins/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/frontend/src/assets/plugins/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /frontend/src/assets/plugins/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/frontend/src/assets/plugins/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /frontend/src/assets/plugins/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/which0113/community-post-manage/HEAD/frontend/src/assets/plugins/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /frontend/src/utils/get-page-title.js: -------------------------------------------------------------------------------- 1 | const title = '咸鱼社区' 2 | 3 | export default function getPageTitle(pageTitle) { 4 | if (pageTitle) { 5 | return `${pageTitle} - ${title}` 6 | } 7 | return `${title}` 8 | } 9 | -------------------------------------------------------------------------------- /backend/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /frontend/src/api/tag.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getTopicsByTag(paramMap) { 4 | return request({ 5 | url: '/tag/' + paramMap.name, 6 | method: 'get', 7 | params: { 8 | page: paramMap.page, 9 | size: paramMap.size 10 | } 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/IBmsFollowService.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.which.freefish.model.entity.BmsFollow; 5 | 6 | 7 | public interface IBmsFollowService extends IService { 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/IBmsBillboardService.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.which.freefish.model.entity.BmsBillboard; 5 | 6 | public interface IBmsBillboardService extends IService { 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | import user from './modules/user' 5 | 6 | Vue.use(Vuex) 7 | 8 | const store = new Vuex.Store({ 9 | modules: { 10 | user 11 | }, 12 | getters 13 | }) 14 | 15 | export default store -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/IBmsPromotionService.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.which.freefish.model.entity.BmsPromotion; 5 | 6 | 7 | public interface IBmsPromotionService extends IService { 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/IBmsTipService.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.which.freefish.model.entity.BmsTip; 5 | 6 | public interface IBmsTipService extends IService { 7 | BmsTip getRandomTip(); 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/test/java/com/which/freefish/MainApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class MainApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/api/search.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 关键词检索 4 | export function searchByKeyword(query) { 5 | return request({ 6 | url: `/search`, 7 | method: 'get', 8 | params: { 9 | keyword: query.keyword, 10 | pageNum: query.pageNum, 11 | pageSize: query.pageSize 12 | } 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/BmsPostMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.which.freefish.model.entity.BmsPost; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface BmsPostMapper extends BaseMapper { 9 | 10 | } -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker 镜像构建 2 | FROM maven:3.5-jdk-8-alpine as builder 3 | 4 | WORKDIR /app 5 | 6 | COPY backend-0.0.1-SNAPSHOT.jar . 7 | 8 | CMD ["java","-jar","backend-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"] 9 | 10 | # 构建:docker build -t freefish-community-backend:v0.0.1 . 11 | # 运行:docker run -p 9001:9001 -d freefish-community-backend:v0.0.1 -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/BmsFollowMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.which.freefish.model.entity.BmsFollow; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BmsFollowMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/BmsTagMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.which.freefish.model.entity.BmsTag; 5 | import org.springframework.stereotype.Repository; 6 | 7 | 8 | @Repository 9 | public interface BmsTagMapper extends BaseMapper { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/BmsBillboardMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.which.freefish.model.entity.BmsBillboard; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BmsBillboardMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/common/api/IErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.common.api; 2 | 3 | 4 | public interface IErrorCode { 5 | /** 6 | * 错误编码: -1失败;200成功 7 | * 8 | * @return 错误编码 9 | */ 10 | Integer getCode(); 11 | 12 | /** 13 | * 错误描述 14 | * 15 | * @return 错误描述 16 | */ 17 | String getMessage(); 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/BmsPromotionMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.which.freefish.model.entity.BmsPromotion; 5 | import org.springframework.stereotype.Repository; 6 | 7 | 8 | @Repository 9 | public interface BmsPromotionMapper extends BaseMapper { 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/BmsTipMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.which.freefish.model.entity.BmsTip; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface BmsTipMapper extends BaseMapper { 9 | BmsTip getRandomTip(); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | WORKDIR /usr/share/nginx/html/ 4 | USER root 5 | 6 | COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf 7 | 8 | COPY ./dist /usr/share/nginx/html/ 9 | 10 | EXPOSE 80 11 | 12 | CMD ["nginx", "-g", "daemon off;"] 13 | 14 | # 构建:docker build -t freefish-community-frontend:v0.0.1 . 15 | # 运行:docker run -p 80:80 -d freefish-community-frontend:v0.0.1 -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/UmsUserMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.which.freefish.model.entity.UmsUser; 5 | import org.springframework.stereotype.Repository; 6 | 7 | /** 8 | * 用户 9 | * 10 | * @author Knox 2020/11/7 11 | */ 12 | @Repository 13 | public interface UmsUserMapper extends BaseMapper { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/api/comment.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function fetchCommentsByTopicId(topic_Id) { 4 | return request({ 5 | url: '/comment/get_comments', 6 | method: 'get', 7 | params: { 8 | topicid: topic_Id 9 | } 10 | }) 11 | } 12 | 13 | export function pushComment(data) { 14 | return request({ 15 | url: '/comment/add_comment', 16 | method: 'post', 17 | data: data 18 | }) 19 | } 20 | 21 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/dto/CommentDTO.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | 8 | @Data 9 | public class CommentDTO implements Serializable { 10 | private static final long serialVersionUID = -5957433707110390852L; 11 | 12 | 13 | private String topic_id; 14 | 15 | /** 16 | * 内容 17 | */ 18 | private String content; 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/resources/mapper/BmsPostTagMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /backend/src/main/resources/mapper/BmsTipMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/vo/CommentVO.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | 8 | @Data 9 | public class CommentVO { 10 | 11 | private String id; 12 | 13 | private String content; 14 | 15 | private String topicId; 16 | 17 | private String userId; 18 | 19 | private String username; 20 | 21 | private String alias; 22 | 23 | private Date createTime; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/impl/IBmsFollowServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.which.freefish.mapper.BmsFollowMapper; 5 | import com.which.freefish.model.entity.BmsFollow; 6 | import com.which.freefish.service.IBmsFollowService; 7 | import org.springframework.stereotype.Service; 8 | 9 | 10 | @Service 11 | public class IBmsFollowServiceImpl extends ServiceImpl implements IBmsFollowService { 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9001 3 | spring: 4 | datasource: 5 | driver-class-name: com.mysql.cj.jdbc.Driver 6 | username: your_database 7 | password: your_pwd 8 | url: jdbc:mysql://your_addr:3306/freefish?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=GMT%2B8 9 | type: com.zaxxer.hikari.HikariDataSource 10 | redis: 11 | host: localhost 12 | port: 6379 13 | password: 123456 14 | database: 0 15 | logging: 16 | level: 17 | root: info 18 | com.which.freefish: info -------------------------------------------------------------------------------- /frontend/src/api/follow.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 关注 4 | export function follow(id) { 5 | return request(({ 6 | url: `/relationship/subscribe/${id}`, 7 | method: 'get' 8 | })) 9 | } 10 | 11 | // 关注 12 | export function unFollow(id) { 13 | return request(({ 14 | url: `/relationship/unsubscribe/${id}`, 15 | method: 'get' 16 | })) 17 | } 18 | 19 | // 验证是否关注 20 | export function hasFollow(topicUserId) { 21 | return request(({ 22 | url: `/relationship/validate/${topicUserId}`, 23 | method: 'get' 24 | })) 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9001 3 | spring: 4 | datasource: 5 | driver-class-name: com.mysql.cj.jdbc.Driver 6 | username: root 7 | password: 123456 8 | url: jdbc:mysql://localhost:3306/freefish?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=GMT%2B8 9 | type: com.zaxxer.hikari.HikariDataSource 10 | redis: 11 | host: localhost 12 | port: 6379 13 | password: 123456 14 | database: 0 15 | logging: 16 | level: 17 | root: info 18 | com.which.freefish: debug 19 | 20 | 21 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/impl/IBmsPromotionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.which.freefish.mapper.BmsPromotionMapper; 5 | import com.which.freefish.model.entity.BmsPromotion; 6 | import com.which.freefish.service.IBmsPromotionService; 7 | import org.springframework.stereotype.Service; 8 | 9 | 10 | @Service 11 | public class IBmsPromotionServiceImpl extends ServiceImpl implements IBmsPromotionService { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/views/card/CardBar.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | 25 | 27 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/impl/IBmsBillboardServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.which.freefish.mapper.BmsBillboardMapper; 5 | import com.which.freefish.model.entity.BmsBillboard; 6 | import com.which.freefish.service.IBmsBillboardService; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class IBmsBillboardServiceImpl extends ServiceImpl implements IBmsBillboardService { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/dto/CreateTopicDTO.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | 8 | @Data 9 | public class CreateTopicDTO implements Serializable { 10 | private static final long serialVersionUID = -5957433707110390852L; 11 | 12 | /** 13 | * 标题 14 | */ 15 | private String title; 16 | 17 | /** 18 | * 内容 19 | */ 20 | private String content; 21 | 22 | /** 23 | * 标签 24 | */ 25 | private List tags; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/dto/LoginDTO.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.dto; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.NotBlank; 6 | import javax.validation.constraints.Size; 7 | 8 | 9 | @Data 10 | public class LoginDTO { 11 | 12 | @NotBlank(message = "用户名不能为空") 13 | @Size(min = 2, max = 15, message = "登录用户名长度在2-15") 14 | private String username; 15 | 16 | @NotBlank(message = "密码不能为空") 17 | @Size(min = 6, max = 20, message = "登录密码长度在6-20") 18 | private String password; 19 | 20 | private Boolean rememberMe; 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/common/exception/ApiAsserts.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.common.exception; 2 | 3 | import com.which.freefish.common.api.IErrorCode; 4 | 5 | 6 | public class ApiAsserts { 7 | /** 8 | * 抛失败异常 9 | * 10 | * @param message 说明 11 | */ 12 | public static void fail(String message) { 13 | throw new ApiException(message); 14 | } 15 | 16 | /** 17 | * 抛失败异常 18 | * 19 | * @param errorCode 状态码 20 | */ 21 | public static void fail(IErrorCode errorCode) { 22 | throw new ApiException(errorCode); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/common/exception/ApiException.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.common.exception; 2 | 3 | import com.which.freefish.common.api.IErrorCode; 4 | 5 | 6 | public class ApiException extends RuntimeException { 7 | private IErrorCode errorCode; 8 | 9 | public ApiException(IErrorCode errorCode) { 10 | super(errorCode.getMessage()); 11 | this.errorCode = errorCode; 12 | } 13 | 14 | public ApiException(String message) { 15 | super(message); 16 | } 17 | 18 | public IErrorCode getErrorCode() { 19 | return errorCode; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 用户主页 4 | export function getInfoByName(username, page, size) { 5 | return request({ 6 | url: '/ums/user/' + username, 7 | method: 'get', 8 | params: { 9 | pageNo: page, 10 | size: size 11 | } 12 | }) 13 | } 14 | // 用户主页 15 | export function getInfo() { 16 | return request({ 17 | url: '/ums/user/info', 18 | method: 'get' 19 | }) 20 | } 21 | // 更新 22 | export function update(user) { 23 | return request({ 24 | url: '/ums/user/update', 25 | method: 'post', 26 | data: user 27 | }) 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/src/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 用户主页 4 | export function getInfoByName(username, page, size) { 5 | return request({ 6 | url: '/ums/user/' + username, 7 | method: 'get', 8 | params: { 9 | pageNo: page, 10 | size: size 11 | } 12 | }) 13 | } 14 | 15 | // 用户主页 16 | export function getInfo() { 17 | return request({ 18 | url: '/ums/user/info', 19 | method: 'get' 20 | }) 21 | } 22 | 23 | // 更新 24 | export function update(user) { 25 | return request({ 26 | url: '/ums/user/update', 27 | method: 'post', 28 | data: user 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/BmsTopicTagMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.which.freefish.model.entity.BmsTopicTag; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.Set; 9 | 10 | 11 | @Repository 12 | public interface BmsTopicTagMapper extends BaseMapper { 13 | /** 14 | * 根据标签获取话题ID集合 15 | * 16 | * @param id 17 | * @return 18 | */ 19 | Set getTopicIdsByTagId(@Param("id") String id); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/components/Backtop/BackTop.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 25 | 26 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | 27 | 32 | -------------------------------------------------------------------------------- /frontend/src/api/auth/auth.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 注册 4 | export function userRegister(userDTO) { 5 | return request({ 6 | url: '/ums/user/register', 7 | method: 'post', 8 | data: userDTO 9 | }) 10 | } 11 | 12 | // 前台用户登录 13 | export function login(data) { 14 | return request({ 15 | url: '/ums/user/login', 16 | method: 'post', 17 | data 18 | }) 19 | } 20 | // 登录后获取前台用户信息 21 | export function getUserInfo() { 22 | return request({ 23 | url: '/ums/user/info', 24 | method: 'get' 25 | }) 26 | } 27 | // 前台用户注销 28 | export function logout() { 29 | return request({ 30 | url: '/ums/user/logout' 31 | }) 32 | } -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/BmsCommentMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.which.freefish.model.entity.BmsComment; 5 | import com.which.freefish.model.vo.CommentVO; 6 | import org.apache.ibatis.annotations.Param; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | 12 | @Repository 13 | public interface BmsCommentMapper extends BaseMapper { 14 | 15 | /** 16 | * getCommentsByTopicID 17 | * 18 | * @param topicid 19 | * @return 20 | */ 21 | List getCommentsByTopicID(@Param("topicid") String topicid); 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/IBmsCommentService.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.which.freefish.model.dto.CommentDTO; 5 | import com.which.freefish.model.entity.BmsComment; 6 | import com.which.freefish.model.entity.UmsUser; 7 | import com.which.freefish.model.vo.CommentVO; 8 | 9 | import java.util.List; 10 | 11 | 12 | public interface IBmsCommentService extends IService { 13 | /** 14 | * @param topicid 15 | * @return {@link BmsComment} 16 | */ 17 | List getCommentsByTopicID(String topicid); 18 | 19 | BmsComment create(CommentDTO dto, UmsUser principal); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%= htmlWebpackPlugin.options.title %> 11 | 12 | 13 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const uToken = 'u_token' 4 | const darkMode = 'dark_mode'; 5 | 6 | // 获取Token 7 | export function getToken() { 8 | return Cookies.get(uToken); 9 | } 10 | 11 | // 设置Token,1天,与后端同步 12 | export function setToken(token) { 13 | return Cookies.set(uToken, token, {expires: 1}) 14 | } 15 | 16 | // 删除Token 17 | export function removeToken() { 18 | return Cookies.remove(uToken) 19 | } 20 | 21 | export function removeAll() { 22 | return Cookies.Cookies.removeAll() 23 | } 24 | 25 | export function setDarkMode(mode) { 26 | return Cookies.set(darkMode, mode, {expires: 365}) 27 | } 28 | 29 | export function getDarkMode() { 30 | return !(undefined === Cookies.get(darkMode) || 'false' === Cookies.get(darkMode)); 31 | } 32 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/IBmsTagService.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.which.freefish.model.entity.BmsPost; 6 | import com.which.freefish.model.entity.BmsTag; 7 | 8 | import java.util.List; 9 | 10 | 11 | public interface IBmsTagService extends IService { 12 | /** 13 | * 插入标签 14 | * 15 | * @param tags 16 | * @return 17 | */ 18 | List insertTags(List tags); 19 | 20 | /** 21 | * 获取标签关联话题 22 | * 23 | * @param topicPage 24 | * @param id 25 | * @return 26 | */ 27 | Page selectTopicsByTagId(Page topicPage, String id); 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/components/Comment/CommentsItem.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/dto/RegisterDTO.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.dto; 2 | 3 | import lombok.Data; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.Email; 7 | import javax.validation.constraints.NotEmpty; 8 | 9 | 10 | @Data 11 | public class RegisterDTO { 12 | 13 | @NotEmpty(message = "请输入账号") 14 | @Length(min = 2, max = 15, message = "长度在2-15") 15 | private String name; 16 | 17 | @NotEmpty(message = "请输入密码") 18 | @Length(min = 6, max = 20, message = "长度在6-20") 19 | private String pass; 20 | 21 | @NotEmpty(message = "请再次输入密码") 22 | @Length(min = 6, max = 20, message = "长度在6-20") 23 | private String checkPass; 24 | 25 | @NotEmpty(message = "请输入电子邮箱") 26 | @Email(message = "邮箱格式不正确") 27 | private String email; 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/entity/BmsTip.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableField; 4 | import com.baomidou.mybatisplus.annotation.TableName; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | 11 | @Data 12 | @NoArgsConstructor 13 | @TableName("bms_tip") 14 | public class BmsTip implements Serializable { 15 | 16 | private static final long serialVersionUID = 1L; 17 | 18 | /** 19 | * 主键 20 | */ 21 | private Integer id; 22 | 23 | /** 24 | * 内容 25 | */ 26 | @TableField("`content`") 27 | private String content; 28 | 29 | /** 30 | * 作者 31 | */ 32 | private String author; 33 | 34 | /** 35 | * 1:使用,0:过期 36 | */ 37 | private boolean type; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/entity/BmsTopicTag.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | 12 | 13 | @Data 14 | @TableName("bms_post_tag") 15 | @Accessors(chain = true) 16 | public class BmsTopicTag implements Serializable { 17 | private static final long serialVersionUID = -5028599844989220715L; 18 | 19 | @TableId(type = IdType.AUTO) 20 | private Integer id; 21 | 22 | @TableField("tag_id") 23 | private String tagId; 24 | 25 | @TableField("topic_id") 26 | private String topicId; 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/views/card/Promotion.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 37 | 38 | 40 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/BmsTipController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | import com.which.freefish.common.api.ApiResult; 4 | import com.which.freefish.model.entity.BmsTip; 5 | import com.which.freefish.service.IBmsTipService; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import javax.annotation.Resource; 11 | 12 | @RestController 13 | @RequestMapping("/tip") 14 | public class BmsTipController extends BaseController { 15 | @Resource 16 | private IBmsTipService bmsTipService; 17 | 18 | @GetMapping("/today") 19 | public ApiResult getRandomTip() { 20 | BmsTip tip = bmsTipService.getRandomTip(); 21 | return ApiResult.success(tip); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/views/error/404.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 38 | 39 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/impl/IBmsTipServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.which.freefish.mapper.BmsTipMapper; 5 | import com.which.freefish.model.entity.BmsTip; 6 | import com.which.freefish.service.IBmsTipService; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Slf4j 11 | @Service 12 | public class IBmsTipServiceImpl extends ServiceImpl implements IBmsTipService { 14 | 15 | @Override 16 | public BmsTip getRandomTip() { 17 | BmsTip todayTip = null; 18 | try { 19 | todayTip = this.baseMapper.getRandomTip(); 20 | } catch (Exception e) { 21 | log.info("tip转化失败"); 22 | } 23 | return todayTip; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "community-post-manage-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.21.1", 11 | "buefy": "^0.9.4", 12 | "core-js": "^3.6.5", 13 | "darkreader": "^4.9.27", 14 | "date-fns": "^2.17.0", 15 | "dayjs": "^1.10.4", 16 | "element-ui": "^2.15.0", 17 | "js-cookie": "^2.2.1", 18 | "nprogress": "^0.2.0", 19 | "vditor": "^3.9.6", 20 | "vue": "^3.4.27", 21 | "vue-router": "^3.2.0", 22 | "vuex": "^3.4.0" 23 | }, 24 | "devDependencies": { 25 | "@vue/cli-plugin-babel": "~4.5.0", 26 | "@vue/cli-plugin-router": "~4.5.0", 27 | "@vue/cli-plugin-vuex": "~4.5.0", 28 | "@vue/cli-service": "~4.5.0", 29 | "vue-template-compiler": "^2.6.11" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/common/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.common.exception;// package com.knox.aurora.common.exception; 2 | 3 | import com.which.freefish.common.api.ApiResult; 4 | import org.springframework.web.bind.annotation.ControllerAdvice; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | 8 | import java.util.Map; 9 | 10 | @ControllerAdvice 11 | public class GlobalExceptionHandler { 12 | /** 13 | * 捕获自定义异常 14 | */ 15 | @ResponseBody 16 | @ExceptionHandler(value = ApiException.class) 17 | public ApiResult> handle(ApiException e) { 18 | if (e.getErrorCode() != null) { 19 | return ApiResult.failed(e.getErrorCode()); 20 | } 21 | return ApiResult.failed(e.getMessage()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/views/card/Tip.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/vo/ProfileVO.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.vo; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ProfileVO { 7 | 8 | /** 9 | * 用户ID 10 | */ 11 | private String id; 12 | 13 | /** 14 | * 用户名 15 | */ 16 | private String username; 17 | 18 | /** 19 | * 别称 20 | */ 21 | private String alias; 22 | 23 | /** 24 | * 头像 25 | */ 26 | private String avatar; 27 | 28 | /** 29 | * 关注数 30 | */ 31 | private Integer followCount; 32 | 33 | /** 34 | * 关注者数 35 | */ 36 | private Integer followerCount; 37 | 38 | /** 39 | * 文章数 40 | */ 41 | private Integer topicCount; 42 | 43 | /** 44 | * 专栏数 45 | */ 46 | private Integer columns; 47 | 48 | /** 49 | * 评论数 50 | */ 51 | private Integer commentCount; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | **/.idea/ 19 | *.iws 20 | *.iml 21 | *.ipr 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | build/ 30 | !**/src/main/**/build/ 31 | !**/src/test/**/build/ 32 | 33 | ### VS Code ### 34 | .vscode/ 35 | 36 | ### frontend ### 37 | .DS_Store 38 | node_modules 39 | /dist 40 | 41 | # local env files 42 | .env.local 43 | .env.*.local 44 | 45 | # Log files 46 | npm-debug.log* 47 | yarn-debug.log* 48 | yarn-error.log* 49 | pnpm-debug.log* 50 | 51 | # Editor directories and files 52 | .vscode 53 | *.suo 54 | *.ntvs* 55 | *.njsproj 56 | *.sln 57 | *.sw? 58 | 59 | -------------------------------------------------------------------------------- /frontend/src/views/card/LoginWelcome.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | 30 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/IBmsTopicTagService.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.which.freefish.model.entity.BmsTag; 5 | import com.which.freefish.model.entity.BmsTopicTag; 6 | 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | public interface IBmsTopicTagService extends IService { 11 | 12 | /** 13 | * 获取Topic Tag 关联记录 14 | * 15 | * @param topicId TopicId 16 | * @return 17 | */ 18 | List selectByTopicId(String topicId); 19 | 20 | /** 21 | * 创建中间关系 22 | * 23 | * @param id 24 | * @param tags 25 | * @return 26 | */ 27 | void createTopicTag(String id, List tags); 28 | 29 | /** 30 | * 获取标签换脸话题ID集合 31 | * 32 | * @param id 33 | * @return 34 | */ 35 | Set selectTopicIdsByTagId(String id); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/entity/BmsFollow.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | 9 | import java.io.Serializable; 10 | 11 | @Data 12 | @TableName("bms_follow") 13 | public class BmsFollow implements Serializable { 14 | 15 | private static final long serialVersionUID = 1L; 16 | 17 | /** 18 | * 主键 19 | */ 20 | @TableId(type = IdType.AUTO) 21 | private Integer id; 22 | 23 | /** 24 | * 被关注人id 25 | */ 26 | @TableField("parent_id") 27 | private String parentId; 28 | 29 | /** 30 | * 关注人id 31 | */ 32 | @TableField("follower_id") 33 | private String followerId; 34 | 35 | public BmsFollow() { 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/config/RedisConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.RedisConnectionFactory; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.data.redis.serializer.StringRedisSerializer; 8 | 9 | @Configuration 10 | public class RedisConfiguration { 11 | 12 | @Bean 13 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 14 | RedisTemplate redisTemplate = new RedisTemplate<>(); 15 | // 设置redis的连接工厂对象 16 | redisTemplate.setConnectionFactory(redisConnectionFactory); 17 | // 设置redis key的序列化器 18 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 19 | return redisTemplate; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/entity/BmsTag.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | 13 | 14 | @Data 15 | @Builder 16 | @TableName("bms_tag") 17 | @Accessors(chain = true) 18 | public class BmsTag implements Serializable { 19 | private static final long serialVersionUID = 3257790983905872243L; 20 | 21 | @TableId(type = IdType.ASSIGN_ID) 22 | private String id; 23 | 24 | @TableField("name") 25 | private String name; 26 | /** 27 | * 当前标签下的话题个数 28 | */ 29 | @TableField("topic_count") 30 | @Builder.Default 31 | private Integer topicCount = 1; 32 | } 33 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/BmsPromotionController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | import com.which.freefish.common.api.ApiResult; 4 | import com.which.freefish.model.entity.BmsPromotion; 5 | import com.which.freefish.service.IBmsPromotionService; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.List; 12 | 13 | 14 | @RestController 15 | @RequestMapping("/promotion") 16 | public class BmsPromotionController extends BaseController { 17 | 18 | @Resource 19 | private IBmsPromotionService bmsPromotionService; 20 | 21 | @GetMapping("/all") 22 | public ApiResult> list() { 23 | List list = bmsPromotionService.list(); 24 | return ApiResult.success(list); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 43 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/IUmsUserService.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.which.freefish.model.dto.LoginDTO; 5 | import com.which.freefish.model.dto.RegisterDTO; 6 | import com.which.freefish.model.entity.UmsUser; 7 | import com.which.freefish.model.vo.ProfileVO; 8 | 9 | 10 | public interface IUmsUserService extends IService { 11 | 12 | /** 13 | * 注册功能 14 | * 15 | * @param dto 16 | * @return 注册对象 17 | */ 18 | UmsUser executeRegister(RegisterDTO dto); 19 | 20 | /** 21 | * 获取用户信息 22 | * 23 | * @param username 24 | * @return dbUser 25 | */ 26 | UmsUser getUserByUsername(String username); 27 | 28 | /** 29 | * 用户登录 30 | * 31 | * @param dto 32 | * @return 生成的JWT的token 33 | */ 34 | String executeLogin(LoginDTO dto); 35 | 36 | /** 37 | * 获取用户信息 38 | * 39 | * @param id 用户ID 40 | * @return 41 | */ 42 | ProfileVO getUserProfile(String id); 43 | } 44 | -------------------------------------------------------------------------------- /backend/src/main/resources/mapper/BmsCommentMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/api/post.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 列表 4 | export function getList(pageNo, size, tab) { 5 | return request(({ 6 | url: '/post/list', 7 | method: 'get', 8 | params: { pageNo: pageNo, size: size, tab: tab } 9 | })) 10 | } 11 | 12 | // 发布 13 | export function post(topic) { 14 | return request({ 15 | url: '/post/create', 16 | method: 'post', 17 | data: topic 18 | }) 19 | } 20 | 21 | // 浏览 22 | export function getTopic(id) { 23 | return request({ 24 | url: `/post`, 25 | method: 'get', 26 | params: { 27 | id: id 28 | } 29 | }) 30 | } 31 | // 获取详情页推荐 32 | export function getRecommendTopics(id) { 33 | return request({ 34 | url: '/post/recommend', 35 | method: 'get', 36 | params: { 37 | topicId: id 38 | } 39 | }) 40 | } 41 | 42 | export function update(topic) { 43 | return request({ 44 | url: '/post/update', 45 | method: 'post', 46 | data: topic 47 | }) 48 | } 49 | 50 | export function deleteTopic(id) { 51 | return request({ 52 | url: `/post/delete/${id}`, 53 | method: 'delete' 54 | }) 55 | } 56 | 57 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/BmsBillboardController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.which.freefish.common.api.ApiResult; 5 | import com.which.freefish.model.entity.BmsBillboard; 6 | import com.which.freefish.service.IBmsBillboardService; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.List; 13 | 14 | @RestController 15 | @RequestMapping("/billboard") 16 | public class BmsBillboardController extends BaseController { 17 | 18 | @Resource 19 | private IBmsBillboardService bmsBillboardService; 20 | 21 | @GetMapping("/show") 22 | public ApiResult getNotices() { 23 | List list = bmsBillboardService.list(new 24 | LambdaQueryWrapper().eq(BmsBillboard::isShow, true)); 25 | return ApiResult.success(list.get(list.size() - 1)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | // Buefy 6 | import Buefy from 'buefy' 7 | import 'buefy/dist/buefy.css' 8 | // ElementUI 9 | import ElementUI from 'element-ui'; 10 | import 'element-ui/lib/theme-chalk/index.css'; 11 | import '@/assets/app.css' 12 | import './assets/plugins/font-awesome-4.7.0/css/font-awesome.min.css' 13 | import format from 'date-fns/format' 14 | import '@/permission' 15 | import relativeTime from 'dayjs/plugin/relativeTime'; 16 | 17 | // 国际化 18 | import 'dayjs/locale/zh-cn' 19 | const dayjs = require('dayjs'); 20 | 21 | // 相对时间插件 22 | dayjs.extend(relativeTime) 23 | 24 | dayjs.locale('zh-cn') // use locale globally 25 | dayjs().locale('zh-cn').format() // use locale in a specific instance 26 | 27 | Vue.prototype.dayjs = dayjs;//可以全局使用dayjs 28 | 29 | Vue.filter('date', (date) => { 30 | return format(new Date(date), 'yyyy-MM-dd') 31 | }) 32 | 33 | Vue.use(Buefy) 34 | Vue.use(ElementUI); 35 | 36 | Vue.config.productionTip = false 37 | 38 | new Vue({ 39 | router, 40 | store, 41 | render: h => h(App) 42 | }).$mount('#app') 43 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/entity/BmsPromotion.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | 12 | 13 | @Data 14 | @TableName("bms_promotion") 15 | @Accessors(chain = true) 16 | public class BmsPromotion implements Serializable { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | /** 21 | * 主键 22 | */ 23 | @TableId(value = "id", type = IdType.AUTO) 24 | private Integer id; 25 | 26 | /** 27 | * 友情链接标题 28 | */ 29 | @TableField("title") 30 | private String title; 31 | 32 | /** 33 | * 友情链接链接 34 | */ 35 | @TableField("link") 36 | private String link; 37 | 38 | /** 39 | * 说明 40 | */ 41 | @TableField("`description`") 42 | private String description; 43 | 44 | public BmsPromotion() { 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/mapper/BmsTopicMapper.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.which.freefish.model.entity.BmsPost; 6 | import com.which.freefish.model.vo.PostVO; 7 | import org.apache.ibatis.annotations.Param; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import java.util.List; 11 | 12 | @Repository 13 | public interface BmsTopicMapper extends BaseMapper { 14 | /** 15 | * 分页查询首页话题列表 16 | *

17 | * 18 | * @param page 19 | * @param tab 20 | * @return 21 | */ 22 | Page selectListAndPage(@Param("page") Page page, @Param("tab") String tab); 23 | 24 | /** 25 | * 获取详情页推荐 26 | * 27 | * @param id 28 | * @return 29 | */ 30 | List selectRecommend(@Param("id") String id); 31 | 32 | /** 33 | * 全文检索 34 | * 35 | * @param page 36 | * @param keyword 37 | * @return 38 | */ 39 | Page searchByKey(@Param("page") Page page, @Param("keyword") String keyword); 40 | } 41 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/entity/BmsBillboard.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | 14 | @Data 15 | @Builder 16 | @Accessors(chain = true) 17 | @TableName("bms_billboard") 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class BmsBillboard implements Serializable { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | /** 25 | * 主键 26 | */ 27 | @TableId(type = IdType.AUTO) 28 | private Integer id; 29 | 30 | /** 31 | * 公告牌 32 | */ 33 | @TableField("content") 34 | private String content; 35 | 36 | /** 37 | * 公告时间 38 | */ 39 | @TableField(value = "create_time", fill = FieldFill.INSERT) 40 | private Date createTime; 41 | 42 | /** 43 | * 1:展示中,0:过期 44 | */ 45 | @Builder.Default 46 | @TableField("`show`") 47 | private boolean show = false; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/common/api/ApiErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.common.api; 2 | 3 | 4 | public enum ApiErrorCode implements IErrorCode { 5 | 6 | /** 7 | * 成功 8 | */ 9 | SUCCESS(200, "操作成功"), 10 | /** 11 | * 失败 12 | */ 13 | FAILED(-1, "操作失败"), 14 | /** 15 | * 未登录,Token过期 16 | */ 17 | UNAUTHORIZED(401, "暂未登录或token已经过期"), 18 | /** 19 | * 权限不足 20 | */ 21 | FORBIDDEN(403, "权限不足"), 22 | /** 23 | * 参数校验错误 24 | */ 25 | VALIDATE_FAILED(404, "参数检验失败"); 26 | 27 | private final Integer code; 28 | private final String message; 29 | 30 | ApiErrorCode(int code, String message) { 31 | this.code = code; 32 | this.message = message; 33 | } 34 | 35 | @Override 36 | public Integer getCode() { 37 | return code; 38 | } 39 | 40 | @Override 41 | public String getMessage() { 42 | return message; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "ApiErrorCode{" + 48 | "code=" + code + 49 | ", message='" + message + '\'' + 50 | '}'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /frontend/src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import getPageTitle from '@/utils/get-page-title' 4 | 5 | import NProgress from 'nprogress' // progress bar 6 | import 'nprogress/nprogress.css' 7 | import {getToken} from "@/utils/auth"; // progress bar style 8 | 9 | NProgress.configure({showSpinner: false}) // NProgress Configuration 10 | 11 | router.beforeEach(async (to, from, next) => { 12 | // start progress bar 13 | NProgress.start() 14 | // set page title 15 | document.title = getPageTitle(to.meta.title) 16 | // determine whether the user has logged in 17 | const hasToken = getToken(); 18 | 19 | if (hasToken) { 20 | if (to.path === '/login') { 21 | // 登录,跳转首页 22 | next({path: '/'}) 23 | NProgress.done() 24 | } else { 25 | // 获取用户信息 26 | await store.dispatch('user/getInfo') 27 | next() 28 | } 29 | } else if (!to.meta.requireAuth) 30 | { 31 | next() 32 | } 33 | else { 34 | next('/login') 35 | } 36 | }) 37 | 38 | router.afterEach(() => { 39 | // finish progress bar 40 | NProgress.done() 41 | }) 42 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/BmsSearchController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | import com.which.freefish.common.api.ApiResult; 5 | import com.which.freefish.model.vo.PostVO; 6 | import com.which.freefish.service.IBmsPostService; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import javax.annotation.Resource; 13 | 14 | @RestController 15 | @RequestMapping("/search") 16 | public class BmsSearchController extends BaseController { 17 | 18 | @Resource 19 | private IBmsPostService postService; 20 | 21 | @GetMapping 22 | public ApiResult> searchList(@RequestParam("keyword") String keyword, 23 | @RequestParam("pageNum") Integer pageNum, 24 | @RequestParam("pageSize") Integer pageSize) { 25 | Page results = postService.searchByKey(keyword, new Page<>(pageNum, pageSize)); 26 | return ApiResult.success(results); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/common/mybatisplus/MybatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.common.mybatisplus; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer; 5 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 6 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 7 | import org.mybatis.spring.annotation.MapperScan; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | 12 | @Configuration 13 | @MapperScan("com.which.freefish.mapper") 14 | public class MybatisPlusConfig { 15 | 16 | /** 17 | * 新的分页插件,一缓和二缓遵循mybatis的规则, 18 | * 需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除) 19 | */ 20 | @Bean 21 | public MybatisPlusInterceptor mybatisPlusInterceptor() { 22 | MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); 23 | interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 24 | return interceptor; 25 | } 26 | 27 | @Bean 28 | public ConfigurationCustomizer configurationCustomizer() { 29 | return configuration -> configuration.setUseDeprecatedExecutor(false); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/views/post/Recommend.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /frontend/src/components/Comment/Comments.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 58 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/entity/BmsComment.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.validation.constraints.NotBlank; 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | 14 | @Data 15 | @Builder 16 | @TableName("bms_comment") 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class BmsComment implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | /** 24 | * 主键 25 | */ 26 | @TableId(value = "id", type = IdType.ASSIGN_ID) 27 | private String id; 28 | /** 29 | * 内容 30 | */ 31 | @NotBlank(message = "内容不可以为空") 32 | @TableField(value = "content") 33 | private String content; 34 | 35 | 36 | /** 37 | * 作者ID 38 | */ 39 | @TableField("user_id") 40 | private String userId; 41 | 42 | /** 43 | * topicID 44 | */ 45 | @TableField("topic_id") 46 | private String topicId; 47 | 48 | /** 49 | * 创建时间 50 | */ 51 | @TableField(value = "create_time", fill = FieldFill.INSERT) 52 | private Date createTime; 53 | 54 | /** 55 | * 修改时间 56 | */ 57 | @TableField(value = "modify_time", fill = FieldFill.UPDATE) 58 | private Date modifyTime; 59 | } 60 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/IBmsPostService.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.which.freefish.model.dto.CreateTopicDTO; 6 | import com.which.freefish.model.entity.BmsPost; 7 | import com.which.freefish.model.entity.UmsUser; 8 | import com.which.freefish.model.vo.PostVO; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | 14 | public interface IBmsPostService extends IService { 15 | 16 | /** 17 | * 获取首页话题列表 18 | * 19 | * @param page 20 | * @param tab 21 | * @return 22 | */ 23 | Page getList(Page page, String tab); 24 | 25 | /** 26 | * 发布 27 | * 28 | * @param dto 29 | * @param principal 30 | * @return 31 | */ 32 | BmsPost create(CreateTopicDTO dto, UmsUser principal); 33 | 34 | /** 35 | * 查看话题详情 36 | * 37 | * @param id 38 | * @return 39 | */ 40 | Map viewTopic(String id); 41 | 42 | /** 43 | * 获取随机推荐10篇 44 | * 45 | * @param id 46 | * @return 47 | */ 48 | List getRecommend(String id); 49 | 50 | /** 51 | * 关键字检索 52 | * 53 | * @param keyword 54 | * @param page 55 | * @return 56 | */ 57 | Page searchByKey(String keyword, Page page); 58 | } 59 | -------------------------------------------------------------------------------- /frontend/src/components/Layout/Footer.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 47 | 48 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish; 2 | 3 | import com.which.freefish.jwt.JwtAuthenticationFilter; 4 | import org.mybatis.spring.annotation.MapperScan; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.builder.SpringApplicationBuilder; 8 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 9 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 10 | import org.springframework.cache.annotation.EnableCaching; 11 | import org.springframework.context.annotation.Bean; 12 | 13 | @MapperScan("com.which.freefish.mapper") 14 | @EnableCaching 15 | @SpringBootApplication 16 | public class MainApplication extends SpringBootServletInitializer { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(MainApplication.class, args); 20 | } 21 | 22 | @Override 23 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 24 | return builder.sources(MainApplication.class); 25 | } 26 | 27 | @Bean 28 | public FilterRegistrationBean jwtFilter() { 29 | final FilterRegistrationBean registrationBean = new FilterRegistrationBean(); 30 | JwtAuthenticationFilter filter = new JwtAuthenticationFilter(); 31 | registrationBean.setFilter(filter); 32 | return registrationBean; 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/vo/UserVO.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | 10 | /** 11 | * 用户vo 12 | * 13 | * @author which 14 | * @date 2024/05/19 15 | */ 16 | @Data 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class UserVO implements Serializable { 20 | 21 | private static final long serialVersionUID = 3L; 22 | 23 | /** 24 | * id 25 | */ 26 | private String id; 27 | 28 | /** 29 | * 用户名 30 | */ 31 | private String username; 32 | 33 | /** 34 | * 别名 35 | */ 36 | private String alias; 37 | 38 | /** 39 | * 头像 40 | */ 41 | private String avatar; 42 | 43 | /** 44 | * 电子邮件 45 | */ 46 | private String email; 47 | 48 | /** 49 | * 移动电话 50 | */ 51 | private String mobile; 52 | 53 | /** 54 | * 生物 55 | */ 56 | private String bio; 57 | 58 | /** 59 | * 得分 60 | */ 61 | private Integer score; 62 | 63 | /** 64 | * 令牌 65 | */ 66 | private String token; 67 | 68 | /** 69 | * 活动 70 | */ 71 | private Boolean active; 72 | 73 | /** 74 | * 状态 75 | */ 76 | private Boolean status; 77 | 78 | /** 79 | * 角色id 80 | */ 81 | private Integer roleId; 82 | 83 | /** 84 | * 创建时间 85 | */ 86 | private Date createTime; 87 | 88 | /** 89 | * 修改时间 90 | */ 91 | private Date modifyTime; 92 | } 93 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/vo/PostVO.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.vo; 2 | 3 | import com.which.freefish.model.entity.BmsTag; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | import java.util.Date; 10 | import java.util.List; 11 | 12 | 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class PostVO implements Serializable { 17 | private static final long serialVersionUID = -261082150965211545L; 18 | 19 | /** 20 | * 文章ID 21 | */ 22 | private String id; 23 | /** 24 | * 用户ID 25 | */ 26 | private String userId; 27 | /** 28 | * 头像 29 | */ 30 | private String avatar; 31 | /** 32 | * 用户昵称 33 | */ 34 | private String alias; 35 | /** 36 | * 账号 37 | */ 38 | private String username; 39 | /** 40 | * 标题 41 | */ 42 | private String title; 43 | /** 44 | * 评论统计 45 | */ 46 | private Integer comments; 47 | /** 48 | * 置顶 49 | */ 50 | private Boolean top; 51 | /** 52 | * 加精 53 | */ 54 | private Boolean essence; 55 | /** 56 | * 收藏次數 57 | */ 58 | private Integer collects; 59 | /** 60 | * 话题关联标签 61 | */ 62 | private List tags; 63 | /** 64 | * 浏览量 65 | */ 66 | private Integer view; 67 | /** 68 | * 创建时间 69 | */ 70 | private Date createTime; 71 | /** 72 | * 修改时间 73 | */ 74 | private Date modifyTime; 75 | /** 76 | * 匹配度分数 77 | */ 78 | private Float score; 79 | } 80 | -------------------------------------------------------------------------------- /frontend/docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name freefish.love; 4 | 5 | # gzip config 6 | gzip on; 7 | gzip_min_length 1k; 8 | gzip_comp_level 9; 9 | gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml; 10 | gzip_vary on; 11 | gzip_disable "MSIE [1-6]\."; 12 | 13 | root /usr/share/nginx/html; 14 | include /etc/nginx/mime.types; 15 | 16 | location / { 17 | try_files $uri /index.html; 18 | } 19 | } 20 | 21 | server { 22 | listen 80; 23 | server_name back.freefish.love; 24 | 25 | location / { 26 | proxy_pass http://back.freefish.love:8088; 27 | 28 | # 前端支持跨域 29 | # add_header 'Access-Control-Allow-Origin' $http_origin; 30 | # add_header 'Access-Control-Allow-Credentials' 'true'; 31 | # add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; 32 | # add_header Access-Control-Allow-Headers '*'; 33 | # if ($request_method = 'OPTIONS') { 34 | # add_header 'Access-Control-Allow-Credentials' 'true'; 35 | # add_header 'Access-Control-Allow-Origin' $http_origin; 36 | # add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 37 | # add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; 38 | # add_header 'Access-Control-Max-Age' 1728000; 39 | # add_header 'Content-Type' 'text/plain; charset=utf-8'; 40 | # add_header 'Content-Length' 0; 41 | # return 204; 42 | # } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/impl/IBmsCommentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.which.freefish.mapper.BmsCommentMapper; 5 | import com.which.freefish.model.dto.CommentDTO; 6 | import com.which.freefish.model.entity.BmsComment; 7 | import com.which.freefish.model.entity.UmsUser; 8 | import com.which.freefish.model.vo.CommentVO; 9 | import com.which.freefish.service.IBmsCommentService; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | 18 | @Slf4j 19 | @Service 20 | public class IBmsCommentServiceImpl extends ServiceImpl implements IBmsCommentService { 21 | @Override 22 | public List getCommentsByTopicID(String topicid) { 23 | List lstBmsComment = new ArrayList(); 24 | try { 25 | lstBmsComment = this.baseMapper.getCommentsByTopicID(topicid); 26 | } catch (Exception e) { 27 | log.info("lstBmsComment失败"); 28 | } 29 | return lstBmsComment; 30 | } 31 | 32 | @Override 33 | public BmsComment create(CommentDTO dto, UmsUser user) { 34 | BmsComment comment = BmsComment.builder() 35 | .userId(user.getId()) 36 | .content(dto.getContent()) 37 | .topicId(dto.getTopic_id()) 38 | .createTime(new Date()) 39 | .build(); 40 | this.baseMapper.insert(comment); 41 | return comment; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/BmsCommentController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | import com.which.freefish.common.api.ApiResult; 4 | import com.which.freefish.jwt.JwtUtil; 5 | import com.which.freefish.model.dto.CommentDTO; 6 | import com.which.freefish.model.entity.BmsComment; 7 | import com.which.freefish.model.entity.UmsUser; 8 | import com.which.freefish.model.vo.CommentVO; 9 | import com.which.freefish.service.IBmsCommentService; 10 | import com.which.freefish.service.IUmsUserService; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import javax.annotation.Resource; 14 | import java.util.List; 15 | 16 | 17 | @RestController 18 | @RequestMapping("/comment") 19 | public class BmsCommentController extends BaseController { 20 | 21 | @Resource 22 | private IBmsCommentService bmsCommentService; 23 | 24 | @Resource 25 | private IUmsUserService umsUserService; 26 | 27 | @GetMapping("/get_comments") 28 | public ApiResult> getCommentsByTopicID(@RequestParam(value = "topicid", defaultValue = "1") String topicid) { 29 | List lstBmsComment = bmsCommentService.getCommentsByTopicID(topicid); 30 | return ApiResult.success(lstBmsComment); 31 | } 32 | 33 | @PostMapping("/add_comment") 34 | public ApiResult add_comment(@RequestHeader(value = JwtUtil.USER_NAME) String userName, 35 | @RequestBody CommentDTO dto) { 36 | UmsUser user = umsUserService.getUserByUsername(userName); 37 | BmsComment comment = bmsCommentService.create(dto, user); 38 | return ApiResult.success(comment); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/config/GlobalWebMvcConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.cors.CorsConfiguration; 6 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 7 | import org.springframework.web.filter.CorsFilter; 8 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 10 | 11 | @Configuration 12 | public class GlobalWebMvcConfiguration implements WebMvcConfigurer { 13 | 14 | /** 15 | * 跨域 16 | */ 17 | @Override 18 | public void addCorsMappings(CorsRegistry registry) { 19 | registry.addMapping("/**") 20 | .allowedOrigins("*") 21 | .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") 22 | .allowCredentials(true) 23 | .maxAge(3600) 24 | .allowedHeaders("*"); 25 | } 26 | 27 | @Bean 28 | public CorsFilter corsFilter() { 29 | CorsConfiguration config = new CorsConfiguration(); 30 | // 允许所有进行跨域调用 31 | config.addAllowedOrigin("*"); 32 | // 允许指定 网络协议 域名 端口 进行跨域调用 33 | // config.addAllowedOrigin("http://freefish.love"); 34 | // 允许跨越发送cookie 35 | config.setAllowCredentials(true); 36 | // 放行全部原始头信息 37 | config.addAllowedHeader("*"); 38 | // 允许所有请求方法跨域调用 39 | config.addAllowedMethod("*"); 40 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 41 | source.registerCorsConfiguration("/**", config); 42 | return new CorsFilter(source); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/impl/IBmsTopicTagServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.which.freefish.mapper.BmsTopicTagMapper; 7 | import com.which.freefish.model.entity.BmsTag; 8 | import com.which.freefish.model.entity.BmsTopicTag; 9 | import com.which.freefish.service.IBmsTopicTagService; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | 17 | @Service 18 | @Transactional(rollbackFor = Exception.class) 19 | public class IBmsTopicTagServiceImpl extends ServiceImpl implements IBmsTopicTagService { 20 | 21 | @Override 22 | public List selectByTopicId(String topicId) { 23 | QueryWrapper wrapper = new QueryWrapper<>(); 24 | wrapper.lambda().eq(BmsTopicTag::getTopicId, topicId); 25 | return this.baseMapper.selectList(wrapper); 26 | } 27 | 28 | @Override 29 | public void createTopicTag(String id, List tags) { 30 | // 先删除topicId对应的所有记录 31 | this.baseMapper.delete(new LambdaQueryWrapper().eq(BmsTopicTag::getTopicId, id)); 32 | 33 | // 循环保存对应关联 34 | tags.forEach(tag -> { 35 | BmsTopicTag topicTag = new BmsTopicTag(); 36 | topicTag.setTopicId(id); 37 | topicTag.setTagId(tag.getId()); 38 | this.baseMapper.insert(topicTag); 39 | }); 40 | } 41 | 42 | @Override 43 | public Set selectTopicIdsByTagId(String id) { 44 | return this.baseMapper.getTopicIdsByTagId(id); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /frontend/src/utils/scroll-to.js: -------------------------------------------------------------------------------- 1 | Math.easeInOutQuad = function(t, b, c, d) { 2 | t /= d / 2 3 | if (t < 1) { 4 | return c / 2 * t * t + b 5 | } 6 | t-- 7 | return -c / 2 * (t * (t - 2) - 1) + b 8 | } 9 | 10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts 11 | var requestAnimFrame = (function() { 12 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } 13 | })() 14 | 15 | /** 16 | * Because it's so fucking difficult to detect the scrolling element, just move them all 17 | * @param {number} amount 18 | */ 19 | function move(amount) { 20 | document.documentElement.scrollTop = amount 21 | document.body.parentNode.scrollTop = amount 22 | document.body.scrollTop = amount 23 | } 24 | 25 | function position() { 26 | return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop 27 | } 28 | 29 | /** 30 | * @param {number} to 31 | * @param {number} duration 32 | * @param {Function} callback 33 | */ 34 | export function scrollTo(to, duration, callback) { 35 | const start = position() 36 | const change = to - start 37 | const increment = 20 38 | let currentTime = 0 39 | duration = (typeof (duration) === 'undefined') ? 500 : duration 40 | var animateScroll = function() { 41 | // increment the time 42 | currentTime += increment 43 | // find the value with the quadratic in-out easing function 44 | var val = Math.easeInOutQuad(currentTime, start, change, duration) 45 | // move the document.body 46 | move(val) 47 | // do the animation unless its over 48 | if (currentTime < duration) { 49 | requestAnimFrame(animateScroll) 50 | } else { 51 | if (callback && typeof (callback) === 'function') { 52 | // the animation is done so lets callback 53 | callback() 54 | } 55 | } 56 | } 57 | animateScroll() 58 | } 59 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/BmsTagController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.which.freefish.common.api.ApiResult; 6 | import com.which.freefish.model.entity.BmsPost; 7 | import com.which.freefish.model.entity.BmsTag; 8 | import com.which.freefish.service.IBmsTagService; 9 | import org.springframework.util.Assert; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.annotation.Resource; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | @RestController 17 | @RequestMapping("/tag") 18 | public class BmsTagController extends BaseController { 19 | 20 | @Resource 21 | private IBmsTagService bmsTagService; 22 | 23 | @GetMapping("/{name}") 24 | public ApiResult> getTopicsByTag( 25 | @PathVariable("name") String tagName, 26 | @RequestParam(value = "page", defaultValue = "1") Integer page, 27 | @RequestParam(value = "size", defaultValue = "10") Integer size) { 28 | 29 | Map map = new HashMap<>(16); 30 | 31 | LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); 32 | wrapper.eq(BmsTag::getName, tagName); 33 | BmsTag one = bmsTagService.getOne(wrapper); 34 | Assert.notNull(one, "话题不存在,或已被管理员删除"); 35 | Page topics = bmsTagService.selectTopicsByTagId(new Page<>(page, size), one.getId()); 36 | // 其他热门标签 37 | Page hotTags = bmsTagService.page(new Page<>(1, 10), 38 | new LambdaQueryWrapper() 39 | .notIn(BmsTag::getName, tagName) 40 | .orderByDesc(BmsTag::getTopicCount)); 41 | 42 | map.put("topics", topics); 43 | map.put("hotTags", hotTags); 44 | 45 | return ApiResult.success(map); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/components/Comment/CommentsForm.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 76 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/utils/MD5Utils.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.utils; 2 | 3 | 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | 8 | public class MD5Utils { 9 | 10 | public static String getPwd(String pwd) { 11 | try { 12 | // 创建加密对象 13 | MessageDigest digest = MessageDigest.getInstance("md5"); 14 | 15 | // 调用加密对象的方法,加密的动作已经完成 16 | byte[] bs = digest.digest(pwd.getBytes()); 17 | // 接下来,我们要对加密后的结果,进行优化,按照mysql的优化思路走 18 | // mysql的优化思路: 19 | // 第一步,将数据全部转换成正数: 20 | String hexString = ""; 21 | for (byte b : bs) { 22 | // 第一步,将数据全部转换成正数: 23 | // 解释:为什么采用b&255 24 | /* 25 | * b:它本来是一个byte类型的数据(1个字节) 255:是一个int类型的数据(4个字节) 26 | * byte类型的数据与int类型的数据进行运算,会自动类型提升为int类型 eg: b: 1001 1100(原始数据) 27 | * 运算时: b: 0000 0000 0000 0000 0000 0000 1001 1100 255: 0000 28 | * 0000 0000 0000 0000 0000 1111 1111 结果:0000 0000 0000 0000 29 | * 0000 0000 1001 1100 此时的temp是一个int类型的整数 30 | */ 31 | int temp = b & 255; 32 | // 第二步,将所有的数据转换成16进制的形式 33 | // 注意:转换的时候注意if正数>=0&&<16,那么如果使用Integer.toHexString(),可能会造成缺少位数 34 | // 因此,需要对temp进行判断 35 | if (temp < 16 && temp >= 0) { 36 | // 手动补上一个“0” 37 | hexString = hexString + "0" + Integer.toHexString(temp); 38 | } else { 39 | hexString = hexString + Integer.toHexString(temp); 40 | } 41 | } 42 | return hexString; 43 | } catch (NoSuchAlgorithmException e) { 44 | // TODO Auto-generated catch block 45 | e.printStackTrace(); 46 | } 47 | return ""; 48 | } 49 | 50 | 51 | // public static void Main(String[] args) { 52 | // String pwd = MD5Utils.getPwd("234"); 53 | // System.out.println(pwd); 54 | // } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/entity/UmsUser.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | import java.util.Date; 13 | 14 | 15 | @Data 16 | @Builder 17 | @TableName("ums_user") 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | @Accessors(chain = true) 21 | public class UmsUser implements Serializable { 22 | private static final long serialVersionUID = -5051120337175047163L; 23 | 24 | @TableId(value = "id", type = IdType.ASSIGN_ID) 25 | private String id; 26 | 27 | @TableField("username") 28 | private String username; 29 | 30 | @TableField("alias") 31 | private String alias; 32 | 33 | @JsonIgnore() 34 | @TableField("password") 35 | private String password; 36 | 37 | @Builder.Default 38 | @TableField("avatar") 39 | private String avatar = null; 40 | 41 | @TableField("email") 42 | private String email; 43 | 44 | @TableField("mobile") 45 | private String mobile; 46 | 47 | @Builder.Default 48 | @TableField("bio") 49 | private String bio = "自由职业者"; 50 | 51 | @Builder.Default 52 | @TableField("score") 53 | private Integer score = 0; 54 | 55 | @JsonIgnore 56 | @TableField("token") 57 | private String token; 58 | 59 | @Builder.Default 60 | @TableField("active") 61 | private Boolean active = true; 62 | 63 | /** 64 | * 状态。1:使用,0:已停用 65 | */ 66 | @Builder.Default 67 | @TableField("`status`") 68 | private Boolean status = true; 69 | 70 | /** 71 | * 用户角色 72 | */ 73 | @TableField("role_id") 74 | private Integer roleId; 75 | 76 | @TableField(value = "create_time", fill = FieldFill.INSERT) 77 | private Date createTime; 78 | 79 | @TableField(value = "modify_time", fill = FieldFill.INSERT_UPDATE) 80 | private Date modifyTime; 81 | } 82 | -------------------------------------------------------------------------------- /frontend/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { getUserInfo, login, logout } from "@/api/auth/auth"; 2 | import { getToken, setToken, removeToken } from "@/utils/auth"; 3 | 4 | const state = { 5 | token: getToken(), // token 6 | user: "", // 用户对象 7 | }; 8 | 9 | const mutations = { 10 | SET_TOKEN_STATE: (state, token) => { 11 | state.token = token; 12 | }, 13 | SET_USER_STATE: (state, user) => { 14 | state.user = user; 15 | }, 16 | }; 17 | 18 | const actions = { 19 | // 用户登录 20 | login({ commit }, userInfo) { 21 | console.log(userInfo); 22 | const { name, pass, rememberMe } = userInfo; 23 | return new Promise((resolve, reject) => { 24 | login({ username: name.trim(), password: pass, rememberMe: rememberMe }) 25 | .then((response) => { 26 | const { data } = response; 27 | commit("SET_TOKEN_STATE", data.token); 28 | setToken(data.token); 29 | resolve(); 30 | }) 31 | .catch((error) => { 32 | reject(error); 33 | }); 34 | }); 35 | }, 36 | // 获取用户信息 37 | getInfo({ commit, state }) { 38 | return new Promise((resolve, reject) => { 39 | getUserInfo() 40 | .then((response) => { 41 | const { data } = response; 42 | if (!data) { 43 | commit("SET_TOKEN_STATE", ""); 44 | commit("SET_USER_STATE", ""); 45 | removeToken(); 46 | resolve(); 47 | reject("Verification failed, please Login again."); 48 | } 49 | commit("SET_USER_STATE", data); 50 | resolve(data); 51 | }) 52 | .catch((error) => { 53 | reject(error); 54 | }); 55 | }); 56 | }, 57 | // 注销 58 | logout({ commit, state }) { 59 | return new Promise((resolve, reject) => { 60 | logout(state.token) 61 | .then((response) => { 62 | console.log(response); 63 | commit("SET_TOKEN_STATE", ""); 64 | commit("SET_USER_STATE", ""); 65 | removeToken(); 66 | resolve(); 67 | }) 68 | .catch((error) => { 69 | reject(error); 70 | }); 71 | }); 72 | }, 73 | }; 74 | 75 | export default { 76 | namespaced: true, 77 | state, 78 | mutations, 79 | actions, 80 | }; 81 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/impl/IBmsTagServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.which.freefish.mapper.BmsTagMapper; 7 | import com.which.freefish.model.entity.BmsPost; 8 | import com.which.freefish.model.entity.BmsTag; 9 | import com.which.freefish.service.IBmsTagService; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | /** 18 | * Tag 实现类 19 | * 20 | * @author Knox 2020/11/7 21 | */ 22 | @Service 23 | public class IBmsTagServiceImpl extends ServiceImpl implements IBmsTagService { 24 | 25 | @Autowired 26 | private com.which.freefish.service.IBmsTopicTagService IBmsTopicTagService; 27 | 28 | @Autowired 29 | private com.which.freefish.service.IBmsPostService IBmsPostService; 30 | 31 | 32 | @Override 33 | public List insertTags(List tagNames) { 34 | List tagList = new ArrayList<>(); 35 | for (String tagName : tagNames) { 36 | BmsTag tag = this.baseMapper.selectOne(new LambdaQueryWrapper().eq(BmsTag::getName, tagName)); 37 | if (tag == null) { 38 | tag = BmsTag.builder().name(tagName).build(); 39 | this.baseMapper.insert(tag); 40 | } else { 41 | tag.setTopicCount(tag.getTopicCount() + 1); 42 | this.baseMapper.updateById(tag); 43 | } 44 | tagList.add(tag); 45 | } 46 | return tagList; 47 | } 48 | 49 | @Override 50 | public Page selectTopicsByTagId(Page topicPage, String id) { 51 | 52 | // 获取关联的话题ID 53 | Set ids = IBmsTopicTagService.selectTopicIdsByTagId(id); 54 | LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); 55 | wrapper.in(BmsPost::getId, ids); 56 | 57 | return IBmsPostService.page(topicPage, wrapper); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/model/entity/BmsPost.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.model.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.validation.constraints.NotBlank; 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | 14 | @Data 15 | @Builder 16 | @TableName("bms_post") 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class BmsPost implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | /** 24 | * 主键 25 | */ 26 | @TableId(value = "id", type = IdType.ASSIGN_ID) 27 | private String id; 28 | /** 29 | * 标题 30 | */ 31 | @NotBlank(message = "标题不可以为空") 32 | @TableField(value = "title") 33 | private String title; 34 | /** 35 | * markdown 36 | */ 37 | @NotBlank(message = "内容不可以为空") 38 | @TableField("`content`") 39 | private String content; 40 | 41 | /** 42 | * 作者ID 43 | */ 44 | @TableField("user_id") 45 | private String userId; 46 | 47 | /** 48 | * 评论数 49 | */ 50 | @TableField("comments") 51 | @Builder.Default 52 | private Integer comments = 0; 53 | 54 | /** 55 | * 收藏数 56 | */ 57 | @TableField("collects") 58 | @Builder.Default 59 | private Integer collects = 0; 60 | 61 | /** 62 | * 浏览数 63 | */ 64 | @TableField("view") 65 | @Builder.Default 66 | private Integer view = 0; 67 | 68 | /** 69 | * 专栏ID,默认不分栏 70 | */ 71 | @TableField("section_id") 72 | @Builder.Default 73 | private Integer sectionId = 0; 74 | 75 | /** 76 | * 置顶 77 | */ 78 | @TableField("top") 79 | @Builder.Default 80 | private Boolean top = false; 81 | 82 | /** 83 | * 加精 84 | */ 85 | @TableField("essence") 86 | @Builder.Default 87 | private Boolean essence = false; 88 | 89 | /** 90 | * 创建时间 91 | */ 92 | @TableField(value = "create_time", fill = FieldFill.INSERT) 93 | private Date createTime; 94 | 95 | /** 96 | * 修改时间 97 | */ 98 | @TableField(value = "modify_time", fill = FieldFill.UPDATE) 99 | private Date modifyTime; 100 | } 101 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/jwt/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.jwt; 2 | 3 | import org.springframework.util.AntPathMatcher; 4 | import org.springframework.util.PathMatcher; 5 | import org.springframework.web.filter.OncePerRequestFilter; 6 | 7 | import javax.servlet.FilterChain; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | 16 | public class JwtAuthenticationFilter extends OncePerRequestFilter { 17 | private static final PathMatcher pathMatcher = new AntPathMatcher(); 18 | 19 | @Override 20 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 21 | 22 | try { 23 | if (isProtectedUrl(request)) { 24 | // System.out.println(request.getMethod()); 25 | if (!request.getMethod().equals("OPTIONS")) { 26 | request = JwtUtil.validateTokenAndAddUserIdToHeader(request); 27 | } 28 | } 29 | } catch (Exception e) { 30 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); 31 | return; 32 | } 33 | filterChain.doFilter(request, response); 34 | } 35 | 36 | private boolean isProtectedUrl(HttpServletRequest request) { 37 | List protectedPaths = new ArrayList(); 38 | protectedPaths.add("/ums/user/info"); 39 | protectedPaths.add("/ums/user/update"); 40 | protectedPaths.add("/post/create"); 41 | protectedPaths.add("/post/update"); 42 | protectedPaths.add("/post/delete/*"); 43 | protectedPaths.add("/comment/add_comment"); 44 | protectedPaths.add("/relationship/subscribe/*"); 45 | protectedPaths.add("/relationship/unsubscribe/*"); 46 | protectedPaths.add("/relationship/validate/*"); 47 | protectedPaths.add("/admin/post/create"); 48 | 49 | boolean bFind = false; 50 | for (String passedPath : protectedPaths) { 51 | bFind = pathMatcher.match(passedPath, request.getServletPath()); 52 | if (bFind) { 53 | break; 54 | } 55 | } 56 | return bFind; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /frontend/src/components/Pagination/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 93 | 94 | 104 | -------------------------------------------------------------------------------- /frontend/src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message, MessageBox } from 'element-ui' 3 | import store from '@/store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // 1.创建axios实例 7 | const service = axios.create({ 8 | // 公共接口--这里注意后面会讲,url = base url + request url 9 | baseURL: process.env.VUE_APP_SERVER_URL, 10 | 11 | // baseURL: 'https://api.example.com', 12 | // 超时时间 单位是ms,这里设置了5s的超时时间 13 | timeout: 5 * 1000 14 | }) 15 | 16 | // 2.请求拦截器request interceptor 17 | service.interceptors.request.use( 18 | config => { 19 | // 发请求前做的一些处理,数据转化,配置请求头,设置token,设置loading等,根据需求去添加 20 | // 注意使用token的时候需要引入cookie方法或者用本地localStorage等方法,推荐js-cookie 21 | if (store.getters.token) { 22 | // config.params = {'token': token} // 如果要求携带在参数中 23 | // config.headers.token = token; // 如果要求携带在请求头中 24 | // bearer:w3c规范 25 | config.headers['Authorization'] = 'Bearer ' + getToken() 26 | } 27 | return config 28 | }, 29 | error => { 30 | // do something with request error 31 | // console.log(error) // for debug 32 | return Promise.reject(error) 33 | } 34 | ) 35 | 36 | // 设置cross跨域 并设置访问权限 允许跨域携带cookie信息,使用JWT可关闭 37 | service.defaults.withCredentials = false 38 | 39 | service.interceptors.response.use( 40 | // 接收到响应数据并成功后的一些共有的处理,关闭loading等 41 | response => { 42 | const res = response.data 43 | // 如果自定义代码不是200,则将其判断为错误。 44 | if (res.code !== 200) { 45 | // 50008: 非法Token; 50012: 异地登录; 50014: Token失效; 46 | if (res.code === 401 || res.code === 50012 || res.code === 50014) { 47 | // 重新登录 48 | MessageBox.confirm('会话失效,您可以留在当前页面,或重新登录', '权限不足', { 49 | confirmButtonText: '确定', 50 | cancelButtonText: '取消', 51 | type: 'warning', 52 | center: true 53 | }).then(() => { 54 | window.location.href = '#/login' 55 | }) 56 | } else { // 其他异常直接提示 57 | Message({ 58 | showClose: true, 59 | message: '⚠' + res.message || 'Error', 60 | type: 'error', 61 | duration: 3 * 1000 62 | }) 63 | } 64 | return Promise.reject(new Error(res.message || 'Error')) 65 | } else { 66 | return res 67 | } 68 | }, 69 | error => { 70 | /** *** 接收到异常响应的处理开始 *****/ 71 | // console.log('err' + error) // for debug 72 | Message({ 73 | showClose: true, 74 | message: error.message, 75 | type: 'error', 76 | duration: 5 * 1000 77 | }) 78 | return Promise.reject(error) 79 | } 80 | ) 81 | export default service -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | 4 | Vue.use(VueRouter); 5 | 6 | const routes = [ 7 | { 8 | path: "/", 9 | name: "Home", 10 | component: () => import("@/views/Home"), 11 | }, 12 | { 13 | path: "/register", 14 | name: "register", 15 | component: () => import("@/views/auth/Register"), 16 | meta: {title: "注册"}, 17 | }, 18 | // 登录 19 | { 20 | name: "login", 21 | path: "/login", 22 | component: () => import("@/views/auth/Login"), 23 | meta: {title: "登录"}, 24 | }, 25 | // 发布 26 | { 27 | name: "post-create", 28 | path: "/post/create", 29 | component: () => import("@/views/post/Create"), 30 | meta: {title: "信息发布", requireAuth: true}, 31 | }, 32 | // 编辑 33 | { 34 | name: 'topic-edit', 35 | path: '/topic/edit/:id', 36 | component: () => import('@/views/post/Edit'), 37 | meta: { 38 | title: '编辑', 39 | requireAuth: true 40 | } 41 | }, 42 | // 详情 43 | { 44 | name: "post-detail", 45 | path: "/post/:id", 46 | component: () => import("@/views/post/Detail"), 47 | meta: {title: "详情"}, 48 | }, 49 | { 50 | name: 'tag', 51 | path: '/tag/:name', 52 | component: () => import('@/views/tag/Tag'), 53 | meta: {title: '主题列表'} 54 | }, 55 | // 检索 56 | { 57 | name: 'search', 58 | path: '/search', 59 | component: () => import('@/views/Search'), 60 | meta: {title: '检索'} 61 | }, 62 | // 用户主页 63 | { 64 | name: 'user', 65 | path: '/member/:username/home', 66 | component: () => import('@/views/user/Profile'), 67 | meta: {title: '用户主页'} 68 | }, 69 | // 用户设置 70 | { 71 | name: 'user-setting', 72 | path: '/member/:username/setting', 73 | component: () => import('@/views/user/Setting'), 74 | meta: {title: '设置', requireAuth: true} 75 | }, 76 | { 77 | path: "/404", 78 | name: "404", 79 | component: () => import("@/views/error/404"), 80 | meta: {title: "404-NotFound"}, 81 | }, 82 | { 83 | path: "*", 84 | redirect: "/404", 85 | hidden: true, 86 | }, 87 | ]; 88 | 89 | const originalPush = VueRouter.prototype.push; 90 | VueRouter.prototype.push = function push(location) { 91 | return originalPush.call(this, location).catch((err) => err); 92 | }; 93 | 94 | const router = new VueRouter({ 95 | scrollBehavior: () => ({y: 0}), 96 | mode: 'history', 97 | routes, 98 | }); 99 | 100 | export default router; 101 | -------------------------------------------------------------------------------- /frontend/src/views/post/Author.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 100 | 101 | 104 | -------------------------------------------------------------------------------- /frontend/src/assets/app.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body, 7 | html { 8 | background-color: #f6f6f6; 9 | color: black; 10 | width: 100%; 11 | font-size: 14px; 12 | letter-spacing: 0.03em; 13 | font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, 14 | Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei, 15 | sans-serif, Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji, 16 | Segoe UI Symbol, Android Emoji, EmojiSymbols; 17 | } 18 | 19 | /*背景图*/ 20 | /*body {*/ 21 | /* background-image: url('https://api.mz-moe.cn/img.php');*/ 22 | /* background-repeat: round;*/ 23 | /*}*/ 24 | 25 | @media (min-width: 768px) { 26 | .container { 27 | width: 760px; 28 | } 29 | } 30 | 31 | @media (min-width: 992px) { 32 | .container { 33 | width: 980px; 34 | } 35 | } 36 | 37 | @media (min-width: 1200px) { 38 | .container { 39 | width: 1080px; 40 | } 41 | } 42 | 43 | /*滚动条*/ 44 | ::-webkit-scrollbar { 45 | width: 10px; 46 | height: 10px; 47 | /**/ 48 | } 49 | 50 | ::-webkit-scrollbar-track { 51 | background: rgb(239, 239, 239); 52 | border-radius: 2px; 53 | } 54 | 55 | ::-webkit-scrollbar-thumb { 56 | background: #bfbfbf; 57 | border-radius: 10px; 58 | } 59 | 60 | ::-webkit-scrollbar-corner { 61 | background: #179a16; 62 | } 63 | 64 | .header { 65 | position: fixed; 66 | z-index: 89; 67 | top: 0; 68 | width: 100%; 69 | min-width: 1032px; 70 | background: #fff; 71 | box-shadow: 0 1px 0px rgba(26, 26, 26, 0.1); 72 | height: 53px; 73 | font-size: 16px; 74 | } 75 | 76 | a { 77 | color: #1d1d1d; 78 | text-decoration: none; 79 | } 80 | 81 | a:hover { 82 | color: #f60; 83 | text-decoration: none !important; 84 | } 85 | 86 | .shadow-1 { 87 | box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 88 | 0 0 0 1px rgba(10, 10, 10, 0.02); 89 | } 90 | 91 | .navbar-dropdown { 92 | font-size: 15px; 93 | } 94 | 95 | /*统一卡片样式*/ 96 | .el-card { 97 | /*border-radius: 3px !important;*/ 98 | margin-bottom: 16px; 99 | /*border: none;*/ 100 | } 101 | 102 | .my-card { 103 | cursor: pointer; 104 | transition: all 0.1s ease-in-out; 105 | position: relative; 106 | overflow: hidden; 107 | } 108 | 109 | .my-card:hover { 110 | transform: scale(1.03); 111 | } 112 | 113 | ::selection { 114 | text-shadow: none; 115 | background: rgba(67, 135, 244, 0.56); 116 | } 117 | 118 | /* 搜索框 */ 119 | .search-bar input { 120 | border: none; 121 | box-shadow: none; 122 | } 123 | 124 | /*按钮居中*/ 125 | .button-center { 126 | display: block; 127 | margin: 0 auto; 128 | } 129 | 130 | .ellipsis { 131 | display: block; 132 | display: -webkit-box; 133 | margin: 0 auto; 134 | line-height: 1.4; 135 | -webkit-box-orient: vertical; 136 | overflow: hidden; 137 | text-overflow: ellipsis; 138 | } 139 | 140 | .is-ellipsis-1 { 141 | -webkit-line-clamp: 1; 142 | } 143 | 144 | .is-ellipsis-2 { 145 | -webkit-line-clamp: 2; 146 | } 147 | 148 | .is-ellipsis-3 { 149 | -webkit-line-clamp: 3; 150 | } 151 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/jwt/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.jwt; 2 | 3 | import io.jsonwebtoken.Jwts; 4 | import io.jsonwebtoken.SignatureAlgorithm; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletRequestWrapper; 10 | import java.util.*; 11 | 12 | public class JwtUtil { 13 | public static final long EXPIRATION_TIME = 3600_000_000L; // 1000 hour 14 | public static final String SECRET = "ThisIsASecret";// please change to your own encryption secret. 15 | public static final String TOKEN_PREFIX = "Bearer "; 16 | public static final String HEADER_STRING = "Authorization"; 17 | public static final String USER_NAME = "userName"; 18 | private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class); 19 | 20 | public static String generateToken(String userId) { 21 | HashMap map = new HashMap<>(); 22 | // you can put any data in the map 23 | map.put(USER_NAME, userId); 24 | String jwt = Jwts.builder() 25 | .setClaims(map) 26 | .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) 27 | .signWith(SignatureAlgorithm.HS512, SECRET) 28 | .compact(); 29 | return jwt; // jwt前面一般都会加Bearer 30 | } 31 | 32 | public static HttpServletRequest validateTokenAndAddUserIdToHeader(HttpServletRequest request) { 33 | String token = request.getHeader(HEADER_STRING); 34 | if (token != null) { 35 | // parse the token. 36 | try { 37 | Map body = Jwts.parser() 38 | .setSigningKey(SECRET) 39 | .parseClaimsJws(token.replace(TOKEN_PREFIX, "")) 40 | .getBody(); 41 | return new CustomHttpServletRequest(request, body); 42 | } catch (Exception e) { 43 | logger.info(e.getMessage()); 44 | throw new TokenValidationException(e.getMessage()); 45 | } 46 | } else { 47 | throw new TokenValidationException("Missing token"); 48 | } 49 | } 50 | 51 | public static class CustomHttpServletRequest extends HttpServletRequestWrapper { 52 | private final Map claims; 53 | 54 | public CustomHttpServletRequest(HttpServletRequest request, Map claims) { 55 | super(request); 56 | this.claims = new HashMap<>(); 57 | claims.forEach((k, v) -> this.claims.put(k, String.valueOf(v))); 58 | } 59 | 60 | @Override 61 | public Enumeration getHeaders(String name) { 62 | if (claims != null && claims.containsKey(name)) { 63 | return Collections.enumeration(Collections.singletonList(claims.get(name))); 64 | } 65 | return super.getHeaders(name); 66 | } 67 | 68 | } 69 | 70 | static class TokenValidationException extends RuntimeException { 71 | public TokenValidationException(String msg) { 72 | super(msg); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/admin/PostManageController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller.admin; 2 | 3 | import com.baomidou.mybatisplus.core.toolkit.StringUtils; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.vdurmont.emoji.EmojiParser; 6 | import com.which.freefish.common.api.ApiResult; 7 | import com.which.freefish.controller.BaseController; 8 | import com.which.freefish.jwt.JwtUtil; 9 | import com.which.freefish.model.dto.CreateTopicDTO; 10 | import com.which.freefish.model.entity.BmsPost; 11 | import com.which.freefish.model.entity.UmsUser; 12 | import com.which.freefish.model.vo.PostVO; 13 | import com.which.freefish.service.IBmsPostService; 14 | import com.which.freefish.service.IUmsUserService; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.util.Assert; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import javax.annotation.Resource; 20 | import java.util.Date; 21 | import java.util.Map; 22 | 23 | /** 24 | * post管理 25 | * 26 | * @author which 27 | * @date 2024/05/19 28 | */ 29 | @RestController 30 | @RequestMapping("/admin/post") 31 | @Slf4j 32 | public class PostManageController extends BaseController { 33 | 34 | @Resource 35 | private IBmsPostService iBmsPostService; 36 | 37 | @Resource 38 | private IUmsUserService umsUserService; 39 | 40 | @GetMapping("/list") 41 | public ApiResult> list(@RequestParam(value = "tab", defaultValue = "latest") String tab, 42 | @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, 43 | @RequestParam(value = "size", defaultValue = "10") Integer pageSize) { 44 | Page list = iBmsPostService.getList(new Page<>(pageNo, pageSize), tab); 45 | return ApiResult.success(list); 46 | } 47 | 48 | @PostMapping("/create") 49 | public ApiResult create(@RequestHeader(value = JwtUtil.USER_NAME) String userName 50 | , @RequestBody CreateTopicDTO dto) { 51 | UmsUser user = umsUserService.getUserByUsername(userName); 52 | BmsPost topic = iBmsPostService.create(dto, user); 53 | return ApiResult.success(topic); 54 | } 55 | 56 | @GetMapping() 57 | public ApiResult> view(@RequestParam("id") String id) { 58 | Map map = iBmsPostService.viewTopic(id); 59 | return ApiResult.success(map); 60 | } 61 | 62 | @PostMapping("/update") 63 | public ApiResult update(@RequestBody BmsPost post) { 64 | post.setModifyTime(new Date()); 65 | String content = post.getContent(); 66 | if (StringUtils.isNotBlank(content)) { 67 | post.setContent(EmojiParser.parseToAliases(content)); 68 | } 69 | iBmsPostService.updateById(post); 70 | return ApiResult.success(post); 71 | } 72 | 73 | @DeleteMapping("/delete/{id}") 74 | public ApiResult delete(@PathVariable("id") String id) { 75 | BmsPost byId = iBmsPostService.getById(id); 76 | Assert.notNull(byId, "来晚一步,话题已不存在"); 77 | iBmsPostService.removeById(id); 78 | return ApiResult.success(null, "删除成功"); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/admin/UserManageController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller.admin; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 5 | import com.which.freefish.common.api.ApiResult; 6 | import com.which.freefish.controller.BaseController; 7 | import com.which.freefish.model.entity.BmsPost; 8 | import com.which.freefish.model.entity.UmsUser; 9 | import com.which.freefish.model.vo.UserVO; 10 | import com.which.freefish.service.IBmsPostService; 11 | import com.which.freefish.service.IUmsUserService; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.beans.BeanUtils; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import javax.annotation.Resource; 17 | import java.util.Date; 18 | import java.util.List; 19 | import java.util.stream.Collectors; 20 | 21 | /** 22 | * user管理 23 | * 24 | * @author which 25 | * @date 2024/05/19 26 | */ 27 | @RestController 28 | @RequestMapping("/admin/user") 29 | @Slf4j 30 | public class UserManageController extends BaseController { 31 | 32 | @Resource 33 | private IUmsUserService umsUserService; 34 | 35 | @Resource 36 | private IBmsPostService postService; 37 | 38 | @GetMapping("/list") 39 | public ApiResult> list(@RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, 40 | @RequestParam(value = "size", defaultValue = "10") Integer pageSize) { 41 | Page list = new Page<>(); 42 | LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); 43 | wrapper.orderByDesc(UmsUser::getModifyTime); 44 | Page page = umsUserService.page(new Page<>(pageNo, pageSize), wrapper); 45 | List collect = page.getRecords().stream().map(umsUser -> { 46 | UserVO userVO = new UserVO(); 47 | BeanUtils.copyProperties(umsUser, userVO); 48 | return userVO; 49 | }).collect(Collectors.toList()); 50 | list.setRecords(collect); 51 | list.setTotal(page.getTotal()); 52 | return ApiResult.success(list); 53 | } 54 | 55 | @PostMapping(value = "/create") 56 | public ApiResult create(@RequestBody UmsUser user) { 57 | return ApiResult.success(umsUserService.save(user)); 58 | } 59 | 60 | @GetMapping(value = "/get") 61 | public ApiResult create(String id) { 62 | UserVO userVO = new UserVO(); 63 | BeanUtils.copyProperties(umsUserService.getById(id), userVO); 64 | return ApiResult.success(userVO); 65 | } 66 | 67 | @PostMapping("/update") 68 | public ApiResult update(@RequestBody UmsUser user) { 69 | user.setModifyTime(new Date()); 70 | return ApiResult.success(umsUserService.updateById(user)); 71 | } 72 | 73 | @DeleteMapping("/delete/{id}") 74 | public ApiResult delete(@PathVariable("id") String id) { 75 | LambdaQueryWrapper bmsPostQueryWrapper = new LambdaQueryWrapper<>(); 76 | bmsPostQueryWrapper.eq(BmsPost::getUserId, id); 77 | postService.remove(bmsPostQueryWrapper); 78 | return ApiResult.success(umsUserService.removeById(id)); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /frontend/src/views/tag/Tag.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 104 | 105 | 110 | -------------------------------------------------------------------------------- /frontend/src/views/auth/Login.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 114 | 115 | -------------------------------------------------------------------------------- /frontend/src/views/Search.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 101 | 102 | 105 | -------------------------------------------------------------------------------- /backend/src/main/resources/mapper/BmsPostMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 42 | 43 | 50 | 51 | 76 | 77 | 97 | 98 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/UmsUserController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.which.freefish.common.api.ApiResult; 7 | import com.which.freefish.jwt.JwtUtil; 8 | import com.which.freefish.model.dto.LoginDTO; 9 | import com.which.freefish.model.dto.RegisterDTO; 10 | import com.which.freefish.model.entity.BmsPost; 11 | import com.which.freefish.model.entity.UmsUser; 12 | import com.which.freefish.service.IBmsPostService; 13 | import com.which.freefish.service.IUmsUserService; 14 | import org.springframework.util.Assert; 15 | import org.springframework.util.ObjectUtils; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | import javax.annotation.Resource; 19 | import javax.validation.Valid; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | 24 | @RestController 25 | @RequestMapping("/ums/user") 26 | public class UmsUserController extends BaseController { 27 | @Resource 28 | private IUmsUserService iUmsUserService; 29 | @Resource 30 | private IBmsPostService iBmsPostService; 31 | 32 | @RequestMapping(value = "/register", method = RequestMethod.POST) 33 | public ApiResult> register(@Valid @RequestBody RegisterDTO dto) { 34 | UmsUser user = iUmsUserService.executeRegister(dto); 35 | if (ObjectUtils.isEmpty(user)) { 36 | return ApiResult.failed("账号注册失败"); 37 | } 38 | Map map = new HashMap<>(16); 39 | map.put("user", user); 40 | return ApiResult.success(map); 41 | } 42 | 43 | @RequestMapping(value = "/login", method = RequestMethod.POST) 44 | public ApiResult> login(@Valid @RequestBody LoginDTO dto) { 45 | String token = iUmsUserService.executeLogin(dto); 46 | if (ObjectUtils.isEmpty(token)) { 47 | return ApiResult.failed("账号密码错误"); 48 | } 49 | Map map = new HashMap<>(16); 50 | map.put("token", token); 51 | return ApiResult.success(map, "登录成功"); 52 | } 53 | 54 | @RequestMapping(value = "/info", method = RequestMethod.GET) 55 | public ApiResult getUser(@RequestHeader(value = JwtUtil.USER_NAME) String userName) { 56 | UmsUser user = iUmsUserService.getUserByUsername(userName); 57 | return ApiResult.success(user); 58 | } 59 | 60 | @RequestMapping(value = "/logout", method = RequestMethod.GET) 61 | public ApiResult logOut() { 62 | return ApiResult.success(null, "注销成功"); 63 | } 64 | 65 | @GetMapping("/{username}") 66 | public ApiResult> getUserByName(@PathVariable("username") String username, 67 | @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, 68 | @RequestParam(value = "size", defaultValue = "10") Integer size) { 69 | Map map = new HashMap<>(16); 70 | UmsUser user = iUmsUserService.getUserByUsername(username); 71 | Assert.notNull(user, "用户不存在"); 72 | Page page = iBmsPostService.page(new Page<>(pageNo, size), 73 | new LambdaQueryWrapper().eq(BmsPost::getUserId, user.getId())); 74 | map.put("user", user); 75 | map.put("topics", page); 76 | return ApiResult.success(map); 77 | } 78 | 79 | @PostMapping("/update") 80 | public ApiResult updateUser(@RequestBody UmsUser umsUser) { 81 | iUmsUserService.updateById(umsUser); 82 | return ApiResult.success(umsUser); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/BmsRelationshipController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.which.freefish.common.api.ApiResult; 5 | import com.which.freefish.common.exception.ApiAsserts; 6 | import com.which.freefish.jwt.JwtUtil; 7 | import com.which.freefish.model.entity.BmsFollow; 8 | import com.which.freefish.model.entity.UmsUser; 9 | import com.which.freefish.service.IBmsFollowService; 10 | import com.which.freefish.service.IUmsUserService; 11 | import org.springframework.util.ObjectUtils; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import javax.annotation.Resource; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | @RestController 19 | @RequestMapping("/relationship") 20 | public class BmsRelationshipController extends BaseController { 21 | 22 | @Resource 23 | private IBmsFollowService bmsFollowService; 24 | 25 | @Resource 26 | private IUmsUserService umsUserService; 27 | 28 | @GetMapping("/subscribe/{userId}") 29 | public ApiResult handleFollow(@RequestHeader(value = JwtUtil.USER_NAME) String userName 30 | , @PathVariable("userId") String parentId) { 31 | UmsUser umsUser = umsUserService.getUserByUsername(userName); 32 | if (parentId.equals(umsUser.getId())) { 33 | ApiAsserts.fail("您脸皮太厚了,怎么可以关注自己呢 😮"); 34 | } 35 | BmsFollow one = bmsFollowService.getOne( 36 | new LambdaQueryWrapper() 37 | .eq(BmsFollow::getParentId, parentId) 38 | .eq(BmsFollow::getFollowerId, umsUser.getId())); 39 | if (!ObjectUtils.isEmpty(one)) { 40 | ApiAsserts.fail("已关注"); 41 | } 42 | 43 | BmsFollow follow = new BmsFollow(); 44 | follow.setParentId(parentId); 45 | follow.setFollowerId(umsUser.getId()); 46 | bmsFollowService.save(follow); 47 | return ApiResult.success(null, "关注成功"); 48 | } 49 | 50 | @GetMapping("/unsubscribe/{userId}") 51 | public ApiResult handleUnFollow(@RequestHeader(value = JwtUtil.USER_NAME) String userName 52 | , @PathVariable("userId") String parentId) { 53 | UmsUser umsUser = umsUserService.getUserByUsername(userName); 54 | BmsFollow one = bmsFollowService.getOne( 55 | new LambdaQueryWrapper() 56 | .eq(BmsFollow::getParentId, parentId) 57 | .eq(BmsFollow::getFollowerId, umsUser.getId())); 58 | if (ObjectUtils.isEmpty(one)) { 59 | ApiAsserts.fail("未关注!"); 60 | } 61 | bmsFollowService.remove(new LambdaQueryWrapper().eq(BmsFollow::getParentId, parentId) 62 | .eq(BmsFollow::getFollowerId, umsUser.getId())); 63 | return ApiResult.success(null, "取关成功"); 64 | } 65 | 66 | @GetMapping("/validate/{topicUserId}") 67 | public ApiResult> isFollow(@RequestHeader(value = JwtUtil.USER_NAME) String userName 68 | , @PathVariable("topicUserId") String topicUserId) { 69 | UmsUser umsUser = umsUserService.getUserByUsername(userName); 70 | Map map = new HashMap<>(16); 71 | map.put("hasFollow", false); 72 | if (!ObjectUtils.isEmpty(umsUser)) { 73 | BmsFollow one = bmsFollowService.getOne(new LambdaQueryWrapper() 74 | .eq(BmsFollow::getParentId, topicUserId) 75 | .eq(BmsFollow::getFollowerId, umsUser.getId())); 76 | if (!ObjectUtils.isEmpty(one)) { 77 | map.put("hasFollow", true); 78 | } 79 | } 80 | return ApiResult.success(map); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

社区文章管理系统

5 |

基于 SpringBoot + MyBatis + MySQL + Redis + Vue2 + ElementUI 的社区文章管理系统

6 |
7 | 8 | 9 | 10 | Maven 11 | 12 | 13 | 14 | SpringBoot 15 |
16 | 17 | ## 项目介绍 18 | 19 | 社区文章管理系统基于SpringBoot + MyBatis + MySQL + Redis + Vue2 + ElementUI,实现了用户注册、登录、发帖、评论、关注、搜索和管理员管理文章、用户的增删改查等功能。 20 | 21 | ## 使用指导 22 | 23 | ### 克隆项目到本地 24 | 25 | ```bash 26 | git clone git@github.com:which0113/community-post-manage.git 27 | ``` 28 | 29 | ### 运行前端 30 | 31 | #### 切换到前端项目 32 | 33 | ```bash 34 | cd frontend 35 | ``` 36 | 37 | 确保 node >= 16,查看 node 版本 38 | 39 | ```bash 40 | node -v 41 | ``` 42 | 43 | #### 安装依赖 44 | 45 | ```bash 46 | npm install 47 | ``` 48 | 49 | #### 启动 50 | 51 | ```bash 52 | npm run serve 53 | ``` 54 | 55 | #### 其他 56 | 57 | - [vue.config.js](frontend%2Fvue.config.js) 文件可修改前端启动地址, 默认:http://localhost:3001 58 | - [.env](frontend%2F.env) 文件可修改对接后端服务器接口的地址,默认:http://localhost:9001 59 | 60 | #### 部署前端 61 | 62 | 方法一:生成 dist 文件部署 63 | 64 | ```bash 65 | npm run build 66 | ``` 67 | 68 | 方法二:使用 docker 容器部署 69 | 70 | 构建: 71 | 72 | ```bash 73 | docker build -t freefish-community-frontend:v0.0.1 . 74 | ``` 75 | 76 | 运行: 77 | 78 | ```bash 79 | docker run -p 80:80 -d freefish-community-frontend:v0.0.1 80 | ``` 81 | 82 | ### 运行后端 83 | 84 | #### 注意事项 85 | 86 | - JDK 版本为 1.8 87 | - MySQL 版本为 8.0+ 88 | - Maven 版本为 3.9+ 89 | - Redis 版本为 5.0+ 90 | 91 | #### 其他 92 | 93 | - [application-dev.yml](backend%2Fsrc%2Fmain%2Fresources%2Fapplication-dev.yml) 文件可修改 **服务启动端口、MySQL、Redis 配置等等** 94 | - [ddl.sql](backend%2Fsql%2Fddl.sql) 文件是 MySQL 数据库文件,可 **Ctrl A 全选 + Ctrl Enter 执行** 快速初始化数据库 95 | - 管理员默认 **账号 | 密码** 为:**admin | 123456** 96 | 97 | #### 部署后端 98 | 99 | 方法一:生成 jar 包部署(跳过单元测试) 100 | 101 | ``` 102 | mvn package -DskipTests 103 | ``` 104 | 105 | 方法二:使用 docker 容器部署 106 | 107 | 构建: 108 | 109 | ```bash 110 | docker build -t freefish-community-backend:v0.0.1 . 111 | ``` 112 | 113 | 运行: 114 | 115 | ```bash 116 | docker run -p 9001:9001 -d freefish-community-backend:v0.0.1 117 | ``` 118 | 119 | ## 项目展示 120 | 121 | ### 管理员 122 | 123 | #### 帖子管理 124 | 125 | ![post_m.png](doc%2Fpost_m.png) 126 | 127 | #### 用户管理 128 | 129 | ![user_m.png](doc%2Fuser_m.png) 130 | 131 | ### 用户 132 | 133 | #### 主页 134 | 135 | ![home.png](doc%2Fhome.png) 136 | 137 | #### 暗黑主题 138 | 139 | ![dark.png](doc%2Fdark.png) 140 | 141 | #### 用户注册 142 | 143 | ![register.png](doc%2Fregister.png) 144 | 145 | #### 用户登录 146 | 147 | ![login.png](doc%2Flogin.png) 148 | 149 | #### 文章详情 150 | 151 | ![post.png](doc%2Fpost.png) 152 | 153 | #### 文章评论 154 | 155 | ![pl.png](doc%2Fpl.png) 156 | 157 | #### 文章发布 158 | 159 | ![post_f.png](doc%2Fpost_f.png) 160 | 161 | #### 文章搜索 162 | 163 | ![search.png](doc%2Fsearch.png) 164 | 165 | #### 文章管理 166 | 167 | ![all_post.png](doc%2Fall_post.png) 168 | 169 | #### 文章编辑 170 | 171 | ![update.png](doc%2Fupdate.png) 172 | 173 | ## 技术栈 174 | 175 | ### 前端 176 | 177 | - Vue2 构建 JavaScript 的框架 178 | - ElementUI 美观易用的 UI 组件库 179 | 180 | ### 后端 181 | 182 | - SpringBoot 183 | - Spring MVC 184 | - MySQL 数据库 185 | - Redis 数据缓存 186 | - Spring Security(JWT 安全校验) 187 | - MyBatis-Plus 及 MyBatis X 代码自动生成 188 | - Hutool、Apache Common Utils、Gson 等工具库 189 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/impl/IUmsUserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import com.which.freefish.common.exception.ApiAsserts; 6 | import com.which.freefish.jwt.JwtUtil; 7 | import com.which.freefish.mapper.BmsFollowMapper; 8 | import com.which.freefish.mapper.BmsTopicMapper; 9 | import com.which.freefish.mapper.UmsUserMapper; 10 | import com.which.freefish.model.dto.LoginDTO; 11 | import com.which.freefish.model.dto.RegisterDTO; 12 | import com.which.freefish.model.entity.BmsFollow; 13 | import com.which.freefish.model.entity.BmsPost; 14 | import com.which.freefish.model.entity.UmsUser; 15 | import com.which.freefish.model.vo.ProfileVO; 16 | import com.which.freefish.service.IUmsUserService; 17 | import com.which.freefish.utils.MD5Utils; 18 | import lombok.extern.slf4j.Slf4j; 19 | import org.springframework.beans.BeanUtils; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.stereotype.Service; 22 | import org.springframework.transaction.annotation.Transactional; 23 | import org.springframework.util.ObjectUtils; 24 | 25 | import java.util.Date; 26 | 27 | 28 | @Slf4j 29 | @Service 30 | @Transactional(rollbackFor = Exception.class) 31 | public class IUmsUserServiceImpl extends ServiceImpl implements IUmsUserService { 32 | 33 | @Autowired 34 | private BmsTopicMapper bmsTopicMapper; 35 | @Autowired 36 | private BmsFollowMapper bmsFollowMapper; 37 | 38 | @Override 39 | public UmsUser executeRegister(RegisterDTO dto) { 40 | // 查询是否有相同用户名的用户 41 | LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); 42 | wrapper.eq(UmsUser::getUsername, dto.getName()).or().eq(UmsUser::getEmail, dto.getEmail()); 43 | UmsUser umsUser = baseMapper.selectOne(wrapper); 44 | if (!ObjectUtils.isEmpty(umsUser)) { 45 | ApiAsserts.fail("账号或邮箱已存在!"); 46 | } 47 | UmsUser addUser = UmsUser.builder() 48 | .username(dto.getName()) 49 | .alias(dto.getName()) 50 | .password(MD5Utils.getPwd(dto.getPass())) 51 | .email(dto.getEmail()) 52 | .createTime(new Date()) 53 | .status(true) 54 | .build(); 55 | baseMapper.insert(addUser); 56 | 57 | return addUser; 58 | } 59 | 60 | @Override 61 | public UmsUser getUserByUsername(String username) { 62 | return baseMapper.selectOne(new LambdaQueryWrapper().eq(UmsUser::getUsername, username)); 63 | } 64 | 65 | @Override 66 | public String executeLogin(LoginDTO dto) { 67 | String token = null; 68 | try { 69 | UmsUser user = getUserByUsername(dto.getUsername()); 70 | String encodePwd = MD5Utils.getPwd(dto.getPassword()); 71 | if (!encodePwd.equals(user.getPassword())) { 72 | throw new Exception("密码错误"); 73 | } 74 | token = JwtUtil.generateToken(String.valueOf(user.getUsername())); 75 | } catch (Exception e) { 76 | log.warn("用户不存在or密码验证失败=======>{}", dto.getUsername()); 77 | } 78 | return token; 79 | } 80 | 81 | @Override 82 | public ProfileVO getUserProfile(String id) { 83 | ProfileVO profile = new ProfileVO(); 84 | UmsUser user = baseMapper.selectById(id); 85 | BeanUtils.copyProperties(user, profile); 86 | // 用户文章数 87 | int count = bmsTopicMapper.selectCount(new LambdaQueryWrapper().eq(BmsPost::getUserId, id)); 88 | profile.setTopicCount(count); 89 | 90 | // 粉丝数 91 | int followers = bmsFollowMapper.selectCount((new LambdaQueryWrapper().eq(BmsFollow::getParentId, id))); 92 | profile.setFollowerCount(followers); 93 | 94 | return profile; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/controller/BmsPostController.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.controller; 2 | 3 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 4 | import com.vdurmont.emoji.EmojiParser; 5 | import com.which.freefish.common.api.ApiResult; 6 | import com.which.freefish.jwt.JwtUtil; 7 | import com.which.freefish.model.dto.CreateTopicDTO; 8 | import com.which.freefish.model.entity.BmsPost; 9 | import com.which.freefish.model.entity.UmsUser; 10 | import com.which.freefish.model.vo.PostVO; 11 | import com.which.freefish.service.IBmsPostService; 12 | import com.which.freefish.service.IUmsUserService; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.cache.annotation.CacheEvict; 15 | import org.springframework.cache.annotation.Cacheable; 16 | import org.springframework.util.Assert; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import javax.annotation.Resource; 20 | import javax.validation.Valid; 21 | import java.util.Date; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | @RestController 26 | @RequestMapping("/post") 27 | @Slf4j 28 | public class BmsPostController extends BaseController { 29 | 30 | @Resource 31 | private IBmsPostService iBmsPostService; 32 | @Resource 33 | private IUmsUserService umsUserService; 34 | 35 | @GetMapping("/list") 36 | @Cacheable(cacheNames = "allPost", key = "#tab") 37 | public ApiResult> list(@RequestParam(value = "tab", defaultValue = "latest") String tab, 38 | @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, 39 | @RequestParam(value = "size", defaultValue = "10") Integer pageSize) { 40 | Page list = iBmsPostService.getList(new Page<>(pageNo, pageSize), tab); 41 | return ApiResult.success(list); 42 | } 43 | 44 | @RequestMapping(value = "/create", method = RequestMethod.POST) 45 | @CacheEvict(cacheNames = "allPost", allEntries = true) 46 | public ApiResult create(@RequestHeader(value = JwtUtil.USER_NAME) String userName 47 | , @RequestBody CreateTopicDTO dto) { 48 | UmsUser user = umsUserService.getUserByUsername(userName); 49 | BmsPost topic = iBmsPostService.create(dto, user); 50 | return ApiResult.success(topic); 51 | } 52 | 53 | @GetMapping() 54 | public ApiResult> view(@RequestParam("id") String id) { 55 | Map map = iBmsPostService.viewTopic(id); 56 | return ApiResult.success(map); 57 | } 58 | 59 | @GetMapping("/recommend") 60 | public ApiResult> getRecommend(@RequestParam("topicId") String id) { 61 | List topics = iBmsPostService.getRecommend(id); 62 | return ApiResult.success(topics); 63 | } 64 | 65 | @PostMapping("/update") 66 | @CacheEvict(cacheNames = "allPost", allEntries = true) 67 | public ApiResult update(@RequestHeader(value = JwtUtil.USER_NAME) String userName, @Valid @RequestBody BmsPost post) { 68 | UmsUser umsUser = umsUserService.getUserByUsername(userName); 69 | Assert.isTrue(umsUser.getId().equals(post.getUserId()), "非本人无权修改"); 70 | post.setModifyTime(new Date()); 71 | post.setContent(EmojiParser.parseToAliases(post.getContent())); 72 | iBmsPostService.updateById(post); 73 | return ApiResult.success(post); 74 | } 75 | 76 | @DeleteMapping("/delete/{id}") 77 | @CacheEvict(cacheNames = "allPost", allEntries = true) 78 | public ApiResult delete(@RequestHeader(value = JwtUtil.USER_NAME) String userName, @PathVariable("id") String id) { 79 | UmsUser umsUser = umsUserService.getUserByUsername(userName); 80 | BmsPost byId = iBmsPostService.getById(id); 81 | Assert.notNull(byId, "来晚一步,话题已不存在"); 82 | Assert.isTrue(byId.getUserId().equals(umsUser.getId()), "你为什么可以删除别人的话题???"); 83 | iBmsPostService.removeById(id); 84 | return ApiResult.success(null, "删除成功"); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /frontend/src/views/post/Edit.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 132 | 133 | 138 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/common/api/ApiResult.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.common.api; 2 | 3 | 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.io.Serializable; 8 | import java.util.Optional; 9 | 10 | 11 | @Data 12 | @NoArgsConstructor 13 | public class ApiResult implements Serializable { 14 | 15 | private static final long serialVersionUID = -4153430394359594346L; 16 | /** 17 | * 业务状态码 18 | */ 19 | private long code; 20 | /** 21 | * 结果集 22 | */ 23 | private T data; 24 | /** 25 | * 接口描述 26 | */ 27 | private String message; 28 | 29 | /** 30 | * 全参 31 | * 32 | * @param code 业务状态码 33 | * @param message 描述 34 | * @param data 结果集 35 | */ 36 | public ApiResult(long code, String message, T data) { 37 | this.code = code; 38 | this.message = message; 39 | this.data = data; 40 | } 41 | 42 | public ApiResult(IErrorCode errorCode) { 43 | errorCode = Optional.ofNullable(errorCode).orElse(ApiErrorCode.FAILED); 44 | this.code = errorCode.getCode(); 45 | this.message = errorCode.getMessage(); 46 | } 47 | 48 | /** 49 | * 成功 50 | * 51 | * @param data 结果集 52 | * @return {code:200,message:操作成功,data:自定义} 53 | */ 54 | public static ApiResult success() { 55 | return new ApiResult(ApiErrorCode.SUCCESS.getCode(), ApiErrorCode.SUCCESS.getMessage(), null); 56 | } 57 | 58 | /** 59 | * 成功 60 | * 61 | * @param data 结果集 62 | * @return {code:200,message:操作成功,data:自定义} 63 | */ 64 | public static ApiResult success(T data) { 65 | return new ApiResult(ApiErrorCode.SUCCESS.getCode(), ApiErrorCode.SUCCESS.getMessage(), data); 66 | } 67 | 68 | /** 69 | * 成功 70 | * 71 | * @param data 结果集 72 | * @param message 自定义提示信息 73 | * @return {code:200,message:自定义,data:自定义} 74 | */ 75 | public static ApiResult success(T data, String message) { 76 | return new ApiResult(ApiErrorCode.SUCCESS.getCode(), message, data); 77 | } 78 | 79 | /** 80 | * 失败返回结果 81 | */ 82 | public static ApiResult failed() { 83 | return failed(ApiErrorCode.FAILED); 84 | } 85 | 86 | /** 87 | * 失败返回结果 88 | * 89 | * @param message 提示信息 90 | * @return {code:枚举ApiErrorCode取,message:自定义,data:null} 91 | */ 92 | public static ApiResult failed(String message) { 93 | return new ApiResult(ApiErrorCode.FAILED.getCode(), message, null); 94 | } 95 | 96 | /** 97 | * 失败 98 | * 99 | * @param errorCode 错误码 100 | * @return {code:封装接口取,message:封装接口取,data:null} 101 | */ 102 | public static ApiResult failed(IErrorCode errorCode) { 103 | return new ApiResult(errorCode.getCode(), errorCode.getMessage(), null); 104 | } 105 | 106 | /** 107 | * 失败返回结果 108 | * 109 | * @param errorCode 错误码 110 | * @param message 错误信息 111 | * @return {code:枚举ApiErrorCode取,message:自定义,data:null} 112 | */ 113 | public static ApiResult failed(IErrorCode errorCode, String message) { 114 | return new ApiResult(errorCode.getCode(), message, null); 115 | } 116 | 117 | /** 118 | * 参数验证失败返回结果 119 | */ 120 | public static ApiResult validateFailed() { 121 | return failed(ApiErrorCode.VALIDATE_FAILED); 122 | } 123 | 124 | /** 125 | * 参数验证失败返回结果 126 | * 127 | * @param message 提示信息 128 | */ 129 | public static ApiResult validateFailed(String message) { 130 | return new ApiResult(ApiErrorCode.VALIDATE_FAILED.getCode(), message, null); 131 | } 132 | 133 | /** 134 | * 未登录返回结果 135 | */ 136 | public static ApiResult unauthorized(T data) { 137 | return new ApiResult(ApiErrorCode.UNAUTHORIZED.getCode(), ApiErrorCode.UNAUTHORIZED.getMessage(), data); 138 | } 139 | 140 | /** 141 | * 未授权返回结果 142 | */ 143 | public static ApiResult forbidden(T data) { 144 | return new ApiResult(ApiErrorCode.FORBIDDEN.getCode(), ApiErrorCode.FORBIDDEN.getMessage(), data); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /frontend/src/views/post/Create.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 155 | 156 | 158 | -------------------------------------------------------------------------------- /frontend/src/views/auth/Register.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 143 | 144 | -------------------------------------------------------------------------------- /frontend/src/views/post/Detail.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 158 | 159 | 164 | -------------------------------------------------------------------------------- /backend/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.3.8.RELEASE 9 | 10 | 11 | com.which 12 | backend 13 | 0.0.1-SNAPSHOT 14 | 社区文章管理系统后端 15 | Demo project for Spring Boot 16 | 17 | 1.8 18 | UTF-8 19 | UTF-8 20 | 3.4.2 21 | 1.2.75 22 | 0.9.1 23 | 5.1.1 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | true 38 | 39 | 40 | 41 | io.jsonwebtoken 42 | jjwt 43 | ${jwt.version} 44 | 45 | 46 | 47 | com.vdurmont 48 | emoji-java 49 | ${emoji-java.version} 50 | 51 | 52 | 53 | org.apache.commons 54 | commons-pool2 55 | 56 | 57 | 58 | mysql 59 | mysql-connector-java 60 | runtime 61 | 62 | 63 | 64 | com.baomidou 65 | mybatis-plus-boot-starter 66 | ${mybatis-plus.version} 67 | 68 | 69 | 70 | com.alibaba 71 | fastjson 72 | ${fastjson.version} 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-configuration-processor 78 | true 79 | 80 | 81 | 82 | org.hibernate.validator 83 | hibernate-validator 84 | 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-starter-data-redis 89 | 90 | 91 | org.springframework.boot 92 | spring-boot-starter-test 93 | test 94 | 95 | 96 | org.junit.vintage 97 | junit-vintage-engine 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | org.springframework.boot 107 | spring-boot-maven-plugin 108 | 109 | 110 | 111 | org.projectlombok 112 | lombok 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /frontend/src/components/Layout/Header.vue: -------------------------------------------------------------------------------- 1 | 112 | 113 | 176 | 177 | 183 | -------------------------------------------------------------------------------- /backend/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 33 | * use instead of the default one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /frontend/src/views/user/Profile.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 152 | 153 | 156 | -------------------------------------------------------------------------------- /backend/src/main/java/com/which/freefish/service/impl/IBmsPostServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.which.freefish.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 5 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 6 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 7 | import com.vdurmont.emoji.EmojiParser; 8 | import com.which.freefish.mapper.BmsTagMapper; 9 | import com.which.freefish.mapper.BmsTopicMapper; 10 | import com.which.freefish.mapper.UmsUserMapper; 11 | import com.which.freefish.model.dto.CreateTopicDTO; 12 | import com.which.freefish.model.entity.BmsPost; 13 | import com.which.freefish.model.entity.BmsTag; 14 | import com.which.freefish.model.entity.BmsTopicTag; 15 | import com.which.freefish.model.entity.UmsUser; 16 | import com.which.freefish.model.vo.PostVO; 17 | import com.which.freefish.model.vo.ProfileVO; 18 | import com.which.freefish.service.IBmsPostService; 19 | import com.which.freefish.service.IBmsTagService; 20 | import com.which.freefish.service.IUmsUserService; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.context.annotation.Lazy; 23 | import org.springframework.data.redis.core.RedisTemplate; 24 | import org.springframework.stereotype.Service; 25 | import org.springframework.transaction.annotation.Transactional; 26 | import org.springframework.util.Assert; 27 | import org.springframework.util.ObjectUtils; 28 | 29 | import javax.annotation.Resource; 30 | import java.util.*; 31 | import java.util.stream.Collectors; 32 | 33 | @Service 34 | public class IBmsPostServiceImpl extends ServiceImpl implements IBmsPostService { 35 | @Resource 36 | private BmsTagMapper bmsTagMapper; 37 | @Resource 38 | private UmsUserMapper umsUserMapper; 39 | 40 | @Autowired 41 | @Lazy 42 | private IBmsTagService iBmsTagService; 43 | 44 | @Autowired 45 | private IUmsUserService iUmsUserService; 46 | 47 | @Autowired 48 | private com.which.freefish.service.IBmsTopicTagService IBmsTopicTagService; 49 | 50 | @Resource 51 | private RedisTemplate redisTemplate; 52 | 53 | @Override 54 | public Page getList(Page page, String tab) { 55 | // 查询话题 56 | Page iPage = this.baseMapper.selectListAndPage(page, tab); 57 | // 查询话题的标签 58 | setTopicTags(iPage); 59 | return iPage; 60 | } 61 | 62 | @Override 63 | @Transactional(rollbackFor = Exception.class) 64 | public BmsPost create(CreateTopicDTO dto, UmsUser user) { 65 | BmsPost topic1 = this.baseMapper.selectOne(new LambdaQueryWrapper().eq(BmsPost::getTitle, dto.getTitle())); 66 | Assert.isNull(topic1, "话题已存在,请修改"); 67 | 68 | // 封装 69 | BmsPost topic = BmsPost.builder() 70 | .userId(user.getId()) 71 | .title(dto.getTitle()) 72 | .content(EmojiParser.parseToAliases(dto.getContent())) 73 | .createTime(new Date()) 74 | .build(); 75 | this.baseMapper.insert(topic); 76 | 77 | // 清空redis缓存 78 | redisTemplate.delete("*"); 79 | 80 | // 用户积分增加 81 | int newScore = user.getScore() + 1; 82 | umsUserMapper.updateById(user.setScore(newScore)); 83 | 84 | // 标签 85 | if (!ObjectUtils.isEmpty(dto.getTags())) { 86 | // 保存标签 87 | List tags = iBmsTagService.insertTags(dto.getTags()); 88 | // 处理标签与话题的关联 89 | IBmsTopicTagService.createTopicTag(topic.getId(), tags); 90 | } 91 | 92 | return topic; 93 | } 94 | 95 | @Override 96 | public Map viewTopic(String id) { 97 | Map map = new HashMap<>(16); 98 | BmsPost topic = this.baseMapper.selectById(id); 99 | Assert.notNull(topic, "当前话题不存在,或已被作者删除"); 100 | // 查询话题详情 101 | topic.setView(topic.getView() + 1); 102 | this.baseMapper.updateById(topic); 103 | // emoji转码 104 | topic.setContent(EmojiParser.parseToUnicode(topic.getContent())); 105 | map.put("topic", topic); 106 | // 标签 107 | QueryWrapper wrapper = new QueryWrapper<>(); 108 | wrapper.lambda().eq(BmsTopicTag::getTopicId, topic.getId()); 109 | Set set = new HashSet<>(); 110 | for (BmsTopicTag articleTag : IBmsTopicTagService.list(wrapper)) { 111 | set.add(articleTag.getTagId()); 112 | } 113 | List tags = iBmsTagService.listByIds(set); 114 | map.put("tags", tags); 115 | // 作者 116 | ProfileVO user = iUmsUserService.getUserProfile(topic.getUserId()); 117 | map.put("user", user); 118 | 119 | return map; 120 | } 121 | 122 | @Override 123 | public List getRecommend(String id) { 124 | return this.baseMapper.selectRecommend(id); 125 | } 126 | 127 | @Override 128 | public Page searchByKey(String keyword, Page page) { 129 | return this.baseMapper.searchByKey(page, keyword); 130 | } 131 | 132 | private void setTopicTags(Page iPage) { 133 | iPage.getRecords().forEach(topic -> { 134 | List topicTags = IBmsTopicTagService.selectByTopicId(topic.getId()); 135 | if (!topicTags.isEmpty()) { 136 | List tagIds = topicTags.stream().map(BmsTopicTag::getTagId).collect(Collectors.toList()); 137 | List tags = bmsTagMapper.selectBatchIds(tagIds); 138 | topic.setTags(tags); 139 | } 140 | }); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /frontend/src/views/user/Setting.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 159 | 160 | 163 | --------------------------------------------------------------------------------