SSM项目-小说网站

目录

设计目标

需求分析

网站主页

用户注册

1、需求分析

2、数据库设计

3、生成验证码

4、数据加密

1、MD5

2、BCrypt加密 

5、数据交换格式

用户登录

找回密码

新用户注册

邮件发送

书架功能

查看书架

添加书籍进入书架

删除书架上的书籍

 获取全部小说分类

 本周推荐小说

 获取作者信息

 查找作者信息

查找作者的所有书籍

小说展示功能

 小说阅读功能

发布章节


设计目标

实现基本功能的小说阅读网站

1、用户登录和注册功能,密码修改功能

2、书籍展示页面的书籍分类,书籍推荐,书籍排行榜等

3、作者的全部书籍展示

4、用户阅读小说和加入书架功能

需求分析

功能需求:实现上述功能的网站

使用浏览器:联想浏览器(版本 8.0.0.10171(正式版本) (32 位))

网站主页

1、主页前端展示

  对前端页面进行分析:上半部分是搜索框,登录、注册、书架三个按钮,如果用户已经登录,那么就展示用户姓名(隐藏登录和注册按钮)

中间是分类列表和一个轮播图(使用了BootStrapt框架),点击分类列表的标签,可以跳转到详细的分类页面,点击轮播图图片,可以来到对应书籍的介绍页面

下部分是一个本周推荐列表和两个排行榜,也是点击对应位置可以来到详细的分类、作者、书籍介绍页面

用户注册

1、需求分析

实现用户注册功能

用户注册:输入用户名,输入两次密码,输入QQ邮箱,验证码

这里后端对邮箱格式进行判断时,标准是10位QQ账号的邮箱,所以只支持使用QQ邮箱注册

2、数据库设计

-- 用户表 包括 用户id 和 姓名、 密码、用户邮箱
create table user(
    id int primary key  auto_increment ,
    name varchar(20),
    password varchar(100),
    email varchar(100)
);

不过在前端,一直使用的都是用户姓名,所以方便起见,实际上要求用户名唯一

3、生成验证码

一种方式就是直接使用Java类,使用Graphics和Graphics2D进行绘图,BufferedImage类输出照片

在前端发送数据,得到结果为

package com.example.demo.Controler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Random;

/**
 * 获取验证码
 */
@Controller
@Slf4j
@RequestMapping("/novel")
public class VerificationControl {
    @GetMapping("/verification")
    @ResponseBody
    public void getVerificationControl(HttpServletRequest request, HttpServletResponse response,  int width, int height) throws IOException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        //1、生成数据
        String num = getNumbers();
        //2、生成照片
        BufferedImage bufferedImage = getPhoto(num, width,height);

        //3、将数据以会话的形式存储,只要会话没过期,就说明验证码有效,否则验证码不可以使用
        HttpSession session = request.getSession(true);//会话不存在创建会话

        //只要session对象获取成功,无论验证码是否过期 都要重新创建
        if(session!=null) {
            session.setMaxInactiveInterval(60 * 2);//验证码的最大存活时间
            session.setAttribute("Num", num);

            //4、设置不缓存照片
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "No-cache");
            response.setDateHeader("Expire", 0);

            //5、设置返回的数据格式
            response.setContentType("image/png");
            //6、图像输出到 输出字符流中 返回给前端
            ImageIO.write(bufferedImage, "png", response.getOutputStream());
        }
    }

    //获得随机字符串
    private String getNumbers() {
        char[] elem = new char[]{'A','B','C','D','E','F','G','H','I','G','K','L','M','N','O','P','Q','S','R','T','U','V','W','X','Y','Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
        StringBuilder res = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < 4; i++) {
            res.append(elem[random.nextInt(elem.length - 1)]);
        }
        return res.toString();
    }

    //获取照片
    private BufferedImage getPhoto(String num,int width,int height) throws IOException {
//,在Java中主要可以使用Graphics和Graphics2D进行绘图,
//    其中Graphics类是所有图形上下文的抽象基类,而Graphics2D就继承了Graphics类
//。而Graphics功能没有Graphics2D强大,Graphics2D是Graphics的扩展。

        //BufferedImage可以将一个图片写入内存
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
        //输入图片的宽 高 类型 要和前端匹配

        //获取会话环境(也就是得到画笔)
        Graphics graphics = bufferedImage.createGraphics();

        //绘制矩形 (会在画布上,绘制一个矩形,也就是验证码的边界 可以显示, 这个矩形默认纯白色 )
        graphics.drawRect(0, 0, width, height);

        //设置画布颜色
        graphics.setColor(getRandomColor());

        //使用画布颜色 来填充整个矩形
        graphics.fillRect(0, 0, width, height);

        // 传入Font类型变量   Font(String name, int style, int size)从指定的名称,样式和点大小创建一个新的 Font
        graphics.setFont(new Font("仿宋", Font.BOLD, 25));//字体样式 字体格式 字体大小

        print(graphics,num);//设置字体倾斜

        //图片输出到指定目录
        //  ImageIO.write(bufferedImage, "png", new File("C:\\Users\\30283\\Desktop\\novel\\photo\\photo.png"));
        //这时 生成的验证码 只会有数据和照片 但是没有字体斜体和斜线

        //生成斜线
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
            graphics.setColor(getRandomColor());
            //使用当前颜色绘制线条
            graphics.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height));//在两个坐标之间绘制斜线
        }

        return bufferedImage;

    }

    //获取随机颜色
    private Color getRandomColor() {
        Random random = new Random();
        Color color = new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
        return color;
    }

    /**
     * ,使用Graphics2D类的setShear()方法,倾斜绘图上下文对象,
     * 然后使用从Graphics类继承的setFont()方法设置字体、字型和字号,
     * 使用drawString()方法绘制文本,从而实现倾斜文字的效果。
     * public abstract void shear(double shx, double shy);
     * <p>
     * shx,shy分别是在正x轴、正y轴方向移动坐标的乘数,可以作为相应y、x坐标的函数(注意是相反的)
     */

