IDEA创建springboot项目

1、新建springboot项目

2、选择版本依赖

3、maven配置

4、yml文件配置

# YML文件的语法
# 1.数据结构   key-value结构
# 2.写法:     key:(空格)value
# 3.层级代码结构,注意缩进 !!!!!
# 4.字符集  文件读取时,默认采用UTF-8编码 可以写中文
# 规则: 命名时最好指定前缀.
server:
  port: 8080
mysql:
  username: root
  password: root

5、动态为属性赋值,编辑JDBCController

/**
 * 说明:
 *  1.将该类交给Spring容器管理
 *  2.SpringMVC负责调用该对象接收用户的请求.
 *  3.将业务处理之后的结果,为页面返回JSON数据.
 *  @ResponseBody作用: 将数据转化为JSON串
 */
@RestController
public class JDBCController {

    //${key} Spring提供的springel表达式 简称为:spel表达式
    //语法: 从spring容器内部获取key,动态为属性赋值.
    @Value("${mysql.username}")
    String username;     // = "root|";
    @Value("${mysql.password}")
    String password;     // = "root";

    @RequestMapping("/getMsg")
    public String getMsg(){

        return "你好数据库:"+ username +password;
    }

}

6、访问:http://localhost:8080/getMsg,浏览器页面显示

7、利用properties文件为属性赋值,编辑mysql.properties

#默认ISO-8859-1 中文必定乱码
mysql.username2=mysql数据库
mysql.password2=你猜猜

8、通过注解为属性赋值,可以指定字符集

/**
 * 说明:
 *  1.将该类交给Spring容器管理
 *  2.SpringMVC负责调用该对象接收用户的请求.
 *  3.将业务处理之后的结果,为页面返回JSON数据.
 *  @ResponseBody作用: 将数据转化为JSON串
 *
 *  propertySource:  value属性指定路径
 *                   encoding属性指定配置文件编码格式
 */
@RestController
@PropertySource(value="classpath:/mysql.properties", encoding = "UTF-8")
public class JDBCController {

    //${key} Spring提供的springel表达式 简称为:spel表达式
    //语法: 从spring容器内部获取key,动态为属性赋值.
    @Value("${mysql.username}")
    String username;     // = "root|";
    @Value("${mysql.password}")
    String password;     // = "root";

    @RequestMapping("/getMsg")
    public String getMsg(){

        return "你好数据库:"+ username +password;
    }


    /**
     * 难点: 如何将pro文件交给Spring容器管理????
     * 解决方案: @PropertySource("xxxxxx/xxx.properties") 指定配置文件交给Spring容器管理
     */
    @Value("${mysql.username2}")
    private String username2;
    @Value("${mysql.password2}")
    private String password2;

    @RequestMapping("/getMsg2")
    public String getMsg2(){

        return "你好数据库:"+ username2 +password2;
    }

}

9、访问:http://localhost:8080/getMsg2,浏览器页面显示

10、springboot高级用法,创建springboot项目

模板,maven

11、编辑pom.xml

<?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.jt</groupId>
    <artifactId>springboot_demo2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--只需要复制 除了坐标之外的文件即可-->
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.1</spring-boot.version>
    </properties>

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

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

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.1</version>
                <configuration>
                    <mainClass>com.jt.SpringbootDemo1Application</mainClass>
                </configuration>
                <!--排除一些指定的配置-->
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

12、yml配置文件

开发 dev: 8080端口       生产 prod: 9000端口

#指定环境的默认配置
spring:
  profiles:
    active: dev
---
#为环境定义名称
server:
  port: 8080
spring:
  config:
    activate:
      on-profile: dev
# 采用---的方式实现环境分割
---
server:
  port: 9000
spring:
  config:
    activate:
      on-profile: prod

13、热部署

在开发阶段,需要频繁的修改配置文件/代码

需求:要求将代码保存之后,程序自动的编译,并且完成tomcat服务的重启

个别IDEA版本可能不生效

        <!--支持热部署 开发阶段有效-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

14、创建pojo,DemoUser

/**
 * 实体对象要求:
 *      1.类名一般与表名关联
 *      2.属性名称一般与字段关联
 *      3.pojo中的属性类型必须为引用类型(包装类型)
 *      4.实体对象必须有get/set方法
 *      5.一般实体对象需要实现序列化接口(规则)
 *          原因: 数据可能跨平台(跨服务器)传输,必须序列化
 */
public class DemoUser implements Serializable {

    private Integer id;
    private String name;
    private Integer age;
    private String sex;

}

15、lombok依赖

lombok可以为POJO实体对象,动态的生成get/set/toString/hashcode/equals等方法,无需程序员手动编辑

        <!--引入插件lombok 自动的set/get/构造方法插件  -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

16、采用注解

@Data  //动态生成get/set/toString/equals等方法
@Accessors(chain = true) //开启链式加载 重写set方法
@NoArgsConstructor  //无参构造
@AllArgsConstructor //有参构造
public class DemoUser implements Serializable {

    private Integer id;
    private String name;
    private Integer age;
    private String sex;

    //方法测试
    public void add(){
        DemoUser user = new DemoUser();
        user.setId(100).setName("aaaa").setAge(18).setSex("女");
    }

}

17、Mybatis依赖

        <!--mybatis依赖包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!--jdbc依赖包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

18、编辑DemoUser

@Data  //动态生成get/set/toString/equals等方法
@Accessors(chain = true) //开启链式加载 重写set方法
@NoArgsConstructor  //无参构造
@AllArgsConstructor //有参构造
public class DemoUser implements Serializable {

    private Integer id;
    private String name;
    private Integer age;
    private String sex;

}

19、编辑mybatis-config.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>

    <!--环境配置标签 default 默认加载的环境 只能写一个   -->
    <environments default="development">

        <!--编辑开发环境  id是环境唯一标识符  -->
        <environment id="development">
            <!--事物管理器  利用jdbc控制事务 -->
            <transactionManager type="JDBC"/>
            <!--mybatis采用数据库链接池的方式整合数据源 -->
            <dataSource type="POOLED">
                <!--高版本数据库驱动 需要添加cj-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&amp;useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;allowMultiQueries=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>

    </environments>

    <!--Mybatis加载Mapper映射文件-->
    <mappers>
        <mapper resource="mybatis/mappers/UserMapper.xml"/>
    </mappers>
</configuration>

20、构建DemoUserMapper接口

/**
 * 说明:
 *      1.根据面向接口开发的思想需要定义一个Mapper接口
 *      2.在接口中可以写接口方法, 谁用谁去实现!!!
 */
public interface DemoUserMapper {
    //1.查询所有的表数据
    public List<DemoUser> findAll();
}

21、编辑DemoUserMapper.xml配置文件

<?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">
<!--xml映射文件 必须与接口一对一绑定
    namespace: 指定需要绑定的接口名称. 不能重复.
 -->
<mapper namespace="com.jt.mapper.DemoUserMapper">

    <!--实现接口中的方法
        id: 需要与接口中的方法绑定. 一般复制粘贴
        resultType: 对象的包路径.
        规则: sql语句不要添加多余的;号  Oracle数据库不能添加;号
    -->
    <select id="findAll" resultType="com.jt.pojo.DemoUser">
        select id,name,age,sex from demo_user
    </select>

    <!-- <insert id=""></insert>
     <update id=""></update>
     <delete id=""></delete>-->

</mapper>

22、编辑测试类TestMybatis,测试mybatis实现数据库查询

    /**
     * 业务说明: 实现mybatis入门案例
     * 步骤:
     *      1.动态生成SqlSessionFactory
     */
    @Test
    public void demo1() throws IOException {
        //指定配置文件地址
        String resource = "mybatis-config.xml";
        //通过IO流 加载指定的配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //动态生成SqlSessionFactory
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        //获取SqlSession  类比 数据库链接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取Mapper接口
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        //获取数据
        List<DemoUser> userList = demoUserMapper.findAll();
        System.out.println(userList);
        //关闭链接
        sqlSession.close();
    }

23、查询结果

24、编辑DemoUserMapper,根据ID查询数据

DemoUser findOne(int id);

25、编辑DemoUserMapper.xml映射文件

    <!--
        parameterType: 参数类型
        mybatis中通过 #{} 获取参数
        resultType: 返回值结果对象
   -->
    <select id="findOne" parameterType="int" resultType="com.jt.pojo.DemoUser">
        select * from demo_user where id = #{id}
    </select>

26、编辑测试类TestMybatis测试

    /***
     *  需求: 根据ID查询数据库记录   id=1的数据
     */
    @Test
    public void testFindOne() throws IOException {
        //指定配置文件地址
        String resource = "mybatis-config.xml";
        //通过IO流 加载指定的配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //动态生成SqlSessionFactory
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //获取接口
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        int id = 1;
        DemoUser demoUser = demoUserMapper.findOne(id);
        System.out.println(demoUser);
        //关闭链接
        sqlSession.close();
    }

27、测试结果

28、@BeforeEach

该注解的作用是在执行@Test方法前调用,是测试方法提供的测试API

编辑TestMybatis2

public class TestMybatis2 {

    //定义公共的属性
    private SqlSessionFactory sqlSessionFactory;
    /**
     * mybatis的核心 SqlSessionFacotry对象
     * @BeforeEach: 测试API中的注解 在执行@Test注解方法时,会提前执行!!!
     */
    @BeforeEach
    public void init() throws IOException {
        //1.指定资源文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testMybatis01(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        List<DemoUser> list = demoUserMapper.findAll();
        System.out.println(list);
        sqlSession.close();
    }

}

29、运行结果

30、根据名称查询,编辑TestMybatis2

    /**
     * 作业:
     *      1. 查询name="王昭君"的用户
     */
    @Test
    public void testFindByName(){
        //保证每个线程都能获取一个链接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        String name = "王昭君";
        //如果不能保证结果唯一,则使用List集合接收数据.
        List<DemoUser> list = demoUserMapper.findByName(name);
        System.out.println(list);
        sqlSession.close();
    }

31、编辑DemoUserMapper.xml

    <!--说明: parameterType其中的类型程序可以根据参数自动判断,所以可以省略不写
            根据名称,动态取值 使用#{}关键字
     -->
    <select id="findByName" resultType="com.jt.pojo.DemoUser">
        select * from demo_user where name = #{name}
    </select>

32、编辑DemoUserMapper

List<DemoUser> findByName(String name);

33、运行结果

34、编辑TestMybatis2,mybatis中参数封装

    /**
     * 需求 :2. 查询sex=女 and age > 18岁
     * 条件 2个
     */
    @Test
    public void testFindBySA(){
        //保证每个线程都能获取一个链接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        //编程习惯: 面向对象
        DemoUser user = new DemoUser();
        user.setAge(18).setSex("女");
        List<DemoUser> list = demoUserMapper.findBySA(user);
        System.out.println(list);
        sqlSession.close();
    }

35、编辑DemoUserMapper

List<DemoUser> findBySA(DemoUser user);

36、编辑DemoUserMapper.xml

    <!--
        查询sex=女 and age > 18岁
        参数: DemoUser user 意图:传递属性的
        规则: 如果传递的参数是对象,则通过#{属性} 可以直接获取数据.
    -->
    <select id="findBySA" resultType="com.jt.pojo.DemoUser">
        select * from demo_user where sex= #{sex} and age > #{age}
    </select>

37、运行结果 

38、编辑TestMybatis2,参数封装的3种常见情景

    /**
     * 需求 :2. 查询sex=女 and age > 18岁
     * 方式1: User对象封装
     */
    @Test
    public void testFindBySA(){
        //保证每个线程都能获取一个链接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        //编程习惯: 面向对象
        DemoUser user = new DemoUser();
        user.setAge(18).setSex("女");
        List<DemoUser> list = demoUserMapper.findBySA(user);
        System.out.println(list);
        sqlSession.close();
    }

    /**
     *    sex=女 and age > 18
     *    方式2: @Param方式封装.
     */
    @Test
    public void testFindBySA2(){
        //保证每个线程都能获取一个链接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        String sex = "女";
        int age = 18;
        List<DemoUser> list = demoUserMapper.findBySA2(sex,age);
        System.out.println(list);
        sqlSession.close();
    }

    /**
     *    sex=女 and age > 18
     *    方式3: map集合封装
     */
    @Test
    public void testFindBySA3(){
        //保证每个线程都能获取一个链接
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        Map<String,Object> map = new HashMap<>();
        map.put("sex","女");
        map.put("age",18);
        List<DemoUser> list = demoUserMapper.findBySA3(map);
        System.out.println(list);
        sqlSession.close();
    }

39、编辑DemoUserMapper.xml

    <!--
        查询sex=女 and age > 18岁
        参数: DemoUser user 意图:传递属性的
        规则: 如果传递的参数是对象,则通过#{属性} 可以直接获取数据.
    -->
    <select id="findBySA" resultType="com.jt.pojo.DemoUser">
        select * from demo_user where sex= #{sex} and age > #{age}
    </select>

    <!--
    如果参数被@Param("sex") String sex修饰
    则#{参数key}即可获取数据
    -->
    <select id="findBySA2" resultType="com.jt.pojo.DemoUser">
        select * from demo_user where sex= #{sex} and age > #{age}
    </select>

    <!--
        Map<String, Object> map
            sex=女  age=18
        规则: 如果参数是一个map集合,则通过#{key}获取数据.
    -->
    <select id="findBySA3" resultType="com.jt.pojo.DemoUser">
        select * from demo_user where sex= #{sex} and age > #{age}
    </select>

40、编辑DemoUserMapper

    List<DemoUser> findBySA(DemoUser user);

    List<DemoUser> findBySA2(@Param("sex") String sex, @Param("age") Integer age);

    List<DemoUser> findBySA3(Map map);

41、编辑TestMybatis2,Mybatis常规CURD操作,新增操作

    /**
     * 需求: 实现用户入库操作
     * 关于事务说明:
     *    mybatis中的"更新"操作,默认事务都是开启的,如果进行更新操作,
     *    则必须提交事务.
     */
    @Test
    public void testSaveUser(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        //数据库主键自增,所以对象的ID可以为null.
        DemoUser user = new DemoUser(null,"宁荣荣",26,"女");
        int rows = demoUserMapper.saveUser(user);
        if(rows > 0){
            System.out.println("影响的行数:"+rows);
            //事务提交
            sqlSession.commit();
        }
        sqlSession.close();
    }

42、编辑DemoUserMapper

int saveUser(DemoUser user);

