JoyLau's Blog

JoyLau 的技术学习与思考

Redis-Benchmark

说明

  • redis默认提供了性能测试的工具
  • 在linux下文件是redis-benchmark
  • 在windows下文件是redis-benchmark.exe

参数查看

  • redis-benchmark -h
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-
    k <boolean>]

    -h <hostname> Server hostname (default 127.0.0.1)
    -p <port> Server port (default 6379)
    -s <socket> Server socket (overrides host and port)
    -a <password> Password for Redis Auth
    -c <clients> Number of parallel connections (default 50)
    -n <requests> Total number of requests (default 100000)
    -d <size> Data size of SET/GET value in bytes (default 2)
    -dbnum <db> SELECT the specified db number (default 0)
    -k <boolean> 1=keep alive 0=reconnect (default 1)
    -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD
    Using this option the benchmark will expand the string __rand_int__
    inside an argument with a 12 digits number in the specified range
    from 0 to keyspacelen-1. The substitution changes every time a command
    is executed. Default tests use this to hit random keys in the
    specified range.
    -P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).
    -q Quiet. Just show query/sec values
    --csv Output in CSV format
    -l Loop. Run the tests forever
    -t <tests> Only run the comma separated list of tests. The test
    names are the same as the ones produced as output.
    -I Idle mode. Just open N idle connections and wait.

    Examples:

    Run the benchmark with the default configuration against 127.0.0.1:6379:
    $ redis-benchmark

    Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:
    $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20

    Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:
    $ redis-benchmark -t set -n 1000000 -r 100000000

    Benchmark 127.0.0.1:6379 for a few commands producing CSV output:
    $ redis-benchmark -t ping,set,get -n 100000 --csv

    Benchmark a specific command line:
    $ redis-benchmark -r 10000 -n 10000 eval 'return redis.call("ping")' 0

    Fill a list with 10000 random elements:
    $ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__

    On user specified command lines __rand_int__ is replaced with a random integer
    with a range of values selected by the -r option.

开始测试

  • 搞清了参数的含义,可以进行测试了
  • 本次配置为Redis的默认配置,默认的配置项已经有足够好的性能表现了,不需要调优
  • redis-benchmark -h joylau.cn -p 6379 -a XXX -t get,set -n 1000 -c 400 -q
    我是模仿了我自己现在公司的业务需求,测试了我直接服务器上的Redis,向redis服务器发送1000个请求,每个请求附带400个并发客户端,以静默显示
    redis-joylau-test-q
    可以看到,set操作每秒处理17241次,get操作每秒处理17543次
  • redis-benchmark -h joylau.cn -p 6379 -a XXX -t get,set -n 1000 -c 400
    同上,以标准格式显示
    redis-joylau-test
    可以看到,set操作每秒处理17857次,get操作每秒处理18518次
  • 我自己也开了本地的服务器做测试,每秒操作次数可达100000次

一些参数说明

  • -t : 可以选择你需要运行的测试用例
  • -r : 设置随机数来SET/GET/INCR
  • -P : 一次性执行多条命令,记得在多条命令需要处理时候使用 pipelining。

陷阱和错误的认识

第一点是显而易见的:基准测试的黄金准则是使用相同的标准。 用相同的任务量测试不同版本的 Redis,或者用相同的参数测试测试不同版本 Redis。 如果把 Redis 和其他工具测试,那就需要小心功能细节差异。

  • Redis 是一个服务器:所有的命令都包含网络或 IPC 消耗。这意味着和它和 SQLite, Berkeley DB, Tokyo/Kyoto Cabinet 等比较起来无意义, 因为大部分的消耗都在网络协议上面。
  • Redis 的大部分常用命令都有确认返回。有些数据存储系统则没有(比如 MongoDB 的写操作没有返回确认)。把 Redis 和其他单向调用命令存储系统比较意义不大。
    简单的循环操作 Redis 其实不是对 Redis 进行基准测试,而是测试你的网络(或者 IPC)延迟。想要真正测试 Redis,需要使用多个连接(比如 redis-benchmark), 或者使用 pipelining 来聚合多个命令,另外还可以采用多线程或多进程。
  • Redis 是一个内存数据库,同时提供一些可选的持久化功能。 如果你想和一个持久化服务器(MySQL, PostgreSQL 等等) 对比的话, 那你需要考虑启用 AOF 和适当的 fsync 策略。
  • Redis 是单线程服务。它并没有设计为多 CPU 进行优化。如果想要从多核获取好处, 那就考虑启用多个实例吧。将单实例 Redis 和多线程数据库对比是不公平的。

影响 Redis 性能的因素