//设置字体斜体 最好是直接对数字在画布上显示 时设置
    Random random = new Random();

    private void print(Graphics graphics,String num) {
        int x=5;
        for (int i = 0; i < 4; i++) {
            graphics.setColor(getRandomColor());//设置字体颜色
            Graphics2D graphics2D = (Graphics2D) graphics;//这个类 提供了字符旋转功能
            double degree = random.nextDouble();

            //正向旋转
            if (degree > 0.5)
                ((Graphics2D) graphics).rotate(degree*3.14 / 180);
            else
                //反向旋转
                ((Graphics2D) graphics).rotate(-degree*3.14 / 360);
            graphics.drawString(num.charAt(i) + "", x , 25);//将数字画在画板上
            x+=15;
            //这里的坐标要规划 数字一定要显示在画布中 且不能重叠
        }
    }
}

4、数据加密

1、MD5

MD5是一种算法,可以从任何密码,短语或文本中生成32个字符的十六进制字符串。

比如,如果密码是123,我就可以在任意字符串比如“hello”中选取字符拼接,从而可能创建“1hll3”这样的密码,这时,哪怕数据被盗取,他们也无法获得准确的密码

正常来说,为了用户输入123,系统可以知道它的数据库存储的密码是1hll3,这个生成数据库密码的方法肯定是根据某一个依据来的,通常使用的就是哈希算法。

但是随着彩虹表的出现(彩虹表是一个用于加密散列函数逆运算的预先计算好的表, 为破解密码的散列值(或称哈希值、微缩图、摘要、指纹、哈希密文)而准备。一般主流的彩虹表都在100G以上。 这样的表常常用于恢复由有限集字符组成的固定长度的纯文本密码)也就是说我从123加密到1hll3,黑客极有可能可以根据彩虹表破解密码

这时,可以采用的就是加长密码或者加盐;所谓加盐就是指给密码加上其他字符后存储

Md5的依赖支持:

<!--         md5依赖-->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.6</version>
        </dependency>

pom文件导入上述依赖

public class MDUtil {
    //定义一个私有的盐值
    private final static String sal = "hello";

    //DigestUtils是一个加密算法工具类
    public String getMd5(String res) {
        return md5Hex(res);//生成加密数据
    }

    //前端可以增长数据
    public static String addlen(String res) {
        res += sal.charAt(2) + sal.charAt(4);
        return md5Hex(res);
    }
    //后端再次加密 增高密码级别
    public String add(String res) {
        res += sal.charAt(1) + sal.charAt(4)+sal.charAt(3);
        return md5Hex(res);
    }

    public static void main(String[] args) {
        String ret = addlen("123");
        System.out.println("前端加密: " + ret);
        System.out.println("第二次加密: " + addlen(ret));
        System.out.println("第二次加密: " + addlen(ret));
    }
}

2、BCrypt加密 

 MD5加密是32位的,BCrypt是60位的,在MD5的基础上位数更多更难破解,同时采用随机盐值

 (盐不能重复使用。如果所有用户的密码都使用同一个盐进行加密。那么不管盐有多复杂、多大的长度,破解者都可以很容易的使用这个固定盐重新建立彩虹表,破解你的所有用户的密码。所以应当在每一次需要保存新的密码时,都生成一个新的盐,并跟加密后的hash值保存在一起)

安装Hutool依赖,Hutool是一个小而全的Java工具类库

<dependency>

        <groupId>cn.hutool</groupId>

        <artifactId>hutool-all</artifactId>

        <version>5.8.0.M4</version>

</dependency>

HuTool工具的使用_晴城丶的博客-CSDN博客_hutool怎么使用

BCrypt (hutool-码云(gitee.com))

public class BCryptUtil {
    public static void main(String[] args) {
        String password = "123456";//加密数据
        String ret = BCrypt.hashpw(password, BCrypt.gensalt());//根据随机盐值 生成加密数据
        System.out.println(ret);
        System.out.println(BCrypt.checkpw(password, ret));//判断加密后的数据是否匹配
        ret = BCrypt.hashpw(password,BCrypt.gensalt());
        System.out.println(ret);
        System.out.println(BCrypt.checkpw(password, ret));
    }
}

 虽然生成的密文不一样,但是都是可以和密码匹配的

另外:hutool还可以生成图片验证码

利用Java工具类Hutool实现验证码校验功能【java面试】-云海天教程


@Controller
@RequestMapping("/novel")
public class VerificationControlByHutool {
    //LineCaptcha 生成 线段干扰的验证码
    //CaptchaUtil 图片验证工具类
    @ResponseBody
    @GetMapping("/verificationcontrolbyhutool")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //生成 带有线条的验证码图片
        LineCaptcha circleCaptcha = CaptchaUtil.createLineCaptcha(100/*宽*/, 40/*高*/, 4/*验证码显示几个数据*/, 50/*有几个线段*/);
        //告诉浏览器输出内容为jpeg类型的图片
        response.setContentType("image/png");
        //禁止浏览器缓存
        response.setHeader("Pragma", "No-cache");
        //输出图形 内的数字
        //    System.out.println(circleCaptcha.getCode());
        //判断图形是否在有效期
        //   System.out.println(circleCaptcha.verify(circleCaptcha.getCode()));

        //将内容写入Session
        HttpSession session = request.getSession(true);
        //不管会话中 Num是不是存在 每次点击 都应该生成新的Session 来存储新的验证码信息
        if (session != null ) {
            session.setAttribute("Num", circleCaptcha.getCode());
            session.setMaxInactiveInterval(60 );
            //图形验证码写出,可以写出到文件,也可以写出到流
            circleCaptcha.write(response.getOutputStream());
        }
    }
}

5、数据交换格式

前端传递用户名和密码和邮箱,使用post提交,格式为json

在后端处理时

control层:进行参数长度或输入为空的判断和邮箱格式的验证(这里限定死了邮箱格式:以QQ.com或者qq.com为后缀的10位QQ账号邮箱)

package com.example.demo.Controler;

import com.example.demo.Model.User;
import com.example.demo.Service.RegisterService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;



/**
 *用户注册
 */
@Controller
@Slf4j
@RequestMapping("/novel")
public class RegisterControl {
    @Autowired
    private RegisterService registerService;