43、编辑DemoUserMapper.xml

    <!--
       需求: 需要返回影响的行数.
             mybatis执行"更新"操作时,自动的返回影响的行数
    -->
    <insert id="saveUser">
        insert into demo_user value (null,#{name},#{age},#{sex})
    </insert>

44、运行结果

45、查看数据库

46、Mybatis中的转义字符

    xml文件中的转义字符.
            &gt;  > 大于
            &lt;  < 小于
            &amp;  & 号
    说明:如果sql中有大量的转义字符 建议使用转义标签体
    语法: <![CDATA[  xxx内容 报文   ]]>

47、编辑TestMybatis2,查询age> 18 and age< 100 的用户信息

    /**
     * 需求: 查询age> 18 and age< 100 的用户信息.
     * 规则: 如果不能使用对象封装,则一般使用Map集合
     */
    @Test
    public void testSelect01(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        Map<String,Object> map = new HashMap<>();
        map.put("minAge",18);
        map.put("maxAge",100);
        List<DemoUser> userList = demoUserMapper.findByAge(map);
        System.out.println(userList);
        sqlSession.close();
    }

48、编辑DemoUserMapper

List<DemoUser> findByAge(Map<String,Object> map);

49、编辑DemoUserMapper.xml

    <select id="findByAge" resultType="com.jt.pojo.DemoUser">
        <!--select * from demo_user where age > #{minAge} and age &lt; #{maxAge}-->
        <![CDATA[  select * from demo_user where age > #{minAge} and age < #{maxAge}]]>
    </select>

50、运行结果

51、编辑TestMybatis2,Mybatis集合用法,批量删除

    /**
     * 例如: 删除id=232/233/234的数据?
     *  Sql: delete from demo_user where id in (232,233,234)
     * 规则: 如果遇到相同的多个数据,则一般采用集合的方式封装数据.
     * 封装方式:
     *      1. array
     *      2. list
     *      3. map<List>
     */
    @Test
    public void testDeleteIds(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        //将数据封装为数组
        int[] ids = {232,233,234};
        demoUserMapper.deleteIds(ids);
        System.out.println("删除操作成功!!!");
    }

52、编辑DemoUserMapper

void deleteIds(int[] ids);

53、编辑DemoUserMapper.xml

    <!--
        需求: 批量删除多个数据
        难点: 如果使用#{集合}获取的是集合对象的整体.删除无效.
        思路: 将数组拆分为单个数据. 可以通过遍历的方式操作
        语法: mybatis为了参数取值方便,特意封装了遍历的标签 foreach
        关于标签参数说明:
            <foreach collection=""></foreach>
            1.如果传递的参数是数组,     则collection="array"
            2.如果传递的参数是list集合, 则collection="list"
            3.如果传递的参数是Map集合,  则collection="map中的key"


        标签属性说明:
            1.collection 集合的名称
            2.item 每次遍历的数据的形参变量
            3.open 循环的开始标签
            4.close 循环的结束标签
            5.index 循环遍历下标 一般不用
            6.separator 循环遍历的分割符
     -->
    <delete id="deleteIds">
        delete from demo_user where id in (
        <foreach collection="array" item="id" separator=",">
            #{id}
        </foreach>
        )
    </delete>

54、编辑TestMybatis2,Mybatis集合用法,练习list/map的用法

    @Test
    public void testDeleteList(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        List list = new ArrayList();
        list.add(232);
        list.add(233);
        list.add(234);
        demoUserMapper.deleteList(list);
        System.out.println("删除操作成功!!!");
    }

    /*
     * 说明: 有时业务需求导致需要使用map封装list集合
     */
    @Test
    public void testDeleteMap(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        List list = new ArrayList();
        list.add(232);
        list.add(233);
        list.add(234);
        HashMap map = new HashMap();
        map.put("ids",list);
        demoUserMapper.deleteMap(map);
        System.out.println("删除操作成功!!!");
    }

55、编辑DemoUserMapper

    void deleteList(List list);

    void deleteMap(HashMap map);

56、编辑DemoUserMapper.xml

    <!--删除List集合中的数据-->
    <delete id="deleteList">
        delete from demo_user where id in (
        <foreach collection="list" item="id" separator=",">
            #{id}
        </foreach>
        )
    </delete>

    <!--删除List集合中的数据 如何是map,则写map中的key-->
    <delete id="deleteMap">
        delete from demo_user where id in (
        <foreach collection="ids" item="id" separator=",">
            #{id}
        </foreach>
        )
    </delete>

57、编辑TestMybatis2,模糊查询

    /*
        Mybatis作业:
            需求: 查询name中包含"王"的数据.并且按照年龄降序排列
     */
    @Test
    public void findLike(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        String name = "%王%";
        List<DemoUser> list = demoUserMapper.findLike(name);
        System.out.println(list);
        sqlSession.close();
    }

58、编辑DemoUserMapper.xml

    <!--关于模糊查询的说明: 使用%号需要使用""号包裹.
       注意事项: mybatis中的sql 最好小写. 因为不同的系统对于大小写 不敏感.
       键位:  eclipse    ctrl + shift + y  小写
                        ctrl + shift + u  大写/小写
     -->
    <select id="findLike" resultType="com.jt.pojo.DemoUser">
        <!--select * from demo_user where name like "%"#{name}"%" order by age desc -->
        select * from demo_user where name like #{name} order by age desc
    </select>

59、编辑DemoUserMapper

List<DemoUser> findLike(String name);

60、运行结果

61、编辑TestMybatis2,批量更新操作

    /**
     * 作业2: 将name为貂蝉/黄月英/宁荣荣的年龄改为28岁,性别女
     */
    @Test
    public void updateUser(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        Map<String,Object> map = new HashMap<>();
        String[] array = {"貂蝉","黄月英","宁荣荣"};
        map.put("names",array);
        map.put("age",28);
        map.put("sex","女");
        demoUserMapper.updateUser(map);
        sqlSession.close();
    }

62、编辑DemoUserMapper.xml

    <!--批量更新操作-->
    <update id="updateUser">
        update demo_user set age = #{age}, sex = #{sex}
        where name in (
        <foreach collection="names" item="name" separator=",">
            #{name}
        </foreach>
        )
    </update>

63、编辑DemoUserMapper

void updateUser(Map<String,Object> map);

64、编辑mybatis-config.xml,Mybatis简化

<!--核心配置文件-->
<configuration>

    <!--配置别名-->
    <typeAliases>
        <typeAlias type="com.jt.pojo.DemoUser" alias="DemoUser"></typeAlias>
    </typeAliases>

65、编辑DemoUserMapper.xml,可以使用简化写法,代替类型的全路径

<mapper namespace="com.jt.mapper.DemoUserMapper">

    <!--实现接口中的方法
        id: 需要与接口中的方法绑定. 一般复制粘贴
        resultType: 对象的包路径.
        规则: sql语句不要添加多余的;号  Oracle数据库不能添加;号
    -->
    <!--
    <select id="findAll" resultType="com.jt.pojo.DemoUser">-->
    <select id="findAll" resultType="DemoUser">
        select id,name,age,sex from demo_user
    </select>

66、编辑mybatis-config.xml,如果有多个pojo,可以这样配置

<!--核心配置文件-->
<configuration>

    <!-- 配置别名 -->
    <typeAliases>
        <!--别名标签只对某个类有效.-->
        <!--<typeAlias type="com.jt.pojo.DemoUser" alias="DemoUser"></typeAlias>-->

        <!--package 指定的是包路径的信息.-->
        <package name="com.jt.pojo"/>
    </typeAliases>

67、编辑DemoUserMapper.xml

<mapper namespace="com.jt.mapper.DemoUserMapper">
    
    <!--映射原理:
        如果配置文件中定义了包路径,则映射对象时会自动的完成路径的拼接
        resultType="com.jt.pojo.DemoUser"
    -->
    <select id="findAll" resultType="DemoUser">
        select id,name,age,sex from demo_user
    </select>

68、编辑DemoUser,注意新增注解

@Data  //动态生成get/set/toString/equals等方法
@Accessors(chain = true) //开启链式加载 重写set方法
@NoArgsConstructor  //无参构造
@AllArgsConstructor //有参构造
@Alias("DemoUser")
public class DemoUser implements Serializable {

    private Integer id;
    private String name;
    private Integer age;
    private String sex;

}

69、Mybatis简化,sql重复,可以使用sql标签

1. select id,name,age,sex from demo_user where id = 1
2. select id,name,age,sex from demo_user where name = xxx

70、用法

    <!--2.简化Sql标签 -->
    <sql id="demo_user_sql">
        select id,name,age,sex from demo_user
    </sql>

    <!--include 代表包含Sql标签 -->
    <select id="findAll" resultType="DemoUser">
        <include refid="demo_user_sql"/>
    </select>

71、sql标签的说明

优势:
1.使用Sql标签可以节省xml的文件大小.
2.代码的结构相对简单.
弊端:
1.Sql只能抽取公共的Sql语句,局限性稍大.
2.如果大量的使用Sql标签,则代码的可读性差

72、编辑TestMybatis2,Mybatis,动态sql

IF-WHERE用法

    /**
     *   封装DemoUser的对象,根据对象中不为null的属性查询
     */
    @Test
    public void testFindWhere(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        DemoUser demoUser = new DemoUser();
        demoUser.setAge(3000);
        List<DemoUser> list = demoUserMapper.findWhere(demoUser);
        System.out.println(list);
        sqlSession.close();
    }

73、编辑DemoUserMapper.xml

    <!--动态Sql案例
      思路: 如果数据不为null,mybatis才会当做条件
      if标签说明:
          test: 判断的条件 直接写属性即可
      where标签: 去除条件中多余的 and 或者 or的
      说明: if和 where 几乎一起出现.
  -->
    <select id="findWhere" resultType="DemoUser">
        select id,name,age,sex from demo_user
        <where>
            <if test="name != null">name = #{name}</if>
            <if test="age !=null"> and age=#{age}</if>
            <if test="sex !=null"> and sex=#{sex}</if>
        </where>
    </select>

74、编辑DemoUserMapper

List<DemoUser> findWhere(DemoUser demoUser);

75、运行结果

76、编辑TestMybatis2,动态Sql-SET标签

    /**
     * 需求: 根据Id,动态的实现数据的更新.
     */
    @Test
    public void testUpdateSet(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        DemoUser user = new DemoUser();
        user.setId(1).setName("守山大使");
        demoUserMapper.updateUser2(user);
        sqlSession.close();

    }

77、编辑DemoUserMapper

void updateUser2(DemoUser user);

78、编辑DemoUserMapper.xml

    <!--
       规则: 根据对象中不为null的属性当做set条件
       set标签说明: 去除set条件中多余的 ,号
   -->
    <update id="updateUser2">
        update demo_user
        <set>
            <if test="name !=null">name = #{name},</if>
            <if test="age !=null">age = #{age},</if>
            <if test="sex !=null">sex = #{sex}</if>
        </set>
        where
        id = #{id}
    </update>

79、编辑TestMybatis2,动态Sql-choose when otherwise

    /**
     * 需求: 如果存在name则按照name查询,否则按照sex查询.
     */
    @Test
    public void testSelectChoose(){
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        DemoUser user = new DemoUser();
        user.setSex("男");
        List<DemoUser> list = demoUserMapper.selectChoose(user);
        System.out.println(list);
        sqlSession.close();
    }

80、编辑DemoUserMapper.xml

    <!--
       需求: 如果不想将全部的条件当做if的判断.则mybatis提供了分支结构 switch
       语法说明:
             choose:代表分支结构,只有一个条件有效.
             when: 指定判断的条件 和if类似.
             otherwise: 如果上述的条件都不满足时,该行代码有效.
   -->
    <select id="selectChoose" resultType="DemoUser">
        select * from demo_user
        where
        <choose>
            <when test="name !=null">name = #{name}</when>
            <otherwise>sex = #{sex}</otherwise>
        </choose>
    </select>

81、编辑DemoUserMapper

List<DemoUser> selectChoose(DemoUser user);

82、运行结果

83、ResultMap用法

创建表dept

84、编辑Dept

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class Dept implements Serializable {
    //驼峰命名规则
    private Integer deptId;
    private String deptName;
}

85、标签说明

resultType说明:
当结果集中的字段名称,如果与属性的名称一致时,才会实现自动的数据封装
resultMap说明:
当结果集中的字段名称,与对象中的属性不一致时,可以使用resultMap实现自定义的封装.

86、编辑TestMybatis2,测试

    @Test
    public void testFindDept(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
        List<Dept> list = deptMapper.findAll();
        System.out.println(list);
        sqlSession.close();
    }

87、编辑DeptMapper

public interface DeptMapper {

    List<Dept> findAll();

}

88、编辑DeptMapper.xml

<?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.jt.mapper.DeptMapper">

    <!--
        ORM思想: 对象关系映射.
        属性: deptId,deptName
        字段: dept_id,dept_name
        resultType说明:
             当结果集中的字段名称,如果与属性的名称一致时,
             才会实现自动的数据封装.
        resultMap="" 自定义的封装数据的结构
    -->
    <select id="findAll" resultMap="deptRM">
        select * from dept
    </select>
    <!--自定义映射关系的
        语法:
            1.id标签代表主键 (每张表中都会有一个主键)
                1.1.column: 代表结果集中的字段.
                1.2.property: 对象中的属性
            2.result 除了主键之外的配置信息
    -->
    <resultMap id="deptRM" type="Dept">
        <id column="dept_id" property="deptId"/>
        <result column="dept_name" property="deptName"/>
    </resultMap>
</mapper>

89、加一些数据

90、编辑mybatis-config.xml

    <!--Mybatis加载Mapper映射文件-->
    <mappers>
        <mapper resource="mybatis/mappers/DemoUserMapper.xml"/>
        <mapper resource="mybatis/mappers/DeptMapper.xml"/>
    </mappers>


</configuration>

91、运行结果

92、编辑mybatis-config.xml,驼峰映射规则

<!--核心配置文件-->
<configuration>

    <!--Mybatis的核心配置-->
    <settings>
        <!--开启了驼峰映射规则 dept_id 自动映射到 deptId -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

93、Mybatis中的缓存机制

引入缓存,可以有效降低用户访问物理设备的频次,提高用户响应速度

mybatis自身缓存 一级缓存/二级缓存

一级缓存:Mybatis默认开启一级缓存,一级缓存可以在同一个SqlSession对象中查询相同的数据,可以实现数据的共享

编辑TestMybatis2,测试

    /**
     * Mybatis一级缓存: 默认开启
     *      规则: 同一个SqlSession内部有效.
     */
    @Test
    public void cache1(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        List<DemoUser> list1 = demoUserMapper.findAll();
        List<DemoUser> list2 = demoUserMapper.findAll();
        List<DemoUser> list3 = demoUserMapper.findAll();
        System.out.println(list1 == list2);
        System.out.println(list1 == list3);
        System.out.println(list2 == list3);
        sqlSession.close();
    }

94、运行结果 

95、二级缓存

二级缓存mybatis中默认也是开启的,但是需要手动标识

二级缓存可以在同一个SqlSessionFactory内部有效

编辑DemoUserMapper.xml,二级缓存的使用

<mapper namespace="com.jt.mapper.DemoUserMapper">

    <!--应用二级缓存-->
    <cache/>

96、编辑TestMybatis2,测试

    /**
     * 二级缓存说明:
     *     sqlSession查询数据之后,会将缓存信息保存到一级缓存中.但是不会立即将
     *     缓存交给二级缓存保管.如果需要使用二级缓存,则必须将sqlSession业务逻辑执行成功之后关闭.
     */
    @Test
    public void cache2(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper = sqlSession.getMapper(DemoUserMapper.class);
        demoUserMapper.findAll();
        //关闭一级缓存
        sqlSession.close();

        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        DemoUserMapper demoUserMapper2 = sqlSession2.getMapper(DemoUserMapper.class);
        demoUserMapper2.findAll();
        sqlSession2.close();
    }

97、代理对象说明

JDK动态代理
特点:
1.要求被代理者必须实现(有)接口.
2.JDK代理是jdk默认提供的.

CGLIB动态代理
特点:
1.不管被代理者是否有接口,都可以为其创建代理对象. 代理对象是目标对象的子类.
2.cglib需要手动导入jar包
3.spring为了创建代理对象方便,自身自动添加cglib依赖项.

98、三大框架整合

框架概述

Spring框架:Spring为了团队开发将复杂的框架进行整合,使得程序从控制到调用浑然一体,以一种统一的方式进行调用

核心:整合第三方框架

Spring框架核心机制

IOC:
控制反转: 将对象创建的权利交给Spring容器管理,由Spring容器管理对象的生命周期
DI: 依赖注入
创建对象时,如果该对象中有需要依赖的属性,Spring负责为属性赋值
	@RestController
	public class UserController {
		@Autowired
		private UserService userService;
	}
AOP 面向切面编程

99、SpringMVC

该框架的主要的作用,接收用户的请求,之后完成业务处理,最终返回响应给用户

创建项目:模型,springboot项目

100、选择版本信息

101、pom.xml文件

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.jt</groupId>
    <artifactId>springboot_ssm</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_ssm</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.5.6</spring-boot.version>
    </properties>
    <dependencies>
        <!--springBoot整合mvc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--springBoot整合测试方法-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--支持热部署 开发阶段有效-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <!--引入插件lombok 自动的set/get/构造方法插件  -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--mybatis依赖包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!--jdbc依赖包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

    </dependencies>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.1</version>
                <configuration>
                    <mainClass>com.jt.SpringbootDemo1Application</mainClass>
                </configuration>
                <!--排除一些指定的配置-->
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


</project>

102、编辑User

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private Integer id;
    private String name;
    private Integer age;
    private String sex;
}

103、编辑UserMapper

public interface UserMapper {
    //查询demo_user表中的所有数据
    List<User> findAll();
}

104、编辑UserMapper.xml

<?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.jt.mapper.UserMapper">

    <select id="findAll" resultType="com.jt.pojo.User">
        select * from demo_user
    </select>
</mapper>

105、编辑UserService接口及其实现类

public interface UserService {
    //查询user表中的所有的数据
    List<User> findAll();
}
@Service    //将该类交给Spring容器管理
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper; //代理对象 JDK动态代理

    @Override
    public List<User> findAll() {
        //List<User> userList = userMapper.findAll();
        //return userList;
        return userMapper.findAll();
    }
}

106、编辑UserController

@RestController   //@Controller 将该类交给Spring容器管理 +//@ResponseBody 业务返回值时,将数据转化为JSON.
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 需求: 查询全部user表数据
     * 请求类型: get/post/put/delete
     * 路径: /findUser
     * 参数: 无
     * 返回值: List<User>
     */
    @RequestMapping("/getUser")
    public List<User> findUser(){

        return userService.findAll();
    }
    
}

