JavaWeb-SSM

Web开发

一、web前端介绍

1. 组成

  • html:网页的结构(页面的元素和内容)
  • css:网页的表现(页面元素的外观、位置等界面)
  • JavaScript:网页的行为(进行交付)

2. JavaScript的介绍

  • js的引入方式:

    1. 内部脚本:将js代码定义在HTML中,JavaScript代码必须位于script>/script>中,在HTML文档中,可以在任意位置,放置任意数量的script>/script>;一般把脚本放在body>元素的底部,可改变显示速度
    2. 外部脚本:外部JS文件中只包含JS代码,不包含script>标签;script>不能自闭和,eg:script src="路径">/script>
  • 输出语句:

    1. 使用window.alert()写入警告框window可以省略
    2. 使用document.write()写入HTML的输出
    3. 使用console.log()写入到浏览器控制台
  • 变量:

    1. 使用var(variable缩写)关键字来声明变量,var声明的变量是全局变量,可以重复申明;ECMAScript6新增了let关键字来定义变量,变量只在代码块内有效同时变量只能被定义一次;又新增了const关键字,来定义常量,一旦声明,值就无法再改变
    2. JavaScript是一门弱类型语言,变量可以存放不同类型的值
    3. 变量名需要遵循这些规程:(1)组成字符可以是字母、数组、下划线_或者美元符$ (2)数字不能开头 (3)驼峰式命名规则
  • 数据类型:分为原始类型和**引用类型 **

    1. number:数字(整数、小数NaN(Not a Number))
    2. string:字符串
    3. boolean:布尔类型
    4. null:对象为空
    5. undefined:当声明的变量未初始化时,该变量的默认值是undefined
  • 运算符:==会进行数据类型的转换,===不会进行数据类的转换

    <script>
    let a = 10
    alert(a=='10')//true
    alert(a==='10')//false
    </script>
    
  • 数据类型的转换:

    1. 字符串转为数字:使用parseInt关键字,如果字面值不是数字则转换为 NaN

      alert(parseInt('123'))//123
      alert(parseInt('13#23'))//13
      alert(parseInt('#23'))//NaN
      
    2. 其他类型转为boolean:

      • number:0和NaN为false,其他都是true
      • String:空字符为false,其他都为true
      • Null和undefined:均转为false
  • 函数:使用function关键字定义和调用

        <script>
            //定义函数方式1
            function add(a,b){
                return a + b
            }
            //函数的调用
            alert(add(10,39))
        </script> 
        <script>
            //定义函数方式2
            var sum = function add(a,b){
                return a + b
            }
            //函数的调用
            alert(sum(10,30))
        </script>
    
  • 数组

    1. 定义: var arr = new Array(1,3,2)或者var arr = [1,2,3]
    2. 访问:arr[1]
    3. 特点:长度可变、类型可变
    4. 遍历:

      • for循环遍历
      • forEach遍历,只会输出有值的元素

        var arr = [1,2,3,4]
        arr.forEach(function(e){
            console.log(e)
        })
        
      • 箭头函数:简化输出

        var arr = [1,2,3,4]
        arr.forEach((e)=>{
            console.log(e)
        })
        
    5. 常用方法:

      • push():尾部添加
      • splice(int start,int dellength):start开始删除的位置,dellength删除元素的个数
  • 字符串

    1. 定义:和Java一样
    2. 常用方法:

      • charAt(index):得到index的元素
      • indexOf():检索元素所在下标
      • trim():去除左右两侧的空格
      • substring(start,end):[start,end)
  • 自定义对象

    1. 定义格式

      //自定义对象
      var user = {
          name :'tom',
          age : 20,
          gender : 'man',
          //函数完整定义
          eat : function(){
              alert('用膳')
          }
          // 函数的简写
          eat(){
              alert('用膳')
          }
      }
      
    2. 调用格式:

      • 对象名.属性名
      • 对象名.方法名

        // 调用
        alert(user.name)
        user.eat()
        
  • JSON

    1. 概念:JavaScript Object Notation,Javascript对象标记法
    2. JSON是通过Javascript对象标记书写的文本
    3. 由于其语法简单,层次结构鲜明,现在多用于作为数据载体,在网络中进行数据传输
    4. 定义:特别注意:变量名只能用" "包裹

      // 自定义JSON
      var userStr = '{"name":"zjl","age":20,"gender":"man"}'
      
    5. JSON与JS对象的转换

      // 将JSON字符串转为JS对象
      var obj = JSON.parse(userStr)
      alert(obj.age)
      // 将JS对象转换为字符串
      var str = JSON.stringify(obj)
      alert(str)
      
  • BOM

    1. 概念:浏览器对象模型,允许JavaScript与浏览器对话,JavaScript将浏览器的各个组成部分封装成对象
    2. 组成:

      • Window:浏览器窗口对象
      • Navigator:浏览器对象
      • Screen:屏幕对象
      • History:历史记录对象
      • Location:地址栏对象
    3. Window:浏览器窗口对象

      • 获取:直接使用Window,其中Window.可以直接省略
      • 属性:history、location、navigator
      • 方法 :

        • alert():警告框
        • confirm():确认和取消按钮
        • setLnterval():按照的周期(以毫秒计)来调用函数
        • setTimeout():指定的毫秒数来调用函数

          <script>
              //确认与取消按钮
          //    var flag = confirm('你确认删除记录吗')
              /* var i = 0;
              每隔3秒调用一次
              setInterval(()=>{
                  i++;
                  console.log(i);
              },3000); */
              //获取当前导航栏地址
              alert(location.href);
              // 修改导航栏地址并跳转
              location.href = 'https:www.baidu.com'
          </script>
          
  • DOM

    1. 概念:文档对象模型
    2. 将标记语言的各个组成部分封装成对应的对象:

      • Document:整个文档对象
      • Element:元素对象
      • Attribute:属性对象
      • Text:文本对象
      • Comment:注释对象
    3. Javascript通过DOM,就能够对HTML进行操作:

      • 改变HTML元素的内容
      • 改变HTML元素的样式(CSS)
      • 对HTML DOM事件做出反应
      • 添加和删除HTML元素
    4. DOM是W3C的标准,定义了访问HTML和XML文档的标准,分为3个不同的部分:

      • Core DOM 所有文档的标准
      • XML DOM -XML 文档的标准模型,是Core DOM的子集
      • HTML DOM -HTML 文档的标准
    5. DOM案例

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
      </head>
      <body>
          <img id="img" src="./images/home.png" alt="家">
      
          <div class="cls">zjl</div>
          <div class="cls">qs</div>
      
          <input type="checkbox" name="hobby">电影
          <input type="checkbox" name="hobby">旅游
          <input type="checkbox" name="hobby">游戏
      </body>
      
      <script>
          //修改图片标签的路径
          var img = document.getElementById('img');
          img.src = './images/home-fill.png';
      
          //通过获取class修改div
          var div = document.getElementsByClassName('cls');
          for (let i = 0;i < div.length;i++){
              const dv = div[i];
              dv.innerHTML += '<font style="color: red;">very good</font>';
          }
      
          // 通过获取name全选复选框
          var checkbox = document.getElementsByName('hobby');
          for (let i = 0; i < checkbox.length; i++) {
              const ch = checkbox[i];
              ch.checked = true;
          }
      </script>
      </html>
      
    6. 事件:HTML事件是发生在HTML元素上的事件"事件",eg:

      • 按钮被点击
      • 鼠标移动到元素上
      • 按下键盘按钮
    7. 事件监听:JavaScript可以在事件被侦测时执行代码

      1. 事件绑定

        • 方式1:通过标签中事件属性进行绑定

          <body>
              <button id="bt1" onclick="on()">按钮1</button>
          </body>
          
          <script>
          //通过标签中事件属性进行绑定
          function on(){
               alert('按钮1被点击了');
              }
          </script>
          
        • 方式2:通过DOM元素属性绑定

          <button id="bt2" >按钮2</button>
          </body>
          
          <script>
              //通过DOM元素进行绑定
              document.getElementById('bt2').onclick = function(){
                  alert('按钮2被点击了');
              }
          </script>
          
      2. 常见事件

        • onclick:鼠标点击事件
        • onblur:元素失去焦点
        • onfocus:元素获得焦点
        • onload:某个元素或者图像被加载完成
        • onsubmit:当表单提交时触发事件
        • onkeydown:某个键盘的键被按下时触发
        • onmouseover:鼠标移动都某个元素上
        • onmouseout:鼠标从某个元素上移开
      3. 案例

        <body>
            <img id="img" src="./images/home.png" alt="家"><br>
            <button onclick="bt1()">变绿</button>
            <button onclick="bt2()">恢复</button><br><br>
        
            <input type="text" id="ipt" value="IT" onfocus="lower()" onblur="upper()"><br>
        
            <input type="checkbox" name="hobby">旅游
            <input type="checkbox" name="hobby"> 电影
            <input type="checkbox" name="hobby">游戏<br>
            <button class="all" onclick="checkAll()">全选</button>
            <button class="none" onclick="checkNone()">反选</button>
        
        </body>
        
        <script>
            //变绿按钮的点击绑定
            function bt1(){
                let img = document.getElementById('img');
                img.src = './images/home-fill.png';
            }
        
            // 恢复按钮的点击绑定
            function bt2(){
                let img = document.getElementById('img');
                img.src = './images/home.png';
            }
        
            // 聚焦变小写
            function lower(){
                let input = document.getElementById('ipt');
                input.value = input.value.toLowerCase();
            }
            // 失去焦变大写
            function upper(){
                let input = document.getElementById('ipt');
                input.value = input.value.toUpperCase();
            }
        
            // 全选按钮的点击绑定
            function checkAll(){
                let checkboxs = document.getElementsByName('hobby');
                for(let i = 0;i < checkboxs.length;i++){
                    const check = checkboxs[i];
                    check.checked = true;
                }
            }
        
            // 反选按钮的点击绑定
            function checkNone(){
                let checkboxs = document.getElementsByName('hobby');
                for(let i = 0;i < checkboxs.length;i++){
                    const check = checkboxs[i];
                    check.checked = false;
                }
            }
        </script>
        

    3. Vue

    • 框架:是一个半成品软件,是一套可重复、通用的、软件基础代码模型。基于框架开发,更加便捷、更加高效
    • Vue是一套前端框架,免除原生Javascript中的DOM操作,简化书写
    • 基于MVVM(Model-View-ViewModel)思想,实现数据的双向绑定,将编程的关注点放在数据上
    • 常用指令:

    • v-bind:为HTML表情绑定属性值,如设置href,css样式等

    • v-model:在表单元素上创建双向数据绑定

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <script src="./js/vue.js"></script>
      </head>
      <body>
          <div id="app">
              <a v-bind:href="URL" target="_blank">链接1</a>
              <a :href="URL" target="_blank">链接2</a>
      
              <input type="text" v-model="URL">{{URL}}
          </div>
      </body>
      <script>
          //定义Vue对象
          new Vue({
              el:"#app",//vue接管区
              data:{
                  URL:"https:www.baidu.com"
              }
          })
      </script>
      </html>
      
    • v-on:为HTML标签绑定事件

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <script src="./js/vue.js"></script>
      </head>
      <body>
          <div id="app">
              <input type="button" value="点我一下1" v-on:click="handle()">
              <input type="button" value="点我一下2" @click="handle()">
          </div>
      </body>
      <script>
          //定义Vue对象
          new Vue({
              el:"#app",//vue接管区
              data:{
                  URL:"https:www.baidu.com"
              },
              methods:{
                  handle: function(){
                      alert("你点了我一下");
                  }
              }
          })
      </script>
      </html>
      
    • v-if、v-else-if、v-else、v-show:条件性的渲染某元素,判断为true时渲染,否则不渲染
    • v-show:根据条件展示某元素,区别在于切换的是display属性的值

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <script src="./js/vue.js"></script>
      </head>
      <body>
          <div id="app">
              年龄<input type="text" v-model="age">经判定为:
              <span v-if="age < 35">年轻人(35岁以下)</span>
              <span v-else-if="age < 60">中年人(35~60)</span>
              <span v-else>老人(60岁及以上)</span>
              <br>
              年龄<input type="text" v-model="age">经判定为:
              <span v-show="age < 35">年轻人(35岁以下)</span>
              <span v-show="age >= 35 && age < 60">中年人(35~60)</span>
              <span v-show="age >= 60">老人(60岁及以上)</span>
          </div>
      </body>
      <script>
          //定义Vue对象
          new Vue({
              el:"#app",//vue接管区
              data:{
                  age: 20
              },
              methods:{
                  handle: function(){
                      alert("你点了我一下");
                  }
              }
          })
      </script>
      </html>
      
    • v-for:列表渲染展示

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <script src="./js/vue.js"></script>
      </head>
      <body>
          <div id="app">
              <div v-for="addr in address">{{addr}}</div>
              <div v-for="(addr,index) in address">{{index}}.{{addr}}</div>
          </div>
      </body>
      <script>
          //定义Vue对象
          new Vue({
              el:"#app",//vue接管区
              data:{
                  address: ["北京", "昆明", "哈尔滨", "成都"]
              },
              methods:{
                  handle: function(){
                      alert("你点了我一下");
                  }
              }
          })
      </script>
      </html>
      
    • Vue案例

      <!DOCTYPE html>

      <tr align="center" v-for="(user,index) in users">
      编号姓名年龄性别成绩等级
      {{index+1}}{{user.name}}{{user.age}}<span v-if="user.gender == 1">男 <span v-if="user.gender == 2">女{{user.score}}<span v-if="user.score >= 85">优秀 <span v-else-if="user.score >= 60">及格 <span v-else style="color: red;">不及格

    • Vue生命周期

    • 生命周期:指一个对象从创建到销毁

    • 生命周期的八个阶段:触发一个生命周期事件,会自动执行一个生命周期的方法(钩子)

      状态阶段周期
      beforeCreate创建前
      created创建后
      beforeMount挂载前
      mounted挂载完成
      beforeUpdate更新前
      updated更新后
      beforeDestroy销毁前
      destoryed销毁后

