当前位置:   article > 正文

(Vue+SpringBoot+elementUi+WangEditer)仿论坛项目_springboot elementui 论坛项目开源

springboot elementui 论坛项目开源

项目使用到的技术与库

1.前端 Vue2 elementUi Cookie WangEditer

2.后端 SpringBoot Mybatis-Plus

3.数据库 MySql

一、效果展示

1.1主页效果:

1.2 文章编辑页面:

1.3 成功发布文章

1.4 文章关键字搜索提示

 1.5 文章查询结果展示

1.6 文章内容及交互展示


二、表单设计的sql

用户:

  1. create table paitool.user
  2. (
  3. id int auto_increment
  4. primary key,
  5. account varchar(255) not null,
  6. password varchar(255) not null,
  7. phone varchar(20) null,
  8. address varchar(255) null,
  9. isVip tinyint(1) default 0 null,
  10. email varchar(255) null,
  11. registration_date datetime default CURRENT_TIMESTAMP null,
  12. last_login datetime null,
  13. status enum ('active', 'inactive') default 'active' null,
  14. constraint account_UNIQUE
  15. unique (account),
  16. constraint email_UNIQUE
  17. unique (email),
  18. constraint phone_UNIQUE
  19. unique (phone)
  20. );

文章:

  1. create table paitool.forum_posts
  2. (
  3. id int auto_increment
  4. primary key,
  5. title varchar(255) not null,
  6. content text not null,
  7. author_id int not null,
  8. created_at timestamp default CURRENT_TIMESTAMP null,
  9. updated_at timestamp default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP,
  10. heat_value int default 0 null,
  11. rating decimal(3, 2) default 0.00 null,
  12. tag varchar(10) default '其它' null,
  13. constraint forum_posts_ibfk_1
  14. foreign key (author_id) references paitool.user (id)
  15. );

文章交互表-点赞:

  1. create table paitool.forum_post_likes
  2. (
  3. user_id int not null,
  4. post_id int not null,
  5. primary key (user_id, post_id),
  6. constraint forum_post_likes_ibfk_1
  7. foreign key (user_id) references paitool.user (id),
  8. constraint forum_post_likes_ibfk_2
  9. foreign key (post_id) references paitool.forum_posts (id)
  10. );

文章交互表-收藏:

  1. create table paitool.forum_post_favorites
  2. (
  3. user_id int not null,
  4. post_id int not null,
  5. primary key (user_id, post_id),
  6. constraint forum_post_favorites_ibfk_1
  7. foreign key (user_id) references paitool.user (id),
  8. constraint forum_post_favorites_ibfk_2
  9. foreign key (post_id) references paitool.forum_posts (id)
  10. );

文章交互表-评论

  1. create table paitool.forum_comments
  2. (
  3. id int auto_increment
  4. primary key,
  5. post_id int not null,
  6. user_id int not null,
  7. comment_text text not null,
  8. created_at timestamp default CURRENT_TIMESTAMP null,
  9. constraint forum_comments_ibfk_1
  10. foreign key (user_id) references paitool.user (id),
  11. constraint forum_comments_ibfk_2
  12. foreign key (post_id) references paitool.forum_posts (id)
  13. );

三、前端代码

3.1 论坛主页