107、编辑核心配置application.yml,数据源配置

1. serverTimezone=GMT%2B8 指定时区 东八区
2. useUnicode=true&characterEncoding=utf8
开启使用Unicode编码,并且指定字符集utf-8
3. autoReconnect=true 断线是否重新链接.
4. &allowMultiQueries=true 是否允许批量操作
#语法: 1.key:(空格)value结构
server:
  port: 8090

#整合数据源
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/jt?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: root

#SpringBoot整合mybatis
mybatis:
  #指定别名包
  type-aliases-package: com.jt.pojo
  #加载指定的xml映射文件
  mapper-locations: classpath:/mybatis/mappers/*.xml
  #开启驼峰映射
  configuration:
    map-underscore-to-camel-case: true

108、启动一下项目,报错

109、解决问题的两种方式,UserMapper接口和启动类

@Mapper  //将该接口交给Spring容器管理
public interface UserMapper {
    //查询demo_user表中的所有数据
    List<User> findAll();
}
@SpringBootApplication
@MapperScan("com.jt.mapper")  //根据包扫描路径,扫描全部的mapper接口文件
public class SpringbootSsmApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootSsmApplication.class, args);
    }

}

110、访问:localhost:8090/getUser,浏览器页面显示

111、RestFul实现参数传递

根据ID查询数据

Get请求类型

url:http://localhost:8090/findUserById?id=1

编辑UserController

    /**
     * 业务: 根据ID查询用户数据.
     * 请求类型: get
     * URL:http://localhost:8090/findUserById?id=1
     * 参数: id=1
     * 返回值: User对象
     * SpringMVC业务规范:
     *      1.接收参数时,必须与用户参数保持一致.
     */
    //@RequestMapping(value = "findUserById",method = RequestMethod.GET)
    @GetMapping("findUserById") //只能接收Get请求类型
    public User findUserById(Integer id){

        return userService.findUserById(id);
    }

112、编辑UserService接口及其实现类

User findUserById(Integer id);
    @Override
    public User findUserById(Integer id) {
        return userMapper.findUserById(id);
    }

113、编辑UserMapper接口和UserMapper.xml映射文件

User findUserById(Integer id);
    <select id="findUserById" resultType="User">
        select * from demo_user where id = #{id}
    </select>

114、访问:localhost:8090/findUserById?id=6,浏览器页面显示

115、什么是Servlet

Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。
狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

概括: Servlet是java后台程序与用户交互的机制(媒介)

编辑UserController

    //http://localhost:8090/findUserByIds?id=1,3,5,6,7
    @GetMapping("/findUserByIds")
    public String findUserByIds(HttpServletRequest request){
        String id = request.getParameter("id");//id串
        String[] idStr = id.split(",");//数字数组
        Integer[] intArray = new Integer[idStr.length];//多少个数字长度的Integer类型的新空数组
        for (int i=0; i<idStr.length; i++){
            intArray[i] = Integer.parseInt(idStr[i]);//对应位置赋值
            System.out.println(intArray[i]);
        }
        return "参数接收成功!!!";
    }

116、访问:localhost:8090/findUserByIds?id=1,3,5,7,9,11,页面和控制台分别显示

 

 117、编辑application.yml,Sql日志输出

#Sql日志文件打印
logging:
  level:
    com.jt.mapper: debug

118、编辑UserController,SpringMVC参数传值说明,简单参数传值

    /**
     * URL:http://localhost:8090/mvc?name="李四"
     * @return
     */
    @GetMapping("/mvc")
    public Object testDemo(String name){

        return "参数名称:"+name;
    }

119、访问:localhost:8090/mvc?name="李四",浏览器页面显示

120、编辑UserController,使用对象接收数据

如果用户传递的数据有多个,则可以使用对象接收

    /**
     * URL:http://localhost:8090/mvc2?name="李四"&age=18&sex="女"
     * 说明: 如果使用对象的方式进行接收,则必须有Setxx方法.
     * @return
     */
    @GetMapping("/mvc2")
    public Object testDemo(User user){

        return "参数名称:"+user;
    }

121、访问:localhost:8090/mvc2?name="牛魔王"&age=44&sex="男",浏览器页面显示

122、编辑UserController,restful业务说明

    /**
     *  更新操作 利用restFul的结构, 根据Id修改数据,修改name/age
     *  URL:http://localhost:8090/user/貂蝉/18/227  PUT
     *  查询: http://localhost:8090/user/18         GET
     *  解析: URL:http://localhost:8090/user/{name}/{age}/{id}
     *
     *  RestFul语法:
     *      1.用户url编辑
     *          1.1 参数与参数之间使用/分割.
     *          1.2 restFul结构顺序一旦确定,不能随意更改
     *          1.3 如果使用restFul结构,请求路径中不能出现动词.隐藏目的.
     *
     *      2.用户规范:
     *          由于restFul结构请求路径都是名词,所以不能区分业务逻辑.
     *          所以采用请求类型,严格区分业务需求.
     *          2.1  GET   查询操作
     *          2.2  POST  新增操作/form表单提交
     *          2.3  PUT   修改操作
     *          2.4  DELETE 删除操作
     *
     *      3.参数接收
     *          1.参数与参数之间使用/分割.
     *          2.参数必须使用{xxx}包裹
     *          3.使用特定的注解@PathVariable("name") String name 接收参数
     *          4.如果接收参数的名称与对象中的属性名称一致,则可以使用对象接收
     */
    @GetMapping("/user/{name}/{age}/{id}")
    public Object restFul(User user){
    
        userService.update(user);
        return "数据更新成功!!!";
    }

123、编辑UserService及其实现类

void update(User user);
    @Override
    public void update(User user) {
        userMapper.update(user);
    }

124、编辑UserMapper

    //@Delete("xxxxx")
    //@Insert("sql语句")
    //@Select("查询操作的sql")
    @Update("update demo_user set name=#{name}, age=#{age} where id=#{id}")
    void update(User user);

125、访问:localhost:8090/user/戴沐白/37/23,页面和数据库分别显示

 

126、前后端调用,vue入门案例

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>hello 入门案例</title>
	</head>
	<body>
		<div id="app">
			<h1>双向数据绑定测试</h1>
			<!-- {{插值表达式}} -->
			<h3>获取数据:{{ msg }}</h3>
		</div>
		
				<!-- 使用步骤:
					 1.导入js.文件
					 2.准备根标签
					 3.创建vue对象,并且实现挂载.
					 4.定义属性.实现数据动态取值
					 知识补充: new Vue({}) 函数式编程
					 
					 关于变量说明:
						1. js中变量定义 早期 var 全局变量 没有作用域.
						2. let有作用域的概念
						3. const 常量定义  不允许修改
						
				 -->
		
		<!-- 引入js -->
		<script src="../js/vue.js"></script>
		<script type="text/javascript">
			const app = new Vue({
				//element 元素
				el: "#app",
				data: {
					msg: "您好  Vue JS"
				}
			})
		</script>
	</body>
</html>

127、HBuilderX上显示

128、点击运行,运行到浏览器,Chrome,浏览器上显示

129、VUE生命周期函数

概念: 生命周期函数,是VUE针对与用户提供的扩展的功能.如果编辑了生命周期函数,则vue对象自动执行,无需手动调用.
生命周期函数种类:
1. 初始化阶段 beforeCreate created beforeMount mounted VUE对象正在的实例化
2. Vue对象的修改 beforeUpdate, updated
3. 对象销毁 beforeDestroy destroyed

130、案例

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>测试vue生命周期函数</title>
	</head>
	<body>
		
		<div id="app">
			<h3 v-text="msg"></h3>
			<button @click="update">更新</button>
			<button @click="destroy">销毁</button>
		</div>
		
		<!--引入js函数类库  -->
		<script src="../js/vue.js"></script>
		<script>
			const app = new Vue({
				el : "#app",
				data : {
					msg: "vue生命周期"
				},
				
				//在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
				beforeCreate(){
					console.log("beforeCreate")
				},
				//在实例创建完成后被立即调用
				created(){
					console.log("created")
				},
				//在挂载开始之前被调用:相关的 render 函数首次被调用。
				beforeMount(){
					console.log("beforeMount")
				},
				//实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。
				mounted(){
					console.log("mounted")	
				},
				//数据更新时调用,发生在虚拟 DOM 打补丁之前
				beforeUpdate(){
					console.log("beforeUpdate")
				},
				//由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
				updated(){
					console.log("updated")
				},
				//实例销毁之前调用。在这一步,实例仍然完全可用
				beforeDestroy(){
					console.log("beforeDestroy")	
				},
				//实例销毁后调用。
				destroyed(){
					console.log("destroyed")
				},
				methods:{
					destroy(){
						this.$destroy()
					},
					update(){
						this.msg="vue"
					}
				}
			})
		</script>
	</body>
</html>

131、运行至浏览器,打开开发者工具,清除刷新,点击页面按钮,页面和开发者工具上分别显示

 

132、前后端调用Axios,Ajax

同步:一个线程依次加载执行,如果数据没有加载完成,则其他数据处于等待的状态

异步:Ajax特点:局部刷新,异步访问    Ajax为什么可以异步:Ajax的设计原理-Ajax引擎

Ajax引擎

用户发起请求,交给Ajax引擎进行处理, 用户可以执行其它的操作.
Ajax引擎接收到用户请求之后, 发起Http请求访问目标服务器.
后台服务器将数据返回给Ajax引擎.
Ajax引擎 将最终获取的数据 通过回调函数的方式 交给用户处理.

133、Axios案例练习

134、启动后台,运行页面,打开开发者工具,刷新,出现错误

 135、解决跨域问题

136、开发者工具页面显示

137、根据id查询数据

			/* 带参数的数据传递 通过?号传递数据*/
			let url1 = "http://localhost:8090/axios/findUserById?id=8"
			axios.get(url1)
					 .then(function(result){
						 console.log(result.data)
					 })

138、编辑AxiosController

@RestController
@CrossOrigin
@RequestMapping("/axios")
public class AxiosController {
    @Autowired
    private UserService userService;

    /**
     * 根据ID查询用户信息
     * URL: http://localhost:8090/axios/findUserById?id=1
     */
    @GetMapping("/findUserById")
    public User findUserById(Integer id){

        return userService.findUserById(id);
    }

139、重启后台,刷新页面,开发者工具页面显示

140、根据name和age查询数据

			/**
			 *  作业2:  根据age/sex查询用户信息
			 * 	url地址: http://localhost:8090/axios/findUserByAS
			 *  参数:    id=1
			 *  返回值:  console.log输出  
			 *  知识点:  根据对象传递参数时,使用params关键字
			 */
			let url2 = "http://localhost:8090/axios/findUserByAS"
			//封装对象
			let user = {
				age: 18,
				sex: "女"
			}
			axios.get(url2,{params : user})
				 .then(function(promise){
					 console.log(promise.data)
				 })

141、编辑AxiosController

    /**
     * 根据age和sex查询数据
     * URL地址:http://localhost:8090/axios/findUserByAS?age=18&sex=%E5%A5%B3
     * 请求类型: get
     * 参数: age/sex
     * 返回值: list<User>
     */
    @GetMapping("/findUserByAS")
    public List<User> findUserByAS(User user){

        return userService.findUserByAS(user);
    }

142、编辑UserService及其实现类

List<User> findUserByAS(User user);
    @Override
    public List<User> findUserByAS(User user) {
        return userMapper.findUserByAS(user);
    }

143、编辑UserMapper