有几个因素直接决定 Redis 的性能。它们能够改变基准测试的结果, 所以我们必须注意到它们。一般情况下,Redis 默认参数已经可以提供足够的性能, 不需要调优。

  • 网络带宽和延迟通常是最大短板。建议在基准测试之前使用 ping 来检查服务端到客户端的延迟。根据带宽,可以计算出最大吞吐量。 比如将 4 KB 的字符串塞入 Redis,吞吐量是 100000 q/s,那么实际需要 3.2 Gbits/s 的带宽,所以需要 10 GBits/s 网络连接, 1 Gbits/s 是不够的。 在很多线上服务中,Redis 吞吐会先被网络带宽限制住,而不是 CPU。 为了达到高吞吐量突破 TCP/IP 限制,最后采用 10 Gbits/s 的网卡, 或者多个 1 Gbits/s 网卡。
  • CPU 是另外一个重要的影响因素,由于是单线程模型,Redis 更喜欢大缓存快速 CPU, 而不是多核。这种场景下面,比较推荐 Intel CPU。AMD CPU 可能只有 Intel CPU 的一半性能(通过对 Nehalem EP/Westmere EP/Sandy 平台的对比)。 当其他条件相当时候,CPU 就成了 redis-benchmark 的限制因素。
  • 在小对象存取时候,内存速度和带宽看上去不是很重要,但是对大对象(> 10 KB), 它就变得重要起来。不过通常情况下面,倒不至于为了优化 Redis 而购买更高性能的内存模块。
  • Redis 在 VM 上会变慢。虚拟化对普通操作会有额外的消耗,Redis 对系统调用和网络终端不会有太多的 overhead。建议把 Redis 运行在物理机器上, 特别是当你很在意延迟时候。在最先进的虚拟化设备(VMWare)上面,redis-benchmark 的测试结果比物理机器上慢了一倍,很多 CPU 时间被消费在系统调用和中断上面。
  • 如果服务器和客户端都运行在同一个机器上面,那么 TCP/IP loopback 和 unix domain sockets 都可以使用。对 Linux 来说,使用 unix socket 可以比 TCP/IP loopback 快 50%。 默认 redis-benchmark 是使用 TCP/IP loopback。 当大量使用 pipelining 时候,unix domain sockets 的优势就不那么明显了。
  • 当大量使用 pipelining 时候,unix domain sockets 的优势就不那么明显了。
  • 当使用网络连接时,并且以太网网数据包在 1500 bytes 以下时, 将多条命令包装成 pipelining 可以大大提高效率。事实上,处理 10 bytes,100 bytes, 1000 bytes 的请求时候,吞吐量是差不多的

我想说

  • 我分别测试我之前在腾讯云上的Redis服务器 (Windows Server 2008R2) 和现在在阿里云上的服务器 (Linux CentOS 7.2) 及 局域网下同事的Redis服务器,和本机Redis的服务器
    速度最快的本机服务器,其次是同事的服务器,再次是阿里云上的服务器,最后是腾讯云上的服务器
  • 测试差异之大除了在硬件上的差别外,最客观的因素在网络带宽上,我自己2个位于云上的服务器都是1M的带宽,如此测试,正如上面所说,远远不达不到数据传输所需要的带宽值

最后

Docker

  • 安装: yum install docker
  • 卸载: yum remove docker
  • 启动: systemctl start docker
  • 开机自启: systemctl enable docker

Dockerfile

1
2
3
4
5
FROM java:8
MAINTAINER joylau
ADD joyalu-0.0.1-SNAPSHOT.jar joylau.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/joylau.jar"]

镜像

  • 编译镜像: docker build –t joylau/docker .
  • 查看镜像: docker images
  • 删除镜像: docker rmi name/id

容器

  • 运行: docker run –d --name joylau –p 8080:8080 joylau/docker
  • 停止容器: docker stop id/name
  • 查看运行中的容器 : docker ps
  • 查看所有容器: docker ps -a
  • 删除容器: docker rm id/name

2018-07-05 16:05:00 更新

拉取docker镜像

docker pull image_name
查看宿主机上的镜像,Docker镜像保存在/var/lib/docker目录下:

docker images

删除镜像

docker rmi docker.io/tomcat:7.0.77-jre7 或者 docker rmi b39c68b7af30
查看当前有哪些容器正在运行

docker ps
查看所有容器

docker ps -a
启动、停止、重启容器命令:

docker start container_name/container_id
docker stop container_name/container_id
docker restart container_name/container_id
后台启动一个容器后,如果想进入到这个容器,可以使用attach命令:

docker attach container_name/container_id
删除容器的命令:

docker rm container_name/container_id
删除所有停止的容器:

docker rm $(docker ps -a -q)
查看当前系统Docker信息

docker info
从Docker hub上下载某个镜像:

docker pull centos:latest
docker pull centos:latest
查找Docker Hub上的nginx镜像

docker search nginx
执行docker pull centos会将Centos这个仓库下面的所有镜像下载到本地repository。

2018-07-09 14:02:25 更新

docker search xxx : 在docker仓库查找镜像
docker images | grep xxx : 在本地仓库查找镜像

2018-07-12 09:44:40 更新

进入容器: docker exec -it 容器的ID或者NAME /bin/bash

2020-04-12 12:30:25 更新

查找自定义属性

1
docker ps -a --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Status}}"

SpringBatch

组成部分

  • JobRepository: 用来注册Job的容器
  • JobLauncher: 用来启动Job的接口
  • Job : 我要实际执行的任务,包含一个或多个Step
  • Step : Step-步骤包含ItemReaderItemProcessorItemWrite
  • ItemReader : 用来读取数据的接口
  • ItemProcessor : 用来处理数据的接口
  • ItemWrite : 用来输出数据的接口