4.Ajax

  • 概念:异步的Javascript和XML
  • 作用:

    1. 数据交换:通过Ajax可以给服务器发送请求,并获取服务器相应的数据
    2. 异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分页面的技术,如:搜索联想、用户名是否可以校验等等
  • Axios:

    • 介绍:Axios对原生的Ajax进行封装,简化书写,快速开发
    • Vue项目使用Axios:

      1. 在项目目录下安装axios:

        npm install axios;
        
      2. 需要使用axios时,导入

        import axios from 'axios';
        
    • 官网:https://www.axios-http.cn/

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
          <script src="./js/axios.js"></script>
      </head>
      <body>
          <input type="button" value="获取数据get" onclick="get()">
          <input type="button" value="删除数据post" onclick="post()">
      </body>
      <script>
          function get(){
              axios.get("https://api.apiopen.top/api/sentences").then(result => {
                  console.log(result.data);
              })
          }
      
          function post(){
              axios.post("https://api.apiopen.top/api/sentences").then(result => {
                  console.log(result.data);
              })
          }
      </script>
      </html>
      

5.Element

  • Element:一套位开发者、设计师和产品经理准备的基于Vue2.0的桌面端组件库
  • 组件:组成网页的部件,例如 超链接、按钮、图片等
  • 官网:https://element.eleme.cn/2.11/#/zh-CN
  • 快速入门:

    1. 安装ElementUI组件库(在当前工程目录下),在命令行执行指令:

      npm install element-ui@2.15.3
      
    2. 引入ElementUI组件库

    3. 访问官网,复制组件代码,调整

