Redis 使用笔记(二)

R

Redis可以支持存储不同格式的数据,Redis是一个数据结构服务器,它不是简单的key-value存储,以下是可以作为存储的类型:

  • 二进制安全的字符串
  • 列表(Lists),按顺序插入的字符串元素的集合
  • 集合(Sets),是不重复的并且无序的

阅读全文

Maven 添加本地依赖包

M

在实际项目开发中,项目使用到的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 中使用 Config Server

J

JHipster中使用Config Server

JHipster中当启动一个微服务或者网关时,首先会连接到JHipster Registry去获取相应的配置信息,而微服务工程下的配置信息将被覆盖(只覆盖某个配置项的值,不会覆盖整个文件),可在http://localhost:8761/#/config下查看各个微服务在JHipster Registry中的配置信息

  • 开发环境一般使用本地文件系统来保存
  • 生产环境使用git仓库服务器上的配置

保存配置文件的地址需要在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-configusers.yml的配置项:

    testData:
      user:
          id: 本地文件系统配置文件中的id值
          name: 本地文件系统配置文件中的name值
    
  • 远程git仓库下users-dev.yml的配置项:

    testData:
      user:
          id: git远程仓库下配置文件中的id值
          name: git远程仓库下配置文件中的name值
    

在swagger测试结果如下:
http://o743aqnrb.bkt.clouddn.com/config%20server%20testtest-id.png

http://o743aqnrb.bkt.clouddn.com/config%20server%20testtest-name.png

因为远程git仓库下没有配置remark的值,因此取到的值是微服务工程下配置的:
http://o743aqnrb.bkt.clouddn.com/config%20server%20testtest-reamrk.png

参考:http://www.infoq.com/cn/articles/spring-cloud-service-wiring

JHipster 生成的 gateway 服务网关启动无法自动建表

J

在使用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后问题就解决了。

参考:http://stackoverflow.com/questions/4489548/why-there-can-be-only-one-timestamp-column-with-current-timestamp-in-default-cla

MySQL 开启允许公网连接

M

部署项目后,发现本地无法连接到MySQL远程数据库:
原因一:MySQL没有允许公网访问
查看是否公网访问:

netstat

如果mysql的Local Address列的值是127.0.0.1:3306,则说明没有开启公网访问
解决方法:
修改mysql配置文件/etc/mysql/my.cnf,将bind-address的值改为0.0.0.0

Redis 使用笔记(一)

R

最近项目用到redis作为内存数据库,

windows系统启动redis

redis-server

阅读全文

MySQL 使用笔记

M

MySQL修改用户密码

  • 修改root密码
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'@'%';

使用sum时如果没记录不返回null,需要返回0的解决方法

select coalesce(sum(price), 0) as total group by type;

使用iconfont字体图标

使

最近在做移动web app项目,前端很多地方需要用到小图标,移动端不像PC端,图标的选择几乎不会去用到图片,因为要做不同屏幕的适配,一般更好的选择是使用字体图标,这样就可以通过CSS随意控制图标的大小和颜色了。项目用到的是iconfont,下面就介绍下如何使用 icomoon图标制作工具 来快速生成字体图标。

首先到http://iconfont.cn/,选择到需要用到的图标。
通过搜索找一个微信图标:

阅读全文

CSS 相关浏览器兼容问题 [1]

C

在填坑。

问题1:IE7下,如果浮动元素前面的兄弟元素中存在行内元素,浮动元素会掉到下一行

<div class="container">
    <span>span</span>
    <span>span</span>
    <div class="left">左浮动的div</div>
    <div class="right">右浮动的div</div>
</div>

chrome,firefox下:

IE7下:

解决:

  1. 针对IE7为浮动元素设置负的margin-top值
  2. 将浮动元素写到行内元素前面
    <div class="container">
     <div class="left">左浮动的div</div>
     <div class="right">右浮动的div</div>
     <span>span</span>
     <span>span</span>
    </div>
    

问题2:IE6不认识min-height

解决:

div {
    min-height: 200px;
    height: 200px!important;  /* 因为IE6不识别!important,这一句对IE6无效*/
    height: 200px;            /* 这一句对IE6生效,IE6盒子的高度会被内容撑大,即使设置了高度,因此这样做的效果和min-height是一样的*/
}

问题3:IE8, 9, 10 下标签中的图片会出现边框

直接去掉边框:

img {
    border:none;
}

用Node.js爬虫下载花瓣网画板的图片

记一次略坑的过程。 源码地址

最近刚好同学毕设在做爬虫,看了一下,如果不是搞得太复杂的话主要只是写些正则,于是我尝试写个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太多会导致下载较慢。

TOP