    @PostMapping(value = "/register")
    @ResponseBody
    public String registerControl(@RequestBody java.util.Map<String, String> map, HttpServletRequest request, HttpServletResponse response) {
        //RequestBody注解在接收多个参数时,如果是类对象,会将内容匹配到类中
        //如果是多个参数,且有元素不存在在对象中
        // 如果使用(@RequestBody String name, @RequestBody String password, @RequestBody String email, @RequestBody String verification) 直接接收
        //  {
        //  "name":"123",
        //    "password":"123",
        //    "email":"123",
        //    "verification":"tyui"
        //    }
        //因为传入的本来就是json格式的字符串,又不是对象类型,这时name就接受了所有数据,password、email、verification全部为null
        //@RequestBody 也没有设置菲必传参数,这时后面三个参数传入null,就会抛出异常:传入参数为null

        String name = map.get("name");
        String password = map.get("password");
        String email = map.get("email");
        String verification = map.get("verification");
        //这时就只能用map接收
        //1、判断为空
        if (name == null || password == null || name.equals("") || password.equals("") || email == null || email.equals("") || verification == null || verification.equals(""))
            return "存在未输入数据";
        //2、判断用户名 密码 邮箱的格式要求
        if (name.length() > 10 || name.length() < 3 || password.length() > 20 || password.length() < 10) {
            return "用户名或者密码不符合要求";
        }
        //3、判断邮箱的格式
        if (!isTrue(email)) //邮箱格式不正确
            return "邮箱格式不正确";
        return registerService.registerService(name, password, email, verification, request, response);
    }


    //对QQ邮箱格式的判断 10位数字 以QQ.com或者qq.com
    private boolean isTrue(String email) {
        if (email.length() == 17) {
            for (int i = 0; i < 10; i++) {
                if (!(email.charAt(i) >= '0' && email.charAt(i) <= '9')) {
                    return false;
                }
            }
            String value = email.substring(10, 17);
            if (value.equals("@QQ.com") || value.equals("@qq.com"))
                return true;
        }
        return false;
    }

}

Service层:1、判断验证码是否正确或者是否存在(根据验证码创建时的Session判断)2、验证码正确后,判断用户名是否已经存在,不存在执行插入操作,并创建session

package com.example.demo.Service;

import com.example.demo.Mapper.InsertUserMapper;
import com.example.demo.Mapper.RegisterMapper;
import com.example.demo.Model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


/**
 * 注册的数据校验
 */


@Service
@Slf4j
public class RegisterService {
    @Autowired
    private RegisterMapper registerMapper;
    @Autowired
    private InsertUserMapper insertUserMapper;
    @Autowired
    private BCryptUtil bCryptUtil;

    public String registerService(String name, String password, String email, String verification, HttpServletRequest request, HttpServletResponse response) {
        //1、获取验证码 判断验证码是否正确
        //获取Session
        String num = getSession(request);
        if (num == null)
            return "验证码已经过期";
        //2、判断用户输入的验证码值是否正确
        if (verification.equals(num) || isTrue(num, verification)) {//如果验证码正确
//查询用户名 如果用户名存在 表示用户已经存在
            User user = registerMapper.register(name,null);
            if (user == null) {//用户不存在 去新增数据库

                String src;
                if (email.contains("QQ.com"))
                    src = email.replace("QQ.com", "qq.com");
                else
                    src = email.replace("qq.com", "QQ.com");
                if(registerMapper.register(null,email)!=null||registerMapper.register(null,src)!=null)
                    return "邮箱已经被注册";
                String pass = bCryptUtil.add(password);
                Integer ret = insertUserMapper.insertUser(name, pass, email);
                if (ret != 1)
                    return "注册失败";
                HttpSession session = request.getSession(true);
                user = new User();
                user.setName(name);
                user.setEmail(email);
                session.setAttribute("User", user);
                return "注册成功";
            } else {
                return "用户名已经存在";
            }
        }
        return "验证码错误";
    }

    //验证码查验 忽略大小写
    private boolean isTrue(String num, String verification) {
        for (int i = 0; i < 4; i++) {
            char elem = num.charAt(i);
            char ch = verification.charAt(i);
            if (!(elem == ch || elem == Character.toLowerCase(ch) || elem == Character.toUpperCase(ch)))
                return false;
        }
        return true;
    }

    //获取会话
    private String getSession(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null)
            return (String) session.getAttribute("Num");
        return null;
    }
}

Dao层:进行数据库处理

用户登录

在control层进行数据判空或者长度校验


/**
 * 用户登录
 */
@Controller
@RequestMapping("/novel")
public class LoginControl {
    @Autowired
    private LoginService loginService;

    @PostMapping(value = "/login")
    @ResponseBody
    public String loginControl(@RequestBody java.util.Map<String, String> map, HttpServletRequest request, HttpServletResponse response) {
        String name = map.get("name");
        String password = map.get("password");
        String verification = map.get("verification");
        //这时就只能用map接收
        //1、判断为空
        if (name == null || password == null || name.equals("") || password.equals("") || verification == null || verification.equals(""))
            return "存在未输入数据";
        //2、判断用户名 密码的格式要求
        if (name.length() > 10||name.length()<3 || password.length() > 20 || password.length() < 10) {
            return "用户名或者密码不符合要求";
        }
        //3、数据校验
        return loginService.loginService(name, password, verification, request, response);
    }
}

在service层判断验证码是否正确,验证码正确后,调用dao层接口,检验用户密码是否匹配,登陆成功,创建session

package com.example.demo.Service;

import cn.hutool.crypto.digest.BCrypt;
import com.example.demo.Mapper.RegisterMapper;
import com.example.demo.Model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Service
public class LoginService {
    @Autowired
    private RegisterMapper registerMapper;

    public String loginService(String name, String password, String verification, HttpServletRequest request, HttpServletResponse response) {

        //1、获取验证码 判断验证码是否正确
        //获取Session
        String num = getSession(request);
        if (num == null)
            return "验证码已经过期";
        if (!(verification.equals(num) || isTrue(num, verification)))
            return "验证码错误";

        //registerMapper 根据name查找用户 获取到了用户对象
        User user = registerMapper.register(name);
        if (user == null) {
            return "用户不存在";
        }
        //用户存在 且密码核验成功
        if (BCrypt.checkpw(password, user.getPassword())) {
            //用户登录成功
            user.setPassword("");
            HttpSession session = request.getSession(true);
            session.setAttribute("User", user);
            return user.getName();
        }
        return "用户名和密码不匹配";
    }

