JoyLau's Blog

JoyLau 的技术学习与思考

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

这个功能简单,一天就能搞完
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

SpringBootStart-Main

开始

我们开发任何一个Spring Boot项目,都会用到如下的启动类

1
2
3
4
5
6
7
@SpringBootApplication
public class JoylauApplication {

public static void main(String[] args) {
SpringApplication.run(JoylauApplication.class, args);
}
}

从上面代码可以看出,Annotation定义(@SpringBootApplication)和类定义(SpringApplication.run)最为耀眼,所以要揭开SpringBoot的神秘面纱,我们要从这两位开始就可以了。

SpringBootApplication背后的秘密

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
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class,
attribute = "exclude"
)
Class<?>[] exclude() default {};

@AliasFor(
annotation = EnableAutoConfiguration.class,
attribute = "excludeName"
)
String[] excludeName() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}

虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:

  • @Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration)
  • @EnableAutoConfiguration
  • @ComponentScan
    所以,如果我们使用如下的SpringBoot启动类,整个SpringBoot应用依然可以与之前的启动类功能对等:
    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration
    @EnableAutoConfiguration
    @ComponentScan
    public class JoylauApplication {
    public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
    }
    }

每次写这3个比较累,所以写一个@SpringBootApplication方便点。接下来分别介绍这3个Annotation。

@Configuration

这里的@Configuration对我们来说不陌生,它就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。
举几个简单例子回顾下,XML跟config配置方式的区别:

  • 表达形式层面
    基于XML配置的方式是这样:
    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
    default-lazy-init="true">
    <!--bean定义-->
    </beans>
    而基于JavaConfig的配置方式是这样:
    1
    2
    3
    4
    @Configuration
    public class MockConfiguration{
    //bean定义
    }

任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。

  • 注册bean定义层面
    基于XML的配置形式是这样:
    1
    2
    3
    <bean id="mockService" class="..MockServiceImpl">
    ...
    </bean>

而基于JavaConfig的配置形式是这样的:

1
2
3
4
5
6
7
@Configuration
public class MockConfiguration{
@Bean
public MockService mockService(){
return new MockServiceImpl();
}
}

任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

  • 表达依赖注入关系层面
    为了表达bean与bean之间的依赖关系,在XML形式中一般是这样:
    1
    2
    3
    4
    5
    <bean id="mockService" class="..MockServiceImpl">
    <propery name ="dependencyService" ref="dependencyService" />
    </bean>

    <bean id="dependencyService" class="DependencyServiceImpl"></bean>
    而基于JavaConfig的配置形式是这样的:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class MockConfiguration{
    @Bean
    public MockService mockService(){
    return new MockServiceImpl(dependencyService());
    }

    @Bean
    public DependencyService dependencyService(){
    return new DependencyServiceImpl();
    }
    }

如果一个bean的定义依赖其他bean,则直接调用对应的JavaConfig类中依赖bean的创建方法就可以了。

@ComponentScan

@ComponentScan这个注解在Spring中很重要,它对应XML配置中的context:component-scan元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。

注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。

@EnableAutoConfiguration

个人感觉@EnableAutoConfiguration这个Annotation最为重要,所以放在最后来解读,大家是否还记得Spring框架提供的各种名字为@Enable开头的Annotation定义?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义。

  • @EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器。
  • @EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器。
    而@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!

@EnableAutoConfiguration作为一个复合Annotation,其自身定义关键信息如下:

1
2
3
4
5
6
7
8
9
10
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

其中,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样

深入探索SpringApplication执行流程

SpringApplication的run方法的实现是我们本次旅程的主要线路,该方法的主要流程大体可以归纳如下:

1) 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:

  • 根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。
  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。
  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
  • 推断并设置main方法的定义类。
    2) SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。

3) 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。

4) 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。

5) 如果SpringApplication的showBanner属性被设置为true,则打印banner。

6) 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。

7) ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。

8) 遍历调用所有SpringApplicationRunListener的contextPrepared()方法。

9) 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。

10) 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。

11) 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。

12) 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。

13) 正常情况下,遍历执行SpringApplicationRunListener的finished()方法、(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理)
去除事件通知点后,整个流程如下:
SpringBoot-start-model

参考

大部分参考了《SpringBoot揭秘快速构建为服务体系》这本书

SpringBoot-Start

说明

  • 玄铁重剑是神雕侠侣中杨过的兵器,外表看似笨重无比,但内在却精致有细。
  • 在脚本语言和敏捷开发大行其道的时代,JavaEE的开发显得尤为笨重,这使得很多开发人员本应该如此,Spring在提升JavaEE的开发效率上从未停止过努力,SpringBoot的出现时具有颠覆性和划时代意义的。

