分享一个小说网站,课设,毕设,HTML

发布于:2025-05-21 ⋅ 阅读:(18) ⋅ 点赞:(0)

🔥手把手教你用SpringBoot+MyBatisPlus搭建小说网站,CSDN爆款教程来袭!

📌 前言:为什么我要做这个项目?

最近在CSDN刷技术文章时,发现很多新手开发者对前后端分离项目望而却步,特别是想用Java做全栈开发却找不到合适案例。作为小说迷+技术控,我决定用最主流的技术栈(SpringBoot+MyBatisPlus+模板引擎)搭建一个完整的小说网站,从0到1分享整个开发过程。

🌟 项目亮点

  1. 技术栈:SpringBoot 2.7 + MyBatisPlus + Thymeleaf模板引擎
  2. 核心功能:小说分类/搜索/阅读/书架管理/用户系统

需要源码可以添加联系方式,我可以帮你一起把项目运行起来

首页:

小说详情:

章节:

阅读:

项目结构:

数据库:

代码示例:

后端

package cn.book.bus.service.impl;

import cn.book.bus.aop.HttpAspect;
import cn.book.bus.domain.Chapter;
import cn.book.bus.service.DownloadService;
import cn.book.bus.service.IChapterService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.List;

/**
 * 小说下载实现类
 */
@Service
public class DownloadServiceImpl extends HttpServlet implements DownloadService {

    private static final Logger log = LoggerFactory.getLogger(HttpAspect.class);

    @Resource
    private IChapterService iChapterService;


    @Override
    public void download(int fiction_id, String fileName, HttpServletResponse response) {

        String path = fileName + ".txt";
        FileWriter fileWriter=null;
        InputStream is=null;
        OutputStream os=null;
        try {
            //写入文件
            File file = new File(path);
            if (!file.exists()) {
                QueryWrapper<Chapter> queryWrapper=new QueryWrapper<>();
                queryWrapper.eq("fiction_id",fiction_id);
                List<Chapter> list = iChapterService.list(queryWrapper);
                fileWriter = new FileWriter(file);
                int i=0;
                for (Chapter chapter : list) {
                    //获取文章标题
                    fileWriter.write(chapter.getChapterTitle());
                    //获取文章内容
                    Chapter cp=iChapterService.getById(list.get(i).getId());
                    //过滤html
                    fileWriter.flush();
                    i++;
                }
                //关闭输入流
                fileWriter.close();
            }
            //向浏览器传输文件流
            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".txt", "UTF-8"));
            //转换成流
            is = new FileInputStream(path);
            //以字节的形式去流
            os = response.getOutputStream();
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer))>0) {
                os.write(buffer, 0, len);
            }
            log.info("下载小说:"+fileName);
        }catch (Exception e){
            log.error("下载异常:"+e.getMessage());
        }finally {
            if (is!=null){
                try {
                    is.close();
                }catch (Exception e) {
                    log.error("关闭InputStream异常:"+e.getMessage());
                }
            }
            if (os!=null){
                try {
                    os.close();
                }catch (Exception e){
                    log.error("关闭InputStream异常:"+e.getMessage());
                }
            }
            try {
                if (fileWriter!=null){
                    response.getOutputStream().close();
                }
            }catch (Exception e){
                log.error("关闭InputStream异常:"+e.getMessage());
            }
        }
    }
}

前端:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta name="copyright" content="本页版权 www.zongheng.com 无敌中文网所有。All Rights Reserved"/>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <link rel="shortcut icon" href="https://static.zongheng.com/favicon.ico"/>
    <title>[[${title}]]小说_小说推荐_好看的完本免费小说推荐- 无敌中文网</title>
    <meta name="keywords" content="十月的夏天最新章节, 十月的夏天全文阅读, 是十月的小说"/>
    <meta name="description" content="《十月的夏天》是是十月在无敌中文网原创首发的武侠仙侠, 是十月的小说十月的夏天最新章节全文阅读,请随时关注更新最快的小说网站无敌中文网。"/>

    <link rel="stylesheet" type="text/css" th:href="@{/css/basic.min.css}"/>
    <link rel="stylesheet" type="text/css" th:href="@{/css/store.min.css}"/>
    <link rel="stylesheet" type="text/css" th:href="@{/css/book.min.css}"/>

    <link rel="stylesheet" type="text/css" th:href="@{/layui/css/layui.css}"/>
    <script type="text/javascript" th:src="@{/layui/layui.js}"></script>
    <script type="text/javascript" th:src="@{/js/jquery-1.9.1.js}"></script>

