Java Web

Web 网站的开发模式

Web 网站的工作流程

Web 开发课程安排

包括:

  1. 前端 Web 开发

    • HTML、CSS、JavaScript
    • Vue、ElementUI、Nginx
    • Ajax、Axios
  2. 后端 Web 开发

    • Maven

    • SpringBoot Web 基础篇

    • MySQL

    • SpringBoot Mybatis

    • SpringBoot Web 开发篇

    • SpringBoot Web 进阶篇


Web 前端开发

Web 标准

Web 标准也称为网页标准,由一系列的标准组成,大部分由 W3C 负责指定。

三个组成部分:

  1. HTML:负责网页的结构(页面元素和内容)
  2. CSS:负责网页的表现(页面元素的外观、位置等页面样式,如颜色、大小等)
  3. JavaScript:负责网页的行为(交互效果)

HTML & CSS

JavaScript & Vue

什么是 JavaScript

  • JavaScript 是一门跨平台面向对象脚本(不需要编译)语言。是用来控制网页行为的,它能使网页可交互。

  • JavaScript 和 Java 是完全不同的语言,不论是概念还是设计。但是基础语法类似。

  • JavaScript 在 1995 年有 Brendan Eich 发明,并于 1997 年成为 ECMA 标准

    ECMA 国际(前身为欧洲计算机制造商协会),制定了标准化的脚本程序设计语言 ECMAScript,这种语言得到广泛应用。而 JavaScript 是遵守 ECMAScript标准的。

  • ECMAScript 6 是最新的 JavaScript 版本 (发布于 2015 年)。

JavaScript 引入方式

  • 内部脚本:将 JavaScript 代码定义在 HTML页面中
    • JavaScript 代码必须位于 <script></script> 标签之间
    • 在 HTML 文档中,可以在任意地方,放置任意数量<script>
    • 一般会把脚本置于 <body> 元素的底部,可以改善页面显示速度
  • 外部脚本:将 JavaScript 代码定义在外部 js 文件中,然后引入到 HTML 页面中
    • 外部 js 文件中,只包含 js 代码,不包含 <script>标签
    • <script> 标签不能自闭合

JavaScript 基础语法

书写语法
  • 区分大小写
  • 每行结尾的分号可有可无
  • 注释:
    • 单行注释: //
    • 多行注释:/* */
  • 大括号表示代码块
  • 输出语句:
    • window.alert(): 浏览器弹出警告框
    • document.write():输出到 HTML 文档,在浏览器页面中展示
    • console.log():输出到浏览器控制台
变量
  • JavaScript 中用 var 关键字来声明变量。

  • JavaScript 是一门弱类型语言,变量可以存放不同类型的值

  • 变量名需要遵循如下规则:

    • 变量名可以是任何字母、数字、下划线或美元符号
    • 数字不能开头
    • 建议使用驼峰命名法
  • var 关键字定义的变量的特点:

    1. 定义出来的变量属于全局变量
    2. 相同变量可以重复定义
    • ECMAScript 6 新增了 let 关键字来定义变量。它的用法类似于 var,但是所声明的变量,只在 let 关键字所在的代码块内有效(局部变量),且不允许重复声明
    • ECMAScript 6 新增了 const 关键字,用来声明一个只读的常量。一旦声明,常量的值就不能改变。
数据类型、运算符、流程控制语句

数据类型:JavaScript 中分为:原始类型引用类型

  • 原始类型有:

    • number:数字(整数、小鼠、NaN)
    • string:字符串,单双引号皆可
    • boolean:布尔类型,取值为 turefalse
    • null:对象为空
    • undefined:当声明的变量未初始化时,该变量的默认类型 是 undefined

    使用 typeof 运算符可以获取数据类型

  • 运算符:

    • 算数运算符:+、-、*、/、%、++、–
    • 赋值运算符:=、+=、-=、*=、/=、%=
    • 比较运算符:>、<、>=、<=、!=、==、===
    • 逻辑运算符:&&、||、!
    • 三元运算符:条件表达式 ? true_value : false_value

    == 与 ===:

    • == 在比较时会先进行类型转换,再判断是否相等
    • === 在比较时不会进行类型转换,如果类型不同直接返回 false

    类型转换:

    • 字符串类型转为数字类型:
      • 将字符串字面值转为数字。如果字面值不是数字,则转为 NaN
      • 使用 parseInt() 函数转换,该函数从字符串头开始匹配数字,直到遇到非数字为止
      • 举例:parseInt(12) -> 12, parseInt(12A45) -> 12, parseInt(A45) -> NaN
    • 其他类型转为布尔类型:
      • Number:0 和 NaN 为 false,其他均为 true
      • String:空字符串为 false,其他均为 true
      • Null 和 undefined:均为 false
  • 流程控制语句:

    • if…else if…else
    • switch
    • for
    • while
    • do … while
    • 语法与 Java 一致

JavaScript 函数

  • 介绍:函数(方法)是被设计为执行特定任务的代码块

  • 定义:JavaScript 函数通过 function 关键字进行定义,语法为:

    function funcName(arg1, arg2, ...) {
    // function body
    }

    或:

    var funcName = function(arg1, arg2, ...) {
    // funciton body
    }
  • 注意:

    • 形式参数不需要类型。因为 JavaScript 是弱类型语言
    • 返回值也不需要定义类型,在函数内部直接使用 return 返回即可
  • 调用:函数名称(实参列表)

  • 注意事项:JavaScript 中,函数调用可以传递任意个数的参数(但函数使用哪些参数会按照顺序来决定)

JavaScript 对象

基础对象
Array
  • JavaScript中 Array 对象用于定义数组。

  • 定义:

    var name = new Array(element list);

    或:

    var name = [element list];
  • 访问:name[index];

  • 特点:

    1. 长度可变:

      var arr = [1, 2, 3, 4];
      arr[10] = 50; // 允许的操作
      console.log(arr[10]); // 50
      console.log(arr[9]); // undefined
      console.log(arr[8]); // undefined
    2. 类型可变(任意):

      arr[5] = 'hello'; // 允许的操作
      console.log(arr[5]); // 'hello'
  • 属性:

    • length:设置或返回数组中元素的数量
  • 方法:

    • forEach():遍历数组中的每个有值的元素,并调用一次传入的函数:

      arr.forEach(function(e) {
      console.log(e); // 遍历数组每个有值的元素,并对其执行一次函数的操作
      })

      或用箭头函数简化函数定义:

      arr.forEach((e) => {
      console.log(e);
      })
    • push():将新元素添加到数组的末尾,并返回新的长度

    • splice():从数组中删除元素

      arr.splice(2, 2); // 从arr的索引为2的位置开始删2个元素
String
  • 创建方式:
    • 方式1:var name = new String("...");
    • 方式2:var name = "...";
  • 属性:length:获取字符串的长度
  • 方法:
    • charAt():返回在指定位置的字符
    • indexOf():检索某个子字符串第一次出现的位置
    • trim():去除字符串两边的空格,返回新字符串
    • substring():提取字符串中两个指定索引之间(含头不含尾)的字符子串
JSON
  • JavaScript 自定义对象

    • 定义格式:

      var name = {
      attr1: value1,
      attr2: value2,
      ...
      attrn: valuen,
      funcName: function(arg list){},
      funcName(){} // 定义函数的另一种语法
      };
    • 调用格式:

      • name.attr;
      • name.funcName(arg list);
  • JSON 介绍

    • 概念:JSON 全称为 JavaScript Object Notation,JavaScript对象标记法

    • JSON 是通过 JavaScript 对象标记法书写的文本

    • JSON 格式的数据:

    • key 都要用双引号引起来,其他的与 JavaScript对象的格式相同

    • 由于 JSON语法简单,层次结构鲜明,现在多用于数据载体,在网络中进行数据传输

  • JSON 基础语法

    • 定义:var name = '{"key1": value1, "key2": value2}';

      • 示例:var userStr = '{"name": "Jerry", "age": 18, "addr": ["北京", "上海", "西安"]}';

      • value 的数据类型可以为:

        • 数字(整数或浮点数)
        • 字符串(在双引号中)
        • 逻辑值(true 或 false)
        • 数组(在方括号中)
        • 对象(在花括号中)
        • null
      • JSON 字符串转为 JS 对象

        var jsObject = JSON.parse(userStr);

      • JS 对象转为 JSON 字符串

        var jsonStr = JSON.stringify(jsObject);

浏览器对象模型 - BOM
  • 概念:Browser Object Model 浏览器对象模型,允许 JavaScript 与浏览器对话,JavaScript 将浏览器的各个组成部分封装为对象。
  • 组成:
    • Window:浏览器窗口对象
    • Navigator:浏览器对象
    • Screen:屏幕对象
    • History:历史记录对象
    • Location:地址栏对象
  • Window
    • 介绍:浏览器窗口对象
    • 获取:直接使用 window,其中 window. 可省略
    • 属性
      • history:对 History 对象的只读引用。
      • location:对于窗口或框架的 Location 对象
      • navigator:对 Navigator 对象的只读引用
    • 方法
      • alert():显示带有一段消息和一个确认按钮的警告框
      • confirm():显示带有一段消息以及确认按钮和取消按钮的对话框,有返回值,点击确定返回 true,点击取消返回 false
      • setInterval():按照指定的周期(毫秒为单位)来调用函数或计算表达式
      • setTimeout():在指定的毫秒数后调用函数或计算表达式
    • Location
      • 介绍:地址栏对象
      • 获取:使用 window.location 获取,其中 window. 可以省略
      • 属性:
        • href:设置或返回完整的 URL
文档对象模型 - DOM
  • 概念:Document Object Model,文档对象模型

  • 将标记语言的各个组成部分封装为对应的对象:

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

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

    1. Core DOM - 所有文档类型的标准模型‘
      • Document:整个文档对象
      • Element:元素对象
      • Attribute:属性对象
      • Text:文本对象
      • Comment:注释对象
    2. XML DOM- XML 文档的标准模型
    3. HTML DOM - HTML 文档的标准模型
      • Image:<img>
      • Button:<input type='button'>
  • 获取指定 HTML 元素的对象

    • HTML 中的 Element 对象可以通过 Document 对象获取,而 Document 对象是通过 window 对象获取的

    • Document 对象中提供了以下获取 Element 元素对象的函数:

      1. 根据 id 属性值获取,返回单个 Element 对象

        var h1 = document.getElementById('h1');

      2. 根据标签名称获取,返回 Element 对象数组

        var divs = document.getElementsByTagName('div');

      3. 根据 name 属性值获取,返回 Element 对象数组

        var hobbies = document.getElementsByName('hobby');

      4. 根据 class 属性值获取,返回 Element 对象数组

        var clss = document.getElementsByClassName('cls');

  • 改变 Element 元素

    • 查阅文档,根据 Element 元素所拥有的属性和方法来改变 Element 元素

JavaScript 事件监听

  • 事件: HTML 事件是发生在 HTML 元素上的 “事情”。比如:
    • 按钮被点击
    • 鼠标移动到元素上
    • 按下键盘按键
  • 事件监听:JavaScript 可以在事件被侦测到时 执行代码