Html:

  1. <template>
  2. <div id="forumLayOut">
  3. <div id="Top" style="background-color: rgb(250, 250, 250); padding-top: 20px">
  4. <div id="serchBorder" style="padding-bottom: 13px;">
  5. <!-- 搜索框 -->
  6. <el-autocomplete v-model="searchKeyWord" :fetch-suggestions="querySearchAsync" :trigger-on-focus="false"
  7. placeholder="请输入关键字" style="width: 300px;" @select="handleSelect">
  8. </el-autocomplete>
  9. <el-button type="primary" @click="onSubmit">查询</el-button>
  10. </div>
  11. <!-- 分类查询 -->
  12. <div>
  13. <div style="margin-bottom: 15px;">
  14. <el-checkbox-group v-model="checkboxGroup1" :max="1">
  15. <el-checkbox-button v-for="city in cities" :label="city" :key="city">{{ city
  16. }}</el-checkbox-button>
  17. </el-checkbox-group>
  18. </div>
  19. </div>
  20. <div style="height: 380px; width: 100%;">
  21. <!-- 轮播图 -->
  22. <div class="block" style="width: 30%; float: left; margin-left: 5%; height: 400px;">
  23. <el-carousel height="350px" style="width: 100%; ">
  24. <el-carousel-item v-for="item in 4" :key="item">
  25. <img src="https://img95.699pic.com/photo/50035/3211.jpg_wh860.jpg" alt="风景测试">
  26. <h3 class="small">{{ item }}</h3>
  27. </el-carousel-item>
  28. </el-carousel>
  29. </div>
  30. <div style=" height: 350px; background-color: rgb(250, 250, 250); width: 25%; float: left;
  31. border: 1px solid rgb(240, 240, 242); margin-left: 3%;">
  32. <div style="height: 50px; width: 100%; background-color: rgb(245, 245, 245); ">
  33. <i class="el-icon-share"></i>
  34. <div><b>热门</b></div>
  35. <hr>
  36. </div>
  37. <div class="link-container">
  38. <a href="#" class="link" id="TurnLink">杀死谷歌,成为AI时代的搜索皇帝!</a>
  39. <p style="color: gray;">Perplexity CEO 最新四万字访谈</p>
  40. </div>
  41. <div class="link-container">
  42. <a href="#" class="link" id="TurnLink">重写系统后痛批:这门语言烂透了!</a>
  43. <p style="color: gray;">耗时18个月,开发者弃TypeScript投Rust</p>
  44. </div>
  45. <div class="link-container">
  46. <a href="#" class="link" id="TurnLink">Shire 编码智能体语言</a>
  47. <p style="color: gray;">打造你的专属 AI 编程助手</p>
  48. </div>
  49. </div>
  50. <div style="float: left; margin-left: 3%; height: 350px; background-color: rgb(250, 250, 250); width: 25%; float: left;
  51. border: 1px solid rgb(240, 240, 242); ">
  52. <div style="height: 50px; width: 100%; background-color: rgb(245, 245, 245); ">
  53. <i class="el-icon-message-solid"></i>
  54. <div><b>头条</b></div>
  55. <hr>
  56. </div>
  57. <div class="link-container">
  58. <a href="#" class="link" id="TurnLink">史上开发最久的游戏!</a>
  59. <p style="color: gray;">耗时 22 年,5 名打工人凑了几百欧就开工,只剩 1 人坚守到发布...</p>
  60. </div>
  61. <div class="link-container">
  62. <a href="#" class="link" id="TurnLink">实习期间创下 Transformer</a>
  63. <p style="color: gray;">他说:当年整个 AI 圈都无法预见我们今天的高度</p>
  64. </div>
  65. <div class="link-container">
  66. <a href="#" class="link" id="TurnLink">杀死谷歌,成为AI时代的搜索皇帝!</a>
  67. <p style="color: gray;">Perplexity CEO 最新四万字访谈</p>
  68. </div>
  69. </div>
  70. </div>
  71. </div>
  72. <el-divider></el-divider>
  73. <div id="bottom">
  74. <!-- Tabs 标签页 -->
  75. <el-tabs v-model="activeName" @tab-click="handleClick" style="padding-left: 2em; ">
  76. <el-tab-pane label="我的文章" name="first">
  77. <div class="parent-div" style="min-height: 500px">
  78. <div v-if="posts.length === 0">
  79. <el-empty :image-size="200"></el-empty>
  80. </div>
  81. <div class="custom-card" v-for="(post, index) in posts" :key="index"
  82. @click="getForumPostDetail(post.id)">
  83. <div class="card-content">
  84. <h1 class="card-title">标题: {{ post.title }}</h1>
  85. <div class="card-meta">
  86. <span>作者: {{ post.account }}</span>
  87. <span>标签: {{ post.tag }}</span>
  88. <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
  89. </div>
  90. <div class="card-rating">
  91. <span>文章评分:</span>
  92. <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
  93. score-template="{value}" style="display: inline-block;"></el-rate>
  94. </div>
  95. </div>
  96. </div>
  97. </div>
  98. </el-tab-pane>
  99. <el-tab-pane label="推荐文章" name="second">
  100. <div v-if="posts.length === 0">
  101. <el-empty :image-size="200"></el-empty>
  102. </div>
  103. <div class="custom-card" v-for="(post, index) in posts" :key="index"
  104. @click="getForumPostDetail(post.postId)">
  105. <div class="card-content">
  106. <h1 class="card-title">标题: {{ post.title }}</h1>
  107. <div class="card-meta">
  108. <span>作者: {{ post.account }}</span>
  109. <span>标签: {{ post.tag }}</span>
  110. <span><i class="el-icon-view">{{ post.heat_value }}</i></span>
  111. </div>
  112. <div class="card-rating">
  113. <span>文章评分:</span>
  114. <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
  115. score-template="{value}" style="display: inline-block;"></el-rate>
  116. </div>
  117. </div>
  118. </div>
  119. </el-tab-pane>
  120. <el-tab-pane label="热门文章" name="third">
  121. <div class="parent-div" style="min-height: 500px">
  122. <div v-if="posts.length === 0">
  123. <el-empty :image-size="200"></el-empty>
  124. </div>
  125. <div class="custom-card" v-for="(post, index) in posts" :key="index"
  126. @click="getForumPostDetail(post.id)">
  127. <div class="card-content">
  128. <h1 class="card-title">标题: {{ post.title }}</h1>
  129. <div class="card-meta">
  130. <span>作者: {{ post.account }}</span>
  131. <span>标签: {{ post.tag }}</span>
  132. <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
  133. </div>
  134. <div class="card-rating">
  135. <span>文章评分:</span>
  136. <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
  137. score-template="{value}" style="display: inline-block;"></el-rate>
  138. </div>
  139. </div>
  140. </div>
  141. </div>
  142. </el-tab-pane>
  143. <el-tab-pane label="优质文章" name="fourth">
  144. <div class="parent-div" style="min-height: 500px">
  145. <div v-if="posts.length === 0">
  146. <el-empty :image-size="200"></el-empty>
  147. </div>
  148. <div class="custom-card" v-for="(post, index) in posts" :key="index"
  149. @click="getForumPostDetail(post.id)">
  150. <div class="card-content">
  151. <h1 class="card-title">标题: {{ post.title }}</h1>
  152. <div class="card-meta">
  153. <span>作者: {{ post.account }}</span>
  154. <span>标签: {{ post.tag }}</span>
  155. <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
  156. </div>
  157. <div class="card-rating">
  158. <span>文章评分:</span>
  159. <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
  160. score-template="{value}" style="display: inline-block;"></el-rate>
  161. </div>
  162. </div>
  163. </div>
  164. </div>
  165. </el-tab-pane>
  166. <el-tab-pane label="我的收藏" name="fifth">
  167. <div v-if="posts.length === 0">
  168. <el-empty :image-size="200"></el-empty>
  169. </div>
  170. <div class="parent-div" style="min-height: 500px">
  171. <div v-if="posts.length === 0">
  172. <el-empty :image-size="200"></el-empty>
  173. </div>
  174. <div class="custom-card" v-for="(post, index) in posts" :key="index"
  175. @click="getForumPostDetail(post.id)">
  176. <div class="card-content">
  177. <h1 class="card-title">标题: {{ post.title }}</h1>
  178. <div class="card-meta">
  179. <span>作者: {{ post.account }}</span>
  180. <span>标签: {{ post.tag }}</span>
  181. <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
  182. </div>
  183. <div class="card-rating">
  184. <span>文章评分:</span>
  185. <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
  186. score-template="{value}" style="display: inline-block;"></el-rate>
  187. </div>
  188. </div>
  189. </div>
  190. </div>
  191. </el-tab-pane>
  192. </el-tabs>
  193. <el-button type="warning" round id="iWantPost" @click="navigateToPostEdit">我要发布文章</el-button>
  194. </div>
  195. </div>
  196. </template>

