Loading

豆瓣书评网项目

环境配置

1.springmvc 搭建流程

pom.xml :

<!--1.Maven依赖spring-webmvc-->
 <repositories>
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
</repositories>

<dependencies>
  
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>


     <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- Jackson -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.11.0</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.11.0</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.11.0</version>
        </dependency>
</dependencies>

web.xml:

 <!--2.配置DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext*.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

applicationcontent.xml:

 <!--3.开启SpringMVC注解模式-->
    <context:component-scan base-package="com.imooc"/><!--扫描组件-->
    <mvc:annotation-driven>
     
    </mvc:annotation-driven>
    <mvc:default-servlet-handler/> 

web.xml

<!-- 4.解决中文乱码 -->
    <filter>
        <filter-name>characterFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

 

在<mvc:annotation-driven>标签下添加 

<mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=utf-8</value>
                        <!--5.JSON(Jackson)序列化输出配置 -->
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
</mvc:message-converters>

2.freemarker搭建

<!--1.Maven依赖-->
        <!--Freemarker-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

applicationContext.xml

<!-- 配置Freemarker模板引擎 -->
    <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
        <property name="templateLoaderPath" value="/ftl"/>
        <property name="freemarkerSettings">
            <props>
                <prop key="defaultEncoding">UTF-8</prop><!--ajax在读取文件时 -->
            </props>
        </property>
    </bean>
    <bean id="ViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
        <property name="contentType" value="text/html;charset=utf-8"/><!--渲染后响应输出时 -->
        <property name="suffix" value=".ftl"/>
    </bean>

3.Mybatis整合

<!--Mybatis整合步骤: 1.引入依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>
        <!-- MyBatis与Spring整合组件 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>
 <!-- MyBatis与Spring的整合配置 -->

applicationContext.xml
    

<!--2.配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/imooc_reader?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="initialSize" value="5"/>
        <property name="maxActive" value="20"/>
    </bean>
    <!-- 3. SqlSessionFactoryBean用于根据配置信息创建SqlSessionFactory,不再需要我们自己编码创建 -->
    <!-- 原生Mybaits与Spring整合
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    -->
    <bean id="sessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mappers/*.xml"/>
        <!-- 5.MyBatis配置文件地址 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    <!--4.配置Mapper扫描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.imooc.reader.mapper"/>
    </bean>

4.junit

<!-- 单元测试依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

5.logback

<!-- logback日志组件 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

创建logback.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{HH:mm:ss} %-5level [%thread] %logger{30} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

6.声明式事务

applicationcontext.xml

<!--声明式事务配置-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>

7.MyBatis-Plus

1.添加依赖

 <!-- MyBatis-Plus依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.3.2</version>
        </dependency>

2.在applicationContext.xml的sessionFactory处替换
   

<!--    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">-->
    <bean id="sessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mappers/*.xml"/>

        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

3.添加分页组件   在mybatis-config.xml中添加

<configuration>
    <plugins>
        <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor"/>
    </plugins>
</configuration>

4.MyBatis-Plus核心注解

@TableName - 将实体类与表名映射
@TableId - 说明对应属性是表的主键
@TableField - 设置属性与列名的对应关系

与字段名相同时或符合驼峰命名规则时(book_name-----bookName)属性可省略

5.使用步骤

1.创建实体类,基于@Table/@TableId/@TableField进行自动映射
2.创建Mapper接口继承BaseMapper,创建Mapper XML
3.开发时注入Mapper对象,通过内置API实现CRUD操作

@TableName("test") //说明实体对应哪一张表
public class Test {
    @TableId(type = IdType.AUTO)
    @TableField("id") //说明属性对应哪个字段
    private Integer id;

    @TableField("content") //如果字段名与属性名相同或者符合驼峰命名转换规则,则TableField可省略
    private String content;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class MyBatisPlusTest {
    @Resource
    private TestMapper testMapper;

    @org.junit.Test
    public void testInsert(){
        Test test = new Test();
        test.setContent("MyBatis Plus测试");
        testMapper.insert(test);
    }

    @org.junit.Test
    public void testUpdate(){
        Test test = testMapper.selectById(9);
        test.setContent("MyBatis Plus测试1");
        testMapper.updateById(test);
    }

    @org.junit.Test
    public void testDelete(){
        testMapper.deleteById(9);
    }
    @org.junit.Test
    public void testSelect(){
        QueryWrapper<Test> queryWrapper = new QueryWrapper<Test>();
        queryWrapper.eq("id", 7);
        queryWrapper.gt("id", 5);
        List<Test> list = testMapper.selectList(queryWrapper);
        System.out.println(list.get(0));
    }
}


6.BaseMapper接口核心AP

方法名 用途
insert(entity) 数据新增,自动生成insert sql,根据@TableId决定注解生成方式
updateById(entity) 根据主键更新对应对象,自动生成update sql
deleteById(id) 根据主键删除数据,自动生成delete sql
selectById(id) 按主键查询对应的实体对象
selectList(queryWrapper) 根据查询生成器(QueryWrapper)的条件自动生成sql查询返回List集合
selectPage(page,queryWrapper) 分页查询方法,自动生成分页limit子句,返回IPage分页对象

 

插件使用

Art-Template的使用

1.下载引用art-template.js

2.定义模板

新建一个script代码块,类型为text/html   <script type="text/html" id="tpl">

3.把需要渲染的前端代码剪切在代码块中

4.替换   语法{{  对象的属性名 }}

template("tpl",book);//具体按个模板id  传入的数据

评分插件raty

1.引入css,js

2.指定资源目录

$.fn.raty.defaults.path="./resources/raty/lib/images";

3.设置只读

 <span class="stars" data-score="{{evaluationScore}}" title="gorgeous"></span>

$(".stars").raty({readOnly:true});

.

Kaptcha

1.导入依赖

        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

2.配置   

<bean id="kaptchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha">
        <property name="config">
            <bean class="com.google.code.kaptcha.util.Config">
                <constructor-arg>
                    <props>
                        <prop key="kaptcha.border">no</prop>
                        <prop key="kaptcha.image.width">120</prop>
                        <prop key="kaptcha.textproducer.font.color">blue</prop>
                        <prop key="kaptcha.textproducer.font.size">40</prop>
                        <prop key="kaptcha.textproducer.char.length">4</prop>
                    </props>
                </constructor-arg>
            </bean>
        </property>
    </bean>

3.controller控制器

package com.imooc.reader.controller;

import com.google.code.kaptcha.Producer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;

@Controller
public class KaptchaController {
    @Resource
    private Producer kaptchaProducer;
    @GetMapping("/verify_code")
    private void careatVerifyCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setDateHeader("Expires",0);//响应过期时间
        //缓存控制    不存储 不缓存  必须重新校验
        response.setHeader("Cache-Control","no-store,no-cache,must-revalidate");
        response.setHeader("Pragma","no-cache");
        response.setContentType("image/png");

        String verifyCode = kaptchaProducer.createText();
        request.getSession().setAttribute("kaptchaVerifyCode",verifyCode);
        System.out.println( request.getSession().getAttribute("kaptchaVerifyCode"));
        BufferedImage image = kaptchaProducer.createImage(verifyCode);
        ServletOutputStream outputStream = response.getOutputStream();
        ImageIO.write(image,"png",outputStream);

        outputStream.flush();
        outputStream.close();


    }
}

前端验证码对比

 function reloadVerifyCode(){
        $("#imgVerifyCode").attr("src", "/verify_code?ts=" + new Date().getTime());
    }
    $("#imgVerifyCode").click(function () {
        reloadVerifyCode();
    });

MD5使用


1.导入依赖
   

     <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.15</version>
        </dependency> 


2.编写工具类

public class MDUtils {
    public static String md5Digest(String source,Integer salt){
        char[] ca=source.toCharArray();
        for (int i = 0; i < ca.length; i++) {
            ca[i]=(char)(ca[i]+salt);
        }
        String target=new String(ca);
        String md5= DigestUtils.md5Hex(target);
        return md5;
    }
}j

3.使用

			Member member=new Member();
        member.setUsername(username);
        member.setNickname(nickname);
        int salt=new Random().nextInt(1000)+1000;
        String md5 = MD5Utils.md5Digest(password, salt);
        member.setPassword(md5);
        member.setSalt(salt);

 

Spring-Task

1.概念:

定时任务模块:周期后台自动执行任务,可利用Cron表达式实现灵活的定时处理

2.Cron表达式:


秒 0,30      每分的0和30秒
分 0-5       每时的前5分钟
*   所以
?   星期和日 互斥

3.使用步骤
开启注解

 <task:annotation-driven/>

编写任务类

@Component
public class ComputerTask {
    @Resource
    private BookService bookService;
    //调度
    @Scheduled(cron = "0 * * * * ?")
    public void updateEvaluation(){
        bookService.updateEvaluation();
        System.out.println("已更新所以评分");
    }
}

wangeditor

官网:https://www.wangeditor.com/

1.概念Typescript 开发的 Web 富文本编辑器, 轻量、简洁、易用、开源免费

2.使用步骤

//初始化
    var E=window.wangEditor;
    var editor=new E("#divEditor");
    editor.create()
    //读取
    document.getElementById("btnRead").onclick=function(){
        var content = editor.txt.html();
        alert(content);
    }
    //写入
    document.getElementById("btnWrite").onclick=function(){
        var html="<li style='color:red'>张三<b>德玛</b> </li>";
        editor.txt.html(html);
    }

jsoup  

jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,
可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据


1.导入依赖

        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.14.2</version>
        </dependency>

 2.使用

Document doc = Jsoup.parse(html数据);
String cover = doc.select("img").first().attr("src");//标签属性
rawBook.setCover(cover);

springMVC上传文件


1.导入依赖

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>

2.配置

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="utf-8"/>
    </bean>


3.上传

    @PostMapping("/upload")
    @ResponseBody
    public Map upload(@RequestParam("img") MultipartFile file, HttpServletRequest request) throws IOException {//img是前端界面设置的文件名
        String uploadPath=request.getServletContext().getResource("/").getPath()+"/upload/";//运行时根路径
        String fileName=new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));

        file.transferTo(new File(uploadPath+fileName+suffix));//另存为
        Map result=new HashMap();
        result.put("errno",0);
        result.put("data",new String[]{"/upload/"+fileName+suffix});
        return result;
    }

 