    //验证码查验 忽略大小写
    private boolean isTrue(String num, String verification) {
        for (int i = 0; i < 4; i++) {
            char elem = num.charAt(i);
            char ch = verification.charAt(i);
            if (!(elem == ch || elem == Character.toLowerCase(ch) || elem == Character.toUpperCase(ch)))
                return false;
        }
        return true;
    }

    //获取会话
    private String getSession(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null)
            return (String) session.getAttribute("Num");
        return null;
    }

}

找回密码

用户输入用户名,邮箱,验证码,后端通过邮箱进行数据核验 核验成功后 用户输入新的密码 

 数据验证成功之后,发送邮件,根据邮件验证码检验数据,修改密码

新用户注册

页面跳转到注册页面

邮件发送

1、导入依赖

	<!--邮箱依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-mail</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>4.3.7.RELEASE</version>
		</dependency>

2、开启授权码

QQ邮箱:

3、 配置文件配置 (只配置了QQ邮箱,仅支持QQ邮箱发送)


  spring:
    mail:
      #QQ邮箱
      host: smtp.qq.com
      #发送者邮箱账号
      username: ****@qq.com
      #授权码 书写不要有空格
      password:********
      default-encoding: UTF-8
      port: 465
      properties:
        mail:
          smtp:
            socketFactory:
              class: javax.net.ssl.SSLSocketFactory
          debug: true

4、发送邮件

1、在忘记密码页面,传入邮箱,用户名,验证码

请求格式与响应格式

{
 type:"post",
        url:"forget",
        contentType:"application/json"
}

在control层进行数据判断

package com.example.demo.Controler;

import com.example.demo.Service.ForgetService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;


/**
 * 忘记密码 传入用户名 邮箱 验证码
 */
@Controller
@RequestMapping("/novel")
public class ForgetControl {
    @Autowired
    ForgetService forgetService;
    @PostMapping("/forget")
    @ResponseBody
    public String forgetControl(@RequestBody Map<String,String> map, HttpServletRequest request, HttpServletResponse response) {
        String name = map.get("name");
        String email = map.get("email");
        String verification = map.get("verification");
        //这时就只能用map接收
        //1、判断为空
        if (name == null || email == null || name.equals("") || email.equals("") || verification == null || verification.equals(""))
            return "存在未输入数据";
        //2、判断用户名 密码 邮箱的格式要求
        if (name.length() > 10||name.length()<3) {
            return "用户名长度不符合要求";
        }
        //3、判断邮箱的格式
        if (!isTrue(email)) //邮箱格式不正确
            return "邮箱格式不正确";
        return forgetService.forgetService(name, email, verification, request, response);
    }

    //对QQ邮箱格式的判断 10位数字 或者qq.com
    private boolean isTrue(String email) {
        if (email.length() != 17)
            return false;
        for (int i = 0; i <10 ; i++) {
            if(!(email.charAt(i)>='0'&&email.charAt(i)<='9')){
                return false;
            }
        }
        String value=email.substring(10,17);
        if(value.equals("@QQ.com")||value.equals("@qq.com"))
            return true;
        return false;
    }


}

2、在service层进行用户名和邮箱是否匹配的校验,校验成功,创建session

package com.example.demo.Service;

import com.example.demo.Mapper.ForgetMapper;
import com.example.demo.Model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;



/**
 * 忘记密码
 */
@Service
public class ForgetService {
    @Autowired
    ForgetMapper forgetMapper;

    public String forgetService(String name, String email, String verification, HttpServletRequest request, HttpServletResponse response) {

        //1、获取验证码 判断验证码是否正确
        //获取Session
        String num = getSession(request);
        if (num == null)
            return "验证码已经过期";
        if (!(verification.equals(num) || isTrue(num, verification)))
            return "验证码错误";
        //2、数据查询
        String src;
        if (email.contains("QQ.com"))
            src = email.replace("QQ.com", "qq.com");
        else
            src = email.replace("qq.com", "QQ.com");
        User user = forgetMapper.forgetMapper(name, email);
        if (user == null) {
            user = forgetMapper.forgetMapper(name, src);
            if (user == null)
                return "用户和邮箱不匹配";
        } else {
            HttpSession session = request.getSession(true);
            if (session != null) {
                user.setPassword("");
                session.setAttribute("forget", user);
                session.setMaxInactiveInterval(60 * 5);
                return "校验成功";
            }
        }
        return "校验失败";
    }

    //验证码查验 忽略大小写
    private boolean isTrue(String num, String verification) {
        for (int i = 0; i < 4; i++) {
            char elem = num.charAt(i);
            char ch = verification.charAt(i);
            if (!(elem == ch || elem == Character.toLowerCase(ch) || elem == Character.toUpperCase(ch)))
                return false;
        }
        return true;
    }

    //获取会话
    private String getSession(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null)
            return null;
            return (String) session.getAttribute("Num");
    }
}

3、校验成功之后,来到密码修改页面

在control层,进行判断

package com.example.demo.Controler;

import com.example.demo.Model.User;
import com.example.demo.Service.SendEmailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;



/**
 * 发送邮件
 */
@Controller
@RequestMapping("/novel")
public class SendEmailControl {
    @Autowired
 private   SendEmailService sendEmailService;

    @GetMapping("/sendemail")
    @ResponseBody
    public String sendEmail(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession(false);
        //说明存在想要请求找回密码的用户
        if (session != null && session.getAttribute("forget") != null) {
            User user = (User) session.getAttribute("forget");
            if (sendEmailService.sendEmail(user.getEmail(), request, response))
                return "邮件已经发送";
        }
        return "邮件未发送";
    }
}

在service层,发送邮件,将邮件内的验证码存储到session中


/**
 * 发送数据
 */
@Service
public class SendEmailService {
    @Autowired
    private JavaMailSender javaMailSender; //自动注入的Bean

