yblog(二)


yblog

基于SpringBoot和Vue的前后端分离项目

day01 Mybatis Plus入门

一、简介

官网:http://mp.baomidou.com/
参考教程:http://mp.baomidou.com/guide/
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

二、入门

看官方文档可以进行学习,此处只写需要注意的几点。

一 、引入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--lombok用来简化实体类-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--sql 分析打印-->
        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.8.2</version>
        </dependency>
    </dependencies>

之所以不写版本号是因为添加“parent”标签里的内容后,“spring-boot-starter-parent”会为开发者提供常用jar的版本管理,所以我们不需要指定版本,使用人家提供好的就可以了。当然,指定也是可以的,这会覆盖官方默认的版本,不推荐。

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

关于mp的版本,3.0.5的版本里许多功能已经不需要加入bean了,默认配置好的,按照我使用的经验来看,好像3.2.0以上的不需要配置“乐观锁”和“分页插件”。

二、配置

在 application.properties 配置文件中添加 MySQL 数据库的相关配置:

spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=user
spring.datasource.password=123456
spring.profiles.active=dev
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

注意:

1、这里的 url 使用了 ?serverTimezone=GMT%2B8 后缀,因为Spring Boot 2.1 集成了 8.0版本的jdbc驱动,这个版本的 jdbc 驱动需要添加这个后缀,否则运行测试用例报告如下错误:

java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more

2、这里的 driver-class-name 使用了 com.mysql.cj.jdbc.Driver ,在 jdbc 8 中 建议使用这个驱动,之前的 com.mysql.jdbc.Driver 已经被废弃,否则运行测试用例的时候会有 WARN 信息

三、部分代码注意点

  1. 主类

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹
注意:扫描的包名根据实际情况修改,
且可以写在配置类,如MpConfig中:

@Configuration
@MapperScan("com.yhg.mpdemo.mapper")
public class Mpconfig {
    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}
  1. 实体
@Data
public class User {

    @TableId(type = IdType.ID_WORKER)

    private Long id;
    private String name;
    private Integer age;
    private String email;
    //表中含下划线改为驼峰式命名
}
  1. 测试类的部分示例
@SpringBootTest
public class MpdemoApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void findAll() {
        List<User> users = userMapper.selectList(null);
        System.out.println(users);
    }

    //添加操作
    @Test
    public void addUser() {
        User user = new User();
        user.setName("hhhhhh2");
        user.setAge(70);
        user.setEmail("lucy@qq.com");
        int insert = userMapper.insert(user);
        System.out.println("insert:" + insert);
    }
//修改操作
    @Test
    public void updateUser(){
        User user = new User();
        user.setId(3L);
        user.setAge(120);
        int row = userMapper.updateById(user);
        System.out.println(row);
    }
    @Test
    public void testLocker(){
        //查询
        User user = userMapper.selectById(1248623851416322049L);
        //修改数据
        user.setName("Helen Yao");
        user.setEmail("helen@qq.com");
        //执行更新
        userMapper.updateById(user);
    }
    @Test
    public void testSelect(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
        System.out.println(users);

    }

    @Test
    public void testPage(){//pagehandler类似
        //创建page对象
        //传入两个参数,第一个对象是当前页,第二个是每页显示的记录数
        Page<User> page = new Page<>(1,3);
        //调用mp分页查询的方法
        //调用mp分页查询过程中,底层数据封装到page对象里面
        userMapper.selectPage(page,null);

        //通过page对象获取分页数据
        page.getRecords().forEach(System.out::println);
        System.out.println(page.getCurrent());
        System.out.println(page.getPages());
        System.out.println(page.getSize());
        System.out.println(page.getTotal());
        System.out.println(page.hasNext());
        System.out.println(page.hasPrevious());
    }
//删除
    @Test
    public void testDeleteById(){
        int result = userMapper.deleteById(1248818588345819137L);
        System.out.println(result);
    }

@Test
    public void testSelectQuery(){
        //创建querywrapper对象
    QueryWrapper<User> wrapper = new QueryWrapper<>();

    //通过QW设置条件
}

4.注意事项:

  • ID_WORKER
    MyBatis-Plus默认的主键策略是:ID_WORKER 全局唯一ID
  • 自增策略
    要想主键自增需要配置如下主键策略
    需要在创建数据表的时候设置主键自增
    实体字段中配置 @TableId(type = IdType.AUTO)
    要想影响所有实体的配置,可以设置全局主键配置
    #全局设置主键生成策略
    mybatis-plus.global-config.db-config.id-type=auto

    自动填充

    项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
    我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:

(1)数据库表中添加自动填充字段
在User表中添加datetime类型的新的字段 create_time、update_time

(2)实体上添加注解

@TableField(fill = FieldFill.INSERT)
    private Date createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

(3)实现元对象处理器接口
注意:不要忘记添加 @Component 注解
Component表示交给spring来处理,Service或者ResponseXXX

//Component表示交给spring来处理,Service或者ResponseXXX
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

乐观锁

主要适用场景:当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新
乐观锁实现方式:

取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败

(1)数据库中添加version字段

ALTER TABLE `user` ADD COLUMN `version` INT

(2)实体类添加version字段
并添加 @Version 注解

@Version
    @TableField(fill = FieldFill.INSERT)
    private Integer version;//版本号属性

(3)元对象处理器接口添加version的insert默认值

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime",new Date(),metaObject);
        ……
        this.setFieldValByName("version",1,metaObject);//就是这句
        ……
}

特别说明:

支持的数据类型只有 int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
(4)在 MybatisPlusConfig 中注册 Bean
(3.6以上版本不用了,)

//乐观锁插件
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }

分页

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
(1)创建配置类
此时可以删除主类中的 @MapperScan 扫描注解(新版应该也不用了)

/**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

逻辑删除

逻辑删除
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录

(1)数据库中添加 deleted字段
(2)实体类添加deleted 字段
并加上 @TableLogic 注解 和 @TableField(fill = FieldFill.INSERT) 注解
@TableLogic

@TableLogic
    @TableField(fill = FieldFill.INSERT)
    private Integer deleted;

(3)元对象处理器接口添加deleted的insert默认值

@Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }

(4)application.properties 加入配置
此为默认值,如果你的默认值和mp默认的一样,该配置可无

mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

(5)在 MybatisPlusConfig 中注册 Bean(注意新版的好像不用)

@Bean
public ISqlInjector sqlInjector() {
    return new LogicSqlInjector();
}

(6)测试逻辑删除
测试后发现,数据并没有被删除,deleted字段的值由0变成了1
测试后分析打印的sql语句,是一条update
注意:被删除数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作

mp的条件构造器wapper

在这里插入图片描述
上图表格为条件构造器使用中的各个方法格式和说明,如有不懂可参考官方文档
以下代码转载自:https://blog.csdn.net/m0_37034294/article/details/82917234
构造器条件

package com.lqf.crud;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lqf.crud.bean.crm.User;
import com.lqf.crud.dao.crm.UserMapper;
import com.sun.org.apache.xerces.internal.util.EntityResolverWrapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.RowBounds;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.jsf.el.WebApplicationContextFacesELResolver;

import javax.naming.Name;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest
public class QueryWrapperTests {

    @Autowired
    private UserMapper mapper;

    /**
     * <p>
     * 根据根据 entity 条件,删除记录,QueryWrapper实体对象封装操作类(可以为 null)
     * 下方获取到queryWrapper后删除的查询条件为name字段为null的and年龄大于等于12的and email字段不为null的
     * 同理写法条件添加的方式就不做过多介绍了。
     * </p>
     */
    @Test
    public void delete() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .isNull("name")
                .ge("age", 12)
                .isNotNull("email");
        int delete = mapper.delete(queryWrapper);
        System.out.println("delete return count = " + delete);
    }


    /**
     * <p>
     * 根据 entity 条件,查询一条记录,
     * 这里和上方删除构造条件一样,只是seletOne返回的是一条实体记录,当出现多条时会报错
     * </p>
     */
    @Test
    public void selectOne() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "lqf");

        User user = mapper.selectOne(queryWrapper);
        System.out.println(user);
    }


    /**
     * <p>
     * 根据 Wrapper 条件,查询总记录数
     * </p>
     *
     * @param queryWrapper 实体对象
     */
    @Test
    public void selectCount() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "lqf");

        Integer count = mapper.selectCount(queryWrapper);
        System.out.println(count);
    }


    /**
     * <p>
     * 根据 entity 条件,查询全部记录
     * </p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)为null查询全部
     */
    @Test
    public void selectList() {
        List<User> list = mapper.selectList(null);

        System.out.println(list);
    }

    /**
     * <p>
     * 根据 Wrapper 条件,查询全部记录
     * </p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    @Test
    public void selectMaps() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNotNull("name");
        List<Map<String, Object>> maps = mapper.selectMaps(queryWrapper);
        for (Map<String, Object> map : maps) {
            System.out.println(map);
        }
    }

    /**
     * 打印结果
     * {name=lqf, id=1046282328366391406, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391407, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391408, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391409, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391410, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391411, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391412, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391413, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391414, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391415, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391416, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391417, age=12, email=lqf@163.com, status=false}
     * {name=lqf, id=1046282328366391418, age=12, email=lqf@163.com, status=false}
     * json类型的键值对模式
     */

    /**
     * <p>
     * 根据 entity 条件,查询全部记录(并翻页)
     * </p>
     *
     * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    @Test
    public void selectPage() {
        Page<User> page = new Page<>(1, 5);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        IPage<User> userIPage = mapper.selectPage(page, queryWrapper);
        System.out.println(userIPage);
    }

    /**
     * 打印结果
     * ==>  Preparing: SELECT COUNT(1) FROM user
     * ==> Parameters:
     * <==    Columns: COUNT(1)
     * <==        Row: 100
     * ==>  Preparing: SELECT id,name,age,email,status FROM user LIMIT 0,5
     * ==> Parameters:
     * <==    Columns: id, name, age, email, status
     * <==        Row: 1046282328366391319, lqf, 12, lqf@163.com, 0
     * <==        Row: 1046282328366391320, lqf, 12, lqf@163.com, 0
     * <==        Row: 1046282328366391321, lqf, 12, lqf@163.com, 0
     * <==        Row: 1046282328366391322, lqf, 12, lqf@163.com, 0
     * <==        Row: 1046282328366391323, lqf, 12, lqf@163.com, 0
     * <==      Total: 5
     *
     *
     * 这里需要在项目中加入分页插件
     *   @Bean
     *     public PaginationInterceptor paginationInterceptor() {
     *         return new PaginationInterceptor();
     *     }
     */


    /**
     * <p>
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     * </p>
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    @Test
    public void selectMapsPage() {
        Page<User> page = new Page<>(1, 5);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        IPage<Map<String, Object>> mapIPage = mapper.selectMapsPage(page, queryWrapper);
        System.out.println(mapIPage);
    }

    /**
     * 和上个分页同理只是返回类型不同
     */


    /**
     * <p>
     * 根据 whereEntity 条件,更新记录
     * </p>
     *
     * @param entity        实体对象 (set 条件值,不能为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    @Test
    public void update() {

        //修改值
        User user = new User();
        user.setStatus(true);
        user.setName("zhangsan");

        //修改条件s
        UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
        userUpdateWrapper.eq("name", "lqf");

        int update = mapper.update(user, userUpdateWrapper);

        System.out.println(update);
    }

    /**
     * 打印结果
     * ==>  Preparing: UPDATE user SET name=?, status=? WHERE name = ?
     * ==> Parameters: zhangsan(String), true(Boolean), lqf(String)
     * <==    Updates: 100
     * Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@56a4f272]
     * 100
     * 2018-10-02 15:08:03.928  INFO 7972 --- [       Thread-2] o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework.web.context.support.GenericWebApplicationContext@37313c65: startup date [Tue Oct 02 15:08:00 CST 2018]; root of context hierarchy
     * 2018-10-02 15:08:03.937  INFO 7972 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
     * 2018-10-02 15:08:04.053  INFO 7972 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
     *
     * Process finished with exit code 0
     */

}

