(Vue+SpringBoot+elementUi+WangEditer)仿论坛项目

项目使用到的技术与库

1.前端 Vue2 elementUi Cookie WangEditer

2.后端 SpringBoot Mybatis-Plus

3.数据库 MySql

一、效果展示

1.1主页效果:

1.2 文章编辑页面:

1.3 成功发布文章

1.4 文章关键字搜索提示

 1.5 文章查询结果展示

1.6 文章内容及交互展示


二、表单设计的sql

用户:

create table paitool.user
(
    id                int auto_increment
        primary key,
    account           varchar(255)                                          not null,
    password          varchar(255)                                          not null,
    phone             varchar(20)                                           null,
    address           varchar(255)                                          null,
    isVip             tinyint(1)                  default 0                 null,
    email             varchar(255)                                          null,
    registration_date datetime                    default CURRENT_TIMESTAMP null,
    last_login        datetime                                              null,
    status            enum ('active', 'inactive') default 'active'          null,
    constraint account_UNIQUE
        unique (account),
    constraint email_UNIQUE
        unique (email),
    constraint phone_UNIQUE
        unique (phone)
);

文章:

create table paitool.forum_posts
(
    id         int auto_increment
        primary key,
    title      varchar(255)                            not null,
    content    text                                    not null,
    author_id  int                                     not null,
    created_at timestamp     default CURRENT_TIMESTAMP null,
    updated_at timestamp     default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP,
    heat_value int           default 0                 null,
    rating     decimal(3, 2) default 0.00              null,
    tag        varchar(10)   default '其它'              null,
    constraint forum_posts_ibfk_1
        foreign key (author_id) references paitool.user (id)
);

文章交互表-点赞:

create table paitool.forum_post_likes
(
    user_id int not null,
    post_id int not null,
    primary key (user_id, post_id),
    constraint forum_post_likes_ibfk_1
        foreign key (user_id) references paitool.user (id),
    constraint forum_post_likes_ibfk_2
        foreign key (post_id) references paitool.forum_posts (id)
);

文章交互表-收藏:

create table paitool.forum_post_favorites
(
    user_id int not null,
    post_id int not null,
    primary key (user_id, post_id),
    constraint forum_post_favorites_ibfk_1
        foreign key (user_id) references paitool.user (id),
    constraint forum_post_favorites_ibfk_2
        foreign key (post_id) references paitool.forum_posts (id)
);

文章交互表-评论

create table paitool.forum_comments
(
    id           int auto_increment
        primary key,
    post_id      int                                 not null,
    user_id      int                                 not null,
    comment_text text                                not null,
    created_at   timestamp default CURRENT_TIMESTAMP null,
    constraint forum_comments_ibfk_1
        foreign key (user_id) references paitool.user (id),
    constraint forum_comments_ibfk_2
        foreign key (post_id) references paitool.forum_posts (id)
);

三、前端代码

3.1 论坛主页

Html:

<template>
    <div id="forumLayOut">

        <div id="Top" style="background-color: rgb(250, 250, 250); padding-top: 20px">

            <div id="serchBorder" style="padding-bottom: 13px;">
                <!-- 搜索框 -->
                <el-autocomplete v-model="searchKeyWord" :fetch-suggestions="querySearchAsync" :trigger-on-focus="false"
                    placeholder="请输入关键字" style="width: 300px;" @select="handleSelect">
                </el-autocomplete>
                <el-button type="primary" @click="onSubmit">查询</el-button>
            </div>

            <!-- 分类查询 -->
            <div>

                <div style="margin-bottom: 15px;">
                    <el-checkbox-group v-model="checkboxGroup1" :max="1">
                        <el-checkbox-button v-for="city in cities" :label="city" :key="city">{{ city
                            }}</el-checkbox-button>
                    </el-checkbox-group>
                </div>

            </div>


            <div style="height: 380px; width: 100%;">
                <!-- 轮播图 -->
                <div class="block" style="width: 30%; float: left; margin-left: 5%; height: 400px;">
                    <el-carousel height="350px" style="width: 100%;  ">
                        <el-carousel-item v-for="item in 4" :key="item">
                            <img src="https://img95.699pic.com/photo/50035/3211.jpg_wh860.jpg" alt="风景测试">
                            <h3 class="small">{{ item }}</h3>
                        </el-carousel-item>
                    </el-carousel>
                </div>


                <div style=" height: 350px; background-color: rgb(250, 250, 250); width: 25%; float: left;
                border: 1px solid rgb(240, 240, 242);  margin-left: 3%;">


                    <div style="height: 50px; width: 100%; background-color: rgb(245, 245, 245); ">
                        <i class="el-icon-share"></i>
                        <div><b>热门</b></div>
                        <hr>
                    </div>

                    <div class="link-container">
                        <a href="#" class="link" id="TurnLink">杀死谷歌,成为AI时代的搜索皇帝!</a>
                        <p style="color: gray;">Perplexity CEO 最新四万字访谈</p>
                    </div>

                    <div class="link-container">
                        <a href="#" class="link" id="TurnLink">重写系统后痛批:这门语言烂透了!</a>
                        <p style="color: gray;">耗时18个月,开发者弃TypeScript投Rust</p>
                    </div>
                    <div class="link-container">
                        <a href="#" class="link" id="TurnLink">Shire 编码智能体语言</a>
                        <p style="color: gray;">打造你的专属 AI 编程助手</p>
                    </div>


                </div>


                <div style="float: left;  margin-left: 3%; height: 350px; background-color: rgb(250, 250, 250); width: 25%; float: left;
                     border: 1px solid rgb(240, 240, 242); ">


                    <div style="height: 50px; width: 100%; background-color: rgb(245, 245, 245); ">
                        <i class="el-icon-message-solid"></i>
                        <div><b>头条</b></div>
                        <hr>
                    </div>

                    <div class="link-container">
                        <a href="#" class="link" id="TurnLink">史上开发最久的游戏!</a>
                        <p style="color: gray;">耗时 22 年,5 名打工人凑了几百欧就开工,只剩 1 人坚守到发布...</p>
                    </div>

                    <div class="link-container">
                        <a href="#" class="link" id="TurnLink">实习期间创下 Transformer</a>
                        <p style="color: gray;">他说:当年整个 AI 圈都无法预见我们今天的高度</p>
                    </div>

                    <div class="link-container">
                        <a href="#" class="link" id="TurnLink">杀死谷歌,成为AI时代的搜索皇帝!</a>
                        <p style="color: gray;">Perplexity CEO 最新四万字访谈</p>
                    </div>

                </div>
            </div>


        </div>

        <el-divider></el-divider>
        <div id="bottom">

            <!-- Tabs 标签页  -->
            <el-tabs v-model="activeName" @tab-click="handleClick" style="padding-left: 2em; ">

                <el-tab-pane label="我的文章" name="first">
                    <div class="parent-div" style="min-height: 500px">

                        <div v-if="posts.length === 0">
                            <el-empty :image-size="200"></el-empty>
                        </div>

                        <div class="custom-card" v-for="(post, index) in posts" :key="index"
                            @click="getForumPostDetail(post.id)">
                            <div class="card-content">
                                <h1 class="card-title">标题: {{ post.title }}</h1>
                                <div class="card-meta">
                                    <span>作者: {{ post.account }}</span>
                                    <span>标签: {{ post.tag }}</span>
                                    <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
                                </div>
                                <div class="card-rating">
                                    <span>文章评分:</span>
                                    <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
                                        score-template="{value}" style="display: inline-block;"></el-rate>
                                </div>
                            </div>
                        </div>
                    </div>


                </el-tab-pane>


                <el-tab-pane label="推荐文章" name="second">

                    <div v-if="posts.length === 0">
                        <el-empty :image-size="200"></el-empty>
                    </div>

                    <div class="custom-card" v-for="(post, index) in posts" :key="index"
                        @click="getForumPostDetail(post.postId)">
                        <div class="card-content">
                            <h1 class="card-title">标题: {{ post.title }}</h1>
                            <div class="card-meta">
                                <span>作者: {{ post.account }}</span>
                                <span>标签: {{ post.tag }}</span>
                                <span><i class="el-icon-view">{{ post.heat_value }}</i></span>
                            </div>
                            <div class="card-rating">
                                <span>文章评分:</span>
                                <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
                                    score-template="{value}" style="display: inline-block;"></el-rate>
                            </div>
                        </div>
                    </div>

                </el-tab-pane>
                <el-tab-pane label="热门文章" name="third">



                    <div class="parent-div" style="min-height: 500px">
                        <div v-if="posts.length === 0">
                            <el-empty :image-size="200"></el-empty>
                        </div>
                        <div class="custom-card" v-for="(post, index) in posts" :key="index"
                            @click="getForumPostDetail(post.id)">
                            <div class="card-content">
                                <h1 class="card-title">标题: {{ post.title }}</h1>
                                <div class="card-meta">
                                    <span>作者: {{ post.account }}</span>
                                    <span>标签: {{ post.tag }}</span>
                                    <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
                                </div>
                                <div class="card-rating">
                                    <span>文章评分:</span>
                                    <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
                                        score-template="{value}" style="display: inline-block;"></el-rate>
                                </div>
                            </div>
                        </div>
                    </div>




                </el-tab-pane>

                <el-tab-pane label="优质文章" name="fourth">


                    <div class="parent-div" style="min-height: 500px">
                        <div v-if="posts.length === 0">
                            <el-empty :image-size="200"></el-empty>
                        </div>
                        <div class="custom-card" v-for="(post, index) in posts" :key="index"
                            @click="getForumPostDetail(post.id)">
                            <div class="card-content">
                                <h1 class="card-title">标题: {{ post.title }}</h1>
                                <div class="card-meta">
                                    <span>作者: {{ post.account }}</span>
                                    <span>标签: {{ post.tag }}</span>
                                    <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
                                </div>
                                <div class="card-rating">
                                    <span>文章评分:</span>
                                    <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
                                        score-template="{value}" style="display: inline-block;"></el-rate>
                                </div>
                            </div>
                        </div>
                    </div>



                </el-tab-pane>

                <el-tab-pane label="我的收藏" name="fifth">
                    <div v-if="posts.length === 0">
                        <el-empty :image-size="200"></el-empty>
                    </div>



                    <div class="parent-div" style="min-height: 500px">
                        <div v-if="posts.length === 0">
                            <el-empty :image-size="200"></el-empty>
                        </div>
                        <div class="custom-card" v-for="(post, index) in posts" :key="index"
                            @click="getForumPostDetail(post.id)">
                            <div class="card-content">
                                <h1 class="card-title">标题: {{ post.title }}</h1>
                                <div class="card-meta">
                                    <span>作者: {{ post.account }}</span>
                                    <span>标签: {{ post.tag }}</span>
                                    <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
                                </div>
                                <div class="card-rating">
                                    <span>文章评分:</span>
                                    <el-rate v-model="post.rating" disabled show-score text-color="#ff9900"
                                        score-template="{value}" style="display: inline-block;"></el-rate>
                                </div>
                            </div>
                        </div>
                    </div>


                </el-tab-pane>
            </el-tabs>

            <el-button type="warning" round id="iWantPost" @click="navigateToPostEdit">我要发布文章</el-button>

        </div>




    </div>
