Redis可以支持存储不同格式的数据,Redis是一个数据结构服务器,它不是简单的key-value存储,以下是可以作为存储的类型:
- 二进制安全的字符串
- 列表(Lists),按顺序插入的字符串元素的集合
- 集合(Sets),是不重复的并且无序的
Redis可以支持存储不同格式的数据,Redis是一个数据结构服务器,它不是简单的key-value存储,以下是可以作为存储的类型:
在实际项目开发中,项目使用到的jar包不一定来自Maven远程仓库,也可能来自本地的jar包,这时候也可以配置pom.xml
添加本地包的依赖,具体配置如下:
<dependency>
<groupId>com.taobao.api</groupId>
<artifactId>com.taobao.api</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${basedir}/src/main/java/WEB-INF/lib/taobao-sdk-java-auto_1455552377940-20160505.jar</systemPath>
</dependency>
JHipster中当启动一个微服务或者网关时,首先会连接到JHipster Registry去获取相应的配置信息,而微服务工程下的配置信息将被覆盖(只覆盖某个配置项的值,不会覆盖整个文件),可在http://localhost:8761/#/config下查看各个微服务在JHipster Registry中的配置信息
保存配置文件的地址需要在jhipster-registry
下的application-dev.yml
中配置,配置如下:
spring:
profiles:
active: dev
include: native
cloud:
config:
server:
native:
# 绝对路径配置:search-locations: file:///E:/config-repo
search-locations: file:./central-config
git:
uri: https://github.com/chuiliu/config-repo
以上配置项说明:
spring.profiles.active:dev
:使用开发环境的配置spring.profiles.include:git
:使用本地文件系统的配置,如果要使用远程git仓库的配置,则需修改spring.profiles.include
值为git
默认命名规则:{微服务名}[-dev|prod].yml,
此外,命名为application[-dev|prod].yml
的配置文件将对所有注册到注册中心的微服务和网关起作用,可以在其中配置各个微服务所共有的信息。
例如:
在名为gateway的网关的生产环境的配置文件应命名为:gateway-prod.yml
名为users的微服务的配置文件则命名为:users-prod.yml
可在各个微服务项目下的bootstrap[-dev|prod].yml
文件中修改以下配置项来定义配置文件名:
spring:
cloud:
config:
name: newFilename
测试users微服务:
package com.mycompany.myapp.web.rest;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by Administrator on 2016/8/30.
*/
@RestController
@RequestMapping("/api")
@Api(value = "/api", description = "test", position = 1)
// 测试自动刷新配置
@ConfigurationProperties
public class testConfigResource {
@Value("${testData.user.id:defaultValue}")
private String id;
@Value("${testData.user.name:defaultValue}")
private String name;
@Value("${testData.user.remark:defaultValue}")
private String remark;
@RequestMapping(value = "test_id", method = RequestMethod.GET)
public String testIntroduction() {
return "id:" + id;
}
@RequestMapping(value = "test_name", method = RequestMethod.GET)
public String testName() {
return "name:" + name;
}
@RequestMapping(value = "test_remark", method = RequestMethod.GET)
public String testRemark() {
return "remark:" + remark;
}
}
项目配置如下:
users微服务工程下application-dev.yml
的配置项:
testData:
user:
id: users微服务项目下配置文件中的id值
name: users微服务项目下配置文件中的name值
remark: users微服务项目下配置文件中的remark值(在本地文件系统中的配置和git远程仓库中均没有配置这个值)
jhipster-registry
下的application-dev.yml
中配置:
spring:
profiles:
active: dev
include: git
cloud:
config:
server:
git:
uri: https://github.com/chuiliu/config-repo
本地文件系统file:./central-config
下users.yml
的配置项:
testData:
user:
id: 本地文件系统配置文件中的id值
name: 本地文件系统配置文件中的name值
远程git仓库下users-dev.yml
的配置项:
testData:
user:
id: git远程仓库下配置文件中的id值
name: git远程仓库下配置文件中的name值
在swagger测试结果如下:
因为远程git仓库下没有配置remark的值,因此取到的值是微服务工程下配置的:
参考:http://www.infoq.com/cn/articles/spring-cloud-service-wiring
在使用JHipster生成的网关微服务启动时报错,无法生成用户数据库,导致无法进行后续的用户注册和登录,具体报错信息如下:
Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause [Failed SQL: CREATE TABLE gateway.jhi_user (id BIGINT AUTO_INCREMENT NOT NULL, login VARCHAR(50) NOT NULL, password_hash VARCHAR(60) NULL, first_name VARCHAR(50) NULL, last_name VARCHAR(50) NULL, email VARCHAR(100) NULL, activated BIT(1) NOT NULL, lang_key VARCHAR(5) NULL, activation_key VARCHAR(20) NULL, reset_key VARCHAR(20) NULL, created_by VARCHAR(50) NOT NULL, created_date timestamp DEFAULT NOW() NOT NULL, reset_date timestamp NULL, last_modified_by VARCHAR(50) NULL, last_modified_date timestamp DEFAULT NOW() NULL, CONSTRAINT PK_JHI_USER PRIMARY KEY (id), UNIQUE (email), UNIQUE (login))]
这个错误是数据库版本导致的,在一个mysql数据表中同时使用了多个timesatmp类型的字段并且都设置了默认值时,低版本MySQL不支持多个CURRENT_TIMESTAMP默认值,MySQL5.6.5以上版本则允许。
我是使用的数据库版本是MySQL5.1,重新安装为MySQL5.7后问题就解决了。
部署项目后,发现本地无法连接到MySQL远程数据库:
原因一:MySQL没有允许公网访问
查看是否公网访问:
netstat
如果mysql的Local Address
列的值是127.0.0.1:3306
,则说明没有开启公网访问
解决方法:
修改mysql配置文件/etc/mysql/my.cnf
,将bind-address
的值改为0.0.0.0
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('new password');
-- 用户允许来自所有主机地址的登录
CREATE USER username IDENTIFIED BY 'password';
-- 限制只能在localhost登录
CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
GRANT All ON databasename.* TO 'username'@'localhost';
-- 使用%表示来自任何地址的连接
GRANT All ON databasename.* TO 'username'@'%';
-- 授予允许来自所有主机地址,并且对所有数据库的操作权限
GRANT All ON *.* TO 'username'@'%';
-- 授予指定权限
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, GRANT ON *.* TO 'username'@'%';
select coalesce(sum(price), 0) as total group by type;
最近在做移动web app项目,前端很多地方需要用到小图标,移动端不像PC端,图标的选择几乎不会去用到图片,因为要做不同屏幕的适配,一般更好的选择是使用字体图标,这样就可以通过CSS随意控制图标的大小和颜色了。项目用到的是iconfont
,下面就介绍下如何使用 icomoon图标制作工具 来快速生成字体图标。
首先到http://iconfont.cn/,选择到需要用到的图标。
通过搜索找一个微信图标:
在填坑。
<div class="container">
<span>span</span>
<span>span</span>
<div class="left">左浮动的div</div>
<div class="right">右浮动的div</div>
</div>
chrome,firefox下:
IE7下:
解决:
<div class="container">
<div class="left">左浮动的div</div>
<div class="right">右浮动的div</div>
<span>span</span>
<span>span</span>
</div>
解决:
div {
min-height: 200px;
height: 200px!important; /* 因为IE6不识别!important,这一句对IE6无效*/
height: 200px; /* 这一句对IE6生效,IE6盒子的高度会被内容撑大,即使设置了高度,因此这样做的效果和min-height是一样的*/
}
直接去掉边框:
img {
border:none;
}
记一次略坑的过程。 源码地址
最近刚好同学毕设在做爬虫,看了一下,如果不是搞得太复杂的话主要只是写些正则,于是我尝试写个Node版的简单点的,很喜欢逛花瓣网,有时想批量下载,但是却没办法下载整个画板的图片,因此不妨把喜欢的画板图片爬下来并一次性下载到本地。做个功能给自己用也好。
首先是找到任意画板的地址,于是我随意找了个宫崎骏的画板进去,http://huaban.com/boards/25498000/,其实多研究几个url,观察其区别,不难发现,这个url对我们的有效信息就是25498000
,也就是画板ID。
然而当我打开源代码查看时,什么鬼,网页全是用js渲染的,不能用cheerio
来解决问题了,只能想一下有什么捷径,看到有可以用Phantom.js
,感觉比较麻烦,我只是想下载图片而已,继续研究下数据看看是否有必要再说。
于是我打开Chrome控制台,可以发现,当我们点开画板中的图片或者下拉加载更多时,ajax会去请求这么一个url:http://huaban.com/boards/25498000/?iwlq03dw&max=580817693&limit=20&wfl=1,这个请求对应的响应信息中pins
字段下有画板中20张图片的信息,并且是规规则则的js对象,很明显url中limit=20
就是获取前20张图片的意思,我们可以改变这个值来获取,而pin_count
:408
就是这个画板的总图片数量。感觉还是勉强可以通过正则来取出我们要的json
数据的。
于是就试了下,先把数据打印出来看看
var http = require('http');
var url = 'http://huaban.com/boards/25498000/?iwlq03dw&max=580817693&limit=20&wfl=1';
http.get(url, function(res) {
var html = '';
res.on('data', function(data) {
html += data;
});
res.on('end', function() {
console.log(html)
});
});
找到我要的信息是放在app.page["board"]
,于是我从这里开始截取,截取到对象的结束处,对象结束处的标志是};
的出现,于是构造成json数据。
/**
* 取到画板数据
*/
var getBoardObj = function(html) {
var board = /(app\.page\["board"\]).*};/.exec(html)[0];
board = board.substring(17, board.length - 1).trim().substring(1);
return JSON.parse(board);
};
尝试打印出来,并替换多个url测试,最终确认是可行的,感觉快可以实现了。
但是经过一番研究,发现当把limit
的值变成画板总图片数量时并不能获取到所有数据,经测试花瓣网限制住了只能获取到100张,路又走不通了。
最终经过重重失败和测试,终于发现max是上一页的最后一张图片的id,而那个没有值的参数iwlq03dw
是会变的,最后一位从a-z再从0-9重复循环,可能只是个标识吧,无法知道具体是做什么用的,但发现不影响得到的数据。
于是就可以构造加载更多的url了。
/**
* 加载更多
*/
var loadMore = function(url) {
var nextUrl = url.replace(/max=\d*&/, 'max=' + images[images.length - 1].pin_id + '&');
fetchData(nextUrl, downloadAll);
};
发现这些规律后好做多了,然而新的问题又来了,我们应该从哪一张图片开始获取呢?我所取到的url是加载第二页的数据的url,从这个url作为入口去获取只能获取到从第二页到最后一页的数据,第一页丢失了,又把自己带坑里了。折腾一番之后,猜测后台可能的实现逻辑,假如这个字段为空,一般我们写后台会从第一页数据开始获取吧,于是测试下,果然,发现不填max值也可以获取数据,并且是从第一页,感觉又可以有追求了。
程序实现思路:先分页请求,把图片全部取到,放到images数组里面,然后再下载images数组中的图片。
下载的时候使用async
模块来控制并发,一次只给下载3张,每下载完成一张则callback一次,如果没控制的话,下载太多图最后会一直等待没响应。
async.mapLimit(images, 3, function(image, callback) {
// 下载
download(image, callback);
}, function (err, result) {
console.log('下载完成情况:' + result);
});
下载方法的实现
var ws = fs.createWriteStream(filePath);
ws.on('finish', function() {
console.log('' + filename + ' 已下载');
callback(null, filename + '下载成功');
});
http.get(imgUrl, function(res) {
res.pipe(ws);
}).on('finish', function() {
console.log('http请求完成: ', imgUrl);
}).on('error', function() {
console.log('error');
});
最终程序
& 源码地址 爬取花瓣网画板图片
var http = require('http'),
fs = require('fs'),
async = require('async');
var url = 'http://huaban.com/boards/155643/?ip44g0nc&max=&limit=20&wfl=1',
imageUrlBase = 'http://img.hb.aicdn.com/',
downloadPath = 'download/';
// 保存所有图片
var images = [],
// 图片类型
imagesTypes = {
'image/png': '.png',
'image/jpeg': '.jpg',
'image/bmp': '.bmp',
'image/gif': '.gif',
'image/x-icon': '.ico',
'image/tiff': '.tif',
'image/vnd.wap.wbmp': '.wbmp'
};
/**
* 获取花瓣网数据
*/
var fetchData = function(url, callback) {
console.log('开始抓取花瓣网图片地址');
// 爬取数据
http.get(url, function(res) {
var html = '';
res.on('data', function(data) {
html += data;
});
res.on('end', function() {
// 取到画板数据
var board = getBoardObj(html);
var pins = board.pins;
images = images.concat(pins);
// 画板图片总数量
var count = board.pin_count;
console.log('已抓取到' + board.pins.length + '张图片的地址');
if (images.length == count || pins.length == 0) {
// 停止抓取
console.log('抓取结束,即将下载' + images.length + '张图片');
callback && callback();
return;
} else {
// 加载更多
loadMore(url);
}
});
}).on('error', function() {
console.log('error');
});
};
/**
* 取到画板数据
* @return {[type]} [description]
*/
var getBoardObj = function(html) {
var board = /(app\.page\["board"\]).*};/.exec(html)[0];
board = board.substring(17, board.length - 1).trim().substring(1);
return JSON.parse(board);
};
/**
* 加载更多
*/
var loadMore = function(url) {
var nextUrl = url.replace(/max=\d*&/, 'max=' + images[images.length - 1].pin_id + '&');
fetchData(nextUrl, downloadAll);
};
var downloadAll = function() {
// 创建名为画板ID的文件夹
downloadPath += images[0].board_id + '/';
if(!fs.existsSync(downloadPath)) {
fs.mkdirSync(downloadPath);
}
async.mapLimit(images, 3, function(image, callback) {
// 下载
download(image, callback);
}, function (err, result) {
console.log('下载完成情况:' + result);
});
};
/**
* 下载图片
*/
var downloadCount = 0;
var download = function(image, callback) {
var imgUrl = imageUrlBase + image.file.key;
var filename = image.file.id + (imagesTypes[image.file.type] || '.jpg');
var filePath = downloadPath + filename;
if (fs.existsSync(filePath)) {
console.log('图片 ', filePath, ' 已存在');
++downloadCount;
callback(null, '图片已存在');
} else {
var ws = fs.createWriteStream(filePath);
ws.on('finish', function() {
console.log('' , filename, ' 已下载,总下载进度', 100 * (++downloadCount / images.length).toFixed(2), '%');
callback(null, filename + '下载成功');
});
http.get(imgUrl, function(res) {
res.pipe(ws);
}).on('finish', function() {
console.log('http请求完成: ', imgUrl);
}).on('error', function() {
console.log('error');
});
}
};
// begin
fetchData(url, downloadAll);
下载其它画板的话,将画板ID替换就可以了。
注:图片太大or太多会导致下载较慢。