    public boolean sendEmail(String email, HttpServletRequest request, HttpServletResponse response) {
        String ret = getNum();
        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
        //邮件发送方
        simpleMailMessage.setFrom("****@qq.com");
        //邮件接收方
        simpleMailMessage.setTo(email);
        //邮件标题
        simpleMailMessage.setSubject("这是找回密码的验证码");
        //设置邮件标题
        simpleMailMessage.setText("您正在找回密码,请勿告知他人,防止密码被修改,如是本人操作,请输入验证码,验证码将在5分钟后过期" + ret);
        HttpSession session = request.getSession(true);
        if (session != null) {
            session.setAttribute("Email", ret);//存入验证码
            session.setMaxInactiveInterval(5 * 60);
            javaMailSender.send(simpleMailMessage);
            return true;
        }
        return false;

    }

    //生成随机六位数字
    private String getNum() {
        Random random = new Random();
        String ret = "";
        for (int i = 0; i < 6; i++) {
            ret += random.nextInt(10);
        }
        return ret;
    }
}

4、用户点击提交,和后端交互来修改密码

 请求和响应

 type:"post",
 url:"change",
contentType:"application/json",

 在control层,进行数据长度和输入为空的判断

package com.example.demo.Controler;

import com.example.demo.Service.ChangeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;



/**
 * 密码修改功能 输入验证码和新密码
 */
@Controller
@RequestMapping("/novel")
public class ChangeControl {
    @Autowired
    ChangeService changeService;
    @ResponseBody
    @PostMapping("/change")
    public String changeControl(@RequestBody Map<String, String> map, HttpServletRequest request, HttpServletResponse response) {
        String verification = map.get("email");//邮箱验证码
        String password = map.get("password");
        //1、判断为空
        if (verification == null || password == null || verification.equals("") || password.equals(""))
            return "存在未输入数据";
        if(password.length()<10||password.length()>20)
            return "密码长度不符合要求";
        return changeService.changeService(verification, password, request, response);
    }
}

在service层,检验邮箱验证码的正确性,然后调用dao层接口,修改密码

package com.example.demo.Service;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.example.demo.Mapper.ChangePasswordMapper;
import com.example.demo.Model.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;



/**
 * 密码修改
 */
@Service
public class ChangeService {

    @Autowired
    ChangePasswordMapper changePasswordMapper;

    public String changeService(String verification, String password, HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession(false);
        if(session==null)
            return "验证码过期";
        User user = (User) session.getAttribute("forget");
        String num = (String) session.getAttribute("Email");
        if(num==null||user==null)
            return "验证码过期";
        if (num.equals(verification)) {//验证成功 修改密码
            String pass = BCrypt.hashpw(password, BCrypt.gensalt());
            if (changePasswordMapper.change(user.getId(), pass) == 1)
                return "修改成功";
            return "修改失败";
        }
        return "验证码错误";
    }
}

以上实现了用户的登录,修改密码,注册功能

书架功能

create table bookshelf (
    user_name varchar(20),
    book_id int
);

查看书架功能,在用户登录之后,才可以查看书架

查看书架

url:

 响应:

[
	{
		"book_id": 1,
		"writer_name": "曹雪芹",
		"photo": "1.png",
		"book_name": "红楼梦",
		"classify_name": "历史",
		"book_brief": "《红楼梦》初名《石头记》,成书于清代乾隆年间,是一部章回体古典长篇小说,位列中国古典文学四大名著之首,是中国古典文学创作的巅峰之作。据传,《红楼梦》刊行后不久,京师竹枝词里便有“开口不谈《红楼梦》,此公缺典定糊涂”的说法。\r\n清代小说家曹雪芹便是《红楼梦》的作者。曹雪芹(约1715—约1763),名霑,字梦阮,号雪芹、芹圃、芹溪,祖籍辽阳。曹家和《红楼梦》中的贾家一样,是个“钟鸣鼎食”之家。从曹雪芹的曾祖父曹玺开始,曹家三代任江宁织造,是江南显赫一时的名门望族。因此,生于南京的曹雪芹曾经有过一段“饫甘餍肥”的富贵生活,受过良好的文化艺术教养。雍正六年,曹家被抄,一家迁往北京,家道日渐衰落,正值年少的曹雪芹开始过着“茅椽蓬牖”的贫苦生活。曹雪芹晚年居于北京西郊,因爱子夭折而极度悲伤,最终贫病而死。\r\n曹雪芹性情豪放不羁,嗜酒健谈,工诗善画。他以十年时间创作《红楼梦》,“披阅十载,增删五次”,可谓“字字看来尽是血,十年辛苦不寻常”。据说这部凝聚着曹雪芹全部心血的巨著仅存八十回,他写至第八十回便“泪尽而逝”了。乾隆五十六年至乾隆五十七年,程伟元和高鹗先后对现存的后四十回进行反复修改,首次刊行一百二十回本,并取“红楼梦”为书名。其后四十回所据底本旧说是高鹗所续,然而红学专家们认为,《红楼梦》后四十回,曹雪芹基本上已经完成,只是不知为何未能传抄行世,未能流传下来,造成不可弥补的损失。据近年来的研究,高续说尚有可疑,续作的作者究竟为谁,仍尚待探究。\r\n《红楼梦》以错综复杂的清代上层贵族社会为背景,以贾宝玉和林黛玉的爱情悲剧为主线,通过对贾、史、王、薛四大家族荣衰的描写,展示了18世纪上半叶中国封建社会末期的方方面面,囊括了多姿多彩的世俗人情,可谓一部百科全书式的长篇小说。鲁迅先生在《中国小说史略》中评《红楼梦》:“全书所写,虽不外悲喜之情,聚散之迹,而人物事故,则摆脱旧套,与在先之人情小说甚不同……盖叙述皆存本真,闻见悉所亲历,正因写实,转成新鲜……单是命意,就因读者的眼光而有种种:经学家看见《易》,道学家看见淫,才子看见缠绵,革命家看见排满,流言家看见宫闱秘事……”\r\n",
		"score": 9.8,
		"sex": "全部",
		"state": "完结",
		"count": 1000
	}
]