整合

SpringBoot 整合 SpringBatch 只需要引入依赖并注册成Spring 的 Bean 即可,若是想开启批处理的支持还需要在该配置类上添加 @EnableBatchProcessing

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<!-- SpringBatch会自动加载hsqldb,我们去除即可 -->
<exclusions>
<exclusion>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</exclusion>
</exclusions>
</dependency>

来看代码 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Bean
public JobRepository jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager)
throws Exception {
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("oracle");
return jobRepositoryFactoryBean.getObject();
}

@Bean
public SimpleJobLauncher jobLauncher(DataSource dataSource, PlatformTransactionManager transactionManager)
throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository(dataSource, transactionManager));
return jobLauncher;
}

@Bean
public Job importJob(JobBuilderFactory jobs, Step s1) {
return jobs.get("importJob")
.incrementer(new RunIdIncrementer())
.flow(s1)
.end()
.listener(csvJobListener())
.build();
}

@Bean
public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader<Person> reader, ItemWriter<Person> writer,
ItemProcessor<Person,Person> processor) {
return stepBuilderFactory
.get("step1")
.<Person, Person>chunk(65000) //1
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}



//接口分别实现

@Bean
public ItemReader<Person> reader() throws Exception {
//
return reader;
}

@Bean
public ItemProcessor<Person, Person> processor() {
//
return processor;
}



@Bean
public ItemWriter<Person> writer(DataSource dataSource) {//1
//
return writer;
}
}

貌似就这么简单的完成了……

扩展

  • 监听Job的执行情况,自定义类实现JobExecutionListener
  • 执行计划任务,在普通的计划任务方法中执行JobLauncher的run方法即可

emoji

准备

  • MySQL5.5.3+
  • mysql-connector-java5.1.13+

有异常

1
2
3
4
5
6
7
8
9
java.sql.SQLException: Incorrect string value: '\xF0\x9F\x92\x94' for colum n 'name' at row 1 
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1073)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3593)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3525)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1986)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2140)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2620)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1662)
at com.mysql.jdbc.StatementImpl.executeUpdate(StatementImpl.java:1581)

配置项

1
2
3
4
5
6
7
8
9
10
11
    