实现步骤

导入数据库和所有静态资源

https://files.cnblogs.com/files/blogs/665099/%E8%AE%AD%E7%BB%83%E7%B4%A0%E6%9D%90.rar

点击查看代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.imooc</groupId>
    <artifactId>imooc-reader</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
<!--        spring-mvc-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.9</version>
        </dependency>
<!--        jackson-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.13.0-rc2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.13.0-rc2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.0-rc2</version>
        </dependency>
        <!--Freemarker-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.3.9</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.9</version>
        </dependency>
<!--        mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>
<!--        事务管理-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.3.9</version>
        </dependency>
<!--测试 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.9</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

<!--        mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.3.2</version>
        </dependency>
<!--        kaptcha-->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>
<!--        MD5-->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.15</version>
        </dependency>
<!--        文件上传下载-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>
<!--        jsoup-->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.14.2</version>
        </dependency>
    </dependencies>


</project>

web.xml

点击查看代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

 mybatis.xml

点击查看代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <plugins>
        <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor"/>
    </plugins>
</configuration>

applicationContext.xml

点击查看代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/task
            http://www.springframework.org/schema/task/spring-task.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.imooc.reader"/>
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=utf-8</value>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <mvc:default-servlet-handler/>
<!--    FreeMarkerConfigurer视图解析器配置-->
    <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
        <property name="templateLoaderPath" value="/ftl"/>
        <property name="freemarkerSettings">
            <props>
                <prop key="defaultEncoding">UTF-8</prop><!--ajax在读取文件时 -->
            </props>
        </property>
    </bean>
    <bean id="ViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
        <property name="contentType" value="text/html;charset=utf-8"/>
        <property name="suffix" value=".ftl"/>
    </bean>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/imooc_reader?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="initialSize" value="5"/>
        <property name="maxActive" value="20"/>
    </bean>
    <!-- SqlSessionFactoryBean用于根据配置信息创建SqlSessionFactory,不再需要我们自己编码创建 -->
    <!-- 原生Mybaits与Spring整合
    <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    -->
    <bean id="sessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mappers/*.xml"/>
        <!-- MyBatis配置文件地址 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
    <!--配置Mapper扫描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.imooc.reader.mapper"/>
    </bean>
    <!--声明式事务配置-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

一.显示图书类别

1.创建实体类  写注解@TableName

点击查看代码
package com.imooc.reader.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

@TableName("category")
public class Category {
    @TableId(type = IdType.AUTO)
    private Integer categoryId;
    private String categoryName;

    public Integer getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Integer categoryId) {
        this.categoryId = categoryId;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }

    @Override
    public String toString() {
        return "Category{" +
                "categoryId=" + categoryId +
                ", categoryName='" + categoryName + '\'' +
                '}';
    }
}


2.编写接口Mapper

点击查看代码
public interface CategoryMapper extends BaseMapper<Category> {

}


3.写Mapper.xml   写namespace

<?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.imooc.reader.mapper.CategoryMapper">

</mapper>


4.编写service  接口实现类   mapper.selsectList

public interface CategoryService {
    public List<Category> selectCategoryList();

}


package com.imooc.reader.service.Impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.imooc.reader.entity.Category;
import com.imooc.reader.mapper.CategoryMapper;
import com.imooc.reader.service.CategoryService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service("CategoryService")
public class CategoryServiceImp implements CategoryService {
    @Resource
    private CategoryMapper categoryMapper;
    public List<Category> selectCategoryList() {
        QueryWrapper<Category> categoryQueryWrapper = new QueryWrapper<Category>();
        List<Category> categoryList = categoryMapper.selectList(categoryQueryWrapper);
        return categoryList;
    }
}

5.测试  Runwith  ContextConfiguration  声明接口  调用

点击查看代码
package com.imooc.reader.service.Impl;

import com.imooc.reader.entity.Category;
import com.imooc.reader.service.CategoryService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

import java.util.List;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class CategoryServiceImpTest {


    @Resource
    private CategoryService categoryService;
    @Test
    public void selectCategoryList() {
        List<Category> categories = categoryService.selectCategoryList();
        System.out.println(categories);
    }
}

6.编写controller

package com.imooc.reader.controller;

import com.imooc.reader.entity.Category;
import com.imooc.reader.service.CategoryService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import java.util.List;

@Controller
public class CategoryController {
    @Resource
    private CategoryService categoryService;

    @GetMapping("/")
    @ResponseBody
    public ModelAndView showIndex(){
        ModelAndView modelAndView=new ModelAndView("/index");
        List<Category> categoryList = categoryService.selectCategoryList();
        modelAndView.addObject("categoryList",categoryList);
        return modelAndView;

    }
}

7.前端  <#List    <if     ${类目.属性}

            <#list categoryList as category>
                <a style="cursor: pointer" data-category="${category.categoryId}" class="text-black-50 font-weight-bold category">${category.categoryName}</a>
                <#if category_has_next >
                    |
                </#if>
            </#list>

二.完成图书分页查询

1.创建实体类  写注解@TableName

点击查看代码
package com.imooc.reader.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

@TableName("book")
public class Book {
    @TableId(type = IdType.AUTO)
    private Long bookId;
    private String bookName;
    private String subTitle;
    private String author;
    private String cover;
    private String description;
    private Long categoryId;
    private Float evaluationScore;
    private Integer evaluationQuantity;

    public Long getBookId() {
        return bookId;
    }

    public void setBookId(Long bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public String getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getCover() {
        return cover;
    }

    public void setCover(String cover) {
        this.cover = cover;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Long getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Long categoryId) {
        this.categoryId = categoryId;
    }

    public Float getEvaluationScore() {
        return evaluationScore;
    }

    public void setEvaluationScore(Float evaluationScore) {
        this.evaluationScore = evaluationScore;
    }

    public Integer getEvaluationQuantity() {
        return evaluationQuantity;
    }

    public void setEvaluationQuantity(Integer evaluationQuantity) {
        this.evaluationQuantity = evaluationQuantity;
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                ", subTitle='" + subTitle + '\'' +
                ", author='" + author + '\'' +
                ", cover='" + cover + '\'' +
                ", description='" + description + '\'' +
                ", categoryId=" + categoryId +
                ", evaluationScore=" + evaluationScore +
                ", evaluationQuantity=" + evaluationQuantity +
                '}';
    }
}


2.编写接口Mapper

public interface BookMapper extends BaseMapper<Book> {
}


3.写Mapper.xml   写namespace

点击查看代码
<?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.imooc.reader.mapper.BookMapper">

</mapper>


4.编写service  接口实现类   mapper.selsectPage

点击查看代码
public interface BookService {
    public IPage<Book> selectBookPage(Long categoryId,String order,Integer page,Integer rows);
}

package com.imooc.reader.service.Impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.imooc.reader.entity.Book;
import com.imooc.reader.mapper.BookMapper;
import com.imooc.reader.service.BookService;
import org.springframework.stereotype.Service;


import javax.annotation.Resource;
@Service("BookService")
public class BookServiceImp implements BookService {
    @Resource
    private BookMapper bookMapper;
    public IPage<Book> selectBookPage(Long categoryId,String order,Integer page, Integer rows) {
        if(page==null){
            page=1;
        }
        if(rows==null){
            page=10;
        }

        Page<Book> bookpage=new Page<Book>(page,rows);
        QueryWrapper<Book> queryWrapper = new QueryWrapper<Book>();
        Page<Book> bookPage = bookMapper.selectPage(bookpage, queryWrapper);
        return bookPage;
    }
}

5.测试  Runwith  ContextConfiguration  声明接口  调用

点击查看代码
package com.imooc.reader.service.Impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.imooc.reader.entity.Book;
import com.imooc.reader.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

import java.util.List;

import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class BookServiceImpTest {

    @Resource
    private BookService bookService;
    @Test
    public void selectBook() {
        IPage<Book> bookIPage = bookService.selectBookPage(null,null,2,10);
        List<Book> bookList = bookIPage.getRecords();
        for (Book book : bookList) {
            System.out.println(book.getBookId()+"-----"+book.getBookName());
        }
    }
}

6.编写controller    @requestBody返回json数据

点击查看代码
package com.imooc.reader.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.imooc.reader.entity.Book;
import com.imooc.reader.service.BookService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;

@Controller
public class BookController {
    @Resource
    private BookService bookService;

    @GetMapping("/books")
    @ResponseBody
    public IPage<Book>  showBookCategory(Integer p,Long categoryId,String order){
        IPage<Book> bookIPage = bookService.selectBookPage(categoryId, order, p, 10);
        return bookIPage;

    }
}

7.前端  raty初始化  art-template渲染 ajax提交

点击查看代码
<script>
        $.fn.raty.defaults.path="./resources/raty/lib/images";
        function loadMore(isRest){
            if(isRest==true){
                $("#nextPage").val(1);
                $("#bookList").html("") ;
            }
            var nextPage = $("#nextPage").val();
            var categoryId = $("#categoryId").val();
            var order = $("#order").val();
            $.ajax({
                url:"/books",
                type:"get",
                data:{p:nextPage,"categoryId":categoryId,"order":order},
                dataType:"json",
                success:function (json) {
                    console.log(json);
                    var records = json.records;
                    for (let i = 0; i < records.length; i++) {
                        var book=records[i];
                        var html=template("art_template",book);
                        $("#bookList").append(html)
                        console.log(book);
                        $(".stars").raty({readOnly:true});
                        if(json.current<json.pages){
                            $("#nextPage").val(parseInt(json.current)+1);
                            $("#btnMore").show();
                            $("#divNoMore").hide();
                        }
                        else {
                            $("#btnMore").hide();
                            $("#divNoMore").show();
                        }
                    }
                }
            })

        }
        $(function () {
            loadMore(true);
        })
        $(function () {
            $("#btnMore").click(function () {
                loadMore();
            })

            $(".category").click(function () {
                $(".category").removeClass("highlight");
                $(".category").addClass("text-black-50");
                $(this).addClass("highlight");
                var categoryId = $(this).data("category");
                $("#categoryId").val(categoryId)
                loadMore(true);
            })
            $(".order").click(function () {
                $(".order").removeClass("highlight");
                $(".order").addClass("text-black-50");
                $(this).addClass("highlight");
                var order = $(this).data("order");
                $("#order").val(order)
                loadMore(true);
            })
        })