</template>

js:

<script>
import axios from 'axios';
import Cookies from 'js-cookie';

const cityOptions = ['新闻报道', '科技动态', '生活时尚', '教育学习', '健康养生'];
export default {
    components: {

    },

    data() {
        return {
            searchKeyWord: '',
            suggestions: [], // 添加这个属性
            checkboxGroup1: [],
            cities: cityOptions,
            activeName: 'first',
            currentPage1: 5,
            currentPage2: 5,
            currentPage3: 5,
            currentPage4: 4,
            posts: [
            ],
            
        }
    },
    methods: {
        onSubmit() {
            this.$router.push({
                name: 'ArticalSearchView',
                params: {
                    searchKeyWord: this.searchKeyWord
                }
            })


        },
        handleClick(tab) {

            // 我的文章
            if (tab.name === 'first') {
                this.posts = []
                this.getMyArticle();

            }

            // 推荐文章
            if (tab.name === 'second') {
                this.posts = [];

                axios.get('/api/forum/getAllForumPost', {
                    params: {
                        pageSize: 1,
                        pageNumber: 10
                    }
                }).then((response) => {
                    console.log(response.data.data);
                    
                    this.posts = response.data.data;
                });


            }
            // 热门文章
            if (tab.name === 'third') {
                this.posts = [];

                axios.get('/api/forum/getHotPosts').then((response) => {
                    console.log(response.data.data);
                    this.posts = response.data.data;
                });


            }

            // 优质文章
            if (tab.name === 'fourth') {
                this.posts = [];

                axios.get('/api/forum/getOutStandPosts').then((response) => {
                    console.log(response.data.data);
                    this.posts = response.data.data;
                });

            }

            // 我的收藏
            if (tab.name === 'fifth') {
                this.posts = [];
                const id = Cookies.get("userId");

                if (id === null) {
                    this.$message({
                        message: '请先登录',
                        type: 'warning'
                    });
                    return;
                }

                axios.get('/api/forum/getMyFavorite', {
                    params: {
                        id: id
                    }
                }).then((response) => {
                    console.log(response.data.data);
                    this.posts = response.data.data;
                });

            }



        },

        // 处理分页功能
        handleSizeChange(val) {
            console.log(`每页 ${val} 条`);
        },
        handleCurrentChange(val) {
            console.log(`当前页: ${val}`);
        },

        navigateToPostEdit() {
            this.$router.push({ name: 'ForumPostEditView' });
        },


        // 跳转到文章详情
        getForumPostDetail(postId) {
            console.log("getForumPostDetail");
            console.log(postId);
            this.$router.push(`/post/${postId}`);
        },

        getMyArticle() {
            this.posts = [];
            const id = Cookies.get("userId");

            if (id === null) {
                this.$message({
                    message: '请先登录',
                    type: 'warning'
                });
                return;
            } else {
                axios.get('/api/forum/MyArticle', {
                    params: {
                        id: id
                    }
                }).then((response) => {
                    console.log(response.data.data);
                    this.posts = response.data.data;
                })

            }

        },
        // 异步获取建议列表
        querySearchAsync(queryString, cb) {
            if (queryString.length === 0) {
                return cb([]); // 当查询字符串为空时,直接返回空数组
            }
            axios.get('/api/forum/getLikeSearch', { params: { keyword: queryString } })
                .then(response => {
                    // 确保从后端返回的数据中提取出正确的数组
                    const results = response.data.data || [];
                    // 调用callback函数,传入搜索结果
                    cb(results);
                })
                .catch(error => {
                    console.error('Error fetching search suggestions:', error);
                   
                    cb([]);
                });
        },
        // 处理选择事件
        handleSelect(item) {
            this.searchKeyWord = item.value; 
            this.onSubmit();
        }


    },

    mounted() {
        this.getMyArticle();

    }


}

</script>

css:

<style scoped>
#forumLayOut {

    background-color: white;
    height: auto;
    width: 100%;
    line-height: normal;
}

#serchBorder {

    line-height: normal;
}

.el-carousel__item h3 {
    color: #475669;
    font-size: 14px;
    opacity: 0.75;
    line-height: 150px;
    margin: 0;
}

.el-carousel__item:nth-child(2n) {
    background-color: #99a9bf;
}

.el-carousel__item:nth-child(2n+1) {
    background-color: #d3dce6;
}

#Pagination {
    align-self: center;
    /* 居中对齐 */
    margin-bottom: 1rem;
    /* 可选,增加底部边距 */
    margin-top: 10%;
}


#iWantPost {
    position: fixed;
    /* 设置为固定定位 */
    bottom: 60px;
    /* 距离底部的距离,可根据需要调整 */
    right: 40px;
    /* 距离右侧的距离,可根据需要调整 */
}


.el-tabs__content {
    overflow: hidden;
    position: relative;
    height: auto;
}

.custom-card {
    background-color: #ffffff;
    border-radius: 4px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    margin-bottom: 16px;
    transition: box-shadow 0.3s ease-in-out;
}

.custom-card:hover {
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
    cursor: pointer;
    background-color: rgb(245, 245, 245);
}