    @Select("select * from demo_user where age=#{age} and sex=#{sex}")
    List<User> findUserByAS(User user);

144、开发者工具页面显示

145、Axios-GET-RestFul

			 /* 
				案例三: 利用restFul实现数据获取
				需求:  根据name/sex查询数据
				模板字符串: 语法ES7的写法 一对反引号进行数据拼接,通过${} 获取变量
			 */	 
			let name = "宁荣荣"
			let sex = "女"
			//let url3 = "http://localhost:8090/axios/user/"+name+"/"+sex
			let url3 = `http://localhost:8090/axios/user/${name}/${sex}`
			axios.get(url3)
				 .then(function(promise){
					 console.log(promise.data)
				 })

146、编辑AxiosController

    /**
     * 业务说明: 接收restFul请求
     * URL:http://localhost:8090/axios/user/${name}/${sex}
     * 参数: name/sex
     * 结果: List<User>
     */
    @GetMapping("/user/{name}/{sex}")
    public List<User> findUserByNS(User user){

        return userService.findUserByNS(user);
    }

147、编辑UserService及其实现类

List<User> findUserByNS(User user);
    @Override
    public List<User> findUserByNS(User user) {
        return userMapper.findUserByNS(user);
    }

148、编辑UserMapper

    @Select("select * from demo_user where name=#{name} and sex=#{sex}")
    List<User> findUserByNS(User user);

149、开发者工具页面显示

150、Axios-POST/PUT,编辑JS

			/**
			 * 业务: 实现post请求入库操作 实现入库用户
			 * 知识点: 
			 * 		1.post请求中的参数 axios.post(url,user)
			 */
			let url = "http://localhost:8090/axios/saveUser"
			let user2 = {
				name: "石昊",
				age: 25,
				sex: "男"
			}
			axios.post(url,user2)
				 .then(function(promise){
					 console.log(promise.data)
				 })

151、编辑AxiosController

    /**
     * 业务:实现用户的新增
     * URL: http://localhost:8090/axios/saveUser
     * 参数: {"name":"石昊","age":25,"sex":"男"} JSON串
     * 返回值: String 新增成功
     * 知识点: post请求传递对象时,传递的数据是一个json串.不能直接为User对象赋值.
     * 解决方案:
     *        1.对象转化为JSON  @ResponseBody
     *        2.JSON转化为对象  @RequestBody
     */
    @PostMapping("/saveUser")
    public String saveUser(@RequestBody User user){
        System.out.println(user);
        userService.saveUser(user);
        return "用户入库成功!!!";
    }

152、编辑UserService及其实现类,编辑UserMapper

void saveUser(User user);
    @Override
    public void saveUser(User user) {
        userMapper.saveUser(user);
    }
    @Insert("insert into demo_user value(null,#{name},#{age},#{sex})")
    void saveUser(User user);

153、重启后台,打开开发者工具页面,清除,刷新,显示

154、查询一下数据库

155、Axios简化操作

			let user2 = {
				name: "火灵儿",
				age: 24,
				sex: "女"
			}	 
			//指定公共的请求前缀
			axios.defaults.baseURL = "http://localhost:8090"
			
			axios.post("/axios/saveUser",user2)
				 .then(function(promise){
					 console.log(promise.data)
				 }) 

156、可以新增成功

157、async-await关键字

			//1.指定请求前缀
			axios.defaults.baseURL = "http://localhost:8090"
			
			/**
			 * 需求: 要求ajax请求在一行完成!
			 * 解决方案: async-await
			 * 语法说明:
			 * 		1.async 必须标识函数方法
			 * 		2.await 必须标识ajax请求
			 *  实际意义:
			 * 		解决了ajax中的头号难题, "回调地狱"问题
			 */
			//1.定义函数
			async function getUser(){
				let promise = await axios.get("/getUser")
				console.log(promise.data)
				
				//解构赋值 固定写法 死记硬背
				let {data: result} = await axios.get("/getUser")
				console.log(result)
			}
			
			//2.调用函数
			getUser()

158、开发者工具页面显示

159、前后端项目调用案例

用户列表展现

用户只要一访问页面,就要发起Ajax请求,访问后端服务器获取用户列表数据

编辑页面

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>用户列表展现案例</title>
	</head>
	<body>
		<div id="app">
			<div align="center">
				<h3 align="center">用户新增</h3><br>
				<p>
					用户名称: <input type="text" name="name"/>
					用户年龄: <input type="text" name="age"/>
					用户性别: <input type="text" name="sex"/>
					<button>新增</button>
				</p>
			</div>
			<hr />
			<div align="center">
				<h3 align="center">用户修改操作</h3><br>
				<p>
						用户ID号: <input type="text" name="id" disabled/>
						用户名称: <input type="text" name="name"/>
						用户年龄: <input type="text" name="age"/>
						用户性别: <input type="text" name="sex"/>
						<button>修改</button>
				</p>
			</div>
			<h1 align="center">用户列表展现案例</h1>
			<table align="center" border="1px" width="80%">
				<tr align="center">
					<td>ID编号</td>
					<td>姓名</td>
					<td>年龄</td>
					<td>性别</td>
					<td>操作</td>
				</tr>
				<tr align="center" v-for="user in userList">
					<td v-text="user.id"></td>
					<td v-text="user.name"></td>
					<td v-text="user.age"></td>
					<td v-text="user.sex"></td>
					<td>
						<button>修改</button>
						<button>删除</button>
					</td>
				</tr>
				
			</table>
		</div>
		
		<script src="../js/axios.js"></script>
		<script src="../js/vue.js"></script>
		<script>
			//为axios执行前缀
			axios.defaults.baseURL="http://localhost:8090"

			const app = new Vue({
				el: "#app",
				data: {
					//3.定义数据列表
					userList: []
				},
				methods: {
					//2.定义获取列表数据的函数
					async getUserList(){
						let {data: result} = await axios.get("/axios/findAll")
						//console.log(result)
						//将ajax返回值传递给Vue
						this.userList = result
					}
				},
				mounted(){
					//1利用生命周期函数调用getUser函数
					this.getUserList()
				}
			})
			
		</script>
	</body>
</html>

160、运行至浏览器,显示

161、编辑AxiosController

    /**
     * 需求:获取user全部列表
     * URL: /axios/findAll
     * 参数: 没有
     * 返回值: List<User>
     */
    @GetMapping("/findAll")
    public List<User> findAll(){

        return userService.findAll();
    }

162、重启,刷新一下页面,显示

163、用户新增,编辑页面

			<div align="center">
				<h3 align="center">用户新增</h3><br>
				<p>
					<!-- 
					用户名称: <input type="text" name="name"/>
					用户年龄: <input type="text" name="age"/>
					用户性别: <input type="text" name="sex"/>
					<button>新增</button> -->
					
					<!-- 看到input框架 第一时间想到双向数据绑定 -->
					用户名称: <input type="text" name="name" v-model="addUser.name"/>
					用户年龄: <input type="text" name="age"  v-model="addUser.age" />
					用户性别: <input type="text" name="sex"  v-model="addUser.sex" />
					<button @click="addUserBtn">新增</button>
				</p>
			</div>

 164、JS封装属性

			const app = new Vue({
				el: "#app",
				data: {
					//3.定义数据列表
					userList: [],
					//指定新增对象
					addUser: {
						name: '',
						age: '',
						sex: ''
					}
				},

165、JS方法实现

					},
					
					async addUserBtn(){
						//将数据实现入库操作
						let {data: result} = await axios.post("axios/saveUser", this.addUser)
						//用户提示
						alert(result)
						//清空原有数据
						this.addUser = {}
						//如果新增成功,应该重新获取列表信息
						this.getUserList()
					}
				},

166、在页面操作,可以新增成功

167、用户修改,数据回显

 168、定义更新对象

169、实现数据回显

170、添加修改点击事件

			<div align="center">
				<h3 align="center">用户修改操作</h3><br>
				<p>
						<!-- 
						用户ID号: <input type="text" name="id" disabled/>
						用户名称: <input type="text" name="name"/>
						用户年龄: <input type="text" name="age"/>
						用户性别: <input type="text" name="sex"/>
						<button>修改</button> -->
						
						用户ID号: <input type="text" name="id"      v-model="updateUser.id" disabled/>
						用户名称: <input type="text" name="name" v-model="updateUser.name"/>
						用户年龄: <input type="text" name="age"    v-model="updateUser.age" />
						用户性别: <input type="text" name="sex"    v-model="updateUser.sex" />
						<button @click="updateUserCommit">修改</button>
				</p>
			</div>

171、修改函数

172、编辑AxiosController

    /**
     * 业务: 实现用户更新
     * URL: /axios/updateUser
     * 参数: json串
     * 返回值: 提示信息
     */
    @PutMapping("updateUser")
    public String updateUser(@RequestBody User user){

        userService.updateUser(user);
        return "更新操作成功!";
    }

173、编辑UserService及其实现类,编辑UserMapper

void updateUser(User user);
    @Override
    public void updateUser(User user) {
        userMapper.updateUser(user);
    }
    @Update("update demo_user set name=#{name},age=#{age},sex=#{sex} where id=#{id}")
    void updateUser(User user);

174、用户删除

				<tr align="center" v-for="user in userList">
					<td v-text="user.id"></td>
					<td v-text="user.name"></td>
					<td v-text="user.age"></td>
					<td v-text="user.sex"></td>
					<td>
						<button @click="updateBtn(user)">修改</button>
						<button @click="deleteBtn(user)">删除</button>
					</td>
				</tr>

175、删除函数

176、编辑AxiosController

    /**
     * 业务:实现用户删除
     * URL:axios/deleteUser'+id
     * 参数:id
     * 返回值:提示信息
     */
    @DeleteMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable Integer id){
        userService.deleteUserById(id);
        return "删除操作成功!";
    }

177、编辑UserService及其实现类,编辑UserMapper

void deleteUserById(Integer id);
    @Override
    public void deleteUserById(Integer id) {
        userMapper.deleteUserById(id);
    }
    @Delete("delete from demo_user where id=#{id}")
    void deleteUserById(Integer id);

178、京淘项目前端项目搭建,node.js安装

179、导入前端项目后的效果

180、后端项目搭建,模板,springboot项目

181、编辑pom.xml文件

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.6</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.jt</groupId>
	<artifactId>jt</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jt</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<spring-boot.version>2.5.6</spring-boot.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

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

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

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.0</version>
		</dependency>

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

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

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<version>2.4.1</version>
				<configuration>
					<mainClass>com.jt.SpringbootDemo1Application</mainClass>
				</configuration>
				<!--排除一些指定的配置-->
				<executions>
					<execution>
						<id>repackage</id>
						<goals>
							<goal>repackage</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

</project>

182、将ssm的src文件导入jt

183、编辑application.yml,修改后端端口号

184、项目环境结构

185、项目测试,访问:localhost:8091/findAll,浏览器显示

 

186、首页跳转说明

当用户访问localhost:8080时,开始访问系统首页

根据路由规则实现跳转,当用户访问/时,重定向发起新请求/login

根据“/login”的请求,跳转到登录组件

187、关于axios引入说明

在核心main.js中引入axios

定义axios请求访问前缀

实现父子组件参数传递

子组件引用axios,通过this.$http引用axios

 

188、完成用户登录,编辑SysResult对象

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class SysResult {

    private Integer status; //200正常 201失败
    private String msg;     //服务器返回提示信息
    private Object data;    //服务器返回业务数据

    /**
     * 说明: SysResult对象是系统的返回值对象,调用的次数较多.
     * 如果每次都需要手动添加200/201 则较为繁琐,能否封装一些方法
     * 简化代码调用????
     * 解决方案: 添加静态方法简化调用!!!
     */
    public static SysResult fail(){
        return new SysResult(201,"服务器调用失败!",null);
    }

    public static SysResult success(){

        return new SysResult(200,"服务器调用成功!",null);
    }

    //重载规则:参数不要耦合,否则会产生歧义.
    public static SysResult success(Object data){

        return new SysResult(200,"服务器调用成功!",data);
    }

    public static SysResult success(String msg,Object data){

        return new SysResult(200,msg,data);
    }

}

189、实现用户登录,编辑UserController

    /**
     * 需求: 根据u/p查询数据库,返回秘钥token
     * URL: /user/login
     * 类型: post
     * 参数: username/password json
     * 返回值: SysResult对象(token)
     */
    @PostMapping("/login")
    public SysResult login(@RequestBody User user){
        String token = userService.findUserByUP(user);
        if(token == null || "".equals(token)){
            //表示用户名和密码错误
            return SysResult.fail();
        }
        //表示用户名和密码正确,返回秘钥信息
        return SysResult.success(token);
    }

190、编辑UserService及其实现类

String findUserByUP(User user);
    //根据username/password查询数据库
    @Override
    public String findUserByUP(User user) {
        //1.将密码加密
        byte[] bytes = user.getPassword().getBytes();
        String md5Pass = DigestUtils.md5DigestAsHex(bytes);
        user.setPassword(md5Pass);
        //2.根据用户名和密文查询数据库
        User userDB = userMapper.findUserByUP(user);
        //3.判断userDB是否有值
        if(userDB == null){
            //查询没有数据.返回null
            return null;
        }
        String token = "秘钥";
        return token;
    }

191、编辑UserMapper

    @Select("select * from user where username=#{username} and password=#{password}")
    User findUserByUP(User user);

192、编辑UserServiceImpl

    //根据username/password查询数据库
    @Override
    public String findUserByUP(User user) {
        //1.将密码加密
        byte[] bytes = user.getPassword().getBytes();
        String md5Pass = DigestUtils.md5DigestAsHex(bytes);
        user.setPassword(md5Pass);
        //2.根据用户名和密文查询数据库
        User userDB = userMapper.findUserByUP(user);
        //3.判断userDB是否有值
        if(userDB == null){
            //查询没有数据.返回null
            return null;
        }
        //秘钥特点: 唯一性,迷惑性  UUID:几乎可以保证唯一性
        return UUID.randomUUID().toString().replace("-","");
    }

193、登录的前端JS的说明

    login () {
      // 获取表单对象之后进行数据校验
      // valid 表示校验的结果 true表示通过  false表示失败
      this.$refs.loginFormRef.validate(async valid => {
        // 如果没有完成校验则直接返回
        if (!valid) return

        // 如果校验成功,则发起ajax请求
        const { data: result } = await this.$http.post('/user/login', this.loginForm)
        if (result.status !== 200) return this.$message.error('用户登录失败')
        this.$message.success('用户登陆成功')

        // 获取用户token信息
        const token = result.data
        window.sessionStorage.setItem('token', token)

        // 用户登录成功之后,跳转到home页面
        this.$router.push('/home')
      })
    }