[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
  • 数据库字符集:utf8mb4 -- UTF-8 Unicode
  • 排序规则:utf8mb4_general_ci

多样的浏览器兼容

1
2
3
4
5
6
7
8
9
<link href="http://cdn.staticfile.org/emoji/0.2.2/emoji.css" rel="stylesheet" type="text/css" />
<script src="http://cdn.staticfile.org/jquery/2.1.0/jquery.min.js"></script>
<script src="http://cdn.staticfile.org/emoji/0.2.2/emoji.js"></script>



var $text = $('.emojstext');
var html = $text.html().trim().replace(/\n/g, '<br/>');
$text.html(jEmoji.unifiedToHTML(html));

Sonatype

说明

  • 个人感觉第一次发布的步骤非常复杂,我在第一次操作的时候来来回回发布了7,8个版本,最后都是校验失败,导致构件不能关闭(因为我遇到了个大坑)
  • 第一次发布成功之后后面的更新和添加新的构件都相对来说要容易一些(groupid不变的情况下)

开始

账户注册

创建并提交工单

创建工单

  • Project和issue Type的填写如上图所示,不能填写错了
  • 创建完成之后就等待网站工作人员的审核就可以了,不知道为什么,我等待的时间非常短,2分钟都不到,工作人员就回复我了,可能是我的运气比较好吧,但是上个星期买房摇号我却没摇到伤心欲绝
  • 当issue 的 state 变为 RESOLVED时就可继续操作了,同时下面的活动区会给你发消息
    Comment

gpg生成密钥对

  • 下载安装:https://www.gpg4win.org/download.html
    安装时注意的是,只安装主体组件和加密解密窗口的组件就可以了,其他的不需要~~~~
  • 查看是否安装成功:gpg --version
    version
  • 生成密钥对:gpg --gen-key
    gpg --gen-key
    gpg --gen-key
  • 之后往下,会让你输入用户名和邮箱,还有一个Passphase,相当于密钥库密码,不要忘记。
  • 查看公钥:gpg --list-keys
  • 将公钥发布到 PGP 密钥服务器
    1
    2
    3
    4
    5
    gpg --keyserver hkp://pool.sks-keyservers.net --send-keys C990D076
    //可能由于网络问题,有点慢,多重试几次

    //查看发布是否成功
    gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys C990D076

配置setting.xml文件和pom.xml文件

  • setting.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    <servers>
    <server>
    //记住id,需要和pom文件里的id一致
    <id>oss</id>
    <username>username</username>
    <password>password</password>
    </server>
    </servers>
  • pom.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    <!--
    ~ The MIT License (MIT)
    ~
    ~ Copyright (c) 2017 2587038142@qq.com
    ~
    ~ Permission is hereby granted, free of charge, to any person obtaining a copy
    ~ of this software and associated documentation files (the "Software"), to deal
    ~ in the Software without restriction, including without limitation the rights
    ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    ~ copies of the Software, and to permit persons to whom the Software is
    ~ furnished to do so, subject to the following conditions:
    ~
    ~ The above copyright notice and this permission notice shall be included in
    ~ all copies or substantial portions of the Software.
    ~
    ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    ~ THE SOFTWARE.
    -->

    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
    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.joylau.code</groupId>
    <artifactId>joylau-echarts</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <name>joylau-echarts</name>
    <description>Configure the most attribute for ECharts3.0+ by Gson</description>
    <url>http://code.joylau.cn</url>

    <parent>
    <groupId>org.sonatype.oss</groupId>
    <artifactId>oss-parent</artifactId>
    <version>7</version>
    </parent>

    <licenses>
    <license>
    <name>The Apache Software License, Version 2.0</name>
    <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
    </license>
    </licenses>

    <developers>
    <developer>
    <name>JoyLau</name>
    <email>2587038142@qq.com</email>
    <url>http://joylau.cn</url>
    </developer>
    </developers>

    <scm>
    <connection>scm:git:git@github.com:JoyLau/joylau-echarts.git</connection>
    <developerConnection>scm:git:git@github.com:JoyLau/joylau-echarts.git</developerConnection>
    <url>git@github.com:JoyLau/joylau-echarts</url>
    </scm>
    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
    <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.5</version>
    <scope>compile</scope>
    <optional>true</optional>
    </dependency>
    </dependencies>

    <build>
    <testResources>
    <testResource>
    <directory>src/test/resources</directory>
    </testResource>
    <testResource>
    <directory>src/test/java</directory>
    </testResource>
    </testResources>
    </build>

    <profiles>
    <profile>
    <id>release</id>
    <build>
    <plugins>
    <!--Compiler-->
    <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <source>1.7</source>
    <target>1.7</target>
    </configuration>
    </plugin>
    <!-- Source -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>2.2.1</version>
    <executions>
    <execution>
    <phase>package</phase>
    <goals>
    <goal>jar-no-fork</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    <!-- Javadoc -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>2.9.1</version>
    <executions>
    <execution>
    <phase>package</phase>
    <goals>
    <goal>jar</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    <!-- GPG -->
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-gpg-plugin</artifactId>
    <version>1.6</version>
    <executions>
    <execution>
    <phase>verify</phase>
    <goals>
    <goal>sign</goal>
    </goals>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>
    <distributionManagement>
    <snapshotRepository>
    <id>oss</id>
    <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
    </snapshotRepository>
    <repository>
    <id>oss</id>
    <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
    </repository>
    </distributionManagement>
    </profile>
    </profiles>
    </project>

上传构件到 OSS 中

1
2
3
mvn clean deploy -P release
<--! jdk1.8后再生成javadoc时语法较为严格,这时去除javadoc即可 !-->
mvn clean deploy -P release -Dmaven.javadoc.skip=true

在上传之前会自动弹出一个对话框,需要输入上面提到的 Passphase,它就是刚才设置的 GPG 密钥库的密码。
随后会看到大量的 upload 信息,因为在国内网络的缘故,时间有点久,耐心等待吧。

在 OSS 中发布构件

  • 在 OSS 中,使用自己的 Sonatype 账号登录后,可在 Staging Repositories 中查看刚才已上传的构件,这些构件目前是放在 Staging 仓库中,可进行模糊查询,快速定位到自己的构件。
  • 此时,该构件的状态为 Open,需要勾选它,然后点击 Close 按钮。系统会自动验证该构件是否满足指定要求,当验证完毕后,状态会变为 Closed。
    1
  • 最后,点击 Release 按钮来发布该构件,这一步没有截图,将就看吧知道就行:
    2

等待构件审批通过

这个,又只能等待了,当然他们晚上上班,还是第二天看。当审批通过后,将会收到邮件通知。

从中央仓库中搜索构件

这时,就可以在maven的中央仓库中搜索到自己发布的构件了,以后可以直接在pom.xml中使用了!

中央仓库搜索网站:http://search.maven.org/

第一次成功发布之后,以后就不用这么麻烦了,可以直接使用Group Id发布任何的构件,当然前提是Group Id没有变。

以后的发布流程:

a)构件完成后直接使用maven在命令行上传构建;

b)在https://oss.sonatype.org/ close并release构件;

c)等待同步好(大约2小时多)之后,就可以使用了

遇坑记录

  • 安装GPG时候,没有安装弹框组件,导致gpg密码框弹不出来
  • 一开始所有的命令行都在git下操作,每次部署的时候都是提示没有私钥错误,后来发现git生成的gpg密钥对在user更目录下,切换到CMD操作,是生成在AppData下。经查看有私钥,问题解决

引用

有一部分程序员中的老司机,他们善于找各种借口,少干活,少背锅,多拿钱。但是,更多的程序员坦诚、直白、意气用事。
那些年吹过的牛逼都实现了吗?还是随风而去?