js:

  1. <script>
  2. import axios from 'axios';
  3. import Cookies from 'js-cookie';
  4. const cityOptions = ['新闻报道', '科技动态', '生活时尚', '教育学习', '健康养生'];
  5. export default {
  6. components: {
  7. },
  8. data() {
  9. return {
  10. searchKeyWord: '',
  11. suggestions: [], // 添加这个属性
  12. checkboxGroup1: [],
  13. cities: cityOptions,
  14. activeName: 'first',
  15. currentPage1: 5,
  16. currentPage2: 5,
  17. currentPage3: 5,
  18. currentPage4: 4,
  19. posts: [
  20. ],
  21. }
  22. },
  23. methods: {
  24. onSubmit() {
  25. this.$router.push({
  26. name: 'ArticalSearchView',
  27. params: {
  28. searchKeyWord: this.searchKeyWord
  29. }
  30. })
  31. },
  32. handleClick(tab) {
  33. // 我的文章
  34. if (tab.name === 'first') {
  35. this.posts = []
  36. this.getMyArticle();
  37. }
  38. // 推荐文章
  39. if (tab.name === 'second') {
  40. this.posts = [];
  41. axios.get('/api/forum/getAllForumPost', {
  42. params: {
  43. pageSize: 1,
  44. pageNumber: 10
  45. }
  46. }).then((response) => {
  47. console.log(response.data.data);
  48. this.posts = response.data.data;
  49. });
  50. }
  51. // 热门文章
  52. if (tab.name === 'third') {
  53. this.posts = [];
  54. axios.get('/api/forum/getHotPosts').then((response) => {
  55. console.log(response.data.data);
  56. this.posts = response.data.data;
  57. });
  58. }
  59. // 优质文章
  60. if (tab.name === 'fourth') {
  61. this.posts = [];
  62. axios.get('/api/forum/getOutStandPosts').then((response) => {
  63. console.log(response.data.data);
  64. this.posts = response.data.data;
  65. });
  66. }
  67. // 我的收藏
  68. if (tab.name === 'fifth') {
  69. this.posts = [];
  70. const id = Cookies.get("userId");
  71. if (id === null) {
  72. this.$message({
  73. message: '请先登录',
  74. type: 'warning'
  75. });
  76. return;
  77. }
  78. axios.get('/api/forum/getMyFavorite', {
  79. params: {
  80. id: id
  81. }
  82. }).then((response) => {
  83. console.log(response.data.data);
  84. this.posts = response.data.data;
  85. });
  86. }
  87. },
  88. // 处理分页功能
  89. handleSizeChange(val) {
  90. console.log(`每页 ${val} 条`);
  91. },
  92. handleCurrentChange(val) {
  93. console.log(`当前页: ${val}`);
  94. },
  95. navigateToPostEdit() {
  96. this.$router.push({ name: 'ForumPostEditView' });
  97. },
  98. // 跳转到文章详情
  99. getForumPostDetail(postId) {
  100. console.log("getForumPostDetail");
  101. console.log(postId);
  102. this.$router.push(`/post/${postId}`);
  103. },
  104. getMyArticle() {
  105. this.posts = [];
  106. const id = Cookies.get("userId");
  107. if (id === null) {
  108. this.$message({
  109. message: '请先登录',
  110. type: 'warning'
  111. });
  112. return;
  113. } else {
  114. axios.get('/api/forum/MyArticle', {
  115. params: {
  116. id: id
  117. }
  118. }).then((response) => {
  119. console.log(response.data.data);
  120. this.posts = response.data.data;
  121. })
  122. }
  123. },
  124. // 异步获取建议列表
  125. querySearchAsync(queryString, cb) {
  126. if (queryString.length === 0) {
  127. return cb([]); // 当查询字符串为空时,直接返回空数组
  128. }
  129. axios.get('/api/forum/getLikeSearch', { params: { keyword: queryString } })
  130. .then(response => {
  131. // 确保从后端返回的数据中提取出正确的数组
  132. const results = response.data.data || [];
  133. // 调用callback函数,传入搜索结果
  134. cb(results);
  135. })
  136. .catch(error => {
  137. console.error('Error fetching search suggestions:', error);
  138. cb([]);
  139. });
  140. },
  141. // 处理选择事件
  142. handleSelect(item) {
  143. this.searchKeyWord = item.value;
  144. this.onSubmit();
  145. }
  146. },
  147. mounted() {
  148. this.getMyArticle();
  149. }
  150. }
  151. </script>

css:

  1. <style scoped>
  2. #forumLayOut {
  3. background-color: white;
  4. height: auto;
  5. width: 100%;
  6. line-height: normal;
  7. }
  8. #serchBorder {
  9. line-height: normal;
  10. }
  11. .el-carousel__item h3 {
  12. color: #475669;
  13. font-size: 14px;
  14. opacity: 0.75;
  15. line-height: 150px;
  16. margin: 0;
  17. }
  18. .el-carousel__item:nth-child(2n) {
  19. background-color: #99a9bf;
  20. }
  21. .el-carousel__item:nth-child(2n+1) {
  22. background-color: #d3dce6;
  23. }
  24. #Pagination {
  25. align-self: center;
  26. /* 居中对齐 */
  27. margin-bottom: 1rem;
  28. /* 可选,增加底部边距 */
  29. margin-top: 10%;
  30. }
  31. #iWantPost {
  32. position: fixed;
  33. /* 设置为固定定位 */
  34. bottom: 60px;
  35. /* 距离底部的距离,可根据需要调整 */
  36. right: 40px;
  37. /* 距离右侧的距离,可根据需要调整 */
  38. }
  39. .el-tabs__content {
  40. overflow: hidden;
  41. position: relative;
  42. height: auto;
  43. }
  44. .custom-card {
  45. background-color: #ffffff;
  46. border-radius: 4px;
  47. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  48. margin-bottom: 16px;
  49. transition: box-shadow 0.3s ease-in-out;
  50. }
  51. .custom-card:hover {
  52. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  53. cursor: pointer;
  54. background-color: rgb(245, 245, 245);
  55. }
  56. .card-content {
  57. padding: 16px;
  58. }
  59. .card-title {
  60. font-size: 1.2em;
  61. margin-bottom: 8px;
  62. color: #333;
  63. }
  64. .card-meta {
  65. display: flex;
  66. align-items: center;
  67. justify-content: space-between;
  68. margin-bottom: 12px;
  69. color: #666;
  70. }
  71. .card-rating {
  72. color: #666;
  73. }
  74. .link-container {
  75. line-height: normal;
  76. float: left;
  77. width: 100%;
  78. text-align: left;
  79. padding-left: 40px;
  80. padding-top: 10px;
  81. }
  82. .link {
  83. text-decoration: none;
  84. font-size: large;
  85. color: black;
  86. }
  87. .link:hover {
  88. text-decoration: underline;
  89. }
  90. </style>

 

3.2 发布文章页面

  1. <template>
  2. <div style="border: 1px solid #ccc; line-height: normal; height: 100%;">
  3. <div>
  4. <el-form :inline="true" :model="formInline" class="demo-form-inline">
  5. <el-form-item label="文章标题">
  6. <el-input v-model="formInline.title" placeholder="请输入文章标题" maxlength="20"></el-input>
  7. </el-form-item>
  8. <el-form-item label="类别">
  9. <el-select v-model="formInline.category" placeholder="请选择文章类别">
  10. <el-option label="新闻报道" value="news"></el-option>
  11. <el-option label="科技动态" value="technology"></el-option>
  12. <el-option label="生活时尚" value="lifestyle"></el-option>
  13. <el-option label="教育学习" value="education"></el-option>
  14. <el-option label="健康养生" value="health"></el-option>
  15. </el-select>
  16. </el-form-item>
  17. <el-form-item>
  18. <el-button type="primary" @click="onSubmit"
  19. v-loading.fullscreen.lock="fullscreenLoading">提交</el-button>
  20. </el-form-item>
  21. </el-form>
  22. </div>
  23. <Toolbar style="border-bottom: 1px solid #ccc" :editor="editor" :defaultConfig="toolbarConfig" :mode="mode" />
  24. <Editor style="height: 500px; overflow-y: hidden; height: 100%;" v-model="html" :defaultConfig="editorConfig"
  25. :mode="mode" @onCreated="onCreated" />
  26. </div>
  27. </template>
  28. <script>
  29. import Vue from 'vue'
  30. import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
  31. import axios from 'axios'
  32. import Cookies from 'js-cookie'
  33. export default Vue.extend({
  34. components: { Editor, Toolbar },
  35. data() {
  36. return {
  37. editor: null,
  38. html: ' ',
  39. toolbarConfig: {},
  40. editorConfig: { placeholder: '请输入内容...' },
  41. mode: 'default', // or 'simple'
  42. formInline: {
  43. title: '',
  44. category: ''
  45. },
  46. fullscreenLoading: false
  47. }
  48. },
  49. methods: {
  50. onCreated(editor) {
  51. this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错
  52. },
  53. onSubmit() {
  54. this.fullscreenLoading = true;
  55. const userId = Cookies.get('userId'); // 获取并转换userId
  56. axios.post('/api/forum/add', {
  57. "title": this.formInline.title,
  58. "content": this.editor.getHtml(),
  59. "authorId": userId,
  60. "tag": this.formInline.category,
  61. }).then((response) => {
  62. console.log(response.data);
  63. this.fullscreenLoading = false;
  64. this.$router.push({ name: 'ForumSucessPostView' });
  65. }).catch(error => {
  66. console.error(error);
  67. this.fullscreenLoading = false;
  68. });
  69. },
  70. },
  71. mounted() {
  72. },
  73. beforeDestroy() {
  74. const editor = this.editor
  75. if (editor == null) return
  76. editor.destroy() // 组件销毁时,及时销毁编辑器
  77. },
  78. })
  79. </script>
  80. <style src="@wangeditor/editor/dist/css/style.css"></style>

