diff --git a/2016/04/22/hello-world/index.html b/2016/04/22/hello-world/index.html new file mode 100644 index 0000000..6fe21dc --- /dev/null +++ b/2016/04/22/hello-world/index.html @@ -0,0 +1,202 @@ + + + + + + Hello World | Hexo + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+ + +

+ Hello World +

+ + +
+ +
+ +

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

+

Quick Start

Create a new post

1
$ hexo new "My New Post"
+

More info: Writing

+

Run server

1
$ hexo server
+

More info: Server

+

Generate static files

1
$ hexo generate
+

More info: Generating

+

Deploy to remote sites

1
$ hexo deploy
+

More info: Deployment

+ + +
+ +
+ + + + + +
+ +
+ + + +
+
+ +
+ +
+
+
+ + + + + + + + + + + + +
+ + \ No newline at end of file diff --git "a/2016/04/28/HTML5\345\256\236\346\210\230\342\200\224\342\200\224svg\345\255\246\344\271\240/index.html" "b/2016/04/28/HTML5\345\256\236\346\210\230\342\200\224\342\200\224svg\345\255\246\344\271\240/index.html" deleted file mode 100644 index 1bc802f..0000000 --- "a/2016/04/28/HTML5\345\256\236\346\210\230\342\200\224\342\200\224svg\345\255\246\344\271\240/index.html" +++ /dev/null @@ -1,378 +0,0 @@ - - - - - - - HTML5实战——svg学习 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -
- -
-
- - -
- - - -
- - - - -
- - -

- HTML5实战——svg学习 -

- - -
- - - - -
- -

  SVG可缩放矢量图形(Scalable Vector Graphics)是基于可扩展标记语言(XML),用于描述二维矢量图形的一种图形格式。SVG是W3C制定的一种新的二维矢量图形格式,也是规范中的网络矢量图形标准。SVG严格遵从XML语法,并用文本格式的描述性语言来描述图像内容,因此是一种和图像分辨率无关的矢量图形格式。
什么是SVG?
  SVG 指可伸缩矢量图形 (Scalable Vector Graphics)
  SVG 用来定义用于网络的基于矢量的图形
  SVG 使用 XML 格式定义图形
  SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
  SVG 是万维网联盟的标准
  SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体
Canvas 和 SVG 的区别:
  SVG
    SVG 是一种使用 XML 描述 2D 图形的语言。
    SVG 基于 XML,这意味着 SVG DOM 中的每个元素都是可用的。您可以为某个元素附加 JavaScript 事件处理器。
    在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。
    特点:
       不依赖分辨率
       支持事件处理器
       最适合带有大型渲染区域的应用程序(比如谷歌地图)
       复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)
       不适合游戏应用
  Canvas
    Canvas 通过 JavaScript 来绘制 2D 图形。
    Canvas 是逐像素进行渲染的。
    在 canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象。
    特点:
      依赖分辨率
       不支持事件处理器
       弱的文本渲染能力
       能够以 .png 或 .jpg 格式保存结果图像
       最适合图像密集型的游戏,其中的许多对象会被频繁重绘
svg 例子:

- - - -

学习svg非常不错的系列博文
突袭HTML5之SVG 2D入门1 - SVG综述
突袭HTML5之SVG 2D入门2 - 图形绘制
突袭HTML5之SVG 2D入门3 - 文本与图像
突袭HTML5之SVG 2D入门4 - 笔画与填充
突袭HTML5之SVG 2D入门5 - 颜色的表示
突袭HTML5之SVG 2D入门6 - 坐标与变换
突袭HTML5之SVG 2D入门7 - 重用与引用
突袭HTML5之SVG 2D入门8 - 文档结构
突袭HTML5之SVG 2D入门9 - 蒙板
突袭HTML5之SVG 2D入门10 - 滤镜
突袭HTML5之SVG 2D入门11 - 动画
突袭HTML5之SVG 2D入门12 - SVG DOM
突袭HTML5之SVG 2D入门13 - svg对决canvas

-

参考:http://www.w3school.com.cn/svg/svg_intro.asp

- - -
- -
- - - - - -
- - - - - - - - - -
- -
- - - - -
- - - - -
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - -
- - \ No newline at end of file diff --git "a/2016/04/28/HTjavascript-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217/index.html" "b/2016/04/28/HTjavascript-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217/index.html" deleted file mode 100644 index 5056223..0000000 --- "a/2016/04/28/HTjavascript-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217/index.html" +++ /dev/null @@ -1,391 +0,0 @@ - - - - - - - javascript---正则表达式 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -
- -
-
- - -
- - - -
- - - - -
- - -

- javascript---正则表达式 -

- - -
- - - - -
- -

gExp

-

阅读: 1981
字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在。比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦,而且代码难以复用。
正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
所以我们判断一个字符串是否是合法的Email的方法是:
创建一个匹配Email的正则表达式;
用该正则表达式去匹配用户的输入来判断是否合法。
因为正则表达式也是用字符串表示的,所以,我们要首先了解如何用字符来描述字符。
在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字,所以:
‘00\d’可以匹配’007’,但无法匹配’00A’;
‘\d\d\d’可以匹配’010’;
‘\w\w’可以匹配’js’;
.可以匹配任意字符,所以:
‘js.’可以匹配’jsp’、’jss’、’js!’等等。
要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:
来看一个复杂的例子:\d{3}\s+\d{3,8}。
我们来从左到右解读一下:
\d{3}表示匹配3个数字,例如’010’;
\s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配’ ‘,’\t\t’等;
\d{3,8}表示3-8个数字,例如’1234567’。
综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。
如果要匹配’010-12345’这样的号码呢?由于’-‘是特殊字符,在正则表达式中,要用’\’转义,所以,上面的正则是\d{3}-\d{3,8}。
但是,仍然无法匹配’010 - 12345’,因为带有空格。所以我们需要更复杂的匹配方式。
进阶

-

要做更精确地匹配,可以用[]表示范围,比如:
[0-9a-zA-Z_]可以匹配一个数字、字母或者下划线;
[0-9a-zA-Z_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如’a100’,’0Z’,’js2015’等等;
[a-zA-Z\
\$][0-9a-zA-Z_\$]*可以匹配由字母或下划线、$开头,后接任意个由一个数字、字母或者下划线、$组成的字符串,也就是JavaScript允许的变量名;
[a-zA-Z_\$][0-9a-zA-Z_\$]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
A|B可以匹配A或B,所以[J|j]ava[S|s]cript可以匹配’JavaScript’、’Javascript’、’javaScript’或者’javascript’。
^表示行的开头,^\d表示必须以数字开头。
$表示行的结束,\d$表示必须以数字结束。
你可能注意到了,js也可以匹配’jsp’,但是加上^js$就变成了整行匹配,就只能匹配’js’了。
RegExp

-

有了准备知识,我们就可以在JavaScript中使用正则表达式了。
JavaScript有两种方式创建一个正则表达式:
第一种方式是直接通过/正则表达式/写出来,第二种方式是通过new RegExp(‘正则表达式’)创建一个RegExp对象。
两种写法是一样的:
var re1 = /ABC-001/;
var re2 = new RegExp(‘ABC\-001’);

-

re1; // /ABC-001/
re2; // /ABC-001/
注意,如果使用第二种写法,因为字符串的转义问题,字符串的两个\实际上是一个\。
先看看如何判断正则表达式是否匹配:
var re = /^\d{3}-\d{3,8}$/;
re.test(‘010-12345’); //true
re.test(‘010-1234x’); //false
re.test(‘010 12345’); //false
RegExp对象的test()方法用于测试给定的字符串是否符合条件。
切分字符串

-

用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
‘a b c’.split(‘ ‘); // [‘a’, ‘b’, ‘’, ‘’, ‘c’]
嗯,无法识别连续的空格,用正则表达式试试:
‘a b c’.split(/\s+/); // [‘a’, ‘b’, ‘c’]
无论多少个空格都可以正常分割。加入,试试:
‘a,b, c d’.split(/[\s\,]+/); // [‘a’, ‘b’, ‘c’, ‘d’]
再加入;试试:
‘a,b;; c d’.split(/[\s\,\;]+/); // [‘a’, ‘b’, ‘c’, ‘d’]
如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。
分组

-

除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。比如:
^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
var re = /^(\d{3})-(\d{3,8})$/;
re.exec(‘010-12345’); // [‘010-12345’, ‘010’, ‘12345’]
re.exec(‘010 12345’); // null
如果正则表达式中定义了组,就可以在RegExp对象上用exec()方法提取出子串来。
exec()方法在匹配成功后,会返回一个Array,第一个元素始终是原始字符串本身,后面的字符串表示匹配成功的子串。
exec()方法在匹配失败时返回null。
提取子串非常有用。来看一个更凶残的例子:
var re = /^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/;
re.exec(‘19:05:30’); // [‘19:05:30’, ‘19’, ‘05’, ‘30’]
这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
var re = /^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$/;
对于’2-30’,’4-31’这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。
贪婪匹配

-

需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0:
var re = /^(\d+)(0)$/;
re.exec(‘102300’); // [‘102300’, ‘102300’, ‘’]
由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0
只能匹配空字符串了。
必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:
var re = /^(\d+?)(0*)$/;
re.exec(‘102300’); // [‘102300’, ‘1023’, ‘00’]
全局搜索

-

JavaScript的正则表达式还有几个特殊的标志,最常用的是g,表示全局匹配:
var r1 = /test/g;
// 等价于:var r2 = new RegExp(‘test’, ‘g’);
全局匹配可以多次执行exec()方法来搜索一个匹配的字符串。当我们指定g标志后,每次运行exec(),正则表达式本身会更新lastIndex属性,表示上次匹配到的最后索引:
var s = ‘JavaScript, VBScript, JScript and ECMAScript’;
var re=/[a-zA-Z]+Script/g;

-

// 使用全局匹配:
re.exec(s); // [‘JavaScript’]
re.lastIndex; // 10

-

re.exec(s); // [‘VBScript’]
re.lastIndex; // 20

-

re.exec(s); // [‘JScript’]
re.lastIndex; // 29

-

re.exec(s); // [‘ECMAScript’]
re.lastIndex; // 44

-

re.exec(s); // null,直到结束仍没有匹配到
全局匹配类似搜索,因此不能使用/^…$/,那样只会最多匹配一次。
正则表达式还可以指定i标志,表示忽略大小写,m标志,表示执行多行匹配。
小结

-

正则表达式非常强大,要在短短的一节里讲完是不可能的。要讲清楚正则的所有内容,可以写一本厚厚的书了。如果你经常遇到正则表达式的问题,你可能需要一本正则表达式的参考书。
练习

-

请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email:
‘use strict’;
// 测试:
var
i,
success = true,
should_pass = [‘someone@gmail.com’, ‘bill.gates@microsoft.com’, ‘tom@voyager.org’, ‘bob2015@163.com’],
should_fail = [‘test#gmail.com’, ‘bill@microsoft’, ‘bill%gates@ms.com’, ‘@voyager.org’];
for (i = 0; i < should_pass.length; i++) {
if (!re.test(should_pass[i])) {
alert(‘测试失败: ‘ + should_pass[i]);
success = false;
break;
}
}
for (i = 0; i < should_fail.length; i++) {
if (re.test(should_fail[i])) {
alert(‘测试失败: ‘ + should_fail[i]);
success = false;
break;
}
}
if (success) {
alert(‘测试通过!’);
}
版本二可以验证并提取出带名字的Email地址:
‘use strict’;
// 测试:
var r = re.exec(‘ tom@voyager.org’);
if (r === null || r.toString() !== [‘ tom@voyager.org’, ‘Tom Paris’, ‘tom@voyager.org’].toString()) {
alert(‘测试失败!’);
}
else {
alert(‘测试成功!’);
}

- - -
- -
- - - - - -
- - - - - - - - - -
- -
- - - - -
- - - - -
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - -
- - \ No newline at end of file diff --git "a/2016/04/28/Hexo\346\220\255\345\273\272Github\351\235\231\346\200\201\345\215\232\345\256\242/index.html" "b/2016/04/28/Hexo\346\220\255\345\273\272Github\351\235\231\346\200\201\345\215\232\345\256\242/index.html" deleted file mode 100644 index 8ec79b6..0000000 --- "a/2016/04/28/Hexo\346\220\255\345\273\272Github\351\235\231\346\200\201\345\215\232\345\256\242/index.html" +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - Hexo搭建Github静态博客 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -
- -
-
- - -
- - - -
- - - - -
- - -

- Hexo搭建Github静态博客 -

- - -
- - - - -
- -
    -
  1. 环境环境
    1.1 安装Git
  2. -
-

请参考【1】

-

1.2 安装node.js

-

下载:http://nodejs.org/download/

-

可以下载 node-v0.10.33-x64.msi

-

安装时直接保持默认配置即可。

-
    -
  1. 配置Github
    1.1 建立Repository
  2. -
-

建立与你用户名对应的仓库,仓库名必须为【your_user_name.github.io】

-

1.2 配置SSH-Key

-

参考【1】

-
    -
  1. 安装Hexo
    关于Hexo的安装配置过程,请以官方Hexo【2】给出的步骤为准。
  2. -
-

3.1 Installation

-

打开Git命令行,执行如下命令

-

$ npm install -g hexo
3.2 Quick Start

-
    -
  1. Setup your blog
  2. -
-

在电脑中建立一个名字叫「Hexo」的文件夹(比如我建在了D:\Hexo),然后在此文件夹中右键打开Git Bash。执行下面的命令

-

$ hexo init
[info] Copying data
[info] You are almost done! Don’t forget to run npm install before you start b
logging with Hexo!
Hexo随后会自动在目标文件夹建立网站所需要的文件。然后按照提示,运行 npm install(在 /D/Hexo下)

-

npm install
会在D:\Hexo目录中安装 node_modules。

-
    -
  1. Start the server
  2. -
-

运行下面的命令(在 /D/Hexo下)

-

$ hexo server
[info] Hexo is running at http://localhost:4000/. Press Ctrl+C to stop.
表明Hexo Server已经启动了,在浏览器中打开 http://localhost:4000/,这时可以看到Hexo已为你生成了一篇blog。

-

你可以按Ctrl+C 停止Server。

-
    -
  1. Create a new post
  2. -
-

新打开一个git bash命令行窗口,cd到/D/Hexo下,执行下面的命令

-

$ hexo new “My New Post”
[info] File created at d:\Hexo\source_posts\My-New-Post.md
刷新http://localhost:4000/,可以发现已生成了一篇新文章 “My New Post”。

-

NOTE:

-

有一个问题,发现 “My New Post” 被发了2遍,在Hexo server所在的git bash窗口也能看到create了2次。

-

$ hexo server
[info] Hexo is running at http://localhost:4000/. Press Ctrl+C to stop.
[create] d:\Hexo\source_posts\My-New-Post.md
[create] d:\Hexo\source_posts\My-New-Post.md
经验证,在hexo new “My New Post” 时,如果按Ctrl+C将hexo server停掉,就不会出现发2次的问题了。

-

所以,在hexo new文章时,需要stop server。

-
    -
  1. Generate static files
  2. -
-

执行下面的命令,将markdown文件生成静态网页。

-

$ hexo generate
该命令执行完后,会在 D:\Hexo\public\ 目录下生成一系列html,css等文件。

-
    -
  1. 编辑文章
  2. -
-

hexo new “My New Post”会在D:\Hexo\source_posts目录下生成一个markdown文件:My-New-Post.md

-

可以使用一个支持markdown语法的编辑器(比如 Sublime Text 2)来编辑该文件。

-
    -
  1. 部署到Github
  2. -
-

部署到Github前需要配置_config.yml文件,首先找到下面的内容

-

Deployment

Docs: http://hexo.io/docs/deployment.html

deploy:
type:
然后将它们修改为

-

Deployment

Docs: http://hexo.io/docs/deployment.html

deploy:
type: github
repository: git@github.com:zhchnchn/zhchnchn.github.io.git
branch: master
NOTE1:

-

Repository:必须是SSH形式的url(git@github.com:zhchnchn/zhchnchn.github.io.git),而不能是HTTPS形式的url(https://github.com/zhchnchn/zhchnchn.github.io.git),否则会出现错误:

-

$ hexo deploy
[info] Start deploying: github
[error] https://github.com/zhchnchn/zhchnchn.github.io is not a valid repositor URL!
使用SSH url,如果电脑没有开放SSH 端口,会致部署失败。

-

fatal: Could not read from remote repository.

-

Please make sure you have the correct access rights
and the repository exists.
NOTE2:

-

如果你是为一个项目制作网站,那么需要把branch设置为gh-pages。

-
    -
  1. 测试
  2. -
-

当部署完成后,在浏览器中打开http://zhchnchn.github.io/(https://zhchnchn.github.io/) ,正常显示网页,表明部署成功。

-
    -
  1. 总结:部署步骤
  2. -
-

每次部署的步骤,可按以下三步来进行。

-

hexo clean
hexo generate
hexo deploy

-
    -
  1. 总结:本地调试

    -
  2. -
  3. 在执行下面的命令后,

    -
  4. -
-

$ hexo g #生成
$ hexo s #启动本地服务,进行文章预览调试
浏览器输入http://localhost:4000,查看搭建效果。此后的每次变更_config.yml 文件或者新建文件都可以先用此命令调试,尤其是当你想调试新添加的主题时。

-
    -
  1. 可以用简化的一条命令
  2. -
-

hexo s -g
3.3 命令总结

-

3.3.1 常用命令

-

复制代码
hexo new “postName” #新建文章
hexo new page “pageName” #新建页面
hexo generate #生成静态页面至public目录
hexo server #开启预览访问端口(默认端口4000,’ctrl + c’关闭server)
hexo deploy #将.deploy目录部署到GitHub
hexo help # 查看帮助
hexo version #查看Hexo的版本
复制代码
3.3.2 复合命令

-

hexo deploy -g #生成加部署
hexo server -g #生成加预览
命令的简写为:

-

hexo n == hexo new
hexo g == hexo generate
hexo s == hexo server
hexo d == hexo deploy
4 配置Hexo
4.1 配置文件介绍

-

下面的各个部分的介绍,请直接参考【3】。

-
    -
  1. 默认目录结构介绍

    -
  2. -
  3. _config.yml配置文件介绍

    -
  4. -
-

NOTE:在修改_config.yml配置文件时,按照【3】的介绍进行修改后,重新 hexo clean 或者hexo deploy时,可能会出现如下错误:

-

复制代码
$ hexo clean
[error] { name: ‘HexoError’,
reason: ‘can not read a block mapping entry; a multiline key may not be an imp
licit key’,
mark:
{ name: null,
buffer: ‘# Hexo Configuration\n## Docs: http://hexo.io/docs/configuration.h
tml\n## Source: https://github.com/hexojs/hexo/\n\n# Site\ntitle: Zhchnchn\nsubt
itle: Coding on the way\ndescription: Zhchnchn\’s blog\nauthor: Zhchnchn\nemail:
115063497@qq.com\nlanguage:zh-CN\n\n# URL\n## If your site is put in a subdirect
……
,
position: 249,
line: 12,
column: 0 },
message: ‘Config file load failed’,
domain:
{ domain: null,
_events: { error: [Function] },
_maxListeners: 10,
members: [ [Object] ] },
domainThrown: true,
stack: undefined }
复制代码
我的_config.yml配置文件是一个空行,所以错误肯定在前面,经过对比发现,我前面修改了一下 # Site的各项设置,在冒号:后面没留空格导致了该问题,请对比一下下面的区别:

-

错误的设置:

-

author:Zhchnchn
email:XXX@qq.com
language:zh-CN
正确的设置:

-

author: Zhchnchn
email: XXX@qq.com
language: zh-CN

-
    -
  1. 各个主题下的目录介绍(hexo\themes\下的modernist主题为例)
  2. -
-

4.2 安装主题

-

Hexo提供了很多主题,具体可参见Hexo Themes【4】。这里我选择使用Pacman主题。具体设置方法如下【5】

-

4.2.1 安装

-
    -
  1. 将Git Shell 切到/D/Hexo目录下,然后执行下面的命令,将pacman下载到 themes/pacman 目录下。
  2. -
-

$ git clone https://github.com/A-limon/pacman.git themes/pacman

-
    -
  1. 修改你的博客根目录/D/Hexo下的config.yml配置文件中的theme属性,将其设置为pacman。

    -
  2. -
  3. 更新pacman主题

    -
  4. -
-

cd themes/pacman
git pull
NOTE:先备份_config.yml 文件后再升级

-

4.2.2 配置

-

如果pacman的默认设置不能满足需要的话,你可以修改 /themes/pacman/下的配置文件_config.yml来定制。

-

各个config的含义,请参考【5】中的介绍。

-

4.2.3 评论框

-

静态博客要使用第三方评论系统,pacman配置了多说评论系统(/themes/pacman/_config.yml),默认关闭,只要将其打开即可:false->true。直接用你的微博/豆瓣/人人/百度/开心网帐号登录多说,即可发表平评论。

-

Comment

duoshuo:
enable: true ## duoshuo.com
short_name: ## duoshuo short name.
4.2.3 统计

-
    -
  1. pacman配置了google analysis系统(/themes/pacman/_config.yml),默认关闭,将其打开。

    -
  2. -
  3. 需要注册google analysis服务,以获得 跟踪 ID。

    -
  4. -
-

如果已有google账户的话,可以直接注册。注册时,需要正确填写 网站的URL。注册成功后,会得到一个跟踪ID,以及一段跟踪代码。

-
    -
  1. pacman配置了google analysis系统,将其打开
  2. -
-

Analytics

google_analytics:
enable: true
id: UA-57032437-1 ## e.g. UA-1766729-8 your google analytics ID.
site: auto ## e.g. yangjian.me your google analytics site or set the value as auto.

-
    -
  1. 在themes\pacman\layout_partial\google_analytics.ejs 中,已经将google的跟踪代码添加进来了【3】。
  2. -
-

复制代码
<% if (theme.google_analytics.enable){ %>

-


<% } %>
复制代码
而且会将/themes/pacman/_config.yml中的id和site值读取进来。

-
    -
  1. 如果设置不起作用,请试试在\themes\pacman\layout_partial\head.ejs文件中最后,之前,添加上下面的语句试试。
  2. -
-

<%- partial(‘google_analytics’) %>
4.3 Custom 404页面

-
    -
  1. 网上大多数教程都将其说的极其简单:“直接在根目录下创建自己的 404.html 就可以”。但我却在这儿废了不少时间,究其原因是大家觉得太简单而说的不够明白。“根目录下”指的不是Hexo目录下,而是Hexo/source目录下。

    -
  2. -
  3. 404.html的内容可以设置为下面的内容【6】(NOTE: _config.yml中的permalink_defaults属性不需要修改)。
    4.4 安装插件

    -
  4. -
-

4.4.1 sitemap插件

-
    -
  1. 可以将你站点地图提交给搜索引擎,文件路径\sitemap.xml。

    -
  2. -
  3. 安装

    -
  4. -
-

$ npm install hexo-generator-sitemap

-
    -
  1. 启用,修改Hexo_config.yml,增加以下内容
  2. -
-

复制代码

-

Extensions

Plugins:

-
    -
  • hexo-generator-sitemap
  • -
-

#sitemap
sitemap:
path: sitemap.xml
复制代码

-
    -
  1. 使用方法
  2. -
-

(1)访问 http://localhost:4000/sitemap.xml,即可看到站点地图。

-

(2)那么怎么将它显示在页面中呢【7】?

-

可以修改themes/pacman(也就是你正在使用的那个theme)下的 _config.yml,在 menu 节点下添加下面的内容(下面要介绍的RSS插件也同样)

-

menu:
Home: /
Archives: /archives
Rss: /atom.xml
Sitemap: /sitemap.xml
修改后的效果如图所示:

-
    -
  1. 如何向google提交sitemap
  2. -
-

Sitemap 可方便管理员通知搜索引擎他们网站上有哪些可供抓取的网页。向google提交自己hexo博客的sitemap,有助于让别人更好地通过google搜索到自己的博客。

-

如何向google提交sitemap,请参考【8】。

-
    -
  1. 升级插件
  2. -
-

$ npm update

-
    -
  1. 卸载插件
  2. -
-

$ npm uninstall hexo-generator-sitemap
4.4.2 feed插件

-
    -
  1. RSS的生成插件,你可以在配置显示你站点的RSS,文件路径\atom.xml。

    -
  2. -
  3. 安装

    -
  4. -
-

$ npm install hexo-generator-feed

-
    -
  1. 启用,修改Hexo_config.yml,增加以下内容
  2. -
-

复制代码

-

Extensions

Plugins:

-
    -
  • hexo-generator-feed
  • -
  • hexo-generator-sitemap
  • -
-

#Feed Atom
feed:
type: atom
path: atom.xml
limit: 20
复制代码
4.使用方法

-

参见sitemap插件介绍

-
    -
  1. 优化Hexo
    5.1 添加“Fork me on Github” ribbon
  2. -
-

给blog主页添加一个“Fork me on Github”的绶带(ribbon)【9】,比如选择了红色的ribbon,将相应代码复制到Hexo正在使用的theme下layout.ejs中。比如我使用的pacman theme,那么将下面的代码(注意将you改为你自己的github上的注册名)

-

Fork me on GitHub
粘贴到 themes\pacman\layout\layout.ejs中,放置在 最后,标签之前即可。

-

6 其他
6.1 中文乱码

-

在md 文件中写中文内容,发布出来后为乱码,原因是md的编码不对,将md文件另存为“UTF-8”编码的文件即可解决问题。

-

References
【1】Windows下Git安装指南(http://www.cnblogs.com/zhcncn/p/3787849.html)

-

【2】Hexo (https://github.com/hexojs/hexo)

-

【3】hexo你的博客(http://ibruce.info/2013/11/22/hexo-your-blog/)

-

【4】Hexo All Themes(https://github.com/hexojs/hexo/wiki/Themes)

-

【5】Pacman主题介绍(http://yangjian.me/pacman/hello/introducing-pacman-theme/)

-

【6】hexo添加404页面(http://ruocaiwu.github.io/2014/08/14/hexo%E6%B7%BB%E5%8A%A0404%E9%A1%B5%E9%9D%A2/)

-

【7】如何搭建一个独立博客——简明Github Pages与Hexo教程(http://cnfeat.com/2014/05/10/2014-05-11-how-to-build-a-blog/)

-

【8】如何向google提交sitemap(详细)(http://fionat.github.io/blog/2013/10/23/sitemap/)

-

【9】GitHub Ribbons(https://github.com/blog/273-github-ribbons)

-

分类: Browser Dev
标签: Browser Dev
好文要顶 关注我 收藏该文
金石开
关注 - 9
粉丝 - 33
+加关注
8
« 上一篇:VirtualBox中安装CentOS-6.6虚拟机
» 下一篇:Sublime Text 3安装与使用
posted @ 2014-11-14 17:57 金石开 阅读(29881) 评论(8) 编辑 收藏
评论列表

-

#1楼 2015-06-20 21:00 刘岩石
NOTE2:

-

如果你是为一个项目制作网站,那么需要把branch设置为gh-pages
请问这句话是什么意思?要是我想建立一个 读书 的栏目呢?
支持(0)反对(0)

-

#2楼 2015-06-20 21:26 刘岩石
还有的就是我写了branch:gh-pages部署在github分支也成功了,但是怎么通过github.io访问的时候显示失败,也就是怎么显示出来分支的内容
支持(0)反对(0)

-

#3楼 2015-06-20 21:28 刘岩石
请问我在本地还要生成一个gh-pages分支吗?
支持(0)反对(0)

-

#4楼 2015-06-20 21:35 刘岩石
hexo new “postName” #新建文章
hexo new page “pageName” #新建页面
这里的新建页面的意思是?和新建文章有什么区别吗?
支持(0)反对(0)

-

#5楼 2015-06-20 21:37 刘岩石
http://segmentfault.com/q/1010000000618915 解决了
支持(0)反对(0)

-

#6楼 2015-09-11 15:52 Vsdrop
1
2
3
4
5
6
使用SSH url,如果电脑没有开放SSH 端口,会致部署失败。

-

fatal: Could not read from remote repository.

-

Please make sure you have the correct access rights
and the repository exists.

-

请问这个问题在 win7 下如何解决 卡在这里了
支持(0)反对(0)

-

#7楼[楼主] 2015-09-14 16:57 金石开
@ Vsdrop
你是在公司配置的吗?公司的网络一般在域中已经把SSH端口禁掉了。你最好在自己的机器上配置。
支持(0)反对(0)

-

#8楼 2016-03-07 11:37 我不是管哥
@ 刘岩石
请问你这个问题是怎么解决的呢?我现在想把public下面的文件放在master分支下,其他的文件都放在新建的hexo分支下,这个怎么弄呢?
什么情况下需要新建gh-page分支呢?
支持(0)反对(0)
刷新评论刷新页面返回顶部
注册用户登录后才能发表评论,请 登录 或 注册,访问网站首页。
【推荐】50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
【推荐】融云即时通讯云-豆果美食、Faceu等亿级APP都在用

-

最新IT新闻:
· Gear VR太小儿科 三星宣布研发不插手机独立虚拟现实头盔
· 三星电子第一季度净利润45.6亿美元 同比增长13.6%
· 你不知道的关于计算机大师Dijkstra的事情
· 手机销号后五类捆绑须解除
· Facebook第一季度净利润15.1亿美元 同比增长195%
» 更多新闻…

-

最新知识库文章:
· 架构漫谈(九):理清技术、业务和架构的关系
· 架构漫谈(八):从架构的角度看如何写好代码
· 架构漫谈(七):不要空设架构师这个职位,给他实权
· 架构漫谈(六):软件架构到底是要解决什么问题?
· 架构漫谈(五):什么是软件
» 更多知识库文章…
历史上的今天:
2012-11-14 Little Tips
随笔档案(48)

-

2015年9月 (1)

- - -
- -
- - - - - -
- - - - - - - - - -
- -
- - - - -
- - - - -
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - -
- - \ No newline at end of file diff --git "a/2016/04/28/Java\346\223\215\344\275\234MongoDB/index.html" "b/2016/04/28/Java\346\223\215\344\275\234MongoDB/index.html" deleted file mode 100644 index f625481..0000000 --- "a/2016/04/28/Java\346\223\215\344\275\234MongoDB/index.html" +++ /dev/null @@ -1,441 +0,0 @@ - - - - - - - Java操作MongoDB | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -
- -
-
- - -
- - - -
- - - - -
- - -

- Java操作MongoDB -

- - -
- - - - -
- -

MongoDB for Java】Java操作MongoDB

-

上一篇文章: http://www.cnblogs.com/hoojo/archive/2011/06/01/2066426.html介绍到了在MongoDB的控制台完成MongoDB的数据操作,通过前一篇文章我们对MongoDB有了全面的认识和理解。现在我们就用Java来操作MongoDB的数据。

-

开发环境:
System:Windows
IDE:eclipse、MyEclipse 8
Database:mongoDB
开发依赖库:
JavaEE5、mongo-2.5.3.jar、junit-4.8.2.jar
Email:hoojo_@126.com
Blog:http://blog.csdn.net/IBM_hoojo
http://hoojo.cnblogs.com/

-

一、准备工作

-

1、 首先,下载mongoDB对Java支持的驱动包
驱动包下载地址:https://github.com/mongodb/mongo-java-driver/downloads
mongoDB对Java的相关支持、技术:http://www.mongodb.org/display/DOCS/Java+Language+Center
驱动源码下载:https://download.github.com/mongodb-mongo-java-driver-r2.6.1-7-g6037357.zip
在线查看源码:https://github.com/mongodb/mongo-java-driver
2、 下面建立一个JavaProject工程,导入下载下来的驱动包。即可在Java中使用mongoDB,目录如下:

-

二、Java操作MongoDB示例

-

在本示例之前你需要启动mongod.exe的服务,启动后,下面的程序才能顺利执行;

-

1、 建立SimpleTest.java,完成简单的mongoDB数据库操作
Mongo mongo = new Mongo();
这样就创建了一个MongoDB的数据库连接对象,它默认连接到当前机器的localhost地址,端口是27017。
DB db = mongo.getDB(“test”);
这样就获得了一个test的数据库,如果mongoDB中没有创建这个数据库也是可以正常运行的。如果你读过上一篇文章就知道,mongoDB可以在没有创建这个数据库的情况下,完成数据的添加操作。当添加的时候,没有这个库,mongoDB会自动创建当前数据库。
得到了db,下一步我们要获取一个“聚集集合DBCollection”,通过db对象的getCollection方法来完成。
DBCollection users = db.getCollection(“users”);
这样就获得了一个DBCollection,它相当于我们数据库的“表”。
查询所有数据
DBCursor cur = users.find();
while (cur.hasNext()) {
System.out.println(cur.next());
}

-

完整源码
package com.hoo.test;

-

import java.net.UnknownHostException;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.util.JSON;

-

/**

-
    -
  • function:MongoDB 简单示例
  • -
  • @author hoojo
  • -
  • @createDate 2011-5-24 下午02:42:29
  • -
  • @file SimpleTest.java
  • -
  • @package com.hoo.test
  • -
  • @project MongoDB
  • -
  • @blog http://blog.csdn.net/IBM_hoojo
  • -
  • @email hoojo_@126.com
  • -
  • @version 1.0
    */
    publicclass SimpleTest {
  • -
-

publicstaticvoid main(String[] args) throws UnknownHostException, MongoException {
Mongo mg = new Mongo();
//查询所有的Database
for (String name : mg.getDatabaseNames()) {
System.out.println(“dbName: “ + name);
}
DB db = mg.getDB(“test”);
//查询所有的聚集集合
for (String name : db.getCollectionNames()) {
System.out.println(“collectionName: “ + name);
}
DBCollection users = db.getCollection(“users”);
//查询所有的数据
DBCursor cur = users.find();
while (cur.hasNext()) {
System.out.println(cur.next());
}
System.out.println(cur.count());
System.out.println(cur.getCursorId());
System.out.println(JSON.serialize(cur));
}
}

-

2、 完成CRUD操作,首先建立一个MongoDB4CRUDTest.java,基本测试代码如下:
package com.hoo.test;

-

import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import org.bson.types.ObjectId;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.mongodb.BasicDBObject;
import com.mongodb.Bytes;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.QueryOperators;
import com.mongodb.util.JSON;

-

/**

-
    -
  • function:实现MongoDB的CRUD操作
  • -
  • @author hoojo
  • -
  • @createDate 2011-6-2 下午03:21:23
  • -
  • @file MongoDB4CRUDTest.java
  • -
  • @package com.hoo.test
  • -
  • @project MongoDB
  • -
  • @blog http://blog.csdn.net/IBM_hoojo
  • -
  • @email hoojo_@126.com
  • -
  • @version 1.0
    */
    publicclass MongoDB4CRUDTest {
    private Mongo mg = null;
    private DB db;
    private DBCollection users;
    @Before
    publicvoid init() {
    try {
    mg = new Mongo();
    -
    //mg = new Mongo(“localhost”, 27017);
    } catch (UnknownHostException e) {
    -    e.printStackTrace();
    -} catch (MongoException e) {
    -    e.printStackTrace();
    -}
    -
    //获取temp DB;如果默认没有创建,mongodb会自动创建
    db = mg.getDB("temp");
    -
    //获取users DBCollection;如果默认没有创建,mongodb会自动创建
    users = db.getCollection("users");
    -
    }
    @After
    publicvoid destory() {
    if (mg != null)
        mg.close();
    -mg = null;
    -db = null;
    -users = null;
    -System.gc();
    -
    }
    publicvoid print(Object o) {
    System.out.println(o);
    -
    }
    }
  • -
-

3、 添加操作
在添加操作之前,我们需要写个查询方法,来查询所有的数据。代码如下:
/**

-
    -
  • function: 查询所有数据
  • -
  • @author hoojo
  • -
  • @createDate 2011-6-2 下午03:22:40
    */
    privatevoid queryAll() {
    print(“查询users的所有数据:”);
    //db游标
    DBCursor cur = users.find();
    while (cur.hasNext()) {
    print(cur.next());
    -
    }
    }
  • -
-

@Test
publicvoid add() {
//先查询所有数据
queryAll();
print(“count: “ + users.count());
DBObject user = new BasicDBObject();
user.put(“name”, “hoojo”);
user.put(“age”, 24);
//users.save(user)保存,getN()获取影响行数
//print(users.save(user).getN());
//扩展字段,随意添加字段,不影响现有数据
user.put(“sex”, “男”);
print(users.save(user).getN());
//添加多条数据,传递Array对象
print(users.insert(user, new BasicDBObject(“name”, “tom”)).getN());
//添加List集合
List list = new ArrayList();
list.add(user);
DBObject user2 = new BasicDBObject(“name”, “lucy”);
user.put(“age”, 22);
list.add(user2);
//添加List集合
print(users.insert(list).getN());
//查询下数据,看看是否添加成功
print(“count: “ + users.count());
queryAll();
}

-

4、 删除数据
@Test
publicvoid remove() {
queryAll();
print(“删除id = 4de73f7acd812d61b4626a77:” + users.remove(new BasicDBObject(“_id”, new ObjectId(“4de73f7acd812d61b4626a77”))).getN());
print(“remove age >= 24: “ + users.remove(new BasicDBObject(“age”, new BasicDBObject(“$gte”, 24))).getN());
}

-

5、 修改数据
@Test
publicvoid modify() {
print(“修改:” + users.update(new BasicDBObject(“_id”, new ObjectId(“4dde25d06be7c53ffbd70906”)), new BasicDBObject(“age”, 99)).getN());
print(“修改:” + users.update(
new BasicDBObject(“_id”, new ObjectId(“4dde2b06feb038463ff09042”)),
new BasicDBObject(“age”, 121),
true,//如果数据库不存在,是否添加
false//多条修改
).getN());
print(“修改:” + users.update(
new BasicDBObject(“name”, “haha”),
new BasicDBObject(“name”, “dingding”),
true,//如果数据库不存在,是否添加
true//false只修改第一天,true如果有多条就不修改
).getN());
//当数据库不存在就不修改、不添加数据,当多条数据就不修改
//print(“修改多条:” + coll.updateMulti(new BasicDBObject(“_id”, new ObjectId(“4dde23616be7c19df07db42c”)), new BasicDBObject(“name”, “199”)));
}

-

6、 查询数据
@Test
publicvoid query() {
//查询所有
//queryAll();
//查询id = 4de73f7acd812d61b4626a77
print(“find id = 4de73f7acd812d61b4626a77: “ + users.find(new BasicDBObject(“_id”, new ObjectId(“4de73f7acd812d61b4626a77”))).toArray());
//查询age = 24
print(“find age = 24: “ + users.find(new BasicDBObject(“age”, 24)).toArray());
//查询age >= 24
print(“find age >= 24: “ + users.find(new BasicDBObject(“age”, new BasicDBObject(“$gte”, 24))).toArray());
print(“find age <= 24: “ + users.find(new BasicDBObject(“age”, new BasicDBObject(“$lte”, 24))).toArray());
print(“查询age!=25:” + users.find(new BasicDBObject(“age”, new BasicDBObject(“$ne”, 25))).toArray());
print(“查询age in 25/26/27:” + users.find(new BasicDBObject(“age”, new BasicDBObject(QueryOperators.IN, newint[] { 25, 26, 27 }))).toArray());
print(“查询age not in 25/26/27:” + users.find(new BasicDBObject(“age”, new BasicDBObject(QueryOperators.NIN, newint[] { 25, 26, 27 }))).toArray());
print(“查询age exists 排序:” + users.find(new BasicDBObject(“age”, new BasicDBObject(QueryOperators.EXISTS, true))).toArray());
print(“只查询age属性:” + users.find(null, new BasicDBObject(“age”, true)).toArray());
print(“只查属性:” + users.find(null, new BasicDBObject(“age”, true), 0, 2).toArray());
print(“只查属性:” + users.find(null, new BasicDBObject(“age”, true), 0, 2, Bytes.QUERYOPTION_NOTIMEOUT).toArray());
//只查询一条数据,多条去第一条
print(“findOne: “ + users.findOne());
print(“findOne: “ + users.findOne(new BasicDBObject(“age”, 26)));
print(“findOne: “ + users.findOne(new BasicDBObject(“age”, 26), new BasicDBObject(“name”, true)));
//查询修改、删除
print(“findAndRemove 查询age=25的数据,并且删除: “ + users.findAndRemove(new BasicDBObject(“age”, 25)));
//查询age=26的数据,并且修改name的值为Abc
print(“findAndModify: “ + users.findAndModify(new BasicDBObject(“age”, 26), new BasicDBObject(“name”, “Abc”)));
print(“findAndModify: “ + users.findAndModify(
new BasicDBObject(“age”, 28), //查询age=28的数据
new BasicDBObject(“name”, true), //查询name属性
new BasicDBObject(“age”, true), //按照age排序
false, //是否删除,true表示删除
new BasicDBObject(“name”, “Abc”), //修改的值,将name修改成Abc
true,
true));
queryAll();
}
mongoDB不支持联合查询、子查询,这需要我们自己在程序中完成。将查询的结果集在Java查询中进行需要的过滤即可。

-

7、 其他操作
publicvoid testOthers() {
DBObject user = new BasicDBObject();
user.put(“name”, “hoojo”);
user.put(“age”, 24);
//JSON 对象转换
print(“serialize: “ + JSON.serialize(user));
//反序列化
print(“parse: “ + JSON.parse(“{ \”name\” : \”hoojo\” , \”age\” : 24}”));
print(“判断temp Collection是否存在: “ + db.collectionExists(“temp”));
//如果不存在就创建
if (!db.collectionExists(“temp”)) {
DBObject options = new BasicDBObject();
options.put(“size”, 20);
options.put(“capped”, 20);
options.put(“max”, 20);
print(db.createCollection(“account”, options));
}
//设置db为只读
db.setReadOnly(true);
//只读不能写入数据
db.getCollection(“test”).save(user);
}

- - -
- -
- - - - - -
- - - - - - - - - -
- -
- - - - -
- - - - -
-
-
- -
-
-
- - - - - - - - - - - - - - - - - - -
- - \ No newline at end of file diff --git "a/2016/04/28/Markdown-\350\257\255\346\263\225\350\257\264\346\230\216-\347\256\200\344\275\223\344\270\255\346\226\207\347\211\210/index.html" "b/2016/04/28/Markdown-\350\257\255\346\263\225\350\257\264\346\230\216-\347\256\200\344\275\223\344\270\255\346\226\207\347\211\210/index.html" deleted file mode 100644 index 7ecfdee..0000000 --- "a/2016/04/28/Markdown-\350\257\255\346\263\225\350\257\264\346\230\216-\347\256\200\344\275\223\344\270\255\346\226\207\347\211\210/index.html" +++ /dev/null @@ -1,631 +0,0 @@ - - - - - - - Markdown 语法说明 (简体中文版) | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- -
- -
-
- - -
- - - -
- - - - -
- - -

- Markdown 语法说明 (简体中文版) -

- - -
- - - - -
- -

概述

-

宗旨

-

Markdown 的目标是实现「易读易写」。

-

可读性,无论如何,都是最重要的。一份使用 Markdown 格式撰写的文件应该可以直接以纯文本发布,并且看起来不会像是由许多标签或是格式指令所构成。Markdown 语法受到一些既有 text-to-HTML 格式的影响,包括 Setext、atx、Textile、reStructuredText、Grutatext 和 EtText,而最大灵感来源其实是纯文本电子邮件的格式。

-

总之, Markdown 的语法全由一些符号所组成,这些符号经过精挑细选,其作用一目了然。比如:在文字两旁加上星号,看起来就像强调。Markdown 的列表看起来,嗯,就是列表。Markdown 的区块引用看起来就真的像是引用一段文字,就像你曾在电子邮件中见过的那样。

-

兼容 HTML

-

Markdown 语法的目标是:成为一种适用于网络的书写语言。

-

Markdown 不是想要取代 HTML,甚至也没有要和它相近,它的语法种类很少,只对应 HTML 标记的一小部分。Markdown 的构想不是要使得 HTML 文档更容易书写。在我看来, HTML 已经很容易写了。Markdown 的理念是,能让文档更容易读、写和随意改。HTML 是一种发布的格式,Markdown 是一种书写的格式。就这样,Markdown 的格式语法只涵盖纯文本可以涵盖的范围。

-

不在 Markdown 涵盖范围之内的标签,都可以直接在文档里面用 HTML 撰写。不需要额外标注这是 HTML 或是 Markdown;只要直接加标签就可以了。

-

要制约的只有一些 HTML 区块元素――比如

、、

等标签,必须在前后加上空行与其它内容区隔开,还要求它们的开始标签与结尾标签不能用制表符或空格来缩进。Markdown 的生成器有足够智能,不会在 HTML 区块标签外加上不必要的

标签。

-

例子如下,在 Markdown 文件里加上一段 HTML 表格:

-

这是一个普通段落。

-




Foo
- -

这是另一个普通段落。
请注意,在 HTML 区块标签间的 Markdown 格式语法将不会被处理。比如,你在 HTML 区块内使用 Markdown 样式的强调会没有效果。

-

HTML 的区段(行内)标签如 可以在 Markdown 的段落、列表或是标题里随意使用。依照个人习惯,甚至可以不用 Markdown 格式,而直接采用 HTML 标签来格式化。举例说明:如果比较喜欢 HTML 的 标签,可以直接使用这些标签,而不用 Markdown 提供的链接或是图像标签语法。

-

和处在 HTML 区块标签间不同,Markdown 语法在 HTML 区段标签间是有效的。

-

特殊字符自动转换

-

在 HTML 文件中,有两个字符需要特殊处理: < 和 & 。 < 符号用于起始标签,& 符号则用于标记 HTML 实体,如果你只是想要显示这些字符的原型,你必须要使用实体的形式,像是 < 和 &。

-

& 字符尤其让网络文档编写者受折磨,如果你要打「AT&T」 ,你必须要写成「AT&T」。而网址中的 & 字符也要转换。比如你要链接到:

-

http://images.google.com/images?num=30&q=larry+bird
你必须要把网址转换写为:

-

http://images.google.com/images?num=30&q=larry+bird
才能放到链接标签的 href 属性里。不用说也知道这很容易忽略,这也可能是 HTML 标准检验所检查到的错误中,数量最多的。

-

Markdown 让你可以自然地书写字符,需要转换的由它来处理好了。如果你使用的 & 字符是 HTML 字符实体的一部分,它会保留原状,否则它会被转换成 &。

-

所以你如果要在文档中插入一个版权符号 ©,你可以这样写:

-

©
Markdown 会保留它不动。而若你写:

-

AT&T
Markdown 就会将它转为:

-

AT&T
类似的状况也会发生在 < 符号上,因为 Markdown 允许 兼容 HTML ,如果你是把 < 符号作为 HTML 标签的定界符使用,那 Markdown 也不会对它做任何转换,但是如果你写:

-

4 < 5
Markdown 将会把它转换为:

-

4 < 5
不过需要注意的是,code 范围内,不论是行内还是区块, < 和 & 两个符号都一定会被转换成 HTML 实体,这项特性让你可以很容易地用 Markdown 写 HTML code (和 HTML 相对而言, HTML 语法中,你要把所有的 < 和 & 都转换为 HTML 实体,才能在 HTML 文件里面写出 HTML code。)

-

区块元素

-

段落和换行

-

一个 Markdown 段落是由一个或多个连续的文本行组成,它的前后要有一个以上的空行(空行的定义是显示上看起来像是空的,便会被视为空行。比方说,若某一行只包含空格和制表符,则该行也会被视为空行)。普通段落不该用空格或制表符来缩进。

-

「由一个或多个连续的文本行组成」这句话其实暗示了 Markdown 允许段落内的强迫换行(插入换行符),这个特性和其他大部分的 text-to-HTML 格式不一样(包括 Movable Type 的「Convert Line Breaks」选项),其它的格式会把每个换行符都转成
标签。

-

如果你确实想要依赖 Markdown 来插入
标签的话,在插入处先按入两个以上的空格然后回车。

-

的确,需要多费点事(多加空格)来产生
,但是简单地「每个换行都转换为
」的方法在 Markdown 中并不适合, Markdown 中 email 式的 区块引用 和多段落的 列表 在使用换行来排版的时候,不但更好用,还更方便阅读。

-

标题

-

Markdown 支持两种标题的语法,类 Setext 和类 atx 形式。

-

类 Setext 形式是用底线的形式,利用 = (最高阶标题)和 - (第二阶标题),例如:

-

This is an H1

This is an H2

任何数量的 = 和 - 都可以有效果。

-

类 Atx 形式则是在行首插入 1 到 6 个 # ,对应到标题 1 到 6 阶,例如:

-

这是 H1

这是 H2

这是 H6

你可以选择性地「闭合」类 atx 样式的标题,这纯粹只是美观用的,若是觉得这样看起来比较舒适,你就可以在行尾加上 #,而行尾的 # 数量也不用和开头一样(行首的井字符数量决定标题的阶数):

-

这是 H1

这是 H2

这是 H3

区块引用 Blockquotes

-

Markdown 标记区块引用是使用类似 email 中用 > 的引用方式。如果你还熟悉在 email 信件中的引言部分,你就知道怎么在 Markdown 文件中建立一个区块引用,那会看起来像是你自己先断好行,然后在每行的最前面加上 > :

-
-

This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

-

Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.
Markdown 也允许你偷懒只在整个段落的第一行最前面加上 > :

-

This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

-

Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.
区块引用可以嵌套(例如:引用内的引用),只要根据层次加上不同数量的 > :

-

This is the first level of quoting.

-
-

This is nested blockquote.

-
-

Back to the first level.
引用的区块内也可以使用其他的 Markdown 语法,包括标题、列表、代码区块等:

-

这是一个标题。

    -
  1. 这是第一行列表项。
  2. -
  3. 这是第二行列表项。
  4. -
-

给出一些例子代码:

-
return shell_exec("echo $input | $markdown_script");
-

任何像样的文本编辑器都能轻松地建立 email 型的引用。例如在 BBEdit 中,你可以选取文字后然后从选单中选择增加引用阶层。

-
-

列表

-

Markdown 支持有序列表和无序列表。

-

无序列表使用星号、加号或是减号作为列表标记:

-
    -
  • Red
  • -
  • Green
  • -
  • Blue
    等同于:
  • -
-
    -
  • Red
  • -
  • Green
  • -
  • Blue
    也等同于:
  • -
-
    -
  • Red
  • -
  • Green
  • -
  • Blue
    有序列表则使用数字接着一个英文句点:
  • -
-
    -
  1. Bird
  2. -
  3. McHale
  4. -
  5. Parish
    很重要的一点是,你在列表标记上使用的数字并不会影响输出的 HTML 结果,上面的列表所产生的 HTML 标记为:
  6. -
-

    -

  1. Bird
  2. -

  3. McHale
  4. -

  5. Parish


  6. 如果你的列表标记写成:

    -
      -
    1. Bird
    2. -
    3. McHale
    4. -
    5. Parish
      或甚至是:

      -
    6. -
    7. Bird

      -
    8. -
    9. McHale
    10. -
    11. Parish
      你都会得到完全相同的 HTML 输出。重点在于,你可以让 Markdown 文件的列表数字和输出的结果相同,或是你懒一点,你可以完全不用在意数字的正确性。
    12. -
    -

    如果你使用懒惰的写法,建议第一个项目最好还是从 1. 开始,因为 Markdown 未来可能会支持有序列表的 start 属性。

    -

    列表项目标记通常是放在最左边,但是其实也可以缩进,最多 3 个空格,项目标记后面则一定要接着至少一个空格或制表符。

    -

    要让列表看起来更漂亮,你可以把内容用固定的缩进整理好:

    -
      -
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
      Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
      viverra nec, fringilla in, laoreet vitae, risus.
    • -
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
      Suspendisse id sem consectetuer libero luctus adipiscing.
      但是如果你懒,那也行:

      -
    • -
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
      Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
      viverra nec, fringilla in, laoreet vitae, risus.

      -
    • -
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
      Suspendisse id sem consectetuer libero luctus adipiscing.
      如果列表项目间用空行分开,在输出 HTML 时 Markdown 就会将项目内容用

      标签包起来,举例来说:

      -
    • -
    • Bird

      -
    • -
    • Magic
      会被转换为:
    • -
    -

      -

    • Bird
    • -

    • Magic


    • 但是这个:

      -
        -
      • Bird

        -
      • -
      • Magic
        会被转换为:

        -
      • -
      -

        -

      • Bird

      • -

      • Magic



      • 列表项目可以包含多个段落,每个项目下的段落都必须缩进 4 个空格或是 1 个制表符:

        -
          -
        1. This is a list item with two paragraphs. Lorem ipsum dolor
          sit amet, consectetuer adipiscing elit. Aliquam hendrerit
          mi posuere lectus.

          -

          Vestibulum enim wisi, viverra nec, fringilla in, laoreet
          vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
          sit amet velit.

          -
        2. -
        3. Suspendisse id sem consectetuer libero luctus adipiscing.
          如果你每行都有缩进,看起来会看好很多,当然,再次地,如果你很懒惰,Markdown 也允许:

          -
        4. -
        -
          -
        • This is a list item with two paragraphs.

          -

          This is the second paragraph in the list item. You’re
          only required to indent the first line. Lorem ipsum dolor
          sit amet, consectetuer adipiscing elit.

          -
        • -
        • Another item in the same list.
          如果要在列表项目内放进引用,那 > 就需要缩进:

          -
        • -
        • A list item with a blockquote:

          -
          -

          This is a blockquote
          inside a list item.
          如果要放代码区块的话,该区块就需要缩进两次,也就是 8 个空格或是 2 个制表符:

          -
          -
        • -
        • 一列表项包含一个列表区块:

          -
          <代码写在这>
          -

          当然,项目列表很可能会不小心产生,像是下面这样的写法:

          -
        • -
        -
          -
        1. What a great season.
          换句话说,也就是在行首出现数字-句点-空白,要避免这样的状况,你可以在句点前面加上反斜杠。
        2. -
        -

        1986. What a great season.
        代码区块

        -

        和程序相关的写作或是标签语言原始码通常会有已经排版好的代码区块,通常这些区块我们并不希望它以一般段落文件的方式去排版,而是照原来的样子显示,Markdown 会用

         标签来把代码区块包起来。

        -

        要在 Markdown 中建立代码区块很简单,只要简单地缩进 4 个空格或是 1 个制表符就可以,例如,下面的输入:

        -

        这是一个普通段落:

        -
        这是一个代码区块。
        -

        Markdown 会转换成:

        -

        这是一个普通段落:

        - -

        这是一个代码区块。

        这个每行一阶的缩进(4 个空格或是 1 个制表符),都会被移除,例如:

        -

        Here is an example of AppleScript:

        -
        tell application "Foo"
        -    beep
        -end tell
        -

        会被转换为:

        -

        Here is an example of AppleScript:

        - -

        tell application “Foo”
        beep
        end tell

        一个代码区块会一直持续到没有缩进的那一行(或是文件结尾)。

        -

        在代码区块里面, & 、 < 和 > 会自动转成 HTML 实体,这样的方式让你非常容易使用 Markdown 插入范例用的 HTML 原始码,只需要复制贴上,再加上缩进就可以了,剩下的 Markdown 都会帮你处理,例如:

        -
        <div class="footer">
        -    &copy; 2004 Foo Corporation
        -</div>
        -

        会被转换为:

        -

        <div class=”footer”>
        &copy; 2004 Foo Corporation
        </div>

        代码区块中,一般的 Markdown 语法不会被转换,像是星号便只是星号,这表示你可以很容易地以 Markdown 语法撰写 Markdown 语法相关的文件。

        -

        分隔线

        -

        你可以在一行中用三个以上的星号、减号、底线来建立一个分隔线,行内不能有其他东西。你也可以在星号或是减号中间插入空格。下面每种写法都可以建立分隔线:

        -
        -
        -
        -
        -
        -

        区段元素

        -

        链接

        -

        Markdown 支持两种形式的链接语法: 行内式和参考式两种形式。

        -

        不管是哪一种,链接文字都是用 [方括号] 来标记。

        -

        要建立一个行内式的链接,只要在方块括号后面紧接着圆括号并插入网址链接即可,如果你还想要加上链接的 title 文字,只要在网址后面,用双引号把 title 文字包起来即可,例如:

        -

        This is an example inline link.

        -

        This link has no title attribute.
        会产生:

        -

        This is
        an example
        inline link.

        - -

        This link has no
        title attribute.


        如果你是要链接到同样主机的资源,你可以使用相对路径:

        See my About page for details.
        参考式的链接是在链接文字的括号后面再接上另一个方括号,而在第二个方括号里面要填入用以辨识链接的标记:

        This is an example reference-style link.
        你也可以选择性地在两个方括号中间加上一个空格:

        This is an example reference-style link.
        接着,在文件的任意处,你可以把这个标记的链接内容定义出来:

        id: http://example.com/ “Optional Title Here”
        链接内容定义的形式为:

        方括号(前面可以选择性地加上至多三个空格来缩进),里面输入链接文字
        接着一个冒号
        接着一个以上的空格或制表符
        接着链接的网址
        选择性地接着 title 内容,可以用单引号、双引号或是括弧包着
        下面这三种链接的定义都是相同:

        [foo]: http://example.com/ “Optional Title Here”
        [foo]: http://example.com/ ‘Optional Title Here’
        [foo]: http://example.com/ (Optional Title Here)
        请注意:有一个已知的问题是 Markdown.pl 1.0.1 会忽略单引号包起来的链接 title。

        链接网址也可以用方括号包起来:

        id: http://example.com/ “Optional Title Here”
        你也可以把 title 属性放到下一行,也可以加一些缩进,若网址太长的话,这样会比较好看:

        id: http://example.com/longish/path/to/resource/here
        “Optional Title Here”
        网址定义只有在产生链接的时候用到,并不会直接出现在文件之中。

        链接辨别标签可以有字母、数字、空白和标点符号,但是并不区分大小写,因此下面两个链接是一样的:

        [link text][a]
        [link text][A]
        隐式链接标记功能让你可以省略指定链接标记,这种情形下,链接标记会视为等同于链接文字,要用隐式链接标记只要在链接文字后面加上一个空的方括号,如果你要让 “Google” 链接到 google.com,你可以简化成:

        [Google][]
        然后定义链接内容:

        [Google]: http://google.com/
        由于链接文字可能包含空白,所以这种简化型的标记内也许包含多个单词:

        Visit [Daring Fireball][] for more information.
        然后接着定义链接:

        [Daring Fireball]: http://daringfireball.net/
        链接的定义可以放在文件中的任何一个地方,我比较偏好直接放在链接出现段落的后面,你也可以把它放在文件最后面,就像是注解一样。

        下面是一个参考式链接的范例:

        I get 10 times more traffic from [Google] [1] than from
        [Yahoo] [2] or [MSN] [3].

        [1]: http://google.com/ “Google”
        [2]: http://search.yahoo.com/ “Yahoo Search”
        [3]: http://search.msn.com/ “MSN Search”
        如果改成用链接名称的方式写:

        I get 10 times more traffic from [Google][] than from
        [Yahoo][] or [MSN][].

        [google]: http://google.com/ “Google”
        [yahoo]: http://search.yahoo.com/ “Yahoo Search”
        [msn]: http://search.msn.com/ “MSN Search”
        上面两种写法都会产生下面的 HTML。

        I get 10 times more traffic from Google than from
        Yahoo
        or MSN.


        下面是用行内式写的同样一段内容的 Markdown 文件,提供作为比较之用:

        I get 10 times more traffic from Google
        than from Yahoo or
        MSN.
        参考式的链接其实重点不在于它比较好写,而是它比较好读,比较一下上面的范例,使用参考式的文章本身只有 81 个字符,但是用行内形式的却会增加到 176 个字元,如果是用纯 HTML 格式来写,会有 234 个字元,在 HTML 格式中,标签比文本还要多。

        使用 Markdown 的参考式链接,可以让文件更像是浏览器最后产生的结果,让你可以把一些标记相关的元数据移到段落文字之外,你就可以增加链接而不让文章的阅读感觉被打断。

        强调

        Markdown 使用星号()和底线(_)作为标记强调字词的符号,被 包围的字词会被转成用 标签包围,用两个 * 或 包起来的话,则会被转成 ,例如:

        single asterisks

        single underscores

        double asterisks

        double underscores
        会转成:

        single asterisks

        single underscores

        double asterisks

        double underscores
        你可以随便用你喜欢的样式,唯一的限制是,你用什么符号开启标签,就要用什么符号结束。

        强调也可以直接插在文字中间:

        unfriggingbelievable
        但是如果你的 和 _ 两边都有空白的话,它们就只会被当成普通的符号。

        如果要在文字前后直接插入普通的星号或底线,你可以用反斜线:

        \
        this text is surrounded by literal asterisks*
        代码

        如果要标记一小段行内代码,你可以用反引号把它包起来(),例如: - -Use theprintf()function. -会产生: - -<p>Use the <code>printf()</code> function.</p> -如果要在代码区段内插入反引号,你可以用多个反引号来开启和结束代码区段: - -``There is a literal backtick () here.这段语法会产生: - -<p><code>There is a literal backtick (`) here.</code></p> -代码区段的起始和结束端都可以放入一个空白,起始端后面一个,结束端前面一个,这样你就可以在区段的一开始就插入反引号: - -A single backtick in a code span: `` - -A backtick-delimited string in a code span: `` foo`` -会产生: - -<p>A single backtick in a code span: <code>

        - -

        A backtick-delimited string in a code span: foo


        在代码区段内,& 和方括号都会被自动地转成 HTML 实体,这使得插入 HTML 原始码变得很容易,Markdown 会把下面这段:

        -

        Please don’t use any <blink> tags.
        转为:

        -

        Please don’t use any <blink> tags.


        你也可以这样写:

        -

        &#8212; is the decimal-encoded equivalent of &mdash;.
        以产生:

        -

        &#8212; is the decimal-encoded
        equivalent of &mdash;.


        图片

        -

        很明显地,要在纯文字应用中设计一个「自然」的语法来插入图片是有一定难度的。

        -

        Markdown 使用一种和链接很相似的语法来标记图片,同样也允许两种样式: 行内式和参考式。

        -

        行内式的图片语法看起来像是:

        -

        Alt text

        -

        Alt text
        详细叙述如下:

        -

        一个惊叹号 !
        接着一个方括号,里面放上图片的替代文字
        接着一个普通括号,里面放上图片的网址,最后还可以用引号包住并加上 选择性的 ‘title’ 文字。
        参考式的图片语法则长得像这样:

        -

        Alt text
        「id」是图片参考的名称,图片参考的定义方式则和连结参考一样:

        -

        到目前为止, Markdown 还没有办法指定图片的宽高,如果你需要的话,你可以使用普通的 标签。

        -

        其它

        -

        自动链接

        -

        Markdown 支持以比较简短的自动链接形式来处理网址和电子邮件信箱,只要是用方括号包起来, Markdown 就会自动把它转成链接。一般网址的链接文字就和链接地址一样,例如:

        -

        http://example.com/
        Markdown 会转为:

        -

        http://example.com/
        邮址的自动链接也很类似,只是 Markdown 会先做一个编码转换的过程,把文字字符转成 16 进位码的 HTML 实体,这样的格式可以糊弄一些不好的邮址收集机器人,例如:

        -

        address@example.com
        Markdown 会转成:

        -

        address@exa
        mple.com

        在浏览器里面,这段字串(其实是 address@example.com)会变成一个可以点击的「address@example.com」链接。

        -

        (这种作法虽然可以糊弄不少的机器人,但并不能全部挡下来,不过总比什么都不做好些。不管怎样,公开你的信箱终究会引来广告信件的。)

        -

        反斜杠

        -

        Markdown 可以利用反斜杠来插入一些在语法中有其它意义的符号,例如:如果你想要用星号加在文字旁边的方式来做出强调效果(但不用 标签),你可以在星号的前面加上反斜杠:

        -

        *literal asterisks*
        Markdown 支持以下这些符号前面加上反斜杠来帮助插入普通的符号:

        -

        \ 反斜线
        ` 反引号

        -
          -
        • 星号
          _ 底线
          {} 花括号
          [] 方括号
          () 括弧

          井字号

        • -
        -
          -
        • 加号
        • -
        -
          -
        • 减号
          . 英文句点
          ! 惊叹号
        • -
        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/Tomcat\347\232\204\345\233\233\347\247\215\345\237\272\344\272\216HTTP\345\215\217\350\256\256\347\232\204Connector\346\200\247\350\203\275\346\257\224\350\276\203/index.html" "b/2016/04/28/Tomcat\347\232\204\345\233\233\347\247\215\345\237\272\344\272\216HTTP\345\215\217\350\256\256\347\232\204Connector\346\200\247\350\203\275\346\257\224\350\276\203/index.html" deleted file mode 100644 index 6f69f31..0000000 --- "a/2016/04/28/Tomcat\347\232\204\345\233\233\347\247\215\345\237\272\344\272\216HTTP\345\215\217\350\256\256\347\232\204Connector\346\200\247\350\203\275\346\257\224\350\276\203/index.html" +++ /dev/null @@ -1,433 +0,0 @@ - - - - - - - Tomcat的四种基于HTTP协议的Connector性能比较 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - Tomcat的四种基于HTTP协议的Connector性能比较 -

        - - -
        - - - - -
        - -

        Tomcat 6 支持 NIO – Tomcat的四种基于HTTP协议的Connector性能比较

        -

        Tomcat从5.5版本开始,支持以下四种Connector的配置分别为:

        - - - - - - - - - -

        我们姑且把上面四种Connector按照顺序命名为 NIO, HTTP, POOL, NIOP

        -

        为了不让其他因素影响测试结果,我们只对一个很简单的jsp页面进行测试,这个页面仅仅是输出一个Hello World。假设地址是 http://tomcat1/test.jsp

        -

        我们依次对四种Connector进行测试,测试的客户端在另外一台机器上用ab命令来完成,测试命令为: ab -c 900 -n 2000http://tomcat1/test.jsp,最终的测试结果如下表所示(单位:平均每秒处理的请求数):

        -

        NIO HTTP POOL NIOP
        281 65 208 365
        666 66 110 398
        692 65 66 263
        256 63 94 459
        440 67 145 363

        -

        由这五组数据不难看出,HTTP的性能是很稳定,但是也是最差的,而这种方式就是Tomcat的默认配置。NIO方式波动很大,但没有低于280 的,NIOP是在NIO的基础上加入线程池,可能是程序处理更复杂了,因此性能不见得比NIO强;而POOL方式则波动很大,测试期间和HTTP方式一样,不时有停滞。

        -

        由于linux的内核默认限制了最大打开文件数目是1024,因此此次并发数控制在900。

        -

        尽管这一个结果在实际的网站中因为各方面因素导致,可能差别没这么大,例如受限于数据库的性能等等的问题。但对我们在部署网站应用时还是具有参考价值的。

        -

        这个可以利用apache server的 ab测试
        C:\Program Files (x86)\Apache Software Foundation\Apache2.2\bin>ab -n2000 -c100 http://localhost:8080/

        -

        或者

        -

        C:\Program Files (x86)\Apache Software Foundation\Apache2.2\bin>ab -n2000 -c100 -w http://localhost:8080/ >c:/a.html

        -

        -n 代表请求数
        -c 代表并发数
        -w 输出到

        -
        - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/Venus-\345\224\257\345\223\201\344\274\232\345\210\206\345\270\203\345\274\217\346\241\206\346\236\266/index.html" "b/2016/04/28/Venus-\345\224\257\345\223\201\344\274\232\345\210\206\345\270\203\345\274\217\346\241\206\346\236\266/index.html" deleted file mode 100644 index 62651ec..0000000 --- "a/2016/04/28/Venus-\345\224\257\345\223\201\344\274\232\345\210\206\345\270\203\345\274\217\346\241\206\346\236\266/index.html" +++ /dev/null @@ -1,561 +0,0 @@ - - - - - - - Venus----唯品会分布式框架 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - Venus----唯品会分布式框架 -

        - - -
        - - - - -
        - -

        http://wiki.hexnova.com/display/Venus/HOME

        -

        HOME

        -

        附件:1
        被Struct添加,被Struct最后更新于2015-Aug-26 (查看更改)
        Venus(New Version:3.2.14) 是什么?

        -

        它是由(Venus service framework)+服务路由产品(Venus-Bus)+服务注册中心(Venus-Registry) 组合而成,提供远程服务。它着 开发简单、高性能、高并发能力 的服务端框架。
        客户端与服务端之间的通讯对开发人员完全透明
        他跟目前我们经常用到的框架:axis、CXF、Hessian WebService、Jboss Remoting等框架类似。
        系统功能目标:
        提供高性能的服务通讯框架
        具备性能监控(可以清晰的看到每个服务执行的时间、过长可以通过监控告警出去)
        具备流量控制(每个服务每个时刻的调用次数、每天的峰值情况、)
        访问控制(服务的授权控制)
        提供可选择性服务数据缓存(cache支持,key采用表达式,由框架提供缓存支持,而不需要编写任何cache相关的代码)
        提供框架进行再次研发能力,提供interceptor、validator等接口。
        提供高性能的服务总线(Venus-Bus),能够轻易接入高性能的服务总线。(Venus-Bus项目支持,针对该venus的协议,以后接入服务总线轻而易举)
        开发方面:
        服务接口定义清晰(接口、参数、校验、以及服务鉴权)
        自动生成接口文档,以方便阅读接口声明
        客户端框架快速开发、提供多种语言版本的客户端
        提供3种服务调用方式(同步、异步、回调)
        服务端框架提供多种协议提供服务,而不需要做额外的开发
        语言支持情况
        目前客户端SDK暂时只有java、PHP语言版本
        服务端面向java语言
        java 语言开发例子
        演讲稿下载

        -

        Venus演讲稿.pdf
        开发人员的关注点

        -
          -
        1. 如何服务化
        2. -
        -

        采用接口与实现分离,服务接口是一种契约,他与我们开发web Service类似。
        java开发语言:采用对程序员友好的接口申明形式,开发人员不需要关心客户端与服务端之间的传输协议。
        其他语言:可以通过该框架提供自定义协议或者Http协议(http协议即将在2.1.0版本release出来)进行交互

        -
          -
        1. 服务接口定制
        2. -
        -

        定义服务接口
        接口参数命名
        定义参数校验规则
        Java语言服务接口尽量不要依赖其他项目. 接口层面只需要接口相关的参数对象类与服务类
        异常定义

        -
          -
        1. 接口参数校验

          -
        2. -
        3. 提供3种交互方式

          -

          请求应答模式:普通的request、response,一般用于接口有返回值
          异步请求模式:通常用于接口无返回值,客户端并不关心服务器的处理结果,也不用关心服务器处理多少时间
          异步回调模式:接口无返回值,处理通常消化大量时间,需要服务端通知处理结果的业务接口
          源代码:

          -
        4. -
        -

        demo 的svn 地址:svn://svn.hexnova.com/venus/venus-helloworld/trunk
        框架svn地址: svn://svn.hexnova.com/venus/venus-framework/trunk
        svn的用户名:guest
        svn密码:guest
        Maven Repository

        -

        hexnova-openhttp://maven.hexnova.com/nexus/content/groups/hexnova-opentruenevertruealways
        目前版本情况:

        -

        trunk是最新开发版本,会不停有新东西进入
        稳定版本: 3.2.14
        最新版本:3.2.14
        版本发布Blog地址: http://wiki.hexnova.com/pages/viewrecentblogposts.action?key=Venus
        开发人员与blog

        -

        日志

        -

        日志: Venus 3.2.12 Release 创建:
        Struct
        2014-Sep-05
        Venus
        日志: Venus 3.2.10 Release 创建:
        Struct
        2014-Jun-25
        Venus
        日志: Venus 3.2.3 Released 创建:
        Struct
        2014-Feb-19
        Venus
        日志: Venus 3.0.9 Released 创建:
        Struct
        2013-Dec-25
        Venus
        日志: Venus 3.0.6 Released 创建:
        Struct
        2013-Nov-22
        Venus
        日志: Venus 3.0.4 Released 创建:
        Struct
        2013-Nov-14
        Venus
        日志: Venus 3.0.3 Released 创建:
        Struct
        2013-Oct-25
        Venus
        日志: Venus 3.0.2 Released 创建:
        Struct
        2013-Oct-21
        Venus
        日志: Venus 3.0.1 Released 创建:
        Struct
        2013-Oct-14
        Venus
        日志: Venus 2.3.0 Released 创建:
        Struct
        2012-Oct-08
        Venus
        日志: Venus 2.2.7 Released 创建:
        Struct
        2012-Sep-24
        Venus
        日志: Venus 2.2.6 Released 创建:
        Struct
        2012-Jun-28
        Venus
        日志: Venus 2.2.3 Released 创建:
        Struct
        2012-May-21
        Venus
        日志: Venus 2.0.4 Released 创建:
        Struct
        2012-Mar-31
        Venus
        日志: Venus 2.0.3 Released 创建:
        Struct
        2012-Mar-23
        Venus
        日志: Venus 2.0.1 Released 创建:
        Struct
        2012-Jan-02
        Venus
        日志: Venus 1.3.0 Released 创建:
        Struct
        2011-Dec-07
        Venus
        日志: Venus 1.2.0 Released 创建:
        Struct
        2011-Dec-01
        Venus
        日志: Venus 1.1.0 Released 创建:
        Struct
        2011-Nov-28
        Venus

        -

        研发人员列表

        -

        昵称 角色 职责 开源社区 目前供职于
        Struct Member 负责架构设计、通信框架研发、服务框架研发 Hexnova 上海汽车工业集团
        Daisy Member 对象数据序列化、服务接口数据校验、服务框架研发 Hexnova

        -

        Sunng Member 服务框架研发 Hexnova

        -

        Yuanjian Yi Member
        PHP客户端开发 Hexnova
        上海由你网络科技有限公司
        huawei Member
        服务注册中心 Hexnova

        -

        样例:

        -

        简单的接口例子:HelloService.java
        HelloService接口例子
        package com.meidusa.venus.hello.api;

        -

        import com.meidusa.venus.annotations.Endpoint;
        import com.meidusa.venus.annotations.Param;
        import com.meidusa.venus.annotations.Service;
        import com.meidusa.venus.notify.InvocationListener;

        -

        /**

        -
          -
        • Service framework的 HelloService 接口例子.

        • -
        • 支持3种调用方式:

        • -
        • 请求应答模式:普通的request、response,一般用于接口有返回值
        • -
        • 异步请求模式:通常用于接口无返回值,客户端并不关心服务器的处理结果,也不用关心服务器处理多少时间
        • -
        • 异步回调模式:接口无返回值,处理通常消化大量时间,需要服务端通知处理结果的业务接口

        • * -
        • @author Struct
          -/
          @Service(name=”HelloService”,version=1)
          publicinterface HelloService {

          -

          /**

          -
            -
          • 无返回结果的服务调用,支持回调方式,该服务在通讯层面上为异步调用
          • -
          • @param name
          • -
          • @param invocationListener 客户端的回调接口
            */
            @Endpoint(name=”sayHelloWithCallbak”)
            publicabstract void sayHello(@Param(name=”name”) String name,
            @Param(name="callback") InvocationListener<Hello> invocationListener);
            -
            /**
          • -
          • 无返回结果的服务调用,支持同步或者异步调用,
          • -
          • 该接口申明:同步,并且接口申明异常
          • -
          • @param name
            */
            @Endpoint(name=”sayHello”,async=false)
            publicabstract void sayHello(@Param(name=”name”) String name) throws HelloNotFoundException;

            -

            /**

            -
          • -
          • 无返回结果的服务调用,支持同步或者异步调用,无异常申明
          • -
          • @param name
            */
            @Endpoint(name=”sayAsyncHello”,async=true)
            publicabstract void sayAsyncHello(@Param(name=”name”) String name);
          • -
          -
        • -
        -
        /**
        - * 有返回结果的服务调用,该接口只能支持同步调用
        - * @param name
        - * @return
        - */
        -@Endpoint(name="getHello",timeWait=10000)
        -publicabstract Hello getHello(@Param(name="name") String name);
        -

        }
        客户端TestCase编写
        客户端TestCase
        package com.meidusa.venus.hello.client;

        -

        import java.util.concurrent.CountDownLatch;

        -

        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.test.context.ContextConfiguration;
        import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

        -

        import com.meidusa.venus.exception.CodedException;
        import com.meidusa.venus.hello.api.Hello;
        import com.meidusa.venus.hello.api.HelloNotFoundException;
        import com.meidusa.venus.hello.api.HelloService;
        import com.meidusa.venus.notify.InvocationListener;

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(locations=”classpath:/applicationContext-helloworld-client.xml”)
        public class TestHelloService {

        -
        @Autowired
        -private HelloService helloService;
        -
        -@Test
        -public void saySync(){
        -    System.out.println(helloService.getHello("jack"));
        -}
        -
        -@Test
        -public void testSyncWithException(){
        -    try {
        -        helloService.sayHello("jack");
        -    } catch (HelloNotFoundException e) {
        -        System.out.println("throw an user defined HelloNotFoundException");
        -    }
        -}
        -
        -@Test
        -public void testAsync(){
        -    helloService.sayAsyncHello("jack");
        -}
        -
        -@Test
        -public void testCallback() throws Exception{
        -    //为了让回调完成,采用countDownLatch计数器方式,避免testcase主线程运行完成而回调未结束的问题
        -

        final CountDownLatch latch = new CountDownLatch(1);

        -
                      //在正常的使用的代码中这个类需要单实例,避免过多的callback listener导致内存问题
        -                  InvocationListener<Hello> listener = new InvocationListener<Hello>() {
        -        public void callback(Hello myobject) {
        -            System.out.println(" async call back result="+myobject);
        -            latch.countDown();
        -        }
        -
        -        @Override
        -        public void onException(Exception e) {
        -            if(e instanceof CodedException){
        -                CodedException exception = (CodedException) e;
        -                System.out.println(" async call back error:"+exception.getErrorCode()+",message="+exception.getMessage());
        -            }else{
        -                System.out.println(" async call back message="+e.getMessage());
        -            }
        -            latch.countDown();
        -
        -        }
        -    };
        -
        -    helloService.sayHello("jack",listener);
        -    latch.await();
        -}
        -

        }
        服务端的实现
        服务端对HelloService的简单实现
        package com.meidusa.venus.hello.impl;

        -

        import java.math.BigDecimal;
        import java.util.HashMap;
        import java.util.Map;

        -

        import com.meidusa.venus.hello.api.Hello;
        import com.meidusa.venus.hello.api.HelloNotFoundException;
        import com.meidusa.venus.hello.api.HelloService;
        import com.meidusa.venus.notify.InvocationListener;

        -

        public class DefaultHelloService implements HelloService {
        privateString greeting;
        publicString getGreeting() {
        return greeting;
        }

        -
        public void setGreeting(String greeting) {
        -    this.greeting = greeting;
        -}
        -public Hello getHello(String name) {
        -    Hello hello = new Hello();
        -    hello.setName(name);
        -    hello.setGreeting(greeting);
        -    Map<String,Object> map = new HashMap<String,Object>();
        -    hello.setMap(map);
        -    map.put("1", 1);
        -    map.put("2", newLong(2));
        -    map.put("3", newInteger(3));
        -    hello.setBigDecimal(new BigDecimal("1.341241233412"));
        -    return hello;
        -}
        -
        -public void sayHello(String name)  throws HelloNotFoundException {
        -    thrownew HelloNotFoundException(name +" not found");
        -}
        -
        -@Override
        -public void sayAsyncHello(String name) {
        -    System.out.println("method sayAsyncHello invoked");
        -}
        -
        -public void sayHello(String name,
        -        InvocationListener<Hello> invocationListener) {
        -    Hello hello = new Hello();
        -    hello.setName(name);
        -    hello.setGreeting(greeting);
        -    Map<String,Object> map = new HashMap<String,Object>();
        -    hello.setMap(map);
        -    map.put("1", 1);
        -    map.put("2", newLong(2));
        -    map.put("3", newInteger(3));
        -
        -    if(invocationListener != null){
        -        invocationListener.callback(hello);
        -    }
        -
        -}
        -

        }
        最近的更新

        -

        Go语言客户端 go-venus-plugin
        commented by deephex
        Jan 22
        Go语言客户端 go-venus-plugin
        updated by Struct
        (view change)
        Jan 15

        -
          -
        1. 各种客户端简单使用
          updated by Struct
          (view change)
          Jan 15
          go-venus-plugin.zip
          attached by Struct
          Jan 15
          go-venus-plugin.zip
          attached by Struct
          Jan 15
          JAVA语言客户端
          commented by 匿名用户
          2015-Oct-14
          HOME
          commented by wangzhenjun
          2015-Oct-08
          HOME
          updated by Struct
          (view change)
          2015-Aug-26
          JAVA语言客户端
          commented by 匿名用户
          2015-Aug-10
          后端处理线程设置
          updated by Struct
          (view change)
          2015-Jul-29
          . Venus Http协议
          updated by Struct
          (view change)
          2015-Jun-29
          venus-http-adaptor-3.2.13-distribution.zip
          attached by Struct
          2015-Jun-29
          JAVA语言客户端
          updated by Struct
          (view change)
          2015-Jun-11
          HOME
          commented by zhuo
          2015-Feb-14
          Venus 3.2.12 Release
          commented by sunstar_s
          2015-Jan-14
          Venus Eclipse Plugin发布
          commented by 匿名用户
          2015-Jan-10
          Venus Eclipse Plugin发布
          commented by 匿名用户
          2015-Jan-05
          HOME
          commented by 匿名用户
          2014-Nov-06
          HOME
          commented by 匿名用户
          2014-Nov-05
          Venus 3.2.12 Release
          created by Struct
          2014-Sep-05
          More
        2. -
        -

        Navigate space

        -
          -
        1. 各种客户端简单使用
        2. -
        3. 高级使用指南
        4. -
        5. Architecture
        6. -
        7. 协议以及交互序列图介绍
        8. -
        9. 性能测试工具–Service-benchmark
        10. -
        11. 性能测试(单客户端)
        12. -
        13. 极限测试(多客户端)
          FAQ
          Venus Eclipse Plugin发布
          二、其他语言客户端
          子页面 (10)

          -

          隐藏子页面 | 页面重排
          页面: 1. 各种客户端简单使用
          页面: 2. 高级使用指南
          页面: 3. Architecture
          页面: 4. 协议以及交互序列图介绍
          页面: 5. 性能测试工具–Service-benchmark
          页面: 6. 性能测试(单客户端)
          页面: 7. 极限测试(多客户端)
          页面: FAQ
          页面: Venus Eclipse Plugin发布
          页面: 二、其他语言客户端

          -
        14. -
        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/android\351\241\271\347\233\256\346\272\220\347\240\201/index.html" "b/2016/04/28/android\351\241\271\347\233\256\346\272\220\347\240\201/index.html" deleted file mode 100644 index 3c86629..0000000 --- "a/2016/04/28/android\351\241\271\347\233\256\346\272\220\347\240\201/index.html" +++ /dev/null @@ -1,373 +0,0 @@ - - - - - - - android项目源码 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - android项目源码 -

        - - -
        - - - - -
        - -

        源码下载常用地址 http://code.662p.com/list/11_1.html
        Android PDF 阅读器 http://sourceforge.net/projects/andpdf/files/
        个人记账工具 OnMyMeans http://sourceforge.net/projects/onmymeans/develop
        Android电池监控 Android Battery Dog http://sourceforge.net/projects/andbatdog/
        RSS阅读软件 Android RSS http://code.google.com/p/android-rss/
        Android的PDF阅读器 DroidReader http://code.google.com/p/droidreader/
        Android Scripting Environment http://code.google.com/p/android-scripting/
        Android小游戏 Android Shapes http://sourceforge.net/projects/shapes/
        Android JSON RPC http://code.google.com/p/android-json-rpc/
        Android VNC http://code.google.com/p/android-vnc/
        魅族M8的Android移植 M8 Android http://code.google.com/p/m8-android-kernel/
        Android 游戏 Amazed http://code.google.com/p/apps-for-android/
        Android的社交网络 HelloWorld goes mobile http://sourceforge.net/projects/helloworldgm/
        手机聊天程序 Android jChat http://code.google.com/p/jchat4android/
        Android的GPS轨迹记录 MyTracks http://code.google.com/p/mytracks/
        Android国际象棋游戏 Honzovy achy http://sourceforge.net/projects/honzovysachy/
        Android旅行记录软件 AndTripLog http://sourceforge.net/projects/andtriplog/
        音乐播放器 Ambient http://sourceforge.net/projects/ambientmp/
        Android的邮件客户端 K9mail http://code.google.com/p/k9mail/
        多平台应用开发库 QuickConnect http://sourceforge.net/projects/quickconnect/
        gPhone手机空战游戏 http://code.google.com/p/wireless-apps/
        Android 照片小软件 Panoramio http://code.google.com/p/apps-for-android/
        i-jetty http://code.google.com/p/i-jetty/
        Android 小游戏 DivideAndConquer http://code.google.com/p/apps-for-android/
        Android 全球时间 AndroidGlobalTime http://code.google.com/p/apps-for-android/
        Android 2D游戏引擎 Android Angle http://code.google.com/p/angle/
        Android Ruby http://code.google.com/p/android-ruby/
        Android-N810 http://sourceforge.net/projects/android-n810/
        Android的短信应用 Ecclesia http://sourceforge.net/projects/ecclesia
        Android平台上的JXTA客户端 Peerdroid http://code.google.com/p/peerdroid/
        Android游戏引擎 libgdx http://code.google.com/p/libgdx/
        Android 照片小软件 Photostream http://code.google.com/p/apps-for-android/
        Alien3d logo Android 3D游戏引擎 Alien3d http://code.google.com/p/alien3d/
        Winamp Remote Android Server http://sourceforge.net/projects/winampdroid
        Android的Facebook客户端 Andrico http://code.google.com/p/andrico/
        Android Applications Manager http://sourceforge.net/projects/aam/
        Java 3D图形引擎 Catcake http://code.google.com/p/catcake/
        android-gcc-objc2-0 http://code.google.com/p/android-gcc-objc2-0/
        九宫格数独游戏 OpenSudoku http://code.google.com/p/opensudoku-android/
        Android 铃声扩展工具 RingsExtended http://code.google.com/p/apps-for-android/
        JavaEye Android client http://code.google.com/p/javaeye-android-client/
        RemoteDroid http://code.google.com/p/remotedroid/
        Android 小游戏 Clickin2DaBeat http://code.google.com/p/apps-for-android/
        中医大夫助理信息系统 zz-doctor http://code.google.com/p/zz-doctor/
        Facebook Connect for Android http://code.google.com/p/fbconnect-android/
        Android SMSPopup http://code.google.com/p/android-smspopup/
        FreeTTS-Android http://sourceforge.net/projects/freettsandroidi
        Foursquare.com的客户端 Foursquar http://code.google.com/p/foursquared/
        条形码扫描仪 Android PC_BCR http://code.google.com/p/android-pcbcr/

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/2016/04/28/dubbox/index.html b/2016/04/28/dubbox/index.html deleted file mode 100644 index 433ee4e..0000000 --- a/2016/04/28/dubbox/index.html +++ /dev/null @@ -1,406 +0,0 @@ - - - - - - - dubbox | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - dubbox -

        - - -
        - - - - -
        - -

        https://github.com/dangdangdotcom/dubbox
        Dubbox now means Dubbo eXtensions. If you know java, javax and dubbo, you know what dubbox is :)
        Dubbox adds features like RESTful remoting, Kyro/FST serialization, etc to the popular dubbo service framework. It’s been used by several projects of dangdang.com, which is one of the major e-commerce companies in China.
        主要贡献者

        -

        沈理 当当网 shenli@dangdang.com
        王宇轩 当当网 wangyuxuan@dangdang.com
        马金凯 韩都衣舍 majinkai@handu.com
        Dylan 独立开发者 dinguangx@163.com
        Kangfoo 独立开发者
        讨论QQ群:258792161 (不限于dubbox,包括SOA设计、互联网技术等等兴趣交流)
        Dubbox当前的主要功能

        -

        支持REST风格远程调用(HTTP + JSON/XML):基于非常成熟的JBoss RestEasy框架,在dubbo中实现了REST风格(HTTP + JSON/XML)的远程调用,以显著简化企业内部的跨语言交互,同时显著简化企业对外的Open API、无线API甚至AJAX服务端等等的开发。事实上,这个REST调用也使得Dubbo可以对当今特别流行的“微服务”架构提供基础性支持。 另外,REST调用也达到了比较高的性能,在基准测试下,HTTP + JSON与Dubbo 2.x默认的RPC协议(即TCP + Hessian2二进制序列化)之间只有1.5倍左右的差距,详见文档中的基准测试报告。
        支持基于Kryo和FST的Java高效序列化实现:基于当今比较知名的Kryo和FST高性能序列化库,为Dubbo默认的RPC协议添加新的序列化实现,并优化调整了其序列化体系,比较显著的提高了Dubbo RPC的性能,详见文档中的基准测试报告。
        支持基于Jackson的JSON序列化:基于业界应用最广泛的Jackson序列化库,为Dubbo默认的RPC协议添加新的JSON序列化实现。
        支持基于嵌入式Tomcat的HTTP remoting体系:基于嵌入式tomcat实现dubbo的HTTP remoting体系(即dubbo-remoting-http),用以逐步取代Dubbo中旧版本的嵌入式Jetty,可以显著的提高REST等的远程调用性能,并将Servlet API的支持从2.5升级到3.1。(注:除了REST,dubbo中的WebServices、Hessian、HTTP Invoker等协议都基于这个HTTP remoting体系)。
        升级Spring:将dubbo中Spring由2.x升级到目前最常用的3.x版本,减少版本冲突带来的麻烦。
        升级ZooKeeper客户端:将dubbo中的zookeeper客户端升级到最新的版本,以修正老版本中包含的bug。
        支持完全基于Java代码的Dubbo配置:基于Spring的Java Config,实现完全无XML的纯Java代码方式来配置dubbo
        调整Demo应用:暂时将dubbo的demo应用调整并改写以主要演示REST功能、Dubbo协议的新序列化方式、基于Java代码的Spring配置等等。
        修正了dubbo的bug 包括配置、序列化、管理界面等等的bug。
        注:dubbox和dubbo 2.x是兼容的,没有改变dubbo的任何已有的功能和配置方式(除了升级了spring之类的版本)
        文档资料

        -

        在Dubbo中开发REST风格的远程调用(RESTful Remoting)
        在Dubbo中使用高效的Java序列化(Kryo和FST)
        使用JavaConfig方式配置dubbox
        Dubbo Jackson序列化使用说明
        Demo应用简单运行指南
        Dubbox@InfoQ
        Dubbox Wiki (由社区志愿者自由编辑的)
        版本

        -

        详见:https://github.com/dangdangdotcom/dubbox/releases
        dubbox-2.8.0:主要支持REST风格远程调用、支持Kryo和FST序列化、升级了Spring和Zookeeper客户端、调整了demo应用等等
        dubbox-2.8.1:主要支持基于嵌入式tomcat的http-remoting,优化了REST客户端性能,在REST中支持限制服务端接纳的最大HTTP连接数等等
        dubbox-2.8.2:
        支持REST中的HTTP logging,包括HTTP header的字段和HTTP body中的消息体,方便调试、日志纪录等等
        提供辅助类便于REST的中文处理
        改变使用@Reference annotation配置时的异常处理方式,即当用annotation配置时,过去dubbo在启动期间不抛出依赖服务找不到的异常,而是在具体调用时抛出NPE,这与用XML配置时的行为不一致。
        较大的充实了Dubbo REST的文档
        dubbox-2.8.3:
        在REST中支持dubbo统一的方式用bean validation annotation作参数校验(沈理)
        在RpcContext上支持获取底层协议的Request/Response(沈理)
        支持采用Spring的Java Config方式配置dubbo(马金凯)
        在Dubbo协议中支持基于Jackson的json序列化(Dylan)
        在Spring AOP代理过的对象上支持dubbo annotation配置(Dylan)
        修正Dubbo管理界面中没有consumer时出现空指针异常(马金凯)
        修正@Reference annotation中protocol设置不起作用的bug(沈理)
        修正@Reference annotation放在setter方法上即会出错的bug(Dylan)
        依赖

        -

        从dubbox-2.8.4开始,所有依赖库的使用方式将和dubbo原来的一样:即如果要使用REST、Kyro、FST、Jackson等功能,需要用户自行手工添加相关的依赖。例如:
        REST风格远程调用

        -
        org.jboss.resteasy
        resteasy-jaxrs
        3.0.7.Final


        org.jboss.resteasy
        resteasy-client
        3.0.7.Final


        javax.validation
        validation-api
        1.0.0.GA
        - - -
        org.jboss.resteasy
        resteasy-jackson-provider
        3.0.7.Final
        - - -
        org.jboss.resteasy
        resteasy-jaxb-provider
        3.0.7.Final
        - - -
        org.jboss.resteasy
        resteasy-netty
        3.0.7.Final
        - - -
        org.jboss.resteasy
        resteasy-jdk-http
        3.0.7.Final
        - - -


        org.apache.tomcat.embed
        tomcat-embed-core
        8.0.11

        -


        org.apache.tomcat.embed
        tomcat-embed-logging-juli
        8.0.11

        Kyro序列化

        -


        com.esotericsoftware.kryo
        kryo
        2.24.0

        -


        de.javakaffee
        kryo-serializers
        0.26

        FST序列化

        -


        de.ruedigermoeller
        fst
        1.55

        Jackson序列化

        -


        com.fasterxml.jackson.core
        jackson-core
        2.3.3

        -


        com.fasterxml.jackson.core
        jackson-databind
        2.3.3

        FAQ(暂存)

        -

        Dubbox需要什么版本的JDK?

        -

        目前最好在JDK 1.7以上运行
        Dubbo REST的服务能和Dubbo注册中心、监控中心集成吗?

        -

        可以的,而且是自动集成的,也就是你在dubbo中开发的所有REST服务都会自动注册到服务册中心和监控中心,可以通过它们做管理。
        但是,只有当REST的消费端也是基于dubbo的时候,注册中心中的许多服务治理操作才能完全起作用。而如果消费端是非dubbo的,自然不受注册中心管理,所以其中很多操作是不会对消费端起作用的。
        Dubbo REST中如何实现负载均衡和容错(failover)?

        -

        如果dubbo REST的消费端也是dubbo的,则Dubbo REST和其他dubbo远程调用协议基本完全一样,由dubbo框架透明的在消费端做load balance、failover等等。
        如果dubbo REST的消费端是非dubbo的,甚至是非java的,则最好配置服务提供端的软负载均衡机制,目前可考虑用LVS、HAProxy、 Nginx等等对HTTP请求做负载均衡。
        JAX-RS中重载的方法能够映射到同一URL地址吗?

        -

        http://stackoverflow.com/questions/17196766/can-resteasy-choose-method-based-on-query-params
        JAX-RS中作POST的方法能够接收多个参数吗?

        -

        http://stackoverflow.com/questions/5553218/jax-rs-post-multiple-objects

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/hexo-github\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.html" "b/2016/04/28/hexo-github\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.html" deleted file mode 100644 index 4a25218..0000000 --- "a/2016/04/28/hexo-github\346\220\255\345\273\272\344\270\252\344\272\272\345\215\232\345\256\242/index.html" +++ /dev/null @@ -1,390 +0,0 @@ - - - - - - - hexo+github搭建个人博客 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - hexo+github搭建个人博客 -

        - - -
        - - - - -
        - -

        hexo+github搭建个人博客

        -

        字数841 阅读543 评论12 喜欢18
        现在一个程序猿(媛)没有一个自己的博客都不好意思说自己是程序员,哈哈开玩笑的。是否有一个方法,可以让我们自己创建一个属于自己的博客,然后又不用花钱买服务器和域名,也不用自己找人去设计自己的网站呢。
        这样的好东西还真的存在,而且配置还十分简单,下面我就详细的介绍如何用hexo+github搭建自己的(酷炫)博客。
        前期准备

        -

        node.js
        如果你是windows,请戳这里
        如果你是mac,请戳这里
        git账号
        如果没有git帐号,请戳这里
        安装hexo
        npm install -g hexo
        初始化hexo
        hexo init
        npm install hexo –save
        生成静态页面至hexo\public\目录。
        hexo g
        本地启动服务
        hexo server
        这样,我们就可以在浏览器中输入http://localhost:4000/ 访问我们的博客啦(响应式的网站)。

        -

        初始界面-PC.png

        -

        初始界面-移动端.png
        虽然博客基本的已经搭好了,但是我们只能在本地访问,其他人是看不到的,下面我们通过和git绑定来实现我们想要的效果。

        -

        配置github
        新建一个仓库名(该仓库名和你的用户名对应),如我的git账户名是:coder-Yin,则我的仓库名为coder-Yin.github.io
        编辑_config.yml文件,建立与git的关联(在.yml文件的最底部)

        -

        Deployment## Docs: http://hexo.io/docs/deployment.html

        deploy:
        type: git
        repository: https://github.com/coder-Yin/coder-Yin.github.io.git
        branch: master
        然后运行
        npm install hexo-deployer-git –save
        hexo g
        hexo d
        这样你就可以在你的 coder-Yin.github.io 上看到代码已经同步到git上了。
        在浏览器中输入你的**.github..io(例如:http://coder-yin.github.io/)

        -

        访问效果.png
        每次有新的修改需要部署同步,都可以按照下面的步骤来:
        hexo clean
        hexo g
        hexo d
        如果你觉得hexo默认的主题不好看,你可以通过以下方法来修改你的主题。

        -

        下面我通过修改一个主题来给大家做个介绍:
        在git上找到你想要的主题
        我这随意找了一个,比较适合女孩子(缺点:不是自适应的)
        https://github.com/daisygao/hexo-themes-cover
        进入你的hexo目录,执行命令,拷贝主题
        git clone https://github.com/daisygao/hexo-themes-cover.git themes/cover
        拷贝完成后,你会发现你的项目下的themes下多了一个cover文件夹
        我们还需要修改_config.yml文件中的一处来应用新的主题

        -

        Extensions## Plugins: http://hexo.io/plugins/## Themes: http://hexo.io/themes/

        theme: cover
        然后我们重启服务就可以在本地看到效果了
        hexo server

        -

        hexo应用新的主题.png

        -

        注意:我们这样只是本地做了修改,git上并没有实现同步,我们需要按照上面所说的,依次执行以下命令实现部署同步:
        hexo clean
        hexo g
        hexo d
        在刷新你的http://***.github.io/ 就可以发现新的主题应用成功了,是不是很简单,快动手建立你自己的博客吧。
        最后,附上更多的hexo主题,大家可以很戳这里选择你自己喜好的主题。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/javascript-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217/index.html" "b/2016/04/28/javascript-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217/index.html" deleted file mode 100644 index 854b167..0000000 --- "a/2016/04/28/javascript-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217/index.html" +++ /dev/null @@ -1,387 +0,0 @@ - - - - - - - javascript---正则表达式 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - javascript---正则表达式 -

        - - -
        - - - - -
        - -

        gExp

        -

        阅读: 1981
        字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在。比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦,而且代码难以复用。
        正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
        所以我们判断一个字符串是否是合法的Email的方法是:
        创建一个匹配Email的正则表达式;
        用该正则表达式去匹配用户的输入来判断是否合法。
        因为正则表达式也是用字符串表示的,所以,我们要首先了解如何用字符来描述字符。
        在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字,所以:
        ‘00\d’可以匹配’007’,但无法匹配’00A’;
        ‘\d\d\d’可以匹配’010’;
        ‘\w\w’可以匹配’js’;
        .可以匹配任意字符,所以:
        ‘js.’可以匹配’jsp’、’jss’、’js!’等等。
        要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:
        来看一个复杂的例子:\d{3}\s+\d{3,8}。
        我们来从左到右解读一下:
        \d{3}表示匹配3个数字,例如’010’;
        \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配’ ‘,’\t\t’等;
        \d{3,8}表示3-8个数字,例如’1234567’。
        综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。
        如果要匹配’010-12345’这样的号码呢?由于’-‘是特殊字符,在正则表达式中,要用’\’转义,所以,上面的正则是\d{3}-\d{3,8}。
        但是,仍然无法匹配’010 - 12345’,因为带有空格。所以我们需要更复杂的匹配方式。
        进阶

        -

        要做更精确地匹配,可以用[]表示范围,比如:
        [0-9a-zA-Z_]可以匹配一个数字、字母或者下划线;
        [0-9a-zA-Z_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如’a100’,’0Z’,’js2015’等等;
        [a-zA-Z\
        \$][0-9a-zA-Z_\$]*可以匹配由字母或下划线、$开头,后接任意个由一个数字、字母或者下划线、$组成的字符串,也就是JavaScript允许的变量名;
        [a-zA-Z_\$][0-9a-zA-Z_\$]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
        A|B可以匹配A或B,所以[J|j]ava[S|s]cript可以匹配’JavaScript’、’Javascript’、’javaScript’或者’javascript’。
        ^表示行的开头,^\d表示必须以数字开头。
        $表示行的结束,\d$表示必须以数字结束。
        你可能注意到了,js也可以匹配’jsp’,但是加上^js$就变成了整行匹配,就只能匹配’js’了。
        RegExp

        -

        有了准备知识,我们就可以在JavaScript中使用正则表达式了。
        JavaScript有两种方式创建一个正则表达式:
        第一种方式是直接通过/正则表达式/写出来,第二种方式是通过new RegExp(‘正则表达式’)创建一个RegExp对象。
        两种写法是一样的:
        var re1 = /ABC-001/;
        var re2 = new RegExp(‘ABC\-001’);

        -

        re1; // /ABC-001/
        re2; // /ABC-001/
        注意,如果使用第二种写法,因为字符串的转义问题,字符串的两个\实际上是一个\。
        先看看如何判断正则表达式是否匹配:
        var re = /^\d{3}-\d{3,8}$/;
        re.test(‘010-12345’); //true
        re.test(‘010-1234x’); //false
        re.test(‘010 12345’); //false
        RegExp对象的test()方法用于测试给定的字符串是否符合条件。
        切分字符串

        -

        用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
        ‘a b c’.split(‘ ‘); // [‘a’, ‘b’, ‘’, ‘’, ‘c’]
        嗯,无法识别连续的空格,用正则表达式试试:
        ‘a b c’.split(/\s+/); // [‘a’, ‘b’, ‘c’]
        无论多少个空格都可以正常分割。加入,试试:
        ‘a,b, c d’.split(/[\s\,]+/); // [‘a’, ‘b’, ‘c’, ‘d’]
        再加入;试试:
        ‘a,b;; c d’.split(/[\s\,\;]+/); // [‘a’, ‘b’, ‘c’, ‘d’]
        如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。
        分组

        -

        除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。比如:
        ^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
        var re = /^(\d{3})-(\d{3,8})$/;
        re.exec(‘010-12345’); // [‘010-12345’, ‘010’, ‘12345’]
        re.exec(‘010 12345’); // null
        如果正则表达式中定义了组,就可以在RegExp对象上用exec()方法提取出子串来。
        exec()方法在匹配成功后,会返回一个Array,第一个元素始终是原始字符串本身,后面的字符串表示匹配成功的子串。
        exec()方法在匹配失败时返回null。
        提取子串非常有用。来看一个更凶残的例子:
        var re = /^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/;
        re.exec(‘19:05:30’); // [‘19:05:30’, ‘19’, ‘05’, ‘30’]
        这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
        var re = /^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$/;
        对于’2-30’,’4-31’这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。
        贪婪匹配

        -

        需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0:
        var re = /^(\d+)(0)$/;
        re.exec(‘102300’); // [‘102300’, ‘102300’, ‘’]
        由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0
        只能匹配空字符串了。
        必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:
        var re = /^(\d+?)(0*)$/;
        re.exec(‘102300’); // [‘102300’, ‘1023’, ‘00’]
        全局搜索

        -

        JavaScript的正则表达式还有几个特殊的标志,最常用的是g,表示全局匹配:
        var r1 = /test/g;
        // 等价于:var r2 = new RegExp(‘test’, ‘g’);
        全局匹配可以多次执行exec()方法来搜索一个匹配的字符串。当我们指定g标志后,每次运行exec(),正则表达式本身会更新lastIndex属性,表示上次匹配到的最后索引:
        var s = ‘JavaScript, VBScript, JScript and ECMAScript’;
        var re=/[a-zA-Z]+Script/g;

        -

        // 使用全局匹配:
        re.exec(s); // [‘JavaScript’]
        re.lastIndex; // 10

        -

        re.exec(s); // [‘VBScript’]
        re.lastIndex; // 20

        -

        re.exec(s); // [‘JScript’]
        re.lastIndex; // 29

        -

        re.exec(s); // [‘ECMAScript’]
        re.lastIndex; // 44

        -

        re.exec(s); // null,直到结束仍没有匹配到
        全局匹配类似搜索,因此不能使用/^…$/,那样只会最多匹配一次。
        正则表达式还可以指定i标志,表示忽略大小写,m标志,表示执行多行匹配。
        小结

        -

        正则表达式非常强大,要在短短的一节里讲完是不可能的。要讲清楚正则的所有内容,可以写一本厚厚的书了。如果你经常遇到正则表达式的问题,你可能需要一本正则表达式的参考书。
        练习

        -

        请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email:
        ‘use strict’;
        // 测试:
        var
        i,
        success = true,
        should_pass = [‘someone@gmail.com’, ‘bill.gates@microsoft.com’, ‘tom@voyager.org’, ‘bob2015@163.com’],
        should_fail = [‘test#gmail.com’, ‘bill@microsoft’, ‘bill%gates@ms.com’, ‘@voyager.org’];
        for (i = 0; i < should_pass.length; i++) {
        if (!re.test(should_pass[i])) {
        alert(‘测试失败: ‘ + should_pass[i]);
        success = false;
        break;
        }
        }
        for (i = 0; i < should_fail.length; i++) {
        if (re.test(should_fail[i])) {
        alert(‘测试失败: ‘ + should_fail[i]);
        success = false;
        break;
        }
        }
        if (success) {
        alert(‘测试通过!’);
        }
        版本二可以验证并提取出带名字的Email地址:
        ‘use strict’;
        // 测试:
        var r = re.exec(‘ tom@voyager.org’);
        if (r === null || r.toString() !== [‘ tom@voyager.org’, ‘Tom Paris’, ‘tom@voyager.org’].toString()) {
        alert(‘测试失败!’);
        }
        else {
        alert(‘测试成功!’);
        }

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/javascript-\351\227\255\345\214\205/index.html" "b/2016/04/28/javascript-\351\227\255\345\214\205/index.html" deleted file mode 100644 index 5d50d20..0000000 --- "a/2016/04/28/javascript-\351\227\255\345\214\205/index.html" +++ /dev/null @@ -1,396 +0,0 @@ - - - - - - - javascript----闭包 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - javascript----闭包 -

        - - -
        - - - - -
        - -

        -

        阅读: 9165
        函数作为返回值

        -

        高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
        我们来实现一个对Array的求和。通常情况下,求和的函数是这样定义的:
        functionsum(arr) {return arr.reduce(function(x, y) {return x + y;
        });
        }

        -

        sum([1, 2, 3, 4, 5]); // 15
        但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!
        functionlazy_sum(arr) {var sum = function() {return arr.reduce(function(x, y) {return x + y;
        });
        }
        return sum;
        }
        当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
        var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
        调用函数f时,才真正计算求和的结果:
        f(); //15
        在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
        请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
        var f1 = lazy_sum([1, 2, 3, 4, 5]);
        var f2 = lazy_sum([1, 2, 3, 4, 5]);
        f1 === f2; // false
        f1()和f2()的调用结果互不影响。
        闭包

        -

        注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
        另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:
        functioncount() {var arr = [];
        for (var i=1; i<=3; i++) {
        arr.push(function() {return i * i;
        });
        }
        return arr;
        }

        -

        var results = count();
        var f1 = results[0];
        var f2 = results[1];
        var f3 = results[2];
        在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。
        你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
        f1(); //16
        f2(); //16
        f3(); //16
        全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。
        返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
        如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
        functioncount() {var arr = [];
        for (var i=1; i<=3; i++) {
        arr.push((function(n) {returnfunction() {return n * n;
        }
        })(i));
        }
        return arr;
        }

        -

        var results = count();
        var f1 = results[0];
        var f2 = results[1];
        var f3 = results[2];

        -

        f1(); // 1
        f2(); // 4
        f3(); // 9
        注意这里用了一个“创建一个匿名函数并立刻执行”的语法:
        (function(x) {return x x;
        })(3); // 9
        理论上讲,创建一个匿名函数并立刻执行可以这么写:
        function(x) {return x
        x } (3);
        但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:
        (function(x) {return x x }) (3);
        通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:
        (function(x) {return x
        x;
        })(3);
        说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?
        当然不是!闭包有非常强大的功能。举个栗子:
        在面向对象的程序设计语言里,比如Java和C++,要在对象内部封装一个私有变量,可以用private修饰一个成员变量。
        在没有class机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器:
        ‘use strict’;

        -

        functioncreate_counter(initial) {var x = initial || 0;
        return {
        inc: function() {
        x += 1;
        return x;
        }
        }
        }
        它用起来像这样:
        var c1 = create_counter();
        c1.inc(); // 1
        c1.inc(); // 2
        c1.inc(); // 3var c2 = create_counter(10);
        c2.inc(); // 11
        c2.inc(); // 12
        c2.inc(); // 13
        在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
        闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2和pow3:
        functionmake_pow(n) {returnfunction(x) {return Math.pow(x, n);
        }
        }

        -

        // 创建两个新函数:var pow2 = make_pow(2);
        var pow3 = make_pow(3);

        -

        pow2(5); // 25
        pow3(7); // 343
        脑洞大开

        -

        很久很久以前,有个叫阿隆佐·邱奇的帅哥,发现只需要用函数,就可以用计算机实现运算,而不需要0、1、2、3这些数字和+、-、*、/这些符号。
        JavaScript支持函数,所以可以用JavaScript用函数来写这些计算。来试试:
        ‘use strict’;

        -

        // 定义数字0:
        var zero = function (f) {
        return function (x) {
        return x;
        }
        };

        -

        // 定义数字1:
        var one = function (f) {
        return function (x) {
        return f(x);
        }
        };

        -

        // 定义加法:
        function add(n, m) {
        return function (f) {
        return function (x) {
        return m(f)(n(f)(x));
        }
        }
        }

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/java\346\234\215\345\212\241\347\253\257\346\216\250\351\200\201\346\266\210\346\201\257\345\210\260web\351\241\265\351\235\242\345\256\236\344\276\213/index.html" "b/2016/04/28/java\346\234\215\345\212\241\347\253\257\346\216\250\351\200\201\346\266\210\346\201\257\345\210\260web\351\241\265\351\235\242\345\256\236\344\276\213/index.html" deleted file mode 100644 index 79e6f4d..0000000 --- "a/2016/04/28/java\346\234\215\345\212\241\347\253\257\346\216\250\351\200\201\346\266\210\346\201\257\345\210\260web\351\241\265\351\235\242\345\256\236\344\276\213/index.html" +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - - java服务端推送消息到web页面实例 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - java服务端推送消息到web页面实例 -

        - - -
        - - - - -
        - -

        对于页面一直监控,以前都是使用ajax请求即可,但是小并发这做法没多大问题,但是到了大并发就不太合适,如果不想自己写线程来操控就可以偷懒找一些插件,例如comet4j
        下面我来演示下如何使用这个插件
        先准备需要的工具:
        comet4j-tomcat6.jar(tomcat6的就导入这个)
        comet4j-tomcat7.jar(tomcat7的就导入这个)
        comet4j.js(页面引入这个js)
        具体操作看下面

        -

        然后就写个class
        [java] view plaincopy
        package com.shadow.extras.comet4j;

        -

        import javax.servlet.ServletContextEvent;
        import javax.servlet.ServletContextListener;

        -

        import org.comet4j.core.CometContext;
        import org.comet4j.core.CometEngine;

        -

        public class TestComet implements ServletContextListener {
        private static final String CHANNEL = “test”;
        private static final String CHANNEL2 = “test2”;

        -
        public void contextInitialized(ServletContextEvent arg0) {  
        -    CometContext cc = CometContext.getInstance();  
        -    cc.registChannel(CHANNEL);// 注册应用的channel  
        -    cc.registChannel(CHANNEL2);  
        -
        -    Thread helloAppModule = new Thread(new HelloAppModule(),  
        -            "Sender App Module");  
        -    // 是否启动  
        -    helloAppModule.setDaemon(true);  
        -    // 启动线程  
        -    helloAppModule.start();  
        -
        -    Thread helloAppModule2 = new Thread(new HelloAppModule2(),  
        -            "Sender App Module");  
        -    // 是否启动  
        -    helloAppModule2.setDaemon(true);  
        -    // 启动线程  
        -    helloAppModule2.start();  
        -}  
        -
        -class HelloAppModule2 implements Runnable {  
        -    public void run() {  
        -        while (true) {  
        -            try {  
        -                // 睡眠时间  
        -                Thread.sleep(5000);  
        -            } catch (Exception ex) {  
        -                ex.printStackTrace();  
        -            }  
        -            CometEngine engine = CometContext.getInstance().getEngine();  
        -            // 获取消息内容  
        -            long l = getFreeMemory();  
        -            // 开始发送  
        -            engine.sendToAll(CHANNEL2, l);  
        -        }  
        -    }  
        -}  
        -
        -class HelloAppModule implements Runnable {  
        -    public void run() {  
        -        while (true) {  
        -            try {  
        -                // 睡眠时间  
        -                Thread.sleep(2000);  
        -            } catch (Exception ex) {  
        -                ex.printStackTrace();  
        -            }  
        -            CometEngine engine = CometContext.getInstance().getEngine();  
        -            // 获取消息内容  
        -            long l = getFreeMemory();  
        -            // 开始发送  
        -            engine.sendToAll(CHANNEL, l);  
        -        }  
        -    }  
        -}  
        -
        -public void contextDestroyed(ServletContextEvent arg0) {  
        -
        -}  
        -
        -public long getFreeMemory() {  
        -    return Runtime.getRuntime().freeMemory() / 1024;  
        -}  
        -

        }
        然后再写个页面

        -

        [html] view plaincopy
        <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

        -

        -

        -

        -

        Comet4J Hello World

        -

        -


        -


        剩余内存:KB

        剩余内存:KB


        接着配置下web.xml就ok了
        [html] view plaincopy


        Comet4J容器侦听
        org.comet4j.core.CometAppListener


        Comet连接[默认:org.comet4j.core.CometServlet]
        CometServlet
        CometServlet
        org.comet4j.core.CometServlet


        CometServlet
        /comet

        -
        <listener>  
        -    <description>TestComet</description>  
        -    <listener-class>com.shadow.extras.comet4j.TestComet</listener-class>  
        -</listener>  
        -

        最后修改下tomcat的server.xml文件
        把protocol参数值改成下面的,因为这是基于nio开发的插件
        [html] view plaincopy

        - - -

        测试,很简单就是访问我们刚刚创建的test.html,然后就可以看到内存数值一直自动刷新波动

        -
        - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/maven\345\270\270\347\224\250\345\221\275\344\273\244/index.html" "b/2016/04/28/maven\345\270\270\347\224\250\345\221\275\344\273\244/index.html" deleted file mode 100644 index 9430dab..0000000 --- "a/2016/04/28/maven\345\270\270\347\224\250\345\221\275\344\273\244/index.html" +++ /dev/null @@ -1,381 +0,0 @@ - - - - - - - maven常用命令 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - maven常用命令 -

        - - -
        - - - - -
        - -

        maven 全球中心仓库 http://mirrors.ibiblio.org/maven2/org/apache/

        -

        Sonatype Nexus 搭建Maven 私服

        -

        mvn -v 查看版本
        mvn compile 编译
        mvn test 测试
        mvn clean 清楚编译文件
        mvn package 把项目打成jar包
        mvn install 导入
        mvn archetype:generate 更换本地仓库后下载maven骨架

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/memcache\345\256\211\350\243\205\345\217\212\351\205\215\347\275\256/index.html" "b/2016/04/28/memcache\345\256\211\350\243\205\345\217\212\351\205\215\347\275\256/index.html" deleted file mode 100644 index ac908ea..0000000 --- "a/2016/04/28/memcache\345\256\211\350\243\205\345\217\212\351\205\215\347\275\256/index.html" +++ /dev/null @@ -1,437 +0,0 @@ - - - - - - - memcache安装及配置 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - memcache安装及配置 -

        - - -
        - - - - -
        - -

        CentOS6.4安装Memcached与使用例子

        -

        1、安装libevent
        wget http://monkey.org/~provos/libevent-1.4.14b-stable.tar.gz
        tar zxvf libevent-1.4.14b-stable.tar.gz
        cd libevent-1.4.14b-stable
        ./configure –prefix=/usr/local/libevent/
        make && make install
        ln -s /usr/local/libevent/lib/libevent-1.4.so.2 /lib/libevent-1.4.so.2
        /**
        1) 如果共享库文件安装到了/lib或/usr/lib目录下, 那么需执行一下ldconfig命令

        -

        ldconfig命令的用途, 主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下, 搜索出可共享的动态链接库(格式如lib.so), 进而创建出动态装入程序(ld.so)所需的连接和缓存文件. 缓存文件默认为/etc/ld.so.cache, 此文件保存已排好序的动态链接库名字列表.

        -

        2) 如果共享库文件安装到了/usr/local/lib(很多开源的共享库都会安装到该目录下)或其它”非/lib或/usr/lib”目录下, 那么在执行ldconfig命令前, 还要把新共享库目录加入到共享库配置文件/etc/ld.so.conf中, 如下:

        -

        cat /etc/ld.so.conf

        include ld.so.conf.d/*.conf

        -

        echo “/usr/local/lib” >> /etc/ld.so.conf

        ldconfig

        3) 如果共享库文件安装到了其它”非/lib或/usr/lib” 目录下, 但是又不想在/etc/ld.so.conf中加路径(或者是没有权限加路径). 那可以export一个全局变量LD_LIBRARY_PATH, 然后运行程序的时候就会去这个目录中找共享库.

        -

        LD_LIBRARY_PATH的意思是告诉loader在哪些目录中可以找到共享库. 可以设置多个搜索目录, 这些目录之间用冒号分隔开. 比如安装了一个mysql到/usr/local/mysql目录下, 其中有一大堆库文件在/usr/local/mysql/lib下面, 则可以在.bashrc或.bash_profile或shell里加入以下语句即可:

        -

        export LD_LIBRARY_PATH=/usr/local/mysql/lib:$LD_LIBRARY_PATH

        -

        一般来讲这只是一种临时的解决方案, 在没有权限或临时需要的时候使用.

        -

        4)如果程序需要的库文件比系统目前存在的村文件版本低,可以做一个链接
        比如:
        error while loading shared libraries: libncurses.so.4: cannot open shared
        object file: No such file or directory

        -

        ls /usr/lib/libncu*
        /usr/lib/libncurses.a /usr/lib/libncurses.so.5
        /usr/lib/libncurses.so /usr/lib/libncurses.so.5.3

        -

        可见虽然没有libncurses.so.4,但有libncurses.so.5,是可以向下兼容的
        建一个链接就好了
        ln -s /usr/lib/libncurses.so.5.3 /usr/lib/libncurses.so.4

        -

        **/

        -

        2、安装Memcached
        wget http://www.danga.com/memcached/dist/memcached-1.2.0.tar.gz
        tar zxvf memcached-1.4.15.tar.gz
        cd memcached-1.4.15
        ./configure –prefix=/usr/local/memcached/ –with-libevent=/usr/local/libevent/
        make && make install
        3、启动Memcached
        /usr/local/memcached/bin/memcached -d -m 64 -u root -l 127.0.0.100 -p 11211 -c 128 -P /tmp/memcached.pid
        4、为了方便管理,写个SHELL脚本吧。

        -

        vi /etc/rc.d/init.d/memcached

        -
        #!/bin/sh
        -#
        -# memcached: MemCached Daemon
        -#
        -# chkconfig: - 90 25
        -# description: MemCached Daemon
        -#
        -# Source function library.
        -. /etc/rc.d/init.d/functions
        -. /etc/sysconfig/network
        -#[ ${NETWORKING} = "no" ] && exit 0
        -#[ -r /etc/sysconfig/dund ] || exit 0
        -#. /etc/sysconfig/dund
        -#[ -z "$DUNDARGS" ] && exit 0
        -start()
        -{
        -echo -n $"Starting memcached: "
        -daemon $MEMCACHED -u daemon -d -m 64 -l 127.0.0.100 -p 11211 -c 128 -P /tmp/memcached.pid
        -echo
        -}
        -stop()
        -{
        -echo -n $"Shutting down memcached: "
        -killproc memcached
        -echo
        -}
        -MEMCACHED="/usr/local/memcached/bin/memcached"
        -[ -f $MEMCACHED ] || exit 1
        -# See how we were called.
        -case "$1" in
        -start)
        -start
        -;;
        -stop)
        -stop
        -;;
        -restart)
        -stop
        -sleep 3
        -start
        -;;
        -*)
        -echo $"Usage: $0 {start|stop|restart}"
        -exit 1
        -esac
        -exit 0
        -

        5、添加Memcached开机启动
        cd /etc/rc.d/init.d/
        chmod 777 memcached
        chkconfig –add memcached
        chkconfig –level 235 memcached on
        chkconfig –list | grep memcached
        6、Memcached使用
        [root@www.111cn.net] service memcached start
        [root@www.111cn.net] service memcached stop
        [root@www.111cn.net] service memcached restart

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/2016/04/28/my-new-post/index.html b/2016/04/28/my-new-post/index.html index da0bc08..bb10d6c 100644 --- a/2016/04/28/my-new-post/index.html +++ b/2016/04/28/my-new-post/index.html @@ -3,378 +3,183 @@ - - 我的第一个博客 | 菜菜的博客 + my new post | Hexo - + - + - - - + + + - - + + - + + + + + +
        -
        -
        -
        - -
        - +
        + +
        +
        +
        + +
        + + + +
        +
        -
        + - - - - - - + + + - - - - - +
        diff --git "a/2016/04/28/mysql\344\270\273\344\273\216\345\220\214\346\255\245/index.html" "b/2016/04/28/mysql\344\270\273\344\273\216\345\220\214\346\255\245/index.html" deleted file mode 100644 index 4c0f791..0000000 --- "a/2016/04/28/mysql\344\270\273\344\273\216\345\220\214\346\255\245/index.html" +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - mysql主从同步 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - mysql主从同步 -

        - - -
        - - - - -
        - -

        前提条件两个数据库并且局域网能够访问并且两个数据库的版本号要一致(msyql -V 查看mysql版本号)。
        否则容易出现莫名错误不好处理。
        1:主数据库141(10.10.39.101)下面称作master
        2:从数据库142(10.10.49.90)下面称作slave

        -

        配置如下
        1:在主服务器上,设置一个从数据库的账户,使用REPLICATION SLAVE赋予权限,如:
        mysql> GRANT REPLICATION SLAVE ON . TO ‘slave001’@’10.10.49.90’ IDENTIFIED BY ‘1qazZZR@WSX’;

        -

        2:修改主数据库的配置文件my.cnf,开启BINLOG,并设置server-id的值,修改之后必须重启Mysql服务
        [mysqld]
        log-bin = mysql-bin
        server-id=141

        -

        3:之后可以得到主服务器当前二进制日志名和偏移量,这个操作的目的是为了在从数据库启动后,从这个
        点开始进行数据的恢复
        mysql> show master status\G;
        * 1. row *
        File: mysql-bin.000003
        Position: 243
        Binlog_Do_DB:
        Binlog_Ignore_DB:
        1 row in set (0.00 sec)
        4:停止对master数据库的更新然后导出需要同步的数据库,
        mysql> flush tables with read lock;
        mysqldump -h127.0.0.1 -p3306 -uroot -p zzr > /home/zzr.sql
        mysql> unlock tables;
        5:将刚才主数据备份的zzr.sql复制到从数据库,进行导入
        6:接着修改从数据库的my.cnf,增加server-id参数,指定复制使用的用户,主数据库服务器的ip,端口以及开始执
        行复制日志的文件和位置
        [mysqld]
        server-id=142
        log_bin = mysql-bin
        master-host =10.10.39.101
        master-user=slave001
        master-pass=1qazZZR@WSX
        master-port =20010
        master-connect-retry=60
        replicate-do-db =zzr,open
        7:在从服务器上,启动slave进程
        mysql> start slave;
        如果启动失败:在slave上强制执行
        mysql> change master to master_host=’master_host’, master_user=’you_user_name’, master_password=’you_master_user_password’,master_log_file=’mysqld-bin.000001’,master_port=3306, master_log_pos=98;
        (master_log_file和master_log_pos)的值你可以在服务器上运行 show master status; 来得到。
        然后 mysql> start slave;启动slave进程
        8:在从服务器进行show salve status验证
        mysql> SHOW SLAVE STATUS\G
        9:检测一下。看看是否主从同步了。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/mysql\350\267\250\346\225\260\346\215\256\345\272\223\346\223\215\344\275\234\347\261\273\344\274\274\344\270\216oracle\347\232\204dblink/index.html" "b/2016/04/28/mysql\350\267\250\346\225\260\346\215\256\345\272\223\346\223\215\344\275\234\347\261\273\344\274\274\344\270\216oracle\347\232\204dblink/index.html" deleted file mode 100644 index 347f9a7..0000000 --- "a/2016/04/28/mysql\350\267\250\346\225\260\346\215\256\345\272\223\346\223\215\344\275\234\347\261\273\344\274\274\344\270\216oracle\347\232\204dblink/index.html" +++ /dev/null @@ -1,392 +0,0 @@ - - - - - - - mysql跨数据库操作类似与oracle的dblink | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - mysql跨数据库操作类似与oracle的dblink -

        - - -
        - - - - -
        - -

        MySQL FEDERATED 存储引擎

        -
        MySQL中针对不同的功能需求提供了不同的存储引擎。所谓的存储引擎也就是MySQL下特定接口的具体实现。
        -FEDERATED是其中一个专门针对远程数据库的实现。一般情况下在本地数据库中建表会在数据库目录中生成相应的表定义文件,并同时生成相应的数据文件。
        -

        但通过FEDERATED引擎创建的表只是在本地有表定义文件,数据文件则存在于远程数据库中(这一点很重要)。
        通过这个引擎可以实现类似Oracle 下DBLINK的远程数据访问功能。
        使用show engines 命令查看数据库是否已支持FEDERATED引擎:

        -
        Support 的值有以下几个:
        -

        YES 支持并开启
        DEFAULT 支持并开启, 并且为默认引擎
        NO 不支持
        DISABLED 支持,但未开启

        -

        可以看出MyISAM为当前默认的引擎。
        使用FEDERATED建表语句如下:
        CREATE TABLE (……) ENGINE =FEDERATED CONNECTION=’mysql://[name]:[pass]@[location]:[port]/[db-name]/[table-name]’
        创建成功后就可直接在本地查询相应的远程表了。
        需要注意的几点:

        -
        1. 本地的表结构必须与远程的完全一样。
        -2.远程数据库目前仅限MySQL
        -3.不支持事务
        -4.不支持表结构修改
        -
        - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/nginx\345\206\205\347\275\256\345\217\230\351\207\217\345\244\247\345\205\250/index.html" "b/2016/04/28/nginx\345\206\205\347\275\256\345\217\230\351\207\217\345\244\247\345\205\250/index.html" deleted file mode 100644 index b0addd3..0000000 --- "a/2016/04/28/nginx\345\206\205\347\275\256\345\217\230\351\207\217\345\244\247\345\205\250/index.html" +++ /dev/null @@ -1,377 +0,0 @@ - - - - - - - nginx内置变量大全 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - nginx内置变量大全 -

        - - -
        - - - - -
        - -

        在配置基于nginx服务器的网站时,必然会用到 nginx内置变量 ,下面笔者将它整理成列表,把最新版本的变量列出来,以方便做配置时查询
        nginx内置变量

        -

        内置变量存放在 ngx_http_core_module 模块中,变量的命名方式和apache 服务器变量是一致的。总而言之,这些变量代表着客户端请求头的内容,例如$http_user_agent, $http_cookie, 等等。下面是nginx支持的所有内置变量:
        $arg_name
        请求中的的参数名,即“?”后面的arg_name=arg_value形式的arg_name
        $args
        请求中的参数值
        $binary_remote_addr
        客户端地址的二进制形式, 固定长度为4个字节
        $body_bytes_sent
        传输给客户端的字节数,响应头不计算在内;这个变量和Apache的mod_log_config模块中的“%B”参数保持兼容
        $bytes_sent
        传输给客户端的字节数 (1.3.8, 1.2.5)
        $connection
        TCP连接的序列号 (1.3.8, 1.2.5)
        $connection_requests
        TCP连接当前的请求数量 (1.3.8, 1.2.5)
        $content_length
        “Content-Length” 请求头字段
        $content_type
        “Content-Type” 请求头字段
        $cookie_name
        cookie名称
        $document_root
        当前请求的文档根目录或别名
        $document_uri
        同 $uri
        $host
        优先级如下:HTTP请求行的主机名>”HOST”请求头字段>符合请求的服务器名
        $hostname
        主机名
        $http_name
        匹配任意请求头字段; 变量名中的后半部分“name”可以替换成任意请求头字段,如在配置文件中需要获取http请求头:“Accept-Language”,那么将“-”替换为下划线,大写字母替换为小写,形如:$http_accept_language即可。
        $https
        如果开启了SSL安全模式,值为“on”,否则为空字符串。
        $is_args
        如果请求中有参数,值为“?”,否则为空字符串。
        $limit_rate
        用于设置响应的速度限制,详见 limit_rate。
        $msec
        当前的Unix时间戳 (1.3.9, 1.2.6)
        $nginx_version
        nginx版本
        $pid
        工作进程的PID
        $pipe
        如果请求来自管道通信,值为“p”,否则为“.” (1.3.12, 1.2.7)
        $proxy_protocol_addr
        获取代理访问服务器的客户端地址,如果是直接访问,该值为空字符串。(1.5.12)
        $query_string
        同 $args
        $realpath_root
        当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径。
        $remote_addr
        客户端地址
        $remote_port
        客户端端口
        $remote_user
        用于HTTP基础认证服务的用户名
        $request
        代表客户端的请求地址
        $request_body
        客户端的请求主体
        此变量可在location中使用,将请求主体通过proxy_pass, fastcgi_pass, uwsgi_pass, 和 scgi_pass传递给下一级的代理服务器。
        $request_body_file
        将客户端请求主体保存在临时文件中。文件处理结束后,此文件需删除。如果需要之一开启此功能,需要设置client_body_in_file_only。如果将次文件传递给后端的代理服务器,需要禁用request body,即设置proxy_pass_request_body off,fastcgi_pass_request_body off, uwsgi_pass_request_body off, or scgi_pass_request_body off 。
        $request_completion
        如果请求成功,值为”OK”,如果请求未完成或者请求不是一个范围请求的最后一部分,则为空。
        $request_filename
        当前连接请求的文件路径,由root或alias指令与URI请求生成。
        $request_length
        请求的长度 (包括请求的地址, http请求头和请求主体) (1.3.12, 1.2.7)
        $request_method
        HTTP请求方法,通常为“GET”或“POST”
        $request_time
        处理客户端请求使用的时间 (1.3.9, 1.2.6); 从读取客户端的第一个字节开始计时。
        $request_uri
        这个变量等于包含一些客户端请求参数的原始URI,它无法修改,请查看$uri更改或重写URI,不包含主机名,例如:”/cnphp/test.php?arg=freemouse”。
        $scheme
        请求使用的Web协议, “http” 或 “https”
        $sent_http_name
        可以设置任意http响应头字段; 变量名中的后半部分“name”可以替换成任意响应头字段,如需要设置响应头Content-length,那么将“-”替换为下划线,大写字母替换为小写,形如:$sent_http_content_length 4096即可。
        $server_addr
        服务器端地址,需要注意的是:为了避免访问linux系统内核,应将ip地址提前设置在配置文件中。
        $server_name
        服务器名,www.cnphp.info
        $server_port
        服务器端口
        $server_protocol
        服务器的HTTP版本, 通常为 “HTTP/1.0” 或 “HTTP/1.1”
        $status
        HTTP响应代码 (1.3.2, 1.2.2)
        $tcpinfo_rtt, $tcpinfo_rttvar, $tcpinfo_snd_cwnd, $tcpinfo_rcv_space
        客户端TCP连接的具体信息
        $time_iso8601
        服务器时间的ISO 8610格式 (1.3.12, 1.2.7)
        $time_local
        服务器时间(LOG Format 格式) (1.3.12, 1.2.7)
        $uri
        请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改,$uri不包含主机名,如”/foo/bar.html”。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/redis\347\232\204\345\233\276\345\275\242\345\214\226\345\267\245\345\205\267/index.html" "b/2016/04/28/redis\347\232\204\345\233\276\345\275\242\345\214\226\345\267\245\345\205\267/index.html" deleted file mode 100644 index 114710e..0000000 --- "a/2016/04/28/redis\347\232\204\345\233\276\345\275\242\345\214\226\345\267\245\345\205\267/index.html" +++ /dev/null @@ -1,447 +0,0 @@ - - - - - - - redis的图形化工具 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - redis的图形化工具 -

        - - -
        - - - - -
        - -

        redis的图形化工具

        -

        2015-10-29 Rails365 运维帮

        -
          -
        1. 介绍
        2. -
        -

        本篇会介绍几个关于redis的图形化的监控工具和管理工具。

        -
          -
        1. redis-stat
        2. -
        -

        redis-stat(https://github.com/junegunn/redis-stat)提供终端和web端的监控页面,它安装和使用起来很简单。

        -

        安装只需要一条指令。

        -

        $ gem install redis-stat

        -

        运行更简单。

        -

        $ redis-stat

        -

        效果图如下:

        -

        也可以以server的形式运行,默认情况下是监听在63790端口。

        -

        $ redis-stat –server

        -

        还可以以后台进程的形式开启服务。

        -

        redis-stat –server –daemon

        -

        效果图如下:

        -
          -
        1. redis-browser
        2. -
        -

        redis-browser(https://github.com/monterail/redis-browser)是redis的web端的图形化管理工具。利用它可以查看和管理redis的数据,界面很简洁,安装和使用这个工具也比较简单,而且它还能和rails应用结合在一起。

        -

        安装。

        -

        $ gem install redis-browser

        -

        要使用也只是需要一条命令。

        -

        $ redis-browser

        -

        效果图如下:

        -
          -
        1. redmon
        2. -
        -

        redmon也是一个监控redis运行情况的页面监控工具。

        -

        安装。

        -

        $ gem install redmon

        -

        使用。

        -

        $ redmon

        -

        效果图如下:

        -

        完结。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/2016/04/28/solr-query/index.html b/2016/04/28/solr-query/index.html deleted file mode 100644 index 6f6fc33..0000000 --- a/2016/04/28/solr-query/index.html +++ /dev/null @@ -1,413 +0,0 @@ - - - - - - - solr-query | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - solr-query -

        - - -
        - - - - -
        - -

        一. Query参数

        -
          -
        1. CoreQueryParam查询的参数
          1) q: 查询字符串,必须的。
          2) q.op: 覆盖schema.xml的defaultOperator(有空格时用”AND”还是用”OR”操作逻辑),一般默认指定。
          3) df: 默认的查询字段,一般默认指定。
          4) qt: query type,指定查询使用的Query Handler,默认为“standard”。
          5) wt: writer type。指定查询输出结构格式,默认为“xml”。在solrconfig.xml中定义了查询输出格式:xml、json、python、ruby、php、phps、custom。
          6) echoHandler:是否在查询结果中显示使用的Query Handler名称。
          7) echoParams:是否显示查询参数。none:不显示;explicit:只显示查询参数;all:所有,包括在solrconfig.xml定义的Query Handler参数。
          8) indent - 返回的结果是否缩进,默认关闭,用 indent=true|on 开启,一般调试json,php,phps,ruby输出才有必要用这个参数。
          9) version - 查询语法的版本,建议不使用它,由服务器指定默认值。

          -
        2. -
        3. CommonQueryParameters
          1) sort:排序,格式:sort=+[,+]„ 。
          示例:(inStock desc, price asc)表示先 “inStock” 降序, 再 “price” 升序,默认是相关性降序。。
          2) start:用于分页定义结果起始记录数,默认为0。
          3) rows:用于分页定义结果每页返回记录数,默认为10。
          4) fq:filter query。使用Filter Query可以充分利用Filter Query Cache,提高检索性能。作用:在q查询符合结果中同时是fq查询符合的,
          例如:q=mm&fq=date_time:[20081001 TO 20091031],找关键字mm,并且date_time是20081001到20091031之间的。
          5) fl:field list。指定返回结果字段。以空格“ ”或逗号“,”分隔。
          6) debugQuery:设置返回结果是否显示Debug信息。
          7) explainOther:设置当debugQuery=true时,显示其他的查询说明。
          8) defType:设置查询解析器名称。
          9) timeAllowed:设置查询超时时间。
          10) omitHeader:设置是否忽略查询结果返回头信息,默认为“false”。

          -
        4. -
        -

        二. 查询语法

        -
          -
        1. 匹配所有文档::
        2. -
        3. 强制、阻止和可选查询:
          1) Mandatory:查询结果中必须包括的(for example, only entry name containing the word make) Solr/Lucene Statement:+make, +make +up ,+make +up +kiss
          2) prohibited:(for example, all documents except those with word believe) Solr/Lucene Statement:+make +up -kiss 3) optional:Solr/Lucene Statement:+make +up kiss

          -
        4. -
        5. 布尔操作:AND、OR和NOT布尔操作(必须大写)与Mandatory、optional和prohibited相似。
          1) make AND up = +make +up :AND左右两边的操作都是mandatory
          2) make || up = make OR up=make up :OR左右两边的操作都是optional
          3) +make +up NOT kiss = +make +up –kiss
          4) make AND up OR french AND Kiss不可以达到期望的结果,因为AND两边的操作都是mandatory的。

          -
        6. -
        7. 子表达式查询(子查询):可以使用“()”构造子查询。
          For ex:(make AND up) OR (french AND Kiss)

          -
        8. -
        9. 子表达式查询中阻止查询的限制:
          For ex:make (-up):只能取得make的查询结果;要使用make (-up :)查询make或者不包括up的结果。

          -
        10. -
        11. 多字段fields查询:通过字段名加上分号的方式(fieldName:query)来进行查询
          For ex:entryNm:make AND entryId:3cdc86e8e0fb4da8ab17caed42f6760c
        12. -
        13. 通配符查询(wildCard Query):
          1) 通配符?和:“”表示匹配任意字符;“?”表示匹配出现的位置。
          For ex:ma?(ma后面的一个位置匹配),ma??(ma后面两个位置都匹配)
          2) 查询字符必须要小写:+Ma +be可以搜索到结果;+Ma +Be没有搜索结果
          3) 查询速度较慢,尤其是通配符在首位:主要原因一是需要迭代查询字段中的每个term,判断是否匹配;二是匹配上的term被加到内部的查询,当terms数量达到1024的时候,查询会失败。
          4) Solr中默认通配符不能出现在首位(可以修改QueryParser,设置 setAllowLeadingWildcard为true)
          5) set setAllowLeadingWildcard to true.
        14. -
        15. 模糊查询、相似查询:不是精确的查询,通过对查询的字段进行重新插入、删除和转换来取得得分较高的查询解决(由Levenstein Distance Algorithm算法支持)。
          1) 一般模糊查询:for ex:make-believ~
          2) 门槛模糊查询:对模糊查询可以设置查询门槛,门槛是0~1之间的数值,门槛
          越高表面相似度越高。For ex:make-believ~0.5、make-believ~0.8、make-believ~0.9
        16. -
        17. 范围查询(Range Query):Lucene支持对数字、日期甚至文本的范围查询。结束的范围可以使用“”通配符。
          For ex:
          1) 日期范围(ISO-8601 时间GMT):sa_type:2 AND a_begin_date:[1990-01-01T00:00:00.000Z TO 1999-12-31T24:59:99.999Z]
          2) 数字:salary:[2000 TO
          ] 3) 文本:entryNm:[a TO a]

          -
        18. -
        19. 日期匹配:YEAR, MONTH, DAY, DATE (synonymous with DAY) HOUR, MINUTE, SECOND, MILLISECOND, and MILLI (synonymous with MILLISECOND)可以被标志成日期。
          For ex:
          1) r_event_date:[ TO NOW-2YEAR]:2年前的现在这个时间
          2) r_event_date:[
          TO NOW/DAY-2YEAR]:2年前前一天的这个时间

          -
        20. -
        -

        三. 函数查询(Function Query)
        函数查询 可以利用 numeric域的值 或者 与域相关的的某个特定的值的函数,来对文档进行评分。

        -
          -
        1. 使用函数查询的方法
          这里主要有三种方法可以使用函数查询,这三种s方法都是通过solr http接口的。
          1) 使用FunctionQParserPlugin。ie: q={!func}log(foo)
          2) 使用“val”内嵌方法内嵌在正常的solr查询表达式中。即,将函数查询写在 q这个参数中,这时候,我们使用“val”将函数与其他的查询加以区别。 ie:entryNm:make && val:ord(entryNm)
          3) 使用dismax中的bf参数使用明确为函数查询的参数,比如说dismax中的bf(boost function)这个参数。 注意:bf这个参数是可以接受多个函数查询的,它们之间用空格隔开,它们还可以带上权重。所以,当我们使用bf这个参数的时候,我们必须保证单个函数中是没有空格出现的,不然程序有可能会以为是两个函数。
          For ex:
          q=dismax&bf=”ord(popularity)^0.5 recip(rord(price),1,1000,1000)^0.3 2. 函数的格式(Function Query Syntax) 目前,function query 并不支持 a+b 这样的形式,我们得把它写成一个方法形式,这就是 sum(a,b).

          -
        2. -
        3. 使用函数查询注意事项
          1) 用于函数查询的field必须是被索引的;
          2) 字段不可以是多值的(multi-value)

          -
        4. -
        5. 可以利用的函数 (available function)
          1) constant:支持有小数点的常量; 例如:1.5 ;SolrQuerySyntax:val:1.5
          2) fieldvalue:这个函数将会返回numeric field的值,这个域必须是indexd的,非multiValued的。格式很简单,就是该域的名字。如果这个域中没有这样的值,那么将会返回0。
          3) ord:对于一个域,它所有的值都将会按照字典顺序排列,这个函数返回你要查询的那个特定的值在这个顺序中的排名。这个域,必须是非multiValued的,当没有值存在的时候,将返回0。例如:某个特定的域只能去三个值,“apple”、“banana”、“pear”,那么ord(“apple”)=1,ord(“banana”)=2,ord(“pear”)=3.需要注意的是,ord()这个函数,依赖于值在索引中的位置,所以当有文档被删除、或者添加的时候,ord()的值就会发生变化。当你使用MultiSearcher的时候,这个值也就是不定的了。
          4) rord:这个函数将会返回与ord相对应的倒排序的排名。 格式: rord(myIndexedField)。
          5) sum:这个函数的意思就显而易见啦,它就是表示“和”啦。格式:sum(x,1) 、sum(x,y)、 sum(sqrt(x),log(y),z,0.5)
          6) product:product(x,y,…)将会返回多个函数的乘积。格式:product(x,2)、product(x,y)
          7) div:div(x,y)表示x除以y的值,格式:div(1,x)、div(sum(x,100),max(y,1))
          8) pow:pow表示幂值。pow(x,y) =x^y。例如:pow(x,0.5) 表示开方pow(x,log(y))
          9) abs:abs(x)将返回表达式的绝对值。格式:abs(-5)、 abs(x)
          10) log:log(x)将会返回基数为10,x的对数。格式: log(x)、 log(sum(x,100))
          11) Sqrt:sqrt(x) 返回 一个数的平方根。格式:sqrt(2)、sqrt(sum(x,100))
          12) Map:如果 x>=min,且x<=max,那么map(x,min,max,target)=target.如果 x不在[min,max]这个区间内,那么map(x,min,max,target)=x. 格式:map(x,0,0,1)
          13) Scale:scale(x,minTarget,maxTarget) 这个函数将会把x的值限制在[minTarget,maxTarget]范围内。 14) query :query(subquery,default)将会返回给定subquery的分数,如果subquery与文档不匹配,那么将会返回默认值。任何的查询类型都是受支持的。可以通过引用的方式,也可以直接指定查询串。
          例子:q=product(popularity, query({!dismax v=’solr rocks’}) 将会返回popularity和通过dismax 查询得到的分数的乘积。
          q=product(popularity, query($qq)&qq={!dismax}solr rocks 跟上一个例子的效果是一样的。不过这里使用的是引用的方式
          q=product(popularity, query($qq,0.1)&qq={!dismax}solr rocks 在前一个例子的基础上又加了一个默认值。
          15) linear: inear(x,m,c)表示 mx+c ,其中m和c都是常量,x是一个变量也可以是一个函数。例如: linear(x,2,4)=2x+4.
          16) Recip:recip(x,m,a,b)=a/(m*x+b)其中,m、a、b是常量,x是变量或者一个函数。当a=b,并且x>=0的时候,这个函数的最大值是1,值的大小随着x的增大而减小。例如:recip(rord(creationDate),1,1000,1000)
          17) Max: max(x,c)将会返回一个函数和一个常量之间的最大值。 例如:max(myfield,0)

          -
        6. -
        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/\346\226\260\346\265\252\345\274\200\346\272\220\345\210\206\345\270\203\345\274\217\346\241\206\346\236\266-motan/index.html" "b/2016/04/28/\346\226\260\346\265\252\345\274\200\346\272\220\345\210\206\345\270\203\345\274\217\346\241\206\346\236\266-motan/index.html" deleted file mode 100644 index 9c8da11..0000000 --- "a/2016/04/28/\346\226\260\346\265\252\345\274\200\346\272\220\345\210\206\345\270\203\345\274\217\346\241\206\346\236\266-motan/index.html" +++ /dev/null @@ -1,448 +0,0 @@ - - - - - - - 新浪开源分布式框架--motan | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - 新浪开源分布式框架--motan -

        - - -
        - - - - -
        - -

        开源地址 https://github.com/weibocom/motan/wiki/zh_quickstart

        -

        zh_quickstart

        -

        axb edited this page 2 days ago · 2 revisions
        Pages 8

        -

        Documents

        -

        Overview
        Quick Start
        User Guide(Preparing)
        Develop Guide(Preparing)
        FAQ
        中文文档

        -

        概述
        快速入门
        用户指南
        开发指南(准备中)
        常见问题
        Clone this wiki locally

        -

        Clone in Desktop
        快速入门
        简单调用示例
        集群调用示例
        使用Consul作为注册中心
        使用ZooKeeper作为注册中心
        快速入门中会给出一些基本使用场景下的配置方式,更详细的使用文档请参考用户指南.
        如果要执行快速入门介绍中的例子,你需要:
        JDK 1.7或更高版本。
        java依赖管理工具,如Maven或Gradle。
        简单调用示例

        -

        在pom中增加依赖

        -
        com.weibo
        motan-core
        0.0.1


        com.weibo
        motan-transport-netty
        0.0.1
        - - -


        com.weibo
        motan-springsupport
        0.0.1

        -


        org.springframework
        spring-context
        4.2.4.RELEASE

        为调用方和服务方创建接口。
        src/main/java/quickstart/FooService.java
        package quickstart;

        -

        publicinterfaceFooService {
        public String hello(String name);
        }
        实现服务方逻辑。
        src/main/java/quickstart/FooServiceImpl.java
        package quickstart;

        -

        import org.springframework.context.ApplicationContext;
        import org.springframework.context.support.ClassPathXmlApplicationContext;

        -

        publicclassFooServiceImplimplementsFooService {

        -
        public String hello(String name) {
        -    System.out.println(name +" invoked rpc service");
        -    return"hello "+ name;
        -}
        -
        -publicstaticvoidmain(String[] args) throws InterruptedException {
        -    ApplicationContext applicationContext =new ClassPathXmlApplicationContext("classpath:motan_server.xml");
        -    System.out.println("server start...");
        -}
        -

        }
        src/main/resources/motan_server.xml
        <?xml version=”1.0” encoding=”UTF-8”?>

        - - -
        <!-- service implemention bean -->
        -<beanid="serviceImpl"class="quickstart.FooServiceImpl" />
        -<!-- exporting service by motan -->
        -<motan:serviceinterface="quickstart.FooService"ref="serviceImpl"export="8002" />
        -


        执行FooServiceImpl类中的main函数将会启动motan服务,并监听8002端口.
        实现服务调用方。
        src/main/resources/motan_client.xml
        <?xml version=”1.0” encoding=”UTF-8”?>

        - - -
        <!-- reference to the remote service -->
        -<motan:refererid="remoteService"interface="quickstart.FooService"directUrl="localhost:8002"/>
        -


        src/main/java/quickstart/Client.java
        package quickstart;

        -

        import org.springframework.context.ApplicationContext;
        import org.springframework.context.support.ClassPathXmlApplicationContext;

        -

        publicclassClient {

        -
        publicstaticvoidmain(String[] args) throws InterruptedException {
        -    ApplicationContext ctx =new ClassPathXmlApplicationContext("classpath:motan_client.xml");
        -    FooService service = (FooService) ctx.getBean("remoteService");
        -    System.out.println(service.hello("motan"));
        -}
        -

        }
        执行Client类中的main函数将执行一次远程调用,并输出结果。
        集群调用示例

        -

        在集群环境下使用motan需要依赖外部服务发现组件,目前支持consul或zookeeper。
        使用Consul作为注册中心

        -

        Consul安装与启动

        -

        安装(官方文档)

        -

        这里以linux为例

        wget https://releases.hashicorp.com/consul/0.6.4/consul_0.6.4_linux_amd64.zip
        unzip consul_0.6.4_linux_amd64.zip
        sudo mv consul /bin
        启动(官方文档)

        -

        测试环境启动:
        consul agent -dev
        ui后台 http://localhost:8500/ui
        motan-Consul配置

        -

        在server和client中添加motan-registry-consul依赖

        -


        com.weibo
        motan-registry-consul
        0.0.1

        在server和client的配置文件中分别增加consul registry定义。

        -


        在motan client及server配置改为通过registry服务发现。
        client

        server

        server程序启动后,需要显式调用心跳开关,注册到consul。
        MotanSwitcherUtil.setSwitcher(ConsulConstants.NAMING_PROCESS_HEARTBEAT_SWITCHER, true)
        进入ui后台查看服务是否正常提供调用
        启动client,调用服务
        使用ZooKeeper作为注册中心

        -

        ZooKeeper安装与启动(官方文档)

        -

        单机版安装与启动
        wget http://mirrors.cnnic.cn/apache/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz
        tar zxvf zookeeper-3.4.8.tar.gz

        -

        cd zookeeper-3.4.8/conf/
        cp zoo_sample.cfg zoo.cfg

        -

        cd ../
        sh bin/zkServer.sh start
        motan-ZooKeeper配置

        -

        在server和client中添加motan-registry-zookeeper依赖

        -


        com.weibo
        motan-registry-zookeeper
        0.0.1

        在server和client的配置文件中分别增加zookeeper registry定义。
        zookeeper为单节点

        -


        zookeeper多节点集群

        -


        在motan client及server配置改为通过registry服务发现。
        client

        -


        server

        -


        启动client,调用服务

        -
        - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/04/28/\350\277\234\347\250\213\345\205\215\345\257\206\347\240\201copy\346\226\207\344\273\266/index.html" "b/2016/04/28/\350\277\234\347\250\213\345\205\215\345\257\206\347\240\201copy\346\226\207\344\273\266/index.html" deleted file mode 100644 index f7eb201..0000000 --- "a/2016/04/28/\350\277\234\347\250\213\345\205\215\345\257\206\347\240\201copy\346\226\207\344\273\266/index.html" +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - 远程免密码copy文件 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - 远程免密码copy文件 -

        - - -
        - - - - -
        - -

        1.先安装expect
        yum -y install expect
        2.脚本内容:
        cat auto_svn.sh

        -

        #!/bin/bash
        passwd=’123456’
        /usr/bin/expect <<-EOF
        set time 30
        spawn ssh -p18330 root@192.168.10.22
        expect {
        yes/no” { send “yes\r”; exp_continue }
        password:” { send “$passwd\r” }
        }
        expect “#”
        send “cd /home/trunk\r”
        expect “
        #”
        send “svn up\r”
        expect “*#”
        send “exit\r”
        interact
        expect eof
        EOF

        -

        expect的简单用法及举例

        -

        #!/usr/bin/expect -f
        set password 123456

        -

        #download
        spawn scp root@192.168.1.218:/root/a.wmv /home/yangyz/
        set timeout 300
        expect “root@192.168.1.218’s password:”
        set timeout 300
        send “$passwordr”
        set timeout 300
        send “exitr”
        expect eof

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/05/03/\346\261\275\350\275\246\344\271\213\345\256\266\351\205\215\347\275\256\347\256\241\347\220\206\347\263\273\347\273\237AutoCMS/index.html" "b/2016/05/03/\346\261\275\350\275\246\344\271\213\345\256\266\351\205\215\347\275\256\347\256\241\347\220\206\347\263\273\347\273\237AutoCMS/index.html" deleted file mode 100644 index 2725add..0000000 --- "a/2016/05/03/\346\261\275\350\275\246\344\271\213\345\256\266\351\205\215\347\275\256\347\256\241\347\220\206\347\263\273\347\273\237AutoCMS/index.html" +++ /dev/null @@ -1,437 +0,0 @@ - - - - - - - 汽车之家配置管理系统AutoCMS | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - 汽车之家配置管理系统AutoCMS -

        - - -
        - - - - -
        - -

        Break Wang
        Be slow to promise and quick to perform.

        -

        Email
        Twitter
        Google+
        汽车之家配置管理系统AutoCMS

        -

        介绍

        -

        作者介绍

        -

        本文作者是王显宝wangxianbao@autohome.com.cn,主要负责AutoCMS的开发工作和缓存平台的运维工作,擅长python自动化运维,分布式缓存和分布式文件系统应用管理。

        -

        团队介绍

        -

        我们是汽车之家运维团队,是汽车之家技术部里最为核心的团队,由op和dev共同组成。我们的目标是为汽车之家集团打造一个高性能,高可扩展,低成本,并且稳定可靠的网站基础设施平台。

        -

        团队技术博客地址为 http://autohomeops.github.io/

        -

        联系方式:可以通过邮件或者在官方技术博客留言跟我们交流。

        -

        大纲

        -
          -
        1. 前言
        2. -
        3. AutoCMS使用方法介绍
        4. -
        5. 系统架构说明
        6. -
        7. 配置案例
        8. -
        9. 小结
          1.前言
        10. -
        -

        AutoCMS 是汽车之家目前正在使用的统一配置管理工具,本文将详细介绍该系统的使用方法和架构实现。 先来回顾下汽车之家软件部署和配置文件管理的经历的几个阶段:

        -

        原始阶段(依靠手工部署,wiki制定规范)
        最初阶段软件部署和配置文件管理完全依赖人力完成。部署新业务,运维根据研发测试环境的软件版本,去官网下载软件包,在服务器上编译安装,然后根据研发需求,手工配置文件,启动服务。该阶段暴露出诸多问题:
        软件包的来源没有强制规范,带来了很大的安全隐患。
        新业务上线或者业务紧急扩容,运维投入大量人力来完成繁重的体力劳动。
        部署目录标准化依赖wiki规范维护,人力部署的时候经常会出现问题
        半自动化阶段(依赖人力打包,yum方式部署)
        随着运维自动化的推行,搭建自己的yum源,自己打包来规范软件版本来源和部署目录。需要批量部署的时候,逐台登录服务器执行yum install 安装相关的软件包即可,该阶段已经节省了很大的人力,不过依然需要逐台登录服务器去修改配置文件才能够满足上线需求。配置文件变更前的备份和故障后的恢复依然需要人力维护。
        自动化阶段
        现在正在使用的管理系统AutoCMS,实现页面化管理软件部署和配置文件,实现快速批量管理软件。
        下面将详细介绍一下我们正在使用的AutoCMS。

        -

        2.AutoCMS使用方法介绍

        -

        AutoCMS 是基于puppet实现的软件部署和配置文件管理的一套系统,避免手动安装部署软件,提供可靠的软件标准化部署,配置文件和基础服务的管理服务。 本系统主要实现以下几点功能:

        -

        批量部署软件包
        页面管理软件的配置文件变更,支持备份和回滚
        多环境部署,开发,测试,线上环境相互隔离
        灰度推送配置
        远程执行安全的命令
        基本的统计功能
        使用者操作流程如下:

        -

        创建主机组

        -

        主机组是需要批量部署同一个软件或者下发相同配置的主机的集合,创建主机组的同时需要关联一下配置模块,该配置模块是提前写好的puppet模块。

        -

        添加主机

        -

        将需要部署该模块的主机加入该组内(本系统中主机的输入源是cmdb接口)

        -

        选择环境

        -

        根据需求选择该主机的环境,各个环境的配置数据独立存储,彼此不受影响。

        -

        配置部署参数

        -

        根据配置模块不同,编写配置文件,生成不同的配置页面,业务运维只要在页面上选择相应的参数,puppet会根据这些参数生成相应的配置文件。

        -

        推送配置

        -

        业务运维再前端页面配置完成后,返回主机组管理页面,选择主机并进行推送配置,便会触发相关主机的puppet agent 运行,部署相应的软件和配置。

        -

        3.系统架构说明

        -

        AutoCMS使用django作为前端框架,后端部署程序主要基于puppet实现,通过puppet的enc和report功能实现完整的部署和配置逻辑,整体架构如下:

        -

        简要分析下:

        -

        前端配置页面
        由于各个软件配置选项不同,所以前端的软件参数配置页面要采用配置文件动态生成。
        数据存储
        设计初期,我们在mysql和MongoDB之间选择了MongoDB,主要出于一下几点考虑:
        Schema-less,json风格存储:由于配置数据根据软件的不同,其数据不能依靠key-value能够存放的,每个软件的配置项可能会有自定义的结构,所以采用了json格式来处理,而MongoDB的bson恰好能够满足需求,省去了在mysql中频繁使用外键。此外,json格式数据易于掌握和理解,存储的数据一目了然。

        -

        CRUD方便快捷,支持范围查询和正则查询,还支持支持upsert选项(如果不存在则插入)。

        -

        与其他nosql产品相比,我们对mongodb的持久化和高可用方案更加熟悉。

        -

        部署实施层

        -

        该部分是通过puppet模块实现,模块中引用的参数通过自定义facter或者使用enc来读取配置存储的MongoDB获得,

        -

        多实例部署的实现:
        一台主机的多实例部署通过create_resources函数实现,例如一台主机需要配置多个tomcat实例,数据会是如下结构:

        -

        多环境分离实现: 本系统中puppet代码部分有git作为版本控制,通过git自身的多分支的特性来做puppet代码层面的环境分离。根据需求来执行不同分支的puppet代码即可。
        4.配置案例

        -

        下面以tomcat自动部署模块为例,进行详细介绍:

        -

        根据部署需求,设计出配置数参数的存储结构
        填写前端布局的配置文件,自动生成前端配置页面。

        -

        编写puppet自动部署模块。

        -

        编写enc转换程序
        以上准备工作做好之后,系统上线,运维可以创建主机组,关联tomcat模块,将需要批量部署的主机加入该组内,并进行参数配置
        配置完成后,进行灰度发布验证没问题后,进行全量推送,观察状态,配置完成。

        -
          -
        1. 小结
        2. -
        -

        随着云时代的到来,很多服务器可以定制镜像实现所需的运行环境,不过随着业务规模的扩大,服务器的个性化配置会越来越多,镜像的维护工作也会逐步增加,使用AutoCMS可以灵活的解决这个问题。把各自的配置需求写成配置模块,并把服务器分组,批量推送下去,可以节省运维人员很多体力劳动。让运维人员将精力投入到更有价值的工作中。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/05/\345\246\202\344\275\225\344\275\277\347\224\250GoEasy\345\256\236\347\216\260web\345\256\236\346\227\266\346\216\250\351\200\201/index.html" "b/2016/06/05/\345\246\202\344\275\225\344\275\277\347\224\250GoEasy\345\256\236\347\216\260web\345\256\236\346\227\266\346\216\250\351\200\201/index.html" deleted file mode 100644 index 5865fc0..0000000 --- "a/2016/06/05/\345\246\202\344\275\225\344\275\277\347\224\250GoEasy\345\256\236\347\216\260web\345\256\236\346\227\266\346\216\250\351\200\201/index.html" +++ /dev/null @@ -1,386 +0,0 @@ - - - - - - - 如何使用GoEasy实现web实时推送 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - 如何使用GoEasy实现web实时推送 -

        - - -
        - - - - -
        - -

        之前项目需要做一个推送功能,最开始我没有想过用第三方推送服务。想着可以用已知技术方式完成,例如定时到服务器看看是否有新的消息,有的话,就读取下来并显示,但是这种方式很浪费客户以及服务器的资源,当然这种方式在我们项目里是不可取的。再后来我在网上搜了一些,说是可以用web socket实现我的功能,但是我在网上查了一下使用方式,看了一上午一头雾水。即使我可以一周两周内用websocket实现我的推送,那我又拿什么来保证我自己写的推送程序的到达率和速度呢?维护成本一定也会随着增加!况且我们也不允许花太多开发成本在这个项目上!

        -

        经过上面的一番周折后,我跟项目组提出使用第三方的推送服务,原因很简单,第三方推送服务可以满足我们的需求,缩短我们的开发测试维护成本,术业有专攻,它们在推送方面更有优势,服务质量也有保证!经过几番对比后,我们最终决定使用了GoEasy推送。 它真正的从根本上解决了我们的问题!对于他们的服务质量很满意,注册成功后,你可以获得他们的联系方式,问题处理得很及时,不像有些公司的客服,发封邮件好几天都没有任何信息!从而也解决了我们的后顾之忧!

        -

        GoEasy实现向特定用户群推送的原理:
        知道了他们的推送原理,可以更加方便我们了解他们的服务,以及理解我们写的代码。其实原理很简单,只需要确定哪些用户需要接收信息,然后让这些用户都订阅一个相同的channel(频道)。 然后再往这个平台上推送消息即可!所有关键在于channel,channel一致,则可以接收到信息,否则收不到!

        -

        对于订阅必须要的信息有:Appkey, channel
        对于推送必须要的信息有:Appkey, channel, content

        -

        废话不多说,直接进入正题,如何实现:

        -

        从GoEasy获取appkey
        appkey是验证用户的有效性的唯一标识。
        注册账号: GoEasy官网:https://goeasy.io
        用注册好的账号登录到GoEasy的后台管理系统,创建您自己应用(application).
        Application创建好之后系统会自动为您生成appkey
        系统会生成两个keys,一个Super key和一个Subscribe key;它们的区别在于前者既可以订阅又可以推送,但后者只能用于订阅。

        -

        用GoEasy实现订阅(接收)的实例

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <script type="text/javascript" src="https://cdn.goeasy.io/goeasy.js"></script>     
        <script type="text/javascript">
        var goEasy = new GoEasy({appkey: 'your appkey'});
        goEasy.subscribe({
        channel: 'your_channel',
        onMessage: function(message){
        alert('接收到消息:'+message.content);//拿到了信息之后,你可以做你任何想做的事
        }
        });
        </script>

        -

        有了这几行代码后,只要保证网络畅通的情况下,页面会自动弹出你从任何平台上推送的信息。

        -

        用GoEasy的三种方式实现推送及接收
        目前GoEasy支持三种推送方式: Java后台推送(它们有提供JAVA SDK和 maven远程仓库), JS推送,RestAPI推送(有了RestAPI,我们就可以用PHP, .NET, Ruby…来推送信息了,很方便)

        -

        说了这么多,来我们看一下怎么用GoEasy的三种方式分别实现推送吧.

        -

        用GoEasy SDK推送
        引入GoEasy SDK
        方式一,直接在goeasy的官网上进行下载
        GoEasy SDK下载链接:http://maven.goeasy.io/service/local/artifact/maven/redirect?r=releases&g=io.goeasy&a=goeasy-sdk&v=LATEST&e=jar
        方式二,用maven远程库直接导入到项目中,下面是GoEasy远程maven库的配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        <repository>
        <id>goeasy</id>
        <name>goeasy</name>
        <url>http://maven.goeasy.io/content/repositories/releases/</url>
        </repository>

        <dependency>
        <groupId>io.goeasy</groupId>
        <artifactId>goeasy-sdk</artifactId>
        <version>0.3.1</version>
        </dependency>

        -

        实例化GoEasy对象并推送
        GoEasy goEasy = new GoEasy(“your appkey”);
        goEasy.publish(‘your_channel’, ‘First message’);
        JavaScript推送
        引入goeasy.js

        1
        <script type="text/javascript" src="https://cdn.goeasy.io/goeasy.js"></script>

        -

        实例化Goeasy对象,并用publish函数进行推送

        1
        2
        3
        4
        5
        6
        7
        <script type="text/javascript">
        var goEasy = new GoEasy({appkey: 'your appkey'});
        goEasy. publish ({
        channel: 'your_channel',
        message: 'Second message!'
        });
        </script>

        -

        用RestAPI进行推送
        URL: https://goeasy.io/goeasy/publish
        Method: post
        参数:appkey, channel, content
        例如:https://goeasy.io/goeasy/publish?appkey={your_appkey}&channel={your_channel}&content={your_message}
        GoEasy官网:https://goeasy.io
        快速入门:https://goeasy.io/www/started.jsp
        文档下载:https://goeasy.io/www/docs.jsp

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/2016/06/12/hello-world/index.html b/2016/06/12/hello-world/index.html deleted file mode 100644 index 0cf05d5..0000000 --- a/2016/06/12/hello-world/index.html +++ /dev/null @@ -1,381 +0,0 @@ - - - - - - - Hello World | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - Hello World -

        - - -
        - - - - -
        - -

        Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

        -

        Quick Start

        Create a new post

        1
        $ hexo new "My New Post"
        -

        More info: Writing

        -

        Run server

        1
        $ hexo server
        -

        More info: Server

        -

        Generate static files

        1
        $ hexo generate
        -

        More info: Generating

        -

        Deploy to remote sites

        1
        $ hexo deploy
        -

        More info: Deployment

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/12/\345\220\204\345\244\247\345\271\263\345\217\260\345\205\215\350\264\271\346\216\245\345\217\243/index.html" "b/2016/06/12/\345\220\204\345\244\247\345\271\263\345\217\260\345\205\215\350\264\271\346\216\245\345\217\243/index.html" deleted file mode 100644 index 3e0a3d3..0000000 --- "a/2016/06/12/\345\220\204\345\244\247\345\271\263\345\217\260\345\205\215\350\264\271\346\216\245\345\217\243/index.html" +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - 各大平台免费接口 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - 各大平台免费接口 -

        - - -
        - - - - -
        - -

        电商接口

        -

        京东获取单个商品价格接口:
        http://p.3.cn/prices/mgets?skuIds=J_商品ID&type=1
        ps:商品ID这么获取:http://item.jd.com/954086.html

        -

        物流接口

        -

        快递接口:
        http://www.kuaidi100.com/query?type=快递公司代号&postid=快递单号
        ps:快递公司编码:申通=”shentong” EMS=”ems” 顺丰=”shunfeng” 圆通=”yuantong” 中通=”zhongtong” 韵达=”yunda” 天天=”tiantian” 汇通=”huitongkuaidi” 全峰=”quanfengkuaidi” 德邦=”debangwuliu” 宅急送=”zhaijisong”

        -

        谷歌接口

        -

        FeedXml转json接口:
        http://ajax.googleapis.com/ajax/services/feed/load?q=Feed地址&v=1.0
        备选参数:callback:&callback=foo就会在json外面嵌套foo({})方便做jsonp使用。
        备选参数:n:返回多少条记录。

        -

        天气接口

        -

        百度接口:
        http://api.map.baidu.com/telematics/v3/weather?location=嘉兴&output=json&ak=5slgyqGDENN7Sy7pw29IUvrZ
        location:城市名或经纬度 ak:开发者密钥 output:默认xml

        -

        气象局接口:
        http://m.weather.com.cn/data/101010100.html

        -

        音乐接口

        -

        虾米接口:
        http://kuang.xiami.com/app/nineteen/search/key/歌曲名称/diandian/1/page/歌曲当前页?_=当前毫秒&callback=getXiamiData

        -

        QQ空间音乐接口:
        http://qzone-music.qq.com/fcg-bin/cgi_playlist_xml.fcg?uin=QQ号码&json=1&g_tk=1916754934

        -

        QQ空间收藏音乐接口:
        http://qzone-music.qq.com/fcg-bin/fcg_music_fav_getinfo.fcg?dirinfo=0&dirid=1&uin=QQ号&p=0.519638272547262&g_tk=1284234856

        -

        多米音乐接口:
        http://v5.pc.duomi.com/search-ajaxsearch-searchall?kw=关键字&pi=页码&pz=每页音乐数

        -

        soso接口:
        http://cgi.music.soso.com/fcgi-bin/fcg_search_xmldata.q?source=10&w=关键字&perpage=1&ie=utf-8

        -

        视频接口

        -

        土豆接口:
        http://api.tudou.com/v3/gw?method=album.item.get&appKey=Appkey&format=json&albumId=视频剧集ID&pageNo=当前页&pageSize=每页显示

        -

        地图接口

        -

        阿里云根据地区名获取经纬度接口:
        http://gc.ditu.aliyun.com/geocoding?a=苏州市
        参数解释: 纬度,经度type 001 (100代表道路,010代表POI,001代表门址,111可以同时显示前三项)

        -

        阿里云根据经纬度获取地区名接口:
        http://gc.ditu.aliyun.com/regeocoding?l=39.938133,116.395739&type=001

        -

        IP接口

        -

        新浪接口(ip值为空的时候 获取本地的):
        http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=218.4.255.255

        -

        淘宝接口:
        http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42

        -

        手机信息查询接口

        -

        淘宝网接口:
        http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=手机号

        -

        拍拍接口:
        http://virtual.paipai.com/extinfo/GetMobileProductInfo?mobile=手机号&amount=10000&callname=getPhoneNumInfoExtCallback 用例

        -

        百付宝接口:
        https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=手机号

        -

        115接口:
        http://cz.115.com/?ct=index&ac=get_mobile_local&callback=jsonp1333962541001&mobile=手机号

        -

        有道接口:
        http://www.youdao.com/smartresult-xml/search.s?jsFlag=true&type=mobile&q=手机号

        -

        手机在线接口
        http://api.showji.com/Locating/www.showji.com.aspx?m=手机号&output=json&callback=querycallback

        -

        视频信息接口

        -

        优酷:
        http://v.youku.com/player/getPlayList/VideoIDS/视频ID
        (比如 http://v.youku.com/v_show/id_XNTQxNzc4ODg0.html的ID就是XNTQxNzc4ODg0)

        -

        翻译、词典接口

        -

        腾讯:
        http://dict.qq.com/dict?q=词语

        -

        腾讯的部分接口

        -

        获取QQ昵称和用户头像:
        http://r.qzone.qq.com/cgi-bin/user/cgi_personal_card?uin=QQ

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/Cloud\346\236\204\345\273\272\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\234\215\345\212\241\346\266\210\350\264\271\350\200\205/index.html" "b/2016/06/15/Cloud\346\236\204\345\273\272\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\234\215\345\212\241\346\266\210\350\264\271\350\200\205/index.html" deleted file mode 100644 index 87654cd..0000000 --- "a/2016/06/15/Cloud\346\236\204\345\273\272\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\234\215\345\212\241\346\266\210\350\264\271\350\200\205/index.html" +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - - Cloud构建微服务架构(二)——服务消费者 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/Security\350\277\233\350\241\214\345\256\211\345\205\250\346\216\247\345\210\266-1/index.html" "b/2016/06/15/Security\350\277\233\350\241\214\345\256\211\345\205\250\346\216\247\345\210\266-1/index.html" deleted file mode 100644 index b0f71ce..0000000 --- "a/2016/06/15/Security\350\277\233\350\241\214\345\256\211\345\205\250\346\216\247\345\210\266-1/index.html" +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - - Security进行安全控制 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255Web\345\272\224\347\224\250\347\232\204\347\273\237\344\270\200\345\274\202\345\270\270\345\244\204\347\220\206/index.html" "b/2016/06/15/SpringBoot\344\270\255Web\345\272\224\347\224\250\347\232\204\347\273\237\344\270\200\345\274\202\345\270\270\345\244\204\347\220\206/index.html" deleted file mode 100644 index 6e68162..0000000 --- "a/2016/06/15/SpringBoot\344\270\255Web\345\272\224\347\224\250\347\232\204\347\273\237\344\270\200\345\274\202\345\270\270\345\244\204\347\220\206/index.html" +++ /dev/null @@ -1,430 +0,0 @@ - - - - - - - SpringBoot中Web应用的统一异常处理 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中Web应用的统一异常处理 -

        - - -
        - - - - -
        - -

        Spring Boot中Web应用的统一异常处理
        2016年05月02日 标签:Spring Boot
        我们在做Web应用的时候,请求处理过程中发生错误是非常常见的情况。Spring Boot提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异常内容。

        -

        选择一个之前实现过的Web应用(Chapter3-1-2)为基础,启动该应用,访问一个不存在的URL,或是修改处理内容,直接抛出异常,如:

        -

        @RequestMapping(“/hello”)
        public String hello() throws Exception {
        throw new Exception(“发生错误”);
        }
        此时,可以看到类似下面的报错页面,该页面就是Spring Boot提供的默认error映射页面。

        -

        alt=默认的错误页面

        -

        统一异常处理
        虽然,Spring Boot中实现了默认的error映射,但是在实际应用中,上面你的错误页面对用户来说并不够友好,我们通常需要去实现我们自己的异常提示。

        -

        下面我们以之前的Web应用例子为基础(Chapter3-1-2),进行统一异常处理的改造。

        -

        创建全局异常处理类:通过使用@ControllerAdvice定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html中
        @ControllerAdvice
        class GlobalExceptionHandler {

        -
        public static final String DEFAULT_ERROR_VIEW = "error";
        -
        -@ExceptionHandler(value = Exception.class)
        -public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        -    ModelAndView mav = new ModelAndView();
        -    mav.addObject("exception", e);
        -    mav.addObject("url", req.getRequestURL());
        -    mav.setViewName(DEFAULT_ERROR_VIEW);
        -    return mav;
        -}
        -

        }
        实现error.html页面展示:在templates目录下创建error.html,将请求的URL和Exception对象的message输出。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        <!DOCTYPE html>  
        <html>
        <head lang="en">
        <meta charset="UTF-8" />
        <title>统一异常处理</title>
        </head>
        <body>
        <h1>Error Handler</h1>
        <div th:text="${url}"></div>
        <div th:text="${exception.message}"></div>
        </body>
        </html>

        -

        启动该应用,访问:http://localhost:8080/hello,可以看到如下错误提示页面。

        -

        alt=自定义的错误页面

        -

        通过实现上述内容之后,我们只需要在Controller中抛出Exception,当然我们可能会有多种不同的Exception。然后在@ControllerAdvice类中,根据抛出的具体Exception类型匹配@ExceptionHandler中配置的异常类型来匹配错误映射和处理。

        -

        返回JSON格式
        在上述例子中,通过@ControllerAdvice统一定义不同Exception映射到不同错误处理页面。而当我们要实现RESTful API时,返回的错误是JSON格式的数据,而不是HTML页面,这时候我们也能轻松支持。

        -

        本质上,只需在@ExceptionHandler之后加入@ResponseBody,就能让处理函数return的内容转换为JSON格式。

        -

        下面以一个具体示例来实现返回JSON格式的异常处理。

        -

        创建统一的JSON返回对象,code:消息类型,message:消息内容,url:请求的url,data:请求返回的数据
        public class ErrorInfo {

        -
        public static final Integer OK = 0;
        -public static final Integer ERROR = 100;
        -
        -private Integer code;
        -private String message;
        -private String url;
        -private T data;
        -
        -// 省略getter和setter
        -

        }
        创建一个自定义异常,用来实验捕获该异常,并返回json
        public class MyException extends Exception {

        -
        public MyException(String message) {
        -    super(message);
        -}
        -

        }
        Controller中增加json映射,抛出MyException异常
        @Controller
        public class HelloController {

        -
        @RequestMapping("/json")
        -public String json() throws MyException {
        -    throw new MyException("发生错误2");
        -}
        -

        }
        为MyException异常创建对应的处理
        @ControllerAdvice
        public class GlobalExceptionHandler {

        -
        @ExceptionHandler(value = MyException.class)
        -@ResponseBody
        -public ErrorInfo<String> jsonErrorHandler(HttpServletRequest req, MyException e) throws Exception {
        -    ErrorInfo<String> r = new ErrorInfo<>();
        -    r.setMessage(e.getMessage());
        -    r.setCode(ErrorInfo.ERROR);
        -    r.setData("Some Data");
        -    r.setUrl(req.getRequestURL().toString());
        -    return r;
        -}
        -

        }
        启动应用,访问:http://localhost:8080/json,可以得到如下返回内容:
        {
        code: 100,
        data: “Some Data”,
        message: “发生错误2”,
        url: “http://localhost:8080/json
        }
        至此,已完成在Spring Boot中创建统一的异常处理,实际实现还是依靠Spring MVC的注解,更多更深入的使用可参考Spring MVC的文档。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250-Async\345\256\236\347\216\260\345\274\202\346\255\245\350\260\203\347\224\250/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250-Async\345\256\236\347\216\260\345\274\202\346\255\245\350\260\203\347\224\250/index.html" deleted file mode 100644 index a36c4d0..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250-Async\345\256\236\347\216\260\345\274\202\346\255\245\350\260\203\347\224\250/index.html" +++ /dev/null @@ -1,470 +0,0 @@ - - - - - - - SpringBoot中使用@Async实现异步调用 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用@Async实现异步调用 -

        - - -
        - - - - -
        - -

        Spring Boot中使用@Async实现异步调用
        2016年05月16日 标签:Spring Boot
        什么是“异步调用”?

        -

        “异步调用”对应的是“同步调用”,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。

        -

        同步调用
        下面通过一个简单示例来直观的理解什么是同步调用:

        -

        定义Task类,创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)
        @Component
        public class Task {

        -
        public static Random random =new Random();
        -
        -public void doTaskOne() throws Exception {
        -    System.out.println("开始做任务一");
        -    long start = System.currentTimeMillis();
        -    Thread.sleep(random.nextInt(10000));
        -    long end = System.currentTimeMillis();
        -    System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
        -}
        -
        -public void doTaskTwo() throws Exception {
        -    System.out.println("开始做任务二");
        -    long start = System.currentTimeMillis();
        -    Thread.sleep(random.nextInt(10000));
        -    long end = System.currentTimeMillis();
        -    System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
        -}
        -
        -public void doTaskThree() throws Exception {
        -    System.out.println("开始做任务三");
        -    long start = System.currentTimeMillis();
        -    Thread.sleep(random.nextInt(10000));
        -    long end = System.currentTimeMillis();
        -    System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
        -}
        -

        }
        在单元测试用例中,注入Task对象,并在测试用例中执行doTaskOne、doTaskTwo、doTaskThree三个函数。
        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(classes = Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private Task task;
        -
        -@Test
        -public void test() throws Exception {
        -    task.doTaskOne();
        -    task.doTaskTwo();
        -    task.doTaskThree();
        -}
        -

        }
        执行单元测试,可以看到类似如下输出:
        开始做任务一
        完成任务一,耗时:4256毫秒
        开始做任务二
        完成任务二,耗时:4957毫秒
        开始做任务三
        完成任务三,耗时:7173毫秒
        任务一、任务二、任务三顺序的执行完了,换言之doTaskOne、doTaskTwo、doTaskThree三个函数顺序的执行完成。

        -

        异步调用
        上述的同步调用虽然顺利的执行完了三个任务,但是可以看到执行时间比较长,若这三个任务本身之间不存在依赖关系,可以并发执行的话,同步调用在执行效率方面就比较差,可以考虑通过异步调用的方式来并发执行。

        -

        在Spring Boot中,我们只需要通过使用@Async注解就能简单的将原来的同步函数变为异步函数,Task类改在为如下模式:

        -

        @Component
        public class Task {

        -
        @Async
        -public void doTaskOne() throws Exception {
        -    // 同上内容,省略
        -}
        -
        -@Async
        -public void doTaskTwo() throws Exception {
        -    // 同上内容,省略
        -}
        -
        -@Async
        -public void doTaskThree() throws Exception {
        -    // 同上内容,省略
        -}
        -

        }
        为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync,如下所示:

        -

        @SpringBootApplication
        @EnableAsync
        public class Application {

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

        }
        此时可以反复执行单元测试,您可能会遇到各种不同的结果,比如:

        -

        没有任何任务相关的输出
        有部分任务相关的输出
        乱序的任务相关的输出
        原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。

        -

        注: @Async所修饰的函数不要定义为static类型,这样异步调用不会生效

        -

        异步回调
        为了让doTaskOne、doTaskTwo、doTaskThree能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果。

        -

        那么我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用Future来返回异步调用的结果,就像如下方式改造doTaskOne函数:

        -

        @Async
        public Future doTaskOne() throws Exception {
        System.out.println(“开始做任务一”);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println(“完成任务一,耗时:” + (end - start) + “毫秒”);
        return new AsyncResult<>(“任务一完成”);
        }
        按照如上方式改造一下其他两个异步函数之后,下面我们改造一下测试用例,让测试在等待完成三个异步调用之后来做一些其他事情。

        -

        @Test
        public void test() throws Exception {

        -
        long start = System.currentTimeMillis();
        -
        -Future<String> task1 = task.doTaskOne();
        -Future<String> task2 = task.doTaskTwo();
        -Future<String> task3 = task.doTaskThree();
        -
        -while(true) {
        -    if(task1.isDone() && task2.isDone() && task3.isDone()) {
        -        // 三个任务都调用完成,退出循环等待
        -        break;
        -    }
        -    Thread.sleep(1000);
        -}
        -
        -long end = System.currentTimeMillis();
        -
        -System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
        -

        }
        看看我们做了哪些改变:

        -

        在测试用例一开始记录开始时间
        在调用三个异步函数的时候,返回Future类型的结果对象
        在调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。
        跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。
        执行一下上述的单元测试,可以看到如下结果:

        -

        开始做任务一
        开始做任务二
        开始做任务三
        完成任务三,耗时:37毫秒
        完成任务二,耗时:3661毫秒
        完成任务一,耗时:7149毫秒
        任务全部完成,总耗时:8025毫秒
        可以看到,通过异步调用,让任务一、二、三并发执行,有效的减少了程序的总运行时间。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250-Scheduled\345\210\233\345\273\272\345\256\232\346\227\266\344\273\273\345\212\241/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250-Scheduled\345\210\233\345\273\272\345\256\232\346\227\266\344\273\273\345\212\241/index.html" deleted file mode 100644 index 59c084f..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250-Scheduled\345\210\233\345\273\272\345\256\232\346\227\266\344\273\273\345\212\241/index.html" +++ /dev/null @@ -1,391 +0,0 @@ - - - - - - - SpringBoot中使用@Scheduled创建定时任务 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用@Scheduled创建定时任务 -

        - - -
        - - - - -
        - -

        Spring Boot中使用@Scheduled创建定时任务
        2016年05月15日 标签:Spring Boot
        我们在编写Spring Boot应用中经常会遇到这样的场景,比如:我需要定时地发送一些短信、邮件之类的操作,也可能会定时地检查和监控一些标志、参数等。

        -

        创建定时任务
        在Spring Boot中编写定时任务是非常简单的事,下面通过实例介绍如何在Spring Boot中创建定时任务,实现每过5秒输出一下当前时间。

        -

        在Spring Boot的主类中加入@EnableScheduling注解,启用定时任务的配置
        @SpringBootApplication
        @EnableScheduling
        public class Application {

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

        }
        创建定时任务实现类
        @Component
        public class ScheduledTasks {

        -
        private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
        -
        -@Scheduled(fixedRate = 5000)
        -public void reportCurrentTime() {
        -    System.out.println("现在时间:" + dateFormat.format(new Date()));
        -}
        -

        }
        运行程序,控制台中可以看到类似如下输出,定时任务开始正常运作了。
        2016-05-15 10:40:04.073 INFO 1688 — [ main] com.didispace.Application : Started Application in 1.433 seconds (JVM running for 1.967)
        现在时间:10:40:09
        现在时间:10:40:14
        现在时间:10:40:19
        现在时间:10:40:24
        现在时间:10:40:29522
        现在时间:10:40:34
        关于上述的简单入门示例也可以参见官方的Scheduling Tasks

        -

        @Scheduled详解
        在上面的入门例子中,使用了@Scheduled(fixedRate = 5000) 注解来定义每过5秒执行的任务,对于@Scheduled的使用可以总结如下几种方式:

        -

        @Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行
        @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
        @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
        @Scheduled(cron=”/5 “) :通过cron表达式定义规则
        完整示例Chapter4-1-1

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250AOP\347\273\237\344\270\200\345\244\204\347\220\206Web\350\257\267\346\261\202\346\227\245\345\277\227/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250AOP\347\273\237\344\270\200\345\244\204\347\220\206Web\350\257\267\346\261\202\346\227\245\345\277\227/index.html" deleted file mode 100644 index a33ef6a..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250AOP\347\273\237\344\270\200\345\244\204\347\220\206Web\350\257\267\346\261\202\346\227\245\345\277\227/index.html" +++ /dev/null @@ -1,445 +0,0 @@ - - - - - - - SpringBoot中使用AOP统一处理Web请求日志 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用AOP统一处理Web请求日志 -

        - - -
        - - - - -
        - -

        Spring Boot中使用AOP统一处理Web请求日志
        2016年05月20日 标签:Spring Boot
        AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容,比如常见的有:打开数据库连接/关闭数据库连接、打开事务/关闭事务、记录日志等。基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

        -

        下面主要讲两个内容,一个是如何在Spring Boot中引入Aop功能,二是如何使用Aop做切面去统一处理Web请求的日志。

        -

        以下所有操作基于chapter4-2-2工程进行。

        -

        准备工作
        因为需要对web请求做切面来记录日志,所以先引入web模块,并创建一个简单的hello请求的处理。

        -

        pom.xml中引入web模块

        -


        org.springframework.boot
        spring-boot-starter-web

        实现一个简单请求处理:通过传入name参数,返回“hello xxx”的功能。
        @RestController
        public class HelloController {

        -
        @RequestMapping(value = "/hello", method = RequestMethod.GET)
        -@ResponseBody
        -public String hello(@RequestParam String name) {
        -    return "Hello " + name;
        -}
        -

        }
        下面,我们可以对上面的/hello请求,进行切面日志记录。

        -

        引入AOP依赖
        在Spring Boot中引入AOP就跟引入其他模块一样,非常简单,只需要在pom.xml中加入如下依赖:

        -


        org.springframework.boot
        spring-boot-starter-aop

        在完成了引入AOP依赖包后,一般来说并不需要去做其他配置。也许在Spring中使用过注解配置方式的人会问是否需要在程序主类中增加@EnableAspectJAutoProxy来启用,实际并不需要。

        -

        可以看下面关于AOP的默认配置属性,其中spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。

        -

        AOP

        spring.aop.auto=true # Add @EnableAspectJAutoProxy.
        spring.aop.proxy-target-class=false # Whether subclass-based (CGLIB) proxies are to be created (true) as
        opposed to standard Java interface-based proxies (false).
        而当我们需要使用CGLIB来实现AOP的时候,需要配置spring.aop.proxy-target-class=true,不然默认使用的是标准Java的实现。

        -

        实现Web层的日志切面
        实现AOP的切面主要有以下几个要素:

        -

        使用@Aspect注解将一个java类定义为切面类
        使用@Pointcut定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。
        根据需要在切入点不同位置的切入内容
        使用@Before在切入点开始处切入内容
        使用@After在切入点结尾处切入内容
        使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
        使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
        使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
        @Aspect
        @Component
        public class WebLogAspect {

        -
        private Logger logger = Logger.getLogger(getClass());
        -
        -@Pointcut("execution(public * com.didispace.web..*.*(..))")
        -public void webLog(){}
        -
        -@Before("webLog()")
        -public void doBefore(JoinPoint joinPoint) throws Throwable {
        -    // 接收到请求,记录请求内容
        -    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        -    HttpServletRequest request = attributes.getRequest();
        -
        -    // 记录下请求内容
        -    logger.info("URL : " + request.getRequestURL().toString());
        -    logger.info("HTTP_METHOD : " + request.getMethod());
        -    logger.info("IP : " + request.getRemoteAddr());
        -    logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        -    logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
        -
        -}
        -
        -@AfterReturning(returning = "ret", pointcut = "webLog()")
        -public void doAfterReturning(Object ret) throws Throwable {
        -    // 处理完请求,返回内容
        -    logger.info("RESPONSE : " + ret);
        -}
        -

        }
        可以看上面的例子,通过@Pointcut定义的切入点为com.didispace.web包下的所有函数(对web层所有请求处理做切入点),然后通过@Before实现,对请求内容的日志记录(本文只是说明过程,可以根据需要调整内容),最后通过@AfterReturning记录请求返回的对象。

        -

        通过运行程序并访问:http://localhost:8080/hello?name=didi,可以获得下面的日志输出

        -

        2016-05-19 13:42:13,156 INFO WebLogAspect:41 - URL : http://localhost:8080/hello
        2016-05-19 13:42:13,156 INFO WebLogAspect:42 - HTTP_METHOD : http://localhost:8080/hello
        2016-05-19 13:42:13,157 INFO WebLogAspect:43 - IP : 0:0:0:0:0:0:0:1
        2016-05-19 13:42:13,160 INFO WebLogAspect:44 - CLASS_METHOD : com.didispace.web.HelloController.hello
        2016-05-19 13:42:13,160 INFO WebLogAspect:45 - ARGS : [didi]
        2016-05-19 13:42:13,170 INFO WebLogAspect:52 - RESPONSE:Hello didi
        优化:AOP切面中的同步问题
        在WebLogAspect切面中,分别通过doBefore和doAfterReturning两个独立函数实现了切点头部和切点返回后执行的内容,若我们想统计请求的处理时间,就需要在doBefore处记录时间,并在doAfterReturning处通过当前时间与开始处记录的时间计算得到请求处理的消耗时间。

        -

        那么我们是否可以在WebLogAspect切面中定义一个成员变量来给doBefore和doAfterReturning一起访问呢?是否会有同步问题呢?

        -

        的确,直接在这里定义基本类型会有同步问题,所以我们可以引入ThreadLocal对象,像下面这样进行记录:

        -

        @Aspect
        @Component
        public class WebLogAspect {

        -
        private Logger logger = Logger.getLogger(getClass());
        -
        -ThreadLocal<Long> startTime = new ThreadLocal<>();
        -
        -@Pointcut("execution(public * com.didispace.web..*.*(..))")
        -public void webLog(){}
        -
        -@Before("webLog()")
        -public void doBefore(JoinPoint joinPoint) throws Throwable {
        -    startTime.set(System.currentTimeMillis());
        -
        -    // 省略日志记录内容
        -}
        -
        -@AfterReturning(returning = "ret", pointcut = "webLog()")
        -public void doAfterReturning(Object ret) throws Throwable {
        -    // 处理完请求,返回内容
        -    logger.info("RESPONSE : " + ret);
        -    logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));
        -}
        -

        }
        优化:AOP切面的优先级
        由于通过AOP实现,程序得到了很好的解耦,但是也会带来一些问题,比如:我们可能会对Web层做多个切面,校验用户,校验头信息等等,这个时候经常会碰到切面的处理顺序问题。

        -

        所以,我们需要定义每个切面的优先级,我们需要@Order(i)注解来标识切面的优先级。i的值越小,优先级越高。假设我们还有一个切面是CheckNameAspect用来校验name必须为didi,我们为其设置@Order(10),而上文中WebLogAspect设置为@Order(5),所以WebLogAspect有更高的优先级,这个时候执行顺序是这样的:

        -

        在@Before中优先执行@Order(5)的内容,再执行@Order(10)的内容
        在@After和@AfterReturning中优先执行@Order(10)的内容,再执行@Order(5)的内容
        所以我们可以这样子总结:

        -

        在切入点前的操作,按order的值由小到大执行
        在切入点后的操作,按order的值由大到小执行
        本文完整示例Chapter4-2-4

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250JavaMailSender\345\217\221\351\200\201\351\202\256\344\273\266/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250JavaMailSender\345\217\221\351\200\201\351\202\256\344\273\266/index.html" deleted file mode 100644 index fddc27b..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250JavaMailSender\345\217\221\351\200\201\351\202\256\344\273\266/index.html" +++ /dev/null @@ -1,492 +0,0 @@ - - - - - - - SpringBoot中使用JavaMailSender发送邮件 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用JavaMailSender发送邮件 -

        - - -
        - - - - -
        - -

        Spring Boot中使用JavaMailSender发送邮件
        2016年06月14日 标签:Spring Boot
        相信使用过Spring的众多开发者都知道Spring提供了非常好用的JavaMailSender接口实现邮件发送。在Spring Boot的Starter模块中也为此提供了自动化配置。下面通过实例看看如何在Spring Boot中使用JavaMailSender发送邮件。

        -

        快速入门
        在Spring Boot的工程中的pom.xml中引入spring-boot-starter-mail依赖:

        -
        <dependency>  
        -    <groupId>org.springframework.boot</groupId>
        -    <artifactId>spring-boot-starter-mail</artifactId>
        -</dependency> 
        -
        -如其他自动化配置模块一样,在完成了依赖引入之后,只需要在application.properties中配置相应的属性内容。
        -
        -下面我们以QQ邮箱为例,在application.properties中加入如下配置(注意替换自己的用户名和密码):
        -
        -spring.mail.host=smtp.qq.com  
        -spring.mail.username=用户名  
        -spring.mail.password=密码  
        -spring.mail.properties.mail.smtp.auth=true  
        -spring.mail.properties.mail.smtp.starttls.enable=true  
        -spring.mail.properties.mail.smtp.starttls.required=true  
        -通过单元测试来实现一封简单邮件的发送:
        -
        -@RunWith(SpringJUnit4ClassRunner.class)
        -@SpringApplicationConfiguration(classes = Application.class)
        -public class ApplicationTests {
        -
        -    @Autowired
        -    private JavaMailSender mailSender;
        -
        -    @Test
        -    public void sendSimpleMail() throws Exception {
        -        SimpleMailMessage message = new SimpleMailMessage();
        -        message.setFrom("dyc87112@qq.com");
        -        message.setTo("dyc87112@qq.com");
        -        message.setSubject("主题:简单邮件");
        -        message.setText("测试邮件内容");
        -
        -        mailSender.send(message);
        -    }
        -
        -}
        -到这里,一个简单的邮件发送就完成了,运行一下该单元测试,看看效果如何?
        -
        -由于Spring Boot的starter模块提供了自动化配置,所以在引入了spring-boot-starter-mail依赖之后,会根据配置文件中的内容去创建JavaMailSender实例,因此我们可以直接在需要使用的地方直接@Autowired来引入邮件发送对象。
        -进阶使用
        -在上例中,我们通过使用SimpleMailMessage实现了简单的邮件发送,但是实际使用过程中,我们还可能会带上附件、或是使用邮件模块等。这个时候我们就需要使用MimeMessage来设置复杂一些的邮件内容,下面我们就来依次实现一下。
        -
        -发送附件
        -在上面单元测试中加入如下测试用例(通过MimeMessageHelper来发送一封带有附件的邮件):
        -
        -    @Test
        -    public void sendAttachmentsMail() throws Exception {
        -
        -        MimeMessage mimeMessage = mailSender.createMimeMessage();
        -
        -        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        -        helper.setFrom("dyc87112@qq.com");
        -        helper.setTo("dyc87112@qq.com");
        -        helper.setSubject("主题:有附件");
        -        helper.setText("有附件的邮件");
        -
        -        FileSystemResource file = new FileSystemResource(new File("weixin.jpg"));
        -        helper.addAttachment("附件-1.jpg", file);
        -        helper.addAttachment("附件-2.jpg", file);
        -
        -        mailSender.send(mimeMessage);
        -
        -    }
        -嵌入静态资源
        -除了发送附件之外,我们在邮件内容中可能希望通过嵌入图片等静态资源,让邮件获得更好的阅读体验,而不是从附件中查看具体图片,下面的测试用例演示了如何通过MimeMessageHelper实现在邮件正文中嵌入静态资源。
        -
        -    @Test
        -    public void sendInlineMail() throws Exception {
        -
        -        MimeMessage mimeMessage = mailSender.createMimeMessage();
        -
        -        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        -        helper.setFrom("dyc87112@qq.com");
        -        helper.setTo("dyc87112@qq.com");
        -        helper.setSubject("主题:嵌入静态资源");
        -        helper.setText("<html><body><img src=\"cid:weixin\" ></body></html>", true);
        -
        -        FileSystemResource file = new FileSystemResource(new File("weixin.jpg"));
        -        helper.addInline("weixin", file);
        -
        -        mailSender.send(mimeMessage);
        -
        -    }
        -这里需要注意的是addInline函数中资源名称weixin需要与正文中cid:weixin对应起来
        -
        -模板邮件
        -通常我们使用邮件发送服务的时候,都会有一些固定的场景,比如重置密码、注册确认等,给每个用户发送的内容可能只有小部分是变化的。所以,很多时候我们会使用模板引擎来为各类邮件设置成模板,这样我们只需要在发送时去替换变化部分的参数即可。
        -
        -在Spring Boot中使用模板引擎来实现模板化的邮件发送也是非常容易的,下面我们以velocity为例实现一下。
        -
        -引入velocity模块的依赖:
        -
        -<dependency>  
        -    <groupId>org.springframework.boot</groupId>
        -    <artifactId>spring-boot-starter-velocity</artifactId>
        -</dependency>  
        -在resources/templates/下,创建一个模板页面template.vm:
        -
        -<html>  
        -<body>  
        -    <h3>你好, ${username}, 这是一封模板邮件!</h3>
        -</body>  
        -</html>
        -

        我们之前在Spring Boot中开发Web应用时,提到过在Spring Boot的自动化配置下,模板默认位于resources/templates/目录下

        -

        最后,我们在单元测试中加入发送模板邮件的测试用例,具体如下:

        -
        @Test
        -public void sendTemplateMail() throws Exception {
        -
        -    MimeMessage mimeMessage = mailSender.createMimeMessage();
        -
        -    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        -    helper.setFrom("dyc87112@qq.com");
        -    helper.setTo("dyc87112@qq.com");
        -    helper.setSubject("主题:模板邮件");
        -
        -    Map<String, Object> model = new HashedMap();
        -    model.put("username", "didi");
        -    String text = VelocityEngineUtils.mergeTemplateIntoString(
        -            velocityEngine, "template.vm", "UTF-8", model);
        -    helper.setText(text, true);
        -
        -    mailSender.send(mimeMessage);
        -}
        -

        尝试运行一下,就可以收到内容为你好, didi, 这是一封模板邮件!的邮件。这里,我们通过传入username的参数,在邮件内容中替换了模板中的${username}变量。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250JdbcTemplate\350\256\277\351\227\256\346\225\260\346\215\256\345\272\223/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250JdbcTemplate\350\256\277\351\227\256\346\225\260\346\215\256\345\272\223/index.html" deleted file mode 100644 index e5a021d..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250JdbcTemplate\350\256\277\351\227\256\346\225\260\346\215\256\345\272\223/index.html" +++ /dev/null @@ -1,460 +0,0 @@ - - - - - - - SpringBoot中使用JdbcTemplate访问数据库 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用JdbcTemplate访问数据库 -

        - - -
        - - - - -
        - -

        Spring Boot中使用JdbcTemplate访问数据库
        2016年03月17日 标签:Spring Boot
        之前介绍了很多Web层的例子,包括构建RESTful API、使用Thymeleaf模板引擎渲染Web视图,但是这些内容还不足以构建一个动态的应用。通常我们做App也好,做Web应用也好,都需要内容,而内容通常存储于各种类型的数据库,服务端在接收到访问请求之后需要访问数据库获取并处理成展现给用户使用的数据形式。

        -

        本文介绍在Spring Boot基础下配置数据源和通过JdbcTemplate编写数据访问的示例。

        -

        数据源配置
        在我们访问数据库的时候,需要先配置一个数据源,下面分别介绍一下几种不同的数据库配置方式。

        -

        首先,为了连接数据库需要引入jdbc支持,在pom.xml中引入如下配置:
        ``

        -


        org.springframework.boot
        spring-boot-starter-jdbc

        1
        2
        3
        4
        嵌入式数据库支持
        嵌入式数据库通常用于开发和测试环境,不推荐用于生产环境。Spring Boot提供自动配置的嵌入式数据库有H2、HSQL、Derby,你不需要提供任何连接配置就能使用。

        比如,我们可以在pom.xml中引入如下配置使用HSQL

        -


        org.hsqldb
        hsqldb
        runtime

        连接生产数据源
        以MySQL数据库为例,先引入MySQL连接的依赖包,在pom.xml中加入:

        -


        mysql
        mysql-connector-java
        5.1.21

        ```
        在src/main/resources/application.properties中配置数据源信息

        -

        spring.datasource.url=jdbc:mysql://localhost:3306/test
        spring.datasource.username=dbuser
        spring.datasource.password=dbpass
        spring.datasource.driver-class-name=com.mysql.jdbc.Driver
        连接JNDI数据源
        当你将应用部署于应用服务器上的时候想让数据源由应用服务器管理,那么可以使用如下配置方式引入JNDI数据源。

        -

        spring.datasource.jndi-name=java:jboss/datasources/customers
        使用JdbcTemplate操作数据库
        Spring的JdbcTemplate是自动配置的,你可以直接使用@Autowired来注入到你自己的bean中来使用。

        -

        举例:我们在创建User表,包含属性name、age,下面来编写数据访问对象和单元测试用例。

        -

        定义包含有插入、删除、查询的抽象接口UserService
        public interface UserService {

        -
        /**
        - * 新增一个用户
        - * @param name
        - * @param age
        - */
        -void create(String name, Integer age);
        -
        -/**
        - * 根据name删除一个用户高
        - * @param name
        - */
        -void deleteByName(String name);
        -
        -/**
        - * 获取用户总量
        - */
        -Integer getAllUsers();
        -
        -/**
        - * 删除所有用户
        - */
        -void deleteAllUsers();
        -

        }
        通过JdbcTemplate实现UserService中定义的数据访问操作
        @Service
        public class UserServiceImpl implements UserService {

        -
        @Autowired
        -private JdbcTemplate jdbcTemplate;
        -
        -@Override
        -public void create(String name, Integer age) {
        -    jdbcTemplate.update("insert into USER(NAME, AGE) values(?, ?)", name, age);
        -}
        -
        -@Override
        -public void deleteByName(String name) {
        -    jdbcTemplate.update("delete from USER where NAME = ?", name);
        -}
        -
        -@Override
        -public Integer getAllUsers() {
        -    return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
        -}
        -
        -@Override
        -public void deleteAllUsers() {
        -    jdbcTemplate.update("delete from USER");
        -}
        -

        }
        创建对UserService的单元测试用例,通过创建、删除和查询来验证数据库操作的正确性。
        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private UserService userSerivce;
        -
        -@Before
        -public void setUp() {
        -    // 准备,清空user表
        -    userSerivce.deleteAllUsers();
        -}
        -
        -@Test
        -public void test() throws Exception {
        -    // 插入5个用户
        -    userSerivce.create("a", 1);
        -    userSerivce.create("b", 2);
        -    userSerivce.create("c", 3);
        -    userSerivce.create("d", 4);
        -    userSerivce.create("e", 5);
        -
        -    // 查数据库,应该有5个用户
        -    Assert.assertEquals(5, userSerivce.getAllUsers().intValue());
        -
        -    // 删除两个用户
        -    userSerivce.deleteByName("a");
        -    userSerivce.deleteByName("e");
        -
        -    // 查数据库,应该有5个用户
        -    Assert.assertEquals(3, userSerivce.getAllUsers().intValue());
        -
        -}
        -

        }
        上面介绍的JdbcTemplate只是最基本的几个操作,更多其他数据访问操作的使用请参考:JdbcTemplate API

        -

        通过上面这个简单的例子,我们可以看到在Spring Boot下访问数据库的配置依然秉承了框架的初衷:简单。我们只需要在pom.xml中加入数据库依赖,再到application.properties中配置连接信息,不需要像Spring应用中创建JdbcTemplate的Bean,就可以直接在自己的对象中注入使用。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250MongoDB\346\225\260\346\215\256\345\272\223/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250MongoDB\346\225\260\346\215\256\345\272\223/index.html" deleted file mode 100644 index 678d81c..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250MongoDB\346\225\260\346\215\256\345\272\223/index.html" +++ /dev/null @@ -1,438 +0,0 @@ - - - - - - - SpringBoot中使用MongoDB数据库 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用MongoDB数据库 -

        - - -
        - - - - -
        - -

        Spring Boot中使用MongoDB数据库
        2016年04月27日 标签:Spring Boot, mongodb
        前段时间分享了关于Spring Boot中使用Redis的文章,除了Redis之后,我们在互联网产品中还经常会用到另外一款著名的NoSQL数据库MongoDB。

        -

        下面就来简单介绍一下MongoDB,并且通过一个例子来介绍Spring Boot中对MongoDB访问的配置和使用。

        -

        MongoDB简介
        MongoDB是一个基于分布式文件存储的数据库,它是一个介于关系数据库和非关系数据库之间的产品,其主要目标是在键/值存储方式(提供了高性能和高度伸缩性)和传统的RDBMS系统(具有丰富的功能)之间架起一座桥梁,它集两者的优势于一身。

        -

        MongoDB支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型,也因为他的存储格式也使得它所存储的数据在Nodejs程序应用中使用非常流畅。

        -

        既然称为NoSQL数据库,Mongo的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

        -

        但是,MongoDB也不是万能的,同MySQL等关系型数据库相比,它们在针对不同的数据类型和事务要求上都存在自己独特的优势。在数据存储的选择中,坚持多样化原则,选择更好更经济的方式,而不是自上而下的统一化。

        -

        较常见的,我们可以直接用MongoDB来存储键值对类型的数据,如:验证码、Session等;由于MongoDB的横向扩展能力,也可以用来存储数据规模会在未来变的非常巨大的数据,如:日志、评论等;由于MongoDB存储数据的弱类型,也可以用来存储一些多变json数据,如:与外系统交互时经常变化的JSON报文。而对于一些对数据有复杂的高事务性要求的操作,如:账户交易等就不适合使用MongoDB来存储。

        -

        MongoDB官网

        -

        访问MongoDB
        在Spring Boot中,对如此受欢迎的MongoDB,同样提供了自配置功能。

        -

        引入依赖
        Spring Boot中可以通过在pom.xml中加入spring-boot-starter-data-mongodb引入对mongodb的访问支持依赖。它的实现依赖spring-data-mongodb。是的,您没有看错,又是spring-data的子项目,之前介绍过spring-data-jpa、spring-data-redis,对于mongodb的访问,spring-data也提供了强大的支持,下面就开始动手试试吧。

        1
        2
        3
        4
        <dependency>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        -

        快速开始使用Spring-data-mongodb
        若MongoDB的安装配置采用默认端口,那么在自动配置的情况下,我们不需要做任何参数配置,就能马上连接上本地的MongoDB。下面直接使用spring-data-mongodb来尝试对mongodb的存取操作。(记得mongod启动您的mongodb)

        -

        创建要存储的User实体,包含属性:id、username、age
        public class User {

        -
        @Id
        -private Long id;
        -
        -private String username;
        -private Integer age;
        -
        -public User(Long id, String username, Integer age) {
        -    this.id = id;
        -    this.username = username;
        -    this.age = age;
        -}
        -
        -// 省略getter和setter
        -

        }
        实现User的数据访问对象:UserRepository
        public interface UserRepository extends MongoRepository {

        -
        User findByUsername(String username);
        -

        }
        在单元测试中调用
        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private UserRepository userRepository;
        -
        -@Before
        -public void setUp() {
        -    userRepository.deleteAll();
        -}
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 创建三个User,并验证User总数
        -    userRepository.save(new User(1L, "didi", 30));
        -    userRepository.save(new User(2L, "mama", 40));
        -    userRepository.save(new User(3L, "kaka", 50));
        -    Assert.assertEquals(3, userRepository.findAll().size());
        -
        -    // 删除一个User,再验证User总数
        -    User u = userRepository.findOne(1L);
        -    userRepository.delete(u);
        -    Assert.assertEquals(2, userRepository.findAll().size());
        -
        -    // 删除一个User,再验证User总数
        -    u = userRepository.findByUsername("mama");
        -    userRepository.delete(u);
        -    Assert.assertEquals(1, userRepository.findAll().size());
        -
        -}
        -

        }
        参数配置
        通过上面的例子,我们可以轻而易举的对MongoDB进行访问,但是实战中,应用服务器与MongoDB通常不会部署于同一台设备之上,这样就无法使用自动化的本地配置来进行使用。这个时候,我们也可以方便的配置来完成支持,只需要在application.properties中加入mongodb服务端的相关配置,具体示例如下:

        -

        spring.data.mongodb.uri=mongodb://name:pass@localhost:27017/test
        在尝试此配置时,记得在mongo中对test库创建具备读写权限的用户(用户名为name,密码为pass),不同版本的用户创建语句不同,注意查看文档做好准备工作

        -

        若使用mongodb 2.x,也可以通过如下参数配置,该方式不支持mongodb 3.x。

        -

        spring.data.mongodb.host=localhost spring.data.mongodb.port=27017
        版权

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250Redis\346\225\260\346\215\256\345\272\223/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250Redis\346\225\260\346\215\256\345\272\223/index.html" deleted file mode 100644 index b73a785..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250Redis\346\225\260\346\215\256\345\272\223/index.html" +++ /dev/null @@ -1,473 +0,0 @@ - - - - - - - SpringBoot中使用Redis数据库 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用Redis数据库 -

        - - -
        - - - - -
        - -

        Spring Boot中使用Redis数据库
        2016年04月15日 标签:Spring Boot, redis
        Spring Boot中除了对常用的关系型数据库提供了优秀的自动化支持之外,对于很多NoSQL数据库一样提供了自动化配置的支持,包括:Redis, MongoDB, Elasticsearch, Solr和Cassandra。

        -

        使用Redis
        Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库。

        -

        Redis官网
        Redis中文社区
        引入依赖
        Spring Boot提供的数据访问框架Spring Data Redis基于Jedis。可以通过引入spring-boot-starter-redis来配置依赖关系。

        -
        <dependency>  
        -    <groupId>org.springframework.boot</groupId>
        -    <artifactId>spring-boot-starter-redis</artifactId>
        -</dependency>
        -

        参数配置
        按照惯例在application.properties中加入Redis服务端的相关配置,具体说明如下:

        -

        REDIS (RedisProperties)

        Redis数据库索引(默认为0)

        spring.redis.database=0

        -

        Redis服务器地址

        spring.redis.host=localhost

        -

        Redis服务器连接端口

        spring.redis.port=6379

        -

        Redis服务器连接密码(默认为空)

        spring.redis.password=

        -

        连接池最大连接数(使用负值表示没有限制)

        spring.redis.pool.max-active=8

        -

        连接池最大阻塞等待时间(使用负值表示没有限制)

        spring.redis.pool.max-wait=-1

        -

        连接池中的最大空闲连接

        spring.redis.pool.max-idle=8

        -

        连接池中的最小空闲连接

        spring.redis.pool.min-idle=0

        -

        连接超时时间(毫秒)

        spring.redis.timeout=0
        其中spring.redis.database的配置通常使用0即可,Redis在配置的时候可以设置数据库数量,默认为16,可以理解为数据库的schema

        -

        测试访问
        通过编写测试用例,举例说明如何访问Redis。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private StringRedisTemplate stringRedisTemplate;
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 保存字符串
        -    stringRedisTemplate.opsForValue().set("aaa", "111");
        -    Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));
        -
        -}
        -

        }
        通过上面这段极为简单的测试案例演示了如何通过自动配置的StringRedisTemplate对象进行Redis的读写操作,该对象从命名中就可注意到支持的是String类型。如果有使用过spring-data-redis的开发者一定熟悉RedisTemplate接口,StringRedisTemplate就相当于RedisTemplate的实现。

        -

        除了String类型,实战中我们还经常会在Redis中存储对象,这时候我们就会想是否可以使用类似RedisTemplate来初始化并进行操作。但是Spring Boot并支持直接使用,需要我们自己实现RedisSerializer接口来对传入对象进行序列化和反序列化,下面我们通过一个实例来完成对象的读写操作。

        -

        创建要存储的对象:User
        public class User implements Serializable {

        -
        private static final long serialVersionUID = -1L;
        -
        -private String username;
        -private Integer age;
        -
        -public User(String username, Integer age) {
        -    this.username = username;
        -    this.age = age;
        -}
        -
        -// 省略getter和setter
        -

        }
        实现对象的序列化接口
        public class RedisObjectSerializer implements RedisSerializer {

        -

        private Converter serializer = new SerializingConverter();
        private Converter deserializer = new DeserializingConverter();

        -

        static final byte[] EMPTY_ARRAY = new byte[0];

        -

        public Object deserialize(byte[] bytes) {
        if (isEmpty(bytes)) {
        return null;
        }

        -
        try {
        -  return deserializer.convert(bytes);
        -} catch (Exception ex) {
        -  throw new SerializationException("Cannot deserialize", ex);
        -}
        -

        }

        -

        public byte[] serialize(Object object) {
        if (object == null) {
        return EMPTY_ARRAY;
        }

        -
        try {
        -  return serializer.convert(object);
        -} catch (Exception ex) {
        -  return EMPTY_ARRAY;
        -}
        -

        }

        -

        private boolean isEmpty(byte[] data) {
        return (data == null || data.length == 0);
        }
        }
        配置针对User的RedisTemplate实例
        @Configuration
        public class RedisConfig {

        -
        @Bean
        -JedisConnectionFactory jedisConnectionFactory() {
        -    return new JedisConnectionFactory();
        -}
        -
        -@Bean
        -public RedisTemplate<String, User> redisTemplate(RedisConnectionFactory factory) {
        -    RedisTemplate<String, User> template = new RedisTemplate<String, User>();
        -    template.setConnectionFactory(jedisConnectionFactory());
        -    template.setKeySerializer(new StringRedisSerializer());
        -    template.setValueSerializer(new RedisObjectSerializer());
        -    return template;
        -}
        -

        }
        完成了配置工作后,编写测试用例实验效果
        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private RedisTemplate<String, User> redisTemplate;
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 保存对象
        -    User user = new User("超人", 20);
        -    redisTemplate.opsForValue().set(user.getUsername(), user);
        -
        -    user = new User("蝙蝠侠", 30);
        -    redisTemplate.opsForValue().set(user.getUsername(), user);
        -
        -    user = new User("蜘蛛侠", 40);
        -    redisTemplate.opsForValue().set(user.getUsername(), user);
        -
        -    Assert.assertEquals(20, redisTemplate.opsForValue().get("超人").getAge().longValue());
        -    Assert.assertEquals(30, redisTemplate.opsForValue().get("蝙蝠侠").getAge().longValue());
        -    Assert.assertEquals(40, redisTemplate.opsForValue().get("蜘蛛侠").getAge().longValue());
        -
        -}
        -

        }
        当然spring-data-redis中提供的数据操作远不止这些,本文仅作为在Spring Boot中使用redis时的配置参考,更多对于redis的操作使用,请参考Spring-data-redis Reference。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250Spring-data-jpa\350\256\251\346\225\260\346\215\256\350\256\277\351\227\256\346\233\264\347\256\200\345\215\225\346\233\264\344\274\230\351\233\205/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250Spring-data-jpa\350\256\251\346\225\260\346\215\256\350\256\277\351\227\256\346\233\264\347\256\200\345\215\225\346\233\264\344\274\230\351\233\205/index.html" deleted file mode 100644 index 1247474..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250Spring-data-jpa\350\256\251\346\225\260\346\215\256\350\256\277\351\227\256\346\233\264\347\256\200\345\215\225\346\233\264\344\274\230\351\233\205/index.html" +++ /dev/null @@ -1,463 +0,0 @@ - - - - - - - SpringBoot中使用Spring-data-jpa让数据访问更简单更优雅 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用Spring-data-jpa让数据访问更简单更优雅 -

        - - -
        - - - - -
        - -

        Spring Boot中使用Spring-data-jpa让数据访问更简单、更优雅
        2016年03月24日 标签:Spring Boot
        在上一篇Spring中使用JdbcTemplate访问数据库 中介绍了一种基本的数据访问方式,结合构建RESTful API和使用Thymeleaf模板引擎渲染Web视图的内容就已经可以完成App服务端和Web站点的开发任务了。

        -

        然而,在实际开发过程中,对数据库的操作无非就“增删改查”。就最为普遍的单表操作而言,除了表和字段不同外,语句都是类似的,开发人员需要写大量类似而枯燥的语句来完成业务逻辑。

        -

        为了解决这些大量枯燥的数据操作语句,我们第一个想到的是使用ORM框架,比如:Hibernate。通过整合Hibernate之后,我们以操作Java实体的方式最终将数据改变映射到数据库表中。

        -

        为了解决抽象各个Java实体基本的“增删改查”操作,我们通常会以泛型的方式封装一个模板Dao来进行抽象简化,但是这样依然不是很方便,我们需要针对每个实体编写一个继承自泛型模板Dao的接口,再编写该接口的实现。虽然一些基础的数据访问已经可以得到很好的复用,但是在代码结构上针对每个实体都会有一堆Dao的接口和实现。

        -

        由于模板Dao的实现,使得这些具体实体的Dao层已经变的非常“薄”,有一些具体实体的Dao实现可能完全就是对模板Dao的简单代理,并且往往这样的实现类可能会出现在很多实体上。Spring-data-jpa的出现正可以让这样一个已经很“薄”的数据访问层变成只是一层接口的编写方式。比如,下面的例子:

        -

        public interface UserRepository extends JpaRepository {

        -
        User findByName(String name);
        -
        -@Query("from User u where u.name=:name")
        -User findUser(@Param("name") String name);
        -

        }
        我们只需要通过编写一个继承自JpaRepository的接口就能完成数据访问,下面以一个具体实例来体验Spring-data-jpa给我们带来的强大功能。

        -

        使用示例
        由于Spring-data-jpa依赖于Hibernate。如果您对Hibernate有一定了解,下面内容可以毫不费力的看懂并上手使用Spring-data-jpa。如果您还是Hibernate新手,您可以先按如下方式入门,再建议回头学习一下Hibernate以帮助这部分的理解和进一步使用。

        -

        工程配置
        在pom.xml中添加相关依赖,加入以下内容:

        1
        2
        3
        4
        <dependency  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        -

        在application.xml中配置:数据库连接信息(如使用嵌入式数据库则不需要)、自动创建表结构的设置,例如使用mysql的情况如下:

        -

        spring.datasource.url=jdbc:mysql://localhost:3306/test
        spring.datasource.username=root
        spring.datasource.password=root
        spring.datasource.driver-class-name=com.mysql.jdbc.Driver

        -

        spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop
        spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:

        -

        create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
        create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
        update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
        validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
        至此已经完成基础配置,如果您有在Spring下整合使用过它的话,相信你已经感受到Spring Boot的便利之处:JPA的传统配置在persistence.xml文件中,但是这里我们不需要。当然,最好在构建项目时候按照之前提过的最佳实践的工程结构来组织,这样以确保各种配置都能被框架扫描到。

        -

        创建实体
        创建一个User实体,包含id(主键)、name(姓名)、age(年龄)属性,通过ORM框架其会被映射到数据库表中,由于配置了hibernate.hbm2ddl.auto,在应用启动的时候框架会自动去数据库中创建对应的表。

        -

        @Entity
        public class User {

        -
        @Id
        -@GeneratedValue
        -private Long id;
        -
        -@Column(nullable = false)
        -private String name;
        -
        -@Column(nullable = false)
        -private Integer age;
        -
        -// 省略构造函数
        -
        -// 省略getter和setter
        -

        }
        创建数据访问接口
        下面针对User实体创建对应的Repository接口实现对该实体的数据访问,如下代码:

        -

        public interface UserRepository extends JpaRepository {

        -
        User findByName(String name);
        -
        -User findByNameAndAge(String name, Integer age);
        -
        -@Query("from User u where u.name=:name")
        -User findUser(@Param("name") String name);
        -

        }
        在Spring-data-jpa中,只需要编写类似上面这样的接口就可实现数据访问。不再像我们以往编写了接口时候还需要自己编写接口实现类,直接减少了我们的文件清单。

        -

        下面对上面的UserRepository做一些解释,该接口继承自JpaRepository,通过查看JpaRepository接口的API文档,可以看到该接口本身已经实现了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,因此对于这些基础操作的数据访问就不需要开发者再自己定义。

        -

        在我们实际开发中,JpaRepository接口定义的接口往往还不够或者性能不够优化,我们需要进一步实现更复杂一些的查询或操作。由于本文重点在spring boot中整合spring-data-jpa,在这里先抛砖引玉简单介绍一下spring-data-jpa中让我们兴奋的功能,后续再单独开篇讲一下spring-data-jpa中的常见使用。

        -

        在上例中,我们可以看到下面两个函数:

        -

        User findByName(String name)
        User findByNameAndAge(String name, Integer age)
        它们分别实现了按name查询User实体和按name和age查询User实体,可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性:通过解析方法名创建查询。

        -

        除了通过解析方法名来创建查询外,它也提供通过使用@Query 注解来创建查询,您只需要编写JPQL语句,并通过类似“:name”来映射@Param指定的参数,就像例子中的第三个findUser函数一样。

        -

        Spring-data-jpa的能力远不止本文提到的这些,由于本文主要以整合介绍为主,对于Spring-data-jpa的使用只是介绍了常见的使用方式。诸如@Modifying操作、分页排序、原生SQL支持以及与Spring MVC的结合使用等等内容就不在本文中详细展开,这里先挖个坑,后续再补文章填坑,如您对这些感兴趣可以关注我博客或简书,同样欢迎大家留言交流想法。

        -

        单元测试
        在完成了上面的数据访问接口之后,按照惯例就是编写对应的单元测试来验证编写的内容是否正确。这里就不多做介绍,主要通过数据操作和查询来反复验证操作的正确性。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private UserRepository userRepository;
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 创建10条记录
        -    userRepository.save(new User("AAA", 10));
        -    userRepository.save(new User("BBB", 20));
        -    userRepository.save(new User("CCC", 30));
        -    userRepository.save(new User("DDD", 40));
        -    userRepository.save(new User("EEE", 50));
        -    userRepository.save(new User("FFF", 60));
        -    userRepository.save(new User("GGG", 70));
        -    userRepository.save(new User("HHH", 80));
        -    userRepository.save(new User("III", 90));
        -    userRepository.save(new User("JJJ", 100));
        -
        -    // 测试findAll, 查询所有记录
        -    Assert.assertEquals(10, userRepository.findAll().size());
        -
        -    // 测试findByName, 查询姓名为FFF的User
        -    Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());
        -
        -    // 测试findUser, 查询姓名为FFF的User
        -    Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());
        -
        -    // 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User
        -    Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());
        -
        -    // 测试删除姓名为AAA的User
        -    userRepository.delete(userRepository.findByName("AAA"));
        -
        -    // 测试findAll, 查询所有记录, 验证上面的删除是否成功
        -    Assert.assertEquals(9, userRepository.findAll().size());
        -
        -}
        -

        }
        完整示例

        -

        版权申明:署名-非商业性使用-禁止演绎 3.0 (CC BY-NC-ND 3.0)

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250SpringSecurity\350\277\233\350\241\214\345\256\211\345\205\250\346\216\247\345\210\266/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250SpringSecurity\350\277\233\350\241\214\345\256\211\345\205\250\346\216\247\345\210\266/index.html" deleted file mode 100644 index a14ecee..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250SpringSecurity\350\277\233\350\241\214\345\256\211\345\205\250\346\216\247\345\210\266/index.html" +++ /dev/null @@ -1,424 +0,0 @@ - - - - - - - SpringBoot中使用SpringSecurity进行安全控制 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用SpringSecurity进行安全控制 -

        - - -
        - - - - -
        - -

        Spring Boot中使用Spring Security进行安全控制
        2016年05月28日 标签:Spring Boot, Spring Security
        我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面。要实现访问控制的方法多种多样,可以通过Aop、拦截器实现,也可以通过框架实现(如:Apache Shiro、Spring Security)。

        -

        本文将具体介绍在Spring Boot中如何使用Spring Security进行安全控制。

        -

        准备工作
        首先,构建一个简单的Web工程,以用于后续添加安全控制,也可以用之前Chapter3-1-2做为基础工程。若对如何使用Spring Boot构建Web应用,可以先阅读《Spring Boot开发Web应用》一文。

        -

        Web层实现请求映射
        @Controller
        public class HelloController {

        -
        @RequestMapping("/")
        -public String index() {
        -    return "index";
        -}
        -
        -@RequestMapping("/hello")
        -public String hello() {
        -    return "hello";
        -}
        -

        }
        /:映射到index.html
        /hello:映射到hello.html
        实现映射的页面
        src/main/resources/templates/index.html

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <!DOCTYPE html>  
        <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
        <title>Spring Security入门</title>
        </head>
        <body>
        <h1>欢迎使用Spring Security!</h1>
        <p>点击 <a th:href="@{/hello}">这里</a> 打个招呼吧</p>
        </body>
        </html>

        -

        src/main/resources/templates/hello.html

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <!DOCTYPE html>  
        <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
        xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
        <title>Hello World!</title>
        </head>
        <body>
        <h1>Hello world!</h1>
        </body>
        </html>

        -

        可以看到在index.html中提供到/hello的链接,显然在这里没有任何安全控制,所以点击链接后就可以直接跳转到hello.html页面。

        -

        整合Spring Security
        在这一节,我们将对/hello页面进行权限控制,必须是授权用户才能访问。当没有权限的用户访问后,跳转到登录页面。

        -

        添加依赖
        在pom.xml中添加如下配置,引入对Spring Security的依赖。

        1
        2
        3
        4
        5
        6
        <dependencies>  
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        </dependencies>

        -

        Spring Security配置
        创建Spring Security的配置类WebSecurityConfig,具体如下:

        -

        @Configuration
        @EnableWebSecurity
        public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

        -
        @Override
        -protected void configure(HttpSecurity http) throws Exception {
        -    http
        -        .authorizeRequests()
        -            .antMatchers("/", "/home").permitAll()
        -            .anyRequest().authenticated()
        -            .and()
        -        .formLogin()
        -            .loginPage("/login")
        -            .permitAll()
        -            .and()
        -        .logout()
        -            .permitAll();
        -}
        -
        -@Autowired
        -public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        -    auth
        -        .inMemoryAuthentication()
        -            .withUser("user").password("password").roles("USER");
        -}
        -

        }
        通过@EnableWebSecurity注解开启Spring Security的功能
        继承WebSecurityConfigurerAdapter,并重写它的方法来设置一些web安全的细节
        configure(HttpSecurity http)方法
        通过authorizeRequests()定义哪些URL需要被保护、哪些不需要被保护。例如以上代码指定了/和/home不需要任何认证就可以访问,其他的路径都必须通过身份验证。
        通过formLogin()定义当需要用户登录时候,转到的登录页面。
        configureGlobal(AuthenticationManagerBuilder auth)方法,在内存中创建了一个用户,该用户的名称为user,密码为password,用户角色为USER。
        新增登录请求与页面
        在完成了Spring Security配置之后,我们还缺少登录的相关内容。

        -

        HelloController中新增/login请求映射至login.html

        -

        @Controller
        public class HelloController {

        -
        // 省略之前的内容...
        -
        -@RequestMapping("/login")
        -public String login() {
        -    return "login";
        -}
        -

        }
        新增登录页面:src/main/resources/templates/login.html

        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
        <html xmlns="http://www.w3.org/1999/xhtml"  
        xmlns:th="http://www.thymeleaf.org"
        xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
        <title>Spring Security Example </title>
        </head>
        <body>
        <div th:if="${param.error}">
        用户名或密码错
        </div>
        <div th:if="${param.logout}">
        您已注销成功
        </div>
        <form th:action="@{/login}" method="post">
        <div><label> 用户名 : <input type="text" name="username"/> </label></div>
        <div><label> 密 码 : <input type="password" name="password"/> </label></div>
        <div><input type="submit" value="登录"/></div>
        </form>
        </body>
        </html>
        ```
        可以看到,实现了一个简单的通过用户名和密码提交到/login的登录方式。

        根据配置,Spring Security提供了一个过滤器来拦截请求并验证用户身份。如果用户身份认证失败,页面就重定向到/login?error,并且页面中会展现相应的错误信息。若用户想要注销登录,可以通过访问/login?logout请求,在完成注销之后,页面展现相应的成功消息。

        到这里,我们启用应用,并访问http://localhost:8080/,可以正常访问。但是访问http://localhost:8080/hello的时候被重定向到了http://localhost:8080/login页面,因为没有登录,用户没有访问权限,通过输入用户名user和密码password进行登录后,跳转到了Hello World页面,再也通过访问http://localhost:8080/login?logout,就可以完成注销操作。

        为了让整个过程更完成,我们可以修改hello.html,让它输出一些内容,并提供“注销”的链接。

        ```
        <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
        xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
        <title>Hello World!</title>
        </head>
        <body>
        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
        <form th:action="@{/logout}" method="post">
        <input type="submit" value="注销"/>
        </form>
        </body>
        </html>

        -

        本文通过一个最简单的示例完成了对Web应用的安全控制,Spring Security提供的功能还远不止于此,更多Spring Security的使用可参见Spring Security Reference。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250Swagger2\346\236\204\345\273\272\345\274\272\345\244\247\347\232\204RESTful-API\346\226\207\346\241\243/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250Swagger2\346\236\204\345\273\272\345\274\272\345\244\247\347\232\204RESTful-API\346\226\207\346\241\243/index.html" deleted file mode 100644 index 8de189d..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250Swagger2\346\236\204\345\273\272\345\274\272\345\244\247\347\232\204RESTful-API\346\226\207\346\241\243/index.html" +++ /dev/null @@ -1,456 +0,0 @@ - - - - - - - SpringBoot中使用Swagger2构建强大的RESTful-API文档 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用Swagger2构建强大的RESTful-API文档 -

        - - -
        - - - - -
        - -

        Spring Boot中使用Swagger2构建强大的RESTful API文档
        2016年04月18日 标签:Spring Boot, Swagger, RESTful Api
        由于Spring Boot能够快速开发、便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API。而我们构建RESTful API的目的通常都是由于多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web前端。

        -

        这样一来,我们的RESTful API就有可能要面对多个开发人员或多个开发团队:IOS开发、Android开发或是Web开发等。为了减少与其他团队平时开发期间的频繁沟通成本,传统做法我们会创建一份RESTful API文档来记录所有接口细节,然而这样的做法有以下几个问题:

        -

        由于接口众多,并且细节复杂(需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等),高质量地创建这份文档本身就是件非常吃力的事,下游的抱怨声不绝于耳。
        随着时间推移,不断修改接口实现的时候都必须同步修改接口文档,而文档与代码又处于两个不同的媒介,除非有严格的管理机制,不然很容易导致不一致现象。
        为了解决上面这样的问题,本文将介绍RESTful API的重磅好伙伴Swagger2,它可以轻松的整合到Spring Boot中,并与Spring MVC程序配合组织出强大RESTful API文档。它既可以减少我们创建文档的工作量,同时说明内容又整合入实现代码中,让维护文档和修改代码整合为一体,可以让我们在修改代码逻辑的同时方便的修改文档说明。另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API。具体效果如下图所示:

        -

        -

        下面来具体介绍,如果在Spring Boot中使用Swagger2。首先,我们需要一个Spring Boot实现的RESTful API工程,若您没有做过这类内容,建议先阅读 Spring Boot构建一个较为复杂的RESTful APIs和单元测试。

        -

        下面的内容我们会以教程样例中的Chapter3-1-1进行下面的实验(Chpater3-1-5是我们的结果工程,亦可参考)。

        -

        添加Swagger2依赖
        在pom.xml中加入Swagger2的依赖

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <dependency>  
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.2.2</version>
        </dependency>
        <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.2.2</version>
        </dependency>

        -

        创建Swagger2配置类
        在Application.java同级创建Swagger2的配置类Swagger2。

        -

        @Configuration
        @EnableSwagger2
        public class Swagger2 {

        -
        @Bean
        -public Docket createRestApi() {
        -    return new Docket(DocumentationType.SWAGGER_2)
        -            .apiInfo(apiInfo())
        -            .select()
        -            .apis(RequestHandlerSelectors.basePackage("com.didispace.web"))
        -            .paths(PathSelectors.any())
        -            .build();
        -}
        -
        -private ApiInfo apiInfo() {
        -    return new ApiInfoBuilder()
        -            .title("Spring Boot中使用Swagger2构建RESTful APIs")
        -            .description("更多Spring Boot相关文章请关注:http://blog.didispace.com/")
        -            .termsOfServiceUrl("http://blog.didispace.com/")
        -            .contact("程序猿DD")
        -            .version("1.0")
        -            .build();
        -}
        -

        }
        如上代码所示,通过@Configuration注解,让Spring来加载该类配置。再通过@EnableSwagger2注解来启用Swagger2。

        -

        再通过createRestApi函数创建Docket的Bean之后,apiInfo()用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现,本例采用指定扫描的包路径来定义,Swagger会扫描该包下所有Controller定义的API,并产生文档内容(除了被@ApiIgnore指定的请求)。

        -

        添加文档内容
        在完成了上述配置后,其实已经可以生产文档内容,但是这样的文档主要针对请求本身,而描述主要来源于函数等命名产生,对用户并不友好,我们通常需要自己增加一些说明来丰富文档内容。如下所示,我们通过@ApiOperation注解来给API增加说明、通过@ApiImplicitParams、@ApiImplicitParam注解来给参数增加说明。

        -

        @RestController
        @RequestMapping(value=”/users”) // 通过这里配置使下面的映射都在/users下,可去除
        public class UserController {

        -
        static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());
        -
        -@ApiOperation(value="获取用户列表", notes="")
        -@RequestMapping(value={""}, method=RequestMethod.GET)
        -public List<User> getUserList() {
        -    List<User> r = new ArrayList<User>(users.values());
        -    return r;
        -}
        -
        -@ApiOperation(value="创建用户", notes="根据User对象创建用户")
        -@ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
        -@RequestMapping(value="", method=RequestMethod.POST)
        -public String postUser(@RequestBody User user) {
        -    users.put(user.getId(), user);
        -    return "success";
        -}
        -
        -@ApiOperation(value="获取用户详细信息", notes="根据url的id来获取用户详细信息")
        -@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
        -@RequestMapping(value="/{id}", method=RequestMethod.GET)
        -public User getUser(@PathVariable Long id) {
        -    return users.get(id);
        -}
        -
        -@ApiOperation(value="更新用户详细信息", notes="根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
        -@ApiImplicitParams({
        -        @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"),
        -        @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
        -})
        -@RequestMapping(value="/{id}", method=RequestMethod.PUT)
        -public String putUser(@PathVariable Long id, @RequestBody User user) {
        -    User u = users.get(id);
        -    u.setName(user.getName());
        -    u.setAge(user.getAge());
        -    users.put(id, u);
        -    return "success";
        -}
        -
        -@ApiOperation(value="删除用户", notes="根据url的id来指定删除对象")
        -@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
        -@RequestMapping(value="/{id}", method=RequestMethod.DELETE)
        -public String deleteUser(@PathVariable Long id) {
        -    users.remove(id);
        -    return "success";
        -}
        -

        }
        完成上述代码添加上,启动Spring Boot程序,访问:http://localhost:8080/swagger-ui.html 。就能看到前文所展示的RESTful API的页面。我们可以再点开具体的API请求,以POST类型的/users请求为例,可找到上述代码中我们配置的Notes信息以及参数user的描述信息,如下图所示。

        -

        alt

        -

        API文档访问与调试
        在上图请求的页面中,我们看到user的Value是个输入框?是的,Swagger除了查看接口功能外,还提供了调试测试功能,我们可以点击上图中右侧的Model Schema(黄色区域:它指明了User的数据结构),此时Value中就有了user对象的模板,我们只需要稍适修改,点击下方“Try it out!”按钮,即可完成了一次请求调用!

        -

        此时,你也可以通过几个GET请求来验证之前的POST请求是否正确。

        -

        相比为这些接口编写文档的工作,我们增加的配置内容是非常少而且精简的,对于原有代码的侵入也在忍受范围之内。因此,在构建RESTful API的同时,加入swagger来对API文档进行管理,是个不错的选择。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250log4j\345\256\236\347\216\260http\350\257\267\346\261\202\346\227\245\345\277\227\345\205\245mongodb/index.html" "b/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250log4j\345\256\236\347\216\260http\350\257\267\346\261\202\346\227\245\345\277\227\345\205\245mongodb/index.html" deleted file mode 100644 index 374149d..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\344\275\277\347\224\250log4j\345\256\236\347\216\260http\350\257\267\346\261\202\346\227\245\345\277\227\345\205\245mongodb/index.html" +++ /dev/null @@ -1,472 +0,0 @@ - - - - - - - SpringBoot中使用log4j实现http请求日志入mongodb | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用log4j实现http请求日志入mongodb -

        - - -
        - - - - -
        - -

        Spring Boot中使用log4j实现http请求日志入mongodb
        2016年05月25日 标签:Spring Boot, mongodb, log4j
        之前在《使用AOP统一处理Web请求日志》一文中介绍了如何使用AOP统一记录web请求日志。基本思路是通过aop去切web层的controller实现,获取每个http的内容并通过log4j将日志内容写到应用服务器的文件系统中。

        -

        但是当我们在集群中部署应用之后,应用请求的日志被分散记录在了不同应用服务器的文件系统上,这样分散的存储并不利于我们对日志内容的检索。解决日志分散问题的方案多种多样,本文思路以在《使用AOP统一处理Web请求日志》一文的基础之上,扩展log4j实现将日志写入MongoDB。

        -

        准备工作
        可以先拿Chapter4-2-4工程为基础,进行后续的实验改造。该工程实现了一个简单的REST接口,一个对web层的切面,并在web层切面前后记录http请求的日志内容。

        -

        通过自定义appender实现
        思路:log4j提供的输出器实现自Appender接口,要自定义appender输出到MongoDB,只需要继承AppenderSkeleton类,并实现几个方法即可完成。

        -

        引入mongodb的驱动
        在pom.xml中引入下面依赖

        -


        org.mongodb
        mongodb-driver
        3.2.2

        实现MongoAppender
        编写MongoAppender类继承AppenderSkeleton,实现如下:

        -

        public class MongoAppender extends AppenderSkeleton {

        -
        private MongoClient mongoClient;
        -private MongoDatabase mongoDatabase;
        -private MongoCollection<BasicDBObject> logsCollection;
        -
        -private String connectionUrl;
        -private String databaseName;
        -private String collectionName;
        -
        -@Override
        -protected void append(LoggingEvent loggingEvent) {
        -
        -    if(mongoDatabase == null) {
        -        MongoClientURI connectionString = new MongoClientURI(connectionUrl);
        -        mongoClient = new MongoClient(connectionString);
        -        mongoDatabase = mongoClient.getDatabase(databaseName);
        -        logsCollection = mongoDatabase.getCollection(collectionName, BasicDBObject.class);
        -    }
        -    logsCollection.insertOne((BasicDBObject) loggingEvent.getMessage());
        -
        -}
        -
        -@Override
        -public void close() {
        -    if(mongoClient != null) {
        -        mongoClient.close();
        -    }
        -}
        -
        -@Override
        -public boolean requiresLayout() {
        -    return false;
        -}
        -
        -// 省略getter和setter
        -

        }
        定义MongoDB的配置参数,可通过log4j.properties配置:

        -

        connectionUrl:连接mongodb的串
        databaseName:数据库名
        collectionName:集合名
        定义MongoDB的连接和操作对象,根据log4j.properties配置的参数初始化:

        -

        mongoClient:mongodb的连接客户端
        mongoDatabase:记录日志的数据库
        logsCollection:记录日志的集合
        重写append函数:

        -

        根据log4j.properties中的配置创建mongodb连接
        LoggingEvent提供getMessage()函数来获取日志消息
        往配置的记录日志的collection中插入日志消息
        重写close函数:关闭mongodb的

        -

        配置log4j.properties
        设置名为mongodb的logger:

        -

        记录INFO级别日志
        appender实现为com.didispace.log.MongoAppende
        mongodb连接地址:mongodb://localhost:27017
        mongodb数据库名:logs
        mongodb集合名:logs_request
        log4j.logger.mongodb=INFO, mongodb

        -

        mongodb输出

        log4j.appender.mongodb=com.didispace.log.MongoAppender
        log4j.appender.mongodb.connectionUrl=mongodb://localhost:27017
        log4j.appender.mongodb.databaseName=logs
        log4j.appender.mongodb.collectionName=logs_request
        切面中使用mongodb logger
        修改后的代码如下,主要做了以下几点修改:

        -

        logger取名为mongodb的
        通过getBasicDBObject函数从HttpServletRequest和JoinPoint对象中获取请求信息,并组装成BasicDBObject
        getHeadersInfo函数从HttpServletRequest中获取header信息
        通过logger.info(),输出BasicDBObject对象的信息到mongodb
        @Aspect
        @Order(1)
        @Component
        public class WebLogAspect {

        -
        private Logger logger = Logger.getLogger("mongodb");
        -
        -@Pointcut("execution(public * com.didispace.web..*.*(..))")
        -public void webLog(){}
        -
        -@Before("webLog()")
        -public void doBefore(JoinPoint joinPoint) throws Throwable {
        -    // 获取HttpServletRequest
        -    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        -    HttpServletRequest request = attributes.getRequest();
        -    // 获取要记录的日志内容
        -    BasicDBObject logInfo = getBasicDBObject(request, joinPoint);
        -    logger.info(logInfo);
        -}
        -
        -
        -private BasicDBObject getBasicDBObject(HttpServletRequest request, JoinPoint joinPoint) {
        -    // 基本信息
        -    BasicDBObject r = new BasicDBObject();
        -    r.append("requestURL", request.getRequestURL().toString());
        -    r.append("requestURI", request.getRequestURI());
        -    r.append("queryString", request.getQueryString());
        -    r.append("remoteAddr", request.getRemoteAddr());
        -    r.append("remoteHost", request.getRemoteHost());
        -    r.append("remotePort", request.getRemotePort());
        -    r.append("localAddr", request.getLocalAddr());
        -    r.append("localName", request.getLocalName());
        -    r.append("method", request.getMethod());
        -    r.append("headers", getHeadersInfo(request));
        -    r.append("parameters", request.getParameterMap());
        -    r.append("classMethod", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        -    r.append("args", Arrays.toString(joinPoint.getArgs()));
        -    return r;
        -}
        -
        -private Map<String, String> getHeadersInfo(HttpServletRequest request) {
        -    Map<String, String> map = new HashMap<>();
        -    Enumeration headerNames = request.getHeaderNames();
        -    while (headerNames.hasMoreElements()) {
        -        String key = (String) headerNames.nextElement();
        -        String value = request.getHeader(key);
        -        map.put(key, value);
        -    }
        -    return map;
        -}
        -

        }
        完整示例:Chapter4-2-5

        -

        其他补充
        上述内容主要提供一个思路去实现自定义日志的输出和管理。我们可以通过jdbc实现日志记录到mongodb,也可以通过spring-data-mongo来记录到mongodb,当然我们也可以输出到其他数据库,或者输出到消息队列等待其他后续处理等。

        -

        对于日志记录到mongodb,也可以直接使用log4mongo实现更为方便快捷。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\345\257\271log4j\350\277\233\350\241\214\345\244\232\347\216\257\345\242\203\344\270\215\345\220\214\346\227\245\345\277\227\347\272\247\345\210\253\347\232\204\346\216\247\345\210\266/index.html" "b/2016/06/15/SpringBoot\344\270\255\345\257\271log4j\350\277\233\350\241\214\345\244\232\347\216\257\345\242\203\344\270\215\345\220\214\346\227\245\345\277\227\347\272\247\345\210\253\347\232\204\346\216\247\345\210\266/index.html" deleted file mode 100644 index e1db6c6..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\345\257\271log4j\350\277\233\350\241\214\345\244\232\347\216\257\345\242\203\344\270\215\345\220\214\346\227\245\345\277\227\347\272\247\345\210\253\347\232\204\346\216\247\345\210\266/index.html" +++ /dev/null @@ -1,383 +0,0 @@ - - - - - - - SpringBoot中对log4j进行多环境不同日志级别的控制 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中对log4j进行多环境不同日志级别的控制 -

        - - -
        - - - - -
        - -

        Spring Boot中对log4j进行多环境不同日志级别的控制
        2016年05月19日 标签:Spring Boot
        之前介绍了在《Spring boot中使用log4j记录日志》,仅通过log4j.properties对日志级别进行控制,对于需要多环境部署的环境不是很方便,可能我们在开发环境大部分模块需要采用DEBUG级别,在测试环境可能需要小部分采用DEBUG级别,而在生产环境时我们又希望采用INFO级别。这个时候,我们要自己手工编辑log4j.properties文件来调整日志级别,不论在版本库中默认保存哪个环境的级别设定,都会增加其他环境使用人员的工作量,虽然很细微,但是手工修改总不是一件很好的选择,难免会发现修改后误提交等问题。

        -

        那么,有没有办法对于开发人员、运维人员都不需要改变源代码实现不同环境的不同日志级别呢?

        -

        是否还记得之前在《Spring Boot属性配置文件详解》一文中,提到的关于Spring Boot多环境的配置以及属性文件中的参数引用?若没有了解过相关内容,建议先阅读该文后继续此篇内容。

        -

        尝试改造
        先以chapter4-2-2工程作为基础工程,我们来进行多环境配置的改造。

        -

        创建多环境配置文件
        application-dev.properties:开发环境
        application-test.properties:测试环境
        application-prod.properties:生产环境
        application.properties中添加属性:spring.profiles.active=dev(默认激活application-dev.properties配置)
        application-dev.properties和application-test.properties配置文件中添加日志级别定义:logging.level.com.didispace=DEBUG
        application-prod.properties配置文件中添加日志级别定义:logging.level.com.didispace=INFO
        通过上面的定义,根据logging.level.com.didispace在不同环境的配置文件中定义了不同的级别,但是我们已经把日志交给了log4j管理,看看我们log4j.properties中对com.didispace包下的日志定义是这样的,固定定义了DEBUG级别,并输出到名为didifile定义的appender中。

        -

        LOG4J配置

        log4j.category.com.didispace=DEBUG, didifile

        -

        com.didispace下的日志输出

        log4j.appender.didifile=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.didifile.file=logs/my.log
        log4j.appender.didifile.DatePattern=’.’yyyy-MM-dd
        log4j.appender.didifile.layout=org.apache.log4j.PatternLayout
        log4j.appender.didifile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L —- %m%n
        那么,要如何动态的改变这个DEBUG级别呢?在《Spring Boot属性配置文件详解》中还提到了关于配置文件中参数的引用。我们需要将DEBUG替换成application-{profile}.properties配置文件中定义logging.level.com.didispace即可,所以配置变为如下内容:

        -

        LOG4J配置

        log4j.category.com.didispace=${logging.level.com.didispace}, didifile

        -

        com.didispace下的日志输出

        log4j.appender.didifile=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.didifile.file=logs/my.log
        log4j.appender.didifile.DatePattern=’.’yyyy-MM-dd
        log4j.appender.didifile.layout=org.apache.log4j.PatternLayout
        log4j.appender.didifile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L —- %m%n
        到这里我们已经完成了所有配置工作,我们可以通过运行单元测试,然后看my.log文件中输出的日志内容。通过修改默认的application-dev.properties配置的日志级别为INFO,再运行单元测试的DEBUG内容是否被输出到了my.log中验证参数是否被正确引用了。

        -

        对于不同环境的使用人员也不需要改变代码或打包文件,只需要通过执行命令中参加参数即可,比如我想采用生产环境的级别,那么我可以这样运行应用:

        -

        java -jar xxx.jar –spring.profiles.active=prod

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\344\270\255\347\232\204\344\272\213\345\212\241\347\256\241\347\220\206/index.html" "b/2016/06/15/SpringBoot\344\270\255\347\232\204\344\272\213\345\212\241\347\256\241\347\220\206/index.html" deleted file mode 100644 index 1aae64e..0000000 --- "a/2016/06/15/SpringBoot\344\270\255\347\232\204\344\272\213\345\212\241\347\256\241\347\220\206/index.html" +++ /dev/null @@ -1,438 +0,0 @@ - - - - - - - SpringBoot中的事务管理 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中的事务管理 -

        - - -
        - - - - -
        - -

        Spring Boot中的事务管理
        2016年05月27日 标签:Spring Boot, 事务, Transactional
        什么是事务?
        我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合。由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作数据的并不可靠,需要在这种情况下进行回退。

        -

        事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态。

        -

        事务管理是Spring框架中最为常用的功能之一,我们在使用Spring Boot开发应用时,大部分情况下也都需要使用事务。

        -

        快速入门
        在Spring Boot中,当我们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。

        -

        我们以之前实现的《用spring-data-jpa访问数据库》的示例Chapter3-2-2作为基础工程进行事务的使用常识。

        -

        在该样例工程中(若对该数据访问方式不了解,可先阅读该文章),我们引入了spring-data-jpa,并创建了User实体以及对User的数据访问对象UserRepository,在ApplicationTest类中实现了使用UserRepository进行数据读写的单元测试用例,如下:

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private UserRepository userRepository;
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 创建10条记录
        -    userRepository.save(new User("AAA", 10));
        -    userRepository.save(new User("BBB", 20));
        -    userRepository.save(new User("CCC", 30));
        -    userRepository.save(new User("DDD", 40));
        -    userRepository.save(new User("EEE", 50));
        -    userRepository.save(new User("FFF", 60));
        -    userRepository.save(new User("GGG", 70));
        -    userRepository.save(new User("HHH", 80));
        -    userRepository.save(new User("III", 90));
        -    userRepository.save(new User("JJJ", 100));
        -
        -    // 省略后续的一些验证操作
        -}
        -

        }
        可以看到,在这个单元测试用例中,使用UserRepository对象连续创建了10个User实体到数据库中,下面我们人为的来制造一些异常,看看会发生什么情况。

        -

        通过定义User的name属性长度为5,这样通过创建时User实体的name属性超长就可以触发异常产生。

        -

        @Entity
        public class User {

        -
        @Id
        -@GeneratedValue
        -private Long id;
        -
        -@Column(nullable = false, length = 5)
        -private String name;
        -
        -@Column(nullable = false)
        -private Integer age;
        -
        -// 省略构造函数、getter和setter
        -

        }
        修改测试用例中创建记录的语句,将一条记录的name长度超过5,如下:name为HHHHHHHHH的User对象将会抛出异常。

        -

        // 创建10条记录
        userRepository.save(new User(“AAA”, 10));
        userRepository.save(new User(“BBB”, 20));
        userRepository.save(new User(“CCC”, 30));
        userRepository.save(new User(“DDD”, 40));
        userRepository.save(new User(“EEE”, 50));
        userRepository.save(new User(“FFF”, 60));
        userRepository.save(new User(“GGG”, 70));
        userRepository.save(new User(“HHHHHHHHHH”, 80));
        userRepository.save(new User(“III”, 90));
        userRepository.save(new User(“JJJ”, 100));
        执行测试用例,可以看到控制台中抛出了如下异常,name字段超长:

        -

        2016-05-27 10:30:35.948 WARN 2660 — [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1406, SQLState: 22001
        2016-05-27 10:30:35.948 ERROR 2660 — [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data truncation: Data too long for column ‘name’ at row 1
        2016-05-27 10:30:35.951 WARN 2660 — [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Warning Code: 1406, SQLState: HY000
        2016-05-27 10:30:35.951 WARN 2660 — [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data too long for column ‘name’ at row 1

        -

        org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement
        此时查数据库中,创建了name从AAA到GGG的记录,没有HHHHHHHHHH、III、JJJ的记录。而若这是一个希望保证完整性操作的情况下,AAA到GGG的记录希望能在发生异常的时候被回退,这时候就可以使用事务让它实现回退,做法非常简单,我们只需要在test函数上添加@Transactional注解即可。

        -

        @Test
        @Transactional
        public void test() throws Exception {

        -
        // 省略测试内容
        -

        }
        再来执行该测试用例,可以看到控制台中输出了回滚日志(Rolled back transaction for test context),

        -

        2016-05-27 10:35:32.210 WARN 5672 — [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1406, SQLState: 22001
        2016-05-27 10:35:32.210 ERROR 5672 — [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data truncation: Data too long for column ‘name’ at row 1
        2016-05-27 10:35:32.213 WARN 5672 — [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Warning Code: 1406, SQLState: HY000
        2016-05-27 10:35:32.213 WARN 5672 — [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Data too long for column ‘name’ at row 1
        2016-05-27 10:35:32.221 INFO 5672 — [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test context [DefaultTestContext@1d7a715 testClass = ApplicationTests, testInstance = com.didispace.ApplicationTests@95a785, testMethod = test@ApplicationTests, testException = org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement, mergedContextConfiguration = [MergedContextConfiguration@11f39f9 testClass = ApplicationTests, locations = ‘{}’, classes = ‘{class com.didispace.Application}’, contextInitializerClasses = ‘[]’, activeProfiles = ‘{}’, propertySourceLocations = ‘{}’, propertySourceProperties = ‘{}’, contextLoader = ‘org.springframework.boot.test.SpringApplicationContextLoader’, parent = [null]]].

        -

        org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement
        再看数据库中,User表就没有AAA到GGG的用户数据了,成功实现了自动回滚。

        -

        这里主要通过单元测试演示了如何使用@Transactional注解来声明一个函数需要被事务管理,通常我们单元测试为了保证每个测试之间的数据独立,会使用@Rollback注解让每个单元测试都能在结束时回滚。而真正在开发业务逻辑时,我们通常在service层接口中使用@Transactional来对各个业务逻辑进行事务管理的配置,例如:

        -

        public interface UserService {

        -
        @Transactional
        -User login(String name, String password);
        -

        }
        事务详解
        上面的例子中我们使用了默认的事务配置,可以满足一些基本的事务需求,但是当我们项目较大较复杂时(比如,有多个数据源等),这时候需要在声明事务时,指定不同的事务管理器。对于不同数据源的事务管理配置可以见《Spring Boot多数据源配置与使用》中的设置。在声明事务时,只需要通过value属性指定配置的事务管理器名即可,例如:@Transactional(value=”transactionManagerPrimary”)。

        -

        除了指定不同的事务管理器之后,还能对事务进行隔离级别和传播行为的控制,下面分别详细解释:

        -

         隔离级别
        隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。

        -

        我们可以看org.springframework.transaction.annotation.Isolation枚举类中定义了五个表示隔离级别的值:

        -

        public enum Isolation {
        DEFAULT(-1),
        READ_UNCOMMITTED(1),
        READ_COMMITTED(2),
        REPEATABLE_READ(4),
        SERIALIZABLE(8);
        }
        DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是:READ_COMMITTED。
        READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
        READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
        REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
        SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
        指定方法:通过使用isolation属性设置,例如:

        -

        @Transactional(isolation = Isolation.DEFAULT)
        传播行为
        所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

        -

        我们可以看org.springframework.transaction.annotation.Propagation枚举类中定义了6个表示传播行为的枚举值:

        -

        public enum Propagation {
        REQUIRED(0),
        SUPPORTS(1),
        MANDATORY(2),
        REQUIRES_NEW(3),
        NOT_SUPPORTED(4),
        NEVER(5),
        NESTED(6);
        }
        REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
        SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
        MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
        REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
        NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
        NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
        NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。
        指定方法:通过使用propagation属性设置,例如:

        -

        @Transactional(propagation = Propagation.REQUIRED)

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\345\261\236\346\200\247\351\205\215\347\275\256\346\226\207\344\273\266\350\257\246\350\247\243/index.html" "b/2016/06/15/SpringBoot\345\261\236\346\200\247\351\205\215\347\275\256\346\226\207\344\273\266\350\257\246\350\247\243/index.html" deleted file mode 100644 index 4cae89d..0000000 --- "a/2016/06/15/SpringBoot\345\261\236\346\200\247\351\205\215\347\275\256\346\226\207\344\273\266\350\257\246\350\247\243/index.html" +++ /dev/null @@ -1,416 +0,0 @@ - - - - - - - SpringBoot属性配置文件详解 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot属性配置文件详解 -

        - - -
        - - - - -
        - -

        Spring Boot属性配置文件详解
        2016年05月05日 标签:Spring Boot
        相信很多人选择Spring Boot主要是考虑到它既能兼顾Spring的强大功能,还能实现快速开发的便捷。我们在Spring Boot使用过程中,最直观的感受就是没有了原来自己整合Spring应用时繁多的XML配置内容,替代它的是在pom.xml中引入模块化的Starter POMs,其中各个模块都有自己的默认配置,所以如果不是特殊应用场景,就只需要在application.properties中完成一些属性配置就能开启各模块的应用。

        -

        在之前的各篇文章中都有提及关于application.properties的使用,主要用来配置数据库连接、日志相关配置等。除了这些配置内容之外,本文将具体介绍一些在application.properties配置中的其他特性和使用方法。

        -

        自定义属性与加载
        我们在使用Spring Boot的时候,通常也需要定义一些自己使用的属性,我们可以如下方式直接定义:

        -

        com.didispace.blog.name=程序猿DD
        com.didispace.blog.title=Spring Boot教程
        然后通过@Value(“${属性名}”)注解来加载对应的配置属性,具体如下:

        -

        @Component
        public class BlogProperties {

        -
        @Value("${com.didispace.blog.name}")
        -private String name;
        -@Value("${com.didispace.blog.title}")
        -private String title;
        -
        -// 省略getter和setter
        -

        }
        按照惯例,通过单元测试来验证BlogProperties中的属性是否已经根据配置文件加载了。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private BlogProperties blogProperties;
        -
        -
        -@Test
        -public void getHello() throws Exception {
        -    Assert.assertEquals(blogProperties.getName(), "程序猿DD");
        -    Assert.assertEquals(blogProperties.getTitle(), "Spring Boot教程");
        -}
        -

        }
        参数间的引用
        在application.properties中的各个参数之间也可以直接引用来使用,就像下面的设置:

        -

        com.didispace.blog.name=程序猿DD
        com.didispace.blog.title=Spring Boot教程
        com.didispace.blog.desc=${com.didispace.blog.name}正在努力写《${com.didispace.blog.title}》
        com.didispace.blog.desc参数引用了上文中定义的name和title属性,最后该属性的值就是程序猿DD正在努力写《Spring Boot教程》。

        -

        使用随机数
        在一些情况下,有些参数我们需要希望它不是一个固定的值,比如密钥、服务端口等。Spring Boot的属性配置文件中可以通过${random}来产生int值、long值或者string字符串,来支持属性的随机值。

        -

        随机字符串

        com.didispace.blog.value=${random.value}

        -

        随机int

        com.didispace.blog.number=${random.int}

        -

        随机long

        com.didispace.blog.bignumber=${random.long}

        -

        10以内的随机数

        com.didispace.blog.test1=${random.int(10)}

        -

        10-20的随机数

        com.didispace.blog.test2=${random.int[10,20]}
        通过命令行设置属性值
        相信使用过一段时间Spring Boot的用户,一定知道这条命令:java -jar xxx.jar –server.port=8888,通过使用–server.port属性来设置xxx.jar应用的端口为8888。

        -

        在命令行运行时,连续的两个减号–就是对application.properties中的属性值进行赋值的标识。所以,java -jar xxx.jar –server.port=8888命令,等价于我们在application.properties中添加属性server.port=8888,该设置在样例工程中可见,读者可通过删除该值或使用命令行来设置该值来验证。

        -

        通过命令行来修改属性值固然提供了不错的便利性,但是通过命令行就能更改应用运行的参数,那岂不是很不安全?是的,所以Spring Boot也贴心的提供了屏蔽命令行访问属性的设置,只需要这句设置就能屏蔽:SpringApplication.setAddCommandLineProperties(false)。

        -

        多环境配置
        我们在开发Spring Boot应用时,通常同一套程序会被应用和安装到几个不同的环境,比如:开发、测试、生产等。其中每个环境的数据库地址、服务器端口等等配置都会不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常繁琐且容易发生错误的事。

        -

        对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包,Spring Boot也不例外,或者说更加简单。

        -

        在Spring Boot中多环境配置文件名需要满足application-{profile}.properties的格式,其中{profile}对应你的环境标识,比如:

        -

        application-dev.properties:开发环境
        application-test.properties:测试环境
        application-prod.properties:生产环境
        至于哪个具体的配置文件会被加载,需要在application.properties文件中通过spring.profiles.active属性来设置,其值对应{profile}值。

        -

        如:spring.profiles.active=test就会加载application-test.properties配置文件内容

        -

        下面,以不同环境配置不同的服务端口为例,进行样例实验。

        -

        针对各环境新建不同的配置文件application-dev.properties、application-test.properties、application-prod.properties

        -

        在这三个文件均都设置不同的server.port属性,如:dev环境设置为1111,test环境设置为2222,prod环境设置为3333

        -

        application.properties中设置spring.profiles.active=dev,就是说默认以dev环境设置

        -

        测试不同配置的加载

        -

        执行java -jar xxx.jar,可以观察到服务端口被设置为1111,也就是默认的开发环境(dev)
        执行java -jar xxx.jar –spring.profiles.active=test,可以观察到服务端口被设置为2222,也就是测试环境的配置(test)
        执行java -jar xxx.jar –spring.profiles.active=prod,可以观察到服务端口被设置为3333,也就是生产环境的配置(prod)
        按照上面的实验,可以如下总结多环境的配置思路:

        -

        application.properties中配置通用内容,并设置spring.profiles.active=dev,以开发环境为默认配置
        application-{profile}.properties中配置各个环境不同的内容
        通过命令行方式去激活不同环境的配置

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\345\267\245\347\250\213\347\273\223\346\236\204\346\216\250\350\215\220/index.html" "b/2016/06/15/SpringBoot\345\267\245\347\250\213\347\273\223\346\236\204\346\216\250\350\215\220/index.html" deleted file mode 100644 index dbd04a7..0000000 --- "a/2016/06/15/SpringBoot\345\267\245\347\250\213\347\273\223\346\236\204\346\216\250\350\215\220/index.html" +++ /dev/null @@ -1,374 +0,0 @@ - - - - - - - SpringBoot工程结构推荐 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot工程结构推荐 -

        - - -
        - - - - -
        - -

        Spring Boot工程结构推荐
        2016年03月05日 标签:Spring Boot
        今天看了一位简书上朋友发来的工程,于是想到应该要写这么一篇。前人总结的最佳实践案例可以帮助我们免去很多不必要的麻烦。花点时间来看一下本文,绝对物超所值。
        工程结构(最佳实践)
        Spring Boot框架本身并没有对工程结构有特别的要求,但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑,尤其是Spring包扫描机制的存在,如果您使用最佳实践的工程结构,可以免去不少特殊的配置工作。

        -

        典型示例
        root package结构:com.example.myproject
        应用主类Application.java置于root package下,通常我们会在应用主类中做一些框架配置扫描等配置,我们放在root package下可以帮助程序减少手工配置来加载到我们希望被Spring加载的内容
        实体(Entity)与数据访问层(Repository)置于com.example.myproject.domain包下
        逻辑层(Service)置于com.example.myproject.service包下
        Web层(web)置于com.example.myproject.web包下
        com
        +- example
        +- myproject
        +- Application.java
        |
        +- domain
        | +- Customer.java
        | +- CustomerRepository.java
        |
        +- service
        | +- CustomerService.java
        |
        +- web
        | +- CustomerController.java
        |
        看看您现在的功能是否这样配置,如果不是,不妨尝试改变一下,看看是否可以去掉一些@Configuration配置?

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\345\274\200\345\217\221Web\345\272\224\347\224\250/index.html" "b/2016/06/15/SpringBoot\345\274\200\345\217\221Web\345\272\224\347\224\250/index.html" deleted file mode 100644 index 8e6bbe2..0000000 --- "a/2016/06/15/SpringBoot\345\274\200\345\217\221Web\345\272\224\347\224\250/index.html" +++ /dev/null @@ -1,405 +0,0 @@ - - - - - - - SpringBoot开发Web应用 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot开发Web应用 -

        - - -
        - - - - -
        - -

        Spring Boot开发Web应用
        2016年03月03日 标签:Spring Boot
        Spring Boot快速入门中我们完成了一个简单的RESTful Service,体验了快速开发的特性。在留言中也有朋友提到如何把处理结果渲染到页面上。那么本篇就在上篇基础上介绍一下如何进行Web应用的开发。
        静态资源访问
        在我们开发Web应用的时候,需要引用大量的js、css、图片等静态资源。

        -

        默认配置
        Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则:

        -

        /static
        /public
        /resources
        /META-INF/resources
        举例:我们可以在src/main/resources/目录下创建static,在该位置放置一个图片文件。启动程序后,尝试访问http://localhost:8080/D.jpg。如能显示图片,配置成功。

        -

        渲染Web页面
        在之前的示例中,我们都是通过@RestController来处理请求,所以返回的内容为json对象。那么如果需要渲染html页面的时候,要如何实现呢?

        -

        模板引擎
        在动态HTML实现上Spring Boot依然可以完美胜任,并且提供了多种模板引擎的默认配置支持,所以在推荐的模板引擎下,我们可以很快的上手开发动态网站。

        -

        Spring Boot提供了默认配置的模板引擎主要有以下几种:

        -

        Thymeleaf
        FreeMarker
        Velocity
        Groovy
        Mustache
        Spring Boot建议使用这些模板引擎,避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性,具体可见后文:支持JSP的配置

        -

        当你使用上述模板引擎中的任何一个,它们默认的模板配置路径为:src/main/resources/templates。当然也可以修改这个路径,具体如何修改,可在后续各模板引擎的配置属性中查询并修改。

        -

        Thymeleaf
        Thymeleaf是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。它是一个开源的Java库,基于Apache License 2.0许可,由Daniel Fernández创建,该作者还是Java加密库Jasypt的作者。

        -

        Thymeleaf提供了一个用于整合Spring MVC的可选模块,在应用开发中,你可以使用Thymeleaf来完全代替JSP或其他模板引擎,如Velocity、FreeMarker等。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。

        -

        示例模板:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        <table>  
        <thead>
        <tr>
        <th th:text="#{msgs.headers.name}">Name</td>
        <th th:text="#{msgs.headers.price}">Price</td>
        </tr>
        </thead>
        <tbody>
        <tr th:each="prod : ${allProducts}">
        <td th:text="${prod.name}">Oranges</td>
        <td th:text="${#numbers.formatDecimal(prod.price,1,2)}">0.99</td>
        </tr>
        </tbody>
        </table>
        ```
        可以看到Thymeleaf主要以属性的方式加入到html标签中,浏览器在解析html时,当检查到没有的属性时候会忽略,所以Thymeleaf的模板可以通过浏览器直接打开展现,这样非常有利于前后端的分离。

        在Spring Boot中使用Thymeleaf,只需要引入下面依赖,并在默认的模板路径src/main/resources/templates下编写模板文件即可完成。

        -


        org.springframework.boot
        spring-boot-starter-thymeleaf

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        在完成配置之后,举一个简单的例子,在快速入门工程的基础上,举一个简单的示例来通过Thymeleaf渲染一个页面。

        @Controller
        public class HelloController {

        @RequestMapping("/")
        public String index(ModelMap map) {
        // 加入一个属性,用来在模板中读取
        map.addAttribute("host", "http://blog.didispace.com");
        // return模板文件的名称,对应src/main/resources/templates/index.html
        return "index";
        }

        }

        -

        <!DOCTYPE html>

        -

        -




        -

        -

        Hello World




        ```
        如上页面,直接打开html页面展现Hello World,但是启动程序后,访问http://localhost:8080/,则是展示Controller中host的值:http://blog.didispace.com,做到了不破坏HTML自身内容的数据逻辑分离。

        -

        更多Thymeleaf的页面语法,还请访问Thymeleaf的官方文档查询使用。

        -

        Thymeleaf的默认参数配置

        -

        如有需要修改默认配置的时候,只需复制下面要修改的属性到application.properties中,并修改成需要的值,如修改模板文件的扩展名,修改默认的模板路径等。

        -

        Enable template caching.

        spring.thymeleaf.cache=true

        -

        Check that the templates location exists.

        spring.thymeleaf.check-template-location=true

        -

        Content-Type value.

        spring.thymeleaf.content-type=text/html

        -

        Enable MVC Thymeleaf view resolution.

        spring.thymeleaf.enabled=true

        -

        Template encoding.

        spring.thymeleaf.encoding=UTF-8

        -

        Comma-separated list of view names that should be excluded from resolution.

        spring.thymeleaf.excluded-view-names=

        -

        Template mode to be applied to templates. See also StandardTemplateModeHandlers.

        spring.thymeleaf.mode=HTML5

        -

        Prefix that gets prepended to view names when building a URL.

        spring.thymeleaf.prefix=classpath:/templates/

        -

        Suffix that gets appended to view names when building a URL.

        spring.thymeleaf.suffix=.html spring.thymeleaf.template-resolver-order= # Order of the template resolver in the chain. spring.thymeleaf.view-names= # Comma-separated list of view names that can be resolved.
        支持JSP的配置
        Spring Boot并不建议使用,但如果一定要使用,可以参考此工程作为脚手架:JSP支持

        -

        Spring Boot教程完整案例

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\345\277\253\351\200\237\345\205\245\351\227\250/index.html" "b/2016/06/15/SpringBoot\345\277\253\351\200\237\345\205\245\351\227\250/index.html" deleted file mode 100644 index 016b8f4..0000000 --- "a/2016/06/15/SpringBoot\345\277\253\351\200\237\345\205\245\351\227\250/index.html" +++ /dev/null @@ -1,415 +0,0 @@ - - - - - - - SpringBoot快速入门 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot快速入门 -

        - - -
        - - - - -
        - -

        Spring Boot快速入门
        2016年02月26日 标签:Spring Boot
        简介
        在您第1次接触和学习Spring框架的时候,是否因为其繁杂的配置而退却了?在你第n次使用Spring框架的时候,是否觉得一堆反复黏贴的配置有一些厌烦?那么您就不妨来试试使用Spring Boot来让你更易上手,更简单快捷地构建Spring应用!

        -

        Spring Boot让我们的Spring应用变的更轻量化。比如:你可以仅仅依靠一个Java类来运行一个Spring引用。你也可以打包你的应用为jar并通过使用java -jar来运行你的Spring Web应用。

        -

        Spring Boot的主要优点:

        -

        为所有Spring开发者更快的入门
        开箱即用,提供各种默认配置来简化项目配置
        内嵌式容器简化Web项目
        没有冗余代码生成和XML配置的要求
        快速入门
        本章主要目标完成Spring Boot基础项目的构建,并且实现一个简单的Http请求处理,通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。

        -

        系统要求:
        Java 7及以上
        Spring Framework 4.1.5及以上
        本文采用Java 1.8.0_73、Spring Boot 1.3.2调试通过。

        -

        使用Maven构建项目
        通过SPRING INITIALIZR工具产生基础项目
        访问:http://start.spring.io/
        选择构建工具Maven Project、Spring Boot版本1.3.2以及一些工程基本信息,可参考下图所示

        点击Generate Project下载项目压缩包
        解压项目包,并用IDE以Maven项目导入,以IntelliJ IDEA 14为例:
        菜单中选择File–>New–>Project from Existing Sources…
        选择解压后的项目文件夹,点击OK
        点击Import project from external model并选择Maven,点击Next到底为止。
        若你的环境有多个版本的JDK,注意到选择Java SDK的时候请选择Java 7以上的版本
        项目结构解析

        项目结构

        -

        通过上面步骤完成了基础项目的创建,如上图所示,Spring Boot的基础结构共三个文件(具体路径根据用户生成项目时填写的Group所有差异):

        -

        src/main/java下的程序入口:Chapter1Application
        src/main/resources下的配置文件:application.properties
        src/test/下的测试入口:Chapter1ApplicationTests
        生成的Chapter1Application和Chapter1ApplicationTests类都可以直接运行来启动当前创建的项目,由于目前该项目未配合任何数据访问或Web模块,程序会在加载完Spring之后结束运行。

        -

        引入Web模块
        当前的pom.xml内容如下,仅引入了两个模块:

        -

        spring-boot-starter:核心模块,包括自动配置支持、日志和YAML
        spring-boot-starter-test:测试模块,包括JUnit、Hamcrest、Mockito

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        <dependencies>  
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>
        </dependencies>

        -

        引入Web模块,需添加spring-boot-starter-web模块:

        -
        <dependency>
        -    <groupId>org.springframework.boot</groupId>
        -    <artifactId>spring-boot-starter-web</artifactId>
        -</dependency>
        -

        编写HelloWorld服务
        创建package命名为com.didispace.web(根据实际情况修改)
        创建HelloController类,内容如下
        @RestController
        public class HelloController {

        -
        @RequestMapping("/hello")
        -public String index() {
        -    return "Hello World";
        -}
        -

        }
        启动主程序,打开浏览器访问http://localhost:8080/hello,可以看到页面输出Hello World
        编写单元测试用例
        打开的src/test/下的测试入口Chapter1ApplicationTests类。下面编写一个简单的单元测试来模拟http请求,具体如下:

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(classes = MockServletContext.class)
        @WebAppConfiguration
        public class Chapter1ApplicationTests {

        -
        private MockMvc mvc;
        -
        -@Before
        -public void setUp() throws Exception {
        -    mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
        -}
        -
        -@Test
        -public void getHello() throws Exception {
        -    mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
        -            .andExpect(status().isOk())
        -            .andExpect(content().string(equalTo("Hello World")));
        -}
        -

        }
        使用MockServletContext来构建一个空的WebApplicationContext,这样我们创建的HelloController就可以在@Before函数中创建并传递到MockMvcBuilders.standaloneSetup()函数中。

        -

        注意引入下面内容,让status、content、equalTo函数可用
        import static org.hamcrest.Matchers.equalTo;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
        至此已完成目标,通过Maven构建了一个空白Spring Boot项目,再通过引入web模块实现了一个简单的请求处理。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\346\227\245\345\277\227\347\256\241\347\220\206/index.html" "b/2016/06/15/SpringBoot\346\227\245\345\277\227\347\256\241\347\220\206/index.html" deleted file mode 100644 index b9b22a9..0000000 --- "a/2016/06/15/SpringBoot\346\227\245\345\277\227\347\256\241\347\220\206/index.html" +++ /dev/null @@ -1,398 +0,0 @@ - - - - - - - SpringBoot日志管理 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot日志管理 -

        - - -
        - - - - -
        - -

        Spring Boot日志管理
        2016年04月13日 标签:Spring Boot
        Spring Boot在所有内部日志中使用Commons Logging,但是默认配置也提供了对常用日志的支持,如:Java Util Logging,Log4J, Log4J2和Logback。每种Logger都可以通过配置使用控制台或者文件输出日志内容。

        -

        格式化日志
        默认的日志输出如下:

        -

        2016-04-13 08:23:50.120 INFO 37397 — [ main] org.hibernate.Version : HHH000412: Hibernate Core {4.3.11.Final}
        输出内容元素具体如下:

        -

        时间日期 — 精确到毫秒
        日志级别 — ERROR, WARN, INFO, DEBUG or TRACE
        进程ID
        分隔符 — — 标识实际日志的开始
        线程名 — 方括号括起来(可能会截断控制台输出)
        Logger名 — 通常使用源代码的类名
        日志内容
        控制台输出
        在Spring Boot中默认配置了ERROR、WARN和INFO级别的日志输出到控制台。

        -

        我们可以通过两种方式切换至DEBUG级别:

        -

        在运行命令后加入–debug标志,如:$ java -jar myapp.jar –debug
        在application.properties中配置debug=true,该属性置为true的时候,核心Logger(包含嵌入式容器、hibernate、spring)会输出更多内容,但是你自己应用的日志并不会输出为DEBUG级别。
        多彩输出
        如果你的终端支持ANSI,设置彩色输出会让日志更具可读性。通过在application.properties中设置spring.output.ansi.enabled参数来支持。

        -

        NEVER:禁用ANSI-colored输出(默认项)
        DETECT:会检查终端是否支持ANSI,是的话就采用彩色输出(推荐项)
        ALWAYS:总是使用ANSI-colored格式输出,若终端不支持的时候,会有很多干扰信息,不推荐使用
        文件输出
        Spring Boot默认配置只会输出到控制台,并不会记录到文件中,但是我们通常生产环境使用时都需要以文件方式记录。

        -

        若要增加文件输出,需要在application.properties中配置logging.file或logging.path属性。

        -

        logging.file,设置文件,可以是绝对路径,也可以是相对路径。如:logging.file=my.log
        logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log

        -
          -
        • 日志文件会在10Mb大小的时候被截断,产生新的日志文件,默认级别为:ERROR、WARN、INFO *
        • -
        -

        级别控制
        在Spring Boot中只需要在application.properties中进行配置完成日志记录的级别控制。

        -

        配置格式:logging.level.*=LEVEL

        -

        logging.level:日志级别控制前缀,*为包名或Logger名
        LEVEL:选项TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
        举例:

        -

        logging.level.com.didispace=DEBUG:com.didispace包下所有class以DEBUG级别输出
        logging.level.root=WARN:root日志以WARN级别输出
        自定义日志配置
        由于日志服务一般都在ApplicationContext创建前就初始化了,它并不是必须通过Spring的配置文件控制。因此通过系统属性和传统的Spring Boot外部配置文件依然可以很好的支持日志控制和管理。

        -

        根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:

        -

        Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
        Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
        Log4j2:log4j2-spring.xml, log4j2.xml
        JDK (Java Util Logging):logging.properties
        Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml)

        -

        自定义输出格式
        在Spring Boot中可以通过在application.properties配置如下参数控制输出格式:

        -

        logging.pattern.console:定义输出到控制台的样式(不支持JDK Logger)
        logging.pattern.file:定义输出到文件的样式(不支持JDK Logger)

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringBoot\346\236\204\345\273\272RESTfulAPI\344\270\216\345\215\225\345\205\203\346\265\213\350\257\225/index.html" "b/2016/06/15/SpringBoot\346\236\204\345\273\272RESTfulAPI\344\270\216\345\215\225\345\205\203\346\265\213\350\257\225/index.html" deleted file mode 100644 index 7c6af63..0000000 --- "a/2016/06/15/SpringBoot\346\236\204\345\273\272RESTfulAPI\344\270\216\345\215\225\345\205\203\346\265\213\350\257\225/index.html" +++ /dev/null @@ -1,490 +0,0 @@ - - - - - - - SpringBoot构建RESTfulAPI与单元测试 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot构建RESTfulAPI与单元测试 -

        - - -
        - - - - -
        - -

        Spring Boot构建RESTful API与单元测试
        2016年03月13日 标签:Spring Boot
        首先,回顾并详细说明一下在快速入门中使用的@Controller、@RestController、@RequestMapping注解。如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例,建议先看一下快速入门的内容。

        -

        @Controller:修饰class,用来创建处理http请求的对象
        @RestController:Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。
        @RequestMapping:配置url映射
        下面我们尝试使用Spring MVC来实现一组对User对象操作的RESTful API,配合注释详细说明在Spring MVC中如何映射HTTP请求、如何传参、如何编写单元测试。

        -
          -
        • RESTful API具体设计如下:*
        • -
        -

        请求类型 URL 功能说明
        GET /users 查询用户列表
        POST /users 创建一个用户
        GET /users/id 根据id查询一个用户
        PUT /users/id 根据id更新一个用户
        DELETE /users/id 根据id删除一个用户
        User实体定义:

        -

        public class User {

        -
        private Long id; 
        -private String name; 
        -private Integer age; 
        -
        -// 省略setter和getter 
        -

        }
        实现对User对象的操作接口

        -

        @RestController
        @RequestMapping(value=”/users”) // 通过这里配置使下面的映射都在/users下
        public class UserController {

        -
        // 创建线程安全的Map 
        -static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); 
        -
        -@RequestMapping(value="/", method=RequestMethod.GET) 
        -public List<User> getUserList() { 
        -    // 处理"/users/"的GET请求,用来获取用户列表 
        -    // 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递 
        -    List<User> r = new ArrayList<User>(users.values()); 
        -    return r; 
        -} 
        -
        -@RequestMapping(value="/", method=RequestMethod.POST) 
        -public String postUser(@ModelAttribute User user) { 
        -    // 处理"/users/"的POST请求,用来创建User 
        -    // 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数 
        -    users.put(user.getId(), user); 
        -    return "success"; 
        -} 
        -
        -@RequestMapping(value="/{id}", method=RequestMethod.GET) 
        -public User getUser(@PathVariable Long id) { 
        -    // 处理"/users/{id}"的GET请求,用来获取url中id值的User信息 
        -    // url中的id可通过@PathVariable绑定到函数的参数中 
        -    return users.get(id); 
        -} 
        -
        -@RequestMapping(value="/{id}", method=RequestMethod.PUT) 
        -public String putUser(@PathVariable Long id, @ModelAttribute User user) { 
        -    // 处理"/users/{id}"的PUT请求,用来更新User信息 
        -    User u = users.get(id); 
        -    u.setName(user.getName()); 
        -    u.setAge(user.getAge()); 
        -    users.put(id, u); 
        -    return "success"; 
        -} 
        -
        -@RequestMapping(value="/{id}", method=RequestMethod.DELETE) 
        -public String deleteUser(@PathVariable Long id) { 
        -    // 处理"/users/{id}"的DELETE请求,用来删除User 
        -    users.remove(id); 
        -    return "success"; 
        -} 
        -

        }
        下面针对该Controller编写测试用例验证正确性,具体如下。当然也可以通过浏览器插件等进行请求提交验证。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(classes = MockServletContext.class)
        @WebAppConfiguration
        public class ApplicationTests {

        -
        private MockMvc mvc; 
        -
        -@Before 
        -public void setUp() throws Exception { 
        -    mvc = MockMvcBuilders.standaloneSetup(new UserController()).build(); 
        -} 
        -
        -@Test 
        -public void testUserController() throws Exception { 
        -    // 测试UserController 
        -    RequestBuilder request = null; 
        -
        -    // 1、get查一下user列表,应该为空 
        -    request = get("/users/"); 
        -    mvc.perform(request) 
        -            .andExpect(status().isOk()) 
        -            .andExpect(content().string(equalTo("[]"))); 
        -
        -    // 2、post提交一个user 
        -    request = post("/users/") 
        -            .param("id", "1") 
        -            .param("name", "测试大师") 
        -            .param("age", "20"); 
        -    mvc.perform(request) 
        -            .andExpect(content().string(equalTo("success"))); 
        -
        -    // 3、get获取user列表,应该有刚才插入的数据 
        -    request = get("/users/"); 
        -    mvc.perform(request) 
        -            .andExpect(status().isOk()) 
        -            .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"测试大师\",\"age\":20}]"))); 
        -
        -    // 4、put修改id为1的user 
        -    request = put("/users/1") 
        -            .param("name", "测试终极大师") 
        -            .param("age", "30"); 
        -    mvc.perform(request) 
        -            .andExpect(content().string(equalTo("success"))); 
        -
        -    // 5、get一个id为1的user 
        -    request = get("/users/1"); 
        -    mvc.perform(request) 
        -            .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"测试终极大师\",\"age\":30}"))); 
        -
        -    // 6、del删除id为1的user 
        -    request = delete("/users/1"); 
        -    mvc.perform(request) 
        -            .andExpect(content().string(equalTo("success"))); 
        -
        -    // 7、get查一下user列表,应该为空 
        -    request = get("/users/"); 
        -    mvc.perform(request) 
        -            .andExpect(status().isOk()) 
        -            .andExpect(content().string(equalTo("[]"))); 
        -
        -} 
        -

        }
        至此,我们通过引入web模块(没有做其他的任何配置),就可以轻松利用Spring MVC的功能,以非常简洁的代码完成了对User对象的RESTful API的创建以及单元测试的编写。其中同时介绍了Spring MVC中最为常用的几个核心注解:@Controller,@RestController,RequestMapping以及一些参数绑定的注解:@PathVariable,@ModelAttribute,@RequestParam等。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringCloud\346\236\204\345\273\272\345\276\256\346\234\215\345\212\241\346\236\266\346\236\2041\342\200\224\342\200\224\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260/index.html" "b/2016/06/15/SpringCloud\346\236\204\345\273\272\345\276\256\346\234\215\345\212\241\346\236\266\346\236\2041\342\200\224\342\200\224\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260/index.html" deleted file mode 100644 index 63fa22d..0000000 --- "a/2016/06/15/SpringCloud\346\236\204\345\273\272\345\276\256\346\234\215\345\212\241\346\236\266\346\236\2041\342\200\224\342\200\224\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260/index.html" +++ /dev/null @@ -1,427 +0,0 @@ - - - - - - - SpringCloud构建微服务架构1——服务注册与发现 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringCloud构建微服务架构1——服务注册与发现 -

        - - -
        - - - - -
        - -

        Spring Cloud构建微服务架构(一)——服务注册与发现
        2016年05月31日 标签:Spring Cloud, Spring Boot, Netflix, Eureka
        Spring Cloud简介
        Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。

        -

        Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等项目。

        -

        微服务架构
        “微服务架构”在这几年非常的火热,以至于关于微服务架构相关的产品社区也变得越来越活跃(比如:netflix、dubbo),Spring Cloud也因Spring社区的强大知名度和影响力也被广大架构师与开发者备受关注。

        -

        那么什么是“微服务架构”呢?简单的说,微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署、独立维护、独立扩展,服务与服务间通过诸如RESTful API的方式互相调用。

        -

        对于“微服务架构”,大家在互联网可以搜索到很多相关的介绍和研究文章来进行学习和了解。也可以阅读始祖Martin Fowler的《Microservices》,本文不做更多的介绍和描述。

        -

        服务注册与发现
        在简单介绍了Spring Cloud和微服务架构之后,下面回归本文的主旨内容,如何使用Spring Cloud搭建服务注册与发现模块。

        -

        这里我们会用到Spring Cloud Netflix,该项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路有(Zuul),客户端负载均衡(Ribbon)等。

        -

        所以,我们这里的核心内容就是服务发现模块:Eureka。下面我们动手来做一些尝试。

        -

        创建“服务注册中心”
        创建一个基础的Spring Boot工程,并在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
        <parent>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
        </parent>

        <dependencies>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>

        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        </dependencies>

        <dependencyManagement>
        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Brixton.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
        </dependency>
        </dependencies>
        </dependencyManagement>

        -

        通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话。这一步非常的简单,只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能,比如下面的例子:

        -

        @EnableEurekaServer
        @SpringBootApplication
        public class Application {

        -
        public static void main(String[] args) {
        -    new SpringApplicationBuilder(Application.class).web(true).run(args);
        -}
        -

        }
        在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.properties中问增加如下配置:

        -

        server.port=1111

        -

        eureka.client.register-with-eureka=false
        eureka.client.fetch-registry=false
        eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
        为了与后续要进行注册的服务区分,这里将服务注册中心的端口通过server.port属性设置为1111。

        -

        启动工程后,访问:http://localhost:1111/

        -

        可以看到下面的页面,其中还没有发现任何服务

        -

        alt

        -

        该工程可参见:Chapter9-1-1/eureka-server

        -

        创建“服务提供方”
        下面我们创建提供服务的客户端,并向服务注册中心注册自己。

        -

        假设我们有一个提供计算功能的微服务模块,我们实现一个RESTful API,通过传入两个参数a和b,最后返回a + b的结果。

        -

        首先,创建一个基本的Spring Boot应用,在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
        <parent>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
        </parent>

        <dependencies>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>

        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        </dependencies>

        <dependencyManagement>
        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Brixton.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
        </dependency>
        </dependencies>
        </dependencyManagement>

        -

        其次,实现/add请求处理接口,通过DiscoveryClient对象,在日志中打印出服务实例的相关内容。

        -

        @RestController
        public class ComputeController {

        -
        private final Logger logger = Logger.getLogger(getClass());
        -
        -@Autowired
        -private DiscoveryClient client;
        -
        -@RequestMapping(value = "/add" ,method = RequestMethod.GET)
        -public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
        -    ServiceInstance instance = client.getLocalServiceInstance();
        -    Integer r = a + b;
        -    logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
        -    return r;
        -}
        -

        }
        最后在主类中通过加上@EnableDiscoveryClient注解,该注解能激活Eureka中的DiscoveryClient实现,才能实现Controller中对服务信息的输出。

        -

        @EnableDiscoveryClient
        @SpringBootApplication
        public class ComputeServiceApplication {

        -
        public static void main(String[] args) {
        -    new SpringApplicationBuilder(ComputeServiceApplication.class).web(true).run(args);
        -}
        -

        }
        我们在完成了服务内容的实现之后,再继续对application.properties做一些配置工作,具体如下:

        -

        spring.application.name=compute-service

        -

        server.port=2222

        -

        eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
        通过spring.application.name属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。

        -

        eureka.client.serviceUrl.defaultZone属性对应服务注册中心的配置内容,指定服务注册中心的位置。

        -

        为了在本机上测试区分服务提供方和服务注册中心,使用server.port属性设置不同的端口。

        -

        启动该工程后,再次访问:http://localhost:1111/

        -

        可以看到,我们定义的服务被注册了。

        -

        -

        该工程可参见:Chapter9-1-1/compute-service

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/SpringCloud\346\236\204\345\273\272\345\276\256\346\234\215\345\212\241\346\236\266\346\236\2042\342\200\224\342\200\224\346\234\215\345\212\241\346\266\210\350\264\271\350\200\205/index.html" "b/2016/06/15/SpringCloud\346\236\204\345\273\272\345\276\256\346\234\215\345\212\241\346\236\266\346\236\2042\342\200\224\342\200\224\346\234\215\345\212\241\346\266\210\350\264\271\350\200\205/index.html" deleted file mode 100644 index f728dd1..0000000 --- "a/2016/06/15/SpringCloud\346\236\204\345\273\272\345\276\256\346\234\215\345\212\241\346\236\266\346\236\2042\342\200\224\342\200\224\346\234\215\345\212\241\346\266\210\350\264\271\350\200\205/index.html" +++ /dev/null @@ -1,433 +0,0 @@ - - - - - - - SpringCloud构建微服务架构2——服务消费者 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - SpringCloud构建微服务架构2——服务消费者 -

        - - -
        - - - - -
        - -

        Spring Cloud构建微服务架构(二)——服务消费者
        2016年06月02日 标签:Spring Cloud, Spring Boot, Eureka, Netflix, Ribbon, Feign
        在上一篇《Spring Cloud构建微服务架构(一)服务注册与发现》中,我们已经成功创建了“服务注册中心”,实现并注册了一个“服务提供者:COMPUTE-SERVICE”。那么我们要如何去消费服务提供者的接口内容呢?

        -

        Ribbon
        Ribbon是一个基于HTTP和TCP客户端的负载均衡器。Feign中也使用Ribbon,后续会介绍Feign的使用。

        -

        Ribbon可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用。

        -

        当Ribbon与Eureka联合使用时,ribbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务端列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。

        -

        下面我们通过实例看看如何使用Ribbon来调用服务,并实现客户端的均衡负载。

        -

        准备工作
        启动Chapter-9-1-1中的服务注册中心:eureka-server
        启动Chapter-9-1-1中的服务提供方:compute-service
        修改compute-service中的server-port为2223,再启动一个服务提供方:compute-service
        此时访问:http://localhost:1111/

        -

        可以看到COMPUTE-SERVICE服务有两个单元正在运行:

        -

        192.168.21.101:compute-service:2222
        192.168.21.101:compute-service:2223
        使用Ribbon实现客户端负载均衡的消费者
        构建一个基本Spring Boot项目,并在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
        <parent>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.5.RELEASE</version>
        <relativePath/>
        </parent>

        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>
        </dependencies>

        <dependencyManagement>
        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Brixton.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
        </dependency>
        </dependencies>
        </dependencyManagement>

        -

        在应用主类中,通过@EnableDiscoveryClient注解来添加发现服务能力。创建RestTemplate实例,并通过@LoadBalanced注解开启均衡负载能力。

        -

        @SpringBootApplication
        @EnableDiscoveryClient
        public class RibbonApplication {

        -
        @Bean
        -@LoadBalanced
        -RestTemplate restTemplate() {
        -    return new RestTemplate();
        -}
        -
        -public static void main(String[] args) {
        -    SpringApplication.run(RibbonApplication.class, args);
        -}
        -

        }
        创建ConsumerController来消费COMPUTE-SERVICE的add服务。通过直接RestTemplate来调用服务,计算10 + 20的值。

        -

        @RestController
        public class ConsumerController {

        -
        @Autowired
        -RestTemplate restTemplate;
        -
        -@RequestMapping(value = "/add", method = RequestMethod.GET)
        -public String add() {
        -    return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
        -}
        -

        }
        application.properties中配置eureka服务注册中心

        -

        spring.application.name=ribbon-consumer
        server.port=3333

        -

        eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
        启动该应用,并访问两次:http://localhost:3333/add

        -

        然后,打开compute-service的两个服务提供方,分别输出了类似下面的日志内容:

        -

        端口为2222服务提供端的日志:
        2016-06-02 11:16:26.787 INFO 90014 — [io-2222-exec-10] com.didispace.web.ComputeController : /add, host:192.168.21.101, service_id:compute-service, result:30
        端口为2223服务提供端的日志:
        2016-06-02 11:19:41.241 INFO 90122 — [nio-2223-exec-1] com.didispace.web.ComputeController : /add, host:192.168.21.101, service_id:compute-service, result:30
        可以看到,之前启动的两个compute-service服务端分别被调用了一次。到这里,我们已经通过Ribbon在客户端已经实现了对服务调用的均衡负载。

        -

        完整示例可参考:Chapter9-1-2/eureka-ribbon

        -

        Feign
        Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单。我们只需要使用Feign来创建一个接口并用注解来配置它既可完成。它具备可插拔的注解支持,包括Feign注解和JAX-RS注解。Feign也支持可插拔的编码器和解码器。Spring Cloud为Feign增加了对Spring MVC注解的支持,还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。

        -

        下面,通过一个例子来展现Feign如何方便的声明对上述computer-service服务的定义和调用。

        -

        创建一个Spring Boot工程,配置pom.xml,将上述的配置中的ribbon依赖替换成feign的依赖即可,具体如下:

        -
        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
        <parent>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
        </parent>

        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>
        </dependencies>

        <dependencyManagement>
        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Brixton.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
        </dependency>
        </dependencies>
        </dependencyManagement>
        -

        在应用主类中通过@EnableFeignClients注解开启Feign功能,具体如下:

        -

        @SpringBootApplication
        @EnableDiscoveryClient
        @EnableFeignClients
        public class FeignApplication {

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

        }
        定义compute-service服务的接口,具体如下:

        -

        @FeignClient(“compute-service”)
        public interface ComputeClient {

        -
        @RequestMapping(method = RequestMethod.GET, value = "/add")
        -Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);
        -

        }
        使用@FeignClient(“compute-service”)注解来绑定该接口对应compute-service服务
        通过Spring MVC的注解来配置compute-service服务下的具体实现。
        在web层中调用上面定义的ComputeClient,具体如下:

        -

        @RestController
        public class ConsumerController {

        -
        @Autowired
        -ComputeClient computeClient;
        -
        -@RequestMapping(value = "/add", method = RequestMethod.GET)
        -public Integer add() {
        -    return computeClient.add(10, 20);
        -}
        -

        }
        application.properties中不用变,指定eureka服务注册中心即可,如:

        -

        spring.application.name=feign-consumer
        server.port=3333

        -

        eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
        启动该应用,访问几次:http://localhost:3333/add

        -

        再观察日志,可以得到之前使用Ribbon时一样的结果,对服务提供方实现了均衡负载。

        -

        这一节我们通过Feign以接口和注解配置的方式,轻松实现了对compute-service服务的绑定,这样我们就可以在本地应用中像本地服务一下的调用它,并且做到了客户端均衡负载。

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/Springboot\344\270\255\344\275\277\347\224\250log4j\350\256\260\345\275\225\346\227\245\345\277\227/index.html" "b/2016/06/15/Springboot\344\270\255\344\275\277\347\224\250log4j\350\256\260\345\275\225\346\227\245\345\277\227/index.html" deleted file mode 100644 index 8a7523a..0000000 --- "a/2016/06/15/Springboot\344\270\255\344\275\277\347\224\250log4j\350\256\260\345\275\225\346\227\245\345\277\227/index.html" +++ /dev/null @@ -1,387 +0,0 @@ - - - - - - - Springboot中使用log4j记录日志 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - Springboot中使用log4j记录日志 -

        - - -
        - - - - -
        - -

        Spring boot中使用log4j记录日志
        2016年05月19日 标签:Spring Boot
        之前在Spring Boot日志管理 一文中主要介绍了Spring Boot中默认日志工具(logback)的基本配置内容。对于很多习惯使用log4j的开发者,Spring Boot依然可以很好的支持,只是需要做一些小小的配置功能。

        -

        引入log4j依赖
        在创建Spring Boot工程时,我们引入了spring-boot-starter,其中包含了spring-boot-starter-logging,该依赖内容就是Spring Boot默认的日志框架Logback,所以我们在引入log4j之前,需要先排除该包的依赖,再引入log4j的依赖,就像下面这样:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        <dependency>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
        <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
        </exclusions>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j</artifactId>
        </dependency>

        -

        配置log4j.properties
        在引入了log4j依赖之后,只需要在src/main/resources目录下加入log4j.properties配置文件,就可以开始对应用的日志进行配置使用。

        -

        控制台输出
        通过如下配置,设定root日志的输出级别为INFO,appender为控制台输出stdout

        -

        LOG4J配置

        log4j.rootCategory=INFO, stdout

        -

        控制台输出

        log4j.appender.stdout=org.apache.log4j.ConsoleAppender
        log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
        log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
        输出到文件
        在开发环境,我们只是输出到控制台没有问题,但是到了生产或测试环境,或许持久化日志内容,方便追溯问题原因。可以通过添加如下的appender内容,按天输出到不同的文件中去,同时还需要为log4j.rootCategory添加名为file的appender,这样root日志就可以输出到logs/all.log文件中了。

        -

        #
        log4j.rootCategory=INFO, stdout, file

        -

        root日志输出

        log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.file.file=logs/all.log
        log4j.appender.file.DatePattern=’.’yyyy-MM-dd
        log4j.appender.file.layout=org.apache.log4j.PatternLayout
        log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
        分类输出
        当我们日志量较多的时候,查找问题会非常困难,常用的手段就是对日志进行分类,比如:

        -

        可以按不同package进行输出。通过定义输出到logs/my.log的appender,并对com.didispace包下的日志级别设定为DEBUG级别、appender设置为输出到logs/my.log的名为didifile的appender。

        -

        com.didispace包下的日志配置

        log4j.category.com.didispace=DEBUG, didifile

        -

        com.didispace下的日志输出

        log4j.appender.didifile=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.didifile.file=logs/my.log
        log4j.appender.didifile.DatePattern=’.’yyyy-MM-dd
        log4j.appender.didifile.layout=org.apache.log4j.PatternLayout
        log4j.appender.didifile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L —- %m%n
        可以对不同级别进行分类,比如对ERROR级别输出到特定的日志文件中,具体配置可以如下。
        log4j.logger.error=errorfile

        -

        error日志输出

        log4j.appender.errorfile=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.errorfile.file=logs/error.log
        log4j.appender.errorfile.DatePattern=’.’yyyy-MM-dd
        log4j.appender.errorfile.Threshold = ERROR
        log4j.appender.errorfile.layout=org.apache.log4j.PatternLayout
        log4j.appender.errorfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
        本文主要介绍如何在spring boot中引入log4j,以及一些基础用法,对于更多log4j的用法,还请参考log4j官方网站

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git "a/2016/06/15/springBoot\345\244\232\346\225\260\346\215\256\346\272\220\351\205\215\347\275\256\344\270\216\344\275\277\347\224\250/index.html" "b/2016/06/15/springBoot\345\244\232\346\225\260\346\215\256\346\272\220\351\205\215\347\275\256\344\270\216\344\275\277\347\224\250/index.html" deleted file mode 100644 index 659f433..0000000 --- "a/2016/06/15/springBoot\345\244\232\346\225\260\346\215\256\346\272\220\351\205\215\347\275\256\344\270\216\344\275\277\347\224\250/index.html" +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - springBoot多数据源配置与使用 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - -
        - - - - -
        - - -

        - springBoot多数据源配置与使用 -

        - - -
        - - - - -
        - -

        Spring Boot多数据源配置与使用
        2016年03月28日 标签:Spring Boot
        之前在介绍使用JdbcTemplate和Spring-data-jpa时,都使用了单数据源。在单数据源的情况下,Spring Boot的配置非常简单,只需要在application.properties文件中配置连接参数即可。但是往往随着业务量发展,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源,下面基于之前的JdbcTemplate和Spring-data-jpa例子分别介绍两种多数据源的配置方式。

        -

        多数据源配置
        创建一个Spring配置类,定义两个DataSource用来读取application.properties中的不同配置。如下例子中,主数据源配置为spring.datasource.primary开头的配置,第二数据源配置为spring.datasource.secondary开头的配置。

        -

        @Configuration
        public class DataSourceConfig {

        -
        @Bean(name = "primaryDataSource")
        -@Qualifier("primaryDataSource")
        -@ConfigurationProperties(prefix="spring.datasource.primary")
        -public DataSource primaryDataSource() {
        -    return DataSourceBuilder.create().build();
        -}
        -
        -@Bean(name = "secondaryDataSource")
        -@Qualifier("secondaryDataSource")
        -@Primary
        -@ConfigurationProperties(prefix="spring.datasource.secondary")
        -public DataSource secondaryDataSource() {
        -    return DataSourceBuilder.create().build();
        -}
        -

        }
        对应的application.properties配置如下:

        -

        spring.datasource.primary.url=jdbc:mysql://localhost:3306/test1
        spring.datasource.primary.username=root
        spring.datasource.primary.password=root
        spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver

        -

        spring.datasource.secondary.url=jdbc:mysql://localhost:3306/test2
        spring.datasource.secondary.username=root
        spring.datasource.secondary.password=root
        spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
        JdbcTemplate支持
        对JdbcTemplate的支持比较简单,只需要为其注入对应的datasource即可,如下例子,在创建JdbcTemplate的时候分别注入名为primaryDataSource和secondaryDataSource的数据源来区分不同的JdbcTemplate。

        -
        @Bean(name = "primaryJdbcTemplate")
        -public JdbcTemplate primaryJdbcTemplate(
        -        @Qualifier("primaryDataSource") DataSource dataSource) {
        -    return new JdbcTemplate(dataSource);
        -}
        -
        -@Bean(name = "secondaryJdbcTemplate")
        -public JdbcTemplate secondaryJdbcTemplate(
        -        @Qualifier("secondaryDataSource") DataSource dataSource) {
        -    return new JdbcTemplate(dataSource);
        -}
        -

        接下来通过测试用例来演示如何使用这两个针对不同数据源的JdbcTemplate。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -@Qualifier("primaryJdbcTemplate")
        -protected JdbcTemplate jdbcTemplate1;
        -
        -@Autowired
        -@Qualifier("secondaryJdbcTemplate")
        -protected JdbcTemplate jdbcTemplate2;
        -
        -@Before
        -public void setUp() {
        -    jdbcTemplate1.update("DELETE  FROM  USER ");
        -    jdbcTemplate2.update("DELETE  FROM  USER ");
        -}
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 往第一个数据源中插入两条数据
        -    jdbcTemplate1.update("insert into user(id,name,age) values(?, ?, ?)", 1, "aaa", 20);
        -    jdbcTemplate1.update("insert into user(id,name,age) values(?, ?, ?)", 2, "bbb", 30);
        -
        -    // 往第二个数据源中插入一条数据,若插入的是第一个数据源,则会主键冲突报错
        -    jdbcTemplate2.update("insert into user(id,name,age) values(?, ?, ?)", 1, "aaa", 20);
        -
        -    // 查一下第一个数据源中是否有两条数据,验证插入是否成功
        -    Assert.assertEquals("2", jdbcTemplate1.queryForObject("select count(1) from user", String.class));
        -
        -    // 查一下第一个数据源中是否有两条数据,验证插入是否成功
        -    Assert.assertEquals("1", jdbcTemplate2.queryForObject("select count(1) from user", String.class));
        -
        -}
        -

        }
        完整示例:Chapter3-2-3

        -

        Spring-data-jpa支持
        对于数据源的配置可以沿用上例中DataSourceConfig的实现。

        -

        新增对第一数据源的JPA配置,注意两处注释的地方,用于指定数据源对应的Entity实体和Repository定义位置,用@Primary区分主数据源。

        -

        @Configuration
        @EnableTransactionManagement
        @EnableJpaRepositories(
        entityManagerFactoryRef=”entityManagerFactoryPrimary”,
        transactionManagerRef=”transactionManagerPrimary”,
        basePackages= { “com.didispace.domain.p” }) //设置Repository所在位置
        public class PrimaryConfig {

        -
        @Autowired @Qualifier("primaryDataSource")
        -private DataSource primaryDataSource;
        -
        -@Primary
        -@Bean(name = "entityManagerPrimary")
        -public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        -    return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
        -}
        -
        -@Primary
        -@Bean(name = "entityManagerFactoryPrimary")
        -public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
        -    return builder
        -            .dataSource(primaryDataSource)
        -            .properties(getVendorProperties(primaryDataSource))
        -            .packages("com.didispace.domain.p") //设置实体类所在位置
        -            .persistenceUnit("primaryPersistenceUnit")
        -            .build();
        -}
        -
        -@Autowired
        -private JpaProperties jpaProperties;
        -
        -private Map<String, String> getVendorProperties(DataSource dataSource) {
        -    return jpaProperties.getHibernateProperties(dataSource);
        -}
        -
        -@Primary
        -@Bean(name = "transactionManagerPrimary")
        -public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        -    return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
        -}
        -

        }
        新增对第二数据源的JPA配置,内容与第一数据源类似,具体如下:

        -

        @Configuration
        @EnableTransactionManagement
        @EnableJpaRepositories(
        entityManagerFactoryRef=”entityManagerFactorySecondary”,
        transactionManagerRef=”transactionManagerSecondary”,
        basePackages= { “com.didispace.domain.s” }) //设置Repository所在位置
        public class SecondaryConfig {

        -
        @Autowired @Qualifier("secondaryDataSource")
        -private DataSource secondaryDataSource;
        -
        -@Bean(name = "entityManagerSecondary")
        -public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        -    return entityManagerFactorySecondary(builder).getObject().createEntityManager();
        -}
        -
        -@Bean(name = "entityManagerFactorySecondary")
        -public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
        -    return builder
        -            .dataSource(secondaryDataSource)
        -            .properties(getVendorProperties(secondaryDataSource))
        -            .packages("com.didispace.domain.s") //设置实体类所在位置
        -            .persistenceUnit("secondaryPersistenceUnit")
        -            .build();
        -}
        -
        -@Autowired
        -private JpaProperties jpaProperties;
        -
        -private Map<String, String> getVendorProperties(DataSource dataSource) {
        -    return jpaProperties.getHibernateProperties(dataSource);
        -}
        -
        -@Bean(name = "transactionManagerSecondary")
        -PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
        -    return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
        -}
        -

        }
        完成了以上配置之后,主数据源的实体和数据访问对象位于:com.didispace.domain.p,次数据源的实体和数据访问接口位于:com.didispace.domain.s。

        -

        分别在这两个package下创建各自的实体和数据访问接口

        -

        主数据源下,创建User实体和对应的Repository接口
        @Entity
        public class User {

        -
        @Id
        -@GeneratedValue
        -private Long id;
        -
        -@Column(nullable = false)
        -private String name;
        -
        -@Column(nullable = false)
        -private Integer age;
        -
        -public User(){}
        -
        -public User(String name, Integer age) {
        -    this.name = name;
        -    this.age = age;
        -}
        -
        -// 省略getter、setter
        -

        }
        public interface UserRepository extends JpaRepository {

        -

        }
        从数据源下,创建Message实体和对应的Repository接口
        @Entity
        public class Message {

        -
        @Id
        -@GeneratedValue
        -private Long id;
        -
        -@Column(nullable = false)
        -private String name;
        -
        -@Column(nullable = false)
        -private String content;
        -
        -public Message(){}
        -
        -public Message(String name, String content) {
        -    this.name = name;
        -    this.content = content;
        -}
        -
        -// 省略getter、setter
        -

        }
        public interface MessageRepository extends JpaRepository {

        -

        }
        接下来通过测试用例来验证使用这两个针对不同数据源的配置进行数据操作。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private UserRepository userRepository;
        -@Autowired
        -private MessageRepository messageRepository;
        -
        -@Test
        -public void test() throws Exception {
        -
        -    userRepository.save(new User("aaa", 10));
        -    userRepository.save(new User("bbb", 20));
        -    userRepository.save(new User("ccc", 30));
        -    userRepository.save(new User("ddd", 40));
        -    userRepository.save(new User("eee", 50));
        -
        -    Assert.assertEquals(5, userRepository.findAll().size());
        -
        -    messageRepository.save(new Message("o1", "aaaaaaaaaa"));
        -    messageRepository.save(new Message("o2", "bbbbbbbbbb"));
        -    messageRepository.save(new Message("o3", "cccccccccc"));
        -
        -    Assert.assertEquals(3, messageRepository.findAll().size());
        -
        -}
        -

        }
        完整示例:Chapter3-2-4

        -

        版权申明:署名-非商业性使用-禁止演绎 3.0 (CC BY-NC-ND 3.0)

        - - -
        - -
        - - - - - -
        - - - - - - - - - -
        - -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/archives/2016/04/index.html b/archives/2016/04/index.html index e1db279..2454b21 100644 --- a/archives/2016/04/index.html +++ b/archives/2016/04/index.html @@ -3,198 +3,66 @@ - - Archives: 2016/4 | 菜菜的博客 + Archives: 2016/4 | Hexo - + - + - - + + - - + + - + + + + + +
        -
        -
        -
        - -
        - +
        + +
        +
        @@ -210,369 +78,16 @@

        菜菜

        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -582,403 +97,95 @@

        - - - - - - -

        - -
        - -
        - + + +
        - - - - -
        + + +
        + - - - - - + + + - - - - - - + diff --git a/archives/2016/05/index.html b/archives/2016/05/index.html deleted file mode 100644 index 05f555f..0000000 --- a/archives/2016/05/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Archives: 2016/5 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - - - - -
        -
        - 2016 -
        -
        - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/archives/2016/06/index.html b/archives/2016/06/index.html deleted file mode 100644 index f3e58b5..0000000 --- a/archives/2016/06/index.html +++ /dev/null @@ -1,1136 +0,0 @@ - - - - - - - Archives: 2016/6 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - - - - -
        -
        - 2016 -
        -
        - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/archives/2016/index.html b/archives/2016/index.html index ad62673..dcc92a9 100644 --- a/archives/2016/index.html +++ b/archives/2016/index.html @@ -3,198 +3,66 @@ - - Archives: 2016 | 菜菜的博客 + Archives: 2016 | Hexo - + - + - - + + - - + + - + + + + + +
        -
        -
        -
        - -
        - +
        + +
        +
        @@ -210,86 +78,16 @@

        菜菜

        - - - - - - - - @@ -299,1577 +97,95 @@

        - - -

        - -
        - -
        - + + + + +
        +
        - - -
        - +
        +
        - - - - -
        +
        +
        - + Archives + - -
        -
        -
        - - - -

        - SpringBoot中使用Swagger2构建强大的RESTful-API文档 -

        - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot中使用Redis数据库 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot日志管理 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - springBoot多数据源配置与使用 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot中使用Spring-data-jpa让数据访问更简单更优雅 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot中使用JdbcTemplate访问数据库 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot构建RESTfulAPI与单元测试 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot工程结构推荐 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot开发Web应用 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot中使用SpringSecurity进行安全控制 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Security进行安全控制 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot快速入门 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 各大平台免费接口 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 如何使用GoEasy实现web实时推送 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 汽车之家配置管理系统AutoCMS -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - dubbox -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Venus----唯品会分布式框架 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 新浪开源分布式框架--motan -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Tomcat的四种基于HTTP协议的Connector性能比较 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - solr-query -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - mysql跨数据库操作类似与oracle的dblink -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - hexo+github搭建个人博客 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - nginx内置变量大全 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - mysql主从同步 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Java操作MongoDB -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - memcache安装及配置 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - redis的图形化工具 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - maven常用命令 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 远程免密码copy文件 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - javascript----闭包 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - javascript---正则表达式 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - javascript---正则表达式 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - HTML5实战——svg学习 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - android项目源码 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - java服务端推送消息到web页面实例 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Markdown 语法说明 (简体中文版) -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Hexo搭建Github静态博客 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 我的第一个博客 -

        - - - -
        -
        -
        -
        - - - - - - - - - - - + - - - - - - - - - - - - - + diff --git a/archives/index.html b/archives/index.html index 6188ce5..1ff4bf1 100644 --- a/archives/index.html +++ b/archives/index.html @@ -3,198 +3,66 @@ - - Archives | 菜菜的博客 + Archives | Hexo - + - + - - + + - - + + - + + + + + +
        -
        -
        -
        - -
        - +
        + +
        +
        @@ -210,86 +78,16 @@

        菜菜

        - - - - - - - - @@ -299,1577 +97,95 @@

        - - -

        - -
        - -
        - + + + + +
        +
        - - -
        - +
        +
        - - - - -
        +
        +
        - + Archives + - -
        -
        -
        - - - -

        - SpringBoot中使用Swagger2构建强大的RESTful-API文档 -

        - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot中使用Redis数据库 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot日志管理 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - springBoot多数据源配置与使用 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot中使用Spring-data-jpa让数据访问更简单更优雅 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot中使用JdbcTemplate访问数据库 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot构建RESTfulAPI与单元测试 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot工程结构推荐 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot开发Web应用 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot中使用SpringSecurity进行安全控制 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Security进行安全控制 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - SpringBoot快速入门 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 各大平台免费接口 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 如何使用GoEasy实现web实时推送 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 汽车之家配置管理系统AutoCMS -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - dubbox -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Venus----唯品会分布式框架 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 新浪开源分布式框架--motan -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Tomcat的四种基于HTTP协议的Connector性能比较 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - solr-query -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - mysql跨数据库操作类似与oracle的dblink -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - hexo+github搭建个人博客 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - nginx内置变量大全 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - mysql主从同步 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Java操作MongoDB -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - memcache安装及配置 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - redis的图形化工具 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - maven常用命令 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 远程免密码copy文件 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - javascript----闭包 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - javascript---正则表达式 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - javascript---正则表达式 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - HTML5实战——svg学习 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - android项目源码 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - java服务端推送消息到web页面实例 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Markdown 语法说明 (简体中文版) -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - Hexo搭建Github静态博客 -

        - - - -
        -
        -
        -
        - - - -
        -
        -
        - - - -

        - 我的第一个博客 -

        - - - -
        -
        -
        -
        - - - - - - - - - - - + - - - - - - - - - - - - - + diff --git a/categories/index.html b/categories/index.html deleted file mode 100644 index dd452c9..0000000 --- a/categories/index.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - categories | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - - - - - - - - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/css/fonts/FontAwesome.otf b/css/fonts/FontAwesome.otf new file mode 100644 index 0000000..8b0f54e Binary files /dev/null and b/css/fonts/FontAwesome.otf differ diff --git a/css/fonts/fontawesome-webfont.eot b/css/fonts/fontawesome-webfont.eot index 3f669a7..7c79c6a 100644 Binary files a/css/fonts/fontawesome-webfont.eot and b/css/fonts/fontawesome-webfont.eot differ diff --git a/css/fonts/fontawesome-webfont.svg b/css/fonts/fontawesome-webfont.svg index 73c0ad9..45fdf33 100644 --- a/css/fonts/fontawesome-webfont.svg +++ b/css/fonts/fontawesome-webfont.svg @@ -1,175 +1,414 @@ - -This is a custom SVG webfont generated by Font Squirrel. -Designer : Dave Gandy -Foundry : Fort Awesome - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/css/fonts/fontawesome-webfont.svgz b/css/fonts/fontawesome-webfont.svgz deleted file mode 100644 index 2a73cd7..0000000 Binary files a/css/fonts/fontawesome-webfont.svgz and /dev/null differ diff --git a/css/fonts/fontawesome-webfont.ttf b/css/fonts/fontawesome-webfont.ttf index 4972eb4..e89738d 100644 Binary files a/css/fonts/fontawesome-webfont.ttf and b/css/fonts/fontawesome-webfont.ttf differ diff --git a/css/fonts/fontawesome-webfont.woff b/css/fonts/fontawesome-webfont.woff index 6e4cb41..8c1748a 100644 Binary files a/css/fonts/fontawesome-webfont.woff and b/css/fonts/fontawesome-webfont.woff differ diff --git a/css/images/banner.jpg b/css/images/banner.jpg new file mode 100644 index 0000000..b963e06 Binary files /dev/null and b/css/images/banner.jpg differ diff --git a/css/style.css b/css/style.css index 36b3935..57acc5d 100644 --- a/css/style.css +++ b/css/style.css @@ -9,13 +9,6 @@ body:after { body:after { clear: both; } -@font-face { - font-family: 'FontAwesome'; - src: url("fonts/fontawesome-webfont.eot"); - src: url("fonts/fontawesome-webfont.eot?#iefix") format("embedded-opentype"), url("fonts/fontawesome-webfont.woff") format("woff"), url("fonts/fontawesome-webfont.ttf") format("truetype"), url("fonts/fontawesome-webfont.svgz#FontAwesomeRegular") format("svg"), url("fonts/fontawesome-webfont.svg#FontAwesomeRegular") format("svg"); - font-weight: normal; - font-style: normal; -} html, body, div, @@ -107,13 +100,6 @@ td { a img { border: none; } -ol { - list-style: decimal; - padding-left: 20px; -} -ul { - list-style: none; -} input, button { margin: 0; @@ -137,1342 +123,998 @@ body, height: 100%; } body { - font-family: "微软雅黑", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - background: #fff; - color: rgba(0,0,0,0.6); - -webkit-overflow-scrolling: touch; -} -h1, -h2, -h3 { - display: block; -} -h1 { - font-size: 1.5em; + background: #eee; + font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif; + -webkit-text-size-adjust: 100%; } -h2 { - font-size: 1.3em; +.outer { + max-width: 1220px; + margin: 0 auto; + padding: 0 20px; } -h3 { - font-size: 1.1em; +.outer:before, +.outer:after { + content: ""; + display: table; } -h4, -h5, -h6 { - font-size: 1em; +.outer:after { + clear: both; } -a { - text-decoration: none; - outline-width: 0; - color: #258fb8; - outline: none; +.inner { + display: inline; + float: left; + width: 98.33333333333333%; + margin: 0 0.833333333333333%; } +.left, .alignleft { float: left; } +.right, .alignright { float: right; } -.clearfix { - *zoom: 1; -} -.clearfix:after { - content: ""; - display: table; +.clear { clear: both; } -.inner { - width: 1000px; - margin: 0 auto; -} -.hide { - display: none; -} -@media screen and (max-width: 1040px) { - .inner { - width: 100%; - } -} #container { position: relative; - min-height: 100%; -} -#container #mobile-nav { - display: none; -} -#container #mobile-nav .overlay { - height: 110px; - position: absolute; - width: 100%; - background: #4d4d4d; -} -#container #mobile-nav .overlay.fixed { - position: fixed; - height: 42px; - z-index: 99; -} -#container #mobile-nav #header { - padding: 10px 0 0 0; -} -#container #mobile-nav #header .profilepic { - display: block; - position: relative; - z-index: 100; -} -#container #mobile-nav #header .header-menu { - height: auto; - margin: 10px; -} -#container #mobile-nav #header .header-menu ul { - text-align: center; - cursor: default; } -#container #mobile-nav #header .header-menu li { - display: inline-block; - margin: 3px; +.mobile-nav-on { + overflow: hidden; } -#container .left-col { - background: #fff; - width: 300px; - position: fixed; - opacity: 1; - -webkit-transition: all 0.2s ease-in; - -moz-transition: all 0.2s ease-in; - -ms-transition: all 0.2s ease-in; - transition: all 0.2s ease-in; +#wrap { height: 100%; -} -#container .left-col .overlay { width: 100%; - height: 180px; - background-color: #000; position: absolute; - opacity: 0.7; -} -#container .left-col .intrude-less { - width: 76%; - text-align: center; - margin: 112px auto 0; + top: 0; + left: 0; + -webkit-transition: 0.2s ease-out; + -moz-transition: 0.2s ease-out; + -ms-transition: 0.2s ease-out; + transition: 0.2s ease-out; + z-index: 1; + background: #eee; } -#container .mid-col { - position: absolute; - right: 0; - min-height: 100%; - background: #eaeaea; - left: 300px; - width: auto; +.mobile-nav-on #wrap { + left: 280px; } -@media screen and (max-width: 800px) { - #container .left-col { - display: none; - } - #container .mid-col { - left: 0; - } - #container #header .header-nav { - position: relative; - } - #container .header-author.fixed { - position: fixed; - top: -13px; - width: 100%; - color: #ddd; - } - #container .overlay .slider-trigger { - position: absolute; - z-index: 101; - bottom: 0; - left: 0; - width: 42px; - height: 42px; - } - #container .overlay .slider-trigger:hover { - background: #444; - } - #container .overlay .slider-trigger:before { - color: #ddd; - content: "\f00b"; - font: 16px FontAwesome; - width: 16px; - height: 16px; - margin-left: 9px; - margin-top: 14px; - display: block; - } - #container .article-header { - border-left: none; - padding: 0; - border-bottom: 1px dotted #ddd; - } - #container .article-header h1 { - margin-bottom: 10px; - } - #container .header-subtitle { - padding: 0 24px; - } - #container .article-info-index.article-info { - padding-top: 10px; - margin: 0; - border-top: 1px solid #ddd; - } - #container .article-info-post.article-info { - margin: 0; - padding-top: 10px; - border: none; - } - #container .article-more-link a { - float: right; - } - #container #viewer-box .viewer-box-l { - font-size: 14px; - } - #container .article { - padding: 10px; - margin: 10px; - font-size: 14px; - } - #container .article .article-entry { - padding-left: 0; - padding-right: 0; - padding-top: 10px; - } - #container .article .article-title { - font-size: 18px; - max-width: 185px; - display: block; - margin: 0; - } - #container .article .article-meta { - width: auto; - height: 30px; - margin-top: -5px; - position: ralative; - } - #container .article .article-meta .article-date { - font-size: 12px; - -webkit-border-radius: 0; - border-radius: 0; - color: #666; - background: none; - height: auto; - padding: 0; - margin: 0; - width: 100%; - text-align: left; - margin-left: 10px; - } - #container .article .article-meta .article-date time { - width: auto; - float: right; - margin-right: 10px; - } - #container .article .article-meta .article-tag-list { - margin-top: 7px; - position: absolute; - right: 10px; - top: 0; - } - #container .article .article-meta .article-tag-list:before { - float: left; - margin-top: 1px; - left: 0; - } - #container .article .article-meta .article-tag-list .article-tag-list-item { - float: left; - padding-left: 0; - width: auto; - max-width: 83px; - } - #container .article .article-meta .article-category { - margin-top: 7px; - position: absolute; - right: 10px; - top: -30px; - } - #container .article .article-meta .article-category:before { +@media screen and (min-width: 768px) { + #main { + display: inline; float: left; - margin-top: 1px; - left: 15px; - } - #container .article .article-meta .article-category .article-category-link { - max-width: 83px; - width: auto; - padding-left: 10px; - } - #container .article .article-nav-link-wrap { - margin: 5px 0; - } - #container .article .article-nav-link-wrap strong { - float: left; - margin-right: 5px; - } - #container .article #article-nav-older { - float: none; - display: block; - } - #container .share { - padding: 3px 10px; - } - #container .duoshuo { - padding: 0 13px; - } - #container #disqus_thread { - padding: 0 13px; - } - #container #mobile-nav { - display: block; - } - #container #page-nav .extend { - opacity: 1; - } - #container .instagram .open-ins { - left: 2px; - top: -30px; - color: #aaa; - } - #container .info-on-right { - float: initial; - } - #container .archives-wrap { - margin: 10px 10px 0px; - padding: 10px; - } - #container .archives-wrap .archive-article-title { - font-size: 14px; - } - #container .archives-wrap .archive-year-wrap { - position: relative; - padding: 0 0 0 0; - } - #container .archives-wrap .archive-year-wrap a { - padding: 0 0 0 0; - } - #container .archives-wrap .article-meta .archive-article-date { - font-size: 12px; - margin-right: 10px; - margin-top: -5px; - } - #container .archives-wrap .article-meta .article-tag-list-link { - font-size: 12px; - } - #container .archives .archive-article { - padding: 10px 0; - margin-left: 0; - } - #container #footer .footer-left { - float: initial; - margin-bottom: 10px; - } - #container #footer .footer-right { - float: initial; + width: 73.33333333333333%; + margin: 0 0.833333333333333%; } } -.header-author { - text-align: center; - margin: 0.67em 0; - font-family: Roboto, "Roboto", serif; - font-size: 30px; - -webkit-transition: 0.3s; - -moz-transition: 0.3s; - -ms-transition: 0.3s; - transition: 0.3s; +.article-date, +.article-category-link, +.archive-year, +.widget-title { + text-decoration: none; + text-transform: uppercase; + letter-spacing: 2px; + color: #999; + margin-bottom: 1em; + margin-left: 5px; + line-height: 1em; + text-shadow: 0 1px #fff; + font-weight: bold; } -#header { - width: 100%; +.article-inner, +.archive-article-inner { + background: #fff; + -webkit-box-shadow: 1px 2px 3px #ddd; + box-shadow: 1px 2px 3px #ddd; + border: 1px solid #ddd; + -webkit-border-radius: 3px; + border-radius: 3px; } -#header a { - color: #696969; +.article-entry h1, +.widget h1 { + font-size: 2em; } -#header a:hover { - color: #b0a0aa; +.article-entry h2, +.widget h2 { + font-size: 1.5em; } -#header .profilepic { - text-align: center; - display: block; - border: 5px solid #fff; - -webkit-border-radius: 300px; - border-radius: 300px; - width: 128px; - height: 128px; - margin: 0 auto; - position: relative; - overflow: hidden; - background: #88acdb; - -webkit-transition: all 0.2s ease-in; - display: -webkit-box; - -webkit-box-orient: horizontal; - -webkit-box-pack: center; - -webkit-box-align: center; - text-align: center; +.article-entry h3, +.widget h3 { + font-size: 1.3em; } -#header .profilepic img { - width: 20%; - height: 20%; - -webkit-border-radius: 300px; - border-radius: 300px; - opacity: 0; - -webkit-transition: all 0.2s ease-in; +.article-entry h4, +.widget h4 { + font-size: 1.2em; } -#header .profilepic img.show { - width: 100%; - height: 100%; - opacity: 1; +.article-entry h5, +.widget h5 { + font-size: 1em; } -#header .header-subtitle { - text-align: center; +.article-entry h6, +.widget h6 { + font-size: 1em; color: #999; - font-size: 14px; - line-height: 25px; - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; } -#header .header-menu { - font-weight: 300; - line-height: 31px; - cursor: pointer; - text-transform: uppercase; - float: none; - min-height: 150px; - margin-left: -12px; - text-align: center; - display: -webkit-box; - -webkit-box-orient: horizontal; - -webkit-box-pack: center; - -webkit-box-align: center; +.article-entry hr, +.widget hr { + border: 1px dashed #ddd; } -#header .header-menu li { - cursor: default; +.article-entry strong, +.widget strong { + font-weight: bold; } -#header .header-menu li a { - font-size: 14px; - min-width: 300px; +.article-entry em, +.widget em, +.article-entry cite, +.widget cite { + font-style: italic; } -#header .switch-area { +.article-entry sup, +.widget sup, +.article-entry sub, +.widget sub { + font-size: 0.75em; + line-height: 0; position: relative; - width: 100%; - overflow: hidden; - min-height: 500px; - font-size: 14px; + vertical-align: baseline; } -#header .switch-area .switch-wrap { - -webkit-transition: -webkit-transform 0.3s ease-in; - -moz-transition: -moz-transform 0.3s ease-in; - -ms-transition: -ms-transform 0.3s ease-in; - transition: transform 0.3s ease-in; - position: relative; +.article-entry sup, +.widget sup { + top: -0.5em; } -#header .turn-left { - -webkit-transform: translate(-100%, 0); - -moz-transform: translate(-100%, 0); - -ms-transform: translate(-100%, 0); - transform: translate(-100%, 0); +.article-entry sub, +.widget sub { + bottom: -0.2em; } -#header .header-nav { - width: 100%; - position: absolute; - -webkit-transition: -webkit-transform 0.3s ease-in; - -moz-transition: -moz-transform 0.3s ease-in; - -ms-transition: -ms-transform 0.3s ease-in; - transition: transform 0.3s ease-in; -} -#header .header-nav .social { - margin-right: 15px; - margin-top: 10px; - text-align: center; +.article-entry small, +.widget small { + font-size: 0.85em; } -#header .header-nav .social a { - -webkit-border-radius: 50%; - border-radius: 50%; - display: -moz-inline-stack; - display: inline-block; - vertical-align: middle; - *vertical-align: auto; - zoom: 1; - *display: inline; - text-indent: -9999px; - margin: 0 8px 15px 8px; - opacity: 0.7; - width: 28px; - height: 28px; - -webkit-transition: 0.3s; - -moz-transition: 0.3s; - -ms-transition: 0.3s; - transition: 0.3s; -} -#header .header-nav .social a:hover { - opacity: 1; +.article-entry acronym, +.widget acronym, +.article-entry abbr, +.widget abbr { + border-bottom: 1px dotted; } -#header .header-nav .social a:last-of-type { - margin-right: 0; +.article-entry ul, +.widget ul, +.article-entry ol, +.widget ol, +.article-entry dl, +.widget dl { + margin: 0 20px; + line-height: 1.6em; +} +.article-entry ul ul, +.widget ul ul, +.article-entry ol ul, +.widget ol ul, +.article-entry ul ol, +.widget ul ol, +.article-entry ol ol, +.widget ol ol { + margin-top: 0; + margin-bottom: 0; } -#header .header-nav .social a.weibo { - background: url("/img/weibo.png") center no-repeat #aaf; - border: 1px solid #aaf; +.article-entry ul, +.widget ul { + list-style: disc; } -#header .header-nav .social a.weibo:hover { - border: 1px solid #aaf; +.article-entry ol, +.widget ol { + list-style: decimal; } -#header .header-nav .social a.rss { - background: url("/img/rss.png") center no-repeat #ef7522; - border: 1px solid #ef7522; +.article-entry dt, +.widget dt { + font-weight: bold; } -#header .header-nav .social a.rss:hover { - border: 1px solid #cf5d0f; +#header { + height: 300px; + position: relative; + border-bottom: 1px solid #ddd; } -#header .header-nav .social a.github { - background: url("/img/github.png") center no-repeat #afb6ca; - border: 1px solid #afb6ca; +#header:before, +#header:after { + content: ""; + position: absolute; + left: 0; + right: 0; + height: 40px; } -#header .header-nav .social a.github:hover { - border: 1px solid #909ab6; +#header:before { + top: 0; + background: -webkit-linear-gradient(rgba(0,0,0,0.2), transparent); + background: -moz-linear-gradient(rgba(0,0,0,0.2), transparent); + background: -ms-linear-gradient(rgba(0,0,0,0.2), transparent); + background: linear-gradient(rgba(0,0,0,0.2), transparent); } -#header .header-nav .social a.facebook { - background: url("/img/facebook.png") center no-repeat #3b5998; - border: 1px solid #3b5998; +#header:after { + bottom: 0; + background: -webkit-linear-gradient(transparent, rgba(0,0,0,0.2)); + background: -moz-linear-gradient(transparent, rgba(0,0,0,0.2)); + background: -ms-linear-gradient(transparent, rgba(0,0,0,0.2)); + background: linear-gradient(transparent, rgba(0,0,0,0.2)); } -#header .header-nav .social a.facebook:hover { - border: 1px solid #2d4373; +#header-outer { + height: 100%; + position: relative; } -#header .header-nav .social a.google { - background: url("/img/google.png") center no-repeat #c83d20; - border: 1px solid #c83d20; +#header-inner { + position: relative; + overflow: hidden; } -#header .header-nav .social a.google:hover { - border: 1px solid #9c3019; +#banner { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: url("images/banner.jpg") center #000; + -webkit-background-size: cover; + -moz-background-size: cover; + background-size: cover; + z-index: -1; } -#header .header-nav .social a.twitter { - background: url("/img/twitter.png") center no-repeat #55cff8; - border: 1px solid #55cff8; +#header-title { + text-align: center; + height: 40px; + position: absolute; + top: 50%; + left: 0; + margin-top: -20px; } -#header .header-nav .social a.twitter:hover { - border: 1px solid #24c1f6; +#logo, +#subtitle { + text-decoration: none; + color: #fff; + font-weight: 300; + text-shadow: 0 1px 4px rgba(0,0,0,0.3); } -#header .header-nav .social a.linkedin { - background: url("/img/linkedin.png") center no-repeat #005a87; - border: 1px solid #005a87; +#logo { + font-size: 40px; + line-height: 40px; + letter-spacing: 2px; } -#header .header-nav .social a.linkedin:hover { - border: 1px solid #006b98; +#subtitle { + font-size: 16px; + line-height: 16px; + letter-spacing: 1px; } -#header .header-nav .social a.zhihu { - background: url("/img/zhihu.png") center no-repeat #0078d8; - border: 1px solid #0078d8; +#subtitle-wrap { + margin-top: 16px; } -#header .header-nav .social a.zhihu:hover { - border: 1px solid #0078d8; +#main-nav { + float: left; + margin-left: -15px; } -#header .header-nav .social a.douban { - background: url("/img/douban.png") center no-repeat #06c611; - border: 1px solid #06c611; +.nav-icon, +.main-nav-link { + float: left; + color: #fff; + opacity: 0.6; + text-decoration: none; + text-shadow: 0 1px rgba(0,0,0,0.2); + -webkit-transition: opacity 0.2s; + -moz-transition: opacity 0.2s; + -ms-transition: opacity 0.2s; + transition: opacity 0.2s; + display: block; + padding: 20px 15px; } -#header .header-nav .social a.douban:hover { - border: 1px solid #06c611; +.nav-icon:hover, +.main-nav-link:hover { + opacity: 1; } -#header .header-nav .social a.mail { - background: url("/img/mail.png") center no-repeat #005a87; - border: 1px solid #005a87; +.nav-icon { + font-family: FontAwesome; + text-align: center; + font-size: 14px; + width: 14px; + height: 14px; + padding: 20px 15px; + position: relative; + cursor: pointer; } -#header .header-nav .social a.mail:hover { - border: 1px solid #006b98; +.main-nav-link { + font-weight: 300; + letter-spacing: 1px; } -#header .switch-part { - width: 100%; - position: absolute; +@media screen and (max-width: 479px) { + .main-nav-link { + display: none; + } } -#header .switch-part1 { - left: 0; +#main-nav-toggle { + display: none; } -#header .switch-part2 { - left: 100%; - top: 20px; - overlay-x: hidden; - overflow-y: auto; - max-height: 200px; +#main-nav-toggle:before { + content: "\f0c9"; } -#header .switch-part3 { - left: 200%; - line-height: 30px; +@media screen and (max-width: 479px) { + #main-nav-toggle { + display: block; + } } -#header .switch-part3 .switch-friends-link { - margin-right: 9px; - -webkit-border-radius: 3px; - border-radius: 3px; - padding: 5px; +#sub-nav { + float: right; + margin-right: -15px; } -#header .switch-part3 .switch-friends-link:hover { - background: #88acdb; - color: #fff; +#nav-rss-link:before { + content: "\f09e"; } -#header .switch-part4 { - left: 300%; - text-align: left; - line-height: 30px; +#nav-search-btn:before { + content: "\f002"; } -.duoshuo { - padding: 0 40px; +#search-form-wrap { + position: absolute; + top: 15px; + width: 150px; + height: 30px; + right: -150px; + opacity: 0; + -webkit-transition: 0.2s ease-out; + -moz-transition: 0.2s ease-out; + -ms-transition: 0.2s ease-out; + transition: 0.2s ease-out; } -#disqus_thread { - padding: 0 40px; +#search-form-wrap.on { + opacity: 1; + right: 0; } -.archives-wrap { - position: relative; - margin: 0 30px; - padding-right: 60px; - border-bottom: 1px solid #eee; - background: #fff; +@media screen and (max-width: 479px) { + #search-form-wrap { + width: 100%; + right: -100%; + } } -.archives-wrap:first-child { - margin-top: 30px; -} -.archives-wrap:last-child { - margin-bottom: 80px; -} -.archives-wrap .archive-year-wrap { - line-height: 35px; - width: 200px; +.search-form { position: absolute; - padding-top: 15px; - font-size: 1.8em; + top: 0; + left: 0; + right: 0; + background: #fff; + padding: 5px 15px; + -webkit-border-radius: 15px; + border-radius: 15px; + -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.3); + box-shadow: 0 0 10px rgba(0,0,0,0.3); } -.archives-wrap .archive-year-wrap a { - color: #666; - font-weight: bold; - padding-left: 48px; +.search-form-input { + border: none; + background: none; + color: #555; + width: 100%; + font: 13px "Helvetica Neue", Helvetica, Arial, sans-serif; + outline: none; } -.archives { - position: relative; +.search-form-input::-webkit-search-results-decoration, +.search-form-input::-webkit-search-cancel-button { + -webkit-appearance: none; } -.archives .article-info { +.search-form-submit { + position: absolute; + top: 50%; + right: 10px; + margin-top: -7px; + font: 13px FontAwesome; border: none; + background: none; + color: #bbb; + cursor: pointer; } -.archives .archive-article { - margin-left: 200px; - padding: 20px 0; - border-bottom: 1px solid #eee; - border-top: 1px solid #fff; - position: relative; -} -.archives .archive-article:first-child { - border-top: none; +.search-form-submit:hover, +.search-form-submit:focus { + color: #777; } -.archives .archive-article:last-child { - border-bottom: none; +.article { + margin: 50px 0; } -.archives .archive-article-title { - font-size: 16px; - color: #333; - -webkit-transition: color 0.3s; - -moz-transition: color 0.3s; - -ms-transition: color 0.3s; - transition: color 0.3s; +.article-inner { + overflow: hidden; } -.archives .archive-article-title:hover { - color: #657b83; +.article-meta:before, +.article-meta:after { + content: ""; + display: table; } -.archives .archive-article-title span { - display: block; - color: #a8a8a8; - font-size: 12px; - line-height: 14px; - height: 7px; - padding-left: 2px; +.article-meta:after { + clear: both; } -.archives .archive-article-title span:before { - display: inline-block; - content: "“"; - font-family: serif; - font-size: 30px; +.article-date { float: left; - margin: 4px 4px 0 -12px; - color: #c8c8c8; } -.archive-article-inner .archive-article-header { - position: relative; +.article-category { + float: left; + line-height: 1em; + color: #ccc; + text-shadow: 0 1px #fff; + margin-left: 8px; } -.archive-article-inner .article-meta { - position: relative; - float: right; - margin-top: -10px; - color: #555; - background: none; - text-align: right; - width: auto; +.article-category:before { + content: "\2022"; } -.archive-article-inner .article-meta .article-date time { - color: #aaa; +.article-category-link { + margin: 0 12px 1em; } -.archive-article-inner .article-meta .archive-article-date, -.archive-article-inner .article-meta .article-tag-list { - margin-right: 30px; - display: -moz-inline-stack; - display: inline-block; - vertical-align: middle; - zoom: 1; - color: #666; - font-size: 14px; +.article-header { + padding: 20px 20px 0; } -.archive-article-inner .article-meta .archive-article-date { - cursor: default; - font-size: 12px; - margin-bottom: 5px; - margin-top: -10px; +.article-title { + text-decoration: none; + font-size: 2em; + font-weight: bold; + color: #555; + line-height: 1.1em; + -webkit-transition: color 0.2s; + -moz-transition: color 0.2s; + -ms-transition: color 0.2s; + transition: color 0.2s; } -.archive-article-inner .article-meta .archive-article-date time:before { - content: "\f073"; - color: #999; - position: relative; - margin-right: 10px; - font: 16px FontAwesome; +a.article-title:hover { + color: #258fb8; } -.archive-article-inner .article-meta .article-category:before { - float: left; - margin-top: 1px; - left: 15px; +.article-entry { + color: #555; + padding: 0 20px; } -.archive-article-inner .article-meta .article-category .article-category-link { - width: auto; - max-width: 83px; - padding-left: 10px; +.article-entry:before, +.article-entry:after { + content: ""; + display: table; } -.archive-article-inner .article-meta .article-tag-list { - margin-top: 0px; +.article-entry:after { + clear: both; } -.archive-article-inner .article-meta .article-tag-list:before { - left: 15px; +.article-entry p, +.article-entry table { + line-height: 1.6em; + margin: 1.6em 0; } -.archive-article-inner .article-meta .article-tag-list .article-tag-list-item { - display: inline-block; - width: auto; - max-width: 83px; - padding-left: 8px; - font-size: 12px; +.article-entry h1, +.article-entry h2, +.article-entry h3, +.article-entry h4, +.article-entry h5, +.article-entry h6 { + font-weight: bold; } -.body-wrap { - margin-bottom: 80px; +.article-entry h1, +.article-entry h2, +.article-entry h3, +.article-entry h4, +.article-entry h5, +.article-entry h6 { + line-height: 1.1em; + margin: 1.1em 0; } -.article { - margin: 30px; - position: relative; - border: 1px solid #ddd; - background: #fff; - -webkit-transition: all 0.2s ease-in; +.article-entry a { + color: #258fb8; + text-decoration: none; } -.article.show { - visibility: visible; - -webkit-animation: cd-bounce-1 0.6s; - -moz-animation: cd-bounce-1 0.6s; - -webkit-animation: cd-bounce-1 0.6s; - -moz-animation: cd-bounce-1 0.6s; - -ms-animation: cd-bounce-1 0.6s; - animation: cd-bounce-1 0.6s; +.article-entry a:hover { + text-decoration: underline; } -.article.hidden { - visibility: hidden; +.article-entry ul, +.article-entry ol, +.article-entry dl { + margin-top: 1.6em; + margin-bottom: 1.6em; } -.article img { +.article-entry img, +.article-entry video { max-width: 100%; -} -@-webkit-keyframes cd-bounce-1 { - 0% { - opacity: 0; - -webkit-transform: scale(1); - } - 60% { - opacity: 1; - -webkit-transform: scale(1.01); - } - 100% { - -webkit-transform: scale(1); - } -} -@-moz-keyframes cd-bounce-1 { - 0% { - opacity: 0; - -moz-transform: scale(1); - } - 60% { - opacity: 1; - -moz-transform: scale(1.01); - } - 100% { - -moz-transform: scale(1); - } -} -.article-index { - margin-left: 200px; - padding: 15px 0; - margin-right: 75px; -} -.article-index .brief { + height: auto; display: block; - color: #a8a8a8; - font-size: 12px; - line-height: 14px; - height: 7px; - padding-left: 2px; -} -.article-index .brief:before { - display: inline-block; - content: "“"; - font-family: serif; - font-size: 30px; - float: left; - margin: 4px 4px 0 -12px; - color: #c8c8c8; -} -.article-title { - color: #696969; - margin-left: 0px; - font-weight: 300; - line-height: 35px; - margin-bottom: 20px; - font-size: 26px; - -webkit-transition: color 0.3s; - -moz-transition: color 0.3s; - -o-transition: color 0.3s; - -webkit-transition: color 0.3s; - -moz-transition: color 0.3s; - -ms-transition: color 0.3s; - transition: color 0.3s; -} -.article-title:hover { - color: #b0a0aa; -} -.article-inner { - position: relative; - margin-bottom: 20px; -} -.article-header { - border-left: 5px solid; - padding: 15px 0px 15px 25px; -} -.article-info.info-on-right { - margin: 10px 0 0 0; - float: right; -} -.article-info-index.article-info { - padding-top: 20px; - margin: 30px 30px 0 30px; - border-top: 1px solid #ddd; + margin: auto; } -.article-info-post.article-info { - padding: 0; +.article-entry iframe { border: none; - margin: -30px 0 20px 30px; } -.article-entry { - line-height: 1.8em; - padding-right: 30px; - padding-left: 30px; +.article-entry table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; } -.article-entry p { - margin-top: 10px; +.article-entry th { + font-weight: bold; + border-bottom: 3px solid #ddd; + padding-bottom: 0.5em; } -.article-entry p code, -.article-entry li code { - padding: 1px 3px; - margin: 0 3px; - background: #ddd; - border: 1px solid #ccc; - font-family: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace; - word-wrap: break-word; - font-size: 14px; +.article-entry td { + border-bottom: 1px solid #ddd; + padding: 10px 0; } .article-entry blockquote { - background: #ddd; - border-left: 5px solid #ccc; - padding: 15px 20px; - margin-top: 10px; - border-left: 5px solid #657b83; - background: #f6f6f6; -} -.article-entry blockquote p { - margin-top: 0; -} -.article-entry em { - font-style: italic; -} -.article-entry ul li:before { - content: ""; - width: 6px; - height: 6px; - border: 1px solid #999; - -webkit-border-radius: 10px; - border-radius: 10px; - background: #aaa; - display: inline-block; - margin-right: 10px; - float: left; - margin-top: 12px; + font-family: Georgia, "Times New Roman", serif; + font-size: 1.4em; + margin: 1.6em 20px; + text-align: center; } -.article-entry ul, -.article-entry ol { +.article-entry blockquote footer { font-size: 14px; - margin: 10px 0px; -} -.article-entry li ul, -.article-entry li ol { - margin-left: 30px; -} -.article-entry li ul li:before, -.article-entry li ol li:before { - content: ""; - background: #dedede; -} -.article-entry h1 { - margin-top: 30px; + margin: 1.6em 0; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } -.article-entry h2 { - margin-top: 20px; - font-weight: 300; - color: #574c4c; - padding-bottom: 5px; - border-bottom: 1px solid #ddd; +.article-entry blockquote footer cite:before { + content: "—"; + padding: 0 0.5em; } -.article-entry h3, -.article-entry h4, -.article-entry h5, -.article-entry h6 { - margin-top: 20px; - font-weight: 300; - color: #574c4c; - padding-bottom: 5px; - border-bottom: 1px solid #ddd; +.article-entry .pullquote { + text-align: left; + width: 45%; + margin: 0; } -.article-entry video { - max-width: 100%; +.article-entry .pullquote.left { + margin-left: 0.5em; + margin-right: 1em; } -.article-entry strong { - font-weight: bold; +.article-entry .pullquote.right { + margin-right: 0.5em; + margin-left: 1em; } .article-entry .caption { + color: #999; display: block; - font-size: 0.8em; - color: #aaa; + font-size: 0.9em; + margin-top: 0.5em; + position: relative; + text-align: center; } -.article-entry hr { +.article-entry .video-container { + position: relative; + padding-top: 56.25%; height: 0; - margin-top: 20px; - margin-bottom: 20px; - border-left: 0; - border-right: 0; - border-top: 1px solid #ddd; - border-bottom: 1px solid #fff; -} -.article-entry pre { - font-size: 1/0.9em; - line-height: 1.5; - margin-top: 10px; - padding: 5px 15px; - overflow-x: auto; - color: #657b83; - font-size: 10px; - border: 1px solid #ccc; - text-shadow: 0 1px #444; - font-family: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace; -} -.article-entry pre code { - font-size: 14px; + overflow: hidden; } -.article-entry table { +.article-entry .video-container iframe, +.article-entry .video-container object, +.article-entry .video-container embed { + position: absolute; + top: 0; + left: 0; width: 100%; - border: 1px solid #dedede; - margin: 15px 0; - border-collapse: collapse; -} -.article-entry table tr, -.article-entry table td { - height: 35px; + height: 100%; + margin-top: 0; } -.article-entry table thead tr { - background: #f8f8f8; +.article-more-link a { + display: inline-block; + line-height: 1em; + padding: 6px 15px; + -webkit-border-radius: 15px; + border-radius: 15px; + background: #eee; + color: #999; + text-shadow: 0 1px #fff; + text-decoration: none; } -.article-entry table tbody tr:hover { - background: #efefef; +.article-more-link a:hover { + background: #258fb8; + color: #fff; + text-decoration: none; + text-shadow: 0 1px #1e7293; } -.article-entry table td, -.article-entry table th { - border: 1px solid #dedede; - padding: 0 10px; +.article-footer { + font-size: 0.85em; + line-height: 1.6em; + border-top: 1px solid #ddd; + padding-top: 1.6em; + margin: 0 20px 20px; } -.article-entry figure table { - border: none; - width: auto; - margin: 0; +.article-footer:before, +.article-footer:after { + content: ""; + display: table; } -.article-entry figure table tbody tr:hover { - background: none; +.article-footer:after { + clear: both; } -.article-meta { - width: 150px; - font-size: 14; - text-align: right; - position: absolute; - right: 0; - top: 23px; - text-align: center; - z-index: 1; +.article-footer a { + color: #999; + text-decoration: none; } -.article-meta time { - color: #aaa; +.article-footer a:hover { + color: #555; } -.article-meta time:before { - color: #999; - content: "\f073"; - font: 17px FontAwesome; +.article-tag-list-item { + float: left; margin-right: 10px; } -.article-more-link { - margin-top: 5px; - text-align: right; +.article-tag-list-link:before { + content: "#"; } -.article-more-link a { - background: #4d4d4d; - color: #fff; - font-size: 12px; - padding: 2px 8px 4px; - line-height: 16px; - -webkit-border-radius: 2px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - border-radius: 2px; - -webkit-transition: background 0.3s; - -moz-transition: background 0.3s; - -ms-transition: background 0.3s; - transition: background 0.3s; +.article-comment-link { + float: right; } -.article-more-link a:hover { - background: #3c3c3c; +.article-comment-link:before { + content: "\f075"; + font-family: FontAwesome; + padding-right: 8px; } -.article-more-link a.hidden { - visibility: hidden; +.article-share-link { + cursor: pointer; + float: right; + margin-left: 20px; } -#article-nav { - margin: 80px 0 30px 0; - padding-bottom: 10px; +.article-share-link:before { + content: "\f064"; + font-family: FontAwesome; + padding-right: 6px; } -#article-nav .article-nav-link-wrap { - margin: 0px 30px 0px 30px; - font-size: 14px; - color: #333; +#article-nav { + position: relative; } -#article-nav .article-nav-link-wrap .article-nav-title { - display: inline-block; - font-size: 12px; - color: #aaa; - -webkit-transition: color 0.3s; - -moz-transition: color 0.3s; - -ms-transition: color 0.3s; - transition: color 0.3s; -} -#article-nav .article-nav-link-wrap strong { - background: #ddd; - color: #fff; - -webkit-border-radius: 100%; - border-radius: 100%; - width: 15px; - height: 15px; - display: inline-block; - text-align: center; - -webkit-transition: background 0.3s; - -moz-transition: background 0.3s; - -ms-transition: background 0.3s; - transition: background 0.3s; +#article-nav:before, +#article-nav:after { + content: ""; + display: table; } -#article-nav .article-nav-link-wrap:hover strong { - background: #4d4d4d; +#article-nav:after { + clear: both; } -#article-nav .article-nav-link-wrap:hover .article-nav-title { - color: #4d4d4d; +@media screen and (min-width: 768px) { + #article-nav { + margin: 50px 0; + } + #article-nav:before { + width: 8px; + height: 8px; + position: absolute; + top: 50%; + left: 50%; + margin-top: -4px; + margin-left: -4px; + content: ""; + -webkit-border-radius: 50%; + border-radius: 50%; + background: #ddd; + -webkit-box-shadow: 0 1px 2px #fff; + box-shadow: 0 1px 2px #fff; + } +} +.article-nav-link-wrap { + text-decoration: none; + text-shadow: 0 1px #fff; + color: #999; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-top: 50px; + text-align: center; + display: block; } -#article-nav #article-nav-older { - float: right; +.article-nav-link-wrap:hover { + color: #555; } -@-moz-keyframes cd-bounce-1 { - 0% { - opacity: 0; - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } - 60% { - opacity: 1; - -webkit-transform: scale(1.01); - -moz-transform: scale(1.01); - -ms-transform: scale(1.01); - -o-transform: scale(1.01); - -webkit-transform: scale(1.01); - -moz-transform: scale(1.01); - -ms-transform: scale(1.01); - transform: scale(1.01); - } - 100% { - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); +@media screen and (min-width: 768px) { + .article-nav-link-wrap { + width: 50%; + margin-top: 0; } } -@-webkit-keyframes cd-bounce-1 { - 0% { - opacity: 0; - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } - 60% { - opacity: 1; - -webkit-transform: scale(1.01); - -moz-transform: scale(1.01); - -ms-transform: scale(1.01); - -o-transform: scale(1.01); - -webkit-transform: scale(1.01); - -moz-transform: scale(1.01); - -ms-transform: scale(1.01); - transform: scale(1.01); - } - 100% { - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); +@media screen and (min-width: 768px) { + #article-nav-newer { + float: left; + text-align: right; + padding-right: 20px; } } -@-o-keyframes cd-bounce-1 { - 0% { - opacity: 0; - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } - 60% { - opacity: 1; - -webkit-transform: scale(1.01); - -moz-transform: scale(1.01); - -ms-transform: scale(1.01); - -o-transform: scale(1.01); - -webkit-transform: scale(1.01); - -moz-transform: scale(1.01); - -ms-transform: scale(1.01); - transform: scale(1.01); - } - 100% { - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); +@media screen and (min-width: 768px) { + #article-nav-older { + float: right; + text-align: left; + padding-left: 20px; } } -@keyframes cd-bounce-1 { - 0% { - opacity: 0; - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } - 60% { - opacity: 1; - -webkit-transform: scale(1.01); - -moz-transform: scale(1.01); - -ms-transform: scale(1.01); - -o-transform: scale(1.01); - -webkit-transform: scale(1.01); - -moz-transform: scale(1.01); - -ms-transform: scale(1.01); - transform: scale(1.01); - } - 100% { - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - -o-transform: scale(1); - -webkit-transform: scale(1); - -moz-transform: scale(1); - -ms-transform: scale(1); - transform: scale(1); - } +.article-nav-caption { + text-transform: uppercase; + letter-spacing: 2px; + color: #ddd; + line-height: 1em; + font-weight: bold; } -.archives-wrap { - position: relative; - margin: 0 30px; - padding-right: 60px; - border-bottom: 1px solid #eee; - background: #fff; +#article-nav-newer .article-nav-caption { + margin-right: -2px; } -.archives-wrap:first-child { - margin-top: 30px; -} -.archives-wrap:last-child { - margin-bottom: 80px; +.article-nav-title { + font-size: 0.85em; + line-height: 1.6em; + margin-top: 0.5em; } -.archives-wrap .archive-year-wrap { - line-height: 35px; - width: 200px; +.article-share-box { position: absolute; - padding-top: 15px; - font-size: 1.8em; + display: none; + background: #fff; + -webkit-box-shadow: 1px 2px 10px rgba(0,0,0,0.2); + box-shadow: 1px 2px 10px rgba(0,0,0,0.2); + -webkit-border-radius: 3px; + border-radius: 3px; + margin-left: -145px; + overflow: hidden; + z-index: 1; } -.archives-wrap .archive-year-wrap a { - color: #666; - font-weight: bold; - padding-left: 48px; +.article-share-box.on { + display: block; } -.archives { - position: relative; +.article-share-input { + width: 100%; + background: none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif; + padding: 0 15px; + color: #555; + outline: none; + border: 1px solid #ddd; + -webkit-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; + height: 36px; + line-height: 36px; } -.archives .article-info { - border: none; +.article-share-links { + background: #eee; +} +.article-share-links:before, +.article-share-links:after { + content: ""; + display: table; +} +.article-share-links:after { + clear: both; } -.archives .archive-article { - margin-left: 200px; - padding: 20px 0; - border-bottom: 1px solid #eee; - border-top: 1px solid #fff; +.article-share-twitter, +.article-share-facebook, +.article-share-pinterest, +.article-share-google { + width: 50px; + height: 36px; + display: block; + float: left; position: relative; + color: #999; + text-shadow: 0 1px #fff; +} +.article-share-twitter:before, +.article-share-facebook:before, +.article-share-pinterest:before, +.article-share-google:before { + font-size: 20px; + font-family: FontAwesome; + width: 20px; + height: 20px; + position: absolute; + top: 50%; + left: 50%; + margin-top: -10px; + margin-left: -10px; + text-align: center; } -.archives .archive-article:first-child { - border-top: none; +.article-share-twitter:hover, +.article-share-facebook:hover, +.article-share-pinterest:hover, +.article-share-google:hover { + color: #fff; } -.archives .archive-article:last-child { - border-bottom: none; +.article-share-twitter:before { + content: "\f099"; } -.archives .archive-article-title { - font-size: 16px; - color: #333; - -webkit-transition: color 0.3s; - -moz-transition: color 0.3s; - -ms-transition: color 0.3s; - transition: color 0.3s; +.article-share-twitter:hover { + background: #00aced; + text-shadow: 0 1px #008abe; } -.archives .archive-article-title:hover { - color: #657b83; +.article-share-facebook:before { + content: "\f09a"; } -.archives .archive-article-title span { - display: block; - color: #a8a8a8; - font-size: 12px; - line-height: 14px; - height: 7px; - padding-left: 2px; +.article-share-facebook:hover { + background: #3b5998; + text-shadow: 0 1px #2f477a; } -.archives .archive-article-title span:before { - display: inline-block; - content: "“"; - font-family: serif; - font-size: 30px; - float: left; - margin: 4px 4px 0 -12px; - color: #c8c8c8; +.article-share-pinterest:before { + content: "\f0d2"; +} +.article-share-pinterest:hover { + background: #cb2027; + text-shadow: 0 1px #a21a1f; } -.archive-article-inner .archive-article-header { +.article-share-google:before { + content: "\f0d5"; +} +.article-share-google:hover { + background: #dd4b39; + text-shadow: 0 1px #be3221; +} +.article-gallery { + background: #000; position: relative; } -.archive-article-inner .article-meta { +.article-gallery-photos { position: relative; - float: right; - margin-top: -10px; + overflow: hidden; +} +.article-gallery-img { + display: none; + max-width: 100%; +} +.article-gallery-img:first-child { + display: block; +} +.article-gallery-img.loaded { + position: absolute; + display: block; +} +.article-gallery-img img { + display: block; + max-width: 100%; + margin: 0 auto; +} +#comments { + background: #fff; + -webkit-box-shadow: 1px 2px 3px #ddd; + box-shadow: 1px 2px 3px #ddd; + padding: 20px; + border: 1px solid #ddd; + -webkit-border-radius: 3px; + border-radius: 3px; + margin: 50px 0; +} +#comments a { + color: #258fb8; +} +.archives-wrap { + margin: 50px 0; +} +.archives:before, +.archives:after { + content: ""; + display: table; +} +.archives:after { + clear: both; +} +.archive-year-wrap { + margin-bottom: 1em; +} +.archives { + -webkit-column-gap: 10px; + -moz-column-gap: 10px; + column-gap: 10px; +} +@media screen and (min-width: 480px) and (max-width: 767px) { + .archives { + -webkit-column-count: 2; + -moz-column-count: 2; + column-count: 2; + } +} +@media screen and (min-width: 768px) { + .archives { + -webkit-column-count: 3; + -moz-column-count: 3; + column-count: 3; + } +} +.archive-article { + -webkit-column-break-inside: avoid; + page-break-inside: avoid; + overflow: hidden; + break-inside: avoid-column; +} +.archive-article-inner { + padding: 10px; + margin-bottom: 15px; +} +.archive-article-title { + text-decoration: none; + font-weight: bold; color: #555; - background: none; - text-align: right; - width: auto; + -webkit-transition: color 0.2s; + -moz-transition: color 0.2s; + -ms-transition: color 0.2s; + transition: color 0.2s; + line-height: 1.6em; } -.archive-article-inner .article-meta .article-date time { - color: #aaa; +.archive-article-title:hover { + color: #258fb8; } -.archive-article-inner .article-meta .archive-article-date, -.archive-article-inner .article-meta .article-tag-list { - margin-right: 30px; - display: -moz-inline-stack; - display: inline-block; - vertical-align: middle; - zoom: 1; - color: #666; - font-size: 14px; +.archive-article-footer { + margin-top: 1em; } -.archive-article-inner .article-meta .archive-article-date { - cursor: default; - font-size: 12px; - margin-bottom: 5px; - margin-top: -10px; +.archive-article-date { + color: #999; + text-decoration: none; + font-size: 0.85em; + line-height: 1em; + margin-bottom: 0.5em; + display: block; } -.archive-article-inner .article-meta .archive-article-date time:before { - content: "\f073"; +#page-nav { + margin: 50px auto; + background: #fff; + -webkit-box-shadow: 1px 2px 3px #ddd; + box-shadow: 1px 2px 3px #ddd; + border: 1px solid #ddd; + -webkit-border-radius: 3px; + border-radius: 3px; + text-align: center; color: #999; - position: relative; - margin-right: 10px; - font: 16px FontAwesome; + overflow: hidden; } -.archive-article-inner .article-meta .article-category:before { - float: left; - margin-top: 1px; - left: 15px; +#page-nav:before, +#page-nav:after { + content: ""; + display: table; } -.archive-article-inner .article-meta .article-category .article-category-link { - width: auto; - max-width: 83px; - padding-left: 10px; +#page-nav:after { + clear: both; +} +#page-nav a, +#page-nav span { + padding: 10px 20px; + line-height: 1; + height: 2ex; +} +#page-nav a { + color: #999; + text-decoration: none; +} +#page-nav a:hover { + background: #999; + color: #fff; } -.archive-article-inner .article-meta .article-tag-list { - margin-top: 0px; +#page-nav .prev { + float: left; } -.archive-article-inner .article-meta .article-tag-list:before { - left: 15px; +#page-nav .next { + float: right; } -.archive-article-inner .article-meta .article-tag-list .article-tag-list-item { +#page-nav .page-number { display: inline-block; - width: auto; - max-width: 83px; - padding-left: 8px; - font-size: 12px; +} +@media screen and (max-width: 479px) { + #page-nav .page-number { + display: none; + } +} +#page-nav .current { + color: #555; + font-weight: bold; +} +#page-nav .space { + color: #ddd; +} +#footer { + background: #262a30; + padding: 50px 0; + border-top: 1px solid #ddd; + color: #999; +} +#footer a { + color: #258fb8; + text-decoration: none; +} +#footer a:hover { + text-decoration: underline; +} +#footer-info { + line-height: 1.6em; + font-size: 0.85em; } .article-entry pre, .article-entry .highlight { - background: #272822; - margin: 10px 0; - padding: 10px 10px; + background: #2d2d2d; + margin: 0 -20px; + padding: 15px 20px; + border-style: solid; + border-color: #ddd; + border-width: 1px 0; overflow: auto; - color: #4c4c4c; + color: #ccc; line-height: 22.400000000000002px; } .article-entry .highlight .gutter pre, .article-entry .gist .gist-file .gist-data .line-numbers { color: #666; + font-size: 0.85em; } .article-entry pre, .article-entry code { @@ -1480,21 +1122,13 @@ a { } .article-entry code { background: #eee; + text-shadow: 0 1px #fff; padding: 0 0.3em; - border: none; -} -.article-entry pre { - color: #fff; } .article-entry pre code { background: none; text-shadow: none; padding: 0; - color: #fff; -} -.article-entry .highlight { - -webkit-border-radius: 4px; - border-radius: 4px; } .article-entry .highlight pre { border: none; @@ -1511,7 +1145,7 @@ a { } .article-entry .highlight figcaption { font-size: 0.85em; - color: highlight-comment; + color: #999; line-height: 1em; margin-bottom: 1em; } @@ -1530,19 +1164,18 @@ a { text-align: right; padding-right: 20px; } -.article-entry .highlight .gutter pre .line { - text-shadow: none; -} .article-entry .highlight .line { - font-size: 15px; - height: 15px; + height: 22.400000000000002px; +} +.article-entry .highlight .line.marked { + background: #515151; } .article-entry .gist { margin: 0 -20px; border-style: solid; border-color: #ddd; border-width: 1px 0; - background: #272822; + background: #2d2d2d; padding: 15px 20px 15px 0; } .article-entry .gist .gist-file { @@ -1568,8 +1201,8 @@ a { border: none; } .article-entry .gist .gist-file .gist-meta { - background: #272822; - color: highlight-comment; + background: #2d2d2d; + color: #999; font: 0.85em "Helvetica Neue", Helvetica, Arial, sans-serif; text-shadow: 0 0; padding: 0; @@ -1583,712 +1216,168 @@ a { .article-entry .gist .gist-file .gist-meta a:hover { text-decoration: underline; } -pre .comment { - color: #75715e; -} -pre .keyword, -pre .function .keyword, -pre .class .params { - color: #66d9ef; +pre .comment, +pre .title { + color: #999; } +pre .variable, +pre .attribute, pre .tag, -pre .doctype, -pre .params, -pre .function, -pre .css .value { - color: #fff; -} -pre .css ~ * .tag, -pre .title, -pre .at_rule, -pre .at_rule .keyword, +pre .regexp, +pre .ruby .constant, +pre .xml .tag .title, +pre .xml .pi, +pre .xml .doctype, +pre .html .doctype, +pre .css .id, +pre .css .class, +pre .css .pseudo { + color: #f2777a; +} +pre .number, pre .preprocessor, -pre .preprocessor .keyword { - color: #f92672; -} -pre .attribute, pre .built_in, +pre .literal, +pre .params, +pre .constant { + color: #f99157; +} pre .class, -pre .css ~ * .class, -pre .function .title { - color: #a6e22e; +pre .ruby .class .title, +pre .css .rules .attribute { + color: #9c9; } +pre .string, pre .value, -pre .string { - color: #e6db74; -} -pre .number { - color: #7163d7; -} -pre .id, -pre .css ~ * .id { - color: #fd971f; -} -#footer { - font-size: 12px; - font-family: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace; - text-shadow: 0 1px #fff; - position: absolute; - bottom: 30px; - opacity: 0.6; - width: 100%; - text-align: center; -} -#footer .outer { - padding: 0 30px; -} -.footer-left { - float: left; -} -.footer-right { - float: right; -} -.share { - padding: 0px 75px 30px 40px; - margin-bottom: 15px; -} -.back_home { - float: right; -} -.back_home a { - font-weight: bold; - margin-left: 10px; -} -.back_home a:hover { - color: #6e6e6e; -} -#page-nav { - text-align: center; - margin-top: 30px; -} -#page-nav .page-number { - width: 20px; - height: 20px; - background: #4d4d4d; - -webkit-border-radius: 50%; - -moz-border-radius: 50%; - -webkit-border-radius: 50%; - border-radius: 50%; - display: inline-block; - color: #fff; - line-height: 20px; - font-size: 12px; - margin: 0 3px 30px; -} -#page-nav .page-number:hover { - background: #5e5e5e; -} -#page-nav .current { - background: #88acdb; - cursor: default; -} -#page-nav .current:hover { - background: #88acdb; -} -#page-nav .extend { - color: #4d4d4d; - margin: 0 27px; - opacity: 0; -} -#page-nav .extend:hover { - color: #5e5e5e; -} -#page-nav:hover .extend { - opacity: 1; +pre .inheritance, +pre .header, +pre .ruby .symbol, +pre .xml .cdata { + color: #9c9; } -#post-instagram { - padding: 30px; +pre .css .hexcolor { + color: #6cc; } -#post-instagram .article-entry { - padding-right: 0; -} -.instagram { - position: relative; - min-height: 500px; -} -.instagram .open-ins { - display: block; - padding: 10px 0; - position: absolute; - right: 28px; - top: -75px; - color: #333; -} -.instagram .open-ins:hover { - color: #657b83; -} -.instagram .year { - display: inline; -} -.instagram .album h1 em { - font-style: normal; - font-size: 14px; - margin-left: 10px; -} -.instagram .album ul { - min-height: 149px; - padding-top: 17px; - border-bottom: 1px solid #ddd; - list-style: none; -} -.instagram .album li { - position: relative; - display: inline-block; - min-width: 157px; - margin: 0; -} -.instagram .album li:before { - display: none; -} -.instagram .album div.img-box { - position: relative; - margin: 0 20px 10px; - -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.4), 0 1px 0 1px rgba(255,255,255,0.1); - -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.4), 0 1px 0 1px rgba(255,255,255,0.1); - -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.4), 0 1px 0 1px rgba(255,255,255,0.1); - box-shadow: 0 1px 0 rgba(255,255,255,0.4), 0 1px 0 1px rgba(255,255,255,0.1); -} -.instagram .album div.img-box .img-bg { - position: absolute; - top: 0; - left: 0; - bottom: 0px; - width: 100%; - margin: -5px; - padding: 5px; - background: rgba(204,204,204,0.8); - -webkit-box-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 5px rgba(0,0,0,0.1); - -moz-box-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 5px rgba(0,0,0,0.1); - -webkit-box-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 5px rgba(0,0,0,0.1); - box-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 5px rgba(0,0,0,0.1); - -webkit-transition: all 0.15s ease-out 0.1s; - -moz-transition: all 0.15s ease-out 0.1s; - -o-transition: all 0.15s ease-out 0.1s; - -webkit-transition: all 0.15s ease-out 0.1s; - -moz-transition: all 0.15s ease-out 0.1s; - -ms-transition: all 0.15s ease-out 0.1s; - transition: all 0.15s ease-out 0.1s; - opacity: 0.2; - cursor: pointer; - display: block; -} -.instagram .album div.img-box .img-bg:hover { - opacity: 0; +pre .function, +pre .python .decorator, +pre .python .title, +pre .ruby .function .title, +pre .ruby .title .keyword, +pre .perl .sub, +pre .javascript .title, +pre .coffeescript .title { + color: #69c; } -.instagram .album div.img-box img { - width: 100%; - height: auto; - display: block; +pre .keyword, +pre .javascript .function { + color: #c9c; } -@media screen and (max-width: 600px) { - .instagram .album ul { - margin-left: 0; - padding: 0; - text-align: center; +@media screen and (max-width: 479px) { + #mobile-nav { + position: absolute; + top: 0; + left: 0; + width: 280px; + height: 100%; + background: #191919; + border-right: 1px solid #fff; } - .instagram .album li { - max-width: 300px; +} +@media screen and (max-width: 479px) { + .mobile-nav-link { + display: block; + color: #999; + text-decoration: none; + padding: 15px 20px; + font-weight: bold; } - .instagram .album div.img-box { - margin: 0; + .mobile-nav-link:hover { + color: #fff; } } -.switch-btn { - margin-left: 74px; - margin-top: 23px; - position: relative; - width: 70px; - text-align: center; +@media screen and (min-width: 768px) { + #sidebar { + display: inline; + float: left; + width: 23.333333333333332%; + margin: 0 0.833333333333333%; + } } -.switch-btn .tips-box { - position: relative; +.widget-wrap { + margin: 50px 0; } -.switch-btn .tips-box .tips-arrow { - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-bottom: 6px solid #000; - opacity: 0.8; - position: absolute; - top: -6px; - left: 30px; -} -.switch-btn .tips-box .tips-inner { - padding: 5px 8px 4px 8px; - background-color: #000; - color: #fff; - width: 80px; - text-align: center; +.widget { + color: #777; + text-shadow: 0 1px #fff; + background: #ddd; + -webkit-box-shadow: 0 -1px 4px #ccc inset; + box-shadow: 0 -1px 4px #ccc inset; + border: 1px solid #ccc; + padding: 15px; -webkit-border-radius: 3px; border-radius: 3px; - opacity: 0.8; - position: absolute; - z-index: 99; - left: -13px; -} -.switch-btn .tips-box .tips-inner li { - padding: 4px 0; - cursor: pointer; - font-size: 13px; - color: #ddd; -} -.switch-btn .tips-box .tips-inner li:hover { - background: #111; -} -.switch-btn .icon { - height: 32px; - width: 32px; - background: #cecece; - -webkit-border-radius: 100px; - border-radius: 100px; - position: relative; - overflow: hidden; - display: inline-block; - padding: 10px; - -webkit-transform: scale(0.6); - -moz-transform: scale(0.6); - -ms-transform: scale(0.6); - transform: scale(0.6); - cursor: pointer; -} -.switch-btn .icon-ctn { - width: 34px; - height: 34px; - position: relative; - overflow: hidden; -} -.switch-btn .icon-ctn .birdhouse { - width: 0; - height: 0; - position: absolute; - top: 0; - left: 0; - border-style: solid; - border-color: transparent transparent #4d4d4d transparent; - border-width: 0 16px 14px; -} -.switch-btn .icon-ctn .birdhouse:after { - content: ''; - position: absolute; - top: 12px; - left: -12px; - border-style: solid; - border-color: #4d4d4d transparent transparent transparent; - border-width: 100px 12px; -} -.switch-btn .icon-ctn .birdhouse_holes { - width: 12px; - height: 12px; - background: #ddd; - position: absolute; - -webkit-border-radius: 60px; - -moz-border-radius: 60px; - -webkit-border-radius: 60px; - border-radius: 60px; - left: 10px; - top: 8px; -} -.switch-btn .icon-ctn .birdhouse_holes:after { - content: ''; - position: absolute; - width: 4px; - height: 4px; - background: #ccc; - bottom: -8px; - left: 4px; - -webkit-border-radius: 60px; - -moz-border-radius: 60px; - -webkit-border-radius: 60px; - border-radius: 60px; -} -.switch-btn .icon-ctn .ribbon { - width: 12px; - height: 20px; - background: #333; - margin: 2px 0 0 10px; } -.switch-btn .icon-ctn .ribbon:after { - content: ''; - position: absolute; - left: 10px; - top: 16px; - width: 0; - height: 0; - border: solid #333; - border-color: transparent #333 transparent #333; - border-width: 6px 6px 8px 6px; -} -.switch-btn .icon-ctn .loopback_l { - width: 10px; - height: 5px; - border-style: solid; - border-width: 0px 3px 3px 3px; - border-color: #333; - -webkit-border-radius: 0 0 50px 50px; - border-radius: 0 0 50px 50px; - position: absolute; - top: 16px; - left: 1px; -} -.switch-btn .icon-ctn .loopback_l:before { - content: ''; - position: absolute; - width: 0; - height: 0; - border-style: solid; - border-width: 4px; - border-color: transparent transparent transparent #333; - bottom: 7px; - left: 5px; -} -.switch-btn .icon-ctn .loopback_l:after { - content: ''; - width: 5px; - height: 5px; - border-style: solid; - border-width: 3px 0 0 3px; - border-color: #333; - -webkit-border-radius: 50px 0 0 0; - border-radius: 50px 0 0 0; - position: absolute; - left: -3px; - bottom: 5px; -} -.switch-btn .icon-ctn .loopback_r { - width: 10px; - height: 5px; - border-style: solid; - border-width: 3px 3px 0 3px; - border-color: #333; - -webkit-border-radius: 50px 50px 0 0; - border-radius: 50px 50px 0 0; - position: absolute; - top: 8px; - right: 2px; -} -.switch-btn .icon-ctn .loopback_r:before { - content: ''; - position: absolute; - width: 0; - height: 0; - border-style: solid; - border-width: 4px; - border-color: transparent #333 transparent transparent; - top: 7px; - right: 5px; -} -.switch-btn .icon-ctn .loopback_r:after { - content: ''; - width: 5px; - height: 5px; - border-style: solid; - border-width: 0 3px 3px 0; - border-color: #333; - -webkit-border-radius: 0 0 50px 0; - border-radius: 0 0 50px 0; - position: absolute; - left: 5px; - top: 5px; -} -.switch-btn .icon-ctn .user { - width: 12px; - height: 14px; - background: #333; - -webkit-border-radius: 5px 5px 0 0; - border-radius: 5px 5px 0 0; - margin-left: 10px; - margin-top: 2px; -} -.switch-btn .icon-ctn .user:before { - content: ''; - width: 0px; - height: 0px; - position: absolute; - top: 17px; - left: 0px; - border-style: solid; - border-color: transparent transparent #333 transparent; - border-width: 0 16px 6px 16px; -} -.switch-btn .icon-ctn .user:after { - content: ''; - width: 8px; - height: 5px; - background: #333; - position: absolute; - margin-top: 13px; - margin-left: -4px; -} -.switch-btn .icon-ctn .shoulder { - width: 32px; - height: 6px; - background: #333; - position: absolute; - bottom: 6px; -} -#header .tagcloud a { - color: #fff; -} -.tagcloud a { - display: inline-block; +.widget a { + color: #258fb8; text-decoration: none; - font-weight: normal; - font-size: 10px; - color: #fff; - height: 18px; - line-height: 18px; - float: left; - padding: 0 5px 0px 10px; - position: relative; - -webkit-border-radius: 0 5px 5px 0; - border-radius: 0 5px 5px 0; - margin: 5px 9px 5px 8px; - font-family: Menlo, Monaco, "Andale Mono", "lucida console", "Courier New", monospace; -} -.tagcloud a:hover { - opacity: 0.8; -} -.tagcloud a:before { - content: " "; - width: 0px; - height: 0px; - position: absolute; - top: 0; - left: -18px; - border: 9px solid transparent; -} -.tagcloud a:after { - content: " "; - width: 4px; - height: 4px; - background-color: #fff; - -webkit-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.3); - box-shadow: 0px 0px 0px 1px rgba(0,0,0,0.3); - position: absolute; - top: 7px; - left: 2px; -} -.tagcloud a.color1 { - background: #ff945c; } -.tagcloud a.color1:before { - border-right-color: #ff945c; -} -.tagcloud a.color2 { - background: #f5c7b7; -} -.tagcloud a.color2:before { - border-right-color: #f5c7b7; -} -.tagcloud a.color3 { - background: #ba8f6c; -} -.tagcloud a.color3:before { - border-right-color: #ba8f6c; -} -.tagcloud a.color4 { - background: #cfb7c4; -} -.tagcloud a.color4:before { - border-right-color: #cfb7c4; -} -.tagcloud a.color5 { - background: #7b5d5f; -} -.tagcloud a.color5:before { - border-right-color: #7b5d5f; -} -.article-tag-list .article-tag-list-item { - float: left; -} -.article-tag { - float: left; -} -.article-tag .article-tag-list { - float: left; +.widget a:hover { + text-decoration: underline; } -.article-tag:before { - float: left; - color: #999; - content: "\f06c"; - font: 16px FontAwesome; - float: left; - margin-right: 10px; - margin-top: 9px; +.widget ul ul, +.widget ol ul, +.widget dl ul, +.widget ul ol, +.widget ol ol, +.widget dl ol, +.widget ul dl, +.widget ol dl, +.widget dl dl { + margin-left: 15px; + list-style: disc; +} +.widget { + line-height: 1.6em; + word-wrap: break-word; + font-size: 0.9em; } -.article-tag:after { - float: left; - content: ""; - margin-right: 20px; - margin-top: 9px; - float: left; +.widget ul, +.widget ol { + list-style: none; + margin: 0; } -.article-category { - float: left; +.widget ul ul, +.widget ol ul, +.widget ul ol, +.widget ol ol { + margin: 0 20px; } -.article-category:before { - float: left; - color: #999; - content: "\f02d"; - font: 16px FontAwesome; - float: left; - margin-right: 10px; - margin-top: 9px; +.widget ul ul, +.widget ol ul { + list-style: disc; } -.article-category:after { - float: left; - content: ""; - margin-right: 20px; - margin-top: 9px; - float: left; -} -.article-pop-out { - float: left; +.widget ul ol, +.widget ol ol { + list-style: decimal; } -.article-pop-out:before { - float: left; +.category-list-count, +.tag-list-count, +.archive-list-count { + padding-left: 5px; color: #999; - content: "\f08d"; - font: 16px FontAwesome; - float: left; - margin-right: 10px; - margin-top: 9px; -} -.article-pop-out:after { - float: left; - content: ""; - margin-right: 20px; - margin-top: 9px; - float: left; -} -::-webkit-scrollbar { - width: 10px; - height: 10px; -} -::-webkit-scrollbar-button { - width: 0; - height: 0; -} -::-webkit-scrollbar-button:start:increment, -::-webkit-scrollbar-button:end:decrement { - display: none; -} -::-webkit-scrollbar-corner { - display: block; -} -::-webkit-scrollbar-thumb { - -webkit-border-radius: 8px; - border-radius: 8px; - background-color: rgba(0,0,0,0.2); -} -::-webkit-scrollbar-thumb:hover { - -webkit-border-radius: 8px; - border-radius: 8px; - background-color: rgba(0,0,0,0.5); -} -::-webkit-scrollbar-track, -::-webkit-scrollbar-thumb { - border-right: 1px solid transparent; - border-left: 1px solid transparent; -} -::-webkit-scrollbar-track:hover { - background-color: rgba(0,0,0,0.15); -} -::-webkit-scrollbar-button:start { - width: 10px; - height: 10px; - background: url("../img/scrollbar_arrow.png") no-repeat 0 0; -} -::-webkit-scrollbar-button:start:hover { - background: url("../img/scrollbar_arrow.png") no-repeat -15px 0; -} -::-webkit-scrollbar-button:start:active { - background: url("../img/scrollbar_arrow.png") no-repeat -30px 0; -} -::-webkit-scrollbar-button:end { - width: 10px; - height: 10px; - background: url("../img/scrollbar_arrow.png") no-repeat 0 -18px; -} -::-webkit-scrollbar-button:end:hover { - background: url("../img/scrollbar_arrow.png") no-repeat -15px -18px; -} -::-webkit-scrollbar-button:end:active { - background: url("../img/scrollbar_arrow.png") no-repeat -30px -18px; -} -#viewer { - position: fixed; - z-index: 1000000; - top: 0; - bottom: 0; - left: 0; - right: 0; - overflow: hidden; -} -#viewer-box { - width: 100%; - height: 100%; - position: relative; - color: #ccc; - -webkit-transform: translate3d(-100%, 0, 0); - -webkit-transition: -webkit-transform 0.25s ease-in-out; -} -#viewer-box .viewer-box-l { - background: rgba(20,20,20,0.9); - width: 80%; - height: 100%; - float: left; + font-size: 0.85em; } -#viewer-box .viewer-box-l .viewer-box-wrap { - margin: 20px 10px 0px 20px; +.category-list-count:before, +.tag-list-count:before, +.archive-list-count:before { + content: "("; } -#viewer-box .viewer-box-l .viewer-title { - line-height: 32px; +.category-list-count:after, +.tag-list-count:after, +.archive-list-count:after { + content: ")"; } -#viewer-box .viewer-box-l .viewer-title:before { - content: ""; - width: 6px; - height: 6px; - border: 1px solid #999; - -webkit-border-radius: 10px; - -webkit-border-radius: 10px; - border-radius: 10px; - background: #aaa; +.tagcloud a { + margin-right: 5px; display: inline-block; - margin-right: 10px; -} -#viewer-box .viewer-box-l .viewer-div { - border-bottom: 1px dotted #666; - padding-bottom: 13px; - line-height: 20px; -} -#viewer-box .viewer-box-l .viewer-div:last-child { - border-bottom: none; -} -#viewer-box .viewer-box-l .viewer-div .switch-friends-link { - line-height: 20px; -} -#viewer-box .viewer-box-r { - background: rgba(0,0,0,0); - width: 20%; - height: 100%; - float: right; -} -#viewer-box.anm-swipe { - -webkit-transform: translate3d(0, 0, 0); -} -#viewer-box.anm-swipe .viewer-box-r { - background: rgba(0,0,0,0); -} -.hide { - display: none; -} -#viewer-box .viewer-list { - margin: 0; - padding: 0; - height: 100%; - overflow: hidden; } diff --git a/fancybox/jquery.fancybox.css b/fancybox/jquery.fancybox.css index a767be2..c75d051 100644 --- a/fancybox/jquery.fancybox.css +++ b/fancybox/jquery.fancybox.css @@ -233,7 +233,7 @@ color: #FFF; font-weight: bold; line-height: 24px; - text-align: left; + white-space: nowrap; } .fancybox-title-outside-wrap { diff --git a/fancybox/jquery.fancybox.js b/fancybox/jquery.fancybox.js index 99f2b34..7a0f8ac 100644 --- a/fancybox/jquery.fancybox.js +++ b/fancybox/jquery.fancybox.js @@ -143,8 +143,7 @@ error : '

        The requested content cannot be loaded.
        Please try again later.

        ', closeBtn : '', next : '', - prev : '', - loading : '
        ' + prev : '' }, // Properties for each animation type @@ -262,7 +261,7 @@ if (isQuery(element)) { obj = { href : element.data('fancybox-href') || element.attr('href'), - title : $('
        ').text( element.data('fancybox-title') || element.attr('title') || '' ).html(), + title : $('
        ').text( element.data('fancybox-title') || element.attr('title') ).html(), isDom : true, element : element }; @@ -615,7 +614,7 @@ F.hideLoading(); - el = $(F.opts.tpl.loading).click(F.cancel).appendTo('body'); + el = $('
        ').click(F.cancel).appendTo('body'); // If user will press the escape-button, the request will be canceled D.bind('keydown.loading', function(e) { @@ -1719,7 +1718,7 @@ parent = F.coming ? F.coming.parent : opts.parent; - this.overlay = $('
        ').appendTo( parent && parent.length ? parent : 'body' ); + this.overlay = $('
        ').appendTo( parent && parent.lenth ? parent : 'body' ); this.fixed = false; if (opts.fixed && F.defaults.fixed) { @@ -2015,4 +2014,4 @@ $("").appendTo("head"); }); -}(window, document, jQuery)); +}(window, document, jQuery)); \ No newline at end of file diff --git a/img/ccz.jpg b/img/ccz.jpg deleted file mode 100644 index 64deabb..0000000 Binary files a/img/ccz.jpg and /dev/null differ diff --git a/img/coderwall.png b/img/coderwall.png deleted file mode 100644 index 0673e7d..0000000 Binary files a/img/coderwall.png and /dev/null differ diff --git a/img/delicious.png b/img/delicious.png deleted file mode 100644 index a936776..0000000 Binary files a/img/delicious.png and /dev/null differ diff --git a/img/douban.png b/img/douban.png deleted file mode 100644 index 93a99cf..0000000 Binary files a/img/douban.png and /dev/null differ diff --git a/img/facebook.png b/img/facebook.png deleted file mode 100644 index 2e43bd8..0000000 Binary files a/img/facebook.png and /dev/null differ diff --git a/img/github.png b/img/github.png deleted file mode 100644 index 6962c9d..0000000 Binary files a/img/github.png and /dev/null differ diff --git a/img/google.png b/img/google.png deleted file mode 100644 index 68f6978..0000000 Binary files a/img/google.png and /dev/null differ diff --git a/img/img-err.png b/img/img-err.png deleted file mode 100644 index 5bc0bf2..0000000 Binary files a/img/img-err.png and /dev/null differ diff --git a/img/img-loading.png b/img/img-loading.png deleted file mode 100644 index ba77ce3..0000000 Binary files a/img/img-loading.png and /dev/null differ diff --git a/img/linkedin.png b/img/linkedin.png deleted file mode 100644 index 1a7ae91..0000000 Binary files a/img/linkedin.png and /dev/null differ diff --git a/img/mail.png b/img/mail.png deleted file mode 100644 index e308682..0000000 Binary files a/img/mail.png and /dev/null differ diff --git a/img/pinboard.png b/img/pinboard.png deleted file mode 100644 index 2961f30..0000000 Binary files a/img/pinboard.png and /dev/null differ diff --git a/img/pinterest.png b/img/pinterest.png deleted file mode 100644 index 34a64d6..0000000 Binary files a/img/pinterest.png and /dev/null differ diff --git a/img/rss.png b/img/rss.png deleted file mode 100644 index 8492267..0000000 Binary files a/img/rss.png and /dev/null differ diff --git a/img/scrollbar_arrow.png b/img/scrollbar_arrow.png deleted file mode 100644 index 81bba97..0000000 Binary files a/img/scrollbar_arrow.png and /dev/null differ diff --git a/img/stackoverflow.png b/img/stackoverflow.png deleted file mode 100644 index be71ba0..0000000 Binary files a/img/stackoverflow.png and /dev/null differ diff --git a/img/twitter.png b/img/twitter.png deleted file mode 100644 index 1d63f2d..0000000 Binary files a/img/twitter.png and /dev/null differ diff --git a/img/weibo.png b/img/weibo.png deleted file mode 100644 index 8784c36..0000000 Binary files a/img/weibo.png and /dev/null differ diff --git a/img/zhihu.png b/img/zhihu.png deleted file mode 100644 index 4408163..0000000 Binary files a/img/zhihu.png and /dev/null differ diff --git a/index.html b/index.html index 256b703..04d7aa3 100644 --- a/index.html +++ b/index.html @@ -3,1404 +3,220 @@ - - 菜菜的博客 + Hexo - + - + - - + + - - + + - + + + + + +
        -
        -
        -
        - -
        - -
        -
        - - -
        - -
        - - - -
        - - - - -
        +
        + - - -
        - -

        Spring Boot中使用JavaMailSender发送邮件
        2016年06月14日 标签:Spring Boot
        相信使用过Spring的众多开发者都知道Spring提供了非常好用的JavaMailSender接口实现邮件发送。在Spring Boot的Starter模块中也为此提供了自动化配置。下面通过实例看看如何在Spring Boot中使用JavaMailSender发送邮件。

        -

        快速入门
        在Spring Boot的工程中的pom.xml中引入spring-boot-starter-mail依赖:

        -
        <dependency>  
        -    <groupId>org.springframework.boot</groupId>
        -    <artifactId>spring-boot-starter-mail</artifactId>
        -</dependency> 
        -
        -如其他自动化配置模块一样,在完成了依赖引入之后,只需要在application.properties中配置相应的属性内容。
        -
        -下面我们以QQ邮箱为例,在application.properties中加入如下配置(注意替换自己的用户名和密码):
        -
        -spring.mail.host=smtp.qq.com  
        -spring.mail.username=用户名  
        -spring.mail.password=密码  
        -spring.mail.properties.mail.smtp.auth=true  
        -spring.mail.properties.mail.smtp.starttls.enable=true  
        -spring.mail.properties.mail.smtp.starttls.required=true  
        -通过单元测试来实现一封简单邮件的发送:
        -
        -@RunWith(SpringJUnit4ClassRunner.class)
        -@SpringApplicationConfiguration(classes = Application.class)
        -public class ApplicationTests {
        -
        -    @Autowired
        -    private JavaMailSender mailSender;
        -
        -    @Test
        -    public void sendSimpleMail() throws Exception {
        -        SimpleMailMessage message = new SimpleMailMessage();
        -        message.setFrom("dyc87112@qq.com");
        -        message.setTo("dyc87112@qq.com");
        -        message.setSubject("主题:简单邮件");
        -        message.setText("测试邮件内容");
        -
        -        mailSender.send(message);
        -    }
        -
        -}
        -到这里,一个简单的邮件发送就完成了,运行一下该单元测试,看看效果如何?
        -
        -由于Spring Boot的starter模块提供了自动化配置,所以在引入了spring-boot-starter-mail依赖之后,会根据配置文件中的内容去创建JavaMailSender实例,因此我们可以直接在需要使用的地方直接@Autowired来引入邮件发送对象。
        -进阶使用
        -在上例中,我们通过使用SimpleMailMessage实现了简单的邮件发送,但是实际使用过程中,我们还可能会带上附件、或是使用邮件模块等。这个时候我们就需要使用MimeMessage来设置复杂一些的邮件内容,下面我们就来依次实现一下。
        -
        -发送附件
        -在上面单元测试中加入如下测试用例(通过MimeMessageHelper来发送一封带有附件的邮件):
        -
        -    @Test
        -    public void sendAttachmentsMail() throws Exception {
        -
        -        MimeMessage mimeMessage = mailSender.createMimeMessage();
        -
        -        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        -        helper.setFrom("dyc87112@qq.com");
        -        helper.setTo("dyc87112@qq.com");
        -        helper.setSubject("主题:有附件");
        -        helper.setText("有附件的邮件");
        -
        -        FileSystemResource file = new FileSystemResource(new File("weixin.jpg"));
        -        helper.addAttachment("附件-1.jpg", file);
        -        helper.addAttachment("附件-2.jpg", file);
        -
        -        mailSender.send(mimeMessage);
        -
        -    }
        -嵌入静态资源
        -除了发送附件之外,我们在邮件内容中可能希望通过嵌入图片等静态资源,让邮件获得更好的阅读体验,而不是从附件中查看具体图片,下面的测试用例演示了如何通过MimeMessageHelper实现在邮件正文中嵌入静态资源。
        -
        -    @Test
        -    public void sendInlineMail() throws Exception {
        -
        -        MimeMessage mimeMessage = mailSender.createMimeMessage();
        -
        -        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        -        helper.setFrom("dyc87112@qq.com");
        -        helper.setTo("dyc87112@qq.com");
        -        helper.setSubject("主题:嵌入静态资源");
        -        helper.setText("<html><body><img src=\"cid:weixin\" ></body></html>", true);
        -
        -        FileSystemResource file = new FileSystemResource(new File("weixin.jpg"));
        -        helper.addInline("weixin", file);
        -
        -        mailSender.send(mimeMessage);
        -
        -    }
        -这里需要注意的是addInline函数中资源名称weixin需要与正文中cid:weixin对应起来
        -
        -模板邮件
        -通常我们使用邮件发送服务的时候,都会有一些固定的场景,比如重置密码、注册确认等,给每个用户发送的内容可能只有小部分是变化的。所以,很多时候我们会使用模板引擎来为各类邮件设置成模板,这样我们只需要在发送时去替换变化部分的参数即可。
        -
        -在Spring Boot中使用模板引擎来实现模板化的邮件发送也是非常容易的,下面我们以velocity为例实现一下。
        -
        -引入velocity模块的依赖:
        -
        -<dependency>  
        -    <groupId>org.springframework.boot</groupId>
        -    <artifactId>spring-boot-starter-velocity</artifactId>
        -</dependency>  
        -在resources/templates/下,创建一个模板页面template.vm:
        -
        -<html>  
        -<body>  
        -    <h3>你好, ${username}, 这是一封模板邮件!</h3>
        -</body>  
        -</html>
        -

        我们之前在Spring Boot中开发Web应用时,提到过在Spring Boot的自动化配置下,模板默认位于resources/templates/目录下

        -

        最后,我们在单元测试中加入发送模板邮件的测试用例,具体如下:

        -
        @Test
        -public void sendTemplateMail() throws Exception {
        -
        -    MimeMessage mimeMessage = mailSender.createMimeMessage();
        -
        -    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        -    helper.setFrom("dyc87112@qq.com");
        -    helper.setTo("dyc87112@qq.com");
        -    helper.setSubject("主题:模板邮件");
        -
        -    Map<String, Object> model = new HashedMap();
        -    model.put("username", "didi");
        -    String text = VelocityEngineUtils.mergeTemplateIntoString(
        -            velocityEngine, "template.vm", "UTF-8", model);
        -    helper.setText(text, true);
        -
        -    mailSender.send(mimeMessage);
        -}
        -

        尝试运行一下,就可以收到内容为你好, didi, 这是一封模板邮件!的邮件。这里,我们通过传入username的参数,在邮件内容中替换了模板中的${username}变量。

        - - -
        - - - -
        - -
        - - - - - - - - - - - - - - - - - - - - - -
        - - - -
        - - - - -
        + +
        - - -
        - -

        Spring Cloud构建微服务架构(二)——服务消费者
        2016年06月02日 标签:Spring Cloud, Spring Boot, Eureka, Netflix, Ribbon, Feign
        在上一篇《Spring Cloud构建微服务架构(一)服务注册与发现》中,我们已经成功创建了“服务注册中心”,实现并注册了一个“服务提供者:COMPUTE-SERVICE”。那么我们要如何去消费服务提供者的接口内容呢?

        -

        Ribbon
        Ribbon是一个基于HTTP和TCP客户端的负载均衡器。Feign中也使用Ribbon,后续会介绍Feign的使用。

        -

        Ribbon可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用。

        -

        当Ribbon与Eureka联合使用时,ribbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务端列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。

        -

        下面我们通过实例看看如何使用Ribbon来调用服务,并实现客户端的均衡负载。

        -

        准备工作
        启动Chapter-9-1-1中的服务注册中心:eureka-server
        启动Chapter-9-1-1中的服务提供方:compute-service
        修改compute-service中的server-port为2223,再启动一个服务提供方:compute-service
        此时访问:http://localhost:1111/

        -

        可以看到COMPUTE-SERVICE服务有两个单元正在运行:

        -

        192.168.21.101:compute-service:2222
        192.168.21.101:compute-service:2223
        使用Ribbon实现客户端负载均衡的消费者
        构建一个基本Spring Boot项目,并在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
        <parent>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.5.RELEASE</version>
        <relativePath/>
        </parent>

        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>
        </dependencies>

        <dependencyManagement>
        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Brixton.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
        </dependency>
        </dependencies>
        </dependencyManagement>

        -

        在应用主类中,通过@EnableDiscoveryClient注解来添加发现服务能力。创建RestTemplate实例,并通过@LoadBalanced注解开启均衡负载能力。

        -

        @SpringBootApplication
        @EnableDiscoveryClient
        public class RibbonApplication {

        -
        @Bean
        -@LoadBalanced
        -RestTemplate restTemplate() {
        -    return new RestTemplate();
        -}
        -
        -public static void main(String[] args) {
        -    SpringApplication.run(RibbonApplication.class, args);
        -}
        -

        }
        创建ConsumerController来消费COMPUTE-SERVICE的add服务。通过直接RestTemplate来调用服务,计算10 + 20的值。

        -

        @RestController
        public class ConsumerController {

        -
        @Autowired
        -RestTemplate restTemplate;
        -
        -@RequestMapping(value = "/add", method = RequestMethod.GET)
        -public String add() {
        -    return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
        -}
        -

        }
        application.properties中配置eureka服务注册中心

        -

        spring.application.name=ribbon-consumer
        server.port=3333

        -

        eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
        启动该应用,并访问两次:http://localhost:3333/add

        -

        然后,打开compute-service的两个服务提供方,分别输出了类似下面的日志内容:

        -

        端口为2222服务提供端的日志:
        2016-06-02 11:16:26.787 INFO 90014 — [io-2222-exec-10] com.didispace.web.ComputeController : /add, host:192.168.21.101, service_id:compute-service, result:30
        端口为2223服务提供端的日志:
        2016-06-02 11:19:41.241 INFO 90122 — [nio-2223-exec-1] com.didispace.web.ComputeController : /add, host:192.168.21.101, service_id:compute-service, result:30
        可以看到,之前启动的两个compute-service服务端分别被调用了一次。到这里,我们已经通过Ribbon在客户端已经实现了对服务调用的均衡负载。

        -

        完整示例可参考:Chapter9-1-2/eureka-ribbon

        -

        Feign
        Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单。我们只需要使用Feign来创建一个接口并用注解来配置它既可完成。它具备可插拔的注解支持,包括Feign注解和JAX-RS注解。Feign也支持可插拔的编码器和解码器。Spring Cloud为Feign增加了对Spring MVC注解的支持,还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。

        -

        下面,通过一个例子来展现Feign如何方便的声明对上述computer-service服务的定义和调用。

        -

        创建一个Spring Boot工程,配置pom.xml,将上述的配置中的ribbon依赖替换成feign的依赖即可,具体如下:

        -
        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
        <parent>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
        </parent>

        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>
        </dependencies>

        <dependencyManagement>
        <dependencies>
        <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Brixton.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
        </dependency>
        </dependencies>
        </dependencyManagement>
        -

        在应用主类中通过@EnableFeignClients注解开启Feign功能,具体如下:

        -

        @SpringBootApplication
        @EnableDiscoveryClient
        @EnableFeignClients
        public class FeignApplication {

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

        }
        定义compute-service服务的接口,具体如下:

        -

        @FeignClient(“compute-service”)
        public interface ComputeClient {

        -
        @RequestMapping(method = RequestMethod.GET, value = "/add")
        -Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);
        -

        }
        使用@FeignClient(“compute-service”)注解来绑定该接口对应compute-service服务
        通过Spring MVC的注解来配置compute-service服务下的具体实现。
        在web层中调用上面定义的ComputeClient,具体如下:

        -

        @RestController
        public class ConsumerController {

        -
        @Autowired
        -ComputeClient computeClient;
        -
        -@RequestMapping(value = "/add", method = RequestMethod.GET)
        -public Integer add() {
        -    return computeClient.add(10, 20);
        -}
        -

        }
        application.properties中不用变,指定eureka服务注册中心即可,如:

        -

        spring.application.name=feign-consumer
        server.port=3333

        -

        eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
        启动该应用,访问几次:http://localhost:3333/add

        -

        再观察日志,可以得到之前使用Ribbon时一样的结果,对服务提供方实现了均衡负载。

        -

        这一节我们通过Feign以接口和注解配置的方式,轻松实现了对compute-service服务的绑定,这样我们就可以在本地应用中像本地服务一下的调用它,并且做到了客户端均衡负载。

        - - -
        - - -
        - -
        - - - - - - - - - - -
        - -
        - - - - - - - - + -
        - - - -
        - - - -
        - - -

        - Springboot中使用log4j记录日志 -

        - -
        - -
        - -

        Spring boot中使用log4j记录日志
        2016年05月19日 标签:Spring Boot
        之前在Spring Boot日志管理 一文中主要介绍了Spring Boot中默认日志工具(logback)的基本配置内容。对于很多习惯使用log4j的开发者,Spring Boot依然可以很好的支持,只是需要做一些小小的配置功能。

        -

        引入log4j依赖
        在创建Spring Boot工程时,我们引入了spring-boot-starter,其中包含了spring-boot-starter-logging,该依赖内容就是Spring Boot默认的日志框架Logback,所以我们在引入log4j之前,需要先排除该包的依赖,再引入log4j的依赖,就像下面这样:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        <dependency>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
        <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
        </exclusions>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j</artifactId>
        </dependency>

        -

        配置log4j.properties
        在引入了log4j依赖之后,只需要在src/main/resources目录下加入log4j.properties配置文件,就可以开始对应用的日志进行配置使用。

        -

        控制台输出
        通过如下配置,设定root日志的输出级别为INFO,appender为控制台输出stdout

        -

        LOG4J配置

        log4j.rootCategory=INFO, stdout

        -

        控制台输出

        log4j.appender.stdout=org.apache.log4j.ConsoleAppender
        log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
        log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
        输出到文件
        在开发环境,我们只是输出到控制台没有问题,但是到了生产或测试环境,或许持久化日志内容,方便追溯问题原因。可以通过添加如下的appender内容,按天输出到不同的文件中去,同时还需要为log4j.rootCategory添加名为file的appender,这样root日志就可以输出到logs/all.log文件中了。

        -

        #
        log4j.rootCategory=INFO, stdout, file

        -

        root日志输出

        log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.file.file=logs/all.log
        log4j.appender.file.DatePattern=’.’yyyy-MM-dd
        log4j.appender.file.layout=org.apache.log4j.PatternLayout
        log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
        分类输出
        当我们日志量较多的时候,查找问题会非常困难,常用的手段就是对日志进行分类,比如:

        -

        可以按不同package进行输出。通过定义输出到logs/my.log的appender,并对com.didispace包下的日志级别设定为DEBUG级别、appender设置为输出到logs/my.log的名为didifile的appender。

        -

        com.didispace包下的日志配置

        log4j.category.com.didispace=DEBUG, didifile

        -

        com.didispace下的日志输出

        log4j.appender.didifile=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.didifile.file=logs/my.log
        log4j.appender.didifile.DatePattern=’.’yyyy-MM-dd
        log4j.appender.didifile.layout=org.apache.log4j.PatternLayout
        log4j.appender.didifile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L —- %m%n
        可以对不同级别进行分类,比如对ERROR级别输出到特定的日志文件中,具体配置可以如下。
        log4j.logger.error=errorfile

        -

        error日志输出

        log4j.appender.errorfile=org.apache.log4j.DailyRollingFileAppender
        log4j.appender.errorfile.file=logs/error.log
        log4j.appender.errorfile.DatePattern=’.’yyyy-MM-dd
        log4j.appender.errorfile.Threshold = ERROR
        log4j.appender.errorfile.layout=org.apache.log4j.PatternLayout
        log4j.appender.errorfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
        本文主要介绍如何在spring boot中引入log4j,以及一些基础用法,对于更多log4j的用法,还请参考log4j官方网站

        - - +
        +

        Archives

        + - - -
        - -
        - - - - - - - - -
        - - -
        - - - -
        +
        +

        Articles récents

        +
        - - -
        - -

        Spring Boot中使用@Async实现异步调用
        2016年05月16日 标签:Spring Boot
        什么是“异步调用”?

        -

        “异步调用”对应的是“同步调用”,同步调用指程序按照定义顺序依次执行,每一行程序都必须等待上一行程序执行完成之后才能执行;异步调用指程序在顺序执行时,不等待异步调用的语句返回结果就执行后面的程序。

        -

        同步调用
        下面通过一个简单示例来直观的理解什么是同步调用:

        -

        定义Task类,创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)
        @Component
        public class Task {

        -
        public static Random random =new Random();
        -
        -public void doTaskOne() throws Exception {
        -    System.out.println("开始做任务一");
        -    long start = System.currentTimeMillis();
        -    Thread.sleep(random.nextInt(10000));
        -    long end = System.currentTimeMillis();
        -    System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
        -}
        -
        -public void doTaskTwo() throws Exception {
        -    System.out.println("开始做任务二");
        -    long start = System.currentTimeMillis();
        -    Thread.sleep(random.nextInt(10000));
        -    long end = System.currentTimeMillis();
        -    System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
        -}
        -
        -public void doTaskThree() throws Exception {
        -    System.out.println("开始做任务三");
        -    long start = System.currentTimeMillis();
        -    Thread.sleep(random.nextInt(10000));
        -    long end = System.currentTimeMillis();
        -    System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
        -}
        -

        }
        在单元测试用例中,注入Task对象,并在测试用例中执行doTaskOne、doTaskTwo、doTaskThree三个函数。
        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(classes = Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private Task task;
        -
        -@Test
        -public void test() throws Exception {
        -    task.doTaskOne();
        -    task.doTaskTwo();
        -    task.doTaskThree();
        -}
        -

        }
        执行单元测试,可以看到类似如下输出:
        开始做任务一
        完成任务一,耗时:4256毫秒
        开始做任务二
        完成任务二,耗时:4957毫秒
        开始做任务三
        完成任务三,耗时:7173毫秒
        任务一、任务二、任务三顺序的执行完了,换言之doTaskOne、doTaskTwo、doTaskThree三个函数顺序的执行完成。

        -

        异步调用
        上述的同步调用虽然顺利的执行完了三个任务,但是可以看到执行时间比较长,若这三个任务本身之间不存在依赖关系,可以并发执行的话,同步调用在执行效率方面就比较差,可以考虑通过异步调用的方式来并发执行。

        -

        在Spring Boot中,我们只需要通过使用@Async注解就能简单的将原来的同步函数变为异步函数,Task类改在为如下模式:

        -

        @Component
        public class Task {

        -
        @Async
        -public void doTaskOne() throws Exception {
        -    // 同上内容,省略
        -}
        -
        -@Async
        -public void doTaskTwo() throws Exception {
        -    // 同上内容,省略
        -}
        -
        -@Async
        -public void doTaskThree() throws Exception {
        -    // 同上内容,省略
        -}
        -

        }
        为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync,如下所示:

        -

        @SpringBootApplication
        @EnableAsync
        public class Application {

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

        }
        此时可以反复执行单元测试,您可能会遇到各种不同的结果,比如:

        -

        没有任何任务相关的输出
        有部分任务相关的输出
        乱序的任务相关的输出
        原因是目前doTaskOne、doTaskTwo、doTaskThree三个函数的时候已经是异步执行了。主程序在异步调用之后,主程序并不会理会这三个函数是否执行完成了,由于没有其他需要执行的内容,所以程序就自动结束了,导致了不完整或是没有输出任务相关内容的情况。

        -

        注: @Async所修饰的函数不要定义为static类型,这样异步调用不会生效

        -

        异步回调
        为了让doTaskOne、doTaskTwo、doTaskThree能正常结束,假设我们需要统计一下三个任务并发执行共耗时多少,这就需要等到上述三个函数都完成调动之后记录时间,并计算结果。

        -

        那么我们如何判断上述三个异步调用是否已经执行完成呢?我们需要使用Future来返回异步调用的结果,就像如下方式改造doTaskOne函数:

        -

        @Async
        public Future doTaskOne() throws Exception {
        System.out.println(“开始做任务一”);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println(“完成任务一,耗时:” + (end - start) + “毫秒”);
        return new AsyncResult<>(“任务一完成”);
        }
        按照如上方式改造一下其他两个异步函数之后,下面我们改造一下测试用例,让测试在等待完成三个异步调用之后来做一些其他事情。

        -

        @Test
        public void test() throws Exception {

        -
        long start = System.currentTimeMillis();
        -
        -Future<String> task1 = task.doTaskOne();
        -Future<String> task2 = task.doTaskTwo();
        -Future<String> task3 = task.doTaskThree();
        -
        -while(true) {
        -    if(task1.isDone() && task2.isDone() && task3.isDone()) {
        -        // 三个任务都调用完成,退出循环等待
        -        break;
        -    }
        -    Thread.sleep(1000);
        -}
        -
        -long end = System.currentTimeMillis();
        -
        -System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
        -

        }
        看看我们做了哪些改变:

        -

        在测试用例一开始记录开始时间
        在调用三个异步函数的时候,返回Future类型的结果对象
        在调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断。
        跳出循环之后,根据结束时间 - 开始时间,计算出三个任务并发执行的总耗时。
        执行一下上述的单元测试,可以看到如下结果:

        -

        开始做任务一
        开始做任务二
        开始做任务三
        完成任务三,耗时:37毫秒
        完成任务二,耗时:3661毫秒
        完成任务一,耗时:7149毫秒
        任务全部完成,总耗时:8025毫秒
        可以看到,通过异步调用,让任务一、二、三并发执行,有效的减少了程序的总运行时间。

        - - -
        - - -
        - -
        - - - - - - - - - - - -
        + + +
        +
        -
        + - + - - - + + - - - - - - - +
        diff --git a/js/instagram.js b/js/instagram.js deleted file mode 100644 index 93cdc7b..0000000 --- a/js/instagram.js +++ /dev/null @@ -1,138 +0,0 @@ -var Instagram = (function(){ - - var _collection = []; - - var preLoad = function(data){ - for(var em in data){ - for(var i=0,len=data[em].srclist.length;i\ - \ -
        \ - '; - } - $('

        '+data[em].year+''+data[em].month+'月

        \ - \ -
        ').appendTo($(".instagram")); - } - - $(".instagram").lazyload(); - changeSize(); - - setTimeout(function(){ - preLoad(data); - },3000); - - $("a[rel=example_group]").fancybox(); - } - - var replacer = function(str){ - if(str.indexOf("outbound-distilleryimage") >= 0 ){ - var cdnNum = str.match(/outbound-distilleryimage([\s\S]*?)\//)[1]; - var arr = str.split("/"); - return "http://distilleryimage"+cdnNum+".ak.instagram.com/"+arr[arr.length-1]; - }else{ - var url = "http://photos-g.ak.instagram.com/hphotos-ak-xpf1/"; - var arr = str.split("/"); - return url+arr[arr.length-1]; - } - } - - var ctrler = function(data){ - var imgObj = {}; - for(var i=0,len=data.length;i - * options.errCallBack {Function} 可为空.提供img加载失败回调,供业务额外去处理加载失败逻辑 - * options.container {Dom} 提供容器节点内可视区域的加载能力,默认为window - */ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define(['jquery'], factory); - } else { - factory(root['jQuery']); - } -}(this, function ($) { - - $.fn.lazyload = function(options) { - return this.each(function() { - - options = options || {}; - var defualts = {}; - - var opts = $.extend({}, defualts, options); - var obj = $(this); - var dom = this; - - var srcSign = options.srcSign || "lazy-src"; - var errCallBack = options.errCallBack || function(){}; - var container = options.container || $(window); - - /** - * @description src正常 - */ - var imgload = function (e, target) { - //todo: 上报 - } - - /** - * @description src失效 - */ - var imgerr = function (e, target, fn, src) { - if(target[0].src && (target[0].src.indexOf("img-err.png")>0 || target[0].src.indexOf("img-err2.png")>0)){ - return ; - } - var w = target.width(); - var h = target.height(); - target[0].src = "/img/img-err.png"; - - fn(); - //todo: 上报 - }; - - var tempImg = function(target){ - var w = target.width(); - var h = target.height(); - var t = target.offset().top; - var l = target.offset().left; - var tempDom = target.clone().addClass("lazy-loding").insertBefore(target); - tempDom[0].src = "/img/img-loading.png"; - target.hide(); - } - /** - * @description src替换,loading过程中添加类lazy-loading; - */ - var setSrc = function(target, srcSign, errCallBack){ - - if(target.attr("src"))return ; - - if(options.cache == true){ - console.log(target); - //存进localstorage - var canvas1 = document.getElementById('canvas1'); - var ctx1 = canvas1.getContext('2d'); - var imageData; - - image = new Image(); - image.src = target.attr(srcSign); - image.onload=function(){ - ctx1.drawImage(image,0,0); - imageData = ctx1.getImageData(0,0,500,250); - console.log(imageData); - } - - }else{ - tempImg(target); - - var src = target.attr(srcSign); - target[0].onerror = function (e) { - imgerr(e, target, errCallBack, src); - }; - target[0].onload = function (e) { - target.parent().find(".lazy-loding").remove(); - target.show(); - imgload(e, target); - } - target[0].src = src; - } - } - - /** - * @description 重组 - */ - opts.cache = []; - - if(dom.tagName == "IMG"){ - var data = { - obj: obj, - tag: "img", - url: obj.attr(srcSign) - }; - opts.cache.push(data); - }else{ - var imgArr = obj.find("img"); - imgArr.each(function(index) { - var node = this.nodeName.toLowerCase(), url = $(this).attr(srcSign); - //重组 - var data = { - obj: imgArr.eq(index), - tag: node, - url: url - }; - opts.cache.push(data); - }); - } - - - //动态显示数据 - var scrollHandle = function() { - var contHeight = container.height(); - var contop; - if ($(window).get(0) === window) { - contop = $(window).scrollTop(); - } else { - contop = container.offset().top; - } - $.each(opts.cache, function(i, data) { - var o = data.obj, tag = data.tag, url = data.url, post, posb; - if (o) { - post = o.offset().top - contop, post + o.height(); - - if ((post >= 0 && post < contHeight) || (posb > 0 && posb <= contHeight)) { - if (url) { - //在浏览器窗口内 - if (tag === "img") { - //改变src - setSrc(o, srcSign, errCallBack); - } - } - data.obj = null; - } - } - }); - } - - //加载完毕即执行 - scrollHandle(); - //滚动执行 - container.bind("scroll", scrollHandle); - container.bind("resize", scrollHandle); - - }); - }; - -})); diff --git a/js/main.js b/js/main.js deleted file mode 100644 index b8219b1..0000000 --- a/js/main.js +++ /dev/null @@ -1,110 +0,0 @@ -require([], function (){ - - var isMobileInit = false; - var loadMobile = function(){ - require(['/js/mobile.js'], function(mobile){ - mobile.init(); - isMobileInit = true; - }); - } - var isPCInit = false; - var loadPC = function(){ - require(['/js/pc.js'], function(pc){ - pc.init(); - isPCInit = true; - }); - } - - var browser={ - versions:function(){ - var u = window.navigator.userAgent; - return { - trident: u.indexOf('Trident') > -1, //IE内核 - presto: u.indexOf('Presto') > -1, //opera内核 - webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核 - gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核 - mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端 - ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端 - android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或者uc浏览器 - iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, //是否为iPhone或者安卓QQ浏览器 - iPad: u.indexOf('iPad') > -1, //是否为iPad - webApp: u.indexOf('Safari') == -1 ,//是否为web应用程序,没有头部与底部 - weixin: u.indexOf('MicroMessenger') == -1 //是否为微信浏览器 - }; - }() - } - - $(window).bind("resize", function(){ - if(isMobileInit && isPCInit){ - $(window).unbind("resize"); - return; - } - var w = $(window).width(); - if(w >= 700){ - loadPC(); - }else{ - loadMobile(); - } - }); - - if(browser.versions.mobile === true || $(window).width() < 700){ - loadMobile(); - }else{ - loadPC(); - } - - //是否使用fancybox - if(yiliaConfig.fancybox === true){ - require(['/fancybox/jquery.fancybox.js'], function(pc){ - var isFancy = $(".isFancy"); - if(isFancy.length != 0){ - var imgArr = $(".article-inner img"); - for(var i=0,len=imgArr.length;i"); - } - $(".article-inner .fancy-ctn").fancybox(); - } - }); - - } - //是否开启动画 - if(yiliaConfig.animate === true){ - - require(['/js/jquery.lazyload.js'], function(){ - //avatar - $(".js-avatar").attr("src", $(".js-avatar").attr("lazy-src")); - $(".js-avatar")[0].onload = function(){ - $(".js-avatar").addClass("show"); - } - }); - - if(yiliaConfig.isHome === true){ - //content - function showArticle(){ - $(".article").each(function(){ - if( $(this).offset().top <= $(window).scrollTop()+$(window).height() && !($(this).hasClass('show')) ) { - $(this).removeClass("hidden").addClass("show"); - $(this).addClass("is-hiddened"); - }else{ - if(!$(this).hasClass("is-hiddened")){ - $(this).addClass("hidden"); - } - } - }); - } - $(window).on('scroll', function(){ - showArticle(); - }); - showArticle(); - } - - } - - //是否新窗口打开链接 - if(yiliaConfig.open_in_new == true){ - $(".article a[href]").attr("target", "_blank") - } - -}); \ No newline at end of file diff --git a/js/mobile.js b/js/mobile.js deleted file mode 100644 index 3342f15..0000000 --- a/js/mobile.js +++ /dev/null @@ -1,154 +0,0 @@ -define([], function(){ - var _isShow = false; - var $tag, $aboutme, $friends; - - var ctn,radio,scaleW,idx,basicwrap; - - //第一步 -- 初始化 - var reset = function() { - //设定窗口比率 - radio = document.body.scrollHeight/document.body.scrollWidth; - //设定一页的宽度 - scaleW = document.body.scrollWidth; - //设定初始的索引值 - idx = 0; - }; - //第一步 -- 组合 - var combine = function(){ - if($tag){ - document.getElementById("js-mobile-tagcloud").innerHTML = $tag.innerHTML; - } - if($aboutme){ - document.getElementById("js-mobile-aboutme").innerHTML = $aboutme.innerHTML; - } - if($friends){ - document.getElementById("js-mobile-friends").innerHTML = $friends.innerHTML; - } - } - //第三步 -- 根据数据渲染DOM - var renderDOM = function(){ - //生成节点 - var $viewer = document.createElement("div"); - $viewer.id = "viewer"; - $viewer.className = "hide"; - $tag = document.getElementById("js-tagcloud"); - $aboutme = document.getElementById("js-aboutme"); - $friends = document.getElementById("js-friends"); - var tagStr = $tag?'标签
        ':""; - var friendsStr = $friends?'友情链接
        ':""; - var aboutmeStr = $aboutme?'关于我
        ':""; - - $viewer.innerHTML = '
        \ -
        \ -
        '+aboutmeStr+friendsStr+tagStr+'
        \ -
        \ -
        \ -
        '; - - //主要图片节点 - document.getElementsByTagName("body")[0].appendChild($viewer); - var wrap = document.getElementById("viewer-box"); - basicwrap = wrap; - wrap.style.height = document.body.scrollHeight + 'px'; - }; - - var show = function(target, idx){ - document.getElementById("viewer").className = ""; - setTimeout(function(){ - basicwrap.className = "anm-swipe"; - },0); - _isShow = true; - document.ontouchstart=function(e){ - if(e.target.tagName != "A"){ - return false; - } - } - } - - var hide = function(){ - document.getElementById("viewer-box").className = ""; - _isShow = false; - document.ontouchstart=function(){ - return true; - } - } - - //第四步 -- 绑定 DOM 事件 - var bindDOM = function(){ - var scaleW = scaleW; - - //滑动隐藏 - document.getElementById("viewer-box").addEventListener("webkitTransitionEnd", function(){ - - if(_isShow == false){ - document.getElementById("viewer").className = "hide"; - _isShow = true; - }else{ - } - - }, false); - - //点击展示和隐藏 - ctn.addEventListener("touchend", function(){ - show(); - }, false); - - var $right = document.getElementsByClassName("viewer-box-r")[0]; - var touchStartTime; - var touchEndTime; - $right.addEventListener("touchstart", function(){ - touchStartTime = + new Date(); - }, false); - $right.addEventListener("touchend", function(){ - touchEndTime = + new Date(); - if(touchEndTime - touchStartTime < 300){ - hide(); - } - touchStartTime = 0; - touchEndTime = 0; - }, false); - - //滚动样式 - var $overlay = $("#mobile-nav .overlay"); - var $header = $(".js-mobile-header"); - window.onscroll = function(){ - var scrollTop = document.documentElement.scrollTop + document.body.scrollTop; - if(scrollTop >= 69){ - $overlay.addClass("fixed"); - }else{ - $overlay.removeClass("fixed"); - } - if(scrollTop >= 160){ - $header.removeClass("hide").addClass("fixed"); - }else{ - $header.addClass("hide").removeClass("fixed"); - } - }; - $header[0].addEventListener("touchstart", function(){ - $('html, body').animate({scrollTop:0}, 'slow'); - }, false); - }; - - var resetTags = function(){ - var tags = $(".tagcloud a"); - tags.css({"font-size": "12px"}); - for(var i=0,len=tags.length; i', + '', + '
        ', + '', + '', + '', + '', + '
        ', + '' + ].join(''); + + var box = $(html); + + $('body').append(box); + } + + $('.article-share-box.on').hide(); + + box.css({ + top: offset.top + 25, + left: offset.left + }).addClass('on'); + }).on('click', '.article-share-box', function(e){ + e.stopPropagation(); + }).on('click', '.article-share-box-input', function(){ + $(this).select(); + }).on('click', '.article-share-box-link', function(e){ + e.preventDefault(); + e.stopPropagation(); + + window.open(this.href, 'article-share-box-window-' + Date.now(), 'width=500,height=450'); + }); + + // Caption + $('.article-entry').each(function(i){ + $(this).find('img').each(function(){ + if ($(this).parent().hasClass('fancybox')) return; + + var alt = this.alt; + + if (alt) $(this).after('' + alt + ''); + + $(this).wrap(''); + }); + + $(this).find('.fancybox').each(function(){ + $(this).attr('rel', 'article' + i); + }); + }); + + if ($.fancybox){ + $('.fancybox').fancybox(); + } + + // Mobile nav + var $container = $('#container'), + isMobileNavAnim = false, + mobileNavAnimDuration = 200; + + var startMobileNavAnim = function(){ + isMobileNavAnim = true; + }; + + var stopMobileNavAnim = function(){ + setTimeout(function(){ + isMobileNavAnim = false; + }, mobileNavAnimDuration); + } + + $('#main-nav-toggle').on('click', function(){ + if (isMobileNavAnim) return; + + startMobileNavAnim(); + $container.toggleClass('mobile-nav-on'); + stopMobileNavAnim(); + }); + + $('#wrap').on('click', function(){ + if (isMobileNavAnim || !$container.hasClass('mobile-nav-on')) return; + + $container.removeClass('mobile-nav-on'); + }); +})(jQuery); \ No newline at end of file diff --git a/page/2/index.html b/page/2/index.html deleted file mode 100644 index 554da3a..0000000 --- a/page/2/index.html +++ /dev/null @@ -1,1577 +0,0 @@ - - - - - - - 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用@Scheduled创建定时任务 -

        - - -
        - - -
        - -

        Spring Boot中使用@Scheduled创建定时任务
        2016年05月15日 标签:Spring Boot
        我们在编写Spring Boot应用中经常会遇到这样的场景,比如:我需要定时地发送一些短信、邮件之类的操作,也可能会定时地检查和监控一些标志、参数等。

        -

        创建定时任务
        在Spring Boot中编写定时任务是非常简单的事,下面通过实例介绍如何在Spring Boot中创建定时任务,实现每过5秒输出一下当前时间。

        -

        在Spring Boot的主类中加入@EnableScheduling注解,启用定时任务的配置
        @SpringBootApplication
        @EnableScheduling
        public class Application {

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

        }
        创建定时任务实现类
        @Component
        public class ScheduledTasks {

        -
        private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
        -
        -@Scheduled(fixedRate = 5000)
        -public void reportCurrentTime() {
        -    System.out.println("现在时间:" + dateFormat.format(new Date()));
        -}
        -

        }
        运行程序,控制台中可以看到类似如下输出,定时任务开始正常运作了。
        2016-05-15 10:40:04.073 INFO 1688 — [ main] com.didispace.Application : Started Application in 1.433 seconds (JVM running for 1.967)
        现在时间:10:40:09
        现在时间:10:40:14
        现在时间:10:40:19
        现在时间:10:40:24
        现在时间:10:40:29522
        现在时间:10:40:34
        关于上述的简单入门示例也可以参见官方的Scheduling Tasks

        -

        @Scheduled详解
        在上面的入门例子中,使用了@Scheduled(fixedRate = 5000) 注解来定义每过5秒执行的任务,对于@Scheduled的使用可以总结如下几种方式:

        -

        @Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行
        @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行
        @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次
        @Scheduled(cron=”/5 “) :通过cron表达式定义规则
        完整示例Chapter4-1-1

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot属性配置文件详解 -

        - - -
        - - -
        - -

        Spring Boot属性配置文件详解
        2016年05月05日 标签:Spring Boot
        相信很多人选择Spring Boot主要是考虑到它既能兼顾Spring的强大功能,还能实现快速开发的便捷。我们在Spring Boot使用过程中,最直观的感受就是没有了原来自己整合Spring应用时繁多的XML配置内容,替代它的是在pom.xml中引入模块化的Starter POMs,其中各个模块都有自己的默认配置,所以如果不是特殊应用场景,就只需要在application.properties中完成一些属性配置就能开启各模块的应用。

        -

        在之前的各篇文章中都有提及关于application.properties的使用,主要用来配置数据库连接、日志相关配置等。除了这些配置内容之外,本文将具体介绍一些在application.properties配置中的其他特性和使用方法。

        -

        自定义属性与加载
        我们在使用Spring Boot的时候,通常也需要定义一些自己使用的属性,我们可以如下方式直接定义:

        -

        com.didispace.blog.name=程序猿DD
        com.didispace.blog.title=Spring Boot教程
        然后通过@Value(“${属性名}”)注解来加载对应的配置属性,具体如下:

        -

        @Component
        public class BlogProperties {

        -
        @Value("${com.didispace.blog.name}")
        -private String name;
        -@Value("${com.didispace.blog.title}")
        -private String title;
        -
        -// 省略getter和setter
        -

        }
        按照惯例,通过单元测试来验证BlogProperties中的属性是否已经根据配置文件加载了。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private BlogProperties blogProperties;
        -
        -
        -@Test
        -public void getHello() throws Exception {
        -    Assert.assertEquals(blogProperties.getName(), "程序猿DD");
        -    Assert.assertEquals(blogProperties.getTitle(), "Spring Boot教程");
        -}
        -

        }
        参数间的引用
        在application.properties中的各个参数之间也可以直接引用来使用,就像下面的设置:

        -

        com.didispace.blog.name=程序猿DD
        com.didispace.blog.title=Spring Boot教程
        com.didispace.blog.desc=${com.didispace.blog.name}正在努力写《${com.didispace.blog.title}》
        com.didispace.blog.desc参数引用了上文中定义的name和title属性,最后该属性的值就是程序猿DD正在努力写《Spring Boot教程》。

        -

        使用随机数
        在一些情况下,有些参数我们需要希望它不是一个固定的值,比如密钥、服务端口等。Spring Boot的属性配置文件中可以通过${random}来产生int值、long值或者string字符串,来支持属性的随机值。

        -

        随机字符串

        com.didispace.blog.value=${random.value}

        -

        随机int

        com.didispace.blog.number=${random.int}

        -

        随机long

        com.didispace.blog.bignumber=${random.long}

        -

        10以内的随机数

        com.didispace.blog.test1=${random.int(10)}

        -

        10-20的随机数

        com.didispace.blog.test2=${random.int[10,20]}
        通过命令行设置属性值
        相信使用过一段时间Spring Boot的用户,一定知道这条命令:java -jar xxx.jar –server.port=8888,通过使用–server.port属性来设置xxx.jar应用的端口为8888。

        -

        在命令行运行时,连续的两个减号–就是对application.properties中的属性值进行赋值的标识。所以,java -jar xxx.jar –server.port=8888命令,等价于我们在application.properties中添加属性server.port=8888,该设置在样例工程中可见,读者可通过删除该值或使用命令行来设置该值来验证。

        -

        通过命令行来修改属性值固然提供了不错的便利性,但是通过命令行就能更改应用运行的参数,那岂不是很不安全?是的,所以Spring Boot也贴心的提供了屏蔽命令行访问属性的设置,只需要这句设置就能屏蔽:SpringApplication.setAddCommandLineProperties(false)。

        -

        多环境配置
        我们在开发Spring Boot应用时,通常同一套程序会被应用和安装到几个不同的环境,比如:开发、测试、生产等。其中每个环境的数据库地址、服务器端口等等配置都会不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常繁琐且容易发生错误的事。

        -

        对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包,Spring Boot也不例外,或者说更加简单。

        -

        在Spring Boot中多环境配置文件名需要满足application-{profile}.properties的格式,其中{profile}对应你的环境标识,比如:

        -

        application-dev.properties:开发环境
        application-test.properties:测试环境
        application-prod.properties:生产环境
        至于哪个具体的配置文件会被加载,需要在application.properties文件中通过spring.profiles.active属性来设置,其值对应{profile}值。

        -

        如:spring.profiles.active=test就会加载application-test.properties配置文件内容

        -

        下面,以不同环境配置不同的服务端口为例,进行样例实验。

        -

        针对各环境新建不同的配置文件application-dev.properties、application-test.properties、application-prod.properties

        -

        在这三个文件均都设置不同的server.port属性,如:dev环境设置为1111,test环境设置为2222,prod环境设置为3333

        -

        application.properties中设置spring.profiles.active=dev,就是说默认以dev环境设置

        -

        测试不同配置的加载

        -

        执行java -jar xxx.jar,可以观察到服务端口被设置为1111,也就是默认的开发环境(dev)
        执行java -jar xxx.jar –spring.profiles.active=test,可以观察到服务端口被设置为2222,也就是测试环境的配置(test)
        执行java -jar xxx.jar –spring.profiles.active=prod,可以观察到服务端口被设置为3333,也就是生产环境的配置(prod)
        按照上面的实验,可以如下总结多环境的配置思路:

        -

        application.properties中配置通用内容,并设置spring.profiles.active=dev,以开发环境为默认配置
        application-{profile}.properties中配置各个环境不同的内容
        通过命令行方式去激活不同环境的配置

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中Web应用的统一异常处理 -

        - - -
        - - -
        - -

        Spring Boot中Web应用的统一异常处理
        2016年05月02日 标签:Spring Boot
        我们在做Web应用的时候,请求处理过程中发生错误是非常常见的情况。Spring Boot提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异常内容。

        -

        选择一个之前实现过的Web应用(Chapter3-1-2)为基础,启动该应用,访问一个不存在的URL,或是修改处理内容,直接抛出异常,如:

        -

        @RequestMapping(“/hello”)
        public String hello() throws Exception {
        throw new Exception(“发生错误”);
        }
        此时,可以看到类似下面的报错页面,该页面就是Spring Boot提供的默认error映射页面。

        -

        alt=默认的错误页面

        -

        统一异常处理
        虽然,Spring Boot中实现了默认的error映射,但是在实际应用中,上面你的错误页面对用户来说并不够友好,我们通常需要去实现我们自己的异常提示。

        -

        下面我们以之前的Web应用例子为基础(Chapter3-1-2),进行统一异常处理的改造。

        -

        创建全局异常处理类:通过使用@ControllerAdvice定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html中
        @ControllerAdvice
        class GlobalExceptionHandler {

        -
        public static final String DEFAULT_ERROR_VIEW = "error";
        -
        -@ExceptionHandler(value = Exception.class)
        -public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        -    ModelAndView mav = new ModelAndView();
        -    mav.addObject("exception", e);
        -    mav.addObject("url", req.getRequestURL());
        -    mav.setViewName(DEFAULT_ERROR_VIEW);
        -    return mav;
        -}
        -

        }
        实现error.html页面展示:在templates目录下创建error.html,将请求的URL和Exception对象的message输出。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        <!DOCTYPE html>  
        <html>
        <head lang="en">
        <meta charset="UTF-8" />
        <title>统一异常处理</title>
        </head>
        <body>
        <h1>Error Handler</h1>
        <div th:text="${url}"></div>
        <div th:text="${exception.message}"></div>
        </body>
        </html>

        -

        启动该应用,访问:http://localhost:8080/hello,可以看到如下错误提示页面。

        -

        alt=自定义的错误页面

        -

        通过实现上述内容之后,我们只需要在Controller中抛出Exception,当然我们可能会有多种不同的Exception。然后在@ControllerAdvice类中,根据抛出的具体Exception类型匹配@ExceptionHandler中配置的异常类型来匹配错误映射和处理。

        -

        返回JSON格式
        在上述例子中,通过@ControllerAdvice统一定义不同Exception映射到不同错误处理页面。而当我们要实现RESTful API时,返回的错误是JSON格式的数据,而不是HTML页面,这时候我们也能轻松支持。

        -

        本质上,只需在@ExceptionHandler之后加入@ResponseBody,就能让处理函数return的内容转换为JSON格式。

        -

        下面以一个具体示例来实现返回JSON格式的异常处理。

        -

        创建统一的JSON返回对象,code:消息类型,message:消息内容,url:请求的url,data:请求返回的数据
        public class ErrorInfo {

        -
        public static final Integer OK = 0;
        -public static final Integer ERROR = 100;
        -
        -private Integer code;
        -private String message;
        -private String url;
        -private T data;
        -
        -// 省略getter和setter
        -

        }
        创建一个自定义异常,用来实验捕获该异常,并返回json
        public class MyException extends Exception {

        -
        public MyException(String message) {
        -    super(message);
        -}
        -

        }
        Controller中增加json映射,抛出MyException异常
        @Controller
        public class HelloController {

        -
        @RequestMapping("/json")
        -public String json() throws MyException {
        -    throw new MyException("发生错误2");
        -}
        -

        }
        为MyException异常创建对应的处理
        @ControllerAdvice
        public class GlobalExceptionHandler {

        -
        @ExceptionHandler(value = MyException.class)
        -@ResponseBody
        -public ErrorInfo<String> jsonErrorHandler(HttpServletRequest req, MyException e) throws Exception {
        -    ErrorInfo<String> r = new ErrorInfo<>();
        -    r.setMessage(e.getMessage());
        -    r.setCode(ErrorInfo.ERROR);
        -    r.setData("Some Data");
        -    r.setUrl(req.getRequestURL().toString());
        -    return r;
        -}
        -

        }
        启动应用,访问:http://localhost:8080/json,可以得到如下返回内容:
        {
        code: 100,
        data: “Some Data”,
        message: “发生错误2”,
        url: “http://localhost:8080/json
        }
        至此,已完成在Spring Boot中创建统一的异常处理,实际实现还是依靠Spring MVC的注解,更多更深入的使用可参考Spring MVC的文档。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用MongoDB数据库 -

        - - -
        - - -
        - -

        Spring Boot中使用MongoDB数据库
        2016年04月27日 标签:Spring Boot, mongodb
        前段时间分享了关于Spring Boot中使用Redis的文章,除了Redis之后,我们在互联网产品中还经常会用到另外一款著名的NoSQL数据库MongoDB。

        -

        下面就来简单介绍一下MongoDB,并且通过一个例子来介绍Spring Boot中对MongoDB访问的配置和使用。

        -

        MongoDB简介
        MongoDB是一个基于分布式文件存储的数据库,它是一个介于关系数据库和非关系数据库之间的产品,其主要目标是在键/值存储方式(提供了高性能和高度伸缩性)和传统的RDBMS系统(具有丰富的功能)之间架起一座桥梁,它集两者的优势于一身。

        -

        MongoDB支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型,也因为他的存储格式也使得它所存储的数据在Nodejs程序应用中使用非常流畅。

        -

        既然称为NoSQL数据库,Mongo的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

        -

        但是,MongoDB也不是万能的,同MySQL等关系型数据库相比,它们在针对不同的数据类型和事务要求上都存在自己独特的优势。在数据存储的选择中,坚持多样化原则,选择更好更经济的方式,而不是自上而下的统一化。

        -

        较常见的,我们可以直接用MongoDB来存储键值对类型的数据,如:验证码、Session等;由于MongoDB的横向扩展能力,也可以用来存储数据规模会在未来变的非常巨大的数据,如:日志、评论等;由于MongoDB存储数据的弱类型,也可以用来存储一些多变json数据,如:与外系统交互时经常变化的JSON报文。而对于一些对数据有复杂的高事务性要求的操作,如:账户交易等就不适合使用MongoDB来存储。

        -

        MongoDB官网

        -

        访问MongoDB
        在Spring Boot中,对如此受欢迎的MongoDB,同样提供了自配置功能。

        -

        引入依赖
        Spring Boot中可以通过在pom.xml中加入spring-boot-starter-data-mongodb引入对mongodb的访问支持依赖。它的实现依赖spring-data-mongodb。是的,您没有看错,又是spring-data的子项目,之前介绍过spring-data-jpa、spring-data-redis,对于mongodb的访问,spring-data也提供了强大的支持,下面就开始动手试试吧。

        1
        2
        3
        4
        <dependency>  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        -

        快速开始使用Spring-data-mongodb
        若MongoDB的安装配置采用默认端口,那么在自动配置的情况下,我们不需要做任何参数配置,就能马上连接上本地的MongoDB。下面直接使用spring-data-mongodb来尝试对mongodb的存取操作。(记得mongod启动您的mongodb)

        -

        创建要存储的User实体,包含属性:id、username、age
        public class User {

        -
        @Id
        -private Long id;
        -
        -private String username;
        -private Integer age;
        -
        -public User(Long id, String username, Integer age) {
        -    this.id = id;
        -    this.username = username;
        -    this.age = age;
        -}
        -
        -// 省略getter和setter
        -

        }
        实现User的数据访问对象:UserRepository
        public interface UserRepository extends MongoRepository {

        -
        User findByUsername(String username);
        -

        }
        在单元测试中调用
        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private UserRepository userRepository;
        -
        -@Before
        -public void setUp() {
        -    userRepository.deleteAll();
        -}
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 创建三个User,并验证User总数
        -    userRepository.save(new User(1L, "didi", 30));
        -    userRepository.save(new User(2L, "mama", 40));
        -    userRepository.save(new User(3L, "kaka", 50));
        -    Assert.assertEquals(3, userRepository.findAll().size());
        -
        -    // 删除一个User,再验证User总数
        -    User u = userRepository.findOne(1L);
        -    userRepository.delete(u);
        -    Assert.assertEquals(2, userRepository.findAll().size());
        -
        -    // 删除一个User,再验证User总数
        -    u = userRepository.findByUsername("mama");
        -    userRepository.delete(u);
        -    Assert.assertEquals(1, userRepository.findAll().size());
        -
        -}
        -

        }
        参数配置
        通过上面的例子,我们可以轻而易举的对MongoDB进行访问,但是实战中,应用服务器与MongoDB通常不会部署于同一台设备之上,这样就无法使用自动化的本地配置来进行使用。这个时候,我们也可以方便的配置来完成支持,只需要在application.properties中加入mongodb服务端的相关配置,具体示例如下:

        -

        spring.data.mongodb.uri=mongodb://name:pass@localhost:27017/test
        在尝试此配置时,记得在mongo中对test库创建具备读写权限的用户(用户名为name,密码为pass),不同版本的用户创建语句不同,注意查看文档做好准备工作

        -

        若使用mongodb 2.x,也可以通过如下参数配置,该方式不支持mongodb 3.x。

        -

        spring.data.mongodb.host=localhost spring.data.mongodb.port=27017
        版权

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用Swagger2构建强大的RESTful-API文档 -

        - - -
        - - -
        - -

        Spring Boot中使用Swagger2构建强大的RESTful API文档
        2016年04月18日 标签:Spring Boot, Swagger, RESTful Api
        由于Spring Boot能够快速开发、便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API。而我们构建RESTful API的目的通常都是由于多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web前端。

        -

        这样一来,我们的RESTful API就有可能要面对多个开发人员或多个开发团队:IOS开发、Android开发或是Web开发等。为了减少与其他团队平时开发期间的频繁沟通成本,传统做法我们会创建一份RESTful API文档来记录所有接口细节,然而这样的做法有以下几个问题:

        -

        由于接口众多,并且细节复杂(需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等),高质量地创建这份文档本身就是件非常吃力的事,下游的抱怨声不绝于耳。
        随着时间推移,不断修改接口实现的时候都必须同步修改接口文档,而文档与代码又处于两个不同的媒介,除非有严格的管理机制,不然很容易导致不一致现象。
        为了解决上面这样的问题,本文将介绍RESTful API的重磅好伙伴Swagger2,它可以轻松的整合到Spring Boot中,并与Spring MVC程序配合组织出强大RESTful API文档。它既可以减少我们创建文档的工作量,同时说明内容又整合入实现代码中,让维护文档和修改代码整合为一体,可以让我们在修改代码逻辑的同时方便的修改文档说明。另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API。具体效果如下图所示:

        -

        -

        下面来具体介绍,如果在Spring Boot中使用Swagger2。首先,我们需要一个Spring Boot实现的RESTful API工程,若您没有做过这类内容,建议先阅读 Spring Boot构建一个较为复杂的RESTful APIs和单元测试。

        -

        下面的内容我们会以教程样例中的Chapter3-1-1进行下面的实验(Chpater3-1-5是我们的结果工程,亦可参考)。

        -

        添加Swagger2依赖
        在pom.xml中加入Swagger2的依赖

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <dependency>  
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.2.2</version>
        </dependency>
        <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.2.2</version>
        </dependency>

        -

        创建Swagger2配置类
        在Application.java同级创建Swagger2的配置类Swagger2。

        -

        @Configuration
        @EnableSwagger2
        public class Swagger2 {

        -
        @Bean
        -public Docket createRestApi() {
        -    return new Docket(DocumentationType.SWAGGER_2)
        -            .apiInfo(apiInfo())
        -            .select()
        -            .apis(RequestHandlerSelectors.basePackage("com.didispace.web"))
        -            .paths(PathSelectors.any())
        -            .build();
        -}
        -
        -private ApiInfo apiInfo() {
        -    return new ApiInfoBuilder()
        -            .title("Spring Boot中使用Swagger2构建RESTful APIs")
        -            .description("更多Spring Boot相关文章请关注:http://blog.didispace.com/")
        -            .termsOfServiceUrl("http://blog.didispace.com/")
        -            .contact("程序猿DD")
        -            .version("1.0")
        -            .build();
        -}
        -

        }
        如上代码所示,通过@Configuration注解,让Spring来加载该类配置。再通过@EnableSwagger2注解来启用Swagger2。

        -

        再通过createRestApi函数创建Docket的Bean之后,apiInfo()用来创建该Api的基本信息(这些基本信息会展现在文档页面中)。select()函数返回一个ApiSelectorBuilder实例用来控制哪些接口暴露给Swagger来展现,本例采用指定扫描的包路径来定义,Swagger会扫描该包下所有Controller定义的API,并产生文档内容(除了被@ApiIgnore指定的请求)。

        -

        添加文档内容
        在完成了上述配置后,其实已经可以生产文档内容,但是这样的文档主要针对请求本身,而描述主要来源于函数等命名产生,对用户并不友好,我们通常需要自己增加一些说明来丰富文档内容。如下所示,我们通过@ApiOperation注解来给API增加说明、通过@ApiImplicitParams、@ApiImplicitParam注解来给参数增加说明。

        -

        @RestController
        @RequestMapping(value=”/users”) // 通过这里配置使下面的映射都在/users下,可去除
        public class UserController {

        -
        static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());
        -
        -@ApiOperation(value="获取用户列表", notes="")
        -@RequestMapping(value={""}, method=RequestMethod.GET)
        -public List<User> getUserList() {
        -    List<User> r = new ArrayList<User>(users.values());
        -    return r;
        -}
        -
        -@ApiOperation(value="创建用户", notes="根据User对象创建用户")
        -@ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
        -@RequestMapping(value="", method=RequestMethod.POST)
        -public String postUser(@RequestBody User user) {
        -    users.put(user.getId(), user);
        -    return "success";
        -}
        -
        -@ApiOperation(value="获取用户详细信息", notes="根据url的id来获取用户详细信息")
        -@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
        -@RequestMapping(value="/{id}", method=RequestMethod.GET)
        -public User getUser(@PathVariable Long id) {
        -    return users.get(id);
        -}
        -
        -@ApiOperation(value="更新用户详细信息", notes="根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
        -@ApiImplicitParams({
        -        @ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"),
        -        @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
        -})
        -@RequestMapping(value="/{id}", method=RequestMethod.PUT)
        -public String putUser(@PathVariable Long id, @RequestBody User user) {
        -    User u = users.get(id);
        -    u.setName(user.getName());
        -    u.setAge(user.getAge());
        -    users.put(id, u);
        -    return "success";
        -}
        -
        -@ApiOperation(value="删除用户", notes="根据url的id来指定删除对象")
        -@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
        -@RequestMapping(value="/{id}", method=RequestMethod.DELETE)
        -public String deleteUser(@PathVariable Long id) {
        -    users.remove(id);
        -    return "success";
        -}
        -

        }
        完成上述代码添加上,启动Spring Boot程序,访问:http://localhost:8080/swagger-ui.html 。就能看到前文所展示的RESTful API的页面。我们可以再点开具体的API请求,以POST类型的/users请求为例,可找到上述代码中我们配置的Notes信息以及参数user的描述信息,如下图所示。

        -

        alt

        -

        API文档访问与调试
        在上图请求的页面中,我们看到user的Value是个输入框?是的,Swagger除了查看接口功能外,还提供了调试测试功能,我们可以点击上图中右侧的Model Schema(黄色区域:它指明了User的数据结构),此时Value中就有了user对象的模板,我们只需要稍适修改,点击下方“Try it out!”按钮,即可完成了一次请求调用!

        -

        此时,你也可以通过几个GET请求来验证之前的POST请求是否正确。

        -

        相比为这些接口编写文档的工作,我们增加的配置内容是非常少而且精简的,对于原有代码的侵入也在忍受范围之内。因此,在构建RESTful API的同时,加入swagger来对API文档进行管理,是个不错的选择。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用Redis数据库 -

        - - -
        - - -
        - -

        Spring Boot中使用Redis数据库
        2016年04月15日 标签:Spring Boot, redis
        Spring Boot中除了对常用的关系型数据库提供了优秀的自动化支持之外,对于很多NoSQL数据库一样提供了自动化配置的支持,包括:Redis, MongoDB, Elasticsearch, Solr和Cassandra。

        -

        使用Redis
        Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库。

        -

        Redis官网
        Redis中文社区
        引入依赖
        Spring Boot提供的数据访问框架Spring Data Redis基于Jedis。可以通过引入spring-boot-starter-redis来配置依赖关系。

        -
        <dependency>  
        -    <groupId>org.springframework.boot</groupId>
        -    <artifactId>spring-boot-starter-redis</artifactId>
        -</dependency>
        -

        参数配置
        按照惯例在application.properties中加入Redis服务端的相关配置,具体说明如下:

        -

        REDIS (RedisProperties)

        Redis数据库索引(默认为0)

        spring.redis.database=0

        -

        Redis服务器地址

        spring.redis.host=localhost

        -

        Redis服务器连接端口

        spring.redis.port=6379

        -

        Redis服务器连接密码(默认为空)

        spring.redis.password=

        -

        连接池最大连接数(使用负值表示没有限制)

        spring.redis.pool.max-active=8

        -

        连接池最大阻塞等待时间(使用负值表示没有限制)

        spring.redis.pool.max-wait=-1

        -

        连接池中的最大空闲连接

        spring.redis.pool.max-idle=8

        -

        连接池中的最小空闲连接

        spring.redis.pool.min-idle=0

        -

        连接超时时间(毫秒)

        spring.redis.timeout=0
        其中spring.redis.database的配置通常使用0即可,Redis在配置的时候可以设置数据库数量,默认为16,可以理解为数据库的schema

        -

        测试访问
        通过编写测试用例,举例说明如何访问Redis。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private StringRedisTemplate stringRedisTemplate;
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 保存字符串
        -    stringRedisTemplate.opsForValue().set("aaa", "111");
        -    Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));
        -
        -}
        -

        }
        通过上面这段极为简单的测试案例演示了如何通过自动配置的StringRedisTemplate对象进行Redis的读写操作,该对象从命名中就可注意到支持的是String类型。如果有使用过spring-data-redis的开发者一定熟悉RedisTemplate接口,StringRedisTemplate就相当于RedisTemplate的实现。

        -

        除了String类型,实战中我们还经常会在Redis中存储对象,这时候我们就会想是否可以使用类似RedisTemplate来初始化并进行操作。但是Spring Boot并支持直接使用,需要我们自己实现RedisSerializer接口来对传入对象进行序列化和反序列化,下面我们通过一个实例来完成对象的读写操作。

        -

        创建要存储的对象:User
        public class User implements Serializable {

        -
        private static final long serialVersionUID = -1L;
        -
        -private String username;
        -private Integer age;
        -
        -public User(String username, Integer age) {
        -    this.username = username;
        -    this.age = age;
        -}
        -
        -// 省略getter和setter
        -

        }
        实现对象的序列化接口
        public class RedisObjectSerializer implements RedisSerializer {

        -

        private Converter serializer = new SerializingConverter();
        private Converter deserializer = new DeserializingConverter();

        -

        static final byte[] EMPTY_ARRAY = new byte[0];

        -

        public Object deserialize(byte[] bytes) {
        if (isEmpty(bytes)) {
        return null;
        }

        -
        try {
        -  return deserializer.convert(bytes);
        -} catch (Exception ex) {
        -  throw new SerializationException("Cannot deserialize", ex);
        -}
        -

        }

        -

        public byte[] serialize(Object object) {
        if (object == null) {
        return EMPTY_ARRAY;
        }

        -
        try {
        -  return serializer.convert(object);
        -} catch (Exception ex) {
        -  return EMPTY_ARRAY;
        -}
        -

        }

        -

        private boolean isEmpty(byte[] data) {
        return (data == null || data.length == 0);
        }
        }
        配置针对User的RedisTemplate实例
        @Configuration
        public class RedisConfig {

        -
        @Bean
        -JedisConnectionFactory jedisConnectionFactory() {
        -    return new JedisConnectionFactory();
        -}
        -
        -@Bean
        -public RedisTemplate<String, User> redisTemplate(RedisConnectionFactory factory) {
        -    RedisTemplate<String, User> template = new RedisTemplate<String, User>();
        -    template.setConnectionFactory(jedisConnectionFactory());
        -    template.setKeySerializer(new StringRedisSerializer());
        -    template.setValueSerializer(new RedisObjectSerializer());
        -    return template;
        -}
        -

        }
        完成了配置工作后,编写测试用例实验效果
        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private RedisTemplate<String, User> redisTemplate;
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 保存对象
        -    User user = new User("超人", 20);
        -    redisTemplate.opsForValue().set(user.getUsername(), user);
        -
        -    user = new User("蝙蝠侠", 30);
        -    redisTemplate.opsForValue().set(user.getUsername(), user);
        -
        -    user = new User("蜘蛛侠", 40);
        -    redisTemplate.opsForValue().set(user.getUsername(), user);
        -
        -    Assert.assertEquals(20, redisTemplate.opsForValue().get("超人").getAge().longValue());
        -    Assert.assertEquals(30, redisTemplate.opsForValue().get("蝙蝠侠").getAge().longValue());
        -    Assert.assertEquals(40, redisTemplate.opsForValue().get("蜘蛛侠").getAge().longValue());
        -
        -}
        -

        }
        当然spring-data-redis中提供的数据操作远不止这些,本文仅作为在Spring Boot中使用redis时的配置参考,更多对于redis的操作使用,请参考Spring-data-redis Reference。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot日志管理 -

        - - -
        - - -
        - -

        Spring Boot日志管理
        2016年04月13日 标签:Spring Boot
        Spring Boot在所有内部日志中使用Commons Logging,但是默认配置也提供了对常用日志的支持,如:Java Util Logging,Log4J, Log4J2和Logback。每种Logger都可以通过配置使用控制台或者文件输出日志内容。

        -

        格式化日志
        默认的日志输出如下:

        -

        2016-04-13 08:23:50.120 INFO 37397 — [ main] org.hibernate.Version : HHH000412: Hibernate Core {4.3.11.Final}
        输出内容元素具体如下:

        -

        时间日期 — 精确到毫秒
        日志级别 — ERROR, WARN, INFO, DEBUG or TRACE
        进程ID
        分隔符 — — 标识实际日志的开始
        线程名 — 方括号括起来(可能会截断控制台输出)
        Logger名 — 通常使用源代码的类名
        日志内容
        控制台输出
        在Spring Boot中默认配置了ERROR、WARN和INFO级别的日志输出到控制台。

        -

        我们可以通过两种方式切换至DEBUG级别:

        -

        在运行命令后加入–debug标志,如:$ java -jar myapp.jar –debug
        在application.properties中配置debug=true,该属性置为true的时候,核心Logger(包含嵌入式容器、hibernate、spring)会输出更多内容,但是你自己应用的日志并不会输出为DEBUG级别。
        多彩输出
        如果你的终端支持ANSI,设置彩色输出会让日志更具可读性。通过在application.properties中设置spring.output.ansi.enabled参数来支持。

        -

        NEVER:禁用ANSI-colored输出(默认项)
        DETECT:会检查终端是否支持ANSI,是的话就采用彩色输出(推荐项)
        ALWAYS:总是使用ANSI-colored格式输出,若终端不支持的时候,会有很多干扰信息,不推荐使用
        文件输出
        Spring Boot默认配置只会输出到控制台,并不会记录到文件中,但是我们通常生产环境使用时都需要以文件方式记录。

        -

        若要增加文件输出,需要在application.properties中配置logging.file或logging.path属性。

        -

        logging.file,设置文件,可以是绝对路径,也可以是相对路径。如:logging.file=my.log
        logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log

        -
          -
        • 日志文件会在10Mb大小的时候被截断,产生新的日志文件,默认级别为:ERROR、WARN、INFO *
        • -
        -

        级别控制
        在Spring Boot中只需要在application.properties中进行配置完成日志记录的级别控制。

        -

        配置格式:logging.level.*=LEVEL

        -

        logging.level:日志级别控制前缀,*为包名或Logger名
        LEVEL:选项TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
        举例:

        -

        logging.level.com.didispace=DEBUG:com.didispace包下所有class以DEBUG级别输出
        logging.level.root=WARN:root日志以WARN级别输出
        自定义日志配置
        由于日志服务一般都在ApplicationContext创建前就初始化了,它并不是必须通过Spring的配置文件控制。因此通过系统属性和传统的Spring Boot外部配置文件依然可以很好的支持日志控制和管理。

        -

        根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:

        -

        Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
        Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
        Log4j2:log4j2-spring.xml, log4j2.xml
        JDK (Java Util Logging):logging.properties
        Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml)

        -

        自定义输出格式
        在Spring Boot中可以通过在application.properties配置如下参数控制输出格式:

        -

        logging.pattern.console:定义输出到控制台的样式(不支持JDK Logger)
        logging.pattern.file:定义输出到文件的样式(不支持JDK Logger)

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - springBoot多数据源配置与使用 -

        - - -
        - - -
        - -

        Spring Boot多数据源配置与使用
        2016年03月28日 标签:Spring Boot
        之前在介绍使用JdbcTemplate和Spring-data-jpa时,都使用了单数据源。在单数据源的情况下,Spring Boot的配置非常简单,只需要在application.properties文件中配置连接参数即可。但是往往随着业务量发展,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源,下面基于之前的JdbcTemplate和Spring-data-jpa例子分别介绍两种多数据源的配置方式。

        -

        多数据源配置
        创建一个Spring配置类,定义两个DataSource用来读取application.properties中的不同配置。如下例子中,主数据源配置为spring.datasource.primary开头的配置,第二数据源配置为spring.datasource.secondary开头的配置。

        -

        @Configuration
        public class DataSourceConfig {

        -
        @Bean(name = "primaryDataSource")
        -@Qualifier("primaryDataSource")
        -@ConfigurationProperties(prefix="spring.datasource.primary")
        -public DataSource primaryDataSource() {
        -    return DataSourceBuilder.create().build();
        -}
        -
        -@Bean(name = "secondaryDataSource")
        -@Qualifier("secondaryDataSource")
        -@Primary
        -@ConfigurationProperties(prefix="spring.datasource.secondary")
        -public DataSource secondaryDataSource() {
        -    return DataSourceBuilder.create().build();
        -}
        -

        }
        对应的application.properties配置如下:

        -

        spring.datasource.primary.url=jdbc:mysql://localhost:3306/test1
        spring.datasource.primary.username=root
        spring.datasource.primary.password=root
        spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver

        -

        spring.datasource.secondary.url=jdbc:mysql://localhost:3306/test2
        spring.datasource.secondary.username=root
        spring.datasource.secondary.password=root
        spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
        JdbcTemplate支持
        对JdbcTemplate的支持比较简单,只需要为其注入对应的datasource即可,如下例子,在创建JdbcTemplate的时候分别注入名为primaryDataSource和secondaryDataSource的数据源来区分不同的JdbcTemplate。

        -
        @Bean(name = "primaryJdbcTemplate")
        -public JdbcTemplate primaryJdbcTemplate(
        -        @Qualifier("primaryDataSource") DataSource dataSource) {
        -    return new JdbcTemplate(dataSource);
        -}
        -
        -@Bean(name = "secondaryJdbcTemplate")
        -public JdbcTemplate secondaryJdbcTemplate(
        -        @Qualifier("secondaryDataSource") DataSource dataSource) {
        -    return new JdbcTemplate(dataSource);
        -}
        -

        接下来通过测试用例来演示如何使用这两个针对不同数据源的JdbcTemplate。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -@Qualifier("primaryJdbcTemplate")
        -protected JdbcTemplate jdbcTemplate1;
        -
        -@Autowired
        -@Qualifier("secondaryJdbcTemplate")
        -protected JdbcTemplate jdbcTemplate2;
        -
        -@Before
        -public void setUp() {
        -    jdbcTemplate1.update("DELETE  FROM  USER ");
        -    jdbcTemplate2.update("DELETE  FROM  USER ");
        -}
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 往第一个数据源中插入两条数据
        -    jdbcTemplate1.update("insert into user(id,name,age) values(?, ?, ?)", 1, "aaa", 20);
        -    jdbcTemplate1.update("insert into user(id,name,age) values(?, ?, ?)", 2, "bbb", 30);
        -
        -    // 往第二个数据源中插入一条数据,若插入的是第一个数据源,则会主键冲突报错
        -    jdbcTemplate2.update("insert into user(id,name,age) values(?, ?, ?)", 1, "aaa", 20);
        -
        -    // 查一下第一个数据源中是否有两条数据,验证插入是否成功
        -    Assert.assertEquals("2", jdbcTemplate1.queryForObject("select count(1) from user", String.class));
        -
        -    // 查一下第一个数据源中是否有两条数据,验证插入是否成功
        -    Assert.assertEquals("1", jdbcTemplate2.queryForObject("select count(1) from user", String.class));
        -
        -}
        -

        }
        完整示例:Chapter3-2-3

        -

        Spring-data-jpa支持
        对于数据源的配置可以沿用上例中DataSourceConfig的实现。

        -

        新增对第一数据源的JPA配置,注意两处注释的地方,用于指定数据源对应的Entity实体和Repository定义位置,用@Primary区分主数据源。

        -

        @Configuration
        @EnableTransactionManagement
        @EnableJpaRepositories(
        entityManagerFactoryRef=”entityManagerFactoryPrimary”,
        transactionManagerRef=”transactionManagerPrimary”,
        basePackages= { “com.didispace.domain.p” }) //设置Repository所在位置
        public class PrimaryConfig {

        -
        @Autowired @Qualifier("primaryDataSource")
        -private DataSource primaryDataSource;
        -
        -@Primary
        -@Bean(name = "entityManagerPrimary")
        -public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        -    return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
        -}
        -
        -@Primary
        -@Bean(name = "entityManagerFactoryPrimary")
        -public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
        -    return builder
        -            .dataSource(primaryDataSource)
        -            .properties(getVendorProperties(primaryDataSource))
        -            .packages("com.didispace.domain.p") //设置实体类所在位置
        -            .persistenceUnit("primaryPersistenceUnit")
        -            .build();
        -}
        -
        -@Autowired
        -private JpaProperties jpaProperties;
        -
        -private Map<String, String> getVendorProperties(DataSource dataSource) {
        -    return jpaProperties.getHibernateProperties(dataSource);
        -}
        -
        -@Primary
        -@Bean(name = "transactionManagerPrimary")
        -public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
        -    return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
        -}
        -

        }
        新增对第二数据源的JPA配置,内容与第一数据源类似,具体如下:

        -

        @Configuration
        @EnableTransactionManagement
        @EnableJpaRepositories(
        entityManagerFactoryRef=”entityManagerFactorySecondary”,
        transactionManagerRef=”transactionManagerSecondary”,
        basePackages= { “com.didispace.domain.s” }) //设置Repository所在位置
        public class SecondaryConfig {

        -
        @Autowired @Qualifier("secondaryDataSource")
        -private DataSource secondaryDataSource;
        -
        -@Bean(name = "entityManagerSecondary")
        -public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        -    return entityManagerFactorySecondary(builder).getObject().createEntityManager();
        -}
        -
        -@Bean(name = "entityManagerFactorySecondary")
        -public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
        -    return builder
        -            .dataSource(secondaryDataSource)
        -            .properties(getVendorProperties(secondaryDataSource))
        -            .packages("com.didispace.domain.s") //设置实体类所在位置
        -            .persistenceUnit("secondaryPersistenceUnit")
        -            .build();
        -}
        -
        -@Autowired
        -private JpaProperties jpaProperties;
        -
        -private Map<String, String> getVendorProperties(DataSource dataSource) {
        -    return jpaProperties.getHibernateProperties(dataSource);
        -}
        -
        -@Bean(name = "transactionManagerSecondary")
        -PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
        -    return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
        -}
        -

        }
        完成了以上配置之后,主数据源的实体和数据访问对象位于:com.didispace.domain.p,次数据源的实体和数据访问接口位于:com.didispace.domain.s。

        -

        分别在这两个package下创建各自的实体和数据访问接口

        -

        主数据源下,创建User实体和对应的Repository接口
        @Entity
        public class User {

        -
        @Id
        -@GeneratedValue
        -private Long id;
        -
        -@Column(nullable = false)
        -private String name;
        -
        -@Column(nullable = false)
        -private Integer age;
        -
        -public User(){}
        -
        -public User(String name, Integer age) {
        -    this.name = name;
        -    this.age = age;
        -}
        -
        -// 省略getter、setter
        -

        }
        public interface UserRepository extends JpaRepository {

        -

        }
        从数据源下,创建Message实体和对应的Repository接口
        @Entity
        public class Message {

        -
        @Id
        -@GeneratedValue
        -private Long id;
        -
        -@Column(nullable = false)
        -private String name;
        -
        -@Column(nullable = false)
        -private String content;
        -
        -public Message(){}
        -
        -public Message(String name, String content) {
        -    this.name = name;
        -    this.content = content;
        -}
        -
        -// 省略getter、setter
        -

        }
        public interface MessageRepository extends JpaRepository {

        -

        }
        接下来通过测试用例来验证使用这两个针对不同数据源的配置进行数据操作。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private UserRepository userRepository;
        -@Autowired
        -private MessageRepository messageRepository;
        -
        -@Test
        -public void test() throws Exception {
        -
        -    userRepository.save(new User("aaa", 10));
        -    userRepository.save(new User("bbb", 20));
        -    userRepository.save(new User("ccc", 30));
        -    userRepository.save(new User("ddd", 40));
        -    userRepository.save(new User("eee", 50));
        -
        -    Assert.assertEquals(5, userRepository.findAll().size());
        -
        -    messageRepository.save(new Message("o1", "aaaaaaaaaa"));
        -    messageRepository.save(new Message("o2", "bbbbbbbbbb"));
        -    messageRepository.save(new Message("o3", "cccccccccc"));
        -
        -    Assert.assertEquals(3, messageRepository.findAll().size());
        -
        -}
        -

        }
        完整示例:Chapter3-2-4

        -

        版权申明:署名-非商业性使用-禁止演绎 3.0 (CC BY-NC-ND 3.0)

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用Spring-data-jpa让数据访问更简单更优雅 -

        - - -
        - - -
        - -

        Spring Boot中使用Spring-data-jpa让数据访问更简单、更优雅
        2016年03月24日 标签:Spring Boot
        在上一篇Spring中使用JdbcTemplate访问数据库 中介绍了一种基本的数据访问方式,结合构建RESTful API和使用Thymeleaf模板引擎渲染Web视图的内容就已经可以完成App服务端和Web站点的开发任务了。

        -

        然而,在实际开发过程中,对数据库的操作无非就“增删改查”。就最为普遍的单表操作而言,除了表和字段不同外,语句都是类似的,开发人员需要写大量类似而枯燥的语句来完成业务逻辑。

        -

        为了解决这些大量枯燥的数据操作语句,我们第一个想到的是使用ORM框架,比如:Hibernate。通过整合Hibernate之后,我们以操作Java实体的方式最终将数据改变映射到数据库表中。

        -

        为了解决抽象各个Java实体基本的“增删改查”操作,我们通常会以泛型的方式封装一个模板Dao来进行抽象简化,但是这样依然不是很方便,我们需要针对每个实体编写一个继承自泛型模板Dao的接口,再编写该接口的实现。虽然一些基础的数据访问已经可以得到很好的复用,但是在代码结构上针对每个实体都会有一堆Dao的接口和实现。

        -

        由于模板Dao的实现,使得这些具体实体的Dao层已经变的非常“薄”,有一些具体实体的Dao实现可能完全就是对模板Dao的简单代理,并且往往这样的实现类可能会出现在很多实体上。Spring-data-jpa的出现正可以让这样一个已经很“薄”的数据访问层变成只是一层接口的编写方式。比如,下面的例子:

        -

        public interface UserRepository extends JpaRepository {

        -
        User findByName(String name);
        -
        -@Query("from User u where u.name=:name")
        -User findUser(@Param("name") String name);
        -

        }
        我们只需要通过编写一个继承自JpaRepository的接口就能完成数据访问,下面以一个具体实例来体验Spring-data-jpa给我们带来的强大功能。

        -

        使用示例
        由于Spring-data-jpa依赖于Hibernate。如果您对Hibernate有一定了解,下面内容可以毫不费力的看懂并上手使用Spring-data-jpa。如果您还是Hibernate新手,您可以先按如下方式入门,再建议回头学习一下Hibernate以帮助这部分的理解和进一步使用。

        -

        工程配置
        在pom.xml中添加相关依赖,加入以下内容:

        1
        2
        3
        4
        <dependency  
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        -

        在application.xml中配置:数据库连接信息(如使用嵌入式数据库则不需要)、自动创建表结构的设置,例如使用mysql的情况如下:

        -

        spring.datasource.url=jdbc:mysql://localhost:3306/test
        spring.datasource.username=root
        spring.datasource.password=root
        spring.datasource.driver-class-name=com.mysql.jdbc.Driver

        -

        spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop
        spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:

        -

        create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
        create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
        update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
        validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
        至此已经完成基础配置,如果您有在Spring下整合使用过它的话,相信你已经感受到Spring Boot的便利之处:JPA的传统配置在persistence.xml文件中,但是这里我们不需要。当然,最好在构建项目时候按照之前提过的最佳实践的工程结构来组织,这样以确保各种配置都能被框架扫描到。

        -

        创建实体
        创建一个User实体,包含id(主键)、name(姓名)、age(年龄)属性,通过ORM框架其会被映射到数据库表中,由于配置了hibernate.hbm2ddl.auto,在应用启动的时候框架会自动去数据库中创建对应的表。

        -

        @Entity
        public class User {

        -
        @Id
        -@GeneratedValue
        -private Long id;
        -
        -@Column(nullable = false)
        -private String name;
        -
        -@Column(nullable = false)
        -private Integer age;
        -
        -// 省略构造函数
        -
        -// 省略getter和setter
        -

        }
        创建数据访问接口
        下面针对User实体创建对应的Repository接口实现对该实体的数据访问,如下代码:

        -

        public interface UserRepository extends JpaRepository {

        -
        User findByName(String name);
        -
        -User findByNameAndAge(String name, Integer age);
        -
        -@Query("from User u where u.name=:name")
        -User findUser(@Param("name") String name);
        -

        }
        在Spring-data-jpa中,只需要编写类似上面这样的接口就可实现数据访问。不再像我们以往编写了接口时候还需要自己编写接口实现类,直接减少了我们的文件清单。

        -

        下面对上面的UserRepository做一些解释,该接口继承自JpaRepository,通过查看JpaRepository接口的API文档,可以看到该接口本身已经实现了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,因此对于这些基础操作的数据访问就不需要开发者再自己定义。

        -

        在我们实际开发中,JpaRepository接口定义的接口往往还不够或者性能不够优化,我们需要进一步实现更复杂一些的查询或操作。由于本文重点在spring boot中整合spring-data-jpa,在这里先抛砖引玉简单介绍一下spring-data-jpa中让我们兴奋的功能,后续再单独开篇讲一下spring-data-jpa中的常见使用。

        -

        在上例中,我们可以看到下面两个函数:

        -

        User findByName(String name)
        User findByNameAndAge(String name, Integer age)
        它们分别实现了按name查询User实体和按name和age查询User实体,可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性:通过解析方法名创建查询。

        -

        除了通过解析方法名来创建查询外,它也提供通过使用@Query 注解来创建查询,您只需要编写JPQL语句,并通过类似“:name”来映射@Param指定的参数,就像例子中的第三个findUser函数一样。

        -

        Spring-data-jpa的能力远不止本文提到的这些,由于本文主要以整合介绍为主,对于Spring-data-jpa的使用只是介绍了常见的使用方式。诸如@Modifying操作、分页排序、原生SQL支持以及与Spring MVC的结合使用等等内容就不在本文中详细展开,这里先挖个坑,后续再补文章填坑,如您对这些感兴趣可以关注我博客或简书,同样欢迎大家留言交流想法。

        -

        单元测试
        在完成了上面的数据访问接口之后,按照惯例就是编写对应的单元测试来验证编写的内容是否正确。这里就不多做介绍,主要通过数据操作和查询来反复验证操作的正确性。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private UserRepository userRepository;
        -
        -@Test
        -public void test() throws Exception {
        -
        -    // 创建10条记录
        -    userRepository.save(new User("AAA", 10));
        -    userRepository.save(new User("BBB", 20));
        -    userRepository.save(new User("CCC", 30));
        -    userRepository.save(new User("DDD", 40));
        -    userRepository.save(new User("EEE", 50));
        -    userRepository.save(new User("FFF", 60));
        -    userRepository.save(new User("GGG", 70));
        -    userRepository.save(new User("HHH", 80));
        -    userRepository.save(new User("III", 90));
        -    userRepository.save(new User("JJJ", 100));
        -
        -    // 测试findAll, 查询所有记录
        -    Assert.assertEquals(10, userRepository.findAll().size());
        -
        -    // 测试findByName, 查询姓名为FFF的User
        -    Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());
        -
        -    // 测试findUser, 查询姓名为FFF的User
        -    Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());
        -
        -    // 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User
        -    Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());
        -
        -    // 测试删除姓名为AAA的User
        -    userRepository.delete(userRepository.findByName("AAA"));
        -
        -    // 测试findAll, 查询所有记录, 验证上面的删除是否成功
        -    Assert.assertEquals(9, userRepository.findAll().size());
        -
        -}
        -

        }
        完整示例

        -

        版权申明:署名-非商业性使用-禁止演绎 3.0 (CC BY-NC-ND 3.0)

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用JdbcTemplate访问数据库 -

        - - -
        - - -
        - -

        Spring Boot中使用JdbcTemplate访问数据库
        2016年03月17日 标签:Spring Boot
        之前介绍了很多Web层的例子,包括构建RESTful API、使用Thymeleaf模板引擎渲染Web视图,但是这些内容还不足以构建一个动态的应用。通常我们做App也好,做Web应用也好,都需要内容,而内容通常存储于各种类型的数据库,服务端在接收到访问请求之后需要访问数据库获取并处理成展现给用户使用的数据形式。

        -

        本文介绍在Spring Boot基础下配置数据源和通过JdbcTemplate编写数据访问的示例。

        -

        数据源配置
        在我们访问数据库的时候,需要先配置一个数据源,下面分别介绍一下几种不同的数据库配置方式。

        -

        首先,为了连接数据库需要引入jdbc支持,在pom.xml中引入如下配置:
        ``

        -


        org.springframework.boot
        spring-boot-starter-jdbc

        1
        2
        3
        4
        嵌入式数据库支持
        嵌入式数据库通常用于开发和测试环境,不推荐用于生产环境。Spring Boot提供自动配置的嵌入式数据库有H2、HSQL、Derby,你不需要提供任何连接配置就能使用。

        比如,我们可以在pom.xml中引入如下配置使用HSQL

        -


        org.hsqldb
        hsqldb
        runtime

        连接生产数据源
        以MySQL数据库为例,先引入MySQL连接的依赖包,在pom.xml中加入:

        -


        mysql
        mysql-connector-java
        5.1.21

        ```
        在src/main/resources/application.properties中配置数据源信息

        -

        spring.datasource.url=jdbc:mysql://localhost:3306/test
        spring.datasource.username=dbuser
        spring.datasource.password=dbpass
        spring.datasource.driver-class-name=com.mysql.jdbc.Driver
        连接JNDI数据源
        当你将应用部署于应用服务器上的时候想让数据源由应用服务器管理,那么可以使用如下配置方式引入JNDI数据源。

        -

        spring.datasource.jndi-name=java:jboss/datasources/customers
        使用JdbcTemplate操作数据库
        Spring的JdbcTemplate是自动配置的,你可以直接使用@Autowired来注入到你自己的bean中来使用。

        -

        举例:我们在创建User表,包含属性name、age,下面来编写数据访问对象和单元测试用例。

        -

        定义包含有插入、删除、查询的抽象接口UserService
        public interface UserService {

        -
        /**
        - * 新增一个用户
        - * @param name
        - * @param age
        - */
        -void create(String name, Integer age);
        -
        -/**
        - * 根据name删除一个用户高
        - * @param name
        - */
        -void deleteByName(String name);
        -
        -/**
        - * 获取用户总量
        - */
        -Integer getAllUsers();
        -
        -/**
        - * 删除所有用户
        - */
        -void deleteAllUsers();
        -

        }
        通过JdbcTemplate实现UserService中定义的数据访问操作
        @Service
        public class UserServiceImpl implements UserService {

        -
        @Autowired
        -private JdbcTemplate jdbcTemplate;
        -
        -@Override
        -public void create(String name, Integer age) {
        -    jdbcTemplate.update("insert into USER(NAME, AGE) values(?, ?)", name, age);
        -}
        -
        -@Override
        -public void deleteByName(String name) {
        -    jdbcTemplate.update("delete from USER where NAME = ?", name);
        -}
        -
        -@Override
        -public Integer getAllUsers() {
        -    return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
        -}
        -
        -@Override
        -public void deleteAllUsers() {
        -    jdbcTemplate.update("delete from USER");
        -}
        -

        }
        创建对UserService的单元测试用例,通过创建、删除和查询来验证数据库操作的正确性。
        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(Application.class)
        public class ApplicationTests {

        -
        @Autowired
        -private UserService userSerivce;
        -
        -@Before
        -public void setUp() {
        -    // 准备,清空user表
        -    userSerivce.deleteAllUsers();
        -}
        -
        -@Test
        -public void test() throws Exception {
        -    // 插入5个用户
        -    userSerivce.create("a", 1);
        -    userSerivce.create("b", 2);
        -    userSerivce.create("c", 3);
        -    userSerivce.create("d", 4);
        -    userSerivce.create("e", 5);
        -
        -    // 查数据库,应该有5个用户
        -    Assert.assertEquals(5, userSerivce.getAllUsers().intValue());
        -
        -    // 删除两个用户
        -    userSerivce.deleteByName("a");
        -    userSerivce.deleteByName("e");
        -
        -    // 查数据库,应该有5个用户
        -    Assert.assertEquals(3, userSerivce.getAllUsers().intValue());
        -
        -}
        -

        }
        上面介绍的JdbcTemplate只是最基本的几个操作,更多其他数据访问操作的使用请参考:JdbcTemplate API

        -

        通过上面这个简单的例子,我们可以看到在Spring Boot下访问数据库的配置依然秉承了框架的初衷:简单。我们只需要在pom.xml中加入数据库依赖,再到application.properties中配置连接信息,不需要像Spring应用中创建JdbcTemplate的Bean,就可以直接在自己的对象中注入使用。

        - - -
        - - - -
        - -
        - - - - - - - - - - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/page/3/index.html b/page/3/index.html deleted file mode 100644 index a39f20a..0000000 --- a/page/3/index.html +++ /dev/null @@ -1,1192 +0,0 @@ - - - - - - - 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - -
        - - - -
        - - - - -
        - - -

        - SpringBoot构建RESTfulAPI与单元测试 -

        - - -
        - - -
        - -

        Spring Boot构建RESTful API与单元测试
        2016年03月13日 标签:Spring Boot
        首先,回顾并详细说明一下在快速入门中使用的@Controller、@RestController、@RequestMapping注解。如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例,建议先看一下快速入门的内容。

        -

        @Controller:修饰class,用来创建处理http请求的对象
        @RestController:Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。
        @RequestMapping:配置url映射
        下面我们尝试使用Spring MVC来实现一组对User对象操作的RESTful API,配合注释详细说明在Spring MVC中如何映射HTTP请求、如何传参、如何编写单元测试。

        -
          -
        • RESTful API具体设计如下:*
        • -
        -

        请求类型 URL 功能说明
        GET /users 查询用户列表
        POST /users 创建一个用户
        GET /users/id 根据id查询一个用户
        PUT /users/id 根据id更新一个用户
        DELETE /users/id 根据id删除一个用户
        User实体定义:

        -

        public class User {

        -
        private Long id; 
        -private String name; 
        -private Integer age; 
        -
        -// 省略setter和getter 
        -

        }
        实现对User对象的操作接口

        -

        @RestController
        @RequestMapping(value=”/users”) // 通过这里配置使下面的映射都在/users下
        public class UserController {

        -
        // 创建线程安全的Map 
        -static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); 
        -
        -@RequestMapping(value="/", method=RequestMethod.GET) 
        -public List<User> getUserList() { 
        -    // 处理"/users/"的GET请求,用来获取用户列表 
        -    // 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递 
        -    List<User> r = new ArrayList<User>(users.values()); 
        -    return r; 
        -} 
        -
        -@RequestMapping(value="/", method=RequestMethod.POST) 
        -public String postUser(@ModelAttribute User user) { 
        -    // 处理"/users/"的POST请求,用来创建User 
        -    // 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数 
        -    users.put(user.getId(), user); 
        -    return "success"; 
        -} 
        -
        -@RequestMapping(value="/{id}", method=RequestMethod.GET) 
        -public User getUser(@PathVariable Long id) { 
        -    // 处理"/users/{id}"的GET请求,用来获取url中id值的User信息 
        -    // url中的id可通过@PathVariable绑定到函数的参数中 
        -    return users.get(id); 
        -} 
        -
        -@RequestMapping(value="/{id}", method=RequestMethod.PUT) 
        -public String putUser(@PathVariable Long id, @ModelAttribute User user) { 
        -    // 处理"/users/{id}"的PUT请求,用来更新User信息 
        -    User u = users.get(id); 
        -    u.setName(user.getName()); 
        -    u.setAge(user.getAge()); 
        -    users.put(id, u); 
        -    return "success"; 
        -} 
        -
        -@RequestMapping(value="/{id}", method=RequestMethod.DELETE) 
        -public String deleteUser(@PathVariable Long id) { 
        -    // 处理"/users/{id}"的DELETE请求,用来删除User 
        -    users.remove(id); 
        -    return "success"; 
        -} 
        -

        }
        下面针对该Controller编写测试用例验证正确性,具体如下。当然也可以通过浏览器插件等进行请求提交验证。

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(classes = MockServletContext.class)
        @WebAppConfiguration
        public class ApplicationTests {

        -
        private MockMvc mvc; 
        -
        -@Before 
        -public void setUp() throws Exception { 
        -    mvc = MockMvcBuilders.standaloneSetup(new UserController()).build(); 
        -} 
        -
        -@Test 
        -public void testUserController() throws Exception { 
        -    // 测试UserController 
        -    RequestBuilder request = null; 
        -
        -    // 1、get查一下user列表,应该为空 
        -    request = get("/users/"); 
        -    mvc.perform(request) 
        -            .andExpect(status().isOk()) 
        -            .andExpect(content().string(equalTo("[]"))); 
        -
        -    // 2、post提交一个user 
        -    request = post("/users/") 
        -            .param("id", "1") 
        -            .param("name", "测试大师") 
        -            .param("age", "20"); 
        -    mvc.perform(request) 
        -            .andExpect(content().string(equalTo("success"))); 
        -
        -    // 3、get获取user列表,应该有刚才插入的数据 
        -    request = get("/users/"); 
        -    mvc.perform(request) 
        -            .andExpect(status().isOk()) 
        -            .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"测试大师\",\"age\":20}]"))); 
        -
        -    // 4、put修改id为1的user 
        -    request = put("/users/1") 
        -            .param("name", "测试终极大师") 
        -            .param("age", "30"); 
        -    mvc.perform(request) 
        -            .andExpect(content().string(equalTo("success"))); 
        -
        -    // 5、get一个id为1的user 
        -    request = get("/users/1"); 
        -    mvc.perform(request) 
        -            .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"测试终极大师\",\"age\":30}"))); 
        -
        -    // 6、del删除id为1的user 
        -    request = delete("/users/1"); 
        -    mvc.perform(request) 
        -            .andExpect(content().string(equalTo("success"))); 
        -
        -    // 7、get查一下user列表,应该为空 
        -    request = get("/users/"); 
        -    mvc.perform(request) 
        -            .andExpect(status().isOk()) 
        -            .andExpect(content().string(equalTo("[]"))); 
        -
        -} 
        -

        }
        至此,我们通过引入web模块(没有做其他的任何配置),就可以轻松利用Spring MVC的功能,以非常简洁的代码完成了对User对象的RESTful API的创建以及单元测试的编写。其中同时介绍了Spring MVC中最为常用的几个核心注解:@Controller,@RestController,RequestMapping以及一些参数绑定的注解:@PathVariable,@ModelAttribute,@RequestParam等。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot工程结构推荐 -

        - - -
        - - -
        - -

        Spring Boot工程结构推荐
        2016年03月05日 标签:Spring Boot
        今天看了一位简书上朋友发来的工程,于是想到应该要写这么一篇。前人总结的最佳实践案例可以帮助我们免去很多不必要的麻烦。花点时间来看一下本文,绝对物超所值。
        工程结构(最佳实践)
        Spring Boot框架本身并没有对工程结构有特别的要求,但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑,尤其是Spring包扫描机制的存在,如果您使用最佳实践的工程结构,可以免去不少特殊的配置工作。

        -

        典型示例
        root package结构:com.example.myproject
        应用主类Application.java置于root package下,通常我们会在应用主类中做一些框架配置扫描等配置,我们放在root package下可以帮助程序减少手工配置来加载到我们希望被Spring加载的内容
        实体(Entity)与数据访问层(Repository)置于com.example.myproject.domain包下
        逻辑层(Service)置于com.example.myproject.service包下
        Web层(web)置于com.example.myproject.web包下
        com
        +- example
        +- myproject
        +- Application.java
        |
        +- domain
        | +- Customer.java
        | +- CustomerRepository.java
        |
        +- service
        | +- CustomerService.java
        |
        +- web
        | +- CustomerController.java
        |
        看看您现在的功能是否这样配置,如果不是,不妨尝试改变一下,看看是否可以去掉一些@Configuration配置?

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot开发Web应用 -

        - - -
        - - -
        - -

        Spring Boot开发Web应用
        2016年03月03日 标签:Spring Boot
        Spring Boot快速入门中我们完成了一个简单的RESTful Service,体验了快速开发的特性。在留言中也有朋友提到如何把处理结果渲染到页面上。那么本篇就在上篇基础上介绍一下如何进行Web应用的开发。
        静态资源访问
        在我们开发Web应用的时候,需要引用大量的js、css、图片等静态资源。

        -

        默认配置
        Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则:

        -

        /static
        /public
        /resources
        /META-INF/resources
        举例:我们可以在src/main/resources/目录下创建static,在该位置放置一个图片文件。启动程序后,尝试访问http://localhost:8080/D.jpg。如能显示图片,配置成功。

        -

        渲染Web页面
        在之前的示例中,我们都是通过@RestController来处理请求,所以返回的内容为json对象。那么如果需要渲染html页面的时候,要如何实现呢?

        -

        模板引擎
        在动态HTML实现上Spring Boot依然可以完美胜任,并且提供了多种模板引擎的默认配置支持,所以在推荐的模板引擎下,我们可以很快的上手开发动态网站。

        -

        Spring Boot提供了默认配置的模板引擎主要有以下几种:

        -

        Thymeleaf
        FreeMarker
        Velocity
        Groovy
        Mustache
        Spring Boot建议使用这些模板引擎,避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性,具体可见后文:支持JSP的配置

        -

        当你使用上述模板引擎中的任何一个,它们默认的模板配置路径为:src/main/resources/templates。当然也可以修改这个路径,具体如何修改,可在后续各模板引擎的配置属性中查询并修改。

        -

        Thymeleaf
        Thymeleaf是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。它是一个开源的Java库,基于Apache License 2.0许可,由Daniel Fernández创建,该作者还是Java加密库Jasypt的作者。

        -

        Thymeleaf提供了一个用于整合Spring MVC的可选模块,在应用开发中,你可以使用Thymeleaf来完全代替JSP或其他模板引擎,如Velocity、FreeMarker等。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。

        -

        示例模板:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        <table>  
        <thead>
        <tr>
        <th th:text="#{msgs.headers.name}">Name</td>
        <th th:text="#{msgs.headers.price}">Price</td>
        </tr>
        </thead>
        <tbody>
        <tr th:each="prod : ${allProducts}">
        <td th:text="${prod.name}">Oranges</td>
        <td th:text="${#numbers.formatDecimal(prod.price,1,2)}">0.99</td>
        </tr>
        </tbody>
        </table>
        ```
        可以看到Thymeleaf主要以属性的方式加入到html标签中,浏览器在解析html时,当检查到没有的属性时候会忽略,所以Thymeleaf的模板可以通过浏览器直接打开展现,这样非常有利于前后端的分离。

        在Spring Boot中使用Thymeleaf,只需要引入下面依赖,并在默认的模板路径src/main/resources/templates下编写模板文件即可完成。

        -


        org.springframework.boot
        spring-boot-starter-thymeleaf

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        在完成配置之后,举一个简单的例子,在快速入门工程的基础上,举一个简单的示例来通过Thymeleaf渲染一个页面。

        @Controller
        public class HelloController {

        @RequestMapping("/")
        public String index(ModelMap map) {
        // 加入一个属性,用来在模板中读取
        map.addAttribute("host", "http://blog.didispace.com");
        // return模板文件的名称,对应src/main/resources/templates/index.html
        return "index";
        }

        }

        -

        <!DOCTYPE html>

        -

        -




        -

        -

        Hello World




        ```
        如上页面,直接打开html页面展现Hello World,但是启动程序后,访问http://localhost:8080/,则是展示Controller中host的值:http://blog.didispace.com,做到了不破坏HTML自身内容的数据逻辑分离。

        -

        更多Thymeleaf的页面语法,还请访问Thymeleaf的官方文档查询使用。

        -

        Thymeleaf的默认参数配置

        -

        如有需要修改默认配置的时候,只需复制下面要修改的属性到application.properties中,并修改成需要的值,如修改模板文件的扩展名,修改默认的模板路径等。

        -

        Enable template caching.

        spring.thymeleaf.cache=true

        -

        Check that the templates location exists.

        spring.thymeleaf.check-template-location=true

        -

        Content-Type value.

        spring.thymeleaf.content-type=text/html

        -

        Enable MVC Thymeleaf view resolution.

        spring.thymeleaf.enabled=true

        -

        Template encoding.

        spring.thymeleaf.encoding=UTF-8

        -

        Comma-separated list of view names that should be excluded from resolution.

        spring.thymeleaf.excluded-view-names=

        -

        Template mode to be applied to templates. See also StandardTemplateModeHandlers.

        spring.thymeleaf.mode=HTML5

        -

        Prefix that gets prepended to view names when building a URL.

        spring.thymeleaf.prefix=classpath:/templates/

        -

        Suffix that gets appended to view names when building a URL.

        spring.thymeleaf.suffix=.html spring.thymeleaf.template-resolver-order= # Order of the template resolver in the chain. spring.thymeleaf.view-names= # Comma-separated list of view names that can be resolved.
        支持JSP的配置
        Spring Boot并不建议使用,但如果一定要使用,可以参考此工程作为脚手架:JSP支持

        -

        Spring Boot教程完整案例

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot中使用SpringSecurity进行安全控制 -

        - - -
        - - -
        - -

        Spring Boot中使用Spring Security进行安全控制
        2016年05月28日 标签:Spring Boot, Spring Security
        我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面。要实现访问控制的方法多种多样,可以通过Aop、拦截器实现,也可以通过框架实现(如:Apache Shiro、Spring Security)。

        -

        本文将具体介绍在Spring Boot中如何使用Spring Security进行安全控制。

        -

        准备工作
        首先,构建一个简单的Web工程,以用于后续添加安全控制,也可以用之前Chapter3-1-2做为基础工程。若对如何使用Spring Boot构建Web应用,可以先阅读《Spring Boot开发Web应用》一文。

        -

        Web层实现请求映射
        @Controller
        public class HelloController {

        -
        @RequestMapping("/")
        -public String index() {
        -    return "index";
        -}
        -
        -@RequestMapping("/hello")
        -public String hello() {
        -    return "hello";
        -}
        -

        }
        /:映射到index.html
        /hello:映射到hello.html
        实现映射的页面
        src/main/resources/templates/index.html

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <!DOCTYPE html>  
        <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
        <title>Spring Security入门</title>
        </head>
        <body>
        <h1>欢迎使用Spring Security!</h1>
        <p>点击 <a th:href="@{/hello}">这里</a> 打个招呼吧</p>
        </body>
        </html>

        -

        src/main/resources/templates/hello.html

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <!DOCTYPE html>  
        <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
        xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
        <title>Hello World!</title>
        </head>
        <body>
        <h1>Hello world!</h1>
        </body>
        </html>

        -

        可以看到在index.html中提供到/hello的链接,显然在这里没有任何安全控制,所以点击链接后就可以直接跳转到hello.html页面。

        -

        整合Spring Security
        在这一节,我们将对/hello页面进行权限控制,必须是授权用户才能访问。当没有权限的用户访问后,跳转到登录页面。

        -

        添加依赖
        在pom.xml中添加如下配置,引入对Spring Security的依赖。

        1
        2
        3
        4
        5
        6
        <dependencies>  
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        </dependencies>

        -

        Spring Security配置
        创建Spring Security的配置类WebSecurityConfig,具体如下:

        -

        @Configuration
        @EnableWebSecurity
        public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

        -
        @Override
        -protected void configure(HttpSecurity http) throws Exception {
        -    http
        -        .authorizeRequests()
        -            .antMatchers("/", "/home").permitAll()
        -            .anyRequest().authenticated()
        -            .and()
        -        .formLogin()
        -            .loginPage("/login")
        -            .permitAll()
        -            .and()
        -        .logout()
        -            .permitAll();
        -}
        -
        -@Autowired
        -public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        -    auth
        -        .inMemoryAuthentication()
        -            .withUser("user").password("password").roles("USER");
        -}
        -

        }
        通过@EnableWebSecurity注解开启Spring Security的功能
        继承WebSecurityConfigurerAdapter,并重写它的方法来设置一些web安全的细节
        configure(HttpSecurity http)方法
        通过authorizeRequests()定义哪些URL需要被保护、哪些不需要被保护。例如以上代码指定了/和/home不需要任何认证就可以访问,其他的路径都必须通过身份验证。
        通过formLogin()定义当需要用户登录时候,转到的登录页面。
        configureGlobal(AuthenticationManagerBuilder auth)方法,在内存中创建了一个用户,该用户的名称为user,密码为password,用户角色为USER。
        新增登录请求与页面
        在完成了Spring Security配置之后,我们还缺少登录的相关内容。

        -

        HelloController中新增/login请求映射至login.html

        -

        @Controller
        public class HelloController {

        -
        // 省略之前的内容...
        -
        -@RequestMapping("/login")
        -public String login() {
        -    return "login";
        -}
        -

        }
        新增登录页面:src/main/resources/templates/login.html

        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
        <html xmlns="http://www.w3.org/1999/xhtml"  
        xmlns:th="http://www.thymeleaf.org"
        xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
        <title>Spring Security Example </title>
        </head>
        <body>
        <div th:if="${param.error}">
        用户名或密码错
        </div>
        <div th:if="${param.logout}">
        您已注销成功
        </div>
        <form th:action="@{/login}" method="post">
        <div><label> 用户名 : <input type="text" name="username"/> </label></div>
        <div><label> 密 码 : <input type="password" name="password"/> </label></div>
        <div><input type="submit" value="登录"/></div>
        </form>
        </body>
        </html>
        ```
        可以看到,实现了一个简单的通过用户名和密码提交到/login的登录方式。

        根据配置,Spring Security提供了一个过滤器来拦截请求并验证用户身份。如果用户身份认证失败,页面就重定向到/login?error,并且页面中会展现相应的错误信息。若用户想要注销登录,可以通过访问/login?logout请求,在完成注销之后,页面展现相应的成功消息。

        到这里,我们启用应用,并访问http://localhost:8080/,可以正常访问。但是访问http://localhost:8080/hello的时候被重定向到了http://localhost:8080/login页面,因为没有登录,用户没有访问权限,通过输入用户名user和密码password进行登录后,跳转到了Hello World页面,再也通过访问http://localhost:8080/login?logout,就可以完成注销操作。

        为了让整个过程更完成,我们可以修改hello.html,让它输出一些内容,并提供“注销”的链接。

        ```
        <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
        xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
        <title>Hello World!</title>
        </head>
        <body>
        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
        <form th:action="@{/logout}" method="post">
        <input type="submit" value="注销"/>
        </form>
        </body>
        </html>

        -

        本文通过一个最简单的示例完成了对Web应用的安全控制,Spring Security提供的功能还远不止于此,更多Spring Security的使用可参见Spring Security Reference。

        - - -
        - - - -
        - -
        - - - - - - - - - - - - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - SpringBoot快速入门 -

        - - -
        - - -
        - -

        Spring Boot快速入门
        2016年02月26日 标签:Spring Boot
        简介
        在您第1次接触和学习Spring框架的时候,是否因为其繁杂的配置而退却了?在你第n次使用Spring框架的时候,是否觉得一堆反复黏贴的配置有一些厌烦?那么您就不妨来试试使用Spring Boot来让你更易上手,更简单快捷地构建Spring应用!

        -

        Spring Boot让我们的Spring应用变的更轻量化。比如:你可以仅仅依靠一个Java类来运行一个Spring引用。你也可以打包你的应用为jar并通过使用java -jar来运行你的Spring Web应用。

        -

        Spring Boot的主要优点:

        -

        为所有Spring开发者更快的入门
        开箱即用,提供各种默认配置来简化项目配置
        内嵌式容器简化Web项目
        没有冗余代码生成和XML配置的要求
        快速入门
        本章主要目标完成Spring Boot基础项目的构建,并且实现一个简单的Http请求处理,通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。

        -

        系统要求:
        Java 7及以上
        Spring Framework 4.1.5及以上
        本文采用Java 1.8.0_73、Spring Boot 1.3.2调试通过。

        -

        使用Maven构建项目
        通过SPRING INITIALIZR工具产生基础项目
        访问:http://start.spring.io/
        选择构建工具Maven Project、Spring Boot版本1.3.2以及一些工程基本信息,可参考下图所示

        点击Generate Project下载项目压缩包
        解压项目包,并用IDE以Maven项目导入,以IntelliJ IDEA 14为例:
        菜单中选择File–>New–>Project from Existing Sources…
        选择解压后的项目文件夹,点击OK
        点击Import project from external model并选择Maven,点击Next到底为止。
        若你的环境有多个版本的JDK,注意到选择Java SDK的时候请选择Java 7以上的版本
        项目结构解析

        项目结构

        -

        通过上面步骤完成了基础项目的创建,如上图所示,Spring Boot的基础结构共三个文件(具体路径根据用户生成项目时填写的Group所有差异):

        -

        src/main/java下的程序入口:Chapter1Application
        src/main/resources下的配置文件:application.properties
        src/test/下的测试入口:Chapter1ApplicationTests
        生成的Chapter1Application和Chapter1ApplicationTests类都可以直接运行来启动当前创建的项目,由于目前该项目未配合任何数据访问或Web模块,程序会在加载完Spring之后结束运行。

        -

        引入Web模块
        当前的pom.xml内容如下,仅引入了两个模块:

        -

        spring-boot-starter:核心模块,包括自动配置支持、日志和YAML
        spring-boot-starter-test:测试模块,包括JUnit、Hamcrest、Mockito

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        <dependencies>  
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        </dependency>
        </dependencies>

        -

        引入Web模块,需添加spring-boot-starter-web模块:

        -
        <dependency>
        -    <groupId>org.springframework.boot</groupId>
        -    <artifactId>spring-boot-starter-web</artifactId>
        -</dependency>
        -

        编写HelloWorld服务
        创建package命名为com.didispace.web(根据实际情况修改)
        创建HelloController类,内容如下
        @RestController
        public class HelloController {

        -
        @RequestMapping("/hello")
        -public String index() {
        -    return "Hello World";
        -}
        -

        }
        启动主程序,打开浏览器访问http://localhost:8080/hello,可以看到页面输出Hello World
        编写单元测试用例
        打开的src/test/下的测试入口Chapter1ApplicationTests类。下面编写一个简单的单元测试来模拟http请求,具体如下:

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringApplicationConfiguration(classes = MockServletContext.class)
        @WebAppConfiguration
        public class Chapter1ApplicationTests {

        -
        private MockMvc mvc;
        -
        -@Before
        -public void setUp() throws Exception {
        -    mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
        -}
        -
        -@Test
        -public void getHello() throws Exception {
        -    mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
        -            .andExpect(status().isOk())
        -            .andExpect(content().string(equalTo("Hello World")));
        -}
        -

        }
        使用MockServletContext来构建一个空的WebApplicationContext,这样我们创建的HelloController就可以在@Before函数中创建并传递到MockMvcBuilders.standaloneSetup()函数中。

        -

        注意引入下面内容,让status、content、equalTo函数可用
        import static org.hamcrest.Matchers.equalTo;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
        至此已完成目标,通过Maven构建了一个空白Spring Boot项目,再通过引入web模块实现了一个简单的请求处理。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - 各大平台免费接口 -

        - - -
        - - -
        - -

        电商接口

        -

        京东获取单个商品价格接口:
        http://p.3.cn/prices/mgets?skuIds=J_商品ID&type=1
        ps:商品ID这么获取:http://item.jd.com/954086.html

        -

        物流接口

        -

        快递接口:
        http://www.kuaidi100.com/query?type=快递公司代号&postid=快递单号
        ps:快递公司编码:申通=”shentong” EMS=”ems” 顺丰=”shunfeng” 圆通=”yuantong” 中通=”zhongtong” 韵达=”yunda” 天天=”tiantian” 汇通=”huitongkuaidi” 全峰=”quanfengkuaidi” 德邦=”debangwuliu” 宅急送=”zhaijisong”

        -

        谷歌接口

        -

        FeedXml转json接口:
        http://ajax.googleapis.com/ajax/services/feed/load?q=Feed地址&v=1.0
        备选参数:callback:&callback=foo就会在json外面嵌套foo({})方便做jsonp使用。
        备选参数:n:返回多少条记录。

        -

        天气接口

        -

        百度接口:
        http://api.map.baidu.com/telematics/v3/weather?location=嘉兴&output=json&ak=5slgyqGDENN7Sy7pw29IUvrZ
        location:城市名或经纬度 ak:开发者密钥 output:默认xml

        -

        气象局接口:
        http://m.weather.com.cn/data/101010100.html

        -

        音乐接口

        -

        虾米接口:
        http://kuang.xiami.com/app/nineteen/search/key/歌曲名称/diandian/1/page/歌曲当前页?_=当前毫秒&callback=getXiamiData

        -

        QQ空间音乐接口:
        http://qzone-music.qq.com/fcg-bin/cgi_playlist_xml.fcg?uin=QQ号码&json=1&g_tk=1916754934

        -

        QQ空间收藏音乐接口:
        http://qzone-music.qq.com/fcg-bin/fcg_music_fav_getinfo.fcg?dirinfo=0&dirid=1&uin=QQ号&p=0.519638272547262&g_tk=1284234856

        -

        多米音乐接口:
        http://v5.pc.duomi.com/search-ajaxsearch-searchall?kw=关键字&pi=页码&pz=每页音乐数

        -

        soso接口:
        http://cgi.music.soso.com/fcgi-bin/fcg_search_xmldata.q?source=10&w=关键字&perpage=1&ie=utf-8

        -

        视频接口

        -

        土豆接口:
        http://api.tudou.com/v3/gw?method=album.item.get&appKey=Appkey&format=json&albumId=视频剧集ID&pageNo=当前页&pageSize=每页显示

        -

        地图接口

        -

        阿里云根据地区名获取经纬度接口:
        http://gc.ditu.aliyun.com/geocoding?a=苏州市
        参数解释: 纬度,经度type 001 (100代表道路,010代表POI,001代表门址,111可以同时显示前三项)

        -

        阿里云根据经纬度获取地区名接口:
        http://gc.ditu.aliyun.com/regeocoding?l=39.938133,116.395739&type=001

        -

        IP接口

        -

        新浪接口(ip值为空的时候 获取本地的):
        http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=218.4.255.255

        -

        淘宝接口:
        http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42

        -

        手机信息查询接口

        -

        淘宝网接口:
        http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=手机号

        -

        拍拍接口:
        http://virtual.paipai.com/extinfo/GetMobileProductInfo?mobile=手机号&amount=10000&callname=getPhoneNumInfoExtCallback 用例

        -

        百付宝接口:
        https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=手机号

        -

        115接口:
        http://cz.115.com/?ct=index&ac=get_mobile_local&callback=jsonp1333962541001&mobile=手机号

        -

        有道接口:
        http://www.youdao.com/smartresult-xml/search.s?jsFlag=true&type=mobile&q=手机号

        -

        手机在线接口
        http://api.showji.com/Locating/www.showji.com.aspx?m=手机号&output=json&callback=querycallback

        -

        视频信息接口

        -

        优酷:
        http://v.youku.com/player/getPlayList/VideoIDS/视频ID
        (比如 http://v.youku.com/v_show/id_XNTQxNzc4ODg0.html的ID就是XNTQxNzc4ODg0)

        -

        翻译、词典接口

        -

        腾讯:
        http://dict.qq.com/dict?q=词语

        -

        腾讯的部分接口

        -

        获取QQ昵称和用户头像:
        http://r.qzone.qq.com/cgi-bin/user/cgi_personal_card?uin=QQ

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - 如何使用GoEasy实现web实时推送 -

        - - -
        - - -
        - -

        之前项目需要做一个推送功能,最开始我没有想过用第三方推送服务。想着可以用已知技术方式完成,例如定时到服务器看看是否有新的消息,有的话,就读取下来并显示,但是这种方式很浪费客户以及服务器的资源,当然这种方式在我们项目里是不可取的。再后来我在网上搜了一些,说是可以用web socket实现我的功能,但是我在网上查了一下使用方式,看了一上午一头雾水。即使我可以一周两周内用websocket实现我的推送,那我又拿什么来保证我自己写的推送程序的到达率和速度呢?维护成本一定也会随着增加!况且我们也不允许花太多开发成本在这个项目上!

        -

        经过上面的一番周折后,我跟项目组提出使用第三方的推送服务,原因很简单,第三方推送服务可以满足我们的需求,缩短我们的开发测试维护成本,术业有专攻,它们在推送方面更有优势,服务质量也有保证!经过几番对比后,我们最终决定使用了GoEasy推送。 它真正的从根本上解决了我们的问题!对于他们的服务质量很满意,注册成功后,你可以获得他们的联系方式,问题处理得很及时,不像有些公司的客服,发封邮件好几天都没有任何信息!从而也解决了我们的后顾之忧!

        -

        GoEasy实现向特定用户群推送的原理:
        知道了他们的推送原理,可以更加方便我们了解他们的服务,以及理解我们写的代码。其实原理很简单,只需要确定哪些用户需要接收信息,然后让这些用户都订阅一个相同的channel(频道)。 然后再往这个平台上推送消息即可!所有关键在于channel,channel一致,则可以接收到信息,否则收不到!

        -

        对于订阅必须要的信息有:Appkey, channel
        对于推送必须要的信息有:Appkey, channel, content

        -

        废话不多说,直接进入正题,如何实现:

        -

        从GoEasy获取appkey
        appkey是验证用户的有效性的唯一标识。
        注册账号: GoEasy官网:https://goeasy.io
        用注册好的账号登录到GoEasy的后台管理系统,创建您自己应用(application).
        Application创建好之后系统会自动为您生成appkey
        系统会生成两个keys,一个Super key和一个Subscribe key;它们的区别在于前者既可以订阅又可以推送,但后者只能用于订阅。

        -

        用GoEasy实现订阅(接收)的实例

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <script type="text/javascript" src="https://cdn.goeasy.io/goeasy.js"></script>     
        <script type="text/javascript">
        var goEasy = new GoEasy({appkey: 'your appkey'});
        goEasy.subscribe({
        channel: 'your_channel',
        onMessage: function(message){
        alert('接收到消息:'+message.content);//拿到了信息之后,你可以做你任何想做的事
        }
        });
        </script>

        -

        有了这几行代码后,只要保证网络畅通的情况下,页面会自动弹出你从任何平台上推送的信息。

        -

        用GoEasy的三种方式实现推送及接收
        目前GoEasy支持三种推送方式: Java后台推送(它们有提供JAVA SDK和 maven远程仓库), JS推送,RestAPI推送(有了RestAPI,我们就可以用PHP, .NET, Ruby…来推送信息了,很方便)

        -

        说了这么多,来我们看一下怎么用GoEasy的三种方式分别实现推送吧.

        -

        用GoEasy SDK推送
        引入GoEasy SDK
        方式一,直接在goeasy的官网上进行下载
        GoEasy SDK下载链接:http://maven.goeasy.io/service/local/artifact/maven/redirect?r=releases&g=io.goeasy&a=goeasy-sdk&v=LATEST&e=jar
        方式二,用maven远程库直接导入到项目中,下面是GoEasy远程maven库的配置

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        <repository>
        <id>goeasy</id>
        <name>goeasy</name>
        <url>http://maven.goeasy.io/content/repositories/releases/</url>
        </repository>

        <dependency>
        <groupId>io.goeasy</groupId>
        <artifactId>goeasy-sdk</artifactId>
        <version>0.3.1</version>
        </dependency>

        -

        实例化GoEasy对象并推送
        GoEasy goEasy = new GoEasy(“your appkey”);
        goEasy.publish(‘your_channel’, ‘First message’);
        JavaScript推送
        引入goeasy.js

        1
        <script type="text/javascript" src="https://cdn.goeasy.io/goeasy.js"></script>

        -

        实例化Goeasy对象,并用publish函数进行推送

        1
        2
        3
        4
        5
        6
        7
        <script type="text/javascript">
        var goEasy = new GoEasy({appkey: 'your appkey'});
        goEasy. publish ({
        channel: 'your_channel',
        message: 'Second message!'
        });
        </script>

        -

        用RestAPI进行推送
        URL: https://goeasy.io/goeasy/publish
        Method: post
        参数:appkey, channel, content
        例如:https://goeasy.io/goeasy/publish?appkey={your_appkey}&channel={your_channel}&content={your_message}
        GoEasy官网:https://goeasy.io
        快速入门:https://goeasy.io/www/started.jsp
        文档下载:https://goeasy.io/www/docs.jsp

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - 汽车之家配置管理系统AutoCMS -

        - - -
        - - -
        - -

        Break Wang
        Be slow to promise and quick to perform.

        -

        Email
        Twitter
        Google+
        汽车之家配置管理系统AutoCMS

        -

        介绍

        -

        作者介绍

        -

        本文作者是王显宝wangxianbao@autohome.com.cn,主要负责AutoCMS的开发工作和缓存平台的运维工作,擅长python自动化运维,分布式缓存和分布式文件系统应用管理。

        -

        团队介绍

        -

        我们是汽车之家运维团队,是汽车之家技术部里最为核心的团队,由op和dev共同组成。我们的目标是为汽车之家集团打造一个高性能,高可扩展,低成本,并且稳定可靠的网站基础设施平台。

        -

        团队技术博客地址为 http://autohomeops.github.io/

        -

        联系方式:可以通过邮件或者在官方技术博客留言跟我们交流。

        -

        大纲

        -
          -
        1. 前言
        2. -
        3. AutoCMS使用方法介绍
        4. -
        5. 系统架构说明
        6. -
        7. 配置案例
        8. -
        9. 小结
          1.前言
        10. -
        -

        AutoCMS 是汽车之家目前正在使用的统一配置管理工具,本文将详细介绍该系统的使用方法和架构实现。 先来回顾下汽车之家软件部署和配置文件管理的经历的几个阶段:

        -

        原始阶段(依靠手工部署,wiki制定规范)
        最初阶段软件部署和配置文件管理完全依赖人力完成。部署新业务,运维根据研发测试环境的软件版本,去官网下载软件包,在服务器上编译安装,然后根据研发需求,手工配置文件,启动服务。该阶段暴露出诸多问题:
        软件包的来源没有强制规范,带来了很大的安全隐患。
        新业务上线或者业务紧急扩容,运维投入大量人力来完成繁重的体力劳动。
        部署目录标准化依赖wiki规范维护,人力部署的时候经常会出现问题
        半自动化阶段(依赖人力打包,yum方式部署)
        随着运维自动化的推行,搭建自己的yum源,自己打包来规范软件版本来源和部署目录。需要批量部署的时候,逐台登录服务器执行yum install 安装相关的软件包即可,该阶段已经节省了很大的人力,不过依然需要逐台登录服务器去修改配置文件才能够满足上线需求。配置文件变更前的备份和故障后的恢复依然需要人力维护。
        自动化阶段
        现在正在使用的管理系统AutoCMS,实现页面化管理软件部署和配置文件,实现快速批量管理软件。
        下面将详细介绍一下我们正在使用的AutoCMS。

        -

        2.AutoCMS使用方法介绍

        -

        AutoCMS 是基于puppet实现的软件部署和配置文件管理的一套系统,避免手动安装部署软件,提供可靠的软件标准化部署,配置文件和基础服务的管理服务。 本系统主要实现以下几点功能:

        -

        批量部署软件包
        页面管理软件的配置文件变更,支持备份和回滚
        多环境部署,开发,测试,线上环境相互隔离
        灰度推送配置
        远程执行安全的命令
        基本的统计功能
        使用者操作流程如下:

        -

        创建主机组

        -

        主机组是需要批量部署同一个软件或者下发相同配置的主机的集合,创建主机组的同时需要关联一下配置模块,该配置模块是提前写好的puppet模块。

        -

        添加主机

        -

        将需要部署该模块的主机加入该组内(本系统中主机的输入源是cmdb接口)

        -

        选择环境

        -

        根据需求选择该主机的环境,各个环境的配置数据独立存储,彼此不受影响。

        -

        配置部署参数

        -

        根据配置模块不同,编写配置文件,生成不同的配置页面,业务运维只要在页面上选择相应的参数,puppet会根据这些参数生成相应的配置文件。

        -

        推送配置

        -

        业务运维再前端页面配置完成后,返回主机组管理页面,选择主机并进行推送配置,便会触发相关主机的puppet agent 运行,部署相应的软件和配置。

        -

        3.系统架构说明

        -

        AutoCMS使用django作为前端框架,后端部署程序主要基于puppet实现,通过puppet的enc和report功能实现完整的部署和配置逻辑,整体架构如下:

        -

        简要分析下:

        -

        前端配置页面
        由于各个软件配置选项不同,所以前端的软件参数配置页面要采用配置文件动态生成。
        数据存储
        设计初期,我们在mysql和MongoDB之间选择了MongoDB,主要出于一下几点考虑:
        Schema-less,json风格存储:由于配置数据根据软件的不同,其数据不能依靠key-value能够存放的,每个软件的配置项可能会有自定义的结构,所以采用了json格式来处理,而MongoDB的bson恰好能够满足需求,省去了在mysql中频繁使用外键。此外,json格式数据易于掌握和理解,存储的数据一目了然。

        -

        CRUD方便快捷,支持范围查询和正则查询,还支持支持upsert选项(如果不存在则插入)。

        -

        与其他nosql产品相比,我们对mongodb的持久化和高可用方案更加熟悉。

        -

        部署实施层

        -

        该部分是通过puppet模块实现,模块中引用的参数通过自定义facter或者使用enc来读取配置存储的MongoDB获得,

        -

        多实例部署的实现:
        一台主机的多实例部署通过create_resources函数实现,例如一台主机需要配置多个tomcat实例,数据会是如下结构:

        -

        多环境分离实现: 本系统中puppet代码部分有git作为版本控制,通过git自身的多分支的特性来做puppet代码层面的环境分离。根据需求来执行不同分支的puppet代码即可。
        4.配置案例

        -

        下面以tomcat自动部署模块为例,进行详细介绍:

        -

        根据部署需求,设计出配置数参数的存储结构
        填写前端布局的配置文件,自动生成前端配置页面。

        -

        编写puppet自动部署模块。

        -

        编写enc转换程序
        以上准备工作做好之后,系统上线,运维可以创建主机组,关联tomcat模块,将需要批量部署的主机加入该组内,并进行参数配置
        配置完成后,进行灰度发布验证没问题后,进行全量推送,观察状态,配置完成。

        -
          -
        1. 小结
        2. -
        -

        随着云时代的到来,很多服务器可以定制镜像实现所需的运行环境,不过随着业务规模的扩大,服务器的个性化配置会越来越多,镜像的维护工作也会逐步增加,使用AutoCMS可以灵活的解决这个问题。把各自的配置需求写成配置模块,并把服务器分组,批量推送下去,可以节省运维人员很多体力劳动。让运维人员将精力投入到更有价值的工作中。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - dubbox -

        - - -
        - - -
        - -

        https://github.com/dangdangdotcom/dubbox
        Dubbox now means Dubbo eXtensions. If you know java, javax and dubbo, you know what dubbox is :)
        Dubbox adds features like RESTful remoting, Kyro/FST serialization, etc to the popular dubbo service framework. It’s been used by several projects of dangdang.com, which is one of the major e-commerce companies in China.
        主要贡献者

        -

        沈理 当当网 shenli@dangdang.com
        王宇轩 当当网 wangyuxuan@dangdang.com
        马金凯 韩都衣舍 majinkai@handu.com
        Dylan 独立开发者 dinguangx@163.com
        Kangfoo 独立开发者
        讨论QQ群:258792161 (不限于dubbox,包括SOA设计、互联网技术等等兴趣交流)
        Dubbox当前的主要功能

        -

        支持REST风格远程调用(HTTP + JSON/XML):基于非常成熟的JBoss RestEasy框架,在dubbo中实现了REST风格(HTTP + JSON/XML)的远程调用,以显著简化企业内部的跨语言交互,同时显著简化企业对外的Open API、无线API甚至AJAX服务端等等的开发。事实上,这个REST调用也使得Dubbo可以对当今特别流行的“微服务”架构提供基础性支持。 另外,REST调用也达到了比较高的性能,在基准测试下,HTTP + JSON与Dubbo 2.x默认的RPC协议(即TCP + Hessian2二进制序列化)之间只有1.5倍左右的差距,详见文档中的基准测试报告。
        支持基于Kryo和FST的Java高效序列化实现:基于当今比较知名的Kryo和FST高性能序列化库,为Dubbo默认的RPC协议添加新的序列化实现,并优化调整了其序列化体系,比较显著的提高了Dubbo RPC的性能,详见文档中的基准测试报告。
        支持基于Jackson的JSON序列化:基于业界应用最广泛的Jackson序列化库,为Dubbo默认的RPC协议添加新的JSON序列化实现。
        支持基于嵌入式Tomcat的HTTP remoting体系:基于嵌入式tomcat实现dubbo的HTTP remoting体系(即dubbo-remoting-http),用以逐步取代Dubbo中旧版本的嵌入式Jetty,可以显著的提高REST等的远程调用性能,并将Servlet API的支持从2.5升级到3.1。(注:除了REST,dubbo中的WebServices、Hessian、HTTP Invoker等协议都基于这个HTTP remoting体系)。
        升级Spring:将dubbo中Spring由2.x升级到目前最常用的3.x版本,减少版本冲突带来的麻烦。
        升级ZooKeeper客户端:将dubbo中的zookeeper客户端升级到最新的版本,以修正老版本中包含的bug。
        支持完全基于Java代码的Dubbo配置:基于Spring的Java Config,实现完全无XML的纯Java代码方式来配置dubbo
        调整Demo应用:暂时将dubbo的demo应用调整并改写以主要演示REST功能、Dubbo协议的新序列化方式、基于Java代码的Spring配置等等。
        修正了dubbo的bug 包括配置、序列化、管理界面等等的bug。
        注:dubbox和dubbo 2.x是兼容的,没有改变dubbo的任何已有的功能和配置方式(除了升级了spring之类的版本)
        文档资料

        -

        在Dubbo中开发REST风格的远程调用(RESTful Remoting)
        在Dubbo中使用高效的Java序列化(Kryo和FST)
        使用JavaConfig方式配置dubbox
        Dubbo Jackson序列化使用说明
        Demo应用简单运行指南
        Dubbox@InfoQ
        Dubbox Wiki (由社区志愿者自由编辑的)
        版本

        -

        详见:https://github.com/dangdangdotcom/dubbox/releases
        dubbox-2.8.0:主要支持REST风格远程调用、支持Kryo和FST序列化、升级了Spring和Zookeeper客户端、调整了demo应用等等
        dubbox-2.8.1:主要支持基于嵌入式tomcat的http-remoting,优化了REST客户端性能,在REST中支持限制服务端接纳的最大HTTP连接数等等
        dubbox-2.8.2:
        支持REST中的HTTP logging,包括HTTP header的字段和HTTP body中的消息体,方便调试、日志纪录等等
        提供辅助类便于REST的中文处理
        改变使用@Reference annotation配置时的异常处理方式,即当用annotation配置时,过去dubbo在启动期间不抛出依赖服务找不到的异常,而是在具体调用时抛出NPE,这与用XML配置时的行为不一致。
        较大的充实了Dubbo REST的文档
        dubbox-2.8.3:
        在REST中支持dubbo统一的方式用bean validation annotation作参数校验(沈理)
        在RpcContext上支持获取底层协议的Request/Response(沈理)
        支持采用Spring的Java Config方式配置dubbo(马金凯)
        在Dubbo协议中支持基于Jackson的json序列化(Dylan)
        在Spring AOP代理过的对象上支持dubbo annotation配置(Dylan)
        修正Dubbo管理界面中没有consumer时出现空指针异常(马金凯)
        修正@Reference annotation中protocol设置不起作用的bug(沈理)
        修正@Reference annotation放在setter方法上即会出错的bug(Dylan)
        依赖

        -

        从dubbox-2.8.4开始,所有依赖库的使用方式将和dubbo原来的一样:即如果要使用REST、Kyro、FST、Jackson等功能,需要用户自行手工添加相关的依赖。例如:
        REST风格远程调用

        -
        org.jboss.resteasy
        resteasy-jaxrs
        3.0.7.Final


        org.jboss.resteasy
        resteasy-client
        3.0.7.Final


        javax.validation
        validation-api
        1.0.0.GA
        - - -
        org.jboss.resteasy
        resteasy-jackson-provider
        3.0.7.Final
        - - -
        org.jboss.resteasy
        resteasy-jaxb-provider
        3.0.7.Final
        - - -
        org.jboss.resteasy
        resteasy-netty
        3.0.7.Final
        - - -
        org.jboss.resteasy
        resteasy-jdk-http
        3.0.7.Final
        - - -


        org.apache.tomcat.embed
        tomcat-embed-core
        8.0.11

        -


        org.apache.tomcat.embed
        tomcat-embed-logging-juli
        8.0.11

        Kyro序列化

        -


        com.esotericsoftware.kryo
        kryo
        2.24.0

        -


        de.javakaffee
        kryo-serializers
        0.26

        FST序列化

        -


        de.ruedigermoeller
        fst
        1.55

        Jackson序列化

        -


        com.fasterxml.jackson.core
        jackson-core
        2.3.3

        -


        com.fasterxml.jackson.core
        jackson-databind
        2.3.3

        FAQ(暂存)

        -

        Dubbox需要什么版本的JDK?

        -

        目前最好在JDK 1.7以上运行
        Dubbo REST的服务能和Dubbo注册中心、监控中心集成吗?

        -

        可以的,而且是自动集成的,也就是你在dubbo中开发的所有REST服务都会自动注册到服务册中心和监控中心,可以通过它们做管理。
        但是,只有当REST的消费端也是基于dubbo的时候,注册中心中的许多服务治理操作才能完全起作用。而如果消费端是非dubbo的,自然不受注册中心管理,所以其中很多操作是不会对消费端起作用的。
        Dubbo REST中如何实现负载均衡和容错(failover)?

        -

        如果dubbo REST的消费端也是dubbo的,则Dubbo REST和其他dubbo远程调用协议基本完全一样,由dubbo框架透明的在消费端做load balance、failover等等。
        如果dubbo REST的消费端是非dubbo的,甚至是非java的,则最好配置服务提供端的软负载均衡机制,目前可考虑用LVS、HAProxy、 Nginx等等对HTTP请求做负载均衡。
        JAX-RS中重载的方法能够映射到同一URL地址吗?

        -

        http://stackoverflow.com/questions/17196766/can-resteasy-choose-method-based-on-query-params
        JAX-RS中作POST的方法能够接收多个参数吗?

        -

        http://stackoverflow.com/questions/5553218/jax-rs-post-multiple-objects

        - - -
        - - - -
        - -
        - - - - - - - - - - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/page/4/index.html b/page/4/index.html deleted file mode 100644 index ef20e5f..0000000 --- a/page/4/index.html +++ /dev/null @@ -1,1284 +0,0 @@ - - - - - - - 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - -
        - - - -
        - - - - -
        - - -

        - Venus----唯品会分布式框架 -

        - - -
        - - -
        - -

        http://wiki.hexnova.com/display/Venus/HOME

        -

        HOME

        -

        附件:1
        被Struct添加,被Struct最后更新于2015-Aug-26 (查看更改)
        Venus(New Version:3.2.14) 是什么?

        -

        它是由(Venus service framework)+服务路由产品(Venus-Bus)+服务注册中心(Venus-Registry) 组合而成,提供远程服务。它着 开发简单、高性能、高并发能力 的服务端框架。
        客户端与服务端之间的通讯对开发人员完全透明
        他跟目前我们经常用到的框架:axis、CXF、Hessian WebService、Jboss Remoting等框架类似。
        系统功能目标:
        提供高性能的服务通讯框架
        具备性能监控(可以清晰的看到每个服务执行的时间、过长可以通过监控告警出去)
        具备流量控制(每个服务每个时刻的调用次数、每天的峰值情况、)
        访问控制(服务的授权控制)
        提供可选择性服务数据缓存(cache支持,key采用表达式,由框架提供缓存支持,而不需要编写任何cache相关的代码)
        提供框架进行再次研发能力,提供interceptor、validator等接口。
        提供高性能的服务总线(Venus-Bus),能够轻易接入高性能的服务总线。(Venus-Bus项目支持,针对该venus的协议,以后接入服务总线轻而易举)
        开发方面:
        服务接口定义清晰(接口、参数、校验、以及服务鉴权)
        自动生成接口文档,以方便阅读接口声明
        客户端框架快速开发、提供多种语言版本的客户端
        提供3种服务调用方式(同步、异步、回调)
        服务端框架提供多种协议提供服务,而不需要做额外的开发
        语言支持情况
        目前客户端SDK暂时只有java、PHP语言版本
        服务端面向java语言
        java 语言开发例子
        演讲稿下载

        -

        Venus演讲稿.pdf
        开发人员的关注点

        -
          -
        1. 如何服务化
        2. -
        -

        采用接口与实现分离,服务接口是一种契约,他与我们开发web Service类似。
        java开发语言:采用对程序员友好的接口申明形式,开发人员不需要关心客户端与服务端之间的传输协议。
        其他语言:可以通过该框架提供自定义协议或者Http协议(http协议即将在2.1.0版本release出来)进行交互

        -
          -
        1. 服务接口定制
        2. -
        -

        定义服务接口
        接口参数命名
        定义参数校验规则
        Java语言服务接口尽量不要依赖其他项目. 接口层面只需要接口相关的参数对象类与服务类
        异常定义

        -
          -
        1. 接口参数校验

          -
        2. -
        3. 提供3种交互方式

          -

          请求应答模式:普通的request、response,一般用于接口有返回值
          异步请求模式:通常用于接口无返回值,客户端并不关心服务器的处理结果,也不用关心服务器处理多少时间
          异步回调模式:接口无返回值,处理通常消化大量时间,需要服务端通知处理结果的业务接口
          源代码:

          -
        4. -
        -

        demo 的svn 地址:svn://svn.hexnova.com/venus/venus-helloworld/trunk
        框架svn地址: svn://svn.hexnova.com/venus/venus-framework/trunk
        svn的用户名:guest
        svn密码:guest
        Maven Repository

        -

        hexnova-openhttp://maven.hexnova.com/nexus/content/groups/hexnova-opentruenevertruealways
        目前版本情况:

        -

        trunk是最新开发版本,会不停有新东西进入
        稳定版本: 3.2.14
        最新版本:3.2.14
        版本发布Blog地址: http://wiki.hexnova.com/pages/viewrecentblogposts.action?key=Venus
        开发人员与blog

        -

        日志

        -

        日志: Venus 3.2.12 Release 创建:
        Struct
        2014-Sep-05
        Venus
        日志: Venus 3.2.10 Release 创建:
        Struct
        2014-Jun-25
        Venus
        日志: Venus 3.2.3 Released 创建:
        Struct
        2014-Feb-19
        Venus
        日志: Venus 3.0.9 Released 创建:
        Struct
        2013-Dec-25
        Venus
        日志: Venus 3.0.6 Released 创建:
        Struct
        2013-Nov-22
        Venus
        日志: Venus 3.0.4 Released 创建:
        Struct
        2013-Nov-14
        Venus
        日志: Venus 3.0.3 Released 创建:
        Struct
        2013-Oct-25
        Venus
        日志: Venus 3.0.2 Released 创建:
        Struct
        2013-Oct-21
        Venus
        日志: Venus 3.0.1 Released 创建:
        Struct
        2013-Oct-14
        Venus
        日志: Venus 2.3.0 Released 创建:
        Struct
        2012-Oct-08
        Venus
        日志: Venus 2.2.7 Released 创建:
        Struct
        2012-Sep-24
        Venus
        日志: Venus 2.2.6 Released 创建:
        Struct
        2012-Jun-28
        Venus
        日志: Venus 2.2.3 Released 创建:
        Struct
        2012-May-21
        Venus
        日志: Venus 2.0.4 Released 创建:
        Struct
        2012-Mar-31
        Venus
        日志: Venus 2.0.3 Released 创建:
        Struct
        2012-Mar-23
        Venus
        日志: Venus 2.0.1 Released 创建:
        Struct
        2012-Jan-02
        Venus
        日志: Venus 1.3.0 Released 创建:
        Struct
        2011-Dec-07
        Venus
        日志: Venus 1.2.0 Released 创建:
        Struct
        2011-Dec-01
        Venus
        日志: Venus 1.1.0 Released 创建:
        Struct
        2011-Nov-28
        Venus

        -

        研发人员列表

        -

        昵称 角色 职责 开源社区 目前供职于
        Struct Member 负责架构设计、通信框架研发、服务框架研发 Hexnova 上海汽车工业集团
        Daisy Member 对象数据序列化、服务接口数据校验、服务框架研发 Hexnova

        -

        Sunng Member 服务框架研发 Hexnova

        -

        Yuanjian Yi Member
        PHP客户端开发 Hexnova
        上海由你网络科技有限公司
        huawei Member
        服务注册中心 Hexnova

        -

        样例:

        -

        简单的接口例子:HelloService.java
        HelloService接口例子
        package com.meidusa.venus.hello.api;

        -

        import com.meidusa.venus.annotations.Endpoint;
        import com.meidusa.venus.annotations.Param;
        import com.meidusa.venus.annotations.Service;
        import com.meidusa.venus.notify.InvocationListener;

        -

        /**

        -
          -
        • Service framework的 HelloService 接口例子.

        • -
        • 支持3种调用方式:

        • -
        • 请求应答模式:普通的request、response,一般用于接口有返回值
        • -
        • 异步请求模式:通常用于接口无返回值,客户端并不关心服务器的处理结果,也不用关心服务器处理多少时间
        • -
        • 异步回调模式:接口无返回值,处理通常消化大量时间,需要服务端通知处理结果的业务接口

        • * -
        • @author Struct
          -/
          @Service(name=”HelloService”,version=1)
          publicinterface HelloService {

          -

          /**

          -
            -
          • 无返回结果的服务调用,支持回调方式,该服务在通讯层面上为异步调用
          • -
          • @param name
          • -
          • @param invocationListener 客户端的回调接口
            */
            @Endpoint(name=”sayHelloWithCallbak”)
            publicabstract void sayHello(@Param(name=”name”) String name,
            @Param(name="callback") InvocationListener<Hello> invocationListener);
            -
            /**
          • -
          • 无返回结果的服务调用,支持同步或者异步调用,
          • -
          • 该接口申明:同步,并且接口申明异常
          • -
          • @param name
            */
            @Endpoint(name=”sayHello”,async=false)
            publicabstract void sayHello(@Param(name=”name”) String name) throws HelloNotFoundException;

            -

            /**

            -
          • -
          • 无返回结果的服务调用,支持同步或者异步调用,无异常申明
          • -
          • @param name
            */
            @Endpoint(name=”sayAsyncHello”,async=true)
            publicabstract void sayAsyncHello(@Param(name=”name”) String name);
          • -
          -
        • -
        -
        /**
        - * 有返回结果的服务调用,该接口只能支持同步调用
        - * @param name
        - * @return
        - */
        -@Endpoint(name="getHello",timeWait=10000)
        -publicabstract Hello getHello(@Param(name="name") String name);
        -

        }
        客户端TestCase编写
        客户端TestCase
        package com.meidusa.venus.hello.client;

        -

        import java.util.concurrent.CountDownLatch;

        -

        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.test.context.ContextConfiguration;
        import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

        -

        import com.meidusa.venus.exception.CodedException;
        import com.meidusa.venus.hello.api.Hello;
        import com.meidusa.venus.hello.api.HelloNotFoundException;
        import com.meidusa.venus.hello.api.HelloService;
        import com.meidusa.venus.notify.InvocationListener;

        -

        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(locations=”classpath:/applicationContext-helloworld-client.xml”)
        public class TestHelloService {

        -
        @Autowired
        -private HelloService helloService;
        -
        -@Test
        -public void saySync(){
        -    System.out.println(helloService.getHello("jack"));
        -}
        -
        -@Test
        -public void testSyncWithException(){
        -    try {
        -        helloService.sayHello("jack");
        -    } catch (HelloNotFoundException e) {
        -        System.out.println("throw an user defined HelloNotFoundException");
        -    }
        -}
        -
        -@Test
        -public void testAsync(){
        -    helloService.sayAsyncHello("jack");
        -}
        -
        -@Test
        -public void testCallback() throws Exception{
        -    //为了让回调完成,采用countDownLatch计数器方式,避免testcase主线程运行完成而回调未结束的问题
        -

        final CountDownLatch latch = new CountDownLatch(1);

        -
                      //在正常的使用的代码中这个类需要单实例,避免过多的callback listener导致内存问题
        -                  InvocationListener<Hello> listener = new InvocationListener<Hello>() {
        -        public void callback(Hello myobject) {
        -            System.out.println(" async call back result="+myobject);
        -            latch.countDown();
        -        }
        -
        -        @Override
        -        public void onException(Exception e) {
        -            if(e instanceof CodedException){
        -                CodedException exception = (CodedException) e;
        -                System.out.println(" async call back error:"+exception.getErrorCode()+",message="+exception.getMessage());
        -            }else{
        -                System.out.println(" async call back message="+e.getMessage());
        -            }
        -            latch.countDown();
        -
        -        }
        -    };
        -
        -    helloService.sayHello("jack",listener);
        -    latch.await();
        -}
        -

        }
        服务端的实现
        服务端对HelloService的简单实现
        package com.meidusa.venus.hello.impl;

        -

        import java.math.BigDecimal;
        import java.util.HashMap;
        import java.util.Map;

        -

        import com.meidusa.venus.hello.api.Hello;
        import com.meidusa.venus.hello.api.HelloNotFoundException;
        import com.meidusa.venus.hello.api.HelloService;
        import com.meidusa.venus.notify.InvocationListener;

        -

        public class DefaultHelloService implements HelloService {
        privateString greeting;
        publicString getGreeting() {
        return greeting;
        }

        -
        public void setGreeting(String greeting) {
        -    this.greeting = greeting;
        -}
        -public Hello getHello(String name) {
        -    Hello hello = new Hello();
        -    hello.setName(name);
        -    hello.setGreeting(greeting);
        -    Map<String,Object> map = new HashMap<String,Object>();
        -    hello.setMap(map);
        -    map.put("1", 1);
        -    map.put("2", newLong(2));
        -    map.put("3", newInteger(3));
        -    hello.setBigDecimal(new BigDecimal("1.341241233412"));
        -    return hello;
        -}
        -
        -public void sayHello(String name)  throws HelloNotFoundException {
        -    thrownew HelloNotFoundException(name +" not found");
        -}
        -
        -@Override
        -public void sayAsyncHello(String name) {
        -    System.out.println("method sayAsyncHello invoked");
        -}
        -
        -public void sayHello(String name,
        -        InvocationListener<Hello> invocationListener) {
        -    Hello hello = new Hello();
        -    hello.setName(name);
        -    hello.setGreeting(greeting);
        -    Map<String,Object> map = new HashMap<String,Object>();
        -    hello.setMap(map);
        -    map.put("1", 1);
        -    map.put("2", newLong(2));
        -    map.put("3", newInteger(3));
        -
        -    if(invocationListener != null){
        -        invocationListener.callback(hello);
        -    }
        -
        -}
        -

        }
        最近的更新

        -

        Go语言客户端 go-venus-plugin
        commented by deephex
        Jan 22
        Go语言客户端 go-venus-plugin
        updated by Struct
        (view change)
        Jan 15

        -
          -
        1. 各种客户端简单使用
          updated by Struct
          (view change)
          Jan 15
          go-venus-plugin.zip
          attached by Struct
          Jan 15
          go-venus-plugin.zip
          attached by Struct
          Jan 15
          JAVA语言客户端
          commented by 匿名用户
          2015-Oct-14
          HOME
          commented by wangzhenjun
          2015-Oct-08
          HOME
          updated by Struct
          (view change)
          2015-Aug-26
          JAVA语言客户端
          commented by 匿名用户
          2015-Aug-10
          后端处理线程设置
          updated by Struct
          (view change)
          2015-Jul-29
          . Venus Http协议
          updated by Struct
          (view change)
          2015-Jun-29
          venus-http-adaptor-3.2.13-distribution.zip
          attached by Struct
          2015-Jun-29
          JAVA语言客户端
          updated by Struct
          (view change)
          2015-Jun-11
          HOME
          commented by zhuo
          2015-Feb-14
          Venus 3.2.12 Release
          commented by sunstar_s
          2015-Jan-14
          Venus Eclipse Plugin发布
          commented by 匿名用户
          2015-Jan-10
          Venus Eclipse Plugin发布
          commented by 匿名用户
          2015-Jan-05
          HOME
          commented by 匿名用户
          2014-Nov-06
          HOME
          commented by 匿名用户
          2014-Nov-05
          Venus 3.2.12 Release
          created by Struct
          2014-Sep-05
          More
        2. -
        -

        Navigate space

        -
          -
        1. 各种客户端简单使用
        2. -
        3. 高级使用指南
        4. -
        5. Architecture
        6. -
        7. 协议以及交互序列图介绍
        8. -
        9. 性能测试工具–Service-benchmark
        10. -
        11. 性能测试(单客户端)
        12. -
        13. 极限测试(多客户端)
          FAQ
          Venus Eclipse Plugin发布
          二、其他语言客户端
          子页面 (10)

          -

          隐藏子页面 | 页面重排
          页面: 1. 各种客户端简单使用
          页面: 2. 高级使用指南
          页面: 3. Architecture
          页面: 4. 协议以及交互序列图介绍
          页面: 5. 性能测试工具–Service-benchmark
          页面: 6. 性能测试(单客户端)
          页面: 7. 极限测试(多客户端)
          页面: FAQ
          页面: Venus Eclipse Plugin发布
          页面: 二、其他语言客户端

          -
        14. -
        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - 新浪开源分布式框架--motan -

        - - -
        - - -
        - -

        开源地址 https://github.com/weibocom/motan/wiki/zh_quickstart

        -

        zh_quickstart

        -

        axb edited this page 2 days ago · 2 revisions
        Pages 8

        -

        Documents

        -

        Overview
        Quick Start
        User Guide(Preparing)
        Develop Guide(Preparing)
        FAQ
        中文文档

        -

        概述
        快速入门
        用户指南
        开发指南(准备中)
        常见问题
        Clone this wiki locally

        -

        Clone in Desktop
        快速入门
        简单调用示例
        集群调用示例
        使用Consul作为注册中心
        使用ZooKeeper作为注册中心
        快速入门中会给出一些基本使用场景下的配置方式,更详细的使用文档请参考用户指南.
        如果要执行快速入门介绍中的例子,你需要:
        JDK 1.7或更高版本。
        java依赖管理工具,如Maven或Gradle。
        简单调用示例

        -

        在pom中增加依赖

        -
        com.weibo
        motan-core
        0.0.1


        com.weibo
        motan-transport-netty
        0.0.1
        - - -


        com.weibo
        motan-springsupport
        0.0.1

        -


        org.springframework
        spring-context
        4.2.4.RELEASE

        为调用方和服务方创建接口。
        src/main/java/quickstart/FooService.java
        package quickstart;

        -

        publicinterfaceFooService {
        public String hello(String name);
        }
        实现服务方逻辑。
        src/main/java/quickstart/FooServiceImpl.java
        package quickstart;

        -

        import org.springframework.context.ApplicationContext;
        import org.springframework.context.support.ClassPathXmlApplicationContext;

        -

        publicclassFooServiceImplimplementsFooService {

        -
        public String hello(String name) {
        -    System.out.println(name +" invoked rpc service");
        -    return"hello "+ name;
        -}
        -
        -publicstaticvoidmain(String[] args) throws InterruptedException {
        -    ApplicationContext applicationContext =new ClassPathXmlApplicationContext("classpath:motan_server.xml");
        -    System.out.println("server start...");
        -}
        -

        }
        src/main/resources/motan_server.xml
        <?xml version=”1.0” encoding=”UTF-8”?>

        - - -
        <!-- service implemention bean -->
        -<beanid="serviceImpl"class="quickstart.FooServiceImpl" />
        -<!-- exporting service by motan -->
        -<motan:serviceinterface="quickstart.FooService"ref="serviceImpl"export="8002" />
        -


        执行FooServiceImpl类中的main函数将会启动motan服务,并监听8002端口.
        实现服务调用方。
        src/main/resources/motan_client.xml
        <?xml version=”1.0” encoding=”UTF-8”?>

        - - -
        <!-- reference to the remote service -->
        -<motan:refererid="remoteService"interface="quickstart.FooService"directUrl="localhost:8002"/>
        -


        src/main/java/quickstart/Client.java
        package quickstart;

        -

        import org.springframework.context.ApplicationContext;
        import org.springframework.context.support.ClassPathXmlApplicationContext;

        -

        publicclassClient {

        -
        publicstaticvoidmain(String[] args) throws InterruptedException {
        -    ApplicationContext ctx =new ClassPathXmlApplicationContext("classpath:motan_client.xml");
        -    FooService service = (FooService) ctx.getBean("remoteService");
        -    System.out.println(service.hello("motan"));
        -}
        -

        }
        执行Client类中的main函数将执行一次远程调用,并输出结果。
        集群调用示例

        -

        在集群环境下使用motan需要依赖外部服务发现组件,目前支持consul或zookeeper。
        使用Consul作为注册中心

        -

        Consul安装与启动

        -

        安装(官方文档)

        -

        这里以linux为例

        wget https://releases.hashicorp.com/consul/0.6.4/consul_0.6.4_linux_amd64.zip
        unzip consul_0.6.4_linux_amd64.zip
        sudo mv consul /bin
        启动(官方文档)

        -

        测试环境启动:
        consul agent -dev
        ui后台 http://localhost:8500/ui
        motan-Consul配置

        -

        在server和client中添加motan-registry-consul依赖

        -


        com.weibo
        motan-registry-consul
        0.0.1

        在server和client的配置文件中分别增加consul registry定义。

        -


        在motan client及server配置改为通过registry服务发现。
        client

        server

        server程序启动后,需要显式调用心跳开关,注册到consul。
        MotanSwitcherUtil.setSwitcher(ConsulConstants.NAMING_PROCESS_HEARTBEAT_SWITCHER, true)
        进入ui后台查看服务是否正常提供调用
        启动client,调用服务
        使用ZooKeeper作为注册中心

        -

        ZooKeeper安装与启动(官方文档)

        -

        单机版安装与启动
        wget http://mirrors.cnnic.cn/apache/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz
        tar zxvf zookeeper-3.4.8.tar.gz

        -

        cd zookeeper-3.4.8/conf/
        cp zoo_sample.cfg zoo.cfg

        -

        cd ../
        sh bin/zkServer.sh start
        motan-ZooKeeper配置

        -

        在server和client中添加motan-registry-zookeeper依赖

        -


        com.weibo
        motan-registry-zookeeper
        0.0.1

        在server和client的配置文件中分别增加zookeeper registry定义。
        zookeeper为单节点

        -


        zookeeper多节点集群

        -


        在motan client及server配置改为通过registry服务发现。
        client

        -


        server

        -


        启动client,调用服务

        -
        - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - Tomcat的四种基于HTTP协议的Connector性能比较 -

        - - -
        - - -
        - -

        Tomcat 6 支持 NIO – Tomcat的四种基于HTTP协议的Connector性能比较

        -

        Tomcat从5.5版本开始,支持以下四种Connector的配置分别为:

        - - - - - - - - - -

        我们姑且把上面四种Connector按照顺序命名为 NIO, HTTP, POOL, NIOP

        -

        为了不让其他因素影响测试结果,我们只对一个很简单的jsp页面进行测试,这个页面仅仅是输出一个Hello World。假设地址是 http://tomcat1/test.jsp

        -

        我们依次对四种Connector进行测试,测试的客户端在另外一台机器上用ab命令来完成,测试命令为: ab -c 900 -n 2000http://tomcat1/test.jsp,最终的测试结果如下表所示(单位:平均每秒处理的请求数):

        -

        NIO HTTP POOL NIOP
        281 65 208 365
        666 66 110 398
        692 65 66 263
        256 63 94 459
        440 67 145 363

        -

        由这五组数据不难看出,HTTP的性能是很稳定,但是也是最差的,而这种方式就是Tomcat的默认配置。NIO方式波动很大,但没有低于280 的,NIOP是在NIO的基础上加入线程池,可能是程序处理更复杂了,因此性能不见得比NIO强;而POOL方式则波动很大,测试期间和HTTP方式一样,不时有停滞。

        -

        由于linux的内核默认限制了最大打开文件数目是1024,因此此次并发数控制在900。

        -

        尽管这一个结果在实际的网站中因为各方面因素导致,可能差别没这么大,例如受限于数据库的性能等等的问题。但对我们在部署网站应用时还是具有参考价值的。

        -

        这个可以利用apache server的 ab测试
        C:\Program Files (x86)\Apache Software Foundation\Apache2.2\bin>ab -n2000 -c100 http://localhost:8080/

        -

        或者

        -

        C:\Program Files (x86)\Apache Software Foundation\Apache2.2\bin>ab -n2000 -c100 -w http://localhost:8080/ >c:/a.html

        -

        -n 代表请求数
        -c 代表并发数
        -w 输出到

        -
        - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - solr-query -

        - - -
        - - -
        - -

        一. Query参数

        -
          -
        1. CoreQueryParam查询的参数
          1) q: 查询字符串,必须的。
          2) q.op: 覆盖schema.xml的defaultOperator(有空格时用”AND”还是用”OR”操作逻辑),一般默认指定。
          3) df: 默认的查询字段,一般默认指定。
          4) qt: query type,指定查询使用的Query Handler,默认为“standard”。
          5) wt: writer type。指定查询输出结构格式,默认为“xml”。在solrconfig.xml中定义了查询输出格式:xml、json、python、ruby、php、phps、custom。
          6) echoHandler:是否在查询结果中显示使用的Query Handler名称。
          7) echoParams:是否显示查询参数。none:不显示;explicit:只显示查询参数;all:所有,包括在solrconfig.xml定义的Query Handler参数。
          8) indent - 返回的结果是否缩进,默认关闭,用 indent=true|on 开启,一般调试json,php,phps,ruby输出才有必要用这个参数。
          9) version - 查询语法的版本,建议不使用它,由服务器指定默认值。

          -
        2. -
        3. CommonQueryParameters
          1) sort:排序,格式:sort=+[,+]„ 。
          示例:(inStock desc, price asc)表示先 “inStock” 降序, 再 “price” 升序,默认是相关性降序。。
          2) start:用于分页定义结果起始记录数,默认为0。
          3) rows:用于分页定义结果每页返回记录数,默认为10。
          4) fq:filter query。使用Filter Query可以充分利用Filter Query Cache,提高检索性能。作用:在q查询符合结果中同时是fq查询符合的,
          例如:q=mm&fq=date_time:[20081001 TO 20091031],找关键字mm,并且date_time是20081001到20091031之间的。
          5) fl:field list。指定返回结果字段。以空格“ ”或逗号“,”分隔。
          6) debugQuery:设置返回结果是否显示Debug信息。
          7) explainOther:设置当debugQuery=true时,显示其他的查询说明。
          8) defType:设置查询解析器名称。
          9) timeAllowed:设置查询超时时间。
          10) omitHeader:设置是否忽略查询结果返回头信息,默认为“false”。

          -
        4. -
        -

        二. 查询语法

        -
          -
        1. 匹配所有文档::
        2. -
        3. 强制、阻止和可选查询:
          1) Mandatory:查询结果中必须包括的(for example, only entry name containing the word make) Solr/Lucene Statement:+make, +make +up ,+make +up +kiss
          2) prohibited:(for example, all documents except those with word believe) Solr/Lucene Statement:+make +up -kiss 3) optional:Solr/Lucene Statement:+make +up kiss

          -
        4. -
        5. 布尔操作:AND、OR和NOT布尔操作(必须大写)与Mandatory、optional和prohibited相似。
          1) make AND up = +make +up :AND左右两边的操作都是mandatory
          2) make || up = make OR up=make up :OR左右两边的操作都是optional
          3) +make +up NOT kiss = +make +up –kiss
          4) make AND up OR french AND Kiss不可以达到期望的结果,因为AND两边的操作都是mandatory的。

          -
        6. -
        7. 子表达式查询(子查询):可以使用“()”构造子查询。
          For ex:(make AND up) OR (french AND Kiss)

          -
        8. -
        9. 子表达式查询中阻止查询的限制:
          For ex:make (-up):只能取得make的查询结果;要使用make (-up :)查询make或者不包括up的结果。

          -
        10. -
        11. 多字段fields查询:通过字段名加上分号的方式(fieldName:query)来进行查询
          For ex:entryNm:make AND entryId:3cdc86e8e0fb4da8ab17caed42f6760c
        12. -
        13. 通配符查询(wildCard Query):
          1) 通配符?和:“”表示匹配任意字符;“?”表示匹配出现的位置。
          For ex:ma?(ma后面的一个位置匹配),ma??(ma后面两个位置都匹配)
          2) 查询字符必须要小写:+Ma +be可以搜索到结果;+Ma +Be没有搜索结果
          3) 查询速度较慢,尤其是通配符在首位:主要原因一是需要迭代查询字段中的每个term,判断是否匹配;二是匹配上的term被加到内部的查询,当terms数量达到1024的时候,查询会失败。
          4) Solr中默认通配符不能出现在首位(可以修改QueryParser,设置 setAllowLeadingWildcard为true)
          5) set setAllowLeadingWildcard to true.
        14. -
        15. 模糊查询、相似查询:不是精确的查询,通过对查询的字段进行重新插入、删除和转换来取得得分较高的查询解决(由Levenstein Distance Algorithm算法支持)。
          1) 一般模糊查询:for ex:make-believ~
          2) 门槛模糊查询:对模糊查询可以设置查询门槛,门槛是0~1之间的数值,门槛
          越高表面相似度越高。For ex:make-believ~0.5、make-believ~0.8、make-believ~0.9
        16. -
        17. 范围查询(Range Query):Lucene支持对数字、日期甚至文本的范围查询。结束的范围可以使用“”通配符。
          For ex:
          1) 日期范围(ISO-8601 时间GMT):sa_type:2 AND a_begin_date:[1990-01-01T00:00:00.000Z TO 1999-12-31T24:59:99.999Z]
          2) 数字:salary:[2000 TO
          ] 3) 文本:entryNm:[a TO a]

          -
        18. -
        19. 日期匹配:YEAR, MONTH, DAY, DATE (synonymous with DAY) HOUR, MINUTE, SECOND, MILLISECOND, and MILLI (synonymous with MILLISECOND)可以被标志成日期。
          For ex:
          1) r_event_date:[ TO NOW-2YEAR]:2年前的现在这个时间
          2) r_event_date:[
          TO NOW/DAY-2YEAR]:2年前前一天的这个时间

          -
        20. -
        -

        三. 函数查询(Function Query)
        函数查询 可以利用 numeric域的值 或者 与域相关的的某个特定的值的函数,来对文档进行评分。

        -
          -
        1. 使用函数查询的方法
          这里主要有三种方法可以使用函数查询,这三种s方法都是通过solr http接口的。
          1) 使用FunctionQParserPlugin。ie: q={!func}log(foo)
          2) 使用“val”内嵌方法内嵌在正常的solr查询表达式中。即,将函数查询写在 q这个参数中,这时候,我们使用“val”将函数与其他的查询加以区别。 ie:entryNm:make && val:ord(entryNm)
          3) 使用dismax中的bf参数使用明确为函数查询的参数,比如说dismax中的bf(boost function)这个参数。 注意:bf这个参数是可以接受多个函数查询的,它们之间用空格隔开,它们还可以带上权重。所以,当我们使用bf这个参数的时候,我们必须保证单个函数中是没有空格出现的,不然程序有可能会以为是两个函数。
          For ex:
          q=dismax&bf=”ord(popularity)^0.5 recip(rord(price),1,1000,1000)^0.3 2. 函数的格式(Function Query Syntax) 目前,function query 并不支持 a+b 这样的形式,我们得把它写成一个方法形式,这就是 sum(a,b).

          -
        2. -
        3. 使用函数查询注意事项
          1) 用于函数查询的field必须是被索引的;
          2) 字段不可以是多值的(multi-value)

          -
        4. -
        5. 可以利用的函数 (available function)
          1) constant:支持有小数点的常量; 例如:1.5 ;SolrQuerySyntax:val:1.5
          2) fieldvalue:这个函数将会返回numeric field的值,这个域必须是indexd的,非multiValued的。格式很简单,就是该域的名字。如果这个域中没有这样的值,那么将会返回0。
          3) ord:对于一个域,它所有的值都将会按照字典顺序排列,这个函数返回你要查询的那个特定的值在这个顺序中的排名。这个域,必须是非multiValued的,当没有值存在的时候,将返回0。例如:某个特定的域只能去三个值,“apple”、“banana”、“pear”,那么ord(“apple”)=1,ord(“banana”)=2,ord(“pear”)=3.需要注意的是,ord()这个函数,依赖于值在索引中的位置,所以当有文档被删除、或者添加的时候,ord()的值就会发生变化。当你使用MultiSearcher的时候,这个值也就是不定的了。
          4) rord:这个函数将会返回与ord相对应的倒排序的排名。 格式: rord(myIndexedField)。
          5) sum:这个函数的意思就显而易见啦,它就是表示“和”啦。格式:sum(x,1) 、sum(x,y)、 sum(sqrt(x),log(y),z,0.5)
          6) product:product(x,y,…)将会返回多个函数的乘积。格式:product(x,2)、product(x,y)
          7) div:div(x,y)表示x除以y的值,格式:div(1,x)、div(sum(x,100),max(y,1))
          8) pow:pow表示幂值。pow(x,y) =x^y。例如:pow(x,0.5) 表示开方pow(x,log(y))
          9) abs:abs(x)将返回表达式的绝对值。格式:abs(-5)、 abs(x)
          10) log:log(x)将会返回基数为10,x的对数。格式: log(x)、 log(sum(x,100))
          11) Sqrt:sqrt(x) 返回 一个数的平方根。格式:sqrt(2)、sqrt(sum(x,100))
          12) Map:如果 x>=min,且x<=max,那么map(x,min,max,target)=target.如果 x不在[min,max]这个区间内,那么map(x,min,max,target)=x. 格式:map(x,0,0,1)
          13) Scale:scale(x,minTarget,maxTarget) 这个函数将会把x的值限制在[minTarget,maxTarget]范围内。 14) query :query(subquery,default)将会返回给定subquery的分数,如果subquery与文档不匹配,那么将会返回默认值。任何的查询类型都是受支持的。可以通过引用的方式,也可以直接指定查询串。
          例子:q=product(popularity, query({!dismax v=’solr rocks’}) 将会返回popularity和通过dismax 查询得到的分数的乘积。
          q=product(popularity, query($qq)&qq={!dismax}solr rocks 跟上一个例子的效果是一样的。不过这里使用的是引用的方式
          q=product(popularity, query($qq,0.1)&qq={!dismax}solr rocks 在前一个例子的基础上又加了一个默认值。
          15) linear: inear(x,m,c)表示 mx+c ,其中m和c都是常量,x是一个变量也可以是一个函数。例如: linear(x,2,4)=2x+4.
          16) Recip:recip(x,m,a,b)=a/(m*x+b)其中,m、a、b是常量,x是变量或者一个函数。当a=b,并且x>=0的时候,这个函数的最大值是1,值的大小随着x的增大而减小。例如:recip(rord(creationDate),1,1000,1000)
          17) Max: max(x,c)将会返回一个函数和一个常量之间的最大值。 例如:max(myfield,0)

          -
        6. -
        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - mysql跨数据库操作类似与oracle的dblink -

        - - -
        - - -
        - -

        MySQL FEDERATED 存储引擎

        -
        MySQL中针对不同的功能需求提供了不同的存储引擎。所谓的存储引擎也就是MySQL下特定接口的具体实现。
        -FEDERATED是其中一个专门针对远程数据库的实现。一般情况下在本地数据库中建表会在数据库目录中生成相应的表定义文件,并同时生成相应的数据文件。
        -

        但通过FEDERATED引擎创建的表只是在本地有表定义文件,数据文件则存在于远程数据库中(这一点很重要)。
        通过这个引擎可以实现类似Oracle 下DBLINK的远程数据访问功能。
        使用show engines 命令查看数据库是否已支持FEDERATED引擎:

        -
        Support 的值有以下几个:
        -

        YES 支持并开启
        DEFAULT 支持并开启, 并且为默认引擎
        NO 不支持
        DISABLED 支持,但未开启

        -

        可以看出MyISAM为当前默认的引擎。
        使用FEDERATED建表语句如下:
        CREATE TABLE (……) ENGINE =FEDERATED CONNECTION=’mysql://[name]:[pass]@[location]:[port]/[db-name]/[table-name]’
        创建成功后就可直接在本地查询相应的远程表了。
        需要注意的几点:

        -
        1. 本地的表结构必须与远程的完全一样。
        -2.远程数据库目前仅限MySQL
        -3.不支持事务
        -4.不支持表结构修改
        -
        - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - hexo+github搭建个人博客 -

        - - -
        - - -
        - -

        hexo+github搭建个人博客

        -

        字数841 阅读543 评论12 喜欢18
        现在一个程序猿(媛)没有一个自己的博客都不好意思说自己是程序员,哈哈开玩笑的。是否有一个方法,可以让我们自己创建一个属于自己的博客,然后又不用花钱买服务器和域名,也不用自己找人去设计自己的网站呢。
        这样的好东西还真的存在,而且配置还十分简单,下面我就详细的介绍如何用hexo+github搭建自己的(酷炫)博客。
        前期准备

        -

        node.js
        如果你是windows,请戳这里
        如果你是mac,请戳这里
        git账号
        如果没有git帐号,请戳这里
        安装hexo
        npm install -g hexo
        初始化hexo
        hexo init
        npm install hexo –save
        生成静态页面至hexo\public\目录。
        hexo g
        本地启动服务
        hexo server
        这样,我们就可以在浏览器中输入http://localhost:4000/ 访问我们的博客啦(响应式的网站)。

        -

        初始界面-PC.png

        -

        初始界面-移动端.png
        虽然博客基本的已经搭好了,但是我们只能在本地访问,其他人是看不到的,下面我们通过和git绑定来实现我们想要的效果。

        -

        配置github
        新建一个仓库名(该仓库名和你的用户名对应),如我的git账户名是:coder-Yin,则我的仓库名为coder-Yin.github.io
        编辑_config.yml文件,建立与git的关联(在.yml文件的最底部)

        -

        Deployment## Docs: http://hexo.io/docs/deployment.html

        deploy:
        type: git
        repository: https://github.com/coder-Yin/coder-Yin.github.io.git
        branch: master
        然后运行
        npm install hexo-deployer-git –save
        hexo g
        hexo d
        这样你就可以在你的 coder-Yin.github.io 上看到代码已经同步到git上了。
        在浏览器中输入你的**.github..io(例如:http://coder-yin.github.io/)

        -

        访问效果.png
        每次有新的修改需要部署同步,都可以按照下面的步骤来:
        hexo clean
        hexo g
        hexo d
        如果你觉得hexo默认的主题不好看,你可以通过以下方法来修改你的主题。

        -

        下面我通过修改一个主题来给大家做个介绍:
        在git上找到你想要的主题
        我这随意找了一个,比较适合女孩子(缺点:不是自适应的)
        https://github.com/daisygao/hexo-themes-cover
        进入你的hexo目录,执行命令,拷贝主题
        git clone https://github.com/daisygao/hexo-themes-cover.git themes/cover
        拷贝完成后,你会发现你的项目下的themes下多了一个cover文件夹
        我们还需要修改_config.yml文件中的一处来应用新的主题

        -

        Extensions## Plugins: http://hexo.io/plugins/## Themes: http://hexo.io/themes/

        theme: cover
        然后我们重启服务就可以在本地看到效果了
        hexo server

        -

        hexo应用新的主题.png

        -

        注意:我们这样只是本地做了修改,git上并没有实现同步,我们需要按照上面所说的,依次执行以下命令实现部署同步:
        hexo clean
        hexo g
        hexo d
        在刷新你的http://***.github.io/ 就可以发现新的主题应用成功了,是不是很简单,快动手建立你自己的博客吧。
        最后,附上更多的hexo主题,大家可以很戳这里选择你自己喜好的主题。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - nginx内置变量大全 -

        - - -
        - - -
        - -

        在配置基于nginx服务器的网站时,必然会用到 nginx内置变量 ,下面笔者将它整理成列表,把最新版本的变量列出来,以方便做配置时查询
        nginx内置变量

        -

        内置变量存放在 ngx_http_core_module 模块中,变量的命名方式和apache 服务器变量是一致的。总而言之,这些变量代表着客户端请求头的内容,例如$http_user_agent, $http_cookie, 等等。下面是nginx支持的所有内置变量:
        $arg_name
        请求中的的参数名,即“?”后面的arg_name=arg_value形式的arg_name
        $args
        请求中的参数值
        $binary_remote_addr
        客户端地址的二进制形式, 固定长度为4个字节
        $body_bytes_sent
        传输给客户端的字节数,响应头不计算在内;这个变量和Apache的mod_log_config模块中的“%B”参数保持兼容
        $bytes_sent
        传输给客户端的字节数 (1.3.8, 1.2.5)
        $connection
        TCP连接的序列号 (1.3.8, 1.2.5)
        $connection_requests
        TCP连接当前的请求数量 (1.3.8, 1.2.5)
        $content_length
        “Content-Length” 请求头字段
        $content_type
        “Content-Type” 请求头字段
        $cookie_name
        cookie名称
        $document_root
        当前请求的文档根目录或别名
        $document_uri
        同 $uri
        $host
        优先级如下:HTTP请求行的主机名>”HOST”请求头字段>符合请求的服务器名
        $hostname
        主机名
        $http_name
        匹配任意请求头字段; 变量名中的后半部分“name”可以替换成任意请求头字段,如在配置文件中需要获取http请求头:“Accept-Language”,那么将“-”替换为下划线,大写字母替换为小写,形如:$http_accept_language即可。
        $https
        如果开启了SSL安全模式,值为“on”,否则为空字符串。
        $is_args
        如果请求中有参数,值为“?”,否则为空字符串。
        $limit_rate
        用于设置响应的速度限制,详见 limit_rate。
        $msec
        当前的Unix时间戳 (1.3.9, 1.2.6)
        $nginx_version
        nginx版本
        $pid
        工作进程的PID
        $pipe
        如果请求来自管道通信,值为“p”,否则为“.” (1.3.12, 1.2.7)
        $proxy_protocol_addr
        获取代理访问服务器的客户端地址,如果是直接访问,该值为空字符串。(1.5.12)
        $query_string
        同 $args
        $realpath_root
        当前请求的文档根目录或别名的真实路径,会将所有符号连接转换为真实路径。
        $remote_addr
        客户端地址
        $remote_port
        客户端端口
        $remote_user
        用于HTTP基础认证服务的用户名
        $request
        代表客户端的请求地址
        $request_body
        客户端的请求主体
        此变量可在location中使用,将请求主体通过proxy_pass, fastcgi_pass, uwsgi_pass, 和 scgi_pass传递给下一级的代理服务器。
        $request_body_file
        将客户端请求主体保存在临时文件中。文件处理结束后,此文件需删除。如果需要之一开启此功能,需要设置client_body_in_file_only。如果将次文件传递给后端的代理服务器,需要禁用request body,即设置proxy_pass_request_body off,fastcgi_pass_request_body off, uwsgi_pass_request_body off, or scgi_pass_request_body off 。
        $request_completion
        如果请求成功,值为”OK”,如果请求未完成或者请求不是一个范围请求的最后一部分,则为空。
        $request_filename
        当前连接请求的文件路径,由root或alias指令与URI请求生成。
        $request_length
        请求的长度 (包括请求的地址, http请求头和请求主体) (1.3.12, 1.2.7)
        $request_method
        HTTP请求方法,通常为“GET”或“POST”
        $request_time
        处理客户端请求使用的时间 (1.3.9, 1.2.6); 从读取客户端的第一个字节开始计时。
        $request_uri
        这个变量等于包含一些客户端请求参数的原始URI,它无法修改,请查看$uri更改或重写URI,不包含主机名,例如:”/cnphp/test.php?arg=freemouse”。
        $scheme
        请求使用的Web协议, “http” 或 “https”
        $sent_http_name
        可以设置任意http响应头字段; 变量名中的后半部分“name”可以替换成任意响应头字段,如需要设置响应头Content-length,那么将“-”替换为下划线,大写字母替换为小写,形如:$sent_http_content_length 4096即可。
        $server_addr
        服务器端地址,需要注意的是:为了避免访问linux系统内核,应将ip地址提前设置在配置文件中。
        $server_name
        服务器名,www.cnphp.info
        $server_port
        服务器端口
        $server_protocol
        服务器的HTTP版本, 通常为 “HTTP/1.0” 或 “HTTP/1.1”
        $status
        HTTP响应代码 (1.3.2, 1.2.2)
        $tcpinfo_rtt, $tcpinfo_rttvar, $tcpinfo_snd_cwnd, $tcpinfo_rcv_space
        客户端TCP连接的具体信息
        $time_iso8601
        服务器时间的ISO 8610格式 (1.3.12, 1.2.7)
        $time_local
        服务器时间(LOG Format 格式) (1.3.12, 1.2.7)
        $uri
        请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改,$uri不包含主机名,如”/foo/bar.html”。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - mysql主从同步 -

        - - -
        - - -
        - -

        前提条件两个数据库并且局域网能够访问并且两个数据库的版本号要一致(msyql -V 查看mysql版本号)。
        否则容易出现莫名错误不好处理。
        1:主数据库141(10.10.39.101)下面称作master
        2:从数据库142(10.10.49.90)下面称作slave

        -

        配置如下
        1:在主服务器上,设置一个从数据库的账户,使用REPLICATION SLAVE赋予权限,如:
        mysql> GRANT REPLICATION SLAVE ON . TO ‘slave001’@’10.10.49.90’ IDENTIFIED BY ‘1qazZZR@WSX’;

        -

        2:修改主数据库的配置文件my.cnf,开启BINLOG,并设置server-id的值,修改之后必须重启Mysql服务
        [mysqld]
        log-bin = mysql-bin
        server-id=141

        -

        3:之后可以得到主服务器当前二进制日志名和偏移量,这个操作的目的是为了在从数据库启动后,从这个
        点开始进行数据的恢复
        mysql> show master status\G;
        * 1. row *
        File: mysql-bin.000003
        Position: 243
        Binlog_Do_DB:
        Binlog_Ignore_DB:
        1 row in set (0.00 sec)
        4:停止对master数据库的更新然后导出需要同步的数据库,
        mysql> flush tables with read lock;
        mysqldump -h127.0.0.1 -p3306 -uroot -p zzr > /home/zzr.sql
        mysql> unlock tables;
        5:将刚才主数据备份的zzr.sql复制到从数据库,进行导入
        6:接着修改从数据库的my.cnf,增加server-id参数,指定复制使用的用户,主数据库服务器的ip,端口以及开始执
        行复制日志的文件和位置
        [mysqld]
        server-id=142
        log_bin = mysql-bin
        master-host =10.10.39.101
        master-user=slave001
        master-pass=1qazZZR@WSX
        master-port =20010
        master-connect-retry=60
        replicate-do-db =zzr,open
        7:在从服务器上,启动slave进程
        mysql> start slave;
        如果启动失败:在slave上强制执行
        mysql> change master to master_host=’master_host’, master_user=’you_user_name’, master_password=’you_master_user_password’,master_log_file=’mysqld-bin.000001’,master_port=3306, master_log_pos=98;
        (master_log_file和master_log_pos)的值你可以在服务器上运行 show master status; 来得到。
        然后 mysql> start slave;启动slave进程
        8:在从服务器进行show salve status验证
        mysql> SHOW SLAVE STATUS\G
        9:检测一下。看看是否主从同步了。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - Java操作MongoDB -

        - - -
        - - -
        - -

        MongoDB for Java】Java操作MongoDB

        -

        上一篇文章: http://www.cnblogs.com/hoojo/archive/2011/06/01/2066426.html介绍到了在MongoDB的控制台完成MongoDB的数据操作,通过前一篇文章我们对MongoDB有了全面的认识和理解。现在我们就用Java来操作MongoDB的数据。

        -

        开发环境:
        System:Windows
        IDE:eclipse、MyEclipse 8
        Database:mongoDB
        开发依赖库:
        JavaEE5、mongo-2.5.3.jar、junit-4.8.2.jar
        Email:hoojo_@126.com
        Blog:http://blog.csdn.net/IBM_hoojo
        http://hoojo.cnblogs.com/

        -

        一、准备工作

        -

        1、 首先,下载mongoDB对Java支持的驱动包
        驱动包下载地址:https://github.com/mongodb/mongo-java-driver/downloads
        mongoDB对Java的相关支持、技术:http://www.mongodb.org/display/DOCS/Java+Language+Center
        驱动源码下载:https://download.github.com/mongodb-mongo-java-driver-r2.6.1-7-g6037357.zip
        在线查看源码:https://github.com/mongodb/mongo-java-driver
        2、 下面建立一个JavaProject工程,导入下载下来的驱动包。即可在Java中使用mongoDB,目录如下:

        -

        二、Java操作MongoDB示例

        -

        在本示例之前你需要启动mongod.exe的服务,启动后,下面的程序才能顺利执行;

        -

        1、 建立SimpleTest.java,完成简单的mongoDB数据库操作
        Mongo mongo = new Mongo();
        这样就创建了一个MongoDB的数据库连接对象,它默认连接到当前机器的localhost地址,端口是27017。
        DB db = mongo.getDB(“test”);
        这样就获得了一个test的数据库,如果mongoDB中没有创建这个数据库也是可以正常运行的。如果你读过上一篇文章就知道,mongoDB可以在没有创建这个数据库的情况下,完成数据的添加操作。当添加的时候,没有这个库,mongoDB会自动创建当前数据库。
        得到了db,下一步我们要获取一个“聚集集合DBCollection”,通过db对象的getCollection方法来完成。
        DBCollection users = db.getCollection(“users”);
        这样就获得了一个DBCollection,它相当于我们数据库的“表”。
        查询所有数据
        DBCursor cur = users.find();
        while (cur.hasNext()) {
        System.out.println(cur.next());
        }

        -

        完整源码
        package com.hoo.test;

        -

        import java.net.UnknownHostException;
        import com.mongodb.DB;
        import com.mongodb.DBCollection;
        import com.mongodb.DBCursor;
        import com.mongodb.Mongo;
        import com.mongodb.MongoException;
        import com.mongodb.util.JSON;

        -

        /**

        -
          -
        • function:MongoDB 简单示例
        • -
        • @author hoojo
        • -
        • @createDate 2011-5-24 下午02:42:29
        • -
        • @file SimpleTest.java
        • -
        • @package com.hoo.test
        • -
        • @project MongoDB
        • -
        • @blog http://blog.csdn.net/IBM_hoojo
        • -
        • @email hoojo_@126.com
        • -
        • @version 1.0
          */
          publicclass SimpleTest {
        • -
        -

        publicstaticvoid main(String[] args) throws UnknownHostException, MongoException {
        Mongo mg = new Mongo();
        //查询所有的Database
        for (String name : mg.getDatabaseNames()) {
        System.out.println(“dbName: “ + name);
        }
        DB db = mg.getDB(“test”);
        //查询所有的聚集集合
        for (String name : db.getCollectionNames()) {
        System.out.println(“collectionName: “ + name);
        }
        DBCollection users = db.getCollection(“users”);
        //查询所有的数据
        DBCursor cur = users.find();
        while (cur.hasNext()) {
        System.out.println(cur.next());
        }
        System.out.println(cur.count());
        System.out.println(cur.getCursorId());
        System.out.println(JSON.serialize(cur));
        }
        }

        -

        2、 完成CRUD操作,首先建立一个MongoDB4CRUDTest.java,基本测试代码如下:
        package com.hoo.test;

        -

        import java.net.UnknownHostException;
        import java.util.ArrayList;
        import java.util.List;
        import org.bson.types.ObjectId;
        import org.junit.After;
        import org.junit.Before;
        import org.junit.Test;
        import com.mongodb.BasicDBObject;
        import com.mongodb.Bytes;
        import com.mongodb.DB;
        import com.mongodb.DBCollection;
        import com.mongodb.DBCursor;
        import com.mongodb.DBObject;
        import com.mongodb.Mongo;
        import com.mongodb.MongoException;
        import com.mongodb.QueryOperators;
        import com.mongodb.util.JSON;

        -

        /**

        -
          -
        • function:实现MongoDB的CRUD操作
        • -
        • @author hoojo
        • -
        • @createDate 2011-6-2 下午03:21:23
        • -
        • @file MongoDB4CRUDTest.java
        • -
        • @package com.hoo.test
        • -
        • @project MongoDB
        • -
        • @blog http://blog.csdn.net/IBM_hoojo
        • -
        • @email hoojo_@126.com
        • -
        • @version 1.0
          */
          publicclass MongoDB4CRUDTest {
          private Mongo mg = null;
          private DB db;
          private DBCollection users;
          @Before
          publicvoid init() {
          try {
          mg = new Mongo();
          -
          //mg = new Mongo(“localhost”, 27017);
          } catch (UnknownHostException e) {
          -    e.printStackTrace();
          -} catch (MongoException e) {
          -    e.printStackTrace();
          -}
          -
          //获取temp DB;如果默认没有创建,mongodb会自动创建
          db = mg.getDB("temp");
          -
          //获取users DBCollection;如果默认没有创建,mongodb会自动创建
          users = db.getCollection("users");
          -
          }
          @After
          publicvoid destory() {
          if (mg != null)
              mg.close();
          -mg = null;
          -db = null;
          -users = null;
          -System.gc();
          -
          }
          publicvoid print(Object o) {
          System.out.println(o);
          -
          }
          }
        • -
        -

        3、 添加操作
        在添加操作之前,我们需要写个查询方法,来查询所有的数据。代码如下:
        /**

        -
          -
        • function: 查询所有数据
        • -
        • @author hoojo
        • -
        • @createDate 2011-6-2 下午03:22:40
          */
          privatevoid queryAll() {
          print(“查询users的所有数据:”);
          //db游标
          DBCursor cur = users.find();
          while (cur.hasNext()) {
          print(cur.next());
          -
          }
          }
        • -
        -

        @Test
        publicvoid add() {
        //先查询所有数据
        queryAll();
        print(“count: “ + users.count());
        DBObject user = new BasicDBObject();
        user.put(“name”, “hoojo”);
        user.put(“age”, 24);
        //users.save(user)保存,getN()获取影响行数
        //print(users.save(user).getN());
        //扩展字段,随意添加字段,不影响现有数据
        user.put(“sex”, “男”);
        print(users.save(user).getN());
        //添加多条数据,传递Array对象
        print(users.insert(user, new BasicDBObject(“name”, “tom”)).getN());
        //添加List集合
        List list = new ArrayList();
        list.add(user);
        DBObject user2 = new BasicDBObject(“name”, “lucy”);
        user.put(“age”, 22);
        list.add(user2);
        //添加List集合
        print(users.insert(list).getN());
        //查询下数据,看看是否添加成功
        print(“count: “ + users.count());
        queryAll();
        }

        -

        4、 删除数据
        @Test
        publicvoid remove() {
        queryAll();
        print(“删除id = 4de73f7acd812d61b4626a77:” + users.remove(new BasicDBObject(“_id”, new ObjectId(“4de73f7acd812d61b4626a77”))).getN());
        print(“remove age >= 24: “ + users.remove(new BasicDBObject(“age”, new BasicDBObject(“$gte”, 24))).getN());
        }

        -

        5、 修改数据
        @Test
        publicvoid modify() {
        print(“修改:” + users.update(new BasicDBObject(“_id”, new ObjectId(“4dde25d06be7c53ffbd70906”)), new BasicDBObject(“age”, 99)).getN());
        print(“修改:” + users.update(
        new BasicDBObject(“_id”, new ObjectId(“4dde2b06feb038463ff09042”)),
        new BasicDBObject(“age”, 121),
        true,//如果数据库不存在,是否添加
        false//多条修改
        ).getN());
        print(“修改:” + users.update(
        new BasicDBObject(“name”, “haha”),
        new BasicDBObject(“name”, “dingding”),
        true,//如果数据库不存在,是否添加
        true//false只修改第一天,true如果有多条就不修改
        ).getN());
        //当数据库不存在就不修改、不添加数据,当多条数据就不修改
        //print(“修改多条:” + coll.updateMulti(new BasicDBObject(“_id”, new ObjectId(“4dde23616be7c19df07db42c”)), new BasicDBObject(“name”, “199”)));
        }

        -

        6、 查询数据
        @Test
        publicvoid query() {
        //查询所有
        //queryAll();
        //查询id = 4de73f7acd812d61b4626a77
        print(“find id = 4de73f7acd812d61b4626a77: “ + users.find(new BasicDBObject(“_id”, new ObjectId(“4de73f7acd812d61b4626a77”))).toArray());
        //查询age = 24
        print(“find age = 24: “ + users.find(new BasicDBObject(“age”, 24)).toArray());
        //查询age >= 24
        print(“find age >= 24: “ + users.find(new BasicDBObject(“age”, new BasicDBObject(“$gte”, 24))).toArray());
        print(“find age <= 24: “ + users.find(new BasicDBObject(“age”, new BasicDBObject(“$lte”, 24))).toArray());
        print(“查询age!=25:” + users.find(new BasicDBObject(“age”, new BasicDBObject(“$ne”, 25))).toArray());
        print(“查询age in 25/26/27:” + users.find(new BasicDBObject(“age”, new BasicDBObject(QueryOperators.IN, newint[] { 25, 26, 27 }))).toArray());
        print(“查询age not in 25/26/27:” + users.find(new BasicDBObject(“age”, new BasicDBObject(QueryOperators.NIN, newint[] { 25, 26, 27 }))).toArray());
        print(“查询age exists 排序:” + users.find(new BasicDBObject(“age”, new BasicDBObject(QueryOperators.EXISTS, true))).toArray());
        print(“只查询age属性:” + users.find(null, new BasicDBObject(“age”, true)).toArray());
        print(“只查属性:” + users.find(null, new BasicDBObject(“age”, true), 0, 2).toArray());
        print(“只查属性:” + users.find(null, new BasicDBObject(“age”, true), 0, 2, Bytes.QUERYOPTION_NOTIMEOUT).toArray());
        //只查询一条数据,多条去第一条
        print(“findOne: “ + users.findOne());
        print(“findOne: “ + users.findOne(new BasicDBObject(“age”, 26)));
        print(“findOne: “ + users.findOne(new BasicDBObject(“age”, 26), new BasicDBObject(“name”, true)));
        //查询修改、删除
        print(“findAndRemove 查询age=25的数据,并且删除: “ + users.findAndRemove(new BasicDBObject(“age”, 25)));
        //查询age=26的数据,并且修改name的值为Abc
        print(“findAndModify: “ + users.findAndModify(new BasicDBObject(“age”, 26), new BasicDBObject(“name”, “Abc”)));
        print(“findAndModify: “ + users.findAndModify(
        new BasicDBObject(“age”, 28), //查询age=28的数据
        new BasicDBObject(“name”, true), //查询name属性
        new BasicDBObject(“age”, true), //按照age排序
        false, //是否删除,true表示删除
        new BasicDBObject(“name”, “Abc”), //修改的值,将name修改成Abc
        true,
        true));
        queryAll();
        }
        mongoDB不支持联合查询、子查询,这需要我们自己在程序中完成。将查询的结果集在Java查询中进行需要的过滤即可。

        -

        7、 其他操作
        publicvoid testOthers() {
        DBObject user = new BasicDBObject();
        user.put(“name”, “hoojo”);
        user.put(“age”, 24);
        //JSON 对象转换
        print(“serialize: “ + JSON.serialize(user));
        //反序列化
        print(“parse: “ + JSON.parse(“{ \”name\” : \”hoojo\” , \”age\” : 24}”));
        print(“判断temp Collection是否存在: “ + db.collectionExists(“temp”));
        //如果不存在就创建
        if (!db.collectionExists(“temp”)) {
        DBObject options = new BasicDBObject();
        options.put(“size”, 20);
        options.put(“capped”, 20);
        options.put(“max”, 20);
        print(db.createCollection(“account”, options));
        }
        //设置db为只读
        db.setReadOnly(true);
        //只读不能写入数据
        db.getCollection(“test”).save(user);
        }

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - memcache安装及配置 -

        - - -
        - - -
        - -

        CentOS6.4安装Memcached与使用例子

        -

        1、安装libevent
        wget http://monkey.org/~provos/libevent-1.4.14b-stable.tar.gz
        tar zxvf libevent-1.4.14b-stable.tar.gz
        cd libevent-1.4.14b-stable
        ./configure –prefix=/usr/local/libevent/
        make && make install
        ln -s /usr/local/libevent/lib/libevent-1.4.so.2 /lib/libevent-1.4.so.2
        /**
        1) 如果共享库文件安装到了/lib或/usr/lib目录下, 那么需执行一下ldconfig命令

        -

        ldconfig命令的用途, 主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下, 搜索出可共享的动态链接库(格式如lib.so), 进而创建出动态装入程序(ld.so)所需的连接和缓存文件. 缓存文件默认为/etc/ld.so.cache, 此文件保存已排好序的动态链接库名字列表.

        -

        2) 如果共享库文件安装到了/usr/local/lib(很多开源的共享库都会安装到该目录下)或其它”非/lib或/usr/lib”目录下, 那么在执行ldconfig命令前, 还要把新共享库目录加入到共享库配置文件/etc/ld.so.conf中, 如下:

        -

        cat /etc/ld.so.conf

        include ld.so.conf.d/*.conf

        -

        echo “/usr/local/lib” >> /etc/ld.so.conf

        ldconfig

        3) 如果共享库文件安装到了其它”非/lib或/usr/lib” 目录下, 但是又不想在/etc/ld.so.conf中加路径(或者是没有权限加路径). 那可以export一个全局变量LD_LIBRARY_PATH, 然后运行程序的时候就会去这个目录中找共享库.

        -

        LD_LIBRARY_PATH的意思是告诉loader在哪些目录中可以找到共享库. 可以设置多个搜索目录, 这些目录之间用冒号分隔开. 比如安装了一个mysql到/usr/local/mysql目录下, 其中有一大堆库文件在/usr/local/mysql/lib下面, 则可以在.bashrc或.bash_profile或shell里加入以下语句即可:

        -

        export LD_LIBRARY_PATH=/usr/local/mysql/lib:$LD_LIBRARY_PATH

        -

        一般来讲这只是一种临时的解决方案, 在没有权限或临时需要的时候使用.

        -

        4)如果程序需要的库文件比系统目前存在的村文件版本低,可以做一个链接
        比如:
        error while loading shared libraries: libncurses.so.4: cannot open shared
        object file: No such file or directory

        -

        ls /usr/lib/libncu*
        /usr/lib/libncurses.a /usr/lib/libncurses.so.5
        /usr/lib/libncurses.so /usr/lib/libncurses.so.5.3

        -

        可见虽然没有libncurses.so.4,但有libncurses.so.5,是可以向下兼容的
        建一个链接就好了
        ln -s /usr/lib/libncurses.so.5.3 /usr/lib/libncurses.so.4

        -

        **/

        -

        2、安装Memcached
        wget http://www.danga.com/memcached/dist/memcached-1.2.0.tar.gz
        tar zxvf memcached-1.4.15.tar.gz
        cd memcached-1.4.15
        ./configure –prefix=/usr/local/memcached/ –with-libevent=/usr/local/libevent/
        make && make install
        3、启动Memcached
        /usr/local/memcached/bin/memcached -d -m 64 -u root -l 127.0.0.100 -p 11211 -c 128 -P /tmp/memcached.pid
        4、为了方便管理,写个SHELL脚本吧。

        -

        vi /etc/rc.d/init.d/memcached

        -
        #!/bin/sh
        -#
        -# memcached: MemCached Daemon
        -#
        -# chkconfig: - 90 25
        -# description: MemCached Daemon
        -#
        -# Source function library.
        -. /etc/rc.d/init.d/functions
        -. /etc/sysconfig/network
        -#[ ${NETWORKING} = "no" ] && exit 0
        -#[ -r /etc/sysconfig/dund ] || exit 0
        -#. /etc/sysconfig/dund
        -#[ -z "$DUNDARGS" ] && exit 0
        -start()
        -{
        -echo -n $"Starting memcached: "
        -daemon $MEMCACHED -u daemon -d -m 64 -l 127.0.0.100 -p 11211 -c 128 -P /tmp/memcached.pid
        -echo
        -}
        -stop()
        -{
        -echo -n $"Shutting down memcached: "
        -killproc memcached
        -echo
        -}
        -MEMCACHED="/usr/local/memcached/bin/memcached"
        -[ -f $MEMCACHED ] || exit 1
        -# See how we were called.
        -case "$1" in
        -start)
        -start
        -;;
        -stop)
        -stop
        -;;
        -restart)
        -stop
        -sleep 3
        -start
        -;;
        -*)
        -echo $"Usage: $0 {start|stop|restart}"
        -exit 1
        -esac
        -exit 0
        -

        5、添加Memcached开机启动
        cd /etc/rc.d/init.d/
        chmod 777 memcached
        chkconfig –add memcached
        chkconfig –level 235 memcached on
        chkconfig –list | grep memcached
        6、Memcached使用
        [root@www.111cn.net] service memcached start
        [root@www.111cn.net] service memcached stop
        [root@www.111cn.net] service memcached restart

        - - -
        - - - -
        - -
        - - - - - - - - - - - - - -
        -
        -
        - -
        -
        -
        - - - - - - - - - - - - - - - - - - -
        - - \ No newline at end of file diff --git a/page/5/index.html b/page/5/index.html deleted file mode 100644 index 9d721e4..0000000 --- a/page/5/index.html +++ /dev/null @@ -1,1254 +0,0 @@ - - - - - - - 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
        -
        -
        -
        - -
        - -
        -
        - - -
        - -
        - - - -
        - - - - -
        - - -

        - redis的图形化工具 -

        - - -
        - - -
        - -

        redis的图形化工具

        -

        2015-10-29 Rails365 运维帮

        -
          -
        1. 介绍
        2. -
        -

        本篇会介绍几个关于redis的图形化的监控工具和管理工具。

        -
          -
        1. redis-stat
        2. -
        -

        redis-stat(https://github.com/junegunn/redis-stat)提供终端和web端的监控页面,它安装和使用起来很简单。

        -

        安装只需要一条指令。

        -

        $ gem install redis-stat

        -

        运行更简单。

        -

        $ redis-stat

        -

        效果图如下:

        -

        也可以以server的形式运行,默认情况下是监听在63790端口。

        -

        $ redis-stat –server

        -

        还可以以后台进程的形式开启服务。

        -

        redis-stat –server –daemon

        -

        效果图如下:

        -
          -
        1. redis-browser
        2. -
        -

        redis-browser(https://github.com/monterail/redis-browser)是redis的web端的图形化管理工具。利用它可以查看和管理redis的数据,界面很简洁,安装和使用这个工具也比较简单,而且它还能和rails应用结合在一起。

        -

        安装。

        -

        $ gem install redis-browser

        -

        要使用也只是需要一条命令。

        -

        $ redis-browser

        -

        效果图如下:

        -
          -
        1. redmon
        2. -
        -

        redmon也是一个监控redis运行情况的页面监控工具。

        -

        安装。

        -

        $ gem install redmon

        -

        使用。

        -

        $ redmon

        -

        效果图如下:

        -

        完结。

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - maven常用命令 -

        - - -
        - - -
        - -

        maven 全球中心仓库 http://mirrors.ibiblio.org/maven2/org/apache/

        -

        Sonatype Nexus 搭建Maven 私服

        -

        mvn -v 查看版本
        mvn compile 编译
        mvn test 测试
        mvn clean 清楚编译文件
        mvn package 把项目打成jar包
        mvn install 导入
        mvn archetype:generate 更换本地仓库后下载maven骨架

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - 远程免密码copy文件 -

        - - -
        - - -
        - -

        1.先安装expect
        yum -y install expect
        2.脚本内容:
        cat auto_svn.sh

        -

        #!/bin/bash
        passwd=’123456’
        /usr/bin/expect <<-EOF
        set time 30
        spawn ssh -p18330 root@192.168.10.22
        expect {
        yes/no” { send “yes\r”; exp_continue }
        password:” { send “$passwd\r” }
        }
        expect “#”
        send “cd /home/trunk\r”
        expect “
        #”
        send “svn up\r”
        expect “*#”
        send “exit\r”
        interact
        expect eof
        EOF

        -

        expect的简单用法及举例

        -

        #!/usr/bin/expect -f
        set password 123456

        -

        #download
        spawn scp root@192.168.1.218:/root/a.wmv /home/yangyz/
        set timeout 300
        expect “root@192.168.1.218’s password:”
        set timeout 300
        send “$passwordr”
        set timeout 300
        send “exitr”
        expect eof

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - javascript----闭包 -

        - - -
        - - -
        - -

        -

        阅读: 9165
        函数作为返回值

        -

        高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
        我们来实现一个对Array的求和。通常情况下,求和的函数是这样定义的:
        functionsum(arr) {return arr.reduce(function(x, y) {return x + y;
        });
        }

        -

        sum([1, 2, 3, 4, 5]); // 15
        但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!
        functionlazy_sum(arr) {var sum = function() {return arr.reduce(function(x, y) {return x + y;
        });
        }
        return sum;
        }
        当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
        var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
        调用函数f时,才真正计算求和的结果:
        f(); //15
        在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
        请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
        var f1 = lazy_sum([1, 2, 3, 4, 5]);
        var f2 = lazy_sum([1, 2, 3, 4, 5]);
        f1 === f2; // false
        f1()和f2()的调用结果互不影响。
        闭包

        -

        注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
        另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:
        functioncount() {var arr = [];
        for (var i=1; i<=3; i++) {
        arr.push(function() {return i * i;
        });
        }
        return arr;
        }

        -

        var results = count();
        var f1 = results[0];
        var f2 = results[1];
        var f3 = results[2];
        在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。
        你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
        f1(); //16
        f2(); //16
        f3(); //16
        全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。
        返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
        如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
        functioncount() {var arr = [];
        for (var i=1; i<=3; i++) {
        arr.push((function(n) {returnfunction() {return n * n;
        }
        })(i));
        }
        return arr;
        }

        -

        var results = count();
        var f1 = results[0];
        var f2 = results[1];
        var f3 = results[2];

        -

        f1(); // 1
        f2(); // 4
        f3(); // 9
        注意这里用了一个“创建一个匿名函数并立刻执行”的语法:
        (function(x) {return x x;
        })(3); // 9
        理论上讲,创建一个匿名函数并立刻执行可以这么写:
        function(x) {return x
        x } (3);
        但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:
        (function(x) {return x x }) (3);
        通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:
        (function(x) {return x
        x;
        })(3);
        说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?
        当然不是!闭包有非常强大的功能。举个栗子:
        在面向对象的程序设计语言里,比如Java和C++,要在对象内部封装一个私有变量,可以用private修饰一个成员变量。
        在没有class机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器:
        ‘use strict’;

        -

        functioncreate_counter(initial) {var x = initial || 0;
        return {
        inc: function() {
        x += 1;
        return x;
        }
        }
        }
        它用起来像这样:
        var c1 = create_counter();
        c1.inc(); // 1
        c1.inc(); // 2
        c1.inc(); // 3var c2 = create_counter(10);
        c2.inc(); // 11
        c2.inc(); // 12
        c2.inc(); // 13
        在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
        闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2和pow3:
        functionmake_pow(n) {returnfunction(x) {return Math.pow(x, n);
        }
        }

        -

        // 创建两个新函数:var pow2 = make_pow(2);
        var pow3 = make_pow(3);

        -

        pow2(5); // 25
        pow3(7); // 343
        脑洞大开

        -

        很久很久以前,有个叫阿隆佐·邱奇的帅哥,发现只需要用函数,就可以用计算机实现运算,而不需要0、1、2、3这些数字和+、-、*、/这些符号。
        JavaScript支持函数,所以可以用JavaScript用函数来写这些计算。来试试:
        ‘use strict’;

        -

        // 定义数字0:
        var zero = function (f) {
        return function (x) {
        return x;
        }
        };

        -

        // 定义数字1:
        var one = function (f) {
        return function (x) {
        return f(x);
        }
        };

        -

        // 定义加法:
        function add(n, m) {
        return function (f) {
        return function (x) {
        return m(f)(n(f)(x));
        }
        }
        }

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - javascript---正则表达式 -

        - - -
        - - -
        - -

        gExp

        -

        阅读: 1981
        字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在。比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦,而且代码难以复用。
        正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
        所以我们判断一个字符串是否是合法的Email的方法是:
        创建一个匹配Email的正则表达式;
        用该正则表达式去匹配用户的输入来判断是否合法。
        因为正则表达式也是用字符串表示的,所以,我们要首先了解如何用字符来描述字符。
        在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字,所以:
        ‘00\d’可以匹配’007’,但无法匹配’00A’;
        ‘\d\d\d’可以匹配’010’;
        ‘\w\w’可以匹配’js’;
        .可以匹配任意字符,所以:
        ‘js.’可以匹配’jsp’、’jss’、’js!’等等。
        要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:
        来看一个复杂的例子:\d{3}\s+\d{3,8}。
        我们来从左到右解读一下:
        \d{3}表示匹配3个数字,例如’010’;
        \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配’ ‘,’\t\t’等;
        \d{3,8}表示3-8个数字,例如’1234567’。
        综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。
        如果要匹配’010-12345’这样的号码呢?由于’-‘是特殊字符,在正则表达式中,要用’\’转义,所以,上面的正则是\d{3}-\d{3,8}。
        但是,仍然无法匹配’010 - 12345’,因为带有空格。所以我们需要更复杂的匹配方式。
        进阶

        -

        要做更精确地匹配,可以用[]表示范围,比如:
        [0-9a-zA-Z_]可以匹配一个数字、字母或者下划线;
        [0-9a-zA-Z_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如’a100’,’0Z’,’js2015’等等;
        [a-zA-Z\
        \$][0-9a-zA-Z_\$]*可以匹配由字母或下划线、$开头,后接任意个由一个数字、字母或者下划线、$组成的字符串,也就是JavaScript允许的变量名;
        [a-zA-Z_\$][0-9a-zA-Z_\$]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
        A|B可以匹配A或B,所以[J|j]ava[S|s]cript可以匹配’JavaScript’、’Javascript’、’javaScript’或者’javascript’。
        ^表示行的开头,^\d表示必须以数字开头。
        $表示行的结束,\d$表示必须以数字结束。
        你可能注意到了,js也可以匹配’jsp’,但是加上^js$就变成了整行匹配,就只能匹配’js’了。
        RegExp

        -

        有了准备知识,我们就可以在JavaScript中使用正则表达式了。
        JavaScript有两种方式创建一个正则表达式:
        第一种方式是直接通过/正则表达式/写出来,第二种方式是通过new RegExp(‘正则表达式’)创建一个RegExp对象。
        两种写法是一样的:
        var re1 = /ABC-001/;
        var re2 = new RegExp(‘ABC\-001’);

        -

        re1; // /ABC-001/
        re2; // /ABC-001/
        注意,如果使用第二种写法,因为字符串的转义问题,字符串的两个\实际上是一个\。
        先看看如何判断正则表达式是否匹配:
        var re = /^\d{3}-\d{3,8}$/;
        re.test(‘010-12345’); //true
        re.test(‘010-1234x’); //false
        re.test(‘010 12345’); //false
        RegExp对象的test()方法用于测试给定的字符串是否符合条件。
        切分字符串

        -

        用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
        ‘a b c’.split(‘ ‘); // [‘a’, ‘b’, ‘’, ‘’, ‘c’]
        嗯,无法识别连续的空格,用正则表达式试试:
        ‘a b c’.split(/\s+/); // [‘a’, ‘b’, ‘c’]
        无论多少个空格都可以正常分割。加入,试试:
        ‘a,b, c d’.split(/[\s\,]+/); // [‘a’, ‘b’, ‘c’, ‘d’]
        再加入;试试:
        ‘a,b;; c d’.split(/[\s\,\;]+/); // [‘a’, ‘b’, ‘c’, ‘d’]
        如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。
        分组

        -

        除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。比如:
        ^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
        var re = /^(\d{3})-(\d{3,8})$/;
        re.exec(‘010-12345’); // [‘010-12345’, ‘010’, ‘12345’]
        re.exec(‘010 12345’); // null
        如果正则表达式中定义了组,就可以在RegExp对象上用exec()方法提取出子串来。
        exec()方法在匹配成功后,会返回一个Array,第一个元素始终是原始字符串本身,后面的字符串表示匹配成功的子串。
        exec()方法在匹配失败时返回null。
        提取子串非常有用。来看一个更凶残的例子:
        var re = /^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/;
        re.exec(‘19:05:30’); // [‘19:05:30’, ‘19’, ‘05’, ‘30’]
        这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
        var re = /^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$/;
        对于’2-30’,’4-31’这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。
        贪婪匹配

        -

        需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0:
        var re = /^(\d+)(0)$/;
        re.exec(‘102300’); // [‘102300’, ‘102300’, ‘’]
        由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0
        只能匹配空字符串了。
        必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:
        var re = /^(\d+?)(0*)$/;
        re.exec(‘102300’); // [‘102300’, ‘1023’, ‘00’]
        全局搜索

        -

        JavaScript的正则表达式还有几个特殊的标志,最常用的是g,表示全局匹配:
        var r1 = /test/g;
        // 等价于:var r2 = new RegExp(‘test’, ‘g’);
        全局匹配可以多次执行exec()方法来搜索一个匹配的字符串。当我们指定g标志后,每次运行exec(),正则表达式本身会更新lastIndex属性,表示上次匹配到的最后索引:
        var s = ‘JavaScript, VBScript, JScript and ECMAScript’;
        var re=/[a-zA-Z]+Script/g;

        -

        // 使用全局匹配:
        re.exec(s); // [‘JavaScript’]
        re.lastIndex; // 10

        -

        re.exec(s); // [‘VBScript’]
        re.lastIndex; // 20

        -

        re.exec(s); // [‘JScript’]
        re.lastIndex; // 29

        -

        re.exec(s); // [‘ECMAScript’]
        re.lastIndex; // 44

        -

        re.exec(s); // null,直到结束仍没有匹配到
        全局匹配类似搜索,因此不能使用/^…$/,那样只会最多匹配一次。
        正则表达式还可以指定i标志,表示忽略大小写,m标志,表示执行多行匹配。
        小结

        -

        正则表达式非常强大,要在短短的一节里讲完是不可能的。要讲清楚正则的所有内容,可以写一本厚厚的书了。如果你经常遇到正则表达式的问题,你可能需要一本正则表达式的参考书。
        练习

        -

        请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email:
        ‘use strict’;
        // 测试:
        var
        i,
        success = true,
        should_pass = [‘someone@gmail.com’, ‘bill.gates@microsoft.com’, ‘tom@voyager.org’, ‘bob2015@163.com’],
        should_fail = [‘test#gmail.com’, ‘bill@microsoft’, ‘bill%gates@ms.com’, ‘@voyager.org’];
        for (i = 0; i < should_pass.length; i++) {
        if (!re.test(should_pass[i])) {
        alert(‘测试失败: ‘ + should_pass[i]);
        success = false;
        break;
        }
        }
        for (i = 0; i < should_fail.length; i++) {
        if (re.test(should_fail[i])) {
        alert(‘测试失败: ‘ + should_fail[i]);
        success = false;
        break;
        }
        }
        if (success) {
        alert(‘测试通过!’);
        }
        版本二可以验证并提取出带名字的Email地址:
        ‘use strict’;
        // 测试:
        var r = re.exec(‘ tom@voyager.org’);
        if (r === null || r.toString() !== [‘ tom@voyager.org’, ‘Tom Paris’, ‘tom@voyager.org’].toString()) {
        alert(‘测试失败!’);
        }
        else {
        alert(‘测试成功!’);
        }

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - javascript---正则表达式 -

        - - -
        - - -
        - -

        gExp

        -

        阅读: 1981
        字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在。比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦,而且代码难以复用。
        正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
        所以我们判断一个字符串是否是合法的Email的方法是:
        创建一个匹配Email的正则表达式;
        用该正则表达式去匹配用户的输入来判断是否合法。
        因为正则表达式也是用字符串表示的,所以,我们要首先了解如何用字符来描述字符。
        在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字,所以:
        ‘00\d’可以匹配’007’,但无法匹配’00A’;
        ‘\d\d\d’可以匹配’010’;
        ‘\w\w’可以匹配’js’;
        .可以匹配任意字符,所以:
        ‘js.’可以匹配’jsp’、’jss’、’js!’等等。
        要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:
        来看一个复杂的例子:\d{3}\s+\d{3,8}。
        我们来从左到右解读一下:
        \d{3}表示匹配3个数字,例如’010’;
        \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配’ ‘,’\t\t’等;
        \d{3,8}表示3-8个数字,例如’1234567’。
        综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。
        如果要匹配’010-12345’这样的号码呢?由于’-‘是特殊字符,在正则表达式中,要用’\’转义,所以,上面的正则是\d{3}-\d{3,8}。
        但是,仍然无法匹配’010 - 12345’,因为带有空格。所以我们需要更复杂的匹配方式。
        进阶

        -

        要做更精确地匹配,可以用[]表示范围,比如:
        [0-9a-zA-Z_]可以匹配一个数字、字母或者下划线;
        [0-9a-zA-Z_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如’a100’,’0Z’,’js2015’等等;
        [a-zA-Z\
        \$][0-9a-zA-Z_\$]*可以匹配由字母或下划线、$开头,后接任意个由一个数字、字母或者下划线、$组成的字符串,也就是JavaScript允许的变量名;
        [a-zA-Z_\$][0-9a-zA-Z_\$]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
        A|B可以匹配A或B,所以[J|j]ava[S|s]cript可以匹配’JavaScript’、’Javascript’、’javaScript’或者’javascript’。
        ^表示行的开头,^\d表示必须以数字开头。
        $表示行的结束,\d$表示必须以数字结束。
        你可能注意到了,js也可以匹配’jsp’,但是加上^js$就变成了整行匹配,就只能匹配’js’了。
        RegExp

        -

        有了准备知识,我们就可以在JavaScript中使用正则表达式了。
        JavaScript有两种方式创建一个正则表达式:
        第一种方式是直接通过/正则表达式/写出来,第二种方式是通过new RegExp(‘正则表达式’)创建一个RegExp对象。
        两种写法是一样的:
        var re1 = /ABC-001/;
        var re2 = new RegExp(‘ABC\-001’);

        -

        re1; // /ABC-001/
        re2; // /ABC-001/
        注意,如果使用第二种写法,因为字符串的转义问题,字符串的两个\实际上是一个\。
        先看看如何判断正则表达式是否匹配:
        var re = /^\d{3}-\d{3,8}$/;
        re.test(‘010-12345’); //true
        re.test(‘010-1234x’); //false
        re.test(‘010 12345’); //false
        RegExp对象的test()方法用于测试给定的字符串是否符合条件。
        切分字符串

        -

        用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
        ‘a b c’.split(‘ ‘); // [‘a’, ‘b’, ‘’, ‘’, ‘c’]
        嗯,无法识别连续的空格,用正则表达式试试:
        ‘a b c’.split(/\s+/); // [‘a’, ‘b’, ‘c’]
        无论多少个空格都可以正常分割。加入,试试:
        ‘a,b, c d’.split(/[\s\,]+/); // [‘a’, ‘b’, ‘c’, ‘d’]
        再加入;试试:
        ‘a,b;; c d’.split(/[\s\,\;]+/); // [‘a’, ‘b’, ‘c’, ‘d’]
        如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。
        分组

        -

        除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。比如:
        ^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
        var re = /^(\d{3})-(\d{3,8})$/;
        re.exec(‘010-12345’); // [‘010-12345’, ‘010’, ‘12345’]
        re.exec(‘010 12345’); // null
        如果正则表达式中定义了组,就可以在RegExp对象上用exec()方法提取出子串来。
        exec()方法在匹配成功后,会返回一个Array,第一个元素始终是原始字符串本身,后面的字符串表示匹配成功的子串。
        exec()方法在匹配失败时返回null。
        提取子串非常有用。来看一个更凶残的例子:
        var re = /^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/;
        re.exec(‘19:05:30’); // [‘19:05:30’, ‘19’, ‘05’, ‘30’]
        这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:
        var re = /^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$/;
        对于’2-30’,’4-31’这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。
        贪婪匹配

        -

        需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0:
        var re = /^(\d+)(0)$/;
        re.exec(‘102300’); // [‘102300’, ‘102300’, ‘’]
        由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0
        只能匹配空字符串了。
        必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:
        var re = /^(\d+?)(0*)$/;
        re.exec(‘102300’); // [‘102300’, ‘1023’, ‘00’]
        全局搜索

        -

        JavaScript的正则表达式还有几个特殊的标志,最常用的是g,表示全局匹配:
        var r1 = /test/g;
        // 等价于:var r2 = new RegExp(‘test’, ‘g’);
        全局匹配可以多次执行exec()方法来搜索一个匹配的字符串。当我们指定g标志后,每次运行exec(),正则表达式本身会更新lastIndex属性,表示上次匹配到的最后索引:
        var s = ‘JavaScript, VBScript, JScript and ECMAScript’;
        var re=/[a-zA-Z]+Script/g;

        -

        // 使用全局匹配:
        re.exec(s); // [‘JavaScript’]
        re.lastIndex; // 10

        -

        re.exec(s); // [‘VBScript’]
        re.lastIndex; // 20

        -

        re.exec(s); // [‘JScript’]
        re.lastIndex; // 29

        -

        re.exec(s); // [‘ECMAScript’]
        re.lastIndex; // 44

        -

        re.exec(s); // null,直到结束仍没有匹配到
        全局匹配类似搜索,因此不能使用/^…$/,那样只会最多匹配一次。
        正则表达式还可以指定i标志,表示忽略大小写,m标志,表示执行多行匹配。
        小结

        -

        正则表达式非常强大,要在短短的一节里讲完是不可能的。要讲清楚正则的所有内容,可以写一本厚厚的书了。如果你经常遇到正则表达式的问题,你可能需要一本正则表达式的参考书。
        练习

        -

        请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email:
        ‘use strict’;
        // 测试:
        var
        i,
        success = true,
        should_pass = [‘someone@gmail.com’, ‘bill.gates@microsoft.com’, ‘tom@voyager.org’, ‘bob2015@163.com’],
        should_fail = [‘test#gmail.com’, ‘bill@microsoft’, ‘bill%gates@ms.com’, ‘@voyager.org’];
        for (i = 0; i < should_pass.length; i++) {
        if (!re.test(should_pass[i])) {
        alert(‘测试失败: ‘ + should_pass[i]);
        success = false;
        break;
        }
        }
        for (i = 0; i < should_fail.length; i++) {
        if (re.test(should_fail[i])) {
        alert(‘测试失败: ‘ + should_fail[i]);
        success = false;
        break;
        }
        }
        if (success) {
        alert(‘测试通过!’);
        }
        版本二可以验证并提取出带名字的Email地址:
        ‘use strict’;
        // 测试:
        var r = re.exec(‘ tom@voyager.org’);
        if (r === null || r.toString() !== [‘ tom@voyager.org’, ‘Tom Paris’, ‘tom@voyager.org’].toString()) {
        alert(‘测试失败!’);
        }
        else {
        alert(‘测试成功!’);
        }

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - HTML5实战——svg学习 -

        - - -
        - - -
        - -

          SVG可缩放矢量图形(Scalable Vector Graphics)是基于可扩展标记语言(XML),用于描述二维矢量图形的一种图形格式。SVG是W3C制定的一种新的二维矢量图形格式,也是规范中的网络矢量图形标准。SVG严格遵从XML语法,并用文本格式的描述性语言来描述图像内容,因此是一种和图像分辨率无关的矢量图形格式。
        什么是SVG?
          SVG 指可伸缩矢量图形 (Scalable Vector Graphics)
          SVG 用来定义用于网络的基于矢量的图形
          SVG 使用 XML 格式定义图形
          SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失
          SVG 是万维网联盟的标准
          SVG 与诸如 DOM 和 XSL 之类的 W3C 标准是一个整体
        Canvas 和 SVG 的区别:
          SVG
            SVG 是一种使用 XML 描述 2D 图形的语言。
            SVG 基于 XML,这意味着 SVG DOM 中的每个元素都是可用的。您可以为某个元素附加 JavaScript 事件处理器。
            在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。
            特点:
               不依赖分辨率
               支持事件处理器
               最适合带有大型渲染区域的应用程序(比如谷歌地图)
               复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)
               不适合游戏应用
          Canvas
            Canvas 通过 JavaScript 来绘制 2D 图形。
            Canvas 是逐像素进行渲染的。
            在 canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象。
            特点:
              依赖分辨率
               不支持事件处理器
               弱的文本渲染能力
               能够以 .png 或 .jpg 格式保存结果图像
               最适合图像密集型的游戏,其中的许多对象会被频繁重绘
        svg 例子:

        - - - -

        学习svg非常不错的系列博文
        突袭HTML5之SVG 2D入门1 - SVG综述
        突袭HTML5之SVG 2D入门2 - 图形绘制
        突袭HTML5之SVG 2D入门3 - 文本与图像
        突袭HTML5之SVG 2D入门4 - 笔画与填充
        突袭HTML5之SVG 2D入门5 - 颜色的表示
        突袭HTML5之SVG 2D入门6 - 坐标与变换
        突袭HTML5之SVG 2D入门7 - 重用与引用
        突袭HTML5之SVG 2D入门8 - 文档结构
        突袭HTML5之SVG 2D入门9 - 蒙板
        突袭HTML5之SVG 2D入门10 - 滤镜
        突袭HTML5之SVG 2D入门11 - 动画
        突袭HTML5之SVG 2D入门12 - SVG DOM
        突袭HTML5之SVG 2D入门13 - svg对决canvas

        -

        参考:http://www.w3school.com.cn/svg/svg_intro.asp

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - android项目源码 -

        - - -
        - - -
        - -

        源码下载常用地址 http://code.662p.com/list/11_1.html
        Android PDF 阅读器 http://sourceforge.net/projects/andpdf/files/
        个人记账工具 OnMyMeans http://sourceforge.net/projects/onmymeans/develop
        Android电池监控 Android Battery Dog http://sourceforge.net/projects/andbatdog/
        RSS阅读软件 Android RSS http://code.google.com/p/android-rss/
        Android的PDF阅读器 DroidReader http://code.google.com/p/droidreader/
        Android Scripting Environment http://code.google.com/p/android-scripting/
        Android小游戏 Android Shapes http://sourceforge.net/projects/shapes/
        Android JSON RPC http://code.google.com/p/android-json-rpc/
        Android VNC http://code.google.com/p/android-vnc/
        魅族M8的Android移植 M8 Android http://code.google.com/p/m8-android-kernel/
        Android 游戏 Amazed http://code.google.com/p/apps-for-android/
        Android的社交网络 HelloWorld goes mobile http://sourceforge.net/projects/helloworldgm/
        手机聊天程序 Android jChat http://code.google.com/p/jchat4android/
        Android的GPS轨迹记录 MyTracks http://code.google.com/p/mytracks/
        Android国际象棋游戏 Honzovy achy http://sourceforge.net/projects/honzovysachy/
        Android旅行记录软件 AndTripLog http://sourceforge.net/projects/andtriplog/
        音乐播放器 Ambient http://sourceforge.net/projects/ambientmp/
        Android的邮件客户端 K9mail http://code.google.com/p/k9mail/
        多平台应用开发库 QuickConnect http://sourceforge.net/projects/quickconnect/
        gPhone手机空战游戏 http://code.google.com/p/wireless-apps/
        Android 照片小软件 Panoramio http://code.google.com/p/apps-for-android/
        i-jetty http://code.google.com/p/i-jetty/
        Android 小游戏 DivideAndConquer http://code.google.com/p/apps-for-android/
        Android 全球时间 AndroidGlobalTime http://code.google.com/p/apps-for-android/
        Android 2D游戏引擎 Android Angle http://code.google.com/p/angle/
        Android Ruby http://code.google.com/p/android-ruby/
        Android-N810 http://sourceforge.net/projects/android-n810/
        Android的短信应用 Ecclesia http://sourceforge.net/projects/ecclesia
        Android平台上的JXTA客户端 Peerdroid http://code.google.com/p/peerdroid/
        Android游戏引擎 libgdx http://code.google.com/p/libgdx/
        Android 照片小软件 Photostream http://code.google.com/p/apps-for-android/
        Alien3d logo Android 3D游戏引擎 Alien3d http://code.google.com/p/alien3d/
        Winamp Remote Android Server http://sourceforge.net/projects/winampdroid
        Android的Facebook客户端 Andrico http://code.google.com/p/andrico/
        Android Applications Manager http://sourceforge.net/projects/aam/
        Java 3D图形引擎 Catcake http://code.google.com/p/catcake/
        android-gcc-objc2-0 http://code.google.com/p/android-gcc-objc2-0/
        九宫格数独游戏 OpenSudoku http://code.google.com/p/opensudoku-android/
        Android 铃声扩展工具 RingsExtended http://code.google.com/p/apps-for-android/
        JavaEye Android client http://code.google.com/p/javaeye-android-client/
        RemoteDroid http://code.google.com/p/remotedroid/
        Android 小游戏 Clickin2DaBeat http://code.google.com/p/apps-for-android/
        中医大夫助理信息系统 zz-doctor http://code.google.com/p/zz-doctor/
        Facebook Connect for Android http://code.google.com/p/fbconnect-android/
        Android SMSPopup http://code.google.com/p/android-smspopup/
        FreeTTS-Android http://sourceforge.net/projects/freettsandroidi
        Foursquare.com的客户端 Foursquar http://code.google.com/p/foursquared/
        条形码扫描仪 Android PC_BCR http://code.google.com/p/android-pcbcr/

        - - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - java服务端推送消息到web页面实例 -

        - - -
        - - -
        - -

        对于页面一直监控,以前都是使用ajax请求即可,但是小并发这做法没多大问题,但是到了大并发就不太合适,如果不想自己写线程来操控就可以偷懒找一些插件,例如comet4j
        下面我来演示下如何使用这个插件
        先准备需要的工具:
        comet4j-tomcat6.jar(tomcat6的就导入这个)
        comet4j-tomcat7.jar(tomcat7的就导入这个)
        comet4j.js(页面引入这个js)
        具体操作看下面

        -

        然后就写个class
        [java] view plaincopy
        package com.shadow.extras.comet4j;

        -

        import javax.servlet.ServletContextEvent;
        import javax.servlet.ServletContextListener;

        -

        import org.comet4j.core.CometContext;
        import org.comet4j.core.CometEngine;

        -

        public class TestComet implements ServletContextListener {
        private static final String CHANNEL = “test”;
        private static final String CHANNEL2 = “test2”;

        -
        public void contextInitialized(ServletContextEvent arg0) {  
        -    CometContext cc = CometContext.getInstance();  
        -    cc.registChannel(CHANNEL);// 注册应用的channel  
        -    cc.registChannel(CHANNEL2);  
        -
        -    Thread helloAppModule = new Thread(new HelloAppModule(),  
        -            "Sender App Module");  
        -    // 是否启动  
        -    helloAppModule.setDaemon(true);  
        -    // 启动线程  
        -    helloAppModule.start();  
        -
        -    Thread helloAppModule2 = new Thread(new HelloAppModule2(),  
        -            "Sender App Module");  
        -    // 是否启动  
        -    helloAppModule2.setDaemon(true);  
        -    // 启动线程  
        -    helloAppModule2.start();  
        -}  
        -
        -class HelloAppModule2 implements Runnable {  
        -    public void run() {  
        -        while (true) {  
        -            try {  
        -                // 睡眠时间  
        -                Thread.sleep(5000);  
        -            } catch (Exception ex) {  
        -                ex.printStackTrace();  
        -            }  
        -            CometEngine engine = CometContext.getInstance().getEngine();  
        -            // 获取消息内容  
        -            long l = getFreeMemory();  
        -            // 开始发送  
        -            engine.sendToAll(CHANNEL2, l);  
        -        }  
        -    }  
        -}  
        -
        -class HelloAppModule implements Runnable {  
        -    public void run() {  
        -        while (true) {  
        -            try {  
        -                // 睡眠时间  
        -                Thread.sleep(2000);  
        -            } catch (Exception ex) {  
        -                ex.printStackTrace();  
        -            }  
        -            CometEngine engine = CometContext.getInstance().getEngine();  
        -            // 获取消息内容  
        -            long l = getFreeMemory();  
        -            // 开始发送  
        -            engine.sendToAll(CHANNEL, l);  
        -        }  
        -    }  
        -}  
        -
        -public void contextDestroyed(ServletContextEvent arg0) {  
        -
        -}  
        -
        -public long getFreeMemory() {  
        -    return Runtime.getRuntime().freeMemory() / 1024;  
        -}  
        -

        }
        然后再写个页面

        -

        [html] view plaincopy
        <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

        -

        -

        -

        -

        Comet4J Hello World

        -

        -


        -


        剩余内存:KB

        剩余内存:KB


        接着配置下web.xml就ok了
        [html] view plaincopy


        Comet4J容器侦听
        org.comet4j.core.CometAppListener


        Comet连接[默认:org.comet4j.core.CometServlet]
        CometServlet
        CometServlet
        org.comet4j.core.CometServlet


        CometServlet
        /comet

        -
        <listener>  
        -    <description>TestComet</description>  
        -    <listener-class>com.shadow.extras.comet4j.TestComet</listener-class>  
        -</listener>  
        -

        最后修改下tomcat的server.xml文件
        把protocol参数值改成下面的,因为这是基于nio开发的插件
        [html] view plaincopy

        - - -

        测试,很简单就是访问我们刚刚创建的test.html,然后就可以看到内存数值一直自动刷新波动

        -
        - -
        - - - -
        - -
        - - - - - - - - - - -
        - - - -
        - - - - -
        - - -

        - Markdown 语法说明 (简体中文版) -

        - - -
        - - -
        - -

        概述

        -

        宗旨

        -

        Markdown 的目标是实现「易读易写」。

        -

        可读性,无论如何,都是最重要的。一份使用 Markdown 格式撰写的文件应该可以直接以纯文本发布,并且看起来不会像是由许多标签或是格式指令所构成。Markdown 语法受到一些既有 text-to-HTML 格式的影响,包括 Setext、atx、Textile、reStructuredText、Grutatext 和 EtText,而最大灵感来源其实是纯文本电子邮件的格式。

        -

        总之, Markdown 的语法全由一些符号所组成,这些符号经过精挑细选,其作用一目了然。比如:在文字两旁加上星号,看起来就像强调。Markdown 的列表看起来,嗯,就是列表。Markdown 的区块引用看起来就真的像是引用一段文字,就像你曾在电子邮件中见过的那样。

        -

        兼容 HTML

        -

        Markdown 语法的目标是:成为一种适用于网络的书写语言。

        -

        Markdown 不是想要取代 HTML,甚至也没有要和它相近,它的语法种类很少,只对应 HTML 标记的一小部分。Markdown 的构想不是要使得 HTML 文档更容易书写。在我看来, HTML 已经很容易写了。Markdown 的理念是,能让文档更容易读、写和随意改。HTML 是一种发布的格式,Markdown 是一种书写的格式。就这样,Markdown 的格式语法只涵盖纯文本可以涵盖的范围。

        -

        不在 Markdown 涵盖范围之内的标签,都可以直接在文档里面用 HTML 撰写。不需要额外标注这是 HTML 或是 Markdown;只要直接加标签就可以了。

        -

        要制约的只有一些 HTML 区块元素――比如

        、、

        等标签,必须在前后加上空行与其它内容区隔开,还要求它们的开始标签与结尾标签不能用制表符或空格来缩进。Markdown 的生成器有足够智能,不会在 HTML 区块标签外加上不必要的

        标签。

        -

        例子如下,在 Markdown 文件里加上一段 HTML 表格:

        -

        这是一个普通段落。

        -




        Foo
        - -

        这是另一个普通段落。
        请注意,在 HTML 区块标签间的 Markdown 格式语法将不会被处理。比如,你在 HTML 区块内使用 Markdown 样式的强调会没有效果。

        -

        HTML 的区段(行内)标签如 可以在 Markdown 的段落、列表或是标题里随意使用。依照个人习惯,甚至可以不用 Markdown 格式,而直接采用 HTML 标签来格式化。举例说明:如果比较喜欢 HTML 的 标签,可以直接使用这些标签,而不用 Markdown 提供的链接或是图像标签语法。

        -

        和处在 HTML 区块标签间不同,Markdown 语法在 HTML 区段标签间是有效的。

        -

        特殊字符自动转换

        -

        在 HTML 文件中,有两个字符需要特殊处理: < 和 & 。 < 符号用于起始标签,& 符号则用于标记 HTML 实体,如果你只是想要显示这些字符的原型,你必须要使用实体的形式,像是 < 和 &。

        -

        & 字符尤其让网络文档编写者受折磨,如果你要打「AT&T」 ,你必须要写成「AT&T」。而网址中的 & 字符也要转换。比如你要链接到:

        -

        http://images.google.com/images?num=30&q=larry+bird
        你必须要把网址转换写为:

        -

        http://images.google.com/images?num=30&q=larry+bird
        才能放到链接标签的 href 属性里。不用说也知道这很容易忽略,这也可能是 HTML 标准检验所检查到的错误中,数量最多的。

        -

        Markdown 让你可以自然地书写字符,需要转换的由它来处理好了。如果你使用的 & 字符是 HTML 字符实体的一部分,它会保留原状,否则它会被转换成 &。

        -

        所以你如果要在文档中插入一个版权符号 ©,你可以这样写:

        -

        ©
        Markdown 会保留它不动。而若你写:

        -

        AT&T
        Markdown 就会将它转为:

        -

        AT&T
        类似的状况也会发生在 < 符号上,因为 Markdown 允许 兼容 HTML ,如果你是把 < 符号作为 HTML 标签的定界符使用,那 Markdown 也不会对它做任何转换,但是如果你写:

        -

        4 < 5
        Markdown 将会把它转换为:

        -

        4 < 5
        不过需要注意的是,code 范围内,不论是行内还是区块, < 和 & 两个符号都一定会被转换成 HTML 实体,这项特性让你可以很容易地用 Markdown 写 HTML code (和 HTML 相对而言, HTML 语法中,你要把所有的 < 和 & 都转换为 HTML 实体,才能在 HTML 文件里面写出 HTML code。)

        -

        区块元素

        -

        段落和换行

        -

        一个 Markdown 段落是由一个或多个连续的文本行组成,它的前后要有一个以上的空行(空行的定义是显示上看起来像是空的,便会被视为空行。比方说,若某一行只包含空格和制表符,则该行也会被视为空行)。普通段落不该用空格或制表符来缩进。

        -

        「由一个或多个连续的文本行组成」这句话其实暗示了 Markdown 允许段落内的强迫换行(插入换行符),这个特性和其他大部分的 text-to-HTML 格式不一样(包括 Movable Type 的「Convert Line Breaks」选项),其它的格式会把每个换行符都转成
        标签。

        -

        如果你确实想要依赖 Markdown 来插入
        标签的话,在插入处先按入两个以上的空格然后回车。

        -

        的确,需要多费点事(多加空格)来产生
        ,但是简单地「每个换行都转换为
        」的方法在 Markdown 中并不适合, Markdown 中 email 式的 区块引用 和多段落的 列表 在使用换行来排版的时候,不但更好用,还更方便阅读。

        -

        标题

        -

        Markdown 支持两种标题的语法,类 Setext 和类 atx 形式。

        -

        类 Setext 形式是用底线的形式,利用 = (最高阶标题)和 - (第二阶标题),例如:

        -

        This is an H1

        This is an H2

        任何数量的 = 和 - 都可以有效果。

        -

        类 Atx 形式则是在行首插入 1 到 6 个 # ,对应到标题 1 到 6 阶,例如:

        -

        这是 H1

        这是 H2

        这是 H6

        你可以选择性地「闭合」类 atx 样式的标题,这纯粹只是美观用的,若是觉得这样看起来比较舒适,你就可以在行尾加上 #,而行尾的 # 数量也不用和开头一样(行首的井字符数量决定标题的阶数):

        -

        这是 H1

        这是 H2

        这是 H3

        区块引用 Blockquotes

        -

        Markdown 标记区块引用是使用类似 email 中用 > 的引用方式。如果你还熟悉在 email 信件中的引言部分,你就知道怎么在 Markdown 文件中建立一个区块引用,那会看起来像是你自己先断好行,然后在每行的最前面加上 > :

        -
        -

        This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
        consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
        Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

        -

        Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
        id sem consectetuer libero luctus adipiscing.
        Markdown 也允许你偷懒只在整个段落的第一行最前面加上 > :

        -

        This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
        consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
        Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

        -

        Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
        id sem consectetuer libero luctus adipiscing.
        区块引用可以嵌套(例如:引用内的引用),只要根据层次加上不同数量的 > :

        -

        This is the first level of quoting.

        -
        -

        This is nested blockquote.

        -
        -

        Back to the first level.
        引用的区块内也可以使用其他的 Markdown 语法,包括标题、列表、代码区块等:

        -

        这是一个标题。

          -
        1. 这是第一行列表项。
        2. -
        3. 这是第二行列表项。
        4. -
        -

        给出一些例子代码:

        -
        return shell_exec("echo $input | $markdown_script");
        -

        任何像样的文本编辑器都能轻松地建立 email 型的引用。例如在 BBEdit 中,你可以选取文字后然后从选单中选择增加引用阶层。

        -
        -

        列表

        -

        Markdown 支持有序列表和无序列表。

        -

        无序列表使用星号、加号或是减号作为列表标记:

        -
          -
        • Red
        • -
        • Green
        • -
        • Blue
          等同于:
        • -
        -
          -
        • Red
        • -
        • Green
        • -
        • Blue
          也等同于:
        • -
        -
          -
        • Red
        • -
        • Green
        • -
        • Blue
          有序列表则使用数字接着一个英文句点:
        • -
        -
          -
        1. Bird
        2. -
        3. McHale
        4. -
        5. Parish
          很重要的一点是,你在列表标记上使用的数字并不会影响输出的 HTML 结果,上面的列表所产生的 HTML 标记为:
        6. -
        -

          -

        1. Bird
        2. -

        3. McHale
        4. -

        5. Parish


        6. 如果你的列表标记写成:

          -
            -
          1. Bird
          2. -
          3. McHale
          4. -
          5. Parish
            或甚至是:

            -
          6. -
          7. Bird

            -
          8. -
          9. McHale
          10. -
          11. Parish
            你都会得到完全相同的 HTML 输出。重点在于,你可以让 Markdown 文件的列表数字和输出的结果相同,或是你懒一点,你可以完全不用在意数字的正确性。
          12. -
          -

          如果你使用懒惰的写法,建议第一个项目最好还是从 1. 开始,因为 Markdown 未来可能会支持有序列表的 start 属性。

          -

          列表项目标记通常是放在最左边,但是其实也可以缩进,最多 3 个空格,项目标记后面则一定要接着至少一个空格或制表符。

          -

          要让列表看起来更漂亮,你可以把内容用固定的缩进整理好:

          -
            -
          • Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
            Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
            viverra nec, fringilla in, laoreet vitae, risus.
          • -
          • Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
            Suspendisse id sem consectetuer libero luctus adipiscing.
            但是如果你懒,那也行:

            -
          • -
          • Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
            Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
            viverra nec, fringilla in, laoreet vitae, risus.

            -
          • -
          • Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
            Suspendisse id sem consectetuer libero luctus adipiscing.
            如果列表项目间用空行分开,在输出 HTML 时 Markdown 就会将项目内容用

            标签包起来,举例来说:

            -
          • -
          • Bird

            -
          • -
          • Magic
            会被转换为:
          • -
          -

            -

          • Bird
          • -

          • Magic


          • 但是这个:

            -
              -
            • Bird

              -
            • -
            • Magic
              会被转换为:

              -
            • -
            -

              -

            • Bird

            • -

            • Magic



            • 列表项目可以包含多个段落,每个项目下的段落都必须缩进 4 个空格或是 1 个制表符:

              -
                -
              1. This is a list item with two paragraphs. Lorem ipsum dolor
                sit amet, consectetuer adipiscing elit. Aliquam hendrerit
                mi posuere lectus.

                -

                Vestibulum enim wisi, viverra nec, fringilla in, laoreet
                vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
                sit amet velit.

                -
              2. -
              3. Suspendisse id sem consectetuer libero luctus adipiscing.
                如果你每行都有缩进,看起来会看好很多,当然,再次地,如果你很懒惰,Markdown 也允许:

                -
              4. -
              -
                -
              • This is a list item with two paragraphs.

                -

                This is the second paragraph in the list item. You’re
                only required to indent the first line. Lorem ipsum dolor
                sit amet, consectetuer adipiscing elit.

                -
              • -
              • Another item in the same list.
                如果要在列表项目内放进引用,那 > 就需要缩进:

                -
              • -
              • A list item with a blockquote:

                -
                -

                This is a blockquote
                inside a list item.
                如果要放代码区块的话,该区块就需要缩进两次,也就是 8 个空格或是 2 个制表符:

                -
                -
              • -
              • 一列表项包含一个列表区块:

                -
                <代码写在这>
                -

                当然,项目列表很可能会不小心产生,像是下面这样的写法:

                -
              • -
              -
                -
              1. What a great season.
                换句话说,也就是在行首出现数字-句点-空白,要避免这样的状况,你可以在句点前面加上反斜杠。
              2. -
              -

              1986. What a great season.
              代码区块

              -

              和程序相关的写作或是标签语言原始码通常会有已经排版好的代码区块,通常这些区块我们并不希望它以一般段落文件的方式去排版,而是照原来的样子显示,Markdown 会用

               标签来把代码区块包起来。

              -

              要在 Markdown 中建立代码区块很简单,只要简单地缩进 4 个空格或是 1 个制表符就可以,例如,下面的输入:

              -

              这是一个普通段落:

              -
              这是一个代码区块。
              -

              Markdown 会转换成:

              -

              这是一个普通段落:

              - -

              这是一个代码区块。

              这个每行一阶的缩进(4 个空格或是 1 个制表符),都会被移除,例如:

              -

              Here is an example of AppleScript:

              -
              tell application "Foo"
              -    beep
              -end tell
              -

              会被转换为:

              -

              Here is an example of AppleScript:

              - -

              tell application “Foo”
              beep
              end tell

              一个代码区块会一直持续到没有缩进的那一行(或是文件结尾)。

              -

              在代码区块里面, & 、 < 和 > 会自动转成 HTML 实体,这样的方式让你非常容易使用 Markdown 插入范例用的 HTML 原始码,只需要复制贴上,再加上缩进就可以了,剩下的 Markdown 都会帮你处理,例如:

              -
              <div class="footer">
              -    &copy; 2004 Foo Corporation
              -</div>
              -

              会被转换为:

              -

              <div class=”footer”>
              &copy; 2004 Foo Corporation
              </div>

              代码区块中,一般的 Markdown 语法不会被转换,像是星号便只是星号,这表示你可以很容易地以 Markdown 语法撰写 Markdown 语法相关的文件。

              -

              分隔线

              -

              你可以在一行中用三个以上的星号、减号、底线来建立一个分隔线,行内不能有其他东西。你也可以在星号或是减号中间插入空格。下面每种写法都可以建立分隔线:

              -
              -
              -
              -
              -
              -

              区段元素

              -

              链接

              -

              Markdown 支持两种形式的链接语法: 行内式和参考式两种形式。

              -

              不管是哪一种,链接文字都是用 [方括号] 来标记。

              -

              要建立一个行内式的链接,只要在方块括号后面紧接着圆括号并插入网址链接即可,如果你还想要加上链接的 title 文字,只要在网址后面,用双引号把 title 文字包起来即可,例如:

              -

              This is an example inline link.

              -

              This link has no title attribute.
              会产生:

              -

              This is
              an example
              inline link.

              - -

              This link has no
              title attribute.


              如果你是要链接到同样主机的资源,你可以使用相对路径:

              See my About page for details.
              参考式的链接是在链接文字的括号后面再接上另一个方括号,而在第二个方括号里面要填入用以辨识链接的标记:

              This is an example reference-style link.
              你也可以选择性地在两个方括号中间加上一个空格:

              This is an example reference-style link.
              接着,在文件的任意处,你可以把这个标记的链接内容定义出来:

              id: http://example.com/ “Optional Title Here”
              链接内容定义的形式为:

              方括号(前面可以选择性地加上至多三个空格来缩进),里面输入链接文字
              接着一个冒号
              接着一个以上的空格或制表符
              接着链接的网址
              选择性地接着 title 内容,可以用单引号、双引号或是括弧包着
              下面这三种链接的定义都是相同:

              [foo]: http://example.com/ “Optional Title Here”
              [foo]: http://example.com/ ‘Optional Title Here’
              [foo]: http://example.com/ (Optional Title Here)
              请注意:有一个已知的问题是 Markdown.pl 1.0.1 会忽略单引号包起来的链接 title。

              链接网址也可以用方括号包起来:

              id: http://example.com/ “Optional Title Here”
              你也可以把 title 属性放到下一行,也可以加一些缩进,若网址太长的话,这样会比较好看:

              id: http://example.com/longish/path/to/resource/here
              “Optional Title Here”
              网址定义只有在产生链接的时候用到,并不会直接出现在文件之中。

              链接辨别标签可以有字母、数字、空白和标点符号,但是并不区分大小写,因此下面两个链接是一样的:

              [link text][a]
              [link text][A]
              隐式链接标记功能让你可以省略指定链接标记,这种情形下,链接标记会视为等同于链接文字,要用隐式链接标记只要在链接文字后面加上一个空的方括号,如果你要让 “Google” 链接到 google.com,你可以简化成:

              [Google][]
              然后定义链接内容:

              [Google]: http://google.com/
              由于链接文字可能包含空白,所以这种简化型的标记内也许包含多个单词:

              Visit [Daring Fireball][] for more information.
              然后接着定义链接:

              [Daring Fireball]: http://daringfireball.net/
              链接的定义可以放在文件中的任何一个地方,我比较偏好直接放在链接出现段落的后面,你也可以把它放在文件最后面,就像是注解一样。

              下面是一个参考式链接的范例:

              I get 10 times more traffic from [Google] [1] than from
              [Yahoo] [2] or [MSN] [3].

              [1]: http://google.com/ “Google”
              [2]: http://search.yahoo.com/ “Yahoo Search”
              [3]: http://search.msn.com/ “MSN Search”
              如果改成用链接名称的方式写:

              I get 10 times more traffic from [Google][] than from
              [Yahoo][] or [MSN][].

              [google]: http://google.com/ “Google”
              [yahoo]: http://search.yahoo.com/ “Yahoo Search”
              [msn]: http://search.msn.com/ “MSN Search”
              上面两种写法都会产生下面的 HTML。

              I get 10 times more traffic from Google than from
              Yahoo
              or MSN.


              下面是用行内式写的同样一段内容的 Markdown 文件,提供作为比较之用:

              I get 10 times more traffic from Google
              than from Yahoo or
              MSN.
              参考式的链接其实重点不在于它比较好写,而是它比较好读,比较一下上面的范例,使用参考式的文章本身只有 81 个字符,但是用行内形式的却会增加到 176 个字元,如果是用纯 HTML 格式来写,会有 234 个字元,在 HTML 格式中,标签比文本还要多。

              使用 Markdown 的参考式链接,可以让文件更像是浏览器最后产生的结果,让你可以把一些标记相关的元数据移到段落文字之外,你就可以增加链接而不让文章的阅读感觉被打断。

              强调

              Markdown 使用星号()和底线(_)作为标记强调字词的符号,被 包围的字词会被转成用 标签包围,用两个 * 或 包起来的话,则会被转成 ,例如:

              single asterisks

              single underscores

              double asterisks

              double underscores
              会转成:

              single asterisks

              single underscores

              double asterisks

              double underscores
              你可以随便用你喜欢的样式,唯一的限制是,你用什么符号开启标签,就要用什么符号结束。

              强调也可以直接插在文字中间:

              unfriggingbelievable
              但是如果你的 和 _ 两边都有空白的话,它们就只会被当成普通的符号。

              如果要在文字前后直接插入普通的星号或底线,你可以用反斜线:

              \
              this text is surrounded by literal asterisks*
              代码

              如果要标记一小段行内代码,你可以用反引号把它包起来(),例如: - -Use theprintf()function. -会产生: - -<p>Use the <code>printf()</code> function.</p> -如果要在代码区段内插入反引号,你可以用多个反引号来开启和结束代码区段: - -``There is a literal backtick () here.这段语法会产生: - -<p><code>There is a literal backtick (`) here.</code></p> -代码区段的起始和结束端都可以放入一个空白,起始端后面一个,结束端前面一个,这样你就可以在区段的一开始就插入反引号: - -A single backtick in a code span: `` - -A backtick-delimited string in a code span: `` foo`` -会产生: - -<p>A single backtick in a code span: <code>

              - -

              A backtick-delimited string in a code span: foo


              在代码区段内,& 和方括号都会被自动地转成 HTML 实体,这使得插入 HTML 原始码变得很容易,Markdown 会把下面这段:

              -

              Please don’t use any <blink> tags.
              转为:

              -

              Please don’t use any <blink> tags.


              你也可以这样写:

              -

              &#8212; is the decimal-encoded equivalent of &mdash;.
              以产生:

              -

              &#8212; is the decimal-encoded
              equivalent of &mdash;.


              图片

              -

              很明显地,要在纯文字应用中设计一个「自然」的语法来插入图片是有一定难度的。

              -

              Markdown 使用一种和链接很相似的语法来标记图片,同样也允许两种样式: 行内式和参考式。

              -

              行内式的图片语法看起来像是:

              -

              Alt text

              -

              Alt text
              详细叙述如下:

              -

              一个惊叹号 !
              接着一个方括号,里面放上图片的替代文字
              接着一个普通括号,里面放上图片的网址,最后还可以用引号包住并加上 选择性的 ‘title’ 文字。
              参考式的图片语法则长得像这样:

              -

              Alt text
              「id」是图片参考的名称,图片参考的定义方式则和连结参考一样:

              -

              到目前为止, Markdown 还没有办法指定图片的宽高,如果你需要的话,你可以使用普通的 标签。

              -

              其它

              -

              自动链接

              -

              Markdown 支持以比较简短的自动链接形式来处理网址和电子邮件信箱,只要是用方括号包起来, Markdown 就会自动把它转成链接。一般网址的链接文字就和链接地址一样,例如:

              -

              http://example.com/
              Markdown 会转为:

              -

              http://example.com/
              邮址的自动链接也很类似,只是 Markdown 会先做一个编码转换的过程,把文字字符转成 16 进位码的 HTML 实体,这样的格式可以糊弄一些不好的邮址收集机器人,例如:

              -

              address@example.com
              Markdown 会转成:

              -

              address@exa
              mple.com

              在浏览器里面,这段字串(其实是 address@example.com)会变成一个可以点击的「address@example.com」链接。

              -

              (这种作法虽然可以糊弄不少的机器人,但并不能全部挡下来,不过总比什么都不做好些。不管怎样,公开你的信箱终究会引来广告信件的。)

              -

              反斜杠

              -

              Markdown 可以利用反斜杠来插入一些在语法中有其它意义的符号,例如:如果你想要用星号加在文字旁边的方式来做出强调效果(但不用 标签),你可以在星号的前面加上反斜杠:

              -

              *literal asterisks*
              Markdown 支持以下这些符号前面加上反斜杠来帮助插入普通的符号:

              -

              \ 反斜线
              ` 反引号

              -
                -
              • 星号
                _ 底线
                {} 花括号
                [] 方括号
                () 括弧

                井字号

              • -
              -
                -
              • 加号
              • -
              -
                -
              • 减号
                . 英文句点
                ! 惊叹号
              • -
              - - -
              - - - -
              - -
              - - - - - - - - - - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/page/6/index.html b/page/6/index.html deleted file mode 100644 index 9e6d7e5..0000000 --- a/page/6/index.html +++ /dev/null @@ -1,617 +0,0 @@ - - - - - - - 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - -
              - - - -
              - - - - -
              - - -

              - Hexo搭建Github静态博客 -

              - - -
              - - -
              - -
                -
              1. 环境环境
                1.1 安装Git
              2. -
              -

              请参考【1】

              -

              1.2 安装node.js

              -

              下载:http://nodejs.org/download/

              -

              可以下载 node-v0.10.33-x64.msi

              -

              安装时直接保持默认配置即可。

              -
                -
              1. 配置Github
                1.1 建立Repository
              2. -
              -

              建立与你用户名对应的仓库,仓库名必须为【your_user_name.github.io】

              -

              1.2 配置SSH-Key

              -

              参考【1】

              -
                -
              1. 安装Hexo
                关于Hexo的安装配置过程,请以官方Hexo【2】给出的步骤为准。
              2. -
              -

              3.1 Installation

              -

              打开Git命令行,执行如下命令

              -

              $ npm install -g hexo
              3.2 Quick Start

              -
                -
              1. Setup your blog
              2. -
              -

              在电脑中建立一个名字叫「Hexo」的文件夹(比如我建在了D:\Hexo),然后在此文件夹中右键打开Git Bash。执行下面的命令

              -

              $ hexo init
              [info] Copying data
              [info] You are almost done! Don’t forget to run npm install before you start b
              logging with Hexo!
              Hexo随后会自动在目标文件夹建立网站所需要的文件。然后按照提示,运行 npm install(在 /D/Hexo下)

              -

              npm install
              会在D:\Hexo目录中安装 node_modules。

              -
                -
              1. Start the server
              2. -
              -

              运行下面的命令(在 /D/Hexo下)

              -

              $ hexo server
              [info] Hexo is running at http://localhost:4000/. Press Ctrl+C to stop.
              表明Hexo Server已经启动了,在浏览器中打开 http://localhost:4000/,这时可以看到Hexo已为你生成了一篇blog。

              -

              你可以按Ctrl+C 停止Server。

              -
                -
              1. Create a new post
              2. -
              -

              新打开一个git bash命令行窗口,cd到/D/Hexo下,执行下面的命令

              -

              $ hexo new “My New Post”
              [info] File created at d:\Hexo\source_posts\My-New-Post.md
              刷新http://localhost:4000/,可以发现已生成了一篇新文章 “My New Post”。

              -

              NOTE:

              -

              有一个问题,发现 “My New Post” 被发了2遍,在Hexo server所在的git bash窗口也能看到create了2次。

              -

              $ hexo server
              [info] Hexo is running at http://localhost:4000/. Press Ctrl+C to stop.
              [create] d:\Hexo\source_posts\My-New-Post.md
              [create] d:\Hexo\source_posts\My-New-Post.md
              经验证,在hexo new “My New Post” 时,如果按Ctrl+C将hexo server停掉,就不会出现发2次的问题了。

              -

              所以,在hexo new文章时,需要stop server。

              -
                -
              1. Generate static files
              2. -
              -

              执行下面的命令,将markdown文件生成静态网页。

              -

              $ hexo generate
              该命令执行完后,会在 D:\Hexo\public\ 目录下生成一系列html,css等文件。

              -
                -
              1. 编辑文章
              2. -
              -

              hexo new “My New Post”会在D:\Hexo\source_posts目录下生成一个markdown文件:My-New-Post.md

              -

              可以使用一个支持markdown语法的编辑器(比如 Sublime Text 2)来编辑该文件。

              -
                -
              1. 部署到Github
              2. -
              -

              部署到Github前需要配置_config.yml文件,首先找到下面的内容

              -

              Deployment

              Docs: http://hexo.io/docs/deployment.html

              deploy:
              type:
              然后将它们修改为

              -

              Deployment

              Docs: http://hexo.io/docs/deployment.html

              deploy:
              type: github
              repository: git@github.com:zhchnchn/zhchnchn.github.io.git
              branch: master
              NOTE1:

              -

              Repository:必须是SSH形式的url(git@github.com:zhchnchn/zhchnchn.github.io.git),而不能是HTTPS形式的url(https://github.com/zhchnchn/zhchnchn.github.io.git),否则会出现错误:

              -

              $ hexo deploy
              [info] Start deploying: github
              [error] https://github.com/zhchnchn/zhchnchn.github.io is not a valid repositor URL!
              使用SSH url,如果电脑没有开放SSH 端口,会致部署失败。

              -

              fatal: Could not read from remote repository.

              -

              Please make sure you have the correct access rights
              and the repository exists.
              NOTE2:

              -

              如果你是为一个项目制作网站,那么需要把branch设置为gh-pages。

              -
                -
              1. 测试
              2. -
              -

              当部署完成后,在浏览器中打开http://zhchnchn.github.io/(https://zhchnchn.github.io/) ,正常显示网页,表明部署成功。

              -
                -
              1. 总结:部署步骤
              2. -
              -

              每次部署的步骤,可按以下三步来进行。

              -

              hexo clean
              hexo generate
              hexo deploy

              -
                -
              1. 总结:本地调试

                -
              2. -
              3. 在执行下面的命令后,

                -
              4. -
              -

              $ hexo g #生成
              $ hexo s #启动本地服务,进行文章预览调试
              浏览器输入http://localhost:4000,查看搭建效果。此后的每次变更_config.yml 文件或者新建文件都可以先用此命令调试,尤其是当你想调试新添加的主题时。

              -
                -
              1. 可以用简化的一条命令
              2. -
              -

              hexo s -g
              3.3 命令总结

              -

              3.3.1 常用命令

              -

              复制代码
              hexo new “postName” #新建文章
              hexo new page “pageName” #新建页面
              hexo generate #生成静态页面至public目录
              hexo server #开启预览访问端口(默认端口4000,’ctrl + c’关闭server)
              hexo deploy #将.deploy目录部署到GitHub
              hexo help # 查看帮助
              hexo version #查看Hexo的版本
              复制代码
              3.3.2 复合命令

              -

              hexo deploy -g #生成加部署
              hexo server -g #生成加预览
              命令的简写为:

              -

              hexo n == hexo new
              hexo g == hexo generate
              hexo s == hexo server
              hexo d == hexo deploy
              4 配置Hexo
              4.1 配置文件介绍

              -

              下面的各个部分的介绍,请直接参考【3】。

              -
                -
              1. 默认目录结构介绍

                -
              2. -
              3. _config.yml配置文件介绍

                -
              4. -
              -

              NOTE:在修改_config.yml配置文件时,按照【3】的介绍进行修改后,重新 hexo clean 或者hexo deploy时,可能会出现如下错误:

              -

              复制代码
              $ hexo clean
              [error] { name: ‘HexoError’,
              reason: ‘can not read a block mapping entry; a multiline key may not be an imp
              licit key’,
              mark:
              { name: null,
              buffer: ‘# Hexo Configuration\n## Docs: http://hexo.io/docs/configuration.h
              tml\n## Source: https://github.com/hexojs/hexo/\n\n# Site\ntitle: Zhchnchn\nsubt
              itle: Coding on the way\ndescription: Zhchnchn\’s blog\nauthor: Zhchnchn\nemail:
              115063497@qq.com\nlanguage:zh-CN\n\n# URL\n## If your site is put in a subdirect
              ……
              ,
              position: 249,
              line: 12,
              column: 0 },
              message: ‘Config file load failed’,
              domain:
              { domain: null,
              _events: { error: [Function] },
              _maxListeners: 10,
              members: [ [Object] ] },
              domainThrown: true,
              stack: undefined }
              复制代码
              我的_config.yml配置文件是一个空行,所以错误肯定在前面,经过对比发现,我前面修改了一下 # Site的各项设置,在冒号:后面没留空格导致了该问题,请对比一下下面的区别:

              -

              错误的设置:

              -

              author:Zhchnchn
              email:XXX@qq.com
              language:zh-CN
              正确的设置:

              -

              author: Zhchnchn
              email: XXX@qq.com
              language: zh-CN

              -
                -
              1. 各个主题下的目录介绍(hexo\themes\下的modernist主题为例)
              2. -
              -

              4.2 安装主题

              -

              Hexo提供了很多主题,具体可参见Hexo Themes【4】。这里我选择使用Pacman主题。具体设置方法如下【5】

              -

              4.2.1 安装

              -
                -
              1. 将Git Shell 切到/D/Hexo目录下,然后执行下面的命令,将pacman下载到 themes/pacman 目录下。
              2. -
              -

              $ git clone https://github.com/A-limon/pacman.git themes/pacman

              -
                -
              1. 修改你的博客根目录/D/Hexo下的config.yml配置文件中的theme属性,将其设置为pacman。

                -
              2. -
              3. 更新pacman主题

                -
              4. -
              -

              cd themes/pacman
              git pull
              NOTE:先备份_config.yml 文件后再升级

              -

              4.2.2 配置

              -

              如果pacman的默认设置不能满足需要的话,你可以修改 /themes/pacman/下的配置文件_config.yml来定制。

              -

              各个config的含义,请参考【5】中的介绍。

              -

              4.2.3 评论框

              -

              静态博客要使用第三方评论系统,pacman配置了多说评论系统(/themes/pacman/_config.yml),默认关闭,只要将其打开即可:false->true。直接用你的微博/豆瓣/人人/百度/开心网帐号登录多说,即可发表平评论。

              -

              Comment

              duoshuo:
              enable: true ## duoshuo.com
              short_name: ## duoshuo short name.
              4.2.3 统计

              -
                -
              1. pacman配置了google analysis系统(/themes/pacman/_config.yml),默认关闭,将其打开。

                -
              2. -
              3. 需要注册google analysis服务,以获得 跟踪 ID。

                -
              4. -
              -

              如果已有google账户的话,可以直接注册。注册时,需要正确填写 网站的URL。注册成功后,会得到一个跟踪ID,以及一段跟踪代码。

              -
                -
              1. pacman配置了google analysis系统,将其打开
              2. -
              -

              Analytics

              google_analytics:
              enable: true
              id: UA-57032437-1 ## e.g. UA-1766729-8 your google analytics ID.
              site: auto ## e.g. yangjian.me your google analytics site or set the value as auto.

              -
                -
              1. 在themes\pacman\layout_partial\google_analytics.ejs 中,已经将google的跟踪代码添加进来了【3】。
              2. -
              -

              复制代码
              <% if (theme.google_analytics.enable){ %>

              -


              <% } %>
              复制代码
              而且会将/themes/pacman/_config.yml中的id和site值读取进来。

              -
                -
              1. 如果设置不起作用,请试试在\themes\pacman\layout_partial\head.ejs文件中最后,之前,添加上下面的语句试试。
              2. -
              -

              <%- partial(‘google_analytics’) %>
              4.3 Custom 404页面

              -
                -
              1. 网上大多数教程都将其说的极其简单:“直接在根目录下创建自己的 404.html 就可以”。但我却在这儿废了不少时间,究其原因是大家觉得太简单而说的不够明白。“根目录下”指的不是Hexo目录下,而是Hexo/source目录下。

                -
              2. -
              3. 404.html的内容可以设置为下面的内容【6】(NOTE: _config.yml中的permalink_defaults属性不需要修改)。
                4.4 安装插件

                -
              4. -
              -

              4.4.1 sitemap插件

              -
                -
              1. 可以将你站点地图提交给搜索引擎,文件路径\sitemap.xml。

                -
              2. -
              3. 安装

                -
              4. -
              -

              $ npm install hexo-generator-sitemap

              -
                -
              1. 启用,修改Hexo_config.yml,增加以下内容
              2. -
              -

              复制代码

              -

              Extensions

              Plugins:

              -
                -
              • hexo-generator-sitemap
              • -
              -

              #sitemap
              sitemap:
              path: sitemap.xml
              复制代码

              -
                -
              1. 使用方法
              2. -
              -

              (1)访问 http://localhost:4000/sitemap.xml,即可看到站点地图。

              -

              (2)那么怎么将它显示在页面中呢【7】?

              -

              可以修改themes/pacman(也就是你正在使用的那个theme)下的 _config.yml,在 menu 节点下添加下面的内容(下面要介绍的RSS插件也同样)

              -

              menu:
              Home: /
              Archives: /archives
              Rss: /atom.xml
              Sitemap: /sitemap.xml
              修改后的效果如图所示:

              -
                -
              1. 如何向google提交sitemap
              2. -
              -

              Sitemap 可方便管理员通知搜索引擎他们网站上有哪些可供抓取的网页。向google提交自己hexo博客的sitemap,有助于让别人更好地通过google搜索到自己的博客。

              -

              如何向google提交sitemap,请参考【8】。

              -
                -
              1. 升级插件
              2. -
              -

              $ npm update

              -
                -
              1. 卸载插件
              2. -
              -

              $ npm uninstall hexo-generator-sitemap
              4.4.2 feed插件

              -
                -
              1. RSS的生成插件,你可以在配置显示你站点的RSS,文件路径\atom.xml。

                -
              2. -
              3. 安装

                -
              4. -
              -

              $ npm install hexo-generator-feed

              -
                -
              1. 启用,修改Hexo_config.yml,增加以下内容
              2. -
              -

              复制代码

              -

              Extensions

              Plugins:

              -
                -
              • hexo-generator-feed
              • -
              • hexo-generator-sitemap
              • -
              -

              #Feed Atom
              feed:
              type: atom
              path: atom.xml
              limit: 20
              复制代码
              4.使用方法

              -

              参见sitemap插件介绍

              -
                -
              1. 优化Hexo
                5.1 添加“Fork me on Github” ribbon
              2. -
              -

              给blog主页添加一个“Fork me on Github”的绶带(ribbon)【9】,比如选择了红色的ribbon,将相应代码复制到Hexo正在使用的theme下layout.ejs中。比如我使用的pacman theme,那么将下面的代码(注意将you改为你自己的github上的注册名)

              -

              Fork me on GitHub
              粘贴到 themes\pacman\layout\layout.ejs中,放置在 最后,标签之前即可。

              -

              6 其他
              6.1 中文乱码

              -

              在md 文件中写中文内容,发布出来后为乱码,原因是md的编码不对,将md文件另存为“UTF-8”编码的文件即可解决问题。

              -

              References
              【1】Windows下Git安装指南(http://www.cnblogs.com/zhcncn/p/3787849.html)

              -

              【2】Hexo (https://github.com/hexojs/hexo)

              -

              【3】hexo你的博客(http://ibruce.info/2013/11/22/hexo-your-blog/)

              -

              【4】Hexo All Themes(https://github.com/hexojs/hexo/wiki/Themes)

              -

              【5】Pacman主题介绍(http://yangjian.me/pacman/hello/introducing-pacman-theme/)

              -

              【6】hexo添加404页面(http://ruocaiwu.github.io/2014/08/14/hexo%E6%B7%BB%E5%8A%A0404%E9%A1%B5%E9%9D%A2/)

              -

              【7】如何搭建一个独立博客——简明Github Pages与Hexo教程(http://cnfeat.com/2014/05/10/2014-05-11-how-to-build-a-blog/)

              -

              【8】如何向google提交sitemap(详细)(http://fionat.github.io/blog/2013/10/23/sitemap/)

              -

              【9】GitHub Ribbons(https://github.com/blog/273-github-ribbons)

              -

              分类: Browser Dev
              标签: Browser Dev
              好文要顶 关注我 收藏该文
              金石开
              关注 - 9
              粉丝 - 33
              +加关注
              8
              « 上一篇:VirtualBox中安装CentOS-6.6虚拟机
              » 下一篇:Sublime Text 3安装与使用
              posted @ 2014-11-14 17:57 金石开 阅读(29881) 评论(8) 编辑 收藏
              评论列表

              -

              #1楼 2015-06-20 21:00 刘岩石
              NOTE2:

              -

              如果你是为一个项目制作网站,那么需要把branch设置为gh-pages
              请问这句话是什么意思?要是我想建立一个 读书 的栏目呢?
              支持(0)反对(0)

              -

              #2楼 2015-06-20 21:26 刘岩石
              还有的就是我写了branch:gh-pages部署在github分支也成功了,但是怎么通过github.io访问的时候显示失败,也就是怎么显示出来分支的内容
              支持(0)反对(0)

              -

              #3楼 2015-06-20 21:28 刘岩石
              请问我在本地还要生成一个gh-pages分支吗?
              支持(0)反对(0)

              -

              #4楼 2015-06-20 21:35 刘岩石
              hexo new “postName” #新建文章
              hexo new page “pageName” #新建页面
              这里的新建页面的意思是?和新建文章有什么区别吗?
              支持(0)反对(0)

              -

              #5楼 2015-06-20 21:37 刘岩石
              http://segmentfault.com/q/1010000000618915 解决了
              支持(0)反对(0)

              -

              #6楼 2015-09-11 15:52 Vsdrop
              1
              2
              3
              4
              5
              6
              使用SSH url,如果电脑没有开放SSH 端口,会致部署失败。

              -

              fatal: Could not read from remote repository.

              -

              Please make sure you have the correct access rights
              and the repository exists.

              -

              请问这个问题在 win7 下如何解决 卡在这里了
              支持(0)反对(0)

              -

              #7楼[楼主] 2015-09-14 16:57 金石开
              @ Vsdrop
              你是在公司配置的吗?公司的网络一般在域中已经把SSH端口禁掉了。你最好在自己的机器上配置。
              支持(0)反对(0)

              -

              #8楼 2016-03-07 11:37 我不是管哥
              @ 刘岩石
              请问你这个问题是怎么解决的呢?我现在想把public下面的文件放在master分支下,其他的文件都放在新建的hexo分支下,这个怎么弄呢?
              什么情况下需要新建gh-page分支呢?
              支持(0)反对(0)
              刷新评论刷新页面返回顶部
              注册用户登录后才能发表评论,请 登录 或 注册,访问网站首页。
              【推荐】50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
              【推荐】融云即时通讯云-豆果美食、Faceu等亿级APP都在用

              -

              最新IT新闻:
              · Gear VR太小儿科 三星宣布研发不插手机独立虚拟现实头盔
              · 三星电子第一季度净利润45.6亿美元 同比增长13.6%
              · 你不知道的关于计算机大师Dijkstra的事情
              · 手机销号后五类捆绑须解除
              · Facebook第一季度净利润15.1亿美元 同比增长195%
              » 更多新闻…

              -

              最新知识库文章:
              · 架构漫谈(九):理清技术、业务和架构的关系
              · 架构漫谈(八):从架构的角度看如何写好代码
              · 架构漫谈(七):不要空设架构师这个职位,给他实权
              · 架构漫谈(六):软件架构到底是要解决什么问题?
              · 架构漫谈(五):什么是软件
              » 更多知识库文章…
              历史上的今天:
              2012-11-14 Little Tips
              随笔档案(48)

              -

              2015年9月 (1)

              - - -
              - - - -
              - -
              - - - - - - - - - - -
              - - - -
              - - - - -
              - - -

              - 我的第一个博客 -

              - - -
              - - -
              - -

              我的第一个博客

                -
              • aaa
              • -
              • asdfasd按时的
              • -
              • 撒发生的多少
              • -
              - - -
              - - - -
              - -
              - - - - - - - - - - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/android/index.html b/tags/android/index.html deleted file mode 100644 index c2ef31e..0000000 --- a/tags/android/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: android | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/git/index.html b/tags/git/index.html deleted file mode 100644 index ea38686..0000000 --- a/tags/git/index.html +++ /dev/null @@ -1,338 +0,0 @@ - - - - - - - Tag: git | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/html5/index.html b/tags/html5/index.html deleted file mode 100644 index 1d1eb90..0000000 --- a/tags/html5/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: html5 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/index.html b/tags/index.html deleted file mode 100644 index 40922de..0000000 --- a/tags/index.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - 所有标签 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - - - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/java/index.html b/tags/java/index.html deleted file mode 100644 index 9a0d178..0000000 --- a/tags/java/index.html +++ /dev/null @@ -1,590 +0,0 @@ - - - - - - - Tag: java | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              - - - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/java/page/2/index.html b/tags/java/page/2/index.html deleted file mode 100644 index fa7580b..0000000 --- a/tags/java/page/2/index.html +++ /dev/null @@ -1,590 +0,0 @@ - - - - - - - Tag: java | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              - - - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/java/page/3/index.html b/tags/java/page/3/index.html deleted file mode 100644 index 649757a..0000000 --- a/tags/java/page/3/index.html +++ /dev/null @@ -1,497 +0,0 @@ - - - - - - - Tag: java | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
              - - - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/javascript/index.html b/tags/javascript/index.html deleted file mode 100644 index 65d4323..0000000 --- a/tags/javascript/index.html +++ /dev/null @@ -1,338 +0,0 @@ - - - - - - - Tag: javascript | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/linux/index.html b/tags/linux/index.html deleted file mode 100644 index ae15604..0000000 --- a/tags/linux/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: linux | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/maven/index.html b/tags/maven/index.html deleted file mode 100644 index dd40536..0000000 --- a/tags/maven/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: maven | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/memcached/index.html b/tags/memcached/index.html deleted file mode 100644 index 5a2bf3c..0000000 --- a/tags/memcached/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: memcached | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/mongodb/index.html b/tags/mongodb/index.html deleted file mode 100644 index faa21c6..0000000 --- a/tags/mongodb/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: mongodb | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/mysql/index.html b/tags/mysql/index.html deleted file mode 100644 index cb8ed90..0000000 --- a/tags/mysql/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: mysql | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/nginx/index.html b/tags/nginx/index.html deleted file mode 100644 index 2d339c6..0000000 --- a/tags/nginx/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: nginx | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/nodejs/index.html b/tags/nodejs/index.html deleted file mode 100644 index 216a83b..0000000 --- a/tags/nodejs/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: nodejs | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/oracle/index.html b/tags/oracle/index.html deleted file mode 100644 index 867b0ce..0000000 --- a/tags/oracle/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: oracle | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/redis/index.html b/tags/redis/index.html deleted file mode 100644 index bb9c1bf..0000000 --- a/tags/redis/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: redis | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/soa/index.html b/tags/soa/index.html deleted file mode 100644 index f5c3e3a..0000000 --- a/tags/soa/index.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - Tag: soa | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - - - - - - - - - - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/solr/index.html b/tags/solr/index.html deleted file mode 100644 index 11b53d8..0000000 --- a/tags/solr/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: solr | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git a/tags/tomcat/index.html b/tags/tomcat/index.html deleted file mode 100644 index 74d567a..0000000 --- a/tags/tomcat/index.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: tomcat | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file diff --git "a/tags/\351\232\217\347\254\224/index.html" "b/tags/\351\232\217\347\254\224/index.html" deleted file mode 100644 index d91f6a9..0000000 --- "a/tags/\351\232\217\347\254\224/index.html" +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - Tag: 随笔 | 菜菜的博客 - - - - - - - - - - - - - - - - - - - - -
              -
              -
              -
              - -
              - -
              -
              - - -
              - - - - - - -
              -
              - 2016 -
              -
              - - - - -
              - - - - -
              -
              -
              - -
              -
              -
              - - - - - - - - - - - - - - - - - - -
              - - \ No newline at end of file