这个功能简单,一天就能搞完
Images
程序员拿到一个新功能,心里暗暗发笑,这剧情我见过啊。于是脱口而出,这功能简单,一天就能做完,明天上线肯定没问题。
结果,眼看着到自己设定的截止日期了,还有一部分代码没有写完,怎么办?
很简单啊,又不是生死状,又不要命。解决办法很简单,加班~~~
程序员,那些年吹过的牛逼,最后都自己加班了。

这段代码肯定没bug,我都测试过了
Images
功能开发完了,拿去测试吧,拿去玩耍吧,上线吧,部署吧,发给客户吧,肯定没问题的。
结果,很多时候还没发布。要么测试发现bug,要么产品发现bug,要么老板发现bug。
你的第一反应就是:是特么你们不会用老子开发的功能吧?你乐呵呵的看着bug复现,怎么办呢?
很简单啊,紧急修复bug,重新发布。时间来不及了?加班啊~~~
程序员,那些年吹过的牛逼,最后都自己加班了。

我用的是最现在最流行的技术,某某大公司也用这个
Images
在技术讨论会上,你侃侃而谈,我精心设计的前后端分离的框架,我使用了现在最流行的界面库,我们用的技术某某独角兽公司都在使用,肯定是最好的。
结果呢,使用的技术太新。Github上很少有相关的开源项目,Stack Overflow上很少有这方面的问答。你被一个问题搞的昏天暗地,只能默默的看官方文档,而且是英文的(这是好事儿)。
啊?项目着急上线怎么办呢?加班啊~~~
程序员,那些年吹过的牛逼,最后都自己加班了。

重构代码,很快就能完成
Images

  • 何为Code refactoring

    Code refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behavior.

之前为了快速迭代,忽略了代码的结构和质量。正好最近这两天没有什么新功能开发,我要重构一下现有的代码,绝对没问题。
结果呢,两天的空窗期没搞定。明天就要开发新的功能了,怎么办呢?加班啊~~~
程序员,那些年吹过的牛逼,最后都自己加班了。

向外行介绍程序员工作的复杂程度
在工作中经常能听到这样的话「不就加个按钮么?怎么要做两天时天?」。那么,作为程序员如何解释自己的工作复杂度呢?
如果你的老板是技术出身,那你很庆幸,他能理解你实现一个小小功能,修改一个小小功能所付出的辛苦劳动。
如果你的老板不懂技术,也许你就要无穷无尽的加班了。给你的忠告就是:做正确的事儿,等着被开除。这是一位谷歌工程师说的话。
如果你的产品经理懂技术,那么你既是幸运的也是不幸的。
幸运的是,他可以理解程序员工作的复杂度。但是“不幸”的是,你再也不能为了偷懒找借口。
当产品经理提出一个方案时,你再也不敢坚定地说“技术不可行”。因为你害怕产品经理自己写好了代码给你,那是多么尴尬的境地。

  • 下面是 Channing Walton 的用泡茶的例子来解释,非常形象。
      - 请他们描述泡出一杯茶需要哪些步骤,他们会这么说:
      - 烧水
      - 把茶叶放到茶壶里
      - 水烧开后倒入茶壶
      - 等待5分钟
      - 把茶倒进杯子
      - 加牛奶
      - 喝
      - 现在,有趣的开始了。你要开始问这样的问题:
      - 烧水?
      - 水哪来的?
      - 热水壶在哪里?
      - 你怎么把水倒进热水壶?
      - 你怎么知道热水壶壶里要倒多少水?
      - 如果没有水/热水壶/电怎么办呢?
      - 假如加水传感器失效怎么办?
      - 假如煮水传感器失效怎么办?
      - 茶叶放到茶壶里?
      - 茶壶在哪里,如果没有茶壶怎么办?烧水之前我们应该考虑到这些问题吗?
      - 茶叶在哪里,要用哪一种茶叶?我们是否应该先问清楚,或许如果没有对应的茶叶,我们甚至都不应该开始泡茶?
      - 关于加水和传感器也可以有类似的问题要问
      - 倒开水?
      - 你确定水已经开了么?你怎么能确保“倒水”的机器从热水壶那收到“烧水完成”的信号呢?
      - 你如何确保倒水的机器知道热水壶在哪里?
      - 如果热水壶在倒水的过程翻了怎么办呢?

程序员代码提交中的骂声
正如你工作中看到的,写代码会让你骂骂咧咧,经常爆粗口。
另外有数据统计,写 C++ 程序,会比写 PHP 或 Python 程序所遭到的骂声更多。
Andrew Vos在找一个周末项目,于是决定在 GitHub 上抓取100百万条提交信息(commit),并扫描其中的脏话。
Images   

  • 而且程序员最喜欢的一句是:
      “去TMD,咱们就这样发布。

1
2
3
4
let arr = [1, 1, 2, 2]
arr = Array.prototype.slice.call(new Set(arr))
alert(arr)
//output: 1, 2

build-better-enterprise

SpringBoot文章推荐

SpringBoot项目实战

不啰嗦,直接上代码

集成Druid

DruidConfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Created by LiuFa on 2016/9/14.
* cn.lfdevelopment.www.sys.druid
* DevelopmentApp
*/
@Configuration
public class DruidConfig{

private Logger logger = LoggerFactory.getLogger(getClass());

@Bean(initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix="spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource() {
@Override
public void setUsername(String username) {
try {
username = ConfigTools.decrypt(username);
} catch (Exception e) {
e.printStackTrace();
}
super.setUsername(username);
}

@Override
public void setUrl(String jdbcUrl) {
try {
jdbcUrl = ConfigTools.decrypt(jdbcUrl);
} catch (Exception e) {
e.printStackTrace();
}
super.setUrl(jdbcUrl);
}
};
}
}

DruidStatViewConfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* Created by LiuFa on 2016/8/8.
* cn.lfdevelopment.www.sys.druid
* DevelopmentApp
* 这样的方式不需要添加注解:@ServletComponentScan
*/
@Configuration
public class DruidStatViewConfig {

@Value("${spring.druid.loginUsername}")
private String loginUsername;

@Value("${spring.druid.loginPassword}")
private String loginPassword;

/**
* 注册一个StatViewServlet
* 使用Druid的内置监控页面
*/
@Bean
public ServletRegistrationBean DruidStatViewServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),
"/druid/*");
//添加初始化参数:initParams
//白名单
//ip配置规则
//配置的格式
//<IP> 或者 <IP>/<SUB_NET_MASK_size> 多个ip地址用逗号隔开
//其中
//128.242.127.1/24
//24表示,前面24位是子网掩码,比对的时候,前面24位相同就匹配。
//由于匹配规则不支持IPV6,配置了allow或者deny之后,会导致IPV6无法访问。
servletRegistrationBean.addInitParameter("allow", "");

//deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝。
//如果allow没有配置或者为空,则允许所有访问
//IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
servletRegistrationBean.addInitParameter("deny", "");

//登录查看信息的账号密码.
try {
servletRegistrationBean.addInitParameter("loginUsername", ConfigTools.decrypt(loginUsername));
servletRegistrationBean.addInitParameter("loginPassword", ConfigTools.decrypt(loginPassword));
} catch (Exception e) {
e.printStackTrace();
}

//是否能够重置数据.
servletRegistrationBean.addInitParameter("resetEnable", "true");

return servletRegistrationBean;
}

/**
* 注册一个:filterRegistrationBean
* 内置监控中的Web关联监控的配置
*/
@Bean
public FilterRegistrationBean druidStatFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());

//添加过滤规则.
filterRegistrationBean.addUrlPatterns("/*");

//排除一些不必要的url
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
//缺省sessionStatMaxCount是1000个,这里设置了3000个
filterRegistrationBean.addInitParameter("sessionStatMaxCount", "3000");
//可以配置principalCookieName,使得druid知道指定的sessionName是谁
// filterRegistrationBean.addInitParameter("principalSessionName", "sessionId");
//druid 0.2.7版本开始支持profile,配置profileEnable能够监控单个url调用的sql列表。
filterRegistrationBean.addInitParameter("profileEnable", "true");
return filterRegistrationBean;
}

/**
* 注册一个:druidStatInterceptor
*/
/*@Bean
public DruidStatInterceptor druidStatInterceptor(){
return new DruidStatInterceptor();
}*/

/**
* 注册一个:beanNameAutoProxyCreator
* 内置监控中的spring关联监控的配置
* 该方法使用的是按照BeanId来拦截配置,还有2种方法,分别是
* 按类型拦截配置
* 方法名正则匹配拦截配置
*/
/*@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setProxyTargetClass(true);
beanNameAutoProxyCreator.setBeanNames("*Controller");
beanNameAutoProxyCreator.setInterceptorNames("druidStatInterceptor");
return beanNameAutoProxyCreator;
}*/
}

application-dev:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#druid配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=G11Jor+OrLz9MFztdkOfqRnrJKVrFCDdBbYJFmB0qGjUARxPr2tiyRzUn4xbnk/XqPgM8PMjdIJ/pO8UF4aeVg==
spring.datasource.username=bNVOqb7WKLX5Bjnw+LMv92taj25KOxDimXxILPQjw42wgv+1lHzOH8kr97xDwWdhpY67QuYCS7sWN4W46YbkFA==
spring.datasource.password=l65GeQaXVXxx2ogcQeZLAFM7VcPwgzc9202vxql4hjCbjM8dVm/sD4osdvaBdVkC+BiYdnYL2EzpaCysXAZ5Gw==


# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
spring.datasource.initialSize=10
spring.datasource.minIdle=25
spring.datasource.maxActive=250

# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000

# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=1200000

# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=1800000

spring.datasource.validationQuery=SELECT 'x'

#建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.testWhileIdle=true

#申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.testOnBorrow=false

#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
spring.datasource.testOnReturn=false

# 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。分库分表较多的数据库,建议配置为false 在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。5.5及以上版本有PSCache,建议开启。
spring.datasource.poolPreparedStatements=true

# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,'stat'用于监控,‘log4j’用于日志,'config'是指ConfigFilter
spring.datasource.filters=wall,stat,config