3.3 文章发布成功页面


3.4 查看文章页面

html:

  1. <template>
  2. <div style="line-height: normal; background-color: rgb(246, 247, 249); height: auto; min-height: 80%;">
  3. <!-- 文章信息 -->
  4. <div style="padding-top: 10px; width: auto; min-width: 40%;">
  5. <!-- 实现文字垂直居中 -->
  6. <div id="Infor" style="background-color: white;">
  7. <h1 style="font-size: 28px; text-align: center">文章标题:{{title}}</h1>
  8. <span>创作者:{{author}}</span>
  9. <span style="margin-left: 20px;">创作日期:{{createAt}}</span>
  10. <span style="margin-left: 20px;"><i class="el-icon-view">{{heatValue}}</i></span>
  11. </div>
  12. <el-divider><i class="el-icon-mobile-phone"></i></el-divider>
  13. <!-- 文章内容展示区 -->
  14. <div id="contentDisplay">
  15. <div v-html="content"
  16. style="padding-left: 2em; padding-top: 15px; padding-right: 2em; padding-bottom: 30px;"></div>
  17. </div>
  18. </div>
  19. <el-divider><i class="el-icon-edit"></i></el-divider>
  20. <!-- 交互按键 -->
  21. <div id="buttom">
  22. <el-button type="warning" round @click="getBackToForum">返回到论坛</el-button>
  23. <el-button type="warning" icon="el-icon-star-off" circle @click="PostFavorite"></el-button>
  24. <el-button type="danger" icon="el-icon-thumb" circle @click="PostLike"></el-button>
  25. </div>
  26. <!-- 评论区 -->
  27. <div id="commentListShow">
  28. <el-card class="box-card">
  29. <div slot="header" class="clearfix">
  30. <span>评论详情</span>
  31. </div>
  32. <div id="commentInputArea">
  33. <el-input type="textarea" placeholder="请您输入友善的评论吧" v-model="textarea" maxlength="300"
  34. show-word-limit id="inputFrame" :clearable="clearAble" resize="none">
  35. </el-input>
  36. <div style="margin-top: 10px; padding-bottom: 50px;">
  37. <el-button type="primary" @click="SubmitComment">发表评论</el-button>
  38. <el-button type="primary" @click="CancelComment">取消评论</el-button>
  39. </div>
  40. </div>
  41. <div id="commentList">
  42. <div class="comment-card" v-for="comment in comments" :key="comment.id">
  43. <div class="comment-head">
  44. <h1 class="username">{{ comment.account }}</h1>
  45. <p class="created-at">发表于:{{ comment.createdAt }}</p>
  46. </div>
  47. <el-divider></el-divider>
  48. <p class="comment-text">{{ comment.commentText }}</p>
  49. </div>
  50. </div>
  51. </el-card>
  52. </div>
  53. </div>
  54. </template>

script:

  1. <script>
  2. import axios from 'axios';
  3. import Cookies from 'js-cookie';
  4. export default{
  5. data() {
  6. return {
  7. postId: '',
  8. title: '',
  9. content: '',
  10. value1: null,
  11. textarea: '',
  12. userId:'',
  13. clearAble: true,
  14. comments:{},
  15. author:'',
  16. createAt:'',
  17. heatValue:'',
  18. }
  19. },
  20. created() {
  21. this.postId = this.$route.params.postId;
  22. this.fetchPostDetail(this.$route.params.postId);
  23. this.userId = Cookies.get('userId');
  24. },
  25. mounted() {
  26. this.readComment();
  27. },
  28. methods: {
  29. // 前端实现路径传参
  30. async fetchPostDetail(postId) {
  31. try {
  32. this.fullscreenLoading = true;
  33. const url = `/api/forum/post/${postId}`;
  34. // 发起GET请求
  35. const response = await axios.get(url);
  36. if (response.status === 200) {
  37. // 请求成功,处理响应数据
  38. const postData = response.data;
  39. console.log('文章详情:', postData);
  40. // 更新组件状态或执行其他操作
  41. this.title = response.data.data.title;
  42. this.content = response.data.data.content;
  43. this.author = response.data.data.account;
  44. this.createAt = response.data.data.createdAt;
  45. this.heatValue = response.data.data.heatValue;
  46. this.fullscreenLoading = false;
  47. } else {
  48. console.error('请求失败,状态码:', response.status);
  49. }
  50. } catch (error) {
  51. console.error('请求错误:', error);
  52. }
  53. },
  54. getBackToForum() {
  55. this.$router.push({ name: 'forum' });
  56. },
  57. // 取消评论
  58. CancelComment(){
  59. this.textarea = '';
  60. },
  61. // 执行点赞按钮
  62. PostLike(){
  63. this.isLogin();
  64. axios.get('/api/forum/like',{
  65. params:{
  66. postId : this.postId,
  67. userId : this.userId
  68. }
  69. }).then((response)=>{
  70. this.MessageNotify(response);
  71. })
  72. },
  73. // 执行收藏按钮
  74. PostFavorite(){
  75. this.isLogin();
  76. axios.get('/api/forum/favorite',{
  77. params:{
  78. postId : this.postId,
  79. userId : this.userId
  80. }
  81. }).then((response)=>{
  82. this.MessageNotify(response);
  83. })
  84. },
  85. SubmitComment(){
  86. this.isLogin();
  87. axios.post('/api/forum/writeComment',{
  88. postId : this.postId,
  89. userId : this.userId,
  90. commentText : this.textarea
  91. }).then((response)=>{
  92. this.MessageNotify(response);
  93. this.textarea = '';
  94. this.readComment();
  95. })
  96. console.log("submit");
  97. },
  98. isLogin(){
  99. if(Cookies.get('userId') == null){
  100. this.$message.error('请先登录');
  101. return;
  102. }
  103. },
  104. // 消息提醒
  105. MessageNotify(response){
  106. if(response.data.code == 200){
  107. this.$message.success(response.data.data);
  108. }else{
  109. console.log(response.data);
  110. this.$message.error(response.data.message);
  111. }
  112. },
  113. readComment(){
  114. axios.get('/api/forum/getComment',{
  115. params:{
  116. postId : this.postId
  117. }
  118. }).then((response)=>{
  119. this.comments = response.data.data;
  120. })
  121. },
  122. }
  123. }
  124. </script>