    </script>
    <script type="text/html" id="art_template">
        <a href="/book/{{bookId}}" style="color: inherit">
            <div class="row mt-2 book">
                <div class="col-4 mb-2 pr-2">
                    <img class="img-fluid" src="/images/2.jpg">
                </div>
                <div class="col-8  mb-2 pl-0">
                    <h5 class="text-truncate">{{bookName}}</h5>

                    <div class="mb-2 bg-light small  p-2 w-100 text-truncate">{{author}}</div>


                    <div class="mb-2 w-100">{{subTitle}}</div>

                    <p>
                        <span class="stars" data-score="{{evaluationScore}}" title="gorgeous"></span>
                        <span class="mt-2 ml-2">{{evaluationScore}}</span>
                        <span class="mt-2 ml-2">{{evaluationQuantity}}</span>
                    </p>
                </div>
            </div>
        </a>
    </script>

 

三.图书详情页

1.前端a标签跳转路径后面包含bookId属性

在bookservice和实现类添加按id查询

点击查看代码
public Book selectBookById(Long bookId);


    public Book selectBookById(Long bookId) {
        Book book = bookMapper.selectById(bookId);
        return book;
    }

2.conrtoller

点击查看代码
    @GetMapping("/book/{id}")
    public ModelAndView showDetail(@PathVariable("id") Long bookId){
        Book book = bookService.selectBookById(bookId);
        ModelAndView modelAndView=new ModelAndView("/detail");
        modelAndView.addObject("book",book);
        return  modelAndView;

    }

3.前端显示

点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>${book.bookName}</title>
    <meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0,user-scalable=no">
    <link rel="stylesheet" href="/resources/bootstrap/bootstrap.css">
    <link rel="stylesheet" href="/resources/raty/lib/jquery.raty.css">
    <script src="/resources/jquery.3.3.1.min.js"></script>
    <script src="/resources/bootstrap/bootstrap.min.js"></script>
    <script src="/resources/art-template.js"></script>
    <script src="/resources/raty/lib/jquery.raty.js"></script>
    <style>
        .container {
            padding: 0px;
            margin: 0px;
        }

        .row {
            padding: 0px;
            margin: 0px;
        }

        .alert {
            padding-left: 0.5rem;
            padding-right: 0.5rem;
        }

        .col- * {
            padding: 0px;
        }

        .description p {
            text-indent: 2em;
        }

        .description img {
            width: 100%;
        }

        .highlight {
            background-color: lightskyblue !important;
        }

    </style>
    <script>
        $.fn.raty.defaults.path = '/resources/raty/lib/images';
        $(function () {
            $(".stars").raty({readOnly: true});
        })
        $(function () {
        <#if memberReadState??>
            $("*[data-read-state='${memberReadState.readState}']").addClass("highlight");
        </#if>
            <#if !loginMember??>
            $("*[data-read-state],#btnEvaluation,*[data-evaluation-id]").click(function () {
                $("#exampleModalCenter").modal("show");
            })
            </#if>
            <#if loginMember??>
            $("*[data-read-state]").click(function () {
                let readState = $(this).data("read-state");
                $.post("/update_read_state",{
                    memberId:${loginMember.memberId},
                    bookId:${book.bookId},
                    readState:readState
                },function (json) {
                    if(json.code=="0"){
                        $("*[data-read-state]").removeClass("highlight");
                        $("*[data-read-state='" +readState +"']").addClass("highlight");
                    }
                },"json")
            })
            $("#btnEvaluation").click(function () {
                $("#score").raty({})
                $("#dlgEvaluation").modal("show")
            })
            $("#btnSubmit").click(function () {
                var score=$("#score").raty("score");
                var content=$("#content").val();
                if(score==0||$.trim(content)==""){
                    return;
                }
                $.post("/evaluate",{
                    score:score,
                    bookId:${book.bookId},
                    memberId:${loginMember.memberId},
                    content:content
                },function (json) {
                    if(json.code=="0"){
                        window.location.reload()
                    }
                },"json")
            })

            $("*[data-evaluation-id]").click(function () {
                var evaluationId= $(this).data("evaluation-id");
                $.post("/enjoy",{evaluationId:evaluationId},function (json) {
                    if(json.code=="0"){
                        $("*[data-evaluation-id='"+evaluationId +"'] span").text(json.evaluate.enjoy);
                    }
                },"json")

            })


            </#if>

        })


    </script>
</head>
<body>
<!--<div style="width: 375px;margin-left: auto;margin-right: auto;">-->
<div class="container ">
    <nav class="navbar navbar-light bg-white shadow mr-auto">
        <ul class="nav">
            <li class="nav-item">
                <a href="/">
                    <img src="https://m.imooc.com/static/wap/static/common/img/logo2.png"  class="mt-1"
                         style="width: 100px">
                </a>
            </li>

        </ul>
    </nav>


    <div class="container mt-2 p-2 m-0" style="background-color:rgb(127, 125, 121)">
        <div class="row">
            <div class="col-4 mb-2 pl-0 pr-0">
                <img style="width: 110px;height: 160px"
                     src="/images/1.jpg">
            </div>
            <div class="col-8 pt-2 mb-2 pl-0">
                <h6 class="text-white">${book.bookName}</h6>
                <div class="p-1 alert alert-warning small" role="alert">
                    ${book.subTitle}
                </div>
                <p class="mb-1">
                    <span class="text-white-50 small">${book.author}</span>
                </p>
                <div class="row pl-1 pr-2">
                    <div class="col-6 p-1">
                        <button type="button" data-read-state="1" class="btn btn-light btn-sm w-100">
                            <img style="width: 1rem;" class="mr-1"
                                 src="https://img3.doubanio.com/f/talion/cf2ab22e9cbc28a2c43de53e39fce7fbc93131d1/pics/card/ic_mark_todo_s.png"/>想看
                        </button>
                    </div>
                    <div class="col-6 p-1">
                        <button type="button" data-read-state="2" class="btn btn-light btn-sm  w-100">
                            <img style="width: 1rem;" class="mr-1"
                                 src="https://img3.doubanio.com/f/talion/78fc5f5f93ba22451fd7ab36836006cb9cc476ea/pics/card/ic_mark_done_s.png"/>看过
                        </button>
                    </div>
                </div>
            </div>
        </div>
        <div class="row" style="background-color: rgba(0,0,0,0.1);">
            <div class="col-2"><h2 class="text-white">${book.evaluationScore}</h2></div>
            <div class="col-5 pt-2">
                <span class="stars" data-score="${book.evaluationScore}"></span>
            </div>
            <div class="col-5  pt-2"><h5 class="text-white">${book.evaluationQuantity}人已评</h5></div>
        </div>
    </div>
    <div class="row p-2 description">
        ${book.description}
    </div>


    <div class="alert alert-primary w-100 mt-2" role="alert">短评
        <button type="button" id="btnEvaluation" class="btn btn-success btn-sm text-white float-right"
                style="margin-top: -3px;">
            写短评
        </button>
    </div>
    <div class="reply pl-2 pr-2">
       

</div>
</div>


<!-- Modal -->
<div class="modal fade" id="exampleModalCenter" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle"
     aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered" role="document">
        <div class="modal-content">
            <div class="modal-body">
                您需要登录才可以操作哦~
            </div>
            <div class="modal-footer">
                <a href="/login.html" type="button" class="btn btn-primary">去登录</a>
            </div>
        </div>
    </div>
</div>

<!-- Modal -->
<div class="modal fade" id="dlgEvaluation" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle"
     aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered" role="document">
        <div class="modal-content">
            <div class="modal-body">
                <h6>${book.bookName}</h6>
                <form id="frmEvaluation">
                    <div class="input-group  mt-2 ">
                        <span id="score"></span>
                    </div>
                    <div class="input-group  mt-2 ">
                        <input type="text" id="content" name="content" class="form-control p-4" placeholder="这里输入短评">
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" id="btnSubmit" class="btn btn-primary">提交</button>
            </div>
        </div>
    </div>
</div>

</body>
</html>

四.显示评论列表

1.实体类Evaluation Member

前台页面评论需要会员表的昵称,所以需要Member表的会员数据,Evaluation表中有会员id,通过会员id可以查到会员昵称

点击查看代码
package com.imooc.reader.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.util.Date;
@TableName("evaluation")
public class Evaluation {
    @TableId(type = IdType.AUTO)
    private Long  evaluationId;
    private String content;
    private Integer score;
    private Date createTime;
    private Long memberId;
    private Long bookId;
    private Integer enjoy;
    private String state;
    private String disableReason;
    private Date disableTime;

    @TableField(exist = false)
    private Book book;

    public Member getMember() {
        return member;
    }

    public void setMember(Member member) {
        this.member = member;
    }

    @TableField(exist = false)
    private Member member;
    
    @TableField(exist = false)
    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }

    public Long getEvaluationId() {
        return evaluationId;
    }