control层:直接调用dao层接口进行查找

<select id="get" resultMap="bookMap">
        <!--        这个id,表示实现StudentMapper接口的哪一个方法-->
        <!--        resultType 表示方法的返回类型-->
        select book.* from book,bookshelf where book.book_id= bookshelf.book_id and
        bookshelf.user_name=#{user_name}
        <!--        这里写具体的执行语句-->

    </select>

添加书籍进入书架

用户已经登录才可以执行相应功能

 加入书架的逻辑:1、查找书籍是否已经在书架了 2、加入书架


/**
 * 给书架新增记录
 */
@Controller
@RequestMapping("/novel")
@ResponseBody
public class InsertBookShelfRecordControl {
    @Autowired
    SelectBookFromBookshelfMapper selectBookFromBookshelfMapper;
    @Autowired
    InsertBookShelfRecordMapper insertBookShelfRecordMapper;

    @RequestMapping("/add")
    public String add(@RequestParam("user_name") String user_name, @RequestParam("book_id") int book_id) {
        if ((selectBookFromBookshelfMapper.select(user_name, book_id))!=null)
            return "书籍已经加入书架了";
        if (user_name == "" || book_id <= 0)
            return "加入书架失败";
        if (insertBookShelfRecordMapper.add(user_name, book_id) == 1)
            return "加入书架成功";
        return "加入书架失败";
    }
}

删除书架上的书籍

用户登录之后,在书架管理页面,可以选中书籍将其从书架中移除

用户可以同时选中多个书籍进行删除,所以前端发送的是数组

请求格式:

  type:"post",
        url:"delete",
        contentType:"application/json",

响应:"移除书架成功"或者"移除书架失败"

 获取全部小说分类

create table classify(
    classify_id int primary key  auto_increment,
    classify_name  varchar(20)
);

请求与响应

type:"get",
url:"getallclassify"

 点击某一个分类,可以跳转到具体的分类

这里因为后序还涉及到了根据连载/完结的状态查询,根据女生/男生的性别查询,根据评分/阅读量的榜单排序查询,这些查找都是返回Book类,而且只是查找条件的不同,所以将这些功能在一个接口实现了

package com.example.demo.Controler;



import com.example.demo.Mapper.GetShowBookMapper;
import com.example.demo.Model.Book;
import com.example.demo.Service.GetShowBookService;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;

import static org.apache.tomcat.util.http.parser.HttpParser.isNumeric;

/**
 * 获取count本展示书籍
 * type标识根据什么条件筛选
 * 1、classify_name表示获取书籍的类型  并且type==classify
 * 2、阅读量 排行榜 (可以是某一分类) type=count
 * 3、评分排行榜 (可以是某一分类) type=score
 * 4、type=sex 标识根据性别查询书籍 sex参数标识男女
 * 5、type=state 标识根据书籍状态获取书籍  state参数标识书籍状态:完本,连载
 * 6、type=total 标识获取所有类型的书籍
 */
@Controller
@RequestMapping("/novel")
public class GetShowBookControl {
    @Autowired
    GetShowBookService getShowBookService;
    @ResponseBody
    @GetMapping("/getshowbooks")
    public List<Book> getbooks( @RequestParam("count")Integer count, String type, String classify_name,String sex,String state) throws IOException {
        System.out.println(count);
        System.out.println(type);
        System.out.println(classify_name);
        System.out.println(sex);
        System.out.println(state);
        if(count==null||type==null)
            return null;
        if(type.equals("classify")&&classify_name==null||type.equals("classify")&&classify_name.equals("null"))
            return null;
        else if(type.equals("sex")&&sex==null||type.equals("sex")&&sex.equals("null"))
            return null;
       else if(type.equals("state")&&state==null||type.equals("state")&&state.equals("null"))
            return null;
        if (count<=0&&!isNumeric(count))
            return null;
        if (Objects.equals(count, "null"))
            return null;
        if (Objects.equals(type, "null"))
            return null;
        if (Objects.equals(classify_name, "null"))
            classify_name=null;
        if (Objects.equals(sex, "null"))
            sex=null;
        if (Objects.equals(state, "null"))
            state=null;
        if(!(type.equals("total")||type.equals("sex")||type.equals("state")||type.equals("score")||type.equals("count")||type.equals("classify")))
            return null;
        return getShowBookService.getbooks(count,type,classify_name,sex,state);
    }
}

这里的参数count,表示数据库分页查找时的数量

type:表示根据某一类型进行查找,其包括几种情况:total:表示想要查找所有数据,sex:表示根据性别查询,state:表示根据书籍状态查询,count:表示根据阅读量排序查询,score表示根据评分排序查询classify:表示根据分类查询

而classify_name,sex,state则表示查询哪一个类别,哪个性别,哪个状态

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.Mapper.GetShowBookMapper">

    <select id="getbooks"  resultType="com.example.demo.Model.Book">
        select * from book
        <choose>
<!--            获取所有书籍-->
            <when test="type=='total' and count!=null" >
               limit #{count}
            </when>
<!--            根据类型查询-->
            <when test="type=='classify' and classify_name!=null">
                where classify_name=#{classify_name} limit #{count}
            </when>
<!--            根据性别查询-->
            <when test="type=='sex' and sex!=null">
                where sex=#{sex} limit #{count}
            </when>
<!--            根据状态查询-->
            <when test="type=='state' and state!=null">
                where state=#{state} limit #{count}
            </when>
<!--            阅读量榜单 -->
<!--            查询时候带有类型-->
<!--            where classify_name='玄幻'  order by count desc limit 2-->

<!--            只根据排序书籍 -->
            <when test="type=='count' and classify_name==null and sex==null and state==null">
                order by count desc  limit #{count}
            </when>

<!--            查询某一类书籍的阅读量前几名-->
           <when test="type=='count' and classify_name!=null and sex==null and state==null">
               where classify_name=#{classify_name}   order by count desc   limit #{count}
           </when>

<!--            查询某一个性别下的最高阅读量书籍-->
            <when test="type=='count' and sex!=null and classify_name==null and state==null">
                where sex=#{sex}   order by count desc   limit #{count}
            </when>