day02

数据库建立

等整个项目做好后会将数据库文件以及源文件都上传百度云,如果此刻没链接说明项目还未完成。

一 、工程结构介绍

二、创建父工程

1、创建sprigboot工程hg-parent
在idea开发工具中,使用 Spring Initializr 快速初始化一个 Spring Boot 模块,版本使用:2.2.1.RELEASE
groupId:com.hgx
artifactId:hg_parent
2、删除 src 目录
3、配置 pom.xml
修改版本为 :2.2.1.RELEASE
在这里插入图片描述
节点后面添加 pom类型

<artifactId>hg_parent</artifactId>
    <packaging>pom</packaging>

4、在pom.xml中添加依赖的版本
删除pom.xml中的内容

<!-- 以下内容删除 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

添加 确定依赖的版本

<properties>
        <java.version>1.8</java.version>
        <guli.version>0.0.1-SNAPSHOT</guli.version>
        <mybatis-plus.version>3.0.5</mybatis-plus.version>
        <velocity.version>2.0</velocity.version>
        <swagger.version>2.7.0</swagger.version>
        <aliyun.oss.version>2.8.3</aliyun.oss.version>
        <jodatime.version>2.10.1</jodatime.version>
        <poi.version>3.17</poi.version>
        <commons-fileupload.version>1.3.1</commons-fileupload.version>
        <commons-io.version>2.6</commons-io.version>
        <httpclient.version>4.5.1</httpclient.version>
        <jwt.version>0.7.0</jwt.version>
        <aliyun-java-sdk-core.version>4.3.3</aliyun-java-sdk-core.version>
        <aliyun-sdk-oss.version>3.1.0</aliyun-sdk-oss.version>
        <aliyun-java-sdk-vod.version>2.15.2</aliyun-java-sdk-vod.version>
        <aliyun-java-vod-upload.version>1.4.11</aliyun-java-vod-upload.version>
        <aliyun-sdk-vod-upload.version>1.4.11</aliyun-sdk-vod-upload.version>
        <fastjson.version>1.2.28</fastjson.version>
        <gson.version>2.8.2</gson.version>
        <json.version>20170516</json.version>
        <commons-dbutils.version>1.7</commons-dbutils.version>
        <canal.client.version>1.1.0</canal.client.version>
        <docker.image.prefix>zx</docker.image.prefix>
        <cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version>
    </properties>

配置 锁定依赖的版本