事件绑定
  • 方式一:通过 HTML 标签中的事件属性进行绑定

    <input type="button" onclick="on()" value="button1">

    <script>
    function on() {
    alert("I'm clicked!");
    }
    </script>
  • 方式二:通过 DOM 元素属性绑定

    <input type="button" id="btn" value="button2">

    <script>
    document.getElementById('btn').onclick = function() {
    alert("I'm clicked!");
    }
    </script>
常见事件
事件名 说明
onclick 鼠标单击事件
onblur 元素失去焦点
onfocus 元素获得焦点
onload 某个页面或图像被完成加载
onsubmit 当表单提交时触发该事件
onkeydown 某个键盘的键被按下
onmouseover 鼠标被移到某元素之上
onmouseout 鼠标从某元素上移开

Vue

什么是Vue?
  • Vue 是一套前端框架,免除原生 JavaScript 中的 DOM 操作,简化书写

  • 基于 MVVM (Model-View-ViewModel) 思想,实现数据的双向绑定,将编程的关注点放在数据上

  • 官网:https://v2.cn.vuejs.org/

    框架:是一个半成品软件,是一套可重用的、通用的软件基础代码模型。基于框架进行开发,更加快捷、更加高效。

  • MVVM模型

Vue快速入门
  • 新建 HTML 页面,引入 Vue.js 文件

    <script src = "js/vue.js"></scripts>
  • 在 JS 代码区域,创建 Vue 核心对象,定义数据模型

    <script>
    new Vue({
    el: "#app",
    data: {
    message: "Hello Vue!"
    }
    })
    </script>
  • 编写视图

    <div id="app">
    <input type="text" v-model="message">
    {{message}}
    </div>

    插值表达式:

    • 形式:
    • 内容可以是:
      • 变量
      • 三元运算符
      • 函数调用
      • 算术运算
Vue 常见指令
  • 指令:指 HTML 标签上带有 v- 前缀的特殊属性,不同指令具有不同含义。

  • 常用指令:

    指令 作用
    v-bind 为 HTML 标签绑定属性值,如设置 href,css 样式等
    v-model 在表单元素上创建双向数据绑定
    v-on 为 HTML 标签绑定事件
    v-if 条件性地渲染某元素,判定为 true 是渲染,否则不渲染
    v-else-if 同上
    v-else 同上
    v-show 根据条件展示某元素,区别在于切换的是 display 属性的值
    v-for 列表渲染,遍历容器的元素或者对象的属性
    • v-bind:

      常规用法:<a v-bind:href="url">xxx</a>

      简化用法:<a :href="url">xxx</a>

    • v-model:

      <input type="text" v-model="url">

      <script>
      new Vue({
      el: "#app",
      data: {
      url:"https://www.google.com"
      }
      })
      </script>

    注意事项:通过 v-bind 或者 v-model 绑定的变量,必须在数据模型中声明。

    • v-on:

      常规用法:<input type="text" value="button" v-on:click="handle()">

      简化写法:<input type="text" value="button" @click="handle()">

      <script>
      new Vue({
      el: "#app",
      data: {
      // ...
      },
      methods: {
      handle: function() {
      alert("I'm clicked!");
      }
      }
      })
      </script>
    • v-if、v-else-if、v-else:

      年龄{{age}},经判定为:
      <span v-if="age <= 35">年轻人</span>
      <span v-else-if="age > 35 && age < 60">中年人</span>
      <span v-else>老年人</span>
    • v-show:

      年龄{{age}},经判定为:
      <span v-show:"age <= 35">年轻人</span>
    • v-for:

      <div v-for="addr in addrs">
      {{addr}}
      </div>
      <div v-for="(addr, index) in addrs">
      {{index + 1}} : {{addr}}
      </div>
      data: {
      ...
      addrs: ['北京', '上海', '广州', '深圳', '成都', '杭州']
      },
Vue 生命周期
  • 生命周期:指一个对象从创建到销毁的整个过程。

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

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

Ajax & Axios & Element