<!--            查询某一个状态下的最高阅读量书籍-->
            <when test="type=='count' and state!=null and classify_name==null and sex==null">
                where state=#{state}  order by count desc  limit #{count}
            </when>

            <!--            评分挡榜单 -->
            <!--            查询时候带有类型-->
            <when test="type=='score' and classify_name!=null and sex==null and state==null">
                where classify_name=#{classify_name}  order by score desc  limit #{count}
            </when>
            <!--            查询时候不带有类型-->
            <when test="type=='score' and classify_name==null and state==null  and sex==null" >
                order by score desc   limit #{count}
            </when>

            <when test="count!=null">
                limit #{count}
            </when>
        </choose>
    </select>

</mapper>

获取书籍信息

请求和响应:

{

type:"get",

url:"getbookbyid"

}

 

 获取作者信息

点击作者姓名,来到作者信息介绍页面

 查找作者信息

根据作者姓名查找这个作者的信息

 这里涉及到了照片的展示,如果在前端html中设置的是照片的路径,由于安全考虑,web工程里是无法访问本地文件的,所以要设置虚拟路径


/**
 * 配置文件
 * 配置虚拟路径映射本地路径
 * 根据虚拟地址就可访问本地图片
 */
@Configuration
public class MyConfig implements WebMvcConfigurer {
    /*
     *addResourceHandler:访问映射路径
     *addResourceLocations:资源绝对路径
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/image/**").addResourceLocations("file:C:\\Users\\30283\\Desktop\\novel\\photo\\");
    }
}

@Controller
@RequestMapping("/novel")
public class UploadPicture {
    @RequestMapping("/upload")
    public void upload(@RequestParam("imgFile") MultipartFile imgFile) throws IOException {
        String fileName = imgFile.getOriginalFilename();//获取上传的文件名
        String filePath = "C:\\Users\\30283\\Desktop\\novel\\photo\\";
        assert fileName != null;
        File targetFile = new File(filePath, fileName);   //在filePath目录下 创建filename的文件
        imgFile.transferTo(targetFile);  //使用transferTo(dest)方法将上传文件写到服务器上指定的文件;
    }
}

调用接口,上传图片,图片会复制到本地路径"C:\\Users\\30283\\Desktop\\novel\\photo\\"里,同时,使用虚拟路径可以访问到图片

 文件上传到了指定路径

 使用虚拟路径,也可以访问图片

查找作者的所有书籍

 

 响应:

[
    {
        "book_id": 1,
        "writer_name": "曹雪芹",
        "photo": "1.png",
        "book_name": "红楼梦",
        "classify_name": "历史",
        "book_brief": "《红楼梦》初名《石头记》,成书于清代乾隆年间,是一部章回体古典长篇小说,位列中国古典文学四大名著之首,是中国古典文学创作的巅峰之作。据传,《红楼梦》刊行后不久,京师竹枝词里便有“开口不谈《红楼梦》,此公缺典定糊涂”的说法。\r\n清代小说家曹雪芹便是《红楼梦》的作者。曹雪芹(约1715—约1763),名霑,字梦阮,号雪芹、芹圃、芹溪,祖籍辽阳。曹家和《红楼梦》中的贾家一样,是个“钟鸣鼎食”之家。从曹雪芹的曾祖父曹玺开始,曹家三代任江宁织造,是江南显赫一时的名门望族。因此,生于南京的曹雪芹曾经有过一段“饫甘餍肥”的富贵生活,受过良好的文化艺术教养。雍正六年,曹家被抄,一家迁往北京,家道日渐衰落,正值年少的曹雪芹开始过着“茅椽蓬牖”的贫苦生活。曹雪芹晚年居于北京西郊,因爱子夭折而极度悲伤,最终贫病而死。\r\n曹雪芹性情豪放不羁,嗜酒健谈,工诗善画。他以十年时间创作《红楼梦》,“披阅十载,增删五次”,可谓“字字看来尽是血,十年辛苦不寻常”。据说这部凝聚着曹雪芹全部心血的巨著仅存八十回,他写至第八十回便“泪尽而逝”了。乾隆五十六年至乾隆五十七年,程伟元和高鹗先后对现存的后四十回进行反复修改,首次刊行一百二十回本,并取“红楼梦”为书名。其后四十回所据底本旧说是高鹗所续,然而红学专家们认为,《红楼梦》后四十回,曹雪芹基本上已经完成,只是不知为何未能传抄行世,未能流传下来,造成不可弥补的损失。据近年来的研究,高续说尚有可疑,续作的作者究竟为谁,仍尚待探究。\r\n《红楼梦》以错综复杂的清代上层贵族社会为背景,以贾宝玉和林黛玉的爱情悲剧为主线,通过对贾、史、王、薛四大家族荣衰的描写,展示了18世纪上半叶中国封建社会末期的方方面面,囊括了多姿多彩的世俗人情,可谓一部百科全书式的长篇小说。鲁迅先生在《中国小说史略》中评《红楼梦》:“全书所写,虽不外悲喜之情,聚散之迹,而人物事故,则摆脱旧套,与在先之人情小说甚不同……盖叙述皆存本真,闻见悉所亲历,正因写实,转成新鲜……单是命意,就因读者的眼光而有种种:经学家看见《易》,道学家看见淫,才子看见缠绵,革命家看见排满,流言家看见宫闱秘事……”\r\n",
        "score": 9.8,
        "sex": "全部",
        "state": "完结",
        "count": 1000
    }
]

到此,就完成了homepage页面的功能实现,接下来就是小说阅读界面

小说展示功能

根据book_id获取某一本书籍信息:通过getbookbyid接口实现

获取目录:


create table article
(
    article_id   int primary key auto_increment,
    article_content varchar(10000),
    book_id         int,
    title           varchar(100),
    read_id         int
);

获取一本书籍的所有目录

 小说阅读功能

小说阅读的方式:1、点击阅读按钮阅读 2、点击目录阅读 3、在阅读界面点击上/下一页阅读

在前端处理时,第一种:传入参数是book_id,第二种是article_id,第三种是book_id和read_id

package com.example.demo.Controler;

import com.example.demo.Model.Article;
import com.example.demo.Service.GetContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;



/**
 * 阅读小说的某一个章节
 *
 *  1、如果是article_id 说明是点击目录阅读 直接查询即可
 *  2、如果是book_id存在  read_id不存在 就是直接阅读按钮
 *  3、如果是book_id存在  read_id存在 直接查询即可
 */
@Controller
@RequestMapping("/novel")
public class GetContentControl {
    @Autowired
    GetContentService service;