# 通过connectProperties属性来打开mergeSql功能;慢SQL记录,超过3秒就是慢sql
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=3000;config.decrypt=true

# 合并多个DruidDataSource的监控数据,缺省多个DruidDataSource的监控数据是各自独立的,在Druid-0.2.17版本之后,支持配置公用监控数据
spring.datasource.useGlobalDataSourceStat=true

#druid登陆用户名
spring.druid.loginUsername=lCzd9geWAuAuJtLhpaG/J+d28H8KiMFAWopxXkYpMNdUai6Xe/LsPqMQeg5MIrmvtMa+hzycdRhWs29ZUPU1IQ==

#druid登录密码
spring.druid.loginPassword=hf96/2MU+Q12fdb9oZN9ghub1OHmUBa8YuW7NJf8Pll/sawcaRVscHTpr4t5SB39+KbJn31Lqy76uEDvj+sgMw==

集成Mybatis

MyBatisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* Created by LiuFa on 2016/8/8.
* cn.lfdevelopment.www.sys.mybatis
* DevelopmentApp
* DataSource 交由Druid自动根据配置创建
*/
@Configuration
@EnableTransactionManagement
public class MyBatisConfig implements TransactionManagementConfigurer {

@Autowired
private DataSource dataSource;


@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setTypeAliasesPackage("cn.lfdevelopment.www.app.**.pojo");
//支持属性使用驼峰的命名,mapper配置不需要写字段与属性的配置,会自动映射。
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
bean.setConfiguration(configuration);

//分页插件
PageHelper pageHelper = new PageHelper();
Properties properties = new Properties();
/* 3.3.0版本可用 - 分页参数合理化,默认false禁用
启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页
禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据
在EXTjs里面配置与否无所谓,因为在前台传过来的分页数据已经进行合理化了 */
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("returnPageInfo", "check");
/* 3.5.0版本可用 - 为了支持startPage(Object params)方法
增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值
可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值
不理解该含义的前提下,不要随便复制该配置 -->*/

// properties.setProperty("params", "count=countSql");
pageHelper.setProperties(properties);

//添加插件
bean.setPlugins(new Interceptor[]{pageHelper});

//添加XML目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("classpath*:mapperxml/**/*Mapper.xml"));
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}

@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}

@Bean
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
try {
return new DataSourceTransactionManager(dataSource);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

MyBatisMapperScannerConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* MyBatis扫描接口,使用的tk.mybatis.spring.mapper.MapperScannerConfigurer,如果你不使用通用Mapper,可以改为org.xxx...
*/
@Configuration
//由于MapperScannerConfigurer执行的比较早
public class MyBatisMapperScannerConfig {

@Bean
public static MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
mapperScannerConfigurer.setBasePackage("cn.lfdevelopment.www.app.**.mapper");
Properties properties = new Properties();
properties.setProperty("mappers", "cn.lfdevelopment.www.sys.base.BaseMapper");
properties.setProperty("notEmpty", "false");
properties.setProperty("IDENTITY", "MYSQL");
mapperScannerConfigurer.setProperties(properties);
return mapperScannerConfigurer;
}

}

集成Redis

RedisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    
/**
* Created by LiuFa on 2016/9/5.
* cn.lfdevelopment.www.sys.redis
* DevelopmentApp
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
};
}

@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
return new RedisCacheManager(redisTemplate);
}

/**
* StringRedisTemplate
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}


/**
* redisTemplateForShiro
* @param factory
* @return
*/
@Bean
public RedisTemplate<byte[], Object> redisTemplateForShiro(RedisConnectionFactory factory) {
RedisTemplate<byte[], Object> redisTemplateForShiro = new RedisTemplate<>();
redisTemplateForShiro.setConnectionFactory(factory);
return redisTemplateForShiro;
}

}

集成Shiro

ShiroConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/**
* Created by LiuFa on 2016/9/13.
* cn.lfdevelopment.www.sys.shiro
* DevelopmentApp
*/
@Configuration
public class ShiroConfiguration {
/**
* FilterRegistrationBean
* @return
*/
@Autowired
private RedisTemplate redisTemplate;

@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
filterRegistration.addInitParameter("targetFilterLifecycle","true");
filterRegistration.addUrlPatterns("/*");
filterRegistration.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
// filterRegistration.setAsyncSupported(true);
filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
return filterRegistration;
}

/**
* @see org.apache.shiro.spring.web.ShiroFilterFactoryBean
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager());
bean.setLoginUrl("/login");
bean.setSuccessUrl("/main");
//验证不具备权限后的转向页面
bean.setUnauthorizedUrl("/main");

Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("authc",shiroFormAuthenticationFilter());
filters.put("session",sessionFilter());
filters.put("rolesOr",rolesAuthorizationFilter());
bean.setFilters(filters);

Map<String, String> chains = new LinkedHashMap<>();
chains.put("/favicon.ico","anon");
chains.put("/","anon");
chains.put("/index","anon");
chains.put("/blog","anon");
chains.put("/blog/**","anon");
chains.put("/weixin","anon");
chains.put("/weixin/**","anon");
chains.put("/static/**", "anon");
chains.put("/getGifCode","anon");
chains.put("/404", "anon");
chains.put("/druid/**","anon");
chains.put("/logout", "logout");

chains.put("/login", "authc");
chains.put("/main","authc");
chains.put("/**", "session,user");
bean.setFilterChainDefinitionMap(chains);
return bean;
}


/**
* @see org.apache.shiro.mgt.SecurityManager
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm());
manager.setCacheManager(redisCacheManager());
manager.setSessionManager(defaultWebSessionManager());
return manager;
}

/**
* @see DefaultWebSessionManager
* @return
*/
@Bean(name="sessionManager")
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setCacheManager(redisCacheManager());
sessionManager.setGlobalSessionTimeout(1800000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionValidationInterval(600000);
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}