Ajax

  • 概念:Asynchronous JavaScript And XML,异步的 JavaScript 和 XML

  • 作用:

    • 数据交换:通过 Ajax 可以给服务器发送请求,并获取服务器相应的数据

    • 异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用的校验等等。

  • 同步与异步

    • 同步:服务器处理请求时客户端需要等待,直到服务器返回数据后才能进行后面操作。
    • 异步:服务器处理请求时客户端可以进行其他操作,不需要等待服务器端。
  • 原生 Ajax(繁琐,已弃用)

    1. 准备数据地址:http://yapi.smart-xwork.cn/mock/169327./emp/list

    2. 创建 XMLHttpRequest 对象:用于和服务器交换数据

    3. 向服务器发送请求

    4. 获取服务器响应数据

      <body>
      <input type="button" value="获取数据" onclick="getData()">
      <div id= "div1"></div>
      </body>

      <script>
      function getData() {
      // 1.创建XMLHttpRequest
      var xmlHttpRequest = new XMLHttpRequest();

      // 2.发送异步请求
      xmlHttpRequest.open('GET', http://yapi.smart-xwork.cn/mock/169327./emp/list);
      xmlHttpRequest.send(); // 发送请求
      // 3.获取服务器响应数据
      xmlHttpRequest.onreadystatechange = function() {
      if (xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200) {
      document.getElementById('div1').innerHTML = xmlHttpRequest.responseText;
      }
      }
      }
      </script>
  • Axios(基于原生 Ajax 封装,简化书写,快速开发)

    • 官网:https://www.axios-http.cn/

    • Axios 使用步骤:

      1. 引入 Axios 的 js 文件:

        <script src="js/axios-0.18.0.js"></script>

      2. 使用 Axios 发送请求,并获取响应结果

        axios({
        method:"get",
        url:"http://..."
        }).then((result) => {
        console.log(result.data);
        });
        axios({
        method:"post",
        url:"http://...",
        data:"id=1"
        }).then((result) => {
        console.log(result.data);
        });

        then 方法中需要传入一个函数参数,称为成功回调函数,在成功获得服务器数据时调用,数据被封装为一个对象赋值给 result(回调函数参数)。

      • 请求方式简化(推荐):

        • axios.get(url [, config])

        • axios.delete(url [, config])

        • axios.post(url [, data [, config]])

        • axios.put(url [,data [,config]])

        • 使用方式示例:

          axios.get("http://...").then((result) => {
          console.log(result.data);
          });
          axios.post("http://...", "id=1").then((result) => {
          console.log(result.data);
          });

前后端分离开发

介绍
早期开发模式:前后端混合开发
  • 前端人员开发好一个静态的 HTML 页面,剩下的 CSS JS AXIOS JAVA 数据库等都交给主力开发人员来开发,前端代码和后端代码都放在一个工程文件中。
  • 问题:
    1. 沟通成本高
    2. 分工不明确
    3. 项目不便于管理
    4. 项目不便于维护扩展
当前主流开发模式:前后端分离开发

前端团队负责前端工程,后端团队负责后端工程,前端与后端交互依靠异步请求,保证两者能够顺利交互的开发规范定义在接口文档中。

接口文档由产品经理通过分析页面原型和需求来定义和书写。

开发流程:

需求分析 -》 接口定义 -》 前后端并行开发 -》 测试 -》 前后端联调测试

YApi
  • 介绍:YApi 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务
  • 官网:[http://yapi.smart-xwork.cn/](YApi 官网)
  • 功能:
    1. API 接口管理
    2. Mock 服务:模拟真实接口,生成接口的模拟测试数据,用于前端工程测试
  • 使用步骤:
    1. 添加项目
    2. 添加分类
    3. 添加接口
    4. 完善接口信息
    5. (可选)添加 mock 期望

前端工程化

前端工程化开发:在企业级的前端项目开发中,把前端开发所需的工具、技术、流程、经验等进行规范化,标准化(借助 Vue 提供的脚手架工具)。

  • 模块化:将 JS 和 CSS 制作成一个个可以复用的模块
  • 组件化:将 UI 结构、CSS 样式以及 JS 的行为封装成一个个的组件
  • 规范化:提供一套标准的规范的目录结构、编码规范和接口规范,所有开发人员都遵循这一套统一的规范进行开发
  • 自动化:项目的构建、部署和测试自动化进行
环境准备
vue-cli
  • 介绍:vue-cli 是 Vue 官方提供的一个脚手架,用于快速生成一个 Vue 的项目模板
  • vue-cli 提供了如下功能:
    • 统一的目录结构
    • 本地调试
    • 热部署
    • 单元测试
    • 集成打包上线
  • 依赖环境:NodeJS
Vue 项目简介
  • 目录结构:

    基于 Vue 脚手架创建出来的工程,有标准的目录结构,如下:

    • node_modules:存放整个项目的依赖包
    • public:存放项目的静态文件
    • src:存放项目的源代码
    • package.json:存放模块基本信息,项目开发所需要模块,版本信息
    • vue.config.js:保存 vue 配置的文件,如:代理、端口的配置等

    src 目录下的结构如下:

    • assets:静态资源
    • components:可重用的组件
    • router:路由配置
    • views:视图组件(页面)
    • App.vue:入口页面(根组件)
    • main.js:入口 js 文件
Vue 项目开发流程

Vue 组件库 Element

什么是 Element
  • Element:是饿了么团队研发的,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件
  • 组件:组成网页的部件,例如 超链接、按钮、图片、表格、表单、分页条等等
  • 官网:https://element.eleme.cn/#/zh-CN
快速入门
  • 安装 ElementUI 组件库(在当前工程的目录下),在命令行执行指令:

    npm install element-ui@2.15.3

  • 引入 ElementUI 组件库

    // 在 main.js 文件中引入
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';

    Vue.use(ElementUI);
  • 访问官网,复制组件代码,调整

常见组件
表格
  • Table 表格:用于展示多条结构类似的数据,可对数据进行排序、筛选、对比或其他自定义操作。
分页
  • Pagination 分页:当数据量过多时,使用分页分解数据。
对话框
  • Dialog 对话框:在保留当前页面状态的情况下,告知用户并承载相关操作。
表单
  • Form 表单:由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据。
案例

完成如下页面:

要求:根据页面原型完成员工管理页面开发,并通过 Axios 完成数据异步加载。

步骤:

  1. 创建页面,完成页面的整体布局规划
  2. 布局中各个部分的组件实现
  3. 列表数据的异步加载,并渲染展示

Vue 路由

  • 前端路由:URL 中 #号 与 组件 之间的对应关系
Vue Router
  • 介绍:Vue Router 是 Vue 的官方路由

  • 组成:

    • VueRouter:路由器类,根据路由请求在路由视图中动态渲染选中的组件。其中维护了一份路由表,记录了路由路径和组件的对应关系。
    • <router-link>:请求链接组件,浏览器会解析成 <a>
    • <router-view>:动态视图组件,用来渲染展示与路由路径对应的组件

打包部署

打包

方式:在项目目录下执行指令:npm run build,之后会在项目目录下生成一个 dist 文件夹,为打包好的文件。

部署
Nginx
  • 介绍:Nginx 是一款轻量级的 Web 服务器/反向代理服务器及电子邮件 (IMAP/PO3)代理服务器。其特点是占用内存少,并发能力强,在各大型互联网公司都有非常广泛的使用

  • 官网:https://nginx.org/

  • Nginx 目录结构如下:

    其中:

    • conf:配置文件目录
    • html:静态资源文件目录
    • logs:日志文件目录
    • temp:临时文件目录
  • 步骤:将打包好的 dist 目录下的文件,复制到 nginx 安装目录的 html 目录下即可

    注意事项:Nginx 默认占用 80 端口号,如果 80 端口号被占用,可以在 /conf/nginx.conf 中修改端口号

    附在 Windows 控制台查看占用端口程序的代码:netstat -ano | findStr port_no


Web 后端开发

Maven

什么是 Maven?

  • Maven 是 Apache 旗下的一个开源项目,是一款用于管理和构建 Java 项目的工具

    Apache 软件基金会,成立于 1999年 7 月,是目前世界上最大的最受欢迎的开源软件基金会,也是一个专门为支持开源项目而生的非盈利组织

    开源项目一览:[https://www.apache.org/index.html#project-list](Apache 开源项目一览)

Maven 的作用

  1. 依赖管理:方便快捷地管理项目依赖的资源包(jar包),避免版本冲突问题

  2. 统一项目结构:提供标准、统一的项目结构

    • main:实际项目资源
      • java:java 源代码目录
      • resources:配置文件目录
    • test:测试项目资源
    • pom.xml:项目配置文件
  3. 项目构建:标准跨平台(Linux、Window、MacOS)的自动化项目构建方式

Maven 概述

介绍
  • Apache Maven 是一个项目管理和构建工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建

  • 作用:

    • 方便的依赖管理
    • 统一的目录结构
    • 标准的项目构建流程
  • 官网:[http://maven.apache.org/](Maven 官网)

  • Maven 基本模型如下:

    • 仓库:用于存储资源,管理各种 jar 包
      • 本地仓库:自己计算机上的一个目录
      • 中央仓库:由 Maven 团队维护的全球唯一的仓库。仓库地址:[https://repo1.maven.org/maven2/](Maven 中央仓库)
      • 远程仓库(私服):一般为公司团队搭建的私有仓库。
      • 查找顺序:先查找本地仓库,如果没有,再从远程仓库(如果有)下载,还未找到再从中央仓库下载至本地仓库。
安装

安装步骤:

  1. 解压下载好的 zip 包

    目录结构如下:

    • bin:存放的是可执行文件,负责执行一些项目周期相关的指令(如 mvn
    • config:存放 Maven 的配置文件
    • lib:存放 Maven 本身所依赖的 jar 包资源(因为 Maven 基于 Java 开发)
  2. 配置本地仓库:修改 conf/settings.xml 中的 <localRepository> 为一个指定目录,如:

    <localRepository>E:\develop\apache-maven-3.6.1\mvn_repo</localRepository>
  3. 配置阿里云私服:修改 conf/settings.xml 中的 <mirrors> 标签,为其添加如下子标签:

    <mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public</url>
    <mirrorOf>central</mirrorOf>
    </mirror>
  4. 配置环境变量:MAVEN_HOME 配置为 Maven 的解压目录,并将其 bin 目录加入 PATH 环境变量

IDEA 集成 Maven

  • 配置 Maven 环境

    配置当前工程:

    • IDEA 中选择 File --> Settings --> Build, Execution, Deployment --> Build Tools --> Maven
    • 设置 IDEA 使用本地安装的 Maven,并修改配置文件及本地仓库路径

    配置 Maven 环境(全局):在创建项目页 Customize 栏中的 All Settings 处配置,步骤同上

  • 创建 Maven 项目

    1. 创建模块,Build system选择 Maven

    2. Advanced Settings 里填写模块名称,坐标信息,点击 Create,创建完成

      Maven 坐标:

      • 什么是坐标?
        • Maven 中的坐标是资源的唯一标识,通过该坐标可以唯一定位资源位置
        • 使用坐标来定义项目或引入项目中需要的依赖
      • 坐标的主要组成
        • groupId:定义当前 Maven 项目隶属组织名称(通常是域名反写,例如:com.yl)
        • artifactId:定义当前 Maven 项目名称(通常是模块名称,例如 order-service、goods-service)
        • version:定义当前项目版本号
    3. 编写 HelloWorld,并运行

  • 导入 Maven 项目

    • 方式一:先将待导入项目文件夹复制一份在当前工程目录下,然后选择 IDEA 右侧 Maven 面板,点击 + 号,选中该项目的 pom.xml 文件,双击即可
    • 方式二:可以在当前工程的 Project Structure 页面 Module 栏中中点击 + 号,选择 Import Module,选择导入项目的 pom.xml 文件即可

依赖管理

依赖配置
  • 依赖:值当前项目运行所需要的 jar 包,一个项目中可以引入多个依赖

  • 配置:

    1. 在 pom.xml 文件中编写 <dependencies> 标签
    2. <dependencies> 标签中,使用 <dependency> 引入坐标
    3. 定义坐标的 groupId, artifaceId, version
    4. 点击刷新按钮,引入最新加入的坐标

    注意事项:

    • 如果引入的依赖,在本地仓库不存在,将会连接远程仓库/中央仓库,然后下载依赖
    • 如果不知道依赖的坐标信息,可以到 [https://mvnrepository.com/ ](Maven 坐标搜索)中搜索
依赖传递
  • 依赖具有传递性

    • 直接依赖:在当前项目中通过依赖配置建立的依赖关系
    • 间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源
    • 可以在项目的 pom.xml 文件中右键选择 Diagrams,选择 Show Diagram 就可以看到项目的依赖情况
  • 排除依赖:指主动断开依赖的资源,被排除的资源无需指定版本

    • 图示:
依赖范围

依赖的 jar 包,默认情况下,可以在任何地方使用。可以通过 <scope>...</scope> 设置其作用范围

作用范围:

  • 主程序范围有效(main 文件夹范围内)

  • 测试程序范围有效(test 文件夹范围内)

  • 是否参与打包运行(package 指令范围内)

    scope 值 主程序 测试程序 打包(运行) 范例
    complie(默认) Y Y Y log4j
    test - Y - junit
    provided Y Y - servlet-api
    runtime - Y Y jdbc 驱动
生命周期

Maven 的生命周期就是为了对所有的 Maven 项目构建过程进行抽象和统一

Maven 中有 3 套相互独立的生命周期:

  • clean:清理工作
  • default:核心工作,如:编译、测试、打包、安装、部署等
  • site:生成报告、发布站点等

每套生命周期包含一些阶段(phase),阶段是有顺序的,后面的阶段依赖于前面的阶段

五个重点关注阶段:

  • clean:一处上一次构建生成的文件

  • compile:编译项目源代码

  • test:使用合适的单元测试框架运行测试(junit)

  • package:将编译后的文件打包,如:jar、war等

  • install:安装项目到本地仓库

    注意事项:在同一套生命周期中,当运行后面的阶段时,前面的阶段都会运行。

如何执行生命周期:

  1. 在 IDEA 中,右侧的 maven 工具栏,选中对应的生命周期,双击执行
  2. 在命令行中,通过命令执行:mvn phase_name

SpringBoot 基础篇

Spring

  • 官网:[spring.io](Spring 官网)
  • Spring 发展到今天已经形成了一种开发生态圈,Spring 提供了若干个子项目,每个项目用于完成特定的功能

SpringBoot

SpringBoot 可以帮助我们非常快速地构建应用程序、简化开发、提高效率,是为了解决使用底层 Spring 框架构建项目太复杂繁琐而生。

SpringBoot Web 入门

需求:使用 SpringBoot 开发一个 Web 应用,浏览器发起请求 /hello 后,给浏览器返回字符串 “Hello World~”

步骤:

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

HTTP 协议

概述
  • 概念:Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器与服务器之间数据传输的规则
  • 特点:
    1. 基于 TCP 协议:面向连接,安全
    2. 基于请求 - 响应模型:一次请求对应一次响应
    3. HTTP 协议是无状态的协议:对于事务处理没有记忆能力。每次请求 - 响应都是独立的
      • 缺点:多次请求间不能共享数据
      • 优点:速度快
请求协议

请求数据格式分为三个部分:

  • 请求行:请求数据的第一行,包括请求方式,资源路径和协议

  • 请求头:第二行开始,格式为 key: value

    常见的请求头及其含义:

    请求头 含义
    Host 请求的主机名
    User-agent 浏览器版本
    Accept 表示浏览器能接收的资源类型,如 text/*,image/* 或者 */* 表示所有
    Accept-Language 表示浏览器偏好的语言,服务器可以据此返回不同语言的网页
    Accept-Encoding 表示浏览器可以支持的压缩类型,例如 gzip、deflate等
    Content-Type 请求主体的数据类型
    Content-Length 请求主体的大小(单位:字节)
  • 请求体:POST 请求特有,与请求行和请求头间隔一个空行,存放请求参数

GET:请求参数在请求行中,没有请求体。GET 请求大小有限制

POST:请求参数在请求体中,POST请求大小没有限制

响应协议

响应数据格式也分为三个部分:

  • 响应行:响应数据的第一行,包括协议、状态码和描述

    状态码分类:

    状态码 描述
    1xx 响应中:临时状态码,表示请求已经接收,告诉客户端应该继续请求或者如果它已经完成则忽略它
    2xx **成功:**表示请求已经被成功接收,处理已完成
    3xx **重定向:**重定向到其他地方;让客户端再发起一次请求以完成整个处理
    4xx **客户端错误:**处理发生错误,责任在客户端。如:请求了不存在的资源、客户端未被授权、禁止访问等
    5xx **服务器错误:**处理发生错误,责任在服务端。如:程序抛出异常等
  • 响应头:第二行开始,格式key: value

    常见的响应头及其含义:

    响应头 含义
    Content-Type 表示该响应内容的类型,例如 text/html, application/json
    Content-Length 表示该响应内容的长度(字节数)
    Content-Encoding 表示该响应压缩算法,例如 gzip
    Cache-Control 指示客户端应如何缓存,例如 max-age=300 表示可以最多缓存 300 秒
    Set-Cookie 告诉浏览器为当前页面所在的域设置 cookie
  • 响应体:最后一部分,与响应行和响应头间隔一个空行,存放响应数据

协议解析

浏览器内置了解析 http 协议格式数据的程序,以下主要研究服务端如何解析 请求协议和返回 响应协议

  • 最原始的方法是在 Java 代码中用 socket 拿到网络的输入输出流,通过输入流按照数据格式一行一行读取请求行,请求头和请求体,通过逻辑判断来决定响应数据,最后通过输出流将 响应数据 输出回请求方(按照数据格式)
  • 由于原始的方法太过繁琐,代码量过大,又因为 http 协议是统一固定的标准,所以有很多已被其他组织写好的软件程序对 http 协议进行了封装,使得程序员不必直接对 http 协议进行操作,这些软件程序称为 Web 服务器,以下将会介绍最为流行的 Web 服务器 —— Tomcat

Web 服务器 - Tomcat

Web 服务器

Web 服务器是一个软件程序,对 http 协议的操作进行了封装,简化了 Web 程序开发,使得程序员不必直接对协议进行操作,让 Web 开发更加便捷。主要功能是 ”提供网上信息浏览服务“

简介
  • 概念:Tomcat 是 Apache 软件基金会的一个核心项目,是一个开源免费的轻量级 Web 服务器,支持 Servlet/JSP等少量 JavaEE 规范
  • JavaEE:Java Enterprise Edition,Java 企业版。指 Java 企业级开发的技术规范总和。包含 13 项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF
  • Tomcat 也被称为 Web 容器、Servlet 容器。Servlet 程序需要依赖于 Tomcat 才能运行
  • 官网:[https://tomcat.apache.org/](Tomcat 官网)
基本使用
  • 下载:官网下载

  • 安装:将压缩包解压到相应目录即为安装完成

    • 解压后目录如下:

      其中:

      • bin:存放可执行文件
      • conf:存放配置文件
        • 其中的 server.xml 文件中可以修改 Tomcat 占用的端口号
      • lib:存放 Tomcat 依赖的 jar 包
      • logs:存放日志文件
      • temp:存放临时文件
      • webapps:应用发布目录
      • work:工作目录
  • 卸载:直接删除目录即可

  • 启动:双击:``bin\startup.bat`

  • 关闭:

    • 直接关掉运行窗口:强制关闭
    • 双击 bin\shutdown.bat:正常关闭
    • Ctrl + C:正常关闭

注意事项:HTTP 协议默认端口号为 80,如果将 Tomcat 端口号改为 80,则将来访问 Tomcat 时,不需要输入端口号,直接输入 localhost 或 IP

  • 部署应用:将打包好的 web 应用目录放在 webapps 目录下即可
入门程序解析
  • 起步依赖:
  • 内嵌 Tomcat 服务器
    • 基于 SpringBoot 开发的 Web 应用程序,内置了 Tomcat 服务器,当启动类运行时,回自动启动内嵌的 Tomcat 服务器。

请求响应

SpringBoot 中的请求响应结构图示:

  • 请求对象(HttpServletRequest):获取请求数据

  • 响应对象(HttpServletResponse):设置响应数据

  • BS 架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端

  • CS 架构:Client/Server,客户端/服务器架构模式

请求

主要讲述各类请求参数的结构和封装

Postman
  • Postman 是一款功能强大的网页调试与发送网页 HTTP 请求的应用

  • 作用:常用于进行接口测试

简单参数
  • 原始方式:在原始的 Web 程序中,获取请求参数,需要通过 HttpServletRequest 对象手动获取

    @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 方式

    • 简单参数:参数名与形参变量名相同定义形参即可接收参数(GET 和 POST请求均可用,只要参数名与形参变量名相同即可),会自动进行类型转换,于是上述代码精简为:

      @RequestMapping("/simpleParam")
      public String simpleParam(String name, Integer age) {
      System.out.println(name + ": " + age);
      return "OK";
      }
    • 如果方法的形参名称与请求参数名称不匹配,可以使用 @RequestParam 完成映射

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

      注意事项:

      @RequestParam 中的 required 属性默认为 true,代表该请求参数必须传递,如果不传递将报错。如果该参数是可选的,可以将 required 属性设置为 false

实体参数
  • 简单实体对象:请求参数名与形参对象属性名相同,定义 POJO 接收即可

    @RequestMapping("/simplePojo")
    public String simplePojo(User user) {
    System.out.println(user);
    return "OK";
    }


    public class User {
    private String name;
    private Integer age;
    }

    注意事项:

    要确保实体类有合适的getter和setter方法。Spring 使用这些方法来绑定请求参数到对象属性上。

  • 复杂实体对象:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套 POJO 属性参数,如:

    @RequestMapping("/complexPojo")
    public String complexPojo(User user) {
    System.out.println(user);
    return "OK";
    }

    public class User {
    private String name;
    private Integer age;
    private Address address;
    }

    public class Address {
    private String province;
    private String city;
    }

    则对应的请求参数顺序为:name, age, address.province, address.city.

数组集合参数
  • 应用场景:用于处理前端复选框组件传递的参数(一般为数组或集合)

  • 数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数

    @RequestMapping("/arrayParam")
    public String arrayParam(String[] hobby) {
    System.out.println(Arrays.toString(hobby));
    return "OK";
    }
  • 集合参数:请求参数名与形参集合名称相同且请求参数为多个,@RequestParam 绑定参数关系(因为默认多个值是封装到数组里的)

    @RequestParam("/listParam")
    public String listParam(@ReqeustParam List<String> hobby) {
    System.out.println(hobby);
    return "OK";
    }
日期参数
  • 日期参数:使用 @DateTimeFormat 注解完成日期参数格式转换,请求参数名与形参名称相同

    @RequestMapping("/dateParam")
    public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) {
    System.out.println(updateTime);
    return "OK";
    }
Json 参数
  • JSON参数:JSON 参数键名与形参对象属性名相同,定义 POJO 类型形参即可接收参数,需要使用 @RequestBody 标识

    @RequestMapping("jsonParam")
    public String jsonParam(@RequestBody Client client) {
    System.out.println(client);
    return "OK";
    }
    {
    "name": "Tom",
    "age": 12,
    "address": {
    "province": "shanghai",
    "city": "shanghai"
    }
    }
路径参数
  • 路径参数:通过请求 URL 直接传递参数,使用 {…} 来表示该路径参数,需要使用 @PathVariable 获取路径参数,路径参数名需要与方法形参名相同

    @RequestMapping("/pathParam/{num}")
    public String pathParam(@PathVariable Integer num) {
    System.out.println(num);
    return "OK";
    }
响应
@ResponseBody
  • 类型:方法注解、类注解
  • 位置:Controller 方法上/类上
  • 作用:将方法返回值直接响应,如果返回值类型是 实体对象/集合,将会转换为 JSON 格式响应
  • 说明:@RestController = @Controller + @ResponseBody
统一响应结果

图示:

public class Result {
// 状态码
private Integer code;
// 提示信息
private String msg;
// 数据
private Object data;
}

温馨提示:SpringBoot 项目的静态资源(html、css、js等前端资源)默认存放目录为:classpath:/static、classpath:/public、classpath:/resources

分层解耦
三层架构(基于单一职责原则)
  • Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。

  • Service:业务逻辑层。处理具体的业务逻辑

  • Dao:数据访问层(Data Access Object)(持久层)。负责数据访问操作,包括数据的增删改查。

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

步骤:

  1. 将需要解耦的实现类,交给 IOC 容器来管理

    在类上加入注解:@Component

  2. 在耦合的代码部分,进行依赖注入

    在代码部分上方,加上注解 @Autowired

  3. 运行测试

IOC详解
  • Bean 的声明

    要把某个对象交给 IOC 容器管理,需要在对应的类上加上如下注解之一:

    注解 说明 位置
    @Component 声明 bean 的基础注解 不属于以下三类时,用此注解,例如:工具类
    @Controller @Component 的衍生注解 标注在控制器类上
    @Service @Component 的衍生注解 标注在业务类上
    @Repository @Component 的衍生注解 标注在数据访问类上(由于与mabatis整合,用的少)

    注意事项:

    • 声明 bean 的时候,可以给每个 bean 取名字,具体做法是在以上四个注解后加上 (value = "beanName")
    • 使用以上四个注解都可以声明 bean,但是在 SpringBoot 集成 Web 开发中,声明控制器 bean 只能用 @Controller
  • Bean 组件扫描

    • 前面声明 bean 的四大注解要想生效,还需要被组件扫描注解 @ComponentScan 扫描
    • @ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中,默认扫描范围是启动类所在包及其子包
DI详解
  • @Autowired 注解,默认是按照类型进行,如果存在多个相同类型的 bean,则会报错,程序无法启动,可通过以下几种方案来解决:
    • @Primary:在想要生效的 bean 上加上该注解即可
    • @Qualifier:在 @Autowired 下加上该注解并在该注解后加上 ("beanName") 即可选择你想要生效的 bean
    • @Resource:不用 @Autowired 而改用 @Resource(name = "beanName") 来指定你想要生效的 bean

MySQL

数据库设计

MySQL概述

登录语法:

mysql -uusername -ppassword [-hip] [-pport]

DDL

基于页面原型和需求文档来设计表结构

数据库操作

DML
DQL
多表设计
  • 一对多
  • 多对多
  • 一对一
  • 根据页面原型和需求文档分析多张表之间的关系,以及每张表中有哪些字段。

数据库优化

索引
介绍
  • 索引(index)是帮助数据库高效获取数据数据结构
  • 优缺点:
    • 优点:
      • 提高数据查询效率,降低数据库 IO 成本
      • 通过索引列对数据进行排序,降低数据排序成本,降低 CPU 消耗
    • 缺点:
      • 索引会占用存储空间
      • 索引大大提高了查询效率,同时却降低了插入、更新和删除的效率
结构

MySQL 数据库支持的索引结构有很多,如:

  • Hash 索引
  • B+Tree 索引
  • Full-Text 索引

我们平常所说的索引,如果没有特别指明,都是指默认的 B+Tree 结构组织的索引。

  • B+Tree(多路平衡搜索树)

    • 每一个节点,可以存储多个 key(有 n 个 key,就有 n 个指针)
    • 所有的数据都存储在叶子节点,非叶子节点仅用于索引数据
    • 叶子节点形成了一条双向链表,便于数据的排序及区间范围查询
语法
  • 创建索引

    CREATE [UNIQUE] INDEX 索引名 ON 表名(字段名, ...);
  • 查看索引

    SHOW INDEX FROM 表名;
  • 删除索引

    DROP INDEX 索引名 ON 表名;
  • 主键字段,在建表时,会自动创建主键索引

  • 添加唯一约束时,数据库实际上会添加唯一索引

SpringBoot Mybatis

什么是 MyBatis?

  • MyBatis 是一款优秀的持久层框架,用于简化 JDBC 的开发
  • MyBatis 本是 Apache 的一个开源项目 iBatis,2010 年这个项目由 Apache 迁移到了 google code,并且改名为 MyBatis。2013 年 11 月 迁移到 Github
  • 官网:[https://mybatis.org/mybatis-3/zh/index.html](MyBatis 官网)

MyBatis 入门

快速入门

步骤:

  1. 准备工作(创建 SpringBoot 工程、数据库表 user、实体类 User)
  2. 引入 MyBatis 的相关依赖,配置 MyBatis(数据库连接信息)
  3. 编写 SQL 语句(注解/XML)

配置 SQL 提示

  • 产生原因:IDEA 和数据库没有建立连接,不识别表信息
  • 解决方式:在 IDEA 中配置 MySQL 连接
JDBC 介绍
  • JDBC:Java DataBase Connectivity,就是使用 JAVA 语言操作关系型数据库的一套 API(规范,只有接口,无实现类)

    JAVA 程序操作数据库流程图示

  • 本质:

    • sun 公司官方定义的一套操作所有关系型数据库的规范,即接口
    • 各个数据库厂商去实现这套接口,提供数据库驱动jar包
    • 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
  • 原始 JDBC 操作数据库程序

    问题:

    • 注册驱动的驱动包名和获取连接的 url、用户名和密码变动频繁,原始程序是硬编码的方式,在更改后需要重新编译打包部署,维护成本高
    • 解析封装返回数据的代码量大,操作繁琐
    • 频繁地进行数据库资源的连接和释放,造成资源浪费和性能降低
  • MyBatis 实现的优点

    • 驱动报名和获取连接的信息写在 application.properties 文件中,不在 JAVA 程序中,变更无需重新编译程序
    • 解析封装返回数据由框架代为执行,不需要手动编写代码
    • 数据库连接对象都放在数据库连接池之中,使用时取出,用完后退还,不会频繁地获取和释放连接对象,避免了资源的浪费
数据库连接池

介绍:

  • 数据库连接池是一个容器,负责分配、管理数据库连接对象(Connection)
  • 它允许应用程序重复使用一个现有的数据库连接对象,而不是再重新建立一个
  • 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的客户端连接遗漏(有客户端获取不到连接)

标准接口:DataSource

  • 官方(sun)提供的数据库连接池接口,由第三方组织实现此接口

  • 功能:获取连接

  • 核心方法:Connection getConnection() throws SQLException;

  • 常见产品:

    • C3P0

    • DBCP

    • Druid(阿里巴巴开源的数据库连接池项目,是 JAVA 语言最好的数据库连接池之一)

    • Hikari(SpringBoot 默认)

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

  • lombok 常用注解:

    注解 作用
    @Getter/@Setter 为所有的属性提供 getter/setter 方法
    @ToString 会给类自动生成易于阅读的 toString 方法
    @EqualsAndHashCode 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法
    @Data 提供了综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode)
    @NoArgsConstructor 为实体类生成无参的构造器方法
    @AllArgsConstructor 为实体类生成除了 static 修饰的字段之外带有各参数的构造器方法
  • 依赖引入:

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

MyBatis 基础增删改查

准备
  • 准备数据库表 emp
  • 创建一个 SpringBoot 工程,选择引入对应的起步依赖(lombok, mybatis, mysql driver)
  • application.properties 中引入数据库连接信息
  • 准备对应的实体类 Emp(实体类属性采用驼峰命名)
  • 准备 Mapper 接口 EmpMapper
删除
  • 根据主键删除

  • SQL 语句:

    DELETE FROM emp WHERE id = 17;
  • 接口方法:

    @Mapper
    public interface EmpMapper {
    @Delete("DELETE FROM emp WHERE id = #{id} ")
    public int delete(Integer id);
    }

    注释:

    1. #{} 为 MyBatis 的变量占位符,可以拿到方法接收的参数
    2. @Delete 注解拥有返回值:该 SQL 操作影响的行的数量,要拿到该返回值,只需将方法声明为 int 返回类型即可,方法执行后会返回该值。

注意事项:如果 mapper 接口方法形参只有一个普通类型的参数,#{…} 里面的属性名可以随便写,如 #{id}、#{value},但建议保持一致

日志输出:可以在 application.properties 中,打开 mybatis 的日志,并指定输出到控制台

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

预编译 SQL:

优势:

  • 性能更高

  • 更安全(防止SQL注入)

    • SQL 注入是通过操作输入的数据来修改事先定义好的 SQL 语句,以达到执行代码对服务器进行攻击的方法。

参数占位符

  • #{…}
    • 执行 SQL 时,会将 #{…} 替换为 ?,生成预编译 SQL,会自动设置参数值
    • 使用时机:参数传递,都是用 #{…}
  • ${…}
    • 拼接 SQL。直接将参数拼接在 SQL 语句中,存在 SQL 注入问题
    • 使用时机:如果对表名、列表进行动态设置时使用
插入
接口方法:
@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);

使用实体类将传递参数封装起来,并在 #{}内写这个实体类的属性

主键返回
  • 描述:在数据添加成功后,需要获取插入数据库数据的主键。如添加套餐数据时,还需要维护套餐菜品关系表数据

  • 实现:

    @Options(keyProperty = "id", useGeneratedKeys = true) // 会自动将生成的主键值,赋值给emp对象的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);
更新
接口方法:
@Update("UPDATE emp SET username = #{username}, name = #{name}, gender = #{gender}, image = #{image}, job = #{job}, entrydate = #{entryDate}, dept_id = #{deptId}, update_time = #{updateTime} WHERE id = #{id}")
public void update(Emp emp);
查询
根据 ID 查询

接口方法:

@Select("SELECT * FROM emp WHERE id = #{id}")
public Emp getById(Integer id);

数据封装

  • 实体类属性名 和 数据库表查询返回的字段名一致,MyBatis 会自动封装

  • 如果字段名不一致,则不能自动封装

  • 解决方案:

    1. 给字段起别名,让别名与实体类属性名一致

      @Select("SELECT id, username, password, name, gander, image, job, entrydate entryDate, dept_id deptId, create_time createTime, update_time updateTime FROM emp WHERE id = #{id}")
      public Emp getById(Integer id);
    2. 通过 @Results, @Result 注解手动映射封装

      @Results({
      @Result(column = "entrydate", property = "entryDate"),
      @Result(column = "dept_id", property = "deptId"),
      @Result(column = "create_time", property = "createTime"),
      @Result(column = "update_time", property = "updateTime")
      })
      @Select("SELECT * FROM emp WHERE id = #{id}")
      public Emp getById(Integer id);
    3. 开启 MyBatis 的驼峰命名自动映射开关,可以实现 a_column --------> aColumn。在 application.properties 中 输入

      mybatis.configuration.map-underscore-to-camel-case=true

条件查询

接口方法:

@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> list(String name, Short gender, LocalDate begin, LocalDate end);

XML 映射文件

规范
  • XML 映射文件的名称与 Mapper 接口名称一致,并且将 XML 映射文件和 Mapper 接口放置在相同包下(同包同名)

  • XML 映射文件的 namespace 属性与 Mapper 接口全限定名一致

  • XML 映射文件中 SQL 语句的 id 与 Mapper 接口中的方法名一致,并保持返回类型一致

  • 文件结构:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.yl.mapper.EmpMapper">
    <!-- resultType 指单条记录所封装的类型-->
    <select id="list" resultType="com.yl.pojo.Emp">
    // SQL 语句
    </select>
    <delete>

    </delete>

    <update>

    </update>

    <insert>

    </insert>
    </mapper>

原则:使用 MyBatis 的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的 SQL 功能,建议使用 XML 来配置映射语句

MyBatis 动态 SQL

动态 SQL:随着用户的输入或外部条件的变化而变化的 SQL 语句,我们称为动态 SQL

<if>
  • 用户判断条件是否成立。使用 test 属性进行条件判断,如果条件为 true,则拼接 SQL;否则不。

  • test 属性中的 and、or必须小写!!!

  • 示例1:

    <select id="list" resultType="com.yl.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>

    <where> 标签可以

    1. 动态生成 WHERE 子句(有条件成立则生成,否则不生产)
    2. 自动去除掉条件前面多余的 and 和 or
  • 示例2:

    <update id = "update2">
    UPDATE emp
    <set>
    <if test = "username != null">
    username = #{username},
    </if>
    <if test = "name != null">
    name = #{name},
    </if>
    <if test = "gender != null">
    gender = #{gender}
    </if>
    </set>
    WHERE id = #{id}
    </update>

    <set> 标签可以

    1. 动态地在行首插入 SET 关键字,机制同 <where>
    2. 自动去除掉额外的逗号

<foreach>

要完成这个 SQL 语句的实现:

DELETE FROM emp WHERE id in(18, 19, 20);

实现如下:

<delete id = "deleteByIds">
DELETE FROM emp WHERE id in
<foreach collection = "ids" item = "id" separator = "," open = "(" close = ")">
#{id}
</foreach>
</delete>

属性解释:

  • collection:要遍历的集合
  • item:遍历出的元素的名字(自定)
  • separator:分隔符
  • open:遍历开始前拼接的 SQL 片段
  • close:遍历结束后拼接的 SQL 片段
<sql><include>
  • <sql>:用来定义可重用的 SQL 片段

    <sql id="yourName">
    // sql
    </sql>
  • <include>:通过属性 refid,指定引用的 sql 片段

    // place to reuse the sql
    <include refid="yourName" />

SpringBoot Web 开发篇

文件上传

简介
  • 文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程
  • 文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能
  • 前端页面文件上传三要素:
    • form 表单元素中:method="post", enctype="multipart/form-data"
    • input 元素中:type="file"
    • 图示:
    • 使用 Spring 框架提供的类 MultipartFile 来接收上传的文件
本地存储

在服务端,接收到上传上来的文件之后,将文件存储在本地服务器磁盘中。

@RequestMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws IOException {
log.info("文件上传:{}, {}, {}", username, age, image);
// 获取原始文件名
String originalFilename = image.getOriginalFilename();
// 构造唯一文件名——加入 uuid(通用唯一识别码)
int index = originalFilename.lastIndexOf('.');
String extname = originalFilename.substring(index);
String newFileName = UUID.randomUUID().toString() + extname;
log.info("新的文件名:{}", newFileName);
// 将文件保存到本地
image.transferTo(new File("E:\\code\\Java Web\\tlias\\image\\" + newFileName));
return Result.success();
}

在 SpringBoot 中,文件上传,默认单个文件允许最大大小为 1MB。如果需要上传大文件,可以进行如下配置

# 配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB
# 配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB

本地存储的问题:

  1. 前端无法访问服务端保存的文件
  2. 服务器磁盘容量有限,扩容困难
  3. 服务器磁盘损坏后,造成的损失大

MultipartFile 类的常用方法:

  • String getOriginalFilename(); // 获取原始文件名
  • void transferTo(File dest); // 将接收的文件转存到磁盘中
  • long getSize(); // 获取文件的大小,单位:字节
  • byte[] getBytes(); // 获取文件内容的字节数组
  • InputStream getInputStream(); // 获取接收到的文件内容的输入流
阿里云 OSS
介绍

阿里云:阿里云是阿里巴巴集团旗下全球领先的云计算公司,也是国内最大的云服务提供商

云服务:通过互联网向外界提供的各种服务

阿里云对象存储OSS(Object Storage Service):是一款海量、安全、低成本、高可靠的云存储服务。使用 OSS,可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件

使用第三方服务的通用思路
  1. 准备工作:账号注册、实名认证、后台基本配置等

  2. 参照官方 SDK 编写入门程序

    SDK:Software Development Kit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做 SDK

  3. 项目中集成该服务并使用

阿里云 OSS 使用步骤
  1. 注册阿里云(实名认证)

  2. 充值

  3. 开通对象存储服务(OSS)

  4. 创建 bucket

    bucket 是阿里云 OSS 中的存储空间,是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间

  5. 获取 AccessKey(密钥)

  6. 参照官方 SDK 编写入门程序

  7. 项目集成 OSS

集成

步骤:

  1. 引入依赖:

    <dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.1</version>
    </dependency>
  2. 引入阿里云 OSS 上传文件工具类(由官方的示例代码改造而来)

  3. 上传图片接口开发

配置文件

参数配置化

问题:如果在一些类中定义所需要的参数(如OSS的密钥和bucket),会导致不变维护和管理

问题解决:

# OSS 参数配置
aliyun.oss.endpoint=https://oss-cn-hangzhou.aliyuncs.com
aliyun.oss.accessKeyId=xxx
aliyun.oss.accessKeySecret=xxx
aliyun.oss.bucketName=java-web-learn3
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
@Value("${aliyun.oss.bucketName}")
private String bucketName;

@Value(Spring提供) 注解通常用于外部配置的属性注入,具体用法为:@Value(“${配置文件中的key}”)

yml 配置文件
常见配置文件格式对比
  • XML:臃肿
  • properties:层级结构不清晰
  • yml/yaml:简洁、数据为中心(推荐)
yml 基本语法
  • 大小写敏感
  • 数值前边必须有空格作为分隔符
  • 使用缩进表示层级关系,缩进时,不允许使用 Tab 键,只能用空格(idea 会自动将 Tab 转换为空格)
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • # 表示注释,从这个字符一直到行尾,都会被解析器忽略
yml 数据格式
  • 对象/Map集合

    user:
    name: zhangsan
    age: 18
    password: 123456
  • 数组/List/Set集合:

    hobby:
    - java
    - game
    - sport
@ConfigurationProperties

想法:想要配置文件中的配置参数自动注入到相应对象的属性,而不需要每个属性都加上 @Value 注解

实现

  • Spring 提供了方法以实现以上操作,步骤:

    1. 配置文件的 key 名称与实体类的属性名一致,并且提供对应的 getter/setter 方法

    2. 将该实体类交给 IOC 容器管理

    3. 配置文件的 key 名称前面的部分用 @ConfigurationProperties 注解来指定。如:

      @ConfigurationProperties(prefix = "aliyun.oss")

    配置好的实体类示例如下:

    @Data
    @Component
    @ConfigurationProperties(prefix = "aliyun.oss")
    public class AliOSSProperties {
    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
    }

配置文件提示:

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

@ConfigurationProperties 与 @Value

  • 相同点:都是用来注入外部配置的属性的
  • 不同点:
    • @Value 注解只能一个一个地进行外部属性的注入
    • @ConfigurationProperties 注解可以批量地将外部属性注入到 bean 对象的属性中

登录认证

登录功能

查询数据库中对应用户名和密码的用户,返回 Emp 对象,如果不为空,则登录成功;为空,则登录失败

登录校验

**问题:**在未登录的情况下,直接输入 url 就可以不登陆而访问部门管理、员工管理等功能

实现蓝图:

会话技术
  • 会话:用户打开浏览器,访问 web 服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。

  • 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

  • 会话跟踪方案:

    • 客户端会话跟踪技术:Cookie

      • 图示:
      • 优点:HTTP 协议中支持的技术

      • 缺点:

        • 移动端 APP 无法使用 Cookie

        • 不安全,并且用户可以自己禁用 Cookie

        • Cookie 不能跨域

        • 跨域区分三个维度:协议、IP/域名、端口,任何一个不同将导致跨域

        • Cookie 不能跨域,简单来说,就是浏览器目前访问的 ip + 端口的 Cookie 不能被其他 ip + 端口设置

    // 设置Cookie
    @GetMapping("/c1")
    public Result cookie1(HttpServletResponse response) {
    // 设置 Cookie(响应Cookie),cookie名为login_username,值为yl
    response.addCookie(new Cookie("login_username", "yl"));
    return Result.success();
    }
    // 获取Cookie
    @GetMapping("c2")
    public Result cookie2(HttpServletRequest request) {
    // 获取所有的Cookie
    Cookie[] cookies = request.getCookies();
    for (Cookie cookie: cookies) {
    // 输出name为login_username的cookie
    if (cookie.getName().equals("login_username")) {
    System.out.println("login_username" + cookie.getValue());
    }
    }
    return Result.success();
    }
    • 服务端会话跟踪技术:Session

      • 与 Cookie 方案类似,Session 是基于 Cookie 实现的,区别在于存储方在服务端,而不是客户端浏览器

      • 优点:

        • 存储在服务端,安全
      • 缺点:

        • 服务器集群环境下无法直接使用 Session
        • Cookie 的缺点
      // 往 HttpSession 中存储数据
      @GetMapping("/s1")
      public Result session1(HttpSession session) {
      // 往 Session 中存储数据
      session.setAttribute("loginUser", "tom");
      return Result.success();
      }
      // 从 HttpSession 中获取数据
      @GetMapping("/s2")
      public Result session2(HttpServletRequest request) {
      HttpSession session = request.getSession();
      // 从 Session 中获取数据
      Object loginUser = session.getAttribute("loginUser");
      return Result.success(loginUser);
      }
    • 令牌技术(主流方案)

      • 优点:支持 PC 端、移动端

        • 解决了集群环境下的认证问题
        • 减轻了服务器端存储压力(不需要存 Session)
      • 缺点:

        • 需要自己实现
JWT 令牌

简介

  • 全称:JSON Web Token(https://jwt.io/)

  • 定义了一种简洁的自包含的格式,用于在通信双方以 json 数据格式安全地传输信息。由于数字签名的存在,这些信息是可靠的。

  • 组成:

    • 第一部分:Header(头),记录令牌类型、签名算法等。例如 {"alg": "Hs256", "typ": "JWT"}

    • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如 {"id": "1", "username": "Tom"}

    • 第三部分:Signature(签名),防止 Token 被篡改、确保安全性。将 Header、Payload 部分内容融入,并加入指定密钥,通过指定签名算法计算而来。

    • 图示:

      Base64:是一种基于 64 个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式

场景:登录认证

  1. 登录成功后,生成令牌
  2. 将令牌发放给前端和浏览器,浏览器在后续发送请求时都会携带 JWT 令牌到服务端以校验身份
  3. 服务端会在每次请求前先校验令牌,通过后,再处理请求

令牌生成和校验

  1. 引入依赖

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

    public void getJwt() {
    HashMap<String, Object> claims = new HashMap<>();
    claims.put("id", 1);
    claims.put("name", "Tom");
    String secretString = "yl"; // 原始签名密钥
    byte[] secretBytes = Base64.getEncoder().encode(secretString.getBytes()); // 将原始签名密钥编码为 Base64编码以满足生成需要
    String jwt = Jwts.builder()
    .signWith(SignatureAlgorithm.HS256, secretBytes) // 设置签名算法和签名密钥
    .setClaims(claims) // 自定义内容
    .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 设置 JWT 令牌有限期为一小时
    .compact();
    System.out.println(jwt);
    }
  3. 校验并解析 JWT 令牌

    public void parseJwt() {
    Claims claims = Jwts.parser()
    .setSigningKey(Base64.getEncoder().encode("yl".getBytes())) // 设置签名密钥,需与生成时一致
    .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwODkzNzU0Mn0.-_402mawcQ0HxFvxPMm8DfJC_XcK8btNmLwJYvwAn3w") // 传递 JWT 令牌,校验
    .getBody(); // 获取令牌自定义内容
    System.out.println(claims);
    }

注意事项

  • JWT 校验时所使用的签名密钥,必须和生成 JWT 令牌时使用的密钥是配套的
  • 如果 JWT 令牌解析校验时报错,则说明 JWT 令牌被篡改 或 失效了,令牌非法

登录-生成令牌

  • 引入 JWT 令牌操作工具类
  • 登录完成后,调用工具类生成 JWT 令牌,并返回
过滤器 Filter

概述

  • 概念:Filter 过滤器,是 JavaWeb 三大组件之一(Servlet、Filter、Listener)之一
  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
  • 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等

快速入门

步骤:

  1. 定义 Filter:定义一个类,实现 Filter 接口并重写其所有方法(可以只重写 doFilter 方法)
  2. 配置 Filter:Filter 类加上 @WebFilter 注解,配置拦截资源的路径引导类加上 @ServletComponentScan 开启 Servlet 组件支持

详解

  • 执行流程

    • 拦截到请求之后,会自动调用 doFilter 方法,在放行前,会执行放行代码之前的逻辑,放行时执行对应的访问操作,对应操作执行完毕后,还会返回来执行放行代码之后的逻辑。此为整个执行流程
  • 拦截路径

    • Filter 可以根据需求,配置不同的拦截资源路径:

      拦截路径 urlPatterns值 含义
      拦截具体路径 /login 只有访问 /login 路径时,才会被拦截
      目录拦截 /emps/* 访问 /emps 下的所有资源,都会被拦截
      拦截所有 /* 访问所有资源,都会被拦截
  • 过滤器链

    • 介绍:一个 Web 应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链(类似栈)
    • 图示:
    • 顺序:注解配置的 Filter,顺序是按照过滤器类名(字符串)的自然排序

登录校验-Filter

流程:

拦截器 Interceptor

简介&快速入门

  • 概述

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

    1. 定义拦截器,实现 HandlerInterceptor 接口,并重写其所有方法

      @Component
      public class LoginInterceptor implements HandlerInterceptor {
      // 目标资源方法执行前执行,放行则返回 true,不放行则返回 false
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      System.out.println("preHandle...");
      return true;
      }
      // 目标资源方法执行后执行
      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
      System.out.println("postHandle...");
      }
      // 视图渲染完毕后执行,最后执行
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      System.out.println("afterCompletion...");
      }
      }
    2. 注册拦截器

      @Configuration
      public class WebConfig implements WebMvcConfigurer {
      @Autowired
      private LoginInterceptor loginInterceptor;
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
      }
      }

详解

  • 拦截路径

    • 拦截器可以根据需求,配置不同的拦截路径:

      @Override
      public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
      }

      addPathPatterns 可以配置需要拦截哪些资源

      excludePathPatterns 可以配置不需要拦截哪些资源

      拦截路径 含义 举例
      /* 一级路径 只能匹配类别 /login,不能匹配 /depts/1
      /** 任意级路径 任意匹配
      /depts/* /depts 下的一级路径 可匹配 /depts/1,不能匹配 /depts/1/2 和 /depts
      /depts/** /depts 下的任意级路径 能匹配 /depts 和其下任何路径,不能匹配 /emps 和其下任何路径
  • 执行流程

    • 图示:
    • 浏览器请求会先被 Tomcat 服务器中的过滤器拦截,过滤器放行的请求会转交给 Spring 中的 DispatcherServlet,然后被拦截器拦截拦截器放行的请求才到达 controller 执行操作,然后再依次返回

Filter 与 Interceptor 比较

  • 接口规范不同:过滤器需要实现 Filter 接口,而拦截器需要实现 HandlerInterceptor 接口
  • 拦截范围不同:过滤器 Filter 会拦截所有的资源,而 Interceptor 只会拦截 Spring 环境中的资源

登录校验-Interceptor

只需要将 Filter 中的代码照搬即可

异常处理

出现异常(如添加数据时数据库数据重复,添加失败),如何处理?

  • **方案一:**在 Controller 的方法中进行 try…catch 处理(代码臃肿,不推荐)
  • **方案二:**全局异常处理器(简单、优雅、推荐)

全局异常处理器图示:

@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result ex(Exception e) {
e.printStackTrace();
return Result.error("对不起,操作失败,请联系管理员");
}
}

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

事务管理

事务回顾
  • 概念:事务 是一组操作的集合,它是一个不可分割的工作单位,这些操作 要么同时成功,要么同时失效
  • 操作:
    • 开启事务:start transaction / begin;
    • 提交事务:commit;
    • 回滚事务:rollback;
Spring 事务管理

案例:解散部门:删除部门,同时删除该部门下的员工

注解 @Transactional
  • 位置:业务(service)层的方法上(当前方法交给 Spring 事务管理)、类上(当前类所有方法交给 Spring 事务管理)、接口上(当前接口下的所有实现类的所有方法交给 Spring 事务管理)

  • 作用:将当前方法交给 Spring 进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务

  • 开启 Spring 事务管理日志

    logging:
    level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug
事务进阶

@Transactional 注解有两个属性:

  • rollbackFor:默认情况下,只有出现 RuntimeException 才回滚异常。rollbackFor 属性用于控制出现何种异常类型时,回滚事务

    @Transaction(rollbackFor = Exception.class)

    上面这条语句使得事务在执行时出现任何异常时都回滚

  • propagation:控制事务的传播行为

    事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个被调用的事务方法应该如何进行事务控制

    常见事务传播行为:

    属性值 含义
    REQUIRED 【默认值】需要事务,有则加入(共用一个事务,任何一方抛异常都会导致两方的操作都无法完成),无则创建新事务
    REQUIRED_NEW 需要新事务,无论有无,总是创建新事物(两方事务不会互相影响)
    SUPPORTS 支持事务,有则加入,无则在无事务状态中运行
    NOT_SUPPORTED 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
    MANDATORY 必须有事务,否则抛异常
    NEVER 必须没事务,否则抛异常

AOP

AOP 基础
AOP 概述
  • AOP:Aspect Oriented Programming(面向切面编程),其实就是面向特定方法编程
  • 实现:动态代理是面向切面编程最主流的实现。而 SpringAOP 是 Spring 框架的高级技术,目的是在管理 bean 对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程
AOP 快速入门
  • 案例:统计各个业务层方法的执行耗时

  • 步骤:

    1. 导入依赖

      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
      </dependency>
    2. 编写 AOP 程序:针对于特定方法根据业务需要进行编程

      @Slf4j
      @Component
      @Aspect
      public class TimeAspect {
      // 前一个 * 表示返回值任意,后面的一串指定要将切面应用于哪些方法,(..)表示方法的参数任意
      @Around("execution(* com.yl.service.*.*(..))") // 切入点表达式
      public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
      // 获取开始时间
      long begin = System.currentTimeMillis();
      // 调用原始方法运行,返回值 rtn 是原始方法的返回值
      Object rtn = proceedingJoinPoint.proceed();
      // 获取结束时间
      long end = System.currentTimeMillis();
      log.info(proceedingJoinPoint.getSignature() + "执行耗时:{}ms", end - begin);
      return rtn;
      }
      }
  • 场景:

    • 记录操作日志
    • 权限控制
    • 事务管理(底层就是通过 AOP 来完成的)
  • 优势:

    • 代码无侵入
    • 减少重复代码
    • 提高开发效率
    • 维护方便
AOP 核心概念
  • **连接点:JointPoint,**指可以被 AOP 控制的方法(暗含方法执行时的相关信息)
  • **通知:Advice,**指那些被抽取出来的重复的逻辑,也就是共性功能(最终体现为一个方法)
  • **切入点:PointCut,**匹配连接点的条件,通知仅会在切入点方法执行时被应用(用切入点表达式来描述)
  • **切面:Aspect,**描述通知与切入点的对应关系(通知 + 切入点)
  • **目标对象:Target,**通知所应用的对象
  • 执行流程:
    • AOP 会动态代理目标对象,生成增强之后的代理对象实例,并将此实例进行依赖注入
    • 当执行到对应方法时,执行的是代理对象实例的增强方法,而不是原方法
AOP 进阶
通知类型
通知类型 功能
@Around 环绕通知,此注解标注的通知方法在目标方法前、后都被执行
@Before 前置通知,此注解标注的通知方法在目标方法前被执行
@After 后置通知,此注解标注的通知方法在目标方法后被执行无论是否有异常都会执行
@AfterReturning 返回后通知,此注解标注的通知方法在目标方法后被执行有异常不会执行
@AfterThrowing 异常后通知,此注解标注的通知方法发生异常后执行

注意事项:

  • @Around 环绕通知需要自己调用 ProceedingJointPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around 环绕通知方法的返回值,必须指定为 Object,来接收原始方法的返回值并返回

避免重复,抽取切入点表达式,进行复用:

// 抽取切入点表达式
@PointCut("execution(* com.yl.service.*.*(..))")
// 访问修饰符可以限定此切入点表达式的被引用范围,规则同访问修饰符的规则
private void pt(){}
// 引用切入点表达式
@Around("pt()")
通知顺序

当有多个切面类的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行

  1. 不同切面类中,默认按照切面类的类名字母排序
    • 目标方法前的通知方法:字母排名靠前的执行
    • 目标方法后的通知方法:字母排名靠前的执行
  2. 用 @Order(数字) 加在切面类上来控制顺序
    • 目标方法的通知方法:数字小的先执行
    • 目标方法的通知方法:数字大的先执行

一个切面类的中的多个通知方法匹配到了目标方法时,通知方法执行顺序:

  1. 未抛异常情况:环绕前置 -> @Before -> 目标方法执行 -> @AfterReturning -> @After -> 环绕返回 -> 环绕最终
  2. 抛异常情况:环绕前置 -> @Before -> 目标方法执行 -> @AfterThrowing -> @After -> 环绕异常 -> 环绕最终
切入点表达式
  • 切入点表达式:描述切入点方法的一种表达式

  • 作用:主要用来决定项目中的哪些方法需要加入通知

  • 常见形式

    • execution(...):根据方法签名来匹配

      • 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

        execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数类型全类名) throws 异常?)

        • 其中带 ? 的标识可以省略的部分
          • 访问修饰符:可省略
          • 包名.类名:可省略,但不建议
          • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
      • 可以使用通配符描述切入点

        • *:单个独立的任意符号,可以通配任意返回值、包名、类型、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
        • …:多个连续的任意符号,可以通配任意层级的包、或任意类型、任意个数的参数
      • 组合切入点表达式

        • 可以使用 &&、||、! 来组合比较复杂的切入点表达式,如:

          // 只有满足发送  或者  接收  这个切面都会切进去
          @Pointcut("logSender() || logReceiver()")
      • 书写建议:

        • 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开头,更新类方法都是 update 开头
        • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
        • 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名尽量不使用…,使用 * 匹配单个包
    • @Annotation(...):根据注解匹配

      • @Annotation 切入点表达式,用于匹配标识有特定注解的方法,如

        @annotation(com.yl.anno.Log)
        @Before("@annotation(com.yl.anno.Log)")
        public void before() {
        log.info("before...");
        }
连接点
  • 在 Spring 中用 JointPoint 抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等

    • 对于 @Around 通知,获取连接点信息只能使用 ProceedingJointPoint
    • 对于其他四种通知,获取连接点信息只能使用 JointPoint,它是 ProceedingJointPoint 的父类型
  • 方法 功能
    getTarget().getClass().getName() 获取目标对象的全类名
    getSignature().getName() 获取目标方法的方法名
    getArgs() 获取目标方法的参数
    proceed() 执行目标方法
AOP 案例

案例:将项目中 增删改 相关接口的操作日志记录到数据库表中

日志信息包括:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长

步骤:

  1. 准备:
    1. 引入 AOP 起步依赖
    2. 创建好数据库表结构,准备好日志实体类
  2. 编码
    1. 自定义注解 @Log
    2. 定义切面类,完成记录操作日志的逻辑
@Slf4j
@Aspect
@Component
public class OperateLogAspect {
@Autowired
private OperateLogMapper logMapper;
// 拿到http请求,以获取请求头中的jwt令牌
@Autowired
private HttpServletRequest request;

@Around("@annotation(com.yl.annotation.OperateLog)")
public Object recordOperateLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 用户id,通过获取jwt令牌中存储的用户信息来获得
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");
// 操作时间
LocalDateTime operateTime = LocalDateTime.now();
// 类名
String className = joinPoint.getTarget().getClass().getName();
// 方法名
String methodName = joinPoint.getSignature().getName();
// 方法参数
String methodParams = Arrays.toString(joinPoint.getArgs());
// 记录方法耗时
long begin = System.currentTimeMillis();
Object rtn = joinPoint.proceed();
long end = System.currentTimeMillis();
// 方法返回值
String returnValue = JSONObject.toJSONString(rtn);
// 计算方法耗时
Long costTime = end - begin;
// id主键自增,不用赋值,直接给 null
OperateLog operateLog = new OperateLog(null, operateUser, operateTime, className, methodName, methodParams, returnValue, costTime);
logMapper.insert(operateLog);
log.info("AOP 记录的操作日志:{}", operateLog);
return rtn;
}
}

SpringBoot Web 进阶篇(原理篇)

配置优先级

  • SpringBoot 中支持三种格式的配置文件:

    • .properties
    • .yml
    • .yaml
    • 三种配置文件优先级:properties > yml > yaml
  • SpringBoot 除了支持配置文件属性配置,还支持 Java 系统属性命令行参数 的方式进行属性配置

    • Java 系统属性

      -Dserver.port=9000

    • 命令行参数

      --server.port:10010

    • 优先级:命令行参数 > Java 系统属性

    • 在打包后添加这两个参数的方法:

      1. 执行 maven 打包指令 package

      2. 执行 java 指令,运行 jar 包

        java -Dserver.port=9000 -jar tlias-web-management-0.0.1-SNAPSHOT.jar --server.port=10010

  • 总优先级(高 -> 低):

    • 命令行参数
    • Java 系统属性
    • properties
    • yml
    • yaml

Bean 管理

获取 bean
  • 默认情况下,Spring 项目启动时,会把 bean 都创建好放在 IOC 容器中,如果想要主动获取这些 bean,可以通过如下方式:

    • 先获取 IOC 容器对象:

      @Autowired
      private ApplicationContext applicationContext;
    • 再调用下列方法来获取 bean

    • 根据 name 获取 bean:

      Object getBean(String name)
    • 根据类型获取 bean

      <T> T getBean(Class<T> requiredType)
    • 根据 name 获取 bean(带类型转换)

      <T> T getBean(String name, Class<T> requiredType)

上述【Spring 项目启动时,会把 bean 都创建好放在 IOC 容器中】还会收到作用域及延迟初始化影响,这里主要针对 默认的单例非延迟加载的 bean 而言

bean 作用域
  • Spring 支持五种作用域,后三种在 Web 环境才生效

    作用域 说明
    singleton IOC 容器内相同名称的 bean 只有一个实例(单例)(默认)
    prototype 每次使用该 bean 时会创建新的实例(非单例)
    request 每个请求范围内会创建新的实例(Web 环境中,了解)
    session 每个会话范围内会创建新的实例(Web 环境中,了解)
    application 每个应用范围内会创建新的实例(Web 环境中,了解)
  • 可以通过 @Scope 注解来配置作用域,如

    @Scope("prototype")
  • 如果想要在第一次使用 bean 时才创建实例,可以在 bean 上添加注解 @Lazy

注意事项:

  • 默认 singleton 的 bean,在容器启动时被创建,可以使用 @Lazy 注解来延迟初始化
  • prototype 的 bean,每一次使用该 bean 的时候都会创建一个新的实例
  • 实际开发当中,绝大部分的 bean 是单例的,也就是说绝大部分的 bean 不需要配置 Scope 属性
第三方 bean
  • 第三方的类无法通过直接在类上加注解的形式来声明为 bean,如果要将第三方的类交给 IOC 容器管理成为项目中的 bean,就需要用到 @Bean 注解,如

    // 不建议在启动类中声明第三方bean
    @SpringBootApplication
    public class SpringBootWebConfig2Application {
    // Spring会自动执行该方法,并将方法返回值交给IOC容器管理,成为IOC容器的bean对象
    @Bean
    public SAXReader saxReader() {
    return new SAXReader();
    }
    }
    // 建议单独开一个类来集中声明第三方bean
    @Configuration
    public class CommonConfig {
    // 可以通过@Bean的name或value属性来指定bean的名称,默认为方法名
    @Bean
    public SAXReader saxReader() {
    return new SAXReader();
    }
    }

注意事项:

  • 通过 @Bean 注解的 name 或 value 属性可以设置 bean 的名称,默认名称为方法名
  • 如果第三方 bean 需要依赖其他 bean 对象,直接在 bean 定义方法中设置形参即可,IOC 容器会根据类型自动装配

SpringBoot 原理

起步依赖

为什么 SpringBoot 项目只需要引入一个起步依赖就可以使用诸多功能了?

原理&解答:Maven 的依赖传递

自动配置
什么是自动配置
  • SpringBoot 的自动配置就是当 Spring 容器启动后,一些配置类、bean 对象就自动存入到了 IOC 容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作
自动配置原理

@SpringBootApplication

  • 该注解标识在 SpringBoot 工程引导类上,是 SpringBoot 中最最最重要的注解。该注解由三个部分组成

    • @SpringBootcConfiguration:该注解与 @Configuration 注解作用相同,用来声明当前类也是一个配置类

    • @ComponentScan:组件扫描,默认扫描当前引导类所在包及其子包

    • @EnableAutoConfiguration:SpringBoot 实现自动化配置的核心注解

      • 该注解中封装了 @Import 注解,此注解制定了一个 InportSelector 接口的实现类,该实现类实现了 InportSelector 接口的方法 selectImports,该方法返回一个 String类型的数组,数组封装了要导入到 IOC 容器的全部的配置类的全类名

      • selectImports 方法中加载了文件:

        META/INF/spring/org.springframework.boot.autoconfiguration.AutoConfiguration.imports

      • 这个文件中就包含了所有要导入到 IOC 容器的配置类的全类名

      • 导入到 IOC 容器的配置类中配置了该项目的所有 bean,所以导入该配置类就可以导入所有的 bean

@Conditional

  • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的 bean 对象到 IOC 容器中
  • 位置:方法、类
  • @Conditional 本身是一个父注解,派生出大量的子注解,如:
    • @ConditionalOnClass:判断环境中是否有对应字节码文件,有才注册 bean 到 IOC 容器
    • @ConditionalOnMissingBean:判断环境中是否没有对应的 bean(类型或名称),没有才注册 bean 到 IOC 容器
    • @ConditionalOnProperty:判断配置文件中是否有对应的属性和值,有才注册 bean 到 IOC 容器
自定义 starter

需求

  • 需求:自定义 aliyun-oss-spring-boot-starter,完成阿里云 OSS 操作工具类 AliyunOssUtils 的自动配置
  • 目标:引入起步依赖之后,要想使用阿里云OSS,直接注入 AliyunOssUtils 直接使用即可

步骤

  1. 创建 aliyun-oss-spring-boot-starter 模块
  2. 创建 aliyun-oss-spring-boot-autoconfigure 模块,在 starter 中引入该模块
  3. aliyun-oss-spring-boot-autoconfigure 模块中自定义自动配置功能,并定义自动配置文件 META-INF/spring/xxx.imports

Maven 高级

分模块设计与开发

当开发中大型项目时,如果把所有的代码都写在一个模块中,则代码会难以维护且不便复用,为了解决这些问题,Maven 中引入了分模块设计与开发的思想

将项目按照功能拆分成若干个子模块,方便项目的管理维护、扩展,也方便模块间的相互调用、资源共享

现在,我们来对 Tlias 案例进行模块拆分

  1. 创建 maven 模块 tlias-pojo,存放实体类
  2. 创建 maven 模块 tlias-utils,存放相关工具类

注意:分模块开发需要先针对模块功能进行设计,再进行编码,不会先将工程开发完毕,然后进行拆分

几点注意事项:

  1. 创建模块选择 New Module,然后选择 Maven 作为构建工具
  2. 在高级设置中填写自己的 GroupId
  3. 多个模块最好放在同一工程文件夹中
  4. 复制相应的代码到新模块中,并把需要的依赖配置在 pom.xml 文件中
  5. 最后在主模块的 pom.xml 文件中引入这些分模块即可

继承与聚合

继承
继承关系
  • 概念:继承描述的是两个工程间的关系,与 Java 中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承,即可以把通用的依赖写在父工程中,然后子工程直接继承
  • 作用:简化依赖配置、统一管理依赖
  • 实现:在子工程的 pom.xml 文件中用 <parent>...</parent> 标签来指定父工程的坐标

现在,我们下面就创建一个父工程,把我们目前三个模块中所有的共同依赖放到父工程中,有以下几个步骤:

  1. 创建 maven 模块 tlias-parent,该工程为父工程设置打包方式为 pom(默认为 jar)

    maven 中的三种打包方式:

    • jar:普通模块打包,springboot 项目基本都是 jar 包(内嵌 tomcat 运行)
    • war:普通 web 程序打包,需要部署在外部的 tomcat 服务器中运行
    • pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理

    设置方法如下:

    <groupId>org.example</groupId>
    <artifactId>tlias-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    由于 maven 项目只能有一个父工程,而我们的 tlias-web-management 项目已经继承了 springboot 项目的父工程,所以为了实现我们的需求,我们将继承关系更改为如下(采用级联继承):

  2. 在子工程的 pom.xml 文件中,配置继承关系,如

    <parent>
    <groupId>com.yl</groupId>
    <artifactId>tlias-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--在该标签中指定父工程pom文件的相对路径-->
    <relativePath>../tlias-parent/pom.xml</relativePath>
    </parent>
  3. 在父工程中配置各个子工程共有的依赖

若父工程和子工程都配置了同一个依赖的不同版本,以子工程的为准

版本锁定

版本锁定用于统一管理项目中各个模块的各个依赖的版本,如我们想控制各个子工程的 jwt 依赖的版本为 0.9.1,则可以这样做:

  1. 在父工程的 pom 文件中通过 <dependencyManagement> 标签来统一管理版本

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
    </dependency>
    </dependencies>
    </dependencyManagement>
  2. 在子工程中引入该依赖

    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    </dependency>

此时,子工程引入依赖就不需要再指定版本号,版本号由父工程统一管理。要变更依赖版本,只需在父工程中统一变更。

此时,各个依赖的版本号还是零散地分布在 <dependencyManagement>,为了解决这一问题,我们可以使用 自定义属性/引用属性,如

<properties>
<lombok.version>1.18.24</lombok.version>
<jjwt.version>0.9.1</jjwt.version>
</properties>

然后在版本控制和引入依赖时使用自定义属性:

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
</dependencies>
聚合

分模块开发完毕后,如果要对主模块打包,需要把主模块依赖的全部分模块和它的父模块都安装到 maven 本地仓库中(使用 maven 声明周期的 install 命令),十分繁琐且枯燥,因此,为了解决这个问题,就引入了 maven 的聚合功能。

  • 聚合:将多个模块组织成一个整体,同时进行项目的构建,可以实现一键打包,一键安装等操作

  • 要实现聚合功能,需要提供一个聚合工程——一个不具有业务功能的 ”空“ 工程,有且仅有一个 pom 文件,一般由 父工程 来充当聚合工程的角色

  • 作用:快速构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)

  • 实现:只要在聚合工程的 pom 文件中用 modules 标签设置当前聚合工程所包含的子模块的名称即可,如:

    <modules>
    <module>tlias-pojo</module>
    <module>tlias-utils</module>
    <module>tlias-web-management</module>
    </modules>

    maven 会自动根据模块间的依赖关系来设置构建顺序,与聚合工程中模块的配置书写位置无关

    配置聚合工程后,只需要对聚合工程实行 maven 声明周期指令就可以对其下的所有子工程实行相同的 maven 指令

小结:继承与聚合的异同

私服

介绍

私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部资源共享与资源同步问题。

依赖查找顺序:

  1. 本地仓库
  2. 私服
  3. 中央仓库
资源上传与下载

项目版本:

  • RELEASE(发行版本):功能趋于稳定,当前停止更新,可用于发行的版本,存储在私服的 RELEASE 仓库中。
  • SNAPSHOT(快照版本):功能不稳定,尚处于开发中的版本,即快照版本,存储在私服的 SNAPSHOT 仓库中。

步骤:

  1. 设置私服的访问用户名/密码(settings.xml 中的 servers 中配置),如:

    <server>
    <id>maven-releases</id>
    <username>admin</username>
    <password>admin</password>
    </server>
    <server>
    <id>maven-snapshot</id>
    <username>admin</username>
    <password>admin</password>
    </server>
  2. 在 IDEA 的 maven 工程的 pom 文件中配置上传(发布)地址,如:

    <distributionManagement>
    <!--配置releases仓库的地址-->
    <repository>
    <!--id需要与server中配置的id一致,用于对应用户名和密码-->
    <id>maven-releases</id>
    <url>http://192.168.150.101:8081/repository/maven-releases</url>
    </repository>
    <!--配置snapshots仓库的地址-->
    <snapshotRepository>
    <id>maven-snapshots</id>
    <url>http://192.168.150.101:8081/repository/maven-snapshots</url>
    </snapshotRepository>
    </distributionManagement>
  3. 设置从私服下载依赖的对应仓库组的地址(settings.xml 中的 mirrorsprofiles 中配置),如:

    <!--如果配置过阿里云的地址,需要删除掉,mirror标签只能有一对-->
    <mirror>
    <id>maven-public</id>
    <url>http://192.168.150.101:8081/repository/maven-public/</url>
    <mirrorOf>*</mirrorOf>
    </mirror>
    <profile>
    <id>allow-snapshots</id>
    <!--开启可以下载快照版本-->
    <activation>
    <activeByDefault>true</activeByDefault>
    </activation>
    <!--指定下载的仓库组位置-->
    <repositories>
    <repository>
    <!--指定id,url以及允许下载releases仓库和snapshots仓库-->
    <id>maven-public</id>
    <url>http://192.168.150.101:8081/repository/maven-public</url>
    <releases>
    <enabled>true</enabled>
    </releases>
    <snapshots>
    <enabled>true</enabled>
    </snapshots>
    </repository>
    </repositories>
    </profile>

配置好后,执行 maven 生命周期的 deploy 指令就可以将工程上传到私服

如果工程的版本号末尾带 SNAPSHOT,则会上传到快照仓库,其他的一律上传到发布仓库