    @ResponseBody
    @GetMapping("/getcontent")
    public Article get( @RequestParam(value = "article_id", required = false) Integer article_id, @RequestParam(value = "book_id", required = false) Integer book_id, @RequestParam(value = "read_id", required = false) Integer read_id, HttpServletRequest request) {
        if (article_id == null && book_id != null && book_id > 0) {
            //根据书籍阅读
            if (read_id != null && read_id > 0)
                return service.get(article_id, book_id, read_id, request);
            if (read_id == null)
                return service.get(article_id, book_id, 1, request);
        }
        if (article_id != null && article_id > 0 && book_id == null && read_id == null)//点击目录阅读
            return service.get(article_id, null, null, request);
        return null;
    }
}
package com.example.demo.Service;

import com.example.demo.Mapper.*;
import com.example.demo.Model.Article;
import com.example.demo.Model.Record;
import com.example.demo.Model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;


/*
获取目录
//获取书籍内容的方式

//如果用户没有登录:
   //1、如果传入参数是article_id 说明是点击目录阅读 直接根据article_id查询即可
   //2、如果是直接点击阅读按钮 那么就是只有book_id,这时直接从第一章阅读
   //3、如果是book_id存在  read_id存在 那就是根据上一页/下一页按钮阅读

//如果用户登录 阅读哪一个章节都需要更新阅读记录
//1、如果传入参数是article_id 说明是点击目录阅读 直接根据article_id查询即可
   //2、如果是直接点击阅读按钮 那么就是只有book_id,这时根据阅读记录查询的read——id 查找书籍内容
   //3、如果是book_id存在  read_id存在 那就是根据上一页/下一页按钮阅读
 */
@Service
public class GetContentService {
    @Autowired
    GetContentMapper getContentMapper;
    @Autowired
    GetRecordMapper getRecordMapper;//查询用户记录
    @Autowired
    InsertRecordMapper insertRecordMapper;//增加用户记录
    @Autowired
    UpdateRecordMapper updateRecordMapper;//更新用户记录
    @Autowired
    GetBookIdByArticleIdMapper getBookIdByArticleId;//根据文章id获取read_id
    @Autowired
    UpdateBookCountMapper updateBookCountMapper;

    public Article get(Integer article_id, Integer book_id, Integer read_id, HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        User user = null;
        if (session != null)
            user = (User) session.getAttribute("User");
        if (session == null || user == null) {
            //没有登录
            //1、优先根据目录查询
            if (article_id != null) {
                return getContentMapper.get(article_id, null, null);
            }
            //2、根据书籍Id和章节Id读取
            else if (book_id != null && read_id != null) {//如果带有read_id,说明是根据 一本书的某一章节阅读
                return getContentMapper.get(null, book_id, read_id);
            } else if (book_id != null) {
                //只携带 默认从第一篇阅读
                return getContentMapper.get(null, book_id, 1);
            }
            return null;
        } else {//用户登录了
            //1、优先根据目录查询
            if (article_id != null) {
                Article article = getBookIdByArticleId.get(article_id);//获取read_id
                if (article != null) {
                    Record record = getRecordMapper.get(user.getName(), article.getBook_id());//读取用户记录
                    if (record == null) {//不存在此记录
                        insertRecordMapper.insert(user.getName(), article.getBook_id(), article.getRead_id());//新增阅读记录
                        updateBookCountMapper.set(article.getBook_id());//修改这本书的阅读次数
                    } else//修改记录
                        updateRecordMapper.upload(user.getName(), article.getBook_id(), article.getRead_id());
                    return getContentMapper.get(article_id, null, null);
                }
            }else if (book_id != null & read_id != null) {
                    //2、根据书籍Id和章节Id读取
                    // 如果带有read_id,说明是根据 一本书的某一章节阅读
                    Record record = getRecordMapper.get(user.getName(), book_id);//读取用户记录
                    if (record == null) {//不存在此记录
                        insertRecordMapper.insert(user.getName(), book_id, read_id);//新增阅读记录
                        updateBookCountMapper.set(book_id);//修改这本书的阅读次数
                    } else//修改记录
                        updateRecordMapper.upload(user.getName(), book_id, read_id);
                    return getContentMapper.get(null, book_id, read_id);
                } else if (book_id != null) {
                    //直接点击阅读 查询阅读记录
                    Record record = getRecordMapper.get(user.getName(), book_id);//读取用户记录
                    if (record == null) {//不存在此记录
                        insertRecordMapper.insert(user.getName(), book_id, 1);//新增阅读记录
                        updateBookCountMapper.set(book_id);//修改这本书的阅读次数
                    } else //不需要修改
                        return getContentMapper.get(null, book_id, record.getRead_id());
                }
            }
        return null;
    }
}
  <select id="get" resultType="com.example.demo.Model.Article">
   select * from article 
       <if test="article_id!=null">
           where  article_id=#{article_id}
       </if>

        <if test="read_id!=null and book_id!=null">
            where   book_id=#{book_id} and read_id=#{read_id}
        </if>
    </select>

发布章节

为了让小说阅读界面排版美观,使用了Markdown编辑器发布小说内容和展示

请求和响应


        type:"post",
        url:"send",
        contentType:"application/json",

添加统一的异常处理

@ControllerAdvice
public class ErrorAdvice {
    @ExceptionHandler(Exception.class)
    //异常处理器
    //Exception.class 表示接受所有的异常
    @ResponseBody
    public Object errorAdvice(Exception e, HttpServletResponse response) {
        System.out.println(e.getMessage());
        response.setStatus(200);
        return null;
    }
}
@Component
@Aspect
public class NovelAspect {
    //定义一个切点
    @Pointcut("execution(* com.example.demo..*.*(..))")
    public void pointcut(){

    }
}

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值