二、后端

1.maven

  • 管理和构建Java项目的工具
  • Maven的作用:

    1. 依赖的管理:方便快捷的管理项目的依赖的资源(jar包),避免版本冲突问题
    2. 统一项目结构:提供标准、统一的项目结构
    3. 项目构建:标准跨平台(Linux、Windows)的自动化项目构建方式
  • 官网:Maven – Welcome to Apache Maven

  • maven仓库:https://mvnrepository.com/

三、Web入门

1.Spring

  • 官网: Spring | Home
  • Spring 发展到如今已经形成了一种开发生态圈,Spring提供了若干子系统,每个项目用于完成特定的功能

2.SpringBoot

  • 是Spring的子系统,简化了Spring的配置
  • SpringBootWeb快速入门:

    1. 创建SpringBoot工程,并勾选web开发的相关依赖
    2. 定义HelloController类,添加方法hello,并添加注释
    3. 运行测试

3.HTTP

  • 概念:超文本传输协议,规定了浏览器和服务器之间数据传输的规则
  • 特点:

    1. 基于TCP协议:面向连接、安全
    2. 基于请求-响应模型的:一次请求对应一次响应
    3. HTTP协议是无状态的协议:对于事物处理没有记忆能力。每次请求-响应都是独立的

      • 缺点:多次请求间不能共享数据
      • 优点:速度快
  • 请求格式

    1. 请求行
    2. 请求头
    3. 请求体
  • 响应格式

    1. 请求行
    2. 请求头: 格式 key : value
    3. 请求体
  • 响应状态码:https://cloud.tencent.com/developer/chapter/13553

4.请求

  • 简单请求:

    • 原始方式:在原始的web程序中,获取请求参数,需要通过HttpServletRequest对象手动获取,但过于繁琐,还需要手动转换数据类型

      @RestController
      public class RequestController {
          @RequestMapping("/simpleParam")
          public String simpleParam(HttpServletRequest request){
              // 获取请求参数
              String name = request.getParameter("name");
              String ageStr = request.getParameter("age");
      
              int age = Integer.parseInt(ageStr);
              System.out.println(name + ": " + age);
              return "OK";
          }
      }
      
    • SpringBoot方式:参数名和形参名相同,定义形参即可接受参数;如果参数不一致需要使用@RequestParam映射

      @RestController
      public class RequestController {
          @RequestMapping("/simpleParam")
          public String simpleParam(String name,Integer age){
              System.out.println(name + ": " + age);
              return "OK";
          }
      }
      

        @RequestMapping("/simpleParam")
        public String simpleParam(@RequestParam(name = "name",required = false) String name, Integer age){
            System.out.println(name + ": " + age);
            return "OK";
        }
  • 实体参数

    • 简单实体参数

      // 2.简单实体参数
      @RequestMapping("/simplePojo")
      public String simplePojo(User user){
          System.out.println(user);
          return "OK";
      }
      
    • 复杂实体参数

      // 3.复杂实体参数
      @RequestMapping("/complexPojo ")
      public String complexPojo(User user){
          System.out.println(user);
          return "OK";
      }
      
  • 数组集合参数

    • 数组参数:请求参数名与形参的名字相同且请求参数为多个,可以直接使用数组封装
    • 集合参数:请求参数名和形参中集合变量名相同,通用@RequestParam绑定参数关系
  • 日期参数:使用@DateTimeFormat注解完成日期的格式

    // 4.日期参数
    @RequestMapping("/dateParam")
    public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){
        System.out.println(updateTime);
        return "OK";
    }
    
  • Json参数

    // 5.json参数
    @RequestMapping("/jsonParam")
    public String jsonParam(@RequestBody User user){
        System.out.println(user);
        return "OK";
    }  
    
  • 路径参数:通过URL直接传递参数,使用{...}来标识该路径参数,需要使用@PathVariable获取路径参数

    // 6.路径参数
    @RequestMapping("/path/{id}/{name}")
    public String pathParam(@PathVariable int id,@PathVariable String name){
        System.out.println(id);
        System.out.println(name);
        return "OK";
    }
    
  • ResponseBody

    • 类型:方法注解、类注解
    • 位置:Controller方法上/类上
    • 作用:将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转换为JSON格式响应
    • 说明:@RestController=@Controller+@ResponseBody
  • 统一响应结果Result(code,msg,data)