    public void setEvaluationId(Long evaluationId) {
        this.evaluationId = evaluationId;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Integer getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Long getMemberId() {
        return memberId;
    }

    public void setMemberId(Long memberId) {
        this.memberId = memberId;
    }

    public Long getBookId() {
        return bookId;
    }

    public void setBookId(Long bookId) {
        this.bookId = bookId;
    }

    public Integer getEnjoy() {
        return enjoy;
    }

    public void setEnjoy(Integer enjoy) {
        this.enjoy = enjoy;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getDisableReason() {
        return disableReason;
    }

    public void setDisableReason(String disableReason) {
        this.disableReason = disableReason;
    }

    public Date getDisableTime() {
        return disableTime;
    }

    public void setDisableTime(Date disableTime) {
        this.disableTime = disableTime;
    }

    @Override
    public String toString() {
        return "Evaluation{" +
                "evaluationId=" + evaluationId +
                ", content='" + content + '\'' +
                ", score=" + score +
                ", createTime=" + createTime +
                ", memberId=" + memberId +
                ", bookId=" + bookId +
                ", enjoy=" + enjoy +
                ", state='" + state + '\'' +
                ", disableReason='" + disableReason + '\'' +
                ", disableTime=" + disableTime +
                '}';
    }
}
点击查看代码
package com.imooc.reader.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.util.Date;

@TableName("`member`")
public class Member {
    @TableId(type = IdType.AUTO)
    private Long memberId;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    private String username;
    private String password;
    private  Integer salt;
    private String nickname;
    private Date createTime;

    public Long getMemberId() {
        return memberId;
    }

    public void setMemberId(Long memberId) {
        this.memberId = memberId;
    }



    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getSalt() {
        return salt;
    }

    public void setSalt(Integer salt) {
        this.salt = salt;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

2.创建mapper接口和mapper.xml

点击查看代码
public interface EvaluationMapper extends BaseMapper<Evaluation> {
}
点击查看代码
public interface MemberMapper extends BaseMapper<Member> {
}
点击查看代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mpper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.imooc.reader.mapper.EvaluationMapper">

</mapper>
<?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.imooc.reader.mapper.BookMapper">

</mapper>

3.接口实现类

点击查看代码
public interface EvaluationService {
    public List<Evaluation> selectById(Long bookId);
}
点击查看代码
package com.imooc.reader.service.Impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.imooc.reader.entity.Book;
import com.imooc.reader.entity.Evaluation;
import com.imooc.reader.entity.Member;
import com.imooc.reader.mapper.BookMapper;
import com.imooc.reader.mapper.EvaluationMapper;
import com.imooc.reader.mapper.MemberMapper;
import com.imooc.reader.service.EvaluationService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
@Service("EvaluationService")
@Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly = true)
public class EvaluationServiceImp implements EvaluationService {
    @Resource
    private EvaluationMapper evaluationMapper;
    @Resource
    private MemberMapper memberMapper;
    @Resource
    private BookMapper bookMapper;
    public List<Evaluation> selectById(Long bookId) {
        Book book = bookMapper.selectById(bookId);

        QueryWrapper<Evaluation> QueryWrapper = new QueryWrapper<Evaluation>();
        QueryWrapper.eq("book_id",bookId);
        QueryWrapper.eq("state","enable");
        QueryWrapper.orderByDesc("create_time");
        List<Evaluation> evaluationList = evaluationMapper.selectList(QueryWrapper);
        for (Evaluation eva:evaluationList) {
            Long memberId = eva.getMemberId();
            Member member = memberMapper.selectById(memberId);
            eva.setMember(member);
            eva.setBook(book);
        }

        return evaluationList;
    }
}

4.前端

点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>${book.bookName}</title>
    <meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0,user-scalable=no">
    <link rel="stylesheet" href="/resources/bootstrap/bootstrap.css">
    <link rel="stylesheet" href="/resources/raty/lib/jquery.raty.css">
    <script src="/resources/jquery.3.3.1.min.js"></script>
    <script src="/resources/bootstrap/bootstrap.min.js"></script>
    <script src="/resources/art-template.js"></script>
    <script src="/resources/raty/lib/jquery.raty.js"></script>
    <style>
        .container {
            padding: 0px;
            margin: 0px;
        }

        .row {
            padding: 0px;
            margin: 0px;
        }

        .alert {
            padding-left: 0.5rem;
            padding-right: 0.5rem;
        }

        .col- * {
            padding: 0px;
        }

        .description p {
            text-indent: 2em;
        }

        .description img {
            width: 100%;
        }

        .highlight {
            background-color: lightskyblue !important;
        }

    </style>
    <script>
        $.fn.raty.defaults.path = '/resources/raty/lib/images';
        $(function () {
            $(".stars").raty({readOnly: true});
        })
        $(function () {
            <#if memberReadState??>
                $("*[data-read-state='${memberReadState.readState}']").addClass("highlight");
            </#if>
            <#if !loginMember??>
            $("*[data-read-state],#btnEvaluation,*[data-evaluation-id]").click(function () {
                $("#exampleModalCenter").modal("show");
            })
            </#if>
            <#if loginMember??>
            $("*[data-read-state]").click(function () {
                let readState = $(this).data("read-state");
                $.post("/update_read_state",{
                    memberId:${loginMember.memberId},
                    bookId:${book.bookId},
                    readState:readState
                },function (json) {
                    if(json.code=="0"){
                        $("*[data-read-state]").removeClass("highlight");
                        $("*[data-read-state='" +readState +"']").addClass("highlight");
                    }
                },"json")
            })
            $("#btnEvaluation").click(function () {
                $("#score").raty({})
                $("#dlgEvaluation").modal("show")
            })
            $("#btnSubmit").click(function () {
                var score=$("#score").raty("score");
                var content=$("#content").val();
                if(score==0||$.trim(content)==""){
                    return;
                }
                $.post("/evaluate",{
                    score:score,
                    bookId:${book.bookId},
                    memberId:${loginMember.memberId},
                    content:content
                },function (json) {
                    if(json.code=="0"){
                        window.location.reload()
                    }
                },"json")
            })

            $("*[data-evaluation-id]").click(function () {
                var evaluationId= $(this).data("evaluation-id");
                $.post("/enjoy",{evaluationId:evaluationId},function (json) {
                    if(json.code=="0"){
                        $("*[data-evaluation-id='"+evaluationId +"'] span").text(json.evaluate.enjoy);
                    }
                },"json")

            })


            </#if>

        })


    </script>
</head>
<body>
<!--<div style="width: 375px;margin-left: auto;margin-right: auto;">-->
<div class="container ">
    <nav class="navbar navbar-light bg-white shadow mr-auto">
        <ul class="nav">
            <li class="nav-item">
                <a href="/">
                    <img src="https://m.imooc.com/static/wap/static/common/img/logo2.png"  class="mt-1"
                         style="width: 100px">
                </a>
            </li>

        </ul>
    </nav>


    <div class="container mt-2 p-2 m-0" style="background-color:rgb(127, 125, 121)">
        <div class="row">
            <div class="col-4 mb-2 pl-0 pr-0">
                <img style="width: 110px;height: 160px"
                     src="/images/1.jpg">
            </div>
            <div class="col-8 pt-2 mb-2 pl-0">
                <h6 class="text-white">${book.bookName}</h6>
                <div class="p-1 alert alert-warning small" role="alert">
                    ${book.subTitle}
                </div>
                <p class="mb-1">
                    <span class="text-white-50 small">${book.author}</span>
                </p>
                <div class="row pl-1 pr-2">
                    <div class="col-6 p-1">
                        <button type="button" data-read-state="1" class="btn btn-light btn-sm w-100">
                            <img style="width: 1rem;" class="mr-1"
                                 src="https://img3.doubanio.com/f/talion/cf2ab22e9cbc28a2c43de53e39fce7fbc93131d1/pics/card/ic_mark_todo_s.png"/>想看
                        </button>
                    </div>
                    <div class="col-6 p-1">
                        <button type="button" data-read-state="2" class="btn btn-light btn-sm  w-100">
                            <img style="width: 1rem;" class="mr-1"
                                 src="https://img3.doubanio.com/f/talion/78fc5f5f93ba22451fd7ab36836006cb9cc476ea/pics/card/ic_mark_done_s.png"/>看过
                        </button>
                    </div>
                </div>
            </div>
        </div>
        <div class="row" style="background-color: rgba(0,0,0,0.1);">
            <div class="col-2"><h2 class="text-white">${book.evaluationScore}</h2></div>
            <div class="col-5 pt-2">
                <span class="stars" data-score="${book.evaluationScore}"></span>
            </div>
            <div class="col-5  pt-2"><h5 class="text-white">${book.evaluationQuantity}人已评</h5></div>
        </div>
    </div>
    <div class="row p-2 description">
        ${book.description}
    </div>


    <div class="alert alert-primary w-100 mt-2" role="alert">短评
        <button type="button" id="btnEvaluation" class="btn btn-success btn-sm text-white float-right"
                style="margin-top: -3px;">
            写短评
        </button>
    </div>
    <div class="reply pl-2 pr-2">
        <#list evaluationList as evaluation>
            <div>
                <div>
                    <span class="pt-1 small text-black-50 mr-2">${evaluation.createTime?string('MM-dd')}</span>
                    <span class="mr-2 small pt-1">${evaluation.member.nickname}</span>
                    <span class="stars mr-2" data-score="${evaluation.score}"></span>

                    <button type="button" data-evaluation-id="${evaluation.evaluationId}"
                            class="btn btn-success btn-sm text-white float-right" style="margin-top: -3px;">
                        <img style="width: 24px;margin-top: -5px;" class="mr-1"
                             src="https://img3.doubanio.com/f/talion/7a0756b3b6e67b59ea88653bc0cfa14f61ff219d/pics/card/ic_like_gray.svg"/>
                        <span>${evaluation.enjoy}</span>
                    </button>
                </div>

                <div class="row mt-2 small mb-3">
                    ${evaluation.content}
                </div>
                <hr/>
            </div>
        </#list>


    </div>
</div>


<!-- Modal -->
<div class="modal fade" id="exampleModalCenter" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle"
     aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered" role="document">
        <div class="modal-content">
            <div class="modal-body">
                您需要登录才可以操作哦~
            </div>
            <div class="modal-footer">
                <a href="/login.html" type="button" class="btn btn-primary">去登录</a>
            </div>
        </div>
    </div>
</div>

<!-- Modal -->
<div class="modal fade" id="dlgEvaluation" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle"
     aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered" role="document">
        <div class="modal-content">
            <div class="modal-body">
                <h6>${book.bookName}</h6>
                <form id="frmEvaluation">
                    <div class="input-group  mt-2 ">
                        <span id="score"></span>
                    </div>
                    <div class="input-group  mt-2 ">
                        <input type="text" id="content" name="content" class="form-control p-4" placeholder="这里输入短评">
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" id="btnSubmit" class="btn btn-primary">提交</button>
            </div>
        </div>
    </div>
</div>

</body>
</html>

五.会员注册   Member

1.Kaptcha验证码控制器

点击查看代码
package com.imooc.reader.controller;

import com.google.code.kaptcha.Producer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;

@Controller
public class KaptchaController {
    @Resource
    private Producer kaptchaProducer;
    @GetMapping("/verify_code")
    private void careatVerifyCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setDateHeader("Expires",0);//响应过期时间
        //缓存控制    不存储 不缓存  必须重新校验
        response.setHeader("Cache-Control","no-store,no-cache,must-revalidate");
        response.setHeader("Pragma","no-cache");
        response.setContentType("image/png");

        String verifyCode = kaptchaProducer.createText();
        request.getSession().setAttribute("kaptchaVerifyCode",verifyCode);
        System.out.println( request.getSession().getAttribute("kaptchaVerifyCode"));
        BufferedImage image = kaptchaProducer.createImage(verifyCode);
        ServletOutputStream outputStream = response.getOutputStream();
        ImageIO.write(image,"png",outputStream);

        outputStream.flush();
        outputStream.close();


    }
}

2.前端 ajax提交验证码数据

点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>会员注册-慕课书评网</title>
    <meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0,user-scalable=no">
	
	<link rel="stylesheet" href="./resources/bootstrap/bootstrap.css">
    <link rel="stylesheet" href="./resources/raty/lib/jquery.raty.css">
    <script src="./resources/jquery.3.3.1.min.js"></script>
    <script src="./resources/bootstrap/bootstrap.min.js"></script>
    <style>
        .container {
            padding: 0px;
            margin: 0px;
        }

        .row {
            padding: 0px;
            margin: 0px;
        }

        .col- * {
            padding: 0px;
        }

        .description p {
            text-indent: 2em;
        }

        .description img {
            width: 100%;
        }

    </style>

</head>
<body>
<!--<div style="width: 375px;margin-left: auto;margin-right: auto;">-->
<div class="container ">
    <nav class="navbar navbar-light bg-white shadow">
        <ul class="nav">
            <li class="nav-item">
                <a href="/">
                    <img src="https://m.imooc.com/static/wap/static/common/img/logo2.png" class="mt-1"
                         style="width: 100px">
                </a>
            </li>
        </ul>
    </nav>


    <div class="container mt-2 p-2 m-0">
        <form id="frmLogin">
            <div class="passport bg-white">
                <h4 class="float-left">会员注册</h4>
                <h6 class="float-right pt-2"><a href="/login.html">会员登录</a></h6>
                <div class="clearfix"></div>
                <div class="alert d-none mt-2" id="tips" role="alert">

                </div>

                <div class="input-group  mt-2 ">
                    <input type="text" id="username" name="username" class="form-control p-4" placeholder="请输入用户名">
                </div>

                <div class="input-group  mt-4 ">
                    <input id="password" name="password" class="form-control p-4" placeholder="请输入密码" type="password">
                </div>

                <div class="input-group  mt-4 ">
                    <input type="text" id="nickname" name="nickname" class="form-control p-4" placeholder="请输入昵称"
                    >
                </div>

                <div class="input-group mt-4 ">
                    <div class="col-5 p-0">
                        <input type="text" id="verifyCode" name="vc" class="form-control p-4" placeholder="验证码">
                    </div>
                    <div class="col-4 p-0 pl-2 pt-0">
						<!-- 验证码图片 -->
                        <img id="imgVerifyCode" src="/verify_code"
                             style="width: 120px;height:50px;cursor: pointer">
                    </div>

                </div>

                <a id="btnSubmit" class="btn btn-success  btn-block mt-4 text-white pt-3 pb-3">注&nbsp;&nbsp;&nbsp;&nbsp;册</a>
            </div>
        </form>

    </div>
</div>


<!-- Modal -->
<div class="modal fade" id="exampleModalCenter" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered" role="document">
        <div class="modal-content">
            <div class="modal-body">
                您已注册成功
            </div>
            <div class="modal-footer">
                <a href="/login.html" type="button" class="btn btn-primary">去登录</a>
            </div>
        </div>
    </div>
</div>


<script>
	//控制错误信息的显示与隐藏
    function showTips(isShow, css, text) {
        if (isShow) {
            $("#tips").removeClass("d-none")
            $("#tips").hide();
            $("#tips").addClass(css);
            $("#tips").text(text);
            $("#tips").fadeIn(200);
        } else {
            $("#tips").text("");
            $("#tips").fadeOut(200);
            $("#tips").removeClass();
            $("#tips").addClass("alert")
        }
    }
	//重新发送请求,刷新验证码
    function reloadVerifyCode(){
        //请在这里实现刷新验证码
        $("#imgVerifyCode").attr("src","/verify_code?ts="+new Date().getTime())
    }
	
	//点击验证码图片刷新验证码
    $("#imgVerifyCode").click(function () {
        reloadVerifyCode();
    });
	
	//点击提交按钮,向/registe发起ajax请求
	//提交请求包含四个参数
	//vc:前台输入验证码  username:用户名 password:密码 nickname:昵称
    $("#btnSubmit").click(function () {
		//表单校验
        var username = $.trim($("#username").val());
        var regex = /^.{6,10}$/;
        if (!regex.test(username)) {
            showTips(true, "alert-danger", "用户名请输入正确格式(6-10位)");
            return;
        } else {
            showTips(false);
        }

        var password = $.trim($("#password").val());

        if (!regex.test(password)) {
            showTips(true, "alert-danger", "密码请输入正确格式(6-10位)");
            return;
        } else {
            showTips(false);
        }

        $btnReg = $(this);

        $btnReg.text("正在处理...");
        $btnReg.attr("disabled", "disabled");
		
		//发送ajax请求
        $.ajax({
            url: "/registe",
            type: "post",
            dataType: "json",
            data: $("#frmLogin").serialize(),
            success: function (data) {
				//结果处理,根据服务器返回code判断服务器处理状态
				//服务器要求返回JSON格式:
				//{"code":"0","msg":"处理消息"}
                console.info("服务器响应:" , data);
                if (data.code == "0") {
					//显示注册成功对话框
                    $("#exampleModalCenter").modal({});
                    $("#exampleModalCenter").modal("show");
                } else {
					//服务器校验异常,提示错误信息
                    showTips(true, "alert-danger", data.msg);
                    reloadVerifyCode();
                    $btnReg.text("注    册");
                    $btnReg.removeAttr("disabled");
                }
            }
        });
        return false;
    });


</script>
</body>
</html>

前面已经创建了Member实体类 mapper 

3.创建异常类

点击查看代码
package com.imooc.reader.service.exception;

/**
 * BussinessException业务逻辑异常
 */
public class BussinessException extends RuntimeException{
    private String code;
    private String msg;
    public BussinessException(String code , String msg){
        super(msg);
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

4.创建MD5Utils

点击查看代码
package com.imooc.reader.utils;

import org.apache.commons.codec.digest.DigestUtils;

public class MD5Utils {
    public static String md5Digest(String source , Integer salt){
        char[] ca = source.toCharArray();
        //混淆源数据
        for(int i = 0 ; i < ca.length ; i++){
            ca[i] = (char) (ca[i] + salt);
        }
        String target = new String(ca);
        String md5 = DigestUtils.md5Hex(target);
        return md5;
    }
}

5.新建接口就其实现类

点击查看代码
public interface MemberService {

    public Member createMember(String username,String password,String nickname);

}

package com.imooc.reader.service.Impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.imooc.reader.entity.Evaluation;
import com.imooc.reader.entity.Member;
import com.imooc.reader.entity.MemberReadState;
import com.imooc.reader.mapper.EvaluationMapper;
import com.imooc.reader.mapper.MemberMapper;
import com.imooc.reader.mapper.MemberReadStateMapper;
import com.imooc.reader.service.MemberService;

import com.imooc.reader.service.exception.BussinessException;
import com.imooc.reader.utils.MD5Utils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Random;

@Service("memberService")
@Transactional
public class MemberServiceImp  implements MemberService {
    @Resource
    private MemberMapper memberMapper;

    public Member createMember(String username, String password, String nickname) throws BussinessException {
        QueryWrapper<Member> queryWrapper = new QueryWrapper<Member>();
        queryWrapper.eq("username", username);
        List<Member> memberList = memberMapper.selectList(queryWrapper);
        //判断用户名是否已存在
        if(memberList.size() > 0){
            throw new BussinessException("M01","用户名已存在");
        }
        Member member = new Member();
        member.setUsername(username);
        member.setNickname(nickname);
        int salt = new Random().nextInt(1000) + 1000; //盐值
        String md5 = MD5Utils.md5Digest(password, salt);
        member.setPassword(md5);
        member.setSalt(salt);
        member.setCreateTime(new Date());
        memberMapper.insert(member);
        return member;
    }

}

6.验证码和用户注册比对 MemberController

点击查看代码
package com.imooc.reader.controller;

import com.imooc.reader.entity.Evaluation;
import com.imooc.reader.entity.Member;
import com.imooc.reader.service.MemberService;

import com.imooc.reader.service.exception.BussinessException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

@Controller
public class MemberController {
    @Resource
    private MemberService memberService;


    @GetMapping("/register.html")
    public ModelAndView showRegister(){
        return new ModelAndView("/register");
    }


    @PostMapping("/registe")
    @ResponseBody
    public Map registe(String vc, String username, String password , String nickname , HttpServletRequest request){
        //正确验证码
        String verifyCode = (String)request.getSession().getAttribute("kaptchaVerifyCode");
        //验证码对比
        Map result = new HashMap();
        if(vc == null || verifyCode == null || !vc.equalsIgnoreCase(verifyCode)){
            result.put("code", "VC01");
            result.put("msg", "验证码错误");
        }else{
            try {
                memberService.createMember(username, password, nickname);
                result.put("code", "0");
                result.put("msg", "success");
            }catch (BussinessException ex){
                ex.printStackTrace();
                result.put("code", ex.getCode());
                result.put("msg", ex.getMsg());
            }
        }
        return result;
    }
   
}

六.会员登录  

1.前端  Ajax 提交数据

点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>会员登录-慕课书评网</title>
    <meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0,user-scalable=no">
    <link rel="stylesheet" href="http://cdn.itlaoqi.com./resources/bootstrap4/css/bootstrap.css">
    <link rel="stylesheet" href="./resources/raty/lib/jquery.raty.css">

    <script src="http://cdn.itlaoqi.com./resources/jquery.3.3.1.min.js"></script>
    <script src="http://cdn.itlaoqi.com./resources/bootstrap4/js/bootstrap.min.js"></script>
    <style>
        .container {
            padding: 0px;
            margin: 0px;
        }

        .row {
            padding: 0px;
            margin: 0px;
        }

        .col- * {
            padding: 0px;
        }

        .description p {
            text-indent: 2em;
        }

        .description img {
            width: 100%;
        }

    </style>

</head>
<body>
<!--<div style="width: 375px;margin-left: auto;margin-right: auto;">-->
<div class="container ">
    <nav class="navbar navbar-light bg-white shadow">
        <ul class="nav">
            <li class="nav-item">
                <a href="/">
                    <img src="https://m.imooc.com/static/wap/static/common/img/logo2.png" class="mt-1"
                         style="width: 100px">
                </a>
            </li>
        </ul>
    </nav>


    <div class="container mt-2 p-2 m-0">
        <form id="frmLogin">
            <div class="passport bg-white">
                <h4 class="float-left">会员登录</h4>
                <h6 class="float-right pt-2"><a href="/register.html">会员注册</a></h6>
                <div class="clearfix"></div>
                <div class="alert d-none mt-2" id="tips" role="alert">

                </div>

                <div class="input-group  mt-2 ">
                    <input type="text" id="username" name="username" class="form-control p-4" placeholder="请输入用户名"
                           aria-label="Username" aria-describedby="basic-addon1">
                </div>

                <div class="input-group  mt-4 ">
                    <input id="password" name="password" class="form-control p-4" placeholder="请输入密码" type="password"
                           aria-describedby="basic-addon1">
                </div>

                <div class="input-group mt-4 ">
                    <div class="col-5 p-0">
                        <input type="text" id="verifyCode" name="vc" class="form-control p-4" placeholder="验证码">
                    </div>
                    <div class="col-4 p-0 pl-2 pt-0">
                        <img id="imgVerifyCode" src="/verify_code"
                             style="width: 120px;height:50px;cursor: pointer">
                    </div>

                </div>

                <a id="btnSubmit" class="btn btn-success  btn-block mt-4 text-white pt-3 pb-3">登&nbsp;&nbsp;&nbsp;&nbsp;录</a>
            </div>
        </form>

    </div>
</div>

<script>
    function showTips(isShow, css, text) {
        if (isShow) {
            $("#tips").removeClass("d-none")
            $("#tips").hide();
            $("#tips").addClass(css);
            $("#tips").text(text);
            $("#tips").fadeIn(200);
        } else {
            $("#tips").text("");
            $("#tips").fadeOut(200);
            $("#tips").removeClass();
            $("#tips").addClass("alert")
        }
    }
    function reloadVerifyCode(){
        $("#imgVerifyCode").attr("src", "/verify_code?ts=" + new Date().getTime());
    }
    $("#imgVerifyCode").click(function () {
        reloadVerifyCode();
    });

    $("#btnSubmit").click(function () {
        var username = $.trim($("#username").val());
        var regex = /^.{1,10}$/;
        if (!regex.test(username)) {
            showTips(true, "alert-danger", "用户名请输入正确格式(1-10位)");
            return;
        } else {
            showTips(false);
        }

        var password = $.trim($("#password").val());

        if (!regex.test(password)) {
            showTips(true, "alert-danger", "密码请输入正确格式(1-10位)");
            return;
        } else {
            showTips(false);
        }

        $btnReg = $(this);

        $btnReg.text("正在处理...");
        $btnReg.attr("disabled", "disabled");
        $.ajax({
            url: "/check_login",
            type: "post",
            dataType: "json",
            data: $("#frmLogin").serialize(),
            success: function (data) {
                console.info(data);
                if (data.code == "0") {
                    window.location = "/?ts=" + new Date().getTime();
                } else {
                    showTips(true, "alert-danger", data.msg);
                    reloadVerifyCode();
                    $btnReg.text("登录");
                    $btnReg.removeAttr("disabled");
                }
            }
        });
        return false;
    });


</script>
</body>
</html>

2.MemberController

点击查看代码
    @GetMapping("/login.html")
    public ModelAndView showLogin(){
        return new ModelAndView("/login");
    }

3.service接口和实现类

点击查看代码
    public Member checkLogin(String username,String password);
    
    public Member checkLogin(String username, String password) {
        QueryWrapper<Member> memberQueryWrapper = new QueryWrapper<Member>();
        memberQueryWrapper.eq("username",username);
        Member member = memberMapper.selectOne(memberQueryWrapper);
        if(member==null){
            throw  new BussinessException("M02","用户不存在");
        }else{
            String md5 = MD5Utils.md5Digest(password, member.getSalt());
            if( !md5.equals(member.getPassword())){
                throw  new BussinessException("M03","输入密码有误");
            }
        }
        return member;
    }
    
    

4.控制器MemberController 校验

点击查看代码
  @PostMapping("/check_login")
    @ResponseBody
    public Map checkLogin(String username, String password, String vc, HttpSession session){
        //正确验证码
        String verifyCode = (String)session.getAttribute("kaptchaVerifyCode");
        //验证码对比
        Map result = new HashMap();
        if(vc == null || verifyCode == null || !vc.equalsIgnoreCase(verifyCode)){
            result.put("code", "VC01");
            result.put("msg", "验证码错误");
        }else{
            try{
                Member member = memberService.checkLogin(username, password);
                session.setAttribute("loginMember",member);
                result.put("code", "0");
                result.put("msg", "success");
            }catch (BussinessException ex){
                ex.printStackTrace();
                result.put("code", ex.getCode());
                result.put("msg", ex.getMsg());
            }
        }
        return result;
    }

 

七.会员阅读状态显示  

1.实体类MemberReadState表

点击查看代码
package com.imooc.reader.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.util.Date;

@TableName("member_read_state")
public class MemberReadState {
    @TableId(type = IdType.AUTO)
    private Long rsId;
    private Long bookId;

    public Long getMemberId() {
        return memberId;
    }

    public void setMemberId(Long memberId) {
        this.memberId = memberId;
    }

    private Long memberId;
    private Integer readState;
    private Date createTime;

    public Long getRsId() {
        return rsId;
    }

    public void setRsId(Long rsId) {
        this.rsId = rsId;
    }

    public Long getBookId() {
        return bookId;
    }

    public void setBookId(Long bookId) {
        this.bookId = bookId;
    }



    public Integer getReadState() {
        return readState;
    }

    public void setReadState(Integer readState) {
        this.readState = readState;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

2.mapper接口 mapper.xml

public interface MemberReadStateMapper extends BaseMapper<MemberReadState> {
}
  
  
<?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.imooc.reader.mapper.MemberReadStateMapper">

</mapper>

3.在Memberservice中创建方法

获取当前会员的阅读状态

点击查看代码
public MemberReadState selectMemberReadState(Long memberId,Long bookId);
    
    @Transactional(propagation = Propagation.NOT_SUPPORTED,readOnly = true)
    public MemberReadState selectMemberReadState(Long memberId, Long bookId) {
        QueryWrapper<MemberReadState> memberQueryWrapper = new QueryWrapper<MemberReadState>();
        memberQueryWrapper.eq("book_id",bookId);
        memberQueryWrapper.eq("member_id",memberId);
        MemberReadState memberReadState = memberReadStateMapper.selectOne(memberQueryWrapper);
        return memberReadState;
    }

4.在加载图书详情时调用阅读状态

点击查看代码
@GetMapping("/book/{id}")
    public ModelAndView showDetail(@PathVariable("id") Long id, HttpSession session){
        Book book = bookService.selectById(id);
        List<Evaluation> evaluationList = evaluationService.selectById(id);
        ModelAndView modelAndView = new ModelAndView("/detail");
        Member member=(Member)session.getAttribute("loginMember");
        if(member!=null){
            MemberReadState memberReadState = memberService.selectMemberReadState(member.getMemberId(), id);
            modelAndView.addObject("memberReadState",memberReadState);
        }
        modelAndView.addObject("book",book);
        modelAndView.addObject("evaluationList",evaluationList);
        return modelAndView;
    }

5.前端

点击查看代码
$(function () {
            <#if memberReadState??>
                $("*[data-read-state='${memberReadState.readState}']").addClass("highlight");
            </#if>
            <#if !loginMember??>
            $("*[data-read-state],#btnEvaluation,*[data-evaluation-id]").click(function () {
                $("#exampleModalCenter").modal("show");
            })
            </#if>

八.更新会员阅读状态

1.前端

点击查看代码
$(function () {

            <#if memberReadState??>
                $("*[data-read-state='${memberReadState.readState}']").addClass("highlight");
            </#if>
    //未登录情况下 显示需要登录
            <#if !loginMember??>
            $("*[data-read-state],#btnEvaluation,*[data-evaluation-id]").click(function () {
                $("#exampleModalCenter").modal("show");
            })
            </#if>
            <#if loginMember??>
            $("*[data-read-state]").click(function () {
                let readState = $(this).data("read-state");
                $.post("/update_read_state",{
                    memberId:${loginMember.memberId},
                    bookId:${book.bookId},
                    readState:readState
                },function (json) {
                    if(json.code=="0"){
                        $("*[data-read-state]").removeClass("highlight");
                        $("*[data-read-state='" +readState +"']").addClass("highlight");
                    }
                },"json")
            })
            $("#btnEvaluation").click(function () {
                $("#score").raty({})
                $("#dlgEvaluation").modal("show")
            })
            $("#btnSubmit").click(function () {
                var score=$("#score").raty("score");
                var content=$("#content").val();
                if(score==0||$.trim(content)==""){
                    return;
                }
                $.post("/evaluate",{
                    score:score,
                    bookId:${book.bookId},
                    memberId:${loginMember.memberId},
                    content:content
                },function (json) {
                    if(json.code=="0"){
                        window.location.reload()
                    }
                },"json")
            })

            $("*[data-evaluation-id]").click(function () {
                var evaluationId= $(this).data("evaluation-id");
                $.post("/enjoy",{evaluationId:evaluationId},function (json) {
                    if(json.code=="0"){
                        $("*[data-evaluation-id='"+evaluationId +"'] span").text(json.evaluate.enjoy);
                    }
                },"json")

            })
            </#if>
        })

2.在MemberService添加更新阅读状态方法

点击查看代码
 public MemberReadState updateMemberReadState(Long memberId,Long bookId,Integer readState);
 
 public MemberReadState updateMemberReadState(Long memberId, Long bookId, Integer readState) {
        QueryWrapper<MemberReadState> memberReadStateQueryWrapper = new QueryWrapper<MemberReadState>();
        memberReadStateQueryWrapper.eq("book_id",bookId);
        memberReadStateQueryWrapper.eq("member_id",memberId);
        MemberReadState memberReadState = memberReadStateMapper.selectOne(memberReadStateQueryWrapper);
        if(memberReadState==null){
            memberReadState=new MemberReadState();
            memberReadState.setMemberId(memberId);
            memberReadState.setBookId(bookId);
            memberReadState.setReadState(readState);
            memberReadState.setCreateTime(new Date());
            memberReadStateMapper.insert(memberReadState);
        }else {
            memberReadState.setReadState(readState);
            memberReadStateMapper.updateById(memberReadState);
        }

        return memberReadState;
    }

3.MemberController调用

点击查看代码
 @PostMapping("/update_read_state")
    @ResponseBody
    public Map updateReadState(Long memberId,Long bookId,Integer readState){
            Map map=new HashMap();
        try {
            memberService.updateMemberReadState(memberId, bookId, readState);
            map.put("code","0");
            map.put("msg","success");

        }catch (BussinessException ex){
            map.put("caode",ex.getCode());
            map.put("msg",ex.getMsg());

        }
        return map;
    }

 

九.写短评


1.在  <#if loginMember??>在建立点击事件,显示评分框

点击查看代码
            $("#btnEvaluation").click(function () {
                $("#score").raty({})
                $("#dlgEvaluation").modal("show")
            })

在写短评填充书名

2.    在Memberservice中添加方法

点击查看代码
public Evaluation evaluate(Long memberId, Long bookId, Integer score, String content);


public Evaluation evaluate(Long memberId, Long bookId, Integer score, String content) {
        Evaluation evaluation=new Evaluation();
        evaluation.setMemberId(memberId);
        evaluation.setBookId(bookId);
        evaluation.setScore(score);
        evaluation.setContent(content);
        evaluation.setCreateTime(new Date());
        evaluation.setState("enable");
        evaluation.setEnjoy(0);

        evaluationMapper.insert(evaluation);

        return evaluation;
    }

3.控制器MemberController

点击查看代码
 @PostMapping("/evaluation")
    public Map evaluation(Long memberId, Long bookId, Integer score, String content){
        Map map=new HashMap();
        try {
            Evaluation evaluate = memberService.evaluate(memberId, bookId, score, content);
            map.put("code","0");
            map.put("msg","success");
            map.put("evaluate",evaluate);
        }catch (BussinessException ex){
            map.put("caode",ex.getCode());
            map.put("msg",ex.getMsg());

        }
        return map;
    } 

4.前端Ajax提交  

      $("#btnEvaluation").click(function () {
                $("#score").raty({})
                $("#dlgEvaluation").modal("show")
            })
            $("#btnSubmit").click(function () {
                var score=$("#score").raty("score");
                var content=$("#content").val();
                if(score==0||$.trim(content)==""){
                    return;
                }
                $.post("/evaluate",{
                    score:score,
                    bookId:${book.bookId},
                    memberId:${loginMember.memberId},
                    content:content
                },function (json) {
                    if(json.code=="0"){
                        window.location.reload()
                    }
                },"json")
            })


十.会员点赞

1.在MemberService创建方法

点击查看代码
public Evaluation enjoy(Long evaluationId);


public Evaluation enjoy(Long evaluationId) {
        Evaluation evaluation = evaluationMapper.selectById(evaluationId);
        evaluation.setEnjoy(evaluation.getEnjoy()+1);
        evaluationMapper.updateById(evaluation);

        return evaluation;
    }

2.在MemberController调用

点击查看代码
@PostMapping("/enjoy")
    @ResponseBody
    public Map enjoy(Long evaluationId){
        Map map=new HashMap();
        try {
            Evaluation evaluate = memberService.enjoy(evaluationId);
            map.put("code","0");
            map.put("msg","success");
            map.put("evaluate",evaluate);
    }catch (BussinessException ex){
            map.put("code",ex.getCode());
            map.put("msg",ex.getMsg());
        }
        return map;
    }

3.前端

点击查看代码
 $("*[data-evaluation-id]").click(function () {
                var evaluationId= $(this).data("evaluation-id");
                $.post("/enjoy",{evaluationId:evaluationId},function (json) {
                    if(json.code=="0"){
                        $("*[data-evaluation-id='"+evaluationId +"'] span").text(json.evaluate.enjoy);
                    }
                },"json")

            })

十一.评分自动计算

spring-task

1.在bookMapper.xml中添加

点击查看代码
<update id="updateEvaluation">
        update book b SET evaluation_score = (
            select ifnull(avg(score),0) from evaluation where book_id = b.book_id and state='enable'
            ),evaluation_quantity = (
            select ifnull(count(*),0) from evaluation where book_id = b.book_id and state='enable'
            )
    </update>

2.在BookMapper定义

点击查看代码
public interface BookMapper extends BaseMapper<Book> {
    public void updateEvaluation();
}

3.bookService中实现方法

点击查看代码
    @Transactional
    public void updateEvaluation() {
        bookMapper.updateEvaluation();
    }

4.引入依赖 配置开启注解

点击查看代码
<task:annotation-driven/>

 5.创建任务组件

点击查看代码
package com.imooc.reader.task;

import com.imooc.reader.service.BookService;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class ComputerTask {
    @Resource
    private BookService bookService;
    //调度
    @Scheduled(cron = "0 * * * * ?")
    public void updateEvaluation(){
        bookService.updateEvaluation();
        System.out.println("已更新所以评分");
    }
}

十二:wangedit图片上传

1.创建MbookController

点击查看代码
package com.imooc.reader.controller.management;


import com.baomidou.mybatisplus.core.metadata.IPage;
import com.imooc.reader.entity.Book;
import com.imooc.reader.service.BookService;
import com.imooc.reader.service.exception.BussinessException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/management/book")
public class MBookController {

    @GetMapping("/index.html")
    public ModelAndView showBook(){
        return new ModelAndView("/management/book");
    }
    
}

2.前端  初始化wangEditor

点击查看代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图书管理</title>
    <style>
        #dlgBook{
            padding: 10px
        }
    </style>
    <link rel="stylesheet" href="/resources/layui/css/layui.css">

    <script src="/resources/wangEditor.min.js"></script>


    <script type="text/html" id="toolbar">
        <div class="layui-btn-container">
            <button class="layui-btn layui-btn-sm" id="btnAdd" onclick="showCreate()">添加</button>
        </div>
    </script>

</head>
<body>


<div class="layui-container">
    <blockquote class="layui-elem-quote">图书列表</blockquote>
	<!-- 数据表格 -->
    <table id="grdBook" lay-filter="grdBook"></table>
</div>
<!--表单内容-->
<div id="dialog" style="padding: 10px;display: none">
    <form class="layui-form" >
        <div class="layui-form-item">
			<!-- 图书类别 -->
            <select id="categoryId" name="categoryId" lay-verify="required" lay-filter=
			"categoryId">
                <option value=""></option>
                <option value="1">前端</option>
				<option value="2">后端</option>
				<option value="3">测试</option>
				<option value="4">产品</option>
            </select>

        </div>
        <div class="layui-form-item">
			<!-- 书名 -->
            <input type="text" id="bookName" name="bookName" required lay-verify="required" placeholder="请输入书名"
                   autocomplete="off" class="layui-input">
        </div>


        <div class="layui-form-item">
			<!-- 子标题 -->
            <input type="text" id="subTitle" name="subTitle" required lay-verify="required" placeholder="请输入子标题"
                   autocomplete="off" class="layui-input">
        </div>

        <div class="layui-form-item">
			<!-- 作者 -->
            <input type="text" id="author" name="author" required lay-verify="required" placeholder="请输入作者信息"
                   autocomplete="off" class="layui-input">
        </div>

        <div style="margin-top: 30px;font-size: 130%">图书介绍(默认第一图将作为图书封面)</div>
        <div class="layui-form-item" >
			<!-- wangEditor编辑器 -->
            <div id="editor" style="width: 100%">

            </div>
        </div>
		<!-- 图书编号 -->
        <input id="bookId" type="hidden">
		<!-- 当前表单操作类型,create代表新增 update代表修改 -->
        <input id="optype"  type="hidden">
        <div class="layui-form-item" style="text-align: center">
			<!-- 提交按钮 -->
            <button class="layui-btn" lay-submit="" lay-filter="btnSubmit">立即提交</button>
        </div>
    </form>
</div>
<script src="/resources/layui/layui.all.js"></script>
<script>

    var table = layui.table; //table数据表格对象
    var $ = layui.$; //jQuery
    var editor = null; //wangEditor富文本编辑器对象
    //初始化图书列表
    table.render({
        elem: '#grdBook'  //指定div
        , id : "bookList" //数据表格id
        , toolbar: "#toolbar" //指定工具栏,包含新增添加
        , url: "/management/book/list" //数据接口
        , page: true //开启分页
        , cols: [[ //表头
            {field: 'bookName', title: '书名', width: '300'}
            , {field: 'subTitle', title: '子标题', width: '200'}
            , {field: 'author', title: '作者', width: '200'}
            , {type: 'space', title: '操作', width: '200' , templet : function(d){
					//为每一行表格数据生成"修改"与"删除"按钮,并附加data-id属性代表图书编号
                    return "<button class='layui-btn layui-btn-sm btn-update'  data-id='" + d.bookId + "' data-type='update' onclick='showUpdate(this)'>修改</button>" +
                        "<button class='layui-btn layui-btn-sm btn-delete'  data-id='" + d.bookId + "'   onclick='showDelete(this)'>删除</button>";
                }
            }
        ]]
    });
	//显示更新图书对话框
	//obj对应点击的"修改"按钮对象
    function showUpdate(obj){
		//弹出"编辑图书"对话框
        layui.layer.open({
            id: "dlgBook", //指定div
            title: "编辑图书", //标题
            type: 1, 
            content: $('#dialog').html(), //设置对话框内容,复制自dialog DIV
            area: ['820px', '730px'], //设置对话框宽度高度
            resize: false //是否允许调整尺寸
        })

        var bookId = $(obj).data("id"); //获取"修改"按钮附带的图书编号
        $("#dlgBook #bookId").val(bookId); //为表单隐藏域赋值,提交表单时用到

        editor = new wangEditor('#dlgBook #editor'); //初始化富文本编辑器
        editor.customConfig.uploadImgServer = '/management/book/upload' //设置图片上传路径
        editor.customConfig.uploadFileName = 'img'; //图片上传时的参数名
        editor.create(); //创建wangEditor
        $("#dlgBook #optype").val("update"); //设置当前表单提交时提交至"update"更新地址

		//发送ajax请求,获取对应图书信息
        $.get("/management/book/id/" + bookId , {} , function(json){
			//文本框回填已有数据
            $("#dlgBook #bookName").val(json.data.bookName);//书名
            $("#dlgBook #subTitle").val(json.data.subTitle); //子标题
            $("#dlgBook #author").val(json.data.author);//作者
            $("#dlgBook #categoryId").val(json.data.categoryId); //分类选项
            editor.txt.html(json.data.description); //设置图文内容
            layui.form.render();//重新渲染LayUI表单
        } , "json")



    }
	//显示新增图书对话框
    function showCreate(){
		//弹出"新增图书"对话框
        layui.layer.open({
            id: "dlgBook",
            title: "新增图书",
            type: 1,
            content: $('#dialog').html(),
            area: ['820px', '730px'],
            resize: false
        })
		//初始化wangEditor
        editor = new wangEditor('#dlgBook #editor');
        editor.customConfig.uploadImgServer = '/management/book/upload';//设置图片上传地址
        editor.customConfig.uploadFileName = 'img';//设置图片上传参数
        editor.create();//创建wangEditor

        layui.form.render(); //LayUI表单重新
        $("#dlgBook #optype").val("create");//设置当前表单提交时提交至"create"新增地址

    };

	//对话框表单提交
    layui.form.on('submit(btnSubmit)', function(data){
		//获取表单数据
        var formData = data.field;
		
		//判断是否包含至少一副图片,默认第一图作为封面显示
        var description = editor.txt.html();
        if(description.indexOf("img") == -1){
            layui.layer.msg('请放置一副图片作为封面');
            return false;
        }
		//获取当前表单要提交的地址
		//如果是新增数据则提交至create
		//如果是更新数据则提交至update
        var optype = $("#dlgBook #optype").val();
		
        if(optype == "update"){
			//更新数据时,提交时需要附加图书编号
            formData.bookId=$("#dlgBook #bookId").val();
        }
		//附加图书详细描述的图文html
        formData.description = description;
		//向服务器发送请求
        $.post("/management/book/" + optype , formData , function(json){
            if(json.code=="0"){
				//处理成功,关闭对话框,刷新列表,提示操作成功
                layui.layer.closeAll();
                table.reload('bookList');
                layui.layer.msg('数据操作成功,图书列表已刷新');
            }else{
				//处理失败,提示错误信息
                layui.layer.msg(json.msg);
            }
        } ,"json")
        return false;
    });
	//删除图书
    function showDelete(obj){
		//获取当前点击的删除按钮中包含的图书编号
        var bookId = $(obj).data("id");
		//利用layui的询问对话框进行确认
        layui.layer.confirm('确定要执行删除操作吗?', {icon: 3, title:'提示'}, function(index){
					
				//确认按钮后发送ajax请求,包含图书编号
				$.get("/management/book/delete/" + bookId, {}, function (json) {
					if(json.code=="0"){
						//删除成功刷新表格
						table.reload('bookList');
						//提示操作成功
						layui.layer.msg('数据操作成功,图书列表已刷新');
						//关闭对话框
						layui.layer.close(index);
					}else{
						//处理失败,提示错误信息
						layui.layer.msg(json.msg);
					}
				}, "json");
			
        });

    }

</script>
</body>
</html>

3.导入文件上传依赖和配置

点击查看代码
<dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>


    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"/>
    </bean>

4.在MbookController新增方法 

点击查看代码
 @PostMapping("/upload")
    @ResponseBody
    public Map upload(@RequestParam("img") MultipartFile file, HttpServletRequest request) throws IOException {//img是前端界面设置的文件名
        String uploadPath=request.getServletContext().getResource("/").getPath()+"/upload/";//运行时根路径
        String fileName=new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));

        file.transferTo(new File(uploadPath+fileName+suffix));//另存为
        Map result=new HashMap();
        result.put("errno",0);
        result.put("data",new String[]{"/upload/"+fileName+suffix});
        return result;
    }

十三:图书新增

1.在bookService添加方法

点击查看代码
public Book createBook(Book book);

@Transactional
    public Book createBook(Book book) {
        bookMapper.insert(book);
        return book;//自动回填 有id值
    }

2.在MBookController新增方法 

Jsoup.parse(html代码).select(语法类似JQuery)

点击查看代码
@PostMapping("/create")
    @ResponseBody
    private  Map createBook(Book book){
        Map result=new HashMap();
        try{
            book.setEvaluationQuantity(0);
            book.setEvaluationScore(0f);

            Document document = Jsoup.parse(book.getDescription());
            Element img = document.select("img").first();
            String cover = img.attr("src");
            book.setCover(cover);
            bookService.createBook(book);

            result.put("code",0);
            result.put("msg","success");
        }catch(BussinessException ex){
            result.put("code",ex.getCode());
            result.put("msg",ex.getMsg());
        }
        return result;
    }

十三.图书分页查询

1.在MBookController新增方法

点击查看代码
@GetMapping("/list")
@ResponseBody
    public Map list(Integer page,Integer limit){
        if(page==null){
            page=1;
        }
        if(page==limit){
            limit=10;
        }
        IPage<Book> bookIPage = bookService.selectBookPage(null, null, page, limit);
        Map result=new HashMap();
        result.put("code",0);
        result.put("msg","success");
        result.put("data",bookIPage.getRecords());
        result.put("count",bookIPage.getTotal());

        return result;

    }

十四:图书修改更新

1.前端

点击查看代码
    table.render({
        elem: '#grdBook'  //指定div
        , id : "bookList" //数据表格id
        , toolbar: "#toolbar" //指定工具栏,包含新增添加
        , url: "/management/book/list" //数据接口
        , page: true //开启分页
        , cols: [[ //表头
            {field: 'bookName', title: '书名', width: '300'}
            , {field: 'subTitle', title: '子标题', width: '200'}
            , {field: 'author', title: '作者', width: '200'}
            , {type: 'space', title: '操作', width: '200' , templet : function(d){
					//为每一行表格数据生成"修改"与"删除"按钮,并附加data-id属性代表图书编号
                    return "<button class='layui-btn layui-btn-sm btn-update'  data-id='" + d.bookId + "' data-type='update' onclick='showUpdate(this)'>修改</button>" +
                        "<button class='layui-btn layui-btn-sm btn-delete'  data-id='" + d.bookId + "'   onclick='showDelete(this)'>删除</button>";
                }
            }
        ]]
    });

2.在bookService定义

点击查看代码
  public Book updateBook(Book book);
  
  @Transactional
    public Book updateBook(Book book) {
        bookMapper.updateById(book);
        return book;

3.数据回显  在mbookController添加方法

点击查看代码
 @GetMapping("/id/{id}")
    @ResponseBody
    public Map selectById(@PathVariable("id") Long bookId){
        Book book = bookService.selectById(bookId);
        Map result=new HashMap();
        result.put("code",0);
        result.put("msg","success");
        result.put("data",book);
       return result;
    }

4.数据更新  在mbookController添加方法,根据bookid查询到原有的数据,在此基础上更新

点击查看代码
@PostMapping("/update")
    @ResponseBody
    public Map updateBook(Book book){
        Map result = new HashMap();
        try {
            Book rawBook = bookService.selectById(book.getBookId());
            rawBook.setBookName(book.getBookName());
            rawBook.setSubTitle(book.getSubTitle());
            rawBook.setCategoryId(book.getCategoryId());
            rawBook.setDescription(book.getDescription());
            Document doc = Jsoup.parse(book.getDescription());
            String cover = doc.select("img").first().attr("src");
            rawBook.setCover(cover);
            bookService.updateBook(rawBook);

            result.put("code", 0);
            result.put("msg", "success");
        }catch (BussinessException ex){
            ex.printStackTrace();
            result.put("code", ex.getCode());
            result.put("msg",ex.getMsg());
        }
        return result;
    }

十五:图书删除

1.在bookService定义  需要删除阅读状态和评论数据

点击查看代码
 public void deleteBook(Long bookId);
 
  @Transactional
    public void deleteBook(Long bookId) {
        bookMapper.deleteById(bookId);

        QueryWrapper<MemberReadState> memberReadStateQueryWrapper = new QueryWrapper<MemberReadState>();
        memberReadStateQueryWrapper.eq("book_id",bookId);
        memberReadStateMapper.delete(memberReadStateQueryWrapper);

        QueryWrapper<Evaluation> evaluationQueryWrapper = new QueryWrapper<Evaluation>();
        evaluationQueryWrapper.eq("book_id",bookId);
        evaluationMapper.delete(evaluationQueryWrapper);
    }

2.在mbookController添加方法

点击查看代码
 @GetMapping("/delete/{id}")
    @ResponseBody
    public Map deleteBook(@PathVariable("id") Long bookId){
        Map result = new HashMap();
        try {
            bookService.deleteBook(bookId);

            result.put("code", 0);
            result.put("msg", "success");
        }catch (BussinessException ex){
            ex.printStackTrace();
            result.put("code", ex.getCode());
            result.put("msg",ex.getMsg());
        }
        return result;
    }

十六.后台首页

点击查看代码
@Controller
@RequestMapping("/management")
public class ManagementController {
    @GetMapping("/index.html")
    public ModelAndView showIndex(){
        return new ModelAndView("/management/index");
    }
}

 

posted @ 2021-09-06 19:46  丨渍丨  阅读(165)  评论(0)    收藏  举报