</head>
<body>
<div class="wrap">
    <div class="head">
        <div class="head-top clearfix">
            <div class="logo imgbox fl">
                <a th:href="@{/index}"><img th:src="@{/picture/logo.png}" alt="logo"  src=""></a>
            </div>
            <form id="commSearch" name="searchForm" method="get" th:action="@{/search}" target="_blank">
                <div class="search-box fl" data-hook="searchSuggest">
                    <label>
                        <input class="search-text fl" name="fictionName" type="text"
                               placeholder="请输入书名"/>
                    </label>
                    <input type="submit" class="search-btn fr"/>
                </div>
            </form>
            <div class="right-wrap-login ud_userTox" style="right: 0">
                <div class="unlogin ud_unlogin clearfix">
                    <div th:if="${session.username==null}" class="login fl"><a class="user_login layui-icon" th:href="@{/login}">&#xe66f;登录</a></div>
                    <div th:if="${session.username==null}" class="regist fl"><a class="user_register layui-icon" th:href="@{/signin}">&#xe672;注册</a></div>
                    <div th:if="${session.username!=null}" class="shelf">
                        <a th:href="@{/shelf}" class="layui-icon" target="_blank">&#xe705;书架</a>
                        <a style="padding-left: 18px;" class="layui-icon" th:href="@{/logout}">&#x1006;注销</a></div>
                </div>
            </div>
        </div>
    </div>
    <div class="h20-blank"></div>
    <!-- 书封页start -->
    <div id="container"  class="book-html-box clearfix">
        <!-- 书籍详细信息 -->
        <div   class="book-top clearfix">
            <div  class="book-main fl">
                    <div class="book-detail clearfix">
                        <div class="book-img fl">
                            <em class=""></em>
                            <img th:src="${fiction.imgUrl}" src="" th:alt="${fiction.fictionName}" width="200" height="264" alt="">
                        </div>
                        <div class="book-info">
                            <div th:text="${fiction.fictionName}" class="book-name">
                            </div>
                            <div class="book-label">
                                <a href="" class="state" th:text="${fiction.state}" target="_blank"></a>
                                <a href="" class="label" th:text="${fiction.type}" target="_blank"></a>
                            </div>
                            <div class="nums"><span>字数 <i th:text="${fiction.number}"> </i> </span>
                                <span>总点击 <i th:text="${fiction.views}"></i> </span> <span>周推荐 <i>0</i> </span></div>
                            <div class="book-dec Jbook-dec hide">
                                <p th:text="${fiction.brief}"><br> </p>
                            </div>
                            <div class="btn-group">
                                <a class="btn read-btn" target="_blank" th:href="'/fiction/chapter/read/'+${fiction.id}+'/1'">开始阅读</a>
                                <a class="btn read-btn" target="_blank" th:href="'/fiction/chapter/list/'+${fiction.id}">章节目录</a>
                                <div th:if="${presence==0}" id="add-shelf" th:name="${fiction.id}" class="btn store-btn no-stored">
                                    加入书架
                                </div>
                                <div  style="display: none"  class="btn store-btn stored">已在书架</div>
                                <div th:if="${presence==1}"  class="btn store-btn stored">已在书架</div>
                            </div>
                        </div>
                    </div>
                    <div class="book-new-chapter">
                        <h4>最新章节</h4>
                        <div class="tit"><a href="javascript:void(0)" th:text="${fiction.newest}" target="_blank"></a></div>
                        <div class="time">
                            · 1小时前<br/>
                            · 今日更新10章
                        </div>
                    </div>
            </div>
            <div class="book-side fr">
                <div class="book-author">
                    <div class="au-head">
                        <a href=""><img th:src="@{/picture/book.png}" src="" alt="是十月" width="60" height="60"></a><em>本书作家</em>

                    </div>
                    <div class="au-name"><a th:text="${fiction.author}" href=""></a>
                    </div>
                    <div class="au-words">
                        <span>作品总数<i>1</i></span>
                        <span>累计字数<i>3.9万</i></span>
                        <span>本月更新<i>1天</i></span>
                    </div>
                    <div class="au-says">
                        <h4>作者有话说</h4>

                        <div class="con empty">作者大大正在奋力码字哦~~~</div>

                    </div>
                </div>
                <div class="app-in clearfix">
                </div>
            </div>
        </div>
    </div>
    <div class="h20-blank"></div>
    <!-- 书封end -->
</div>
<script>
    layui.use(['layer'], function(){
        //监听提交
        $("#add-shelf").click(function () {
        let fiction_id=$(this).attr("name");
            $.ajax({
                url: "/fiction/addShelf",
                type: "POST",
                data: {"fiction_id": fiction_id},
                dataType: 'json',
                success: function (res) {
                    if (res.status===200){
                        $(".no-stored").hide();
                        $(".stored").show();
                        layer.msg(res.msg, {icon: 6});
                    }else {
                        layer.msg(res.msg, {icon: 5});
                    }
                }
            });
        });
    });
</script>
</body>
</html>