/**
* @return
*/
@Bean
@DependsOn(value={"lifecycleBeanPostProcessor", "shrioRedisCacheManager"})
public AuthorizingRealm userRealm() {
AuthorizingRealm userRealm = new AuthorizingRealm();
userRealm.setCacheManager(redisCacheManager());
userRealm.setCachingEnabled(true);
userRealm.setAuthenticationCachingEnabled(true);
userRealm.setAuthorizationCachingEnabled(true);
return userRealm;
}


@Bean(name="shrioRedisCacheManager")
@DependsOn(value="redisTemplate")
public ShrioRedisCacheManager redisCacheManager() {
ShrioRedisCacheManager cacheManager = new ShrioRedisCacheManager(redisTemplate);
return cacheManager;
}

@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

@Bean(name = "authcFilter")
public FormAuthenticationFilter shiroFormAuthenticationFilter(){
return new AuthcFilter();
}

@Bean
public SessionFilter sessionFilter(){
return new SessionFilter();
}


@Bean(name = "rolesOrFilter")
public RolesAuthorizationFilter rolesAuthorizationFilter(){
return new RolesAuthorizationFilter() {
@Override
public boolean isAccessAllowed(ServletRequest request,
ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;

if ((rolesArray == null) || (rolesArray.length == 0)) {
return true;
}
for (String aRolesArray : rolesArray) {
if (subject.hasRole(aRolesArray)) {
//用户只要拥有任何一个角色则验证通过
return true;
}
}
return false;
}
};
}
}

ShiroRedisCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/**
* Created by LiuFa on 2016/9/13.
* cn.lfdevelopment.www.sys.shiro
* DevelopmentApp
*/
public class ShrioRedisCache<K, V> implements Cache<K, V> {
private org.slf4j.Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private RedisTemplate<byte[], V> redisTemplate;
private String prefix = "shiro_redis:";

public ShrioRedisCache(RedisTemplate<byte[], V> redisTemplate) {
this.redisTemplate = redisTemplate;
}

public ShrioRedisCache(RedisTemplate<byte[], V> redisTemplate, String prefix) {
this(redisTemplate);
this.prefix = prefix;
}

@Override
public V get(K key) throws CacheException {
if(log.isDebugEnabled()) {
log.debug("Key: {}", key);
}
if(key == null) {
return null;
}

byte[] bkey = getByteKey(key);
return redisTemplate.opsForValue().get(bkey);
}

@Override
public V put(K key, V value) throws CacheException {
if(log.isDebugEnabled()) {
log.debug("Key: {}, value: {}", key, value);
}

if(key == null || value == null) {
return null;
}

byte[] bkey = getByteKey(key);
redisTemplate.opsForValue().set(bkey, value);
return value;
}

@Override
public V remove(K key) throws CacheException {
if(log.isDebugEnabled()) {
log.debug("Key: {}", key);
}

if(key == null) {
return null;
}

byte[] bkey = getByteKey(key);
ValueOperations<byte[], V> vo = redisTemplate.opsForValue();
V value = vo.get(bkey);
redisTemplate.delete(bkey);
return value;
}

@Override
public void clear() throws CacheException {
redisTemplate.getConnectionFactory().getConnection().flushDb();
}

@Override
public int size() {
Long len = redisTemplate.getConnectionFactory().getConnection().dbSize();
return len.intValue();
}

@SuppressWarnings("unchecked")
@Override
public Set<K> keys() {
byte[] bkey = (prefix+"*").getBytes();
Set<byte[]> set = redisTemplate.keys(bkey);
Set<K> result = new HashSet<>();

if(CollectionUtils.isEmpty(set)) {
return Collections.emptySet();
}

for(byte[] key: set) {
result.add((K)key);
}
return result;
}

@Override
public Collection<V> values() {
Set<K> keys = keys();
List<V> values = new ArrayList<>(keys.size());
for(K k: keys) {
byte[] bkey = getByteKey(k);
values.add(redisTemplate.opsForValue().get(bkey));
}
return values;
}

private byte[] getByteKey(K key){
if(key instanceof String){
String preKey = this.prefix + key;
return preKey.getBytes();
}else{
return SerializeUtils.serialize(key);
}
}

public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}
}

最后

  • 本文暂未完结,后续将持续集成更多第三方框架,或接着更新,或另起新篇
  • 详细代码内容可在GitHub上follow
0%