194、Session和Cookie选择

特点:
1.如果数据需要临时保存,则选用Session, 如果数据需要长时间存储选用Cookie.
2.如果对于安全性要求较高的数据,选用Session,如果安全性要求不高选用Cookie.

问题: 财务系统用户信息选用什么技术保存信息? 选用session.

195、实现系统首页跳转

用户完成登录之后,系统应该跳转到系统首页,用户请求如下

localhost:8080/#/home

编辑路由实现系统首页跳转,index.js

 

196、登录之后,页面效果

197、VUE的路由导航守卫

用户在未经登录的条件下,可以直接访问其他页面,导致系统不安全。如果需要控制该漏洞,则需要在前端进行权限的校验。

校验的规则:

如果用户访问没有token信息,则表示用户没有登录,则需要跳转到登录页面

难点:如何实现每一次的请求都要校验

解决方案:拦截器

198、路由导航守卫(拦截器),编辑index.js

// 定义路由导航守卫 考点: 拦截器
/**
 * 1.遍历的每个路由都会执行回调函数.
 * 2.参数信息: 3个
 *    2.1 to: 请求访问的地址           到哪去
 *    2.2 from: 请求从哪里跳转而来     从哪来
 *    2.3 next: 是一个函数  next() 请求放行
 *                         next("/login") 发起login请求
 */
router.beforeEach((to, from, next) => {
    if(to.path === '/login'){
      //1.直接跳转并且结束代码
      return next()
    }

    // 2.不是访问的登录页面,所以判断用户是否登录. 判断依据token
    let token = window.sessionStorage.getItem('token')

    if(token !== null && token !==''){ //更加安全
      //有值,跳转指定页面
      return next()
    }
    //3.如果数据为空则跳转到登录页面
    next('/login')
})

export default router

199、左侧菜单列表实现,权限层级代码结构

200、前端页面JS分析,生命周期函数,触发ajax请求

201、获取远程服务器数据

202、菜单列表实现,编辑RightsController

@RestController
@CrossOrigin
@RequestMapping("/rights")
public class RightsController {

    @Autowired
    private RightsService rightsService;

    /**
     * 业务说明:  完成菜单列表查询 1-2级
     * URL: /rights/getRightsList
     * 请求方式: get请求
     * 参数: 没有参数
     * 返回值: SysResult(List)
     */
    @GetMapping("getRightsList")
    public SysResult getRightsList(){

        List<Rights> list = rightsService.getRightsList();
        return SysResult.success(list);
    }

}

203、编辑Rights

@Data
@Accessors(chain = true)
public class Rights implements Serializable {

    private Integer id;
    private String name;
    private Integer parentId;
    private String path;
    private Integer level;
    private Date created;
    private Date updated;

    private List<Rights> children;

}

204、编辑RightsService及其实现类

public interface RightsService {

    List<Rights> getRightsList();
    
}
@Service
public class RightsServiceImpl implements RightsService {

    @Autowired
    private RightsMapper rightsMapper;

    @Override
    public List<Rights> getRightsList() {
        return rightsMapper.getRightsList();
    }
}

205、编辑RightsMapper

public interface RightsMapper {
    
    List<Rights> getRightsList();
    
}

206、编辑RightsMapper.xml

<?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.jt.mapper.RightsMapper">

    <select id="getRightsList" resultMap="rightsRM">
        SELECT  p.id,p.name,p.parent_id,p.path,p.level,p.created,p.updated,
                c.id c_id,c.name c_name,c.parent_id c_parent_id,
                c.path c_path,c.level c_level,c.created c_created,
                c.updated c_updated	 FROM
                (SELECT * FROM rights WHERE parent_id = 0) p
                    LEFT JOIN
            rights c
            ON
                p.id = c.parent_id
    </select>

    <!--
    完成左侧菜单列表的数据封装 1-2级
-->
    <resultMap id="rightsRM" type="Rights" autoMapping="true">
        <id property="id" column="id"></id>
        <!--封装一对多数据-->
        <collection property="children" ofType="Rights" javaType="java.util.List">
            <id property="id" column="c_id"/>
            <result property="name" column="c_name"/>
            <result property="parentId" column="c_parent_id"/>
            <result property="path" column="c_path"/>
            <result property="level" column="c_level"/>
            <result property="created" column="c_created"/>
            <result property="updated" column="c_updated"/>
        </collection>
    </resultMap>
</mapper>

207、启动后台服务器,登录系统,浏览器页面显示

208、利用mybatis子查询的方式获取数据(扩展),编辑RightsMapper

public interface RightsMapper {

    List<Rights> getRightsList();

    List<Rights> getRightsList2();

}

209、编辑RightsMapper.xml

    <!--
       利用子查询的方式获取数据
       弊端: 查询的效率低
    -->
    <select id="getRightsList2" resultMap="childrenRM">
        select * from rights where parent_id=0
    </select>

    <resultMap id="childrenRM" type="Rights" autoMapping="true">
        <id property="id" column="id"></id>
        <!--一对多封装  column="子查询的条件中传递的参数"-->
        <collection property="children" ofType="Rights"
                    select="selectRightsByParentId" column="id"/>
    </resultMap>

    <!--单表查询子结果-->
    <select id="selectRightsByParentId" resultType="Rights">
        SELECT * FROM rights WHERE parent_id=#{id}
    </select>

210、用户模块实现,实现子级路由跳转

路由填充位设定,在Home.vue中定义路由占位符

211、实现父子关系的路由映射,通过children属性实现父子组件跳转功能

212、页面效果

213、用户列表展现,页面JS分析,生命周期函数,user.vue

214、获取列表页面的JS

215、编辑分页对象PageResult

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class PageResult {

    private String query;       //查询数据
    private Integer pageNum;    //页数
    private Integer pageSize;   //条数
    private Long total;         //总数
    private Object rows;        //查询的结果

}

216、编辑UserController

    /**
     * 需求: 利用分页查询用户信息
     * URL:  /user/list
     * 参数: http://localhost:8091/user/list?query=查询关键字&pageNum=1&pageSize=10
     * 返回值: SysResult(pageResult)
     */
    @GetMapping("/list")
    public SysResult getUserListByPage(PageResult pageResult){

        pageResult = userService.getUserListByPage(pageResult);
        return SysResult.success(pageResult);
    }

217、编辑UserService及其实现类

public interface UserService {

    List<User> findAll();

    String findUserByUP(User user);

    PageResult getUserListByPage(PageResult pageResult);

}
    /**
     * 分页Sql:  每页10条
     *      select * from user limit 起始位置,查询条数
     * 第一页:
     *      select * from user limit 0,10  0-9 含头不含尾
     * 第二页:
     *      select * from user limit 10,10
     * 第三页:
     *      select * from user limit 20,10
     * 第N页:
     *      select * from user limit (页数-1)条数,条数
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getUserListByPage(PageResult pageResult) {
        //1.总数
        long total = userMapper.findTotal();
        //2.分页结果
        int size = pageResult.getPageSize();    //条数
        int start = (pageResult.getPageNum()-1) * size; //起始位置
        String query = pageResult.getQuery();   //查询条件
        //查询分页数据
        List<User> userList = userMapper.findUserListByPage(start,size,query);
        //将返回值结果进行封装
        return pageResult.setTotal(total).setRows(userList);
    }

218、编辑UserMapper

public interface UserMapper {

    //查询demo_user表中的所有数据
    List<User> findAll();

    @Select("select * from user where username=#{username} and password=#{password}")
    User findUserByUP(User user);

    @Select("select count(1) from user")
    long findTotal();

    List<User> findUserListByPage(@Param("start") int start,
                                  @Param("size") int size,
                                  @Param("query") String query);
}

219、编辑UserMapper.xml

    <!--需求:query有参数 则添加like关键字
      如果数据不等于null并且同时不等于''串时,条件生效 &&
    -->
    <select id="findUserListByPage" resultType="User">
        SELECT * FROM USER
        <where>
            <!--<if test="query !=null &amp;&amp; query !=''" >username LIKE "%"#{query}"%"</if>-->
            <if test="query !=null and query !=''" >username LIKE "%"#{query}"%"</if>
        </where>
        LIMIT #{start},#{size}
    </select>

220、页面效果

221、用户状态修改,当点击开关,则实现状态信息的修改,status=true/false

222、请求路径,开发者工具

 

 223、页面JS分析

224、添加修改事件

225、编辑UserController

    /**
     * 需求: 根据Id修改状态
     * URL:  /user/status/{id}/{status}  restFul
     * 参数:  id/status
     * 请求方式: put
     * 返回值: SysResult对象
     */
    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatusById(User user){

        userService.updateStatusById(user);
        return SysResult.success();
    }

226、编辑UserService及其实现类

public interface UserService {

    List<User> findAll();

    String findUserByUP(User user);

    PageResult getUserListByPage(PageResult pageResult);

    void updateStatusById(User user);
}
    @Override
    public void updateStatusById(User user) {
        userMapper.updateStatusById(user);
    }

227、编辑UserMapper

    @Update("update user set status = #{status} where id = #{id}")
    void updateStatusById(User user);

228、用户新增,页面弹出框效果

    <!-- 编辑用户新增对话框 visible.sync 控制对话框的显示-->
    <el-dialog title="添加用户" :visible.sync="dialogVisible" width="50%" @close="closeDialog">

      <!-- 定义用户提交表单数据-->
      <el-form :model="addUserModel" :rules="rules" ref="addUserRef" label-width="100px" class="demo-ruleForm">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="addUserModel.username"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password">
          <el-input v-model="addUserModel.password" type="password"></el-input>
        </el-form-item>
        <el-form-item label="密码确认" prop="password2">
          <el-input v-model="addUserModel.password2" type="password"></el-input>
        </el-form-item>
        <el-form-item label="电话" prop="phone">
          <el-input v-model="addUserModel.phone"></el-input>
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="addUserModel.email"></el-input>
        </el-form-item>
      </el-form>

      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addUserBtn">确 定</el-button>
      </span>
    </el-dialog>

229、页面JS分析,点击确定事件

230、函数说明

231、编辑UserController

    /**
     *  用户新增
     *  URL:  /user/addUser
     *  请求方式: post
     *  参数:  JS对象经过浏览器解析变为JSON串
     *        {"username":"111","password":"222","password2":"222","email":"22@qq.com","phone":"13111111111"}
     *  返回值: SysResult对象
     *      对象转化为JSON @ResponseBody
     *      JSON转化为对象 @RequestBody  规则
     */
    @PostMapping("/addUser")
    public SysResult saveUser(@RequestBody User user){

        userService.saveUser(user);
        return SysResult.success();
    }

232、编辑UserService及其实现类

public interface UserService {

    List<User> findAll();

    String findUserByUP(User user);

    PageResult getUserListByPage(PageResult pageResult);

    void updateStatusById(User user);

    void saveUser(User user);
}
    /**
     *   业务:实现业务数据封装
     *   1.密码加密
     *   2.设定默认状态
     *   3.设定默认时间
     */
    @Override
    public void saveUser(User user) {
        String password = user.getPassword();
        String MD5Pass = DigestUtils.md5DigestAsHex(password.getBytes());
        //获取当前时间
        Date date = new Date();
        user.setPassword(MD5Pass).setStatus(true).setCreated(date).setUpdated(date);
        userMapper.saveUser(user);
    }

233、编辑UserMapper

public interface UserMapper {

    //查询demo_user表中的所有数据
    List<User> findAll();

    @Select("select * from user where username=#{username} and password=#{password}")
    User findUserByUP(User user);

    @Select("select count(1) from user")
    long findTotal();

    List<User> findUserListByPage(@Param("start") int start,
                                  @Param("size") int size,
                                  @Param("query") String query);

    @Update("update user set status = #{status} where id = #{id}")
    void updateStatusById(User user);

    void saveUser(User user);
}