css:


3.5 文章搜索页面

html:

  1. <template>
  2. <div id="layout">
  3. <div id="searchFrame">
  4. <div id="InputFrame">
  5. <el-input type="textarea" placeholder="请输入内容" v-model="textarea" rows="1" resize="none"
  6. style="font-size: larger; width: 80%;">
  7. </el-input>
  8. <el-button type="warning" @click="SearchSubmit" icon="el-icon-search">查询</el-button>
  9. </div>
  10. </div>
  11. <div id="excess">
  12. <div id="Interate">
  13. <i class="el-icon-search"> 搜索结果</i>
  14. </div>
  15. </div>
  16. <div id="SearchContent">
  17. <div v-if="posts.length === 0">
  18. <el-empty :image-size="200"></el-empty>
  19. </div>
  20. <div class="custom-card" v-for="(post, index) in posts" :key="index" @click="getForumPostDetail(post.id)">
  21. <div class="card-content">
  22. <h1 class="card-title">标题: {{ post.title }}</h1>
  23. <div class="card-meta">
  24. <span>作者: {{ post.account }}</span>
  25. <span>标签: {{ post.tag }}</span>
  26. <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
  27. </div>
  28. <div class="card-rating">
  29. <span>文章评分:</span>
  30. <el-rate v-model="post.rating" disabled show-score text-color="#ff9900" score-template="{value}"
  31. style="display: inline-block;"></el-rate>
  32. </div>
  33. </div>
  34. </div>
  35. </div>
  36. </div>
  37. </template>

script:

  1. <script>
  2. import axios from 'axios';
  3. export default {
  4. data() {
  5. return {
  6. textarea: '',
  7. searchKeyWord: '',
  8. posts: [],
  9. }
  10. },
  11. methods: {
  12. SearchSubmit() {
  13. console.log(this.textarea);
  14. axios.get('/api/forum/search', {
  15. params: {
  16. searchKeyWord: this.textarea
  17. }
  18. }).then((response) => {
  19. if (response.data.code !== 200) {
  20. this.$notify({
  21. title: '警告',
  22. message: '搜索失败',
  23. type: 'warning'
  24. });
  25. }
  26. console.log(response.data.data);
  27. this.posts = response.data.data;
  28. });
  29. },
  30. Search() {
  31. axios.get('/api/forum/search', {
  32. params: {
  33. searchKeyWord: this.searchKeyWord
  34. }
  35. }).then((response) => {
  36. if (response.data.code !== 200) {
  37. this.$notify({
  38. title: '警告',
  39. message: '搜索失败',
  40. type: 'warning'
  41. });
  42. }
  43. console.log(response.data.data);
  44. this.posts = response.data.data;
  45. });
  46. },
  47. // 跳转到文章详情
  48. getForumPostDetail(postId) {
  49. console.log("getForumPostDetail");
  50. console.log(postId);
  51. this.$router.push(`/post/${postId}`);
  52. },
  53. },
  54. mounted() {
  55. this.searchKeyWord = this.$route.params.searchKeyWord;
  56. this.textarea = this.searchKeyWord;
  57. this.Search();
  58. }
  59. }
  60. </script>

css:

  1. <style scoped>
  2. #layout {
  3. width: 100%;
  4. min-height: 90%;
  5. background-color: rgb(245, 246, 247);
  6. line-height: normal;
  7. }
  8. #SearchContent{
  9. min-height: 800px;
  10. }
  11. #searchFrame {
  12. height: 70px;
  13. width: 100%;
  14. background-color: white;
  15. box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
  16. position: -webkit-sticky;
  17. /* Safari */
  18. position: sticky;
  19. top: 0;
  20. z-index: 1000;
  21. line-height: normal;
  22. }
  23. #InputFrame {
  24. width: 40%;
  25. margin: 0 auto;
  26. height: 60%;
  27. padding-top: 15px;
  28. }
  29. #Interate {
  30. float: left;
  31. margin-top: 20px;
  32. margin-left: 20px;
  33. }
  34. #excess {
  35. height: 61px;
  36. width: 80%;
  37. background-color: white;
  38. margin: 0 auto;
  39. margin-top: 25px;
  40. border: 1px solid rgb(245, 245, 245);
  41. border-radius: 4px;
  42. }
  43. .custom-card {
  44. background-color: #ffffff;
  45. border-radius: 4px;
  46. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  47. margin-bottom: 16px;
  48. transition: box-shadow 0.3s ease-in-out;
  49. width: 80%;
  50. margin: 0 auto;
  51. border: 1px solid rgb(245, 245, 245);
  52. }
  53. .custom-card:hover {
  54. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  55. cursor: pointer;
  56. background-color: rgb(245, 245, 245);
  57. }
  58. .card-content {
  59. padding: 16px;
  60. }
  61. .card-title {
  62. font-size: 1.2em;
  63. margin-bottom: 8px;
  64. color: #333;
  65. }
  66. .card-meta {
  67. display: flex;
  68. align-items: center;
  69. justify-content: space-between;
  70. margin-bottom: 12px;
  71. color: #666;
  72. }
  73. .card-rating {
  74. color: #666;
  75. }
  76. .card-rating {
  77. color: #666;
  78. }
  79. </style>

 


 

四、后端代码

