MacOS 垃圾清理
GarageBand,这个是系统上的模拟乐器,一般都使用不到
1 | rm -rf /Library/Application\ Support/GarageBand |
但是有些系统文件显示占用的空间很大,该怎么看呢
1 | du -sh * |
这个命令用来查看根目录下,所有文件的大小分布
比如,我的电脑 Library 文件路径最大
那就在进入 Library 文件路径,再执行 du -sh *
直至找到占用内存最大的文件,然后结合实际情况,进行删减
GarageBand,这个是系统上的模拟乐器,一般都使用不到
1 | rm -rf /Library/Application\ Support/GarageBand |
但是有些系统文件显示占用的空间很大,该怎么看呢
1 | du -sh * |
这个命令用来查看根目录下,所有文件的大小分布
比如,我的电脑 Library 文件路径最大
那就在进入 Library 文件路径,再执行 du -sh *
直至找到占用内存最大的文件,然后结合实际情况,进行删减
在 SpringSecurity 中,我想配置一个关于session并发的控制,于是我是这样配置的
1 | @Override |
上下文的配置我在此省略了
这里设置 maximumSessions 为 -1,表示不限制同一账号登录的客户端数
session过期后执行的逻辑是进入我自定义的类 expiredSessionStrategy() 中
因为我是构建的 rest 服务,所以我是返回的 http 状态码
1 | public class ExpiredSessionStrategyImpl implements SessionInformationExpiredStrategy { |
在这里,问题就来了
我测试的时候,把 -1 改成了 1,之后登录同一个用户,后面登录的用户会把前面一个已经登录的用户挤下线,就是说之前登录的那个用户的session 会过期
就是说他所在的页面再发送任何请求的话会收到我返回的 405 状态码
在这里是没问题的
问题就在发完一个请求后,在发一个请求,在浏览器的 network 上会看到发出的请求会被重定向的 /login 请求上
后续再发任何请求都会被重定向到 /login 上
为什么会出现这样的情况呢?
为什么会第一个请求会收到405的状态码,后续的请求会被重定向到 /login 呢?
通过 debug 断点,我定位到过滤器的前置执行方法 beforeInvocation() 上
1 | protected InterceptorStatusToken beforeInvocation(Object object) { |
问题出在了 SecurityContextHolder.getContext().getAuthentication() == null
getAuthentication() 为 null,于是进入了credentialsNotFound(),抛出了 AuthenticationCredentialsNotFoundException 异常
确实,在控制台上也能看到抛出的异常信息
AuthenticationCredentialsNotFoundException 是 AuthenticationException 异常的子类
不仅仅是 AuthenticationCredentialsNotFoundException 还有其他很多异常都是异常的子类
既然抛出了异常,猜测肯定是被某个处理器给处理了而且处理的默认机制是重定向到 /login
于是继续搜索 SpringSecurity 异常处理器
我找到的答案是 ExceptionTranslationFilter
ExceptionTranslationFilter 是Spring Security的核心filter之一,用来处理AuthenticationException和AccessDeniedException两种异常(由FilterSecurityInterceptor认证请求返回的异常)
ExceptionTranslationFilter 对异常的处理是通过这两个处理类实现的,处理规则很简单:
规则1. 如果异常是 AuthenticationException,使用 AuthenticationEntryPoint 处理
规则2. 如果异常是 AccessDeniedException 且用户是匿名用户,使用 AuthenticationEntryPoint 处理
规则3. 如果异常是 AccessDeniedException 且用户不是匿名用户,如果否则交给 AccessDeniedHandler 处理。
1 | private void handleSpringSecurityException(HttpServletRequest request, |
我们这里的异常是 AuthenticationException ,紧接着就找 sendStartAuthentication() 方法
1 | protected void sendStartAuthentication(HttpServletRequest request, |
上面的方法是先保存请求,之后执行 authenticationEntryPoint.commence(request, response, reason), 再深入来看
默认实现 commence 接口的是 LoginUrlAuthenticationEntryPoint 类
1 | public void commence(HttpServletRequest request, HttpServletResponse response, |
我们看到了 redirectUrl = buildRedirectUrlToLoginPage(request, response, authException)
这下总算是知道了为什么会重定向了 /login 请求了
知道问题的原因了,解决问题就很简单了,重新实现 commence 接口,返回http 状态码就可以了,于是加上这样的配置
1 | @Override |
1 | public class UnauthenticatedEntryPoint implements AuthenticationEntryPoint { |
再次重试,发现会返回 405状态码了,不会在重定向到 /login 了
问题解决
1 | <dependency> |
1 | @EqualsAndHashCode(callSuper = true) |
1 | public class UserService implements UserDetailsService { |
1 | @Data |
1 | public class AuthorizationFilter extends AbstractSecurityInterceptor implements Filter { |
1 | public class AuthorizationAccessDecisionManager implements AccessDecisionManager { |
1 | public class AuthorizationMetadataSource implements FilterInvocationSecurityMetadataSource { |
1 | public class SecurityUtils { |
主要的实现类都列举在内了,还有一些成功和失败的处理类,再次没有列举出来
因为该项目为构建纯restful风格的后台项目,这些成功或失败的处理类基本都是返回的http状态码
首先需要说明一点 ,和 Example 使用相同的还有 Condition 类 该类继承自 Example,使用方法和 Example 完全一样,只是为了避免语义有歧义重命名的一个类,这里我们都用 Example 来说明
1 | Example example = new Example(XXX.class); |
其中构造方法为生成的 model 实体类,还有 2 个构造方法
1 |
|
然后可以对 example 的实体类的单表进行查询了
1 | Example example = new Example(XXX.class); |
以上查询的条件是,查询 id 大于 100 并且小于 151 或者 id 小于 41 的记录
还可以写成 sql 的方式:
1 | Example example = new Example(XXX.class); |
andCondition() 有2中使用方法:
andCondition(String condition) : 手写条件,例如 “length(name)<5”
andCondition(String condition, Object value) : 手写左边条件,右边用value值,例如 “length(name)=” “5”
orCondition() 也是类似的
example 里有很多 mysql 常用的方法,使用方法和 elasticsearch 的 java api 很类似,这里列举几个
Set<String> selectColumns
: 查询的字段Set<String> excludeColumns
: 排除的查询字段Map<String, EntityColumn> propertyMap
: 属性和列对应还有一些一看就知道意思的
上面是以 and 条件举例 ,or的条件也是一样的
我们知道 PageHelper.startPage(pageNum, pageSize); 可以对 后面的一个 select 进行分页
那么我们可以对 example 进行一个分页查询的封装
1 |
|
SpringBoot 默认有2种打包方式,一种是直接打成 jar 包,直接使用 java -jar 跑起来,另一种是打成 war 包,移除掉 web starter 里的容器依赖,然后丢到外部容器跑起来。
第一种方式的缺点是整个项目作为一个 jar,部署到生产环境中一旦有配置文件需要修改,则过程比较麻烦
linux 下可以使用 vim jar 包,找到配置文件修改后再保存
window 下需要使用 解压缩软件打开 jar 再找到配置文件,修改后替换更新
第二种方式的缺点是需要依赖外部容器,这无非多引入了一部分,很多时候我们很不情愿这么做
spring boot 项目启动时 指定配置有2种方式:一种是启动时修改配置参数,像 java -jar xxxx.jar –server.port=8081 这样;另外一种是 指定外部配置文件加载,像 java -jar xxxx.jar -Dspring.config.location=applixxx.yml这样
我们希望打包成 tomcat 或者 maven 那样的软件包结构,即
--- bin
--- start.sh
--- stop.sh
--- restart.sh
--- start.bat
--- stop.bat
--- restart.bat
--- boot
--- xxxx.jar
--- lib
--- conf
--- logs
--- README.md
--- LICENSE
就像这样
bin
目录放一些我们程序的启动停止脚本boot
目录放我们自己的程序包lib
目录是我们程序的依赖包conf
目录是项目的配置文件logs
目录是程序运行时的日志文件README.md
使用说明LICENSE
许可说明1 | <build> |
下面重要的是 assembly.xml 配置文件了,这个文件才是把我们的程序打成标准的目录结构
1 | <assembly> |
上次自己写这篇文章 已经是今年初了,一年过去了, Spring Boot 项目在不停的更新着,与此同时其他的 stater项目也在不停的更新着,今天就来重新整合下Druid,MyBatis,通用 Mapper,PageHelper,打算在企业级项目中使用
当前 SpringBoot 最新的发布版是 1.5.9.RELEASE
昨天还是 1.5.8,今天发现就是1.5.9.RELEASE了
本篇文章搭建的脚手架就是基于 1.5.9.RELEASE
年初我自己搭建这个脚手架使用的时候,那时 Druid,MyBatis,Mapper,PageHelper,这几个开源项目都没有集成 SpringBoot,我自己还是使用 JavaConfig 配置的
现在不一样了,一年过去了,这些项目的作者也开发了对 SpringBoot 支持的 starter 版本
本篇文章就来整合这些开源框架制作一个脚手架
另外是有打算将它应用到企业级项目中的
SpringBoot: 1.5.9.RELEASE
SpringBoot-mybatis : 1.3.1
mapper-spring-boot-starter : 1.1.5
pagehelper-spring-boot-starter: 1.2.3
druid-spring-boot-starter: 1.1.5
1 | <?xml version="1.0" encoding="UTF-8"?> |
说明:
pom 中除了应用了必要的依赖,还引入了SpringSecurity, 打算做脚手架的安全认证
caffeine cache 打算做 restapi 的 cache 的
使用 devtools 开发时热部署
lombok 简化代码配置
websocket 全双工通信
集群的话 spring-session 做到 session 共享
1 | spring: |
正常配置了druid 的连接配置,其中使用 ConfigTool 的密码加密功能,提供加密后的密文个公钥,在连接数据库时会自动解密
mybatis 配置了各个 model 的位置,配置开启驼峰命名转换,SQL 语句的打印使用的 springboot 的日志功能,将实现的 StdOutImpl给注释了
配置的分页插件 pagehelper 参数
最后在@ SpringBoot 注解下加入@MapperScan(basePackages = "com.ahtsoft.**.mapper")
用来扫描 mapper自动注入为 bean
在数组末尾添加一项,并返回数组的长度, 可以添加任意类型的值作为数组的一项。
1 | var arr = [1,2]; |
1 | var arr = [1,2]; |
删除最后一项,并返回删除元素的值;如果数组为空则返回undefine。对数组本身操作
1 | var arr = [1,2,3,4,5]; |
1 | var arr = [1,2,3,4,5]; |
不改变原始数组,得到新的数组
slice(start,end)
1 | var arr = [1,2,3,4,5]; |
改变原数组,可以实现shift前删除,pop后删除,unshift前增加,同push后增加一样的效果。索引从0开始
splice(index,howmany,item1,…..,itemX)
1 | var arr = [1,2,3,4,5]; |
合并后得到新数组,原始数组不改变
1 | var arr1 = [1,2]; |
并返回元素索引,不存在返回-1,索引从0开始
1 | var arr = ['a','b','c','d','e']; |
1 | var a, b; |
并返回翻转后的原数组,原数组翻转了
1 | var a = [1,2,3,4,5]; |
1 | unique(arr){ |
遍历数组,无return
callback的参数:
value –当前索引的值
index –索引
array –原数组
映射数组(遍历数组),有return 返回一个新数组
callback的参数:
value –当前索引的值
index –索引
array –原数组
注意: arr.forEach()和arr.map()的区别
过滤数组,返回一个满足要求的数组
依据判断条件,数组的元素是否全满足,若满足则返回ture
1 | let arr = [1,2,3,4,5] |
依据判断条件,数组的元素是否有一个满足,若有一个满足则返回ture
1 | let arr = [1,2,3,4,5] |
迭代数组的所有项,累加器,数组中的每个值(从左到右)合并,最终计算为一个值
参数:
callback:
previousValue 必选 –上一次调用回调返回的值,或者是提供的初始值(initialValue)
currentValue 必选 –数组中当前被处理的数组项
index 可选 –当前数组项在数组中的索引值
array 可选 –原数组
initialValue: 可选 –初始值
实行方法:回调函数第一次执行时,preValue 和 curValue 可以是一个值,如果 initialValue 在调用 reduce() 时被提供,那么第一个 preValue 等于 initialValue ,并且curValue 等于数组中的第一个值;如果initialValue 未被提供,那么preValue 等于数组中的第一个值.
1 | let arr = [0,1,2,3,4] |
find的参数为回调函数,回调函数可以接收3个参数,值x、所以i、数组arr,回调函数默认返回值x。
1 | let arr=[1,2,234,'sdf',-2]; |
findIndex和find差不多,不过默认返回的是索引。
includes函数与string的includes一样,接收2参数,查询的项以及查询起始位置。
1 | let arr=[1,2,234,'sdf',-2]; |
keys,对数组索引的遍历
1 | let arr=[1,2,234,'sdf',-2]; |
values, 对数组项的遍历
1 | let arr=[1,2,234,'sdf',-2]; |
entries,对数组键值对的遍历。
1 | let arr=['w','b']; |
fill方法改变原数组,当第三个参数大于数组长度时候,以最后一位为结束位置。
1 | let arr=['w','b']; |
Array.of()方法永远返回一个数组,参数不分类型,只分数量,数量为0返回空数组。
1 | Array.of('w','i','r')//["w", "i", "r"]返回数组 |
copyWithin方法接收三个参数,被替换数据的开始处、替换块的开始处、替换块的结束处(不包括);copyWithin(s,m,n).
1 | ["w", "i", "r"].copyWithin(0)//此时数组不变 |
Array.from可以把带有lenght属性类似数组的对象转换为数组,也可以把字符串等可以遍历的对象转换为数组,它接收2个参数,转换对象与回调函数
1 | Array.from({'0':'w','1':'b',length:2})//["w", "b"],返回数组的长度取决于对象中的length,故此项必须有! |
如果方法没有使用参数,那么将按照字母顺序对数组元素进行排序
1 | var arr = [ |
从前往后遍历,返回item在数组中的索引位,如果没有返回-1;通常用来判断数组中有没有某个元素。可以接收两个参数,第一个参数是要查找的项,第二个参数是查找起点位置的索引
与indexOf一样,区别是从后往前找。
数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维数组。该方法返回一个新数组,对原数据没有影响
flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1
1 | [1, 2, [3, 4]].flat() |
flatMap()方法对原数组的每个成员执行一个函数,相当于执行Array.prototype.map(),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。
1 | // 相当于 [[2, 4], [3, 6], [4, 8]].flat() |
发现一个比较好的js组件,地址: https://www.lodashjs.com/ 里面有很多关于对数组的操作
首先要说明的是,本篇文章用的 Spark 的版本都是目前最新版,直接在官网上下载就可以了,有注意的,下面详细说
有些命令可能已经不适应之前的旧版本了,以最新的版的为准
以下操作命令均是在服务的根目录下,使用的是相对目录
安装 jdk 环境,再此不做详细叙述了,需要注意的是 jdk 的环境变量的配置
安装 Hadoop 环境,必须安装 Hadoop 才能使用 Spark,但如果使用 Spark 过程中没用到 HDFS,不启动 Hadoop 也是可以的
打开官网下载的地址: http://spark.apache.org/downloads.html
需要注意的是,在选择下载包类型 Choose a package type
这个需要根据安装的 Hadoop 的版本来定的,或者直接选择 Pre-build with user-provided Apache Hadoop
这样我们可以自己配置 Hadoop 的版本
下载后,解压
进入 conf目录拷贝一份配置文件
1 | cp ./conf/spark-env.sh.template ./conf/spark-env.sh |
加入环境变量
1 | export SPARK_DIST_CLASSPATH=$(/home/hadoop-2.8.2/bin/hadoop classpath) |
我们运行
1 | # ./sbin/start-all.sh |
Spark 便会运行起来,查看地址 : http://localhost:8080 可查看到集群情况
正如前面的 Hadoop 一样, Spark 自带有很多示例程序,目录在 ./example 下面,有 Java 的 Python,Scala ,R 语言的,
这里我们选个最熟悉的 Java 版的来跑下
我们找到 Java 的目录里也能看到里面有很多程序,能看到我们熟悉的 wordcount
这里我们跑个 计算π的值
1 | # ./bin/run-example SparkPi |
运行后控制台打印很多信息,但是能看到这么一行:
Pi is roughly 3.1432557162785812
这就可以了
RDD : Spark 的分布式的元素集合(distributed collection of items),称为RDD(Resilient Distributed Dataset,弹性分布式数据集),它可被分发到集群各个节点上,进行并行操作。RDDs 可以通过 Hadoop InputFormats 创建(如 HDFS),或者从其他 RDDs 转化而来
我就简单的理解为 类比 Hadoop 的 MapReduce
RDDs 支持两种类型的操作
Spark-shell 支持 Scala 和 Python 2中语言,这里我们就用 Scala 来做,关于 Scala 的使用和语法我打算新写一篇文章来记录下,
在之前我也写过 在 maven 中集成使用 Scala 来编程,这里我先用下
执行 shell
1 | # ./bin/spark-shell |
来执行一个文本统计
1 | scala> val textFile = sc.textFile("file:///home/hadoop-2.8.2/input/test.txt").count() |
默认读取的文件是 Hadoop HDFS 上的,上面的示例是从本地文件读取
来一个从 HDFS 上读取的,在这里我们之前在 HDFS 上传了个 tets.txt 的文档,在这里就可以直接使用了
1 | scala> val textFile = sc.textFile("test2.txt");textFile.count() |
可以看到结果是一样的
Spark SQL 是 Spark 内嵌的模块,用于结构化数据。在 Spark 程序中可以使用 SQL 查询语句或 DataFrame API。DataFrames 和 SQL 提供了通用的方式来连接多种数据源,支持 Hive、Avro、Parquet、ORC、JSON、和 JDBC,并且可以在多种数据源之间执行 join 操作。
下面仍在 Spark shell 中演示一下 Spark SQL 的基本操作,该部分内容主要参考了 Spark SQL、DataFrames 和 Datasets 指南。
Spark SQL 的功能是通过 SQLContext 类来使用的,而创建 SQLContext 是通过 SparkContext 创建的。
1 | scala> var df = spark.read.json("file:///home/spark-2.2.0-bin-without-hadoop/examples/src/main/resources/employees.json") |
再来执行2条查询语句df.select("name").show()
df.filter(df("salary")>=4000).show()
1 | scala> df.select("name").show() |
执行一条 sql 语句试试
1 | scala> df.registerTempTable("employees") |
其实还有很多功能呢, http://spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.DataFrame ,这里先写2个试试,后续再细节学习
这篇文章暂时先写到这,还有后续的 Spark Streaming ,想先学学看流式计算Storm,之后对比下看看写一篇文章
接下来,熟悉 Scala 语法写一个 JavaScala 应用程序来通过 SparkAPI 单独部署一下试试
这篇文章写下来等于将当时搭建 Spark 环境重复了一遍, 也是一遍敲命令,一遍记录下来,温故而知新,自己也学到不少东西,棒棒哒💯