234、编辑UserMapper.xml

    <!--实现user入库操作-->
    <insert id="saveUser">
        insert into user value (null,#{username},#{password},#{phone},#{email},#{status},#{created},#{updated})
    </insert>

235、用户修改数据回显,页面JS

236、根据id获取数据

237、页面JS函数

238、编辑UserController

    /**
     * 业务分析:根据用户ID查询数据库
     * URL地址:"/user/"+user.id
     * 请求方式:get
     * 返回值:SysResult对象
     */
    @GetMapping("/{id}")
    public SysResult findUserById(@PathVariable Integer id){

        User user = userService.findUserById(id);
        return SysResult.success(user);
    }

239、编辑UserService及其实现类

User findUserById(Integer id);
    @Override
    public User findUserById(Integer id) {
        return userMapper.findUserById(id);
    }

240、编辑UserMapper

    @Select("select * from user where id=#{id}")
    User findUserById(Integer id);

241、页面效果

242、用户修改操作,页面JS分析,修改按钮说明

243、 编辑页面JS

244、编辑UserController

    /**
     * 用户修改操作
     * URL:  /user/updateUser
     * 参数:  User(id/phone/email) JSON串
     * 请求方式: put
     * 返回值: SysResult对象
     */
    @PutMapping("/updateUser")
    public SysResult updateUser(@RequestBody User user){

        userService.updateUser(user);
        return SysResult.success();
    }

245、编辑UserService及其实现类

void updateUser(User user);
    @Override
    public void updateUser(User user) {
        //设定当前时间
        user.setUpdated(new Date());
        userMapper.updateUser(user);
    }

246、编辑UserMapper

    @Update("update user set phone=#{phone},email=#{email},updated=#{updated} where id=#{id}")
    void updateUser(User user);

247、用户删除操作,编辑页面JS,删除按钮说明

248、删除JS函数

249、 编辑UserController

    /**
     * 删除数据
     * URL: /user/{id}
     * 参数: id
     * 请求方式: delete
     * 返回值: SysResult对象
     */
    @DeleteMapping("/{id}")
    public SysResult deleteById(@PathVariable Integer id){
        //防止与MP方法冲突 业务方法最好添加业务名称
        userService.deleteUserById(id);
        return SysResult.success();
    }

250、编辑UserService及其实现类

void deleteUserById(Integer id);
    @Override
    public void deleteUserById(Integer id) {
        userMapper.deleteUserById(id);
    }

251、编辑UserMapper

    @Delete("delete from user where id=#{id}")
    void deleteUserById(Integer id);

252、全局异常处理

当后台服务器发生异常时,前端用户无法获取异常信息,用户体验差

如果业务代码中添加了大量的try-catch语句,则会导致业务的复杂度变高,不便于维护

Spring的全局异常处理机制

//1.标识该类是全局异常处理机制.返回值都是JSON串
//  通知: AOP中的技术,解决特定问题的
//  特点: 该异常处理机制,只拦截Controller层抛出的异常
@RestControllerAdvice
public class SystemExe {

    /**
     * 说明:  需要为全局异常定义一个方法.
     * 要求:  返回的统一的业务数据 SysResult
     * 拦截:  指定遇到某种异常实现AOP处理.
     * 注意事项: 在业务方法中不要随意添加try-catch
     */
    @ExceptionHandler({RuntimeException.class})
    public SysResult fail(Exception e){
        e.printStackTrace();
        return SysResult.fail();
    }

}

253、Spring中事务控制

事务特性

原子性: 事务要么同时成功,要么同时失败(不可分割)
隔离性: 多个事务之间相互独立,互补干扰
一致性: 保证数据的一致 (脏读/幻读/不可重复读)
持久性: 一旦事务提交,就应该持久化保存.

254、Spring添加事务@Transactional,UserServiceImpl

    /**
     *  问题: 数据是否真的被删除?????
     *  解决方案: @Transactional
     *  作用:
     *      1.默认条件下,只拦截运行时异常
     *      2.rollbackFor: 指定异常的类型回滚 rollbackFor = RuntimeException.class
     *      3.noRollbackFor: 指定异常不回滚  noRollbackFor = RuntimeException.class
     */
    @Transactional
    @Override
    public void deleteUserById(Integer id) {
        userMapper.deleteUserById(id);
    }

255、MybatisPlus,准备新测试环境,新的项目,模板,springboot

256、项目结构 

257、ORM知识回顾

核心概念: 对象关系映射,以对象的方式操作数据库.
mybatis回顾: Mybatis是半自动化的ORM映射框架.
半自动: Sql是自己手写的,但是结果集映射是自动的.
MybatisPlus: 全自动的ORM映射框架,对Mybatis的扩展.

258、MP入门案例,导入jar包

        <!--mybatis依赖包-->
        <!-- <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
             <version>2.2.0</version>
         </dependency>
        -->
        <!--MP内部已经加载了mybatis,所以无需再次添加jar包-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

259、编辑POJO对象

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@TableName("demo_user") //对象与表名映射
public class User implements Serializable {

    /**
     * 规则:
     *  1.如果数据库中的字段与表中的属性名称一致,则可以省略不写
     *  2.如果对象名称与表名一致,则名称可以省略
     */
    @TableId(type = IdType.AUTO)//主键自增
    private Integer id;
    private String name;
    //@TableField("age") //实现属性与字段映射
    private Integer age;
    private String sex;
}

260、编辑UserMapper

public interface UserMapper extends BaseMapper<User> {

    List<User> findAll();

}

261、SpringBoot整合MybatisPlus

MP:是Mybatis的增强版本,只做增强不做改变

编辑application.yml

#SpringBoot整合mybatisPlus
mybatis-plus:
  #指定别名包
  type-aliases-package: com.jt.pojo
  #加载指定的xml映射文件
  mapper-locations: classpath:/mybatis/mappers/*.xml
  #开启驼峰映射
  configuration:
    map-underscore-to-camel-case: true

262、MP入门案例,编辑测试类SpringbootSsmApplicationTests

//效果:只要@Test测试方法执行,则整个Spring容器启动,可以根据自身的需要实现依赖注入
//注意事项:  该注解只能在测试类中使用.
//         测试类的包路径必须在主启动类的同包及子包中编辑.
@SpringBootTest
class SpringbootSsmApplicationTests {

    @Autowired
    private UserMapper userMapper;

    /**
     * MP入门案例
     */
    @Test
    public void insertUser() {
        User user = new User();
        user.setId(null).setName("MybatisPlus").setAge(10).setSex("男");
        userMapper.insert(user);
    }

}

263、查看数据库

264、 MybatisPlus工作原理

1. 用户调用接口方法 userMapper.insert(User)方法
2. 根据UserMapper的接口找到父级接口BaseMapper<T>
3. 根据父级接口动态获取当前接口的泛型对象T
4. 根据泛型T 获取指定的注解@TableName("demo_user"),之后获取表名demo_user
5. 根据泛型对象T,获取其中的属性,之后再找到属性的注解@TableField("id"),之后再次获取注解的值, 即字段名称.
6. 根据字段名称,获取对应属性的值.
7. 根据Sql拼接 形成最终的可以执行的Sql.
8. MP将生成的Sql交给Mybatis执行入库操作.

265、MybatisPlus API介绍,编辑测试类,根据id查询数据

    /**
     * 学习技巧: MP设计思想!!!!  对象
     * 查询Id=1的用户
     */
    @Test
    public void selectById() {
        int id = 271;
        User user = userMapper.selectById(id);
        System.out.println(user);
    }

266、编辑测试类,根据name和sex查询数据

    /**
     * 查询name="火灵儿",sex="女"的用户
     * 规则: 根据对象中不为null的属性进行业务操作
     * 语法:
     *      1.QueryWrapper条件构造器 动态拼接where条件
     *      2.默认的关系连接符 and
     * 例子:
     *      select * from demo_user where xx=xx and xx=xx
     */
    @Test
    public void selectByNS() {
        User user = new User();
        user.setName("火灵儿").setSex("女");
        QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
        List<User> list = userMapper.selectList(queryWrapper);
        System.out.println(list);
    }

267、编辑测试类,利用queryWrapper查询数据

    /**
     * 查询name="比比东",sex="女"的用户
     * 方式2: 利用条件构造器,构建条件
     * 说明:
     *     1. eq =
     *     2. gt >
     *     3. lt >
     *     4. ge >=
     *     5. le <=
     *     6. ne <>
     */
    @Test
    public void selectByNS2() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name","比比东").eq("sex","女");
        List<User> list = userMapper.selectList(queryWrapper);
        System.out.println(list);
    }

    /**
     * 需求: 查询age>18岁的用户,并且sex=男.
     */
    @Test
    public void selectByAS() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("age",18).eq("sex","男");
        List<User> list = userMapper.selectList(queryWrapper);
        System.out.println(list);
    }

268、编辑测试类,like关键字

    /**
     * 需求: 查询name中包含"君",性别="女"
     * Sql:  where name like "%君%"
     * 需求2: 查询name中以"君"结尾的,性别="女"  like "%君"
     */
    @Test
    public void selectLike() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //queryWrapper.like("name","君")   //"%君%"
        queryWrapper.likeLeft("name","君") //"%君"
                .eq("sex","女");
        List<User> list = userMapper.selectList(queryWrapper);
        System.out.println(list);
    }

269、编辑测试类,in关键字

    /**
     * 需求: 查询id=1,3,4,5的数据 并且按照年龄降序排列
     * 规则: 基本类型有没有方法? 所以使用包装类型
     *      面向对象开发
     */
    @Test
    public void selectIds() {
        Integer[] ids = {1,3,4,5};
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("id",ids)
                .orderByDesc("age");
        List<User> list = userMapper.selectList(queryWrapper);
        System.out.println(list);
    }

270、编辑测试类,查询第一列字段

    /**
     * 需求:
     *  只想获取第一列数据(主键),sex="女"
     * 用法: .selectObjs(queryWrapper);
     * 实际用途: 做关联查询时可以使用
     */
    @Test
    public void selectObjs() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("sex","女");
        List list = userMapper.selectObjs(queryWrapper);
        System.out.println(list);
    }

271、编辑测试类,动态sql实现

    /**
     * 说明: 根据不为null的属性当做where 动态sql实现
     * 需求: 查询age>18岁,sex=女的用户
     * 参数说明:
     *  1. boolean condition, true时,当前的条件才会成立
     *                        false  该条件不拼接.
     *  2.R column 字段信息
     *  3.Object val 值
     * 判断字符串API:
     *      Spring提供的API StringUtils.hasLength(sex);
     */
    @Test
    public void selectList2() {
        Integer age = 20;
        String  sex = "女";
        //boolean flag = sex !=null && "".equals(sex);
        boolean flag = StringUtils.hasLength(sex);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt(age!=null, "age",age )
                .eq(flag,"sex",sex);
        List<User> list = userMapper.selectList(queryWrapper);
        System.out.println(list);
    }

272、商品分类实现,item_cat表

273、项目改造,导入jar包

		<!--MP内部已经加载了mybatis,所以无需再次添加jar包-->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.4.3</version>
		</dependency>

274、编辑ItemCat

@Data
@Accessors(chain = true)
@TableName("item_cat")
public class ItemCat extends BasePojo {

    @TableId(type = IdType.AUTO) //主键自增
    private Integer id;         //定义主键
    private Integer parentId;   //定义父级菜单
    private String name;        //分类名称
    private Boolean status;     //分类状态 0 停用 1 正常
    private Integer level;      //商品分类等级  1 2 3
    
    @TableField(exist = false)      //属性sql不负责操作
    private List<ItemCat> children; //业务属性

}

275、编辑ItemCatMapper

public interface ItemCatMapper extends BaseMapper<ItemCat> {
    //CURD操作如果没有特殊需求可以省略
    //如果没有sql的需求,则xml映射文件可以简化
}

276、编辑application.yml

#SpringBoot整合MP
mybatis-plus:
  #指定别名包
  type-aliases-package: com.jt.pojo
  #加载指定的xml映射文件
  mapper-locations: classpath:/mybatis/mappers/*.xml
  #开启驼峰映射
  configuration:
    map-underscore-to-camel-case: true

277、项目结构

278、商品分类页面跳转

279、商品分类列表展现,页面分析,生命周期函数,ItemCat.vue

 

280、调用业务函数

281、编辑ItemCatController

@RestController
@CrossOrigin
@RequestMapping("/itemCat")
public class ItemCatController {

    @Autowired
    private ItemCatService itemCatService;

    /**
     * 需求: 查询3级分类数据信息
     * 类型: get
     * URL: /itemCat/findItemCatList/{level}
     * 参数: level
     * 返回值: SysResult(list)
     */
    @GetMapping("/findItemCatList/{level}")
    public SysResult findItemCatList(@PathVariable Integer level){

        List<ItemCat> list = itemCatService.findItemCatList(level);
        return SysResult.success(list);
    }

}

282、编辑ItemCatService及其实现类(常规写法-效率低)

public interface ItemCatService {
    
    List<ItemCat> findItemCatList(Integer level);
    
}
@Service
public class ItemCatServiceImpl implements ItemCatService {

    @Autowired
    private ItemCatMapper itemCatMapper;

    /**
     * 弊端: 由于多次循环遍历 查询数据库,导致数据库查询次数太多效率极低.
     * 思路:
     *      1.刚才的业务逻辑梳理
     *      2.如何优化?????   提高效率
     * @param level
     * @return
     */
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        //查询一级商品分类信息
        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id",0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
        //查询二级商品分类信息
        for(ItemCat oneItemCat: oneList){
            //1.为了复用条件构造器 将之前的数据清空
            queryWrapper.clear();
            //查询二级数据 parent_id = 一级ID
            queryWrapper.eq("parent_id",oneItemCat.getId());
            List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
            //遍历二级列表 查询三级数据,封装数据返回
            oneItemCat.setChildren(twoList);
        }
        return oneList;
    }
}

283、商品分类页面显示

284、商品分类列表优化,Map封装策略

遍历数据,如果该数据的父级不存在,准备list集合,将自己作为第一个元素

遍历数据,如果该数据的父级存在,获取list集合,将自己追加到其中

封装Map集合,编辑ItemCatServiceImpl

    /**
     * 优化手段:
     *      思路:获取所有的数据库记录,之后按照父子级关系进行封装
     *      数据结构: Map<k,v>
     *               Map<parentId,List当前父级的子级信息(不嵌套)>
     *      例子:     Map<0,List[{id=1,name="xx",children=null}.....]>
     *
     * 封装数据规则:
     *      1.遍历所有的数据.
     *      2.获取parentId
     *      3.判断parentId是否存在,之后实现数据封装
     */
    public Map<Integer,List<ItemCat>> getMap(){
        Map<Integer,List<ItemCat>> map = new HashMap<>();
        //查询所有的数据库记录
        List<ItemCat> list = itemCatMapper.selectList(null);
        //1.遍历数据
        for(ItemCat itemCat:list){
            int parentId = itemCat.getParentId();
            if(map.containsKey(parentId)){
                //表示数据存在,将自己追加
                map.get(parentId).add(itemCat);
            }else{
                //key不存在, 定义list集合,将自己作为第一个元素追加
                List<ItemCat> childrenList = new ArrayList<>();
                childrenList.add(itemCat);
                //将数据保存到map集合中
                map.put(parentId,childrenList);
            }
        }
        return map;
    }

285、获取二级数据信息

    //该方法获取1-2级数据信息
    public List<ItemCat> getTwoList(Map<Integer,List<ItemCat>> map){
        //1.先查询一级菜单数据
        List<ItemCat> oneList = map.get(0);
        //2.遍历每个一级菜单去封装二级数据
        for(ItemCat oneItemCat : oneList){
            //parent_id = 一级ID
            int parentId = oneItemCat.getId();
            //查询二级数据
            List<ItemCat> twoList = map.get(parentId);
            //将数据进行封装
            oneItemCat.setChildren(twoList);
        }
        //返回一级数据
        return oneList;
    }

286、商品分类全部数据获取

@Service
public class ItemCatServiceImpl implements ItemCatService {

    @Autowired
    private ItemCatMapper itemCatMapper;