4.1项目后端依赖库

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-web</artifactId>
  8. <scope>compile</scope>
  9. </dependency>
  10. <dependency>
  11. <groupId>com.mysql</groupId>
  12. <artifactId>mysql-connector-j</artifactId>
  13. <scope>runtime</scope>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.projectlombok</groupId>
  17. <artifactId>lombok</artifactId>
  18. <optional>true</optional>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-test</artifactId>
  23. <scope>test</scope>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.mybatis.spring.boot</groupId>
  27. <artifactId>mybatis-spring-boot-starter</artifactId>
  28. <version>2.3.1</version>
  29. </dependency>
  30. <dependency>
  31. <groupId>cn.hutool</groupId>
  32. <artifactId>hutool-all</artifactId>
  33. <version>5.8.11</version>
  34. </dependency>
  35. <dependency>
  36. <groupId>com.baomidou</groupId>
  37. <artifactId>mybatis-plus-boot-starter</artifactId>
  38. <version>3.5.3.1</version>
  39. </dependency>
  40. <!--swagger-->
  41. <dependency>
  42. <groupId>com.github.xiaoymin</groupId>
  43. <artifactId>knife4j-spring-boot-starter</artifactId>
  44. <version>3.0.2</version>
  45. </dependency>
  46. <!--web-->
  47. <dependency>
  48. <groupId>org.springframework.boot</groupId>
  49. <artifactId>spring-boot-starter-web</artifactId>
  50. </dependency>
  51. <dependency>
  52. <groupId>org.springframework</groupId>
  53. <artifactId>spring-webmvc</artifactId>
  54. <version>5.3.15</version>
  55. </dependency>
  56. <dependency>
  57. <groupId>io.springfox</groupId>
  58. <artifactId>springfox-spring-web</artifactId>
  59. <version>3.0.0</version>
  60. </dependency>
  61. <dependency>
  62. <groupId>com.alibaba</groupId>
  63. <artifactId>fastjson</artifactId>
  64. <version>1.2.76</version>
  65. </dependency>
  66. <!-- https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
  67. <dependency>
  68. <groupId>com.alibaba</groupId>
  69. <artifactId>dashscope-sdk-java</artifactId>
  70. <version>2.8.2</version>
  71. </dependency>
  72. <!--okhttp3 依赖-->
  73. <dependency>
  74. <groupId>com.squareup.okhttp3</groupId>
  75. <artifactId>okhttp</artifactId>
  76. <version>4.9.3</version>
  77. </dependency>
  78. <!-- Lombok dependency -->
  79. <dependency>
  80. <groupId>org.projectlombok</groupId>
  81. <artifactId>lombok</artifactId>
  82. <optional>true</optional>
  83. </dependency>
  84. <!-- 验证码模块-->
  85. <dependency>
  86. <groupId>org.apache.commons</groupId>
  87. <artifactId>commons-lang3</artifactId>
  88. <version>3.12.0</version>
  89. </dependency>

4.2工具类Result类与实体类

  1. public class Result<T> {
  2. // 状态码常量
  3. public static final int SUCCESS = 200;
  4. public static final int ERROR = 500;
  5. private int code; // 状态码
  6. private String message; // 消息
  7. private T data; // 数据
  8. // 构造函数,用于创建成功的结果对象
  9. private Result(int code, String message, T data) {
  10. this.code = code;
  11. this.message = message;
  12. this.data = data;
  13. }
  14. // 成功结果的静态方法
  15. public static <T> Result<T> success(T data) {
  16. return new Result<>(SUCCESS, "Success", data);
  17. }
  18. // 错误结果的静态方法
  19. public static <T> Result<T> error(String message) {
  20. return new Result<>(ERROR, message, null);
  21. }
  22. // 错误结果的静态方法,可以传入自定义的状态码
  23. public static <T> Result<T> error(int code, String message) {
  24. return new Result<>(code, message, null);
  25. }
  26. // 获取状态码
  27. public int getCode() {
  28. return code;
  29. }
  30. // 设置状态码
  31. public void setCode(int code) {
  32. this.code = code;
  33. }
  34. // 获取消息
  35. public String getMessage() {
  36. return message;
  37. }
  38. // 设置消息
  39. public void setMessage(String message) {
  40. this.message = message;
  41. }
  42. // 获取数据
  43. public T getData() {
  44. return data;
  45. }
  46. // 设置数据
  47. public void setData(T data) {
  48. this.data = data;
  49. }
  50. // 用于转换为Map类型的方法,方便序列化为JSON
  51. public Map<String, Object> toMap() {
  52. Map<String, Object> map = new HashMap<>();
  53. map.put("code", code);
  54. map.put("message", message);
  55. map.put("data", data);
  56. return map;
  57. }
  58. }

Entity:

Forumpost:

  1. @TableName(value ="forum_posts")
  2. @Data
  3. public class ForumPosts implements Serializable {
  4. /**
  5. *
  6. */
  7. @TableId(value = "id", type = IdType.AUTO)
  8. private Integer id;
  9. /**
  10. *
  11. */
  12. @TableField(value = "title")
  13. private String title;
  14. /**
  15. *
  16. */
  17. @TableField(value = "content")
  18. private String content;
  19. /**
  20. *
  21. */
  22. @TableField(value = "author_id")
  23. private Integer authorId;
  24. /**
  25. *
  26. */
  27. @TableField(value = "created_at")
  28. private LocalDateTime createdAt;
  29. /**
  30. *
  31. */
  32. @TableField(value = "updated_at")
  33. private LocalDateTime updatedAt;
  34. /**
  35. *
  36. */
  37. @TableField(value = "heat_value")
  38. private Integer heatValue;
  39. /**
  40. *
  41. */
  42. @TableField(value = "rating")
  43. private BigDecimal rating;
  44. /**
  45. *
  46. */
  47. @TableField(value = "tag")
  48. private String tag;
  49. @TableField(exist = false)
  50. private static final long serialVersionUID = 1L;
  51. @Override
  52. public boolean equals(Object that) {
  53. if (this == that) {
  54. return true;
  55. }
  56. if (that == null) {
  57. return false;
  58. }
  59. if (getClass() != that.getClass()) {
  60. return false;
  61. }
  62. ForumPosts other = (ForumPosts) that;
  63. return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
  64. && (this.getTitle() == null ? other.getTitle() == null : this.getTitle().equals(other.getTitle()))
  65. && (this.getContent() == null ? other.getContent() == null : this.getContent().equals(other.getContent()))
  66. && (this.getAuthorId() == null ? other.getAuthorId() == null : this.getAuthorId().equals(other.getAuthorId()))
  67. && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()))
  68. && (this.getUpdatedAt() == null ? other.getUpdatedAt() == null : this.getUpdatedAt().equals(other.getUpdatedAt()))
  69. && (this.getHeatValue() == null ? other.getHeatValue() == null : this.getHeatValue().equals(other.getHeatValue()))
  70. && (this.getRating() == null ? other.getRating() == null : this.getRating().equals(other.getRating()))
  71. && (this.getTag() == null ? other.getTag() == null : this.getTag().equals(other.getTag()));
  72. }
  73. @Override
  74. public int hashCode() {
  75. final int prime = 31;
  76. int result = 1;
  77. result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
  78. result = prime * result + ((getTitle() == null) ? 0 : getTitle().hashCode());
  79. result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode());
  80. result = prime * result + ((getAuthorId() == null) ? 0 : getAuthorId().hashCode());
  81. result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
  82. result = prime * result + ((getUpdatedAt() == null) ? 0 : getUpdatedAt().hashCode());
  83. result = prime * result + ((getHeatValue() == null) ? 0 : getHeatValue().hashCode());
  84. result = prime * result + ((getRating() == null) ? 0 : getRating().hashCode());
  85. result = prime * result + ((getTag() == null) ? 0 : getTag().hashCode());
  86. return result;
  87. }
  88. @Override
  89. public String toString() {
  90. StringBuilder sb = new StringBuilder();
  91. sb.append(getClass().getSimpleName());
  92. sb.append(" [");
  93. sb.append("Hash = ").append(hashCode());
  94. sb.append(", id=").append(id);
  95. sb.append(", title=").append(title);
  96. sb.append(", content=").append(content);
  97. sb.append(", authorId=").append(authorId);
  98. sb.append(", createdAt=").append(createdAt);
  99. sb.append(", updatedAt=").append(updatedAt);
  100. sb.append(", heatValue=").append(heatValue);
  101. sb.append(", rating=").append(rating);
  102. sb.append(", tag=").append(tag);
  103. sb.append(", serialVersionUID=").append(serialVersionUID);
  104. sb.append("]");
  105. return sb.toString();
  106. }
  107. }