5.三层架构

  • Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
  • Service:业务逻辑层,处理具体的业务逻辑
  • Dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查

6.分层解耦

  • 内聚:软件中各个功能模块内的联系
  • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度
  • 控制反转:Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
  • 依赖注入:Dependency Injected,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入
  • Bean对象:IOC容器中创建、管理的对象,称之为Bean

7.声明bean的注解

  • @Component,@Controller,@Service,@Repository
  • SpringBootApplication具有包扫描作用,默认扫描当前包及其子包

8.Bean组件扫描

  • 声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描
  • @ComponentScan虽然没有显示配置,但是实际上已经包含在启动类声明注解@SpringBootApplication中,默认扫描的范围:当前包及其子包

9.DI详解

  • @Autowired:默认按照类型自动装配
  • 如果存在类型的Bean:、

    • @Primary
    • @AutoWired+@Qualifier("Bean名称")
    • @Resource(name="Bean名称")
  • @Resource与@AutoWired区别:

    • @Autowired是Spring框架提供的注解,而@Resource是JDK提供的
    • @AutoWired默认是按照类型注入,而@Resource是按照名称注入的

四、 MyBatis

1. Mybatis入门

  • 连接池
  • JDBC

2. lombok

  • Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器,getter/setter、equals、hashcode、toString等方法,并可以自动化生成变量,简化Java开发、提高效率
  • 里面的注解

    1. @Getter:get方法
    2. @Setter:set方法
    3. @ToString:toString方法
    4. @EqualsAndHashCode:Equals和HashCode方法
    5. @Data:包含@Getter、@Setter、@ToString、@EqualsAndHashCode四个注解
    6. @NoArgsConstructor:无参构造器
    7. @AllArgsConstructor:全参构造器

3. 删除功能

  • pojo

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        public class Emp {
            private Integer id;
            private String username;
            private String password;
            private String name;
            private Short gender;
            private String image;
            private Short job;
            private LocalDate entrydate;
            private Integer deptId;
            private LocalDateTime createTime;
            private LocalDateTime updateTime;
        }
    
  • mapper

    import org.apache.ibatis.annotations.*;
    
    @Mapper
    public interface EmpMapper {
    
        // 根据ID删除数据
        @Delete("delete from emp where id = #{id}")
        public int delete(Integer id);
    }
    
  • test

    @SpringBootTest
    class SpringbootMybatisCrudApplicationTests {
    
        @Autowired
        private EmpMapper empMapper;
        @Test
        public void testDelete(){
            int delete = empMapper.delete(17);
            System.out.println(delete);
        }
    
    } 
    

4. 预编译SQL

  • 性能更高
  • 更安全(防止SQL注入)
  • SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,已达到执行代码对服务器进行攻击的方式

5. 插入功能

  • mapper

    @Mapper
    public interface EmpMapper {
        @Options(useGeneratedKeys = true,keyProperty = "id")  // 主键值的返回
        @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
                "VALUES(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
        public void insert(Emp emp);
    }
    
  • test

    @Test
    public void testInsert(){
        Emp emp = new Emp();
        emp.setUsername("zj");
        emp.setPassword("123");
        emp.setName("zjl");
        emp.setGender((short)1);
        emp.setGender((short) 1);
        emp.setEntrydate(LocalDate.of(2023, 12, 4));
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
    
        empMapper.insert(emp);
        System.out.println(emp.getId());
    }
    
  • 返回主键值:@Options(useGeneratedKeys = true,keyProperty = "id") // 主键值的返回

6. 根据Id查询员工

  • Mapper

    // 根据员工查询数据
        @Select("select * from emp where id = #{id}")
        public Emp getById(Integer id);
    
  • Test

    public void testGetById() {
            Emp emp = empMapper.getById(20);
            System.out.println(emp);
        }
    
  • 开启驼峰式命名的自动映射

    # 开启Mybatis驼峰命令的自动映射 a_Colum -> aColum
    mybatis.configuration.map-underscore-to-camel-case=true
    

7. 条件查询员工

  • Mapper

    // 条件查询员工信息
        @Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and " +
                "entrydate between #{begin} and #{end} order by update_time desc")
        public List<Emp> getEmp(String name, Short gender, LocalDate begin,LocalDate end);
    
  • test

    @Test
    public void testGetEmp(){
        List<Emp> emp = empMapper.getEmp("郑", (short) 1, LocalDate.of(2010, 1, 1), LocalDate.of(2023, 12, 6));
        System.out.println(emp);
    }
    

8. XML映射文件

  • 规范

    1. XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同胞同名)
    2. XML映射文件的namespace属性为Mapper接口全限定名一致
    3. XML映射文件中SQL语句的id和Mapper接口中的方法名一致,并保持返回类型一致
    4. EmpMapper.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.zjl.mapper.EmpMapper">
          <!--resultType: 单条记录所封装的类型-->
          <select id="getEmp" resultType="com.zjl.pojo.Emp">
              select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and
                  entrydate between #{begin} and #{end} order by update_time desc
          </select>
      </mapper>
      
    5. EmpMapper:不需要注解

      public List<Emp> getEmp(String name, Short gender, LocalDate begin,LocalDate end);
      
    6. SQL语句复杂的时候建议使用XML映射文件,SQL语句简单的时候建议使用注解