    /**
     * 弊端: 由于多次循环遍历 查询数据库,导致数据库查询次数太多效率极低.
     * 思路:
     *      1.刚才的业务逻辑梳理
     *      2.如何优化?????   提高效率
     * @param level
     * @return
     */
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        //查询一级商品分类信息
        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id",0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
        //查询二级商品分类信息
        for(ItemCat oneItemCat: oneList){
            //1.为了复用条件构造器 将之前的数据清空
            queryWrapper.clear();
            //查询二级数据 parent_id = 一级ID
            queryWrapper.eq("parent_id",oneItemCat.getId());
            List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
            //遍历二级列表 查询三级数据,封装数据返回
            oneItemCat.setChildren(twoList);
        }
        return oneList;
    }


    /**
     * 优化手段:
     *      思路:获取所有的数据库记录,之后按照父子级关系进行封装
     *      数据结构: Map<k,v>
     *               Map<parentId,List当前父级的子级信息(不嵌套)>
     *      例子:     Map<0,List[{id=1,name="xx",children=null}.....]>
     *
     * 封装数据规则:
     *      1.遍历所有的数据.
     *      2.获取parentId
     *      3.判断parentId是否存在,之后实现数据封装
     */
    public Map<Integer,List<ItemCat>> getMap(){
        Map<Integer,List<ItemCat>> map = new HashMap<>();
        //查询所有的数据库记录
        List<ItemCat> list = itemCatMapper.selectList(null);
        //1.遍历数据
        for(ItemCat itemCat:list){
            int parentId = itemCat.getParentId();
            if(map.containsKey(parentId)){
                //表示数据存在,将自己追加
                map.get(parentId).add(itemCat);
            }else{
                //key不存在, 定义list集合,将自己作为第一个元素追加
                List<ItemCat> childrenList = new ArrayList<>();
                childrenList.add(itemCat);
                //将数据保存到map集合中
                map.put(parentId,childrenList);
            }
        }
        return map;
    }


    //该方法获取1-2级数据信息
    public List<ItemCat> getTwoList(Map<Integer,List<ItemCat>> map){
        //1.先查询一级菜单数据
        List<ItemCat> oneList = map.get(0);
        //2.遍历每个一级菜单去封装二级数据
        for(ItemCat oneItemCat : oneList){
            //parent_id = 一级ID
            int parentId = oneItemCat.getId();
            //查询二级数据
            List<ItemCat> twoList = map.get(parentId);
            //将数据进行封装
            oneItemCat.setChildren(twoList);
        }
        //返回一级数据
        return oneList;
    }


    /**
     * 实现思路:
     *      1. 获取二级分类列表信息
     *      2. 遍历一级菜单,获取二级数据
     *      3. 根据二级菜单查询三级数据   防止二级数据为null的现象
     *      4. 将三级数据封装到二级中
     * @param map
     * @return
     */
    public List<ItemCat> getThreeList(Map<Integer,List<ItemCat>> map){
        //1.获取1-2数据信息  包含了2级的children
        List<ItemCat> oneList = getTwoList(map);
        //2.编辑一级数据,获取二级数据
        for(ItemCat oneItemCat : oneList){
            List<ItemCat> twoList = oneItemCat.getChildren();
            if(twoList == null || twoList.size()==0){
                //跳过本地循环,进入下一次循环
                continue;
            }
            //3.遍历二级数据,查询三级信息
            for (ItemCat twoItemCat : twoList){
                //查询三级  parentId = 二级ID
                int parentId = twoItemCat.getId();
                List<ItemCat> threeList = map.get(parentId);
                //将三级封装到二级中
                twoItemCat.setChildren(threeList);
            }
        }
        return oneList;
    }


    /**
     * 如果使用常规的写法 耗时: 900毫秒
     *                 经过优化: 20毫秒
     *  CURD: 好代码!!!
     * @param level
     * @return
     */
    @Override
    public List<ItemCat> findItemCatList2(Integer level) {
        long startTime = System.currentTimeMillis();
        //获取所有集合数据
        Map<Integer,List<ItemCat>> map = getMap();
        if(level == 1){
            //1.一级商品分类信息
            return map.get(0);
        }
        //获取一级菜单和二级菜单
        if(level == 2){
            return getTwoList(map);
        }

        //获取三级菜单数据 1-2-3
        List<ItemCat> allList = getThreeList(map);
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+(endTime-startTime)+"毫秒");
        return allList;
    }

}

287、展现效果

288、商品分类新增,页面分析

如果只写商品分类名称,则表示一级菜单

如果父级分类为一级时,则表示二级菜单

如果父级分类为二级时,则表示三级菜单

289、页面的确定按钮

290、页面JS函数

291、编辑ItemCatController

    /**
     * 实现商品分类新增操作
     * URL: /itemCat/saveItemCat
     * 参数:  this.itemCatForm  JSON
     * 请求类型: post
     * 返回值: SysResult对象
     */
    @PostMapping("/saveItemCat")
    public SysResult saveItemCat(@RequestBody ItemCat itemCat){

        itemCatService.saveItemCat(itemCat);
        return SysResult.success();
    }

292、编辑BasePojo

@Data
@Accessors(chain = true)
public class BasePojo implements Serializable {

    @TableField(fill = FieldFill.INSERT)//表示入库时需要赋值
    private Date created;
    @TableField(fill = FieldFill.INSERT_UPDATE)//表示入库/更新时赋值
    private Date updated;

}

293、编辑ItemCatService及其实现类

void saveItemCat(ItemCat itemCat);
    @Override
    @Transactional
    public void saveItemCat(ItemCat itemCat) {
        Date date = new Date();
        itemCat.setStatus(true).setCreated(date).setUpdated(date);
        itemCatMapper.insert(itemCat);
    }

294、新增显示

295、商品分类删除

如果不是父级则直接删除,如果是父级,则先删除子级

页面按钮

296、删除JS函数

297、编辑ItemCatController

    /**
     * 需求: 实现商品分类删除操作
     * 类型: delete
     * URL: /itemCat/deleteItemCat?id=xx&level=xx
     * 参数:  id/level
     * 返回值: SysResult
     */
    @DeleteMapping("/deleteItemCat")
    public SysResult deleteItemCat(ItemCat itemCat){

        itemCatService.deleteItemCat(itemCat);
        return SysResult.success();
    }

298、编辑ItemCatService及其实现类

void deleteItemCat(ItemCat itemCat);
    /**
     * 业务: 如果是父级 则应该删除子级和自己.
     * 思路:
     *      1. 判断是否为3级标签  直接删除
     *      2. 判断是否为2级标签  先删除三级再删除二级
     *      3. 判断是否为1级标签  先查询二级,再删除三级和二级再删除一级
     * @param itemCat
     */
    @Override
    @Transactional
    public void deleteItemCat(ItemCat itemCat) {
        if(itemCat.getLevel() == 3){
            int id = itemCat.getId();
            itemCatMapper.deleteById(id);
            return;
        }

        if(itemCat.getLevel() == 2){ //id=二级id
            //Sql: xxx where parent_id = 二级ID
            int id = itemCat.getId();
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("parent_id",id);
            //删除的是三级数据
            itemCatMapper.delete(queryWrapper);
            //再删除自己
            itemCatMapper.deleteById(id);
            return;
        }

        /**
         * 删除一级菜单
         */
        //1.查询二级数据  Sql: where parent_id=id
        QueryWrapper queryWrapper = new QueryWrapper();
        int id = itemCat.getId();
        queryWrapper.eq("parent_id",id);
        //由于是删除的业务,只需要获取id即可 所以使用objs
        List idList = itemCatMapper.selectObjs(queryWrapper);

        //判断是否有二级数据.如果有直接删除.如果没有直接删除一级
        if(idList.size()>0){
            //根据二级Id删除三级数据 Sql where parent_id in (1,2,3)
            queryWrapper.clear();
            queryWrapper.in("parent_id",idList);
            itemCatMapper.delete(queryWrapper);
            //将所有的1-2级的ID,封装到一个集合中
            idList.add(id);
            //将所有1级2级全部删除  batch 实质就是in关键字
            itemCatMapper.deleteBatchIds(idList);
        }else{
            itemCatMapper.deleteById(id);
        }
    }

299、编辑BasePojo,自动填充时间

@Data
@Accessors(chain=true)
public class BasePojo implements Serializable{
	//新增操作时 自动填充
	@TableField(fill = FieldFill.INSERT)
	private Date created;
	//新增和修改操作时自动填充
	@TableField(fill = FieldFill.INSERT_UPDATE)
	private Date updated;
}

300、编辑config配置

@Component  //将对象交给Spring容器管理
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        Date date = new Date();
        this.setFieldValByName("created",date,metaObject);
        this.setFieldValByName("updated",date,metaObject);
    }

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

301、自动填充代码测试,编辑ItemCatServiceImpl里面的saveItemCat方法

    @Override
    @Transactional
    public void saveItemCat(ItemCat itemCat) {
        //Date date = new Date();
        //itemCat.setStatus(true).setCreated(date).setUpdated(date);
        itemCat.setStatus(true);
        itemCatMapper.insert(itemCat);
    }

302、状态修改,页面JS

303、编辑ItemCatController

    /**
     * 需求:修改状态
     * URL:  /itemCat/status/{id}/{status}
     * 参数: id/status
     * 请求类型: put
     * 返回值: SysResult
     */
    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(ItemCat itemCat){

        itemCatService.updateStatus(itemCat);
        return SysResult.success();
    }

304、编辑ItemCatService及其实现类

void updateStatus(ItemCat itemCat);
    /**
     *   id/status
     *   Sql: update item_cat set status=xx,updated=xx
     *        where id=xx
     *   解释: updateById ID当做唯一where条件,其它不为null的属性
     *        当做set条件
     */
    @Override
    public void updateStatus(ItemCat itemCat) {
        itemCatMapper.updateById(itemCat);
    }

305、修改一下ItemCatServiceImpl里面的findItemCatList,也可以查询到所有数据

    /**
     * 弊端: 由于多次循环遍历 查询数据库,导致数据库查询次数太多效率极低.
     * 思路:
     *      1.刚才的业务逻辑梳理
     *      2.如何优化?????   提高效率
     * @param level
     * @return
     */
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        //查询一级商品分类信息
        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id",0);
        List<ItemCat> oneList = itemCatMapper.selectList(queryWrapper);
        //查询二级商品分类信息
        for(ItemCat oneItemCat: oneList){
            //1.为了复用条件构造器 将之前的数据清空
            queryWrapper.clear();
            //查询二级数据 parent_id = 一级ID
            queryWrapper.eq("parent_id",oneItemCat.getId());
            List<ItemCat> twoList = itemCatMapper.selectList(queryWrapper);
            //遍历二级列表 查询三级数据,封装数据返回
            oneItemCat.setChildren(twoList);
            //查询三级商品分类信息
            for(ItemCat twoItemCat: twoList){
                //1.为了复用条件构造器 将之前的数据清空
                queryWrapper.clear();
                //查询三级数据 parent_id = 二级ID
                queryWrapper.eq("parent_id",twoItemCat.getId());
                List<ItemCat> threeList = itemCatMapper.selectList(queryWrapper);
                //遍历二级列表 查询三级数据,封装数据返回
                twoItemCat.setChildren(threeList);
            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:"+(endTime-startTime)+"毫秒");
        return oneList;
    }

306、商品分类修改,页面JS分析

307、修改的JS

308、修改的JS函数

309、编辑ItemCatController

    /**
     * 需求: 修改商品分类信息
     * URL: /itemCat/updateItemCat
     * 参数: 表单数据 ItemCat对象 {id,name,parentId....}
     * 类型: put
     * 返回值: SysResult对象
     */
    @PutMapping("/updateItemCat")
    public SysResult updateItemCat(@RequestBody ItemCat itemCat){

        itemCatService.updateItemCat(itemCat);
        return SysResult.success();
    }

310、编辑ItemCatService及其实现类

void updateItemCat(ItemCat itemCat);
    /**
     * itemCat对象,不为null的数据执行业务
     * 只改了name属性  set name=xxx where id=xx
     * @param itemCat
     */
    @Override
    public void updateItemCat(ItemCat itemCat) {
        ItemCat temp = new ItemCat();
        temp.setId(itemCat.getId()).setName(itemCat.getName());
        itemCatMapper.updateById(temp);
    }

311、商品业务模块实现,实现路由跳转,router/index.js

312、 页面效果展现

313、商品模块搭建,Item表,价格扩大100倍之后的数据

314、编辑Item

@Data
@Accessors(chain = true)
@TableName("item")  //映射表
public class Item extends BasePojo {

    @TableId(type = IdType.AUTO)
    private Integer id;         //商品Id号
    private String title;       //商品标题信息
    private String sellPoint;   //卖点信息
    private Integer price;      //商品价格
    private Integer num;        //商品数量
    private String images;       //商品图片
    private Integer itemCatId;  //商品分类ID号
    private Boolean status;     //状态信息    0 下架 1 上架

}

315、编辑ItemMapper

public interface ItemMapper extends BaseMapper<Item> {
}

316、构建层级代码

317、商品列表展现,页面分析,调用生命周期函数

318、检查JS函数

319、编辑ItemController

@RestController
@CrossOrigin
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private ItemService itemService;

    /**
     * 业务: 实现商品列表分页展现
     * 类型: get
     * url: /item/getItemList?query=&pageNum=1&pageSize=10
     * 参数: pageResult
     * 返回值: SysResult(pageResult)
     */
    @GetMapping("/getItemList")
    public SysResult getItemList(PageResult pageResult){//3

        pageResult = itemService.getItemList(pageResult);
        return SysResult.success(pageResult);
    }
    
}

320、编辑ItemService及其实现类

public interface ItemService {
    
    PageResult getItemList(PageResult pageResult);
    
}
@Service
public class ItemServiceImpl implements ItemService {

    @Autowired
    private ItemMapper itemMapper;

    /**
     * 语法:selectPage语法说明
     *      1.page: MP内部指定的分页对象
     *      2.queryWrapper 条件构造器
     *      Sql: where title like "%xxx%"
     * @param pageResult
     * @return
     */
    @Override
    public PageResult getItemList(PageResult pageResult) {
        //判断用户的数据是否有值
        boolean flag = StringUtils.hasLength(pageResult.getQuery());
        QueryWrapper<Item> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(flag,"title",pageResult.getQuery());

        //编辑MP的分页对象 四个属性有用(页数/条数/总数/记录)  传递=页数/条数
        IPage<Item> page = new Page<>(pageResult.getPageNum(),pageResult.getPageSize());
        page = itemMapper.selectPage(page,queryWrapper);
        //获取总数
        long total = page.getTotal();
        //获取记录数
        List<Item> rows = page.getRecords();
        //将数据封装
        return pageResult.setTotal(total).setRows(rows);
    }
}

321、MP实现分页配置类

需要指定的数据库类型,按照MP的基本结构进行配置

//什么是bean spring容器管理的对象叫做bean
@Configuration //标识这是一个配置类 相当于早期的xml文件
public class MybatisPlusConfig {

    /**
     * @Bean作用: 将方法的返回值交给Spring容器管理
     * @return
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
        return interceptor;
    }

}

322、页面效果展现

323、商品新增,编辑路由

324、商品新增,页面JS函数分析

325、封装ItemVO对象

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ItemVO {  //该对象封装商品所有的参数信息

    private Item item;
    private ItemDesc itemDesc;

}

326、编辑ItemController

    /**
     * 需求: 实现商品新增
     * URL: http://localhost:8091/item/saveItem
     * 参数: {item:this.addItemForm,itemDesc:this.itemDesc }
     * 类型: post
     * 接收数据: ItemVO
     * 返回值: SysResult对象
     */
    @PostMapping("/saveItem")
    public SysResult saveItem(@RequestBody ItemVO itemVO){
        itemService.saveItem(itemVO);
        return SysResult.success();
    }

327、编辑ItemService及其实现类

void saveItem(ItemVO itemVO);
    @Override
    public void saveItem(ItemVO itemVO) {
        Item item = itemVO.getItem();
        item.setStatus(true);
        itemMapper.insert(item);
    }

328、页面效果展现

329、富文本编辑器,页面JS说明

330、组件引用,main.js

331、已安装

332、实现商品详情入库,ItemDesc说明

说明: item表是商品的基本信息,itemDesc表是商品的详情信息. 用户一般查询时都是查询的基本信息.只有点击某个商品时才会查询详情信息.为了提高查询效率 将商品分为 item/itemDesc

逻辑关系:
1. 一个商品对应一个详情
2. 一个详情对应一个商品
数据库表示: item.id = itemDesc.id

333、商品详情参数传递

当用户点击添加商品时,会将item/itemDesc的对象进行传递,则在后端动态接收数据则可以获取2个对象数据

addItem.vue页面的addItemBtn函数

334、编辑ItemDesc

@Data
@Accessors(chain = true)
@TableName("item_desc")
public class ItemDesc extends BasePojo {