.card-content {
    padding: 16px;
}

.card-title {
    font-size: 1.2em;
    margin-bottom: 8px;
    color: #333;
}

.card-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
    color: #666;
}

.card-rating {
    color: #666;
}

.link-container {
    line-height: normal;
    float: left;
    width: 100%;
    text-align: left;
    padding-left: 40px;
    padding-top: 10px;
}

.link {
    text-decoration: none;
    font-size: large;
    color: black;
}

.link:hover {
    text-decoration: underline;
}
</style>

 

3.2 发布文章页面

<template>

    <div style="border: 1px solid #ccc; line-height: normal; height: 100%;">
        <div>

            <el-form :inline="true" :model="formInline" class="demo-form-inline">
                <el-form-item label="文章标题">
                    <el-input v-model="formInline.title" placeholder="请输入文章标题" maxlength="20"></el-input>
                </el-form-item>


                <el-form-item label="类别">
                    <el-select v-model="formInline.category" placeholder="请选择文章类别">
                        <el-option label="新闻报道" value="news"></el-option>
                        <el-option label="科技动态" value="technology"></el-option>
                        <el-option label="生活时尚" value="lifestyle"></el-option>
                        <el-option label="教育学习" value="education"></el-option>
                        <el-option label="健康养生" value="health"></el-option>
                    </el-select>
                </el-form-item>


                <el-form-item>
                    <el-button type="primary" @click="onSubmit"
                        v-loading.fullscreen.lock="fullscreenLoading">提交</el-button>
                </el-form-item>
            </el-form>

        </div>
        <Toolbar style="border-bottom: 1px solid #ccc" :editor="editor" :defaultConfig="toolbarConfig" :mode="mode" />
        <Editor style="height: 500px; overflow-y: hidden; height: 100%;" v-model="html" :defaultConfig="editorConfig"
            :mode="mode" @onCreated="onCreated" />
    </div>

</template>


<script>
import Vue from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import axios from 'axios'
import Cookies from 'js-cookie'


export default Vue.extend({
    components: { Editor, Toolbar },
    data() {
        return {
            editor: null,
            html: ' ',
            toolbarConfig: {},
            editorConfig: { placeholder: '请输入内容...' },
            mode: 'default', // or 'simple'
            formInline: {
                title: '',
                category: ''
            },
            fullscreenLoading: false
        }
    },
    methods: {
        onCreated(editor) {
            this.editor = Object.seal(editor) // 一定要用 Object.seal() ,否则会报错

        },
        onSubmit() {

            this.fullscreenLoading = true;

            const userId = Cookies.get('userId'); // 获取并转换userId

            axios.post('/api/forum/add', {

                "title": this.formInline.title,

                "content": this.editor.getHtml(), 

                "authorId": userId,

                "tag": this.formInline.category,

            }).then((response) => {

                console.log(response.data);

                this.fullscreenLoading = false;

                this.$router.push({ name: 'ForumSucessPostView' });

            }).catch(error => {

                console.error(error);

                this.fullscreenLoading = false;

            });

        },

    },
    mounted() {

    },
    beforeDestroy() {
        const editor = this.editor
        if (editor == null) return
        editor.destroy() // 组件销毁时,及时销毁编辑器


    },

})
</script>


<style src="@wangeditor/editor/dist/css/style.css"></style>

3.3 文章发布成功页面


3.4 查看文章页面

html:

<template>
    <div style="line-height: normal; background-color: rgb(246, 247, 249); height: auto; min-height: 80%;">

        <!-- 文章信息 -->
        <div style="padding-top: 10px; width: auto; min-width: 40%;">
            <!-- 实现文字垂直居中 -->
            <div id="Infor" style="background-color: white;">
                <h1 style="font-size: 28px; text-align: center">文章标题:{{title}}</h1>
                <span>创作者:{{author}}</span>
                <span style="margin-left: 20px;">创作日期:{{createAt}}</span>
                <span style="margin-left: 20px;"><i class="el-icon-view">{{heatValue}}</i></span>
            </div>

            <el-divider><i class="el-icon-mobile-phone"></i></el-divider>


            <!-- 文章内容展示区 -->
            <div id="contentDisplay">
                <div v-html="content"
                    style="padding-left: 2em; padding-top: 15px; padding-right: 2em; padding-bottom: 30px;"></div>
            </div>
        </div>

        <el-divider><i class="el-icon-edit"></i></el-divider>

        <!-- 交互按键 -->
        <div id="buttom">
            <el-button type="warning" round @click="getBackToForum">返回到论坛</el-button>
            <el-button type="warning" icon="el-icon-star-off" circle @click="PostFavorite"></el-button>
            <el-button type="danger" icon="el-icon-thumb" circle @click="PostLike"></el-button>

        </div>

        <!-- 评论区 -->
        <div id="commentListShow">
            <el-card class="box-card">
                <div slot="header" class="clearfix">
                    <span>评论详情</span>
                </div>
                <div id="commentInputArea">

                    <el-input type="textarea" placeholder="请您输入友善的评论吧" v-model="textarea" maxlength="300"
                        show-word-limit id="inputFrame" :clearable="clearAble" resize="none">
                    </el-input>
                    <div style="margin-top: 10px; padding-bottom: 50px;">
                        <el-button type="primary" @click="SubmitComment">发表评论</el-button>
                        <el-button type="primary" @click="CancelComment">取消评论</el-button>
                    </div>
                </div>

                <div id="commentList">
                    <div class="comment-card" v-for="comment in comments" :key="comment.id">
                        <div class="comment-head">
                            <h1 class="username">{{ comment.account }}</h1>
                            <p class="created-at">发表于:{{ comment.createdAt }}</p>
                        </div>
                        <el-divider></el-divider>
                        <p class="comment-text">{{ comment.commentText }}</p>
                    </div>
                </div>

            </el-card>
        </div>


    </div>