9. Mybatis动态SQL

  • 动态SQL:随着用户的输入或者外部条件的变化而变化的SQL语句
  • if:用于判断条件是否成立,使用test属性进行条件的判断,如果条件为true,则拼接SQL
  • where:where元素只会在子元素有内容的情况下才插入where子句,而且会自动去除句子开头的and或or

    <?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.zjl.mapper.EmpMapper">
        <!--resultType: 单条记录所封装的类型-->
        <select id="getEmp" resultType="com.zjl.pojo.Emp">
            select *
            from emp
            <where>
            <if test="name != null">
                name like concat('%', #{name}, '%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
            </where>
            order by update_time desc
        </select>
    </mapper>
    
  • set:用于update语句,去除多余的,

  • foreach

    • XML映射文件

      <!--批量删除员工(18,19,20)-->
      <!--
          collection:需要遍历的集合
          item:遍历出来的每一个元素
          separator:分隔符
          open:遍历前开始拼接的SQL片段
          close:遍历结束后开始拼接的SQL片段
        -->
      <delete id="deleteByIds">
          delete from emp where id in
          <foreach collection="ids" item="id" separator="," open="(" close=")">
              #{id}
          </foreach>
      </delete>
      
    • Mapper

      // 批量删除员工
      public void deleteByIds(List<Integer> ids);
      
    • Test

      @Test
      public void testDeleteByIds(){
          List<Integer> ids = Arrays.asList(18,16,20);
          empMapper.deleteByIds(ids);
      }
      
  • sql和include标签,sql负责抽取SQL片段,include负责引用sql片段

    • sql片段

      <sql id="commonSelect">
              select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
              from emp
       </sql>
      
    • include引用sql片段

      <select id="getEmp" resultType="com.zjl.pojo.Emp">
              /*引用sql片段*/
              <include refid="commonSelect"/>
              <where>
              <if test="name != null">
                  name like concat('%', #{name}, '%')
              </if>
              <if test="gender != null">
                  and gender = #{gender}
              </if>
              <if test="begin != null and end != null">
                  and entrydate between #{begin} and #{end}
              </if>
              </where>
              order by update_time desc
      </select>
      

三、案例

开发包下的类间规范

1. 准备工作

  • 需求分析
  • 环境搭建

    • 准备数据库表(dept、emp)
    • 创建springboot工程,引入对应的起步依赖(web、mybatis、mysql驱动、lombok)
    • 配置文件application.properties中引入mybatis的配置信息,准备对应的实体类
    • 准备对应的Mapper、Service(接口、实现类)、Controller基础结构

2. 部门查询

  • Controller

    @Slf4j
    @RestController
    public class DeptController {
    
        // 日志  可以用@Slf4j代替
        //private static Logger log = (Logger) LoggerFactory.getLogger(DeptController.class);
        @Autowired
        private DeptService deptService;
        //@RequestMapping(value = "/depts",method = RequestMethod.GET) // 指定请求方式为GET
        @GetMapping("/depts")
        public Result list(){
            log.info("查询全部的部门数据");
            // 调用Service查询部门数据
            List<Dept> deptList= deptService.list();
            return Result.success(deptList);
        }
    }
    
  • Service

    public interface DeptService {
        /*
        * 查询全部部门的数据
        * */
        List<Dept> list();
    }
    
  • ServiceImpl

    @Service
    public class  DeptServiceImpl implements DeptService {
        @Autowired
        private DeptMapper deptMapper;
        @Override
        public List<Dept> list() {
            return deptMapper.list();
        }
    }
    
  • Mapper

    @Mapper
    public interface DeptMapper {
        /*
        * 查询部门的全部数据
        * */
        @Select("select * from dept")
        List<Dept> list();
    }
    

3. 根据Id删除部门

  • Controller

    // 根据id删除部门
        @DeleteMapping("/{id}")
        public Result deleteById(@PathVariable Integer id){
            log.info("删除的部门号Id为:" + id);
            deptService.deleteById(id);
            return Result.success();
        }
    
  • Service

    List<Dept> list();
    // 根据id删除部门号
    void deleteById(Integer id);
    
  • ServiceImpl

    @Override
    public void deleteById(Integer id) {
        deptMapper.deleteById(id);
    }
    
  • Mapper

    // 根据id删除部门号
    @Delete("delete from dept where id = #{id}")
    void deleteById(Integer id);
    

4. 新增员工

  • Controller

    // 新增部门
    @PostMapping
    public Result insertDept(@RequestBody Dept dept){
        log.info("新增部门:" + dept);
        deptService.insertDept(dept);
        return Result.success();
    }
    
  • Service

    //新增员工
    void insertDept(Dept dept);
    
  • ServiceImpl

    @Override
    public void insertDept(Dept dept) {
        dept.setCreateTime(LocalDateTime.now());
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.insertDept(dept);
    }
    
  • Mapper

    // 新增员工
    @Insert("insert into dept(name, create_time, update_time) values (#{name},#{createTime},#{updateTime})")
    void insertDept(Dept dept);
    

5. 修改部门

  • Controller

    // 得到修改的id
    @GetMapping("/{id}")
    public Result getUpdateId(@PathVariable Integer id) {
        log.info("根据Id查询:" + id);
        Dept dept = deptService.getUpdateId(id);
        return Result.success(dept);
    }
    // 修改部门
    @PutMapping
    public Result updateDept(@RequestBody Dept dept){
        log.info("修改部门名为:" + dept.getName());
        deptService.updateDept(dept);
        return Result.success();
    }
    
  • Service

    // 修改部门
    void updateDept(Dept dept);
    // 根据id查询
    Dept getUpdateId(Integer id);
    
  • ServiceImpl

    @Override
    public void updateDept(Dept dept) {
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.updateDept(dept);
    }
    
    @Override
    public Dept getUpdateId(Integer id) {
        return deptMapper.getUpdateId(id);
    }
    
  • Mapper

    // 修改部门名称
    @Update("update dept set name = #{name},update_time = #{updateTime} where id = #{id}")
    void updateDept(Dept dept);
    
    // 查询id
    @Select("select * from dept where id = #{id}")
    Dept getUpdateId(Integer id);
    

6. 员工信息分页查询

  • @RequestParam(defaultValue = "1"):给参数设置默认值
  • Controller

    // 分页查询
    @GetMapping("/emps")
    public Result page(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize){
      log.info("分页查询, 参数:{},{}",page,pageSize);
      PageBean pageBean = empService.page(page,pageSize);
      return Result.success(pageBean);
    }
    
  • Service

    PageBean page(Integer page, Integer pageSize);
    
  • ServiceImpl

    @Override
    public PageBean page(Integer page, Integer pageSize) {
        // 获取总记录数
        Long count = empMapper.count();
        // 获取分页的数据
        Integer start = (page - 1) * pageSize;
        List<Emp> empList = empMapper.page(start, pageSize);
        // 封装pageBean
        PageBean pageBean = new PageBean(count, empList);
        return pageBean;
    }
    
  • Mapper

    // 获取记录数
    @Select("select count(*) from emp")
    public Long count();
    
    // 获取分页的数据
    // start 查询第几页   pageSize每页的大小
    @Select("select * from emp limit #{start},#{pageSize}")
    public List<Emp> page(Integer start,Integer pageSize);
    

7. 分页插件PageHelper

  • 引入依赖

    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.4.6</version>
    </dependency>
    
  • Controller

    // 分页查询
    @GetMapping("/emps")
    public Result page(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize){
      log.info("分页查询, 参数:{},{}",page,pageSize);
      PageBean pageBean = empService.page(page,pageSize);
      return Result.success(pageBean);
    }
    
  • Service

    PageBean page(Integer page, Integer pageSize);
    
  • ServiceImpl

    @Override
    public PageBean page(Integer page, Integer pageSize) {
        // 设置分页参数
        PageHelper.startPage(page,pageSize);
        // 执行查询
        List<Emp> empList = empMapper.page();
        Page<Emp> p = (Page<Emp>) empList;
        // 封装pageBean
        PageBean pageBean = new PageBean(p.getTotal(), p.getResult());
        return pageBean;
    }
    
  • Mapper

    @Mapper
    public interface EmpMapper {
        @Select("select * from emp")
        public List<Emp> page();
    }
    
  • 映射文件.xml

    <!--条件查询-->
    <select id="list" resultType="com.zjl.pojo.Emp">
        select * from emp
        <where>
            <if test="name != null">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
        order by update_time desc
    </select>
    

8. 员工信息

  • 类注解:@RequestMapping("/emps")
  • Controller

    // 删除员工
    @DeleteMapping("/{ids}")
    public Result delete(@PathVariable List<Integer> ids){
        log.info("删除的员工id:{}",ids);
        empService.delete(ids);
        return Result.success();
    }   
    
  • Service

    //删除员工
    void delete(List<Integer> ids);
    
  • ServiceImpl

    @Override
    public void delete(List<Integer> ids) {
        empMapper.delete(ids);
    }
    
  • Mappering

    // 删除员工
    void delete(List<Integer> ids);
    
  • 映射文件.xml

    <mapper namespace="com.zjl.mapper.EmpMapper">
        <!--删除员工id:1,2,3-->
        <delete id="delete">
            delete from emp where id in
            <foreach collection="ids" item="id" separator="," open="(" close=")">
                #{id}
            </foreach>
        </delete>
    </mapper>
    

9.员工增加

  • Controller

    // 增加员工
    @PostMapping
    public Result insert(@RequestBody Emp emp){
        log.info("新增员工:{}",emp);
        empService.insert(emp);
        return Result.success();
    }
    
  • Service

    // 新增员工
    void insert(Emp emp);
    
  • ServiceImpl

    @Override
    public void insert(Emp emp) {
        // 补充基本数据
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        empMapper.insert(emp);
    }
    
  • Mapper

    // 新增员工
    @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) VALUES " +
            "(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
    void insert(Emp emp);
    

10.文件上传-本地存储

  • 构造一个唯一通用识别符(Uuid)

    String uuid = UUID.randomUUID().toString();
    
  • HTML

    <form action="/upload" method="post" enctype="multipart/form-data">
        姓名: <input type="text" name="username"><br>
        年龄: <input type="text" name="age"><br>
        头像: <input type="file" name="image"><br>
        <input type="submit" value="提交">
    </form>
    
  • Controller

    public class UploadController {
        @PostMapping("/upload")
        public Result upload(String username, Integer age, MultipartFile image) throws Exception {
           log.info("上传文件:{}, {}, {}",username,age,image);
           // 获取文件的后缀
            String originalFilename = image.getOriginalFilename();
            int index = originalFilename.lastIndexOf(".");
            String extname = originalFilename.substring(index);
            // 构造一个唯一通用识别符(Uuid)
            String uuid = UUID.randomUUID().toString();
            // 文件全名
            String newFileName = uuid + extname;
            log.info("新的文件名:{}",newFileName);
            // 将文件存储在服务器的磁盘文件中
            image.transferTo(new File("C:\\Users\\21136\\Desktop\\Java学习\\" + newFileName));
    
           return Result.success();
        }
    }
    
  • application.properties

    # 配置当个文件大小
    spring.servlet.multipart.max-file-size=10MB
    # 配置单次上传多个文件大小
    spring.servlet.multipart.max-request-size = 10MB
    

11.文件上传-阿里云OSS

  • 入门程序:上传一张图片

    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.common.auth.*;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.aliyun.oss.model.PutObjectRequest;
    import com.aliyun.oss.model.PutObjectResult;
    import java.io.FileInputStream;
    import java.io.InputStream;
    
    public class Aliyun {
    
        public static void main(String[] args) throws Exception {
            // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider =      CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            String accessKeyId = "LTAI5tAFhq3q2yMchrSBGjkM";
            String accesskeySecret = "gt8ixSRg9Cs8VP0Acn6CS7EJP2jIFJ";
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "web-zjl";
            // 文件上传后的名字
            String objectName = "1.jpg";
            // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
            // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
            String filePath= "C:\\Users\\21136\\Desktop\\图片\\logo\\banner2.png";
    
            // 创建OSSClient实例。
            OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId,accesskeySecret);
    
            try {
                InputStream inputStream = new FileInputStream(filePath);
                // 创建PutObjectRequest对象。
                PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
                // 创建PutObject请求。
                PutObjectResult result = ossClient.putObject(putObjectRequest);
            } catch (OSSException oe) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message:" + oe.getErrorMessage());
                System.out.println("Error Code:" + oe.getErrorCode());
                System.out.println("Request ID:" + oe.getRequestId());
                System.out.println("Host ID:" + oe.getHostId());
            } catch (ClientException ce) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message:" + ce.getMessage());
            } finally {
                if (ossClient != null) {
                    ossClient.shutdown();
                }
            }
        }
    } 
    