    //由于item.id=itemDesc.id 所以ID不能主键自增
    @TableId
    private Integer id;
    private String itemDesc;
    
}

335、编辑ItemDescMapper

public interface ItemDescMapper extends BaseMapper<ItemDesc> {
}

336、编辑ItemServiceImpl里面的saveItem方法

当用户点击入库时,应该将item/itemDesc一起入库操作

    @Override
    @Transactional
    public void saveItem(ItemVO itemVO) {
        Item item = itemVO.getItem(); //id=null
        item.setStatus(true);
        //要求item入库之后,动态返回Id!!!!
        //MP原则: 入库之后动态回显数据!!
        itemMapper.insert(item);
        //实现itemDesc对象入库
        ItemDesc itemDesc = itemVO.getItemDesc();
        itemDesc.setId(item.getId());
        itemDescMapper.insert(itemDesc);
    }

337、文件上传,图片上传UI

338、 请求路径说明

339、编辑ImageVO

@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO {

    private String virtualPath; //图片实际目录
    private String urlPath;     //请求路径
    private String fileName;    //图片名称

}

340、编辑FileController

@RestController
@CrossOrigin
@RequestMapping("/file")
public class FileController {

    /**
     * 需求: 实现文件上传
     * URL地址: "http://localhost:8091/file/upload",
     * 参数: file=xxxx
     * 类型: POST请求
     * 返回值: SysResult(ImageVO)
     * 基础知识:
     *      inputStream,outputStream
     * 高级API: SpringMVC MultipartFile
     * 需求: 接收用户信息,保存到本地磁盘中
     * 控制图片大小: 默认大小1M
     */
    @PostMapping("/upload")
    public SysResult upload(MultipartFile file) throws IOException {
        //1.获取图片的名称
        String fileName = file.getOriginalFilename();
        //2.封装文件上传目录
        String fileDir = "D:/Java/File/images";
        //3.检查目录是否存在
        File dir = new File(fileDir);
        if(!dir.exists()){//如果目录不存在
            //如果目录不存在,则创建目录
            dir.mkdirs();
        }
        //4.封装文件的全路径
        String path = fileDir + "/" +fileName;
        //5.上传文件
        file.transferTo(new File(path));
        return SysResult.success();
    }

}

341、编辑FileController

    /**
     * 需求: 实现文件上传
     * URL地址: "http://localhost:8091/file/upload",
     * 参数: file=xxxx
     * 类型: POST请求
     * 返回值: SysResult(ImageVO)
     * 基础知识:
     *      inputStream,outputStream
     * 高级API: SpringMVC MultipartFile
     * 需求: 接收用户信息,保存到本地磁盘中
     * 控制图片大小: 默认大小1M
     */
    @PostMapping("/upload")
    public SysResult upload2(MultipartFile file){

        ImageVO imageVO = fileService.upload(file);
        if(imageVO == null){
            return SysResult.fail();
        }
        return SysResult.success(imageVO);
    }

342、编辑FileService及其实现类

public interface FileService {
    
    ImageVO upload(MultipartFile file);
    
}
@Service
public class FileServiceImpl implements FileService {

    private String localDir = "D:/Java/File/images";
    private String urlPath = "http://image.jt.com";

    /**
     * 考虑的问题:
     *      1. 校验图片类型    xx.jpg
     *      2. 校验是否为恶意程序 xx.exe.jpg
     *      3. 将文件分目录存储.
     *      4. 为了保证图片唯一性 ,UUID
     * @param file
     * @return
     */
    @Override
    public ImageVO upload(MultipartFile file) {
        //xxxxxx.jpg|png|gif 防止大小写问题,将所有字母转化为小写
        String fileName = file.getOriginalFilename().toLowerCase();
        //利用正则判断是否为图片
        if(!fileName.matches("^.+\\.(jpg|png|gif)$")){
            //如果校验不通过,则终止程序
            return null;
        }
        System.out.println("图片类型正确的!!!!!!");
        //第二步 防止恶意程序 判断图片是否有宽度和高度
        try {
            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            if(width == 0 || height == 0){
                return null;
            }
            System.out.println("用户上传的是图片");

            //第三步: 目录如何划分 yyyy/MM/dd
            String dateDir = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
            // E:/images + /2022/11/11/  拼接目录
            String dirPath = localDir + dateDir;
            File dirFile = new File(dirPath);
            if(!dirFile.exists()){
                //如果目录不存在时, 创建目录
                dirFile.mkdirs();
            }

            //第四步: 使用uuid实现文件名称 uuid.jpg
            //4.1 生成UUID
            String uuid = UUID.randomUUID().toString().replace("-","");
            //截取文件类型
            int index = fileName.lastIndexOf(".");
            String fileType = fileName.substring(index);
            //生成新文件名称
            String newFile = uuid + fileType;

            //第五步:实现文件上传  全路径 再上传
            // E:/images/2021/10/15/uuid.jpg
            String path = dirPath + newFile;
            file.transferTo(new File(path));
            System.out.println("文件上传成功!!!!");

            //第六步: 返回ImageVO数据
            //6.1 虚拟路径只写动态变化的数据  /2021/11/11/uuid.jpg
            String virtualPath = dateDir + newFile;
            //6.2 真实图片名称
            String fileNameVO = newFile;
            //6.3 网络地址 http://image.jt.com/xx/uuid.jpg
            String url =  urlPath + virtualPath;
            return new ImageVO(virtualPath,url,fileNameVO);

        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

343、关于磁盘地址与网络地址的说明

磁盘地址:
用户将图片上传到本地磁盘中 有磁盘地址.
D:/Java/File/images/2021/10/15/a.jpg

网络地址:
准备一个可以通过网络访问服务器的一个固定的地址.该地址指向真实的磁盘地址,即可以看到该图片
http://image.jt.com/2021/10/15/a.jpg

344、文件删除操作,页面JS分析

345、 编辑FileController

    /**
     * 需求: 删除图片信息
     * url: http://localhost:8091/file/deleteFile
     * 类型: delete
     * 参数: virtualPath
     * 返回值: SysResult对象
     */
    @DeleteMapping("/deleteFile")
    public SysResult deleteFile(String virtualPath){

        fileService.deleteFile(virtualPath);
        return SysResult.success();
    }

346、编辑FileService及其实现类

public interface FileService {

    ImageVO upload(MultipartFile file);

    void deleteFile(String virtualPath);
    
}
    /**
     * 1.准备全文件路径
     * 2.实现文件删除
     * @param virtualPath
     */
    @Override
    public void deleteFile(String virtualPath) {
        String path = localDir + virtualPath;
        File file = new File(path);
        if(file.exists()){
            //如果文件存在,则删除文件
            file.delete();
        }
    }

347、代理机制

反向代理

反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率

反向代理特点

代理服务器介于用户和服务器之间.
用户以为反向代理服务器就是目标服务器.
用户不清楚真实的服务器到底是谁!
反向代理服务器保护了目标服务器的信息 所以也称之为"服务器端代理".

正向代理

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。

正向代理特点

正向代理服务器介于用户和服务器之间
用户将请求发送给代理服务器,并且指定目标服务器.
目标服务器以为是代理服务器访问的,保护了用户的信息,所以也称之为 “客户端代理”

正向和反向代理特点

用户的每一次请求都包含了正向代理和反向代理机制.

348、Nginx服务器介绍

Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、简单的配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

Nginx特点

1. 占有内存少 运行时内存占用量不超过2M
2. 并发能力强 官网测试数据 5万次/秒 实测 3万次/秒
tomcat服务器 并发能力 150-220左右 1000/秒
扩展: F5 负载均衡服务器 运营商一般使用 20万/秒

Nginx下载

选右边的Windows版本,下载下来之后解压

http://nginx.org/en/download.html

Nginx安装说明

 nginx路径说明

路径问题:
nginx是c语言开发的所以对中文不友好. 额外需要注意空格
端口问题:
1.Nginx运行的端口是80. 80端口不能被其它的服务占用.
2.如果80端口被其它的服务占用,则通过dos命令 kill 杀死进程.
占用端口:
http协议默认端口号80端口.
https协议默认端口号443端口

nginx进程项说明

nginx 每次启动都会有2个进程. 一个主进程, 一个是守护进程
主进程: 主要提供反向代理服务. 占用内存空间大
守护进程: 防止主进程意外关闭的.
如果需要关闭nginx 则先关闭守护 再关闭主进程.

nginx启动占用端口80,所以需要释放80资源

查询80端口被哪个进程占用

netstat -ano | findstr "80" 

检查nginx启动是否正常,访问:localhost

nginx命令(必须掌握)

说明: 要求执行nginx 命令 应该在nginx.exe所在目录执行.
命令:
1. start nginx 启动nginx
2. nginx -s reload 重启nginx 只有nginx启动时才可以重启
3. nginx -s stop 停止nginx

注意事项: nginx的运行只能启动一次,如果启动多次则会产生多余项,影响程序的正常运行

349、Nginx反向代理说明

#Nginx只能支持http/https
http {
	#反向代理服务  一个服务一个server
    server {
    	#监听端口    一般都是80
        listen       80;
        #拦截的域名   
        server_name  localhost;
		# 进行反向代理服务配置   
		# / 根目录  拦截用户所有的请求
        location / {
        	#root关键字  代理的是一个目录
            root   html;
            # 默认访问的页面
            index  index.html index.htm;
        }
      }
}

350、图片回显

磁盘地址: D:\Java\File\images\2021\10\15\a.jpg
网络地址: http://image.jt.com\2021\10\15\a.jpg
代理核心: http://image.jt.com 映射到 D:\Java\File\images
实现域名代理,编辑conf目录下的nginx.conf:

注意事项:
1. start nginx 命令 如果配置文件有错, 不会提示.
2. nginx -s reload 重启命令 会有错误提示

351、图片回显原理

根据hosts文件可以实现域名与IP的映射关系,在本机实现域名与IP的映射,只对本机有效

修改hosts文件

位置:C:\Windows\System32\drivers\etc

如果勾选只读,则去掉

 

352、编辑hosts文件 

353、hosts测试,启动后台服务器, 访问:web.jt.com:8091/rights/getRightsList

354、后台运行,前台运行,Nginx启动,页面效果展现

355、查看本地目录中的文件

356、京淘项目前端发布

前端组成部分

原始文件-开发阶段:
1. node.js
2. 脚手架项目
3. Vue组件
4. Vue路由
5. Axios ajax请求.

项目发布阶段:
1.html
2.css样式
3.js

说明: 如果前端项目需要发布项目,则将项目打包部署即可.

357、前端部署准备工作

路径说明

前端向后端发送请求时路径都是 http://localhost:8091/xxx/xxx,该请求需要转化为网络地址. http://manage.jt.com/xxx/xxx
图片上传的地址 http://localhost:8091 换为http://manage.jt.com

358、修改ajax请求地址,修改main.js

359、修改addItem.vue

 

360、项目前端发布

 任务,build,运行,对号

361、发布之后的文件位置,dist

362、复制到nginx的根目录

363、前端项目发布

用户通过域名: http://www.jt.com:80 访问前端的项目路径 dist/index.html

编辑nginx.conf文件

364、http协议自动转化为https问题

开发: 使用谷歌浏览器 最为标准的浏览器
解决方案:
1.浏览器中输入: “chrome://net-internals/#hsts:“
2.删除指定的域名

365、清空浏览器缓存,然后重启 

 

366、访问:http://www.jt.com/#/login(有问题)

367、实现域名的代理

后端请求的网址: http://manage.jt.com 转向到 http://localhost:8091
代理规则: 反向代理.

368、修改之后,重启nginx(出现问题)

369、Nginx实现负载均衡

动态获取端口号,编辑PortController

@RestController
@CrossOrigin
public class PortController {

    @Value("${server.port}")
    private Integer port;

    //动态获取端口号
    @GetMapping("/getPort")
    public String getPort(){

        return "当前端口号:" + port;
    }
    
}

370、关闭热部署

说明: 由于需要同时开启2台tomcat服务器,所以需要关闭热部署操作
将POM.xml文件中的热部署jar包去除即可.

		<!--热部署,开发阶段有效-->
		<!--
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>-->

371、修改端口号

先8091启动,再改为8092,更改一下下面的选项,再启动

分别启动2台tomcat服务器. 端口号分别为8091/8092. 通过下图实现进程项执行2次的操作.

 访问:http://localhost:8091/getPort

 访问:http://localhost:8092/getPort

 

 372、负载均衡策略

 

373、找到nginx.conf文件

 

374、编辑nginx.conf,配置后端集群,并更改上面的代理

	#manage.jt.com:80 映射localhost:8091
	server {
		listen 80;
		server_name manage.jt.com;
		location / {
			#代理请求
			#proxy_pass http://127.0.0.1:8091;
			proxy_pass http://tomcats;
		}
	}

	#配置后端集群
	upstream tomcats {
		server 127.0.0.1:8091;
		server 127.0.0.1:8092;
	}

375、保存,启动一下nginx,在有nginx.exe文件夹上cmd,start nginx

 

376、访问:http://manage.jt.com/getPort

 

 刷新

 

377、负载均衡策略

轮询策略

根据配置文件内容,依次访问服务器

	#manage.jt.com:80 映射localhost:8091
	server {
		listen 80;
		server_name manage.jt.com;
		location / {
			#代理请求
			#proxy_pass http://127.0.0.1:8091;
			proxy_pass http://tomcats;
		}
	}

	#配置后端集群
	upstream tomcats {
		server 127.0.0.1:8091;
		server 127.0.0.1:8092;
	}

权重策略(十年以前,现在是微服务的轮询)

根据服务器的性能,手动分配服务器的负载

	#配置后端集群 1.默认轮询 2.权重 weight
	upstream tomcats {
		server 127.0.0.1:8091 weight=4;
		server 127.0.0.1:8092 weight=1;
	}

保存,重启nginx

访问:manage.jt.com/getPort,8091出现的概率比较多,80%

 IPHASH

让用户的请求与服务器绑定,用户访问某台服务器,以后永远访问该服务器

	#配置后端集群 1.默认轮询 2.权重 weight 3.iphash策略
	upstream tomcats {
		ip_hash;   
		server 127.0.0.1:8091;
		server 127.0.0.1:8092;
	}

保存,重启nginx,nginx -s reload,再访问:http://manage.jt.com/getPort,是8091,后面刷新,都是8091


版权声明:本文为hpcbluesky原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。