[{"content":"参考:码上开学\nval 「只读变量」 var 「可变变量」\n基本类型拆装箱 Kotlin 里的 Int 和 Java 里的 int 以及 Integer 不同，主要是在装箱方面不同。\n1.Java 里的 int 是 unbox 的，而 Integer 是 box 的： ☕️ int a = 1; Integer b = 2; // 👈会被自动装箱 autoboxing Java Kotlin 里，Int 是否装箱根据场合来定： 🏝️ var a: Int = 1 // unbox var b: Int? = 2 // box var list: List\u0026lt;Int\u0026gt; = listOf(1, 2) // box 2.Java 中的数组和 Kotlin 中的数组的写法也有区别： ☕️ int[] array = new int[] {1, 2}; Java 而在 Kotlin 里，上面的写法是这样的： 🏝️ var array: IntArray = intArrayOf(1, 2) // 👆这种也是 unbox 的 Java 里的基本类型，类比到 Kotlin 里面，条件满足如下之一就不装箱：\n1.不可空类型。 2.使用 IntArray、FloatArray 等。 Kotlin 处理强转错误的方式 🏝️ fun main() { var activity: Activity = NewActivity() // 👇\u0026#39;(activity as? NewActivity)\u0026#39; 之后是一个可空类型的对象，所以，需要使用 \u0026#39;?.\u0026#39; 来调用 (activity as? NewActivity)?.action() } 思考:\n1.子类重写父类的 override 函数，能否修改它的可见性？ 2.以下的写法有什么区别？\n🏝️ activity as? NewActivity activity as NewActivity? activity as? NewActivity? 🍎\n@Test fun test() { val str = \u0026#34;hello\u0026#34; //str as Context //str as? Context str as? Context? /* 1.直接抛异常 : java.lang.ClassCastException: java.lang.String cannot be cast to android.content.Context 2.通过 3.通过 此时 : val str=\u0026#34;hello\u0026#34; -\u0026gt; val str=null str as? Context? 和 str 是等效的 */ } val自定义 getter 不过 val 和 final 还是有一点区别的，虽然 val 修饰的变量不能二次赋值，但可以通过自定义变量的 getter 函数，让变量每次被访问时，返回动态获取的值：\n🏝️ 👇 val size: Int get() { // 👈 每次获取 size 值时都会执行 items.size return items.size } 不过这个属于 val 的另外一种用法，大部分情况下 val 还是对应于 Java 中的 final 使用的。\nKotlin 中的 object 创建一个类，并且创建一个这个类的对象。这个就是 object 的意思：对象。 创建单例 , 只需要把 class 换成 object 就可以了。 object 实现的单例是一个饿汉式的单例，并且实现了线程安全。 Java 中的静态初始化 static{ ... } 🏝️ Java 中的静态变量和方法，在 Kotlin 中都放在了 companion object 中。因此 Java 中的静态初始化在 Kotlin 中自然也是放在 companion object 中的，像类的初始化代码一样，由 init 和一对大括号表示： 🏝️ class Sample { 👇 companion object { 👇 init { ... } } } 那在实际使用中，在 object、companion object 和 top-level 中该选择哪一个呢？简单来说按照下面这两个原则判断：\n如果想写工具类的功能，直接创建文件，写 top-level「顶层」函数。 如果需要继承别的类或者实现接口，就用 object 或 companion object。 Kotlin常量和Java常量的区别 发现不同点有： Kotlin 的常量必须声明在对象（包括伴生对象）或者「top-level 顶层」中，因为常量是静态的。 Kotlin 新增了修饰常量的 const 关键字。 除此之外还有一个区别： Kotlin 中只有基本类型和 String 类型可以声明成常量。 原因是 Kotlin 中的常量指的是 「compile-time constant 编译时常量」，它的意思是「编译器在编译的时候就知道这个东西在每个调用处的实际值」，因此可以在编译时直接把这个值硬编码到代码里使用的地方。 Java 中的常量可以认为是「伪常量」，因为可以通过上面这种方式改变它内部的值。而 Kotlin 的常量因为限制类型必须是基本类型，所以不存在这种问题，更符合常量的定义。 Kotlin mutable 前缀注意问题 listOf() 创建不可变的 List，mutableListOf() 创建可变的 List。可通过 toMutableList 由不可变转成可变集合: 🏝️ val strList = listOf(\u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;, \u0026#34;c\u0026#34;) 👇 strList.toMutableList() 👉 toMutable*() 返回的是一个新建的集合，原有的集合还是不可变的，所以只能对函数返回的集合修改。 可见性修饰符 public ：公开，可见性最大，哪里都可以引用。 private：私有，可见性最小，根据声明位置不同可分为类中可见和文件中可见。 protected：保护，相当于 private + 子类可见。 internal：内部，仅对 module 内可见。 Kotlin 中如果不写可见性修饰符，就表示公开，和 Java 中 public 修饰符具有相同效果。在 Kotlin 中 public 修饰符「可以加，但没必要」。\ninternal 表示修饰的类、函数仅对 module 内可见，这里的 module 具体指的是一组共同编译的 kotlin 文件，常见的形式有：\nAndroid Studio 里的 module Maven project private Java 中的 private 表示类中可见，作为内部类时对外部类「可见」。 Kotlin 中的 private 表示类中或所在文件内可见，作为内部类时对外部类「不可见」\n🌰\n在 Java 中，外部类可以访问内部类的 private 变量： ☕️ public class Outter { public void method() { Inner inner = new Inner(); 👇 int result = inner.number * 2; // success } private class Inner { private int number = 0; } } Java 在 Kotlin 中，外部类不可以访问内部类的 private 变量： 🏝️ class Outter { fun method() { val inner = Inner() 👇 val result = inner.number * 2 // compile-error: Cannot access \u0026#39;number\u0026#39;: it is private in \u0026#39;Inner\u0026#39; } class Inner { private val number = 1 } } 高阶函数和Lambda表达式 课件 👉 https://kaixue.io/kotlin-lambda/ Bilibili 👉 https://www.bilibili.com/video/av96768809\n概念 「参数或者返回值为函数类型的函数」，在 Kotlin 中就被称为「高阶函数」—— Higher-Order Functions。 ⭐ Kotlin中双冒号函数,匿名函数和Lambda表达式本质上都是函数类型的对象。\n高阶函数 双冒号 :: 把声明好的函数变成函数类型的对象 :\nfun b(param: Int): String { return \u0026#34;\u0026#34; } val d = ::b val e = d 对象是不能加个括号来调用的，对吧？但是函数类型的对象可以。为什么？因为这其实是个假的调用，它是 Kotlin 的语法糖，实际上你对一个函数类型的对象加括号、加参数，它真正调用的是这个对象的 invoke() 函数：\nd(1) // 实际上会调用 d.invoke(1) (::b)(1) // 实际上会调用 (::b).invoke(1) 包括双冒号加上函数名的这个写法，它是一个指向对象的引用，但并不是指向函数本身，而是指向一个我们在代码里看不见的对象。这个对象复制了原函数的功能，但它并不是原函数。\n协程 一种线程框架。\n创建协程 🏝️ // 方法一，使用 runBlocking 顶层函数 runBlocking { getImage(imageId) } // 方法二，使用 GlobalScope 单例对象 // 👇 可以直接调用 launch 开启协程 GlobalScope.launch { getImage(imageId) } // 方法三，自行通过 CoroutineContext 创建一个 CoroutineScope 对象 // 👇 需要一个类型为 CoroutineContext 的参数 val coroutineScope = CoroutineScope(context) coroutineScope.launch { getImage(imageId) } 方法一通常适用于单元测试的场景，而业务开发中不会用到这种方法，因为它是线程阻塞的。\n方法二和使用 runBlocking 的区别在于不会阻塞线程。但在 Android 开发中同样不推荐这种用法，因为它的生命周期会和 app 一致，且不能取消（什么是协程的取消后面的文章会讲）。\n方法三是比较推荐的使用方法，我们可以通过 context 参数去管理和控制协程的生命周期（这里的 context 和 Android 里的不是一个东西，是一个更通用的概念，会有一个 Android 平台的封装来配合使用）。\nwithContext withContext 这个函数可以切换到指定的线程，并在闭包内的逻辑执行结束之后，自动把线程切回去继续执行。\n🏝️ launch(Dispatchers.Main) { // 👈 在 UI 线程开始 val image = getImage(imageId) avatarIv.setImageBitmap(image) // 👈 执行结束后，自动切换回 UI 线程 } // 👇 suspend fun getImage(imageId: Int) = withContext(Dispatchers.IO) { ... } 合并网络请求并更新UI 🏝️ 「协作式任务」 coroutineScope.launch(Dispatchers.Main) { // 👇 async 函数之后再讲 val avatar = async { api.getAvatar(user) } // 获取用户头像 val logo = async { api.getCompanyLogo(user) } // 获取用户所在公司的 logo val merged = suspendingMerge(avatar, logo) // 合并结果 // 👆 show(merged) // 更新 UI } 项目中配置对 Kotlin 协程的支持 项目根目录下的 build.gradle : buildscript { ... // 👇 ext.kotlin_coroutines = \u0026#39;1.3.1\u0026#39; ... } Module 下的 build.gradle : dependencies { ... // 👇 依赖协程核心库 implementation \u0026#34;org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines\u0026#34; // 👇 依赖当前平台所对应的平台库 implementation \u0026#34;org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines\u0026#34; ... } launch 与 async 接下来我们主要来对比 launch 与 async 这两个函数。\n相同点：它们都可以用来启动一个协程，返回的都是 Coroutine，我们这里不需要纠结具体是返回哪个类。\n不同点：async 返回的 Coroutine 多实现了 Deferred 接口。\n关于 Deferred 更深入的知识就不在这里过多阐述，它的意思就是延迟，也就是结果稍后才能拿到。\n我们调用 Deferred.await() 就可以得到结果了。\n接下来我们继续看看 async 是如何使用的，先回忆一下上期中获取头像的场景：\n🏝️ coroutineScope.launch(Dispatchers.Main) { // 👇 async 函数启动新的协程 val avatar: Deferred = async { api.getAvatar(user) } // 获取用户头像 val logo: Deferred = async { api.getCompanyLogo(user) } // 获取用户所在公司的 logo // 👇 👇 获取返回值 show(avatar.await(), logo.await()) // 更新 UI } 可以看到 avatar 和 logo 的类型可以声明为 Deferred ，通过 await 获取结果并且更新到 UI 上显示。\nawait 函数签名如下：\n🏝️ public suspend fun await(): T 「挂起」的本质 一个稍后会被自动切回来的线程调度操作。\n挂起的对象是协程。\n从当前线程挂起。换句话说，就是这个协程从正在执行它的线程上脱离。注意，不是这个协程停下来了！是脱离，当前线程不再管这个协程要去做什么了。\nsuspend 是有暂停的意思，但我们在协程中应该理解为：当线程执行到协程的 suspend 函数的时候，暂时不继续执行协程代码了。\n我们先让时间静止，然后兵分两路，分别看看这两个互相脱离的线程和协程接下来将会发生什么事情：\n1.线程 前面我们提到，挂起会让协程从正在执行它的线程上脱离，具体到代码其实是：\n协程的代码块中，线程执行到了 suspend 函数这里的时候，就暂时不再执行剩余的协程代码，跳出协程的代码块。\n那线程接下来会做什么呢？\n如果它是一个后台线程：\n要么无事可做，被系统回收 要么继续执行别的后台任务 跟 Java 线程池里的线程在工作结束之后是完全一样的：回收或者再利用。\n如果这个线程它是 Android 的主线程，那它接下来就会继续回去工作：也就是一秒钟 60 次的界面刷新任务。\n一个常见的场景是，获取一个图片，然后显示出来：\n🏝️ // 主线程中 GlobalScope.launch(Dispatchers.Main) { val image = suspendingGetImage(imageId) // 获取图片 avatarIv.setImageBitmap(image) // 显示出来 } suspend fun suspendingGetImage(id: String) = withContext(Dispatchers.IO) { ... } 这段执行在主线程的协程，它实质上会往你的主线程 post 一个 Runnable，这个 Runnable 就是你的协程代码：\n🏝️ handler.post { val image = suspendingGetImage(imageId) avatarIv.setImageBitmap(image) } 当这个协程被挂起的时候，就是主线程 post 的这个 Runnable 提前结束，然后继续执行它界面刷新的任务。\n2.协程 它从 suspend 函数开始脱离启动它的线程，继续执行在 Dispatchers 所指定的 IO 线程。\n紧接着在 suspend 函数执行完成之后，协程为我们做的最爽的事就来了：会自动帮我们把线程再切回来。\n这个「切回来」是什么意思？\n我们的协程原本是运行在主线程的，当代码遇到 suspend 函数的时候，发生线程切换，根据 Dispatchers 切换到了 IO 线程；\n当这个函数执行完毕后，线程又切了回来，「切回来」也就是协程会帮我再 post 一个 Runnable，让我剩下的代码继续回到主线程去执行。\n「非阻塞式」挂起指的是用看起来阻塞的代码写出非阻塞式操作的。\n协程练习 使用协程下载一张图，并行进行两次切割\n一次切成大小相同的 4 份，取其中的第一份 一次切成大小相同的 9 份，取其中的最后一份 得到结果后，将它们展示在两个 ImageView 上。\nGlobalScope.launch(Dispatchers.Main) { Log.w(\u0026#34;coroutines\u0026#34;,\u0026#34;launch ${Thread.currentThread().name}\u0026#34;) val bitmap = getSuspendImage() Log.w(\u0026#34;coroutines\u0026#34;,\u0026#34;launch getImage Ok\u0026#34;) iv1.setImageBitmap(bitmap) val bitmapHalf2 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width / 2, bitmap.height / 2) iv2.setImageBitmap(bitmapHalf2) val bitmapHalf3 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width / 3, bitmap.height / 3) iv3.setImageBitmap(bitmapHalf3) } private suspend fun getSuspendImage(): Bitmap = withContext(Dispatchers.IO) { Log.w(\u0026#34;coroutines\u0026#34;,\u0026#34;suspend ${Thread.currentThread().name}\u0026#34;) OkHttpClient() .newCall(Request.Builder() .url(\u0026#34;http://oss.tuyuing.com/TUYU/trend/20190930/trend257401569854904487.jpeg\u0026#34;) .get() .build()) .execute().body?.byteStream().use { Log.w(\u0026#34;coroutines\u0026#34;,\u0026#34;suspend getImage Ok\u0026#34;) BitmapFactory.decodeStream(it) } } Log : W/coroutines: launch main W/coroutines: suspend DefaultDispatcher-worker-1 W/coroutines: suspend getImage Ok W/coroutines: launch getImage Ok 效果图 :\n扩展函数和扩展属性 扩展函数和扩展属性（Extension Functions / Properties）, 在不改变既有代码的情况下扩展函数或扩展对象。\n(Int::toFloat)(1) Int::toFloat.invoke(1) 1.toFloat() 1.toFloat().invoke() 🌰 Global.kt\n//扩展函数 fun String.method1(i: Int) {} //扩展属性 val Float.dp get() = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics ) MainActivity.kt\nval str: String.(Int) -\u0026gt; Unit = String::method1 val str2: (String, Int) -\u0026gt; Unit = String::method1 fun strTest() { str(\u0026#34;hello\u0026#34;, 1) str.invoke(\u0026#34;hello\u0026#34;, 1) \u0026#34;\u0026#34;.method1(1) } //also fun strTest2() { fun String.method1(i: Int) { } val str: String.(Int) -\u0026gt; Unit = String::method1 val str2: (String, Int) -\u0026gt; Unit = String::method1 \u0026#34;hello\u0026#34;.method1(100) } 其中val str2: (String, Int) -\u0026gt; Unit = String::method1 的 (String, Int) 第一个参数是调用者,往后是参数\n注: Kotlin不允许使用既是成员函数又是扩展函数的函数。\n此外 , 普通的无Receiver的函数也可以赋值给有Receiver的变量 :\nGlobal.kt fun method2(s:String,i: Int) {} 比 method1 多了一个 String 参数 MainActivity.kt val str3: String.(Int) -\u0026gt; Unit = ::method2 val str4: (String, Int) -\u0026gt; Unit = ::method2 .\n","date":"2020-07-22T15:54:23+08:00","permalink":"/2020/07/22/Kotlin%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","title":"Kotlin学习笔记"},{"content":"JVM内存模型 todo 2019年12月23日 20:23:30 https://www.bilibili.com/video/av64709063?p=3\n视频 https://www.bilibili.com/video/av64709063 JVM指令手册 https://blog.csdn.net/hudashi/article/details/7062675 有助理解的帖子 https://segmentfault.com/q/1010000008683475 JVM结构图\nJava线程内存模型JMM\nJVM 从软件层面屏蔽不同操作系统在底层硬件与指令上的区别 ; 栈帧 : 一个方法对应一块栈帧内存区域 ; 先进后出 FILO , 方法执行完后依次出栈 程序计数器: 线程特有的,标识线程马上要执行的那一行代码的内存地址指针 store 和 load : 操作数栈 👉 局部变量表 , store 局部变量表 👉 操作数栈 , load 操作数栈:数据临时存储区域 方法出口:调用该方法时的位置 \u0026ldquo;从哪进来的从哪出去\u0026rdquo; 引用类型(new 出来的对象) : 用堆替代操作数栈存储数据 , 即 局部变量表 👉 堆 public class JVM01_Math { public static final Integer CONSTANT = 666; public int compute() { int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { JVM01_Math jvm01Math = new JVM01_Math(); jvm01Math.compute(); } } ☘ 反汇编 javap -c JVM01_Math.class :\nPS C:\\fastwork\\Project\\Idea\\AndoJava\\target\\classes\\jvm\u0026gt; javap -c JVM01_Math.class Compiled from \u0026#34;JVM01_Math.java\u0026#34; public class jvm.JVM01_Math { public static final java.lang.Integer CONSTANT; public jvm.JVM01_Math(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object.\u0026#34;\u0026lt;init\u0026gt;\u0026#34;:()V 4: return public int compute(); Code: 0: iconst_1 //将int型(1)推送至栈顶 1: istore_1 //将栈顶int型数值存入第二个本地变量 2: iconst_2 // 3: istore_2 4: iload_1 //将第二个int型本地变量推送至栈顶 5: iload_2 //将第三个int型本地变量推送至栈顶 6: iadd //将栈顶两int型数值相加并将结果压入栈顶,执行1+2 7: bipush 10 //将单字节的常量值(-128~127)推送至栈顶 9: imul //将栈顶两int型数值相乘并将结果压入栈顶,执行3x10 10: istore_3 //将栈顶int型数值存入第四个本地变量, int c =3 11: iload_3 // 12: ireturn public static void main(java.lang.String[]); Code: 0: new #2 // class jvm/JVM01_Math 3: dup 4: invokespecial #3 // Method \u0026#34;\u0026lt;init\u0026gt;\u0026#34;:()V 7: astore_1 8: aload_1 9: invokevirtual #4 // Method compute:()I 12: pop 13: return static {}; Code: 0: sipush 666 3: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 6: putstatic #6 // Field CONSTANT:Ljava/lang/Integer; 9: return } ✨ 参考: JVM指令手册.pdf\n🍎 分析\nint a = 1; istore_1 ... iload_1 iload_2 将第三个int型本地变量推送至栈顶 GC\nGC调优步骤： 1、打印GC日志 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:./gc.log Tomcat则直接加在JAVA_OPTS变量里 2、分析日志得到关键性指标 3、分析GC原因，调优JVM参数 ","date":"2019-12-23T20:41:15+08:00","permalink":"/2019/12/23/JVM-1.JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B%28%E6%A0%88%E5%B8%A7%29/","title":"JVM内存模型(栈帧)"},{"content":"JVM指令手册 From https://blog.csdn.net/hudashi/article/details/7062675 \u0026amp; https://blog.csdn.net/hudashi/article/details/7062781 一、未归类系列A 此系列暂未归类。 指令码 助记符 说明 0x00 nop 什么都不做 0x01 aconst_null 将null推送至栈顶 二、const系列 该系列命令主要负责把简单的数值类型送到栈顶。该系列命令不带参数。注意只把简单的数值类型送到栈 顶时，才使用如下的命令。 比如对应int型才该方式只能把-1,0,1,2,3,4,5（分别采用iconst_m1,iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, iconst_5） 送到栈顶。对于int型，其他的数值请使用push系列命令（比如bipush）。 指令码 助记符 说明 0x02 iconst_m1 将int型(-1)推送至栈顶 0x03 iconst_0 将int型(0)推送至栈顶 0x04 iconst_1 将int型(1)推送至栈顶 0x05 iconst_2 将int型(2)推送至栈顶 0x06 iconst_3 将int型(3)推送至栈顶 0x07 iconst_4 将int型(4)推送至栈顶 0x08 iconst_5 将int型(5)推送至栈顶 0x09 lconst_0 将long型(0)推送至栈顶 0x0a lconst_1 将long型(1)推送至栈顶 0x0bfconst_0 将float型(0)推送至栈顶 0x0c fconst_1 将float型(1)推送至栈顶 0x0d fconst_2 将float型(2)推送至栈顶 0x0edconst_0 将double型(0)推送至栈顶 0x0f dconst_1 将double型(1)推送至栈顶 三、push系列 该系列命令负责把一个整形数字（长度比较小）送到到栈顶。该系列命令有一个 参数，用于指定要送到栈顶的数字。 注意该系列命令只能操作一定范围内的整形数值，超出该范围的使用将使用ldc 命令系列。 指令码 助记符 说明 0x10 bipush 将单字节的常量值(-128~127)推送至栈顶 0x11 sipush 将一个短整型常量值(-32768~32767)推送至栈顶 四、ldc系列 该系列命令负责把数值常量或String常量值从常量池中推送至栈顶。该命令后面 需要给一个表示常量在常量池中位置(编号)的参数， 哪些常量是放在常量池呢？比如：final static int id=32768;final static float double=6.5。 对于const系列命令和push系列命令操作范围之外的数值类型常量，都放在常量 池中. 另外，所有不是通过new创建的String都是放在常量池中的。 指令码 助记符 说明 0x12 ldc 将int, float或String型常量值从常量池中推送至栈 顶 0x13 ldc_w 将int, float或String型常量值从常量池中推送至栈 顶（宽索引） 0x14 ldc2_w 将long或double型常量值从常量池中推送至栈顶 （宽索引） 五、load系列 5.1、load系列A 该系列命令负责把本地变量的送到栈顶。这里的本地变量不仅可以是数值类型， 还可以是引用类型。 对于前四个本地变量可以采用iload_0,iload_1,iload_2,iload_3(它们分别表示第 0,1,2,3个整形变量)这种不到参数的简化命令形式。 对于第4以上的本地变量将使用iload命令这种形式，在它后面给一参数，以表示 是对第几个(从0开始)本类型的本地变量进行操作。 对本地变量所进行的编号，是对所有类型的本地变量进行的（并不按照类型分 类）。 对于非静态函数，第一变量是this,即其对于的操作是aload_0. 还有函数传入参数也算本地变量，在进行编号时，它是先于函数体的本地变量 的。 指令码 助记符 说明 0x15 iload 将指定的int型本地变量推送至栈顶 0x16 lload 将指定的long型本地变量推送至栈顶 0x17 fload 将指定的float型本地变量推送至栈顶 0x18 dload 将指定的double型本地变量推送至栈顶 0x19 aload 将指定的引用类型本地变量推送至栈顶 0x1a iload_0 将第一个int型本地变量推送至栈顶 0x1b iload_1 将第二个int型本地变量推送至栈顶 0x1c iload_2 将第三个int型本地变量推送至栈顶 0x1d iload_3 将第四个int型本地变量推送至栈顶 0x1e lload_0 将第一个long型本地变量推送至栈顶 0x1f lload_1 将第二个long型本地变量推送至栈顶 0x20 lload_2 将第三个long型本地变量推送至栈顶 0x21 lload_3 将第四个long型本地变量推送至栈顶 0x22 fload_0 将第一个float型本地变量推送至栈顶 0x23 fload_1 将第二个float型本地变量推送至栈顶 0x24 fload_2 将第三个float型本地变量推送至栈顶 0x25 fload_3 将第四个float型本地变量推送至栈顶 0x26 dload_0 将第一个double型本地变量推送至栈顶 0x27 dload_1 将第二个double型本地变量推送至栈顶 0x28 dload_2 将第三个double型本地变量推送至栈顶 0x29 dload_3 将第四个double型本地变量推送至栈顶 0x2a aload_0 将第一个引用类型本地变量推送至栈顶 0x2b aload_1 将第二个引用类型本地变量推送至栈顶 0x2c aload_2 将第三个引用类型本地变量推送至栈顶 0x2d aload_3 将第四个引用类型本地变量推送至栈顶 5.2、load系列B 该系列命令负责把数组的某项送到栈顶。该命令根据栈里内容来确定对哪个数组 的哪项进行操作。 比如，如果有成员变量：final String names[]={\u0026#34;robin\u0026#34;,\u0026#34;hb\u0026#34;}; 那么这句话：String str=names[0];对应的指令为 17:aload_0 //将this引用推送至栈 顶，即压入栈。 18:getfield#5; //Field names:[Ljava/lang/String;//将栈顶的指定的对象的 第5个实例域（Field）的值（这个值可能是引用，这里就是引用）压入栈顶 21:iconst_0 //数组的索引值（下标） 推至栈顶，即压入栈 22:aaload //根据栈里内容来把 name数组的第一项的值推至栈顶 23:astore5 //把栈顶的值存到str变量 里。因为str在我的程序中是其所在非静态函数的第5个变量(从0开始计数)， 指令码 助记符 说明 0x2e iaload 将int型数组指定索引的值推送至栈顶 0x2f laload 将long型数组指定索引的值推送至栈顶 0x30 faload 将float型数组指定索引的值推送至栈顶 0x31 daload 将double型数组指定索引的值推送至栈顶 0x32 aaload 将引用型数组指定索引的值推送至栈顶 0x33 baload 将boolean或byte型数组指定索引的值推送至 栈顶 0x34 caload 将char型数组指定索引的值推送至栈顶 0x35 saload 将short型数组指定索引的值推送至栈顶 六、store系列 6.1、store系列A 该系列命令负责把栈顶的值存入本地变量。这里的本地变量不仅可以是数值类 型，还可以是引用类型。 如果是把栈顶的值存入到前四个本地变量的话，采用的是istore_0,istore_1， istore_2，istore_3(它们分别表示第0,1,2,3个本地整形变量)这种不到参数的简 化命令形式。如果是把栈顶的值存入到第四个以上本地变量的话，将使用istore 命令这种形式，在它后面给一参数，以表示是把栈顶的值存入到第几个(从0开 始)本地变量中。 对本地变量所进行的编号，是对所有类型的本地变量进行的（并不按照类型分 类）。 对于非静态函数，第一变量是this,它是只读的. 还有函数传入参数也算本地变量，在进行编号时，它是先于函数体的本地变量 的。 指令码 助记符 说明 0x36 istore 将栈顶int型数值存入指定本地变量 0x37 lstore 将栈顶long型数值存入指定本地变量 0x38 fstore 将栈顶float型数值存入指定本地变量 0x39 dstore 将栈顶double型数值存入指定本地变量 0x3a astore 将栈顶引用型数值存入指定本地变量 0x3b istore_0 将栈顶int型数值存入第一个本地变量 0x3c istore_1 将栈顶int型数值存入第二个本地变量 0x3d istore_2 将栈顶int型数值存入第三个本地变量 0x3e istore_3 将栈顶int型数值存入第四个本地变量 0x3f lstore_0 将栈顶long型数值存入第一个本地变量 0x40 lstore_1 将栈顶long型数值存入第二个本地变量 0x41 lstore_2 将栈顶long型数值存入第三个本地变量 0x42 lstore_3 将栈顶long型数值存入第四个本地变量 0x43 fstore_0 将栈顶float型数值存入第一个本地变量 0x44 fstore_1 将栈顶float型数值存入第二个本地变量 0x45 fstore_2 将栈顶float型数值存入第三个本地变量 0x46 fstore_3 将栈顶float型数值存入第四个本地变量 0x47 dstore_0 将栈顶double型数值存入第一个本地变量 0x48 dstore_1 将栈顶double型数值存入第二个本地变量 0x49 dstore_2 将栈顶double型数值存入第三个本地变量 0x4a dstore_3 将栈顶double型数值存入第四个本地变量 0x4b astore_0 将栈顶引用型数值存入第一个本地变量 0x4c astore_1 将栈顶引用型数值存入第二个本地变量 0x4d astore_2 将栈顶引用型数值存入第三个本地变量 0x4e astore_3 将栈顶引用型数值存入第四个本地变量 6.2、store系列B 该系列命令负责把栈顶项的值存到数组里。该命令根据栈里内容来确定对哪个数 组的哪项进行操作。 比如，如下代码: int moneys[]=new int[5]; moneys[1]=100; 其对应的指令为： 49:iconst_5 50:newarray int 52:astore11 54:aload11 56:iconst_1 57:bipush100 59:iastore 60:lload6 //因为str在我的程序中是其所非静态在函数的第6个变量(从0开 始计数). 指令码 助记符 说明 0x4f iastore 将栈顶int型数值存入指定数组的指定索引位置 0x50 lastore 将栈顶long型数值存入指定数组的指定索引位置 0x51 fastore 将栈顶float型数值存入指定数组的指定索引位置 0x52 dastore 将栈顶double型数值存入指定数组的指定索引位置 0x53 aastore 将栈顶引用型数值存入指定数组的指定索引位置 0x54 bastore 将栈顶boolean或byte型数值存入指定数组的指定 索引位置 0x55 castore 将栈顶char型数值存入指定数组的指定索引位置 0x56 sastore 将栈顶short型数值存入指定数组的指定索引位置 七、pop系列 该系列命令似乎只是简单对栈顶进行操作，更多详情待补充。 指令码 助记符 说明 0x57 pop 将栈顶数值弹出 (数值不能是long或double类型的) 0x58 pop2 将栈顶的一个（long或double类型的)或两个数值弹出 （其它） 0x59 dup 复制栈顶数值(数值不能是long或double类型的)并将 复制值压入栈顶 0x5a dup_x1 复制栈顶数值(数值不能是long或double类型的)并将两 个复制值压入栈顶 0x5b dup_x2 复制栈顶数值(数值不能是long或double类型的)并将三 个（或两个）复制值压入栈顶 0x5c dup2 复制栈顶一个（long或double类型的)或两个（其它） 数值并将复制值压入栈顶 0x5d dup2_x1 复制栈顶数值(long或double类型的)并将两个复制值 压入栈顶 0x5e dup2_x2 复制栈顶数值(long或double类型的)并将三个（或两 个）复制值压入栈顶 八、栈顶元素数学操作及移位操作系列 该系列命令用于对栈顶元素行数学操作，和对数值进行移位操作。移位操作的操 作数和要移位的数都是从栈里取得。 比如对于代码：int k=100;k=k\u0026gt;\u0026gt;1;其对应的JVM指令为： 60:bipush100 62:istore12//因为k在我的程序中是其所在非静态函数的第12个变量(从0开始 计数). 64:iload12 66:iconst_1 67:ishr 68:istore12 指令码 助记符 说明 0x5f swap 将栈最顶端的两个数值互换(数值不能是long或 double类型的) 0x60 iadd 将栈顶两int型数值相加并将结果压入栈顶 0x61 ladd 将栈顶两long型数值相加并将结果压入栈顶 0x62 fadd 将栈顶两float型数值相加并将结果压入栈顶 0x63 dadd 将栈顶两double型数值相加并将结果压入栈顶 0x64 isub 将栈顶两int型数值相减并将结果压入栈顶 0x65 lsub 将栈顶两long型数值相减并将结果压入栈顶 0x66 fsub 将栈顶两float型数值相减并将结果压入栈顶 0x67 dsub 将栈顶两double型数值相减并将结果压入栈顶 0x68 imul 将栈顶两int型数值相乘并将结果压入栈顶 0x69 lmul 将栈顶两long型数值相乘并将结果压入栈顶 0x6a fmul 将栈顶两float型数值相乘并将结果压入栈顶 0x6b dmul 将栈顶两double型数值相乘并将结果压入栈顶 0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶 0x6d ldiv 将栈顶两long型数值相除并将结果压入栈顶 0x6e fdiv 将栈顶两float型数值相除并将结果压入栈顶 0x6f ddiv 将栈顶两double型数值相除并将结果压入栈顶 0x70 irem 将栈顶两int型数值作取模运算并将结果压入栈顶 0x71 lrem 将栈顶两long型数值作取模运算并将结果压入栈顶 0x72 frem 将栈顶两float型数值作取模运算并将结果压入栈顶 0x73 drem 将栈顶两double型数值作取模运算并将结果压入栈 顶 0x74 ineg 将栈顶int型数值取负并将结果压入栈顶 0x75 lneg 将栈顶long型数值取负并将结果压入栈顶 0x76 fneg 将栈顶float型数值取负并将结果压入栈顶 0x77 dneg 将栈顶double型数值取负并将结果压入栈顶 0x78 ishl 将int型数值左移位指定位数并将结果压入栈顶 0x79 lshl 将long型数值左移位指定位数并将结果压入栈顶 0x7a ishr 将int型数值右（符号）移位指定位数并将结果压入 栈顶 0x7b lshr 将long型数值右（符号）移位指定位数并将结果压 入栈顶 0x7c iushr 将int型数值右（无符号）移位指定位数并将结果压 入栈顶 0x7d lushr 将long型数值右（无符号）移位指定位数并将结果 压入栈顶 0x7e iand 将栈顶两int型数值作“按位与”并将结果压入栈顶 0x7f land 将栈顶两long型数值作“按位与”并将结果压入栈 顶 0x80 ior 将栈顶两int型数值作“按位或”并将结果压入栈顶 0x81 lor 将栈顶两long型数值作“按位或”并将结果压入栈 顶 0x82 ixor 将栈顶两int型数值作“按位异或”并将结果压入栈 顶 0x83 lxor 将栈顶两long型数值作“按位异或”并将结果压入 栈顶 九、自增减指令 该指令用于对本地(局部)变量进行自增减操作。该指令第一参数为本地变量的编 号，第二个参数为自增减的数量。 比如对于代码： int d=10; d++; d+=2; d--; 其指令为： 2: bipush10 4: istore_2//在我的程序中是其所在非静态函数的第2个变量(从0开始计数). 5: iinc2, 1//在我的程序中是其所在非静态函数的第2个变量(从0开始计数). 8: iinc2, 2 11: iinc2, -1 对本地变量所进行的编号，是对所有类型的本地变量进行的（并不按照类型分 类）。 对于非静态函数，第一变量是this,它是只读的.还有函数传入参数也算本地变 量，在进行编号时，它是先于函数体的本地变量的。 指令码 助记符 说明 0x84 iinc 将指定int型变量增加指定值（i++, i--, i+=2） 十、类型转化系列 该系列指令负责对栈顶数值类型进行类型转化，并把结果压入栈顶。 指令码 助记符 说明 0x85 i2l 将栈顶int型数值强制转换成long型数值并将 结果压入栈顶 0x86 i2f 将栈顶int型数值强制转换成float型数值并将 结果压入栈顶 0x87 i2d 将栈顶int型数值强制转换成double型数值并 将结果压入栈顶 0x88 l2i 将栈顶long型数值强制转换成int型数值并将 结果压入栈顶 0x89 l2f 将栈顶long型数值强制转换成float型数值并 将结果压入栈顶 0x8a l2d 将栈顶long型数值强制转换成double型数值 并将结果压入栈顶 0x8b f2i 将栈顶float型数值强制转换成int型数值并将 结果压入栈顶 0x8c f2l 将栈顶float型数值强制转换成long型数值并 将结果压入栈顶 0x8d f2d 将栈顶float型数值强制转换成double型数值 并将结果压入栈顶 0x8e d2i 将栈顶double型数值强制转换成int型数值并 将结果压入栈顶 0x8f d2l 将栈顶double型数值强制转换成long型数值 并将结果压入栈顶 0x90 d2f 将栈顶double型数值强制转换成float型数值 并将结果压入栈顶 0x91 i2b 将栈顶int型数值强制转换成byte型数值并将 结果压入栈顶 0x92 i2c 将栈顶int型数值强制转换成char型数值并将 结果压入栈顶 0x93 i2s 将栈顶int型数值强制转换成short型数值并将 结果压入栈顶 十一、比较指令系列A 该系列指令用于对栈顶非int型元素进行比较，并把结果压入栈顶。 比如，代码： void test() { long a=11; long b=10; boolean result=(a\u0026gt;b); } 其指令为： void test(); Code: 0:ldc2_w#16; //long 11l 3:lstore_1 4:ldc2_w#18; //long 10l 7:lstore_3 8:lload_1 9:lload_3 10:lcmp 11:ifle18 14:iconst_1 15:goto19 18:iconst_0 19:istore5 21:return 指令码 助记符 说明 0x94 lcmp 比较栈顶两long型数值大小，并将结果（1， 0，-1）压入栈顶 0x95 fcmpl 比较栈顶两float型数值大小，并将结果（1， 0，-1）压入栈顶；当其中一个数值为NaN时，将-1压入栈顶 0x96 fcmpg 比较栈顶两float型数值大小，并将结果（1， 0，-1）压入栈顶；当其中一个数值为NaN时，将1压入栈顶 0x97 dcmpl 比较栈顶两double型数值大小，并将结果（1， 0，-1）压入栈顶；当其中一个数值为NaN时，将-1压入栈顶 0x98 dcmpg 比较栈顶两double型数值大小，并将结果（1， 0，-1）压入栈顶；当其中一个数值为NaN时，将1压入栈顶 十二、有条件跳转指令系列A 该系列指令用于对栈顶int型元素进行比较，根据结果进行跳转。第一个参数为 要跳转到的代码的地址（这里的地址是指其指令在函数内是第几个指令）。注意 对于boolean型，其实是把它当做int型来处理的。另外对于引用比较使用的 时，其实是对存储的对象的地址进行比较。 比如代码： void test() { int a=11; int b=10; boolean result=(a\u0026gt;b); if(result) a+=2; if(!result) a+=2; if(a\u0026gt;0) a--; } 其对应的指令为： void test(); Code: 0:bipush11 2:istore_1 3:bipush10 5:istore_2 6:iload_1 7:iload_2 8:if_icmple15//如果比较结果小于0，就跳到第15个指令继续执行 11:iconst_1 12:goto16 15:iconst_0 16:istore_3 17:iload_3 18:ifeq24//如果结果为0时(即为false)，就跳转到第24个指令继续执行 21:iinc1, 2 24:iload_3 25:ifne31//如果结果不为0时(即为true)，就跳转到第31个指令继续执行 28:iinc1, 2 31:iload_1 32:ifle38 35:iinc1, -1//如果结果小于0时，就跳转到第38个指令继续执行 38:return 指令码 助记符 说明 0x99 ifeq 当栈顶int型数值等于0时跳转 0x9a ifne 当栈顶int型数值不等于0时跳转 0x9b iflt 当栈顶int型数值小于0时跳转 0x9c ifge 当栈顶int型数值大于等于0时跳转 0x9d ifgt 当栈顶int型数值大于0时跳转 0x9e ifle 当栈顶int型数值小于等于0时跳转 0x9f if_icmpeq 比较栈顶两int型数值大小，当结果等于0时跳转 0xa0 if_icmpne 比较栈顶两int型数值大小，当结果不等于0时跳转 0xa1 if_icmplt 比较栈顶两int型数值大小，当结果小于0时跳转 0xa2 if_icmpge 比较栈顶两int型数值大小，当结果大于等于0时跳转 0xa3 if_icmpgt 比较栈顶两int型数值大小，当结果大于0时跳转 0xa4 if_icmple 比较栈顶两int型数值大小，当结果小于等于0时跳转 0xa5 if_acmpeq 比较栈顶两引用型数值，当结果相等时跳转 0xa6 if_acmpne 比较栈顶两引用型数值，当结果不相等时跳转 十三、无条件跳转指令系列A 该系列指令用于指令的跳转。 指令码 助记符 说明 0xa7 goto 无条件跳转 0xa8 jsr 跳转至指定16位offset位置，并将jsr下一条指 令地址压入栈顶 0xa9 ret 返回至本地变量指定的index的指令位置（一般 与jsr, jsr_w联合使用） 0xaa tableswitch 用于switch条件跳转，case值连续（可变长度 指令） 0xab lookupswitch 用于switch条件跳转，case值不连续（可变长 度指令） 十四、返回指令系列 该系列指令用于从函数中返回。如果有返回值的话，都把函数的返回值放在栈道 中，以便它的调用方法取得它。 return 10;这个语句其实对应的指令是两条： 9:bipush10 11:ireturn 指令码 助记符 说明 0xac ireturn 从当前方法返回int 0xad lreturn 从当前方法返回long 0xae freturn 从当前方法返回float 0xaf dreturn 从当前方法返回double 0xb0 areturn 从当前方法返回对象引用 0xb1 return 从当前方法返回void 十五、域操作指令系列 该系列指令用于对静态域和非静态域进行读写。该系列命令需要跟一个表明域编 号的参数， 比如,在函数中对成员变量m进行;m++ 其指令为： 0:aload_0 1:dup 2:getfield#2; //Field m:I 5:iconst_1 6:iadd 7:putfield#2; //Field m:I 指令码 助记符 说明 0xb2 getstatic 获取指定类的静态域，并将其值压入栈顶 0xb3 putstatic 用栈顶的值为指定的类的静态域赋值 0xb4 getfield 获取指定类的实例域，并将其值压入栈顶 0xb5 putfield 用栈顶的值为指定的类的实例域赋值 十六、方法操作命令系列 该系列指令用于对静态方法和非静方法进行调用。该系列命令需要跟一个表明方 法编号的参数。 如果方法有传入参数的话，则需要先压栈到栈顶。另外，方法的返回参数是保存 到栈顶的，因此我们可以通过栈道值取得方法的返回值。 比如对于代码： void test(){int k=add(12,45);} 其指令为： void test(); Code: 0:aload_0 1:bipush12 3:bipush45 5:invokevirtual#2; //Method add:(II)I 8:istore_1 9:return 指令码 助记符 说明 0xb6 invokevirtual 调用实例方法 0xb7 invokespecial 调用超类构造方法，实例初始化方法，私 有方法 0xb8 invokestatic 调用静态方法 0xb9 invokeinterface 调用接口方法 十七、未归类系列B 此系列暂未归类。 指令码 助记符 说明 0xba -- 十八、new及数组系列 该系列用于创建一个对象和数组。 比如代码： void test() { int ids[]=new int[5]; Object objs[]=new Object[5]; Object obj=new Object(); Hello hello=new Hello(); int len=objs.length; } 其指令为： void test(); Code: 0:iconst_5 1:newarray int 3:astore_1 4:iconst_5 5:anewarray#2; //class java/lang/Object 8:astore_2 9:new#2; //class java/lang/Object 12:dup 13:invokespecial#1; //Method java/lang/Object.\u0026#34;\u0026#34;:()V 16:astore_3 17:new#3; //class Hello 20:dup 21:invokespecial#4; //Method \u0026#34;\u0026#34;:()V 24:astore4 26:aload_2 27:arraylength 28:istore5 30:return 指令码 助记符 说明 0xbb new 创建一个对象，并将其引用值压入栈顶 0xbc newarray 创建一个指定原始类型（如int, float, char…）的数组，并将其引用值压入栈顶 0xbd anewarray 创建一个引用型（如类，接口，数组）的数 组，并将其引用值压入栈顶 0xbe arraylength 获得数组的长度值并压入栈顶 十九、异常抛出指令 用于抛出异常。 指令码 助记符 说明 0xbf athrow 将栈顶的异常抛出 二十、对象操作指令 该系列指令用于操作对象。 指令码 助记符 说明 0xc0 checkcast 检验类型转换，检验未通过将抛出 ClassCastException 0xc1 instanceof 检验对象是否是指定的类的实例，如果是 将1压入栈顶，否则将0压入栈顶 0xc2 monitorenter 获得对象的锁，用于同步方法或同步块 0xc3 monitorexit 释放对象的锁，用于同步方法或同步块 二十一、未归类系列C 此系列暂未归类。 指令码 助记符 说明 0xc4 wide \u0026lt;待补充\u0026gt; 二十二、new多维数组系列 指令码 助记符 说明 0xc5 multianewarray 创建指定类型和指定维度的多维数组（执行该指令 时，操作栈中必须包含各维度的长度值），并将其引用值压入栈顶 二十三、有条件跳转指令系列B 该系列用于根据引用是否为空，来进行相应的指令跳转。 比如代码： void test() { int i=0; Object obj=new Object(); if(obj==null){i=0;} if(obj!=null){i=1;} } 其对应的指令为： void test(); Code: 0:iconst_0 1:istore_1 2:new#2; //class java/lang/Object 5:dup 6:invokespecial#1; //Method java/lang/Object.\u0026#34;\u0026#34;:()V 9:astore_2 10:aload_2 11:ifnonnull16 14:iconst_0 15:istore_1 16:aload_2 17:ifnull22 20:iconst_1 21:istore_1 22:return 指令码 助记符 说明 0xc6 ifnull 为null时跳转 0xc7 ifnonnull 不为null时跳转 二十四、无条件跳转指令系列B 该系列指令用于进行无条件指令跳转。 指令码 助记符 说明 0xc8 goto_w 无条件跳转（宽索引） 0xc9 jsr_w 跳转至指定32位offset位置，并将jsr_w下一 条指令地址压入栈顶 ","date":"2019-12-23T20:40:01+08:00","permalink":"/2019/12/23/JVM%E6%8C%87%E4%BB%A4%E6%89%8B%E5%86%8C/","title":"JVM指令手册[转]"},{"content":"课程地址:\n视频1 视频2 Base64 Base64 由64个字符组成a-zA-Z0-9\u0026hellip;\nBase64 是一种编码而不是加密;\n缺点👉Base64 会浪费网络带宽,并不高效!!!\n为什么还用 Base64 呢? 很大原因是因为后端开发只会这种早期方式。\n图片上传的三种方式: @POST + @FormUrlEncoded 参数为 @Filed Base64文本 @POST + @Multipart 参数为 @Part RequestBody 最不常见的: @POST 参数为 @Body RequestBody ","date":"2019-12-23T20:36:12+08:00","permalink":"/2019/12/23/%E6%A8%A1%E6%9D%BF/","title":"模板"},{"content":"课程安排\n启动速度、内存、卡顿、布局 电量、流量、瘦身、稳定性及专项技术优化 原理解析、工具选择、案例实战 方案改善、黑科技、模拟面试 启动优化 精准度量启动速度 异步初始化的更优解 更优秀的延迟初始化方案 ","date":"2019-12-21T17:42:03+08:00","permalink":"/2019/12/21/Android%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96-%E4%B8%80/","title":"Android性能优化（一）"},{"content":"Git思维导图 Linux 常用命令 pwd 查看当前文件路径\nmkdir 创建目录、touch 新建文件、rm 删除文件 查看 readme.txt 文件内容 cat readme.txt r 就是向下递归，不管有多少级目录，一并删除 ; -f 就是直接强行删除，不作任何提示的意思 删除目录和目录下所有文件 rm -rf [dir] ; 删除文件 rm -f xxx.log\n配置 当前PC下所有Git仓库都会使用如下配置，也可以对某个仓库指定不同的用户和邮箱 git config \u0026ndash;global user.name \u0026ldquo;changbao\u0026rdquo; git config \u0026ndash;global user.email \u0026ldquo;1772664927@qq.com\u0026rdquo; 使当前目录变成git仓库，生成.git目录 git init 版本回退 git reset \u0026ndash;hard HEAD^ git reset \u0026ndash;hard HEAD^^ git reset \u0026ndash;hard HEAD^^^ git reset \u0026ndash;hard HEAD~100 Tips : 先用 git reflog 查看版本号 eg: 435ccc9 HEAD@{2}:commit:增加666 再执行 git reset \u0026ndash;hard 版本号(435ccc9)\n工作区与暂存区 工作区：操作文件的目录； 版本库：工作区中的 .git 隐藏目录，它不属于工作区。其中最重要的就是 stage（暂存区）。还有git自动创建的第一个分支master，以及指向master\u0026rsquo;的一个 指针HEAD。 增加/删除文件 添加指定文件到 暂存区 git add file1 file2 \u0026hellip; 添加指定目录到 暂存区 git add [dir] 添加当前目录所有文件到 暂存区 git add . 添加每个变化前，都会要求确认 对于同一个文件的多处变化，可以实现分次提交 git add -p 把文件同时从删除 工作区 和 暂存区删除 git rm file1 file2 停止追踪指定文件，但该文件会保留在 工作区 git rm \u0026ndash;cached file 改名文件，并且将这个文件放入暂存区 git mv oldFile newFile 代码提交 提交暂存区到仓库区并添加注释 git commit -m \u0026ldquo;提交 readme.txt\u0026rdquo; 提交暂存区指定文件到仓库区 git commit file1 file2 \u0026hellip; -m \u0026ldquo;提交file1和file2\u0026rdquo; 提交工作区自上次commit之后的变化，直接到仓库区 git commit -a 提交时显示所有diff信息 git commit -v 使用一次新的commit，替代上一次提交 ？ what ？ 如果代码没有任何新的变化，则用来改写上一次commit的提交信息 git commit \u0026ndash;amend -m \u0026ldquo;提交啦\u0026rdquo; 重做上一次commit，并包括指定文件的变化 git commit \u0026ndash;amend file1 file2 分支 创建并切换分支 git checkout -b dev 列出所有本地分支 git branch 列出所有远程分支 git branch -r 列出所有本地分支 + 远程分支 git branch -a 切换分支，并更新工作区 git checkout dev 新建分支，但依然停留在当前分支 git branch feature/001 新建分支，并切换到该分支 git checkout -b feature/001 新建一个分支，指定提交： git branch [branch] [commit] 新建一个分支，与指定的远程分支建立追踪关系 ※ git branch \u0026ndash;track [branch] [remote-branch] 建立追踪关系，在现有分支与指定的远程分支之间 ※ git branch \u0026ndash;set-upstream dev origin/dev 切换到上一分支 git checkout - 选择一个commit，合并进当前分支 git cherry-pick [commit] 删除分支 git branch -d feature/001 删除远程分支 git push origin \u0026ndash;delete feature/001 git branch -dr feature/001 在master上合并dev分支的内容 git merge dev git branch -d dev 终止 merge git merge \u0026ndash;abort 分支管理策略 通常合并分支时，git一般使用”Fast forward”模式，在这种模式下，删除分支后，会丢掉分支信息，现在我们来使用带参数 –no-ff 来禁用”Fast forward”模式。\ngit merge \u0026ndash;no-ff -m \u0026ldquo;merge with no-ff\u0026rdquo; dev git branch -d dev 最后在查看历史记录，发现被删除的分支信息还在\n查看历史记录 git log \u0026ndash;graph ;\ngit log \u0026ndash;graph \u0026ndash;pretty=oneline \u0026ndash;abbrev-commit bug分支、stash功能 git stash 查看stash记录 git stash list 恢复/删除 git stash apply 恢复，git stash drop 删除 ，git stash pop 恢复+删除 刚才执行了 git stash 命令，把之前写的东西隐藏了起来，现在去创建新的 issue-404 分支修复bug，然后再把内容恢复回来。\n标签Tag (略) 查看记录 显示有变更的文件，commit之前之后都调一次 git status 显示当前分支的版本历史 git log 显示commit历史，以及每次commit发生变更的文件 git log \u0026ndash;stat 根据关键字搜索提交历史 git log -S [keyword] 显示某个文件的版本历史，包括文件改名 git log \u0026ndash;follow file1 ; git whatchanged file1 查看对 readme.txt 文件修改的历史记录，从近到远显示日志 git log git log \u0026ndash;pretty=oneline 显示最近五次提交 git log -5 \u0026ndash;pretty=oneline 等价于 git log -5 \u0026ndash;pretty \u0026ndash;oneline 显示指定文件是什么人在什么时间修改过 git blame file1 显示暂存区和工作区的差异 git diff 查看 readme.txt 改变了哪些内容 git diff readme.txt 显示工作区与当前分支最新commit之间的差异 git diff HEAD 显示今天你写了多少行代码 git diff \u0026ndash;shortstat \u0026ldquo;@{0 day ago}\u0026rdquo; 显示当前分支的最近几次提交 git reflog 远程同步 下载远程仓库的所有变动 git fetch [remote] 显示所有远程仓库 git remote -v 显示某个远程仓库的信息 - ？失败？ git remote show [remote] 增加新的远程仓库，并命名 git remote add feature/001 xxx.git 取回远程仓库的变化，并与本地分支合并 git pull origin dev 上传本地分支到远程 git push origin dev 强行推送当前分支到远程仓库，即使有冲突 git push origin \u0026ndash;force 推送所有分支到远程仓库 git push [remote] \u0026ndash;all git push origin \u0026ndash;all 撤销修改 恢复暂存区的指定文件到工作区 git checkout \u0026ndash; file1 恢复暂存区所有文件到工作区 git checkout . 重置暂存区与工作区，与上一次commit保持一致 git reset \u0026ndash;hard 重置当前分支的HEAD为指定commit，同时重置暂存区和工作区，与指定commit一致 git reset \u0026ndash;hard [commit] 重置当前HEAD为指定commit，但保持暂存区和工作区不变 git reset \u0026ndash;keep [commit] 新建一个commit，用来撤销指定commit 后者的所有变化都将被前者抵消，并且应用到当前分支 git revert [commit] 当 readme.txt 文件中有问题未提交之前，git status 命令提示的两种方法：\n直接修改文件内容，add/commit； git checkout \u0026ndash; readme.txt 会丢弃掉工作区的修改，优先从 暂存区 里拿 readme.txt自动修改后，还没有放到暂存区，使用 撤销修改就回到和版本库一模一样的状态。 另外一种是readme.txt已经放入暂存区了，接着又作了修改，撤销修改就回到添加暂存区后的状态。 删除文件 touch b.md -\u0026gt; git add b.md -\u0026gt; git commit -m \u0026ldquo;Add b.md\u0026rdquo; -\u0026gt; rm b.md -\u0026gt; git status 显示 deleted: b.md 此时b.md已从工作区删除但仍存在于stage（暂存区），可通过:\n恢复文件到工作区 git checkout \u0026ndash; b.md 恢复文件到工作区 如果从暂存区删除，在rm 之后调用 : git commit - a 之后会提示提交 message的vim窗口。或者：git commit -a -m \u0026ldquo;重命名 Git常用命令.md\u0026rdquo;\ngit清除本地缓存（改变成未track状态），然后再提交: git rm -r \u0026ndash;cached .\n删除多提交的目录,比如.idea,build文件夹:\n$ git pull origin master 将远程仓库里面的项目拉下来 $ dir 查看有哪些文件夹 $ git rm -r --cached .idea 删除.idea文件夹 $ git commit -m \u0026#39;删除了.idea\u0026#39; 提交,添加操作说明 $ git push -u origin master 将本次更改更新到github项目上去 操作完成. 多人协作 查看远程仓库信息 git remote 加上 -v 查看详细信息 创建本地dev分支 + 从远程检出dev分支 git checkout -b dev origin/dev 修改内容后 git pull origin dev , git push origin dev git pull 失败，可能是没有指定本地dev分支与远程origin/dev分支的链接，根据提示 设置dev origin/dev 的链接： git branch \u0026ndash;set-upstream dev origin/dev ; git pull 如果GitHub上新建的仓库为空 目前，在GitHub上的这个testgit仓库还是空的，GitHub告诉我们，可以从这个仓库克隆出新的仓库，也可以把一个已有的本地仓库与之关联，然后，把本地仓库的内容推送到GitHub仓库。 现在，我们根据GitHub的提示，在本地的testgit仓库下运行命令： git remote add origin https://github.com/javakam/gitpractice.git\n把本地仓库的 master 分支推到远程仓库 ，第一次加上 -u 由于远程库是空的，我们第一次推送master分支时，加上了 –u参数，Git不但会把本地的master分支内容推送的远程新的master分支，还会把本地的master分支和远程的master分支关联起来，在以后的推送或者拉取时就可以简化命令。 git push -u origin master\ngit库所在的文件夹中的文件大致有4种状态 Untracked: 未跟踪, 此文件在文件夹中, 但并没有加入到git库, 不参与版本控制. 通过git add 状态变为Staged.\nUnmodify: 文件已经入库, 未修改, 即版本库中的文件快照内容与文件夹中完全一致. 这种类型的文件有两种去处, 如果它被修改, 而变为Modified. 如果使用git rm移出版本库, 则成为Untracked文件\nModified: 文件已修改, 仅仅是修改, 并没有进行其他的操作. 这个文件也有两个去处, 通过git add可进入暂存staged状态, 使用git checkout 则丢弃修改过, 返回到unmodify状态, 这个git checkout即从库中取出文件, 覆盖当前修改\nStaged: 暂存状态. 执行git commit则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodify状态. 执行git reset HEAD filename取消暂存, 文件状态为Modified\nGit 状态 untracked 和 not staged的区别\nuntrack 表示是新文件，没有被add过，是为跟踪的意思。 not staged 表示add过的文件，即跟踪文件，再次修改没有add，就是没有暂存的意思 每次开发完新feature之后 git pull origin develop git push origin feature/dialog After PR merged : git branch -dr feature/dialog push之后，GitHub上会有黄色提示窗提示Pull Request，提完PR后如果没问题， 直接merge，若需要review时候，可直接在review上修改，也可以在IDE上修改之后 再次执行上面的拉取和推送操作，此时不会创建新的PR。\n参考链接 Git 傻瓜教程 ","date":"2019-12-01T08:19:13+08:00","permalink":"/2019/12/01/Git%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/","title":"Git使用指南"},{"content":"adb 全称Android Debug Bridge ，安卓调试桥接器。它是Android SDK里面的一个工具，用这个工具可以直接操作管理Android模拟器或者真实Android设备。adb的工作方式比较特殊采用监听Socket TCP 5554等端口的方式让IDE和Qemu通讯，默认情况下adb会daemon相关的网络端口，所以当我们运行eclipse时adb进程就会自动运行。\nadb devices 主要作用：查看当前已连接的设备，连接到计算机的Android设备或者模拟器将会以列表的形式显示。\n输出格式：[serialNumber][state]\n如果当前没有模拟器或者设备运行，adb则会返回List of devices attached为空\noffline：表明设备没有连接到计算机或无响应 device：设备已经连接到计算机。注意，该状态并不表示Android设备可用，当Android设备处于启动阶段时，若连接成功也会返回该状态。\nadb install \u0026lt;apk文件路径\u0026gt; 主要作用：将指定的apk文件安装到设备上，\u0026lt;apk文件路径\u0026gt;可以从本地文件夹拖动到终端中。\n常用参数：\nadb install –r 覆盖安装 adb install –s 安装到SD卡 adb install –rs覆盖安装到SD卡 adb –s \u0026lt;serial number\u0026gt; install 选定设备安装 当有多个设备连接时，可以用下面的命令来直接选定设备进行安装。\nadb [-d|-e|-s \u0026lt;serial number\u0026gt;] install \u0026lt;path_to_apk\u0026gt; d：真机（多个设备中只有一个真机时适用） e：模拟器（多个设备中只有一个模拟器时适用） s：序列号 adb –d install ./test.apk adb –s emulator-5556 install ./test.apk adb uninstall \u0026lt;包名\u0026gt; 主要作用：卸载设备上的指定程序\n查看包名路径：/data/app，系统安装包路径：/sys/data，这两个路径下文件的查看都需要root权限\nadb uninstall –k \u0026lt;包名\u0026gt; adb shell pm uninstall –k \u0026lt;包名\u0026gt; 卸载程序但是保留其配置和缓存文件，即/data/data/packname下的数据与/sdcard/程序名 的数据\nadb push/pull 主要作用：复制文件\n1）adb push \u0026lt;本地路径\u0026gt;\u0026lt;设备路径\u0026gt;\n把pc上的文件或文件夹复制到设备中。\nadb push /home/test.apk /sdcard/ 2）adb pull \u0026lt;设备路径\u0026gt;\u0026lt;本地路径\u0026gt;\n把设备上的文件或文件夹复制到电脑\nadb pull /sdcard/log/test.xls /home/ Pull命令后可不输入本地地址，不输入时文件会复制到当前终端所在目录\n关闭和启动adb服务 sudo –s adb kill-server //关闭adb服务 adb start-server //启动adb服务 sudo –i和sudo –s的区别 sudo –i：在root用户下，使用root权限执行adb命令 sudo –s：在当前用户下，使用root权限执行adb命令 adb logcat 主要作用：查看日志，在命令行中显示调试信息\nadb logcat \u0026gt;\u0026gt; \u0026lt;指定文件路径\u0026gt; 将logcat信息保存在指定文件中 adb logcat –help：查看logcat命令帮助文档 adb logcat -s NET 查看网络通信信息 每一条日志消息都有一个标记和优先级与其关联。\n格式为：/\n过滤不同优先级的log：adb logcat *:W（过滤比W优先级低的log）\n标记是一个简短的字符串，用于标识原始消息的来源（例如“View”来源于显示系统）。\n优先级是下面的字符，顺序从低到高：\nV—Verbose 明细（最低优先级） D—Debug 调试 I—Info 信息 W—Warm 警告 E—Error 错误 F—Fatal 严重错误 S—Silent 无记载（最高优先级，没有什么会被记载） 建议配合Eclipse使用，查看logcat\nadb shell 由于Android是基于Linux内核的操作系统。因此，在Android上可以执行shell命令。\n常用命令如下：\nadb shell \u0026lt;command\u0026gt; 直接运行设备命令 adb shell su –c “\u0026lt;command\u0026gt;” 直接运行root权限命令 adb shell am am start –n \u0026lt;包名\u0026gt;/\u0026lt;包名\u0026gt;.\u0026lt;Activity名\u0026gt; 启动程序 am force-stop \u0026lt;包名\u0026gt; 强制停止程序 am kill \u0026lt;包名\u0026gt; 杀死与包名有关的后台进程，不影响用户体验，相当于一般的清理内存功能 am kill-all 杀死所有后台进程 adb shell pm pm path \u0026lt;包名\u0026gt; 查看apk安装在手机后的路径 pm uninstall [-k] \u0026lt;包名\u0026gt; 卸载程序（-k：保留配置文件） pm clear \u0026lt;包名\u0026gt; 清除应用缓存数据 其他常用命令 adb help 显示帮助信息 adb version 显示adb版本 adb reboot 重启手机 adb shell am broadcast -a android.intent.action.MASTER_CLEAR（恢复出厂） adb shell dumpsys window | grep init 查看手机的分辨率 adb logcat -s ActivityManager Activity的启动时间 ","date":"2019-11-24T10:54:50+08:00","permalink":"/2019/11/24/adb/","title":"ADB调试工具"},{"content":"GD图像处理 GD图像处理基本技术 GD库引入与介绍 API：外部提供的应用接口：已经准备好了一套处理某些功能的机制，用户只需要按照指定的数据要求，调用指定的函数或者方法（类）就可以实现某个功能。\nGD库的概念：Graphic Device，图像处理扩展（外部提供的API），能够允许PHP在脚本中使用对应的函数来实现某些图像制作功能 GD库的引入：GD库是外部提供的API，已经被集成到PHP扩展库中（不需要下载），但是需要在PHP的配置文件中开启对应的扩展。GD扩展（GD2）。 ","date":"2019-10-29T09:13:35+08:00","permalink":"/2019/10/29/PHP-16.GD%E5%9B%BE%E5%BD%A2%E5%A4%84%E7%90%86/","title":"16.PHP GD图像处理"},{"content":"会话技术 会话技术介绍 web会话可简单理解为：用户开一个浏览器，访问某一个web站点，在这个站点点击多个超链接，访问服务器多个web资源，然后关闭浏览器，整个过程称之为一个会话。 HTTP协议的特点是无状态/无连接，当一个浏览器连续多次请求同一个web服务器时，服务器是无法区分多个操作是否来自于同一个浏览器（用户）。会话技术就是通过HTTP协议想办法让服务器能够识别来自同一个浏览器的多次请求，从而方便浏览器（用户）在访问同一个网站的多次操作中，能够持续进行而不需要进行额外的身份验证。\n会话技术分类 cookie技术 Cookie 是在 HTTP 协议下，服务器或脚本可以维护客户工作站上信息的一种方式。Cookie 是由 Web 服务器保存在用户浏览器（客户端）上的小文本文件(HTTP协议响应头) ，它可以包含有关用户的信息。无论何时用户链接到服务器（HTTP请求携带数据），Web 站点都可以访问 Cookie 信息 session技术 Session直接翻译成中文比较困难，一般都译成时域。在计算机专业术语中，Session是指一个终端用户与交互系统进行通信的时间间隔，通常指从注册进入系统到注销退出系统之间所经过的时间。 以及如果需要的话，可能还有一定的操作空间。Session技术是将 数据保存到服务器端 ，无论何时用户链接到服务器，Web站点都可以访问Session信息：SESSION技术的实现是依赖COOKIE技术的。 两种会话技术区别(🌈身份证和银行卡的区别) 安全性方面 a. Session存储服务器端，安全性高 b. Cookie存储浏览器端，安全性低 数据大小方面 a. Cookie的数量和大小都有限制（20个/4K） b. Session数据存储不限 可用数据类型 a. Cookie只能存储简单数据，数值/字符串 b. Session可以存储复杂数据（自动序列化） 保存位置方面 a. Cookie保存在浏览器上 b. Session保存在服务器上 COOKIE COOKIE 原理与基本使用 COOKIE原理 COOKIE技术：服务器将数据通过HTTP响应存储到浏览器上，浏览器可以在以后携带对应的COOKIE数据访问服务器。\n第一次请求时，PHP通过setcookie函数将数据通过http协议响应头传输给浏览器 浏览器在第一次响应的时候将Cookie数据保存到浏览器 浏览器后续请求同一个网站的时候，会自动检测是否存在Cookie数据，如果存在将在请求头中将数据携带到服务器 PHP执行的时候会自动判断浏览器请求中是否携带Cookie，如果写到，自动保存到 $_ COOKIE 中 利用 $_ COOKIE 访问 Cookie 数据 设置COOKIE信息 setcookie 函数用来设定 COOKIE 信息\nsetcookie (名字，值)\ncookie名的设置：字符串，第一个参数 cookie值的设置：第二个参数 cookie值的类型要求：必须是简单类型中的整数或者字符串 //设置COOKIE setcookie(\u0026#39;age\u0026#39;,1); setcookie(\u0026#39;name\u0026#39;,\u0026#39;Martin\u0026#39;); 🍎查看浏览器中的Cookie 读取COOKIE信息 🌈 COOKIE（会话技术）能够实现跨脚本共享数据\necho \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //读取COOKIE var_dump($_COOKIE); 🍎 array(3) { [\u0026#34;Phpstorm-6d42c3c3\u0026#34;]=\u0026gt; string(36) \u0026#34;544459c3-a698-4c59-8a6e-02f21677a31f\u0026#34; [\u0026#34;age\u0026#34;]=\u0026gt; string(1) \u0026#34;1\u0026#34; [\u0026#34;name\u0026#34;]=\u0026gt; string(6) \u0026#34;Martin\u0026#34; } COOKIE 高级使用 COOKIE 生命周期 COOKIE生命周期：COOKIE在浏览器生存时间（浏览器在下次访问服务器的时候是否携带对应的COOKIE）\n默认（不设定）时的生命周期：不设定周期默认是关闭浏览器（会话结束） 设定为一个常规日期戳的周期：通过 setcookie 第三个参数可以限定生命周期，是用时间戳来管理，从格林威治时间开始 //设置COOKIE setcookie(\u0026#39;a1\u0026#39;,\u0026#39;a1\u0026#39;);\t//普通COOKIE，浏览器关闭失效 setcookie(\u0026#39;a2\u0026#39;,\u0026#39;a2\u0026#39;,7*24*60*60);\t//格林威治时间7天过期 setcookie(\u0026#39;a3\u0026#39;,\u0026#39;a3\u0026#39;,time() + 7*24*60*60);\t//格林威治时间从现在开始7天后过期 🍎 🐖 没有 \u0026ldquo;a2\u0026rdquo; 说明:浏览器发现本来就无效的COOKIE直接不保存\n浏览器关闭后重新查看 发现只剩下 \u0026ldquo;a3\u0026rdquo;\n设定为“0”的周期：在第三个参数设定生命周期的时候，用0代替时间戳：表示就是普通设置，会话结束过期 🌈 默认值为 0 , 即\u0026quot;到期时间\u0026quot;为\u0026quot;浏览会话结束时\u0026quot; //0生命周期 setcookie(\u0026#39;a4\u0026#39;,\u0026#39;a4\u0026#39;,0); 🍎 删除一个cookie的做法：服务器没有权限去操作浏览器上的内容（不可能删除）。可以通过设定生命周期来让浏览器自动判定COOKIE是否有效：无效就清除 4.1\t清空COOKIE数据内容 4.2\t设定时间戳过期 //“删除”COOKIE //setcookie(\u0026#39;a3\u0026#39;,\u0026#39;\u0026#39;);\t//清空COOKIE数据内容 setcookie(\u0026#39;a3\u0026#39;,\u0026#39;a3\u0026#39;,time());\t//设定时间戳过期 COOKIE 作用范围 作用范围：不同的文件夹层级中，设定的 COOKIE 默认是在不同的文件夹下有访问限制。 上层文件夹中设定的 COOKIE 可以在下层（子文件夹）中访问，而子文件夹中设定的 COOKIE 不能在上层文件夹中访问。\n默认（不设定）的范围：就是使用COOKIE默认的作用范围 🌰father.php //访问COOKIE var_dump($_COOKIE); //设定普通COOKIE setcookie(\u0026#39;father1\u0026#39;,\u0026#39;father1\u0026#39;); 🍎 设定为 “/” 的含义：告知浏览器当前COOKIE的作用范围是网站根目录 etcookie (名字,值,生命周期,作用范围) 🌰son.php //访问COOKIE var_dump($_COOKIE); //设定普通COOKIE setcookie(\u0026#39;son1\u0026#39;,\u0026#39;son1\u0026#39;); //设定全局访问COOKIE（localhost） 👉 指定路径的 COOKIE , 使 father 也能访问 setcookie(\u0026#39;global_son\u0026#39;,\u0026#39;global_son\u0026#39;,0,\u0026#39;/\u0026#39;); 🍎 COOKIE跨子域 跨子域：在同一级别域名下，myitcast.com（一级域名），可以有多个子域名（www.myitcast.com和gz.myitcast.com），他们之间是搭建在不同的服务器上（不同文件夹：E:/server/apache/htdocs和E:/web），但是可以通过COOKIE设置实现对应的COOKIE共享访问。但是默认是不允许跨域名访问的。 🌴\nhosts中增加映射 C:\\Windows\\System32\\drivers\\etc\\hosts 加入 127.0.0.1 blog.mysite.com Apache24 httpd-vhosts.conf 中 C:\\fastwork\\Apache24\\conf\\extra\\httpd-vhosts.conf # 再配置一个 blog.mysite.com:80 # 嗯~~主要是用来测 COOKIE跨子域 效果的 👉 setcookie(\u0026#39;xxxaa\u0026#39;,\u0026#39;跨域写入 COOKIE\u0026#39;,0,\u0026#39;/\u0026#39;,\u0026#39;mysite.com\u0026#39;); \u0026lt;VirtualHost *:80\u0026gt; DocumentRoot \u0026#34;${SRVROOT}/htdocs\u0026#34; ServerName localhost \u0026lt;Directory \u0026#34;${SRVROOT}/htdocs\u0026#34;\u0026gt; Order Deny,Allow # Deny from all Allow from all Options Indexes FollowSymLinks DirectoryIndex index.html index.htm index.php \u0026lt;/Directory\u0026gt; \u0026lt;/VirtualHost\u0026gt; 设定cookie的有效域名：不同的域名（包含主机）之间不能共享COOKIE 可以通过setcookie的第五个参数来控制 setcookie(名字,值,生命周期,作用范围,有效域名) 不设定时的默认有效域名,互不相同 跨子域的设定方法：在设定域名访问的时候用设定上级域名即可：myitcast.com ，这个是有 所有以 myitcast.com 结尾的网站都可以共享 COOKIE test.php\necho \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; var_dump($_COOKIE); //设定一个本地COOKIE setcookie(\u0026#39;local\u0026#39;,\u0026#39;local\u0026#39;,0,\u0026#39;/\u0026#39;); //指定域名COOKIE setcookie(\u0026#39;消消息\u0026#39;,\u0026#39;跨域写入COOKIE\u0026#39;,time() + 7*24*60*60,\u0026#39;/\u0026#39;,\u0026#39;mysite.com\u0026#39;);//格林威治时间从现在开始7天后过期 🍎 运行结果 设置中查看COOKIE 若改用设置 setcookie('消消息','跨域写入COOKIE',time()+7*24*60*60,'/','www.mysite.com'); 🍎🍎 什么都写不进去\u0026hellip;\n🐖 必须是同一域名 \u0026lsquo;mysite.com\u0026rsquo; 下的子域名才能彼此设置, 比如通过 localhost/test.php 去为 www.mysite.com 增加 COOKIE 则无效 ; 需要使用 blog.mysite.com 进行设置才能成功 。\nCOOKIE 数组数据 COOKIE本身只支持简单数据（数字或者字符串），能够保留的数据本身有限，也不成体系。\n如果需要使用COOKIE来保留一组数据的化，想办法凑成数组。（COOKIE不支持数组）\n设置形式： setcookie(\u0026lsquo;c1[k1]\u0026rsquo;, 值) 读取形式： $_ COOKIE[\u0026lsquo;c1\u0026rsquo;][\u0026lsquo;k1\u0026rsquo;] 🌰\n//使用“数组”保存COOKIE //尝试保存数组 //setcookie(\u0026#39;goods_ids\u0026#39;,array(1,2,3,4,5)); //🍎错误: Warning: setcookie() expects parameter 2 to be string, array given in C:\\fastwork\\Apache24\\htdocs\\test.php on line 6 //伪装数组 setcookie(\u0026#39;goods_ids[0]\u0026#39;,\u0026#39;abcdefghijklmn\u0026#39;); setcookie(\u0026#39;goods_ids[1]\u0026#39;,\u0026#39;小明\u0026#39;); setcookie(\u0026#39;goods_ids[2]\u0026#39;,2333); setcookie(\u0026#39;goods_ids[3]\u0026#39;,66281); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($_COOKIE); //echo $_COOKIE[\u0026#39;goods_ids\u0026#39;][1]; //🍎 小明 🍎 Array ( [goods_ids] =\u0026gt; Array ( [0] =\u0026gt; abcdefghijklmn [1] =\u0026gt; 小明 [2] =\u0026gt; 2333 [3] =\u0026gt; 66281 ) ) SESSION SESSION 原理 Session与浏览器无关，但是与Cookie有关。\nPHP碰到session_start()时开启session会话，会自动检测sessionID a)\t如果Cookie中存在，使用现成的 b)\t如果Cookie中不存在，创建一个sessionID，并通过响应头以Cookie形式保存到浏览器上 初始化超全局变量 $_ SESSION 为一个空数组 PHP通过 sessionID 去指定位置（session文件存储位置）匹配对应的文件 a)\t不存在该文件：创建一个sessionID命名文件 b)\t存在该文件：读取文件内容（反序列化），将数据存储到 $_ SESSION 中 脚本执行结束，将 $_ SESSION 中保存的所有数据序列化存储到 sessionID 对应的文件中 SESSION基本使用 $_ SESSION 是通过 session_start() 函数的调用才会定义的，没有直接定义\n启用 SESSION ，任何时候都需要开启 SESSION （脚本使用到 $_ SESSION 就开启一次） 设置 SESSION 信息, 如果想存储数据到 SESSION 中，那么只要不断给 $_ SESSION 数组添加元素即可 读取 SESSION 信息, $_ SESSION 就是一个数组，存储什么数据，什么方式存的，就是可以通过什么方式访问什么数据 🌰\n//必须先开启 SESSION,否则报错: //Notice: Undefined variable: _SESSION in C:\\fastwork\\Apache24\\htdocs\\test.php on line 7 NULL //var_dump($_SESSION);\t//开启 SESSION session_start(); //设置 SESSION 数据 $_SESSION[\u0026#39;name\u0026#39;] = \u0026#39;Martin\u0026#39;; $_SESSION[\u0026#39;hobby\u0026#39;] = array(\u0026#39;basketball\u0026#39;,\u0026#39;football\u0026#39;); //访问 SESSION 数据 //echo $_SESSION[\u0026#39;name\u0026#39;]; echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($_SESSION); 🍎 Array ( [name] =\u0026gt; Martin [hobby] =\u0026gt; Array ( [0] =\u0026gt; basketball [1] =\u0026gt; football ) ) 会话技术的本质是为了实现跨脚本共享数据：在一个脚本中定义数据，在另外一个脚本中保存数据\n🌰在test.php中定义 SESSION 数据 ,在另外的脚本中保存数据\n删除一个SESSION信息 unset($_SESSION['name']); , 删除 SESSION 就是将 SESSION 数据清理掉（$_ SESSION拿不到） 删除全部SESSION信息 $_SESSION = array(); , 删除全部数据就是让 $_ SESSION 变成一个空数组 🌰\necho \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; session_start(); print_r($_SESSION); //删除一个SESSION信息 unset($_SESSION[\u0026#39;name\u0026#39;]); //删除全部SESSION信息 //$_SESSION = array(); print_r($_SESSION); 🍎删除一个 Array ( [name] =\u0026gt; Martin [hobby] =\u0026gt; Array ( [0] =\u0026gt; basketball [1] =\u0026gt; football ) ) Array ( [hobby] =\u0026gt; Array ( [0] =\u0026gt; basketball [1] =\u0026gt; football ) ) 🍎删除全部 Array ( [hobby] =\u0026gt; Array ( [0] =\u0026gt; basketball [1] =\u0026gt; football ) ) Array ( ) SESSION 相关配置 SESSION 基础配置 php.ini session.name ：session名字，保存到 COOKIE 中 sessionID 对应的名字 session.name = PHPSESSID session.auto_start ：是否自动开启 session （无需手动 session_start()），默认是关闭的 session.auto_start = 0 session.save_handler ：session 数据的保存方式，默认是文件形式 session.save_handler = files session.save_path：session文件默认存储的位置 , 使用系统的文件夹存储不安全，需要指定对应存储路径 ;session.save_path = \u0026#34;/tmp\u0026#34; 👉 session.save_path = \u0026#34;C:\\fastwork\\Server\\Sessions\u0026#34; SESSION 常用配置 session.cookie_lifetime ： PHPsessionID 在浏览器端对应 COOKIE 的生命周期，默认浏览器关闭后会话结束 session.cookie_lifetime = 0 session.cookie_path ：sessionID在浏览器存储之后允许服务器访问的路径（COOKIE有作用范围）,默认网站根目录 session.cookie_path = / session.cookie_domain ：COOKIE 允许访问的子域（COOKIE可以跨子域）, 默认的 PHPsessionID 只能当前主机访问 session.cookie_domain = ,保证数据 的安全 配置的两种形式 php.ini中配置：全局配置，修改php.ini中的配置项 session.save_path = \u0026quot;C:\\fastwork\\Server\\Sessions 🌈脚本中配置： PHP可以通过 ini_set 函数来在运行中设定某些配置项（只会对当前运行的脚本有效），把这种配置称之为项目级 @ini_set('session.save_path','C:\\fastwork\\Server\\Sessions'); 销毁 SESSION Session删除是指删除session数据，$_ SESSION 中看不到而已；销毁session是指删除session对应的session文件。\n系统提供一个函数： session_destroy()，会自动根据 session_start 得到的 sessionID 去找到指定的session文件，并把其删除。\n//开启session session_start(); //删除数据：清空$_SESSION var_dump($_SESSION); //测试效果 sleep(5);\t//脚本休息5秒执行 //销毁session session_destroy(); SESSION 垃圾回收机制 垃圾回收机制原理 session会话技术后，session文件并不会自动清除，如果每天有大量session文件产生但是又都是失效的，会增加服务器的压力和影响session效率。 垃圾回收，是指session机制提供了一种解决垃圾session文件的方式：给session文件指定周期，通过session文件最后更改时间与生命周期进行结合判定，如果已经过期则删除对应的session文件，如果没有过期则保留。这样就可以及时清理无效的僵尸文件，从而提升空间利用率和session工作效率。\n任何一次session开启（session_start），session都会尝试去读取session文件 读取session文件后，有可能触发垃圾回收机制（在session系统中也是一个函数：自己有一定几率调用） 垃圾回收机制会自动读取所有session文件的最后编辑时间，然后加上生命周期（配置文件）与当前时间进行比较（ 所有session文件 ） a)\t过期：删除 b)\t有效：保留 垃圾回收参数设置 session.gc_maxlifetime = 1440 ：规定的session文件最大的生命周期是1440秒，24分钟 session.gc_probability = 1 ：垃圾回收概率因子（分子） session.gc_divisor = 1000 ：垃圾回收概率分母 默认的触发概率是1/1000\n🌈php.ini\nsession.gc_probability = 1 session.gc_divisor = 1000 session.gc_maxlifetime = 1440 测试垃圾回收效果：\n修改生命周期为2分钟，120秒 修改触发几率：100% 🌰 php.ini session.gc_divisor = 1000 session.gc_maxlifetime = 120 🍎 session 文件会很快被删除(回收) 禁用COOKIE后如何使用SESSION 禁用COOKIE不能使用SESSION原因 Session技术需要利用到COOKIE技术来保存sessionID，从而使得PHP能够在跨脚本的时候得到相同的sessionID，从而访问同一个session文件。 解决思路：最终让session_start在开启之前拿到原来的sessionID（另外一个脚本的）\n实现无COOKIE使用SESSION 在PHP中，想要解决没有COOKIE也实现session技术的方式有两种：\n方案1(推荐)：可以利用PHP提供的session函数：session_id和session_name来获得和设置sessionID或者name从而解决session_start产生新sessionID的情况（手动操作）：\n在session保存数据的脚本中获取sessionID和名字 想办法将数据传递给另外一个脚本：表单传值（URL或者form表单） 在需要使用到session的脚本中，先接收数据 组织session_start产生新的ID，告诉它已经存在：session_id($id) echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //开启 session_start(); //获取sessionID和名字 $id = session_id();\t//获取是在session_start之后 $name = session_name();\t//拿到名字（php.ini session.name） echo $name . \u0026#39; = \u0026#39; . $id . \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; //设置内容 $_SESSION[\u0026#39;name\u0026#39;] = \u0026#39;Changbao\u0026#39;; //传递给另外一个脚本 echo \u0026#34;\u0026lt;a href=\u0026#39;test2.php?{$name}={$id}\u0026#39;\u0026gt;click\u0026lt;/a\u0026gt;\u0026#34;; 🍎 PHPSESSID = 7lhv7dctiksut4trk6aj88urjb click 另外一个脚本,test2.php\necho \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //访问session //接收数据 $name = session_name(); $id = $_GET[$name]; //⭐ 设定sessionID session_id($id); //开启session session_start(); //访问 var_dump($_SESSION); 🍎 点击 click array(1) { [\u0026#34;name\u0026#34;]=\u0026gt; string(8) \u0026#34;Changbao\u0026#34; } 🐖 session_id() 既可以获取 PHPSESSID 也可以为新的脚本设置 PHPSESSID\n方案2：可以利用session集中已经提供的解决方案自动操作（配置）\n原因1：默认session配置只允许使用COOKIE保存sessionID：cookie_ony 原因2：默认关闭了其他能够传送数据的方式，只保留了COOKIE 修改PHP配置文件，开启其他方式传输sessionID，关闭只允许使用COOKIE传输功能 session.use_only_cookies = 0 需要关闭,从1变成0 session.use_trans_sid = 1 开启其他方式保存sid,从0变成1 一旦配置开启，PHP会自动将sessionID和session名字在其他位置绑定数据，同时还会在session_start的时候，考虑其他方式传递（表单）的数据，而不是只有COOKIE 对比与方案一的改变:\n//保存session数据 session_start(); $_SESSION[\u0026#39;age\u0026#39;] = 40; echo \u0026#34;\u0026lt;a href=\u0026#39;40session_nocookie4.php\u0026#39;\u0026gt;click\u0026lt;/a\u0026gt;\u0026#34;; test2.php\n//访问session数据 session_start(); var_dump($_SESSION); ","date":"2019-10-28T09:09:15+08:00","permalink":"/2019/10/28/PHP-15.%E4%BC%9A%E8%AF%9D%E6%8A%80%E6%9C%AF/","title":"15.PHP会话技术"},{"content":"文件编程 目录操作 创建目录 mkdir (路径名字)：创建成功返回true，创建失败返回false\n//创建目录 $res = @mkdir(\u0026#39;ceshi\u0026#39;); var_dump($res); 🐖 $res = mkdir(\u0026#39;ceshi\u0026#39;); 再次创建会报错,加入 \u0026#39;@\u0026#39; 后可以起到抑制错误的作用 删除目录 rmdir (指定文件夹路径)：移出文件夹 @rmdir('ceshi');\n读取目录 读取方式：将文件夹（路径）按照资源方式打开\nopendir()：打开资源，返回一个路径资源，包含指定目录下的所有文件（文件夹） readdir()：从资源中读取指针所在位置的文件名字，然后指针下移，直到指针移出资源 closedir() :关闭资源 🌰读取所有内容：遍历操作\necho \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //读取目录 $r = opendir(\u0026#39;uploads\u0026#39;); var_dump($r); //读取资源 , 循环遍历输出 while($file = readdir($r)){ echo $file,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } //关闭资源 closedir($r); 🍎 resource(3) of type (stream) . .. Chrysanthemum.jpg file image image201720172017201708081414OXEF.jpg Koala.jpg Lighthouse.jpg Penguins.jpg 其他目录操作 dirname(一个路径)：得到的是路径的上一层路径 realpath(真实路径)：得到真实路径(绝对路径),php5中如果是文件则返回 false is_dir()：判断指定路径是否是一个目录，如果是文件那么得到的结果是false scandir()：封装版的 opendir\\readdir\\closedir，获取一个指定路径下的所有文件信息，以数组形式返回 //目录操作 echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //其他函数 $dir1 = \u0026#39;C:\\fastwork\\Apache24\\htdocs\u0026#39;; $dir2 = \u0026#39;C:\\fastwork\\Apache24\\NOTICE.txt\u0026#39;; var_dump(dirname($dir1),dirname($dir2)); var_dump(realpath($dir1),realpath($dir2)); var_dump(is_dir($dir1),is_dir($dir2)); //遍历文件夹 var_dump(scandir(\u0026#39;uploads\u0026#39;)); 🍎 //dirname string(20) \u0026#34;C:\\fastwork\\Apache24\u0026#34; string(20) \u0026#34;C:\\fastwork\\Apache24\u0026#34; //realpath string(27) \u0026#34;C:\\fastwork\\Apache24\\htdocs\u0026#34; string(31) \u0026#34;C:\\fastwork\\Apache24\\NOTICE.txt\u0026#34; //is_dir bool(true) bool(false) //scandir array(9) { [0]=\u0026gt; string(1) \u0026#34;.\u0026#34; [1]=\u0026gt; string(2) \u0026#34;..\u0026#34; [2]=\u0026gt; string(17) \u0026#34;Chrysanthemum.jpg\u0026#34; [3]=\u0026gt; string(9) \u0026#34;Koala.jpg\u0026#34; [4]=\u0026gt; string(14) \u0026#34;Lighthouse.jpg\u0026#34; [5]=\u0026gt; string(12) \u0026#34;Penguins.jpg\u0026#34; [6]=\u0026gt; string(4) \u0026#34;file\u0026#34; [7]=\u0026gt; string(5) \u0026#34;image\u0026#34; [8]=\u0026gt; string(37) \u0026#34;image201720172017201708081414OXEF.jpg\u0026#34; } 递归遍历目录👉Video 递归遍历目录：指定一个目录的情况下，将其下的所有文件和目录，及其目录内部的所有内容都输出出来。\n递归算法：将大问题切成相似的小问题（最小单位），然后可以调用解决大问题的方法来解决小问题。\n递归遍历目录的思维逻辑 设计一个能够遍历一层文件的函数 找到递归点 ：遍历得到的文件是目录，应该调用当前函数（调用自己） 找到递归出口 ：遍历完这个文件夹之后，发现没有任何子文件夹（函数不再调用自己）：自带递归出口 如何显示层级关系？函数第一次运行遍历的结果是最外层目录，内部调用一次说明进入一个子目录，子目录再调用一次函数进行孙子目录…如果能够在第一次调用的时候给个标记，然后在进入的时候，通过标记的变化来识别层级关系，就可以达到目的：该标记还能代表层次关系：缩进 //递归遍历文件夹 echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //定义路径 $dir = \u0026#39;uploads\u0026#39;; /* * 创建函数：能够访问指定路径下的所有文件，且判断出目录还是文件 * @param1 string $dir，指定路径 * @param2 int $level = 0，层级，默认顶层 */ function my_scandir($dir,$level = 0){ //保证文件安全：如果不是路径没有必要往下 if(!is_dir($dir))die($dir . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;); //读取全部路径信息，遍历输出 $files = scandir($dir); foreach($files as $file){ //$file就是一个个文件名 //根据层级来实现缩进：str_repeat() echo str_repeat(\u0026#34;\u0026amp;nbsp;\u0026amp;nbsp;\u0026#34;,$level),$file . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; //排除.和.. if($file == \u0026#39;.\u0026#39; || $file == \u0026#39;..\u0026#39;) continue; //构造路径 $file_dir = $dir . \u0026#39;/\u0026#39; . $file; //echo $file_dir . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; //判断路径 if(is_dir($file_dir)){ //递归点 //$level+1 进入子层级 my_scandir($file_dir,$level+1); } } } //测试 my_scandir($dir); 🍎 . .. Chrysanthemum.jpg Koala.jpg Lighthouse.jpg Penguins.jpg file . .. 01.php son . .. son_readme.txt image . .. 没图啊.txt image201720172017201708081414OXEF.jpg 文件操作 PHP5常见文件操作函数 file_get_contents(文件路径)：获取指定文件的所有内容，如果路径不存在最好做安全处理 //🌈读取内容,类似 Linux 的 cat 命令 echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; $file = \u0026#39;temp.php\u0026#39;; $content = @file_get_contents($file); var_dump($content); 🍎 string(230) \u0026#34;首页 - HTML 教程 - CSS 教程 - JavaScript 教程 - PHP 教程\u0026#39;; ?\u0026gt;\u0026#34; file_put_contents(文件路径，内容)：将指定内容写入到指定文件内：如果当前路径下不存在指定的文件，函数会自动创建（如果路径不存在，不会创建路径） //写入内容,覆盖掉之前的内容... $file = \u0026#39;temp.php\u0026#39;; $text = \u0026#39;hello world\u0026#39;; $res = file_put_contents($file,$text); var_dump($res);// int(11) PHP4常见文件操作函数 PHP4中是将文件操作用资源形式处理：不论是读还是写都依赖资源指针：文件内容中指针所在位置。\nfopen(文件路径，打开模式)：打开一个文件资源，限定打开模式 fread(资源，长度)：从打开的资源中读取指定长度的内容（字节） fwrite(资源，内容)：向打开的资源中写入指定的内容 fclose(资源)：关闭资源 echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; $file = \u0026#39;temp.php\u0026#39;; //打开文件资源：确定操作模式 $f = fopen($file,\u0026#39;r\u0026#39;); var_dump($f); //读取内容 //$string = fread($f,10); $string = fread($f,filesize($file)); echo $string; //写操作对比 $f1 = fopen(\u0026#39;temp.php\u0026#39;,\u0026#39;w\u0026#39;); //写入内容 fwrite($f1,\u0026#39;hello\u0026#39;); //关闭资源 fclose($f); fclose($f1); 🍎 resource(3) of type (stream) aaaaaaaaaaaaaa hello world end 其他文件操作函数 is_file()：判断文件是否正确（不识别路径） filesize()：获取文件大小 file_exists()：判断文件是否存在（识别路径） unLink()：取消文件名字与磁盘地址的连接（删除文件） filemtime()：获取文件最后一次修改的时间 fseek()：设定fopen打开的文件的指针位置 fgetc()：一次获取一个字符 fgets()：一次获取一个字符串（默认行） file()：读取整个文件，类似 file_get_contents，区别是按行读取，返回一个数组 文件下载 文件下载：从服务器将文件通过HTTP协议传输到浏览器，浏览器不解析保存成相应的文件。 提供下载方式可以使用 HTML 中的 a 标签：\u0026lt;a href=\u0026quot;互联网绝对文件路径\u0026quot;\u0026gt;点击下载\u0026lt;/a\u0026gt;\n🔥 缺点\na标签能够让浏览器自动下载的内容有限：浏览器是发现如果解析不了才会启用下载 a标签下载的文件存储路径会需要通过href属性写出来，这样会暴露服务器存储数据的位置 （不安全） PHP下载：读取文件内容，以文件流的形式传递给浏览器：在响应头中告知浏览器不要解析，激活下载框实现下载。\n指定浏览器解析字符集 设定响应头 设定文件返回类型：image/jpg||application/octem-stream 设定返回文件计算方式：Accept-ranges：bytes 设定下载提示：Content-disposition:attachment;filename=文件名字 设定文件大小：Accept-length：文件大小（字节） 读取文件 输出文件 方案1：如果文件较小，可以使用PHP5的文件函数操作：file_get_contents 方案2：文件比较大（网络不好），可以使用PHP4 的文件操作方式：一次读一点 🌰PHP文件下载\n//设定解析字符集 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); $file = \u0026#39;故事.mp3\u0026#39;; //设定下载响应头 header(\u0026#39;Content-type:image/jpg||application/octem-stream\u0026#39;);\t//以文件流形式传输数据给浏览器 header(\u0026#39;Accept-ranges:bytes\u0026#39;);\t//以字节方式计算 header(\u0026#39;Content-disposition:attachment;filename=\u0026#39; . $file);\t//附件下载，指定命名 header(\u0026#39;Accept-length:\u0026#39; . filesize($file)); //如果文件的名字是从文件夹读取出来，而且存在中文，那么如果直接使用名字作为下载名字会出现乱码 //出现这种情况需要进行字符集转码：从GBK转成UTF-8：$file = iconv(\u0026#39;GBK\u0026#39;,\u0026#39;UTF-8\u0026#39;,$file); //输出文件 //php5：小文件 //🐖当文件过大时,会读很长时间,导致安全问题 //echo file_get_contents($file); //php4：大文件 $f = @fopen($file,\u0026#39;r\u0026#39;) or die(); //方案1：直接读，然后输出 //🐵一次读取 1024字节(B) while($row = fread($f,1024)){ echo $row; } //方案2：判定是否可读，然后再读 /* while(!feof($f)){ echo fread($f,1024); }*/ //关闭资源 fclose($f); ","date":"2019-10-25T15:04:22+08:00","permalink":"/2019/10/25/PHP-14.%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C/","title":"14.PHP文件操作"},{"content":"新闻管理项目 bilibili👉 https://www.bilibili.com/video/av15525720/?p=26 项目源码 👉 PHP新闻管理项目源码.zip\n整体流程 添加新闻表单 接收数据 数据验证：合法性验证。标题不能为空，内容不能为空 提示用户，同时让用户重新再来（回到新增表单页面） 数据入库 公共文件 //1. 中文处理 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); //连接认证:本地 \u0026amp; 阿里云 $conn=mysqli_connect(\u0026#39;127.0.0.1:3306\u0026#39;,\u0026#39;root\u0026#39;,\u0026#39;123456\u0026#39;) or die(\u0026#34;连接失败 \u0026#34;.mysqli_connect_error()); //封装MySQL语法错误检查函数并执行 /* * @param $link 数据库连接对象 * @param string $sql，要执行的SQL指令 * @return $res，正确执行完返回的结果，如果SQL错误，直接终止 */ function execSQL($link,$sql){ //执行SQL $res = mysqli_query($link,$sql); //处理可能存在的错误 if(!$res){ echo \u0026#39;SQL执行错误，错误编号为：\u0026#39; . mysqli_errno($link) . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo \u0026#39;SQL执行错误，错误信息为：\u0026#39; . mysqli_error($link) . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; //终止错误继续执行 exit; } //返回结果 return $res; } //字符集处理 execSQL($conn,\u0026#39;set names utf8\u0026#39;); //选择数据库 execSQL($conn,\u0026#39;use News\u0026#39;); //mysqli_close($conn); 添加内容 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //1. 接收数据 //接受用户提交数据 var_dump($_POST); $title=isset($_POST[\u0026#39;title\u0026#39;])?trim($_POST[\u0026#39;title\u0026#39;]):\u0026#39;\u0026#39;;\t//title 是字符串(重要) $isTop=isset($_POST[\u0026#39;isTop\u0026#39;])?(integer)$_POST[\u0026#39;isTop\u0026#39;]:2;\t//数字需求,而且不重要 $publisher=isset($_POST[\u0026#39;publisher\u0026#39;])?trim($_POST[\u0026#39;publisher\u0026#39;]):\u0026#39;佚名\u0026#39;; $content=isset($_POST[\u0026#39;content\u0026#39;])?trim($_POST[\u0026#39;content\u0026#39;]):\u0026#39;\u0026#39;;//trim通常针对字符串 //echo $title; //2. 数据验证：合法性验证。标题不能为空，内容不能为空 //3. 提示用户，同时让用户重新再来（回到新增表单页面） if(empty($title)||empty($content)){ //提示同时回到提交页 //🐖 header 之前不能有 echo 等输出,但不会影响后面的代码执行 //🐖 url 开发中应使用绝对路径 header(\u0026#39;Refresh:3;url=news_add.html\u0026#39;);\t//header前不能有输出，header：refresh不会阻止脚本执行 //标题和内容至少有一个为空 //echo \u0026#39;标题和内容都不能为空！\u0026#39;; //阻止脚本继续执行 exit(\u0026#39;标题和内容都不能为空！\u0026#39;); } //4. 数据入库 include_once \u0026#39;my_public.php\u0026#39;; $time=time(); $sql=\u0026#34;insert into n_news values(null,\u0026#39;{$title}\u0026#39;,$isTop,\u0026#39;{$content}\u0026#39;,\u0026#39;{$publisher}\u0026#39;,$time)\u0026#34;; $insert_id = execSQL($conn,$sql); //4. 提示用户操作结果，并跳转到列表页（跳转到详情页） //成功操作,提示同时去到列表页 header(\u0026#39;Refresh:3;url=my_news.php\u0026#39;); echo $title . \u0026#39; 增加成功！\u0026#39;; 新闻列表 //1. 获取所有新闻内容并显示 //操作数据库获取数据 include_once \u0026#39;my_public.php\u0026#39;; //组织SQL获取所有新闻信息 $sql = \u0026#34;select * from n_news\u0026#34;; //执行 $res = execSQL($conn,$sql);\t//拿到结果集 //2. 从结果集中取出所有的记录：一次取一条，一条一个数组；然后将所有的记录放到一个数组中：形成一个典型的二维数组 //循环遍历所有结果 $news = array();\t//保存取出的记录（数组） //方案1：获取结果集中记录数，然后for循环 //🐖 mysqli_num_fields 会造成指针下移,导致只能取出一半的结果 /*$num = mysqli_num_fields($res); echo $num.\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; for($i = 0;$i \u0026lt; $num;$i++){ //🐖 $news[] 自增 $news[] = mysqli_fetch_assoc($res); } */ //方案2：利用while循环，每次取到数据之后判断保存数据的结果，只要结果不为false，那么一直取 while($row = mysqli_fetch_assoc($res)){ $news[] = $row; } //echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //print_r($news); //3. 一个已经做好的HTML模板能够显示数据：前端 //包含显示模板（HTML） include_once \u0026#39;news.html\u0026#39;; //4. PHP想办法将数据在HTML中显示（二者结合）：PHP包含HTML //修改 news.html 用于显示的 news.html\n\u0026lt;?php foreach($news as $k =\u0026gt; $new):?\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;\u0026lt;?php echo $k + 1;?\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;?php echo $new[\u0026#39;title\u0026#39;];?\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;?php echo date(\u0026#39;Y年m月d日 H时i分s秒\u0026#39;,$new[\u0026#39;pub_time\u0026#39;]);?\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;?php echo $new[\u0026#39;publiser\u0026#39;]?\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;\u0026lt;?php echo $new[\u0026#39;isTop\u0026#39;] == 1 ? \u0026#39;是\u0026#39; : \u0026#39;否\u0026#39;;?\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td style=\u0026#34;text-align:center;\u0026#34;\u0026gt; \u0026lt;a href=\u0026#34;news_edit.php?id=\u0026lt;?php echo $new[\u0026#39;id\u0026#39;];?\u0026gt;\u0026#34;\u0026gt;修改\u0026lt;/a\u0026gt;\u0026amp;nbsp;| \u0026lt;a href=\u0026#34;news_delete.php?id=\u0026lt;?php echo $new[\u0026#39;id\u0026#39;];?\u0026gt;\u0026#34; onclick=\u0026#34;return confirm(\u0026#39;是否确定删除当前新闻？\u0026#39;);\u0026#34;\u0026gt;删除\u0026lt;/a\u0026gt;\u0026amp;nbsp; \u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;?php endforeach;?\u0026gt; 注意细节：\n数据的显示要通过循环遍历输出：需要明确PHP是如何保存数据 数据的格式需要按照需求来显示：列表、时间、是否置顶 删除新闻 删除按钮\n\u0026lt;a href=\u0026#34;my_delete.php?id=\u0026lt;?php echo $new[\u0026#39;id\u0026#39;];?\u0026gt;\u0026#34; onclick=\u0026#34;return confirm(\u0026#39;是否确定删除当前新闻？\u0026#39;);\u0026#34;\u0026gt;删除\u0026lt;/a\u0026gt;\u0026amp;nbsp; //新闻管理：删除指定新闻 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); //接收要删除的新闻ID $id = isset($_GET[\u0026#39;id\u0026#39;]) ? (integer)$_GET[\u0026#39;id\u0026#39;] : 0;\t//0不会存在 if($id == 0) { header(\u0026#39;Refresh:3;url=news.php\u0026#39;); echo \u0026#39;当前要删除的新闻不存在！\u0026#39;; exit; } //删除数据 include_once \u0026#39;my_public.php\u0026#39;; //组织SQL并执行 execSQL($conn,\u0026#39;delete from n_news where id = \u0026#39; . $id); //删除后更新页面 //方案一 header(\u0026#39;Refresh:0;url=my_news.php\u0026#39;); //echo \u0026#39;当前新闻删除成功！\u0026#39;; //方案二 🌈效果更好,页面不会重新刷新 /* $res = execSQL($conn,\u0026#34;select * from n_news\u0026#34;);\t//拿到结果集 $news = array(); while($row = mysqli_fetch_assoc($res)){ $news[] = $row; } include_once \u0026#39;news.html\u0026#39;; */ 修改新闻 \u0026lt;!-- 🐖🐖别忘了传ID --\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#34;id\u0026#34; value=\u0026#34;\u0026lt;?php echo $update[\u0026#39;id\u0026#39;];?\u0026gt;\u0026#34;/\u0026gt; \u0026lt;!-- 单选 --\u0026gt; \u0026lt;td width=\u0026#34;10%\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;isTop\u0026#34; \u0026lt;?php echo isset($update[\u0026#39;isTop\u0026#39;])\u0026amp;\u0026amp;$update[\u0026#39;isTop\u0026#39;]==1?\u0026#39;checked=\u0026#34;checked\u0026#34;\u0026#39;:\u0026#34;\u0026#34;;?\u0026gt; value=\u0026#34;1\u0026#34;/\u0026gt;是 \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;isTop\u0026#34; \u0026lt;?php echo isset($update[\u0026#39;isTop\u0026#39;])\u0026amp;\u0026amp;$update[\u0026#39;isTop\u0026#39;]==0?\u0026#39;checked=\u0026#34;checked\u0026#34;\u0026#39;:\u0026#34;\u0026#34;;?\u0026gt; value=\u0026#34;0\u0026#34;/\u0026gt;否 \u0026lt;/td\u0026gt; 找到指定id的对象,跳转到编辑页(edit.html)进行修改 , ==这里不能用 add.html 取代==\n//新闻管理：修改指定新闻-找到指定id的对象,跳转到编辑页 //🐖编辑页不同于添加页面...不太灵活 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); $id=isset($_GET[\u0026#39;id\u0026#39;])==true?$_GET[\u0026#39;id\u0026#39;]:-1; if($id==-1){ header(\u0026#39;Refresh:3;url=my_news.php\u0026#39;); echo \u0026#39;当前要修改的新闻不存在！\u0026#39;; exit; } //获取当前ID对应的新闻信息 include_once \u0026#39;my_public.php\u0026#39;; $res=execSQL($conn,\u0026#34;select * from n_news where id = \u0026#34;.$id); $update= mysqli_fetch_assoc($res); //print_r($update); include_once \u0026#39;news_edit.html\u0026#39;; mysqli_fetch_assoc() 从结果集中取出一行作为关联数组 👉 https://www.runoob.com/php/func-mysqli-fetch-assoc.html\n写入数据库:\n//新闻管理：修改指定新闻 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); //接收数据并验证 $id = isset($_POST[\u0026#39;id\u0026#39;]) ? intval($_POST[\u0026#39;id\u0026#39;]) : 0; $title = isset($_POST[\u0026#39;title\u0026#39;]) ? trim($_POST[\u0026#39;title\u0026#39;]) : \u0026#39;\u0026#39;;\t//title是字符串（重要） $isTop = isset($_POST[\u0026#39;isTop\u0026#39;]) ? intval($_POST[\u0026#39;isTop\u0026#39;]) : 0 ;\t//数字需求，而且不重要 $publiser = isset($_POST[\u0026#39;publisher\u0026#39;]) ? trim($_POST[\u0026#39;publisher\u0026#39;]) : \u0026#39;\u0026#39;; $content = isset($_POST[\u0026#39;content\u0026#39;]) ? trim($_POST[\u0026#39;content\u0026#39;]) : \u0026#39;\u0026#39;; //数据验证：标题和内容均不能为空 if(empty($title) || empty($content)){ //提示同时回到提交页 header(\u0026#39;Refresh:3;url=my_news.php\u0026#39;);\t//header前不能有输出，header：refresh不会阻止脚本执行 //标题和内容至少有一个为空//阻止脚本继续执行 exit(\u0026#39;标题和内容都不能为空！\u0026#39;); } //组织SQL更新到数据库 include_once \u0026#39;my_public.php\u0026#39;; $sql = \u0026#34;update n_news set title = \u0026#39;{$title}\u0026#39;,isTop = {$isTop} ,publiser=\u0026#39;{$publiser}\u0026#39; ,content=\u0026#39;{$content}\u0026#39; where id = {$id}\u0026#34;; $res = execSQL($conn,$sql); //提示成功 header(\u0026#39;Refresh:0;url=my_news.php\u0026#39;); //echo \u0026#39;当前新闻更新成功！\u0026#39;; 搜索新闻 //新闻管理：删除指定新闻 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); //获取关键字,进行模糊查询 $keywords=$_POST[\u0026#39;title\u0026#39;]; include_once \u0026#39;my_public.php\u0026#39;; //组织SQL并执行 $res=execSQL($conn,\u0026#34;select * from n_news where title like \u0026#39;%$keywords%\u0026#39; \u0026#34;); $news=array(); while($row=mysqli_fetch_assoc($res)){ $news[]= $row ; } include_once \u0026#39;news.html\u0026#39;; 新闻展示页(news.html):\n\u0026lt;form class=\u0026#34;form-inline definewidth m20\u0026#34; action=\u0026#34;my_search.php\u0026#34; method=\u0026#34;post\u0026#34;\u0026gt; 标题：\u0026lt;input name=\u0026#34;title\u0026#34; class=\u0026#34;abc input-default\u0026#34; value=\u0026#34;\u0026lt;?php echo isset($keywords)?$keywords:\u0026#34;\u0026#34;;?\u0026gt;\u0026#34;\u0026gt;\u0026lt;/input\u0026gt;\u0026amp;nbsp;\u0026amp;nbsp; \u0026lt;button type=\u0026#34;submit\u0026#34; class=\u0026#34;btn btn-primary\u0026#34; \u0026gt;查询\u0026lt;/button\u0026gt;\u0026amp;nbsp;\u0026amp;nbsp; \u0026lt;!-- \u0026lt;button type=\u0026#34;button\u0026#34; class=\u0026#34;btn btn-primary\u0026#34; onclick=\u0026#34;location=\u0026#39;news_add.html\u0026#39;\u0026#34;\u0026gt;添加\u0026lt;/button\u0026gt;\u0026amp;nbsp;\u0026amp;nbsp; --\u0026gt; \u0026lt;button type=\u0026#34;button\u0026#34; class=\u0026#34;btn btn-primary\u0026#34; onclick=\u0026#34;javascript:window.open(\u0026#39;news_add.html\u0026#39;)\u0026#34;\u0026gt;添加\u0026lt;/button\u0026gt;\u0026amp;nbsp;\u0026amp;nbsp; \u0026lt;/form\u0026gt; ","date":"2019-10-24T16:59:15+08:00","permalink":"/2019/10/24/PHP-12.%E6%96%B0%E9%97%BB%E7%AE%A1%E7%90%86/","title":"12.新闻管理项目"},{"content":"HTTP HTTP协议 HTTP协议概念 HTTP协议，即超文本传输协议(Hypertext transfer protocol)。是一种详细规定了浏览器和万维网(WWW = World Wide Web)服务器之间互相通信的规则，通过因特网传送万维网文档的数据传送协议。 HTTP协议是用于从WWW服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效，使网络传输减少。它不仅保证计算机正确快速地传输超文本文档，还确定传输文档中的哪一部分，以及哪部分内容首先显示(如文本先于图形)等。\nHTTP协议特点 客户/服务器模式：客户端（浏览器）/服务端 简单快速：客户向服务器请求服务时，只需传送请求方法和路径。由于HTTP协议简单，使得HTTP服务器的程序规模小，因而通信速度很快。 灵活：HTTP允许传输任意类型的数据对象（MIME类型） 无连接：无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求，并收到客户的应答后，即断开连接。采用这种方式可以节省传输时间。 无状态：HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息，则它必须重传，这样可能导致每次连接传送的数据量增大。另一方面，在服务器不需要先前信息时它的应答就较快。 HTTP协议分类 http请求协议：浏览器向服务器发起请求的时候需要遵循的协议 http响应协议：服务器向浏览器发起响应的时候需要遵循的协议 HTTP请求 请求行 形式：请求方式 资源路径 协议版本号 GET /index.php HTTP/1.1 最早的时候HTTP协议有过1.0，请求行独占一行（第一行）\n请求头 请求头就是各项协议内容：具体的协议内容不会每次都使用全部\nHost：请求的主机地址（必须） Accept：当前请求能够接收服务器返回的类型（MIME类型） Accept-Language：接收的语言 User-Agent：客户浏览器所在电脑的一些信息 请求头不固定数量，每个请求协议也是独占一行，最后会有一行空行（用来区分请求头和请求体）\n请求体 请求数据：POST请求会有请求体。GET请求所有的数据都是跟在URL之后，会在请求行中的资源路径上体现。\n基本格式：资源名字=资源值\u0026amp;资源名字=资源值…\nHTTP响应 响应行 形式（独占一行）：协议版本号 + 状态码 + 状态消息 🌴 HTTP/1.1 200 ok 200 ok： 成功 403 Forbidden： 没权限访问 404 Not Found： 未找到页面 500 Server Internal Error： 服务器内部错误 响应头 具体协议内容 1）时间：Wed, 16 Sep 2017 11:43:33 GMT 2）服务器：Server: Apache/2.2.22 (Win32) PHP/5.3.13 3）内容长度：Content-Length: 1571，数据具体的字节数（响应体） 4）内容类型：Content-Type: text/html：告诉浏览器对应的数据格式\n列举了几个常见的响应头，并不是全部：响应头一个占一行，最后一行空行（区分响应头和响应体）\n响应体 实际服务器响应给浏览器的内容 HTTP状态码 🌈 W3C https://www.w3school.com.cn/tags/html_ref_httpmessages.asp\n状态码200：成功 状态码403：forbidden，拒绝访问（没有权限） 状态码404：NOT FOUND，找不到 状态码500：服务器问题 1xx 信息 🌈 服务器正在处理过程中\n消息: 描述: 100 Continue 服务器仅接收到部分请求，但是一旦服务器并没有拒绝该请求，客户端应该继续发送其余的请求。 101 Switching Protocols 服务器转换协议：服务器将遵从客户的请求转换到另外一种协议。 2xx 成功 🌈 服务器正常且正确处理\n消息: 描述: 200 OK 请求成功（其后是对GET和POST请求的应答文档。） 201 Created 请求被创建完成，同时新的资源被创建。 202 Accepted 供处理的请求已被接受，但是处理未完成。 203 Non-authoritative Information 文档已经正常地返回，但一些应答头可能不正确，因为使用的是文档的拷贝。 204 No Content 没有新文档。浏览器应该继续显示原来的文档。如果用户定期地刷新页面，而Servlet可以确定用户文档足够新，这个状态代码是很有用的。 205 Reset Content 没有新文档。但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容。 206 Partial Content 客户发送了一个带有Range头的GET请求，服务器完成了它。 3xx 重定向 🌈 请求的目标已经转移或者需要更新\n消息: 描述: 300 Multiple Choices 多重选择。链接列表。用户可以选择某链接到达目的地。最多允许五个地址。 301 Moved Permanently 所请求的页面已经转移至新的url。 302 Found 所请求的页面已经临时转移至新的url。 303 See Other 所请求的页面可在别的url下被找到。 304 Not Modified 未按预期修改文档。客户端有缓冲的文档并发出了一个条件性的请求（一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档）。服务器告诉客户，原来缓冲的文档还可以继续使用。 305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取。 306 Unused 此代码被用于前一版本。目前已不再使用，但是代码依然被保留。 307 Temporary Redirect 被请求的页面已经临时移至新的url。 4xx 客户端错误 消息: 描述: 400 Bad Request 服务器未能理解请求。 401 Unauthorized 被请求的页面需要用户名和密码。 402 Payment Required 此代码尚无法使用。 403 Forbidden 对被请求页面的访问被禁止。 404 Not Found 服务器无法找到被请求的页面。 405 Method Not Allowed 请求中指定的方法不被允许。 406 Not Acceptable 服务器生成的响应无法被客户端所接受。 407 Proxy Authentication Required 用户必须首先使用代理服务器进行验证，这样请求才会被处理。 408 Request Timeout 请求超出了服务器的等待时间。 409 Conflict 由于冲突，请求无法被完成。 410 Gone 被请求的页面不可用。 411 Length Required \u0026quot;Content-Length\u0026quot; 未被定义。如果无此内容，服务器不会接受请求。 412 Precondition Failed 请求中的前提条件被服务器评估为失败。 413 Request Entity Too Large 由于所请求的实体的太大，服务器不会接受请求。 414 Request-url Too Long 由于url太长，服务器不会接受请求。当post请求被转换为带有很长的查询信息的get请求时，就会发生这种情况。 415 Unsupported Media Type 由于媒介类型不被支持，服务器不会接受请求。 416\u0026nbsp; 服务器不能满足客户在请求中指定的Range头。 417 Expectation Failed \u0026nbsp; 5xx 服务器错误 消息: 描述: 500\u0026nbsp;Internal Server Error 请求未完成。服务器遇到不可预知的情况。 501 Not Implemented 请求未完成。服务器不支持所请求的功能。 502 Bad Gateway 请求未完成。服务器从上游服务器收到一个无效的响应。 503 Service Unavailable 请求未完成。服务器临时过载或当机。 504 Gateway Timeout 网关超时。 505 HTTP Version Not Supported 服务器不支持请求中指明的HTTP协议版本。 HTTP响应设置及使用 PHP中针对HTTP协议（响应）进行了底层设计，可以通过函数header来实现修改HTTP响应（响应头）\n注意事项：\nHeader可以设计HTTP响应，因为HTTP协议特点是：响应行，响应头（空行结尾），响应体。认为通过header设计响应头的时候，不应该有任何内容输出，所以一旦产生内容输出（哪怕一个空格），系统都会认为响应头已经结束而响应体开始了，所有如果先输出内容后设置响应头（header使用），理论设置无效； 在PHP5以后，增加程序缓存内容：允许服务器脚本在输出内容的时候，不直接返回浏览器而是现在服务器端使用程序缓存保留（php.ini中使用output_buffering），有了该内容之后，在程序缓存内会自动调整响应头和响应体（允许响应头在已经输出的内容之后再设置），但是此时会报错（警告）。 总结：header设置响应体之前不要有任何输出 Location Location：重定向，立即跳转（响应体不用解析） 浏览器在解析服务器响应的时候：先判定响应行，继续响应头，最后响应体：location是在响应头中，所以浏览器一旦见到该协议项，不再向下解析。\n//content-type设置 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); echo \u0026#39;中国你好！\u0026#39;; //location：立即重定向 header(\u0026#39;Location:index.php\u0026#39;); echo \u0026#39;世界你好！\u0026#39;; 效果:两个 echo 都不会输出,直接跳转到 index.php 页面\nRefresh Refresh：重定向，定时跳转（响应体会解析） 延时重定向：浏览器会根据具体时间延迟后在访问指定跳转链接：浏览器在准备跳转访问之前，会继续解析HTTP协议（响应头和响应体）\n//content-type设置 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); echo \u0026#39;中国你好！\u0026#39;; //refresh：延时重定向 header(\u0026#39;Refresh:3;url=index.php\u0026#39;); echo \u0026#39;世界你好！\u0026#39;; 效果:先输出两个 echo , 再跳转页面\nContent-type Content-type：内容类型，MIME类型 通过内容告知（MIME类型），浏览器正确解析内容\nContent-disposition Content-disposition：内容类型，MIME类型扩展，激活浏览器文件下载对话框 浏览器在解析内容的时候，默认是直接解析：那么有时候需要浏览器不解析，当做内容下载成文件\n//Content-disposition header(\u0026#39;Content-disposition:attachment;filename=girl.jpg\u0026#39;); echo \u0026#39;hello world\u0026#39;; 效果: PHP模拟HTTP请求 原理: PHP可以通过模拟HTTP协议发起HTTP请求\nCURL是一个非常强大的开源库，支持很多协议，包括HTTP、FTP、TELNET等，我们使用它来发送HTTP请求。它给我 们带来的好处是可以通过灵活的选项设置不同的HTTP协议参数，并且支持HTTPS。CURL可以根据URL前缀是“HTTP” 还是“HTTPS”自动选择是否加密发送内容。\n前提条件：HTTP协议的客户端/服务端模式，HTTP协议不局限于一定要浏览器访问\nCURL 扩展库使用 PHP5方案:\n开启CURL扩展 extension=curl\n有的时候PHP版本会出现即使开启了扩展也无法使用扩展的可能：因为CURL找不到对应的dll文件：需要将相关的DLL文件复制一份到 C:\\Windows 下 重启Apache服务\n✨ PHP 7.3.10 已经没有上面这俩个文件,解决方法:\n将 extension=curl 前的分号去掉； 将 php 目录下的 libssh2.dll 拷贝到 apache 安装目录的bin目录下 重启 apache 命令执行:\n建立连接：curl_init()：激活一个CURL连接功能 ,开启会话: $ch = curl_init(); 设置请求选项：curl_setopt()：设定选项 全部选项 👉 https://www.runoob.com/php/func-curl_setopt.html CURLOPT_URL ：连接对象 CURLOPT_RETURNTRANSFER :将服务器执行的结果（响应）以文件流的形式返回给请求界面（PHP脚本） CURLOPT_POST ：是否才有POST方式发起请求（默认请求是GET） CURLOPT_POSTFIELDS ：用来传递POST提交的数据，分为两种方式：字符串（name=abc\u0026amp;password=123）以及数组形式（array(‘name’=\u0026gt;’abc’,…)） CURLOPT_HEADER ：是否得到响应的header信息（响应头），默认不获取 //设置连接选项 curl_setopt($ch,CURLOPT_URL,\u0026#39;localhost/index.php\u0026#39;);\t//连接选项 curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);\t//文件流形式返回数据（不直接显示） curl_setopt($ch,CURLOPT_HEADER,1);\t//是否获取响应头信息 执行请求：curl_exec()：执行选项（与服务器发起请求），得到服务器返回的内容 乱码原因：20select.php中的内容有告知浏览器，但是当前是被服务器脚本23curl_http.php访问的，没有做解析；输出给浏览器之后，需要当前23curl_http.php告知浏览器对应的字符集 关闭连接：curl_close()：关闭资源 curl_close($ch); //如果要使用post curl_setopt($ch,CURLOPT_POST,TRUE); curl_setopt($ch,CURLOPT_POSTFIELDS,array()); //执行 $content = curl_exec($ch); echo $content; //关闭资源 curl_close($ch); 🍎 resource(2) of type (curl) HTTP/1.1 200 OK Date: Fri, 25 Oct 2019 06:56:40 GMT Server: Apache/2.4.41 (Win64) OpenSSL/1.1.1c PHP/7.3.10 X-Powered-By: PHP/7.3.10 Content-Length: 211 Content-Type: text/html; charset=UTF-8 首页 - HTML 教程 - CSS 教程 - JavaScript 教程 - PHP 教程 ","date":"2019-10-24T16:59:15+08:00","permalink":"/2019/10/24/PHP-13.HTTP/","title":"13.PHP使用HTTP协议"},{"content":"Docker 1、简介 Docker 是一个开源的应用容器引擎，基于 Go 语言 并遵从 Apache2.0 协议开源 。 Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中，然后发布到任何流行的 Linux 机器上可以实现虚拟化。容器是完全使用沙箱机制，相互之间不会有任何接口,更重要的是容器性能开销极低。 Docker支持将软件编译成一个镜像；然后在镜像中各种软件做好配置，将镜像发布出去，其他使用者可以直接使用这个镜像。运行中的这个镜像称为容器，容器启动是非常快速的。类似windows里面的ghost操作系统，安装好后什么都有了；Docker支持将软件编译成一个镜像，然后在镜像中各种软件做好配置，将镜像发布出去，其他使用者可以直接使用这镜像；运行中的这个镜像称为容器，容器启动是非常快速的。\n2、核心概念 docker主机(Host)：安装了Docker程序的机器（Docker直接安装在操作系统之上）；\ndocker客户端(Client)：连接docker主机进行操作；\ndocker仓库(Registry)：用来保存各种打包好的软件镜像；DockerHub 提供了庞大的镜像集合供使用。\ndocker镜像(Images)：软件打包好的镜像；放在docker仓库中；\ndocker容器(Container)：镜像启动后的实例称为一个容器；容器是独立运行的一个或一组应用\n使用Docker的步骤：\n安装Docker\n去Docker仓库找到这个软件对应的镜像；\n使用Docker运行这个镜像，这个镜像就会生成一个Docker容器；\n对容器的启动停止就是对软件的启动停止；\n3、安装Docker 安装linux虚拟机 ​\t1. VMWare、VirtualBox（安装）；\n​\t2. 导入虚拟机文件centos7-atguigu.ova；\n​\t3. 双击启动linux虚拟机;使用 root/ 123456登陆\n​\t4. 使用客户端连接linux服务器进行命令操作；\n​\t5. 设置虚拟机网络；\n​\t桥接网络 ==选好网卡== 接入网线；\n​\t6. 设置好网络以后使用命令重启虚拟机的网络\nservice network restart ​\t7. 查看linux的ip地址\nip addr ​\t8. 使用客户端连接linux；\n在linux虚拟机上安装docker 检查内核版本，必须是3.10及以上 uname -r 安装docker yum install docker 输入y确认安装 启动docker [root@localhost ~]# systemctl start docker [root@localhost ~]# docker -v Docker version 1.12.6, build 3e8e77d/1.12.6 开机启动docker [root@localhost ~]# systemctl enable docker Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service. 停止docker systemctl stop docker 4、Docker常用命令\u0026amp;操作 1. 镜像操作 操作 命令 说明 检索 docker search 关键字 eg：docker search redis 我们经常去docker hub上检索镜像的详细信息，如镜像的TAG。 拉取 docker pull 镜像名:tag :tag是可选的，tag表示标签，多为软件的版本，默认是latest 列表 docker images 查看所有本地镜像 删除 docker rmi image-id 删除指定的本地镜像 https://hub.docker.com/\n2. 容器操作 软件镜像（QQ安装程序）\u0026mdash;-运行镜像\u0026mdash;-产生一个容器（正在运行的软件，运行的QQ）；\n安装步骤\n搜索镜像 [root@localhost ~]# docker search tomcat 拉取镜像 [root@localhost ~]# docker pull tomcat 根据镜像启动容器 docker run --name mytomcat -d tomcat:latest 查看运行中的容器 docker ps\n停止运行中的容器 docker stop 容器的id\n查看所有的容器 docker ps -a\n启动容器 docker start 容器id\n删除一个容器 docker rm 容器id\n启动一个做了端口映射的tomcat\n[root@localhost ~]# docker run -d -p 24:8080 tomcat -d：后台运行 -p: 将主机的端口映射到容器的一个端口 主机端口:容器内部的端口 为了演示简单关闭了linux的防火墙 service firewalld status ；查看防火墙状态 service firewalld stop：关闭防火墙 查看容器的日志 docker logs container-name/container-id 更多命令👉 https://docs.docker.com/engine/reference/commandline/docker/\n可以参考每一个镜像的文档\n3. 安装MySQL示例 拉取镜像 docker pull mysql //or docker pull mysql:8.0.18 错误的启动 [root@localhost ~]# docker run --name mysql01 -d mysql 42f09819908bb72dd99ae19e792e0a5d03c48638421fa64cce5f8ba0f40f5846 mysql退出了 [root@localhost ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 42f09819908b mysql \u0026#34;docker-entrypoint.sh\u0026#34; 34 seconds ago Exited (1) 33 seconds ago mysql01 538bde63e500 tomcat \u0026#34;catalina.sh run\u0026#34; About an hour ago Exited (143) About an hour ago compassionate_ goldstine c4f1ac60b3fc tomcat \u0026#34;catalina.sh run\u0026#34; About an hour ago Exited (143) About an hour ago lonely_fermi 81ec743a5271 tomcat \u0026#34;catalina.sh run\u0026#34; About an hour ago Exited (143) About an hour ago sick_ramanujan //错误日志 [root@localhost ~]# docker logs 42f09819908b error: database is uninitialized and password option is not specified You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD；这个三个参数必须指定一个 正确的启动(没做端口映射) [root@localhost ~]# docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql b874c56bec49fb43024b3805ab51e9097da779f2f572c22c695305dedd684c5f [root@localhost ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b874c56bec49 mysql \u0026#34;docker-entrypoint.sh\u0026#34; 4 seconds ago Up 3 seconds 3306/tcp mysql01 做了端口映射(🌈用这个) 🐖如果要使用docker外部的my.cnf(这货有两个,系统一个,docker内一个)则是用如下启动方式:\n1.将配置文件移动到新建目录 [root@ali /]# mv /etc/my.cnf /opt/mysql/my.cnf mv: cannot move ‘/etc/my.cnf’ to ‘/opt/mysql/my.cnf’: No such file or directory [root@ali /]# cd /opt [root@ali opt]# ls [root@ali opt]# mkdir mysql [root@ali opt]# mv /etc/my.cnf /opt/mysql/my.cnf [root@ali opt]# cd mysql [root@ali mysql]# ls my.cnf 2. -v 后面的参数不同MySQL版本会有差异 ; tag为版本,latest、8.0.17 ... 😭没成功... docker run -p 3306:3306 --name mysql01 -v /opt/mysql/my.cnf:/etc/mysql/my.cnf -e MYSQL_ROOT_PASSWORD=ma1bao2 -d mysql 简单点:\n[root@localhost ~]# docker run -p 3306:3306 --name mysql01 -e MYSQL_ROOT_PASSWORD=ma1bao2 -d mysql ad10e4bc5c6a0f61cbad43898de71d366117d120e39db651844c0e73863b9434 [root@localhost ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ad10e4bc5c6a mysql \u0026#34;docker-entrypoint.sh\u0026#34; 4 seconds ago Up 2 seconds 0.0.0.0:3306-\u0026gt;3306/tcp mysql02 几个其他的高级操作 docker run --name mysql01 -v /conf/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql/tag 把主机的/conf/mysql文件夹挂载到 mysqldocker容器的/etc/mysql/conf.d文件夹里面 改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下（/conf/mysql） , tag 为 latest 或其他版本号 docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci 指定mysql的一些配置参数 4. SpringBoot 中配置 MySQL \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-jdbc\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;runtime\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; spring: datasource: username: root password: 123456 url: jdbc:mysql://192.168.15.22:3306/jdbc driver-class-name: com.mysql.jdbc.Driver 5. MySQL在Docker中的使用 官方文档 👉 https://github.com/docker-library/docs/tree/master/mysql/\n# mysql01 为 docker ps -a 查询出来的 NAMES 字段名 docker exec -it mysql01 bash # 🌈推荐使用 CONTAINER ID docker exec -it 2d581cd2584d /bin/bash # 尝试连接mysql,若失败,则查看并启动mysql服务 mysql -u root -p # 启动mysql服务 systemctl start mysqld # 查看mysql服务器的启动情况 ps aux|grep mysql # 连上mysql服务器 mysql -u root -p 🌈 修改一些配置 (🔥 my.cnf 要在容器外做修改) # 先下载 vim 编辑器 [root@ali ~]# yum install vim 如果是 container 内部: 首先执行以下命令登录到container中 docker exec -it \u0026lt;container\u0026gt; bash 然后运行apt-get命令来安装vi apt-get update apt-get install vim # 修改最大连接数和字符集 1.查看当前对大连接数 show global status like \u0026#39;Max_used_connections\u0026#39;; 查看字符集 show variables like \u0026#39;%char%\u0026#39;; 2.加入配置 👉 [More Topics on Deploying MySQL Server with Docker](https://dev.mysql.com/doc/refman/8.0/en/docker-mysql-more-topics.html#docker-persisting-data-configuration) 外部配置 [root@ali /]# vim /etc/my.cnf 内部配置 root@2d581cd2584d:/etc/mysql# vim my.cnf max_connections=512 🌈 MySQL8.0 参考配置\n[mysqld] default-set-server=utf8 # 允许最大连接数 max_connections=100 [client] default-character-set=utf8 [mysql] # 设置mysql客户端默认字符集 default-character-set=utf8 参考博客 https://www.cnblogs.com/wangjunwei/p/11057976.html\n6. PHP连接远程 MySQL8 use mysql; #使用mysql库 ALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED BY \u0026#39;password\u0026#39; PASSWORD EXPIRE NEVER; #修改加密规则 ALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED WITH mysql_native_password BY \u0026#39;ma1bao2\u0026#39;; #更新密码（mysql_native_password模式） update user set host=\u0026#39;%\u0026#39; where user = \u0026#39;root\u0026#39;; #设置root在任意ip可以使用（支持远程连接） FLUSH PRIVILEGES; #刷新权限 quit; #退出控制台 ","date":"2019-10-22T09:16:14+08:00","permalink":"/2019/10/22/Docker%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2/","title":"Docker开发环境部署"},{"content":"Mysql扩展(基于PHP7) PHP 针对 MySQL 数据库操作提供的扩展：允许PHP当做MySQL的一个客户端连接服务器进行操作。\nPDO或mysqli都可以连接mysql，但更推荐使用PDO连接数据库，因为PDO支持12种不同的数据库驱动程序，mysqli只支持mysql，而且PDO性能更高\n;php.ini中的 MySQL 扩展 extension=mysqli 🌈PHP7对比PHP5的MySQL操作上改动很大,但是仍旧有迹可循、有规律:🌰\nPHP5: mysql_query(\u0026#39;use examples\u0026#39;); PHP7: mysqli_query($conn,\u0026#39;use examples\u0026#39;); 可见,7中在 mysql命令后面多了个\u0026#39;i\u0026#39;,并且传参的时候都要传入 $conn 连接对象 , 另外对于 mysqli_num_rows($res) 等增删改查以下的方法,只是把 mysql 改成 mysqli 即可。 🌈MySQL8.0和PHP7.13 兼容性问题\n官方人员解释👉https://mysqlserverteam.com/upgrading-to-mysql-8-0-default-authentication-plugin-considerations/ caching_sha2_password 问题 👉 https://blog.csdn.net/maoxinwen1/article/details/88629313 授权问题https://www.2ita.com/thread-27166-1-1.html\n//连接阿里云CentOS中Docker内的MySQL8.0 $conn2=mysqli_connect(\u0026#39;116.62.155.140:3306\u0026#39;,\u0026#39;root\u0026#39;,\u0026#39;ma1bao2\u0026#39;) or die(\u0026#34;连接失败 \u0026#34;.mysqli_connect_error()); 🍎 Warning: mysqli_connect(): The server requested authentication method unknown to the client [caching_sha2_password] in C:\\fastwork\\Apache24\\htdocs\\init-mysql.php on line 14 解决流程:\n配置 mysql.cnf 配置默认身份验证插件 [mysqld] default_authentication_plugin = mysql_native_password SELECT Host, User, plugin from user; mysql\u0026gt; SELECT Host, User, plugin from user; +-----------+------------------+-----------------------+ | Host | User | plugin | +-----------+------------------+-----------------------+ | % | root | caching_sha2_password | | localhost | mysql.infoschema | caching_sha2_password | | localhost | mysql.session | caching_sha2_password | | localhost | mysql.sys | caching_sha2_password | | localhost | root | mysql_native_password | +-----------+------------------+-----------------------+ 还得改 % , % 表示外部用户连接\nALTER USER \u0026#39;root\u0026#39;@\u0026#39;%\u0026#39; IDENTIFIED WITH mysql_native_password BY \u0026#39;123456\u0026#39;; ALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED WITH mysql_native_password BY \u0026#39;123456\u0026#39;; FLUSH PRIVILEGES; 😭终于成功了\u0026hellip;🍎\n数据插入成功!阿里云数据插入成功! 61 1 🌴 连接服务器中Docker内的MySQL 连库基本操作 1.连接数据库服务器 资源 mysql_connect(服务器地址，用户名，密码) php7改用 mysqli_connect 连接数据库;若仍旧使用 mysql_connect 则报错:\nFatal error: Uncaught Error: Call to undefined function mysql_connect() in C:\\fastwork\\Apache24\\htdocs\\test.php:7 Stack trace: #0 {main} thrown in C:\\fastwork\\Apache24\\htdocs\\test.php on line 7 PHP连接方式:\n面向过程方式 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //主机地址：默认端口3306 $servername = \u0026#34;localhost\u0026#34;; $username = \u0026#34;root\u0026#34;; $password = \u0026#34;123456\u0026#34;; // 创建连接 $conn = mysqli_connect($servername, $username, $password); //或者采用短路运算方式(https://segmentfault.com/a/1190000002949450) 👇 //$conn=mysqli_connect(\u0026#39;127.0.0.1:3306\u0026#39;,\u0026#39;roo2t\u0026#39;,\u0026#39;123456\u0026#39;) or die(\u0026#34;连接失败 \u0026#34;.mysqli_connect_error()); // 检测连接 if (!$conn) { //👉也可以 if (mysqli_connect_error()) die(\u0026#34;连接失败: \u0026#34; ); mysqli_connect_error()); } var_dump($conn); //关闭连接 mysqli_close($conn); 🍎 object(mysqli)#1 (19) { [\u0026#34;affected_rows\u0026#34;]=\u0026gt; int(0) [\u0026#34;client_info\u0026#34;]=\u0026gt; string(79) \u0026#34;mysqlnd 5.0.12-dev - 20150407 - $Id: 7cc7cc96e675f6d72e5cf0f267f48e167c2abb23 $\u0026#34; [\u0026#34;client_version\u0026#34;]=\u0026gt; int(50012) [\u0026#34;connect_errno\u0026#34;]=\u0026gt; int(0) [\u0026#34;connect_error\u0026#34;]=\u0026gt; NULL [\u0026#34;errno\u0026#34;]=\u0026gt; int(0) [\u0026#34;error\u0026#34;]=\u0026gt; string(0) \u0026#34;\u0026#34; [\u0026#34;error_list\u0026#34;]=\u0026gt; array(0) { } [\u0026#34;field_count\u0026#34;]=\u0026gt; int(0) [\u0026#34;host_info\u0026#34;]=\u0026gt; string(20) \u0026#34;localhost via TCP/IP\u0026#34; [\u0026#34;info\u0026#34;]=\u0026gt; NULL [\u0026#34;insert_id\u0026#34;]=\u0026gt; int(0) [\u0026#34;server_info\u0026#34;]=\u0026gt; string(6) \u0026#34;8.0.17\u0026#34; [\u0026#34;server_version\u0026#34;]=\u0026gt; int(80017) [\u0026#34;stat\u0026#34;]=\u0026gt; string(135) \u0026#34;Uptime: 3074 Threads: 2 Questions: 120 Slow queries: 0 Opens: 306 Flush tables: 3 Open tables: 226 Queries per second avg: 0.039\u0026#34; [\u0026#34;sqlstate\u0026#34;]=\u0026gt; string(5) \u0026#34;00000\u0026#34; [\u0026#34;protocol_version\u0026#34;]=\u0026gt; int(10) [\u0026#34;thread_id\u0026#34;]=\u0026gt; int(48) [\u0026#34;warning_count\u0026#34;]=\u0026gt; int(0) } 连接资源默认也是超全局的，任何地方都可以使用该资源进行数据库的其他操作\n使用 PDO 连接 MySQL try { $conn = new PDO(\u0026#34;mysql:host=$servername;\u0026#34;, $username, $password); echo \u0026#34;连接成功\u0026#34;; } catch(PDOException $e) { echo \u0026#34;连接失败 \u0026#34;.$e-\u0026gt;getMessage(); } //关闭连接 $conn = null; 🍎失败情况 连接失败 SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client 实际开发中 : 推荐使用 PDO连接方式 , 因为可以连接更多的数据库 使用函数 parse_ini_file() 解析配置文件 config.ini 来获得数据库连接参数 , 如下所示： //解析config.ini文件 $config = parse_ini_file(realpath(dirname(__FILE__) . \u0026#39;/config/config.ini\u0026#39;)); //对mysqli类进行实例化 $conn = new mysqli($config[\u0026#39;host\u0026#39;], $config[\u0026#39;username\u0026#39;], $config[\u0026#39;password\u0026#39;], $config[\u0026#39;dbname\u0026#39;]); 补充： mysql_connect 默认是对一个服务器只连接一次（如果再次运行，返回的是同一个资源），但是如果有需要连接同一个资源两次，那么可以使用第四个参数：TRUE\n2.设置连接编码 保持客户端与服务器之间的沟通顺畅：同一“语言”（字符集）\n//php7 mysqli_query($conn, \u0026#34;set character set \u0026#39;utf8\u0026#39;\u0026#34;);//读库 mysqli_query($conn,\u0026#34;set names \u0026#39;utf8\u0026#39;\u0026#34;);//写库 //php5 mysql_query(\u0026#34;set character set \u0026#39;utf8\u0026#39;\u0026#34;);//读库 mysql_query(\u0026#34;set names \u0026#39;utf8\u0026#39;\u0026#34;);//写库 🌰 //设定字符集 $res = mysqli_query($conn,\u0026#34;set names \u0026#39;utf8\u0026#39;\u0026#34;);//写库 //或者 $res = mysqli_set_charset($conn,\u0026#39;utf8\u0026#39;); var_dump($res); 🍎 bool(true) 怎么确定要使用何种编码 客户端当前执行脚本的界面是什么字符集，就设定成什么字符集\n3.选定要使用的数据库 $res = mysqli_query($conn,\u0026#39;use examples\u0026#39;); //或者 $res = mysqli_select_db($conn,\u0026#39;examples\u0026#39;); var_dump($res); 🍎 bool(true)//表示连接数据库成功 4.关闭连接 $res = mysqli_close($conn); var_dump($res); 执行增删改操作 环境准备: create database News charset utf8; create table n_news( id int primary key auto_increment, title varchar(50) not null comment \u0026#39;新闻标题\u0026#39;, isTop tinyint not null comment \u0026#39;是否置顶\u0026#39;, content text comment \u0026#39;内容\u0026#39;, publiser varchar(20) not null comment \u0026#39;发布人\u0026#39;, pub_time int not null comment \u0026#39;发布时间\u0026#39; )charset utf8; 做个专门负责数据库初始化的脚本：之后要操作数据库包含该脚本即可 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //连接认证 $conn=mysqli_connect(\u0026#39;127.0.0.1:3306\u0026#39;,\u0026#39;root\u0026#39;,\u0026#39;123456\u0026#39;) or die(\u0026#34;连接失败 \u0026#34;.mysqli_connect_error()); //设定字符集 mysqli_query($conn,\u0026#39;set names utf8\u0026#39;); //选择数据库 mysqli_query($conn,\u0026#39;use News\u0026#39;); insert 插入 //引入初始文件 include_once \u0026#39;init-mysql.php\u0026#39;; //组织SQL指令 $pub_time = time(); $sql = \u0026#34;insert into n_news values(null,\u0026#39;11.PHP的MySQL扩展\u0026#39;,1,\u0026#39;MySQL扩展......\u0026#39;,\u0026#39;Javakam\u0026#39;,{$pub_time})\u0026#34;; //执行SQL指令 if(mysqli_query($conn,$sql)){ //操作成功：通常是返回自增长ID给用户 echo \u0026#39;数据插入成功!\u0026#39;; }else{ //操作失败 echo \u0026#39;数据插入失败！\u0026#39;.mysqli_error($conn);; } delete 删除 //组织SQL指令：实际删除时通常也是根据ID进行删除，但是ID都是传递过来 $sql = \u0026#34;delete from n_news where id = 5\u0026#34;; update 修改 //组织SQL指令：实际更新时通常也是根据ID进行更新，但是ID都是传递过来 $sql = \u0026#34;update n_news set content = \u0026#39;好嗨友\u0026#39; where id = 20\u0026#34;; 判断执行结果 执行成功，结果为true，只代表SQL指令执行成功 执行失败，结果为false，代表SQL指令错误或者操作对象不存在 select 查询 mysql_query(\u0026quot;select ...\u0026quot;) 其他类似查询语句，比如：show语句，desc语句 以上两种情况的总结：凡是执行操作希望拿到数据库返回的数据进行展示的（结果返回：数据结果）\n执行结果的处理：成功为结果集，失败为false 成功返回结果：SQL指令没有错误，但是如果查询结果本身为空返回也是true（结果集是一种资源：转换成bool永远为TRUE）\ninclude_once \u0026#39;init-mysql.php\u0026#39;; //组织SQL指令 $sql = \u0026#34;select * from n_news\u0026#34;; $res=mysqli_query($conn,$sql); var_dump($res); 🍎 object(mysqli_result)#2 (5) { [\u0026#34;current_field\u0026#34;]=\u0026gt; int(0) [\u0026#34;field_count\u0026#34;]=\u0026gt; int(6) [\u0026#34;lengths\u0026#34;]=\u0026gt; NULL [\u0026#34;num_rows\u0026#34;]=\u0026gt; int(18) [\u0026#34;type\u0026#34;]=\u0026gt; int(0) } 获取结果集行数 mysqli_num_rows('查询结果集合')：获取结果集中到底有多少行记录\ninclude_once \u0026#39;init-mysql.php\u0026#39;; //组织SQL指令 $sql = \u0026#34;select * from n_news\u0026#34;; $res=mysqli_query($conn,$sql); //获取结果集数据条数：多少行记录 $rows = mysqli_num_rows($res); echo $rows; 🍎 18 解析结果集 将一种结果集资源（PHP不能直接使用），转换成一种PHP能够解析的数据格式：通过从结果集中（结果集指针：类似数组指针），按照结果集指针所在位置取出对应的一条记录（一行），返回一个数组，同时指针下移…直到指针移出结果集。\nmysqli_fetch_assoc()： 获取关联数组，表的表单名字作为数组下标，元素值作为数组元素值 //解析结果集 $row = mysqli_fetch_assoc($res); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($row); 🍎 Array ( [id] =\u0026gt; 4 [title] =\u0026gt; 11.PHP的MySQL扩展 [isTop] =\u0026gt; 1 [content] =\u0026gt; MySQL扩展...... [publiser] =\u0026gt; Javakam [pub_time] =\u0026gt; 1571639075 ) mysql_fetch_row()： 获取索引数组，只获取数据的值，然后数组的下标从0开始自动索 $row = mysqli_fetch_row($res); 🍎 Array ( [0] =\u0026gt; 4 [1] =\u0026gt; 学校学习L扩展111 [2] =\u0026gt; 1 [3] =\u0026gt; MySQL扩展...... [4] =\u0026gt; Javakam [5] =\u0026gt; 1571639075 ) mysql_fetch_array()：获取关联或者索引数组，但是默认是同时存在：一个记录取两次，形成一组是关联数组，一组是索引数组；\n但是可以通过第二个参数来决定获取的方式：MYSQL_ASSOC只获取关联数组；MYSQL_NUM只获取索引数组；MYSQL_BOTH获取全部两种。 //MYSQL_FETCH_ARRAY $row = mysqli_fetch_array($res); print_r($row); //获取两种 $row = mysqli_fetch_array($res,MYSQLI_BOTH); print_r($row); //获取关联 $row = mysqli_fetch_array($res,MYSQLI_ASSOC); print_r($row); //获取索引 $row = mysqli_fetch_array($res,MYSQLI_NUM); print_r($row); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($row); 🍎 Array ( [0] =\u0026gt; 4 [id] =\u0026gt; 4 [1] =\u0026gt; 学校学习L扩展111 [title] =\u0026gt; 学校学习L扩展111 [2] =\u0026gt; 1 [isTop] =\u0026gt; 1 [3] =\u0026gt; MySQL扩展...... [content] =\u0026gt; MySQL扩展...... [4] =\u0026gt; Javakam [publiser] =\u0026gt; Javakam [5] =\u0026gt; 1571639075 [pub_time] =\u0026gt; 1571639075 ) Array ( [0] =\u0026gt; 6 [id] =\u0026gt; 6 [1] =\u0026gt; 2312321的MySQL扩展 [title] =\u0026gt; 2312321的MySQL扩展 [2] =\u0026gt; 1 [isTop] =\u0026gt; 1 [3] =\u0026gt; MySQL扩展...... [content] =\u0026gt; MySQL扩展...... [4] =\u0026gt; Javakam [publiser] =\u0026gt; Javakam [5] =\u0026gt; 1571639079 [pub_time] =\u0026gt; 1571639079 ) Array ( [id] =\u0026gt; 7 [title] =\u0026gt; 11.PHP的MySQL扩展 [isTop] =\u0026gt; 1 [content] =\u0026gt; MySQL扩展...... [publiser] =\u0026gt; Javakam [pub_time] =\u0026gt; 1571639496 ) Array ( [0] =\u0026gt; 8 [1] =\u0026gt; 打撒撒多`的MySQL扩展 [2] =\u0026gt; 1 [3] =\u0026gt; MySQL扩展...... [4] =\u0026gt; Javakam [5] =\u0026gt; 1571639497 ) Array ( [0] =\u0026gt; 8 [1] =\u0026gt; 打撒撒多`的MySQL扩展 [2] =\u0026gt; 1 [3] =\u0026gt; MySQL扩展...... [4] =\u0026gt; Javakam [5] =\u0026gt; 1571639497 ) 1.2.3 三种方式,都会使指针下移,mysqli_fetch_array 的结果所示。\n通常结果集的操作：一般是获取里面的所有（全部记录）\n其他相关函数 有字段信息 mysqli_num_fields()：获取一个指定结果集中所有的字段数 //查询当前结果集中所有的字段数量 👉 table有几个字段 echo mysqli_num_fields($res); 🍎 6 mysqli_fetch_field_direct()：获取一个指定结果集中指定位置字段的名字 php7中已废弃 mysqli_field_name() 👉 https://dev.mysql.com/doc/apis-php/en/apis-php-function.mysql-field-name.html //查询当前结果集中所有的字段数量 , 默认从索引0开始 print_r(mysqli_fetch_field_direct($res,1)); 🍎 stdClass Object ( [name] =\u0026gt; title [orgname] =\u0026gt; title [table] =\u0026gt; n_news [orgtable] =\u0026gt; n_news [def] =\u0026gt; [db] =\u0026gt; news [catalog] =\u0026gt; def [max_length] =\u0026gt; 27 [length] =\u0026gt; 150 [charsetnr] =\u0026gt; 33 [flags] =\u0026gt; 4097 [type] =\u0026gt; 253 [decimals] =\u0026gt; 0 ) 有关出错信息 mysqli_error(\u0026lsquo;数据库连接对象 $conn\u0026rsquo;)：获取出错对应的提示信息 mysqli_errno(\u0026lsquo;数据库连接对象 $conn\u0026rsquo;)：获取出错对应的错误提示代号 //错误判定 if(!$res){ //代表结果为false echo \u0026#39;SQL指令执行出错，错误编号为：\u0026#39; . mysqli_errno($conn) . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo \u0026#39;SQL指令执行出错，错误信息是：\u0026#39; . mysqli_error($conn) . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; //终止代码执行 exit(); } 🍎\nSQL指令执行出错，错误编号为：1064 SQL指令执行出错，错误信息是：You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \u0026#39;from2 n_news\u0026#39; at line 1 其他函数 mysql_insert_id($conn) : 获取上次插入操作所产生的自增长ID，如果没有自增长ID返回0\n//引入初始文件 include_once \u0026#39;init-mysql.php\u0026#39;; //组织SQL指令 $pub_time = time(); $sql = \u0026#34;insert into n_news values(null,\u0026#39;11.PHP的MySQL扩展\u0026#39;,1,\u0026#39;MySQL扩展......\u0026#39;,\u0026#39;Javakam\u0026#39;,{$pub_time})\u0026#34;; //执行SQL指令 $res=mysqli_query($conn,$sql); if($res){ //操作成功：通常是返回自增长ID给用户 echo \u0026#39;数据插入成功!\u0026#39;; }else{ //代表结果为false echo \u0026#39;SQL指令执行出错，错误编号为：\u0026#39; . mysqli_errno($conn) . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo \u0026#39;SQL指令执行出错，错误信息是：\u0026#39; . mysqli_error($conn) . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; //终止代码执行 exit(); } //获取自增长ID echo mysqli_insert_id($conn); 🍎 每刷新一次浏览器,自增1 数据插入成功!31 ","date":"2019-10-21T09:16:41+08:00","permalink":"/2019/10/21/PHP-11.MySQL%E6%89%A9%E5%B1%95/","title":"11.PHP中MySQL扩展"},{"content":"表单传值和文件上传 表单传值 GET传值 form表单: \u0026lt;form method=”GET”\u0026gt;表单元素\u0026lt;/form\u0026gt; a标签: \u0026lt;a href=”www.itcast.cn/index.php?学科=PHP”\u0026gt; location对象的href属性 \u0026lt;script\u0026gt;location.href=”www.itcast.cn/index.php?data=PHP”\u0026lt;/script\u0026gt; location对象的assign()方法 \u0026lt;script\u0026gt;location.assign(“www.itcast.cn/index.php?data=PHP”)\u0026lt;/script\u0026gt; get和post能够传输的数据格式有区别： get传输简单数据（数值/字符串），post可以提交复杂数据（二进制等).\nPHP接收数据的三种方式 不管是$_GET/$_POST/$_REQUEST，三个都是PHP超全局（没有范围限制）预定义数组，表单元素的“name”属性的值作为数组的下标，而value属性对应的值就是数组的元素值 $_GET方式：接收GET方式提交的数据 $_POST方式：接收POST方式提交的数据 $_REQUEST方式：接收POST或者GET提交的所有数据 1. $_REQUEST所存储数据的内容：将$_POST和$_GET合并存储到一个数组 2. $_REQUEST和$_POST与$_GET的联系：如果GET和POST中有同名数组元素（下标），POST会覆盖GET（PHP中数组元素下标具有唯一性），这个可以在php.ini中进行配置 GET/POST/REQUEST关系 //接收数据 echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; var_dump($_GET);\t//GET方式提交的数据 echo \u0026#39;\u0026lt;hr\u0026gt;\u0026#39;; var_dump($_POST);\t//POST方式提交的数据 echo \u0026#39;\u0026lt;hr\u0026gt;\u0026#39;; var_dump($_REQUEST);\t//两种方式提交的数据 浏览器 http://localhost/test.php?xxx=true 🍎 array(1) { [\u0026#34;xxx\u0026#34;]=\u0026gt; string(4) \u0026#34;true\u0026#34; } ------------------------------------ array(0) { } ------------------------------------ array(1) { [\u0026#34;xxx\u0026#34;]=\u0026gt; string(4) \u0026#34;true\u0026#34; } 证明在REQUEST中POST会覆盖GET\n\u0026lt;!doctype html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;Generator\u0026#34; content=\u0026#34;EditPlus®\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;Author\u0026#34; content=\u0026#34;\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;Keywords\u0026#34; content=\u0026#34;\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;Description\u0026#34; content=\u0026#34;\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Document\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form method=\u0026#34;POST\u0026#34; action=\u0026#34;01receive_data.php?username=getusername\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; name=\u0026#34;username\u0026#34; value=\u0026#34;\u0026#34;/\u0026gt; \u0026lt;input type=\u0026#34;password\u0026#34; value=\u0026#34;\u0026#34;/\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; name=\u0026#34;sub\u0026#34; value=\u0026#34;submit\u0026#34;/\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 如图: PHP处理复选框数据 复选框表单项的命名方式 复选框：通常是将一类内容以同样（同名）的形式传递给后台，数据库存储通常是一个字段存储。复选框的特点：选中才会提交 1、\t在浏览器端，checkbox的name属性的值不论什么都会被浏览器毫无保留的提交 2、\t在PHP中$_POST/$_GET都会对同名name属性进行覆盖 解决方案：浏览器不识别[]（浏览器不认为有特殊性），但是PHP认为 [] 有特殊性：系统自动认为该符号是数组的形式， 所以PHP就会自动的将同名的但是带有 [] 的元素组合到一起形成一个数组\nhobby[]\n\u0026lt;form method=\u0026#34;POST\u0026#34; action=\u0026#34;04checkbox.php\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;checkbox\u0026#34; name=\u0026#34;hobby[]\u0026#34; value=\u0026#34;basketball\u0026#34;\u0026gt;basketball \u0026lt;input type=\u0026#34;checkbox\u0026#34; name=\u0026#34;hobby[]\u0026#34; value=\u0026#34;football\u0026#34;\u0026gt;football \u0026lt;input type=\u0026#34;checkbox\u0026#34; name=\u0026#34;hobby[]\u0026#34; value=\u0026#34;pingpang\u0026#34;\u0026gt;pingpang \u0026lt;input type=\u0026#34;submit\u0026#34; name=\u0026#34;btn\u0026#34; value=\u0026#34;提交\u0026#34;/\u0026gt; \u0026lt;/form\u0026gt; 复选框数据的接收形式 PHP会自动组合同名元素的为数组 其中,action 指向的 php 文件只做结果输出\n//接收checkbox数据 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;);\t//告诉浏览器当前服务器返回的内容是text/html，同时需要浏览器用utf-8字符集解析 echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($_POST); 复选框数据的常见处理(implode,explode) 单选按钮的数据处理 Radio button：可以出现多个选择项，但是只能选择其中一个\n1、\t表单中使用的name属性，使用同名即可：只能选中一个 2、\t后台接收数据也不需要额外处理 3、\t数据库存储的话只需要一个字段存储普通数据即可（数字或者字符串. 4、\tPHP拿到数据之后，组织SQL直接存储到数据表即可 🌰 \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;gender\u0026#34; value=\u0026#34;1\u0026#34; checked=\u0026#34;checked\u0026#34;/\u0026gt;男 \u0026lt;input type=\u0026#34;radio\u0026#34; name=\u0026#34;gender\u0026#34; value=\u0026#34;2\u0026#34;/\u0026gt;女 多选按钮的数据处理 1、\t表单中name属性使用数组格式：名字[]（一类复选框数据使用一个. 2、\t后台接收到数据之后，是一个数组（数组不能存储到数据库. 3、\tPHP需要将数组转换成指定格式的字符串：使用分隔符分隔每一个元素并且形成字符串：implode(’分隔符’ ,数组),如果是反过来操作，那么取出数据之后使用 explode 把字符串变成数组 🌰 //将复选框内容存储到数据库 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); $hobby = isset($_POST[\u0026#39;hobby\u0026#39;]) ? $_POST[\u0026#39;hobby\u0026#39;] : array(); echo \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; var_dump($hobby); //数组转换成有格式的字符串 $hobby_string = implode($hobby,\u0026#39;|\u0026#39;); echo \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $hobby_string; //假设 $hobby_string 是从数据库取出来的字段 echo \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; var_dump(explode(\u0026#39;|\u0026#39;,$hobby_string)); 🍎 array(2) { [0]=\u0026gt; string(8) \u0026#34;football\u0026#34; [1]=\u0026gt; string(8) \u0026#34;pingpang\u0026#34; } football|pingpang array(2) { [0]=\u0026gt; string(8) \u0026#34;football\u0026#34; [1]=\u0026gt; string(8) \u0026#34;pingpang\u0026#34; } 🐖在HTML显示当中，通过判断复选框元素是否在数组中存在，来确定复选框checkbox是否有checked=“checked”属性：in_array() 4、\tPHP组织SQL直接存储到数据库 其他常规同名表单项的数据处理 除开radio button单选框和checkbox复选框，很少会出现同名的表单项。如果非要使用同名的来进行管理，那么可以采用checkbox方式进行操作 1、\t表单中同名增加[] 2、\tPHP接收时数组处理 3、\tPHP转换成有格式的字符串 4、\t数据库字符串存储 复选框细节 如果复选框没有选中，那么浏览器就不会提交。因此在PHP接收使用复选框（单选框）数据的时候，应该先判断是否存在该数据\n$hobby = isset($_POST[\u0026#39;hobby\u0026#39;]) ? $_POST[\u0026#39;hobby\u0026#39;] : array(); 文件上传 文件上传原理 文件从用户本地电脑通过传输方式（Web表单）保存到服务器所在电脑 指定的目录 下。\n1、\t增加文件上传的表单：浏览器请求一个服务器的HTML脚本（包含文件上传表单. 2、\t用户从本地选择一个文件（点击上传框（按钮）. 3、\t用户点击上传：文件会通过物联网传输到 服务器 上 4、\t服务器操作系统会将文件保存到临时目录：是以临时文件格式保存（windows下tmp. 5、\t服务器脚本开始工作：判断文件有效 6、\t服务器脚本将有效文件从临时目录移动到指定的目录下（完成. 表单写法 method 属性：表单提交方式必须为POST\nenctype (encode type)属性：form 表单属性，主要是规范表单数据的编码方式 上传表单：file表单\n\u0026lt;body\u0026gt; \u0026lt;form method=\u0026#34;POST\u0026#34; enctype=\u0026#34;multipart/form-data\u0026#34; action=\u0026#34;xxx.php\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;image\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; name=\u0026#34;btn\u0026#34; value=\u0026#34;上传文件\u0026#34;/\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;/body\u0026gt; 酱婶儿👇\n在PHP中，有一个 预定义变量$_FILES 是专门用来存储用户上传的文件的。\nheader(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; var_dump($_POST); var_dump($_FILES); 🍎 array(1) { [\u0026#34;btn\u0026#34;]=\u0026gt; string(12) \u0026#34;上传文件\u0026#34; } array(1) { [\u0026#34;image\u0026#34;]=\u0026gt; //表单name属性的值 array(5) { [\u0026#34;name\u0026#34;]=\u0026gt; string(40) \u0026#34;clementine_16px_1173677_easyicon.net.ico\u0026#34; [\u0026#34;type\u0026#34;]=\u0026gt; string(12) \u0026#34;image/x-icon\u0026#34; [\u0026#34;tmp_name\u0026#34;]=\u0026gt; string(27) \u0026#34;C:\\Windows\\Temp\\phpFF6C.tmp\u0026#34; [\u0026#34;error\u0026#34;]=\u0026gt; int(0) [\u0026#34;size\u0026#34;]=\u0026gt; int(1150) } } 可以通过 $_FILES 查看文件的一些信息。\n测试：如果没有enctype属性，那么上传文件会是什么样子？\narray(2) { [\u0026#34;image\u0026#34;]=\u0026gt; string(40) \u0026#34;clementine_16px_1173677_easyicon.net.ico\u0026#34; [\u0026#34;btn\u0026#34;]=\u0026gt; string(12) \u0026#34;上传文件\u0026#34; } array(0) { } $_ FILES变量详解 name：文件在用户（浏览器端）电脑上实际存在的名字（实际用来保留后缀. tmp_name：文件上传到服务器后操作系统保存的临时路径（实际用来给PHP后期使用. type：MIME（多功能互联网邮件扩展）类型，用来在计算机中客户端识别文件类型（确定软件. error：文件上传的代号，用来告知应用软件（PHP）文件接收过程中出现了什么问题（PHP后期根据代码进行文件判断. size：文件大小（PHP根据实际需求来确定是否该保留. $_ FILES错误信息说明 从 PHP 4.2.0 开始，PHP 将随文件信息数组一起返回一个对应的错误代码。 该代码可以在文件上传时生成的文件数组中的 error 字段中被找到，也就是 $_FILES[\u0026#39;userfile\u0026#39;][\u0026#39;error\u0026#39;]。 UPLOAD_ERR_OK 其值为 0，没有错误发生，文件上传成功。 UPLOAD_ERR_INI_SIZE 其值为 1，上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。 UPLOAD_ERR_FORM_SIZE 其值为 2，上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。 UPLOAD_ERR_PARTIAL 其值为 3，文件只有部分被上传。 UPLOAD_ERR_NO_FILE 其值为 4，没有文件被上传。 UPLOAD_ERR_NO_TMP_DIR 其值为 6，找不到临时文件夹。PHP 4.3.10 和 PHP 5.0.3 引进。 UPLOAD_ERR_CANT_WRITE 其值为 7，文件写入失败。PHP 5.1.0 引进。 Note: 以上值在 PHP 4.3.0 之后变成了 PHP 常量。 移动临时文件到目标位置 文件上传之后会保存到 $FILES 中，那么访问文件信息的形式就是 $ FILES[‘表单name属性值’][‘元素信息’]\n判断是否为上传的文件： is_uploaded_file() 移动文件： move_uploaded_file() //1、取得文件信息 👉 五个元素 $file = $_FILES[\u0026#39;image\u0026#39;]; //2、判断是否是上传文件：临时文件 if(is_uploaded_file($file[\u0026#39;tmp_name\u0026#39;])){ //是上传文件 👉 文件被复制到目录 C:\\fastwork\\Apache24\\htdocs\\uploads //$file[\u0026#39;tmp_name\u0026#39;] 取出的是\u0026#34;合法的上传文件\u0026#34; if(move_uploaded_file($file[\u0026#39;tmp_name\u0026#39;],\u0026#39;uploads/\u0026#39; . $file[\u0026#39;name\u0026#39;])){ echo \u0026#39;文件保存成功！\u0026#39;; }else{ echo \u0026#39;文件保存失败！\u0026#39;; } }else{ //不是上传文件 echo \u0026#39;文件上传失败！\u0026#39;; } 多文件上传 🌈准备 test.php\nheader(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; var_dump($_POST); var_dump($_FILES); 当商品需要上传多个图片进行展示的时候：那么需要使用多文件上传 针对一个内容但是不同文件说明： 同名表单\n\u0026lt;form method=\u0026#34;POST\u0026#34; enctype=\u0026#34;multipart/form-data\u0026#34; action=\u0026#34;test.php\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;image[]\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;image[]\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;image[]\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; name=\u0026#34;btn\u0026#34; value=\u0026#34;批量上传\u0026#34;/\u0026gt; \u0026lt;/form\u0026gt; 🍎 array(1) { [\u0026#34;btn\u0026#34;]=\u0026gt; string(12) \u0026#34;批量上传\u0026#34; } array(1) { [\u0026#34;image\u0026#34;]=\u0026gt; array(5) { [\u0026#34;name\u0026#34;]=\u0026gt; array(3) { [0]=\u0026gt; string(7) \u0026#34;app.ico\u0026#34; [1]=\u0026gt; string(8) \u0026#34;atom.exe\u0026#34; [2]=\u0026gt; string(10) \u0026#34;Update.exe\u0026#34; } [\u0026#34;type\u0026#34;]=\u0026gt; array(3) { [0]=\u0026gt; string(12) \u0026#34;image/x-icon\u0026#34; [1]=\u0026gt; string(24) \u0026#34;application/x-msdownload\u0026#34; [2]=\u0026gt; string(24) \u0026#34;application/x-msdownload\u0026#34; } [\u0026#34;tmp_name\u0026#34;]=\u0026gt; array(3) { [0]=\u0026gt; string(27) \u0026#34;C:\\Windows\\Temp\\phpBA60.tmp\u0026#34; [1]=\u0026gt; string(27) \u0026#34;C:\\Windows\\Temp\\phpBA61.tmp\u0026#34; [2]=\u0026gt; string(27) \u0026#34;C:\\Windows\\Temp\\phpBA71.tmp\u0026#34; } [\u0026#34;error\u0026#34;]=\u0026gt; array(3) { [0]=\u0026gt; int(0) [1]=\u0026gt; int(0) [2]=\u0026gt; int(0) } [\u0026#34;size\u0026#34;]=\u0026gt; array(3) { [0]=\u0026gt; int(186361) [1]=\u0026gt; int(431616) [2]=\u0026gt; int(1838112) } } } 👀 $_ FILES 数组按照 name,type,tmp_name,error,size 五元素进行分类。\n当商品需要进行多个维度图片说明的时候：需要使用多文件上传 针对是不同内容所以表单名字不一样： 批量解决问题\n\u0026lt;form method=\u0026#34;POST\u0026#34; enctype=\u0026#34;multipart/form-data\u0026#34; action=\u0026#34;test.php\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;banner\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;ad\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;header\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; name=\u0026#34;btn\u0026#34; value=\u0026#34;批量上传\u0026#34;/\u0026gt; \u0026lt;/form\u0026gt; 🍎 array(1) { [\u0026#34;btn\u0026#34;]=\u0026gt; string(12) \u0026#34;批量上传\u0026#34; } array(3) { [\u0026#34;banner\u0026#34;]=\u0026gt; array(5) { [\u0026#34;name\u0026#34;]=\u0026gt; string(7) \u0026#34;app.ico\u0026#34; [\u0026#34;type\u0026#34;]=\u0026gt; string(12) \u0026#34;image/x-icon\u0026#34; [\u0026#34;tmp_name\u0026#34;]=\u0026gt; string(27) \u0026#34;C:\\Windows\\Temp\\php1615.tmp\u0026#34; [\u0026#34;error\u0026#34;]=\u0026gt; int(0) [\u0026#34;size\u0026#34;]=\u0026gt; int(186361) } [\u0026#34;ad\u0026#34;]=\u0026gt; array(5) { [\u0026#34;name\u0026#34;]=\u0026gt; string(8) \u0026#34;atom.exe\u0026#34; [\u0026#34;type\u0026#34;]=\u0026gt; string(24) \u0026#34;application/x-msdownload\u0026#34; [\u0026#34;tmp_name\u0026#34;]=\u0026gt; string(27) \u0026#34;C:\\Windows\\Temp\\php1616.tmp\u0026#34; [\u0026#34;error\u0026#34;]=\u0026gt; int(0) [\u0026#34;size\u0026#34;]=\u0026gt; int(431616) } [\u0026#34;header\u0026#34;]=\u0026gt; array(5) { [\u0026#34;name\u0026#34;]=\u0026gt; string(10) \u0026#34;Update.exe\u0026#34; [\u0026#34;type\u0026#34;]=\u0026gt; string(24) \u0026#34;application/x-msdownload\u0026#34; [\u0026#34;tmp_name\u0026#34;]=\u0026gt; string(27) \u0026#34;C:\\Windows\\Temp\\php1626.tmp\u0026#34; [\u0026#34;error\u0026#34;]=\u0026gt; int(0) [\u0026#34;size\u0026#34;]=\u0026gt; int(1838112) } } 👀 $_ FILES 数组按照 banner,ad,header 业务内容进行分类。\n分析以上两种情况 👉 多文件上传的$_ FILES变量的数据结构形式 同名指的是 html 中 input 标签的 name \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;ad\u0026#34; /\u0026gt; 批量上传：同名表单：将表单名字形成一个数组，而且同时将文件对应的五个要素：name、tmp_name、size、type、error都形成对应数量的数组，每个文件上传对应数组元素的下标都是一样的：name[0] 和type[0]是属于同一个文件\n批量上传：不同名表单：每个文件都会形成一个属于自己独立的5个元素的数组\n对多文件信息的遍历读取和处理 不同名多文件上传处理方式：按照表单名字从$_FILES中取出来就可以直接使用（明确知道表单中有多少个文件上传）； 如果不确定表单中有多少个文件上传，不适合挨个去取（效率不高），可以通过遍历 $_ FILES 数组，挨个取出来实现文件上传 \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;banner\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;ad\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;header\u0026#34; /\u0026gt; //不同名文件遍历处理方式 foreach($_FILES as $file){ //$file就是一个完整的上传文件信息：找到临时路径，指定存放路径 if(is_uploaded_file($file[\u0026#39;tmp_name\u0026#39;])){ //存储 move_uploaded_file($file[\u0026#39;tmp_name\u0026#39;],\u0026#39;uploads/\u0026#39; . $file[\u0026#39;name\u0026#39;]); } } 同名多文件上传：想办法得到一个文件对应的五个元素数组。从 $_ FILES 中把对应的 name\\tmp_name\\size\\error\\type 挨个取出来， 然后存放到不同的数组中。 \u0026lt;form method=\u0026#34;POST\u0026#34; enctype=\u0026#34;multipart/form-data\u0026#34; action=\u0026#34;test.php\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;image[]\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;image[]\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; name=\u0026#34;image[]\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; name=\u0026#34;btn\u0026#34; value=\u0026#34;批量上传\u0026#34;/\u0026gt; \u0026lt;/form\u0026gt; //同名文件遍历处理方式 //此时遍历$_FILES没有意义，只有一个数组元素；应该变量$_FILES[\u0026#39;userfile\u0026#39;][\u0026#39;任意一个要素：name|tmp_name|size|error|type\u0026#39;] //判断元素存在而且是数组：name有代表是文件，name元素有多个（数组）代表是同名批量上传 if(isset($_FILES[\u0026#39;image\u0026#39;][\u0026#39;name\u0026#39;]) \u0026amp;\u0026amp; is_array($_FILES[\u0026#39;image\u0026#39;][\u0026#39;name\u0026#39;])){ //遍历构造数组元素 $images = array();\t//存储所有的文件信息，一个元素代表一个文件（数组. foreach($_FILES[\u0026#39;image\u0026#39;][\u0026#39;name\u0026#39;] as $k =\u0026gt; $name){ $images[] = array( \u0026#39;name\u0026#39; =\u0026gt; $name, \u0026#39;tmp_name\u0026#39; =\u0026gt; $_FILES[\u0026#39;image\u0026#39;][\u0026#39;type\u0026#39;][$k], \u0026#39;type\u0026#39; =\u0026gt; $_FILES[\u0026#39;image\u0026#39;][\u0026#39;tmp_name\u0026#39;][$k], \u0026#39;error\u0026#39; =\u0026gt; $_FILES[\u0026#39;image\u0026#39;][\u0026#39;error\u0026#39;][$k], \u0026#39;size\u0026#39; =\u0026gt; $_FILES[\u0026#39;image\u0026#39;][\u0026#39;size\u0026#39;][$k] ); } } print_r($images); 文件上传后续问题 实现上传功能代码的重复利用：封装文件上传函数 功能：上传文件 条件：条件判断 🌈需要上传的文件信息：对应的5个元素的数组 1、\t文件类型是否合适？外部指定MIME类型 2、 文件存储到什么位置？外部指定 3、\t文件格式限制（文件后缀）？外部限定 4、 文件大小限制？外部指定 结果：实现文件上传 1、\t成功：结果能够在以后看到：需要将文件的路径和文件名字返回（存储到数据库. 2、 失败：返回false，指定错误原因（引用参数. 锚点\n步骤 1. 封装出一个上传函数 2. 判断文件是否有效 3. 判断保存路径是否有效 4. 判断文件本身上传的过程中是否有错误：error 5. 文件类型的处理：通过MIME匹配即可 6. 文件格式的处理：后缀名的问题 7. 文件大小的处理 8. 移动到指定目录 8. 命名冲突的处理：上传同名文件？中文名字文件怎么办？ ✨\n//PHP文件上传功能封装函数 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); /* * 实现文件上传（单. * @param1 array $file，需要上传的文件信息：一维5元素数组（name\\tmp_name\\error\\size\\type) * @param2 array $allow_type，允许上传的MIME类型 * @param3 string $path，存储的路径 * @param4 string \u0026amp;$error，如果出现错误的原因 * @param5 array $allow_format = array()，允许上传的文件格式 * @param6 int $max_size = 2000000，允许上传的最大值 */ function upload_single($file,$allow_type,$path,\u0026amp;$error,$allow_format = array(),$max_size = 2000000){ //判断文件是否有效 if(!is_array($file) || !isset($file[\u0026#39;error\u0026#39;])){ //文件无效 $error = \u0026#39;不是一个有效的上传文件！\u0026#39;; return false; } //判断文件存储路径是否有效 if(!is_dir($path)){ //路径不存在 $error = \u0026#39;文件存储路径不存在！\u0026#39;; return false; } //判断文件上传过程是否出错 switch($file[\u0026#39;error\u0026#39;]){ case 1: case 2: $error = \u0026#39;文件超出服务器允许大小！\u0026#39;; return false; case 3: $error = \u0026#39;文件上传过程中出现问题，只上传一部分！\u0026#39;; return false; case 4: $error = \u0026#39;用户没有选中要上传的文件！\u0026#39;; return false; case 6: case 7: $error = \u0026#39;文件保存失败！\u0026#39;; return false; } //判断MIME类型 if(!in_array($file[\u0026#39;type\u0026#39;],$allow_type)){ //该文件类型不允许上传 $error = \u0026#39;当前文件类型不允许上传！\u0026#39;; return false; } //判断后缀是否允许 //取出后缀 $ext = ltrim(strrchr($file[\u0026#39;name\u0026#39;],\u0026#39;.\u0026#39;),\u0026#39;.\u0026#39;); if(!empty($allow_format) \u0026amp;\u0026amp; !in_array($ext,$allow_format)){ //不允许上传 $error = \u0026#39;当前文件的格式不允许上传！\u0026#39;; return false; } //判断当前文件大小是否满足当前需求 if($file[\u0026#39;size\u0026#39;] \u0026gt; $max_size){ //文件过大 $error = \u0026#39;当前上传的文件超出大小，最大允许\u0026#39; . $max_size . \u0026#39;字节!\u0026#39;; return false; } //构造文件名字：类型_年月日+随机字符串.$ext $fullname = strstr($file[\u0026#39;type\u0026#39;],\u0026#39;/\u0026#39;,TRUE) . date(\u0026#39;YYYYmmdd\u0026#39;); //产生随机字符串 for($i = 0;$i \u0026lt; 4;$i++){ $fullname .= chr(mt_rand(65,90)); } //拼凑后缀 $fullname .= \u0026#39;.\u0026#39; . $ext; //移动到指定目录 if(!is_uploaded_file($file[\u0026#39;tmp_name\u0026#39;])){ //文件不是上传的 $error = \u0026#39;错误：不是上传文件！\u0026#39;; return false; } if(move_uploaded_file($file[\u0026#39;tmp_name\u0026#39;],$path . \u0026#39;/\u0026#39; . $fullname)){ //成功 return $fullname; }else{ //移动失败 $error = \u0026#39;文件上传失败！\u0026#39;; return false; } } //提供数据 $file = $_FILES[\u0026#39;image\u0026#39;]; $path = \u0026#39;uploads\u0026#39;; $allow_type = array(\u0026#39;image/jpg\u0026#39;,\u0026#39;image/jpeg\u0026#39;,\u0026#39;image/gif\u0026#39;,\u0026#39;image/pjpeg\u0026#39;); $allow_format = array(\u0026#39;jpg\u0026#39;,\u0026#39;gif\u0026#39;,\u0026#39;jpeg\u0026#39;); $max_size = 8000000; if($filename = upload_single($file,$allow_type,$path,$error,$allow_format,$max_size)){ echo $filename; }else{ echo $error; } ","date":"2019-10-18T16:57:12+08:00","permalink":"/2019/10/18/PHP-10.%E8%A1%A8%E5%8D%95%E4%BC%A0%E5%80%BC%E5%92%8C%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0/","title":"10.PHP表单传值和文件上传"},{"content":"课程地址:\n视频1 视频2 Base64 Base64 由64个字符组成a-zA-Z0-9\u0026hellip;\nBase64 是一种编码而不是加密;\n缺点👉Base64 会浪费网络带宽,并不高效!!!\n为什么还用 Base64 呢? 很大原因是因为后端开发只会这种早期方式。\n图片上传的三种方式: @POST + @FormUrlEncoded 参数为 @Filed Base64文本 @POST + @Multipart 参数为 @Part RequestBody 最不常见的: @POST 参数为 @Body RequestBody Retrofit对比OkHttp 加密算法;解密算法 加密算法;密钥 密码丢了可以重置密码,密钥丢了\u0026hellip;就完了\n以上内容在 AndoJava 文字测量:精确的像素 居中对齐\n左对齐\n精准换行\n","date":"2019-10-16T15:21:58+08:00","permalink":"/2019/10/16/Android%E9%AB%98%E7%BA%A701/","title":"Android高级技术"},{"content":" 编程思想：如何利用数学模式，来解决对应的需求问题；然后利用代码实现对应的数据模型（逻辑）。\n算法：使用代码实现对应的数学模型，从而解决对应的业务问题。\n递推算法 递推算法是一种简单的算法，即通过已知条件，利用特定关系得出中间推论，直至得到结果的算法。递推算法分为顺推和逆推两种。\n顺推：通过最简单的条件（已知），然后逐步推演结果 逆推：通过结果找到规律，然后推到已知条件 🌰斐波那契数列：1 1 2 3 5 8 13 …，通常需求：请求得指定位置N所对应的值是多少 ?\n找规律： 1、\t第一个数是1 2、\t第二个数也是1 3、\t从第三位开始：属于前两个数的和\n代码解决思路： 1、\t如果数字位置为1和2，结果都是1 2、\t从第三个开始，想办法得到前两个的结果，就可以得到\n终极解决办法：想办法把要求的位置之前的所有的值都列出来，那么要求的数就可以通过前两个之和计算出来：使用数组存储所有结果即可。\n//需求：规律1 1 2 3 5 ... //求出指定位数对应的值 function my_recursive($des){ //已知条件：第一个和第二个数都为1，第三个开始为前两个之和 if($des==1||$des==2)return 1; $arr[1]=1; $arr[2]=1; for($i=3;$i\u0026lt;=$des;$i++){ $arr[$i]=$arr[$i-1]+$arr[$i-2]; echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($arr[$i]); } //返回最后一个位置的结果 return $arr[$des]; } print_r(\u0026#39;\u0026lt;hr\u0026gt;\u0026lt;b\u0026gt;\u0026#39;.my_recursive(15)); 🍎 2 3 5 8 13 21 34 55 89 144 233 377 610 ---------------------------------- 610 递归算法 递归算法是把问题转化为规模缩小了的同类问题的子问题。然后递归调用函数（或过程）来表示问题的解。\n简化问题：找到最优子问题（不能再小） 函数自己调用自己 🌰斐波那契数列：1 1 2 3 5 8 13 … 需求：求指定位置的数列的值 ; 规律：第一个和第二个为1，从第三个开始为前两个之后\nF(N) = F(N-1) + F(N-2); F(N-1) = F(N-2) + F(N - 3); … F(2) = F(1) = 1;\n递归思想中：有两个非常重要的点 递归点：发现当前问题可以有解决当期问题的函数，去解决规模比当前小一点的问题来解决 F(N) = F(N - 1) + F(N - 2); 递归出口：当问题解决的时候，已经到达（必须有）最优子问题，不能再次调用函数 ✨如果一个函数递归调用自己而没有递归出口：就是死循环 递归的本质是函数调用函数：一个函数需要开辟一块内存空间，递归会出现同时调用N多个函数（自己）：递归的本质是利用空间换时间\n//递归一定有函数 function recursion($n){ //递归出口 if($n == 1 || $n == 2) return 1; //递归点：求N的值，与求N-1的值一模一样，只是N-1的规模比N小 return recursion($n - 1) + recursion($n - 2); } //调用 echo recursion(15); 冒泡排序(最简单) 冒泡排序（Bubble Sort），是一种计算机科学领域的较简单的排序算法。 它重复地走访过要排序的数列，一次比较两个元素，如果他们的顺序错误就把他们交换过来。 走访数列的工作是重复地进行直到没有再需要交换，也就是说该数列已经排序完成。\n冒泡排序的算法思路： 1. 比较相邻的元素。如果第一个比第二个大，就交换他们两个。 2. 对每一对相邻元素作同样的工作，从开始第一对到结尾的最后一对。在这一点，最后的元素应该会是最大的数。 3. 针对所有的元素重复以上的步骤，除了最后一个。🐖 4. 持续每次对越来越少的元素重复上面的步骤，直到没有任何一对数字需要比较。 //bubble sort function bubbleSort(\u0026amp;$arr){ for($i=0,$len=count($arr);$i\u0026lt;=$len-1;$i++){ for($j=0;$j\u0026lt;$len-1;$j++){//👉 优化 for($j=0;$j\u0026lt;$len-1-$i;$j++){ if($arr[$j]\u0026gt;$arr[$j+1]){ $temp=$arr[$j]; $arr[$j]=$arr[$j+1]; $arr[$j+1]=$temp; } } } return $arr; } $arr = array(8,6,2,9,1,5,7); //执行函数 bubbleSort($arr); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($arr); 🍎 Array ( [0] =\u0026gt; 1 [1] =\u0026gt; 2 [2] =\u0026gt; 5 [3] =\u0026gt; 6 [4] =\u0026gt; 7 [5] =\u0026gt; 8 [6] =\u0026gt; 9 ) 🌴内循环挨个比较替换值,外部循环控制替换个数,执行效率低\n选择排序 比冒泡排序效率较高 选择排序（Selection sort）是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小（或最大）的一个元素， 存放在序列的起始位置，直到全部待排序的数据元素排完。 选择排序是不稳定的排序方法（比如序列[5， 5， 3]第一次就将第一个[5]与[3]交换，导致第一个5挪动到第二个5后面）。\n选择排序的算法思路： 1. 假设第一个元素为最小元素，记下下标。 2. 寻找右侧剩余的元素，如果有更小的，重新记下最新的下标。 3. 如果有新的最小的，交换两个元素。 4. 往右重复以上步骤，直到元素本身是最后一个。 $arr = array(1,5,2,9,6,3,4); //1、\t确定要交换多少次：一次只能找到一个最小的，需要找数组长度对应的次数 for($i = 0,$len = count($arr);$i \u0026lt; $len;$i++){ //2、假设当前第一个已经排好序 $min = $i;\t//当前第一个数是最小的 //3、拿该最小的取比较剩余的其他 for($j = $i + 1;$j \u0026lt; $len;$j++){ //4、比较：比较当前元素与选定的最小的元素 if($arr[$j] \u0026lt; $arr[$min]){ //说明当前指定的$min不合适 $min = $j; } } //5、交换当前选定的值与实际最小的元素值 if($min != $i){ $temp = $arr[$i]; $arr[$i] = $arr[$min]; $arr[$min] = $temp; } } echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($arr); 🍎 Array ( [0] =\u0026gt; 1 [1] =\u0026gt; 2 [2] =\u0026gt; 5 [3] =\u0026gt; 6 [4] =\u0026gt; 7 [5] =\u0026gt; 8 [6] =\u0026gt; 9 ) 🌴相对于冒泡排序效率高,因为内循环只处理找到最小值索引,而没有其他操作;具有不稳定性,如果数组中有重复的值存在,会出现重复值移动的现象,影响效率。\n插入排序 插入排序（Insert sort）,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中，从而得到一个新的、个数加一的有序数据，算法适用于少量数据的排序，是稳定的排序方法。插入算法把要排序的数组分成两部分：第一部分包含了这个数组的所有元素，但将最后一个元素除外（让数组多一个空间才有插入的位置），而第二部分就只包含这一个元素（即待插入元素）。在第一部分排序完成后，再将这个最后元素插入到已排好序的第一部分中。 插入排序的基本思想是：每步将一个待排序的纪录，按其关键码值的大小插入前面已经排序的文件中适当位置上，直到全部插入完为止。\n插入排序的算法思路：\n1. 设置监视哨r[0]，将待插入纪录的值赋值给r[0]； 2. 设置开始查找的位置j； 3. 在数组中进行搜索，搜索中将第j个纪录后移，直至r[0].key≥r[j].key为止； 4. 将r[0]插入r[j+1]的位置上。 1、\t认定第一个元素已经排好序； 2、\t取出第二个元素，作为待插入数据； 3、\t与已经排好序的数组的最右侧元素开始进行比较 4、\t如果后面的小于前面的：说明前面已经排好序的那个数组元素不在对的位置（向后移一个），然后让新的元素填充进去（继续向前比：高级） 5、\t重复前面的步骤：直到当前元素插入到对位置； 6、\t重复以上步骤，直到所有的数组元素都插入到对的位置。 //todo 2019-10-17 15:30:14 头大...https://www.bilibili.com/video/av12863134/?p=93 🌴稳定的排序方法,适用于少量数据\n🌴\n🌴\n","date":"2019-10-14T16:14:45+08:00","permalink":"/2019/10/14/PHP-9.%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3%E5%92%8C%E7%AE%97%E6%B3%95/","title":"9.PHP编程思想和算法"},{"content":"数组 数组概念 数组定义语法 👉 跟Kotlin很像 三种方式:\narray关键字 🐘 $变量 = array(元素1,元素2,元素3..); [] 🐘 $变量 = [元素1,元素2…]; 隐形定义数组 //1.定义数组：array $arr1 = array(\u0026#39;1\u0026#39;,2,\u0026#39;hello\u0026#39;); var_dump($arr1); 🍎 array(3) { [0]=\u0026gt; string(1) \u0026#34;1\u0026#34; [1]=\u0026gt; int(2) [2]=\u0026gt; string(5) \u0026#34;hello\u0026#34; } //2.定义数组：[] $arr2 = [\u0026#39;1\u0026#39;,2,\u0026#39;hello\u0026#39;]; var_dump($arr2); 🍎 array(3) { [0]=\u0026gt; string(1) \u0026#34;1\u0026#34; [1]=\u0026gt; int(2) [2]=\u0026gt; string(5) \u0026#34;hello\u0026#34; } //3.隐形定义数组：给变量增加一个中括号，系统自动变成数组 $变量[] = 值1;\t//如果不提供下标也可以，系统自动生成（数字：从0开始） $变量[下标] = 值;\t//中括号里面的内容称之为下标key，该下标可以是字母（单词）或者数字，与变量命名的规则相似 //隐形数组： $arr3[] = 1; $arr3[10] = 100; $arr3[] = \u0026#39;1\u0026#39;; // [11]=\u0026gt; string(1) \u0026#34;1\u0026#34; $arr3[\u0026#39;key\u0026#39;] = \u0026#39;key\u0026#39;; $arr3[1] = \u0026#39;value\u0026#39;; // [1]=\u0026gt; string(5) \u0026#34;value\u0026#34; $arr3[] = \u0026#39;xxx\u0026#39;; // [12] var_dump($arr3); 🍎 array(6) { [0]=\u0026gt; int(1) [10]=\u0026gt; int(100) [11]=\u0026gt; string(1) \u0026#34;1\u0026#34; [\u0026#34;key\u0026#34;]=\u0026gt; string(3) \u0026#34;key\u0026#34; [1]=\u0026gt; string(5) \u0026#34;value\u0026#34; [12]=\u0026gt; string(3) \u0026#34;xxx\u0026#34; } 如图 PHP数组特点 1. 可以整数下标或者字符串下标 如果数组下标都为整数：索引数组 如果数组下标都为字符串：关联数组 2. 不同下标可以混合存在：混合数组 3. 数组元素的顺序以放入顺序为准，跟下标无关 4. 数字下标的自增长特性：从0开始自动增长，如果中间手动出现较大的，那么后面的自增长元素从最大的值+1开始 5. 特殊值下标的自动转换 布尔值：true 和 false 空：NULL 🌰 //特殊下标转换 $arr4[false] = false; $arr4[true] = true; $arr4[NULL] = NULL; var_dump($arr4); 🍎 array(3) { [0]=\u0026gt; bool(false) [1]=\u0026gt; bool(true) [\u0026#34;\u0026#34;]=\u0026gt; NULL } 6. PHP中数组元素没有类型限制 7. PHP中数组元素没有长度限制 补充：PHP中的数组是很大的数据，所以存储位置是堆区 ，为当前数组分配一块连续的内存。\n多维数组 在第二维的数组元素中可以继续是数组，在PHP中没有维度限制（PHP本质并没有二维数组） 但是：不建议使用超过三维以上的数组，会增加访问的复杂度，降低访问效率。\n//定义二维数组 $info = array( array(\u0026#39;name\u0026#39; =\u0026gt; \u0026#39;Jim\u0026#39;,\u0026#39;age\u0026#39; =\u0026gt; 30), array(\u0026#39;name\u0026#39; =\u0026gt; \u0026#39;Tom\u0026#39;,\u0026#39;age\u0026#39; =\u0026gt; 28), array(\u0026#39;name\u0026#39; =\u0026gt; \u0026#39;Lily\u0026#39;,\u0026#39;age\u0026#39; =\u0026gt; 20)\t//最后一个元素，后面可以跟逗号不影响（不建议） ); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($info); print_r($info[1][\u0026#39;age\u0026#39;]); 🍎 Array ( [0] =\u0026gt; Array ( [name] =\u0026gt; Jim [age] =\u0026gt; 30 ) [1] =\u0026gt; Array ( [name] =\u0026gt; Tom [age] =\u0026gt; 28 ) [2] =\u0026gt; Array ( [name] =\u0026gt; Lily [age] =\u0026gt; 20 ) ) 28 数组遍历 遍历的基本含义 数组遍历：普通数组数据的访问都是通过数组元素的下标来实现访问，如果说数组中所有的数据都需要依次输出出来，就需要我们使用到一些简化的规则来实现自动获取下标以及输出数组元素。\n$arr = array(0=\u0026gt;array(‘name’ =\u0026gt; ‘Tom’),1=\u0026gt;array(‘name’ =\u0026gt; ‘Jim’));\t//二维数组 //访问一维元素：$arr[一维下标] $arr[0];\t//结果：array(‘name’ =\u0026gt; ‘Tom’); //访问二维元素：$arr[一维下标][二维下标 $arr[1][‘name’];\t//Jim Foreach遍历语法 //定义数组 $arr = array(1,2,3,4,5,6,7,8,9,10); //foreach循环 foreach($arr as $v){ //$v 随意命名 echo $v,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } 如果是 key =\u0026gt; value 形式\n//foreach循环 foreach($arr as $k =\u0026gt; $v){ //$v 随意命名 echo \u0026#39;key:\u0026#39;,$k,\u0026#39; == value:\u0026#39;,$v,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } 🍎\nkey:0 == value:1 key:1 == value:2 key:2 == value:3 key:3 == value:4 key:4 == value:5 key:5 == value:6 key:6 == value:7 key:7 == value:8 key:8 == value:9 key:9 == value:10 实操:\n$arr = array( 0 =\u0026gt; array(\u0026#39;name\u0026#39; =\u0026gt; \u0026#39;Tom\u0026#39;,\u0026#39;age\u0026#39; =\u0026gt; 30), 1 =\u0026gt; array(\u0026#39;name\u0026#39; =\u0026gt; \u0026#39;Jim\u0026#39;,\u0026#39;age\u0026#39; =\u0026gt; 28) ); //通过foreach遍历一维元素 foreach($arr as $value){ //1、可以继续遍历：增加foreach遍历$value //2、可以使用下标访问 echo \u0026#39;name is : \u0026#39; ,$value[\u0026#39;name\u0026#39;],\u0026#39; and age is : \u0026#39;,$value[\u0026#39;age\u0026#39;],\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } 🍎\nname is : Tom and age is : 30 name is : Jim and age is : 28 Foreach遍历原理 Foreach遍历的原理：本质是数组的内部有一颗指针，默认是指向数组元素的第一个元素，foreach就是利用指针去获取数据，同时移动指针。\nforeach会重置指针：让指针指向第一个元素； 进入foreach循环：通过指针取得当前第一个元素，然后将下标取出放到对应的下标变量$k中（如果存在），将值取出来放到对应的值变量$v中；（指针下移） 进入到循环内部（循环体），开始执行； 重复2和3，直到在2的时候遇到指针取不到内容（指针指向数组最后） foreach($arr as $k =\u0026gt; $v){ //循环体 } For循环遍历数组 使用关键字 count(数组) 得到数组元素的长度\n//数组特点：索引数组，下标规律 $arr = array(1,2,3,4,5); for($i = 0,$len = count($arr); $i \u0026lt; $len; $i++){ echo \u0026#39;key is : \u0026#39;,$i,\u0026#39; and value is : \u0026#39; , $arr[$i],\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } 🍎 key is : 0 and value is : 1 key is : 1 and value is : 2 key is : 2 and value is : 3 key is : 3 and value is : 4 key is : 4 and value is : 5 while配合each和list遍历数组 while是在外部定义边界条件，如果要实现可以和for循环一样。 each 函数使用：each 能够从一个数组中获取当前数组指针所指向的元素的下标和值，拿到之后将数组指针下移，同时将拿到的元素下标和值以一个四个元素的数组返回： 0下标 – 》 取得元素的下标值 1下标 - 》 取得元素的值 Key下标 – 》取得元素的下标值 Value下标 – 》取得元素的值 each //while配合each和list遍历数组 $arr = array(5,4,\u0026#39;name\u0026#39; =\u0026gt; \u0026#39;Tom\u0026#39;,3,\u0026#39;age\u0026#39; =\u0026gt; 30); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; //each函数指针操作 print_r(each($arr)); print_r(each($arr)); print_r(each($arr)); print_r(each($arr)); print_r(each($arr)); //超出元素个数后 👉 bool(false) print_r(each($arr)); 🍎\nDeprecated: The each() function is deprecated. This message will be suppressed on further calls in C:\\fastwork\\Apache24\\htdocs\\test.php on line 9 Array ( [1] =\u0026gt; 5 [value] =\u0026gt; 5 [0] =\u0026gt; 0 [key] =\u0026gt; 0 ) Array ( [1] =\u0026gt; 4 [value] =\u0026gt; 4 [0] =\u0026gt; 1 [key] =\u0026gt; 1 ) Array ( [1] =\u0026gt; Tom [value] =\u0026gt; Tom [0] =\u0026gt; name [key] =\u0026gt; name ) Array ( [1] =\u0026gt; 3 [value] =\u0026gt; 3 [0] =\u0026gt; 2 [key] =\u0026gt; 2 ) Array ( [1] =\u0026gt; 30 [value] =\u0026gt; 30 [0] =\u0026gt; age [key] =\u0026gt; age ) bool(false) list List函数使用：list是一种结构，不是一种函数（没有返回值），是list提供一堆变量去从一个数组中取得元素值，然后依次存放到对应的变量当中（批量为变量赋值：值来源于数组）：list必须从索引数组中去获取数据，而且必须从0开始。 正确操作：\n//list结构 $arr = array(1,2 =\u0026gt; 1); list($first) = $arr; var_dump($first); 🍎 int(1) 错误操作:\n//list结构 $arr = array(5,2 =\u0026gt; 1); //错误：second变量对应的下标为元素下标1的，但是数组没有 list($first,$second) = $arr;\tvar_dump($first,$second); 🍎 Notice: Undefined offset: 1 in C:\\fastwork\\Apache24\\htdocs\\test.php on line 9 int(5) NULL 若改为:\n//list结构 $arr = array(5,3); //错误：second变量对应的下标为元素下标1的，但是数组没有 list($first,$second) = $arr;\tvar_dump($first,$second); 🍎 int(5) int(3) list 与 each 配合特别好：each 一定有两个元素就是0和1下标元素 list (变量1,变量2) = each(数组);\t//是一种赋值运算，但是可以得到false结果（each取不到正确的结果），整个表达式为false\n错误用法:\n//while循环 $arr = array(1,\u0026#39;name\u0026#39; =\u0026gt; \u0026#39;Tom\u0026#39;,3,\u0026#39;age\u0026#39; =\u0026gt; 30); while(each($arr)){ //list搭配each list($key,$value) = each($arr); //输出 echo \u0026#39;key is :\u0026#39; . $key . \u0026#39; value is :\u0026#39; . $value . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } 🍎 输出不全: 1和3在上面 key is :name value is :Tom key is :age value is :30 正确做法:\n//while循环 $arr = array(1,\u0026#39;name\u0026#39; =\u0026gt; \u0026#39;Tom\u0026#39;,3,\u0026#39;age\u0026#39; =\u0026gt; 30); while(list($key,$value) = each($arr)){ //list搭配each //list($key,$value) = each($arr); //输出 echo \u0026#39;key is :\u0026#39; . $key . \u0026#39; value is :\u0026#39; . $value . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } 🍎 key is :0 value is :1 key is :name value is :Tom key is :1 value is :3 key is :age value is :30 基于特性: each 遍历到最后返回false ; each 指针下移 ; list key index 0,value index 1 。\n数组的相关函数 1. 排序函数 sort 顺序排序(下标重排) rsort 逆序排序 asort 顺序排序(下标保留) ksort 顺序排序:按照键名(下标) krsort 逆序排序 shuffle 随机打乱数组元素 🌰\n//排序函数 $arr = array(3,1,5,2,0); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; $sort_arr = sort($arr); print_r($arr); 🍎 //1.sort Array ( [0] =\u0026gt; 0 [1] =\u0026gt; 1 [2] =\u0026gt; 2 [3] =\u0026gt; 3 [4] =\u0026gt; 5 ) //2.asort(下标保留) Array ( [4] =\u0026gt; 0 [1] =\u0026gt; 1 [3] =\u0026gt; 2 [0] =\u0026gt; 3 [2] =\u0026gt; 5 ) //3.krsort Array ( [4] =\u0026gt; 0 [3] =\u0026gt; 2 [2] =\u0026gt; 5 [1] =\u0026gt; 1 [0] =\u0026gt; 3 ) 2. 指针函数,比如: reset,end,next,prev,; reset()：重置指针，将数组指针回到首位 end()：重置指针，将数组指针指导最后一个元素 next()：指针下移，取得下一个元素的值 prev()：指针上移，取得上一个元素的值 current()：获取当前指针对应的元素值 key()：获取当前指针对应的下标值 🌰\n$arr = array(3,1,5,2,0); //指针函数 //**默认为第一个元素** echo current($arr),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo key($arr),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo next($arr),next($arr),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo prev($arr),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo end($arr),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo reset($arr),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; 🍎 3 0 15 1 0 3 注意事项：next和prev会移动指针，有可能导致指针移动到最前或者最后（离开数组）， 导致数组不能使用，即通过next和prev不能回到真确的指针位置。只能通过end或者reset进行指针重置\n3. 其他函数 count()：统计数组中元素的数量 array_push()：往数组中加入一个元素（数组后面） array_pop()：从数组中取出一个元素（数组后面） array_shift()：从数组中取出一个元素（数组前面） array_unshift()：从数组中加入一个元素（数组前面） PHP模拟数据结构: 👇\n栈：压栈，先进去后出来（FILO） //模拟数据结构：栈和队列 $arr = array(); //栈：先压栈后出栈：都是从一端出来 //前面：array_shift/array_unshift //后面：array_push/array_pop //压栈 array_push($arr,3); array_push($arr,2); array_push($arr,1); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($arr); //出栈 echo array_pop($arr),array_pop($arr),array_pop($arr),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; 🍎 Array ( [0] =\u0026gt; 3 [1] =\u0026gt; 2 [2] =\u0026gt; 1 ) 123 队列：排队，先进去的先出去（FIFO） //队列：先排队，先出来，一端进，另外一端出 //后进前出：array_push/array_shift //前进后出：array_unshift/array_pop $arr = array(); //入队 array_unshift($arr,3); array_unshift($arr,2); array_unshift($arr,1); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r($arr); //出队 echo array_pop($arr),array_pop($arr),array_pop($arr),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; 🍎 Array ( [0] =\u0026gt; 1 [1] =\u0026gt; 2 [2] =\u0026gt; 3 ) 321 其他 array_reverse()：数组元素反过来 $arr = array(1,2,3,6,5); print_r(array_reverse($arr)); 🍎 Array ( [0] =\u0026gt; 5 [1] =\u0026gt; 6 [2] =\u0026gt; 3 [3] =\u0026gt; 2 [4] =\u0026gt; 1 ) in_array()：判断一个元素在数组中是否存在 $arr = array(1,2,3,6,5); var_dump(in_array(6,$arr)); var_dump(in_array(100,$arr)); 🍎 bool(true) bool(false) array_keys()：获取一个数组的所有下标，返回一个索引数组 array_values()：获取一个数组的所有值，返回一个索引数组 $arr = array(1,2,3,6,5); echo \u0026#39;\u0026lt;pre\u0026gt;\u0026#39;; print_r(array_keys($arr)); print_r(array_values($arr)); 🍎 Array ( [0] =\u0026gt; 0 [1] =\u0026gt; 1 [2] =\u0026gt; 2 [3] =\u0026gt; 3 [4] =\u0026gt; 4 ) Array ( [0] =\u0026gt; 1 [1] =\u0026gt; 2 [2] =\u0026gt; 3 [3] =\u0026gt; 6 [4] =\u0026gt; 5 ) ","date":"2019-10-14T15:00:49+08:00","permalink":"/2019/10/14/PHP-8.%E6%95%B0%E7%BB%84%E5%92%8C%E6%95%B0%E7%BB%84%E5%87%BD%E6%95%B0/","title":"8.PHP数组和数组函数"},{"content":" JUC JVM Spring Boot 高级 数据结构和算法 ","date":"2019-10-14T15:00:49+08:00","permalink":"/2019/10/14/%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/","title":"学习计划"},{"content":"字符串类型 字符串定义语法 PHP中有四种:\n1. 单引号字符串：使用单引号包裹 2. 双引号字符串：使用双引号包裹 引号方式：比较适合定义那些比较短（不超过一行）或者没有结构要求的字符串 如果有结构要求，或者内容超过一行，可以使用以下两种结构定义 3. nowdoc字符串：没有单引号的单引号字符串 $str = \u0026lt;\u0026lt;\u0026lt;’边界符’ 字符串内容 边界符; 4. heredoc字符串：没哟双引号的双引号字符串 $str = \u0026lt;\u0026lt;\u0026lt;边界符 字符串内容 边界符; //PHP字符串：定义 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); //引号定义 $str1 = \u0026#39;hello\u0026#39;; $str2 = \u0026#34;hello\u0026#34;; //var_dump($str1,$str2); //结构化定义 //heredoc结构 $str3 = \u0026lt;\u0026lt;\u0026lt;EOD hello world EOD; //nowdoc结构 $str4 = \u0026lt;\u0026lt;\u0026lt;\u0026#39;EOD\u0026#39; hello world EOD; //var_dump($str3,$str4); 字符串转义 PHP中系统常用的转义符号： \\’：在单引号字符串中显示单引号 \\”：在双引号字符串中显示双引号 \\r：代表回车（理论上是回到当前行的首位置） \\n：代表新一行 \\t：类似tab键，输出4个空格 \\$：在PHP中使用$符号作为变量符号，因此需要特定识别 //定义字符串识别转义符号 $str1 = \u0026#39;abc\\r\\ndef\\t\\\u0026#39;\\\u0026#34;\\$fg\u0026#39;; $str2 = \u0026#34;abc\\r\\ndef\\t\\\u0026#39;\\\u0026#34;\\$fg\u0026#34;; echo $str1,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,$str2; 其中,双引号中因为能够识别$符号，所以双引号中可以解析变量，而单引号不可以\n$a = \u0026#39;hello\u0026#39;; //变量识别 //双引号中变量识别的规则 //1. 变量本身系统能够与后面的内容区分：应该保证变量的独立性，不要让系统难以区分 👉str2,str3 //2. 使用变量专业标识符（区分），给变量加上一组大括号{} 👉 str4 $str1 = \u0026#39;abce $a dfg\u0026#39;; $str2 = \u0026#34;abce $a dfg\u0026#34;; $str3 = \u0026#34;abce$adfg\u0026#34;; $str4 = \u0026#34;abce{$a}dfg\u0026#34;; echo $str1,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,$str2,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,$str3,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,$str4; 🍎\nNotice: Undefined variable: adfg in C:\\fastwork\\Apache24\\htdocs\\test.php on line 9 abce $a dfg abce hello dfg abce abcehellodfg 结构化定义字符串变量的规则 1、\t结构化定义字符串对应的边界符有条件： 1.1\t上边界符后面不能跟任何内容； 1.2\t下边界符必须顶格：最左边； 1.3\t下边界同样后面只能跟分号，不能跟任何内容； 2、\t结构化定义字符串的内部（边界符之间）的所有内容都是字符串本身 $a = \u0026#39;hello\u0026#39;; $str1 = \u0026lt;\u0026lt;\u0026lt;XXX //这是弹出内容 \u0026lt;script\u0026gt; alert(\u0026#39;$a\u0026#39;);\t//js弹出字符串必须要有引号 \u0026lt;/script\u0026gt; XXX; echo $str1; 字符串长度问题 基本函数strlen()：得到字符串的长度（字节为单位） //字符串长度 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); //定义字符串 $str1 = \u0026#39;abcefjdoifaoi\u0026#39;; $str2 = \u0026#39;你好中国123\u0026#39;; echo strlen($str1),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,strlen($str2); 🌴中文在utf8字符集下占3个字节\n多字节字符串的长度问题：包含中文的长度\n多字节字符串扩展模块：mbstring扩展（mb：Multi Bytes）\n首先需要加载PHP的 mbstring 扩展, php.ini中\nextension = mbstring mb扩展可以带来更多函数。 mbstring 扩展针对的是一些关于字符统计：strlen只是针对标准交换码ASCII，mtstring会针对不同的字符集\n//字符串长度 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); //定义字符串 $str1 = \u0026#39;abcefjdoifaoi\u0026#39;; $str2 = \u0026#39;你好中国123\u0026#39;; echo strlen($str1),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,strlen($str2); echo \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;; //使用mbstring扩展 echo mb_strlen($str1),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,mb_strlen($str2),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,mb_strlen($str2,\u0026#39;utf-8\u0026#39;); 🍎 字符串相关函数 转换函数：implode(), explode(), str_split() implode (连接方式,数组)：将数组中的元素按照某个规则连接成一个字符串 explode (分割字符,目标字符串)：将字符串按照某个格式进行分割，变成数组 中国|北京|顺义 == array(‘中国’,‘北京’,’顺义’); str_split(字符串,字符长度)：按照指定长度拆分字符串得到数组 截取函数：trim(), ltrim(), rtrim() trim (字符串[,指定字符])：本身默认是用来去除字符串两边的空格（中间不行），但是也可以指定要去除的内容，是按照指定的内容循环去除两边有的内容：直到碰到一个不是目标字符为止 ltrim()：去除左边的 rtrim()：去除右边的 var_dump(trim($str)); 👉 string(8) \u0026#34;abcd e f\u0026#34; 截取函数：substr(), strstr() substr (字符串,起始位置从0开始[,长度])：指定位置开始截取字符串，可以截取指定长度（不指定到最后） strstr (字符串,匹配字符)：从指定位置开始，截取到最后（✨可以用来去文件后缀名） 🏋🌰 $str = \u0026#39; abcd e f \u0026#39;; //字符串截取 echo substr($str,1,3),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo strstr($str,\u0026#39;c\u0026#39;); 🍎 abc cd e f 大小转换函数：strtolower(), strtoupper(), ucfirst() strtolower：全部小写 strtoupper：全部大写 ucfirst：首字母大写 $str = \u0026#39; abcd e f \u0026#39;; //字符串大小写 echo strtoupper($str),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo ucfirst($str); 查找函数：strpos(), strrpos() strpos (字符串，匹配字符)：判断字符在目标字符串中出现的位置（首次） strrpos (字符串，匹配字符)：判断字符在目标字符串中最后出现的位置 🌰 $str = \u0026#39;123a234a3b2a\u0026#39;; //查找位置 echo strpos($str,\u0026#39;a\u0026#39;),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo strrpos($str,\u0026#39;a\u0026#39;); 🍎 3 11 替换函数：str_replace() str_replace (匹配目标,替换的内容,字符串本身)：将目标字符串中部分字符串进行替换 $str = \u0026#39;123a234a3b2a\u0026#39;; //字符串替换 echo str_replace(\u0026#39;a\u0026#39;,\u0026#39;X\u0026#39;,$str); 👉 123X234X3b2X 格式化函数： printf(), sprintf() printf/sprintf (输出字符串有占位符,顺序占位内容..)：格式化输出数据 $age = 50; $name = \u0026#39;TOM\u0026#39;; //格式化输出 ✨**sprintf** echo sprintf(\u0026#34;你好，今年我%d岁,我叫%s\u0026#34;,$age,$name); 🍎 你好，今年我50岁,我叫TOM 其他：str_repeat(), str_shuffle() str_repeat()：重复某个字符串N次 str_shuffle()：随机打乱字符串 //其他字符串函数 $str = \u0026#39;abcdefg\u0026#39;; echo str_repeat($str,5),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo str_shuffle($str); 🍎 abcdefgabcdefgabcdefgabcdefgabcdefg cefbdag ","date":"2019-10-14T14:58:42+08:00","permalink":"/2019/10/14/PHP-7.%E5%AD%97%E7%AC%A6%E4%B8%B2/","title":"7.PHP字符串"},{"content":"错误处理 错误处理 指的是系统（或者用户）在对某些代码进行执行的时候，发现有错误， 就会通过错误处理的形式告知程序员。\n错误分类 语法错误：用户书写的代码不符合PHP的语法规范，语法错误会导致代码在编译过程中不通过， 所以代码不会执行（Parse error） 运行时错误：代码编译通过，但是代码在执行的过程中会出现一些条件不满足导致的错误（runtime error） 逻辑错误：程序员在写代码的时候不够规范，出现了一些逻辑性的错误，导致代码正常执行，但是得不到想要的结果 $a = 10; If($a = 1){\t//最常见把比较符号写成赋值符号 //执行代码 } 错误代号 所有看到的错误代号在PHP中都被定义成了系统常量（可以直接使用）\n系统错误： E_PARSE：编译错误，代码不会执行 E_ERROR：fatal error，致命错误，会导致代码不能正确继续执行（出错的位置断掉） E_WARNING：warning，警告错误，不会影响代码执行，但是可能得到意想不到的结果 E_NOTICE：notice，通知错误，不会影响代码执行 用户错误：E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE 用户在使用自定义错误触发的时候，会使用到的错误代号（系统不会用到） 其他：E_ALL，代表着所有从错误（通常在进行错误控制的时候使用比较多）， 建议在开发过程中（开发环境）使用 所有以E开头的错误常量（代号）其实都是由一个字节存储，然后每一种错误占据一个对应的位，如果想进行一些错误的控制，可以使用位运算进行操作 排除通知级别notice：E_ALL \u0026amp; ~E_NOTICE 只要警告和通知：E_WARNING | E_NOTICE 错误触发 程序运行时触发：系统自动根据错误发生后，对比对应的错误信息，输出给用户：主要针对代码的语法错误和运行时错误。\n人为触发：知道某些逻辑可能会出错，从而使用对应的判断代码来触发响应的错误提示\ntrigger_error(错误提示)：可以通过第二个参数进行严格性控制\n//PHP错误处理 //处理脚本让浏览器按照指定字符集解析的方法 ✨ 处理中文乱码问题 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); //除法运算 $b = 0; if($b == 0){ //人为触发错误 trigger_error(\u0026#39;除数不能为0！\u0026#39;);\t//默认 notice，会继续执行 trigger_error(\u0026#39;除数不能为0！\u0026#39;,E_USER_ERROR); //默认 error，代码不会执行 } echo \u0026#39;hello \u0026#39;; 错误显示设置 错误显示设置：哪些错误该显示，以及该如何显示 ; 在PHP中，其实有两种方式来设置当前脚本的错误处理\n# PHP的配置文件：全局配置：php.ini文件 display_errors ：是否显示错误 error_reporting ：显示什么级别的错误 # 默认配置 error_reporting = E_ALL display_errors = On 2、\t可以在运行的PHP脚本中去设置：在脚本中定义的配置项级别比配置文件高（通常在开发当中都会在代码中去进行控制和配置. Error_reporting()：设置对应的错误显示级别 Ini_set(‘配置文件中的配置项’,配置值) Ini_set(‘error_reporting’,E_ALL); Ini_set(‘display_errors’,1); 错误日志设置 在实际生产环境中，不会直接让错误赤裸裸的展示给用户： 1、\t不友好 2、\t不安全：错误会暴露网站很多信息（路径、文件名. 所以在生产环境中，一般不显示错误（错误也比较少），但是不可能避免会出现错误（测试的时候不会发现所有的问题），这个时候不希望看到，但是又希望捕捉到可以让后台程序员去修改：需要保存到日志文件中，需要在PHP配置文件中或者代码中（ini_set）设置对应error_log配置项 1、\t开启日志功能\n; Besides displaying errors, PHP can also log errors to locations such as a ; server-specific log, STDERR, or a location specified by the error_log ; directive found below. While errors should not be displayed on productions ; servers they should still be monitored and logging is a great way to do that. ; Default Value: Off ; Development Value: On ; Production Value: On ; http://php.net/log-errors log_errors = On 2、指定路径\n;windows.show_crt_warning 自定义错误处理 最简单的错误处理：trigger_errors()函数，但是该函数不会阻止系统报错 PHP系统提供了一种用户处理错误的机制：用户自定义错误处理函数，然后将该函数增加操系统错误处理的句柄中，然后系统会在碰到错误之后，使用用户定义的错误函数。 如何将用户自定义的函数放到系统中？ set_error_handler()\nmixed set_error_handler( callable $error_handler[, int $error_types = E_ALL | E_STRICT] ) 设置一个用户的函数(error_handler)来处理脚本中出现的错误。 ✨ $error_handle 👉 回调函数:用户自定义的函数\n自定义错误处理函数，系统有要求 对应代码实现：\n自定义错误处理函数：注意参数 //自定义错误处理机制 header(\u0026#39;Content-type:text/html;charset=utf-8\u0026#39;); //自定义函数 /* @param1 $errno，是系统提供的错误代码：E_ALL，E_NOTICE.. */ function my_error($errno,$errstr,$errfile,$errline){ //判断：当前会碰到错误有哪些 👉 排除系统本事就要排除的错误 if(!(error_reporting() \u0026amp; $errno)){ return false; //error_reporting没有参数代表获取当前系统错误处理对应的级别 } //开始判断错误类型 👉 匹配错误 switch($errno){ case E_ERROR: case E_USER_ERROR: echo \u0026#39;fatal error in file \u0026#39; . $errfile . \u0026#39; on line \u0026#39; . $errline . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo \u0026#39;error info ：\u0026#39; . $errstr; break; case E_WARNING: case E_USER_WARNING: echo \u0026#39;Warning in file \u0026#39; . $errfile . \u0026#39; on line \u0026#39; . $errline . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo \u0026#39;error info ：\u0026#39; . $errstr; break; case E_NOTICE: case E_USER_NOTICE: echo \u0026#39;Notice in file \u0026#39; . $errfile . \u0026#39; on line \u0026#39; . $errline . \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo \u0026#39;error info ：\u0026#39; . $errstr; break; } return true; } //报错 echo $a; 👉 原来系统报错 //修改错误机制 set_error_handler(\u0026#39;my_error\u0026#39;); echo $a; 👉 修改错误机制后,使用自定义报错 🍎\nNotice: Undefined variable: a in C:\\fastwork\\Apache24\\htdocs\\test.php on line 40 Notice in file C:\\fastwork\\Apache24\\htdocs\\test.php on line 45 error info ：Undefined variable: a 注册自定义函数：修改错误处理机制 Notice: Undefined variable: a in C:\\fastwork\\Apache24\\htdocs\\test.php on line 40 Notice in file C:\\fastwork\\Apache24\\htdocs\\test.php on line 45 error info ：Undefined variable: a 当前属于简单自定义模式，如果要复杂，可以在某些影响代码功能的错误发生后，让用户跳转到某个指定界面。\n","date":"2019-10-11T12:26:29+08:00","permalink":"/2019/10/11/PHP-6.%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/","title":"6.PHP错误处理"},{"content":"文件包含 文件包含：在一个PHP脚本中，去将另外一个文件（PHP）包含进来，去合作完成一件事情。\n文件包含的作用 文件包含的意义： 要么使用被包含文件中的内容，实现代码的共享（重用）：向上包含（索要） 向上包含：在当前脚本要用某个代码之前包含别的文件\n要么自己有东西可以给别的文件使用，实现代码的共享（重用）：向下包含（给予） 向下包含：在自己有某个东西的时候，需要别的脚本来显示（自己代码写完之后包含其他文件）\n最大的作用：分工协作，每个脚本做的事情不一样，因此可以使用协作方式，让多个脚本共同完成一件事情。\n文件包含四种形式 在PHP中文件的包含有四种形式（两种大形式） Include：包含文件 Include_once：系统会自动判断文件包含过程中，是否已经包含过（一个文件最多被包含一次） Require：与include相同 Require_once：以include_once相同 包含基本语法 include ‘文件名字’; include(‘文件名字’);\t//文件名字：路径问题 向上包含(索要)：是先包含文件，后使用文件中的内容（向上包含） 向下包含(给予)：先准备内容，然后包含另外的文件，在另外的文件中，使用当前的内容 简单例子:test.php中引入temp.php , 两文件位于同一文件夹下: temp.php\necho \u0026#39;\u0026lt;a href=\u0026#34;/index.asp\u0026#34;\u0026gt;首页\u0026lt;/a\u0026gt; - \u0026lt;a href=\u0026#34;/html/index.asp\u0026#34;\u0026gt;HTML 教程\u0026lt;/a\u0026gt; - \u0026lt;a href=\u0026#34;/css/index.asp\u0026#34;\u0026gt;CSS 教程\u0026lt;/a\u0026gt; - \u0026lt;a href=\u0026#34;/js/index.asp\u0026#34;\u0026gt;JavaScript 教程\u0026lt;/a\u0026gt; - \u0026lt;a href=\u0026#34;/php/index.asp\u0026#34;\u0026gt;PHP 教程\u0026lt;/a\u0026gt;\u0026#39;; test.php\necho \u0026#39;dddddd\u0026lt;br\u0026gt;\u0026#39;; include \u0026#39;temp.php\u0026#39;; 效果:\ndddddd 首页 - HTML 教程 - CSS 教程 - JavaScript 教程 - PHP 教程 文件包含 PHP代码的执行流程 读取代码文件(PHP程序) 编译:将PHP代码转换成字节码(生成 opcode) zendengine 来解析 opcode,按照字节码进行逻辑运算; 转换成对应的 HTML 代码 文件加载原理 在文件加载(include 或者 require)的时候,系统会自动的将被包含文件中的代码相当于嵌入到当前文件中; 加载位置:在哪加载,对应的文件中的代码嵌入的位置就是对应的 include 位置 在 PHP 中被包含的文件是单独进行编译的 总结:PHP文件在编译过程中有错误,则不执行;但是如果是从外部引入的PHP文件中有错误, 系统会在从上往下执行到该 include 文件所在语句时停止执行。\ninclude 和 require 区别 可以完全理解为Android中的 include\ninclude 和 include_once 的区别： include 系统会碰到一次，执行一次；如果对统一个文件进行多次加载，那么系统会执行多次； include_once 只会执行一次;\n还是上面 temp.php 和 test.php include \u0026#39;temp.php\u0026#39;; echo \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; include \u0026#39;temp.php\u0026#39;; include_once\u0026#39;temp.php\u0026#39;; include_once\u0026#39;temp.php\u0026#39;; 效果:\n首页 - HTML 教程 - CSS 教程 - JavaScript 教程 - PHP 教程 首页 - HTML 教程 - CSS 教程 - JavaScript 教程 - PHP 教程 其中这两次输出都是由 include 执行的。\nrequire 和 include的区别：本质都是包含文件，唯一的区别在于包含不到文件的时候，报错的形式不一样 include 的错误级别比较轻：不会阻止代码执行\n文件加载路径 绝对路径 从磁盘的根目录开始（本地绝对路径） Windows：盘符C:/路径/PHP文件 Linux：/路径/PHP文件 从网站根目录开始（网络绝对路径） /：相对于网站主机名字对应的路径 Localhost/index.php -\u0026gt; E:/server/apache/htdocs/index.php 相对路径 从当前文件所在目录开始的路径 ）或 ./ 👉表示当前文件夹 ../ 👉上级目录（当前文件夹的上一层文件夹） 🌰 //绝对路径 include_once \u0026#39;E:/server/apache/htdocs/include1.php\u0026#39;; //相对路径加载 //include_once \u0026#39;include1.php\u0026#39;;\t//默认当前文件本身 //include_once \u0026#39;./include1.php\u0026#39;; //复杂相对路径 //include_once \u0026#39;../htdocs/include1.php\u0026#39;; echo $a; 绝对路径和相对路径的加载区别\n绝对路径相对效率偏低，但是相对安全（路径不会出问题） 相对路径相对效率高些，但是容易出错（相对路径会发生改变） 文件嵌套包含 嵌套包含的时候就很容易出现相对路径出错的问题：相对路径会因为文件的包含而改变（./和../）：windows下面，每一个文件夹下都有.和..的文件夹。\n","date":"2019-10-10T14:14:36+08:00","permalink":"/2019/10/10/PHP-4.%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB/","title":"4.PHP文件包含"},{"content":" 🌴输出函数;时间函数;函数的函数;函数执行;作用于;可变函数;匿名函数和闭包;常用的系统函数\u0026hellip;\n函数 函数执行的内存分析 函数执行的内存分析： 1. 读取代码进入到代码段（编译：将代码变成字节码存储到内存） 2. 根据代码逐行执行 原因：编译和执行是分开的（先编译后执行） 系统执行函数流程 函数支持默认值 - 类似于 Kotlin $num1 = 10; //add($num1,20);\t//传入的实参，可以是变量或者其他有值的表达式（变量、常量、运算符计算结果） //函数的默认值 function jian($num1 = 0,$num2 = 0){ //当前的$num1是形参，在编译时不执行，即便执行也是在jian函数内部，不会与外部的$num1变量冲突 echo $num1 - $num2; } //调用：默认值如果存在，可以不用传入 jian($num1); echo \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo $num1; 注意事项：\n1 .默认值的定义是放在最右边的（多个），不能左边形参有默认值，但是右边没有 2. 函数外部定义的变量名字与函数定义的形参名字冲突（同名）是没有任何关联关系的；如果多个函数使用同样的形参名字也不冲突。\n引用传值(\u0026amp;$b) 👉 \u0026amp;$b 在调用的时候，必须给引用传值的参数位置传入实际参数，而且参数本身必须是变量。（变量才有指向的数据的内存地址）\n//引用传值 function display($a,\u0026amp;$b){ //修改形参的值 $a = $a * $a; $b = $b * $b; echo $a,\u0026#39;\u0026lt;br\u0026gt;\u0026#39;,$b,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } //定义变量 $a = 10; $b = 5; //调用函数 display($a,$b); echo \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;,$a,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,$b; 运行结果:\n100 25 --------------------------------------------- 10 25 说明:函数在定义的时候,对应的b采用的是取地址,所以在实参传入之后,系统b取到了外部变量b的内存地址, 而a取的是值因此改变之后:a只改变了函数内部自己;b改变自己的同时也改变了外部。\n引用传值注意事项：在传入实参的时候，必须传入变量。\n//错误调用：引用传值直接传入数据本身而不是变量,因为只有变量能够引用传递🐖 display(10,5); // Fatal error: Only variables can be passed by reference 函数返回值 返回值：return，指的是将函数实现的结果，通过return关键字，返回给函数外部（函数调用处）： 在PHP中所有的函数都有返回值。（如果没有明确 return 使用，那么系统默认返回 NULL ）\n注意：函数的返回值可以是任意数据类型。\nfunction display(){ //输出 echo __FUNCTION__;\t//输出当前函数名字 } var_dump(display()); 结果:\ndisplayNULL 作用域(不同于Java) 在PHP中作用域严格来说分为两种：但是PHP内部还定义一些在严格意义之外的一种，所以总共算三种：\n全局变量：就是用户普通定义的变量（函数外部定义）👉不同于Java 所属全局空间：在PHP中只允许在全局空间使用：理论上函数内部不可调用 脚本周期：直到脚本运行结束（最后一行代码执行完） 🌰\n//默认的代码空间：全局空间 $global = \u0026#39;global area\u0026#39;;\t//最终会被系统纳入到超全局变量中：$GLOBALS[\u0026#39;global\u0026#39;] = global area; //局部变量（函数内部定义） function display(){ //所有的形参都可以理解为局部变量 $inner = __FUNCTION__;\t//局部变量 //访问全局变量 echo $global;\t//不能访问 } //调用函数 display(); 结果:\nNotice: Undefined variable: global in C:\\fastwork\\Apache24\\htdocs\\test.php on line 11 display 局部变量：就是在函数内部定义的变量 特效与Java相同 所属当前函数空间：在PHP中只允许在当前函数自己内部使用 函数周期：函数执行结束（函数是在栈区中开辟独立内存空间运行） 超全局变量 超全局变量：系统定义的变量（预定义变量：$_SERVER、$_POST等）\n所属超全局空间：没有访问限制（函数内外都可以访问） 超全局变量会将 \u0026#34;全局变量\u0026#34; 自动纳入到$GLOBALS里面，而$GLOBALS没有作用域限制， 所以能够帮助局部去访问全局变量：但是必须使用数组方式 🌰\n//把上面的输出方式稍加改动 //访问全局变量 echo $global;\t//不能访问 👇 echo $GLOBALS[\u0026#39;global\u0026#39;]; 如果想函数内部使用外部变量：除了$GLOBALS之外，通过参数传值（如果要统一战线还可以使用引用传值）。\nglobal 关键字 本质的形式：在函数的内部和外部，对一个同名变量（全局和局部）使用同一块内存地址保存数据，从而实现共同拥有。\n基本语法： global 变量名;\t//不能赋值 变量名 = 值;\t//修改 //默认的代码空间：全局空间 $global = \u0026#39;global area\u0026#39;;\t//最终会被系统纳入到超全局变量中：$GLOBALS[\u0026#39;global\u0026#39;] = global area; function display(){ $inner = __FUNCTION__; //定义变量：使用全局变量 global $global;\t//全局空间存在 echo $global; //定义变量：全局不存在 global $local; $local = \u0026#39;inner\u0026#39;; } //调用函数 👉 1.函数访问全局变量 display(); //👉2.全局空间访问局部变量 //echo $inner;\t//不能访问 //访问“局部”变量 echo $local;\t//可以访问 虽然以上方式可以实现局部与全局的互访，但是通常不会这么用。 一般如果会存在特殊使用，也会使用参数的形式来访问。（还可以使用常量：define定义的）\n函数内静态变量(static) 静态变量：static，是在**函数内部** 定义的变量，使用static关键字修饰， 用来实现跨函数共享数据的变量：函数运行结束所有局部变量都会清空，如果重新运行一下函数， 所有的局部变量又会重新初始化。 基本语法： Function 函数名(){ //定义变量 static $变量名 = 值;\t//通常会在定义的时候就直接赋值 } //定义函数 function display(){ //定义变量 $local = 1;\t//局部变量 //定义静态变量 static $count = 1;\t//静态变量 echo $local++,$count++,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } //调用 display(); display(); display(); 🍎\n11 12 13 静态变量的作用是为了跨函数共享数据（同一个函数被多次调用）\n🌴静态变量原理: 系统在进行编译时候就会对static这一行进行初始化,并默认赋值为空值(0,\u0026quot;\u0026quot;); 函数在调用的时候,会自动跳过static关键字这一行。\n默认赋值为空值(0,\u0026quot;\u0026quot;),会自动判段类型,验证 (☄⊙ω⊙)☄\nfunction display(){ $local = 1; //定义静态变量 static $count ;\t//静态变量 var_dump(empty($count)); echo $local++,$count++,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; } //调用 display(); display(); display(); 🍎\nbool(true) 1 bool(false) 11 bool(false) 12 思考:静态变量的使用：\n为了统计：当前函数被调用的次数（有没有替代方法？） 为了统筹函数多次调用得到的不同结果（递归思想） 可变函数 可变函数：当前有一个变量所保存到值，刚好是一个函数的名字，那么就可以使用变量+()来充当函数名使用。\nfunction display(){ echo \u0026#39;line number \u0026#39;.__LINE__, \u0026#39;\u0026lt;br\u0026gt;fun name is \u0026#39;.__FUNCTION__; } $a = \u0026#39;display\u0026#39;; //可变函数 $a(); 可变函数在系统使用的过程中还是比较多的，尤其是使用很多系统函数的时候： 需要用户在外部定义一个自定义函数，但是是需要传入到系统函数内部使用。\n✨将一个用户定义的函数传入给另外一个函数(函数名)去使用的过程,称之为回调过程,而被传入的函数称之为回调函数。\n//定义系统函数（假设） function sys_function($arg1,$arg2){ //给指定的函数（第一个参数），求对应的第二个参数值的4次方 //为实际用户输入的数值进行处理 $arg2 = $arg2 + 10; return $arg1($arg2);\t//user_function(20) } //定义一个用户函数：求一个数的四次方👉回调函数 function user_function($num){ return $num * $num * $num * $num; } //求10的4次方 echo sys_function(\u0026#39;user_function\u0026#39;,10); 匿名函数和闭包 视频地址👉https://www.bilibili.com/video/av12863134/?p=68\n变量保存匿名函数，本质得到的是一个对象（Closure）👉var_dump($func);\n//定义基本匿名函数 $func = function(){ echo \u0026#39;hello world\u0026#39;.\u0026#39;\u0026lt;br\u0026gt;\u0026#39;;//函数体 }; //调用匿名函数：可变函数 $func(); //查看变量内容 var_dump($func); 结果\nhello world object(Closure)#1 (0) { } 闭包 闭包：closure， 一词来源于以下两者的结合：要执行的代码块（由于自由变量被包含在代码块中， 这些自由变量以及它们引用的对象没有被释放）和为自由变量提供绑定的计算环境（作用域） 简单理解：函数内部有一些局部变量（要执行的代码块）在函数执行之后没有被释放， 是因为在函数内部还有对应的函数在引用（函数的内部函数：匿名函数）\n证明：函数的局部变量在函数使用完之后没有被释放？ 1、\t使用内部匿名函数； 2、\t匿名函数使用局部变量：use 关键字； 3、\t匿名函数被返回给外部使用； 验证👇\n//闭包函数 function display1(){ //定义变量：局部变量 $name = __FUNCTION__; //定义匿名函数 $innerfunction = function() use($name){\t//use就是将外部变量（局部）保留给内部使用（闭包） //函数内部的函数 echo $name; }; //返回内部匿名函数 return $innerfunction; } $closure = display1(); //程序运行到此行：display1 函数运行结束：理论上局部变量 $name 应该已经被释放 $closure(); 伪类型 伪类型：假类型，实际上在PHP中不存在的类型。但是通过伪类型可以帮助程序员去更好的查看操作手册从而更方便学习。 伪类型主要有两种：在三大类八小类之外 Mixed：混合的，可以是多种PHP中的数据类型 Number：数值的，可以是任意数值类型（整形和浮点型） 常用的系统函数 1.有关输出的函数 print()：类似于echo输出提供的内容，本质是一种结构（不是函数），返回1，可以不需要使用括号 print_r()：类似于var_dump，但是比var_dump简单，不会输出数据的类型，只会输出值（数组打印使用比较多） //输出相关 echo print(\u0026#39;hello world\u0026lt;br/\u0026gt;\u0026#39;); echo \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;; print \u0026#39;hello world\u0026lt;br/\u0026gt;\u0026#39;; echo \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;; $a = \u0026#39;hello world\u0026lt;br/\u0026gt;\u0026#39;; print_r($a); var_dump($a); 🍎\nhello world 1 ---------------------------- hello world ---------------------------- hello world string(16) \u0026#34;hello world \u0026#34; echo print(\u0026lsquo;hello world\n\u0026rsquo;);的输出结果为 : hello world👉换行👉1\n2.有关时间的函数 👉 https://www.php.net/manual/zh/function.date.php\ndate()：按照指定格式对对应的时间戳（从1970年格林威治时间开始计算的秒数）， 如果没有指定特定的时间戳，那么就是默认解释当前时间戳\ntime()：获取当前时间对应的时间戳 microtime()：获取微秒级别的时间 strtotime()：按照规定格式的字符串转换成时间戳 echo date(\u0026#39;Y 年 m 月 d 日 H:i:s\u0026#39;,12345678),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo time(),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo microtime(),\u0026#39;\u0026lt;br\u0026gt;\u0026#39;; echo strtotime(\u0026#39;tomorrow 10 hours\u0026#39;); 对应的输出结果为\n1970 年 05 月 24 日 05:21:18 1570690459 0.61865800 1570690459 1570759200 3.有关数学的函数 max()：指定参数中最大的值 min()：比较两个数中较小的值 rand()：得到一个随机数，指定区间的随机整数 mt_rand()：与rand一样，只是底层结构不一样，效率比rand高（建议使用） round()：四舍五入 ceil()：向上取整 floor()：向下取整 pow()：求指定数字的指定指数次结果：pow(2,8) == 2^8 == 256 abs()：绝对值 sqrt()：求平方根 🍎 echo max(2,3); // 3 有关函数的函数 function_exists()：判断指定的函数名字是否在内存中存在（帮助用户不去使用一个不存在的函数，让代码安全性更高） func_get_arg()：在自定义函数中去获取指定数值对应的参数 func_get_args()：在自定义函数中获取所有的参数（数组） func_num_args()：获取当前自定义函数的参数数量 func_get_args 和 func_num_args 都是统计的对应实参的数量🌴 //函数相关函数 function test($a,$b){ //获取指定参数 var_dump(func_get_arg(1)); echo \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; //获取所有参数 var_dump(func_get_args()); echo \u0026#39;\u0026lt;br\u0026gt;\u0026#39;; //获取参数数量 var_dump(func_num_args()); } //调用函数 function_exists(\u0026#39;test\u0026#39;) \u0026amp;\u0026amp; test(1,\u0026#39;abs\u0026#39;,3,4); 🍎\nstring(3) \u0026#34;abs\u0026#34; array(4) { [0]=\u0026gt; int(1) [1]=\u0026gt; string(3) \u0026#34;abs\u0026#34; [2]=\u0026gt; int(3) [3]=\u0026gt; int(4) } int(4) ","date":"2019-10-10T14:14:36+08:00","permalink":"/2019/10/10/PHP-5.%E5%87%BD%E6%95%B0/","title":"5.PHP函数"},{"content":" 🌴运算符;位运算;流程控制;流程控制替代语法(小九九表格)\n运算符 赋值运算符(同Java) 算术运算符 在进行除法运算或者取余运算的时候，对应的除数（第二个数）不能为0\n$a = $b = 10;\t//连贯赋值运算：两个不同变量 $c = 0; //var_dump($a / 0);\t//错误：被除数不能为0 比较运算符 ==：左边的与右边的相同（大小相同） ===：全等于，左边与右边相同：大小以及数据的类型都要相同 !=：左边的与右边的不同（大小不同） !==：不全等于，只有大小或者类型不同 $a = \u0026#39;123\u0026#39;;\t//字符串 $b = 123;\t//整型 //判断相等 var_dump($a == $b); //全等判断 var_dump($a === $b); 逻辑运算符(同Java) $a = \u0026#39;weekend\u0026#39;; $b = \u0026#39;goods\u0026#39;; //逻辑与 var_dump($a == \u0026#39;weekend\u0026#39; \u0026amp;\u0026amp; $b == \u0026#39;good\u0026#39;); //逻辑或 var_dump($a == \u0026#39;weekend\u0026#39; || $b == \u0026#39;good\u0026#39;); //逻辑非 var_dump($b == \u0026#39;good\u0026#39;); var_dump(!($b == \u0026#39;good\u0026#39;)); 连接运算符 连接运算：是PHP中将多个字符串拼接的一种符号 . ：将两个字符串连接到一起 .= ： 复合运算，将左边的内容与右边的内容连接起来，然后重新赋值给左边变量 A .= b 相当于 A = A . b\n$a = \u0026#39;hello \u0026#39;; $b = 123; echo $a . $b; //将a变量和b变量连接起来 $a .= $b; //$a = $a . $b; echo $a; 错误抑制符 在PHP中有一些错误可以提前预知，但是这些错误可能无法避免，但是又比希望报错给用户看，可以使用错误抑制符处理。 @：在可能出错的表达式前面使用@符号即可 ; 错误抑制符通常在生产环境（上线）会用到，在开发的时候不会用：系统本身最好没有任何错误。\necho \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;; $a = 10; $b = 0; //$a % $b; @($a % $b); 三目运算符(同Java) $a = 10; $b = $a \u0026gt; 10 ? 100 : 0; echo $b; 自操作运算符 如果进行除法或者取余运算，那么要考虑右边表达式的结果是否为0（为0出错）\n$a = $b = 1; $a++; ++$b;\t//独立操作，不参与其他运算 echo $a,$b; echo \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo $a++,++$b;\t//$a和$b不只是独立运算，还参与了输出操作 echo $a,$b; $a = 10; $b = 5; $a += $b;\t//$a = $a + $b = 15; $a -= $b - 1;\t//$a = $a - ($b - 1);\t//15 - 5 + 1 = 11 echo \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,$a,$b; 位运算 注意：\n系统进行任何位运算的时候都是使用的补码 运算结束之后都必须转换成原码才是最终要显示的数据 计算机码 计算机码：计算机在实际存储数据的时候，采用的编码规则（二进制规则）\n计算机码：原码、反码和补码，数值本身最左边一位是用来充当符号位：正数为0，负数为1\n原码：数据本身从十进制转换成二进制得到的结果\n正数：左边符号位为0（正数的原码、反码和补码就是原码本身） 负数：左边符号位为1 反码：针对负数，符号位不变，其他位取反\n补码：针对负数，反码+1\n系统中存在两个0：+0和-0 +0： 00000000 -0： 10000000\t原码 取反 11111111 补码 00000000 例如\n//计算机码 $a = 5; $b = -5; /* 5原码： 00000101 -5原码：10000101 取反： 11111010\t//反码：\u0026lt;a style=\u0026#39;color:red\u0026#39;\u0026gt;符号位不变\u0026lt;/a\u0026gt;，其他位取反 求补：\t11111011\t//补码：反码+1 */ 位运算符 位运算：取出计算机中最小的单位（位bit）进行运算 \u0026amp;：按位与，两个位都为1，结果为1，否则为0 |：按位或，两个有一个为1，结果为1 ~：按位非，一个位如果为1则变成0，否则反之 ^：按位异或，两个相同则为0，不同则为1 \u0026lt;\u0026lt;：按位左移，整个位（32位），向左移动一位，右边补0 \u0026gt;\u0026gt;：按位右移，整个位向右移动一位，左边补符号位对应内容（正数补0，负数补1） 按位左移：乘以2的操作 按位右移：除以2的操作（不完全正确）：整数除2会出现小数 按位与 var_dump($a \u0026amp; $b); /* //取出系统存储的结果进行与操作 5\t00000101 -5 11111011 \u0026amp;\t00000001\t//最终结果 转换：判断符号位，0表示正数（原码），1表示负数（补码） */ 按位非 var_dump(~$b); /* -5 11111011\t补码 取反 00000100 原码 00000100 */ 按位右移 var_dump($b\u0026gt;\u0026gt;1); var_dump($b\u0026gt;\u0026gt;2); /* -5\t11111011 \u0026gt;\u0026gt;1 11111110\t//运算结果：补码 -1 11111101\t//反码 取反 10000010\t//原码：-2 */ 运算符优先级 https://www.php.cn/manual/view/102.html\n流程控制 PHP中\u0026quot;else if\u0026quot;可以连起来写\u0026quot;elseif\u0026quot;\n流程控制替代语法 流程控制替代语法：分支和循环结构的替代语法 PHP本身是嵌入到HTML中的脚本语言，需要在HTML中书写一些关于判断或者循环的结构语法，必须符合PHP标签规范，需要HTML与PHP进行混搭， 如果使用原始的PHP代码那么会非常不美观。 需求：打印一个九九乘法表，使用表格来展示\n\u0026lt;table border=1\u0026gt; \u0026lt;?php for($i = 1;$i \u0026lt; 10;$i++){?\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;?php for($j = 1;$j \u0026lt;= $i;$j++){?\u0026gt; \u0026lt;td\u0026gt; \u0026lt;?php echo $i . \u0026#39; * \u0026#39; . $j . \u0026#39; = \u0026#39; . $i * $j;?\u0026gt; \u0026lt;/td\u0026gt; \u0026lt;?php }?\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;?php }?\u0026gt; \u0026lt;/table\u0026gt; 在PHP书写到HTML中的这些大括号{}非常不美观，所以PHP提供了一种替代机制，让其可以不用书写大括号：\nFor(;;){ 👉 for(;;): } 👉 endfor; \u0026lt;table border=1\u0026gt; \u0026lt;?php for($i = 1;$i \u0026lt; 10;$i++):?\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;?php for($j = 1;$j \u0026lt;= $i;$j++):?\u0026gt; \u0026lt;td\u0026gt; \u0026lt;?php echo $i . \u0026#39; * \u0026#39; . $j . \u0026#39; = \u0026#39; . $i * $j;?\u0026gt; \u0026lt;/td\u0026gt; \u0026lt;?php endfor;?\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;?php endfor;?\u0026gt; \u0026lt;/table\u0026gt; 类似的还有endif,endswitch等。\n","date":"2019-10-10T13:19:44+08:00","permalink":"/2019/10/10/PHP-3.%E8%BF%90%E7%AE%97%E7%AC%A6%E5%92%8C%E6%B5%81%E7%A8%8B%E6%8E%A7%E5%88%B6/","title":"3.PHP运算符和流程控制"},{"content":"变量 //定义变量：在PHP中不需要任何关键字定义变量（赋值） $var1;\t//定义变量 $var2 = 1;\t//定义同时赋值 //访问变量 echo $var2;\t//通过var2变量名字找到存储的内容1，然后输出 //修改变量 $var2 = 2; echo \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;,$var2; //删除变量：使用unset(变量名字) unset($var2); //echo $var2; //变量命名规则 $var_1;$var_var_1;$_var1;\t//正确格式 $var; //中文变量 $中国 = \u0026#39;China\u0026#39;; 可变变量 //定义两个变量 $a = \u0026#39;ccc\u0026#39;; $b = \u0026#39;bb\u0026#39;; $ccc = \u0026#39;b\u0026#39;; echo $$a;//b 预定义变量 预定义变量：提前定义的变量，系统定义的变量，存储许多需要用到的数据（预定义变量都是数组）\n$_GET：获取所有表单以get方式提交的数据 $_POST：POST提交的数据都会保存在此 $_REQUEST：GET和POST提交的都会保存 $GLOBALS：PHP中所有的全局变量 $_SERVER：服务器信息 $_SESSION：session会话数据 $_COOKIE：cookie会话数据 $_ENV：环境信息 $_FILES：用户上传的文件信息 变量传递 值传递：将变量保存的值赋值一份，然后将新的值给另外一个变量保存（两个变量没有关系）; 引用传递：将变量保存的值所在的内存地址，传递给另外一个变量：两个变量指向同一块内存空间（两个变量是同一个值） $新变量 = \u0026amp;$老变量;\n//值传递 $a = 10; $b = $a; $b = 5; echo $a,$b,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; //引用传递 $c = 10; $d = \u0026amp;$c; $c = 5; echo $c,$d,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; 常量 //使用函数定义常量：define define(\u0026#39;PI\u0026#39;,3.14); //使用const关键字定义 const PII = 3; //定义特殊常量 define(\u0026#39;-_-\u0026#39;,\u0026#39;smile\u0026#39;); //const -_- = \u0026#39;smile\u0026#39;;\t//错误 //使用常量 echo PI; //echo -_-;\t//特殊符号不能直接使用 echo constant(\u0026#39;-_-\u0026#39;); //系统常量 echo \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;,PHP_VERSION,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,PHP_INT_SIZE,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,PHP_INT_MAX; //魔术常量 echo \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;; echo __DIR__,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,__FILE__,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,__LINE__,\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; echo __LINE__; 系统常量 系统常量：系统帮助用户定义的常量，用户可以直接使用\n常用的几个系统常量 PHP_VERSION：PHP版本号 PHP_INT_SIZE：整形大小 PHP_INT_MAX：整形能表示的最大值（PHP中整形是允许出现负数：带符号） 在PHP中还有一些特殊的常量，他们有双下划线开始+长两名+双下划线结束，这种常量称之为系统魔术常量：魔术常量的值通常会跟着环境变化，但是用户改变不了 __DIR__ ：当前被执行的脚本所在电脑的绝对路径 __FILE__ ：当前被执行的脚本所在的电脑的绝对路径（带自己文件的名字） __LINE__ ：当前所属的行数 __NAMESPACE__ ：当前所属的命名空间 __CLASS__ ：当前所属的类 __METHOD__ ：当前所属的方法 数据类型 其他类型转数值的说明\n布尔true为1，false为0； 字符串转数值有自己的规则 2.1\t以字母开头的字符串，永远为0； 2.2\t以数字开头的字符串，取到碰到字符串为止（不会同时包含两个小数点） //创建数据 $a = \u0026#39;abc1.1.1\u0026#39;; $b = \u0026#39;1.1.1abc\u0026#39;; //自动转换 echo $a + $b;\t//算术+运算，系统先转换成数值类型（整型和浮点型），然后运算 //强制转换 echo \u0026#39;\u0026lt;br/\u0026gt;\u0026#39;,(float)$a,(float)$b; //判断数据类型 通过一组类型判断函数，来判断变量，最终返回这个变量所保存数据的数据类型（相同结果为true，失败为false）：是一组以is_开头后面跟类型名字的函数：is_XXX(变量名) Bool类型不能用echo来查看，可以使用var_dump结构查看 Var_dump(变量1,变量2…) echo \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;; var_dump(is_int($a));\t//false var_dump(is_string($a));\t//true //获取以及设定数据（变量）的类型 //Gettype(变量名)：获取类型，得到的是该类型对应的字符串 //Settype(变量名,类型)：设定数据类型：与强制转换不同 //1、\t强制转换(类型)变量名，是对数据值复制的内容进行处理（不会处理实际存储的内容） //2、\tsettype会直接改变数据本身 echo \u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;; echo gettype($a),\u0026#39;\u0026lt;br/\u0026gt;\u0026#39;; //设置类型 var_dump(settype($b,\u0026#39;int\u0026#39;)); echo gettype($b),$b; 整数类型和进制转换 在PHP中提供了四种整型的定义方式：十进制定义，二进制定义，八进制定义和十六进制定义 同java $a = 120;\t//10进制 $a = 0b110;\t//2进制 $a = 0120;\t//8进制 $a = 0x120;\t//16进制 不管得到的结果如何，需要补足32位：前面补0：00000000 00000000 00000000 00001010\n\u0026lt;?php //简单数据类型：整型、浮点型和布尔型 //定义4种整型数据 $a1 = 110; $a2 = 0b110; $a3 = 0110; $a4 = 0x110; echo $a1,\u0026#39;~\u0026#39;,$a2,\u0026#39;~\u0026#39;,$a3,\u0026#39;~\u0026#39;,$a4,\u0026#39;\u0026lt;hr/\u0026gt;\u0026#39;;\t//默认的PHP输出数值都会自动转换成10进制输出 /* 十进制转二进制 10 ---》8 + 2 ---》2^3 + 2^1 ---》从二进制右侧开始，按照对应的指数次位置补1，没有的补0 从最后侧开始：00000000 00000000 00000000 00001010 二进制转十进制 01101011 ==\u0026gt;从右侧开始 1*2^0 + 1*2^1 + 0*2^2 + 1*2^3 + 0*2^4 + 1*2^5 + 1*2^6 + 0*2^7 == 1 + 2 + 0 + 8 + 0 + 32 + 64 + 0 == 107 */ /* PHP中不需要用户这么复杂的去计算，提供了很多的函数进行转换 Decbin()：十进制转二进制 Decoct()：十进制转八进制 Dechex()：十进制转十六进制 Bindec()：二进制转十进制 */ //利用进制函数运算 var_dump(decbin(10));// string(4) \u0026#34;1010\u0026#34; 浮点类型 注意浮点数的判断!\n//浮点数 $f1 = 1.23; $f2 = 1.23e10; $f3 = PHP_INT_MAX + 1;\t//整型超过自身存储的大小之后会用浮点型存储 var_dump($f1,$f2,$f3); //浮点数判断 $f4 = 0.7; $f5 = 2.1; $f6 = $f5 / 3; //var_dump($f4 == $f6); 布尔类型 //Empty()：判断数据的值是否为“空”，不是 NULL，如果为空返回 true，不为空返回false //Isset()：判断数据存储的变量本身是否存在，存在变量返回true，不存在返回false $b1 = true; $b2 = FALSE; var_dump($b1,$b2); 简单说明浮点数为什么同样的字节数存储数据，但是却能表示更大的数据呢？ 00000000 00000000 00000000 00000000 👉11111111 11111111 11111111 1111111 （整形最大值：所有位都是有效数据）\n浮点数：红色7位算的结果是10的指数，后面三个字节存储表示具体数值 00000000 00000000 00000000 00000000 👉 11111111 11111111 11111111 1111111 尽量不用用浮点数做精确判断：浮点数保存的数据不够精确，而且在计算机中凡是小数基本上存的都不准确\nvar_dump(0.1==(0.2/2));//bool(true) var_dump(0.7==(2.1/3));//bool(false) 可见结果不一定对。\n特别注意empty()为true的情况 ","date":"2019-10-09T13:59:17+08:00","permalink":"/2019/10/09/PHP-2.%E5%8F%98%E9%87%8F%E5%92%8C%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/","title":"2.PHP变量和数据类型"},{"content":"Apache HTTP Server 配置默认站点 让Apache确定服务器上访问的位置：网站文件夹所在位置 httpd.conf： Define SRVROOT \u0026#34;C:\\fastwork\\Apache24\\htdocs\u0026#34; ServerRoot \u0026#34;${SRVROOT}\u0026#34; 方便用户使用名字访问对应的网站：给文件夹对应的取一个别名httpd.conf：ServerName localhost:8081 端口可以单独实现：httpd.conf：Listen 80 凡是涉及到Apache配置文件的修改，那么需要重启Apache才能生效 实现DNS域名解析：通常默认站点都是本地DNS。修改hosts文件, Windows下为 C:\\Windows\\System32\\drivers\\etc httpd.exe 常用命令 最后, cmd.exe以管理员身份启动后, 进入bin目录下。\n查看Apache具有哪些功能以及配置文件是否有错：httpd 或者 httpd.exe（文件所在目录）; 查看使用的模块：httpd -M ,static 静态加载,Apache 启动就加载好的直接使用; shared 动态加载,用的时候才加载; 验证配置文件是否有效：httpd –t ;\n安装服务: httpd -k install ;\nhttpd -k start 或者 win+R，输入 services.msc , 启动Apache服务\nhttpd -k stop httpd -k restart httpd -k uninstall 删除Apache服务应先停止再删除 PHP安装 PHP.exe 就是可以解析PHP代码转变成 HTML 代码从而让浏览器可以解析的。 php-7.3.10-Win32-VC15-x64.zip 解压后重命名为php7 , CMD测试:\n1. index.php \u0026lt;?php echo \u0026#39;你好PHP\\r\\nNew\u0026#39; ?\u0026gt; 2. cmd.exe C:\\fastwork\\php7\u0026gt;php.exe -f C:\\fastwork\\Apache24\\htdocs\\index.php 你好PHP\\r\\nNew 配置Apache加载PHP模块 httpd.conf 中配置\n# 加载PHP LoadModule php7_module \u0026#39;C:\\fastwork\\php7\\php7apache2_4.dll\u0026#39; Apache分配工作给PHP模块：如果是PHP代码就交给PHP处理：文件后缀判断.php结尾肯定是PHP代码:\n# 分配给PHP AddType application/x-httpd-php .php 👉http://localhost/index.php 将PHP的配置文件加载到Apache配置文件中：共同生效在Apache中指定PHP配置文件所在路径 PHPIniDir php.ini 所在路径:\n# 加载PHP配置文件,需要创建php.ini文件 PHPIniDir \u0026#39;C:\\fastwork\\php7\u0026#39; 🍎 修改配置文件后,重启才会生效。\n目前 httpd.conf 中配置项 Define SRVROOT \u0026#34;C:\\fastwork\\Apache24\u0026#34; Listen 80 # 加载PHP LoadModule php7_module \u0026#39;C:\\fastwork\\php7\\php7apache2_4.dll\u0026#39; # 加载PHP配置文件,需要创建php.ini文件 PHPIniDir \u0026#39;C:\\fastwork\\php7\u0026#39; # 分配给PHP AddType application/x-httpd-php .php MySQL安装 下载地址 https://dev.mysql.com/downloads/mysql/ ;\n使用版本 mysql-8.0.17-winx64.zip\n准备配置文件(mysql.ini) 如果没有自动生成需要手动创建\n[mysqld] # Remove leading # and set to the amount of RAM for the most important data # cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%. # innodb_buffer_pool_size = 128M # Remove leading # to turn on a very important data integrity option: logging # changes to the binary log between backups. # log_bin # These are commonly set, remove the # and set as required. # basedir = ..... # datadir = ..... # port = ..... # server_id = ..... basedir = \u0026#34;C:\\fastwork\\mysql\u0026#34; datadir = \u0026#34;C:\\fastwork\\mysql\\data\\\u0026#34; port = 3306 max_connections=1000 max_user_connections=500 wait_timeout=200 character-set-server=utf8 # Remove leading # to set options mainly useful for reporting servers. # The server defaults are faster for transactions and fast SELECTs. # Adjust sizes as needed, experiment to find the optimal values. # join_buffer_size = 128M # sort_buffer_size = 2M # read_rnd_buffer_size = 2M #character-set-server=UTF-8 default-storage-engine=INNODB sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES #忘记密码时使用 #skip-grant-tables #设置协议认证方式(重点啊) default_authentication_plugin=mysql_native_password [mysql] default-character-set=utf8 初始化 mysqld --install 安装多个MySQL,需要用服务名作区分 👉mysqld \u0026ndash;install [服务名] 执行安装, 这里会等待一段时间\nmysqld --initialize-insecure 启动MySQL服务: net start mysql\n登录(初始化密码为NULL,直接回车进入): mysql -u root -p\n修改密码\nALTER USER \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; IDENTIFIED WITH mysql_native_password BY \u0026#39;123456\u0026#39;; PHP连接MySQL数据库👉php.ini php.ini (为 php.ini-development 或者 php.ini-production的副本) 放开mysql扩展\nextension=pdo_mysql PHP中所有的扩展都在 ext 文件夹中，需要指定扩展所在路径：extension_dir\n; On windows: ;extension_dir = \u0026#34;ext\u0026#34; ;增加扩展路径 extension_dir = \u0026#34;C:\\fastwork\\php7\\ext\u0026#34; 时区设置\n; 增加时区 date.timezone = PRC 配置虚拟主机 虚拟主机的分类 在Apache中，可以将虚拟主机划分成两类：\n基于IP的虚拟主机：一台电脑上有多个IP，每个IP对应一个网站 原理：电脑默认只有一个IP，因为通常只配有一个网卡；但是有的电脑（服务器居多）可以配置多个网卡，每个网卡可以绑定一个IP地址。 基于域名的虚拟主机：一台电脑上只有一个IP，但是IP下可以制作多个网站，但是需要给每个网站不同的名字（虚拟主机名. 搭建基于域名的虚拟主机 官方指南👉http://httpd.apache.org/docs/trunk/vhosts/\n在Apache中，虚拟主机的搭建有两种方式：\n在主配置文件中搭建：需要手动开启虚拟主机（基于域名） NameVirtualHost *:80\n在专门的虚拟主机配置文件中配置\n2.1 在主配置文件中加载虚拟主机配置文件(httpd.conf)：虚拟主机配置文件已经开启了虚拟主机NameVirtualHost # Virtual hosts Include conf/extra/httpd-vhosts.conf 2.2\t虚拟主机从配置文件（extra/httpd-vhosts.conf）中配置虚拟主机 \u0026lt;VirtualHost *:80\u0026gt; # 1.增加对应的站点文件夹位置 DocumentRoot \u0026#34;${SRVROOT}/mysite\u0026#34; # 2.增加主机名,文件夹DocumentRoot的别名 ServerName www.mysite.com:80 # 3.hosts(C:\\Windows\\System32\\drivers\\etc)中增加: # 127.0.0.1 www.mysite.com # \u0026lt;Directory \u0026#34;站点目录\u0026#34;\u0026gt; \u0026lt;Directory \u0026#34;${SRVROOT}/mysite\u0026#34;\u0026gt; # 4.设定方式:代表匹配权限的顺序 # 需要给当前设定的站点（目录）访问权限,其中 Order 没有顺序关系：实际顺序有意义 Order Deny,Allow # 5.限定范围:按照顺序执行 Deny from all Allow from all # 6.列表显示(开发环境) # 配置访问列表：当访问是一个目录时候（没有指定具体要访问的文件） # Options Indexes [FollowSymLinks]，如果访问目录，那么列表显示所有文件 Options Indexes FollowSymLinks # 7.指定默认访问文件 # 当用户访问指定目录不指定文件的时候，通常需要系统指定一个默认文件 # DirectoryIndex 默认的文件列表，使用空格分离 DirectoryIndex index.html index.htm index.php \u0026lt;/Directory\u0026gt; \u0026lt;/VirtualHost\u0026gt; 注意：如果说网站中整个网站根目录允许访问，但是其中还有其他文件夹不允许访问：可以增加多个Directory标签，针对不同的文件夹\n一旦启用虚拟主机配置文件，那么默认的主机地址localhost就不再生效，如果想要生效：为localhost增加单独的虚拟主机 # 配置 localhost \u0026lt;VirtualHost *:80\u0026gt; DocumentRoot \u0026#34;${SRVROOT}/htdocs\u0026#34; ServerName localhost \u0026lt;Directory \u0026#34;${SRVROOT}/htdocs\u0026#34;\u0026gt; Order Deny,Allow Deny from all Allow from all Options Indexes FollowSymLinks DirectoryIndex index.html index.htm index.php \u0026lt;/Directory\u0026gt; \u0026lt;/VirtualHost\u0026gt; httpd -S 可查看虚拟机设置信息,方便调试。 ","date":"2019-10-09T13:58:16+08:00","permalink":"/2019/10/09/PHP-1.%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/","title":"1.PHP环境搭建"},{"content":"第一章 对象导论 1.5 复用具体实现 组合(composition)优先于继承\n如果组合是动态发生的，则称为聚合（aggregation)\n组合经常被视为“has-a”（拥有)关系，就像“汽车拥有引擎”一样。\n组合关系UML图： 组合相较于继承显得灵活性，因此在建立新类时，应该首先考虑组合。 1.6 继承 对象可以理解为一种十分方便的工具，是数据和功能的封装。\n继承关系UML图：\n经典的几何形例子：每一种都具有额外的特性和行为。\n子类复制了基类的接口，与基类具有相同的类型👉类型等价性 。导出类如果没有某些具体实现，会直接使用基类接口中的方法，所以说导出类要有自己的实现，这样才有意义，\n使基类与导出类产生差异的两种方法：\n1)直接在导出类中添加新方法：\n2)更重要的一种是改变现有基类的方法和行为（覆盖overriding)\n![1569465066008](/files/Java编程思想/1569465066008.png) 1.6.1 “是一个”与“像是一个”关系 is-a 是一个（替代原则==纯粹替代)：“一个圆形就是一个几何形状” 。即继承只覆盖基类的方法，而不添加新的方法。\nis-like-a 像是一个：导出类添加了新的接口元素，基类中无法访问新添加的方法。比如说空调坏了，改用热力泵制冷，那么这个热力泵就is-like-a空调。\n当然,在看过这个设计之后,很显然会发现,制冷系统这个基类不够一般化,应该将其更名为“温度控制系统”,使其可以包括制热功能,这样我们就可以套用替代原则了。这张图说明了在真实世界中进行设计时可能会发生的事情。\n使用替代原则设计更好。\n1.7 伴随多态的可互换对象 面向对象程序设计语言使用了后期绑定的概念。即当对象发送消息时，被调用的代码直到运行时才能确定。c++使用virtual关键字来实现，某些语言默认情况不是动态绑定的，而在Java中动态绑定是默认行为，并不需要添加额外的关键字来实现多态。 void doSomething(Shape shape){ shape.erase(); //... shape.draw(); } 这个方法可以与任何Shape对话，因此它是独立于任何它要绘制和擦除的对象的具体类型的。当程序中其他地方用到了doSomething()方法：\nCircle circle=new Circle(); Triangle triangle=new Triangle(); Line line=new Line(); doSomething(circle); doSomething(triangle); doSomething(line); 对doSomething()的调用会自动地正确处理，而不管对象的确切类型。\n向上转型（upcasting)：把导出类看做是它的基类的过程。向上转型是安全的，向下转型==几乎==是不安全的。 1.8 单根继承结构 概念：多有的类最终都继承自单一的基类。C++以外所有的OOP语言，Java的终极基类是Object。\n好处：所有对象都先天具备某些功能，使基本操作更容易，此外，使垃圾回收器的实现变得容易很多，而垃圾回收器也正是Java相对C++的重要改进之一。由于所有对象都具有类型信息，因此不会因为无法确定对象的类型而陷入僵局。这对于系统级操作（如异常处理)显得尤其重要，并且给编程带来了更大的灵活性\n其他 Java完全采用动态内存分配方式\n并发编程：同一时刻处理多个任务的思想\n","date":"2019-09-26T13:19:44+08:00","permalink":"/2019/09/26/Java%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3%E7%AC%94%E8%AE%B0/","title":"Java编程思想笔记"},{"content":"一、 Spring Boot 入门 1、Spring Boot 简介 简化Spring应用开发的一个框架；\n整个Spring技术栈的一个大整合；\nJ2EE开发的一站式解决方案；\n2、微服务 2014，martin fowler\n微服务：架构风格（服务微化.\n一个应用应该是一组小型服务；可以通过HTTP的方式进行互通；\n单体应用：ALL IN ONE\n微服务：每一个功能元素最终都是一个可独立替换和独立升级的软件单元；\n详细参照微服务文档\n3、环境准备 http://www.gulixueyuan.com/ 谷粒学院\n环境约束\n–jdk1.8：Spring Boot 推荐jdk1.7及以上；java version \u0026ldquo;1.8.0_112\u0026rdquo;\n–maven3.x：maven 3.3以上版本；Apache Maven 3.3.9\n–IntelliJIDEA2017：IntelliJ IDEA 2017.2.2 x64、STS\n–SpringBoot 1.5.9.RELEASE：1.5.9；\n统一环境；\n1、MAVEN设置； 给maven 的settings.xml配置文件的profiles标签添加\n\u0026lt;profile\u0026gt; \u0026lt;id\u0026gt;jdk-1.8\u0026lt;/id\u0026gt; \u0026lt;activation\u0026gt; \u0026lt;activeByDefault\u0026gt;true\u0026lt;/activeByDefault\u0026gt; \u0026lt;jdk\u0026gt;1.8\u0026lt;/jdk\u0026gt; \u0026lt;/activation\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;maven.compiler.source\u0026gt;1.8\u0026lt;/maven.compiler.source\u0026gt; \u0026lt;maven.compiler.target\u0026gt;1.8\u0026lt;/maven.compiler.target\u0026gt; \u0026lt;maven.compiler.compilerVersion\u0026gt;1.8\u0026lt;/maven.compiler.compilerVersion\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;/profile\u0026gt; 2、IDEA设置 整合maven进来；\n4、Spring Boot HelloWorld 一个功能：\n浏览器发送hello请求，服务器接受请求并处理，响应Hello World字符串；\n1、创建一个maven工程；（jar. 2、导入spring boot相关的依赖 \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.5.9.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; 3、编写一个主程序；启动Spring Boot应用 /** * @SpringBootApplication 来标注一个主程序类，说明这是一个Spring Boot应用 */ @SpringBootApplication public class HelloWorldMainApplication { public static void main(String[] args) { // Spring应用启动起来 SpringApplication.run(HelloWorldMainApplication.class,args); } } 4、编写相关的Controller、Service @Controller public class HelloController { @ResponseBody @RequestMapping(\u0026#34;/hello\u0026#34;) public String hello(){ return \u0026#34;Hello World!\u0026#34;; } } 5、运行主程序测试 6、简化部署 \u0026lt;!-- 这个插件，可以将应用打包成一个可执行的jar包；--\u0026gt; \u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; 将这个应用打成jar包，直接使用java -jar的命令进行执行；\n5、Hello World探究 1、POM文件 1、父项目 \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.5.9.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; 他的父项目是 \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-dependencies\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.5.9.RELEASE\u0026lt;/version\u0026gt; \u0026lt;relativePath\u0026gt;../../spring-boot-dependencies\u0026lt;/relativePath\u0026gt; \u0026lt;/parent\u0026gt; 他来真正管理Spring Boot应用里面的所有依赖版本； Spring Boot的版本仲裁中心；\n以后我们导入依赖默认是不需要写版本；（没有在dependencies里面管理的依赖自然需要声明版本号.\n2、启动器 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; spring-boot-starter-==web==：\n​\tspring-boot-starter：spring-boot场景启动器；帮我们导入了web模块正常运行所依赖的组件；\nSpring Boot将所有的功能场景都抽取出来，做成一个个的starters（启动器），只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来。要用什么功能就导入什么场景的启动器\n2、主程序类，主入口类 /** * @SpringBootApplication 来标注一个主程序类，说明这是一个Spring Boot应用 */ @SpringBootApplication public class HelloWorldMainApplication { public static void main(String[] args) { // Spring应用启动起来 SpringApplication.run(HelloWorldMainApplication.class,args); } } @SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类，SpringBoot就应该运行这个类的main方法来启动SpringBoot应用；\n@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { @SpringBootConfiguration:Spring Boot的配置类；\n​\t标注在某个类上，表示这是一个Spring Boot的配置类；\n​\t@Configuration:配置类上来标注这个注解；\n​\t配置类 \u0026mdash;\u0026ndash; 配置文件；配置类也是容器中的一个组件；@Component\n@EnableAutoConfiguration：开启自动配置功能；\n​\t以前我们需要配置的东西，Spring Boot帮我们自动配置；@EnableAutoConfiguration告诉SpringBoot开启自动配置功能；这样自动配置才能生效；\n@AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ​ @AutoConfigurationPackage：自动配置包\n​\t@Import(AutoConfigurationPackages.Registrar.class)：\n​\tSpring的底层注解@Import，给容器中导入一个组件；导入的组件由AutoConfigurationPackages.Registrar.class；\n==将主配置类（@SpringBootApplication标注的类）的所在包及下面所有子包里面的所有组件扫描到Spring容器；==\n​\t@Import(EnableAutoConfigurationImportSelector.class)；\n​\t给容器中导入组件？\n​\tEnableAutoConfigurationImportSelector：导入哪些组件的选择器；\n​\t将所有需要导入的组件以全类名的方式返回；这些组件就会被添加到容器中；\n​\t会给容器中导入非常多的自动配置类（xxxAutoConfiguration）；就是给容器中导入这个场景需要的所有组件，并配置好这些组件；\t有了自动配置类，免去了我们手动编写配置注入功能组件等的工作；\n​\tSpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,classLoader)；\n==Spring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值，将这些值作为自动配置类导入到容器中，自动配置类就生效，帮我们进行自动配置工作；==以前我们需要自己配置的东西，自动配置类都帮我们；\nJ2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-1.5.9.RELEASE.jar；\n​\n==Spring注解版（谷粒学院）==\n6、使用Spring Initializer快速创建Spring Boot项目 1、IDEA：使用 Spring Initializer快速创建项目 IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目；\n选择我们需要的模块；向导会联网创建Spring Boot项目；\n默认生成的Spring Boot项目；\n主程序已经生成好了，我们只需要我们自己的逻辑 resources文件夹中目录结构 static：保存所有的静态资源； js css images； templates：保存所有的模板页面；（Spring Boot默认jar包使用嵌入式的Tomcat，默认不支持JSP页面）；可以使用模板引擎（freemarker、thymeleaf）； application.properties：Spring Boot应用的配置文件；可以修改一些默认设置； 2、STS使用 Spring Starter Project快速创建项目 二、配置文件 1、配置文件 SpringBoot使用一个全局的配置文件，配置文件名是固定的；\n•application.properties\n•application.yml\n配置文件的作用：修改SpringBoot自动配置的默认值；SpringBoot在底层都给我们自动配置好；\nYAML（YAML Ain\u0026rsquo;t Markup Language.\n​\tYAML A Markup Language：是一个标记语言\n​\tYAML isn\u0026rsquo;t Markup Language：不是一个标记语言；\n标记语言：\n​\t以前的配置文件；大多都使用的是 xxxx.xml文件；\n​\tYAML：以数据为中心，比json、xml等更适合做配置文件；\n​\tYAML：配置例子\nserver: port: 8081 ​\tXML：\n\u0026lt;server\u0026gt; \u0026lt;port\u0026gt;8081\u0026lt;/port\u0026gt; \u0026lt;/server\u0026gt; 2、YAML语法： 1、基本语法 k:(空格)v：表示一对键值对（空格必须有）；\n以空格的缩进来控制层级关系；只要是左对齐的一列数据，都是同一个层级的\nserver: port: 8081 path: /hello 属性和值也是大小写敏感；\n2、值的写法 字面量：普通的值（数字，字符串，布尔. ​\tk: v：字面直接来写；\n​\t字符串默认不用加上单引号或者双引号；\n​\t\u0026ldquo;\u0026quot;：双引号；不会转义字符串里面的特殊字符；特殊字符会作为本身想表示的意思\n​\tname: \u0026ldquo;zhangsan \\n lisi\u0026rdquo;：输出；zhangsan 换行 lisi\n​\t\u0026lsquo;\u0026rsquo;：单引号；会转义特殊字符，特殊字符最终只是一个普通的字符串数据\n​\tname: ‘zhangsan \\n lisi’：输出；zhangsan \\n lisi\n对象、Map（属性和值）（键值对）： ​\tk: v：在下一行来写对象的属性和值的关系；注意缩进\n​\t对象还是k: v的方式\nfriends: lastName: zhangsan age: 20 行内写法：\nfriends: {lastName: zhangsan,age: 18} 数组（List、Set）： 用- 值表示数组中的一个元素\npets: - cat - dog - pig 行内写法\npets: [cat,dog,pig] 3、配置文件值注入 配置文件\nperson: lastName: hello age: 18 boss: false birth: 2017/12/12 maps: {k1: v1,k2: 12} lists: - lisi - zhaoliu dog: name: 小狗 age: 12 javaBean：\n/** * 将配置文件中配置的每一个属性的值，映射到这个组件中 * @ConfigurationProperties：告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定； * prefix = \u0026#34;person\u0026#34;：配置文件中哪个下面的所有属性进行一一映射 * * 只有这个组件是容器中的组件，才能容器提供的@ConfigurationProperties功能； * */ @Component @ConfigurationProperties(prefix = \u0026#34;person\u0026#34;) public class Person { private String lastName; private Integer age; private Boolean boss; private Date birth; private Map\u0026lt;String,Object\u0026gt; maps; private List\u0026lt;Object\u0026gt; lists; private Dog dog; 我们可以导入配置文件处理器，以后编写配置就有提示了\n\u0026lt;!--导入配置文件处理器，配置文件进行绑定就会有提示--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-configuration-processor\u0026lt;/artifactId\u0026gt; \u0026lt;optional\u0026gt;true\u0026lt;/optional\u0026gt; \u0026lt;/dependency\u0026gt; 1、properties配置文件在idea中默认utf-8可能会乱码 调整\n2、@Value获取值和@ConfigurationProperties获取值比较 @ConfigurationProperties @Value 功能 批量注入配置文件中的属性 一个个指定 松散绑定（松散语法） 支持 不支持 SpEL 不支持 支持 JSR303数据校验 支持 不支持 复杂类型封装 支持 不支持 配置文件yml还是properties他们都能获取到值；\n如果说，我们只是在某个业务逻辑中需要获取一下配置文件中的某项值，使用@Value；\n如果说，我们专门编写了一个javaBean来和配置文件进行映射，我们就直接使用@ConfigurationProperties；\n3、配置文件注入值数据校验 @Component @ConfigurationProperties(prefix = \u0026#34;person\u0026#34;) @Validated public class Person { /** * \u0026lt;bean class=\u0026#34;Person\u0026#34;\u0026gt; * \u0026lt;property name=\u0026#34;lastName\u0026#34; value=\u0026#34;字面量/${key}从环境变量、配置文件中获取值/#{SpEL}\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; * \u0026lt;bean/\u0026gt; */ //lastName必须是邮箱格式 @Email //@Value(\u0026#34;${person.last-name}\u0026#34;) private String lastName; //@Value(\u0026#34;#{11*2}\u0026#34;) private Integer age; //@Value(\u0026#34;true\u0026#34;) private Boolean boss; private Date birth; private Map\u0026lt;String,Object\u0026gt; maps; private List\u0026lt;Object\u0026gt; lists; private Dog dog; 4、@PropertySource\u0026amp;@ImportResource\u0026amp;@Bean @PropertySource：加载指定的配置文件；\n/** * 将配置文件中配置的每一个属性的值，映射到这个组件中 * @ConfigurationProperties：告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定； * prefix = \u0026#34;person\u0026#34;：配置文件中哪个下面的所有属性进行一一映射 * * 只有这个组件是容器中的组件，才能容器提供的@ConfigurationProperties功能； * @ConfigurationProperties(prefix = \u0026#34;person\u0026#34;)默认从全局配置文件中获取值； * */ @PropertySource(value = {\u0026#34;classpath:person.properties\u0026#34;}) @Component @ConfigurationProperties(prefix = \u0026#34;person\u0026#34;) //@Validated public class Person { /** * \u0026lt;bean class=\u0026#34;Person\u0026#34;\u0026gt; * \u0026lt;property name=\u0026#34;lastName\u0026#34; value=\u0026#34;字面量/${key}从环境变量、配置文件中获取值/#{SpEL}\u0026#34;\u0026gt;\u0026lt;/property\u0026gt; * \u0026lt;bean/\u0026gt; */ //lastName必须是邮箱格式 // @Email //@Value(\u0026#34;${person.last-name}\u0026#34;) private String lastName; //@Value(\u0026#34;#{11*2}\u0026#34;) private Integer age; //@Value(\u0026#34;true\u0026#34;) private Boolean boss; @ImportResource：导入Spring的配置文件，让配置文件里面的内容生效；\nSpring Boot里面没有Spring的配置文件，我们自己编写的配置文件，也不能自动识别；\n想让Spring的配置文件生效，加载进来；@ImportResource标注在一个配置类上\n@ImportResource(locations = {\u0026#34;classpath:beans.xml\u0026#34;}) 导入Spring的配置文件让其生效 不来编写Spring的配置文件\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;beans xmlns=\u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\u0026#34;\u0026gt; \u0026lt;bean id=\u0026#34;helloService\u0026#34; class=\u0026#34;com.atguigu.springboot.service.HelloService\u0026#34;\u0026gt;\u0026lt;/bean\u0026gt; \u0026lt;/beans\u0026gt; SpringBoot推荐给容器中添加组件的方式；推荐使用全注解的方式\n1、配置类**@Configuration**\u0026mdash;\u0026mdash;\u0026gt;Spring配置文件\n2、使用**@Bean**给容器中添加组件\n/** * @Configuration：指明当前类是一个配置类；就是来替代之前的Spring配置文件 * * 在配置文件中用\u0026lt;bean\u0026gt;\u0026lt;bean/\u0026gt;标签添加组件 * */ @Configuration public class MyAppConfig { //将方法的返回值添加到容器中；容器中这个组件默认的id就是方法名 @Bean public HelloService helloService02(){ System.out.println(\u0026#34;配置类@Bean给容器中添加组件了...\u0026#34;); return new HelloService(); } } ##4、配置文件占位符\n1、随机数 ${random.value}、${random.int}、${random.long} ${random.int(10)}、${random.int[1024,65536]} 2、占位符获取之前配置的值，如果没有可以是用:指定默认值 person.last-name=张三${random.uuid} person.age=${random.int} person.birth=2017/12/15 person.boss=false person.maps.k1=v1 person.maps.k2=14 person.lists=a,b,c person.dog.name=${person.hello:hello}_dog person.dog.age=15 5、Profile 1、多Profile文件 我们在主配置文件编写的时候，文件名可以是 application-{profile}.properties/yml\n默认使用application.properties的配置；\n2、yml支持多文档块方式 server: port: 8081 spring: profiles: active: prod --- server: port: 8083 spring: profiles: dev --- server: port: 8084 spring: profiles: prod #指定属于哪个环境 3、激活指定profile ​\t1、在配置文件中指定 spring.profiles.active=dev\n​\t2、命令行：\n​\tjava -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar \u0026ndash;spring.profiles.active=dev；\n​\t可以直接在测试的时候，配置传入命令行参数\n​\t3、虚拟机参数；\n​\t-Dspring.profiles.active=dev\n6、配置文件加载位置 springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件\n–file:./config/\n–file:./\n–classpath:/config/\n–classpath:/\n优先级由高到底，高优先级的配置会覆盖低优先级的配置；\nSpringBoot会从这四个位置全部加载主配置文件；互补配置；\n==我们还可以通过spring.config.location来改变默认的配置文件位置==\n项目打包好以后，我们可以使用命令行参数的形式，启动项目的时候来指定配置文件的新位置；指定配置文件和默认加载的这些配置文件共同起作用形成互补配置；\njava -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar \u0026ndash;spring.config.location=G:/application.properties\n7、外部配置加载顺序 ==SpringBoot也可以从以下位置加载配置； 优先级从高到低；高优先级的配置覆盖低优先级的配置，所有的配置会形成互补配置==\n1.命令行参数\n所有的配置都可以在命令行上进行指定\njava -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar \u0026ndash;server.port=8087 \u0026ndash;server.context-path=/abc\n多个配置用空格分开； \u0026ndash;配置项=值\n2.来自java:comp/env的JNDI属性\n3.Java系统属性（System.getProperties().\n4.操作系统环境变量\n5.RandomValuePropertySource配置的random.*属性值\n==由jar包外向jar包内进行寻找；==\n==优先加载带profile==\n6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件\n7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件\n==再来加载不带profile==\n8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件\n9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件\n10.@Configuration注解类上的@PropertySource\n11.通过SpringApplication.setDefaultProperties指定的默认属性\n所有支持的配置加载来源；\n参考官方文档\n8、自动配置原理 配置文件到底能写什么？怎么写？自动配置原理；\n配置文件能配置的属性参照\n1、自动配置原理： 1）SpringBoot启动的时候加载主配置类，开启了自动配置功能 ==@EnableAutoConfiguration==\n2）@EnableAutoConfiguration 作用：\n利用EnableAutoConfigurationImportSelector给容器中导入一些组件？\n可以查看selectImports()方法的内容；\nList configurations = getCandidateConfigurations(annotationMetadata, attributes);获取候选的配置\nSpringFactoriesLoader.loadFactoryNames() 扫描所有jar包类路径下 META-INF/spring.factories 把扫描到的这些文件的内容包装成properties对象 从properties中获取到EnableAutoConfiguration.class类（类名）对应的值，然后把他们添加在容器中 ==将 类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中；==\n# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\\ org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\\ org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\\ org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\\ org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\\ org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\\ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\\ org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\\ org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\\ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\\ org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\\ org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\\ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\\ org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\\ org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\\ org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\\ org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\\ org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\\ org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\\ org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\\ org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\\ org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\\ org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\\ org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\\ org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\\ org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\\ org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\\ org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\\ org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\\ org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\\ org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\\ org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\\ org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\\ org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\\ org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\\ org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\\ org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\\ org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\\ org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\\ org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\\ org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\\ org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\\ org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\\ org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\\ org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\\ org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\\ org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\\ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\\ org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\\ org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\\ org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\\ org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\\ org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\\ org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration 每一个这样的 xxxAutoConfiguration类都是容器中的一个组件，都加入到容器中；用他们来做自动配置；\n3）每一个自动配置类进行自动配置功能；\n4）以**HttpEncodingAutoConfiguration（Http编码自动配置）**为例解释自动配置原理；\n@Configuration //表示这是一个配置类，以前编写的配置文件一样，也可以给容器中添加组件 @EnableConfigurationProperties(HttpEncodingProperties.class) //启动指定类的ConfigurationProperties功能；将配置文件中对应的值和HttpEncodingProperties绑定起来；并把HttpEncodingProperties加入到ioc容器中 @ConditionalOnWebApplication //Spring底层@Conditional注解（Spring注解版），根据不同的条件，如果满足指定的条件，整个配置类里面的配置就会生效； 判断当前应用是否是web应用，如果是，当前配置类生效 @ConditionalOnClass(CharacterEncodingFilter.class) //判断当前项目有没有这个类CharacterEncodingFilter；SpringMVC中进行乱码解决的过滤器； @ConditionalOnProperty(prefix = \u0026#34;spring.http.encoding\u0026#34;, value = \u0026#34;enabled\u0026#34;, matchIfMissing = true) //判断配置文件中是否存在某个配置 spring.http.encoding.enabled；如果不存在，判断也是成立的 //即使我们配置文件中不配置pring.http.encoding.enabled=true，也是默认生效的； public class HttpEncodingAutoConfiguration { //他已经和SpringBoot的配置文件映射了 private final HttpEncodingProperties properties; //只有一个有参构造器的情况下，参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) { this.properties = properties; } @Bean //给容器中添加一个组件，这个组件的某些值需要从properties中获取 @ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件？ public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); return filter; } 根据当前不同的条件判断，决定这个配置类是否生效？\n一但这个配置类生效；这个配置类就会给容器中添加各种组件；这些组件的属性是从对应的properties类中获取的，这些类里面的每一个属性又是和配置文件绑定的；\n5）所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘；配置文件能配置什么就可以参照某个功能对应的这个属性类\n@ConfigurationProperties(prefix = \u0026#34;spring.http.encoding\u0026#34;) //从配置文件中获取指定的值和bean的属性进行绑定 public class HttpEncodingProperties { public static final Charset DEFAULT_CHARSET = Charset.forName(\u0026#34;UTF-8\u0026#34;); 精髓：\n​\t1）SpringBoot启动会加载大量的自动配置类\n​\t2）我们看我们需要的功能有没有SpringBoot默认写好的自动配置类；\n​\t3）我们再来看这个自动配置类中到底配置了哪些组件；（只要我们要用的组件有，我们就不需要再来配置了）\n​\t4）给容器中自动配置类添加组件的时候，会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值；\nxxxxAutoConfigurartion：自动配置类；\n给容器中添加组件\nxxxxProperties:封装配置文件中相关属性；\n2、细节 1、@Conditional派生注解（Spring注解版原生的@Conditional作用. 作用：必须是@Conditional指定的条件成立，才给容器中添加组件，配置配里面的所有内容才生效；\n@Conditional扩展注解 作用（判断是否满足当前指定条件） @ConditionalOnJava 系统的java版本是否符合要求 @ConditionalOnBean 容器中存在指定Bean； @ConditionalOnMissingBean 容器中不存在指定Bean； @ConditionalOnExpression 满足SpEL表达式指定 @ConditionalOnClass 系统中有指定的类 @ConditionalOnMissingClass 系统中没有指定的类 @ConditionalOnSingleCandidate 容器中只有一个指定的Bean，或者这个Bean是首选Bean @ConditionalOnProperty 系统中指定的属性是否有指定的值 @ConditionalOnResource 类路径下是否存在指定资源文件 @ConditionalOnWebApplication 当前是web环境 @ConditionalOnNotWebApplication 当前不是web环境 @ConditionalOnJndi JNDI存在指定项 自动配置类必须在一定的条件下才能生效；\n我们怎么知道哪些自动配置类生效；\n==我们可以通过启用 debug=true属性；来让控制台打印自动配置报告==，这样我们就可以很方便的知道哪些自动配置类生效；\n========================= AUTO-CONFIGURATION REPORT ========================= Positive matches:（自动配置类启用的. ----------------- DispatcherServletAutoConfiguration matched: - @ConditionalOnClass found required class \u0026#39;org.springframework.web.servlet.DispatcherServlet\u0026#39;; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) - @ConditionalOnWebApplication (required) found StandardServletEnvironment (OnWebApplicationCondition) Negative matches:（没有启动，没有匹配成功的自动配置类. ----------------- ActiveMQAutoConfiguration: Did not match: - @ConditionalOnClass did not find required classes \u0026#39;javax.jms.ConnectionFactory\u0026#39;, \u0026#39;org.apache.activemq.ActiveMQConnectionFactory\u0026#39; (OnClassCondition) AopAutoConfiguration: Did not match: - @ConditionalOnClass did not find required classes \u0026#39;org.aspectj.lang.annotation.Aspect\u0026#39;, \u0026#39;org.aspectj.lang.reflect.Advice\u0026#39; (OnClassCondition) 三、日志 1、日志框架 小张；开发一个大型系统；\n​\t1、System.out.println(\u0026rdquo;\u0026quot;)；将关键数据打印在控制台；去掉？写在一个文件？\n​\t2、框架来记录系统的一些运行时信息；日志框架 ； zhanglogging.jar；\n​\t3、高大上的几个功能？异步模式？自动归档？xxxx？ zhanglogging-good.jar？\n​\t4、将以前框架卸下来？换上新的框架，重新修改之前相关的API；zhanglogging-prefect.jar；\n​\t5、JDBC\u0026mdash;数据库驱动；\n​\t写了一个统一的接口层；日志门面（日志的一个抽象层）；logging-abstract.jar；\n​\t给项目中导入具体的日志实现就行了；我们之前的日志框架都是实现的抽象层；\n市面上的日志框架；\nJUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j\u0026hellip;.\n日志门面 （日志的抽象层） 日志实现 JCL（Jakarta Commons Logging） SLF4j（Simple Logging Facade for Java） jboss-logging Log4j JUL（java.util.logging） Log4j2 Logback 左边选一个门面（抽象层）右边来选一个实现；\n日志门面： SLF4J；\n日志实现：Logback；\nSpringBoot：底层是Spring框架，Spring框架默认是用JCL；‘\n​\t==SpringBoot选用 SLF4j和logback；==\n2、SLF4j使用 1、如何在系统中使用SLF4j https://www.slf4j.org 以后开发的时候，日志记录方法的调用，不应该来直接调用日志的实现类，而是调用日志抽象层里面的方法；\n给系统里面导入slf4j的jar和 logback的实现jar\nimport org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info(\u0026#34;Hello World\u0026#34;); } } 图示；\n每一个日志的实现框架都有自己的配置文件。使用slf4j以后，配置文件还是做成日志实现框架自己本身的配置文件；\n2、遗留问题 a（slf4j+logback）: Spring（commons-logging）Hibernate（jboss-logging）MyBatis、xxxx\n统一日志记录，即使是别的框架和我一起统一使用slf4j进行输出？\n如何让系统中所有的日志都统一到slf4j；\n==1、将系统中其他日志框架先排除出去；==\n==2、用中间包来替换原有的日志框架；==\n==3、我们导入slf4j其他的实现==\n3、SpringBoot日志关系 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; SpringBoot使用它来做日志功能；\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-logging\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 底层依赖关系\n总结：\n​\t1）SpringBoot底层也是使用slf4j+logback的方式进行日志记录\n​\t2）SpringBoot也把其他的日志都替换成了slf4j；\n​\t3）中间替换包？\n@SuppressWarnings(\u0026#34;rawtypes\u0026#34;) public abstract class LogFactory { static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = \u0026#34;http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j\u0026#34;; static LogFactory logFactory = new SLF4JLogFactory(); ​\t4）如果我们要引入其他框架？一定要把这个框架的默认日志依赖移除掉？\n​\tSpring框架用的是commons-logging；\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-core\u0026lt;/artifactId\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;groupId\u0026gt;commons-logging\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;commons-logging\u0026lt;/artifactId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; ==SpringBoot能自动适配所有的日志，而且底层使用slf4j+logback的方式记录日志，引入其他框架的时候，只需要把这个框架依赖的日志框架排除掉即可；==\n4、日志使用； 1、默认配置 SpringBoot默认帮我们配置好了日志；\n//记录器 Logger logger = LoggerFactory.getLogger(getClass()); @Test public void contextLoads() { //System.out.println(); //日志的级别； //由低到高 trace\u0026lt;debug\u0026lt;info\u0026lt;warn\u0026lt;error //可以调整输出的日志级别；日志就只会在这个级别以以后的高级别生效 logger.trace(\u0026#34;这是trace日志...\u0026#34;); logger.debug(\u0026#34;这是debug日志...\u0026#34;); //SpringBoot默认给我们使用的是info级别的，没有指定级别的就用SpringBoot默认规定的级别；root级别 logger.info(\u0026#34;这是info日志...\u0026#34;); logger.warn(\u0026#34;这是warn日志...\u0026#34;); logger.error(\u0026#34;这是error日志...\u0026#34;); } 日志输出格式： %d表示日期时间， %thread表示线程名， %-5level：级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符，否则按照句点分割。 %msg：日志消息， %n是换行符 --\u0026gt; %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n SpringBoot修改日志的默认配置\nlogging.level.com.atguigu=trace #logging.path= # 不指定路径在当前项目下生成springboot.log日志 # 可以指定完整的路径； #logging.file=G:/springboot.log # 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹；使用 spring.log 作为默认文件 logging.path=/spring/log # 在控制台输出的日志的格式 logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n # 指定文件中日志输出的格式 logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n logging.file logging.path Example Description (none) (none) 只在控制台输出 指定文件名 (none) my.log 输出日志到my.log文件 (none) 指定目录 /var/log 输出到指定目录的 spring.log 文件中 2、指定配置 给类路径下放上每个日志框架自己的配置文件即可；SpringBoot就不使用他默认配置的了\nLogging System Customization Logback logback-spring.xml, logback-spring.groovy, logback.xml or logback.groovy Log4j2 log4j2-spring.xml or log4j2.xml JDK (Java Util Logging) logging.properties logback.xml：直接就被日志框架识别了；\nlogback-spring.xml：日志框架就不直接加载日志的配置项，由SpringBoot解析日志配置，可以使用SpringBoot的高级Profile功能\n\u0026lt;springProfile name=\u0026#34;staging\u0026#34;\u0026gt; \u0026lt;!-- configuration to be enabled when the \u0026#34;staging\u0026#34; profile is active --\u0026gt; 可以指定某段配置只在某个环境下生效 \u0026lt;/springProfile\u0026gt; 如：\n\u0026lt;appender name=\u0026#34;stdout\u0026#34; class=\u0026#34;ch.qos.logback.core.ConsoleAppender\u0026#34;\u0026gt; \u0026lt;!-- 日志输出格式： %d表示日期时间， %thread表示线程名， %-5level：级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符，否则按照句点分割。 %msg：日志消息， %n是换行符 --\u0026gt; \u0026lt;layout class=\u0026#34;ch.qos.logback.classic.PatternLayout\u0026#34;\u0026gt; \u0026lt;springProfile name=\u0026#34;dev\u0026#34;\u0026gt; \u0026lt;pattern\u0026gt;%d{yyyy-MM-dd HH:mm:ss.SSS} ----\u0026gt; [%thread] ---\u0026gt; %-5level %logger{50} - %msg%n\u0026lt;/pattern\u0026gt; \u0026lt;/springProfile\u0026gt; \u0026lt;springProfile name=\u0026#34;!dev\u0026#34;\u0026gt; \u0026lt;pattern\u0026gt;%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n\u0026lt;/pattern\u0026gt; \u0026lt;/springProfile\u0026gt; \u0026lt;/layout\u0026gt; \u0026lt;/appender\u0026gt; 如果使用logback.xml作为日志配置文件，还要使用profile功能，会有以下错误\nno applicable action for [springProfile]\n5、切换日志框架 可以按照slf4j的日志适配图，进行相关的切换；\nslf4j+log4j的方式；\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;artifactId\u0026gt;logback-classic\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;ch.qos.logback\u0026lt;/groupId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;artifactId\u0026gt;log4j-over-slf4j\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.slf4j\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;slf4j-log4j12\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 切换为log4j2\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-logging\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-log4j2\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 四、Web开发 1、简介 使用SpringBoot；\n1）创建SpringBoot应用，选中我们需要的模块；\n2）SpringBoot已经默认将这些场景配置好了，只需要在配置文件中指定少量配置就可以运行起来\n3）自己编写业务代码；\n自动配置原理？\n这个场景SpringBoot帮我们配置了什么？能不能修改？能修改哪些配置？能不能扩展？xxx\nxxxxAutoConfiguration：帮我们给容器中自动配置组件； xxxxProperties:配置类来封装配置文件的内容； 2、SpringBoot对静态资源的映射规则； @ConfigurationProperties(prefix = \u0026#34;spring.resources\u0026#34;, ignoreUnknownFields = false) public class ResourceProperties implements ResourceLoaderAware { //可以设置和静态资源有关的参数，缓存时间等 WebMvcAuotConfiguration： @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug(\u0026#34;Default resource handling disabled\u0026#34;); return; } Integer cachePeriod = this.resourceProperties.getCachePeriod(); if (!registry.hasMappingForPattern(\u0026#34;/webjars/**\u0026#34;)) { customizeResourceHandlerRegistration( registry.addResourceHandler(\u0026#34;/webjars/**\u0026#34;) .addResourceLocations( \u0026#34;classpath:/META-INF/resources/webjars/\u0026#34;) .setCachePeriod(cachePeriod)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); //静态资源文件夹映射 if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration( registry.addResourceHandler(staticPathPattern) .addResourceLocations( this.resourceProperties.getStaticLocations()) .setCachePeriod(cachePeriod)); } } //配置欢迎页映射 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping( ResourceProperties resourceProperties) { return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), this.mvcProperties.getStaticPathPattern()); } //配置喜欢的图标 @Configuration @ConditionalOnProperty(value = \u0026#34;spring.mvc.favicon.enabled\u0026#34;, matchIfMissing = true) public static class FaviconConfiguration { private final ResourceProperties resourceProperties; public FaviconConfiguration(ResourceProperties resourceProperties) { this.resourceProperties = resourceProperties; } @Bean public SimpleUrlHandlerMapping faviconHandlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); //所有 **/favicon.ico mapping.setUrlMap(Collections.singletonMap(\u0026#34;**/favicon.ico\u0026#34;, faviconRequestHandler())); return mapping; } @Bean public ResourceHttpRequestHandler faviconRequestHandler() { ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); requestHandler .setLocations(this.resourceProperties.getFaviconLocations()); return requestHandler; } } ==1）所有 /webjars/** ，都去 classpath:/META-INF/resources/webjars/ 找资源；==\n​\twebjars：以jar包的方式引入静态资源；\nhttp://www.webjars.org/\nlocalhost:8080/webjars/jquery/3.3.1/jquery.js\n\u0026lt;!--引入jquery-webjar--\u0026gt;在访问的时候只需要写webjars下面资源的名称即可 \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.webjars\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jquery\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;3.3.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; ==2）\u0026quot;/**\u0026quot; 访问当前项目的任何资源，都去（静态资源的文件夹）找映射==\n\u0026#34;classpath:/META-INF/resources/\u0026#34;, \u0026#34;classpath:/resources/\u0026#34;, \u0026#34;classpath:/static/\u0026#34;, \u0026#34;classpath:/public/\u0026#34; \u0026#34;/\u0026#34;：当前项目的根路径 localhost:8080/abc === 去静态资源文件夹里面找abc\n==3）欢迎页； 静态资源文件夹下的所有index.html页面；被\u0026quot;/**\u0026ldquo;映射；==\n​\tlocalhost:8080/ 找index页面\n==4）所有的 **/favicon.ico 都是在静态资源文件下找；==\n3、模板引擎 JSP、Velocity、Freemarker、Thymeleaf\nSpringBoot推荐的Thymeleaf；\n语法更简单，功能更强大；\n1、引入thymeleaf； \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-thymeleaf\u0026lt;/artifactId\u0026gt; 2.1.6 \u0026lt;/dependency\u0026gt; 切换thymeleaf版本 \u0026lt;properties\u0026gt; \u0026lt;thymeleaf.version\u0026gt;3.0.9.RELEASE\u0026lt;/thymeleaf.version\u0026gt; \u0026lt;!-- 布局功能的支持程序 thymeleaf3主程序 layout2以上版本 --\u0026gt; \u0026lt;!-- thymeleaf2 layout1--\u0026gt; \u0026lt;thymeleaf-layout-dialect.version\u0026gt;2.2.2\u0026lt;/thymeleaf-layout-dialect.version\u0026gt; \u0026lt;/properties\u0026gt; 2、Thymeleaf使用 @ConfigurationProperties(prefix = \u0026#34;spring.thymeleaf\u0026#34;) public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING = Charset.forName(\u0026#34;UTF-8\u0026#34;); private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf(\u0026#34;text/html\u0026#34;); public static final String DEFAULT_PREFIX = \u0026#34;classpath:/templates/\u0026#34;; public static final String DEFAULT_SUFFIX = \u0026#34;.html\u0026#34;; // 只要我们把HTML页面放在classpath:/templates/，thymeleaf就能自动渲染；\n使用：\n1、导入thymeleaf的名称空间\n\u0026lt;html lang=\u0026#34;en\u0026#34; xmlns:th=\u0026#34;http://www.thymeleaf.org\u0026#34;\u0026gt; 2、使用thymeleaf语法；\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34; xmlns:th=\u0026#34;http://www.thymeleaf.org\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;成功！\u0026lt;/h1\u0026gt; \u0026lt;!--th:text 将div里面的文本内容设置为 --\u0026gt; \u0026lt;div th:text=\u0026#34;${hello}\u0026#34;\u0026gt;这是显示欢迎信息\u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 3、语法规则 1）th:text；改变当前元素里面的文本内容；\n​\tth：任意html属性；来替换原生属性的值\n2）表达式？\nSimple expressions:（表达式语法. Variable Expressions: ${...}：获取变量值；OGNL； 1）获取对象的属性、调用方法 2）使用内置的基本对象： #ctx : the context object. #vars: the context variables. #locale : the context locale. #request : (only in Web Contexts) the HttpServletRequest object. #response : (only in Web Contexts) the HttpServletResponse object. #session : (only in Web Contexts) the HttpSession object. #servletContext : (only in Web Contexts) the ServletContext object. ${session.foo} 3）内置的一些工具对象： #execInfo : information about the template being processed. #messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax. #uris : methods for escaping parts of URLs/URIs #conversions : methods for executing the configured conversion service (if any). #dates : methods for java.util.Date objects: formatting, component extraction, etc. #calendars : analogous to #dates , but for java.util.Calendar objects. #numbers : methods for formatting numeric objects. #strings : methods for String objects: contains, startsWith, prepending/appending, etc. #objects : methods for objects in general. #bools : methods for boolean evaluation. #arrays : methods for arrays. #lists : methods for lists. #sets : methods for sets. #maps : methods for maps. #aggregates : methods for creating aggregates on arrays or collections. #ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration). Selection Variable Expressions: *{...}：选择表达式：和${}在功能上是一样； 补充：配合 th:object=\u0026#34;${session.user}： \u0026lt;div th:object=\u0026#34;${session.user}\u0026#34;\u0026gt; \u0026lt;p\u0026gt;Name: \u0026lt;span th:text=\u0026#34;*{firstName}\u0026#34;\u0026gt;Sebastian\u0026lt;/span\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;Surname: \u0026lt;span th:text=\u0026#34;*{lastName}\u0026#34;\u0026gt;Pepper\u0026lt;/span\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;Nationality: \u0026lt;span th:text=\u0026#34;*{nationality}\u0026#34;\u0026gt;Saturn\u0026lt;/span\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; Message Expressions: #{...}：获取国际化内容 Link URL Expressions: @{...}：定义URL； @{/order/process(execId=${execId},execType=\u0026#39;FAST\u0026#39;)} Fragment Expressions: ~{...}：片段引用表达式 \u0026lt;div th:insert=\u0026#34;~{commons :: main}\u0026#34;\u0026gt;...\u0026lt;/div\u0026gt; Literals（字面量. Text literals: \u0026#39;one text\u0026#39; , \u0026#39;Another one!\u0026#39; ,… Number literals: 0 , 34 , 3.0 , 12.3 ,… Boolean literals: true , false Null literal: null Literal tokens: one , sometext , main ,… Text operations:（文本操作. String concatenation: + Literal substitutions: |The name is ${name}| Arithmetic operations:（数学运算. Binary operators: + , - , * , / , % Minus sign (unary operator): - Boolean operations:（布尔运算. Binary operators: and , or Boolean negation (unary operator): ! , not Comparisons and equality:（比较运算. Comparators: \u0026gt; , \u0026lt; , \u0026gt;= , \u0026lt;= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) Conditional operators:条件运算（三元运算符. If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) Special tokens: No-Operation: _ 4、SpringMVC自动配置 https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications\n1）Spring MVC auto-configuration Spring Boot 自动配置好了SpringMVC\n以下是SpringBoot对SpringMVC的默认配置:==（WebMvcAutoConfiguration）==\nInclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.\n自动配置了ViewResolver（视图解析器：根据方法的返回值得到视图对象（View），视图对象决定如何渲染（转发？重定向？）. ContentNegotiatingViewResolver：组合所有的视图解析器的； ==如何定制：我们可以自己给容器中添加一个视图解析器；自动的将其组合进来；== Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径,webjars\nStatic index.html support）静态首页访问\nCustom Favicon support (see below)） favicon.ico\n自动注册了 of Converter, GenericConverter, Formatter beans.\nConverter：转换器； public String hello(User user)：类型转换使用Converter Formatter 格式化器； 2017.12.17===Date； @Bean @ConditionalOnProperty(prefix = \u0026#34;spring.mvc\u0026#34;, name = \u0026#34;date-format\u0026#34;)//在文件中配置日期格式化的规则 public Formatter\u0026lt;Date\u0026gt; dateFormatter() { return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件 } ​\t==自己添加的格式化器转换器，我们只需要放在容器中即可==\nSupport for HttpMessageConverters (see below).\nHttpMessageConverter：SpringMVC用来转换Http请求和响应的；User\u0026mdash;Json；\nHttpMessageConverters 是从容器中确定；获取所有的HttpMessageConverter；\n==自己给容器中添加HttpMessageConverter，只需要将自己的组件注册容器中（@Bean,@Component）==\nAutomatic registration of MessageCodesResolver (see below).定义错误代码生成规则\nAutomatic use of a ConfigurableWebBindingInitializer bean (see below).\n==我们可以配置一个ConfigurableWebBindingInitializer来替换默认的；（添加到容器）==\n初始化WebDataBinder； 请求数据=====JavaBean； org.springframework.boot.autoconfigure.web：web的所有自动场景；\nIf you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own @Configuration class of type WebMvcConfigurerAdapter, but without @EnableWebMvc）If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver you can declare a WebMvcRegistrationsAdapter instance providing such components.\nIf you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.\n2、扩展SpringMVC \u0026lt;mvc:view-controller path=\u0026#34;/hello\u0026#34; view-name=\u0026#34;success\u0026#34;/\u0026gt; \u0026lt;mvc:interceptors\u0026gt; \u0026lt;mvc:interceptor\u0026gt; \u0026lt;mvc:mapping path=\u0026#34;/hello\u0026#34;/\u0026gt; \u0026lt;bean\u0026gt;\u0026lt;/bean\u0026gt; \u0026lt;/mvc:interceptor\u0026gt; \u0026lt;/mvc:interceptors\u0026gt; ==编写一个配置类（@Configuration），是WebMvcConfigurerAdapter类型；不能标注@EnableWebMvc==;\n既保留了所有的自动配置，也能用我们扩展的配置；\n//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能 @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { // super.addViewControllers(registry); //浏览器发送 /atguigu 请求来到 success registry.addViewController(\u0026#34;/atguigu\u0026#34;).setViewName(\u0026#34;success\u0026#34;); } } 原理：\n​\t1）WebMvcAutoConfiguration是SpringMVC的自动配置类\n​\t2）在做其他自动配置时会导入；@Import(EnableWebMvcConfiguration.class)\n@Configuration public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); //从容器中获取所有的WebMvcConfigurer @Autowired(required = false) public void setConfigurers(List\u0026lt;WebMvcConfigurer\u0026gt; configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); //一个参考实现；将所有的WebMvcConfigurer相关配置都来一起调用； @Override // public void addViewControllers(ViewControllerRegistry registry) { // for (WebMvcConfigurer delegate : this.delegates) { // delegate.addViewControllers(registry); // } } } } ​\t3）容器中所有的WebMvcConfigurer都会一起起作用；\n​\t4）我们的配置类也会被调用；\n​\t效果：SpringMVC的自动配置和我们的扩展配置都会起作用；\n3、全面接管SpringMVC； SpringBoot对SpringMVC的自动配置不需要了，所有都是我们自己配置；所有的SpringMVC的自动配置都失效了\n我们需要在配置类中添加@EnableWebMvc即可；\n//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能 @EnableWebMvc @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { // super.addViewControllers(registry); //浏览器发送 /atguigu 请求来到 success registry.addViewController(\u0026#34;/atguigu\u0026#34;).setViewName(\u0026#34;success\u0026#34;); } } 原理：\n为什么@EnableWebMvc自动配置就失效了；\n1）@EnableWebMvc的核心\n@Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { @Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { @Configuration @ConditionalOnWebApplication @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class }) //容器中没有这个组件的时候，这个自动配置类才生效 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { 4）@EnableWebMvc将WebMvcConfigurationSupport组件导入进来；\n5）导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能；\n5、如何修改SpringBoot的默认配置 模式：\n​\t1）SpringBoot在自动配置很多组件的时候，先看容器中有没有用户自己配置的（@Bean、@Component）如果有就用用户配置的，如果没有，才自动配置；如果有些组件可以有多个（ViewResolver）将用户配置的和自己默认的组合起来；\n​\t2）在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置\n​\t3）在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置\n6、RestfulCRUD 1）默认访问首页 //使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能 //@EnableWebMvc 不要接管SpringMVC @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { // super.addViewControllers(registry); //浏览器发送 /atguigu 请求来到 success registry.addViewController(\u0026#34;/atguigu\u0026#34;).setViewName(\u0026#34;success\u0026#34;); } //所有的WebMvcConfigurerAdapter组件都会一起起作用 @Bean //将组件注册在容器 public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){ WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(\u0026#34;/\u0026#34;).setViewName(\u0026#34;login\u0026#34;); registry.addViewController(\u0026#34;/index.html\u0026#34;).setViewName(\u0026#34;login\u0026#34;); } }; return adapter; } } 2）国际化 1）编写国际化配置文件；\n2）使用ResourceBundleMessageSource管理国际化资源文件\n3）在页面使用fmt:message取出国际化内容\n步骤：\n1）编写国际化配置文件，抽取页面需要显示的国际化消息\n2）SpringBoot自动配置好了管理国际化资源文件的组件；\n@ConfigurationProperties(prefix = \u0026#34;spring.messages\u0026#34;) public class MessageSourceAutoConfiguration { /** * Comma-separated list of basenames (essentially a fully-qualified classpath * location), each following the ResourceBundle convention with relaxed support for * slash based locations）If it doesn\u0026#39;t contain a package qualifier (such as * \u0026#34;org.mypackage\u0026#34;), it will be resolved from the classpath root. */ private String basename = \u0026#34;messages\u0026#34;; //我们的配置文件可以直接放在类路径下叫messages.properties； @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(this.basename)) { //设置国际化资源文件的基础名（去掉语言国家代码的. messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(this.basename))); } if (this.encoding != null) { messageSource.setDefaultEncoding(this.encoding.name()); } messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale); messageSource.setCacheSeconds(this.cacheSeconds); messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat); return messageSource; } 3）去页面获取国际化的值；\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34; xmlns:th=\u0026#34;http://www.thymeleaf.org\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta http-equiv=\u0026#34;Content-Type\u0026#34; content=\u0026#34;text/html; charset=UTF-8\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1, shrink-to-fit=no\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;description\u0026#34; content=\u0026#34;\u0026#34;\u0026gt; \u0026lt;meta name=\u0026#34;author\u0026#34; content=\u0026#34;\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Signin Template for Bootstrap\u0026lt;/title\u0026gt; \u0026lt;!-- Bootstrap core CSS --\u0026gt; \u0026lt;link href=\u0026#34;asserts/css/bootstrap.min.css\u0026#34; th:href=\u0026#34;@{/webjars/bootstrap/4.0.0/css/bootstrap.css}\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;!-- Custom styles for this template --\u0026gt; \u0026lt;link href=\u0026#34;asserts/css/signin.css\u0026#34; th:href=\u0026#34;@{/asserts/css/signin.css}\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body class=\u0026#34;text-center\u0026#34;\u0026gt; \u0026lt;form class=\u0026#34;form-signin\u0026#34; action=\u0026#34;dashboard.html\u0026#34;\u0026gt; \u0026lt;img class=\u0026#34;mb-4\u0026#34; th:src=\u0026#34;@{/asserts/img/bootstrap-solid.svg}\u0026#34; src=\u0026#34;asserts/img/bootstrap-solid.svg\u0026#34; alt=\u0026#34;\u0026#34; width=\u0026#34;72\u0026#34; height=\u0026#34;72\u0026#34;\u0026gt; \u0026lt;h1 class=\u0026#34;h3 mb-3 font-weight-normal\u0026#34; th:text=\u0026#34;#{login.tip}\u0026#34;\u0026gt;Please sign in\u0026lt;/h1\u0026gt; \u0026lt;label class=\u0026#34;sr-only\u0026#34; th:text=\u0026#34;#{login.username}\u0026#34;\u0026gt;Username\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; placeholder=\u0026#34;Username\u0026#34; th:placeholder=\u0026#34;#{login.username}\u0026#34; required=\u0026#34;\u0026#34; autofocus=\u0026#34;\u0026#34;\u0026gt; \u0026lt;label class=\u0026#34;sr-only\u0026#34; th:text=\u0026#34;#{login.password}\u0026#34;\u0026gt;Password\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026#34;password\u0026#34; class=\u0026#34;form-control\u0026#34; placeholder=\u0026#34;Password\u0026#34; th:placeholder=\u0026#34;#{login.password}\u0026#34; required=\u0026#34;\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;checkbox mb-3\u0026#34;\u0026gt; \u0026lt;label\u0026gt; \u0026lt;input type=\u0026#34;checkbox\u0026#34; value=\u0026#34;remember-me\u0026#34;/\u0026gt; [[#{login.remember}]] \u0026lt;/label\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;button class=\u0026#34;btn btn-lg btn-primary btn-block\u0026#34; type=\u0026#34;submit\u0026#34; th:text=\u0026#34;#{login.btn}\u0026#34;\u0026gt;Sign in\u0026lt;/button\u0026gt; \u0026lt;p class=\u0026#34;mt-5 mb-3 text-muted\u0026#34;\u0026gt;© 2017-2018\u0026lt;/p\u0026gt; \u0026lt;a class=\u0026#34;btn btn-sm\u0026#34;\u0026gt;中文\u0026lt;/a\u0026gt; \u0026lt;a class=\u0026#34;btn btn-sm\u0026#34;\u0026gt;English\u0026lt;/a\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 效果：根据浏览器语言设置的信息切换了国际化；\n原理：\n​\t国际化Locale（区域信息对象）；LocaleResolver（获取区域信息对象）；\n@Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = \u0026#34;spring.mvc\u0026#34;, name = \u0026#34;locale\u0026#34;) public LocaleResolver localeResolver() { if (this.mvcProperties .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } 默认的就是根据请求头带来的区域信息获取Locale进行国际化 4）点击链接切换国际化\n/** * 可以在连接上携带区域信息 */ public class MyLocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { String l = request.getParameter(\u0026#34;l\u0026#34;); Locale locale = Locale.getDefault(); if(!StringUtils.isEmpty(l)){ String[] split = l.split(\u0026#34;_\u0026#34;); locale = new Locale(split[0],split[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } } @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); } } 3）登录 开发期间模板引擎页面修改以后，要实时生效\n1）禁用模板引擎的缓存\n# 禁用缓存 spring.thymeleaf.cache=false 2）页面修改完成以后ctrl+f9：重新编译；\n登陆错误消息的显示\n\u0026lt;p style=\u0026#34;color: red\u0026#34; th:text=\u0026#34;${msg}\u0026#34; th:if=\u0026#34;${not #strings.isEmpty(msg)}\u0026#34;\u0026gt;\u0026lt;/p\u0026gt; 4）拦截器进行登陆检查 拦截器\n/** * 登陆检查， */ public class LoginHandlerInterceptor implements HandlerInterceptor { //目标方法执行之前 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute(\u0026#34;loginUser\u0026#34;); if(user == null){ //未登陆，返回登陆页面 request.setAttribute(\u0026#34;msg\u0026#34;,\u0026#34;没有权限请先登陆\u0026#34;); request.getRequestDispatcher(\u0026#34;/index.html\u0026#34;).forward(request,response); return false; }else{ //已登陆，放行请求 return true; } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } } 注册拦截器\n//所有的WebMvcConfigurerAdapter组件都会一起起作用 @Bean //将组件注册在容器 public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){ WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(\u0026#34;/\u0026#34;).setViewName(\u0026#34;login\u0026#34;); registry.addViewController(\u0026#34;/index.html\u0026#34;).setViewName(\u0026#34;login\u0026#34;); registry.addViewController(\u0026#34;/main.html\u0026#34;).setViewName(\u0026#34;dashboard\u0026#34;); } //注册拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { //super.addInterceptors(registry); //静态资源； *.css , *.js //SpringBoot已经做好了静态资源映射 registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns(\u0026#34;/**\u0026#34;) .excludePathPatterns(\u0026#34;/index.html\u0026#34;,\u0026#34;/\u0026#34;,\u0026#34;/user/login\u0026#34;); } }; return adapter; } 5）CRUD-员工列表 实验要求：\n1）RestfulCRUD：CRUD满足Rest风格；\nURI： /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作\n普通CRUD（uri来区分操作） RestfulCRUD 查询 getEmp emp\u0026mdash;GET 添加 addEmp?xxx emp\u0026mdash;POST 修改 updateEmp?id=xxx\u0026amp;xxx=xx emp/{id}\u0026mdash;PUT 删除 deleteEmp?id=1 emp/{id}\u0026mdash;DELETE 2）实验的请求架构;\n实验功能 请求URI 请求方式 查询所有员工 emps GET 查询某个员工(来到修改页面) emp/1 GET 来到添加页面 emp GET 添加员工 emp POST 来到修改页面（查出员工进行信息回显） emp/1 GET 修改员工 emp PUT 删除员工 emp/1 DELETE 3）员工列表：\nthymeleaf公共页面元素抽取 1、抽取公共片段 \u0026lt;div th:fragment=\u0026#34;copy\u0026#34;\u0026gt; \u0026amp;copy; 2011 The Good Thymes Virtual Grocery \u0026lt;/div\u0026gt; 2、引入公共片段 \u0026lt;div th:insert=\u0026#34;~{footer :: copy}\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; ~{templatename::selector}：模板名::选择器 ~{templatename::fragmentname}:模板名::片段名 3、默认效果： insert的公共片段在div标签中 如果使用th:insert等属性进行引入，可以不用写~{}： 行内写法可以加上：[[~{}]];[(~{})]； 三种引入公共片段的th属性：\nth:insert：将公共片段整个插入到声明引入的元素中\nth:replace：将声明引入的元素替换为公共片段\nth:include：将被引入的片段的内容包含进这个标签中\n\u0026lt;footer th:fragment=\u0026#34;copy\u0026#34;\u0026gt; \u0026amp;copy; 2011 The Good Thymes Virtual Grocery \u0026lt;/footer\u0026gt; 引入方式 \u0026lt;div th:insert=\u0026#34;footer :: copy\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div th:replace=\u0026#34;footer :: copy\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div th:include=\u0026#34;footer :: copy\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; 效果 \u0026lt;div\u0026gt; \u0026lt;footer\u0026gt; \u0026amp;copy; 2011 The Good Thymes Virtual Grocery \u0026lt;/footer\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;footer\u0026gt; \u0026amp;copy; 2011 The Good Thymes Virtual Grocery \u0026lt;/footer\u0026gt; \u0026lt;div\u0026gt; \u0026amp;copy; 2011 The Good Thymes Virtual Grocery \u0026lt;/div\u0026gt; 引入片段的时候传入参数：\n\u0026lt;nav class=\u0026#34;col-md-2 d-none d-md-block bg-light sidebar\u0026#34; id=\u0026#34;sidebar\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;sidebar-sticky\u0026#34;\u0026gt; \u0026lt;ul class=\u0026#34;nav flex-column\u0026#34;\u0026gt; \u0026lt;li class=\u0026#34;nav-item\u0026#34;\u0026gt; \u0026lt;a class=\u0026#34;nav-link active\u0026#34; th:class=\u0026#34;${activeUri==\u0026#39;main.html\u0026#39;?\u0026#39;nav-link active\u0026#39;:\u0026#39;nav-link\u0026#39;}\u0026#34; href=\u0026#34;#\u0026#34; th:href=\u0026#34;@{/main.html}\u0026#34;\u0026gt; \u0026lt;svg xmlns=\u0026#34;http://www.w3.org/2000/svg\u0026#34; width=\u0026#34;24\u0026#34; height=\u0026#34;24\u0026#34; viewBox=\u0026#34;0 0 24 24\u0026#34; fill=\u0026#34;none\u0026#34; stroke=\u0026#34;currentColor\u0026#34; stroke-width=\u0026#34;2\u0026#34; stroke-linecap=\u0026#34;round\u0026#34; stroke-linejoin=\u0026#34;round\u0026#34; class=\u0026#34;feather feather-home\u0026#34;\u0026gt; \u0026lt;path d=\u0026#34;M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\u0026#34;\u0026gt;\u0026lt;/path\u0026gt; \u0026lt;polyline points=\u0026#34;9 22 9 12 15 12 15 22\u0026#34;\u0026gt;\u0026lt;/polyline\u0026gt; \u0026lt;/svg\u0026gt; Dashboard \u0026lt;span class=\u0026#34;sr-only\u0026#34;\u0026gt;(current)\u0026lt;/span\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;/li\u0026gt; \u0026lt;!--引入侧边栏;传入参数--\u0026gt; \u0026lt;div th:replace=\u0026#34;commons/bar::#sidebar(activeUri=\u0026#39;emps\u0026#39;)\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; 6）CRUD-员工添加 添加页面\n\u0026lt;form\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;LastName\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; placeholder=\u0026#34;zhangsan\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;Email\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026#34;email\u0026#34; class=\u0026#34;form-control\u0026#34; placeholder=\u0026#34;zhangsan@atguigu.com\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;Gender\u0026lt;/label\u0026gt;\u0026lt;br/\u0026gt; \u0026lt;div class=\u0026#34;form-check form-check-inline\u0026#34;\u0026gt; \u0026lt;input class=\u0026#34;form-check-input\u0026#34; type=\u0026#34;radio\u0026#34; name=\u0026#34;gender\u0026#34; value=\u0026#34;1\u0026#34;\u0026gt; \u0026lt;label class=\u0026#34;form-check-label\u0026#34;\u0026gt;男\u0026lt;/label\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-check form-check-inline\u0026#34;\u0026gt; \u0026lt;input class=\u0026#34;form-check-input\u0026#34; type=\u0026#34;radio\u0026#34; name=\u0026#34;gender\u0026#34; value=\u0026#34;0\u0026#34;\u0026gt; \u0026lt;label class=\u0026#34;form-check-label\u0026#34;\u0026gt;女\u0026lt;/label\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;department\u0026lt;/label\u0026gt; \u0026lt;select class=\u0026#34;form-control\u0026#34;\u0026gt; \u0026lt;option\u0026gt;1\u0026lt;/option\u0026gt; \u0026lt;option\u0026gt;2\u0026lt;/option\u0026gt; \u0026lt;option\u0026gt;3\u0026lt;/option\u0026gt; \u0026lt;option\u0026gt;4\u0026lt;/option\u0026gt; \u0026lt;option\u0026gt;5\u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;Birth\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; placeholder=\u0026#34;zhangsan\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;button type=\u0026#34;submit\u0026#34; class=\u0026#34;btn btn-primary\u0026#34;\u0026gt;添加\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; 提交的数据格式不对：生日：日期；\n2017-12-12；2017/12/12；2017.12.12；\n日期的格式化；SpringMVC将页面提交的值需要转换为指定的类型;\n2017-12-12\u0026mdash;Date； 类型转换，格式化;\n默认日期是按照/的方式；\n7）CRUD-员工修改 修改添加二合一表单\n\u0026lt;!--需要区分是员工修改还是添加；--\u0026gt; \u0026lt;form th:action=\u0026#34;@{/emp}\u0026#34; method=\u0026#34;post\u0026#34;\u0026gt; \u0026lt;!--发送put请求修改员工数据--\u0026gt; \u0026lt;!-- 1、SpringMVC中配置HiddenHttpMethodFilter;（SpringBoot自动配置好的. 2、页面创建一个post表单 3、创建一个input项，name=\u0026#34;_method\u0026#34;;值就是我们指定的请求方式 --\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#34;_method\u0026#34; value=\u0026#34;put\u0026#34; th:if=\u0026#34;${emp!=null}\u0026#34;/\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#34;id\u0026#34; th:if=\u0026#34;${emp!=null}\u0026#34; th:value=\u0026#34;${emp.id}\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;LastName\u0026lt;/label\u0026gt; \u0026lt;input name=\u0026#34;lastName\u0026#34; type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; placeholder=\u0026#34;zhangsan\u0026#34; th:value=\u0026#34;${emp!=null}?${emp.lastName}\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;Email\u0026lt;/label\u0026gt; \u0026lt;input name=\u0026#34;email\u0026#34; type=\u0026#34;email\u0026#34; class=\u0026#34;form-control\u0026#34; placeholder=\u0026#34;zhangsan@atguigu.com\u0026#34; th:value=\u0026#34;${emp!=null}?${emp.email}\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;Gender\u0026lt;/label\u0026gt;\u0026lt;br/\u0026gt; \u0026lt;div class=\u0026#34;form-check form-check-inline\u0026#34;\u0026gt; \u0026lt;input class=\u0026#34;form-check-input\u0026#34; type=\u0026#34;radio\u0026#34; name=\u0026#34;gender\u0026#34; value=\u0026#34;1\u0026#34; th:checked=\u0026#34;${emp!=null}?${emp.gender==1}\u0026#34;\u0026gt; \u0026lt;label class=\u0026#34;form-check-label\u0026#34;\u0026gt;男\u0026lt;/label\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-check form-check-inline\u0026#34;\u0026gt; \u0026lt;input class=\u0026#34;form-check-input\u0026#34; type=\u0026#34;radio\u0026#34; name=\u0026#34;gender\u0026#34; value=\u0026#34;0\u0026#34; th:checked=\u0026#34;${emp!=null}?${emp.gender==0}\u0026#34;\u0026gt; \u0026lt;label class=\u0026#34;form-check-label\u0026#34;\u0026gt;女\u0026lt;/label\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;department\u0026lt;/label\u0026gt; \u0026lt;!--提交的是部门的id--\u0026gt; \u0026lt;select class=\u0026#34;form-control\u0026#34; name=\u0026#34;department.id\u0026#34;\u0026gt; \u0026lt;option th:selected=\u0026#34;${emp!=null}?${dept.id == emp.department.id}\u0026#34; th:value=\u0026#34;${dept.id}\u0026#34; th:each=\u0026#34;dept:${depts}\u0026#34; th:text=\u0026#34;${dept.departmentName}\u0026#34;\u0026gt;1\u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;form-group\u0026#34;\u0026gt; \u0026lt;label\u0026gt;Birth\u0026lt;/label\u0026gt; \u0026lt;input name=\u0026#34;birth\u0026#34; type=\u0026#34;text\u0026#34; class=\u0026#34;form-control\u0026#34; placeholder=\u0026#34;zhangsan\u0026#34; th:value=\u0026#34;${emp!=null}?${#dates.format(emp.birth, \u0026#39;yyyy-MM-dd HH:mm\u0026#39;)}\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;button type=\u0026#34;submit\u0026#34; class=\u0026#34;btn btn-primary\u0026#34; th:text=\u0026#34;${emp!=null}?\u0026#39;修改\u0026#39;:\u0026#39;添加\u0026#39;\u0026#34;\u0026gt;添加\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; 8）CRUD-员工删除 \u0026lt;tr th:each=\u0026#34;emp:${emps}\u0026#34;\u0026gt; \u0026lt;td th:text=\u0026#34;${emp.id}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;[[${emp.lastName}]]\u0026lt;/td\u0026gt; \u0026lt;td th:text=\u0026#34;${emp.email}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td th:text=\u0026#34;${emp.gender}==0?\u0026#39;女\u0026#39;:\u0026#39;男\u0026#39;\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td th:text=\u0026#34;${emp.department.departmentName}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td th:text=\u0026#34;${#dates.format(emp.birth, \u0026#39;yyyy-MM-dd HH:mm\u0026#39;)}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt; \u0026lt;a class=\u0026#34;btn btn-sm btn-primary\u0026#34; th:href=\u0026#34;@{/emp/}+${emp.id}\u0026#34;\u0026gt;编辑\u0026lt;/a\u0026gt; \u0026lt;button th:attr=\u0026#34;del_uri=@{/emp/}+${emp.id}\u0026#34; class=\u0026#34;btn btn-sm btn-danger deleteBtn\u0026#34;\u0026gt;删除\u0026lt;/button\u0026gt; \u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;script\u0026gt; $(\u0026#34;.deleteBtn\u0026#34;).click(function(){ //删除当前员工的 $(\u0026#34;#deleteEmpForm\u0026#34;).attr(\u0026#34;action\u0026#34;,$(this).attr(\u0026#34;del_uri\u0026#34;)).submit(); return false; }); \u0026lt;/script\u0026gt; 7、错误处理机制 1）SpringBoot默认的错误处理机制 默认效果：\n​\t1）浏览器，返回一个默认的错误页面\n浏览器发送请求的请求头：\n​\t2）如果是其他客户端，默认响应一个json数据\n​\t原理：\n​\t可以参照ErrorMvcAutoConfiguration；错误处理的自动配置；\n给容器中添加了以下组件 ​\t1、DefaultErrorAttributes：\n帮我们在页面共享信息； @Override public Map\u0026lt;String, Object\u0026gt; getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map\u0026lt;String, Object\u0026gt; errorAttributes = new LinkedHashMap\u0026lt;String, Object\u0026gt;(); errorAttributes.put(\u0026#34;timestamp\u0026#34;, new Date()); addStatus(errorAttributes, requestAttributes); addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); addPath(errorAttributes, requestAttributes); return errorAttributes; } ​\t2、BasicErrorController：处理默认/error请求\n@Controller @RequestMapping(\u0026#34;${server.error.path:${error.path:/error}}\u0026#34;) public class BasicErrorController extends AbstractErrorController { @RequestMapping(produces = \u0026#34;text/html\u0026#34;)//产生html类型的数据；浏览器发送的请求来到这个方法处理 public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map\u0026lt;String, Object\u0026gt; model = Collections.unmodifiableMap(getErrorAttributes( request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); //去哪个页面作为错误页面；包含页面地址和页面内容 ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView == null ? new ModelAndView(\u0026#34;error\u0026#34;, model) : modelAndView); } @RequestMapping @ResponseBody //产生json数据，其他客户端来到这个方法处理； public ResponseEntity\u0026lt;Map\u0026lt;String, Object\u0026gt;\u0026gt; error(HttpServletRequest request) { Map\u0026lt;String, Object\u0026gt; body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity\u0026lt;Map\u0026lt;String, Object\u0026gt;\u0026gt;(body, status); } ​\t3、ErrorPageCustomizer：\n@Value(\u0026#34;${error.path:/error}\u0026#34;) private String path = \u0026#34;/error\u0026#34;; 系统出现错误以后来到error请求进行处理；（web.xml注册的错误页面规则. ​\t4、DefaultErrorViewResolver：\n@Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map\u0026lt;String, Object\u0026gt; model) { ModelAndView modelAndView = resolve(String.valueOf(status), model); if (modelAndView == null \u0026amp;\u0026amp; SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map\u0026lt;String, Object\u0026gt; model) { //默认SpringBoot可以去找到一个页面？ error/404 String errorViewName = \u0026#34;error/\u0026#34; + viewName; //模板引擎可以解析这个页面地址就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext); if (provider != null) { //模板引擎可用的情况下返回到errorViewName指定的视图地址 return new ModelAndView(errorViewName, model); } //模板引擎不可用，就在静态资源文件夹下找errorViewName对应的页面 error/404.html return resolveResource(errorViewName, model); } ​\t步骤：\n​\t一但系统出现4xx或者5xx之类的错误；ErrorPageCustomizer就会生效（定制错误的响应规则）；就会来到/error请求；就会被BasicErrorController处理；\n​\t1）响应页面；去哪个页面是由DefaultErrorViewResolver解析得到的；\nprotected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map\u0026lt;String, Object\u0026gt; model) { //所有的ErrorViewResolver得到ModelAndView for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; } 2）如果定制错误响应： 1）如何定制错误的页面； ​\t1）有模板引擎的情况下；error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】，发生此状态码的错误就会来到 对应的页面；\n​\t我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误，精确优先（优先寻找精确的状态码.html）；\n​\t页面能获取的信息；\n​\ttimestamp：时间戳\n​\tstatus：状态码\n​\terror：错误提示\n​\texception：异常对象\n​\tmessage：异常消息\n​\terrors：JSR303数据校验的错误都在这里\n​\t2）没有模板引擎（模板引擎找不到这个错误页面），静态资源文件夹下找；\n​\t3）以上都没有错误页面，就是默认来到SpringBoot默认的错误提示页面；\n2）如何定制错误的json数据； ​\t1）自定义异常处理\u0026amp;返回定制json数据；\n@ControllerAdvice public class MyExceptionHandler { @ResponseBody @ExceptionHandler(UserNotExistException.class) public Map\u0026lt;String,Object\u0026gt; handleException(Exception e){ Map\u0026lt;String,Object\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); map.put(\u0026#34;code\u0026#34;,\u0026#34;user.notexist\u0026#34;); map.put(\u0026#34;message\u0026#34;,e.getMessage()); return map; } } //没有自适应效果... ​\t2）转发到/error进行自适应响应效果处理\n@ExceptionHandler(UserNotExistException.class) public String handleException(Exception e, HttpServletRequest request){ Map\u0026lt;String,Object\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); //传入我们自己的错误状态码 4xx 5xx，否则就不会进入定制错误页面的解析流程 /** * Integer statusCode = (Integer) request .getAttribute(\u0026#34;javax.servlet.error.status_code\u0026#34;); */ request.setAttribute(\u0026#34;javax.servlet.error.status_code\u0026#34;,500); map.put(\u0026#34;code\u0026#34;,\u0026#34;user.notexist\u0026#34;); map.put(\u0026#34;message\u0026#34;,e.getMessage()); //转发到/error return \u0026#34;forward:/error\u0026#34;; } 3）将我们的定制数据携带出去； 出现错误以后，会来到/error请求，会被BasicErrorController处理，响应出去可以获取的数据是由getErrorAttributes得到的（是AbstractErrorController（ErrorController）规定的方法）；\n​\t1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】，放在容器中；\n​\t2、页面上能用的数据，或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到；\n​\t容器中DefaultErrorAttributes.getErrorAttributes()；默认进行数据处理的；\n自定义ErrorAttributes\n//给容器中加入我们自己定义的ErrorAttributes @Component public class MyErrorAttributes extends DefaultErrorAttributes { @Override public Map\u0026lt;String, Object\u0026gt; getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map\u0026lt;String, Object\u0026gt; map = super.getErrorAttributes(requestAttributes, includeStackTrace); map.put(\u0026#34;company\u0026#34;,\u0026#34;atguigu\u0026#34;); return map; } } 最终的效果：响应是自适应的，可以通过定制ErrorAttributes改变需要返回的内容，\n8、配置嵌入式Servlet容器 SpringBoot默认使用Tomcat作为嵌入式的Servlet容器；\n问题？\n1）如何定制和修改Servlet容器的相关配置； 1、修改和server有关的配置（ServerProperties【也是EmbeddedServletContainerCustomizer】）；\nserver.port=8081 server.context-path=/crud server.tomcat.uri-encoding=UTF-8 //通用的Servlet容器设置 server.xxx //Tomcat的设置 server.tomcat.xxx 2、编写一个EmbeddedServletContainerCustomizer：嵌入式的Servlet容器的定制器；来修改Servlet容器的配置\n@Bean //一定要将这个定制器加入到容器中 public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){ return new EmbeddedServletContainerCustomizer() { //定制嵌入式的Servlet容器相关的规则 @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.setPort(8083); } }; } 2）注册Servlet三大组件【Servlet、Filter、Listener】 由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用，没有web.xml文件。\n注册三大组件用以下方式\nServletRegistrationBean\n//注册三大组件 @Bean public ServletRegistrationBean myServlet(){ ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),\u0026#34;/myServlet\u0026#34;); return registrationBean; } FilterRegistrationBean\n@Bean public FilterRegistrationBean myFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new MyFilter()); registrationBean.setUrlPatterns(Arrays.asList(\u0026#34;/hello\u0026#34;,\u0026#34;/myServlet\u0026#34;)); return registrationBean; } ServletListenerRegistrationBean\n@Bean public ServletListenerRegistrationBean myListener(){ ServletListenerRegistrationBean\u0026lt;MyListener\u0026gt; registrationBean = new ServletListenerRegistrationBean\u0026lt;\u0026gt;(new MyListener()); return registrationBean; } SpringBoot帮我们自动SpringMVC的时候，自动的注册SpringMVC的前端控制器；DIspatcherServlet；\nDispatcherServletAutoConfiguration中：\n@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public ServletRegistrationBean dispatcherServletRegistration( DispatcherServlet dispatcherServlet) { ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet, this.serverProperties.getServletMapping()); //默认拦截： / 所有请求；包静态资源，但是不拦截jsp请求； /*会拦截jsp //可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径 registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup( this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } return registration; } 2）SpringBoot能不能支持其他的Servlet容器；\n3）替换为其他嵌入式Servlet容器 默认支持：\nTomcat（默认使用.\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器； \u0026lt;/dependency\u0026gt; Jetty\n\u0026lt;!-- 引入web模块 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-tomcat\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!--引入其他的Servlet容器--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-jetty\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;/dependency\u0026gt; Undertow\n\u0026lt;!-- 引入web模块 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-tomcat\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!--引入其他的Servlet容器--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-undertow\u0026lt;/artifactId\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;/dependency\u0026gt; 4）嵌入式Servlet容器自动配置原理； EmbeddedServletContainerAutoConfiguration：嵌入式的Servlet容器自动配置？\n==2.0 👉 ServletWebServerFactoryAutoConfiguration , ServletWebServerFactoryConfiguration==\n@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration @ConditionalOnWebApplication @Import(BeanPostProcessorsRegistrar.class) //导入BeanPostProcessorsRegistrar：Spring注解版；给容器中导入一些组件 //导入了EmbeddedServletContainerCustomizerBeanPostProcessor： //后置处理器：bean初始化前后（创建完对象，还没赋值赋值）执行初始化工作 public class EmbeddedServletContainerAutoConfiguration { @Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class })//判断当前是否引入了Tomcat依赖； @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory：嵌入式的Servlet容器工厂；作用：创建嵌入式的Servlet容器 public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { return new TomcatEmbeddedServletContainerFactory(); } } /** * Nested configuration if Jetty is being used. */ @Configuration @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedJetty { @Bean public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() { return new JettyEmbeddedServletContainerFactory(); } } /** * Nested configuration if Undertow is being used. */ @Configuration @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedUndertow { @Bean public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() { return new UndertowEmbeddedServletContainerFactory(); } } 1）EmbeddedServletContainerFactory（嵌入式Servlet容器工厂.\n==2.0👉WebServerFactoryCustomizer==\npublic interface EmbeddedServletContainerFactory { //获取嵌入式的Servlet容器 EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer..）initializers); } 2）EmbeddedServletContainer：（嵌入式的Servlet容器.\n3）以TomcatEmbeddedServletContainerFactory为例\n==2.0👉TomcatServletWebServerFactory==\n@Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer..）initializers) { //创建一个Tomcat Tomcat tomcat = new Tomcat(); //配置Tomcat的基本环节 File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir(\u0026#34;tomcat\u0026#34;)); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); //将配置好的Tomcat传入进去，返回一个EmbeddedServletContainer；并且启动Tomcat服务器 return getTomcatEmbeddedServletContainer(tomcat); } 4）我们对嵌入式容器的配置修改是怎么生效？\nServerProperties、EmbeddedServletContainerCustomizer EmbeddedServletContainerCustomizer：定制器帮我们修改了Servlet容器的配置？\n怎么修改的原理？\n5）容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor\n//初始化之前 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件 if (bean instanceof ConfigurableEmbeddedServletContainer) { // postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean); } return bean; } private void postProcessBeforeInitialization( ConfigurableEmbeddedServletContainer bean) { //获取所有的定制器，调用每一个定制器的customize方法来给Servlet容器进行属性赋值； for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { customizer.customize(bean); } } private Collection\u0026lt;EmbeddedServletContainerCustomizer\u0026gt; getCustomizers() { if (this.customizers == null) { // Look up does not include the parent context this.customizers = new ArrayList\u0026lt;EmbeddedServletContainerCustomizer\u0026gt;( this.beanFactory //从容器中获取所有这葛类型的组件：EmbeddedServletContainerCustomizer //定制Servlet容器，给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件 .getBeansOfType(EmbeddedServletContainerCustomizer.class, false, false) .values()); Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE); this.customizers = Collections.unmodifiableList(this.customizers); } return this.customizers; } ServerProperties也是定制器 步骤：\n1）SpringBoot根据导入的依赖情况，给容器中添加相应的EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】\n2）容器中某个组件要创建对象就会惊动后置处理器；EmbeddedServletContainerCustomizerBeanPostProcessor；\n只要是嵌入式的Servlet容器工厂，后置处理器就工作；\n3）后置处理器，从容器中获取所有的EmbeddedServletContainerCustomizer，调用定制器的定制方法\n###5）嵌入式Servlet容器启动原理；\n什么时候创建嵌入式的Servlet容器工厂？什么时候获取嵌入式的Servlet容器并启动Tomcat；\n获取嵌入式的Servlet容器工厂：\n1）SpringBoot应用启动运行run方法\n2）refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器对象，并初始化容器，创建容器中的每一个组件】；如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext，否则：AnnotationConfigApplicationContext\n3）refresh(context);刷新刚才创建好的ioc容器；\npublic void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn(\u0026#34;Exception encountered during context initialization - \u0026#34; + \u0026#34;cancelling refresh attempt: \u0026#34; + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset \u0026#39;active\u0026#39; flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring\u0026#39;s core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } } 4） onRefresh(); web的ioc容器重写了onRefresh方法\n5）webioc容器会创建嵌入式的Servlet容器；createEmbeddedServletContainer();\n6）获取嵌入式的Servlet容器工厂：\nEmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();\n​\t从ioc容器中获取EmbeddedServletContainerFactory 组件；TomcatEmbeddedServletContainerFactory创建对象，后置处理器一看是这个对象，就获取所有的定制器来先定制Servlet容器的相关配置；\n7）使用容器工厂获取嵌入式的Servlet容器：this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer());\n8）嵌入式的Servlet容器创建对象并启动Servlet容器；\n先启动嵌入式的Servlet容器，再将ioc容器中剩下没有创建出的对象获取出来；\n==IOC容器启动创建嵌入式的Servlet容器==\n9、使用外置的Servlet容器 嵌入式Servlet容器：应用打成可执行的jar\n​\t优点：简单、便携；\n​\t缺点：默认不支持JSP、优化定制比较复杂（使用定制器【ServerProperties、自定义EmbeddedServletContainerCustomizer】，自己编写嵌入式Servlet容器的创建工厂【EmbeddedServletContainerFactory】）；\n外置的Servlet容器：外面安装Tomcat\u0026mdash;应用war包的方式打包；\n步骤 1）必须创建一个war项目；（利用idea创建好目录结构.\n2）将嵌入式的Tomcat指定为provided；\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-tomcat\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; 3）必须编写一个SpringBootServletInitializer的子类，并调用configure方法\npublic class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { //传入SpringBoot应用的主程序 return application.sources(SpringBoot04WebJspApplication.class); } } 4）启动服务器就可以使用；\n原理 jar包：执行SpringBoot主类的main方法，启动ioc容器，创建嵌入式的Servlet容器；\nwar包：启动服务器，服务器启动SpringBoot应用【SpringBootServletInitializer】，启动ioc容器；\nservlet3.0（Spring注解版）：\n8.2.4 Shared libraries / runtimes pluggability：\n规则：\n​\t1）服务器启动（web应用启动）会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例：\n​\t2）ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下，有一个名为javax.servlet.ServletContainerInitializer的文件，内容就是ServletContainerInitializer的实现类的全类名\n​\t3）还可以使用@HandlesTypes，在应用启动的时候加载我们感兴趣的类；\n流程：\n1）启动Tomcat\n2）org\\springframework\\spring-web\\4.3.14.RELEASE\\spring-web-4.3.14.RELEASE.jar!\\META-INF\\services\\javax.servlet.ServletContainerInitializer：\nSpring的web模块里面有这个文件：org.springframework.web.SpringServletContainerInitializer\n3）SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set\u0026lt;Class\u003c?\u003e\u0026gt;；为这些WebApplicationInitializer类型的类创建实例；\n4）每一个WebApplicationInitializer都调用自己的onStartup；\n5）相当于我们的SpringBootServletInitializer的类会被创建对象，并执行onStartup方法\n6）SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext；创建容器\nprotected WebApplicationContext createRootApplicationContext( ServletContext servletContext) { //1、创建SpringApplicationBuilder SpringApplicationBuilder builder = createSpringApplicationBuilder(); StandardServletEnvironment environment = new StandardServletEnvironment(); environment.initPropertySources(servletContext, null); builder.environment(environment); builder.main(getClass()); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info(\u0026#34;Root context already created (using as parent).\u0026#34;); servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); builder.initializers(new ParentContextApplicationContextInitializer(parent)); } builder.initializers( new ServletContextApplicationContextInitializer(servletContext)); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); //调用configure方法，子类重写了这个方法，将SpringBoot的主程序类传入了进来 builder = configure(builder); //使用builder创建一个Spring应用 SpringApplication application = builder.build(); if (application.getSources().isEmpty() \u0026amp;\u0026amp; AnnotationUtils .findAnnotation(getClass(), Configuration.class) != null) { application.getSources().add(getClass()); } Assert.state(!application.getSources().isEmpty(), \u0026#34;No SpringApplication sources have been defined）Either override the \u0026#34; + \u0026#34;configure method or add an @Configuration annotation\u0026#34;); // Ensure error pages are registered if (this.registerErrorPageFilter) { application.getSources().add(ErrorPageFilterConfiguration.class); } //启动Spring应用 return run(application); } 7）Spring的应用就启动并且创建IOC容器\npublic ConfigurableApplicationContext run(String..）args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); analyzers = new FailureAnalyzers(context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新IOC容器 refreshContext(context); afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } } ==启动Servlet容器，再启动SpringBoot应用==\n五、Docker 1、简介 Docker是一个开源的应用容器引擎；是一个轻量级容器技术；\nDocker支持将软件编译成一个镜像；然后在镜像中各种软件做好配置，将镜像发布出去，其他使用者可以直接使用这个镜像；\n运行中的这个镜像称为容器，容器启动是非常快速的。\n2、核心概念 docker主机(Host)：安装了Docker程序的机器（Docker直接安装在操作系统之上）；\ndocker客户端(Client)：连接docker主机进行操作；\ndocker仓库(Registry)：用来保存各种打包好的软件镜像；\ndocker镜像(Images)：软件打包好的镜像；放在docker仓库中；\ndocker容器(Container)：镜像启动后的实例称为一个容器；容器是独立运行的一个或一组应用\n使用Docker的步骤：\n1）安装Docker\n2）去Docker仓库找到这个软件对应的镜像；\n3）使用Docker运行这个镜像，这个镜像就会生成一个Docker容器；\n4）对容器的启动停止就是对软件的启动停止；\n3、安装Docker 1）安装linux虚拟机 ​\t1）VMWare、VirtualBox（安装）；\n​\t2）导入虚拟机文件centos7-atguigu.ova；\n​\t3）双击启动linux虚拟机;使用 root/ 123456登陆\n​\t4）使用客户端连接linux服务器进行命令操作；\n​\t5）设置虚拟机网络；\n​\t桥接网络 ==选好网卡== 接入网线；\n​\t6）设置好网络以后使用命令重启虚拟机的网络\nservice network restart ​\t7）查看linux的ip地址\nip addr ​\t8）使用客户端连接linux；\n2）在linux虚拟机上安装docker 步骤：\n1、检查内核版本，必须是3.10及以上 uname -r 2、安装docker yum install docker 3、输入y确认安装 4、启动docker [root@localhost ~]# systemctl start docker [root@localhost ~]# docker -v Docker version 1.12.6, build 3e8e77d/1.12.6 5、开机启动docker [root@localhost ~]# systemctl enable docker Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service. 6、停止docker systemctl stop docker 4、Docker常用命令\u0026amp;操作 1）镜像操作 操作 命令 说明 检索 docker search 关键字 eg：docker search redis 我们经常去docker hub上检索镜像的详细信息，如镜像的TAG。 拉取 docker pull 镜像名:tag :tag是可选的，tag表示标签，多为软件的版本，默认是latest 列表 docker images 查看所有本地镜像 删除 docker rmi image-id 删除指定的本地镜像 https://hub.docker.com/\n2）容器操作 软件镜像（QQ安装程序）\u0026mdash;-运行镜像\u0026mdash;-产生一个容器（正在运行的软件，运行的QQ）；\n步骤：\n1、搜索镜像 [root@localhost ~]# docker search tomcat 2、拉取镜像 [root@localhost ~]# docker pull tomcat 3、根据镜像启动容器 docker run --name mytomcat -d tomcat:latest 4、docker ps 查看运行中的容器 5、 停止运行中的容器 docker stop 容器的id 6、查看所有的容器 docker ps -a 7、启动容器 docker start 容器id 8、删除一个容器 docker rm 容器id 9、启动一个做了端口映射的tomcat [root@localhost ~]# docker run -d -p 8888:8080 tomcat -d：后台运行 -p: 将主机的端口映射到容器的一个端口 主机端口:容器内部的端口 10、为了演示简单关闭了linux的防火墙 service firewalld status ；查看防火墙状态 service firewalld stop：关闭防火墙 11、查看容器的日志 docker logs container-name/container-id 更多命令参看 https://docs.docker.com/engine/reference/commandline/docker/ 可以参考每一个镜像的文档\n3）安装MySQL示例 docker pull mysql 错误的启动\n[root@localhost ~]# docker run --name mysql01 -d mysql 42f09819908bb72dd99ae19e792e0a5d03c48638421fa64cce5f8ba0f40f5846 mysql退出了 [root@localhost ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 42f09819908b mysql \u0026#34;docker-entrypoint.sh\u0026#34; 34 seconds ago Exited (1) 33 seconds ago mysql01 538bde63e500 tomcat \u0026#34;catalina.sh run\u0026#34; About an hour ago Exited (143) About an hour ago compassionate_ goldstine c4f1ac60b3fc tomcat \u0026#34;catalina.sh run\u0026#34; About an hour ago Exited (143) About an hour ago lonely_fermi 81ec743a5271 tomcat \u0026#34;catalina.sh run\u0026#34; About an hour ago Exited (143) About an hour ago sick_ramanujan //错误日志 [root@localhost ~]# docker logs 42f09819908b error: database is uninitialized and password option is not specified You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD；这个三个参数必须指定一个 正确的启动\nhttps://docs.docker.com/samples/library/mysql/\n[root@localhost ~]# docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql b874c56bec49fb43024b3805ab51e9097da779f2f572c22c695305dedd684c5f [root@localhost ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b874c56bec49 mysql \u0026#34;docker-entrypoint.sh\u0026#34; 4 seconds ago Up 3 seconds 3306/tcp mysql01 做了端口映射\n[root@localhost ~]# docker run -p 3306:3306 --name mysql02 -e MYSQL_ROOT_PASSWORD=123456 -d mysql ad10e4bc5c6a0f61cbad43898de71d366117d120e39db651844c0e73863b9434 [root@localhost ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ad10e4bc5c6a mysql \u0026#34;docker-entrypoint.sh\u0026#34; 4 seconds ago Up 2 seconds 0.0.0.0:3306-\u0026gt;3306/tcp mysql02 几个其他的高级操作\ndocker run --name mysql03 -v /conf/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag 把主机的/conf/mysql文件夹挂载到 mysqldocker容器的/etc/mysql/conf.d文件夹里面 改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下（/conf/mysql. docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci 指定mysql的一些配置参数 六、SpringBoot与数据访问 1、JDBC \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-jdbc\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;mysql\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mysql-connector-java\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;runtime\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; spring: datasource: username: root password: 123456 url: jdbc:mysql://192.168.15.22:3306/jdbc driver-class-name: com.mysql.jdbc.Driver 效果：\n​\t默认是用org.apache.tomcat.jdbc.pool.DataSource作为数据源；\n​\t数据源的相关配置都在DataSourceProperties里面；\n自动配置原理：\norg.springframework.boot.autoconfigure.jdbc：\n1、参考DataSourceConfiguration，根据配置创建数据源，默认使用Tomcat连接池；可以使用spring.datasource.type指定自定义的数据源类型；\n2、SpringBoot默认可以支持；\norg.apache.tomcat.jdbc.pool.DataSource、HikariDataSource、BasicDataSource、 3、自定义数据源类型\n/** * Generic DataSource configuration. */ @ConditionalOnMissingBean(DataSource.class) @ConditionalOnProperty(name = \u0026#34;spring.datasource.type\u0026#34;) static class Generic { @Bean public DataSource dataSource(DataSourceProperties properties) { //使用DataSourceBuilder创建数据源，利用反射创建响应type的数据源，并且绑定相关属性 return properties.initializeDataSourceBuilder().build(); } } 4、DataSourceInitializer：ApplicationListener；\n​\t作用：\n​\t1）runSchemaScripts();运行建表语句；\n​\t2）runDataScripts();运行插入数据的sql语句；\n默认只需要将文件命名为：\nschema-*.sql、data-*.sql 默认规则：schema.sql，schema-all.sql； 可以使用 schema: - classpath:department.sql 指定位置 5、操作数据库：自动配置了JdbcTemplate操作数据库\n2、整合Druid数据源 导入druid数据源 @Configuration public class DruidConfig { @ConfigurationProperties(prefix = \u0026#34;spring.datasource\u0026#34;) @Bean public DataSource druid(){ return new DruidDataSource(); } //配置Druid的监控 //1、配置一个管理后台的Servlet @Bean public ServletRegistrationBean statViewServlet(){ ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), \u0026#34;/druid/*\u0026#34;); Map\u0026lt;String,String\u0026gt; initParams = new HashMap\u0026lt;\u0026gt;(); initParams.put(\u0026#34;loginUsername\u0026#34;,\u0026#34;admin\u0026#34;); initParams.put(\u0026#34;loginPassword\u0026#34;,\u0026#34;123456\u0026#34;); initParams.put(\u0026#34;allow\u0026#34;,\u0026#34;\u0026#34;);//默认就是允许所有访问 initParams.put(\u0026#34;deny\u0026#34;,\u0026#34;192.168.15.21\u0026#34;); bean.setInitParameters(initParams); return bean; } //2、配置一个web监控的filter @Bean public FilterRegistrationBean webStatFilter(){ FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); Map\u0026lt;String,String\u0026gt; initParams = new HashMap\u0026lt;\u0026gt;(); initParams.put(\u0026#34;exclusions\u0026#34;,\u0026#34;*.js,*.css,/druid/*\u0026#34;); bean.setInitParameters(initParams); bean.setUrlPatterns(Arrays.asList(\u0026#34;/*\u0026#34;)); return bean; } } 3、整合MyBatis \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.mybatis.spring.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;mybatis-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.3.1\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 步骤：\n​\t1）配置数据源相关属性（见上一节Druid.\n​\t2）给数据库建表\n​\t3）创建JavaBean\n4）注解版 //指定这是一个操作数据库的mapper @Mapper public interface DepartmentMapper { @Select(\u0026#34;select * from department where id=#{id}\u0026#34;) public Department getDeptById(Integer id); @Delete(\u0026#34;delete from department where id=#{id}\u0026#34;) public int deleteDeptById(Integer id); @Options(useGeneratedKeys = true,keyProperty = \u0026#34;id\u0026#34;) @Insert(\u0026#34;insert into department(departmentName) values(#{departmentName})\u0026#34;) public int insertDept(Department department); @Update(\u0026#34;update department set departmentName=#{departmentName} where id=#{id}\u0026#34;) public int updateDept(Department department); } 问题：\n自定义MyBatis的配置规则；给容器中添加一个ConfigurationCustomizer；\n@org.springframework.context.annotation.Configuration public class MyBatisConfig { @Bean public ConfigurationCustomizer configurationCustomizer(){ return new ConfigurationCustomizer(){ @Override public void customize(Configuration configuration) { configuration.setMapUnderscoreToCamelCase(true); } }; } } 使用MapperScan批量扫描所有的Mapper接口； @MapperScan(value = \u0026#34;com.atguigu.springboot.mapper\u0026#34;) @SpringBootApplication public class SpringBoot06DataMybatisApplication { public static void main(String[] args) { SpringApplication.run(SpringBoot06DataMybatisApplication.class, args); } } 5）配置文件版 mybatis: config-location: classpath:mybatis/mybatis-config.xml 指定全局配置文件的位置 mapper-locations: classpath:mybatis/mapper/*.xml 指定sql映射文件的位置 更多使用参照\nhttp://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/\n4、整合SpringData JPA 1）SpringData简介 2）整合SpringData JPA JPA:ORM（Object Relational Mapping）；\n1）编写一个实体类（bean）和数据表进行映射，并且配置好映射关系；\n//使用JPA注解配置映射关系 @Entity //告诉JPA这是一个实体类（和数据表映射的类. @Table(name = \u0026#34;tbl_user\u0026#34;) //@Table来指定和哪个数据表对应;如果省略默认表名就是user； public class User { @Id //这是一个主键 @GeneratedValue(strategy = GenerationType.IDENTITY)//自增主键 private Integer id; @Column(name = \u0026#34;last_name\u0026#34;,length = 50) //这是和数据表对应的一个列 private String lastName; @Column //省略默认列名就是属性名 private String email; 2）编写一个Dao接口来操作实体类对应的数据表（Repository.\n//继承JpaRepository来完成对数据库的操作 public interface UserRepository extends JpaRepository\u0026lt;User,Integer\u0026gt; { } 3）基本的配置JpaProperties\nspring: jpa: hibernate: # 更新或者创建数据表结构 ddl-auto: update # 控制台显示SQL show-sql: true 七、启动配置原理 几个重要的事件回调机制\n配置在META-INF/spring.factories\nApplicationContextInitializer\nSpringApplicationRunListener\n只需要放在ioc容器中\nApplicationRunner\nCommandLineRunner\n启动流程：\n1、创建SpringApplication对象 initialize(sources); private void initialize(Object[] sources) { //保存主配置类 if (sources != null \u0026amp;\u0026amp; sources.length \u0026gt; 0) { this.sources.addAll(Arrays.asList(sources)); } //判断当前是否一个web应用 this.webEnvironment = deduceWebEnvironment(); //从类路径下找到META-INF/spring.factories配置的所有ApplicationContextInitializer；然后保存起来 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); //从类路径下找到ETA-INF/spring.factories配置的所有ApplicationListener setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //从多个配置类中找到有main方法的主配置类 this.mainApplicationClass = deduceMainApplicationClass(); } 2、运行run方法 public ConfigurableApplicationContext run(String..）args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); //获取SpringApplicationRunListeners；从类路径下META-INF/spring.factories SpringApplicationRunListeners listeners = getRunListeners(args); //回调所有的获取SpringApplicationRunListener.starting()方法 listeners.starting(); try { //封装命令行参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //准备环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //创建环境完成后回调SpringApplicationRunListener.environmentPrepared()；表示环境准备完成 Banner printedBanner = printBanner(environment); //创建ApplicationContext；决定创建web的ioc还是普通的ioc context = createApplicationContext(); analyzers = new FailureAnalyzers(context); //准备上下文环境;将environment保存到ioc中；而且applyInitializers()； //applyInitializers()：回调之前保存的所有的ApplicationContextInitializer的initialize方法 //回调所有的SpringApplicationRunListener的contextPrepared()； // prepareContext(context, environment, listeners, applicationArguments, printedBanner); //prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded（）； //s刷新容器；ioc容器初始化（如果是web应用还会创建嵌入式的Tomcat）；Spring注解版 //扫描，创建，加载所有组件的地方；（配置类，组件，自动配置. refreshContext(context); //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调 //ApplicationRunner先回调，CommandLineRunner再回调 afterRefresh(context, applicationArguments); //所有的SpringApplicationRunListener回调finished方法 listeners.finished(context, null); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } //整个SpringBoot应用启动完成以后返回启动的ioc容器； return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } } 3、事件监听机制 配置在META-INF/spring.factories\nApplicationContextInitializer\npublic class HelloApplicationContextInitializer implements ApplicationContextInitializer\u0026lt;ConfigurableApplicationContext\u0026gt; { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println(\u0026#34;ApplicationContextInitializer...initialize...\u0026#34;+applicationContext); } } SpringApplicationRunListener\npublic class HelloSpringApplicationRunListener implements SpringApplicationRunListener { //必须有的构造器 public HelloSpringApplicationRunListener(SpringApplication application, String[] args){ } @Override public void starting() { System.out.println(\u0026#34;SpringApplicationRunListener...starting...\u0026#34;); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { Object o = environment.getSystemProperties().get(\u0026#34;os.name\u0026#34;); System.out.println(\u0026#34;SpringApplicationRunListener...environmentPrepared..\u0026#34;+o); } @Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println(\u0026#34;SpringApplicationRunListener...contextPrepared...\u0026#34;); } @Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println(\u0026#34;SpringApplicationRunListener...contextLoaded...\u0026#34;); } @Override public void finished(ConfigurableApplicationContext context, Throwable exception) { System.out.println(\u0026#34;SpringApplicationRunListener...finished...\u0026#34;); } } 配置（META-INF/spring.factories.\norg.springframework.context.ApplicationContextInitializer=\\ com.atguigu.springboot.listener.HelloApplicationContextInitializer org.springframework.boot.SpringApplicationRunListener=\\ com.atguigu.springboot.listener.HelloSpringApplicationRunListener 只需要放在ioc容器中\nApplicationRunner\n@Component public class HelloApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println(\u0026#34;ApplicationRunner...run....\u0026#34;); } } CommandLineRunner\n@Component public class HelloCommandLineRunner implements CommandLineRunner { @Override public void run(String..）args) throws Exception { System.out.println(\u0026#34;CommandLineRunner...run...\u0026#34;+ Arrays.asList(args)); } } 八、自定义starter starter：\n​\t1、这个场景需要使用到的依赖是什么？\n​\t2、如何编写自动配置\n@Configuration //指定这个类是一个配置类 @ConditionalOnXXX //在指定条件成立的情况下自动配置类生效 @AutoConfigureAfter //指定自动配置类的顺序 @Bean //给容器中添加组件 @ConfigurationPropertie结合相关xxxProperties类来绑定相关的配置 @EnableConfigurationProperties //让xxxProperties生效加入到容器中 自动配置类要能加载 将需要启动就加载的自动配置类，配置在META-INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration=\\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\\ ​\t3、模式：\n启动器只用来做依赖导入；\n专门来写一个自动配置模块；\n启动器依赖自动配置；别人只需要引入启动器（starter.\nmybatis-spring-boot-starter；自定义启动器名-spring-boot-starter\n步骤：\n1）启动器模块\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;groupId\u0026gt;com.atguigu.starter\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;atguigu-spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;!--启动器--\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!--引入自动配置模块--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;com.atguigu.starter\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;atguigu-spring-boot-starter-autoconfigurer\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;0.0.1-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/project\u0026gt; 2）自动配置模块\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;project xmlns=\u0026#34;http://maven.apache.org/POM/4.0.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\u0026#34;\u0026gt; \u0026lt;modelVersion\u0026gt;4.0.0\u0026lt;/modelVersion\u0026gt; \u0026lt;groupId\u0026gt;com.atguigu.starter\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;atguigu-spring-boot-starter-autoconfigurer\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;0.0.1-SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;packaging\u0026gt;jar\u0026lt;/packaging\u0026gt; \u0026lt;name\u0026gt;atguigu-spring-boot-starter-autoconfigurer\u0026lt;/name\u0026gt; \u0026lt;description\u0026gt;Demo project for Spring Boot\u0026lt;/description\u0026gt; \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.5.10.RELEASE\u0026lt;/version\u0026gt; \u0026lt;relativePath/\u0026gt; \u0026lt;!-- lookup parent from repository --\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;project.build.sourceEncoding\u0026gt;UTF-8\u0026lt;/project.build.sourceEncoding\u0026gt; \u0026lt;project.reporting.outputEncoding\u0026gt;UTF-8\u0026lt;/project.reporting.outputEncoding\u0026gt; \u0026lt;java.version\u0026gt;1.8\u0026lt;/java.version\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!--引入spring-boot-starter；所有starter的基本配置--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; \u0026lt;/project\u0026gt; package com.atguigu.starter; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = \u0026#34;atguigu.hello\u0026#34;) public class HelloProperties { private String prefix; private String suffix; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } } package com.atguigu.starter; public class HelloService { HelloProperties helloProperties; public HelloProperties getHelloProperties() { return helloProperties; } public void setHelloProperties(HelloProperties helloProperties) { this.helloProperties = helloProperties; } public String sayHellAtguigu(String name){ return helloProperties.getPrefix()+\u0026#34;-\u0026#34; +name + helloProperties.getSuffix(); } } package com.atguigu.starter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @ConditionalOnWebApplication //web应用才生效 @EnableConfigurationProperties(HelloProperties.class) public class HelloServiceAutoConfiguration { @Autowired HelloProperties helloProperties; @Bean public HelloService helloService(){ HelloService service = new HelloService(); service.setHelloProperties(helloProperties); return service; } } 更多SpringBoot整合示例 https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples\n","date":"2019-09-23T23:21:10+08:00","permalink":"/2019/09/23/SpringBoot-AndoWeb/","title":"SpringBoot AndoWeb"},{"content":"GitHub支持 Markdown 目录生成 CSDN https://blog.csdn.net/JavaKam/article/details/101020144\n参考项目: flexmark-java i5ting_ztree_toc https://github.com/YuraAAA/Markdown-Toc-Generator/ https://github.com/zhang0peter/markdown2html\n效果图 使用方式 1.下载 github-markdown-toc.jar 2.安装 Java 运行环境; 3.双击运行\n可以用 [TOC] 或 [toc] 开头才会生成目录 , 不写也行\u0026hellip; [TOC] # Heading **some bold** ## Heading 1.1 _some italic ### Heading 1.1.1 ### Heading 1.1.2 **_some bold italic_** #### Heading 1.1.2.1 **_some bold italic_666** ##### Heading 1.1.2.1.1 **_some bold 2222** 效果 目录 Heading some bold Heading 1.1 _some italic Heading 1.1.1 Heading 1.1.2 some bold italic Heading 1.1.2.1 _some bold italic_666 Heading 1.1.2.1.1 _some bold 2222 支持html文件生成目录 在线示例\n项目源码 README\n","date":"2019-09-23T15:04:45+08:00","permalink":"/2019/09/23/markdown-toc-dom/","title":"Markdown目录生成"},{"content":"GitHub + Jekyll 参考项目 👉 https://github.com/ShawnTeoh/shawnteoh.github.io\n","date":"2019-09-23T14:55:39+08:00","permalink":"/2019/09/23/first-github-blog/","title":"GitHub + Jekyll"}]