12. 阿里云OSS-集成

  • 步骤:

    1. 引入阿里云OSS上传文件工具类(根据官方文档修改)
    2. 上传图片接口的开发
  • AliOSSUtils(工具类)

    @Component
    public class AliOSSUtils {
    
        private String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        private String accessKeyId = "LTAI5tAFhq3q2yMchrSBGjkM";
        private String accessKeySecret = "gt8ixSRg9Cs8VP0Acn6CS7EJP2jIFJ";
        private String bucketName = "web-zjl";
    
        /**
         * 实现上传图片到OSS
         */
        public String upload(MultipartFile file) throws IOException {
            // 获取上传的文件的输入流
            InputStream inputStream = file.getInputStream();
    
            // 避免文件覆盖
            String originalFilename = file.getOriginalFilename();
            String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
    
            //上传文件到 OSS
            OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
            ossClient.putObject(bucketName, fileName, inputStream);
    
            //文件访问路径
            String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
            // 关闭ossClient
            ossClient.shutdown();
            return url;// 把上传到oss的路径返回
        }
    
    }
    
  • Controller

    public class UploadController {
        @Autowired
        private AliOSSUtils aliOSSUtils;
    
        @PostMapping("/upload")
        public Result upload(MultipartFile image) throws IOException {
            log.info("上传的文件名是:{}",image.getOriginalFilename());
            // 获取url
            String url = aliOSSUtils.upload(image);
            return Result.success(url);
        }
    }
    