</template>

script:

<script>
import axios from 'axios';
import Cookies from 'js-cookie';


export default{
    data() {
        return {
            postId: '',
            title: '',
            content: '',
            value1: null,
            textarea: '',
            userId:'',
            clearAble: true,
            comments:{},
            author:'',
            createAt:'',
            heatValue:'',
        }
    },
    created() {

        this.postId = this.$route.params.postId;
        this.fetchPostDetail(this.$route.params.postId);
        this.userId = Cookies.get('userId');
    },
    mounted() {
        this.readComment();
        
    },
    methods: {
        // 前端实现路径传参
        async fetchPostDetail(postId) {
            try {
                this.fullscreenLoading = true;
                const url = `/api/forum/post/${postId}`;
                // 发起GET请求
                const response = await axios.get(url);

                if (response.status === 200) {
                    // 请求成功,处理响应数据
                    const postData = response.data;
                    console.log('文章详情:', postData);
                    // 更新组件状态或执行其他操作
                    this.title = response.data.data.title;
                    this.content = response.data.data.content;
                    this.author = response.data.data.account;
                    this.createAt = response.data.data.createdAt;
                    this.heatValue = response.data.data.heatValue;
                    this.fullscreenLoading = false;
                } else {
                    console.error('请求失败,状态码:', response.status);
                }
            } catch (error) {
                console.error('请求错误:', error);
            }
        },

        getBackToForum() {
            this.$router.push({ name: 'forum' });
        },


        // 取消评论
        CancelComment(){
            this.textarea = '';

        },

        // 执行点赞按钮
        PostLike(){
            this.isLogin();
            axios.get('/api/forum/like',{
                params:{
                    postId : this.postId,
                    userId : this.userId
                }
            }).then((response)=>{
               this.MessageNotify(response);
            })
        },

        // 执行收藏按钮
        PostFavorite(){
            this.isLogin();
            axios.get('/api/forum/favorite',{
                params:{
                    postId : this.postId,
                    userId : this.userId
                }
            }).then((response)=>{
               this.MessageNotify(response);
            })


        },
        SubmitComment(){

            this.isLogin();

            axios.post('/api/forum/writeComment',{
                postId : this.postId,
                userId : this.userId,
                commentText : this.textarea
            }).then((response)=>{
                this.MessageNotify(response);
                this.textarea = '';
                this.readComment();
            })

            console.log("submit");
        },

        isLogin(){
            if(Cookies.get('userId') == null){
                this.$message.error('请先登录');
                return;
            }
        },

        // 消息提醒
        MessageNotify(response){
            if(response.data.code == 200){
                this.$message.success(response.data.data);
            }else{
                console.log(response.data);
                this.$message.error(response.data.message);
            }
        },

        readComment(){
            axios.get('/api/forum/getComment',{
                params:{
                    postId : this.postId
                }
            }).then((response)=>{
                this.comments = response.data.data;
            })
        },


    }

}


</script>

css:


3.5 文章搜索页面

html:

<template>
    <div id="layout">

        <div id="searchFrame">
            <div id="InputFrame">
                <el-input type="textarea" placeholder="请输入内容" v-model="textarea" rows="1" resize="none"
                    style="font-size: larger; width: 80%;">
                </el-input>
                <el-button type="warning" @click="SearchSubmit"  icon="el-icon-search">查询</el-button>
            </div>
        </div>

        <div id="excess">

            <div id="Interate">
                <i class="el-icon-search"> 搜索结果</i>
            </div>

        </div>

        
        <div id="SearchContent">

            <div v-if="posts.length === 0">
                <el-empty :image-size="200"></el-empty>
            </div>



            <div class="custom-card" v-for="(post, index) in posts" :key="index" @click="getForumPostDetail(post.id)">
                <div class="card-content">
                    <h1 class="card-title">标题: {{ post.title }}</h1>
                    <div class="card-meta">
                        <span>作者: {{ post.account }}</span>
                        <span>标签: {{ post.tag }}</span>
                        <span><i class="el-icon-view">{{ post.heatValue }}</i></span>
                    </div>
                    <div class="card-rating">
                        <span>文章评分:</span>
                        <el-rate v-model="post.rating" disabled show-score text-color="#ff9900" score-template="{value}"
                            style="display: inline-block;"></el-rate>
                    </div>
                </div>
            </div>
        </div>


    </div>
</template>

script:

<script>
import axios from 'axios';


export default {
    data() {
        return {
            textarea: '',
            searchKeyWord: '',
            posts: [],
        }
    },

    methods: {
        SearchSubmit() {
            console.log(this.textarea);
            axios.get('/api/forum/search', {
                params: {
                    searchKeyWord: this.textarea
                }
            }).then((response) => {

                if (response.data.code !== 200) {
                    this.$notify({
                        title: '警告',
                        message: '搜索失败',
                        type: 'warning'
                    });
                }

                console.log(response.data.data);
                this.posts = response.data.data;
            });
        },


        Search() {

            axios.get('/api/forum/search', {
                params: {
                    searchKeyWord: this.searchKeyWord
                }
            }).then((response) => {

                if (response.data.code !== 200) {
                    this.$notify({
                        title: '警告',
                        message: '搜索失败',
                        type: 'warning'
                    });
                }

                console.log(response.data.data);
                this.posts = response.data.data;
            });
        },

         // 跳转到文章详情
         getForumPostDetail(postId) {
            console.log("getForumPostDetail");
            console.log(postId);
            this.$router.push(`/post/${postId}`);
        },



    },

    mounted() {
        this.searchKeyWord = this.$route.params.searchKeyWord;
        this.textarea = this.searchKeyWord;
        this.Search();
    }

}
</script>