ForumPostLike:
  1. @Data
  2. @TableName(value ="forum_post_likes")
  3. public class ForumPostLike {
  4. private int userId;
  5. private int postId;
  6. }
ForumPostFavorites:
  1. @Data
  2. @TableName(value ="forum_post_favorites")
  3. public class ForumPostFavorites {
  4. private int userId;
  5. private int postId;
  6. }
ForumComments
  1. @TableName(value ="forum_comments")
  2. @Data
  3. public class ForumComments implements Serializable {
  4. /**
  5. *
  6. */
  7. @TableId(value = "id", type = IdType.AUTO)
  8. private Integer id;
  9. /**
  10. *
  11. */
  12. @TableField(value = "post_id")
  13. private Integer postId;
  14. /**
  15. *
  16. */
  17. @TableField(value = "user_id")
  18. private Integer userId;
  19. /**
  20. *
  21. */
  22. @TableField(value = "comment_text")
  23. private String commentText;
  24. /**
  25. *
  26. */
  27. @TableField(value = "created_at")
  28. private LocalDateTime createdAt;
  29. @TableField(exist = false)
  30. private static final long serialVersionUID = 1L;
  31. @Override
  32. public boolean equals(Object that) {
  33. if (this == that) {
  34. return true;
  35. }
  36. if (that == null) {
  37. return false;
  38. }
  39. if (getClass() != that.getClass()) {
  40. return false;
  41. }
  42. ForumComments other = (ForumComments) that;
  43. return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
  44. && (this.getPostId() == null ? other.getPostId() == null : this.getPostId().equals(other.getPostId()))
  45. && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
  46. && (this.getCommentText() == null ? other.getCommentText() == null : this.getCommentText().equals(other.getCommentText()))
  47. && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()));
  48. }
  49. @Override
  50. public int hashCode() {
  51. final int prime = 31;
  52. int result = 1;
  53. result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
  54. result = prime * result + ((getPostId() == null) ? 0 : getPostId().hashCode());
  55. result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
  56. result = prime * result + ((getCommentText() == null) ? 0 : getCommentText().hashCode());
  57. result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
  58. return result;
  59. }
  60. @Override
  61. public String toString() {
  62. StringBuilder sb = new StringBuilder();
  63. sb.append(getClass().getSimpleName());
  64. sb.append(" [");
  65. sb.append("Hash = ").append(hashCode());
  66. sb.append(", id=").append(id);
  67. sb.append(", postId=").append(postId);
  68. sb.append(", userId=").append(userId);
  69. sb.append(", commentText=").append(commentText);
  70. sb.append(", createdAt=").append(createdAt);
  71. sb.append(", serialVersionUID=").append(serialVersionUID);
  72. sb.append("]");
  73. return sb.toString();
  74. }
  75. }

 DTO:

  1. @Data
  2. public class CommentDTO {
  3. private int userId;
  4. private int postId;
  5. private String commentText;
  6. }
  1. @Data
  2. public class ForumAddPostDTO {
  3. @JsonProperty("title")
  4. private String title;
  5. @JsonProperty("content")
  6. private String content;
  7. @JsonProperty("authorId")
  8. private Integer authorId;
  9. @JsonProperty("tag")
  10. private String tag;
  11. }

 VO:

  1. @Data
  2. public class ArticleVO {
  3. private String title;
  4. private String content;
  5. private String account;
  6. private Integer heatValue;
  7. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  8. private LocalDateTime createdAt;
  9. }
  1. @Data
  2. public class CommentVo {
  3. private int id;
  4. private String commentText;
  5. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  6. private LocalDateTime createdAt;
  7. private String account;
  8. }
  1. @Data
  2. public class LikeSearchVo {
  3. private String value;
  4. }

 


4.3 自定义异常与全局异常

  1. public class BaseException extends RuntimeException{
  2. public BaseException(){
  3. }
  4. public BaseException(String msg){
  5. super(msg);
  6. }
  7. }
  1. public class NotFoundArticleException extends BaseException{
  2. public NotFoundArticleException(String msg){
  3. super(msg);
  4. }
  5. }
  1. public class AlreadyLikeException extends BaseException{
  2. public AlreadyLikeException(String msg){
  3. super(msg);
  4. }
  5. }

全局异常处理类:

  1. @RestControllerAdvice
  2. @Slf4j
  3. public class GlobalExceptionHandler {
  4. @ExceptionHandler
  5. public Result exceptionHandler(BaseException ex){
  6. log.error("异常信息:{}", ex.getMessage());
  7. return Result.error(ex.getMessage());
  8. }
  9. }

4.4Controller层