13. 修改员工

  1. 查询回显

    • Controller

      // 查询回显
      @GetMapping("/{id}")
      public Result getById(@PathVariable Integer id){
          log.info("查询回显的Id:{}",id);
          Emp emp = empService.getById(id);
          return Result.success(emp);
      }
      
    • Service

      // 修改员工
      void update(Emp emp);
      
    • ServiceImpl

      @Override
      public void update(Emp emp) {
          // 修改  修改时间
          emp.setUpdateTime(LocalDateTime.now());
          empMapper.update(emp);
      }
      
    • Mapper

      // 查询回显
      @Select("select * from emp where id = #{id}")
      Emp getById(Integer id);
      
  2. 修改员工

    • Controller

      // 修改员工
      @PutMapping
      public Result update(@RequestBody Emp emp){
          log.info("更新员工信息为:{}",emp);
          empService.update(emp);
          return Result.success();
      }
      
    • Service

      // 修改员工
      void update(Emp emp);
      
    • ServiceImpl

      @Override
      public void update(Emp emp) {
          // 修改  修改时间
          emp.setUpdateTime(LocalDateTime.now());
          empMapper.update(emp);
      }
      
    • Mapper

      //修改员工
      void update(Emp emp);
      
    • 映射文件xml

      <!--  修改员工  -->
      <update id="update">
          update emp
          <set>
              <if test="username != null and username != ''">
                  username = #{username},
              </if>
              <if test="password != null and password != ''">
                  password = #{password},
              </if>
              <if test="name != null and name != ''">
                  name = #{name},
              </if>
              <if test="gender != null">
                  gender =  #{gender},
              </if>
              <if test="image != null and image!= ''">
                  image = #{image},
              </if>
              <if test="job != null">
                  job = #{job},
              </if>
              <if test="entrydate != null">
                  entrydate = #{entrydate},
              </if>
              <if test="deptId != null">
                  dept_id = #{deptId},
              </if>
              <if test="updateTime != null">
                  update_time = #{updateTime}
              </if>
          </set>
          where id = #{id}
      </update>
      

14. 配置格式

  1. 常见配置文件格式对比

    • xml(臃肿)

      <server>
          <port>8080</port>
          <address>127.0.0.1</address>
      </server>
      
    • properties

      server.port=8080
      server.address=127.0.0.1
      
    • yml/yaml(推荐)

      server:
        port: 9000
        address: 127.0.0.1
      
  2. yml的基础语法

    • 大小写敏感
    • 数值前边必须有空格,作为分隔符
    • 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能使用空格(idea中自动将Tab转换为空格)
    • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
    • #表示注释
    • port: 9000 9090前面必须有空格
  3. yml数据格式

    • 对象/Map集合:

      # 定义map集合
      user:
        name: Tom
        age: 20
        address: yunnan
      
    • 数组/List/Set集合:

      # 定义数组
      hobby:
        - java
        - python
        - c   
      
    • 文件配置

      # 定义map集合
      #user:
      #  name: Tom
      #  age: 20
      #  address: yunnan
      #
      ## 定义数组
      #hobby:
      #  - java
      #  - python
      #  - c
      
      spring:
        # 数据库的连接信息
        datasource:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/zjl
          username: root
          password: 801322
        # 文件上传
        servlet:
          multipart:
            max-file-size: 10MB
            max-request-size: 100MB
      # mybatis配置
      mybatis:
        configuration:
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
          map-underscore-to-camel-case: true
      
      # 阿里云OSS
      aliyun:
        oss:
          endpoint: https://oss-cn-hangzhou.aliyuncs.com
          accessKeyId: LTAI5tAFhq3q2yMchrSBGjkM
          accessKeySecret: gt8ixSRg9Cs8VP0Acn6CS7EJP2jIFJ
          bucketName: web-zjl
      

15. 登录功能

  • Controller

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        log.info("员工登录:{}", emp);
        Emp e = empService.login(emp);
        return e != null ? Result.success() : Result.error("用户名或者密码错误");
    }
    
  • Service

    // 登录
    Emp login(Emp emp);
    
  • ServiceImpl

    @Override
    public Emp login(Emp emp) {
        return empMapper.getByUsernameAndPassword(emp);
    }   
    
  • Mapper

    // 登录
    @Select("select * from emp where username = #{username} and password = #{password}")
    Emp getByUsernameAndPassword(Emp emp);
    