css:

<style scoped>
#layout {
    width: 100%;
    min-height: 90%;
    background-color: rgb(245, 246, 247);
    line-height: normal;
}

#SearchContent{
    min-height: 800px;
    

}

#searchFrame {
    height: 70px;
    width: 100%;
    background-color: white;
    box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
    position: -webkit-sticky;
    /* Safari */
    position: sticky;
    top: 0;
    z-index: 1000;
    line-height: normal;
}

#InputFrame {
    width: 40%;
    margin: 0 auto;
    height: 60%;
    padding-top: 15px;
}

#Interate {

    float: left;
    margin-top: 20px;
    margin-left: 20px;
}

#excess {

    height: 61px;
    width: 80%;
    background-color: white;
    margin: 0 auto;
    margin-top: 25px;
    border: 1px solid rgb(245, 245, 245);
    border-radius: 4px;
}



.custom-card {
    background-color: #ffffff;
    border-radius: 4px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    margin-bottom: 16px;
    transition: box-shadow 0.3s ease-in-out;
    width: 80%;
    margin: 0 auto;

    border: 1px solid rgb(245, 245, 245);
}

.custom-card:hover {
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
    cursor: pointer;
    background-color: rgb(245, 245, 245);
}

.card-content {
    padding: 16px;
}

.card-title {
    font-size: 1.2em;
    margin-bottom: 8px;
    color: #333;
}

.card-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
    color: #666;
}

.card-rating {
    color: #666;
}

.card-rating {
    color: #666;
}
</style>

 


 

四、后端代码

4.1项目后端依赖库

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.11</version>
        </dependency>

        <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>
             <version>3.5.3.1</version>
        </dependency>

        <!--swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.15</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-spring-web</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/dashscope-sdk-java -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dashscope-sdk-java</artifactId>
            <version>2.8.2</version>
        </dependency>
        <!--okhttp3 依赖-->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.3</version>
        </dependency>

        <!-- Lombok dependency -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--        验证码模块-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

4.2工具类Result类与实体类

public class Result<T> {
    // 状态码常量
    public static final int SUCCESS = 200;
    public static final int ERROR = 500;
    
    private int code; // 状态码
    private String message; // 消息
    private T data; // 数据

    // 构造函数,用于创建成功的结果对象
    private Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    // 成功结果的静态方法
    public static <T> Result<T> success(T data) {
        return new Result<>(SUCCESS, "Success", data);
    }


    // 错误结果的静态方法
    public static <T> Result<T> error(String message) {
        return new Result<>(ERROR, message, null);
    }

    // 错误结果的静态方法,可以传入自定义的状态码
    public static <T> Result<T> error(int code, String message) {
        return new Result<>(code, message, null);
    }

    // 获取状态码
    public int getCode() {
        return code;
    }

    // 设置状态码
    public void setCode(int code) {
        this.code = code;
    }

    // 获取消息
    public String getMessage() {
        return message;
    }

    // 设置消息
    public void setMessage(String message) {
        this.message = message;
    }

    // 获取数据
    public T getData() {
        return data;
    }

    // 设置数据
    public void setData(T data) {
        this.data = data;
    }

    // 用于转换为Map类型的方法,方便序列化为JSON
    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("code", code);
        map.put("message", message);
        map.put("data", data);
        return map;
    }
}

Entity:

Forumpost:

@TableName(value ="forum_posts")
@Data
public class ForumPosts implements Serializable {
    /**
     * 
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * 
     */
    @TableField(value = "title")
    private String title;
    /**
     * 
     */
    @TableField(value = "content")
    private String content;
    /**
     * 
     */
    @TableField(value = "author_id")
    private Integer authorId;
    /**
     * 
     */
    @TableField(value = "created_at")
    private LocalDateTime createdAt;
    /**
     * 
     */
    @TableField(value = "updated_at")
    private LocalDateTime updatedAt;
    /**
     * 
     */
    @TableField(value = "heat_value")
    private Integer heatValue;
    /**
     * 
     */
    @TableField(value = "rating")
    private BigDecimal rating;
    /**
     * 
     */
    @TableField(value = "tag")
    private String tag;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        ForumPosts other = (ForumPosts) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
            && (this.getTitle() == null ? other.getTitle() == null : this.getTitle().equals(other.getTitle()))
            && (this.getContent() == null ? other.getContent() == null : this.getContent().equals(other.getContent()))
            && (this.getAuthorId() == null ? other.getAuthorId() == null : this.getAuthorId().equals(other.getAuthorId()))
            && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()))
            && (this.getUpdatedAt() == null ? other.getUpdatedAt() == null : this.getUpdatedAt().equals(other.getUpdatedAt()))
            && (this.getHeatValue() == null ? other.getHeatValue() == null : this.getHeatValue().equals(other.getHeatValue()))
            && (this.getRating() == null ? other.getRating() == null : this.getRating().equals(other.getRating()))
            && (this.getTag() == null ? other.getTag() == null : this.getTag().equals(other.getTag()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getTitle() == null) ? 0 : getTitle().hashCode());
        result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode());
        result = prime * result + ((getAuthorId() == null) ? 0 : getAuthorId().hashCode());
        result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
        result = prime * result + ((getUpdatedAt() == null) ? 0 : getUpdatedAt().hashCode());
        result = prime * result + ((getHeatValue() == null) ? 0 : getHeatValue().hashCode());
        result = prime * result + ((getRating() == null) ? 0 : getRating().hashCode());
        result = prime * result + ((getTag() == null) ? 0 : getTag().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", title=").append(title);
        sb.append(", content=").append(content);
        sb.append(", authorId=").append(authorId);
        sb.append(", createdAt=").append(createdAt);
        sb.append(", updatedAt=").append(updatedAt);
        sb.append(", heatValue=").append(heatValue);
        sb.append(", rating=").append(rating);
        sb.append(", tag=").append(tag);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}