1.ForumPostController

  1. @RequestMapping("/forum")
  2. @RestController
  3. @Api(tags = "文章管理")
  4. @Slf4j
  5. public class ForumPostController {
  6. @Autowired
  7. ForumPostsService forumPostsService;
  8. @Autowired
  9. UserService userService;
  10. @Autowired
  11. ForumCommentsService forumCommentsService;
  12. @ApiOperation("新增文章")
  13. @PostMapping("/add")
  14. public Result addForumPost(@RequestBody ForumAddPostDTO forumAddPostDTO) throws ParseException {
  15. ForumPosts forumPosts = new ForumPosts();
  16. BeanUtils.copyProperties(forumAddPostDTO, forumPosts);
  17. forumPostsService.save(forumPosts);
  18. return Result.success("新增成功");
  19. }
  20. @GetMapping("/getAllForumPost")
  21. @ApiOperation("推荐文章查询")
  22. public Result getAllForumPost(@RequestParam(value="pageSize", defaultValue = "10") int pageSize,
  23. @RequestParam(value="pageNumber", defaultValue = "1") int pageNumber){
  24. Page<ForumPosts> page = new Page<>(pageNumber, pageSize);
  25. // 创建查询包装器并指定排序规则
  26. QueryWrapper<ForumPosts> queryWrapper = new QueryWrapper<>();
  27. // 假设你想按照创建时间降序排序
  28. queryWrapper.orderByDesc("created_at");
  29. Page<ForumPosts> paged = forumPostsService.page(page,queryWrapper);
  30. List<ForumPosts> postsList = paged.getRecords();
  31. List<ForumPageVO> posts = new ArrayList<>();
  32. for (ForumPosts post : postsList) {
  33. ForumPageVO vo = new ForumPageVO();
  34. // 获取账户信息
  35. User user = userService.getById(post.getAuthorId());
  36. vo.setAccount(user.getAccount());
  37. // 直接从ForumPosts对象复制其他字段
  38. vo.setTag(post.getTag());
  39. vo.setRating(post.getRating()); // 如果需要字符串形式
  40. vo.setTitle(post.getTitle());
  41. vo.setPostId(post.getId());
  42. vo.setHeat_value(post.getHeatValue());
  43. // 添加到列表
  44. posts.add(vo);
  45. }
  46. return Result.success(posts);
  47. }
  48. // 文章阅读
  49. @ApiOperation("读取文章")
  50. @GetMapping("post/{id}")
  51. public Result<ArticleVO> readArtical(@PathVariable int id){
  52. ArticleVO articleVO = forumPostsService.readArticle(id);
  53. return Result.success(articleVO);
  54. }
  55. // 我的文章功能
  56. @ApiOperation("我的文章")
  57. @GetMapping("/MyArticle")
  58. public Result getMyArticle(@Param("id") int id){
  59. List<ForumPosts> forumPostsList = forumPostsService.getByAuthorId(id);
  60. return Result.success(forumPostsList);
  61. }
  62. // 热门文章功能
  63. @ApiOperation("热门文章")
  64. @GetMapping("/getHotPosts")
  65. public Result getHotPosts(){
  66. QueryWrapper<ForumPosts> queryWrapper = new QueryWrapper();
  67. queryWrapper.orderByDesc("heat_value");
  68. List<ForumPosts> forumPosts = forumPostsService.list(queryWrapper);
  69. return Result.success(forumPosts);
  70. }
  71. // 热门文章功能
  72. @ApiOperation("优质文章")
  73. @GetMapping("/getOutStandPosts")
  74. public Result getOutStandPosts(){
  75. QueryWrapper<ForumPosts> queryWrapper = new QueryWrapper();
  76. queryWrapper.orderByDesc("rating");
  77. List<ForumPosts> forumPosts = forumPostsService.list(queryWrapper);
  78. return Result.success(forumPosts);
  79. }
  80. // 我的收藏
  81. @ApiOperation("我的收藏")
  82. @GetMapping("/getMyFavorite")
  83. public Result getMyFavorite(@Param("id") int id){
  84. List<ForumPosts> forumPostsList = forumPostsService.getMyFavorite(id);
  85. return Result.success(forumPostsList);
  86. }
  87. // 文章查询
  88. @ApiOperation("文章查询")
  89. @GetMapping("/search")
  90. public Result postSearch(@RequestParam("searchKeyWord") String searchText){
  91. if (searchText == null){
  92. return Result.error("不能输入为空噢");
  93. }
  94. LambdaQueryWrapper<ForumPosts> lambdaQueryWrapper = new LambdaQueryWrapper<>();
  95. // TODO:通过LamdaQueryWrapper 模糊查询
  96. // 模糊查询title字段,%searchText%会被自动添加
  97. lambdaQueryWrapper.like(ForumPosts::getTitle, searchText);
  98. // 假设这里有一个service接口用于操作ForumPosts表
  99. List<ForumPosts> postsList = forumPostsService.list(lambdaQueryWrapper);
  100. // 根据你的Result类的具体实现,返回查询结果
  101. return Result.success(postsList);
  102. }
  103. /**
  104. * 标题模糊查询
  105. * @param keyword 关键词
  106. * @return 匹配的标题列表
  107. */
  108. @GetMapping("/getLikeSearch")
  109. @ApiOperation("标题模糊查询")
  110. public Result getLikeSearch(@RequestParam String keyword){
  111. List<LikeSearchVo> titles = forumPostsService.findTitlesByKeyword(keyword);
  112. return Result.success(titles);
  113. }
  114. // 用户交互
  115. // 点赞
  116. @ApiOperation("点赞")
  117. @GetMapping("/like")
  118. public Result PostLike(@RequestParam("postId") int postId,@RequestParam("userId") int userId){
  119. String result = forumPostsService.PostLike(postId,userId);
  120. return Result.success(result);
  121. }
  122. // 收藏
  123. @ApiOperation("收藏")
  124. @GetMapping("/favorite")
  125. public Result PostFavorite(@RequestParam("postId") int postId,@RequestParam("userId") int userId){
  126. String result = forumPostsService.PostFavorite(postId,userId);
  127. return Result.success(result);
  128. }
  129. // 写评论
  130. @ApiOperation("写评论")
  131. @PostMapping("/writeComment")
  132. public Result WriteComment(@RequestBody CommentDTO commentdto){
  133. ForumComments forumComments = new ForumComments();
  134. BeanUtils.copyProperties(commentdto,forumComments);
  135. forumCommentsService.save(forumComments);
  136. return Result.success("评论成功");
  137. }
  138. // 读评论
  139. @ApiOperation("读取评论")
  140. @GetMapping("/getComment")
  141. public Result<List<CommentVo>> GetComment(@RequestParam("postId") int postId){
  142. List<CommentVo> comment = forumCommentsService.getComment(postId);
  143. return Result.success(comment);
  144. }
  145. }


4.5 Service层
 

  1. public interface ForumPostsService extends IService<ForumPosts> {
  2. List<ForumPosts> getByAuthorId(Integer id);
  3. String PostLike(int postId, int userId);
  4. String PostFavorite(int postId, int userId);
  5. ArticleVO readArticle(int id);
  6. List<ForumPosts> getMyFavorite(int id);
  7. List<LikeSearchVo> findTitlesByKeyword(String keyword);
  8. }
  1. public interface ForumCommentsService extends IService<ForumComments> {
  2. List<CommentVo> getComment(int postId);
  3. }

4.6 Mapper层

  1. public interface ForumCommentsMapper extends BaseMapper<ForumComments> {
  2. }
  1. @Mapper
  2. public interface ForumPostFavoritesMapper extends BaseMapper<ForumPostFavorites> {
  3. }
  1. @Mapper
  2. public interface ForumPostLikeMapper extends BaseMapper<ForumPostLike> {
  3. }
  1. @Mapper
  2. public interface ForumPostsMapper extends BaseMapper<ForumPosts> {
  3. @Select("select * from paitool.user as a,paitool.forum_posts as b where a.id=b.author_id and a.id = #{id}")
  4. List<ForumPosts> getByAuthorId(Integer id);
  5. }

 


五、进阶思路

1.通过ElasticSearch优化搜索引擎

2.使用Redis存储热门文章,以减少数据库压力

3.通过若依框架+AI 完善管理系统


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/994524
推荐阅读
相关标签
  

闽ICP备14008679号