
不想当码农的程序员
源码地址 https://github.com/javanan/slife slife spring boot 搭建的一个企业级快速开发脚手架。 技术栈 Spring Boot MySQL Freemark SiteMesh Shiro Bootstrap mybatis、mybatisPlus redis Activiti 编码约定 系统分为controller、service、dao层。controller主要负责转发、service主要负责业务逻辑、dao主要是数据库的操作。 文件名称约定 在页面文件夹中,按照功能模块分别建立不同的文件夹存放页面,如用户的页面就放在user文件夹中,而角色的就放在role文件夹中。 页面如果是列表类型的。页面的文件名用list.ftl命名。 页面如果是详情类型的。页面的文件名用detail.ftl命名。 controller、service、dao方法名称约定 如果是增加数据操作用insert做前缀。 如果是删除操作用delete做前缀 如果是修改操作用update做前缀 如果是查询操作用select做前缀 数据库读写分离 缓存ecache、redis 新建模块 new Module GroupId --->com.slife ArtifactId---> slife-模块名称 如 slife-activiti Version --> 版本号 如 1.0SNAPSHOT Module-Name--> slife-模块名称 如 slife-activiti 提交新建模块 pom 文件引入 <name>slife-模块名称</name> <dependencies> <dependency> <groupId>com.slife</groupId> <artifactId>slife-common</artifactId> </dependency> . . .其他的依赖 . </dependencies> JDK版本 1.8 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <compilerArgs> <arg>-parameters</arg> </compilerArgs> <useIncrementalCompilation>false</useIncrementalCompilation> </configuration> </plugin> </plugins> </build> 新建一个功能模块 1、创建数据库 2、创建entity类 3、创建service类 4、创建controller类 5、创建list界面 5.1 到其他list复制代码过 5.2 修改 <script> var url = "${base}/sys/user/"; </script> 中的 url 为你刚刚创建的 controller的类 @Controller @RequestMapping(value = "/sys/user") public class SysUserController extends BaseController { 的 @RequestMapping(value = "/sys/user") 值 5.3 修改搜索条件 目前的搜索条件有 /** * 等于 */ public static final String SEARCH_EQ="search_eq_"; /** * 左模糊 */ public static final String SEARCH_LLIKE="search_llike_"; /** * 右模糊 */ public static final String SEARCH_RLIKE="search_rlike_"; /*** * 全模糊 */ public static final String SEARCH_LIKE="search_like_"; <input type="text" class="form-filter input-sm _search" name="search_eq_login_name"> 只要在 input中 的 name 加入 search_eq_ 前缀 再加数据库中的字段名称即可 5.4 修改表格的字段名称 项目截图介绍 系统用户管理 系统菜单管理 系统角色管理 RBAC权限管理模型 日志监控 系统自定义注解,结合AOP,监控用户操作行为 Spring Boot Admin 监控 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-erd7cAoP-1577440125214)(ya)] Activit工作流 API文档 swaggerUi接口文档展示 数据库监控 使用druid监控数据库健康。本来这里是三个数据源的,使用aop动态的书写切换。没上传到git,需要的同学可以私我 maven构建 多模块开发 根据不同的业务,不在不同的业务模块中开发,如果基本的用户、组织等的管理在 sys模块 日志的业务逻辑在 log模块 可插拔式部署 把不同的模块打包成jar,对应的freemark文件也打包在对应的模块jar中。实现了功能模块的可插拔式部署。
欢迎关注 公众号 逗着玩,一起学习一起交流进步! 排序算法是《数据结构与算法》中最基本的算法之一。 排序算法可以分为内部排序和外部排序。 内部排序是数据记录在内存中进行排序。 而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。 常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。 用一张图概括: 关于时间复杂度: 平方阶 (O(n2)) 排序 各类简单排序:直接插入、直接选择和冒泡排序。 线性对数阶 (O(nlog2n)) 排序 快速排序、堆排序和归并排序; O(n1+§)) 排序,§ 是介于 0 和 1 之间的常数。 希尔排序 线性阶 (O(n)) 排序 基数排序,此外还有桶、箱排序。 关于稳定性: 稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。 不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。 1. 冒泡排序 1.1 算法步骤 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 针对所有的元素重复以上的步骤,除了最后一个。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 1.2 动画演示 1.3 参考代码 // Java 代码实现 public class BubbleSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); for (int i = 1; i < arr.length; i++) { // 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。 boolean flag = true; for (int j = 0; j < arr.length - i; j++) { if (arr[j] > arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; flag = false; } } if (flag) { break; } } return arr; } } 2. 选择排序 2.1 算法步骤 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。 重复第二步,直到所有元素均排序完毕。 2.2 动画演示 2.3 参考代码 //Java 代码实现 public class SelectionSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); // 总共要经过 N-1 轮比较 for (int i = 0; i < arr.length - 1; i++) { int min = i; // 每轮需要比较的次数 N-i for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[min]) { // 记录目前能找到的最小值元素的下标 min = j; } } // 将找到的最小值和i位置所在的值进行交换 if (i != min) { int tmp = arr[i]; arr[i] = arr[min]; arr[min] = tmp; } } return arr; } } 3. 插入排序 3.1 算法步骤 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。) 3.2 动画演示 3.3 参考代码 //Java 代码实现 public class InsertSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); // 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的 for (int i = 1; i < arr.length; i++) { // 记录要插入的数据 int tmp = arr[i]; // 从已经排序的序列最右边的开始比较,找到比其小的数 int j = i; while (j > 0 && tmp < arr[j - 1]) { arr[j] = arr[j - 1]; j--; } // 存在比其小的数,插入 if (j != i) { arr[j] = tmp; } } return arr; } } 关注我的公众号 获取更多资料 程序员必备(云服务器,学习练手,必须要 一天的饭钱换一年的练手机会) 阿里云新老账户都是享受1折,89一年(老用户比较贵,但是其实你注册一个新账号就好,用家人的支付宝直接扫描一下,几分钟的事情),https://www.aliyun.com/minisite/goods?userCode=vf2b5zld&share_source=copy_link 腾讯云也有https://cloud.tencent.com/act/cps/redirect?redirect=1052&cps_key=1cdaea7b77fe67188b187bce55796594&from=console 4. 希尔排序 4.1 算法步骤 选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1; 按增量序列个数 k,对序列进行 k 趟排序; 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。 4.2 动画演示 4.3 参考代码 //Java 代码实现 public class ShellSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); int gap = 1; while (gap < arr.length) { gap = gap * 3 + 1; } while (gap > 0) { for (int i = gap; i < arr.length; i++) { int tmp = arr[i]; int j = i - gap; while (j >= 0 && arr[j] > tmp) { arr[j + gap] = arr[j]; j -= gap; } arr[j + gap] = tmp; } gap = (int) Math.floor(gap / 3); } return arr; } } 5. 归并排序 5.1 算法步骤 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列; 设定两个指针,最初位置分别为两个已经排序序列的起始位置; 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置; 重复步骤 3 直到某一指针达到序列尾; 将另一序列剩下的所有元素直接复制到合并序列尾。 5.2 动画演示 5.3 参考代码 public class MergeSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); if (arr.length < 2) { return arr; } int middle = (int) Math.floor(arr.length / 2); int[] left = Arrays.copyOfRange(arr, 0, middle); int[] right = Arrays.copyOfRange(arr, middle, arr.length); return merge(sort(left), sort(right)); } protected int[] merge(int[] left, int[] right) { int[] result = new int[left.length + right.length]; int i = 0; while (left.length > 0 && right.length > 0) { if (left[0] <= right[0]) { result[i++] = left[0]; left = Arrays.copyOfRange(left, 1, left.length); } else { result[i++] = right[0]; right = Arrays.copyOfRange(right, 1, right.length); } } while (left.length > 0) { result[i++] = left[0]; left = Arrays.copyOfRange(left, 1, left.length); } while (right.length > 0) { result[i++] = right[0]; right = Arrays.copyOfRange(right, 1, right.length); } return result; } } 6. 快速排序 6.1 算法步骤 从数列中挑出一个元素,称为 “基准”(pivot); 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序; 6.2 动画演示 6.3 参考代码 //Java 代码实现 public class QuickSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); return quickSort(arr, 0, arr.length - 1); } private int[] quickSort(int[] arr, int left, int right) { if (left < right) { int partitionIndex = partition(arr, left, right); quickSort(arr, left, partitionIndex - 1); quickSort(arr, partitionIndex + 1, right); } return arr; } private int partition(int[] arr, int left, int right) { // 设定基准值(pivot) int pivot = left; int index = pivot + 1; for (int i = index; i <= right; i++) { if (arr[i] < arr[pivot]) { swap(arr, i, index); index++; } } swap(arr, pivot, index - 1); return index - 1; } private void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } 7. 堆排序 7.1 算法步骤 创建一个堆 H[0……n-1]; 把堆首(最大值)和堆尾互换; 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置; 重复步骤 2,直到堆的尺寸为 1。 7.2 动画演示 7.3 参考代码 //Java 代码实现 public class HeapSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); int len = arr.length; buildMaxHeap(arr, len); for (int i = len - 1; i > 0; i--) { swap(arr, 0, i); len--; heapify(arr, 0, len); } return arr; } private void buildMaxHeap(int[] arr, int len) { for (int i = (int) Math.floor(len / 2); i >= 0; i--) { heapify(arr, i, len); } } private void heapify(int[] arr, int i, int len) { int left = 2 * i + 1; int right = 2 * i + 2; int largest = i; if (left < len && arr[left] > arr[largest]) { largest = left; } if (right < len && arr[right] > arr[largest]) { largest = right; } if (largest != i) { swap(arr, i, largest); heapify(arr, largest, len); } } private void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } 8. 计数排序 8.1 算法步骤 花O(n)的时间扫描一下整个序列 A,获取最小值 min 和最大值 max 开辟一块新的空间创建新的数组 B,长度为 ( max - min + 1) 数组 B 中 index 的元素记录的值是 A 中某元素出现的次数 最后输出目标整数序列,具体的逻辑是遍历数组 B,输出相应元素以及对应的个数 8.2 动画演示 8.3 参考代码 //Java 代码实现 public class CountingSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); int maxValue = getMaxValue(arr); return countingSort(arr, maxValue); } private int[] countingSort(int[] arr, int maxValue) { int bucketLen = maxValue + 1; int[] bucket = new int[bucketLen]; for (int value : arr) { bucket[value]++; } int sortedIndex = 0; for (int j = 0; j < bucketLen; j++) { while (bucket[j] > 0) { arr[sortedIndex++] = j; bucket[j]--; } } return arr; } private int getMaxValue(int[] arr) { int maxValue = arr[0]; for (int value : arr) { if (maxValue < value) { maxValue = value; } } return maxValue; } } 9. 桶排序 9.1 算法步骤 设置固定数量的空桶。 把数据放到对应的桶中。 对每个不为空的桶中数据进行排序。 拼接不为空的桶中数据,得到结果 9.2 动画演示 9.3 参考代码 //Java 代码实现 public class BucketSort implements IArraySort { private static final InsertSort insertSort = new InsertSort(); @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); return bucketSort(arr, 5); } private int[] bucketSort(int[] arr, int bucketSize) throws Exception { if (arr.length == 0) { return arr; } int minValue = arr[0]; int maxValue = arr[0]; for (int value : arr) { if (value < minValue) { minValue = value; } else if (value > maxValue) { maxValue = value; } } int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1; int[][] buckets = new int[bucketCount][0]; // 利用映射函数将数据分配到各个桶中 for (int i = 0; i < arr.length; i++) { int index = (int) Math.floor((arr[i] - minValue) / bucketSize); buckets[index] = arrAppend(buckets[index], arr[i]); } int arrIndex = 0; for (int[] bucket : buckets) { if (bucket.length <= 0) { continue; } // 对每个桶进行排序,这里使用了插入排序 bucket = insertSort.sort(bucket); for (int value : bucket) { arr[arrIndex++] = value; } } return arr; } /** * 自动扩容,并保存数据 * * @param arr * @param value */ private int[] arrAppend(int[] arr, int value) { arr = Arrays.copyOf(arr, arr.length + 1); arr[arr.length - 1] = value; return arr; } } 10. 基数排序 10.1 算法步骤 将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零 从最低位开始,依次进行一次排序 从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列 10.2 动画演示 10.3 参考代码 //Java 代码实现 public class RadixSort implements IArraySort { @Override public int[] sort(int[] sourceArray) throws Exception { // 对 arr 进行拷贝,不改变参数内容 int[] arr = Arrays.copyOf(sourceArray, sourceArray.length); int maxDigit = getMaxDigit(arr); return radixSort(arr, maxDigit); } /** * 获取最高位数 */ private int getMaxDigit(int[] arr) { int maxValue = getMaxValue(arr); return getNumLenght(maxValue); } private int getMaxValue(int[] arr) { int maxValue = arr[0]; for (int value : arr) { if (maxValue < value) { maxValue = value; } } return maxValue; } protected int getNumLenght(long num) { if (num == 0) { return 1; } int lenght = 0; for (long temp = num; temp != 0; temp /= 10) { lenght++; } return lenght; } private int[] radixSort(int[] arr, int maxDigit) { int mod = 10; int dev = 1; for (int i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) { // 考虑负数的情况,这里扩展一倍队列数,其中 [0-9]对应负数,[10-19]对应正数 (bucket + 10) int[][] counter = new int[mod * 2][0]; for (int j = 0; j < arr.length; j++) { int bucket = ((arr[j] % mod) / dev) + mod; counter[bucket] = arrayAppend(counter[bucket], arr[j]); } int pos = 0; for (int[] bucket : counter) { for (int value : bucket) { arr[pos++] = value; } } } return arr; } private int[] arrayAppend(int[] arr, int value) { arr = Arrays.copyOf(arr, arr.length + 1); arr[arr.length - 1] = value; return arr; } } 说明:本文思路来源于:https://github.com/hustcc/JS-Sorting-Algorithm,整理人 hustcc。
最近很多阿里云双 12 做活动,优惠力度还挺大的,很多朋友都买以最低的价格买到了自己的云服务器。不论是作为学习机还是部署自己的小型网站或者服务来说都是很不错的! 但是,很多朋友都不知道如何正确去使用。下面我简单分享一下自己的使用经验。 总结一下,主要涉及下面几个部分,对于新手以及没有这么使用过云服务的朋友还是比较友好的: 善用阿里云镜像市场节省安装 Java 环境的时间,相关说明都在根目录下的 readme.txt. 文件里面; 本地通过 SSH 连接阿里云服务器很容易,配置好 Host地址,通过 root 用户加上实例密码直接连接即可。 本地连接 MySQL 数据库需要简单配置一下安全组和并且允许 root 用户在任何地方进行远程登录。 通过 Alibaba Cloud Toolkit 部署 Spring Boot 项目到阿里云服务器真的很方便。 **[活动地址]最近很多阿里云双 11 做活动,优惠力度还挺大的,很多朋友都买以最低的价格买到了自己的云服务器。不论是作为学习机还是部署自己的小型网站或者服务来说都是很不错的! 但是,很多朋友都不知道如何正确去使用。下面我简单分享一下自己的使用经验。 总结一下,主要涉及下面几个部分,对于新手以及没有这么使用过云服务的朋友还是比较友好的: 善用阿里云镜像市场节省安装 Java 环境的时间,相关说明都在根目录下的 readme.txt. 文件里面; 本地通过 SSH 连接阿里云服务器很容易,配置好 Host地址,通过 root 用户加上实例密码直接连接即可。 本地连接 MySQL 数据库需要简单配置一下安全组和并且允许 root 用户在任何地方进行远程登录。 通过 Alibaba Cloud Toolkit 部署 Spring Boot 项目到阿里云服务器真的很方便。 活动地址 (仅限新人,老用户可以考虑使用家人或者朋友账号购买,推荐799/3年 2核4G 这个性价比和适用面更广) 善用阿里云镜像市场节省安装环境的时间 基本的购买流程这里就不多说了,另外这里需要注意的是:其实 Java 环境是不需要我们手动安装配置的,阿里云提供的镜像市场有一些常用的环境。 阿里云镜像市场是指阿里云建立的、由镜像服务商向用户提供其镜像及相关服务的网络平台。这些镜像在操作系统上整合了具体的软件环境和功能,比如Java、PHP运行环境、控制面板等,供有相关需求的用户开通实例时选用。 具体如何在购买云服务器的时候通过镜像创建实例或者已有ECS用户如何使用镜像可以查看官方详细的介绍,地址: https://help.aliyun.com/knowledge_detail/41987.html?spm=a2c4g.11186631.2.1.561e2098dIdCGZ 当我们成功购买服务器之后如何通过 SSH 连接呢? 创建好 ECS 后,你绑定的手机会收到短信,会告知你初始密码的。你可以登录管理控制台对密码进行修改,修改密码需要在管理控制台重启服务器才能生效。 你也可以在阿里云 ECS 控制台重置实例密码,第一种连接方式是直接在阿里云服务器管理的网页上连接。如上图所示, 点击远程连接,然后输入远程连接密码,这个并不是你重置实例密码得到的密码,如果忘记了直接修改远程连接密码即可。 第二种方式是在本地通过命令或者软件连接。 推荐使用这种方式,更加方便。 Windows 推荐使用 Xshell 连接,具体方式如下: Window电脑在家,这里直接用找到的一些图片给大家展示一个。 接着点开,输入账号:root,命名输入刚才设置的密码,点ok就可以了 Mac 或者 Linux 系统都可以直接使用 ssh 命令进行连接,非常方便。 成功连接之后,控制台会打印出如下消息。 ➜ ~ ssh root@47.107.159.12 -p 22 root@47.107.159.12's password: Last login: Wed Oct 30 09:31:31 2019 from 220.249.123.170 Welcome to Alibaba Cloud Elastic Compute Service ! 欢迎使用 Tomcat8 JDK8 Mysql5.7 环境 使用说明请参考 /root/readme.txt 文件 我当时选择是阿里云提供好的 Java 环境,自动就提供了 Tomcat、 JDK8 、Mysql5.7,所以不需要我们再进行安装配置了,节省了很多时间。另外,需要注意的是:一定要看 /readme.txt ,Tomcat、 JDK8 、Mysql5.7相关配置以及安装路径等说明都在里面。 如何连接数据库? 如需外网远程访问mysql 请参考以上网址 设置mysql及阿里云安全组。 Mysql为了安全性,在默认情况下用户只允许在本地登录,但是可以使用 SSH 方式连接。如果我们不想通过 SSH 方式连接的话就需要对 MySQL 进行简单的配置。 #允许root用户在任何地方进行远程登录,并具有所有库任何操作权限: # *.*代表所有库表 “%”代表所有IP地址 mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY "自定义密码" WITH GRANT OPTION; Query OK, 0 rows affected, 1 warning (0.00 sec) #刷新权限。 mysql>flush privileges; #退出mysql mysql>exit #重启MySQL生效 [root@snailclimb]# systemctl restart mysql 这样的话,我们就能在本地进行连接了。Windows 推荐使用Navicat或者SQLyog。 Window电脑在家,这里用 Mac 上的MySQL可视化工具Sequel Pro给大家演示一下。 如何把一个Spring Boot 项目部署到服务器上呢? 默认大家都是用 IDEA 进行开发。另外,你要有一个简单的 Spring Boot Web 项目。如果还不了解 Spring Boot 的话,一个简单的 Spring Boot 版 "Hello World "项目,地址如下: https://github.com/Snailclimb/springboot-guide/blob/master/docs/start/springboot-hello-world.md 。 1.下载一个叫做 Alibaba Cloud Toolkit 的插件。 2.进入 Preference 配置一个 Access Key ID 和 Access Key Secret。 3.部署项目到 ECS 上。 按照上面这样填写完基本配置之后,然后点击 run 运行即可。运行成功,控制台会打印出如下信息: [INFO] Deployment File is Uploading... [INFO] IDE Version:IntelliJ IDEA 2019.2 [INFO] Alibaba Cloud Toolkit Version:2019.9.1 [INFO] Start upload hello-world-0.0.1-SNAPSHOT.jar [INFO][##################################################] 100% (18609645/18609645) [INFO] Succeed to upload, 18609645 bytes have been uploaded. [INFO] Upload Deployment File to OSS Success [INFO] Target Deploy ECS: { 172.18.245.148 / 47.107.159.12 } [INFO] Command: { source /etc/profile; cd /springboot; } Tip: The deployment package will be temporarily stored in Alibaba Cloud Security OSS and will be deleted after the deployment is complete. Please be assured that no one can access it except you. [INFO] Create Deploy Directory Success. [INFO] Deployment File is Downloading... [INFO] Download Deployment File from OSS Success [INFO] File Upload Total time: 16.676 s 通过控制台答应出的信息可以看出:通过这个插件会自动把这个 Spring Boot 项目打包成一个 jar 包,然后上传到你的阿里云服务器中指定的文件夹中,你只需要登录你的阿里云服务器,然后通过 java -jar hello-world-0.0.1-SNAPSHOT.jar命令运行即可。 [root@snailclimb springboot]# ll total 18176 -rw-r--r-- 1 root root 18609645 Oct 30 08:25 hello-world-0.0.1-SNAPSHOT.jar [root@snailclimb springboot]# java -jar hello-world-0.0.1-SNAPSHOT.jar 然后你就可以在本地访问访问部署在你的阿里云 ECS 上的服务了。
测评产品 实例:t5 1核2G;磁盘:40G高效云盘带宽:1m 购买机器 测评点: t5提升了baseline,实际cpu是否提升? 是否可以满足用户正常使用需求? 测评过程 第一步:
本站小福利 点我获取阿里云优惠券 为什么大多数程序员相进BAT工作? 在中国互联网技术发展过程中,BAT带给我们程序员太多的回忆,20年发展过程中,他们各自形成自己的的体系和战略规划,掌握着中国互联网信息技术,很多新技术都是BAT创新,然后提供技术支持给我们普通的开发者,这就是程序员进入BAT工作最有力的说服力。 第一题:二叉搜索树与双向链表输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 解题思路由于 BST 的特性,采用中序遍历正好符合排序要考虑 root 节点要与 左节点的最大值连接,与右节点的最小值连接增加一个已排序链表的指针,指向最后一个已排序节点public TreeNode Convert(TreeNode pRootOfTree) { if (pRootOfTree == null) { return null; } TreeNode[] nodeList = {new TreeNode(-1) };ConvertToLink(pRootOfTree, nodeList);TreeNode cursor = pRootOfTree;while (cursor.left != null) { cursor = cursor.left; }cursor.right.left = null;return cursor.right;}private void ConvertToLink(TreeNode root, TreeNode[] nodeList) {if (root == null) { return; }ConvertToLink(root.left, nodeList);root.left = nodeList[0];nodeList[0].right = root;nodeList[0] = root;ConvertToLink(root.right, nodeList);}第二题:合并两个排序的链表输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 解题思路双指针指向两个链表循环选取最小值,加入结果集public ListNode Merge(ListNode list1, ListNode list2) { ListNode head = new ListNode(-1); ListNode cursor = head; while (list1 != null || list2 != null) { if (list1 == null) { while (list2 != null) { cursor.next = list2; cursor = cursor.next; list2 = list2.next; } continue; } if (list2 == null) { while (list1 != null) { cursor.next = list1; cursor = cursor.next; list1 = list1.next; } continue; } if (list1.val < list2.val) { cursor.next = list1; cursor = cursor.next; list1 = list1.next; } else { cursor.next = list2; cursor = cursor.next; list2 = list2.next; } } return head.next; }第三题:树的子结构输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构) 解题思路遍历查找相等根节点通过递归查找当前根节点下是否包含子树 root2public Boolean HasSubtree(TreeNode root1, TreeNode root2) { if (root2 == null) { return false; } LinkedList<TreeNode> pipeline = new LinkedList<>(); pipeline.addLast(root1); while (!pipeline.isEmpty()) { TreeNode node = pipeline.pop(); if (node == null) { continue; } pipeline.addLast(node.left); pipeline.addLast(node.right); if (node.val == root2.val && isSub(node, root2)) { return true; } } return false; }private Boolean isSub(TreeNode root1, TreeNode root2) { if (root1 == null && root2 == null) { return true; } if (root1 == null) { return false; } if (root2 == null) { return true; } if (root1.val == root2.val) { return isSub(root1.left, root2.left) && isSub(root1.right, root2.right); } else { return false; } }第四题:二叉树的镜像操作给定的二叉树,将其变换为源二叉树的镜像。 输入描述: 二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树 8 / \ 10 6 / \ / \ 11 9 7 5 解题思路从上到下进行左右节点交换public void Mirror(TreeNode root) { if (root == null) return; TreeNode temp = root.left; root.left = root.right; root.right = temp; Mirror(root.left); Mirror(root.right); }第五题:顺时针打印矩阵输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10. 解题思路通过4个指针,表示可打印区域,并对区域进行收缩非 n*n 的矩阵,对于剩余非 4 边遍历的元素,要考虑边界public ArrayList printMatrix(int[][] matrix) { ArrayList<Integer> res = new ArrayList<>(); if (matrix.length == 0) { return res; } if (matrix.length == 1) { for (int i : matrix[0]) { res.add(i); } return res; } int top = 0, bottom = matrix.length - 1, left = 0, right = matrix[0].length - 1; for (; left <= right && top <= bottom; ) { if (top == bottom) { for (int i = left; i <= right; i++) { res.add(matrix[top][i]); } break; } if (left == right) { for (int i = top; i <= bottom; i++) { res.add(matrix[i][left]); } break; } for (int p = left; p <= right; p++) { res.add(matrix[top][p]); } top++; for (int p = top; p <= bottom; p++) { res.add(matrix[p][right]); } right--; for (int p = right; p >= left; p--) { res.add(matrix[bottom][p]); } bottom--; for (int p = bottom; p >= top; p--) { res.add(matrix[p][left]); } left++; } return res; }第六题:包含min函数的栈定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min 函数(时间复杂度应为O(1))。 解题思路通过增加最小栈来记录当前最小节点private LinkedList stack = new LinkedList<>();private LinkedList min = new LinkedList<>();public void push(int node) { stack.addLast(node); if (min.isEmpty()) { min.addLast(node); return; } if (node < min.peekLast()) { min.addLast(node); } else { min.addLast(min.peekLast()); } }public void pop() { if (stack.isEmpty()) { return; } stack.removeLast(); min.removeLast(); }public int top() { if (stack.peekLast() == null) { return 0; } return stack.peekLast(); }public int min() { if (min.peekLast() == null) { return 0; } return min.peekLast(); }第七题:栈的压入、弹出序列输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的) 解题思路通过 Stack 进行模拟 push,当 pop 的节点等于 Stack 的 top 节点时,pop Stack最后如果 Stack 剩余数据,则判定为 falsepublic Boolean IsPopOrder(int[] pushA, int[] popA) { if (pushA.length != popA.length) { return false; } if (pushA.length == 0) { return false; } LinkedList<Integer> stack = new LinkedList<>(); int j = 0; for (int value : pushA) { stack.addLast(value); while (stack.peekLast() != null && popA[j] == stack.getLast()) { j++; stack.removeLast(); } } return stack.isEmpty(); }第八题:从上往下打印二叉树从上往下打印出二叉树的每个节点,同层节点从左至右打印。 解题思路层次遍历,通过队列进行辅助遍历public ArrayList PrintFromTopToBottom(TreeNode root) { ArrayList<Integer> res = new ArrayList<>(); LinkedList<TreeNode> nodeQueue = new LinkedList<>(); if (root == null) { return res; } nodeQueue.addLast(root); while (!nodeQueue.isEmpty()) { TreeNode node = nodeQueue.pollFirst(); if (node == null) { continue; } nodeQueue.addLast(node.left); nodeQueue.addLast(node.right); res.add(node.val); } return res; }第九题:二叉搜索树的后序遍历序列输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出 Yes ,否则输出 No 。假设输入的数组的任意两个数字都互不相同。 解题思路后序遍历中,最后一个节点为 root 节点由于 BST 的左子树都小于 root,右子树都大于 root,那么可以判定该节点是否为 BST依次类推,通过递归方式,再判定左右子树public Boolean VerifySquenceOfBST(int[] sequence) { if (sequence.length == 0) { return false; } if (sequence.length == 1) { return true; } return isBST(sequence, 0, sequence.length - 1); }private Boolean isBST(int[] sequence, int start, int end) { if (start < 0 || end < 0 || start >= end) { return true; } int rootV = sequence[end]; int rightIndex = -1, rightV = Integer.MIN_VALUE; for (int i = start; i < end; i++) { if (rightV == Integer.MIN_VALUE && sequence[i] > rootV) { rightV = sequence[i]; rightIndex = i; continue; } if (rightV != Integer.MIN_VALUE && sequence[i] < rootV) { return false; } } return isBST(sequence, start, rightIndex - 1) && isBST(sequence, rightIndex, end - 1); }第十题:二叉树中和为某一值的路径输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的 list 中,数组长度大的数组靠前) 解题思路将走过的路径记录下来,当走过路径总和 = target 并且当前节点是叶子节点时,该路径符合要求通过递归遍历所有可能的路径public ArrayList> FindPath(TreeNode root, int target) { ArrayList<ArrayList<Integer>> res = new ArrayList<>(); FindPath(res, new LinkedList<>(), root, 0, target); res.sort(Comparator.comparingint(list -> -list.size())); return res; }private void FindPath(ArrayList> res, LinkedList<Integer> path, TreeNode node, int pathSum, int target) { if (node == null) { return; } if (pathSum > target) { return; } if (pathSum + node.val == target && node.right == null && node.left == null) { ArrayList<Integer> resPath = new ArrayList<>(path); resPath.add(node.val); res.add(resPath); return; } path.addLast(node.val); if (node.left != null) { FindPath(res, path, node.left, pathSum + node.val, target); } if (node.right != null) { FindPath(res, path, node.right, pathSum + node.val, target); } path.removeLast(); }第十一题:复杂链表的复制输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head 。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) 解题思路复制每个节点,如:复制节点 A 得到 A1 ,将 A1 插入节点 A 后面遍历链表,并将 A1->random = A->random->next;将链表拆分成原链表和复制后的链表public RandomListNode Clone(RandomListNode pHead) { if (pHead == null) { return null; } RandomListNode cursor = pHead; while (cursor != null) { RandomListNode copyNode = new RandomListNode(cursor.label); RandomListNode nextNode = cursor.next; cursor.next = copyNode; copyNode.next = nextNode; cursor = nextNode; } cursor = pHead; while (cursor != null) { RandomListNode copyNode = cursor.next; if (cursor.random == null) { cursor = copyNode.next; continue; } copyNode.random = cursor.random.next; cursor = copyNode.next; } RandomListNode copyHead = pHead.next; cursor = pHead; while (cursor.next != null) { RandomListNode copyNode = cursor.next; cursor.next = copyNode.next; cursor = copyNode; } return copyHead; }第十二题:字符串的排列输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。 解题思路将字符串划分为两个部分,第一个字符以及后面的其他字符将第一个字符和后面所有字符进行交换对于 abc 这个字符串,计算出的排列顺序为: abcacbbacbcacbacab代码: public ArrayList Permutation(String str) { Set<String> res = new HashSet<>(); if (str == null || str.length() == 0) { return new ArrayList<>(); } Permutation(res, str.toCharArray(), 0); ArrayList<String> list = new ArrayList<>(res); list.sort(String::compareTo); return list; }private void Permutation(Set res, char[] chars, int start) { if (start == chars.length) { res.add(new String(chars)); return; } for (int i = start; i < chars.length; i++) { swap(chars, start, i); Permutation(res, chars, start + 1); swap(chars, start, i); } }private void swap(char[] chars, int i, int j) { char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; }第十三题:数组中出现次数超过一半的数字数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出 2 。如果不存在则输出 0。 解题思路由于数组的特性,在排序数组中,超过半数的数字一定包含中位数通过 partition 方法,借用快排的思想,随机选取一个 key,将数组中小于 key 的移动到 key 的左侧,数组中大于 key 的移动到 key 的右侧最终找到中位数的下标,还需要检查中位数是否超过半数public int MoreThanHalfNum_Solution(int[] array) { int start = 0, end = array.length - 1; int mid = array.length / 2; int index = partition(array, start, end); if (index == mid) { return array[index]; } while (index != mid && start <= end) { if (index > mid) { end = index - 1; index = partition(array, start, end); } else { start = index + 1; index = partition(array, start, end); } } if (checkIsHalf(array, index)) return array[index]; return 0; }private Boolean checkIsHalf(int[] array, int index) { if (index < 0) { return false; } int count = 0; for (int i : array) { if (array[index] == i) { count++; } } return count > array.length / 2; }private int partition(int[] array, int start, int end) { if (start >= array.length || start < 0 || end >= array.length || end < 0) { return -1; } int key = array[start]; int left = start, right = end; while (left < right) { while (left < right && array[right] >= key) { right--; } if (left < right) { array[left] = array[right]; left++; } while (left < right && array[left] <= key) { left++; } if (left < right) { array[right] = array[left]; right--; } } array[left] = key; return left; }第十四题:最小的K个数输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。 解题思路 Partition该算法基于 Partition public ArrayList GetLeastNumbers_Solution_Partition(int[] input, int k) { ArrayList<Integer> res = new ArrayList<>(); if (k > input.length || k < 1) { return res; } int start = 0, end = input.length - 1; int index = partition(input, start, end); while (index != k - 1) { if (index > k - 1) { end = index - 1; index = partition(input, start, end); } else { start = index + 1; index = partition(input, start, end); } } for (int i = 0; i < input.length && i < k; i++) { res.add(input[i]); } return res; }private int partition(int[] nums, int start, int end) { int left = start, right = end; int key = nums[left]; while (left < right) { while (left < right && nums[right] > key) { right--; } if (left < right) { nums[left] = nums[right]; left++; } while (left < right && nums[left] <= key) { left++; } if (left < right) { nums[right] = nums[left]; right++; } } nums[left] = key; return left; } 小根堆算法该算法基于小根堆,适合海量数据,时间复杂度为:n*logk public ArrayList GetLeastNumbers_Solution(int[] input, int k) { ArrayList<Integer> res = new ArrayList<>(); if (k > input.length||k==0) { return res; } for (int i = input.length - 1; i >= 0; i--) { minHeap(input, 0, i); swap(input, 0, i); res.add(input[i]); if (res.size() == k) break; } return res; }private void minHeap(int[] heap, int start, int end) { if (start == end) { return; } int childLeft = start * 2 + 1; int childRight = childLeft + 1; if (childLeft <= end) { minHeap(heap, childLeft, end); if (heap[childLeft] < heap[start]) { swap(heap, start, childLeft); } } if (childRight <= end) { minHeap(heap, childRight, end); if (heap[childRight] < heap[start]) { swap(heap, start, childRight); } } }private void swap(int[] nums, int a, int b) { int t = nums[a]; nums[a] = nums[b]; nums[b] = t; }第十五题:连续子数组的最大和例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1) 解题思路通过动态规划计算最大和, $$ f(i) $$ 定义为以第 $$ i $$ 个数字结尾的子数组的最大和,那么 $$ max(f(i)) $$ 就有以下公式: $$ max(f(i))=begin{cases} num[i] & i=0 or f(i)<0\ num[i]+f(i) & ine0 and f(i)>0 end{ cases } $$ public int FindGreatestSumOfSubArray(int[] array) { if (array == null || array.length == 0) { return 0; } int max = array[0]; int sum = 0; for (int a : array) { if (sum + a > a) { sum += a; } else { sum = a; } if (sum > max) { max = sum; } } return max; } 我的官网我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=vf2b5zld
本站小福利 点我获取阿里云优惠券 原文作者:github:CL0610/Java-concurrency免责声明: 1.本文所转载文章均来自公开网络。2.如果出处标注有误或侵犯到原著作者权益,请联系删除。3.转载文章请注明原文链接和作者,否则产生的任何版权纠纷均与本站无关。 1. final的简介 final可以修饰变量,方法和类,用于表示所修饰的内容一旦赋值之后就不会再被改变,比如String类就是一个final类型的类。即使能够知道final具体的使用方法,我想对final在多线程中存在的重排序问题也很容易忽略,希望能够一起做下探讨。 2. final的具体使用场景 final能够修饰变量,方法和类,也就是final使用范围基本涵盖了java每个地方,下面就分别以锁修饰的位置:变量,方法和类分别来说一说。 2.1 变量 在java中变量,可以分为成员变量以及方法局部变量。因此也是按照这种方式依次来说,以避免漏掉任何一个死角。 2.1.1 final成员变量 通常每个类中的成员变量可以分为类变量(static修饰的变量)以及实例变量。针对这两种类型的变量赋初值的时机是不同的,类变量可以在声明变量的时候直接赋初值或者在静态代码块中给类变量赋初值。而实例变量可以在声明变量的时候给实例变量赋初值,在非静态初始化块中以及构造器中赋初值。类变量有两个时机赋初值,而实例变量则可以有三个时机赋初值。当final变量未初始化时系统不会进行隐式初始化,会出现报错。这样说起来还是比较抽象,下面用具体的代码来演示。(代码涵盖了final修饰变量所有的可能情况,耐心看下去会有收获的:) ) 看上面的图片已经将每种情况整理出来了,这里用截图的方式也是觉得在IDE出现红色出错的标记更能清晰的说明情况。现在我们来将这几种情况归纳整理一下: 类变量:必须要在静态初始化块中指定初始值或者声明该类变量时指定初始值,而且只能在这两个地方之一进行指定; 实例变量:必要要在非静态初始化块,声明该实例变量或者在构造器中指定初始值,而且只能在这三个地方进行指定。 2.2.2 final局部变量 final局部变量由程序员进行显式初始化,如果final局部变量已经进行了初始化则后面就不能再次进行更改,如果final变量未进行初始化,可以进行赋值,当且仅有一次赋值,一旦赋值之后再次赋值就会出错。下面用具体的代码演示final局部变量的情况: [外链图片转存失败(img-bz9u3fcz-1567936280860)(https://github.com/CL0610/Java-concurrency/blob/master/06.%E4%BD%A0%E4%BB%A5%E4%B8%BA%E4%BD%A0%E7%9C%9F%E7%9A%84%E4%BA%86%E8%A7%A3final%E5%90%97%EF%BC%9F/final%E4%BF%AE%E9%A5%B0%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F.png?raw=true)] 现在我们来换一个角度进行考虑,final修饰的是基本数据类型和引用类型有区别吗? final基本数据类型 VS final引用数据类型 通过上面的例子我们已经看出来,如果final修饰的是一个基本数据类型的数据,一旦赋值后就不能再次更改,那么,如果final是引用数据类型了?这个引用的对象能够改变吗?我们同样来看一段代码。 public class FinalExample { //在声明final实例成员变量时进行赋值 private final static Person person = new Person(24, 170); public static void main(String[] args) { //对final引用数据类型person进行更改 person.age = 22; System.out.println(person.toString()); } static class Person { private int age; private int height; public Person(int age, int height) { this.age = age; this.height = height; } @Override public String toString() { return "Person{" + "age=" + age + ", height=" + height + '}'; } } } 当我们对final修饰的引用数据类型变量person的属性改成22,是可以成功操作的。通过这个实验我们就可以看出来当final修饰基本数据类型变量时,不能对基本数据类型变量重新赋值,因此基本数据类型变量不能被改变。而对于引用类型变量而言,它仅仅保存的是一个引用,final只保证这个引用类型变量所引用的地址不会发生改变,即一直引用这个对象,但这个对象属性是可以改变的。 宏变量 利用final变量的不可更改性,在满足一下三个条件时,该变量就会成为一个“宏变量”,即是一个常量。 使用final修饰符修饰; 在定义该final变量时就指定了初始值; 该初始值在编译时就能够唯一指定。 注意:当程序中其他地方使用该宏变量的地方,编译器会直接替换成该变量的值 2.2 方法 重写? 当父类的方法被final修饰的时候,子类不能重写父类的该方法,比如在Object中,getClass()方法就是final的,我们就不能重写该方法,但是hashCode()方法就不是被final所修饰的,我们就可以重写hashCode()方法。我们还是来写一个例子来加深一下理解:先定义一个父类,里面有final修饰的方法test(); public class FinalExampleParent { public final void test() { } } 然后FinalExample继承该父类,当重写test()方法时出现报错,如下图: 通过这个现象我们就可以看出来被final修饰的方法不能够被子类所重写。 重载? public class FinalExampleParent { public final void test() { } public final void test(String str) { } } 可以看出被final修饰的方法是可以重载的。经过我们的分析可以得出如下结论: 1. 父类的final方法是不能够被子类重写的 2. final方法是可以被重载的 2.3 类 当一个类被final修饰时,表名该类是不能被子类继承的。子类继承往往可以重写父类的方法和改变父类属性,会带来一定的安全隐患,因此,当一个类不希望被继承时就可以使用final修饰。还是来写一个小例子: public final class FinalExampleParent { public final void test() { } } 父类会被final修饰,当子类继承该父类的时候,就会报错,如下图: 3. final的例子 final经常会被用作不变类上,利用final的不可更改性。我们先来看看什么是不变类。 不变类 不变类的意思是创建该类的实例后,该实例的实例变量是不可改变的。满足以下条件则可以成为不可变类: 使用private和final修饰符来修饰该类的成员变量 提供带参的构造器用于初始化类的成员变量; 仅为该类的成员变量提供getter方法,不提供setter方法,因为普通方法无法修改fina修饰的成员变量; 如果有必要就重写Object类 的hashCode()和equals()方法,应该保证用equals()判断相同的两个对象其Hashcode值也是相等的。 JDK中提供的八个包装类和String类都是不可变类,我们来看看String的实现。 /** The value is used for character storage. */ private final char value[]; 可以看出String的value就是final修饰的,上述其他几条性质也是吻合的。 4. 多线程中你真的了解final吗? 上面我们聊的final使用,应该属于Java基础层面的,当理解这些后我们就真的算是掌握了final吗?有考虑过final在多线程并发的情况吗?在java内存模型中我们知道java内存模型为了能让处理器和编译器底层发挥他们的最大优势,对底层的约束就很少,也就是说针对底层来说java内存模型就是一弱内存数据模型。同时,处理器和编译为了性能优化会对指令序列有编译器和处理器重排序。那么,在多线程情况下,final会进行怎样的重排序?会导致线程安全的问题吗?下面,就来看看final的重排序。 4.1 final域重排序规则 4.1.1 final域为基本类型 先看一段示例性的代码: public class FinalDemo { private int a; //普通域 private final int b; //final域 private static FinalDemo finalDemo; public FinalDemo() { a = 1; // 1. 写普通域 b = 2; // 2. 写final域 } public static void writer() { finalDemo = new FinalDemo(); } public static void reader() { FinalDemo demo = finalDemo; // 3.读对象引用 int a = demo.a; //4.读普通域 int b = demo.b; //5.读final域 } } 假设线程A在执行writer()方法,线程B执行reader()方法。 写final域重排序规则 写final域的重排序规则禁止对final域的写重排序到构造函数之外,这个规则的实现主要包含了两个方面: JMM禁止编译器把final域的写重排序到构造函数之外; 编译器会在final域写之后,构造函数return之前,插入一个storestore屏障(关于内存屏障可以看这篇文章)。这个屏障可以禁止处理器把final域的写重排序到构造函数之外。 我们再来分析writer方法,虽然只有一行代码,但实际上做了两件事情: 构造了一个FinalDemo对象; 把这个对象赋值给成员变量finalDemo。 我们来画下存在的一种可能执行时序图,如下: 由于a,b之间没有数据依赖性,普通域(普通变量)a可能会被重排序到构造函数之外,线程B就有可能读到的是普通变量a初始化之前的值(零值),这样就可能出现错误。而final域变量b,根据重排序规则,会禁止final修饰的变量b重排序到构造函数之外,从而b能够正确赋值,线程B就能够读到final变量初始化后的值。 因此,写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域就不具有这个保障。比如在上例,线程B有可能就是一个未正确初始化的对象finalDemo。 读final域重排序规则 读final域重排序规则为:在一个线程中,初次读对象引用和初次读该对象包含的final域,JMM会禁止这两个操作的重排序。(注意,这个规则仅仅是针对处理器),处理器会在读final域操作的前面插入一个LoadLoad屏障。实际上,读对象的引用和读该对象的final域存在间接依赖性,一般处理器不会重排序这两个操作。但是有一些处理器会重排序,因此,这条禁止重排序规则就是针对这些处理器而设定的。 read()方法主要包含了三个操作: 初次读引用变量finalDemo; 初次读引用变量finalDemo的普通域a; 初次读引用变量finalDemo的final与b; 假设线程A写过程没有重排序,那么线程A和线程B有一种的可能执行时序为下图: 读对象的普通域被重排序到了读对象引用的前面就会出现线程B还未读到对象引用就在读取该对象的普通域变量,这显然是错误的操作。而final域的读操作就“限定”了在读final域变量前已经读到了该对象的引用,从而就可以避免这种情况。 读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读这个包含这个final域的对象的引用。 4.1.2 final域为引用类型 我们已经知道了final域是基本数据类型的时候重排序规则是怎么的了?如果是引用数据类型了?我们接着继续来探讨。 对final修饰的对象的成员域写操作 针对引用数据类型,final域写针对编译器和处理器重排序增加了这样的约束:在构造函数内对一个final修饰的对象的成员域的写入,与随后在构造函数之外把这个被构造的对象的引用赋给一个引用变量,这两个操作是不能被重排序的。注意这里的是“增加”也就说前面对final基本数据类型的重排序规则在这里还是使用。这句话是比较拗口的,下面结合实例来看。 public class FinalReferenceDemo { final int[] arrays; private FinalReferenceDemo finalReferenceDemo; public FinalReferenceDemo() { arrays = new int[1]; //1 arrays[0] = 1; //2 } public void writerOne() { finalReferenceDemo = new FinalReferenceDemo(); //3 } public void writerTwo() { arrays[0] = 2; //4 } public void reader() { if (finalReferenceDemo != null) { //5 int temp = finalReferenceDemo.arrays[0]; //6 } } } 针对上面的实例程序,线程线程A执行wirterOne方法,执行完后线程B执行writerTwo方法,然后线程C执行reader方法。下图就以这种执行时序出现的一种情况来讨论(耐心看完才有收获)。 由于对final域的写禁止重排序到构造方法外,因此1和3不能被重排序。由于一个final域的引用对象的成员域写入不能与随后将这个被构造出来的对象赋给引用变量重排序,因此2和3不能重排序。 对final修饰的对象的成员域读操作 JMM可以确保线程C至少能看到写线程A对final引用的对象的成员域的写入,即能看下arrays[0] = 1,而写线程B对数组元素的写入可能看到可能看不到。JMM不保证线程B的写入对线程C可见,线程B和线程C之间存在数据竞争,此时的结果是不可预知的。如果可见的,可使用锁或者volatile。 关于final重排序的总结 按照final修饰的数据类型分类: 基本数据类型: final域写:禁止final域写与构造方法重排序,即禁止final域写重排序到构造方法之外,从而保证该对象对所有线程可见时,该对象的final域全部已经初始化过。 final域读:禁止初次读对象的引用与读该对象包含的final域的重排序。 引用数据类型: 额外增加约束:禁止在构造函数对一个final修饰的对象的成员域的写入与随后将这个被构造的对象的引用赋值给引用变量 重排序 5.final的实现原理 上面我们提到过,写final域会要求编译器在final域写之后,构造函数返回前插入一个StoreStore屏障。读final域的重排序规则会要求编译器在读final域的操作前插入一个LoadLoad屏障。 很有意思的是,如果以X86处理为例,X86不会对写-写重排序,所以StoreStore屏障可以省略。由于不会对有间接依赖性的操作重排序,所以在X86处理器中,读final域需要的LoadLoad屏障也会被省略掉。也就是说,以X86为例的话,对final域的读/写的内存屏障都会被省略!具体是否插入还是得看是什么处理器 6. 为什么final引用不能从构造函数中“溢出” 这里还有一个比较有意思的问题:上面对final域写重排序规则可以确保我们在使用一个对象引用的时候该对象的final域已经在构造函数被初始化过了。但是这里其实是有一个前提条件的,也就是:在构造函数,不能让这个被构造的对象被其他线程可见,也就是说该对象引用不能在构造函数中“逸出”。以下面的例子来说: public class FinalReferenceEscapeDemo { private final int a; private FinalReferenceEscapeDemo referenceDemo; public FinalReferenceEscapeDemo() { a = 1; //1 referenceDemo = this; //2 } public void writer() { new FinalReferenceEscapeDemo(); } public void reader() { if (referenceDemo != null) { //3 int temp = referenceDemo.a; //4 } } } 可能的执行时序如图所示: 假设一个线程A执行writer方法另一个线程执行reader方法。因为构造函数中操作1和2之间没有数据依赖性,1和2可以重排序,先执行了2,这个时候引用对象referenceDemo是个没有完全初始化的对象,而当线程B去读取该对象时就会出错。尽管依然满足了final域写重排序规则:在引用对象对所有线程可见时,其final域已经完全初始化成功。但是,引用对象“this”逸出,该代码依然存在线程安全的问题。 参看文献 《java并发编程的艺术》 《疯狂java讲义》 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=vf2b5zld 个人微信公众号: dou_zhe_wan 欢迎关注
是什么? 策略模式是指对一系列的算法定义,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。分析下定义:策略模式定义和封装了一系列的算法,它们是可以相互替换的,也就是说它们具有共性,而它们的共性就体现在策略接口的行为上,另外为了达到最后一句话的目的,也就是说让算法独立于使用它的客户而独立变化,我们需要让客户端依赖于策略接口。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。 类别 行为型模式 类图 适用场景 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。 关键 所有的算法(行为)继承一个接口或抽象类,定义好共同的方法。具体的实现在具体的实现类中编写。然后在Content类中使用这个接口,也即是依赖这个接口。Content类中定义一个执行算法的方法,用接口.算法, 这个按照面向接口编程的方式。就能在不同场景执行不同策略(算法、行为)。 注意事项 如果一个系统的策略多于四个,就需要考虑使用混合模式,比如和享元模式结合,把常用的算法(也就是行为)类,用享元模式封装。 常见案例 1、jdk中的 Arrays的排序方法就是用了策略模式,Arrays.sort(排序策略),传入不同的排序策略就会出现不同的 排序结果,比如传入字典方式排序、从小到大排序,从大到下排序等等。 优点 1、算法可以自由切换。 2、避免使用多重条件判断。(一个方法中有和多if else,这时候就可以把这方法里的具体逻辑用策略模式封装了,用户使用 时候直接传入对应的策略就行。就算加了新的策略,也不需要改 if else) 3、扩展性良好。。 缺点 1、策略类会增多。 2、所有策略类都需要对外暴露。 实现步骤 步骤 1 创建一个接口,所有策略的共同接口,他定义了策略方法 package com.pattern.strategy_pattern; /** * Created by chen on 2018/3/29. * <p> * Email 122741482@qq.com * <p> * Describe: 算法策略的接口,这里定义算法的抽象方法 */ public interface Strategy { /** * 对两个整数 做运算 * * @param a * @param b */ void algorithm(int a, int b); } 步骤 2 把具体的策略,实现起来,继承接口,实现接口定义的方法,也即是写具体的业务逻辑 // 加法运算策略 package com.pattern.strategy_pattern; /** * @author chen * @date 2018/3/29 * <p> * Email 122741482@qq.com * <p> * Describe: 加法运算 */ public class AdditionStrategy implements Strategy { @Override public void algorithm(int a, int b) { System.out.println("加法运算 a+b=" + (a + b)); } } // 减法运算策略 package com.pattern.strategy_pattern; /** * @author chen * @date 2018/3/29 * <p> * Email 122741482@qq.com * <p> * Describe: 减法运算 */ public class SubstractStrategy implements Strategy { @Override public void algorithm(int a, int b) { System.out.println("减法运行 a-b=" + (a - b)); } } 步骤3 创建一个Content类,引入策略接口,执行策略方法。 package com.pattern.strategy_pattern; /** * Created by chen on 2018/3/29. * <p> * Email 122741482@qq.com * <p> * Describe: */ public class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } /** * 根据strategy的类型 执行 对应的 策略 * @param a * @param b */ public void executeStrategy(int a, int b) { strategy.algorithm(a, b); } } 步骤4 使用 Context 来查看当它改变策略 Strategy 时的行为变化。 package com.pattern.strategy_pattern; /** * Created by chen on 2018/3/29. * <p> * Email 122741482@qq.com * <p> * Describe: */ public class StrategyDemo { public static void main(String[] args) { Context context1 = new Context(new SubstractStrategy()); context1.executeStrategy(1,2); Context context2 = new Context(new AdditionStrategy()); context2.executeStrategy(1,2); } } 步骤5 查看输出 减法运行 a-b=-1 加法运算 a+b=3 博客源码地址https://gitee.com/jamen/design-pattern 我的官网http://guan2ye.com我的CSDN地址http://blog.csdn.net/chenjianandiyi我的简书地址http://www.jianshu.com/u/9b5d1921ce34我的githubhttps://github.com/javanan我的码云地址https://gitee.com/jamen/阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
是什么? 在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。或者在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使的子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。将主要的方法定义为final,防止子类修改算法骨架,将子类必须实现的方法定义为abstract。而普通的方法(无final或abstract修饰)则称之为钩子。 类别 行为型模式 类图 适用场景 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。3、一次性实现一个算法的不变的部分,并将可变的为留给子类来实现。4、实现一个算法时,整体步骤很固定。但是某些部分易变,这部分可以抽象出来,留给子类去实现。 常见案例 1、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。 2、Spring IOC容器初始化时 优点 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。3、行为由父类控制,子类实现。 缺点 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。 实现步骤 步骤 1 创建一个抽象类,它的模板方法被设置为 final,这样这个方法就不能被重写。这个方法里的执行代码步骤也将固定 demo中创建了一个婚姻模板 第一步 认识 ,第二步 热恋 , 第三步 结婚但是每个人认识的地方,情节不同。 package com.pattern.template_method; /** * Created by chen on 2018/3/19. * <p> * Email 122741482@qq.com * <p> * Describe: 创建一个抽象类,它的模板方法被设置为 final */ public abstract class Marriage { /** * */ abstract void acquaintance(); abstract void amativeness(); abstract void marry(); //婚姻 模板 模板方法被设置为 final 不被继承 public final void run() { //相识 acquaintance(); //恋爱 amativeness(); //结婚 marry(); } } 步骤2 扩展抽象类,实现了张三 和李四的 婚姻模板张三 package com.pattern.template_method; /** * * @author chen * @date 2018/3/19 * <p> * Email 122741482@qq.com * <p> * Describe: */ public class ZhangSan extends Marriage { @Override void acquaintance() { System.out.println("在公园认识"); } @Override void amativeness() { System.out.println("在西湖热恋"); } @Override void marry() { System.out.println("在阿尔卑斯山结婚"); } } 李四 package com.pattern.template_method; /** * Created by chen on 2018/3/19. * <p> * Email 122741482@qq.com * <p> * Describe: */ public class LiSi extends Marriage { @Override void acquaintance() { System.out.println("在酒吧认识"); } @Override void amativeness() { System.out.println("在酒店热恋"); } @Override void marry() { System.out.println("在法国结婚"); } } 步骤3 执行模板方法 package com.pattern.template_method; /** * @author chen * @date 2018/3/19 * <p> * Email 122741482@qq.com * <p> * Describe: */ public class TemplateMethodDemo { public static void main(String[] args) { Marriage zs = new ZhangSan(); zs.run(); Marriage ls = new LiSi(); ls.run(); } } 步骤4 查看输出 在公园认识 在西湖热恋 在阿尔卑斯山结婚 在酒吧认识 在酒店热恋 在法国结婚 博客源码地址https://gitee.com/jamen/design-pattern 我的官网http://guan2ye.com我的CSDN地址http://blog.csdn.net/chenjianandiyi我的简书地址http://www.jianshu.com/u/9b5d1921ce34我的githubhttps://github.com/javanan我的码云地址https://gitee.com/jamen/阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
创建型模式(5种) 工厂方法 抽象工厂 建造者模式 单态模式 原型模式 结构型模式(7种) 适配器模式 桥接模式 组合模式 装饰模式 外观模式 享元模式 代理模式 行为型模式(11种) 责任链模式 命令模式 解释器模式 迭代器模式 中介者模式 备忘录模式 观察者模式 状态模式 策略模式 模板方法 访问者模式 我的官网http://guan2ye.com我的CSDN地址http://blog.csdn.net/chenjianandiyi我的简书地址http://www.jianshu.com/u/9b5d1921ce34我的githubhttps://github.com/javanan我的码云地址https://gitee.com/jamen/阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
创建用户 1.作用 useradd或adduser命令用来建立用户帐号和创建用户的起始目录,使用权限是超级用户。 2.格式 useradd [-d home] [-s shell] [-c comment] [-m [-k template]] [-f inactive] [-e expire ] [-p passwd] [-r] name 3.主要参数 -c:加上备注文字,备注文字保存在passwd的备注栏中。 -d:指定用户登入时的主目录,替换系统默认值/home/<用户名> -D:变更预设值。 -e:指定账号的失效日期,日期格式为MM/DD/YY,例如06/30/12。缺省表示永久有效。 -f:指定在密码过期后多少天即关闭该账号。如果为0账号立即被停用;如果为-1则账号一直可用。默认值为-1. -g:指定用户所属的群组。值可以使组名也可以是GID。用户组必须已经存在的,期默认值为100,即users。 -G:指定用户所属的附加群组。 -m:自动建立用户的登入目录。 -M:不要自动建立用户的登入目录。 -n:取消建立以用户名称为名的群组。 -r:建立系统账号。 -s:指定用户登入后所使用的shell。默认值为/bin/bash。 -u:指定用户ID号。该值在系统中必须是唯一的。0~499默认是保留给系统用户账号使用的,所以该值必须大于499。 4.说明 useradd可用来建立用户账号,它和adduser命令是相同的。账号建好之后,再用passwd设定账号的密码。使用useradd命令所建立的账号,实际上是保存在/etc/passwd文本文件中。 5.案例 #useradd -u 544 -d /usr/testuser1 -g users -m testuser1 加-m 如果主目录不存在则自动创建 6.设置用户的密码 passwd ${username} # 输入密码 创建docker用户组并把用户加入组 1、 首先创建docker用户组,如果docker用户组存在可以忽略 sudo groupadd docker 2、把用户添加进docker组中 sudo gpasswd -a ${USER} docker 3、重启docker sudo service docker restart 4、如果普通用户执行docker命令,如果提示get …… dial unix /var/run/docker.sock权限不够,则修改/var/run/docker.sock权限 使用root用户执行如下命令,即可 sudo chmod a+rw /var/run/docker.sock 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
(git上的源码:https://gitee.com/rain7564/spring_microservices_study/tree/master/fifth-spring-cloud-zuul) 对于服务网关是什么、有什么用?使用API Gateway这篇文章已经讲得很清楚了,这里就不再赘述。当然这只是翻译版,原版在这里:Building Microservices: Using an API Gateway Spring Cloud和Netflix Zuul Using an API Gateway一文中提到Netflix API Gateway其实就是Netflix Zuul。Zuul是一个服务网关,Spring Cloud融入Zuul后,使用Spring Cloud提供的注解很容易就能搭建一个API Gateway。Zuul提供了几个功能,包括: 只用一个URL就能映射应用中所有服务的路由。但Zuul并不局限于单个URL,也可以定义多个路由入口,做到细粒度的路由映射。 自定义过滤器对经过网关的所有请求进行过滤。服务网关的过滤器,可以实现对所有请求进行过滤,而不用在各个服务实现过滤器。 下面进入正题,服务网关的实现。 创建gateway-zuul服务 创建一个Zuul服务端,首先要创建一个Spring Boot项目,然后引入Zuul相关的启动依赖。 创建Spring boot项目并修改pom文件 创建一个空Spring Boot项目后,pom文件修改如下: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.study.microservice</groupId> <artifactId>gateway-zuul</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>gateway-zuul</name> <description>API Gateway</description> <parent> <groupId>cn.study.microservice</groupId> <artifactId>fifth-spring-cloud-zuul</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> </project> 上面的xml文件,只引入了两个启动依赖,第一个是Zuul的启动依赖,该启动依赖除了包含Zuul的核心jar包外,还包括Hystrix、Ribbon、actuator等。第二个是后文介绍将Zuul服务托管在Eureka时会用到。 修改启动类 pom文件修改好之后,需要修改gateway-zuul服务的启动类,如下: @SpringBootApplication @EnableZuulProxy public class GatewayZuulApplication { public static void main(String[] args) { SpringApplication.run(GatewayZuulApplication.class, args); } } 上面的代码实际上只加了一个注解—@EnableZuulProxy。 若在添加注解@EnableZuulProxy时是手动输入,且IDE开启自动补全功能,那么应该会看到另一个注解——@EnableZuulServer。如果使用该注解,虽然也会创建一个Zuul服务端,但不加载任何反向代理过滤器,不使用Eureka的服务发现来发现其他服务。@EnableZuulServer只在搭建自己的路由服务并不使用Zuul的预构建功能时使用。比如需要使用Zuul来配合其它不是Eureka的服务发现引擎,如Consul。 与Eureka结合使用 Zuul proxy server本来就是被设计用在Spring Cloud项目中。正因为如此,Zuul自动使用Eureka来作为服务发现的依赖,可以通过服务发现其它服务,然后在Zuul内部使用Ribbon实现客户端负载均衡。 添加application.yml文件,然后在该文件中加入如下配置: server: port: 5555 eureka: instance: preferIpAddress: true client: serviceUrl: defaultZone: http://localhost:8761/eureka/ 配置路由规则 Zuul的核心是反向代理。反向代理实际上是一个中间服务器,处于想要访问某个资源的客户端与资源中间。反向代理会捕捉客户端的请求并代表客户端想远程资源发起请求。 在微服务架构中,Zuul(反向代理)从客户端“得到”一个调用,然后转发给下游服务。所以,从客户端服务角度来看,与客户端交互的实际上是Zuul。而Zuul需要与下游的服务进行交互,所以必须知道客户端的请求想要路由到哪个服务。Zuul可以通过几种途径来达到这一目标,包括: 通过服务发现自动映射路由 使用服务发现手动映射路由 使用静态URLs手动映射路由 通过服务发现自动映射路由 Zuul的所有路由映射都可以在zuul服务的application.yml文件中定义。然而,Zuul还可以在零配置的情况下,根据请求url携带的serviceId将请求正确路由到目标服务的某个实例。如果没有指定特定的路由(手动配置,下文会介绍),Zuul默认会使用被调用服务的Eureka service ID并将其映射到其中一个目标服务实例。举个简单的例子,如果你想访问organization-service服务的一个接口,该接口是根据orgId获取对应的organization详细信息,而且希望使用Zuul的自动路由,那么你可以让客户端直接访问Zuul服务的实例,然后使用如下URL: http://localhost:5555/organizationservice/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a 其中http://localhost:5555,是Zuul服务实例的访问地址;而organizationservice是organization服务的service ID;剩下的部分则是希望调用的接口。看下面的图可能更容易理解,如下: image.png Eureka配合Zuul使用的优美之处在于,不仅可以通过单个端点来访问应用的所有服务,而且,在添加或移除服务实例的时候不用修改Zuul的路由配置。另外,也可以添加一个新的服务到Eureka,而Zuul会对访问新添加的服务自动路由,因为Zuul是通过与Eureka通信然后从Eureka获取微服务实例真正物理地址,只要服务托管在Eureka中。 如果想要查看Zuul服务器管理的路由,可以访问Zuul暴露的/routes端点。该端点会返回所有服务的映射列表。启动所有服务,然后访问http://localhost:5555/routes,若跟着本教程走,应该可以看到类似如下图的返回: image.png 如果出现下面的情况: image.png 则需要在application.yml或bootstrap.yml文件中加入如下配置: management: security: enabled: false 观察正常访问http://localhost:5555/routes后的返回json对象,类似"/config-server/**"的key,是Zuul基于Eureka service ID自动为服务创建的服务路由,请求匹配到的服务路由就可以映射得到Eureka service ID,即json对象的value,最后就可以根据这个ID定位具体的服务实例。 使用服务发现手动映射路由 Zuul允许配置更细粒度的路由映射规则,可以明确定义路由映射而不是单纯依赖使用服务的Eureka service ID自动创建。假设想要缩短organizationservice来简化路由,而不是使用Zuul默认提供的/organizationservice/**,那么可以通过在Zuul服务的配置文件中手动定义路由映射关系,例如: zuul: routes: organizationservice: /organization/** 在Zuul服务的application.yml加上上面的配置后,就可以使用类似/organization/v1/organizations/{organization-id}的路由来访问organization服务了。此时,若重启Zuul然后再次访问http://localhost:5555/routes,可以出现如下返回结果: image.png 观察上图,可以看到Zuul为organization服务提供了“入口”,第一个是刚刚手动配置上去的,而第二个则是Zuul默认提供的。 注意:如果使用Zuul基于Eureka service ID自动创建的路由映射,那么当某个服务没有任何一个实例处于运行状态,那么Zuul将不会为该服务创建路由映射。然而,如果手动将路由映射到Eureka service ID,那么,即使没有实例注册到Eureka,Zuul依旧会暴露出手动配置的。当然,若尝试使用不存在任何服务实例的路由,Zuul将直接返回500错误。 忽略某些服务 如果想要将Zuul自动创建的路由映射从路由列表中移除,只留下手动配置的,那么可以在application.yml文件中在加一个额外的Zuul参数——ignored-services即可。示例如下: zuul: ignored-services: 'organizationservice' routes: organizationservice: /organization/** 这样,就能将Zuul根据Eureka Service ID自动创建的"/organizationservice/**": "organizationservice"从路由映射列表中移除。添加上面的配置然后重启,访问/routes端点,可以看到如下返回: image.png 上图中,Zuul默认给organization服务创建的路由映射已经被忽略了,只留下手动配置的。如果希望Zuul忽略所有自动配置的路由映射,可以使用: zuul: ignored-services: '*' routes: organizationservice: /organization/** 加上如上配置后重启,然后访问端点/routes,返回如下: image.png 手动配置多个服务 云应用肯定会包含许多微服务,所以一般都有为多个服务手动配置路由映射的需求,这样的需求实现起来也比较简单,如下是对organization服务和license服务的路由映射做手动配置: zuul: ignored-services: '*' routes: organizationservice: /organization/** licenseservice: /license/** 访问/routes结果是: image.png 不同API路由共用一样的模型 在不同服务路由的开头附加一个的前缀是很常见的。比如希望在不同服务的路由的开头都加上一个/api的前缀,Zuul也是支持的。可以使用如下配置来实现这一功能: zuul: ignored-services: '*' prefix: /api routes: organizationservice: /organization/** licenseservice: /license/** 可见,zuul.prefix属性可以用来定制所有服务路由的统一前缀。再次访问/routes端点,返回如下: image.png 现在若需要访问organization服务的/v1/organizations/{organization-id}端口,则需要使用: http://localhost:5555/api/organization/v1/organizations/{organization-id} 使用静态URLs手动映射路由 Zuul也可以用来转发没有注册到Eureka的服务的请求。在某些情况下,需要设置Zuul将部分请求直接路由到定义的静态URL。比如,假设organization服务是使用Python编写的,然后也想让Zuul来做反向代理,那么可以使用如下的配置实现: zuul: prefix: /api routes: organizationstatic: path: /organizationstatic/** url: http://localhost:11000 关闭其他服务,只启动zuul服务和organization服务,启动过程中会报错,这是因为eureka没启动,服务没办法注册到eureka,可以不管它,只要成功启动就行。然后访问zuul的/routes端点,可以看到只有刚刚配置的静态URL,如图: image.png 然后你会发现,访问 http://localhost:5555/api/organizationstatic/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/时,可以成功访问,结果如下: image.png 下面来分析一下,之前的配置是什么含义,如下图: image.png 但是,问题又来了,因为这样的配置会绕过eureka,这就导致请求都会指向单个路由,那么当organization服务有多个怎么办?怎么利用Ribbon来实现服务均衡?有两种做法: 第一种: zuul: prefix: /api routes: organizationstatic: path: /organizationstatic/** serviceId: organizationstatic ribbon: eureka: enabled: false organizationstatic: ribbon: listOfServers: http://localhost:11000,http://localhost:11001 第二种: zuul: prefix: /api routes: organizationstatic: path: /organizationstatic/** serviceId: organizationstatic organizationstatic: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList listOfServers: http://localhost:11000,http://localhost:11001 第一种实现方法需要Ribbon禁用eureka,感觉不太友好。官方文档是这样说的: image.png 我的理解是:因为如果不禁用的话,zuul会以为organizationstatic这个service ID是来自Eureka的,所以就去eureka那边查找对应的服务实例,然而,organizationstatic根本就没注册到eureka,所以就直接报500了。 而第二种实现方法,是告诉zuul在使用Ribbon做负载均衡时,直接在提供的server列表中获取服务实例。观察第二种方法的配置,可以看到添加了.ribbon.NIWSServerListClassName属性。官方文档是这样说的: image.png 官方文档地址:https://github.com/spring-cloud/spring-cloud-netflix/blob/master/docs/src/main/asciidoc/spring-cloud-netflix.adoc 使用上面的配置后,只启动gateway-zuul服务和两个organization服务实例,两个organization服务的端口分别是11000和11001。然后访问http://localhost:5555/api/organizationstatic/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a/,会发现负载均衡器起作用了。至于如何启动多个实例,请参考另一篇教程服务注册与发现——Netflix Eureka,这里面有提及。 非java服务的处理 之前说到静态映射路由负载均衡的第一种实现,需要zuul服务器的Ribbon禁用eureka,禁用后会有一个问题,就是其他注册到eureka的服务无法通过网关访问,需要手动自己配置.listOfServers属性。 所以对于其它语言编写的服务也想要通过zuul统一管理,有两个方案:第一,使用一个独立的zuul服务专门转发那些其它语言的服务;第二,创建Spring Cloud Sidecar实例(推荐使用这种),Spring Cloud sidecar允许注册其它语言实现的服务到eureka,然后就可以通过zuul代理了。Spring Cloud sidecar这里就不细讲了,实现也比较简单,可参考Polyglot support with Sidecar。 zuul的超时时间 zuul使用Hystrix和Ribbon库来帮助避免一个耗时较长的调用影响到整个网关的性能。默认情况下,当一个调用经过1s后还为处理完成并返回,zuul会终止该调用并统一返回500错误(这个1s的超时时间,其实是Hystrix的默认超时时间)。但是,我们可以在zuul的配置文件中对这个超时时间进行修改。比如,将其修改成7s,可以这样设置: ... hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 7000 假如需要修改特定的服务的超时时间,比如organization服务,则需要将上面的hystrix.command.defualt.换成hystrix.command.organizationservice,即将default换成对应的服务名。 最后,需要修改另一个超时时间属性。当我们覆盖hystrix默认超时时间且新超时时间大于5s,那么当一个调用超过5s时,Ribbon也会认为该调用超时。也就是说,当配置hystrix的超时时间大于5s,那么还需要配置Ribbon的超时时间,配置如下: ribbon: ReadTimeout: 7000 如果需要指定某个服务的Ribbon超时时间,则要用(clientname为服务名): clientname: ribbon: ReadTimeout: 7000 动态重新加载路由配置 zuul动态加载路由配置,需要具备Spring Cloud Config基础,若不了解Spring Cloud Config,可参考另一篇教程分布式配置——Spring Cloud Configuration。 动态加载路由在实际应用中是极其有用的,因为可以在变更路由配置后,不用重新编译、重新部署zuul服务。在分布式配置——Spring Cloud Configuration中已经讲过如何将配置文件迁移到config server中,我们也可以将zuul的配置交由config server管理。(注意:本教程不使用git作为config server的配置文件存储库,而是直接使用config server的classpath,这样比较简单,只是每次改完配置文件需要重新启动config server)。 在config server的classpath:config目录下,创建目录gateway-zuul,然后创建gateway-zuul.yml文件,内容如下: server: port: 5555 eureka: instance: preferIpAddress: true client: serviceUrl: defaultZone: http://localhost:8761/eureka/ 接着修改config server服务的application.yml,添加config server扫描路径,如下: ... spring: profiles: active: native cloud: config: server: native: search-locations: classpath:config/,classpath:config/licenseservice, classpath:config/gateway-zuul 然后修改zuul服务的pom.xml文件和bootstrap.yml文件,分别为: ... <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> ... spring: application: name: gateway-zuul profiles: active: default cloud: config: uri: http://localhost:8888 management: security: enabled: false 然后重启config-server服务和gateway-zuul服务,当看到zuul服务的控制台有类似如下输出,才证明成功从config server加载配置文件(不然就要找找是哪里出错了): image.png 访问zuul服务的/routes端点,返回结果如下: image.png 修改config-server服务管理的zuul服务的配置文件,添加如下配置: zuul: ignored-services: '*' prefix: /api routes: organizationservice: /organization/** licenseservice: /license/** 然后使用POST方式访问http://localhost:555/refresh(记得要先重启config-server服务),可以看到返回如下: image.png 证明zuul服务的配置信息中的上面4个配置的值已变更。再次访问zuul服务的/routes,返回结果如下: image.png 证明zuul已经动态加载配置成功了。 有关Spring Cloud Zuul的入门教程就介绍到这里,后续会在进阶教程中继续介绍zuul真正强大的功能——filter。 完! 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
作者: [Aoho’s Blog] 引言: 本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第二篇,本文重点讲解用户身份的认证与token发放的具体实现。本文篇幅较长,对涉及到的大部分代码进行了分析,可收藏于闲暇时间阅读,欢迎订阅本系列文章。 1. 系统概览 在上一篇 认证鉴权与API权限控制在微服务架构中的设计与实现(一)介绍了该项目的背景以及技术调研与最后选型,并且对于最终实现的endpoint执行结果进行展示。对系统架构虽然有提到,但是并未列出详细流程图。在笔者的应用场景中,Auth系统与网关进行结合。在网关出配置相应的端点信息,如登录系统申请token授权,校验check_token等端点。 下图为网关与Auth系统结合的流程图,网关系统的具体实现细节在后面另写文章介绍。(此处流程图的绘制中,笔者使用极简的语言描述,各位同学轻喷��!) [ 授权流程图 上图展示了系统登录的简单流程,其中的细节有省略,用户信息的合法性校验实际是调用用户系统。大体流程是这样,客户端请求到达网关之后,根据网关识别的请求登录端点,转发到Auth系统,将用户的信息进行校验。 另一方面是对于一般请求的校验。一些不需要权限的公开接口,在网关处配置好,请求到达网关后,匹配了路径将会直接放行。如果需要对该请求进行校验,会将该请求的相关验证信息截取,以及API权限校验所需的上下文信息(笔者项目对于一些操作进行权限前置验证,下一篇章会讲到),调用Auth系统,校验成功后进行路由转发。 [ 身份及API权限校验的流程图 这篇文章就重点讲解我们在第一篇文章中提到的用户身份的认证与token发放。这个也主要包含两个方面: 用户合法性的认证 获取到授权的token 2. 配置与类图 2.1 AuthorizationServer主要配置 关于AuthorizationServer和ResourceServer的配置在上一篇文章已经列出。AuthorizationServer主要是继承了AuthorizationServerConfigurerAdapter,覆写了其实现接口的三个方法: //对应于配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { } //配置OAuth2的客户端相关信息 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { } //配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { } 2.2 主要Authentication类的类图 [ AuthorizationServer UML类图 主要的验证方法authenticate(Authentication authentication)在接口AuthenticationManager中,其实现类有ProviderManager,有上图可以看出ProviderManager又依赖于AuthenticationProvider接口,其定义了一个List全局变量。笔者这边实现了该接口的实现类CustomAuthenticationProvider。自定义一个provider,并在GlobalAuthenticationConfigurerAdapter中配置好改自定义的校验provider,覆写configure()方法。 @Configuration public class AuthenticationManagerConfig extends GlobalAuthenticationConfigurerAdapter { @Autowired CustomAuthenticationProvider customAuthenticationProvider; @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(customAuthenticationProvider);//使用自定义的AuthenticationProvider } } AuthenticationManagerBuilder是用来创建AuthenticationManager,允许自定义提供多种方式的AuthenticationProvider,比如LDAP、基于JDBC等等。 3. 认证与授权token 下面讲解认证与授权token主要的类与接口。 3.1 内置端点TokenEndpoint Spring-Security-Oauth2的提供的jar包中内置了与token相关的基础端点。本文认证与授权token与/oauth/token有关,其处理的接口类为TokenEndpoint。下面我们来看一下对于认证与授权token流程的具体处理过程。 @FrameworkEndpoint public class TokenEndpoint extends AbstractEndpoint { ... @RequestMapping(value = "/oauth/token", method=RequestMethod.POST) public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { //首先对client信息进行校验 if (!(principal instanceof Authentication)) { throw new InsufficientAuthenticationException( "There is no client authentication. Try adding an appropriate authentication filter."); } String clientId = getClientId(principal); //根据请求中的clientId,加载client的具体信息 ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); ... //验证scope域范围 if (authenticatedClient != null) { oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient); } //授权方式不能为空 if (!StringUtils.hasText(tokenRequest.getGrantType())) { throw new InvalidRequestException("Missing grant type"); } //token endpoint不支持Implicit模式 if (tokenRequest.getGrantType().equals("implicit")) { throw new InvalidGrantException("Implicit grant type not supported from token endpoint"); } ... //进入CompositeTokenGranter,匹配授权模式,然后进行password模式的身份验证和token的发放 OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); if (token == null) { throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType()); } return getResponse(token); } ... } [ endpoint 上面给代码进行了注释,读者感兴趣可以看看。接口处理的主要流程就是对authentication信息进行检查是否合法,不合法直接抛出异常,然后对请求的GrantType进行处理,根据GrantType,进行password模式的身份验证和token的发放。下面我们来看下TokenGranter的类图。 [ TokenGranter 可以看出TokenGranter的实现类CompositeTokenGranter中有一个List,对应五种GrantType的实际授权实现。这边涉及到的getTokenGranter(),代码也列下: public class CompositeTokenGranter implements TokenGranter { //GrantType的集合,有五种,之前有讲 private final List<TokenGranter> tokenGranters; public CompositeTokenGranter(List<TokenGranter> tokenGranters) { this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters); } //遍历list,匹配到相应的grantType就进行处理 public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { for (TokenGranter granter : tokenGranters) { OAuth2AccessToken grant = granter.grant(grantType, tokenRequest); if (grant!=null) { return grant; } } return null; } ... } 本次请求是使用的password模式,随后进入其GrantType具体的处理流程,下面是grant()方法。 public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { if (!this.grantType.equals(grantType)) { return null; } String clientId = tokenRequest.getClientId(); //加载clientId对应的ClientDetails,为了下一步的验证 ClientDetails client = clientDetailsService.loadClientByClientId(clientId); //再次验证clientId是否拥有该grantType模式,安全 validateGrantType(grantType, client); //获取token return getAccessToken(client, tokenRequest); } protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) { //进入创建token之前,进行身份验证 return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest)); } protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { //身份验证 OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest); return new OAuth2Authentication(storedOAuth2Request, null); } 上面一段代码是grant()方法具体的实现细节。GrantType匹配到其对应的grant()后,先进行基本的验证确保安全,然后进入主流程,就是下面小节要讲的验证身份和发放token。 3.2 自定义的验证类CustomAuthenticationProvider CustomAuthenticationProvider中定义了验证方法的具体实现。其具体实现如下所示。 //主要的自定义验证方法 @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = (String) authentication.getCredentials(); Map data = (Map) authentication.getDetails(); String clientId = (String) data.get("client"); Assert.hasText(clientId,"clientId must have value" ); String type = (String) data.get("type"); //通过调用user服务,校验用户信息 Map map = userClient.checkUsernameAndPassword(getUserServicePostObject(username, password, type)); //校验返回的信息,不正确则抛出异常,授权失败 String userId = (String) map.get("userId"); if (StringUtils.isBlank(userId)) { String errorCode = (String) map.get("code"); throw new BadCredentialsException(errorCode); } CustomUserDetails customUserDetails = buildCustomUserDetails(username, password, userId, clientId); return new CustomAuthenticationToken(customUserDetails); } //构造一个CustomUserDetails,简单,略去 private CustomUserDetails buildCustomUserDetails(String username, String password, String userId, String clientId) { } //构造一个请求userService的map,内容略 private Map<String, String> getUserServicePostObject(String username, String password, String type) { } authenticate()最后返回构造的自定义CustomAuthenticationToken,在CustomAuthenticationToken中,将boolean authenticated设为true,user信息验证成功。这边传入的参数CustomUserDetails与token生成有关,作为payload中的信息,下面会讲到。 //继承抽象类AbstractAuthenticationToken public class CustomAuthenticationToken extends AbstractAuthenticationToken { private CustomUserDetails userDetails; public CustomAuthenticationToken(CustomUserDetails userDetails) { super(null); this.userDetails = userDetails; super.setAuthenticated(true); } ... } 而AbstractAuthenticationToken实现了接口Authentication和CredentialsContainer,里面的具体信息读者可以自己看下源码。 3.3 关于JWT 用户信息校验完成之后,下一步则是要对该用户进行授权。在讲具体的授权之前,先补充下关于JWT Token的相关知识点。 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 从上面的描述可知JWT的定义,这边读者可以对比下token的认证和传统的session认证的区别。推荐一篇文章什么是 JWT – JSON WEB TOKEN,笔者这边就不详细扩展讲了,只是简单介绍下其构成。 JWT包含三部分:header头部、payload信息、signature签名。下面以上一篇生成好的access_token为例介绍。 header header jwt的头部承载两部分信息,一是声明类型,这里是jwt;二是声明加密的算法 通常直接使用 HMAC SHA256。第一部分一般固定为: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 playload playload 存放的有效信息,这些有效信息包含三个部分、标准中注册的声明、公共的声明、私有的声明。这边笔者额外添加的信息为X-KEETS-UserId和X-KEETS-ClientId。读者可根据实际项目需要进行定制。最后playload经过base64编码后的结果为: eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ signature signature jwt的第三部分是一个签证信息,这个签证信息由三部分组成:header (base64后的)、payload (base64后的)、secret。 signature jwt的第三部分是一个签证信息,这个签证信息由三部分组成:header (base64后的)、payload (base64后的)、secret。 关于secret,细心的读者可能会发现之前的配置里面有具体设置。前两部分连接组成的字符串,通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。第三部分结果为: 5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo 至于具体应用方法,可以参见第一篇文章中构建的/logout端点。 3.3 自定义的AuthorizationTokenServices 现在到了为用户创建token,这边主要与自定义的接口AuthorizationServerTokenServices有关。AuthorizationServerTokenServices主要有如下三个方法: //创建token OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException; //刷新token OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest) throws AuthenticationException; //获取token OAuth2AccessToken getAccessToken(OAuth2Authentication authentication); 由于篇幅限制,笔者这边仅对createAccessToken()的实现方法进行分析,其他的方法实现,读者可以下关注笔者的GitHub项目。 public class CustomAuthorizationTokenServices implements AuthorizationServerTokenServices, ConsumerTokenServices { ... public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { //通过TokenStore,获取现存的AccessToken OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); OAuth2RefreshToken refreshToken; //移除已有的AccessToken和refreshToken if (existingAccessToken != null) { if (existingAccessToken.getRefreshToken() != null) { refreshToken = existingAccessToken.getRefreshToken(); // The token store could remove the refresh token when the // access token is removed, but we want to be sure tokenStore.removeRefreshToken(refreshToken); } tokenStore.removeAccessToken(existingAccessToken); } //recreate a refreshToken refreshToken = createRefreshToken(authentication); OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); if (accessToken != null) { tokenStore.storeAccessToken(accessToken, authentication); } refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { tokenStore.storeRefreshToken(refreshToken, authentication); } return accessToken; } ... } 这边具体的实现在上面有注释,基本没有改写多少,读者此处可以参阅源码。createAccessToken()还调用了两个私有方法,分别创建accessToken和refreshToken。创建accessToken,需要基于refreshToken。 这边具体的实现在上面有注释,基本没有改写多少,读者此处可以参阅源码。createAccessToken()还调用了两个私有方法,分别创建accessToken和refreshToken。创建accessToken,需要基于refreshToken。 此处可以自定义设置token的时效长度,accessToken创建实现如下: private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days. private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours. private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { //对应tokenId,存储的标识 DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request()); if (validitySeconds > 0) { token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); } token.setRefreshToken(refreshToken); //scope对应作用范围 token.setScope(authentication.getOAuth2Request().getScope()); //上一节介绍的自定义TokenEnhancer,这边使用 return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token; } 既然提到TokenEnhancer,这边简单贴一下代码。 public class CustomTokenEnhancer extends JwtAccessTokenConverter { private static final String TOKEN_SEG_USER_ID = "X-KEETS-UserId"; private static final String TOKEN_SEG_CLIENT = "X-KEETS-ClientId"; @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); Map<String, Object> info = new HashMap<>(); //从自定义的userDetails中取出UserId info.put(TOKEN_SEG_USER_ID, userDetails.getUserId()); DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken); customAccessToken.setAdditionalInformation(info); OAuth2AccessToken enhancedToken = super.enhance(customAccessToken, authentication); //设置ClientId enhancedToken.getAdditionalInformation().put(TOKEN_SEG_CLIENT, userDetails.getClientId()); return enhancedToken; } } 自此,用户身份校验与发放授权token结束。最终成功返回的结果为: { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE", "expires_in": 43195, "scope": "all", "X-KEETS-UserId": "d6448c24-3c4c-4b80-8372-c2d61868f8c6", "jti": "bad72b19-d9f3-4902-affa-0430e7db79ed", "X-KEETS-ClientId": "frontend" } 4. 总结 本文开头给出了Auth系统概述,画出了简要的登录和校验的流程图,方便读者能对系统的实现有个大概的了解。然后主要讲解了用户身份的认证与token发放的具体实现。对于其中主要的类和接口进行了分析与讲解。下一篇文章主要讲解token的鉴定和API级别的上下文权限校验。 参考 什么是 JWT – JSON WEB TOKEN Re:从零开始的Spring Security OAuth2(二) spring-security-oauth 相关阅读 认证鉴权与API权限控制在微服务架构中的设计与实现(一) 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
作者: [Aoho’s Blog] 引言: 本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现。 1. 背景 最近在做权限相关服务的开发,在系统微服务化后,原有的单体应用是基于session的安全权限方式,不能满足现有的微服务架构的认证与鉴权需求。微服务架构下,一个应用会被拆分成若干个微应用,每个微应用都需要对访问进行鉴权,每个微应用都需要明确当前访问用户以及其权限。尤其当访问来源不只是浏览器,还包括其他服务的调用时,单体应用架构下的鉴权方式就不是特别合适了。在微服务架构下,要考虑外部应用接入的场景、用户–服务的鉴权、服务–服务的鉴权等多种鉴权场景。 最近在做权限相关服务的开发,在系统微服务化后,原有的单体应用是基于session的安全权限方式,不能满足现有的微服务架构的认证与鉴权需求。微服务架构下,一个应用会被拆分成若干个微应用,每个微应用都需要对访问进行鉴权,每个微应用都需要明确当前访问用户以及其权限。尤其当访问来源不只是浏览器,还包括其他服务的调用时,单体应用架构下的鉴权方式就不是特别合适了。在微服务架构下,要考虑外部应用接入的场景、用户–服务的鉴权、服务–服务的鉴权等多种鉴权场景。 比如用户A访问User Service,A如果未登录,则首先需要登录,请求获取授权token。获取token之后,A将携带着token去请求访问某个文件,这样就需要对A的身份进行校验,并且A可以访问该文件。 最近在做权限相关服务的开发,在系统微服务化后,原有的单体应用是基于session的安全权限方式,不能满足现有的微服务架构的认证与鉴权需求。微服务架构下,一个应用会被拆分成若干个微应用,每个微应用都需要对访问进行鉴权,每个微应用都需要明确当前访问用户以及其权限。尤其当访问来源不只是浏览器,还包括其他服务的调用时,单体应用架构下的鉴权方式就不是特别合适了。在微服务架构下,要考虑外部应用接入的场景、用户–服务的鉴权、服务–服务的鉴权等多种鉴权场景。 比如用户A访问User Service,A如果未登录,则首先需要登录,请求获取授权token。获取token之后,A将携带着token去请求访问某个文件,这样就需要对A的身份进行校验,并且A可以访问该文件。 为了适应架构的变化、需求的变化,auth权限模块被单独出来作为一个基础的微服务系统,为其他业务service提供服务。 2. 系统架构的变更 单体应用架构到分布式架构,简化的权限部分变化如下面两图所示。 单体应用架构到分布式架构,简化的权限部分变化如下面两图所示。 (1)单体应用简化版架构图: 单体应用架构到分布式架构,简化的权限部分变化如下面两图所示。 (1)单体应用简化版架构图: [ 单体架构 (2)分布式应用简化版架构图: 分布式架构 分布式架构,特别是微服务架构的优点是可以清晰的划分出业务逻辑来,让每个微服务承担职责单一的功能,毕竟越简单的东西越稳定。 但是,微服务也带来了很多的问题。比如完成一个业务操作,需要跨很多个微服务的调用,那么如何用权限系统去控制用户对不同微服务的调用,对我们来说是个挑战。当业务微服务的调用接入权限系统后,不能拖累它们的吞吐量,当权限系统出现问题后,不能阻塞它们的业务调用进度,当然更不能改变业务逻辑。新的业务微服务快速接入权限系统相对容易把控,那么对于公司已有的微服务,如何能不改动它们的架构方式的前提下,快速接入,对我们来说,也是一大挑战。 3. 技术方案 这主要包括两方面需求:其一是认证与鉴权,对于请求的用户身份的授权以及合法性鉴权;其二是API级别的操作权限控制,这个在第一点之后,当鉴定完用户身份合法之后,对于该用户的某个具体请求是否具有该操作执行权限进行校验。 3.1 认证与鉴权 对于第一个需求,笔者调查了一些实现方案: 分布式Session方案 分布式Session方案 分布式会话方案原理主要是将关于用户认证的信息存储在共享存储中,且通常由用户会话作为 key 来实现的简单分布式哈希映射。当用户访问微服务时,用户数据可以从共享存储中获取。在某些场景下,这种方案很不错,用户登录状态是不透明的。同时也是一个高可用且可扩展的解决方案。这种方案的缺点在于共享存储需要一定保护机制,因此需要通过安全链接来访问,这时解决方案的实现就通常具有相当高的复杂性了。 基于OAuth2 Token方案 基于OAuth2 Token方案 随着 Restful API、微服务的兴起,基于Token的认证现在已经越来越普遍。Token和Session ID 不同,并非只是一个 key。Token 一般会包含用户的相关信息,通过验证 Token 就可以完成身份校验。用户输入登录信息,发送到身份认证服务进行认证。AuthorizationServer验证登录信息是否正确,返回用户基础信息、权限范围、有效时间等信息,客户端存储接口。用户将 Token 放在 HTTP 请求头中,发起相关 API 调用。被调用的微服务,验证Token。ResourceServer返回相关资源和数据。 这边选用了第二种方案,基于OAuth2 Token认证的好处如下: 服务端无状态:Token 机制在服务端不需要存储 session 信息,因为 Token 自身包含了所有用户的相关信息。 性能较好,因为在验证 Token 时不用再去访问数据库或者远程服务进行权限校验,自然可以提升不少性能。 现在很多应用都是同时面向移动端和web端,OAuth2 Token机制可以支持移动设备。 OAuth2与Spring Security结合使用,有提供很多开箱即用的功能,大多特性都可以通过配置灵活的变更。 最后一点,也很重要,Spring Security OAuth2的文档写得较为详细。 oauth2根据使用场景不同,分成了4种模式: 授权码模式(authorization code) 简化模式(implicit) 密码模式(resource owner password credentials) 客户端模式(client credentials) 对于上述oauth2四种模式不熟的同学,可以自行百度oauth2,阮一峰的文章有解释。常使用的是password模式和client模式。 3.2 操作权限控制 对于第二个需求,笔者主要看了Spring Security和Shiro。 Shiro Shiro Shiro是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。Shiro很容易入手,上手快控制粒度可糙可细。自由度高,Shiro既能配合Spring使用也可以单独使用。 Spring Security Spring Security Spring社区生态很强大。除了不能脱离Spring,Spring Security具有Shiro所有的功能。而且Spring Security对Oauth、OpenID也有支持,Shiro则需要自己手动实现。Spring Security的权限细粒度更高。但是Spring Security太过复杂。 看了下网上的评论,貌似一边倒向Shiro。大部分人提出的Spring Security问题就是比较复杂难懂,文档太长。不管是Shiro还是Spring Security,其实现都是基于过滤器,对于自定义实现过滤器,我想对于很多开发者并不是很难,但是这需要团队花费时间与封装可用的jar包出来,对于后期维护和升级,以及功能的扩展。很多中小型公司并不一定具有这样的时间和人力投入这件事。笔者综合评估了下复杂性与所要实现的权限需求,以及上一个需求调研的结果,既然Spring Security功能足够强大且稳定,最终选择了Spring Security。 4. 系统架构 4.1 组件 Auth系统的最终使用组件如下: OAuth2.0 JWT Token Spring Security Spring boot 4.2 步骤 主要步骤为: 配置资源服务器和认证服务器 配置Spring Security 上述步骤比较笼统,对于前面小节提到的需求,属于Auth系统的主要内容,笔者后面会另写文章对应讲解。 4.3 endpoint 提供的endpoint: /oauth/token?grant_type=password #请求授权token /oauth/token?grant_type=refresh_token #刷新token /oauth/check_token #校验token /logout #注销token及权限相关信息 4.4 maven依赖 主要的jar包,pom.xml文件如下: <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> <version>1.2.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>1.2.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> <version>1.5.3.RELEASE</version> </dependency> 4.5 AuthorizationServer配置文件 AuthorizationServer配置主要是覆写如下的三个方法,分别针对endpoints、clients、security配置。 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //配置客户端认证 clients.withClientDetails(clientDetailsService(dataSource)); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //配置token的数据源、自定义的tokenServices等信息 endpoints.authenticationManager(authenticationManager) .tokenStore(tokenStore(dataSource)) .tokenServices(authorizationServerTokenServices()) .accessTokenConverter(accessTokenConverter()) .exceptionTranslator(webResponseExceptionTranslator); } 4.6 ResourceServer配置 资源服务器的配置,覆写了默认的配置。为了支持logout,这边自定义了一个CustomLogoutHandler并且将logoutSuccessHandler指定为返回http状态的HttpStatusReturningLogoutSuccessHandler。 @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .requestMatchers().antMatchers("/**") .and().authorizeRequests() .antMatchers("/**").permitAll() .anyRequest().authenticated() .and().logout() .logoutUrl("/logout") .clearAuthentication(true) .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()) .addLogoutHandler(customLogoutHandler()); 4.7 执行endpoint 首先执行获取授权的endpoint。 method: post url: http://localhost:12000/oauth/token?grant_type=password header: { Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=, Content-Type: application/x-www-form-urlencoded } body: { username: keets, password: *** } 上述构造了一个post请求,具体请求写得很详细。username和password是客户端提供给服务器进行校验用户身份信息。header里面的Authorization是存放的clientId和clientSecret经过编码的字符串。 上述构造了一个post请求,具体请求写得很详细。username和password是客户端提供给服务器进行校验用户身份信息。header里面的Authorization是存放的clientId和clientSecret经过编码的字符串。 返回结果如下: { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE", "expires_in": 43195, "scope": "all", "X-KEETS-UserId": "d6448c24-3c4c-4b80-8372-c2d61868f8c6", "jti": "bad72b19-d9f3-4902-affa-0430e7db79ed", "X-KEETS-ClientId": "frontend" } 可以看到在用户名密码通过校验后,客户端收到了授权服务器的response,主要包括access* token、refresh* token。并且表明token的类型为bearer,过期时间expires_in。笔者在jwt token中加入了自定义的info为UserId和ClientId。 2.鉴权的endpoint method: post url: http://localhost:12000/oauth/check_token header: { Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ=, Content-Type: application/x-www-form-urlencoded } body: { token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsImV4cCI6MTUwODQ0Nzc1NiwidXNlcl9uYW1lIjoia2VldHMiLCJqdGkiOiJiYWQ3MmIxOS1kOWYzLTQ5MDItYWZmYS0wNDMwZTdkYjc5ZWQiLCJjbGllbnRfaWQiOiJmcm9udGVuZCIsInNjb3BlIjpbImFsbCJdfQ.5ZNVN8TLavgpWy8KZQKArcbj7ItJLLaY1zBRaAgMjdo } 上面即为check_token请求的详细信息。需要注意的是,笔者将刚刚授权的token放在了body里面,这边可以有多种方法,此处不扩展。 { "X-KEETS-UserId": "d6448c24-3c4c-4b80-8372-c2d61868f8c6", "user_name": "keets", "scope": [ "all" ], "active": true, "exp": 1508447756, "X-KEETS-ClientId": "frontend", "jti": "bad72b19-d9f3-4902-affa-0430e7db79ed", "client_id": "frontend" } 校验token合法后,返回的response如上所示。在response中也是展示了相应的token中的基本信息。 3.刷新token 3.刷新token 由于token的时效一般不会很长,而refresh* token一般周期会很长,为了不影响用户的体验,可以使用refresh* token去动态的刷新token。 method: post url: http://localhost:12000/oauth/token?grant_type=refresh_token&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUtFRVRTLVVzZXJJZCI6ImQ2NDQ4YzI0LTNjNGMtNGI4MC04MzcyLWMyZDYxODY4ZjhjNiIsInVzZXJfbmFtZSI6ImtlZXRzIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImJhZDcyYjE5LWQ5ZjMtNDkwMi1hZmZhLTA0MzBlN2RiNzllZCIsImV4cCI6MTUxMDk5NjU1NiwianRpIjoiYWE0MWY1MjctODE3YS00N2UyLWFhOTgtZjNlMDZmNmY0NTZlIiwiY2xpZW50X2lkIjoiZnJvbnRlbmQifQ.mICT1-lxOAqOU9M-Ud7wZBb4tTux6OQWouQJ2nn1DeE header: { Authorization: Basic ZnJvbnRlbmQ6ZnJvbnRlbmQ= } 其response和/oauth/token得到正常的相应是一样的,此处不再列出。 4.注销token method: get url: http://localhost:9000/logout header: { Authorization: bearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDgzMzkwNTQsIlgtU0lNVS1Vc2VySWQiOiIwOGFhMTYxYi1lYjI3LTQ2NjAtYjA1MC1lMDc5YTJiODBhODMiLCJ1c2VyX25hbWUiOiJrZWV0cyIsImp0aSI6IjJhNTQ4NjY2LTRjNzEtNGEzNi1hZmY0LTMwZTI1Mjc0ZjQxZSIsImNsaWVudF9pZCI6ImZyb250ZW5kIiwic2NvcGUiOlsibWVua29yIl19.rA-U2iXnjH0AdPaGuvSEJH3bTth6AT3oQrGsKIams30 } 注销成功则会返回200,注销端点主要是将token和SecurityContextHolder进行清空。 5. 总结 本文是《认证鉴权与API权限控制在微服务架构中的设计与实现》系列文章的总述,从遇到的问题着手,介绍了项目的背景。通过调研现有的技术,并结合当前项目的实际,确定了技术选型。最后对于系统的最终的实现进行展示。后面将从实现的细节,讲解本系统的实现。敬请期待后续文章。 参考 理解OAuth 2.0 微服务API级权限的技术架构 微服务架构下的安全认证与鉴权 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
大家在学习编程和以后的工作中,总缺少不了服务器的使用。比如你要部署一个webapp的后台服务。需要一个公网服务器。 很多大型的公司都有自己内部的服务器硬件,但是对于中小型公司或者个人,就需要去购买或者租聘云服务器了。目前国内有比较知名的云服务器有百度云、阿里云、京东云、又拍云等等,几十家。好坏大家去判断了。个人推荐阿里云和腾讯云、百度云。 既然要使用优惠券,那就必须先领取优惠券了。 阿里云优惠券地址为: https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 腾讯云优惠入口为 : https://cloud.tencent.com/redirect.php?redirect=1005&cps_key=1cdaea7b77fe67188b187bce55796594 还有一个阿里云的价格表地址: http://aliyun.guan2ye.com/ 1、领取阿里云优惠券 点击打开 https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 然后点击领取所有 产品的优惠券,包括了ECS 、分布式数据库、云数据库、阿里云企业邮箱等25款产品的优惠券都一次性领取完成。如下图 进入自己需要购买的产品界面,点击购买,最后购买的时候选中优惠券,即可享受优惠。并且购买完成后可以抽奖。如图 优惠购买腾讯云。 优惠购买腾讯云的方式非常简单,只要从这里进入就可以优惠的方式购买 腾讯云为数百万企业和开发者提供安全、稳定的云服务器、云数据库、CDN等云服务 腾讯云服务器安全可靠高性能,多种配置供您选择 腾讯云CDN拥有顶尖加速能力,丰富的功能全面覆盖各业务场景的加速需求,最为用户考虑的加速产品 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
一.应用系统的架构历史 [ 二.什么是微服务? 2.1 微服务概述 起源:微服务的概念源于 2014 年 3 月 Martin Fowler 所写的一篇文章“Microservices”。文中内容提到:微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。 通信方式:每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通(通常是基于 HTTP 的 RESTful API)。 微服务的常规定义:微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务。 把原来的一个完整的进程服务,拆分成两个或两个以上的进程服务,且互相之间存在调用关系,与原先单一的进程服务相比,就是“微服务”。(微服务是一个比较级的概念,而不是单一的概念) 2.2 微服务架构的优势 可扩展性:在增加业务功能时,单一应用架构需要在原先架构的代码基础上做比较大的调整,而微服务架构只需要增加新的微服务节点,并调整与之有关联的微服务节点即可。在增加业务响应能力时,单一架构需要进行整体扩容,而微服务架构仅需要扩容响应能力不足的微服务节点。 容错性:在系统发生故障时,单一应用架构需要进行整个系统的修复,涉及到代码的变更和应用的启停,而微服务架构仅仅需要针对有问题的服务进行代码的变更和服务的启停。其他服务可通过重试、熔断等机制实现应用层面的容错。 技术选型灵活:微服务架构下,每个微服务节点可以根据完成需求功能的不同,自由选择最适合的技术栈,即使对单一的微服务节点进行重构,成本也非常低。 开发运维效率更高:每个微服务节点都是一个单一进程,都专注于单一功能,并通过定义良好的接口清晰表述服务边界。由于体积小、复杂度低,每个微服务可由一个小规模团队或者个人完全掌控,易于保持高可维护性和开发效率。 Spring Cloud作为今年最流行的微服务开发框架,不是采用了Spring Cloud框架就实现了微服务架构,具备了微服务架构的优势。正确的理解是使用Spring Cloud框架开发微服务架构的系统,使系统具备微服务架构的优势(Spring Cloud就像工具,还需要“做”的过程)。 三.Spring Boot/Cloud 3.1 什么是Spring Boot? Spring Boot框架是由Pivotal团队提供的全新框架,其设计目的是用来简化基于Spring应用的初始搭建以及开发过程。SpringBoot框架使用了特定的方式来进行应用系统的配置,从而使开发人 员不再需要耗费大量精力去定义模板化的配置文件。 3.2 什么是Spring Cloud? Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务注册,服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。 3.3 微服务,Spring Boot,Spring Cloud三者之间的关系 思想:微服务是一种架构的理念,提出了微服务的设计原则,从理论为具体的技术落地提供了指导思想。 脚手架:Spring Boot是一套快速配置脚手架,可以基于Spring Boot快速开发单个微服务。 多个组件的集合:Spring Cloud是一个基于Spring Boot实现的服务治理工具包;Spring Boot专注于快速、方便集成的单个微服务个体;Spring Cloud关注全局的服务治理框架。 3.4 Everything is jar, Everything is http Spring Boot通过@SpringBootApplication注解标识为Spring Boot应用程序。所有的应用都通过jar包方式编译,部署和运行. @SpringBootApplication public class Application { private static final Logger LOGGER = LoggerFactory.getLogger(Application.class); public static void main(String[] args) { SpringApplication.run(Application.class, args); LOGGER.info(”启动成功!"); }
1、概括 在博客中,我们将讨论如何让Spring Security OAuth2实现使用JSON Web Tokens。 2、Maven 配置 首先,我们需要在我们的pom.xml中添加spring-security-jwt依赖项。 <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency> 我们需要为Authorization Server和Resource Server添加spring-security-jwt依赖项。 3、授权服务器 接下来,我们将配置我们的授权服务器使用JwtTokenStore - 如下所示 @Configuration @EnableAuthorizationServer public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()) .accessTokenConverter(accessTokenConverter()) .authenticationManager(authenticationManager); } @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("123"); return converter; } @Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); defaultTokenServices.setSupportRefreshToken(true); return defaultTokenServices; } } 请注意,在JwtAccessTokenConverter中使用了一个对称密钥来签署我们的令牌 - 这意味着我们需要为资源服务器使用同样的确切密钥。 4、资源服务器 现在,我们来看看我们的资源服务器配置 - 这与授权服务器的配置非常相似: @Configuration @EnableResourceServer public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer config) { config.tokenServices(tokenServices()); } @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("123"); return converter; } @Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); return defaultTokenServices; } } 请记住,我们将这两个服务器定义为完全独立且可独立部署的服务器。这就是我们需要在新配置中再次声明一些相同的bean的原因。 5、令牌中的自定义声明 现在让我们设置一些基础设施,以便能够在访问令牌中添加一些自定义声明。框架提供的标准声明都很好,但大多数情况下我们需要在令牌中使用一些额外的信息来在客户端使用。 我们将定义一个TokenEnhancer来定制我们的Access Token与这些额外的声明。 在下面的例子中,我们将添加一个额外的字段“组织”到我们的访问令牌 - 与此CustomTokenEnhancer: public class CustomTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance( OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> additionalInfo = new HashMap<>(); additionalInfo.put("organization", authentication.getName() + randomAlphabetic(4)); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } } 然后,我们将把它连接到我们的授权服务器配置 - 如下所示: @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers( Arrays.asList(tokenEnhancer(), accessTokenConverter())); endpoints.tokenStore(tokenStore()) .tokenEnhancer(tokenEnhancerChain) .authenticationManager(authenticationManager); } @Bean public TokenEnhancer tokenEnhancer() { return new CustomTokenEnhancer(); } 有了这个新的配置启动和运行 - 这是一个令牌令牌有效载荷看起来像: { "user_name": "john", "scope": [ "foo", "read", "write" ], "organization": "johnIiCh", "exp": 1458126622, "authorities": [ "ROLE_USER" ], "jti": "e0ad1ef3-a8a5-4eef-998d-00b26bc2c53f", "client_id": "fooClientIdPassword" } 5.1、在JS客户端使用访问令牌 最后,我们要在AngualrJS客户端应用程序中使用令牌信息。我们将使用angular-jwt库。 所以我们要做的就是在index.html中使用“组织”声明: <p class="navbar-text navbar-right">{{organization}}</p> <script type="text/javascript" src="https://cdn.rawgit.com/auth0/angular-jwt/master/dist/angular-jwt.js"> </script> <script> var app = angular.module('myApp', ["ngResource","ngRoute", "ngCookies", "angular-jwt"]); app.controller('mainCtrl', function($scope, $cookies, jwtHelper,...) { $scope.organiztion = ""; function getOrganization(){ var token = $cookies.get("access_token"); var payload = jwtHelper.decodeToken(token); $scope.organization = payload.organization; } ... }); 6、不对称的KeyPair 在我们以前的配置中,我们使用对称密钥来签署我们的令牌: @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("123"); return converter; } 我们还可以使用非对称密钥(公钥和私钥)来执行签名过程。 6.1、生成JKS Java KeyStore文件 我们首先使用命令行工具keytool生成密钥 - 更具体地说.jks文件: keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass 该命令将生成一个名为mytest.jks的文件,其中包含我们的密钥 - 公钥和私钥。 还要确保keypass和storepass是一样的。 6.2、导出公钥 接下来,我们需要从生成的JKS中导出我们的公钥,我们可以使用下面的命令来实现: keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey 示例回应如下所示: -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp OsMquZMyOyteO2RsVeMLF/hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2 /5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo/KHk4nz+Fa6P3L8+L90E/3qwf6j3 DKWnAgJFRY8AbSYXt1d5ELiIG1/gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR xxCLXT+tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43+a9Qn5icSRnDfTAesQ3Cr lAWJKl2kcWU1HwJqw+dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK eQIDAQAB -----END PUBLIC KEY----- -----BEGIN CERTIFICATE----- MIIDCzCCAfOgAwIBAgIEGtZIUzANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJ1 czELMAkGA1UECBMCY2ExCzAJBgNVBAcTAmxhMQ0wCwYDVQQDEwR0ZXN0MB4XDTE2 MDMxNTA4MTAzMFoXDTE2MDYxMzA4MTAzMFowNjELMAkGA1UEBhMCdXMxCzAJBgNV BAgTAmNhMQswCQYDVQQHEwJsYTENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBAICCtlreMdhLQ5eNQu736TrDKrmTMjsrXjtkbFXj Cxf4VyHmL4nCq9EkM1ZKHRxAQjIhl0A8+aa4o06t0Rz8tv+ViQQmKu8h4Ey77KTM urIr1zezXWBOyOaV6Pyh5OJ8/hWuj9y/Pi/dBP96sH+o9wylpwICRUWPAG0mF7dX eRC4iBtf4BKswtH2ZjYYX6wbccFl65aVA09Cn739EFZj0ccQi10/rRHtbHlhhKnj iy+b10S6ps2XAXtUWfZEEJuN/mvUJ+YnEkZw30wHrENwq5QFiSpdpHFlNR8CasPn WUUmdV+JBFzTMsz3TwWxplOjB3YacsCO0imU+5l+AQ51CnkCAwEAAaMhMB8wHQYD VR0OBBYEFOGefUBGquEX9Ujak34PyRskHk+WMA0GCSqGSIb3DQEBCwUAA4IBAQB3 1eLfNeq45yO1cXNl0C1IQLknP2WXg89AHEbKkUOA1ZKTOizNYJIHW5MYJU/zScu0 yBobhTDe5hDTsATMa9sN5CPOaLJwzpWV/ZC6WyhAWTfljzZC6d2rL3QYrSIRxmsp /J1Vq9WkesQdShnEGy7GgRgJn4A8CKecHSzqyzXulQ7Zah6GoEUD+vjb+BheP4aN hiYY1OuXD+HsdKeQqS+7eM5U7WW6dz2Q8mtFJ5qAxjY75T0pPrHwZMlJUhUZ+Q2V FfweJEaoNB9w9McPe1cAiE+oeejZ0jq0el3/dJsx3rlVqZN+lMhRJJeVHFyeb3XF lLFCUGhA7hxn2xf3x1JW -----END CERTIFICATE----- 我们只取得我们的公钥,并将其复制到我们的资源服务器src / main / resources / public.txt中 -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp OsMquZMyOyteO2RsVeMLF/hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2 /5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo/KHk4nz+Fa6P3L8+L90E/3qwf6j3 DKWnAgJFRY8AbSYXt1d5ELiIG1/gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR xxCLXT+tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43+a9Qn5icSRnDfTAesQ3Cr lAWJKl2kcWU1HwJqw+dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK eQIDAQAB -----END PUBLIC KEY----- 6.3、Maven 配置 接下来,我们不希望JMS文件被maven过滤进程拾取 - 所以我们将确保将其排除在pom.xml中: <build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <excludes> <exclude>*.jks</exclude> </excludes> </resource> </resources> </build> 如果我们使用Spring Boot,我们需要确保我们的JKS文件通过Spring Boot Maven插件添加到应用程序classpath - addResources: <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <addResources>true</addResources> </configuration> </plugin> </plugins> </build> 6.4、授权服务器 现在,我们将配置JwtAccessTokenConverter使用mytest.jks中的KeyPair,如下所示: @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray()); converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest")); return converter; } 6.5、资源服务器 最后,我们需要配置我们的资源服务器使用公钥 - 如下所示: @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); Resource resource = new ClassPathResource("public.txt"); String publicKey = null; try { publicKey = IOUtils.toString(resource.getInputStream()); } catch (final IOException e) { throw new RuntimeException(e); } converter.setVerifierKey(publicKey); return converter; } 7、结论 放弃吧~~ 博客福利:点我获取阿里云优惠券 我的官网 我的官网http://guan2ye.com我的CSDN地址http://blog.csdn.net/chenjianandiyi我的简书地址http://www.jianshu.com/u/9b5d1921ce34我的githubhttps://github.com/javanan我的码云地址https://gitee.com/jamen/阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com
slife spring boot 搭建的一个企业级快速开发脚手架。 这本来是我自己平时测试用的项目,没打算开源。但今天放到 开源中国 和 GitHub 没想到会被 码云设置为推荐项目。并且还上了今日热门项目 第一名 联系方式 qq群 421351927 項目地址https://gitee.com/jamen/slife 技术栈 Spring Boot MySQL Freemark SiteMesh Shiro Boostrapt mybatis、mybatisPlus redis Activiti 编码约定 系统分为controller、service、dao层。controller主要负责转发、service主要负责业务逻辑、dao主要是数据库的操作。 文件名称约定 在页面文件夹中,按照功能模块分别建立不同的文件夹存放页面,如用户的页面就放在user文件夹中,而角色的就放在role文件夹中。 页面如果是列表类型的。页面的文件名用list.ftl命名。 页面如果是详情类型的。页面的文件名用detail.ftl命名。 controller、service、dao方法名称约定 如果是增加数据操作用insert做前缀。 如果是删除操作用delete做前缀 如果是修改操作用update做前缀 如果是查询操作用select做前缀 数据库读写分离 缓存ecache、redis 新建模块 new Module GroupId --->com.slife ArtifactId---> slife-模块名称 如 slife-activiti Version --> 版本号 如 1.0SNAPSHOT Module-Name--> slife-模块名称 如 slife-activiti 提交新建模块 pom 文件引入 <name>slife-模块名称</name> <dependencies> <dependency> <groupId>com.slife</groupId> <artifactId>slife-common</artifactId> </dependency> . . .其他的依赖 . </dependencies> JDK版本 1.8 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <compilerArgs> <arg>-parameters</arg> </compilerArgs> <useIncrementalCompilation>false</useIncrementalCompilation> </configuration> </plugin> </plugins> </build> 新建一个功能模块 1、创建数据库 2、创建entity类 3、创建service类 4、创建controller类 5、创建list界面 5.1 到其他list复制代码过 5.2 修改 <script> var url = "${base}/sys/user/"; </script> 中的 url 为你刚刚创建的 controller的类 @Controller @RequestMapping(value = "/sys/user") public class SysUserController extends BaseController { 的 @RequestMapping(value = "/sys/user") 值 5.3 修改搜索条件 目前的搜索条件有 /** * 等于 */ public static final String SEARCH_EQ="search_eq_"; /** * 左模糊 */ public static final String SEARCH_LLIKE="search_llike_"; /** * 右模糊 */ public static final String SEARCH_RLIKE="search_rlike_"; /*** * 全模糊 */ public static final String SEARCH_LIKE="search_like_"; <input type="text" class="form-filter input-sm _search" name="search_eq_login_name"> 只要在 input中 的 name 加入 search_eq_ 前缀 再加数据库中的字段名称即可 5.4 修改表格的字段名称 项目截图介绍 系统用户管理 系统菜单管理 系统角色管理 RBAC权限管理模型 日志监控 系统自定义注解,结合AOP,监控用户操作行为 API文档 swaggerUi接口文档展示 数据库监控 使用druid监控数据库健康。本来这里是三个数据源的,使用aop动态的书写切换。没上传到git,需要的同学可以私我 maven构建 多模块开发 根据不同的业务,不在不同的业务模块中开发,如果基本的用户、组织等的管理在 sys模块 日志的业务逻辑在 log模块 可插拔式部署 把不同的模块打包成jar,对应的freemark文件也打包在对应的模块jar中。实现了功能模块的可插拔式部署。 联系方式 qq群 421351927 福利 点我获取阿里云优惠券 我的官网 个人资源 我的官网http://guan2ye.com我的CSDN地址http://blog.csdn.net/chenjianandiyi我的简书地址http://www.jianshu.com/u/9b5d1921ce34我的githubhttps://github.com/javanan我的码云地址https://gitee.com/jamen/阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com
JetBrains 授权服务器(License Server URL):http://idea.imsxm.com *使用方法:*激活时选择License server 填入http://idea.imsxm.com 点击Active即可。 how-to-active: when active,type the url in License server address input box,and then press the Active button:) PS:在线激活有一个过期时间,这个时间一过就必须再次联网授权服务器请求激活 ps: there is a expired date when u use online active,when expired ,u should online active again. 若资金允许,请点击https://www.jetbrains.com/idea/buy/购买正版 if u r rich,please buy the ide on https://www.jetbrains.com/idea/buy/ 授权服务器理论支持的版本有(supported version): IntelliJ IDEA 7.0 或更高(or above) ReSharper 3.1 或更高 ReSharper Cpp 1.0 或更高 dotTrace 5.5 或更高 dotMemory 4.0 或更高 dotCover 1.0 或更高 RubyMine 1.0 或更高 PyCharm 1.0 或更高 WebStorm 1.0 或更高 PhpStorm 1.0 或更高 AppCode 1.0 或更高 CLion 1.0 或更高 点我获取阿里云优惠券 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com
内容提示:阿里云ECS使用须知 为了保证您云服务器 ECS 实例的正常运行,在使用之前,务必认真阅读以下注意事项。 操作须知 禁忌 禁止用户使用 ECS 实例做流量穿透服务。违规者最高处以关停并锁定实例的处罚,并清退处理。 禁止使用 ECS 针对淘宝等电商网站从事刷单、刷销量、刷广告、进行虚假网站交易的网络行为。 禁止升级云服务器的内核和操作系统版本。 不要开启 SELinux。 不要卸载 PVDriver。 不要随意修改网卡 MAC 地址。 建议 对于 4 GB 以上内存的云服务器,请选择 64 位操作系统(32 位操作系统存在 4 GB 的内存寻址限制),如: Aliyun Linux 15.1 64 位 CoreOS 681.2.0 64 位 CentOS 7.2 64 位 Debian 8.0.4 64 位 FreeBSD 10.1 64 位 Gentoo 13 64 位 OpenSUSE 13.1 64 位 SUSE Linux 64 位 Ubuntu 14.04 64 位 Windows 2008 64 位 Windows 2012 64 位 Windows 32 位操作系统支持最高 CPU 核数为 4 核。 支持变更 CPU、内存。需要先停止云服务器再操作升级,IP 信息不做变更,无需迁移数据。 支持带宽升级。无缝升级,无需停机,可按天选择。 为保证服务的连续性,避免因宕机迁移而导致的服务不可用,建议把相关软件都设置成开机启动。如果有应用服务连接的数据库,需要在程序中设置成自动重连机制。 I/O 优化实例不要关闭 aliyun-service 服务。 Windows 操作系统须知 不要关闭 Windows 系统自带的 shutdownmon.exe 进程。关闭后可能会使服务器重启时间变长。 不要重命名、删除或禁用 Windows 下的 Administrator 账号,以免影响服务器使用。 如果您使用普通云盘,不建议使用虚拟内存。如果是高效云盘或SSD云盘,可以根据实际情况使用虚拟内存。 控制台修改密码针对的是 Administrator 账号。如果 Administrator 账号被删除或者重命名,点击修改密码按钮会自动创建 Administrator 账号。 Linux 操作系统须知 不要修改 Linux 服务器下默认的 /etc/issue 文件内容。修改该文件会导致管理控制台的功能按钮无法正常使用。 不要随意更改分区下目录的权限,尤其是 /etc/sbin/bin/boot/dev/usr/lib 等目录权限。如果权限更改不当会导致系统出现异常。 不要重命名、删除或禁用 Linux下的 root 账号。 不要编译 Linux 系统的内核,或对内核进行任何其他操作。 如果您使用普通云盘,不建议使用swap分区。如果是高效云盘或SSD云盘,可以根据实际情况使用swap分区。 不要开启 NetWorkManager 服务。该服务会跟系统内部网络服务出现冲突,导致网络异常。 按量付费注意事项 使用按量付费之前,需要做实名认证,并确保账户余额至少有 100 元(非代金券)。 不支持“包年包月”和“按量付费”相互更换。1 台云服务器只能选择 1 种付费模式,不能同时选择。 不提供备案服务。 不支持 5 天无理由退款。 按量付费支持更换操作系统,但不支持配置变更功能(包括带宽升级、CPU 和内存升级、新增数据盘);若选择 0 Mbps 固定带宽,则不分配外网 IP,也不支持 0 Mbps 带宽升级。 每小时计费总费用 = CPU 费用 + 内存费用 + 数据盘费用 + 公网带宽费用。CPU、内存、 数据盘、固定带宽均按小时计费。 带宽按使用流量计费,仅单向收取流出流量费用(0.8 元 / GB),流入流量免费。例如,您在 1 小时内公网流出流量为 2.5 GB,则收取费用为 2.5 GB * 0.8 元 / GB = 2.0 元。 点我获取阿里云优惠券 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com
内容提示:阿里云ecs入门教程:步骤 4 格式化和挂载数据盘 如果您在创建实例时选择了数据盘,在登录实例后,系统需要先格式化数据盘,然后挂载数据盘。 另外,您还可以根据业务需要,对数据盘进行多分区配置。建议使用系统自带的工具进行分区操作。 注意:云服务器 ECS 仅支持对 数据盘 进行二次分区,而不支持对 系统盘 进行二次分区(不管是 Windows 还是 Linux 系统)。如果您强行使用第三方工具对系统盘进行二次分区操作,可能引发未知风险,如系统崩溃、数据丢失等。 本文档以 非 I/O 优化+SSD云盘 Linux (Redhat、CentOS、Debian、Ubuntu)实例为例进行介绍。非 I/O 优化和 I/O 优化的区别在于,前者比后者多一个字母 x (如非 I/O优化为 xvdb,则 I/O 优化为 vdb)。 使用管理终端,或远程连接工具,输入用户名 root 和密码登录到实例。 运行 fdisk -l 命令查看数据盘。注意:在没有分区和格式化数据盘之前,使用 df -h 命令是无法看到数据盘的。在下面的示例中,有一个 96.6 GB 的数据盘需要挂载。 运行 fdisk -l 命令查看数据盘。注意:在没有分区和格式化数据盘之前,使用 df -h 命令是无法看到数据盘的。在下面的示例中,有一个 96.6 GB 的数据盘需要挂载。 如果执行了 fdisk -l 命令后,没有发现 /dev/xvdb,则表示您的实例没有数据盘,因此无需挂载,请忽略这一章。 运行 fdisk -l 命令查看数据盘。注意:在没有分区和格式化数据盘之前,使用 df -h 命令是无法看到数据盘的。在下面的示例中,有一个 96.6 GB 的数据盘需要挂载。 如果执行了 fdisk -l 命令后,没有发现 /dev/xvdb,则表示您的实例没有数据盘,因此无需挂载,请忽略这一章。 运行 fdisk /dev/xvdb,对数据盘进行分区。根据提示,依次输入 n,p,1,两次回车,wq,分区就开始了。 运行 fdisk /dev/xvdb,对数据盘进行分区。根据提示,依次输入 n,p,1,两次回车,wq,分区就开始了。 运行 fdisk -l 命令,查看新的分区。新分区 xvdb1 已经创建好。如下面示例中的/dev/xvdb1。 运行 fdisk -l 命令,查看新的分区。新分区 xvdb1 已经创建好。如下面示例中的/dev/xvdb1。 运行 mkfs.ext3 /dev/xvdb1,对新分区进行格式化。格式化所需时间取决于数据盘大小。您也可自主决定选用其他文件格式,如 ext4 等。 运行 mkfs.ext3 /dev/xvdb1,对新分区进行格式化。格式化所需时间取决于数据盘大小。您也可自主决定选用其他文件格式,如 ext4 等。 运行 echo /dev/xvdb1 /mnt ext3 defaults 0 0 >> /etc/fstab 写入新分区信息。完成后,可以使用cat /etc/fstab 命令查看。 运行 echo /dev/xvdb1 /mnt ext3 defaults 0 0 >> /etc/fstab 写入新分区信息。完成后,可以使用cat /etc/fstab 命令查看。 运行 echo /dev/xvdb1 /mnt ext3 defaults 0 0 >> /etc/fstab 写入新分区信息。完成后,可以使用cat /etc/fstab 命令查看。 注意: Ubuntu 12.04 不支持 barrier,所以对该系统正确的命令是:echo /dev/xvdb1 /mnt ext3 defaults 0 0 >> /etc/fstab 运行 echo /dev/xvdb1 /mnt ext3 defaults 0 0 >> /etc/fstab 写入新分区信息。完成后,可以使用cat /etc/fstab 命令查看。 注意: Ubuntu 12.04 不支持 barrier,所以对该系统正确的命令是:echo /dev/xvdb1 /mnt ext3 defaults 0 0 >> /etc/fstab 如果需要把数据盘单独挂载到某个文件夹,比如单独用来存放网页,可以修改以上命令中的 /mnt 部分。 运行 mount /dev/xvdb1 /mnt 挂载新分区,然后执行 df -h 查看分区。如果出现数据盘信息,说明挂载成功,可以使用新分区了。 mount /dev/xvdb1 /mntdf -hFilesystem Size Used Avail Use% Mounted on/dev/xvda1 40G 1.5G 36G 4% /tmpfs 498M 0 498M 0% /dev/shm/dev/xvdb1 5.0G 139M 4.6G 3% /mnt 点我获取阿里云优惠券 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com
内容提示:阿里云ECS服务器入门教程:步骤 3 远程连接 Linux 实例 根据您本地的操作系统,您可以从 Windows、Linux、Mac OS X 等操作系统连接 Linux 实例。本文介绍常用的连接服务器方式。更全面详细的连接实例方式介绍,请参考 连接 Linux 实例。 云服务器 ECS 实例创建好之后,您可以使用以下任意一种方式连接 ECS 实例: 管理控制台的 远程连接 功能:无论您在创建实例时是否购买了带宽,都可以通过管理控制台的远程连接 功能连接实例,进行管理。 使用远程连接软件: 常用的远程连接软件有 Putty、Xshell 等。 手机:您也可以通过手机上的远程桌面 APP (例如 SSH Control Light) 连接实例。由于操作比较简单,此处不再赘述。 忘记实例登录密码怎么办? 如果您忘记了实例的登录密码 (不是 管理终端 的密码),请 重置密码。 使用远程连接功能连接 ECS 实例 当普通远程连接工具(比如 Putty、Xshell、SecureCRT 等)无法使用时,您可以使用云服务器管理控制台的 远程连接 功能进入 ECS 实例登录界面,查看服务器界面当时状态;如果您拥有操作权限,可以连接到服务器进行操作配置,这一功能对于有技术能力的用户解决自己遇到的问题有很大的帮助。 使用场景 远程连接 功能适用的场景包括但不限于: 实例引导速度慢(如启动自检),您可以通过 远程连接 功能查看进度; 由于实例内部设置错误,导致无法使用软件远程连接,例如误操作开启了防火墙,您可以通过 远程连接 功能连接到实例后关闭防火墙; 应用消耗 CPU/带宽比较高,导致无法远程连接(例如被肉鸡,进程 CPU/带宽跑满),您可以通过 远程连接 功能连接到 ECS 实例,结束异常进程等。 操作步骤 登录 云服务器管理控制台。 找到需要连接的实例,在 操作 列,单击 远程连接。 连接 管理终端:如果这是您第一次连接 管理终端,应按照以下方式连接 管理终端:在弹出的 管理终端连接密码 对话框,单击 复制密码 按钮。注意:连接密码提示只出现一次,以后每次登录时都需要输入该密码,因此请务必记下该密码。 单击 关闭 按钮关闭该对话框。在弹出的 输入管理终端密码 对话框中粘贴连接密码后,单击 确定 按钮,开始连接 管理终端。 如果这不是您第一次连接 管理终端,应在弹出的 输入管理终端密码 对话框中输入密码,单击 确定 按钮,开始连接 管理终端。没有任何弹出窗口时,您可以单击界面左上角的 发送远程命令 > 连接管理终端,再在弹出的 输入管理终端密码 对话框中输入密码,单击 确定 按钮,开始连接 管理终端。 输入用户名 root 和密码。密码是您 创建实例 时设置的密码。 输入用户名 root 和密码。密码是您 创建实例 时设置的密码。 Linux 实例支持 CTRL+ALT+F1-F10 的快捷键切换,可以切换不同的终端来进行不同的操作。 如果出现黑屏,是因为 Linux 实例处于休眠状态,单击键盘上任意键即可唤醒。 管理终端 FAQ 管理终端是独享的吗? 管理终端是独享的吗? 目前是独享。用户独占登录情况下,其他用户无法登录。 忘记了管理终端密码,怎么办? 忘记了管理终端密码,怎么办? 第一次打开连接管理终端,界面会显示用户 VNC 密码,而且仅提示 1 次。如果忘记密码,可以通过右上角的 修改管理终端密码 修改密码。密码限制为 6 位,支持数字和大小写字母,不支持特殊字符。 修改了管理终端密码后,怎么登录不上? 修改了管理终端密码后,怎么登录不上? 修改密码后,需要在控制台重启实例,密码才会生效。 Linux 实例登录管理终端后黑屏,怎么恢复登录界面? Linux 实例登录管理终端后黑屏,怎么恢复登录界面? 输入VNC 密码后 Linux 实例如果出现持续黑屏,说明系统处于休眠状态,按任意键可以激活,进入登录界面。Windows 实例则需要点击发送远程命令的 CTRL+ALT+DEL 键后即可到登录界面。 我使用 IE8.0,为什么使用不了管理终端? 我使用 IE8.0,为什么使用不了管理终端? 支持 IE10 及以上。请下载最新的 IE 浏览器或 Chrome 浏览器。 管理终端无法访问了,怎么处理? 管理终端无法访问了,怎么处理? 可以使用 Chrome 浏览器,键盘按 F12,显示开发者工具,然后查看 Console 中的信息进行分析。 从 Windows 环境连接 Linux 实例 远程连接软件的用法大同小异。本文档以 Putty 为例,介绍如何远程连接实例。Putty 操作简单、免费、免安装,下载地址:http://www.chiark.greenend.org.uk/~sgtatham/putty/ 启动 Putty.exe 程序,进入 Putty 主界面。 在 Host Name 中输入实例的公网 IP 地址。使用默认端口 22。在 Connection Type 中,选择 SSH。在 Saved Session 中输入希望保存的名字,然后单击 Save ,这样以后可以方便调用而不需要每次输入 IP 地址。 单击 Open 进行连接。 单击 Open 进行连接。 首次连接,会出现以下提示。单击 是。 首次连接,会出现以下提示。单击 是。 根据提示,分别输入您的 Linux 云服务器 ECS 实例的用户名和密码。注意密码不会显示在屏幕上。输入完成后回车。 根据提示,分别输入您的 Linux 云服务器 ECS 实例的用户名和密码。注意密码不会显示在屏幕上。输入完成后回车。 您现在成功连接到实例,可以进行操作了。 从 Linux 或 Mac OS X 环境连接 Linux 实例 直接使用SSH命令进行连接,如:ssh root@实例的公网IP,然后输入该实例的 root 用户的密码,即可完成连接。 点我获取阿里云优惠券 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com
这里只介绍新购实例。如果您有镜像,可以使用自定义镜像创建实例。 新购实例操作步骤: 登录 云服务器管理控制台。如果尚未注册,单击 免费注册。 在左侧导航栏里,单击 实例,并在 实例列表 页右上角,单击 创建实例。 选择付费方式:包年包月 或 按量付费。关于两种付费方式的区别,请参见 计费模式。 选择付费方式:包年包月 或 按量付费。关于两种付费方式的区别,请参见 计费模式。 如果选择 按量付费,请确保账户余额至少有 100元。如无余额,请进入 充值页面 充值后再开通。 选择付费方式:包年包月 或 按量付费。关于两种付费方式的区别,请参见 计费模式。 如果选择 按量付费,请确保账户余额至少有 100元。如无余额,请进入 充值页面 充值后再开通。 注意:对于按量付费的实例,即使停止实例,也会继续收费。如果您不再需要该按量付费的实例,请及时释放实例。 选择付费方式:包年包月 或 按量付费。关于两种付费方式的区别,请参见 计费模式。 如果选择 按量付费,请确保账户余额至少有 100元。如无余额,请进入 充值页面 充值后再开通。 注意:对于按量付费的实例,即使停止实例,也会继续收费。如果您不再需要该按量付费的实例,请及时释放实例。 选择地域。所谓地域,是指实例所在的地理位置。您可以根据您的用户所在的地理位置选择地域。与用户距离越近,延迟相对越少,下载速度相对越快。例如,您的用户都分布在杭州地区,则可以选择 华东1。 选择地域。所谓地域,是指实例所在的地理位置。您可以根据您的用户所在的地理位置选择地域。与用户距离越近,延迟相对越少,下载速度相对越快。例如,您的用户都分布在杭州地区,则可以选择 华东1。 注意:不同地域间的内网不能互通。实例创建完成后,不支持更换地域。不同地域提供的可用区数量、实例系列、存储类型、实例价格等也会有所差异。请根据您的业务需求进行选择。 选择可用区。如果不选择,系统会随机分配可用区。 选择网络类型。目前,大部分地域提供两种网络类型。网络类型一旦选择后,不能更改,因此请慎重选择。 如果您想使用经典网络,选择 经典网络。然后选择一个已有的安全组或者 新建一个安全组。 如果您想使用经典网络,选择 经典网络。然后选择一个已有的安全组或者 新建一个安全组。 如果您是在 2017 年 6 月 14 日下午 5 点以后第一次购买 ECS 实例,将不能选择经典网络。如果您需要创建经典网络的实例,请 提交工单 申请加入白名单。 如果您需要使用逻辑隔离的专有网络,选择 专有网络。您可以为您的专有网络实例选择是否分配公网 IP 地址。 如果您需要使用逻辑隔离的专有网络,选择 专有网络。您可以为您的专有网络实例选择是否分配公网 IP 地址。 注意:分配的公网 IP 地址不能与 ECS 实例解除绑定关系。如果您需要更加灵活的静态公网 IP 方案,建议选择 不分配 公网 IP 地址,再去配置并绑定弹性公网 IP 地址。 如果您需要使用逻辑隔离的专有网络,选择 专有网络。您可以为您的专有网络实例选择是否分配公网 IP 地址。 注意:分配的公网 IP 地址不能与 ECS 实例解除绑定关系。如果您需要更加灵活的静态公网 IP 方案,建议选择 不分配 公网 IP 地址,再去配置并绑定弹性公网 IP 地址。 选择实例,包括实例系列、I/O 优化实例和实例规格。关于实例规格的详细介绍,请参考 实例规格族。实例系列 II 是实例系列 I 的升级版,提供更高的性能,推荐使用。推荐选择 I/O 优化实例,挂载后可以获得 SSD 云盘的全部性能。 选择实例,包括实例系列、I/O 优化实例和实例规格。关于实例规格的详细介绍,请参考 实例规格族。实例系列 II 是实例系列 I 的升级版,提供更高的性能,推荐使用。推荐选择 I/O 优化实例,挂载后可以获得 SSD 云盘的全部性能。 选择网络带宽。如果选择 0 Mbps,则不分配外网 IP,该实例将无法访问公网。如果您选择了 按量付费,同时选择 0 Mbps 固定带宽,则同样不分配外网IP,而且 不支持 0 Mbps 带宽升级,因此请谨慎选择。按固定带宽付费 选择网络带宽。如果选择 0 Mbps,则不分配外网 IP,该实例将无法访问公网。如果您选择了 按量付费,同时选择 0 Mbps 固定带宽,则同样不分配外网IP,而且 不支持 0 Mbps 带宽升级,因此请谨慎选择。按固定带宽付费 按使用流量付费 选择网络带宽。如果选择 0 Mbps,则不分配外网 IP,该实例将无法访问公网。如果您选择了 按量付费,同时选择 0 Mbps 固定带宽,则同样不分配外网IP,而且 不支持 0 Mbps 带宽升级,因此请谨慎选择。按固定带宽付费 按使用流量付费 选择镜像。您可以选择公共镜像,包含正版操作系统,购买完成后再手动安装部署软件;您也可以选择镜像市场提供的镜像,一般集成了运行环境和各类软件。公共镜像中的操作系统 License 无须额外费用(海外地域除外)。 选择镜像。您可以选择公共镜像,包含正版操作系统,购买完成后再手动安装部署软件;您也可以选择镜像市场提供的镜像,一般集成了运行环境和各类软件。公共镜像中的操作系统 License 无须额外费用(海外地域除外)。 选择操作系统的时候,注意以下内容: 最流行的服务器端操作系统,强大的安全性和稳定性。 免费且开源,轻松建立和编译源代码。 通过 SSH 方式远程访问您的云服务器。 一般用于高性能 Web 等服务器应用,支持常见的 PHP/Python 等编程语言,支持 MySQL 等数据库(需自行安装)。 推荐使用 CentOS。 选择存储。系统盘 为必选,用于安装操作系统。您还可以根据业务需求,选择添加最多 4 块 数据盘,每块数据盘最大 32 TB。关于云盘的详细介绍,请参考 云盘概述。 选择存储。系统盘 为必选,用于安装操作系统。您还可以根据业务需求,选择添加最多 4 块 数据盘,每块数据盘最大 32 TB。关于云盘的详细介绍,请参考 云盘概述。 您还可以选择 用快照创建磁盘,把快照的数据直接复制到磁盘中。 选择存储。系统盘 为必选,用于安装操作系统。您还可以根据业务需求,选择添加最多 4 块 数据盘,每块数据盘最大 32 TB。关于云盘的详细介绍,请参考 云盘概述。 您还可以选择 用快照创建磁盘,把快照的数据直接复制到磁盘中。 设置实例的登录密码和实例名称。请务必牢记密码。如果您选择 创建后设置,实例创建完成后通过 重置密码 设置密码。 设置实例的登录密码和实例名称。请务必牢记密码。如果您选择 创建后设置,实例创建完成后通过 重置密码 设置密码。 设置购买的时长和数量。 单击页面右侧价格下面的 立即购买。 确认订单并付款。 实例创建好之后,您会收到短信和邮件通知,告知您的实例名称、公网 IP 地址、内网 IP 地址等信息。您可以使用这些信息登录和管理实例。 很多重要的信息都是通过绑定手机的短信接收,并且重要的操作(如重启、停止等)都需要手机接收验证码,因此请务必保持绑定手机通信畅通。 遇到没有资源的情况怎么办? 如果在创建实例过程中,您遇到没有资源的情况,建议您采取以下措施: 更换地域 更换可用区 更换资源配置 如果依然没有资源,建议您耐心等待一段时间再购买。实例资源是动态的,如果资源不足,阿里云会尽快补充资源,但是需要一定时间。您可以在晚些时候或者次日再尝试购买。 阿里云幸运券免费领取 幸运券抽奖,参考:幸运券抽奖 本文源自:阿里云服务器 点我获取阿里云优惠券 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com
什么是waf? 如何打造一款可靠的WAF(Web应用防火墙)? WAF攻防实战 如何正确的使用阿里云盾网站安全防御(WAF) 当WEB应用越来越为丰富的同时,WEB 服务器以其强大的计算能力、处理性能及蕴含的较高价值逐渐成为主要攻击目标。SQL注入、网页篡改、网页挂马等安全事件,频繁发生。 这么重要的工具使用,为此无收集了网上的一些博客。希望能帮助到大家。 什么是waf Web应用防护系统(也称:网站应用级入侵防御系统。英文:Web Application Firewall,简称:WAF)。利用国际上公认的一种说法:Web应用防火墙是通过执行一系列针对HTTP/HTTPS的安全策略来专门为Web应用提供保护的一款产品。 如何打造一款可靠的WAF(Web应用防火墙) WAF一句话描述,就是解析HTTP请求(协议解析模块),规则检测(规则模块),做不同的防御动作(动作模块),并将防御过程(日志模块)记录下来。不管硬件款,软件款,云款,核心都是这个,而接下来围绕这句话来YY WAF的实现。WAF的实现由五个模块(配置模块、协议解析模块、规则模块、动作模块、错误处理模块)组成 [如何打造一款可靠的WAF(Web应用防火墙),请看我的博客http://www.guan2ye.com/2017/11/03/%E5%A6%82%E4%BD%95%E6%89%93%E9%80%A0%E4%B8%80%E6%AC%BE%E5%8F%AF%E9%9D%A0%E7%9A%84WAF-Web%E5%BA%94%E7%94%A8%E9%98%B2%E7%81%AB%E5%A2%99.html](http://www.guan2ye.com/2017/11/03/%E5%A6%82%E4%BD%95%E6%89%93%E9%80%A0%E4%B8%80%E6%AC%BE%E5%8F%AF%E9%9D%A0%E7%9A%84WAF-Web%E5%BA%94%E7%94%A8%E9%98%B2%E7%81%AB%E5%A2%99.html) WAF攻防实战 waf 安全防护 摘要 本文主要分为四个部分,一、首先对WAF做了简单的介绍,让读者对WAF这类产品有一个大概的了解;二、这部分通过一个实例演示了如何利用WAF为其后端的Web应用提供安全防护功能;三、安全是相对的,世界上任何一款安全产品都不可能提供100%的安全防护功能,WAF也是一样。因此,第三部分主要讨论了WAF的安全性,介绍了一些主流的WAF绕过技术,并结合真实案例来演示了如何尝试绕过WAF的防护,成功攻击其后端的Web应用;四、这部分对WAF的安全性进行了总结,告诉读者如何用正确、理性的眼光去看待WAF这类产品。 请看我的博客WAF攻防实战http://www.guan2ye.com/2017/11/03/WAF%E6%94%BB%E9%98%B2%E5%AE%9E%E6%88%98.html 阿里云盾网站安全防御(WAF)的正确使用方法 阿里云WAF,支持非阿里云服务器防御,业界知名的WEB漏洞防护,每天更新防护规则,1分钟快速接入,轻松阻挡SQL注入,XSS,远程文件 ,TOP10攻击,同时具备CC攻击防御.里云DDoS高防IP,支持非阿里云服务器防御,300G防御按天付费,一分钟快速接入,防御全部DDoS攻击类型SYN FLood,NTP反射,SSDP反射等,同时提供CC和WEB漏洞防护这是 一篇来自 张戈的 文章 [阿里云盾网站安全防御(WAF)的正确使用方法http://www.guan2ye.com/2017/11/03/%E9%98%BF%E9%87%8C%E4%BA%91%E7%9B%BE%E7%BD%91%E7%AB%99%E5%AE%89%E5%85%A8%E9%98%B2%E5%BE%A1(WAF)%E7%9A%84%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95.html](http://www.guan2ye.com/2017/11/03/%E9%98%BF%E9%87%8C%E4%BA%91%E7%9B%BE%E7%BD%91%E7%AB%99%E5%AE%89%E5%85%A8%E9%98%B2%E5%BE%A1%28WAF%29%E7%9A%84%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95.html) 最后献上包括 waf 在内的 阿里云所有产品优惠券 点我获取 我的CSDN地址http://blog.csdn.net/chenjianandiyi我的简书地址http://www.jianshu.com/u/9b5d1921ce34我的githubhttps://github.com/javanan我的码云地址https://gitee.com/jamen/阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 我的官网 我的官网http://guan2ye.com
1.win10环境中 1.官网下载,解压2.进入解压目录 shift+右键,控制台打开3.进入 bin目录 执行 emqttd console 控制台模式启动:4.运行emqttd bin目录下执行 emqttd start5.使用mosquitto工具测试下。文档6.查看后台控制台 http://localhost:18083/7.关闭emqttd 进入bin目录 执行 emqttd stop ubuntu14.04安装 1.下载emp:wget http://emqtt.com/downloads/2070/ubuntu14_04 解压 unzip 对应的zip包 移动到自己喜欢的目录 mv emqttd/ /usr/local 控制台调试模式启动,检查 EMQ 是否可正常启动: ./bin/emqttd console 浏览器打开控制台 http://ip:18083/ 测试如 win10 环境 集群 net_kernel:connect_node('emqttd@192.168.8.251')../bin/emqttd_ctl cluster join emqttd@192.168.8.251emqttd_ctl cluster leave 博客小福利 阿里云优惠券 点我免费领取
docker 用久了 日志一大堆,很占用空间,不用的日志可以清理掉了。docker logs -f container name 噼里啪啦 一大堆,,,,太对,清理掉 博客小福利 阿里云优惠券免费取 第一步日志位置 找到对应container的日志文件,一般是在 /var/lib/docker/containers/containerid/containerid.log-json.log(containerid是指你的容器id) 找日志位置 如果找不到,可以模糊查询一下 find / -type f -name "*.log" | xargs grep "ERROR" 找到日志位置(这行命令的意思是从根目录开始查找所有扩展名为.log的文本文件,并找出包含”ERROR”的行,你可把 error 换成你日志中存在的内容,docker logs -f container name 就能看到有什么内容啦) 找容器id 如果不知道容器id是什么, docker inspect Container name 可以看到容器id 第二部:清理一下 cat /dev/null >/var/lib/docker/containers/containerid/containerid.log-json.log
最近在用Spring Cloud,搭建微服务应用,其中一个微服务是把文件上传到七牛,其他的文件上传都是通过他。但是在使用Fegin调用该服务的接口的时候,一直有问题,恩--------先用RestTemplate试试 博客小福利 点我 阿里云优惠券免费取 步骤 1、声明对象 @Bean public RestTemplate restTemplate() { return new RestTemplate(); } 2、发送请求 @Autowired private RestTemplate rest; public void ExceptInfoForRestTemplate(String excelTitle, List<String[]> arrayList) throws ParseException { String fileLocal="E://xxccccccccc" String url = "http://xxxxxx.com/upload"; FileSystemResource resource = new FileSystemResource(new File(fileLocal)); MultiValueMap<String, Object> param = new LinkedMultiValueMap<>(); param.add("file", resource); HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(param); ResponseEntity<String> responseEntity = rest.exchange(url, HttpMethod.POST, httpEntity, String.class); System.out.println(responseEntity.getBody()); } 我的官网http://guan2ye.com我的CSDN地址http://blog.csdn.net/chenjianandiyi我的简书地址http://www.jianshu.com/u/9b5d1921ce34我的githubhttps://github.com/javanan我的码云地址https://gitee.com/jamen/
本文小福利 点我获取阿里云优惠券 AOP核心概念 1、横切关注点 对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点 2、切面(aspect)-》(通知+切点) 类是对物体特征的抽象,切面就是对横切关注点的抽象。通知+切点意思就是所有要被应用到增强(advice)代码的地方。(包括方法的方位信息) 3、连接点(joinpoint)-》(被拦截的方法) 被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截的方法,实际上连接点还可以是字段或者构造器 4、切入点(pointcut)-》(描述拦截那些方法的部分) 对连接点进行拦截的定义 5、通知(advice)-》(拦截后执行自己业务逻辑的那些部分) 所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类这玩意也叫 增强 在逻辑层次上包括了我们抽取的公共逻辑和方位信息。因为Spring只能方法级别的应用AOP,也就是我们常见的before,after,after-returning,after-throwing,around五种,意思就是在方法调用前后,异常时候执行我这段公共逻辑呗。 6、目标对象 代理的目标对象 7、织入(weave) 将切面应用到目标对象并导致代理对象创建的过程。比如根据Advice中的方位信息在指定切点的方法前后,执行增强。这个过程Spring 替我们做好了。利用的是CGLIB动态代理技术。 8、引入(introduction) 在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段 图解 上面那一堆看不懂对吗? 我也不太懂。来看张图 通知(Advice)类型 切面一共有五种通知 Before 某方法调用之前发出通知。 前置通知(Before advice) :在某连接点(JoinPoint)之前执行的通知, 但这个通知不能阻止连接点前的执行。在方法调用之前发出通 @Before("execution(* com.slife.java8.aspect.AspectTest.test())") public void beforeTest() { System.out.println("执行 方法 之前 调用----"); } After 某方法完成之后发出通知 后通知(After advice) :当某连接点退出的时候执行的通知(不论是正常 返回还是异常退出)。不考虑方法运行的结果 。在方法调用之后发出通 @After("execution(* com.slife.java8.aspect.AspectTest.test())") public void afterTest() { System.out.println(); System.out.println("执行 方法 之后 调用----"); } After-returning 将通知放置在被通知的方法成功执行之后。 方法正常返回后,调用通知。在方法调用后,正常退出发出通 @AfterReturning("execution(* com.slife.java8.aspect.AspectTest.test())") public void afterReturningTest() { System.out.println(); System.out.println("执行 方法 AfterReturning 调用----"); } After-throwing 将通知放置在被通知的方法抛出异常之后。 抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行 的通知。在方法调用时,异常退出发出通 @AfterThrowing("execution(* com.slife.java8.aspect.AspectTest.test())") public void afterThrowingTest() { System.out.println(); System.out.println("执行 方法 AfterThrowing 调用----"); } Around 通知包裹在被通知的方法的周围知。 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet 规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。在方法调用之前和之后发出通 @Around("execution(* com.slife.java8.aspect.AspectTest.test())") public void aroundTest() { System.out.println(); System.out.println("执行 方法 前后 调用----"); } 执行结果 2017-10-27 19:51:51.605 DEBUG 15592 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet : Last-Modified value for [/aspecttest] is: -1 执行 方法 之前 调用---- JoinpointTest++++执行我正常流水线的业务逻辑 执行 方法 之后 调用---- 执行 方法 AfterReturning 调用---- 2017-10-27 19:51:51.622 DEBUG 15592 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet : Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling 2017-10-27 19:51:51.622 DEBUG 15592 --- [io-8081-exec-10] o.s.web.servlet.DispatcherServlet : Successfully completed request 切入点表达式 切入点指示符用来指示切入点表达式目的,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支持的AspectJ切入点指示符如下: args() 定制join-point去匹配那些参数为指定类型的方法的执行动作。 @args() 定制join-point去匹配那些参数被指定类型注解的方法的执行动作 execution() 开始匹配在其内部编写的定制 this() 定制join-pont去匹配由AOP代理的Bean引用的指定类型的类。 target() 定制join-point去匹配特定的对象,这些对象一定是指定类型的类。 @target() 定制join-point去匹配特定的对象,这些对象要具有的指定类型的注解。 within() 定制join-point在必须哪一个包中。 @within() 定制join-point在必须由指定注解标注的类中。 @annotation 定制连接点具有指定的注解。 只有execution用来执行匹配,其他标志符都只是为了限制/定制他们所要匹配的连接点的位置。 命名及匿名切入点 类型匹配语法 *:匹配任何数量字符。 ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。 +:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。 例子 java.lang.String 匹配String类型; java.*.String 匹配java包下的任何“一级子包”下的String类型; 如匹配java.lang.String,但不匹配java.lang.ss.String java..* 匹配java包及任何子包下的任何类型; 如匹配java.lang.String、java.lang.annotation.Annotation java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型; java.lang.Number+ 匹配java.lang包下的任何Number的自类型; 如匹配java.lang.Integer,也匹配java.math.BigInteger 详细语法 注解? 修饰符? 返回值类型 类型声明?方法名(参数列表) 异常列表? 注解:可选,方法上持有的注解,如@Deprecated; 修饰符:可选,如public、protected; 返回值类型:必填,可以是任何类型模式;“*”表示所有类型; 类型声明:可选,可以是任何类型模式; 方法名:必填,可以使用“*”进行模式匹配; 参数列表:“()”表示方法没有任何参数;“(..)”表示匹配接受任意个参数的方法,“(..,java.lang.String)”表示匹配接受java.lang.String类型的参数结束,且其前边可以接受有任意个参数的方法;“(java.lang.String,..)” 表示匹配接受java.lang.String类型的参数开始,且其后边可以接受任意个参数的方法;“(*,java.lang.String)” 表示匹配接受java.lang.String类型的参数结束,且其前边接受有一个任意类型参数的方法; 异常列表:可选,以“throws 异常全限定名列表”声明,异常全限定名列表如有多个以“,”分割,如throws java.lang.IllegalArgumentException, java.lang.ArrayIndexOutOfBoundsException。 匹配Bean名称:可以使用Bean的id或name进行匹配,并且可使用通配符“*”; 组合切入点表达式 AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。在Schema风格下,由于在XML中使用“&&”需要使用转义字符“&&”来代替之,所以很不方便,因此Spring ASP 提供了and、or、not来代替&&、||、!。 通知参数 使用JoinPoint获取:Spring AOP提供使用org.aspectj.lang.JoinPoint类型获取连接点数据,任何通知方法的第一个参数都可以是JoinPoint(环绕通知是ProceedingJoinPoint,JoinPoint子类),当然第一个参数位置也可以是JoinPoint.StaticPart类型,这个只返回连接点的静态部分。 运用场景 AOP 、IOC 做为Spring 的支柱,使用场景非常广泛。 1、日志记录 我的另一篇博客 2、权限控制 3、事务 4、多数据源读写切换 我的另一篇博客 原理 动态代理Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为: 1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了 2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB spring boot 项目中定义使用自己的aop 1、引入jar <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 2、编写切面 @Aspect // FOR AOP @Order(-99) // 控制多个Aspect的执行顺序,越小越先执行 @Component public class AdviceTest { @Pointcut(value="execution(* com.slife.java8.aspect.AspectTest.test())") public void poincut(){ } @Before("poincut()") public void beforeTest() { System.out.println("执行 方法 之前 调用----"); } } 这样就完成了在spring boot 项目中定义使用自己的aop 注意代理模式 CGLIB动态代理技术有时候你会发现 你的配置和我一样,但是aop没有生效,这很有可能是SpringMVC的配置的代理模式不对。 问题描述 方法里的xxxService对象如果使用autowared注入,无法启动aspect,但是xxxService = ctx.getBean("xxxxx")获取,是可以启用aspect的 原因 这个时候 xxxService 并不是注入进来的,即使有 @Autowired 注解,这时的注解没有任何作用。只有 Spring 生成的对象才有 AOP 功能,因为 Spring 生成的代理对象才有 AOP 功能。 解决方法 配置spring.aop.proxy-target-class=true 不错的一个问题描述已经解决方法 文章代码 /** * Created by chen on 2017/10/27. * <p> * Email 122741482@qq.com * <p> * Describe: */ @Service public class JoinpointTest { public void JoinpointTest(){ System.out.println("**********JoinpointTest*****************"); } public void test(){ System.out.println("JoinpointTest++++执行我正常流水线的业务逻辑"); } } @Aspect // FOR AOP @Order(-99) // 控制多个Aspect的执行顺序,越小越先执行 @Component public class AdviceTest { @Pointcut(value="execution(* com.slife.java8.aspect.AspectTest.test())") public void poincut(){ } @Before("poincut()") public void beforeTest() { System.out.println("执行 方法 之前 调用----"); } @After("execution(* com.slife.java8.aspect.AspectTest.test())") public void afterTest() { System.out.println(); System.out.println("执行 方法 之后 调用----"); } @Around("execution(* com.slife.java8.aspect.AspectTest.test())") public void aroundTest() { System.out.println(); System.out.println("执行 方法 前后 调用----"); } @AfterReturning("execution(* com.slife.java8.aspect.AspectTest.test())") public void afterReturningTest() { System.out.println(); System.out.println("执行 方法 AfterReturning 调用----"); } @AfterThrowing("execution(* com.slife.java8.aspect.AspectTest.test())") public void afterThrowingTest() { System.out.println(); System.out.println("执行 方法 AfterThrowing 调用----"); } @Before("execution(* com.slife.java8..*test*(..))") public void aspecttest1() { System.out.println(); System.out.println("执行 方法aspecttest1 Before 调用----"); } } 点击获取阿里云优惠券 我的官网[图片上传失败...(image-8a5f4a-1509673150463)] 我的官网http://guan2ye.com我的CSDN地址http://blog.csdn.net/chenjianandiyi我的简书地址http://www.jianshu.com/u/9b5d1921ce34我的githubhttps://github.com/javanan我的码云地址https://gitee.com/jamen/阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
本站小福利 点我获取阿里云优惠券 原文作者:杨大仙的程序空间 4 微服务发布与调用 要点 认识Eureka框架 运行Eureka服务器 发布微服务 调用微服务 本章将讲述Spring Cloud中Eureka的使用,包括在Eureka服务器上发布、调用微服务,Eureka的配置以及Eureka集群等内容。 4.1 Eureka介绍 Spring Cloud集成了Netflix OSS的多个项目,形成了spring-cloud-netflix项目,该项目包含了多个子模块,这些子模块对集成的Netflix旗下框架进行了封装,本小节将讲述其中一个较为重要的服务管理框架:Eureka。 4.1.1 关于Eureka Eureka提供基于REST的服务,在集群中主要用于服务管理。Eureka提供了基于Java语言的客户端组件,客户端组件实现了负载均衡的功能,为业务组件的集群部署创造了条件。使用该框架,可以将业务组件注册到Eureka容器中,这些业务组件可进行集群部署,Eureka主要维护这些服务的列表并自动检查它们的状态。 4.1.2 Eureka架构 一个简单的Eureka集群,需要有一个Eureka服务器、若干个服务提供者。我们可以将业务组件注册到Eureka服务器中,其他客户端组件可以向服务器获取服务并且进行远程调用。图3-1为Eureka的架构图。 图3-1 Eureka架构图 图3-1中有两个服务器,服务器支持集群部署,每个服务器也可以作为对方服务器的客户端进行相互注册与复制。图3-1的三个Eureka客户端,两个用于发布服务,其中一个用于调用服务。不管服务器还是客户端,都可以部署多个实例,如此一来,就很容易构建高可用的服务集群。 4.1.3服务器端 对于注册到服务器端的服务组件,Eureka服务器并没有提供后台的存储,这些注册的服务实例被保存在内存的注册中心,它们通过心跳来保持其最新状态,这些操作都可以在内存中完成。客户端存在着相同的机制,同样在内存中保存了注册表信息,这样的机制提升了Eureka组件的性能,每次服务的请求都不必经过服务器端的注册中心。 4.1.4服务提供者 作为Eureka客户端存在的服务提供者,主要进行以下工作:第一、向服务器注册服务;第二、发送心跳给服务器;第三、向服务器端获取注册列表。当客户端注册到服务器时,它将会提供一些关于它自己的信息给服务器端,例如自己的主机、端口、健康检测连接等。 4.1.5服务调用者 对于发布到Eureka服务器的服务,使用调用者可对其进行服务查找与调用,服务调用者也是作为客户端存在,但其职责主要是发现与调用服务。在实际情况中,有可能出现本身既是服务提供者,也是服务调用者的情况,例如传统的企业应用三层架构中,服务层会调用数据访问层的接口进行数据操作,它本身也会提供服务给控制层使用。 本小节对Eureka作了介绍,读者大概了解Eureka架构及各个角色的作用即可,说一千道一万,还不如来个案例实际,下一小节将以一个案例来展示Eureka的作用。 4.2 第一个Eureka应用 本小节将编写一个Hello Wrold小程序,来演示Eureka作用,程序中将会包含服务器、服务提供者以及服务调用者。 4.2.1 构建服务器 先创建一个名称为first-ek-server的Maven项目作为服务器,在pom.xml文件中加入Spring Cloud的依赖,如代码清单3-1所示。 代码清单3-1:codes033.2first-ek-serverpom.xml <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> </dependencies> 加入的spring-cloud-starter-eureka-server,会自动引入spring-boot-starter-web,因此只需要加入该依赖,我们的项目就具有Web容器的功能。接下来,编写一个最简单的启动类,启动我们的Eureka服务器,启动类如代码清单3-2所示。 代码清单3-2:codes033.2first-ek-serversrcmainjavaorgcrazyitcloudFirstServer.java package org.crazyit.cloud; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class FirstServer { public static void main(String[] args) { new SpringApplicationBuilder(FirstServer.class).run(args); } } 启动类几乎与前面章节的Spring Boot项目一致,只是加入了@EnableEurekaServer,声明这是一个Eureka服务器。直接运行FirstServer即可启动Eureka服务器,需要注意的是,本例中并没有配置服务器端口,因此默认端口为8080,我们将端口配置为8761,在src/main/resources目录下创建application.yml配置文件,内容如下: server: port: 8761 本书的大部分案例使用yml文件进行配置,以上的配置片断声明服务器的HTTP端口为8761,该配置是Spring Boot的公共配置,Sprin Boot有近千个公共配置,如想了解这些配置,可参看Spring Boot的文档。运行FirstServer的main方法后,可以看到控制台输出如下: 2017-08-04 15:35:58.900 INFO 4028 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8761 (http) 2017-08-04 15:35:58.901 INFO 4028 --- [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761 2017-08-04 15:35:58.906 INFO 4028 --- [ main] org.crazyit.cloud.FirstServer : Started FirstServer in 12.361 seconds (JVM running for 12.891) 2017-08-04 15:35:59.488 INFO 4028 --- [nio-8761-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet' 在启动过程中会出现部分异常信息,暂时不需要进行处理,将在后面章节讲解如何避免出现这些异常。成功启动后,打开浏览器,输入:http://localhost:8761,可以看到Eureka服务器控制台,如图3-2所示。 图3-2 Eureka控制台 在图3-2的下方,可以看到服务的实例列表,目前我们并没有注册服务,因此列表为空。 4.2.2 服务器注册开关 在启动Eureka服务器时,会在控制台看到以下两个异常信息: java.net.ConnectException: Connection refused: connect com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server 这是由于在服务器启动时,服务器会把自己当作一个客户端,注册去Eureka服务器,并且会到Eureka服务器抓取注册信息,它自己本身只是一个服务器,而不是服务的提供者(客户端),因此可以修改application.yml文件,修改以下两个配置: eureka: client: registerWithEureka: false fetchRegistry: false 以上配置中的eureka.client.registerWithEureka属性,声明是否将自己的信息注册到Eureka服务器,默认值为true。属性eureka.client.fetchRegistry则表示,是否到Eureka服务器中抓取注册信息。将这两个属性设置为false,则启动时不会出现异常信息。 4.2.3 编写服务提供者 在前面搭建环境章节,我们使用了Spring Boot来建立了一个简单的Web工程,并且在里面编写了一个REST服务,本例中的服务提供者,与该案例类似。建立名称为“first-ek-service-invoker”的项目,pom.xml中加入依赖,如代码清单3-2所示。 代码清单3-2:codes033.2first-ek-service-providerpom.xml <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> 在src/main/resources目录中建立application.yml配置文件,文件内容如代码清单3-3所示。 代码清单3-3:codes033.2first-ek-service-providersrcmainresourcesapplication.yml spring: application: name: first-service-provider eureka: instance: hostname: localhost client: serviceUrl: defaultZone: http://localhost:8761/eureka/ 以上配置中,将应用名称配置为“first-service-provider”,该服务将会被注册到端口为8761的Ereka服务器,也就是本小节前面所构建的服务器。另外,还使用了eureka.instance.hostname来配置该服务实例的主机名称。编写一个Controller类,并提供一个最简单的REST服务,如代码清单3-4。 代码清单3-4: codes033.2first-ek-service-providersrcmainjavaorgcrazyitcloudFirstController.java package org.crazyit.cloud; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController public class FirstController { @RequestMapping(value = "/person/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public Person findPerson(@PathVariable("personId") Integer personId) { Person person = new Person(personId, "Crazyit", 30); return person; } } 编写启动类,请见代码清单3-5。 代码清单3-5: codes033.2first-ek-service-providersrcmainjavaorgcrazyitcloudFirstServiceProvider.java package org.crazyit.cloud; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class FirstServiceProvider { public static void main(String[] args) { new SpringApplicationBuilder(FirstServiceProvider.class).run(args); } } 在启动类中,使用了@EnableEurekaClient注解,声明该应用是一个Eureka客户端。配置完成后,运行服务器项目“first-ek-server”的启动类FirstServer,再运行代码清单3-5的FirstServiceProvider,的浏览器中访问Eureka:http://localhost:8761/,可以看到服务列表如图3-3所示。 图3-3 查看发布的服务 如图3-3,可以看到当前注册的服务列表,只有我们编写的“first-service-provider”。服务注册成功后,接下来编写服务调用者。 4.2.4 编写服务调用者 服务被注册、发布到Eureka服务器后,就需要有程序去发现它,并且进行调用。此处所说的调用者,是指同样注册到Eureka的客户端,来调用其他客户端发布的服务,简单的说,就是Eureak内部调用。由于同一个服务,可能会部署多个实例,调用过程可能涉及负载均衡、服务器查找等问题,Netflix的项目已经帮我们解决,并且Spring Cloud已经封装了一次,我们仅需编写少量代码,就可以实现服务调用。 新建名称为“first-ek-service-invoker”的项目,在pom.xml文件中加入依赖,如代码清单3-6所示。 代码清单3-6:codes033.2first-ek-service-invokerpom.xml <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> </dependencies> 建立配置文件application.yml,内容如代码清单3-7。 代码清单3-7:codes033.2first-ek-service-invokersrcmainresourcesapplication.yml server: port: 9000 spring: application: name: first-service-invoker eureka: instance: hostname: localhost client: serviceUrl: defaultZone: http://localhost:8761/eureka/ 在配置文件中,配置了应用名称为“first-service-invoker”,这个调用者的访问端口为9000,需要注意的,这个调用本身也可以对外提供服务。与提供者一样,使用eureka的配置,将调用者注册到“first-ek-server”上面。下面编写一个控制器,让调用者对外提供一个测试的服务,代码清单3-8为控制器的代码实现。 代码清单3-8: codes033.2first-ek-service-invokersrcmainjavaorgcrazyitcloudInvokerController.java package org.crazyit.cloud; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @Configuration public class InvokerController { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } @RequestMapping(value = "/router", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public String router() { RestTemplate restTpl = getRestTemplate(); // 根据应用名称调用服务 String json = restTpl.getForObject( "http://first-service-provider/person/1", String.class); return json; } } 在控制器中,配置了RestTemplate的bean,RestTemplate本来是spring-web模块下面的类,主要用来调用REST服务,本身并不具备调用分布式服务的能力,但是RestTemplate的bean被@LoadBalanced注解修饰后,这个RestTemplate实例就具有访问分布式服务的能力,关于该类的一些机制,我们将放到负载均衡一章中讲解。 在控制器中,新建了一个router的测试方法,用来对外发布REST服务,该方法只是一个路由作用,实际上使用RestTemplate来调用“first-ek-service-provider”(服务提供者)的服务。需要注意的是,调用服务时,仅仅是通过服务名称来进行调用。接下来编写启动类,如代码清单3-9所示。 代码清单3-9: codes033.2first-ek-service-invokersrcmainjavaorgcrazyitcloudFirstInvoker.java package org.crazyit.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class FirstInvoker { public static void main(String[] args) { SpringApplication.run(FirstInvoker.class, args); } } 在启动类中,使用了@EnableDiscoveryClient注解来修改启动类,该注解使得服务调用者,有能力去Eureka中发现服务,需要注意的是@EnableEurekaClient注解已经包含了@EnableDiscoveryClient的功能,也就是说,一个Eureka客户端,本身就具有发现服务的能力。配置完成后,依次执行以下操作: 启动服务器(first-ek-server) 启动服务提供者(first-ek-service-provider) 启动服务调用者(first-ek-service-invoker) 使用浏览器访问Eureka,可看到注册的客户信息,如图3-4。 图3-4 服务列表 全部成功启动后,在浏览器中访问服务调用者发布的“router”服务: http://localhost:9000/router 可以看到在浏览器输出:{"id":1,"name":"Crazyit","age":30} 根据输出可知,实际上调用了服务提供者的/person/1服务,第一个Eureka应用到此结束,下面对这个应用程序的结构作一个简单描述。 4.2.5 程序结构 本案例新建了三个项目,如果读者对程序的结构不太清晰,可以参看图3-5。 图3-5 本案例的结构 如图3-5,Eureka服务为本例的“first-ek-server”,服务提供者为“first-ek-service-provider”,而调用者为“first-ek-service-invoker”,用户通过浏览器访问调用者的9000端口的router服务,router服务中查找服务提供者的服务并进行调用。在本例中,服务调用有点像路由器的角色。 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
就在今天 10月14日上午9:00 阿里巴巴于在杭州云栖大会《研发效能峰会》上,正式发布《阿里巴巴Java开发手册》扫描插件,该插件在扫描代码后,将不符合《手册》的代码按Blocker/Critical/Major三个等级显示在下方,甚至在IDEA上,还基于Inspection机制提供了实时检测功能,编写代码的同时也能快速发现问题所在。对于历史代码,部分规则实现了批量一键修复的功能。--两个字牛逼git地址为这里写链接内容https://github.com/alibaba/p3c 本站小福利 点我获取阿里云优惠券 IDea的安装方式: IDEA版的插件发布到了IDEA官方仓库中(最低支持版本14.1.7,JDK1.7+),只需打开 Settings >> Plugins >> Browse repositories 输入 Alibaba 搜索一下便可以看到对应插件了,点击安装等待安装完成。 如图 Eclipse的安装方式: Eclipse版插件支持4.2(Juno,JDK1.8+)及以上版本,提供Update Site,通过 Help >> Install New Software 然后输入https://p3c.alibaba.com/plugin/eclipse/update 即可看到安装列表,安装即可。 插件的更新,可以通过 Help >> Check for Udates 进行新版本检测。 怎么用 呵呵 右键,,看图 -- 还有自动提示的效果 可以说是非常棒了 文章如有侵权地方,请告知删除 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 我的官网http://guan2ye.com我的CSDN地址http://blog.csdn.net/chenjianandiyi我的简书地址http://www.jianshu.com/u/9b5d1921ce34我的githubhttps://github.com/javanan我的码云地址https://gitee.com/jamen/
本站小福利 点我获取阿里云优惠券 原文作者:杨大仙的程序空间 3 Spring Boot简介与配置 3.1 Spring Boot Spring Cloud基于Spring Boot搭建,本小节将对Spring Boot作一个大致的讲解,读者知道Spring Boot作用即可。 3.1.1 Spring Boot简介 开发一个全新的项目,需要先进行开发环境的搭建,例如要确定技术框架以及版本,还要考虑各个框架之间的版本兼容问题,完成这些繁琐的工作后,还要对新项目进行配置,测试能否正常运行,最后才将搭建好的环境提交给项目组的其他成员使用。经常出现的情形是,表面上已经成功运行,但部分项目组成员仍然无法运行,项目初期浪费大量的时间做这些工作,几乎每个项目都会投入部分工作量来做这些固定的事情。 受Ruby On Rails、Node.js等技术的影响,JavaEE领域需要一种更为简便的开发方式,来取代这些繁琐的项目搭建工作。在此背景下,Spring推出了Spring Boot项目,该项目可以让使用者更快速的搭建项目,使用者可以更专注、快速的投入到业务系统开发中。系统配置、基础代码、项目依赖的jar包,甚至是开发时所用到的应用服务器等,Spring Boot已经帮我们准备好,只要在建立项目时,使用构建工具加入相应的Spring Boot依赖包,项目即可运行,使用者无需关心版本兼容等问题。 Spring Boot支持Maven和Gradle这两款构建工具。Gradle使用Groovy语言进行构建脚本的编写,与Maven、Ant等构建工具有良好的兼容性。鉴于笔者使用Maven较多,因此本书使用Maven作为项目构建工具。笔者成书时,Spring Boot最新的正式版本为1.5.4,要求Maven版本为3.2或以上。 3.1.2 新建Maven项目 在新建菜单中选择新建“Maven Project”,填写的项目信息如图2-5所示。 图2-5 新建Maven项目 为了测试项目的可用性,加入Spring Boot的web启动模块,让该项目具有Web容器的功能,pom.xml文件内容如代码清单2-1所示。 代码清单2-1:codes02env-testpom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.crazyit.cloud</groupId> <artifactId>env-test</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.4.RELEASE</version> </dependency> </dependencies> </project> 配置完依赖后,该依赖会自动帮我们的项目加上其他的Spring模块以及所依赖的第三方包,例如spring-core、sprin-beans、spring-mvc等,除了这些模块外,还加入了嵌入式的Tomcat。 3.1.3 编写启动类 加入了依赖后,只需要编写一个简单的启动类,即可启动Web服务,启动类如代码清单2-2所示。 代码清单2-2:codes02env-testsrcmainjavaorgcrazyitcloudMyApplication.java package org.crazyit.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } MyApplication类使用了@SpringBootApplication注解,该注解声明了该类是一个Spring Boot应用,该注解具有“@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan”等注解的功能。直接运行MyApplication的main方法,看到以下输出信息后,证明成功启动: 2017-08-02 20:53:05.327 INFO 1976 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2017-08-02 20:53:05.530 INFO 1976 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2017-08-02 20:53:05.878 INFO 1976 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) 2017-08-02 20:53:05.885 INFO 1976 --- [ main] org.crazyit.cloud.MyApplication : Started MyApplication in 5.758 seconds (JVM running for 6.426) 根据输出信息可知,启动的Tomcat端口为8080,打开浏览器访问:http://localhost:8080,可以看到错误页面,表示应用已经成功启动。 3.1.4 编写控制器 在前面小节加入的spring-boot-starter-web模块,默认集成了SpringMVC,因此只需要编写一个Controller,即可实现一个最简单的HelloWord程序,代码清单2-3为控制器。 代码清单2-3:codes02env-testsrcmainjavaorgcrazyitcloudMyController.java package org.crazyit.cloud; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @Controller public class MyController { @GetMapping("/hello") @ResponseBody public String hello() { return "Hello World"; } } 代码清单2-3中使用了@Controller注解来修饰MyController,由于启动类中使用了@SpringBootApplication注解,该注解含有@ComponentScan的功能,因此@Controller会被扫描并注册。在hello方法中使用了@GetMapping与@ResponseBody注解,声明hello方法的访问地址以及返回内容。重新运行启动类,打开浏览器并访问以下地址:http://localhost:8080/hello,可以看到控制器的返回。 3.1.5 发布REST WebService Spring MVC支持直接发布REST风格的WebService,新建测试的对象Person,如代码清单2-4所示。 代码清单2-4:codes02env-testsrcmainjavaorgcrazyitcloudPerson.java package org.crazyit.cloud; public class Person { private Integer id; private String name; private Integer age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } 修改控制器类,修改后如代码清单2-5。 代码清单2-5:codes02env-testsrcmainjavaorgcrazyitcloudMyController.java package org.crazyit.cloud; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class MyController { @GetMapping("/hello") public String hello() { return "Hello World"; } @RequestMapping(value = "/person/{personId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public Person findPerson(@PathVariable("personId") Integer personId) { Person p = new Person(); p.setId(personId); p.setName("Crazyit"); p.setAge(30); return p; } } MyController类中,将原来的@Controller注解修改为@RestController,原来的hello方法也不需要再使用@ResponseBody进行修饰,@RestController已含有@ResponseBody注解。新建findPerson方法,该方法将会根据参数id来创建一个Person实例并返回,访问该方法将会得到JSON字符串。运行启动类,在浏览器中输入:http://localhost:8080/person/1,可看到接口返回以下JSON字符串: {"id":1,"name":"Crazyit","age":30} 调用REST服务的方式有很多,此部分内容将在后面章节中讲述。 3.2 Spring Boot配置文件 Spring Cloud基于Spring Boot构建,很多模块的配置均放在Spring Boot的配置文件中,因此有必要了解一下Spring Boot的配置文件规则,为学习后面的章节打下基础。 3.2.1 默认配置文件 Spring Boot会按顺序读取各种配置,例如命令行参数、系统参数等,本章只讲述配置文件的参数读取。默认情况下,Spring Boot会按顺序到以下目录读取application.properties或者application.yml文件: 项目根目录的config目录。 项目根目录。 项目classpath下的config目录。 项目classpath根目录。 如对以上描述有疑问,可参看图2-6。 图2-6 配置文件读取顺序 图2-6中的数字为文件的读取顺序,本小节使用的boot-config-file项目依赖了spring-boot-starter-web项目,为pom.xml加入以下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.4.RELEASE</version> </dependency> 3.2.2 指定配置文件位置 如果想自己指定配置文件,可以在Spring容器的启动命令中加入参数,例子如代码清单2-6所示。 代码清单2-6:codes02boot-config-filesrcmainjavaorgcrazyitbootTestDefaultFile.java package org.crazyit.boot; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class TestDefaultFile { public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder( TestDefaultFile.class) .properties( "spring.config.location=classpath:/test-folder/my-config.properties") .run(args); // 输出变量 System.out.println(context.getEnvironment().getProperty("jdbc.user")); } TestDefaultFile类,在使用SpringApplicationBuilder时,配置了spring.config.location属性来设定需要读取的配置文件。 3.2.3 yml文件 YAML语言使用一种方便的格式的来进行数据配置,通过配置分层、缩进,在很大程度上增强了配置文件的可读性,使用YAML语言的配置文件以“.yml”作为后缀。代码清单2-7为一份yml配置文件。 代码清单2-7:codes02boot-config-filesrcmainresourcesmy-config.yml jdbc: user: root passwd: 123456 driver: com.mysql.jdbc.Driver 在此,需要注意的是,每一行配置的缩进要使用空格,不要使用tab键进行缩进。代码清单2-7对应的properties文件内容如下: jdbc.user=root jdbc.passwd=123456 jdbc.driver=com.mysql.jdbc.Driver 3.2.4 运行时指定profiles配置 如果在不同的环境下激活不同的配置,可以使用profiles,代码清单2-8中配置了两个profiles。 代码清单2-8:codes02boot-config-filesrcmainresourcestest-profiles.yml spring: profiles: mysql jdbc: driver: com.mysql.jdbc.Driver --- spring: profiles: oracle jdbc: driver: oracle.jdbc.driver.OracleDriver 定义了mysql与oracle两个profiles,profiels间使用“---”进行分隔,在Spring容器启动时,使用spring.profiles.active来指定激活哪个profiles,如代码清单2-9所示。 代码清单2-9:codes02boot-config-filesrcmainjavaorgcrazyitbootTestProfiles.java package org.crazyit.boot; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class TestProfiles { public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder( TestProfiles.class) .properties( "spring.config.location=classpath:/test-profiles.yml") .properties("spring.profiles.active=oracle").run(args); // 输出变量 System.out.println(context.getEnvironment().getProperty("jdbc.driver")); // 启动第二个Spring容器,指定端口为8081 ConfigurableApplicationContext context2 = new SpringApplicationBuilder( TestProfiles.class) .properties( "spring.config.location=classpath:/test-profiles.yml") .properties("spring.profiles.active=mysql").properties("server.port=8081").run(args); // 输出变量 System.out.println(context2.getEnvironment().getProperty("jdbc.driver")); } } 对Spring Boot的配置文件有一定了解后,对后面章节Spring Cloud的配置内容就不会陌生。 3.2.5 热部署 每次修改Java后,都需要重新运行Main方法才能生效,这样的会降低开发效果,我们可以使用Spring Boot提供的开发工具来实现热部署,为项目加上以下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> 当Java文件修改后,容器会重新加载本项目的Java类。 3.3 小结 本文主要讲述了本书基础环境的搭建,读者主要掌握Maven的使用,本书的案例几乎都是Maven项目。Spring Cloud项目以Spring Boot作为基础进行构建,本书的大部分案例也是基于Spring Boot,本章对Spring Boot作了大致的讲解,并且配合一个Hello World例子来演示Spring Boot的便捷,学习完本章后,读者知道Spring Boot的大致功能,即可达到目标。 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
本站小福利 点我获取阿里云优惠券 原文作者:杨大仙的程序空间 2 开发环境搭建 工欲善其事,必先利其器。在讲述本书的技术内容前,先将开发环境搭建好,本书所涉及基础环境将在本章准备,包括Eclipse、Maven等。如果读者对Maven、Eclipse、Spring Boot等项目较为熟悉,可以直接跳过本章的相关章节。 笔者建议读者在查阅本书过程中,使用与本书相同的工具以及版本。本章使用的Java版本为1.8,图2-1为“java –version”命令的输出,Java安装与配置较为简单,本书不再赘述。 图2-1 Java版本 注:本书全部的案例均在Windows7下开发和运行。 2.1 安装与配置Maven 2.1.1 关于Maven Maven是Apache下的一个开源项目,用于项目的构建。使用Maven可以对项目的依赖包进行管理,支持构建脚本的继承,对于一些模块(子项目)较多的项目来说,Maven是更好的选择,子项目可以继承父项目的构建脚本,减少了构建脚本的冗余。 除此之外,Maven本身的插件机制让其更加强大和灵活,使用者可以配置各种Maven插件来完成自己的事,如果感觉官方或者第三方提供的Maven插件不够用,还可以自行编写符合自己要求的Maven插件。Maven为使用者提供了一个统一的依赖仓库,各种开源项目的发布包可以在上面找到,在一间公司或者一个项目组内部,甚至可以搭建私有的Maven仓库,将自己项目的包放到私有仓库中,供其他项目组或者开发者使用。 Maven的众多特性中,最为重要的是它对依赖包的管理,Maven将项目所使用的依赖包的信息放到pom.xml的dependencies节点。例如我们需要使用spring-core模块的jar包,只需在pom.xml配置该模块的依赖信息,Maven会自动将spring-beans等模块引入到我们项目的环境变量中。Spring Cloud项目基于Spring Boot搭建,正是由于依赖管理的特性,使得Maven与Spring Boot更加相得益彰,可以让我们更快速的搭建一个可用的开发环境。 2.1.2 下载与安装Maven 本书所使用的Maven版本为3.5,可以到Maven官方网站下载:http://maven.apache.org/。下载并解压后得到apache-maven-3.5.0目录,将主目录下的的bin目录加入到系统的环境变量中,如图2-2所示。 图2-2 修改环境变量 配置完后,打开cmd命令行,输入“mvn –v”,可以看到输出的Maven版本信息。Maven下载的依赖包会存放到本地仓库中,默认路径为:C:Users用户名.m2repository。 2.1.3 配置远程仓库 如果不进行仓库配置,默认情况下,会到apache官方的仓库下载依赖包,由于Apache官方的仓库位于国外,下载速度较慢,会降低开发效率,笔者建议使用国内的Maven仓库或者搭建自己的私服,本书重点不是Maven,因此直接使用了由阿里云提供的Maven仓库。修改apache-maven-3.5.0/conf目录下的setting.xml,在mirrors节点下加入以下配置: <mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror> 配置完后,以后在使用过程中,Maven会先到阿里云的仓库中下载依赖包。另外,需要注意的是,本书的大部分案例,都没有使用Maven的继承特性,每一个Maven项目都可以独立引入。 2.2 安装Eclipse 2.2.1 Eclipse版本 本书使用Eclipse作为开发工具,使用版本为Luna(4.4),大家可以从以下的地址得到该版本的Eclipse:http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/lunasr2,也可以在本书所附的soft目录下找到该版本的Eclipse。目前Eclipse已经发展到4.7版本,本书主要在Eclipse中使用Maven插件。 2.2.2 在Eclipse配置Maven Luna版本的Eclipse自带了Maven插件,默认使用的是Maven3.2,由于我们前面安装的是Maven3.5版本,因此需要在Eclipse中指定Maven版本以及配置文件。指定Maven的配置如图2-3所示,指定配置文件如图2-4所示。 图2-3 Eclipse指定Maven版本 图2-4 指定Maven配置文件 注意:本书的案例,如无特别说明均以Maven项目的形式导入。 如读者已经安装Eclipse、Maven等工具,可直接跳过本文。 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
本站小福利 点我获取阿里云优惠券 原文作者:杨大仙的程序空间 1 Spring Cloud概述 本文要点 传统应用的问题 微服务与Spring Cloud 本书介绍 本章将会简述Spring Cloud的功能,描述什么是Spring Cloud,它能为我们带来什么,为后面学习该框架的知识打下理论的基础。 1.1 传统的应用 1.1.1 单体应用 在此之前,笔者所在公司开发Java程序,大都使用Struts、Spring、Hibernate(MyBatis)等技术框架,每一个项目都会发布一个单体应用。例如开发一个进销存系统,将会开发一个war包部署到Tomcat中,每一次需要开发新的模块或添加新功能时,都会在原来的基础上不断的添加。若干年后,这个war包不断的膨胀,程序员在进行调试时,服务器也可能需要启动半天,维护这个系统的效率极为低下。这样一个war包,涵盖了库存、销售、会员、报表等模块,如图1-1。 图1-1 单体应用 这样的单体应用隐患非常多,任何的一个bug,都有可能导致整个系统宕机。笔者印象最深刻的是,曾经有一客户在高峰期,导出一张销售明细报表(数据量较大),最终造成整个系统瘫痪,前台的销售人员无法售卖。维护这样一个系统,不仅效率极低,而且充满风险,项目组的各个成员惶惶不可终日,我们需要本质上的改变。 1.1.2 架构演进 针对以上的单体应用的问题,我们参考SOA架构,将各个模块划分独立的服务模块(war),并且使用了数据库的读写分离,架构如图1-2。 图1-2 架构演进 各个模块之间会存在相互调用的依赖关系,例如销售模块会调用会员模块的接口,为了减少各个模块之间的耦合,我们加入了企业服务总线(ESB),各模块与ESB之间的架构如图1-3所示。 图1-3 ESB 加入ESB后,各个模块将服务发布到ESB中,它们与ESB之间使用SOAP协议进行通信。图1-2与图1-3的架构实现后,整个系统的性能有了明显的提升,各个模块的耦合度也降低了。运行了一段日子后,又出现了新的问题,由于销售终端数量的增多,销售模块明显超过其承受能力,为了保证销售前端的正常运行,我们使用了Nginx做负载均衡,请见图1-4。 图1-4 使用Nginx 随着销售模块的增多,带来了许多问题,例如管理这些模块,对于运维工程师来说,是一项艰巨的任务,一旦销售模块有所修改,他们将通宵达旦进行升级。另外,企业服务总线也有可能成为性能的瓶颈,虽然目前仍未出现该问题,但我们需要未雨绸缪。 1.1.3 架构要求 从前面的架构演进可知,应用中的每一个点,都有可能成为系统的问题点。随着互联网应用的普及,在大数据、高并发的环境下,我们的系统架构需要面对更为严苛的挑战,我们需要一套新的架构,它起码能满足以下要求: 高性能:这是应用程序的基本要求。 独立性:其中一个模块出现bug或者其他问题,不可以影响其他模块或者整个应用。 容易扩展:应用中的每一个节点,都可以根据实际需要进行扩展。 便于管理:对于各个模块的资源,可以轻松进行管理、升级,减少维护成本。 状态监控与警报:对整个应用程序进行监控,当某一个节点出现问题时,能及时发出警报。 为了能解决遇到的问题、达到以上的架构要求,我们开始研究Spring Cloud。 1.2 微服务与Spring Cloud 1.2.1 什么是微服务 微服务一词来源Martin Fowler的“Microservices”一文,微服务是一种架构风格,将单体应用划分为小型的服务单元,微服务之间使用HTTP的API进行资源访问与操作。 在对单体应用的划分上,微服务与前面的SOA架构有点类似,但是SOA架构侧重于将每个单体应用的服务集成到ESB上,而微服务做得更加彻底,强调将整个模块变成服务组件,微服务对模块的划分粒度可能会更细。以我们前面的销售、会员模块为例,在SOA架构中,只需要将相应的服务发布到ESB容器就可以了,而在微服务架构中,这两个模块本身,将会变为一个或多个的服务组件。SOA架构与微服务架构,请见图1-5与图1-6。 图1-5 SOA架构 图1-6 微服务架构 在微服务的架构上,Martin Fowler的文章肯定了Netflix的贡献,接下来,我们了解一下Netflix OSS。 1.2.2 关于Netflix OSS Netflix是一个互联网影片提供商,在几年前,Netflix公司成立了自己的开源中心,名称为Netflix Open Source Software Center,简称Netflix OSS。这个开源组织专注于大数据、云计算方面的技术,提供了多个开源框架,这些框架包括大数据工具、构建工具、基于云平台的服务工具等。Netflix所提供的这些框架,很好的遵循微服务所推崇的理念,实现了去中心化的服务管理、服务容错等机制。 1.2.3 Spring Cloud与Netflix Spring Cloud并不是一个具体的框架,大家可以把它理解为一个工具箱,它提供的各类工具,可以帮助我们快速的构建分布式系统。 Spring Cloud的各个项目基于Spring Boot,将Netflix的多个框架进行封装,并且通过自动配置的方式将这些框架绑定到Spring的环境中,从而简化了这些框架的使用。由于Spring Boot的简便,使得我们在使用Spring Cloud时,很容易的将Netflix各个框架整合进我们的项目中。Spring Cloud下的“Spring Cloud Netflix”模块,主要封装了Netflix的以下项目: Eureka:基于REST服务的分布式中间件,主要用于服务管理。 Hystrix:容错框架,通过添加延迟阀值以及容错的逻辑,来帮助我们控制分布式系统间组件的交互。 Feign:一个REST客户端,目的是为了简化Web Service客户端的开发 Ribbon:负载均衡框架,在微服务集群中为各个客户端的通信提供支持,它主要实现中间层应用程序的负载均衡 Zuul:为微服务集群提供过代理、过滤、路由等功能。 1.2.4 Spring Cloud的主要模块 除了Spring Cloud Netflix模块外,Spring Cloud还包括以下几个重要的模块: Spring Cloud Config:为分布式系统提供了配置服务器和配置客户端,通过对它们的配置,可以很好的管理集群中的配置文件。 Spring Cloud Sleuth:服务跟踪框架,可以与Zipkin、Apache HTrace和ELK等数据分析、服务跟踪系统进行整合,为服务跟踪、解决问题提供了便利。 Spring Cloud Stream:用于构建消息驱动微服务的框架,该框架在Spring Boot的基础上,整合了“Spring Integration”来连接消息代理中间件。 Spring Cloud Bus:连接RabbitMQ、Kafka等消息代理的集群消息总线。 1.3 本章小结 本章的1.1小节,对传统的单体应用、SOA架构做了一个简单的总结,在此过程中分析我们所遇到的问题。在1.2小节,简单介绍了微服务与Spring Cloud。接下来,我们正式开始讲述本书的知识点。 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
spring Cloud 无疑是现在 做web开发 逼格最高的框架之一了。如果你不会,出门就别打招呼了。 在自己的博客园里转了一些 Spring Cloud 入门系列。希望帮助到大家 欢迎大家到我的官网上查看这些技术博客,博客地址http://www.guan2ye.com 分享点小福利 点我获取阿里云优惠券 博客包括如下图 分享点小福利 点我获取阿里云优惠券 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
希望的效果为 需求 用户点击了某个界面,请求了后台某个接口。接口请求到后台后,记录请求的数据到数据库中。 实现方式 1、自定义一个注解,被加注解的方法,请求的数据被保存下来 2、定义一个aop 去拦截被注解的方法 3、写一个线程池、执行拦截后的逻辑。也就是保存到数据库中 效果图 查看到刚刚请求用户列表界面的执行情况 实现步骤 1、自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SLog { String value() default ""; } 定义一个aop拦截注解 @Aspect @Component public class SLogAspect { /** * 保存日志到数据库的线程池 */ ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("SLogAspect-Thread-%d").build(); ExecutorService executor = new ThreadPoolExecutor(5,200,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy()); @Pointcut("@annotation(com.slife.annotation.SLog)") public void logPointCut() { } @Around("logPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { long beginTime = System.currentTimeMillis(); // 执行方法 Object result = point.proceed(); // 执行时长(毫秒) long time = System.currentTimeMillis() - beginTime; // 获取request HttpServletRequest request = ServletUtils.getHttpServletRequest(); //获取请求的ip String ip = IPUtils.getIpAddr(request); SaveLogTask saveLogTask = new SaveLogTask(point, time, ip); //保存日志到数据库 executor.execute(saveLogTask); return result; } } 线程池执行保存数据到数据库 /** * * @author chen * @date 2017/9/19 * <p> * Email 122741482@qq.com * <p> * Describe: */ public class SaveLogTask implements Runnable { private SlifeLogDao slifeLogDao = ApplicationContextRegister.getBean(SlifeLogDao.class); private ProceedingJoinPoint joinPoint; private long time; private String ip; public SaveLogTask(ProceedingJoinPoint point, long time, String ip) { this.joinPoint = point; this.time = time; this.ip = ip; } @Override public void run() { saveLog(joinPoint, time, ip); } /** * 保存日志 到数据库 * * @param joinPoint * @param time */ private void saveLog(ProceedingJoinPoint joinPoint, long time, String ip) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); SlifeLog slifeLog = new SlifeLog(); SLog sLog = method.getAnnotation(SLog.class); if (slifeLog != null) { // 注解上的描述 slifeLog.setMsg(sLog.value()); } // 请求的方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); slifeLog.setSrc(className + "." + methodName + "()"); // 请求的参数 Object[] args = joinPoint.getArgs(); try { String params = JSON.toJSONString(args[0]); slifeLog.setParams(params); } catch (Exception e) { } // 设置IP地址 slifeLog.setIp(ip); // 用户名 ShiroUser currUser = SlifeSysUser.ShiroUser(); if (null == currUser) { if (null != slifeLog.getParams()) { slifeLog.setName(slifeLog.getParams()); slifeLog.setLoginName(slifeLog.getParams()); } else { slifeLog.setName("获取用户信息为空"); slifeLog.setLoginName("获取用户信息为空"); slifeLog.setCreateId(-1L); } } else { slifeLog.setName(currUser.getName()); slifeLog.setLoginName(currUser.getUsername()); } slifeLog.setUseTime(time); // 保存系统日志 slifeLogDao.insert(slifeLog); } } 给需要的方法加注解 @SLog("获取用户列表数据") @ApiOperation(value = "获取用户列表数据", notes = "获取用户列表:使用约定的DataTable") @PostMapping(value = "/list") @ResponseBody public DataTable<SysUser> list(@RequestBody DataTable dt, ServletRequest request) { return sysUserService.pageSearch(dt); } @SLog("获取用户列表数据") 简单的一个 记录请求的日志 实现。 说明 这里把保存到数据库的逻辑写到了一个线程池中,主要是不希望记录日志的逻辑影响了用户请求数据接口的逻辑,和性能。 点击获取阿里云优惠券 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
项目中如果需要由多个数据源,比如3个,一个主两个从。主库主要是写操作,两个从库做读操作。 那么在spring boot中怎么使用AOP判断程序是读还是写,并且分配到不同的数据源中呢? 本文重要是 的代码是使用 spring boot 、druid、mybatis、mybatis plus等技术做支持的。 逻辑步骤 大概的逻辑为, 1、引入durid 2、配置三个数据源,1个写,2个读,两个从库实现简单的负载功能。 3、配置mybatis 4、配置mybatis plus 5、配置aop 6、定义却点 读(select)的方法操作,使用读库的数据源,其他的 update、delete、insert等使用写库的数据源 7、给写库配置spring 的事务,出现异常的时候回滚。 引入jar 这里不累赘写 mysql 、druid等jar包的引入。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 配置Druid @Configuration public class DruidConfig { /** * 注册DruidServlet * http://localhost:8080/druid/datasource.html查看监控信息 * @return */ @Bean public ServletRegistrationBean druidServletRegistrationBean() { ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(); servletRegistrationBean.setServlet(new StatViewServlet()); servletRegistrationBean.addUrlMappings("/druid/*"); return servletRegistrationBean; } /** * 注册DruidFilter拦截 * * @return */ @Bean public FilterRegistrationBean duridFilterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new WebStatFilter()); Map<String, String> initParams = new HashMap<String, String>(); //设置忽略请求 initParams.put("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/monitor/druid/*"); filterRegistrationBean.setInitParameters(initParams); filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; } } 配置yml writedatasource: url: jdbc:mysql://xx.x.x.x:3306/slife?useUnicode=true&characterEncoding=utf8&useSSL=false driverClass: com.mysql.jdbc.Driver username: xx password: cdd initialSize: 1 minIdle: 1 maxActive: 20 testOnBorrow: true timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 filters: stat,wall,logback #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 useGlobalDataSourceStat: true mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.slife.entity readdatasource01: url: jdbc:mysql://xx.x.x.x:3306/slife?useUnicode=true&characterEncoding=utf8&useSSL=false driverClass: com.mysql.jdbc.Driver username: xx password: cdd initialSize: 1 minIdle: 1 maxActive: 20 testOnBorrow: true timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 filters: stat,wall,logback #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 useGlobalDataSourceStat: true readdatasource02: url: jdbc:mysql://xx.x.x.x:3306/slife?useUnicode=true&characterEncoding=utf8&useSSL=false driverClass: com.mysql.jdbc.Driver username: xx password: cdd initialSize: 1 minIdle: 1 maxActive: 20 testOnBorrow: true timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnReturn: false poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 filters: stat,wall,logback #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 useGlobalDataSourceStat: true 加载配置文件数据 /** * 只提供了常用的属性,如果有需要,自己添加 * */ @Component @ConfigurationProperties(prefix = "writedatasource") public class WriteProperties extends DataProperties{ } /** * 只提供了常用的属性,如果有需要,自己添加 * */ @Component @ConfigurationProperties(prefix = "readdatasource02") public class ReadProperties2 extends DataProperties{ } @ConfigurationProperties(prefix = "druid") public class DruidProperties { private String url; private String username; private String password; private String driverClass; private int maxActive; private int minIdle; private int initialSize; private boolean testOnBorrow; private String filters; 。 。 。 。 get set 省略 3、定义数据源 public enum DataSourceType { read("read", "从库"), write("write", "主库"); private String type; private String name; DataSourceType(String type, String name) { this.type = type; this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } } 简单的负载均衡配置 public class BlifeAbstractRoutingDataSource extends AbstractRoutingDataSource { private int dataSourceNumber; private AtomicInteger count = new AtomicInteger(0); public BlifeAbstractRoutingDataSource(int dataSourceNumber) { this.dataSourceNumber = dataSourceNumber; } @Override protected Object determineCurrentLookupKey() { String typeKey = DataSourceContextHolder.getJdbcType(); if (DataSourceType.write.getType().equals(typeKey)){ return DataSourceType.write.getType(); } // 读 简单负载均衡 int number = count.getAndAdd(1); int lookupKey = number % dataSourceNumber; return new Integer(lookupKey); } } 把数据源加ThreadLocal中 public class DataSourceContextHolder { private static final ThreadLocal<String> LOCAL = new ThreadLocal<String>(); private static Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class); public static ThreadLocal<String> getLocal() { return LOCAL; } /** * 读可能是多个库 */ public static void read() { LOCAL.set(DataSourceType.read.getType()); } /** * 写只有一个库 */ public static void write() { logger.debug("writewritewrite"); LOCAL.set(DataSourceType.write.getType()); } public static String getJdbcType() { return LOCAL.get(); } } 配置写库的事物 读库 一般没配置事物的需求,当然配置了只读会有更好的效果。 @Configuration @EnableTransactionManagement public class DataSourceTransactionManager extends DataSourceTransactionManagerAutoConfiguration { private Logger logger= LoggerFactory.getLogger(getClass()); /** * 自定义事务 * MyBatis自动参与到spring事务管理中,无需额外配置, * 只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与 * DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。 * @return */ @Resource(name = "writeDataSource1") private DataSource dataSource; @Bean(name = "transactionManager") public org.springframework.jdbc.datasource.DataSourceTransactionManager transactionManagers() { logger.info("-------------------- transactionManager init ---------------------"); return new org.springframework.jdbc.datasource.DataSourceTransactionManager(dataSource); } } 约定方法,读写动态切换 @Aspect @Component @Order(-100)// 保证该AOP在@Transactional之前执行 public class DataSourceAop { private Logger logger = LoggerFactory.getLogger(getClass()); @Before("execution(* com.slife.dao..*.select*(..)) || execution(* com.slife.dao..*.get*(..))") public void setReadDataSourceType() { DataSourceContextHolder.read(); logger.info("dataSource切换到:Read"); } @Before("execution(* com.slife.dao..*.*insert*(..)) || execution(* com.slife.dao..*.*update*(..))") public void setWriteDataSourceType() { DataSourceContextHolder.write(); logger.info("dataSource切换到:write"); } } 运行项目,执行结果 点击获取阿里云优惠券 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
推荐、参考资料 参考资料:Java 8 简明教程 中文API:中文API JAVA8 十大新特性详解:JAVA8 十大新特性详解 Java8的新特性以及用法简介:Java8的新特性以及用法简介 扯淡 java8相对以前的版本应该说是一个重要的版本:看过一个笑话,大概是–》 今天CTO推荐了一个10年编程经验的大牛来公司面试。正好主程在开会,于是叫了一个应届生去面试,本想就走个流程,但是没想到,最后这个大牛被拒了。主程问这位应届生,怎么回事。应届生不屑的说,一问三不知,Lambda,Stream都没听说过。 新特性 1、Lambda表达式和函数式接口 由逗号分隔的参数列表、->符号和语句块组成 public static void main(String[] args) { List<String> list = Arrays.asList("3", "1", "2"); //循环输出 list.forEach(e -> System.out.println(e)); //排序 list.sort((e1, e2) -> { int r = e1.compareTo(e2); return r; }); list.forEach(e -> System.out.println(e)); } 函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。 显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义: @FunctionalInterface public interface FunctionalDefaultMethods { void method(); default void defaultMethod() { } } 默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。 @FunctionalInterface public interface FunctionalDefaultMethods { void method(); default void defaultMethod() { } } 2、接口的默认方法、静态方法 Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。 默认方法:往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。 默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写。使用关键字default定义 如 private interface Defaulable { // Interfaces now allow default methods, the implementer may or // may not implement (override) them. default String notRequired() { return "Default implementation"; } } 静态方法:使用关键字static 定义 如 private interface DefaulableFactory { static Defaulable create( Supplier< Defaulable > supplier ) { return supplier.get(); } } 3、方法引用 在学习lambda表达式之后,我们通常使用lambda表达式来创建匿名方法。然而,有时候我们仅仅是调用了一个已存在的方法。如下: Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2)); 在Java8中,我们可以直接通过方法引用来简写lambda表达式中已经存在的方法。 Arrays.sort(stringsArray, String::compareToIgnoreCase); 这种特性就叫做方法引用(Method Reference)。 方法引用的形式 方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号) 有以下四种形式的方法引用: 类型 示例 引用静态方法 ContainingClass::staticMethodName 引用某个对象的实例方法 containingObject::instanceMethodName 引用某个类型的任意对象的实例方法 ContainingType::methodName 引用构造方法 ClassName::new 举个栗子— public class User { public static User create(Supplier<User> supplier) { System.out.println("create "); return supplier.get(); } public static String name(User u) { System.out.println("name "); return "呵呵"; } public int age(User u) { System.out.println("age "); return 18; } } public class MethodTest { public static void main(String[] args) { //构造器的引用 User user = User.create(User::new); List<User> users = Arrays.asList(user); //静态方法引用 users.forEach(User::name); //对象方法的引用 users.forEach(user::age); String[] stringsArray = {"Hello", "World"}; //使用lambda表达式和类型对象的实例方法 Arrays.sort(stringsArray, (s1, s2) -> s1.compareToIgnoreCase(s2)); //使用方法引用 //引用的是类型对象的实例方法 Arrays.sort(stringsArray, String::compareToIgnoreCase); Arrays.asList(stringsArray).stream().forEach(x -> System.out.println(x)); //引用的是类型对象的实例方法 Arrays.asList(stringsArray).stream().forEach(System.out::println); } } 4、重复注解和新的target public class RepeatingAnnotations { @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) public @interface Filters { Filter[] value(); } @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) @Repeatable( Filters.class ) public @interface Filter { String value(); }; @Filter( "filter1" ) @Filter( "filter2" ) public interface Filterable { } public static void main(String[] args) { for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) { System.out.println( filter.value() ); } } } 正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。 另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示: filter1 filter2 新的target 另外Java 8的注解还增加到两种新的target上了: @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {} 5、更好的类型推荐 –好像没什么好说的。感觉要去体会,呵呵。 6、Optional Optional仅仅是一个容易:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查 public class OptionalTest { public static void main(String[] args) { Optional< String > fullName = Optional.ofNullable( null ); //如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false; System.out.println( "Full Name is set? " + fullName.isPresent() ); //orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值 System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); //map()方法可以将现有的Opetional实例的值转换成新的值; System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) ); //orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值 } } 输出 Full Name is set? false Full Name: [none] Hey Stranger! 7、Streams public class StreamEntity { private String name; private int age; public StreamEntity(String name, int age) { this.name = name; this.age = age; } ------------------------ public class StreamTest { public static void main(String[] args) { List<StreamEntity> list = Arrays.asList(new StreamEntity("张三", 19), new StreamEntity("李四", 20), new StreamEntity ("王五", 18), new StreamEntity("呵呵", 19)); int sum = list.stream().filter(u -> u.getAge() > 18).mapToInt(u -> u.getAge()).sum(); System.out.println(sum); } } 输出 58 list集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有age大于18 的;第三,mapToInt操作基于每个task实例的getAge方法将list流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。 ** Steam之上的操作可分为中间操作和晚期操作。 中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。 晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。 steam的另一个价值是创造性地支持并行处理(parallel processing) ** //parallel() 并行流 public class StreamTest { public static void main(String[] args) { List<StreamEntity> list = Arrays.asList(new StreamEntity("张三", 19), new StreamEntity("李四", 20), new StreamEntity ("王五", 18), new StreamEntity("呵呵", 19)); int sum = list.stream().parallel().filter(u -> u.getAge() >= 19).mapToInt(u -> u.getAge()).reduce(0,(a,b)->a+b); int sum1 = list.stream().parallel().filter(u -> u.getAge() >= 19).mapToInt(u -> u.getAge()).reduce(0, Integer::sum); System.out.println(sum); System.out.println(sum1); } } //reduce 万能的,,,后面博客介绍 输出 58 58 Stream的分组 Map<Integer, List< StreamEntity >> m = list.stream().parallel().collect(Collectors.groupingBy(streamEntity -> streamEntity.getAge())); m.forEach((k, v) -> System.out.println("key:value = " + k + ":" + v) ); 输出 key:value = 18:[com.slife.java8.StreamEntity@71c7db30] key:value = 19:[com.slife.java8.StreamEntity@19bb089b, com.slife.java8.StreamEntity@4563e9ab] key:value = 20:[com.slife.java8.StreamEntity@11531931] Date/Time API 使用用Clock替代System.currentTimeMillis()和TimeZone.getDefault()。 final Clock clock = Clock.systemUTC(); System.out.println(clock.instant()); System.out.println(clock.millis()); 输出 2017-10-23T06:20:22.437Z 1508739622508 `` ###LocalDate和LocalTime、LocalDateTime类 final LocalDate date = LocalDate.now(); final LocalDate dateFromClock = LocalDate.now( clock ); System.out.println( date ); System.out.println( dateFromClock ); // Get the local date and local time final LocalTime time = LocalTime.now(); final LocalTime timeFromClock = LocalTime.now( clock ); System.out.println( time ); System.out.println( timeFromClock ); 需要特定时区的data/time信息,则可以使用**ZoneDateTime** final ZonedDateTime zonedDatetime = ZonedDateTime.now(); final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock ); final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( “America/Los_Angeles” ) ); System.out.println( zonedDatetime ); System.out.println( zonedDatetimeFromClock ); System.out.println( zonedDatetimeFromZone ); ###**Duration**类,它持有的时间精确到秒和纳秒 计算两个日期的秒和天数 // Get duration between two dates final LocalDateTime from = LocalDateTime.of( 2016, Month.APRIL, 16, 0, 0, 0 ); final LocalDateTime to = LocalDateTime.of( 2017, Month.APRIL, 16, 23, 59, 59 ); final Duration duration = Duration.between( from, to ); System.out.println( “Duration in days: ” + duration.toDays() ); System.out.println( “Duration in hours: ” + duration.toHours() ); “` Nashorn JavaScript引擎 Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。,允许Java和JavaScript交互使用 Base64 java8提供了Base64的api 并行、并发特性 parallelXXX , DoubleAccumulator DoubleAdder LongAccumulator LongAdder 以后的博客中详细介绍。 VM的新特性 使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld
在spring cloud的项目中,使用Fegin做多个服务之间的调用,是很常见的事情,但是调用失败后虽然能进入熔断器中,但是具体的失败原因,或者日志,如果能看到,那么对开发调试,将会有很大的帮助。 步骤 1、配置FeignConfig @Configuration public class FeignConfig { /** * 配置请求重试 * */ @Bean public Retryer feignRetryer() { return new Retryer.Default(200, SECONDS.toMillis(2), 10); } /** * 设置请求超时时间 *默认 * public Options() { * this(10 * 1000, 60 * 1000); * } * */ @Bean Request.Options feignOptions() { return new Request.Options(60 * 1000, 60 * 1000); } /** * 打印请求日志 * @return */ @Bean public feign.Logger.Level multipartLoggerLevel() { return feign.Logger.Level.FULL; } } 2、配置 FeignClient @FeignClient(value = "test-service", fallback = TestServiceHystrix.class, configuration = FeignConfig.class) public interface TestServiceClient { @PostMapping( value = "/upload",produces = {MediaType.APPLICATION_JSON_UTF8_VALUE},consumes = MediaType.MULTIPART_FORM_DATA_VALUE) UpLoadResult upload(@RequestParam("upload") MultipartFile file); } 3、请求接口,查看控制台日志 2017-10-09 19:31:01.438 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] ---> POST http://test-service/upload? upload=org.springframework.mock.web.MockMultipartFile%4021d2b412 HTTP/1.1 2017-10-09 19:31:01.438 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] Accept: application/json;charset=UTF-8 2017-10-09 19:31:01.439 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] Content-Type: multipart/form-data 2017-10-09 19:31:01.439 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] ---> END HTTP (0-byte body) [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@45a92394: startup date [Mon Oct 09 19:31:01 CST 2017]; parent: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@64b3b1ce 2017-10-09 19:31:01.533 INFO [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring 2017-10-09 19:31:01.776 INFO [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.netflix.config.ChainedDynamicProperty : Flipping property: test-test-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2017-10-09 19:31:01.819 INFO [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-test-test-service 2017-10-09 19:31:01.824 INFO [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.netflix.loadbalancer.BaseLoadBalancer : Client: test-test-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=test-test-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null 2017-10-09 19:31:01.830 INFO [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater 2017-10-09 19:31:01.851 INFO [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.netflix.config.ChainedDynamicProperty : Flipping property: test-test-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647 2017-10-09 19:31:01.852 INFO [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client test-test-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=test-test-service,current list of Servers=[10.10.0.176:8771],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:1; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;] },Server stats: [[Server:10.20.0.176:8700; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0] ]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@15044fde 2017-10-09 19:31:02.047 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] <--- HTTP/1.1 500 (607ms) 2017-10-09 19:31:02.047 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] cache-control: no-cache, no-store, max-age=0, must-revalidate 2017-10-09 19:31:02.047 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] connection: close 2017-10-09 19:31:02.047 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] content-type: application/json;charset=UTF-8 2017-10-09 19:31:02.047 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] date: Mon, 09 Oct 2017 11:31:07 GMT 2017-10-09 19:31:02.047 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] expires: 0 2017-10-09 19:31:02.048 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] pragma: no-cache 2017-10-09 19:31:02.048 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] transfer-encoding: chunked 2017-10-09 19:31:02.048 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] x-application-context: test-test-service:dev:8771 2017-10-09 19:31:02.048 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] x-content-type-options: nosniff 2017-10-09 19:31:02.048 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] x-frame-options: DENY 2017-10-09 19:31:02.048 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] x-xss-protection: 1; mode=block 2017-10-09 19:31:02.048 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] 2017-10-09 19:31:02.049 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] { "code" : 500, "error" : "Internal Server Error", "data" : null } 2017-10-09 19:31:02.049 DEBUG [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.test.client.testServiceClient : [testServiceClient#upload] <--- END HTTP (72-byte body) 2017-10-09 19:31:02.061 INFO [test-sys-test-service,e2818797113202e3,e2818797113202e3,true] 12972 --- [nio-8773-exec-2] c.x.t.c.hystrix.PlUserServiceHystrix : ---upload hystrix--- 从我的日志中可以看到,这次请求失败了,并且能看到发出的请求url,方法类型,返回数据,负载均衡信息,请求的具体的集群ip等,这对我们调试有很大的帮助 关键设置 @Bean public feign.Logger.Level multipartLoggerLevel() { return feign.Logger.Level.FULL; } //----------------------------------- configuration = FeignConfig.class 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/
先分享点小福利给程序员,阿里云的优惠券,购买服务器更便宜 点我获取阿里云优惠券 multipart/form-data Spring MVC中关于关于Content-Type类型信息的使用 首先我们来看看RequestMapping中的Class定义: [html] view plain copy @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface RequestMapping { String[] value() default {}; RequestMethod[] method() default {}; String[] params() default {}; String[] headers() default {}; String[] consumes() default {}; String[] produces() default {}; } value: 指定请求的实际地址, 比如 /action/info之类。 method: 指定请求的method类型, GET、POST、PUT、DELETE等 consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html; produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回 params: 指定request中必须包含某些参数值是,才让该方法处理 headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求 其中,consumes, produces使用content-typ信息进行过滤信息;headers中可以使用content-type进行过滤和判断。 3. 使用示例 3.1 headers [html] view plain copy @RequestMapping(value = "/test", method = RequestMethod.GET, headers="Referer=http://www.ifeng.com/") public void testHeaders(@PathVariable String ownerId, @PathVariable String petId) { // implementation omitted } 这里的Headers里面可以匹配所有Header里面可以出现的信息,不局限在Referer信息。 public static void stream2Multi() throws Exception { // 读入 文件 File file = new File("E:\\模版0720.xls"); FileInputStream in_file = new FileInputStream(file); // 转 MultipartFile MultipartFile multi = new MockMultipartFile("模板.xls", in_file); String name = multi.getOriginalFilename(); // 创建文件夹 String dire = "E:/文件/picture/file.xls"; File file_dire = new File(dire); if (!file_dire.exists()) { file_dire.createNewFile(); } //写入文件 multi.transferTo(file_dire); } org.springframework.web.multipart.MultipartException: Current request is not a multipart request at org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.handleMissingValue(RequestParamMethodArgumentResolver.java:190) at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:109) at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) Caused by: java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found at org.apache.catalina.connector.Request.parseParts(Request.java:2874) at org.apache.catalina.connector.Request.parseParameters(Request.java:3177) at org.apache.catalina.connector.Request.getParameter(Request.java:1110) at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:75) ... 30 more Caused by: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
monitor是什么? 有时候我们需要知道客户端对redis服务端做了那些命令操作。我们可以试用monitor命令来查看。 他能清楚的看到客户端在什么时间点执行了那些命令 MONITOR 是一个调试命令,每个命令流回来的redis服务器处理。它可以帮助理解数据库中正在发生的事情。此命令可用于通过使用CLI通过telnet。看到所有的请求,由服务器处理为了点时使用Redis作为数据库和分布式缓存系统的一个应用程序错误的能力是非常有用的 效果如下 1507515946.410170 [0 10.10.8.20:56169] "AUTH" "123456" 1507515946.426931 [0 10.10.8.20:56169] "PING" 1507515946.458043 [0 10.10.8.20:56169] "INFO" "ALL" 1507515946.477740 [0 10.10.8.20:56169] "select" "1" 1507515946.485924 [1 10.10.8.20:56169] "select" "2" 1507515946.519736 [2 10.10.8.20:56169] "select" "3" 1507515946.536863 [3 10.10.8.20:56169] "select" "4" 1507515946.539574 [4 10.10.8.20:56169] "select" "5" 1507515946.556423 [5 10.10.8.20:56169] "select" "6" 1507515946.583890 [6 10.10.8.20:56169] "select" "7" 1507515946.618607 [7 10.10.8.20:56169] "select" "8" 1507515946.632856 [8 10.10.8.20:56169] "select" "9" 1507515946.635165 [9 10.10.8.20:56169] "select" "10" 1507515946.656267 [10 10.10.8.20:56169] "select" "11" 1507515946.683463 [11 10.10.8.20:56169] "select" "12" 1507515946.702956 [12 10.10.8.20:56169] "select" "13" 1507515946.721350 [13 10.10.8.20:56169] "select" "14" 1507515946.735145 [14 10.10.8.20:56169] "select" "15" 1507515946.751276 [15 10.10.8.20:56169] "select" "16" 1507515947.879896 [15 10.10.8.20:56169] "SELECT" "0" 1507515947.928903 [0 10.10.8.20:56171] "AUTH" "123456" 1507515947.930488 [0 10.10.8.20:56171] "PING" 1507515947.949174 [0 10.10.8.20:56171] "INFO" "ALL" 1507515948.362843 [0 10.10.8.20:56156] "PING" 1507515948.466909 [0 10.10.8.20:56169] "scan" "0" "MATCH" "*" "COUNT" "10000" 1507515949.498885 [0 10.10.8.20:56169] "type" "2" 1507515949.501959 [0 10.10.8.20:56169] "ttl" "2" 1507515949.528084 [0 10.10.8.20:56169] "SCARD" "2" 1507515950.300299 [0 10.10.8.20:56169] "type" "xxx" 1507515950.302353 [0 10.10.8.20:56169] "ttl" "xxx" 1507515950.891284 [0 10.10.8.20:56169] "GET" "xxx" 1507515952.951806 [0 10.10.8.20:56171] "INFO" "all" 1507515954.605228 [0 10.10.8.20:56169] "type" "2" 1507515954.612624 [0 10.10.8.20:56169] "ttl" "2" 1507515954.616165 [0 10.10.8.20:56169] "SCARD" "2" 1507515954.789474 [0 10.10.8.20:56169] "SSCAN" 步骤 1、连接客户端 ./redis-cli 2、输入密码 auth xxxxx #xxxxx 表示密码 3、输入monitor命令 monitor 全部命令如下 [root@localhost src]# ./redis-cli 127.0.0.1:6379> monitor (error) NOAUTH Authentication required. 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> monitor OK 4、客户端连接 可以直接在 redis-cli里输入命令,也可以是其他的客户端,monitor都可以监控到。这里用java为例 项目使用的是spring-boot, 源码可以到我的github或者码云中获取。 jar <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> yml文件 spring: redis: database: 0 host: 10.10.20.100 port: 6379 password: 123456 pool: max-active: 8 max-wait: -1 max-idle: 18 min-idle: 0 timeout: 0 java文件 @Controller @RequestMapping(value = "/test1") public class SysTestController { @Autowired private StringRedisTemplate stringRedisTemplate; @ResponseBody @GetMapping(value = "/redisTest") public void redisTest(){ stringRedisTemplate.opsForValue().set("xxx","dddd"); stringRedisTemplate.opsForSet().add("2","x","c"); } } 5、请求接口 http://localhost:8081/test1/redisTest 监控台可以看到 1507515925.616458 [0 10.10.8.20:56156] "AUTH" "123456" 1507515925.751292 [0 10.10.8.20:56156] "SET" "xxx" "dddd" 1507515925.914660 [0 10.10.8.20:56156] "SADD" "2" "x" "c" 6、注意 redis需要设置密码和bind绑定的ip,在配置文件redis.conf中 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
使用命令 docker stats [OPTIONS] [CONTAINER...] docker stats命令返回一个用于运行容器的实时数据流。要将数据限制到一个或多个特定的容器,可以指定一个由空格分隔的容器名称或ID的列表。您也可以指定一个已停止的容器,但是停止的容器不返回任何数据 显示多个容器的运行信息 docker stats dockername1 dockername2 #容器名之间用空格隔开 扩展参数 OPTIONS 默认 说明 –all, -a false 显示所有容器(默认显示刚刚运行) –format 使用Go模板打印图像 –no-stream false 禁用流统计信息,仅拉取第一个结果 查看所有容器的运行状态 docker stats -a 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
SonarQube 简介 Sonar是一个用于代码质量管理的开源平台,用于管理源代码的质量,可以从七个维度检测代码质量 可以通过插件形式,支持包括java,C#,C/C++,PL/SQL,Cobol,JavaScrip,Groovy等等二十几种编程语言的代码质量管理与检测。 本文记录怎么使用docker安装SonarQube和使用SonarQube检测自己的代码。 1、获取 postgresql 的镜像 $ docker pull postgres 2、启动 postgresql $ docker run --name postgresqldb -e POSTGRES_USER=root -e POSTGRES_PASSWORD=root -d postgres #其中 postgresqldb 为容器名称 POSTGRES_USER POSTGRES_PASSWORD 指定postgresql的用户名密码 3、获取 sonarqube 的镜像 $ docker pull sonarqube 4、启动 sonarqube $ docker run --name sq --link postgresqldb -e SONARQUBE_JDBC_URL=jdbc:postgresql://db:5432/sonar -p 9000:9000 -d sonarqube #其中--link postgresqldb 是指和 postgresqldb 容器连接通讯, 用网关的方式也可以(可以看我另一篇docker多容器设置为一个网络的文章) 至此,平台搭建完毕,接下来就是检测直接的代码了。 5、访问SonarQube 浏览器直接输入 服务器地址和9000端口即可 账户密码都是 admin admin 6、安装jdk和maven 为了编译,这里需要安装jdk和maven 安装方式可以看我的另一篇博客 centos7安装JDK8http://blog.csdn.net/chenjianandiyi/article/details/78069992 centos7 安装mavenhttp://www.jianshu.com/p/ea2554ac6ad4 7、检测maven项目 执行 上图中 copy的代码 或者 mvn sonar:sonar 8、查看检测结果 上图表示 编译成功,接下来就可以在浏览器上看检测的结果了 10、本文检测的项目叫 SLife 是一个使用spring boot、shiro、SiteMesh、Mysql、Redis、Boostrap(inspinia)、Mybatis(Mybatis plus)搭建的一个企业级快速开发项目。可以到我的github或者码云中获取源代码, 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
虽然我已经被Docker腐蚀了,但是有时候还是需要在物理机上安装Java的运行环境的。 今天就简单的记录一下在centos7的操作系统下安装JDK8的过程和注意事项。 1.安装 wget yum -y install wget 2、进入到目录 /usr/local/ 中 cd /usr/local/ 3、创建目录 tools: mkdir -p tools 4 进入到目录 /usr/local/tools 中: cd tools/ 5、下载 jdk-8u91-linux-x64.tar.gz: 下载地址http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 这里用 weget或者其他的方式下载都行,只要拿到压缩包就行 下载放到目录里 /usr/local/tools 6 、加压缩 jdk-8u91-linux-x64.tar.gz 文件: tar -zxvf jdk-8u91-linux-x64.tar.gz 7、打开 /etc/ 目录下的 profile 文件: vi /etc/profile 将如下代码追加到 profile 文件末尾: JAVA_HOME=/usr/local/tools/jdk1.8.0_144 JRE_HOME=$JAVA_HOME/jre PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin CLASSPATH=:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib/dt.jar export JAVA_HOME JRE_HOME PATH CLASSPATH 注意:JAVA_HOME 的版本是你下载的jdk版本,也就是你解压出来的文件夹名称 Esc 键、输入 wq! 回车,保持并退出。 8、即可启用新的配置: source /etc/profile 9、看版本 ok java -version 到此,centos7安装JDK8 完成 我的简书 我的官网
Part 1 - 理论相关 作者 freewolf 关键词 微服务、Spring Cloud、OAuth 2.0、JWT、Spring Security、SSO、UAA 写在前面 作为从业了十多年的IT行业和程序的老司机今天如果你说你不懂微服务都不好意思说自己的做软件的。SOA喊了多年无人不知但又有多少系统开发真正的SOA了呢但是好像一夜之间所有人都投入了微服务的怀抱。 作为目前最主流的“微服务框架”Spring Cloud发展速度很快成为了最全面的微服务解决方案。不管什么软件体系什么框架安全永远是不可能绕开的话题我也把它作为我最近一段时间研究微服务的开篇。 老话题“如何才能在微服务体系中保证安全”为了达成目标这里采用一个简单而可行方式来保护Spring Cloud中服务的安全也就是建立统一的用户授权中心。 这里补充说一下什么是Authentication认证和Authorization鉴权其实很简单认证关心你是谁鉴权关心你能干什么。举个大家一致都再说的例子如果你去机场乘机你持有的护照代表你的身份这是认证你的机票就是你的权限你能干什么。 学习微服务并不是一个简单的探索过程这不得学习很多新的知识其实不管是按照DDDDomain Driven Design领域驱动设计中领域模型的方式还是将微服务拆分成更小的粒度。都会遇到很多新的问题和以前一直都没解决很好的问题。随着不断的思考随着熟悉Facebook/GitHub/AWS这些机构是如何保护内部资源答案也逐渐浮出水面。 为了高效的实现这个目标这里采用OAuth 2和JWT(JSON Web Tokens)技术作为解决方案 为什么使用OAuth 2 尽管微服务在现代软件开发中还算一个新鲜事物但是OAuth 2已经是一个广泛使用的授权技术它让Web开发者在自己提供服务中用一种安全的方式直访问Google/Facebook/GitHub平台用户信息。但在我开始阐述细节之前我将揭开聚焦到本文真正的主题云安全 那么在云服务中对用户访问资源的控制我们一般都怎么做呢然我举一些大家似乎都用过的但又不是很完美的例子。 我们可以设置边界服务器或者带认证功能的反向代理服务器假设所有访问请求都发给它。通过认证后转发给内部相应的服务器。一般在Spring MVC Security开发中几乎都会这样做的。但这并不安全最重要的是一旦是有人从内部攻击你的数据毫无安全性。 其他方式:我们为所有服务建立统一的权限数据库并在每次请求前对用户进行鉴权听起来某些方面的确有点愚蠢但实际上这确实是一个可行的安全方案。 更好的方式: 用户通过授权服务来实现鉴权把用户访问Session映射成一个Token。所有远程访问资源服务器相关的API必须提供Token。然后资源服务器访问授权服务来识别Token得知Token属于哪个用户并了解通过这个Token可以访问什么资源。 这听起来是个不错的方案对不但是如何保证Token的安全传输如何区分是用户访问还是其他服务访问这肯定是我们关心的问题。 所以上述种种问题让我们选择使用OAuth 2其实访问Facebook/Google的敏感数据和访问我们自己后端受保护数据没什么区别并且他们已经使用这样的解决方案很多年我们只要遵循这些方法就好了。 OAuth 2是如何工作的 如果你了解OAuth 2相关的原理那么在部署OAuth 2是非常容易的。 让我们描述下这样一个场景“某App希望获得Tom在Facebook上相关的数据” OAuth 2 在整个流程中有四种角色: 资源拥有者(Resource Owner) - 这里是Tom 资源服务器(Resource Server) - 这里是Facebook 授权服务器(Authorization Server) - 这里当然还是Facebook因为Facebook有相关数据 客户端(Client) - 这里是某App 当Tom试图登录Facebook某App将他重定向到Facebook的授权服务器当Tom登录成功并且许可自己的Email和个人信息被某App获取。这两个资源被定义成一个Scope权限范围一旦准许某App的开发者就可以申请访问权限范围中定义的这两个资源。 +——–+ +—————+ | |–(A)- Authorization Request ->| Resource | | | | Owner | | |<-(B)– Authorization Grant —| | | | +—————+ | | | | +—————+ | |–(C)– Authorization Grant –>| Authorization | | Client | | Server | | |<-(D)—– Access Token ——-| | | | +—————+ | | | | +—————+ | |–(E)—– Access Token ——>| Resource | | | | Server | | |<-(F)— Protected Resource —| | +——–+ +—————+ Tom允许了权限请求再次通过重定向返回某App重定向返回时携带了一个Access Token访问令牌接下来某App就可以通过这个Access Token从Facebook直接获取相关的授权资源也就是Email和个人信息而无需重新做Tom相关的鉴权。而且每当Tom登录了某App都可以通过之前获得的Access Token直接获取相关授权资源。 到目前为止我们如何直接将以上内容用于实际的例子中OAuth 2十分友好并容易部署所有交互都是关于客户端和权限范围的。 OAuth 2中的客户端和权限范围和我们平时的用户和权限是否相同 我需要将授权映射到权限范围中或将用户映射到客户端中 为什么我需要客户端 你也许在之前在类似的企业级开发案例中尝试映射过相关的角色。这会很棘手 任何类型的应用都提供用户登录登录结果是一个Access Token所有的之后的API调用都将这个Access Token加入HTTP请求头中被调用服务去授权服务器验证Access Token并获取该Token可访问的权限信息。这样一来所有服务的访问都会请求另外的服务来完成鉴权。 权限范围和角色客户端和用户 在OAuth 2中你可以定义哪个应用网站、移动客户端、桌面应用、其他可以访问那些资源。这里只有一个尺寸来自哪里的哪个用户可以访问那些数据当然也是哪个应用或者服务可以访问那些资源。换一种说法权限范围就是控制那些端点对客户端可见或者用户根据他的权限来获取相关的数据。 在一个在线商店中前端可以看做一个客户端可以访问商品、订单和客户信息但后端可以关于物流和合同等另一方面用户可以访问一个服务但并不是全部的数据这可以是因为用户正在使用Web应用当他不能的时候其他用户却可以。服务之间的访问时我们要讨论的另一个维度。如果你熟悉数学我可以说在OAuth 2中客户端-权限范围关系是线性独立于用户-权限关系。 为什么是JWT OAuth 2并不关心去哪找Access Token和把它存在什么地方的生成随机字符串并保存Token相关的数据到这些字符串中保存好。通过一个令牌端点其他服务可能会关心这个Token是否有效它可以通过哪些权限。这就是用户信息URL方法授权服务器为了获取用户信息转换为资源服务器。 当我们谈及微服务时我们需要找一个Token存储的方式来保证授权服务器可以被水平扩展尽管这是一个很复杂的任务。所有访问微服务资源的请求都在Http Header中携带Token被访问的服务接下来再去请求授权服务器验证Token的有效性目前这种方式我们需要两次或者更多次的请求但这是为了安全性也没什么其他办法。但扩展Token存储会很大影响我们系统的可扩展性这是我们引入JWT读jot的原因。 +———–+ +————-+ | | 1-Request Authorization | | | |————————————>| | | | grant_type&username&password | |–+ | | |Authorization| | 2-Gen | Client | |Service | | JWT | | 3-Response Authorization | |<-+ | |<————————————| Private Key | | | access_token / refresh_token | | | | token_type / expire_in / jti | | +———–+ +————-+ 简短来说响应一个用户请求时将用户信息和授权范围序列化后放入一个JSON字符串然后使用Base64进行编码最终在授权服务器用私钥对这个字符串进行签名得到一个JSON Web Token我们可以像使用Access Token一样的直接使用它假设其他所有的资源服务器都将持有一个RSA公钥。当资源服务器接收到这个在Http Header中存有Token的请求资源服务器就可以拿到这个Token并验证它是否使用正确的私钥签名是否经过授权服务器签名也就是验签。验签通过反序列化后就拿到OAuth 2的验证信息。 验证服务器返回的信息可以是以下内容 access_token - 访问令牌用于资源访问 refresh_token - 当访问令牌失效使用这个令牌重新获取访问令牌 token_type - 令牌类型这里是Bearer也就是基本HTTP认证 expire_in - 过期时间 jti - JWT ID 由于Access Token是Base64编码反编码后就是下面的格式标准的JWT格式。也就是Header、 Payload、Signature三部分。 { “alg”:”RS256”, “typ”:”JWT” } { “exp”: 1492873315, “user_name”: “reader”, “authorities”: [ “AURH_READ” ], “jti”: “8f2d40eb-0d75-44df-a8cc-8c37320e3548”, “client_id”: “web_app”, “scope”: [ “FOO” ] } &:lƧs)ۡ-[+ F”2”Kآ8ۓٞ:u9ٴ̯ޡ 9Q32Zƌ$ec{3mxJh0DF庖[!뀭N)㥔knVVĖV|夻ׄE㍫}Ŝf9>’<蕱굤Bۋеϵov虀DӨ8C4K}Emޢ YVcaqIW&uʝub!Ť\՟-{ʖX܌WTq 使用JWT可以简单的传输Token用RSA签名保证Token很难被伪造。Access Token字符串中包含用户信息和权限范围我们所需的全部信息都有了所以不需要维护Token存储资源服务器也不必要求Token检查。 +———–+ +———–+ | | 1-Request Resource | | | |———————————–>| | | | Authorization: bearer Access Token | |–+ | | | Resource | | 2-Verify | Client | | Service | | Token | | 3-Response Resource | |<-+ | |<———————————–| Public Key| | | | | +———–+ +———–+ 所以在微服务中使用OAuth 2不会影响到整体架构的可扩展性。淡然这里还有一些问题没有涉及例如Access Token过期后使用Refresh Token到认证服务器重新获取Access Token等后面会有具体的例子来展开讨论这些问题。 点击获取优惠券 点我获取阿里云优惠券 我的官网 我的官网http://guan2ye.com 我的CSDN地址http://blog.csdn.net/chenjianandiyi 我的简书地址http://www.jianshu.com/u/9b5d1921ce34 我的githubhttps://github.com/javanan 我的码云地址https://gitee.com/jamen/ 阿里云优惠券https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld 阿里云教程系列网站http://aliyun.guan2ye.com 我的开源项目spring boot 搭建的一个企业级快速开发脚手架
是什么(是一个软件) MHA(Master High Availability)是MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中,MHA能做到在0~30秒之内自动完成数据库的故障切换操作,并且在进行故障切换的过程中,MHA能在最大程度上保证数据的一致性,以达到真正意义上的高可用。—也就是一个软件 软件结构 两部分组成 MHA Manager(管理节点)和MHA Node(数据节点) MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。 MHA Node运行在每台MySQL服务器上。 MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master。 MHA可以与半同步复制结合起来,提高数据复制的安全性。
是什么 干嘛用 怎么用
在Eureka中, 使用docker 部署高可用的时候,就会出现 容器之间 循环依赖 解决方案很多, 这里使用 网络桥接设置 compose的方式如下 version: "2" services: peer1: # 默认情况下,其他服务可以使用服务名称连接到该服务。因此,对于peer2的节点,它需要连接http://peer1:8761/eureka/,因此需要配置该服务的名称是peer1。 image: itmuch/microservice-discovery-eureka-ha:0.0.1-SNAPSHOT networks: - eureka-net ports: - "8761:8761" environment: - spring.profiles.active=peer1 peer2: image: itmuch/microservice-discovery-eureka-ha:0.0.1-SNAPSHOT hostname: peer2 networks: - eureka-net ports: - "8762:8762" environment: - spring.profiles.active=peer2 networks: eureka-net: driver: bridge 如果不想使用compose 可用这样 1 、创建一个桥接网络 $ docker network create -d bridge --subnet 172.25.0.0/16 isolated_nw 06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8 2、把容器加入网络,重启下容器 $ docker network connect isolated_nw container2 $ docker network inspect isolated_nw [ { "Name": "isolated_nw", "Id": "06a62f1c73c4e3107c0f555b7a5f163309827bfbbf999840166065a8f35455a8", "Scope": "local", "Driver": "bridge", "IPAM": { "Driver": "default", "Config": [ { "Subnet": "172.25.0.0/16", "Gateway": "172.25.0.1/16" } ] }, "Containers": { "90e1f3ec71caf82ae776a827e0712a68a110a3f175954e5bd4222fd142ac9428": { "Name": "container2", "EndpointID": "11cedac1810e864d6b1589d92da12af66203879ab89f4ccd8c8fdaa9b1c48b1d", "MacAddress": "02:42:ac:19:00:02", "IPv4Address": "172.25.0.2/16", "IPv6Address": "" } }, "Options": {} }] 3、或者run的时候设置网络 $ docker run --network=isolated_nw --ip=172.25.3.3 -itd --name=container3 busybox 467a7863c3f0277ef8e661b38427737f28099b61fa55622d6c30fb288d88c551
是什么 干嘛用 怎么用 还没整理完, 只能给点小福利大家 阿里云的优惠券 点我获取阿里云优惠券
是什么 干嘛用 怎么用 引入jar <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> 配置文件boostrapt.yml 这里写代码片 application入口类 @EnableZuulProxy @SpringBootApplication @EnableEurekaClient @EnableZuulProxy public class ZuulServiceApplication { public static void main(String[] args) { SpringApplication.run(ZuulServiceApplication.class, args); } }
是什么 干嘛用 怎么用 加依赖 <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies> Application 入口类 @SpringBootApplication @EnableEurekaServer public class EurekaServiceApplication { public static void main(String[] args) { SpringApplication.run(EurekaServiceApplication.class, args); } } yml配置文件 server: port: 7004 eureka: instance: hostname: localhost client: serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 访问浏览器,发现ok了, 这里没有配置忽略自己成为服务,启动会报错, 配置高可用 host文件 127.0.0.1 x1 127.0.0.1 x2 127.0.0.1 x3 配置中心 partition: host1: name: x1 port: 7003 host2: name: x2 port: 7004 host3: name: x3 port: 7005 host4: name: localhost port: 7006 # eureka配置文件 x3(eureka记得配置文件的名字改为bootstrap.yml) server: port: ${partition.host3.port} eureka: instance: hostname: ${partition.host3.name} client: serviceUrl: defaultZone: http://${partition.host1.name}:${partition.host1.port}/eureka/,http://${partition.host2.name}:${partition.host2.port}/eureka/ spring: cloud: config: allow-override: false #label: master profile: dev uri: http://10.10.8.101:7000 application: name: eureka-service3 eureka配置文件 x2(eureka记得配置文件的名字改为bootstrap.yml) server: port: ${partition.host2.port} eureka: instance: hostname: ${partition.host2.name} client: serviceUrl: defaultZone: http://${partition.host1.name}:${partition.host1.port}/eureka/,http://${partition.host3.name}:${partition.host3.port}/eureka/ spring: cloud: config: allow-override: false #label: master profile: dev uri: http://10.10.8.101:7000 application: name: eureka-service2 eureka配置文件 x1(eureka记得配置文件的名字改为bootstrap.yml) server: port: ${partition.host1.port} eureka: instance: hostname: ${partition.host1.name} client: serviceUrl: defaultZone: http://${partition.host2.name}:${partition.host2.port}/eureka/,http://${partition.host3.name}:${partition.host3.port}/eureka/ spring: cloud: config: allow-override: false #label: master profile: dev uri: http://10.10.8.101:7000 application: name: eureka-service1 三台eureka 实现俩俩互联。 客户端的使用 引入jar <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> 配置文件 eureka: client: service-url: defaultZone: http://x1:7003/eureka/,http://x2:7004/eureka/,http://x3:7005/eureka/ Application 入口类加入 @EnableEurekaClient @SpringBootApplication @EnableEurekaClient @EnableZuulProxy public class ZuulServiceApplication { public static void main(String[] args) { SpringApplication.run(ZuulServiceApplication.class, args); } }
是什么 Spring Cloud Config为分布式系统中的外部化配置提供服务器和客户端支持。使用Config Server,您可以集中管理所有环境中应用程序的外部属性 干嘛用 统一管理各个微服务的配置文件 怎么用 引入jar <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies> 在程序的入口Application类加上@EnableConfigServer注解开启配置服务器。 @SpringBootApplication @EnableConfigServer public class ConfigServiceApplication { public static void main(String[] args) { SpringApplication.run(ConfigServiceApplication.class, args); } } application.yml 配置 server: port: 7000 spring: application: name: config-servie cloud: config: server: git: uri: https://git.oschina.net/jamen/blife-config-centor.git search-paths: blife-config-centor 在git中放 yml文件 config-test-dev.yml spring: application: name: config-test 启动config-service 请求 http://10.10.8.101:7000/config-test/dev 返回 {"name":"config-test","profiles":["dev"],"label":null,"version":null,"state":null,"propertySources":[{"name":"https://git.oschina.net/jamen/blife-config-centor.git/config-test-dev.yml","source":{"spring.application.name":"config-test"}}]} Dockerfile FROM frolvlad/alpine-oraclejdk8:slim VOLUME /tmp ADD config-service-1.0-SNAPSHOT.jar app.jar RUN sh -c 'touch /app.jar' ENV JAVA_OPTS="" ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ] EXPOSE 7000 config 客户端的使用 引入jar <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> boostrapt.yml spring: cloud: config: allow-override: false #label: master profile: dev uri: http://10.10.8.101:7000 #配置中心地址
2019年09月
1、现在出现的问题是怕服务器上存储不够用 答:你需要评估一下数据量,理论上单表300w的数据是没有问题的。存储的是问题,只要磁盘够大就行了。
2、如果在云服务器上自建数据库会不会影响网站的运行速度 答:会影响,毕竟数据库服务需要消耗cpu。
3、但是云数据库有点小贵 答:不贵,舍不得孩子,找不到老婆。 另外 云数据库帮你做了很多运维,实际成本比自建便宜。云数据库帮你做数据备份、数据安全、监控。 另外你可以使用一个全新的账户,没有实名认证过的支付宝或者身份证,购买,能获取2000代金券:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=vf2b5zld
4、我们也考虑了使用oss? 答:先要自己oss是干吗用的,oss是对象存储,非常适合你用来存储商品图,图片不应该存储在数据库里。
使用 阿里云优惠券试试,能便宜一些https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=vf2b5zld&utm_source=vf2b5zld