ForumPostLike:
@Data

@TableName(value ="forum_post_likes")

public class ForumPostLike {

    private int userId;

    private int postId;


}
ForumPostFavorites:
@Data
@TableName(value ="forum_post_favorites")
public class ForumPostFavorites {

    private int userId;

    private int postId;

}
ForumComments
@TableName(value ="forum_comments")
@Data
public class ForumComments implements Serializable {
    /**
     * 
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 
     */
    @TableField(value = "post_id")
    private Integer postId;

    /**
     * 
     */
    @TableField(value = "user_id")
    private Integer userId;

    /**
     * 
     */
    @TableField(value = "comment_text")
    private String commentText;

    /**
     * 
     */
    @TableField(value = "created_at")
    private LocalDateTime createdAt;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        ForumComments other = (ForumComments) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
            && (this.getPostId() == null ? other.getPostId() == null : this.getPostId().equals(other.getPostId()))
            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
            && (this.getCommentText() == null ? other.getCommentText() == null : this.getCommentText().equals(other.getCommentText()))
            && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getPostId() == null) ? 0 : getPostId().hashCode());
        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
        result = prime * result + ((getCommentText() == null) ? 0 : getCommentText().hashCode());
        result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", postId=").append(postId);
        sb.append(", userId=").append(userId);
        sb.append(", commentText=").append(commentText);
        sb.append(", createdAt=").append(createdAt);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}

 DTO:

@Data
public class CommentDTO {


    private int  userId;

    private int postId;

    private String commentText;


}
@Data
public class ForumAddPostDTO {

    @JsonProperty("title")
    private String title;

    @JsonProperty("content")
    private String content;

    @JsonProperty("authorId")
    private Integer authorId;

    @JsonProperty("tag")
    private String tag;
}

 VO:

@Data
public class ArticleVO {

    private String title;

    private String content;

    private String account;

    private Integer heatValue;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createdAt;

}
@Data
public class CommentVo {

    private int id;

    private String commentText;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createdAt;

    private String account;
}
@Data
public class LikeSearchVo {
    private String value;
}

 


4.3 自定义异常与全局异常

public class BaseException extends RuntimeException{

    public BaseException(){
    }
    public BaseException(String msg){
        super(msg);
    }
    
}
public class NotFoundArticleException extends BaseException{
    public NotFoundArticleException(String msg){
        super(msg);
    }

}
public class AlreadyLikeException extends BaseException{

    public AlreadyLikeException(String msg){
        super(msg);
    }


}