16. 登录校验

  • 登录标记:用户登录成功之后,每一次请求中,都可以获取到该标记
  • 统一拦截

    • 过滤器Filter
    • 拦截器Interceptor
  • 会话技术

    • 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多个请求和响应
    • 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据
    • 会话跟踪方案:

      • 客户端会话跟踪技术:Cookie
      • 服务端会话跟踪技术:Session
      • 令牌技术
  • JWT令牌

    • 引入依赖

      <!--  JWT令牌      -->
      <dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.9.1</version>
      </dependency>
      
    • 生成JWT

      /*
      * 生成JWT令牌
      * */
      @Test
      public void testGenJWT(){
          Map<String,Object> claims = new HashMap<>();
          claims.put("id",1);
          claims.put("name","Tom");
      
          String jwt = Jwts.builder()
                  .signWith(SignatureAlgorithm.HS256, "itzjl") // 签名算法
                  .setClaims(claims) // 自定义内容(数据载荷)
                  .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为1h
                  .compact();//设置为字符串类型
          System.out.println(jwt); // eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwNTQ2NDQ1M30.P0tgZ2hetzQlM6D1gUwMylwS_iqROy21kwLs2JjKZDA
      }
      
    • 解析JWT

      @Test
      public void testParseJWT(){
          final Claims claims = Jwts.parser()
                  .setSigningKey("itzjl") //密钥
                  .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwNTQ2NDQ1M30.P0tgZ2hetzQlM6D1gUwMylwS_iqROy21kwLs2JjKZDA")
                  .getBody();
          System.out.println(claims); // {name=Tom, id=1, exp=1705464453}
      }
      
  • 具有JWT令牌的登录功能

    • Controller

      @PostMapping("/login")
      public Result login(@RequestBody Emp emp){
          log.info("员工登录:{}", emp);
          Emp e = empService.login(emp);
          if (e != null) {
              Map<String,Object> claims = new HashMap<>();
              claims.put("id", e.getId());
              claims.put("name", e.getName());
              claims.put("username", e.getUsername());
              String jwt = JwtUtils.generateJwt(claims);
              return Result.success(jwt);
          }
          return Result.error("用户名或者密码错误");
      }
      
  • Filter过滤器:

    • 需要在使用类上加上注解@ServletComponentScan
    • @WebFilter(urlPatterns = "/")拦截的路径,可以具体拦截某个路径urlPatterns = "/login";拦截所有urlPatterns = "/";按照目录拦截urlPatterns = "/emps/*"
    • 过滤器链:多个过滤器形成,执行的顺序是根据类名的字母顺序

      package com.zjl.filter;
      
      import jakarta.servlet.*;
      import jakarta.servlet.annotation.WebFilter;
      
      import java.io.IOException;
      
      @WebFilter(urlPatterns = "/*")
      public class DemoFilter implements Filter {
          @Override // 指挥被调用一次
          public void init(FilterConfig filterConfig) throws ServletException {
              System.out.println("初始化方法被执行了");
          }
      
          @Override // 拦截到请求后调用,可以多次被调用
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
              System.out.println("拦截到请求");
              // 放行
              filterChain.doFilter(servletRequest,servletResponse);
          }
      
          @Override // 也是只被调用一次
          public void destroy() {
              System.out.println("销毁方法被执行了");
          }
      }
      
  • 具有jwt+Filter的登录功能

    package com.zjl.filter;
    
    import com.alibaba.fastjson.JSONObject;
    import com.zjl.pojo.Result;
    import com.zjl.utils.JwtUtils;
    import jakarta.servlet.*;
    import jakarta.servlet.annotation.WebFilter;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.util.StringUtils;
    
    import java.io.IOException;
    @Slf4j
    @WebFilter(urlPatterns = "/*")
    public class LoginCheckFilter implements Filter {
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            // 1. 获取请求的url
            String url = request.getRequestURL().toString();
            log.info("请求的路径为:{}",url);
            // 2. 判断url是否包含login,如果包含,说明是登录操作,放行
            if (url.contains("login")){
                log.info("登录操作,放行");
                filterChain.doFilter(servletRequest,servletResponse);
                return;
            }
            // 3.获取请求头的令牌(Token)
            String jwt = request.getHeader("token");
            // 4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
            if (!StringUtils.hasLength(jwt)){
                log.info("请求头token为空,返回未登录的信息");
                Result error = Result.error("NOT_LOGIN");
                // 手动转为json格式  ----> 阿里巴巴fastJSON
                String notLogin = JSONObject.toJSONString(error);
                response.getWriter().write(notLogin);
                return;
            }
            // 5. 解析Token,如果解析失败,返回错误结果(未登录)
            try {
                JwtUtils.parseJWT(jwt);
            } catch (Exception e) {
                e.printStackTrace();
                log.info("解析令牌失败,返回未登录错误信息");
                Result error = Result.error("NOT_LOGIN");
                // 手动转为json格式  ----> 阿里巴巴fastJSON
                String notLogin = JSONObject.toJSONString(error);
                response.getWriter().write(notLogin);
                return;
            }
            // 6. 放行
            log.info("令牌合法,放行");
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }
    
  • 拦截器(Interceptor):类似于过滤器

    • 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行
    • 作用:拦截请求,在指定的方法调用前后,根据业务需求执行预先设定的代码
    • 需要配置类

      public class LoginCheckInterceptor implements HandlerInterceptor {
      
          @Override  // 目标资源方法运行前运行,返回true放行,不放行返回false
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              String url = request.getRequestURL().toString();
              log.info("请求的路径为:{}",url);
              // 2. 判断url是否包含login,如果包含,说明是登录操作,放行
              if (url.contains("login")){
                  log.info("登录操作,放行");
                  return true;
              }
              // 3.获取请求头的令牌(Token)
              String jwt = request.getHeader("token");
              // 4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
              if (!StringUtils.hasLength(jwt)){
                  log.info("请求头token为空,返回未登录的信息");
                  Result error = Result.error("NOT_LOGIN");
                  // 手动转为json格式  ----> 阿里巴巴fastJSON
                  String notLogin = JSONObject.toJSONString(error);
                  response.getWriter().write(notLogin);
                  return false;
              }
              // 5. 解析Token,如果解析失败,返回错误结果(未登录)
              try {
                  JwtUtils.parseJWT(jwt);
              } catch (Exception e) {
                  e.printStackTrace();
                  log.info("解析令牌失败,返回未登录错误信息");
                  Result error = Result.error("NOT_LOGIN");
                  // 手动转为json格式  ----> 阿里巴巴fastJSON
                  String notLogin = JSONObject.toJSONString(error);
                  response.getWriter().write(notLogin);
                  return false;
              }
              // 6. 放行
              log.info("令牌合法,放行");
              return true;
          }
      }
      

17. 异常处理

  • @RestControllerAdvice = @Controller + @RespondBody
  • 转为josn格式

    // 全局异常处理器
    @RestControllerAdvice
    public class GlobalExceptionHandler {
        @ExceptionHandler(Exception.class)
        public Result ex(Exception exception){
            exception.printStackTrace();
            return Result.error("对不起,请联系管理员");
        }
    }
    

18. 事务管理

  • Spring事务管理

    • 注解:@Transactional
    • 位置:业务(Service)层的方法上、类上、接口上
    • 作用:将前面的方法交给Spring进行事务管理,方法执行前,开启事务;成功执行完毕后,提交事务;出现异常,回滚事务;
  • 事务进阶

    • 默认情况下,只出现RunTimeException才会回滚,rollbackFor属性用于控制出现何种异常类型,回滚事务
    • @Transactional(rollbackFor = Exception.class):使用该属性后,所有类型都可以回滚
    • 事务传播行为propagation:
      1. Propagation.REQUIRED:共用一个事务
      2. Propagation.REQUIRES_NEW:不公用事务,互不影响

19. AOP基础

  • AOP:面向切面编程,其实就是面向特定方法的编程
  • 在pom.xml引入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
  • 编写AOP程序:*** com.zjl.service..(..)**// 切入点表达式

    @Slf4j
    @Component
    @Aspect // AOP类
    public class TimeAspect {
    
        @Around("execution(* com.zjl.service.*.*(..))") // 切入点表达式
        public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            // 1. 记录开始时间
            Long begin = System.currentTimeMillis();
            // 2. 调用原始方法运行
            Object result = proceedingJoinPoint.proceed();
            // 3. 记录结束时间,计算方法执行耗时
            Long end = System.currentTimeMillis();
            log.info(proceedingJoinPoint.getSignature() + "方法的执行耗时:{}ms",end - begin);
    
            return result;
        }
    }
    

20. AOP的核心

  • 连接点:JionPiont,可以被AOP控制的方法(暗含方法执行时的相关信息)
  • 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现出一个方法)
  • 切入点:PiontCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
  • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
  • 目标对象:Target,通知所应用的对象

21. AOP的进阶

  • 通知类型

    1. @Around:环绕通知,次注解标注的通知方法在目标方法前、后都会被执行
    2. @Before:前置通知,此注解标注的通知方法在目标方法前执行
    3. @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会被执行
    4. @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会被执行
    5. @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
  • 切入点的抽取

    @Pointcut("execution(* com.zjl.service.*.*(..))")
    public void pt(){}
    
  • 通知顺序

    • 根据类名字母顺序
    • 可用@Order(1)注解来指定执行顺序,前置通知数字越小越先执行,后置通知相反
  • 切入点表达式

    • 常见形式

      1. execution

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值