<dependencyManagement>
        <dependencies>
            <!--Spring Cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--mybatis-plus 持久层-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>

            <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>${velocity.version}</version>
            </dependency>

            <!--swagger-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>${swagger.version}</version>
            </dependency>

            <!--swagger ui-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>${swagger.version}</version>
            </dependency>


            <!--aliyunOSS-->
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun.oss.version}</version>
            </dependency>

            <!--日期时间工具-->
            <dependency>
                <groupId>joda-time</groupId>
                <artifactId>joda-time</artifactId>
                <version>${jodatime.version}</version>
            </dependency>

            <!--xls-->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>${poi.version}</version>
            </dependency>

            <!--xlsx-->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>${poi.version}</version>
            </dependency>


            <!--文件上传-->

            <dependency>
                <groupId>commons-fileupload</groupId>
                <artifactId>commons-fileupload</artifactId>
                <version>${commons-fileupload.version}</version>
            </dependency>

            <!--commons-io-->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>${commons-io.version}</version>
            </dependency>


            <!--httpclient-->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>${httpclient.version}</version>
            </dependency>


            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>${gson.version}</version>
            </dependency>


            <!-- JWT -->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jwt.version}</version>
            </dependency>


            <!--aliyun-->
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>${aliyun-java-sdk-core.version}</version>
            </dependency>

            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${aliyun-sdk-oss.version}</version>
            </dependency>

            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-vod</artifactId>
                <version>${aliyun-java-sdk-vod.version}</version>
            </dependency>

            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-vod-upload</artifactId>
                <version>${aliyun-java-vod-upload.version}</version>
            </dependency>

            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-sdk-vod-upload</artifactId>
                <version>${aliyun-sdk-vod-upload.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fastjson.version}</version>
            </dependency>

            <dependency>
                <groupId>org.json</groupId>
                <artifactId>json</artifactId>
                <version>${json.version}</version>
            </dependency>


            <dependency>
                <groupId>commons-dbutils</groupId>
                <artifactId>commons-dbutils</artifactId>
                <version>${commons-dbutils.version}</version>
            </dependency>


            <dependency>
                <groupId>com.alibaba.otter</groupId>
                <artifactId>canal.client</artifactId>
                <version>${canal.client.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

三、搭建service模块

1、在父工程hg-parent下面创建模块service

选择 maven类型,点击下一步
输入模块名称 service,下一步完成创建
2、添加模块类型是pom
节点后面添加 pom类型

<artifactId>service</artifactId>
    <packaging>pom</packaging>

3、添加项目需要的依赖

<dependencies>


        <!--<dependency>-->
            <!--<groupId>org.springframework.cloud</groupId>-->
            <!--<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>-->
        <!--</dependency>-->

        <!--hystrix依赖,主要是用  @HystrixCommand -->
        <!--<dependency>-->
            <!--<groupId>org.springframework.cloud</groupId>-->
            <!--<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>-->
        <!--</dependency>-->

        <!--服务注册-->
        <!--<dependency>-->
            <!--<groupId>org.springframework.cloud</groupId>-->
            <!--<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>-->
        <!--</dependency>-->

        <!--服务调用-->
        <!--<dependency>-->
            <!--<groupId>org.springframework.cloud</groupId>-->
            <!--<artifactId>spring-cloud-starter-openfeign</artifactId>-->
        <!--</dependency>-->

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

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

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
        </dependency>

        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>

        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--xls-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
        </dependency>

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

        <!--httpclient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

        <!--commons-io-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>

        <!--gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

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

四、搭建service-edu模块

1、在父工程service模块下面创建子模块service-edu

五、讲师管理模块配置

1、在service下面service-edu模块中创建配置文件
在这里插入图片描述
resources目录下创建文件 application.properties

# 服务端口
server.port=8001
# 服务名
spring.application.name=service-edu
# 环境设置:dev、test、prod
spring.profiles.active=dev
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/hgx?serverTimezone=GMT%2B8
spring.datasource.username=user
spring.datasource.password=123456

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

下面这一段日志部分如果没有例外使用日志,如logback这些就要加上,否则注释掉。

##设置日志级别
#logging.level.root=WARN
##mybatis日志
#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

或者在resources目录下创建文件 application.yml,补一个

#### application.yml
spring:
  application:
    name: service-edu
profiles:
    active: dev
####  application-dev.yml
server:
  port: 8001
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:com/atguigu/service/*/mapper/*.xml
global-config:
    db-config:
      logic-delete-value: 1
logic-not-delete-value: 0
spring:
datasource:
    type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
username: root
password: root
hikari:
      connection-test-query: SELECT 1
connection-timeout: 60000
idle-timeout: 500000
max-lifetime: 540000
maximum-pool-size: 12
minimum-idle: 10
pool-name: GuliHikariPool
jackson:
    date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8

2、创建MP代码生成器
在test/java目录下创建包com.atguigu.eduservice,创建代码生成器:CodeGenerator.java
建议在test下面生成

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

/**
 * @author 雨落弦鸣
 * @since 2020/5/13
 */
public class CodeGenerator {

    @Test
    public void run() {

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir("F:\\WORKING_PLACE\\hgx_edu\\hg_parent\\service\\service_edu" + "/src/main/java");
        gc.setAuthor("雨落弦鸣");
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setFileOverride(false); //重新生成时文件是否覆盖
        gc.setServiceName("%sService");    //去掉Service接口的首字母I
        gc.setIdType(IdType.ID_WORKER); //主键策略
        gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
        gc.setSwagger2(true);//开启Swagger2模式

        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/hgx?serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("user");
        dsc.setPassword("123456");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.hgx");
        pc.setModuleName("eduservice"); //模块名
        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("edu_teacher");
        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
        strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

        strategy.setRestControllerStyle(true); //restful api风格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

        mpg.setStrategy(strategy);


        // 6、执行
        mpg.execute();
    }
}

六、编写后台管理api接口

1、编写controller代码
2、创建SpringBoot配置类
在edu包下创建config包,创建MyBatisPlusConfig.java

@Configuration
@MapperScan("com.hgx.eduservice.mapper")
public class EduConfig {
……
}

关于@Configuration,他指示一个类声明一个或多个@Bean方法,并且可以由Spring容器处理,以便在运行时为这些bean生成BeanDefinition和服务请求。详解请看这里
3、配置SQL执行性能分析插件
4、创建SpringBoot启动类
创建启动类 EduApplication.java,注意启动类的创建位置

@SpringBootApplication
@ComponentScan(basePackages = {"com.hgx"})
public class EduApplication {
    public static void main(String[] args) {
        SpringApplication.run(EduApplication.class, args);
    }
}

5、运行启动类
访问http://localhost:8001/eduservice/teacher
得到json数据

6、统一返回的json时间格式
默认情况下json时间格式带有时区,并且是世界标准时间,和我们的时间差了八个小时
在application.properties中设置

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

七、讲师逻辑删除功能

1、EduTeacherController添加删除方法

//逻辑删除讲师的方法
    @DeleteMapping("{id}")
    public R removeTeacher(@ApiParam(name = "id", value = "讲师ID", required = true) @PathVariable String id) {
        boolean flag = teacherService.removeById(id);
        if (flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }

2、配置逻辑删除插件
EduConfig中配置

/**
 * 逻辑删除插件
 */
    @Bean
    public ISqlInjector sqlInjector() {
        return new LogicSqlInjector();
    }

一、Swagger2介绍

前后端分离开发模式中,api文档是最好的沟通方式。
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
及时性 (接口变更后,能够及时准确地通知相关前后端开发人员)
规范性 (并且保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息)
一致性 (接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧)
可测性 (直接在接口文档上进行测试,以方便理解业务)

二、配置Swagger2
1、创建common模块
在guli-parent下创建模块common
配置:

groupId:com.hgx

artifactId:common
2、在common中引入相关依赖

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

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <scope>provided</scope>
        </dependency>

        <!--lombok用来简化实体类:需要安装lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <scope>provided</scope>
        </dependency>

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

        <!-- spring2.X集成redis所需common-pool2
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>-->

    </dependencies>

注意后面的这个,酌情注意

 <artifactId>common</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>service_base</module>
        <module>common_utils</module>
    </modules>

3、在common下面创建子模块service-base
3、在模块service-base中,创建swagger的配置类
创建包com.atguigu.servicebase.config,创建类SwaggerConfig

package com.hgx.servicebase;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration//配置类
@EnableSwagger2//Swagger注解
public class SwaggerConfig {

    @Bean
    public Docket webApiConfig() {

        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();
    }

    private ApiInfo webApiInfo() {
        return new ApiInfoBuilder()
                .title("网站-课程中心API文档")
                .description("本文档描述了课程中心微服务接口定义")
                .version("1.0")
                .contact(new Contact("雨落弦鸣", "http://hgx.com", "1790464356@qq.com"))
                .build();

    }
}

4、在模块service模块中引入service-base

<dependency>
            <groupId>com.hgx</groupId>
            <artifactId>service_base</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

5、在service-edu启动类上添加注解,进行测试
关于扫描包的注解详细可看。
6、API模型
可以添加一些自定义设置,例如:
定义样例数据

@ApiModelProperty(value = "创建时间", example = "2019-01-01 8:00:00")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@ApiModelProperty(value = "更新时间", example = "2019-01-01 8:00:00")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;

5、定义接口说明和参数说明
定义在类上:@Api
定义在方法上:@ApiOperation
定义在参数上:@ApiParam

@RestController
@RequestMapping("/eduservice/teacher")
@Api(description = "讲师管理")
public class EduTeacherController {

    //把service注入
    @Autowired
    private EduTeacherService teacherService;

    //1.查询讲师表所有数据
    //rest风格
    @ApiOperation(value = "所有讲师列表")
    @GetMapping("findAll")
    public R findAllTeacher() {
        //调用service的方法实现查询所有的操作
        List<EduTeacher> list = teacherService.list(null);
        return R.ok().data("item", list);
    }

    //逻辑删除讲师的方法
    @DeleteMapping("{id}")
    public R removeTeacher(@ApiParam(name = "id", value = "讲师ID", required = true) @PathVariable String id) {
        boolean flag = teacherService.removeById(id);
        if (flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }

一、统一返回数据格式

项目中我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一, 使前端(iOS Android, Web)对数据的操作更一致、轻松。
一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。但是一般会包含状态码、返回消息、数据这几部分内容
因此,我们定义统一结果
二、创建统一结果返回类
1、在common模块下创建子模块common-utils
2、创建接口定义返回码
创建包com.atguigu.commonutils,创建接口 ResultCode.java

public interface ResultCode {
    public static Integer SUCCESS = 20000;//成功
    public static Integer ERROR = 20001;//失败
}

4、创建结果类
创建类 R.java,注意这个R,后面用到的时候,要导入包含R的这个以来的模块

//统一返回借口属性
@Data
public class R {
    @ApiModelProperty(value = "是否成功")
    private Boolean success;
    @ApiModelProperty(value = "返回码")
    private Integer code;
    @ApiModelProperty(value = "返回消息")
    private String message;
    @ApiModelProperty(value = "返回数据")
    private Map<String, Object> data = new HashMap<String, Object>();

    //构造方法私有化
    private R() {
    }

    //链式编程
    //    成功的静态方法
    public static R ok() {
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("成功");
        return r;
    }

    //    失败的静态方法
    public static R error() {
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("失败");
        return r;
    }

    public R success(Boolean success){
        this.setSuccess(success);
        return this;
    }

    public R message(String message){
        this.setMessage(message);
        return this;
    }

    public R code(Integer code){
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value){
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}

二、统一返回结果使用

1、在service模块中添加依赖

<dependency>
            <groupId>com.hgx</groupId>
            <artifactId>common_utils</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

这个时候要注意,如果在service_base中已经包含过commo_utils了,那么直接引入service_base就可以删掉commo_utils了。
2、修改Controller中的返回结果
列表

    //1.查询讲师表所有数据
    //rest风格
    @ApiOperation(value = "所有讲师列表")
    @GetMapping("findAll")
    public R findAllTeacher() {
        //调用service的方法实现查询所有的操作
        List<EduTeacher> list = teacherService.list(null);
        return R.ok().data("item", list);
    }

删除

//逻辑删除讲师的方法
    @DeleteMapping("{id}")
    public R removeTeacher(@ApiParam(name = "id", value = "讲师ID", required = true) @PathVariable String id) {
        boolean flag = teacherService.removeById(id);
        if (flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }

一、分页

1、EduConfig中配置分页插件

/**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

2、分页Controller方法
Controller中添加分页方法

//分页查询讲师的方法
    @GetMapping("pageTeacher/{current}/{limit}")
    public R pageListTeacher(@PathVariable long current,
                             @PathVariable long limit) {

        //创建page对象
        Page<EduTeacher> pageTeacher = new Page<>(current, limit);
        //调用方法实现分类
        //调用方法的时候,底层封装,把分页所有数据封装到pageTeacher对象里面
        teacherService.page(pageTeacher, null);
//        try {
//            int i = 10 / 0;
//        } catch (Exception e) {
//          //执行自定义异常
//            throw new GuliException(20001,"执行了自定义异常处理...");
//        }
        long total = pageTeacher.getTotal();//总记录数
        List<EduTeacher> records = pageTeacher.getRecords();
        return R.ok().data("total", total).data("rows", records);
    }

二、条件查询

根据讲师名称name,讲师头衔level、讲师入驻时间gmt_create(时间段)查询
1、创建查询对象
在entity中新建一个包vo,在里面添加一个teacherQuery类查询对象就好了

@Data
public class TeacherQuery {
    @ApiModelProperty(value = "教师名称,模糊查询")
    private String name;

    @ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
    private Integer level;

    @ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
    private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换

    @ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
    private String end;
}

2、在controller写具体方法

//条件查询带分页的方法
    @PostMapping("pageTeacherCondition/{current}/{limit}")
    public R pageTeacherCondition(@PathVariable long current, @PathVariable long limit,
                                  @RequestBody(required = false) TeacherQuery teacherQuery) {
        //创建page对象
        Page<EduTeacher> pageTeacher = new Page<>(current, limit);
        //构建条件
        QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();

        //多条件组合查询
        //mybatis 动态sql
        //判断条件值是否为空,如果不为空拼接条件
        String name = teacherQuery.getName();
        Integer level = teacherQuery.getLevel();
        String begin = teacherQuery.getBegin();
        String end = teacherQuery.getEnd();

        if (!StringUtils.isEmpty(name)) {
            wrapper.like("name", name);
        }

        if (!StringUtils.isEmpty(level)) {
            wrapper.eq("level", level);
        }

        if (!StringUtils.isEmpty(begin)) {
            wrapper.ge("gmt_create", begin);
        }

        if (!StringUtils.isEmpty(end)) {
            wrapper.le("gmt_create", end);
        }
        //wrapper
        //调用方法实现条件查询分页
        teacherService.page(pageTeacher, wrapper);
        long total = pageTeacher.getTotal();//总记录数
        List<EduTeacher> records = pageTeacher.getRecords();//数据list集合
        return R.ok().data("total", total).data("rows", records);
    }

实际操作中更加建议放到service层,例如(这部分可以暂时先不做)。

此处对于RequestBody和ResponseBody做个解释,

@RequestBody:

使用json传数据,把json数据封装到对应对象里面,此时需要用到post,其中request= false,表示这个可以为空

@ResponseBody:

返回数据,返回json数据

一、新增和修改功能——自动填充封装

1、在service-base模块中添加
创建包handler,创建自动填充类 MyMetaObjectHandler

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        //属性名称,不是字段名称
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }
}

2、在实体类添加自动填充注解

    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)//这个东西
    private Date gmtCreate;

    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;

二、controller方法定义

1、新增
2、根据id查询
3、根据id修改

//添加讲师接口的方法
    @PostMapping("addTeacher")
    public R addTeacher(@RequestBody EduTeacher eduTeacher) {
        boolean save = teacherService.save(eduTeacher);
        if (save) {
            return R.ok();
        } else {
            return R.error();
        }
    }

    //根据讲师id进行查询
    @GetMapping("getTeacher/{id}")
    public R getTeacher(@PathVariable String id) {
        EduTeacher eduTeacher = teacherService.getById(id);
        return R.ok().data("teacher", eduTeacher);
    }

    //讲师修改功能
    @PostMapping("updateTeacher")
    public R updateTeacher(@RequestBody EduTeacher eduTeacher) {
        boolean flag = teacherService.updateById(eduTeacher);
        if (flag) {
            return R.ok();
        } else {
            return R.error();
        }
    }

二、异常处理

  • 创建统一异常处理器
    在service-base中创建统一异常处理类GlobalExceptionHandler.java:
  • 添加特定异常处理方法
    GlobalExceptionHandler.java中添加
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    //指定出现什么异常执行这个方法
    @ExceptionHandler(Exception.class)
    @ResponseBody//为了返回数据
    public R error(Exception e) {
        e.printStackTrace();
        return R.error().message("执行了全局异常处理...");
    }

    //    特定异常处理
    @ExceptionHandler(ArithmeticException.class)
    @ResponseBody//为了返回数据
    public R error(ArithmeticException e) {
        e.printStackTrace();
        return R.error().message("执行了ArithmeticException异常处理...");
    }
  • 创建自定义异常类
    1、guliException中:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GuliException extends RuntimeException {
    //一个类要有有参和无参构造方法
    private Integer code;//状态嘛

    private String msg;//异常信息
}

2、业务中需要的位置抛出GuliException

try {
            int i = 10 / 0;
        } catch (Exception e) {
         //执行自定义异常
            throw new GuliException(20001,"执行了自定义异常处理...");
       }

3、添加异常处理方法
GlobalExceptionHandler.java中添加

//    自定义异常处理
    @ExceptionHandler(GuliException.class)
    @ResponseBody//为了返回数据
    public R error(GuliException e) {
        log.error(e.getMessage());
        e.printStackTrace();
        return R.error().code(e.getCode()).message(e.getMsg());
    }

一、日志

1、配置日志级别
日志记录器(Logger)的行为是分等级的。如下表所示:
分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别

#设置日志级别
logging.level.root=WARN

这种方式只能将日志打印在控制台上

二、Logback日志

spring boot内部使用Logback作为日志实现的框架。
Logback和log4j非常相似,如果你对log4j很熟悉,那对logback很快就会得心应手。
logback相对于log4j的一些优点:https://blog.csdn.net/caisini_vc/article/details/48551287
1、配置logback日志
删除application.properties中的日志配置
安装idea彩色日志插件:grep-console
resources 中创建 logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false-->

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="F:/JAVA_MAKE/day04/guli_1010/edu" />

    <!-- 彩色日志 -->
    <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
    <!-- magenta:洋红 -->
    <!-- boldMagenta:粗红-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋红 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>

    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--输出到文件-->

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_info.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_warn.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${log.path}/log_error.log</file>
        <!--日志文件输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文件保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文件只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!--
        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender><logger>仅有一个name属性,
        一个可选的level和一个可选的addtivity属性。
        name:用来指定受此logger约束的某一个包或者具体的某一个类。
        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
              如果未设置此属性,那么当前logger将会继承上级的级别。
    -->

    <!--
        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
        第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
        第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
     -->

    <!--开发环境:打印控制台-->
    <springProfile name="dev">
        <!--可以输出项目中的debug日志,包括mybatis的sql日志-->
        <logger name="com.guli" level="INFO" />

        <!--
            root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
            level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
            可以包含零个或多个appender元素。
        -->
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
        </root>
    </springProfile>


    <!--生产环境:输出到文件-->
    <springProfile name="pro">
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="WARN_FILE" />
        </root>
    </springProfile>

</configuration>

2、将错误日志输出到文件
GlobalExceptionHandler.java 中
类上添加注解@Slf4j

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {……

异常输出语句:log.error(e.getMessage());

//    自定义异常处理
    @ExceptionHandler(GuliException.class)
    @ResponseBody//为了返回数据
    public R error(GuliException e) {
        log.error(e.getMessage());
        e.printStackTrace();
        return R.error().code(e.getCode()).message(e.getMsg());
    }

day03(以后不分day了,太麻烦了)

补充说明,前景提要,非常重要:

框架的第一个入口就是根目录下的index.html,以下是第二个入口

再说一个:在config文件夹下的index.js中

    notifyOnErrors: false,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-

    // Use Eslint Loader?
    // If true, your code will be linted during bundling and
    // linting errors and warnings will be shown in the console.
    useEslint: false,//将这个改为false,默认为true
    // If true, eslint errors and warnings will also be shown in the error overlay
    // in the browser.
    showEslintErrorsInOverlay: false,

而后的dev.env.js文件可以给我们修改访问后端接口的地址

在src目录中:

api:定义调用方法

assets:静态资源

components:额外的插件

icons:相关的图标

router:路由

store:脚本文件

styles:样式文件

utils:工具

views:具体页面

一、后台系统路由实现分析

1、入口文件中调用路由

src/main.js

......
import router from './router' //引入路由模块
......
new Vue({
  el: '#app',
  router, //挂载路由
  store,
  render: h => h(App)
})

2、路由模块中定义路由

src/router/index.js

......
export const constantRouterMap = [
  ......
]

export default new Router({
  ......
  routes: constantRouterMap
})    

二、路由定义

1、复制icon图标

将vue-element-admin/src/icons/svg 中的图标复制到 guli-admin项目中

2、修改路由

修改 src/router/index.js 文件,重新定义constantRouterMap

注意:每个路由的name不能相同

 export const constantRouterMap = [
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },
  { path: '/404', component: () => import('@/views/404'), hidden: true },

  // 首页
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    name: 'Dashboard',
    children: [{
      path: 'dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: '谷粒学院后台首页', icon: 'dashboard' }
    }]
  },

  // 讲师管理
  {
    path: '/edu/teacher',
    component: Layout,
    redirect: '/edu/teacher/list',
    name: 'Teacher',
    meta: { title: '讲师管理', icon: 'peoples' },
    children: [
      {
        path: 'list',
        name: 'EduTeacherList',
        component: () => import('@/views/edu/teacher/list'),
        meta: { title: '讲师列表' }
      },
      {
        path: 'create',
        name: 'EduTeacherCreate',
        component: () => import('@/views/edu/teacher/form'),
        meta: { title: '添加讲师' }
      },
      {
        path: 'edit/:id',
        name: 'EduTeacherEdit',
        component: () => import('@/views/edu/teacher/form'),
        meta: { title: '编辑讲师', noCache: true },
        hidden: true
      }
    ]
  },

  { path: '*', redirect: '/404', hidden: true }
]

3、创建vue组件

在src/views文件夹下创建以下文件夹和文件

img

4、form.vue

<template>
  <div class="app-container">
    讲师表单
  </div>
</template>

5、list.vue

<template>
  <div class="app-container">
    讲师列表
  </div>
</template>

一、项目中的**Easy Mock**

config/dev.env.js 中BASE_API 为项目的easymock地址,目前具有模拟登录、登出、获取用户信息的功能

BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',

登录:/user/login

获取用户信息:/user/info?token=admin

登出:/user/logout

config/dev.env.js,只有一个api地址的配置位置,而我们实际的后端有很多微服务,所以接口地址有很多,

我们可以使用nginx反向代理让不同的api路径分发到不同的api服务器中

二、配置nginx反向代理

1、安装window版的nginx

将nginx-1.12.0.zip解压到开发目录中

如:E:\development\nginx-1.12.0-guli-api

双击nginx.exe运行nginx

访问:localhost

2、配置nginx代理

在Nginx中配置对应的微服务服务器地址即可

注意:最好修改默认的 80端口到81

http {
    server {
        listen       81;
        ......
    },

    ......
    server {

        listen 8201;
        server_name localhost;

        location ~ /edu/ {           
             proxy_pass http://localhost:8101;
        }

        location ~ /user/ {   
             rewrite /(.+)$ /mock/5950a2419adc231f356a6636/vue-admin/$1  break; 
             proxy_pass https://www.easy-mock.com;
        }
    }
}

3、重启nginx

nginx -s reload

4、测试

访问讲师列表接口:http://localhost:8201/admin/edu/teacher

访问获取用户信息接口:http://localhost:8201/user/info?token=admin

三、配置开发环境

1、修改config/dev.env.js

BASE_API: '"http://127.0.0.1:8201"'

2、重启前端程序

修改配置文件后,需要手动重启前端程序一、分页列表

1、定义api

创建文件 src/api/edu/teacher.js

import request from '@/utils/request'

const api_name = '/admin/edu/teacher'
export default {

  getPageList(page, limit, searchObj) {
    return request({
      url: `${api_name}/${page}/${limit}`,
      method: 'get',
      data: searchObj
    })
  }
}

2、初始化vue组件

src/views/edu/teacher/list.vue

<template>
  <div class="app-container">
    讲师列表
  </div>
</template>

<script>
import teacher from '@/api/edu/teacher'
export default {

  data() {// 定义数据
      return {}
  },

  created() { // 当页面加载时获取数据
    this.fetchData()
  },

  methods: {
    fetchData() { // 调用api层获取数据库中的数据
      console.log('加载列表')
    }
  }
}
</script>

3、定义data

  data() {
    return {
      listLoading: true, // 是否显示loading信息
      list: null, // 数据列表
      total: 0, // 总记录数
      page: 1, // 页码
      limit: 10, // 每页记录数
      searchObj: {}// 查询条件
    }
  },

4、定义methods

  methods: {
    fetchData(page = 1) { // 调用api层获取数据库中的数据
      console.log('加载列表')
      this.page = page
      this.listLoading = true
      teacher.getPageList(this.page, this.limit, this.searchObj).then(response => {
        // debugger 设置断点调试
        if (response.success === true) {
          this.list = response.data.rows
          this.total = response.data.total
        }
        this.listLoading = false
      })
    }
  }

5、表格渲染

    <!-- 表格 -->
    <el-table
      v-loading="listLoading"
      :data="list"
      element-loading-text="数据加载中"
      border
      fit
      highlight-current-row>

      <el-table-column
        label="序号"
        width="70"
        align="center">
        <template slot-scope="scope">
          {{ (page - 1) * limit + scope.$index + 1 }}
        </template>
      </el-table-column>

      <el-table-column prop="name" label="名称" width="80" />

      <el-table-column label="头衔" width="80">
        <template slot-scope="scope">
          {{ scope.row.level===1?'高级讲师':'首席讲师' }}
        </template>
      </el-table-column>

      <el-table-column prop="intro" label="资历" />

      <el-table-column prop="gmtCreate" label="添加时间" width="160"/>

      <el-table-column prop="sort" label="排序" width="60" />

      <el-table-column label="操作" width="200" align="center">
        <template slot-scope="scope">
          <router-link :to="'/edu/teacher/edit/'+scope.row.id">
            <el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
          </router-link>
          <el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

6、分页组件

   <!-- 分页 -->
    <el-pagination
      :current-page="page"
      :page-size="limit"
      :total="total"
      style="padding: 30px 0; text-align: center;"
      layout="total, prev, pager, next, jumper"
      @current-change="fetchData"
    />

7、顶部查询表单

注意:

element-ui的 date-picker组件默认绑定的时间值是默认世界标准时间,和中国时间差8小时

设置 value-format=”yyyy-MM-dd HH:mm:ss” 改变绑定的值

    <!--查询表单-->
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item>
        <el-input v-model="searchObj.name" placeholder="讲师名"/>
      </el-form-item>

      <el-form-item>
        <el-select v-model="searchObj.level" clearable placeholder="讲师头衔">
          <el-option :value="1" label="高级讲师"/>
          <el-option :value="2" label="首席讲师"/>
        </el-select>
      </el-form-item>

      <el-form-item label="添加时间">
        <el-date-picker
          v-model="searchObj.begin"
          type="datetime"
          placeholder="选择开始时间"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
        />
      </el-form-item>
      <el-form-item>
        <el-date-picker
          v-model="searchObj.end"
          type="datetime"
          placeholder="选择截止时间"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
        />
      </el-form-item>

      <el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button>
      <el-button type="default" @click="resetData()">清空</el-button>
    </el-form>

清空方法

resetData() {
    this.searchObj = {}
    this.fetchData()
}

8、测试

二、删除

1、定义api

src/api/edu/teacher.js

removeById(teacherId) {
    return request({
        url: `${api_name}/${teacherId}`,
        method: 'delete'
    })
}

2、定义methods

src/views/edu/teacher/list.vue

使用MessageBox 弹框组件

removeDataById(id) {
    // debugger
    // console.log(memberId)
    this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(() => {
        return teacher.removeById(id)
    }).then(() => {
        this.fetchData()
        this.$message({
            type: 'success',
            message: '删除成功!'
        })
    }).catch((response) => { // 失败
        if (response === 'cancel') {
            this.$message({
                type: 'info',
                message: '已取消删除'
            })
        } else {
            this.$message({
                type: 'error',
                message: '删除失败'
            })
        }
    })
}

文章作者: 寒光君
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 寒光君 !
 上一篇
yblog(一) yblog(一)
yblog项目启动 摘要: 1、本次给大家带来的是SpringBoot+SpringCloud+Vue前后端分离开发的分布式、微服务、博客项目,致力于使广大网友尽可能通过这一个项目学到尽可能多的知识,使用当下最流行的技术以及第三方框架组件。
2020-05-20 寒光君
下一篇 
青缃坊 青缃坊
静斋狭轩陋阁,来宾请允我娓娓道来。2020-06-11 晴今天讲个关于“遗憾”的话题。 你说,一辈子有哪些事情是放不下的,若非是对人间无望,就连庙观的“戒尘”①也守着不肯放下的“清规戒律”。 我又是什么,放不下的可真多,今年大二就进入尾声了
2020-05-03 寒光君
  目录