开始准备

  • JDK1.7+
  • Maven3.x+
  • Tomcat8.5+
  • Spring4.3.x+
  • IntelliJ IDEA / MyEclipse(强烈推荐IDEA,我认为IDEA目前所有 IDE 中最具备沉浸式的 IDE,没有之一

优缺点

优点

  • 习惯优于配置:使用SpringBoot只需要很少的配置,在绝大部分时候我们只需要使用默认配置
  • 项目极速搭建,可无配置整合其他第三方框架,极大提高开发效率
  • 完全不使用XML配置,只使用自动配置和JavaConfig
  • 内嵌Servlet容器,可打成jar包独立运行
  • 强大的运行时监控
  • 浑然天成的集成云计算

缺点

  • 流汗没有

优雅的开始

  • Spring 官方网站搭建

    1. 访问:http://start.spring.io/
    2. 选择构建工具Maven Project、Spring Boot版本1.5.1以及一些工程基本信息,可参考下图所示
      SpringInitializr
    3. 点击Generate Project下载项目压缩包
    4. 导入到你的工程,如果是IDEA,则需要:
      a.菜单中选择File–>New–>Project from Existing Sources...
      b.选择解压后的项目文件夹,点击OK
      c.点击Import project from external model并选择Maven,点击Next到底为止。
      d.若你的环境有多个版本的JDK,注意到选择Java SDK的时候请选择Java 7以上的版本
  • IntelliJ IDEA创建(强烈推荐
    在File菜单里面选择 New > Project,然后选择Spring Initializr,接着如下图一步步操作即可。
    SpringInitializr
    SpringInitializr-2
    SpringInitializr-3
    SpringInitializr-4

若上述步骤步骤没有出现网络错误导致的无法搭建,基本上已经没有什么问题了

项目目录

根据上面的操作已经初始化了一个Spring Boot的框架了,项目结构如下:
SpringBootProject-view

项目里面基本没有代码,除了几个空目录外,还包含如下几样东西。

  • pom.xml:Maven构建说明文件。
  • JoylauApplication.java:一个带有main()方法的类,用于启动应用程序(关键)。
  • JoylauApplicationTests.java:一个空的Junit测试类,它加载了一个使用Spring Boot字典配置功能的Spring应用程序上下文。
  • application.properties:一个空的properties文件,你可以根据需要添加配置属性。(还推荐一种yml文件的配置方式)

项目文件

我们来看pom.xml文件

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

这块配置就是Spring Boot父级依赖,有了这个,当前的项目就是Spring Boot项目了,spring-boot-starter-parent是一个特殊的starter,它用来提供相关的Maven默认依赖,使用它之后,常用的包依赖可以省去version标签。

并不是每个人都喜欢继承自spring-boot-starter-parent POM。也有可能我们需要使用的自己的公司标准parent,或者我们更喜欢显式声明所有的Maven配置。
如果不想使用spring-boot-starter-parent,仍然可以通过使用scope = import依赖关系来保持依赖关系管理:

1
2
3
4
5
6
7
8
9
10
11
12
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

该设置不允许使用spring-boot-dependencies所述的属性(properties)覆盖各个依赖项,要实现相同的结果,需要在spring-boot-dependencies项之前的项目的dependencyManagement中添加一个配置,例如,要升级到另一个Spring Data版本系列,可以将以下内容添加到pom.xml中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencyManagement>
<dependencies>
<!-- Override Spring Data release train provided by Spring Boot -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>Fowler-SR2</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

项目依赖

起步依赖 spring-boot-starter-xx

Spring Boot提供了很多”开箱即用“的依赖模块,都是以spring-boot-starter-xx作为命名的。举个例子来说明一下这个起步依赖的好处,比如组装台式机和品牌机,自己组装的话需要自己去选择不同的零件,最后还要组装起来,期间有可能会遇到零件不匹配的问题。耗时又消力,而品牌机就好一点,买来就能直接用的,后续想换零件也是可以的。相比较之下,后者带来的效果更好点(这里就不讨论价格问题哈),起步依赖就像这里的品牌机,自动给你封装好了你想要实现的功能的依赖。就比如我们之前要实现web功能,引入了spring-boot-starter-web这个起步依赖。我们来看看spring-boot-starter-web到底依赖了哪些,如下图:
SpringBoot-starter-web-dependencies

最后

项目启动的三种方式

  1. main方法
    SpringBoot-Start1
  2. 使用命令 mvn spring-boot:run在命令行启动该应用,IDEA中该命令在如下位置
    SpringBoot-Start2
  3. 运行mvn package进行打包时,会打包成一个可以直接运行的 JAR 文件,使用java -jar命令就可以直接运行
    SpringBoot-Start3
    SpringBoot-Start4

MATE Desktop

上面的截图是我安装好之后界面,安装的是MATE桌面

说明

  • 1.阿里云官网默认的Linux Centos7系统镜像,都是没有安装桌面环境的,用户如果要使用桌面,需要自己在服务器上进行安装
  • 2.生产环境下不要安装桌面,毕竟生产环境下的资源都是很紧张的
  • groups是Centos7才有的命令

开始安装

  • 登录服务器,执行命令安装桌面环境(537M)

    1
    yum groups install "MATE Desktop"
  • 安装好MATE Desktop 后,再安装X Window System(19M)

    1
    yum groups install "X Window System"

配置

  • 设置服务器默认启动桌面
    1
    systemctl  set-default  graphical.target

启动

  • 重启服务器
    1
    reboot

在ECS控制台,用管理终端登录服务器,进入到服务器系统登录界面,用root密码登录服务器。

卸载

1
2
3
4
yum groupremove 'X Window System' -y
yum groupremove 'MATE Desktop' -y
// 恢复至默认启动界面
systemctl set-default multi-user.target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
////////////////////////////////////////////////////////////////////
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永无BUG //
////////////////////////////////////////////////////////////////////

说明

  • 有一些命令是centos7特有的,在低版本的可能无法使用

防火墙

  • 查看防火墙状态: systemctl status firewalld
  • 开启防火墙 : systemctl start firewalld
  • 停止防火墙: systemctl disable firewalld
  • 重启防火墙: systemctl restart firewalld.service
  • 开启80端口: firewall-cmd --zone=public --add-port=80/tcp --permanent
  • 禁用防火墙: systemctl stop firewalld
  • 查看防火墙开放的端口号: firewall-cmd --list-ports

Tomcat

  • 查看tomcat运行状态:ps -ef |grep tomcat
  • 看到tomcat的pid之后:kill -9 pid 可以强制杀死tomcat的进程

系统信息

  • 查看cpu及内存使用情况:top(停止刷新 -q)
  • 查看内存使用情况 :free

文件操作

  • 删除文件里机器子文件夹的内容:rm -rf /var/lib/mysql
  • 查找某个文件所在目录:find / -name filename

压缩与解压缩

  • 解压zip压缩文件:unzip file.zip,相反的,压缩文件 zip file (需安装yum install unzip zip,),解压到指定目录可加参数-d,如:unzip file.zip -d /root/
  • 将 test.txt 文件压缩为 test.zip,zip test.zip test.txt,当然也可以指定压缩包的目录,例如 /root/test.zip ,后面的test.txt也可以换成文件夹
  • linux下是不支持直接解压rar压缩文件,建议将要传输的文件压缩成zip文件
  • yum install p7zip 安装7z解压,支持更多压缩格式(卸载yum remove p7zip

快速删除文件夹/文件

  • 有时我们的文件夹里有很多文件,默认的删除方式是递归删除,文件夹深了及文件多了,删除会非常的慢,这时候:
  • 先建立一个空目录
    mkdir /data/blank
  • 用rsync删除目标目录
    rsync–delete-before -d /data/blank/ /var/spool/clientmqueue/
  • 同样的对于大文件:创建空文件
    touch /data/blank.txt
  • 用rsync清空文件
    rsync-a –delete-before –progress –stats /root/blank.txt /root/nohup.out

端口

  • 查看6379端口是否占用:netstat -tunpl | grep 6379 (注意,redis服务需要 root 权限才能查看,不然只能检查到6379被某个进程占用,但是看不到进程名称。)

主机

  • 修改主机名:hostnamectl set-hostname 新主机名

yum

  • 列出所有可更新的软件清单命令:yum check-update
  • 更新所有软件命令:yum update
  • 仅安装指定的软件命令:yum install <package_name>
  • 仅更新指定的软件命令:yum update <package_name>
  • 列出所有可安裝的软件清单命令:yum list
  • 删除软件包命令:yum remove <package_name>
  • 查找软件包 命令:yum search <keyword>
  • 清除缓存命令:
  • yum clean packages: 清除缓存目录下的软件包
  • yum clean headers: 清除缓存目录下的 headers
  • yum clean oldheaders: 清除缓存目录下旧的 headers

systemctl

  • systemctl restart nginx : 重启nginx
  • systemctl start nginx : 开启nginx
  • systemctl stop nginx : 关闭nginx
  • systemctl enable nginx : nginx开机启动
  • systemctl disable nginx : 禁用nginx开机启动
  • systemctl status nginx : 查看nginx服务信息
  • systemctl is-enabled nginx : 查看服务是否开机启动
  • systemctl list-unit-files|grep enabled : 查看已启动的服务列表
  • systemctl --failed : 查看启动失败的服务列表
  • systemctl daemon-reload : 重新加载service文件
  • systemctl reboot : 重启
  • systemctl poweroff : 关机

压缩解压命令

压缩

  • tar –cvf jpg.tar *.jpg //将目录里所有jpg文件打包成tar.jpg
  • tar –czf jpg.tar.gz *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一个gzip压缩过的包,命名为jpg.tar.gz
  • tar –cjf jpg.tar.bz2 *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用bzip2压缩,生成一个bzip2压缩过的包,命名为jpg.tar.bz2
  • tar –cZf jpg.tar.Z *.jpg //将目录里所有jpg文件打包成jpg.tar后,并且将其用compress压缩,生成一个umcompress压缩过的包,命名为jpg.tar.Z
  • rar a jpg.rar *.jpg //rar格式的压缩,需要先下载rar for linux
  • zip jpg.zip *.jpg //zip格式的压缩,需要先下载zip for linux

解压

  • tar –xvf file.tar //解压 tar包
  • tar -xzvf file.tar.gz //解压tar.gz
  • tar -xjvf file.tar.bz2 //解压 tar.bz2
  • tar –xZvf file.tar.Z //解压tar.Z
  • unrar e file.rar //解压rar
  • unzip file.zip //解压zip

总结

  • .tar 用 tar –xvf 解压
  • .gz 用 gzip -d或者gunzip 解压
  • .tar.gz和*.tgz 用 tar –xzf 解压
  • .bz2 用 bzip2 -d或者用bunzip2 解压
  • .tar.bz2用tar –xjf 解压
  • .Z 用 uncompress 解压
  • .tar.Z 用tar –xZf 解压
  • .rar 用 unrar e解压
  • .zip 用 unzip 解压

yum更换为阿里源

  • 备份 :mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup

  • 下载新的CentOS-Base.repo 到/etc/yum.repos.d/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## CentOS 5 :

wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo

## 或者

curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo

## CentOS 6 :

wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo

## 或者

curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo

## CentOS 7 :

wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

## 或者

curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
  • 之后运行 yum makecache 生成缓存

创建用户,权限,密码等

  • adduser es -s /bin/bash : 创建用户为 es ,shell指定为bash
  • passwd es 更改 es 用户的密码
  • chown -R es:es /project/elasticsearch-5.6.3 循环遍历更改 /project/elasticsearch-5.6.3 目录下的文件拥有者及用户组
  • su - es : 切换成es用户重新登录系统
  • su es : 表示与 es 建立一个连接,通过 es 来执行命令

注: 以上命令在安装 elasticsearch 时都会用的到

2018-06-22 更新

  1. vim 永久显示行号 vim /etc/vimrc 添加 set nu 或者 set number

  2. 最小化安装 centos 是没有 tab 键补全提示的, 需要安装 yum install bash-completion

  3. tab 补全提示不区分大小写 : vim /etc/inputrc 添加 set completion-ignore-case on

注: 以上 增加配置是全局的,只对当前用户的话,可以在当前目录下新建相应的文件,再添加配置,例如: ~/.inputrc

2018-9-12 更新

  1. killall -9 nginx : 批量结束指定进程,比如不小心运行了 nginx,会产生1个master和n个work进程,这时候一个个结束不实际,killall就是最好的方式
  2. 有时候我们安装 rpm 安装包会出现某些依赖库找不到,比如
    libSM.so.6: cannot open shared object file: No such file or directory
    这时候我们使用 yum provides libSM.so.6 来寻找包含的动态库
1
2
3
4
5
6
7
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
gperftools-libs-2.6.1-1.el7.i686 : Libraries provided by gperftools
Repo : Private-Base
Matched from:
Provides : libprofiler.so.0

找到后安装即可 yum install gperftools-libs

2018-12-20 更新

  1. /dev/null 2>&1 解释
    > 覆盖原来的内容
    >> 在原来的内容上追加新的内容
    0是标准输入 使用<或<<
    1是标准输出 使用>或>>
    2是标准错误输出 使用2>或2>>
    >/dev/null 2>&1 即错误输出与标准输出全部重定向到空,可以写成 1>/dev/null 2>/dev/null
    标准输入0和标准输出1可以省略。(当其出现重定向符号左侧时)
    文件描述符在重定向符号左侧时直接写即可,在右侧时前面加&
    文件描述符与重定向符号之间不能有空格

2019-01-23 更新

  1. 在命令后加个 & 代表该命令在后台运行, shell 的控制台会立即释放,但是和守护进程又不一样, shell 断开会终止运行
  2. command > file.log 2>&1 等价于 command 2>file.log 1>&2 前一个指的是标准错误重定向到标准输出,标准输出在重定向到文件 file.log 中, 其中 1 省略了;后一个指的是标准输出重定向到标准错误,标准错误又重定向到文件 file.log, 其中2 不能省略
  3. shell 脚本中无法报命令不存在的错误: 在 shell 脚本第一行使用 #!/usr/bin/env bash 或者 #!/usr/bin/bash 或者 #!/bin/bash
  4. 如果运行还是命令不存在的话: 创建一个软连接 ln -s command /usr/bin/command, 参数 -s 创建了个符号链接,相当于快捷方式,不加参数 -s 就是创建硬链接,相当于文件拷贝

2019-03-07 更新

没有联网的机器做时间服务器,写了个接口获取网络的时间,然后服务器使用 crontab 定时设置时间
java:

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
/**
* 同步时间
*/
@GetMapping("traffic/syncDateTime")
public String syncDateTime() {
String taobaoTime = "http://api.m.taobao.com/rest/api.do?api=mtop.common.getTimestamp";
String suningTime = "http://quan.suning.com/getSysTime.do";
JSONObject jsonObject;
jsonObject = getDateTime(taobaoTime);
if (null != jsonObject && jsonObject.containsKey("data")) {
String time = jsonObject.getJSONObject("data").getString("t");
return LocalDateTime.ofEpochSecond(Long.parseLong(time) / 1000, 0, ZoneOffset.ofHours(8))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
jsonObject = getDateTime(suningTime);
if (null != jsonObject && jsonObject.containsKey("sysTime2")) {
return jsonObject.getString("sysTime2");
}
return null;

}

private JSONObject getDateTime(String url){
return restTemplate.getForObject(url,JSONObject.class);
}

shell:

1
2
3
4
5
6
7
8
#!/usr/bin/env bash
time=$(curl -G -s http://34.0.7.227:9338/traffic/syncDateTime)
if [ ! -n "$time" ]; then
echo "time is null...."
else
date -s "${time}"
hwclock -w
fi

2019-03-15 更新

  1. shell 修改文件固定行的内容
1
sed -i "108c 'update content'" filename

2019-03-18 更新

删除 Ubuntu 多余内核

  1. dpkg --get-selections|grep linux : 查看全部安装的内核
  2. 确定当前使用的内核,一般为版本号最新的内核,删除旧内核: sudo apt remove linux-headers-4.15.0-43 linux-headers-4.15.0-43-generic linux-image-4.15.0-43-generic linux-modules-4.15.0-43-generic linux-modules-extra-4.15.0-43-generic
  3. 再次输入第一步的命令查看现在的内核信息,现在会看到刚才删除的内核会出现 deinstall 的状态
  4. 删除 deinstall 状态的内核: sudo dpkg -P xxxxxx

2024-11-20 更新

测试硬盘的读写速度

  • 写入: dd if=/dev/zero of=testfile bs=1G count=1 oflag=direct
  • 读取: dd if=testfile of=/dev/null bs=1G iflag=direct

Server

前言

  • 本次搭建JavaTomcat的运行环境,后续将接着搭建Mysql,Git,Nginx,Redis,Docker…环境

Java环境搭建

  • 1.在/usr/目录下创建java目录

    1
    2
    [root@JoyLau ~]# mkdir/usr/java
    [root@JoyLau ~]# cd /usr/java
  • 2.官网下载jdk,拷贝到服务器上,然后解压

    1
    [root@JoyLau java]# tar -zxvf jdk-8u121-linux-x64.gz
  • 3.设置环境变量

    1
    [root@JoyLau java]# vi /etc/profile
  • 4.在profile中添加如下内容:

    1
    2
    3
    4
    5
    6
    7
    #set java environment
    JAVA_HOME=/usr/java/jdk1.8.0_121
    JRE_HOME=/usr/java/jdk1.8.0_121
    CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
    PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
    export JAVA_HOME JRE_HOME CLASS_PATH PATH

  • 5.让修改生效:

    1
    2
    [root@JoyLau java]# source /etc/profile

  • 6.验证

    1
    2
    3
    4
    [root@JoyLau ~]# java --version
    java version "1.8.0_121"
    Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
    Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
  • 还有2中方法可以安装jdk:

    • 1.用yum安装jdk
    • 2.Ubuntu 上使用apt-get安装jdk

yum 安装jdk

  • yum search jdk : 查看yum源上的jdk版本信息
  • 选择一个jdk8的来安装: yum install java-1.8.0-openjdk.x86_64
  • 等待即可

注意的是默认安装的只是 Java JRE,而不是 JDK,为了开发方便,我们还是需要通过 yum 进行安装 JDK
yum install java-1.8.0-openjdk-devel.x86_64

之后就可以直接使用 Java javac 命令了

配置 JAVA_HOME变量:

vim ~/.bashrc
在文件最后面添加如下单独一行(指向 JDK 的安装位置),并保存:
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk
接着还需要让该环境变量生效,执行如下代码:
source ~/.bashrc # 使变量设置生效
设置好后我们来检验一下是否设置正确:
echo $JAVA_HOME # 检验变量值
java -version
$JAVA_HOME/bin/java -version # 与直接执行 java -version 一样
如果设置正确的话,$JAVA_HOME/bin/java -version 会输出 java 的版本信息,且和 java -version 的输出结果一样

rpm 安装jdk

  1. 官网下载 jdk rpm包
  2. rpm -ivh jdk_xxxxx.rpm
  3. 配置环境变量(和上述配置一致)
  4. 卸载: rpm -qa|grep jdk , 查出什么,就使用 rpm -e –nodeps java_xxx 来卸载

Tomcat环境搭建

先配置

  • 配置catalina.sh,加入以下配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #add JAVA and TOMCAT config
    JAVA_OPTS="-Xms512m -Xmx1024m -Xss1024K -XX:PermSize=512m -XX:MaxPermSize=1024m"
    export TOMCAT_HOME=/project/apache-tomcat-8.5.11
    export CATALINA_HOME=/project/apache-tomcat-8.5.11
    export JRE_HOME=/usr/java/jdk1.8.0_121/jre
    export JAVA_HOME=/usr/java/jdk1.8.0_121

    #add tomcat pid
    CATALINA_PID="$TOMCAT_HOME/tomcat.pid"
  • 增加tomcat.service在/usr/lib/systemd/system目录下增加tomcat.service,目录必须是绝对目录。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [Unit]
    Description=Tomcat
    After=syslog.target network.target remote-fs.target nss-lookup.target

    [Service]
    Type=forking
    PIDFile=/project/apache-tomcat-8.5.11/tomcat.pid
    ExecStart=/project/apache-tomcat-8.5.11/bin/startup.sh
    ExecReload=/bin/kill -s HUP $MAINPID
    ExecStop=/bin/kill -s QUIT $MAINPID
    PrivateTmp=true

    [Install]
    WantedBy=multi-user.target

再使用

  • 配置开机启动 systemctl enable tomcat
  • 启动tomcat systemctl start tomcat
  • 停止tomcat systemctl stop tomcat
  • 重启tomcat systemctl restart tomcat

说明

  • 因为配置pid,在启动的时候会再tomcat根目录生成tomcat.pid文件,停止之后删除
  • 同时tomcat在启动时候,执行start不会启动两个tomcat,保证始终只有一个tomcat服务在运行
  • 多个tomcat可以配置在多个目录下,互不影响。

遇到2个很尴尬的问题

  • 这个问题我尝试过很多次,那就是Tomcat启动的特别慢,后来查看日志发现是部署项目的时候花费时间特别长,详细看这里
  • 遇到无法启动的问题,最后是startup.sh没有权限,你知道该怎么做的~~

使用yum安装的tomcat注意

  • 安装位置 /etc/tomcat
  • 主程序/软件存放webapp位置 /var/lib/tomcat/webapps
  • 在Centos使用yum安装后,Tomcat相关的目录都已采用符号链接到/usr/share/tomcat6目录,包含webapps等,这很方便我们配置管理 /usr/share/tomcat
  • 日志记录位置 /var/log/tomcat
  • 查看全部tomcat安装目录 rpm -ql tomcat6 | cat -n

Mysql5.7数据库安装

说明

开始

  • 在mysql的官网上找到mysql的源链接

Mysql官网截图

  • 找到原链接:https://repo.mysql.com//mysql57-community-release-el7-9.noarch.rpm

安装

  • 安装命令

    1
    2
    3
    # wget https://repo.mysql.com//mysql57-community-release-el7-9.noarch.rpm
    # rpm -ivh mysql57-community-release-el7-9.noarch.rpm
    # yum install mysql-community-server
  • 安装过程中有确认操作,一律y

  • 接下来就是漫长的下载,只需要等待即可。

启动

  • 安装完成后: systemctl start mysqld 启动mysqld服务

配置

  • 查看/var/log/mysqld.log里的日志(可以查找password关键字)
    mysqlLog
  • 可以看到创建的临时密码
  • 登录MySQL:mysql -u root -p
  • 输入刚才在日志里看到的临时密码
  • 这个时候我输入任何的命令都会提示You must reset your password using ALTER USER statement before executing this statement.
    alterTips
  • 通过 alter user 'root'@'localhost' identified by 'root' 命令,修改 root 用户的密码为 root,注意修改的密码不能过于简单
  • 退出,重新以root用户和刚设置的密码进行登录即可

MySql配置文件

  • 将所有权限赋给root用户并提供外网访问

    1
    grant all privileges on *.* to root@'%'identified by 'root';
  • 紧接着就可以在自己的机器上用Navicat

  • 配置my.cnf:/etc/my.cnf

    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
    [mysqld]
    #
    # Remove leading # and set to the amount of RAM for the most important data
    # cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
    # innodb_buffer_pool_size = 128M
    #
    # Remove leading # to turn on a very important data integrity option: logging
    # changes to the binary log between backups.
    # log_bin
    #
    # Remove leading # to set options mainly useful for reporting servers.
    # The server defaults are faster for transactions and fast SELECTs.
    # Adjust sizes as needed, experiment to find the optimal values.
    # join_buffer_size = 128M
    # sort_buffer_size = 2M
    # read_rnd_buffer_size = 2M
    datadir=/var/lib/mysql
    socket=/var/lib/mysql/mysql.sock
    //设置端口号
    port= 3333

    //设置服务器端编码
    character-set-server=utf8

    //Linux下表名是严格区分大小写的,设置为0表示区分,设置为1表示不区分
    lower_case_table_names= 1

    # Disabling symbolic-links is recommended to prevent assorted security risks
    symbolic-links=0

    log-error=/var/log/mysqld.log
    pid-file=/var/run/mysqld/mysqld.pid

MySQL卸载

  • yum remove mysql mysql-server mysql-libs mysql-server;
  • rpm -qa|grep mysql(查询出来的东西yum remove掉)
  • find / -name mysql (将找到的相关东西delete掉;)

值得注意的是:

  • 1.show variables like 'character%';可以看到数据库的编码方式
    • 其中,character_set_client为客户端编码方式;
    • character_set_connection为建立连接使用的编码;
    • character_set_database数据库的编码;
    • character_set_results结果集的编码;
    • character_set_server数据库服务器的编码;
    • 只要保证以上四个采用的编码方式一样,就不会出现乱码问题。
  • 2.暂时能配置只有这些,以后有更新,我会加上的

Redis的安装

说明

Linux安装redis是需要在官网下载redis的源码然后再编译的

下载安装

  • 下载linux版的压缩包,截止到我写博客的时间,官网给的稳定版是3.2.8,我们就下载redis-3.2.8.tar.gz
  • tar -zxvf redis-3.2.8.tar.gz
  • 进入src目录:cd redis-3.2.8/src
  • make 编译
  • 这时我们不执行make intsall,因为该操作会把编译生成的**重要的文件**拷贝到user/local/bin下,我们想要自定义配置路径

注意

  • 中间可能报/bin/sh: cc:未找到命令,对于这样的情况只需要

    1
    2
    yum install gcc 
    yum install gcc-c++
  • 这里的重要文件指的是下图所示的8个文件,在redis以前版本好像是7个文件(没具体试过)

  • 注意文件和文件夹的权限

路径配置

  • ls查看src下的文件,你会看到有些文件是绿色的,这些事重要的文件,也正是我们所需要的,我们将这些文件单独存下来
    Redis源码编译后的重要文件
  • 我们来看看编译出来的几个程序分别是干什么的:
    redis-server:顾名思义,redis服务
    redis-cli:redis client,提供一个redis客户端,以供连接到redis服务,进行增删改查等操作
    redis-sentinel:redis实例的监控管理、通知和实例失效备援服务
    redis-benchmark:redis的性能测试工具
    redis-check-aof:若以AOF方式产生日志,当意外发生时用来快速修复
    redis-check-rdb:若以RDB方式产生日志,当意外发生时用来快速修复
  • 保存好之后我们的路径如下:
    Redis根目录
    Redis-bin
    Redis-etc

配置文件配置

  • redis.conf我只修改了以下配置

    • port : 端口
    • requirepass 密码
    • bind 0.0.0.0 : 配置外网可访问
    • daemonize yes : 将redis服务作为守护进程,作为开机启动
  • 有了基本配置,redis还需要有一个管理启动、关闭、重启的一个脚本。redis源码里其实已经提供了一个初始化脚本redis_init_script,这是我的配置

    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
    #!/bin/sh
    # chkconfig: 2345 90 10
    # description: Redis is a persistent key-value database
    # Simple Redis init.d script conceived to work on Linux systems
    # as it does use of the /proc filesystem.
    # 如果redis设置了密码,则$CLIEXEC -a $PASSWORD -p $REDISPORT shutdown 需要加一个参数

    REDISPORT=6379
    PASSWORD=123
    EXEC=/project/redis3.2.8/bin/redis-server
    CLIEXEC=/project/redis3.2.8/bin/redis-cli

    PIDFILE=/var/run/redis_${REDISPORT}.pid
    CONF=/project/redis3.2.8/etc/redis.conf

    case "$1" in
    start)
    if [ -f $PIDFILE ]
    then
    echo "$PIDFILE exists, process is already running or crashed"
    else
    echo "Starting Redis server..."
    $EXEC $CONF
    fi
    ;;
    stop)
    if [ ! -f $PIDFILE ]
    then
    echo "$PIDFILE does not exist, process is not running"
    else
    PID=$(cat $PIDFILE)
    echo "Stopping ..."
    $CLIEXEC -a $PASSWORD -p $REDISPORT shutdown
    while [ -x /proc/${PID} ]
    do
    echo "Waiting for Redis to shutdown ..."
    sleep 1
    done
    echo "Redis stopped"
    fi
    ;;
    *)
    echo "Please use start or stop as first argument"
    ;;
    esac

  • 头部的chkconfig的添加是为了保证chkconfig redis on能够执行

  • 接着将redis_init_script脚本拷贝到**/etc/init.d/redis**,这里重命名为redis

    1
    # cp /project/redis-3.2.8/utils/redis_init_script /etc/init.d/redis
  • 现在还缺一个系统启动时的配置:chkconfig redis on

  • 执行之后,redis便是以系统服务启动、关闭了

    1
    2
    3
    systemctl start redis;
    systemctl stop redis;
    systemctl restart redis;

FTP 服务端安装

  1. yum install vsftpd
  2. 修改 vim /etc/vsftpd/vsftpd.conf
    anonymous_enable=NO : 不允许匿名用户登录
    chroot_local_user=YES : 用户不能跳出当前的 home 目录
  3. 安装好之后其实已经创建了个 ftp 的用户了,可以查看 /etc/passwd 文件,只是没有密码,这个时候使用 ftp 工具来登录是可以看到目录的,默认 ftp 的 home 目录在 /var/ftp/ 下
  4. 修改 ftp 用户密码 passwd ftp
  5. 这时使用 ftp 用户登录,发现不能 上传文件和删除文件,有 553 错误

解决不能上传和删除文件

  1. 首先 selinux 会默认拦截 vsftp,想要不被拦截的话,可以关闭 selinux, 但是关闭后安全性得不到保障,可能会出现其他的问题,这里我们不关闭,可以开放权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    setenforce 0 #暂时让SELinux进入Permissive模式
    getsebool -a | grep ftpd #查看 ftpd 的权限

    ftpd_anon_write --> off
    ftpd_connect_all_unreserved --> off
    ftpd_connect_db --> off
    ftpd_full_access --> on
    ftpd_use_cifs --> off
    ftpd_use_fusefs --> off
    ftpd_use_nfs --> off
    ftpd_use_passive_mode --> off

    setsebool -P ftpd_full_access 1 # 设置ftpd_full_access的权限为 on

    setenforce 1 # 开启 selinux
  2. 这时 selinux 已经开放了 vsftpd 的权限

  3. 给 ftp 用户的 home 目录赋予写的权限 chmod a+w /var/ftp

  4. vsftpd 在新版本时,如果检测到用户不能跳出当前的 home 目录,那么用户的 home 不能有写的权限,会报 500 OOPS: vsftpd: refusing to run with writable root inside chroot() 错误,这时就尴尬了

  5. 解决方式: 在配置文件中添加: allow_writeable_chroot=YES

  6. 重启 vsftpd

无法连接 FTP 服务器

使用 FTP 工具连接失败,报如下错误:

1
2
3
4
5
6
7
8
状态: 	连接建立,等待欢迎消息...
状态: 不安全的服务器,不支持 FTP over TLS。
命令: USER ftp
响应: 331 Please specify the password.
命令: PASS ******
响应: 530 Login incorrect.
错误: 严重错误: 无法连接到服务器
状态: 已从服务器断开

解决方法: 找到 /etc/pam.d/vsftpd 注释掉 #auth required pam_shells.so

Docker 安装 FTP

1
docker run -d --name ftp-server --restart always -p 6106:21 -p 6107:20 -p 6108-6109:21100-21101 -v /data/etc-service-data/nginx-data/data/ftp:/home/vsftpd -e PASV_ADDRESS=112.29.246.234 -e FTP_USER=ky -e FTP_PASS='Kaiyuan@2020' -e PASV_MAX_PORT=21101 fauria/vsftpd

RabbitMQ 服务端安装

  1. yum install rabbitmq-server
  2. rabbitmq-plugins enable rabbitmq_management
  3. systemctl start rabbitmq-server

问题

  1. 当主机的 hostname 不是 localhost 时, 启动会报错: unable to connect to epmd (port 4369) on 68: badarg (unknown POSIX error) , 这时在文件中 /etc/rabbitmq/rabbitmq-env.conf 写入 NODENAME=rabbit@localhost 保存重启.

MariaDB 的安装

  1. yum install mariadb-server
  2. 登入 mariadb mysql -uroot -p , 第一次登陆是 root 用户没有密码,直接进入即可
  3. 初始化设置: mysql_secure_installation ,可以设置 root 密码,移除匿名账号等等…
  4. 设置 root 可远程登录:
    1. mysql -uroot -p 登录
    2. GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'IDENTIFIED BY '123456' WITH GRANT OPTION; 授权 root 账户,密码 123456
    3. flush privileges;
  5. 继续后续操作

Mybatis 连接 MariaDB 中文乱码问题

MariaDB的默认编码是latin1,插入中文会乱码,因此需要将编码改为utf8

  1. 首先设置数据库的编码都为 utf8

    1. SHOW VARIABLES LIKE 'character%'; 查看编码
    2. 修改 /etc/my.cnf.d/client.cnf , 在 [client] 里加入 default-character-set=utf8
    3. 修改 /etc/my.cnf.d/server.cnf , 在 [mysqld] 里加入 character-set-server=utf8
    4. systemctl restart mariadb 重启生效
    5. 再次查看 SHOW VARIABLES LIKE 'character%'; 查看编码
  2. 建库,建表,表里的 varchar 字段的字符集都用 utf8, 排序规则都用 utf8_unicode_ci

  3. 至此服务端就配置完成了

  4. 连接数据配置文件,加上参数

1
2
3
datasource:
druid:
url: jdbc:mysql://34.0.7.183:3306/traffic-service?useUnicode=true&characterEncoding=utf8&useSSL=false

DNS 服务安装

  1. yum install bind 安装完成后服务名为 named
  2. vim /etc/named.conf
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
options {
listen-on port 53 { 34.0.7.183; };
listen-on-v6 port 53 { ::1; };
directory "/var/named";
dump-file "/var/named/data/cache_dump.db";
statistics-file "/var/named/data/named_stats.txt";
memstatistics-file "/var/named/data/named_mem_stats.txt";
recursing-file "/var/named/data/named.recursing";
secroots-file "/var/named/data/named.secroots";
allow-query { any; };

/*
- If you are building an AUTHORITATIVE DNS server, do NOT enable recursion.
- If you are building a RECURSIVE (caching) DNS server, you need to enable
recursion.
- If your recursive DNS server has a public IP address, you MUST enable access
control to limit queries to your legitimate users. Failing to do so will
cause your server to become part of large scale DNS amplification
attacks. Implementing BCP38 within your network would greatly
reduce such attack surface
*/
recursion yes;

dnssec-enable yes;
dnssec-validation yes;

/* Path to ISC DLV key */
bindkeys-file "/etc/named.iscdlv.key";

managed-keys-directory "/var/named/dynamic";

pid-file "/run/named/named.pid";
session-keyfile "/run/named/session.key";
};

logging {
channel default_debug {
file "data/named.run";
severity dynamic;
};
};

zone "." IN {
type hint;
file "named.ca";
};

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";

listen-on port 53 { 127.0.0.1; }; # 指定服务监听的端口,建议写本机IP,减少服务器消耗
allow-query { any; }; # 允许哪些客户端访问DNS服务,此处改为“any”,表示任意主机

修改这2项配置即可

配置自定义域名解析

include "/etc/named.rfc1912.zones"; # include代表该文件是子配置文件

  1. vim /etc/named.rfc1912.zones , 添加一个我们自定义的域名配置,这里我使用的是 baidu.com
1
2
3
4
5
zone "baidu.com" IN {
type master;
file "data/baidu.com.zone";
allow-update { none; };
};

上述文件默认的目录在 /var/named/data 目录下

  1. vim /var/named/data/baidu.com.zone

配置如下: 注意格式

1
2
3
4
5
6
7
8
9
10
11
12
13
$TTL 1D
@ IN SOA baidu.com. root (
1 ; serial
1D ; refresh
1H ; retry
1W ; expire
0 ) ; minimum

@ IN NS ns.baidu.com.
ns IN A 34.0.7.183
@ IN A 34.0.7.183
test IN A 34.0.7.183
liufa IN A 34.0.7.227

注意第一条记录 ns.baidu.com. 的解析必须添加否则会报错,添加之后,再加一条 ns 子域名的解析,直接指向自己即可

这里附上一些配置的解释:

  • serial:序列号。可以供从服务器判断何时获取新数据的,这里我设成今天的日期。更新数据文件必须要更新这个序列号,否则从服务器将不更新
  • refresh:指定多长时间从服务器要与主服务器进行核对
  • retry:如果从服务器试图检查主服务器的序列号时,主服务器没有响应,则经过这个时间后将重新进行检查
  • expire:将决定从服务器在没有主服务器的情况下权威地持续提供域数据服务的时间长短
  • minimum:高速缓存否定回答的存活时间
  • SOA记录:每个区仅有一个SOA记录,该区一直延伸到遇见另一个SOA记录为止。SOA记录包括区的名字,一个技术联系人和各种不同的超时值
  • IN记录:使用“IN”,对应的是internet
  • A记录:是DNS数据库的核心。一个主机必须为它的每个网络接口得到一条A记录
  • NS记录:识别对一个区有权威性的服务器(即所有主服务器和从服务器),并把子域委托给其他机构。
  • MX记录:电子邮件系统就是使用MX记录来更有效的路由邮件。
  • PTR记录:从IP地址到主机名的反向映射。与A记录一样,必须为每个网络接口有一条PTR记录。
  1. chown root:named baidu.com.zone 修改权限
  2. systemctl restart named

PostgreSQL 安装

说明

yum 安装的版本可能比较低,对于一些应用来说可能不好,这里使用官网的安装包

安装

  1. 打开 https://www.postgresql.org/download/linux/redhat/
  2. 选择版本
  3. 安装源,我这里以 10 为例, yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
  4. 安装程序, yum install postgresql10 , yum install postgresql10-server
  5. 初始化数据库 /usr/pgsql-10/bin/postgresql-10-setup initdb
  6. 开机启动 systemctl enable postgresql-10;systemctl start postgresql-10

修改管理员密码

数据库的默认管理员用户是 postgres, 在 PostgreSQL 安装好后会创建用户 postgres

  1. 切换到 postgres 用户 su postgres
  2. 执行 psql 登陆数据库
  3. 修改密码: ALTER USER postgres WITH PASSWORD 'postgres';

配置远程访问

  1. 修改 postgresql.conf 文件的 listen_address = *
  2. 用户授权: 修改 pg_hba.conf 添加 host all all 0.0.0.0/0 password 一行

说明:

  • ident: 系统用户和数据库用户对于即可访问
  • md5: md5 加密的密码认证
  • password: 明文密码
  • trust: 无需密码
  • reject: 拒接认证

修改数据存放目录

  1. 修改 /usr/lib/systemd/system/postgresql-10.servicePGDATA
  2. systemctl daemon-reload
  3. postgresql-step initdb 初始化数据库,遇到权限问题,先创建好目录
    1. chrow -R postgres:postgres /…
    2. chmod -R 700 /…
    3. 实在不行就使用 postgres 的身份操作
  4. 如果之前数据库有数据,可以直接拷贝数据目录,之后目录的权限设定好即可
0%