全局异常处理类:

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler
    public Result exceptionHandler(BaseException ex){
        log.error("异常信息:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }

}

4.4Controller层

1.ForumPostController

@RequestMapping("/forum")
@RestController
@Api(tags = "文章管理")
@Slf4j
public class ForumPostController {

    @Autowired
    ForumPostsService forumPostsService;

    @Autowired
    UserService userService;

    @Autowired
    ForumCommentsService forumCommentsService;




    @ApiOperation("新增文章")
    @PostMapping("/add")
    public Result addForumPost(@RequestBody ForumAddPostDTO forumAddPostDTO) throws ParseException {
        ForumPosts forumPosts = new ForumPosts();
        BeanUtils.copyProperties(forumAddPostDTO, forumPosts);
        forumPostsService.save(forumPosts);
        return Result.success("新增成功");
    }


    @GetMapping("/getAllForumPost")
    @ApiOperation("推荐文章查询")
    public Result getAllForumPost(@RequestParam(value="pageSize", defaultValue = "10") int pageSize,
                                  @RequestParam(value="pageNumber", defaultValue = "1") int pageNumber){


        Page<ForumPosts> page = new Page<>(pageNumber, pageSize);
        // 创建查询包装器并指定排序规则
        QueryWrapper<ForumPosts> queryWrapper = new QueryWrapper<>();
        // 假设你想按照创建时间降序排序
        queryWrapper.orderByDesc("created_at");
        Page<ForumPosts> paged = forumPostsService.page(page,queryWrapper);
        List<ForumPosts> postsList = paged.getRecords();
        List<ForumPageVO> posts = new ArrayList<>();


        for (ForumPosts post : postsList) {
            ForumPageVO vo = new ForumPageVO();
            // 获取账户信息
            User user = userService.getById(post.getAuthorId());
            vo.setAccount(user.getAccount());

            // 直接从ForumPosts对象复制其他字段
            vo.setTag(post.getTag());
            vo.setRating(post.getRating()); // 如果需要字符串形式
            vo.setTitle(post.getTitle());
            vo.setPostId(post.getId());
            vo.setHeat_value(post.getHeatValue());
            // 添加到列表
            posts.add(vo);
        }


        return Result.success(posts);

    }


//    文章阅读
    @ApiOperation("读取文章")
    @GetMapping("post/{id}")
    public Result<ArticleVO> readArtical(@PathVariable int id){
        ArticleVO articleVO =  forumPostsService.readArticle(id);
        return Result.success(articleVO);
    }


//   我的文章功能
    @ApiOperation("我的文章")
    @GetMapping("/MyArticle")
    public Result getMyArticle(@Param("id") int id){
        List<ForumPosts> forumPostsList = forumPostsService.getByAuthorId(id);
        return Result.success(forumPostsList);
    }

//    热门文章功能
    @ApiOperation("热门文章")
    @GetMapping("/getHotPosts")
    public Result getHotPosts(){
        QueryWrapper<ForumPosts> queryWrapper = new QueryWrapper();
        queryWrapper.orderByDesc("heat_value");
        List<ForumPosts> forumPosts = forumPostsService.list(queryWrapper);
        return Result.success(forumPosts);
    }

    //    热门文章功能
    @ApiOperation("优质文章")
    @GetMapping("/getOutStandPosts")
    public Result getOutStandPosts(){

        QueryWrapper<ForumPosts> queryWrapper = new QueryWrapper();
        queryWrapper.orderByDesc("rating");
        List<ForumPosts> forumPosts = forumPostsService.list(queryWrapper);
        return Result.success(forumPosts);
    }


    //  我的收藏
    @ApiOperation("我的收藏")
    @GetMapping("/getMyFavorite")
    public Result getMyFavorite(@Param("id") int id){

       List<ForumPosts> forumPostsList =  forumPostsService.getMyFavorite(id);

        return Result.success(forumPostsList);
    }


//    文章查询
    @ApiOperation("文章查询")
    @GetMapping("/search")
    public Result postSearch(@RequestParam("searchKeyWord") String searchText){

        if (searchText == null){
            return Result.error("不能输入为空噢");
        }
        LambdaQueryWrapper<ForumPosts> lambdaQueryWrapper = new LambdaQueryWrapper<>();

//        TODO:通过LamdaQueryWrapper 模糊查询
        // 模糊查询title字段,%searchText%会被自动添加
        lambdaQueryWrapper.like(ForumPosts::getTitle, searchText);

        // 假设这里有一个service接口用于操作ForumPosts表
        List<ForumPosts> postsList = forumPostsService.list(lambdaQueryWrapper);

        // 根据你的Result类的具体实现,返回查询结果
        return Result.success(postsList);
    }

    /**
     * 标题模糊查询
     * @param keyword 关键词
     * @return 匹配的标题列表
     */
    @GetMapping("/getLikeSearch")
    @ApiOperation("标题模糊查询")
    public Result getLikeSearch(@RequestParam String keyword){
        List<LikeSearchVo> titles = forumPostsService.findTitlesByKeyword(keyword);
        return Result.success(titles);
    }


//    用户交互
    //    点赞
    @ApiOperation("点赞")
    @GetMapping("/like")
    public Result PostLike(@RequestParam("postId") int postId,@RequestParam("userId") int userId){
        String result =  forumPostsService.PostLike(postId,userId);
        return Result.success(result);
    }

//    收藏
    @ApiOperation("收藏")
    @GetMapping("/favorite")
    public Result PostFavorite(@RequestParam("postId") int postId,@RequestParam("userId") int userId){

        String result = forumPostsService.PostFavorite(postId,userId);

        return Result.success(result);
    }

//    写评论
  @ApiOperation("写评论")
  @PostMapping("/writeComment")
   public Result WriteComment(@RequestBody CommentDTO commentdto){

      ForumComments forumComments = new ForumComments();
      BeanUtils.copyProperties(commentdto,forumComments);
      forumCommentsService.save(forumComments);
      return Result.success("评论成功");
  }


//    读评论
    @ApiOperation("读取评论")
    @GetMapping("/getComment")
    public Result<List<CommentVo>> GetComment(@RequestParam("postId") int postId){
        List<CommentVo> comment = forumCommentsService.getComment(postId);
        return Result.success(comment);
    }


}


4.5 Service层
 

public interface ForumPostsService extends IService<ForumPosts> {

    List<ForumPosts> getByAuthorId(Integer id);

    String PostLike(int postId, int userId);

    String PostFavorite(int postId, int userId);

    ArticleVO readArticle(int id);

    List<ForumPosts> getMyFavorite(int id);

    List<LikeSearchVo> findTitlesByKeyword(String keyword);
}
public interface ForumCommentsService extends IService<ForumComments> {

    List<CommentVo> getComment(int postId);
}

4.6 Mapper层

public interface ForumCommentsMapper extends BaseMapper<ForumComments> {

}
@Mapper
public interface ForumPostFavoritesMapper extends BaseMapper<ForumPostFavorites> {
}
@Mapper
public interface ForumPostLikeMapper extends BaseMapper<ForumPostLike> {
}
@Mapper
public interface ForumPostsMapper extends BaseMapper<ForumPosts> {


    @Select("select  * from paitool.user as a,paitool.forum_posts as b where  a.id=b.author_id and a.id = #{id}")
    List<ForumPosts> getByAuthorId(Integer id);
    

}

 


五、进阶思路

1.通过ElasticSearch优化搜索引擎

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

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alphamilk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值