Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

github自动提交——强迫症救星!

$
0
0

前言

进入自己github主页会看到自己的提交记录,如果某天没有提交记录,那天的小方框就显示灰色。强迫症的我,每次进来看着就感觉不爽, 想着自己每天记得提交点东西,争取像阮一峰大神一样,每天都有提交记录。

阮一峰github提交记录

但是,毕竟是人,哪天忙了就会忘记提交,所以想着能不能实现在自己阿里云服务器(linux系统)上,设置cron,定制下git命令,实现每天定点自动提交。

第一步:克隆我的项目

不同的克隆方式导致校验方式不同,对应的免秘方式也不一样。简单来说,https通过记住账号密码免登,ssh通过校验生成的密钥免登。

  1. https克隆

https克隆

  1. ssh克隆

ssh克隆

如果,你已经克隆了项目,不知道采用了哪种方式,可以执行:

git remote -v

如果是这样:

origin https://github.com/tywei90/git-auto-commit.git (fetch)  
origin https://github.com/tywei90/git-auto-commit.git (push)  

那么就是https方式;

如果是这样:

origin  git@github.com:tywei90/git-auto-commit.git (fetch)  
origin  git@github.com:tywei90/git-auto-commit.git (push)  

那么就是ssh方式。

更改克隆方式也很简单:

https ——> ssh
git remote set-url origin git@github.com:tywei90/git-auto-commit.git

ssh ——> https
git remote set-url origin https://github.com/tywei90/git-auto-commit.git

第二步:免密登录

针对上面两种克隆项目的方式,有两种免密登录设置。

1.账号密码免登(https克隆)

cd git-auto-commit/.git
vim config

在config文件最后添加如下代码:

[credential]  
    helper = store

保存,输入一次账号密码后第二次就会记住账号密码了

2.公钥私钥免登(ssh克隆)

2.1 生成公钥和私钥

检查本机的ssh密钥:

cd ~/.ssh 
ls

如果提示:No such file or directory,说明你是第一次使用git,那就自己手动创建目录

使用ssh-keygen命令生成ssh密钥,命令如下:

ssh-keygen -t rsa

输入上面命令后,遇到选择直接回车,即可生成ssh 密钥。生成ssh 密钥后,可以到~/.ssh目录下查看相关文件,一般来说ssh 密钥会包含id_rsa和id_rsa.pub两个文件,分别表示生成的私钥和公钥。

2.2 拷贝公钥到你的github

在.ssh目录下,执行cat id_rsa.pub,复制所有公钥内容

点击github的头像,在下拉菜单中选择 setting 选项,在打开页面的左侧菜单中点击 SSH and GPG keys,然后点击新页面右上角绿色按钮 New SSH key。填写title值,并将复制的公钥内容粘贴到key输入框中提交。

2.3 测试链接github

我看网上是输入如下命令:

ssh –t git@github.com

然后,我的会报ssh: Could not resolve hostname \342\200\223t: Name or service not known的错误,搜了下,解决办法是执行下列命令:

ssh -t -p 22 git@github.com 

-p表示修改服务器端口为22,当提示输入(yes/no)?时在后面输入yes回车即可。但是最后还是报错,后来又搜了下,执行如下代码:

ssh git@github.com

即将-t去掉就好了,看到 Hi ** You’ve successfully authenticated, but GitHub does not provide shell access. 说明连接成功了,大家可以都试一试。

第三步:设置cron,定时自动提交任务

项目里的add.js是用来修改records.txt的,每次执行会将当前的时间附加到records.txt文件末尾。然后让git自动提交即可。下面关键是cron的设置,对于linux系统不熟悉的我还是花了点时间的,这里直接将cron设置粘贴出来。先执行crontab -e进入cron编辑,然后粘贴如下代码:

00 12 * * * cd /home/git-auto-commit && git pull && /root/.nvm/versions/node/v6.6.0/bin/node add.js && git commit -a -m 'git auto commit' && git push origin master && git log -1 | mail -s "git auto commit successfully!" wty2368@163.com
  • 00 12 * * *的意思是,每天的12:00执行后面的命令。

  • /root/.nvm/versions/node/v6.6.0/bin/node是node二进制执行文件的绝对路径,不能直接写node命令,不会识别的。如何查出自己的node执行目录,其实很简单,执行which node即可。

  • 'git auto commit'是每次提交的comment,可以随意发挥

  • git log -1 | mail -s "git auto commit successfully!" wty2368@163.com是取最新的一次git提交记录log作为邮件内容,"git auto commit successfully!"作为标题,发送邮件给wty2368@163.com邮箱。当然这个是可选项,我想让每次自动提交结束后给我发一封确认邮件,通过观察邮件内容的date值是不是当前时间,就可以判断这次自动提交是否成功。如果大家要实现这个功能,需要配置下linux邮件发送设置,这个有时间再写。主要要注意阿里云服务器对邮件25端口的限制,比较坑!

第四步:利用shell脚本批量补上之前的提交记录

上面的步骤解决了之后每天的git提交记录,但是github自己主页默认显示之前一年的提交记录,那如何补上之前的记录呢?好在github的提交记录时间以commit时间为准,所以我们可以更改自己电脑的时间,然后再commit。我系统是centos7,这里仅以此为例。不同linux版本修改时间的命令可能不同,大家网上百度下,文章很多。

4.1 修改系统时间为想要弥补时间段的终点

比如,想要修改时间段位2018-01-01~2018-01-31,那么需要修改系统时间为2018-01-31,脚本如下:

timedatectl set-time '2018-01-31 13:00:00'

4.2 进入项目目录,执行loop脚本

确定我们要修改的天数,2018-01-01到2018-01-31一共是31天,我们在命令行传入此参数

cd git-auto-commit
screen -d -m -L sh loop.sh 31

这里,screen -d -m -L命令可以将我们执行的任务后台,这样即使退出服务器连接也不会终止脚本的运行。大家可以刷新自己的github主页,看看是不是灰色区域都变绿了。

后记

至此,github自动提交设置就完成了,妈妈再也不用担心我哪天忘记提交github。:smile:

工作生活中,我们经常会有各种各样的想法,大家不要忽视了或者觉得很难就不去做。其实真正动手去实践,发现并没有那么难,反而很有趣。而且不知不觉中就学到了很多知识。

欢迎大家star学习交流:github地址 | 我的博客

(完)


博客内容管理系统

$
0
0

这个项目最初其实是fork别人的项目。当初想接触下mongodb数据库,找个例子学习下,后来改着改着就面目全非了。后台和数据库重构,前端增加了登录注册功能,仅保留了博客设置页面,但是也优化了。

线上地址

一、更新内容

  1. 数据库重新设计,改成以用户分组的subDocs数据库结构
  2. 应数据库改动,所有接口重新设计,并统一采用和立马理财一致的接口风格
  3. 删除原来游客模式,增加登录注册功能,支持弹窗登录。
  4. 增加首页,展示最新发布文章和注册用户
  5. 增加修改密码,登出,注销等功能。
  6. 优化pop弹窗组件,更加智能,更多配置项,接近网易$.dialog组件。并且一套代码仅修改了下css,实现相同接口下pc端弹窗和wap端toast功能。
  7. 增加移动端适配
  8. 优化原来代码,修复部分bug。

更多的更新内容请移步项目CMS-of-Blog_ProductionCMS-of-Blog

二、核心代码分析

原作者也写过分析的文章。这里,主要分析一下我更新的部分。

1. 数据库

对原数据库进行重新设计,改成以用户分组的subDocs数据库结构。这样以用户为一个整体的数据库结构更加清晰,同时也更方便操作和读取。代码如下:

var mongoose =  require('mongoose'),
    Schema =    mongoose.Schema

    articleSchema = new Schema({
        title: String,
        date: Date,
        content: String,
    }),

    linkSchema = new Schema({
        name: String,
        href: String,
        newPage: Boolean
    }),

    userSchema = new Schema({
        name: String,
        password: String,
        email: String,
        emailCode: String,
        createdTime: Number,
        articles: [articleSchema],
        links: [linkSchema]
    }),

    User = mongoose.model('User', userSchema);

mongoose.connect('mongodb://localhost/platform')
mongoose.set('debug', true)

var db = mongoose.connection
db.on('error', function () {
    console.log('db error'.error)
})
db.once('open', function () {
    console.log('db opened'.silly)
})

module.exports = {
    User: User
}

代码一开始新定义了三个Schema:articleSchema、linkSchema和userSchema。而userSchema里又嵌套了articleSchema和linkSchema,构成了以用户分组的subDocs数据库结构。Schema是一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力。然后将将该Schema发布为Model。Model由Schema发布生成的模型,具有抽象属性和行为的数据库操作对。由Model可以创建的实体,比如新注册一个用户就会创建一个实体。

数据库创建了之后需要去读取和操作,可以看下注册时发送邮箱验证码的这段代码感受下。

router.post('/genEmailCode', function(req, res, next) {
    var email = req.body.email,
    resBody = {
        retcode: '',
        retdesc: '',
        data: {}
    }
    if(!email){
        resBody = {
            retcode: 400,
            retdesc: '参数错误',
        }
        res.send(resBody)
        return
    }
    function genRandomCode(){
        var arrNum = [];
        for(var i=0; i<6; i++){
            var tmpCode = Math.floor(Math.random() * 9);
            arrNum.push(tmpCode);
        }
        return arrNum.join('')
    }
    db.User.findOne({ email: email }, function(err, doc) {
        if (err) {
            return console.log(err)
        } else if (doc && doc.name !== 'tmp') {
            resBody = {
                retcode: 400,
                retdesc: '该邮箱已注册',
            }
            res.send(resBody)
        } else if(!doc){  // 第一次点击获取验证码
            var emailCode = genRandomCode();
            var createdTime = Date.now();
            // setup e-mail data with unicode symbols
            var mailOptions = {
                from: '"CMS-of-Blog ?" <tywei90@163.com>', // sender address
                to: email, // list of receivers
                subject: '亲爱的用户' + email, // Subject line
                text: 'Hello world ?', // plaintext body
                html: [
                    '<p>您好!恭喜您注册成为CMS-of-Blog博客用户。</p>',
                    '<p>这是一封发送验证码的注册认证邮件,请复制一下验证码填写到注册页面以完成注册。</p>',
                    '<p>本次验证码为:' + emailCode + '</p>',
                    '<p>上述验证码30分钟内有效。如果验证码失效,请您登录网站<a href="https://cms.wty90.com/#!/register">CMS-of-Blog博客注册</a>重新申请认证。</p>',
                    '<p>感谢您注册成为CMS-of-Blog博客用户!</p><br/>',
                    '<p>CMS-of-Blog开发团队</p>',
                    '<p>'+ (new Date()).toLocaleString() + '</p>'
                ].join('') // html body
            };
            // send mail with defined transport object
            transporter.sendMail(mailOptions, function(error, info){
                if(error){
                    return console.log(error);
                }
                // console.log('Message sent: ' + info.response);
                new db.User({
                    name: 'tmp',
                    password: '0000',
                    email: email,
                    emailCode: emailCode,
                    createdTime: createdTime,
                    articles: [],
                    links: []
                }).save(function(err) {
                    if (err) return console.log(err)
                    // 半小时内如果不注册成功,则在数据库中删除这条数据,也就是说验证码会失效
                    setTimeout(function(){
                        db.User.findOne({ email: email }, function(err, doc) {
                            if (err) {
                                return console.log(err)
                            } else if (doc && doc.createdTime === createdTime) {
                                db.User.remove({ email: email }, function(err) {
                                    if (err) {
                                        return console.log(err)
                                    }
                                })
                            }
                        })
                    }, 30*60*1000);
                    resBody = {
                        retcode: 200,
                        retdesc: ''
                    }
                    res.send(resBody)
                })
            });
        }else if(doc && doc.name === 'tmp'){
            // 在邮箱验证码有效的时间内,再次点击获取验证码(类似省略)
            ...
        }
    })
})

后台接受到发送邮箱验证码的请求后,会初始化一个tmp的用户。通过new db.User()会创建一个User的实例,然后执行save()操作会将这条数据写到数据库里。如果在半小时内没有注册成功,通过匹配邮箱,然后db.User.remove()将这条数据删除。更多具体用法请移步官方文档

2. 后台

将所有请求分为三种:

  • ajax异步请求,统一路径:/web/
  • 公共页面部分,如博客首页、登录、注册等,统一路径:/
  • 与博客用户id相关的博客部分,统一路径:/:id/

这样每个用户都可以拥有自己的博客页面,具体代码如下:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./index');
var db = require('./db')
var app = express();

// view engine setup
app.set('views', path.join(__dirname, '../'));
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use('/public',express.static(path.join(__dirname, '../public')));

// 公共ajax接口(index.js)
app.use('/web', routes);

// 公共html页面,比如登录页,注册页
app.get('/', function(req, res, next) {
    res.render('common', { title: 'CMS-blog' });
})

// 跟用户相关的博客页面(路由的第一个参数只匹配与处理的相关的,不越权!)
app.get(/^\/[a-z]{1}[a-z0-9_]{3,15}$/, function(req, res, next) {
    // format获取请求的path参数
    var pathPara = req._parsedUrl.pathname.slice(1).toLocaleLowerCase()
    // 查询是否对应有相应的username
    db.User.count({name: pathPara}, function(err, num) {
        if (err) return console.log(err)
        if(num > 0){
            res.render('main', { title: 'CMS-blog' });
        }else{
            // 自定义错误处理
            res.status(403);
            res.render('error', {
                message: '该用户尚未开通博客。<a href="/#!/register">去注册</a>',
            });
        }
    })
})

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

module.exports = app;

具体的ajax接口代码大家可以看server文件夹下的index.js文件。

3. pop/toast组件

3.1 pop/toast组件配置参数说明

  • pop: 弹窗的显示与否, 根据content参数,有内容则为true
  • css: 自定义弹窗的class, 默认为空
  • showClose: 为false则不显示关闭按钮, 默认显示
  • closeFn: 弹窗点击关闭按钮之后的回调
  • title: 弹窗的标题,默认’温馨提示’, 如果不想显示title, 直接传空
  • content(required): 弹窗的内容,支持传html
  • btn1: ‘按钮1文案|按钮1样式class’, 格式化后为btn1Text和btn1Css
  • cb1: 按钮1点击之后的回调,如果cb1没有明确返回true,则默认按钮点击后关闭弹窗
  • btn2: ‘按钮2文案|按钮2样式class’, 格式化后为btn2Text和btn2Css
  • cb2: 按钮2点击之后的回调,如果cb2没有明确返回true,则默认按钮点击后关闭弹窗。按钮参数不传,文案默认’我知道了’,点击关闭弹窗
  • init: 弹窗建立后的初始化函数,可以用来处理复杂交互(注意弹窗一定要是从pop为false变成true才会执行)
  • destroy: 弹窗消失之后的回调函数
  • wapGoDialog: 在移动端时,要不要走弹窗,默认false,走toast

3.2 pop/toast组件代码

<template>
    <div class="m-dialog" :class="getPopPara.css">
        <div class="dialog-wrap">
            <span class="close" @click="handleClose" v-if="getPopPara.showClose">+</span>
            <div class="title" v-if="getPopPara.title">{{getPopPara.title}}</div>
            <div class="content">{{{getPopPara.content}}}</div>
            <div class="button">
                <p class="btn" :class="getPopPara.btn1Css" @click="fn1">
                    <span>{{getPopPara.btn1Text}}</span>
                </p>
                <p class="btn" :class="getPopPara.btn2Css" @click="fn2" v-if="getPopPara.btn2Text">
                    <span>{{getPopPara.btn2Text}}</span>
                </p>
            </div>
        </div>
    </div>
</template>

<script>
    import {pop}                from '../vuex/actions'
    import {getPopPara}         from '../vuex/getters'
    import $                    from '../js/jquery.min'

    export default{
        computed:{
            showDialog(){
                return this.getPopPara.pop
            }
        },
        vuex: {
            getters: {
                getPopPara
            },
            actions: {
                pop
            }
        },
        methods: {
            fn1(){
                let fn = this.getPopPara.cb1
                let closePop = false
                //  如果cb1函数没有明确返回true,则默认按钮点击后关闭弹窗
                if(typeof fn == 'function'){
                    closePop = fn()
                }
                // 初始值为false, 所以没传也默认关闭
                if(!closePop){
                    this.pop()
                }
                // !fn && this.pop()
            },
            fn2(){
                let fn = this.getPopPara.cb2
                let closePop = false
                //  如果cb1函数没有明确返回true,则默认按钮点击后关闭弹窗
                if(typeof fn == 'function'){
                    closePop = fn()
                }
                // 初始值为false, 所以没传也默认关闭
                if(!closePop){
                    this.pop()
                }
                // !fn && this.pop()
            },
            handleClose(){
                // this.pop()要放在最后,因为先执行所有参数就都变了
                let fn = this.getPopPara.closeFn
                typeof fn == 'function' && fn()
                this.pop()
            }
        },
        watch:{
            'showDialog': function(newVal, oldVal){
                // 弹窗打开时
                if(newVal){
                    // 增加弹窗支持键盘操作
                    $(document).bind('keydown', (event)=>{
                        // 回车键执行fn1,会出现反复弹窗bug
                        if(event.keyCode === 27){
                            this.pop()
                        }
                    })
                    var $dialog = $('.dialog-wrap');
                    // 移动端改成类似toast,通过更改样式,既不需要增加toast组件,也不需要更改代码,统一pop方法
                    if(screen.width < 700 && !this.getPopPara.wapGoDialog){
                        $dialog.addClass('toast-wrap');
                        setTimeout(()=>{
                            this.pop();
                            $dialog.removeClass('toast-wrap');
                        }, 2000)
                    }
                    //调整弹窗居中
                    let width = $dialog.width();
                    let height = $dialog.height();
                    $dialog.css('marginTop', - height/2);
                    $dialog.css('marginLeft', - width/2);
                    // 弹窗建立的初始化函数
                    let fn = this.getPopPara.init;
                    typeof fn == 'function' && fn();
                }else{
                    // 弹窗关闭时
                    // 注销弹窗打开时注册的事件
                    $(document).unbind('keydown')
                    // 弹窗消失回调
                    let fn = this.getPopPara.destroy
                    typeof fn == 'function' && fn()
                }
            }
        }
    }
</script>
<style lang="sass">
    @import "../style/components/Pop.scss";
</style>

3.3 pop/toast组件参数格式化代码

为了使用方便,我们在使用的时候进行了简写。为了让组件能识别,需要在vuex的action里对传入的参数格式化。

function pop({dispatch}, para) {
    // 如果没有传入任何参数,默认关闭弹窗
    if(para === undefined){
        para = {}
    }
    // 如果只传入字符串,格式化内容为content的para对象
    if(typeof para === 'string'){
        para = {
            content: para
        }
    }
    // 设置默认值
    para.pop = !para.content? false: true
    para.showClose = para.showClose === undefined? true: para.showClose
    para.title = para.title === undefined? '温馨提示': para.title
    para.wapGoDialog = !!para.wapGoDialog
    // 没有传参数
    if(!para.btn1){
        para.btn1 = '我知道了|normal'
    }
    // 没有传class
    if(para.btn1.indexOf('|') === -1){
        para.btn1 = para.btn1 + '|primary'
    }
    let array1 = para.btn1.split('|')
    para.btn1Text = array1[0]
    // 可能会传多个class
    for(let i=1,len=array1.length; i<len; i++){
        if(i==1){
            // class为disabled属性不加'btn-'
            para.btn1Css = array1[1]=='disabled'? 'disabled': 'btn-' + array1[1]
        }else{
            para.btn1Css = array1[i]=='disabled'? ' disabled': para.btn1Css + ' btn-' + array1[i]
        }
    }

    if(para.btn2){
        if(para.btn2.indexOf('|') === -1){
            para.btn2 = para.btn2 + '|normal'
        }
        let array2 = para.btn2.split('|')
        para.btn2Text = array2[0]
        for(let i=1,len=array2.length; i<len; i++){
            if(i==1){
                para.btn2Css = array2[1]=='disabled'? 'disabled': 'btn-' + array2[1]
            }else{
                para.btn2Css = array2[i]=='disabled'? ' disabled': para.btn2Css + ' btn-' + array2[i]
            }
        }
    }
    dispatch('POP', para)
}

为了让移动端兼容pop弹窗组件,我们采用mediaQuery对移动端样式进行了更改。增加参数wapGoDialog,表明我们在移动端时,要不要走弹窗,默认false,走toast。这样可以一套代码就可以兼容pc和wap。

后记

这里主要分析了下后台和数据库,而且比较简单,大家可以去看源码。总之,这是一个不错的前端入手后台和数据库的例子。功能比较丰富,而且可以学习下vue.js。

欢迎大家star学习交流:github地址 | 我的博客

基于ionic的混合APP实战

$
0
0

这个项目做得比较早,当时是基于ionic1和angular1做的。做了四个tabs的app,首页模仿携程首页,第二页主要是phonegap调用手机核心功能,第三页模仿微信和qq聊天页,第四页模仿一般手机的表单设置页。同时还模仿知乎做了一个侧边栏页(账号:wty,密码:123456)。

没有后台,纯前端展示,功能还比较多,调用系统的声音、震动和手机设备信息等。有二维码扫描功能,还做了类似qq消息可拖拽效果,上拉下拉刷新,轮播图组件。

安卓apk下载

安卓apk下载二维码

线上地址

一、基本概念

1. Angularjs简介

Angularjs是一款优秀的前端 JS 框架,已用于 Google 的多款产品当中 如 Gmail、Maps、Calender 等。AngularJS有着诸多特性,最为核心的是:MVVM、模块化、自动化双向数据绑定、语义标签、依赖注入,等等。

2. Ionic简介

Ionic是一个强大的 HTML5 应用程序开发框架,具有速度快,界面现代化、美观等特点。特别适合用于基于 Hybird 模式的 HTML5 移动应用程序开发。

3. Phonegap简介

Phonegap是一个用基于 HTML, CSS 和 JavaScript 的,创建移动跨平台移动应用程序的 快速开发平台。它使开发者能够手机的核心功能——包括地理定位,加速器,联系人,声音和振动等,此外PhoneGap 拥有丰富的插件,可以调用。

二、项目各tab主要功能介绍

1. 初始化配置

  • 手机上app显示的图标、名称、开机画面
  • 注入依赖
  • 隐藏显示键盘
  • hammer触屏手势插件配置
  • 菜单栏的位置、导航条文字位置、回退按钮图标等
  • 切换页面的过渡效果(bug)
  • AngularUI Router
  • services服务

2. tab-home

  • 幻灯指令 ion-slide-box
  • 触屏手势切换页面
  • 栅格系统
  • 触屏手势touch-bases和hammerjs
  • ng-init、ng-click、 ng-src、 ng-repeat指令,双向数据绑定
  • 打开app内置的浏览器webview方法
  • 上拉刷新

3. tab-dash

  • phonegap功能的应用:二维码扫描、调用系统弹窗、震动铃声功能、获取设备信息
  • ion-side-menus侧边栏功能
  • ionic 动态组件 $ionicModal弹出登录界面
  • ng-show、ng-model 双向数据绑定实现登录验证的实时监控
  • ionic 动态组件 $ionicPopup弹出注销界面
  • 更换头像(访问手机摄像头、图库功能)
  • 切换主题颜色

4. tab-chats

  • 删除按钮和重新排序按钮
  • 下拉刷新
  • 滑动显示分享编辑按钮
  • 长按显示动态组件$ionicActionSheet选项
  • 红色消息badge

5. tab-account

  • ionic的表单应用
  • “声音”选项被选中播放铃声
  • “震动”选项被选中开始震动
  • 实现全选、全不选、反选的功能
  • ionic动态组件$ionicPopup
  • 根据被选择数显示相应弹窗内容

三、演示如下:

ionic实战动态图演示

四、总 结

优点: 通过使用 web 技术开发 App,采用 Cordova/PhoneGap之类进行打包封装。优点是采用标准的web技术开发,避免了不同平台原生开发体系的学习,学习成本低, 上手快、 效率高,一次开发微信 wap app 全部搞定;

缺点:app 在 android 平台性能上有一些损失, 但是相信硬件的发展会接近原生。

欢迎大家star学习交流:github地址 | 我的博客

参考文献

  1. PhoneGap3.4安装视频教程下载
  2. Angular1官网
  3. Angular中文社区
  4. AngularJS Nice Things
  5. phonegap 中文网
  6. ionic官网

NodeJS爬虫——立马理财

$
0
0

其实在早之前,就做过立马理财的销售额统计,只不过是用前端js写的,需要在首页的console调试面板里粘贴一段代码执行,点击这里。主要是通过定时爬取https://www.lmlc.com/s/web/home/user_buying异步接口来获取数据。然后通过一定的排重算法来获取最终的数据。但是这样做有以下缺点:

  1. 代码只能在浏览器窗口下运行,关闭浏览器或者电脑就失效了
  2. 只能爬取一个页面的数据,不能整合其他页面的数据
  3. 爬取的数据无法存储到本地
  4. 上面的异步接口数据会部分过滤,导致我们的排重算法失效

由于最近学习了node爬虫相关知识,我们可以在后台自己模拟请求,爬取页面数据。并且我开通了阿里云服务器,可以把代码放到云端跑。这样,1、2、3都可以解决。4是因为之前不知道这个ajax接口是每三分钟更新一次,这样我们可以根据这个来排重,确保数据不会重复。说到爬虫,大家想到的比较多的还是python,确实python有Scrapy等成熟的框架,可以实现很强大的爬取功能。但是node也有自身的优点,凭借强大的异步特性,可以很轻松的实现高效的异步并发请求,节省cpu的开销。其实node爬虫还是比较简单的,下面我们就来分析整个爬虫爬取的流程和最终如何展示数据的。

线上地址

一、爬虫流程

我们最终的目标是实现爬取立马理财每日的销售额,并知道卖了哪些产品,每个产品又被哪些用户在什么时间点买的。首先,介绍下爬虫爬取的主要步骤:

1. 结构分析

我们要爬取页面的数据,第一步当然是要先分析清楚页面结构,要爬哪些页面,页面的结构是怎样的,需不需要登录;有没有ajax接口,返回什么样的数据等。

2. 数据抓取

分析清楚要爬取哪些页面和ajax,就要去抓取数据了。如今的网页的数据,大体分为同步页面和ajax接口。同步页面数据的抓取就需要我们先分析网页的结构,python抓取数据一般是通过正则表达式匹配来获取需要的数据;node有一个cheerio的工具,可以将获取的页面内容转换成jquery对象,然后就可以用jquery强大的dom API来获取节点相关数据, 其实大家看源码,这些API本质也就是正则匹配。ajax接口数据一般都是json格式的,处理起来还是比较简单的。

3. 数据存储

抓取的数据后,会做简单的筛选,然后将需要的数据先保存起来,以便后续的分析处理。当然我们可以用MySQL和Mongodb等数据库存储数据。这里,我们为了方便,直接采用文件存储。

4. 数据分析

因为我们最终是要展示数据的,所以我们要将原始的数据按照一定维度去处理分析,然后返回给客户端。这个过程可以在存储的时候去处理,也可以在展示的时候,前端发送请求,后台取出存储的数据再处理。这个看我们要怎么展示数据了。

5. 结果展示

做了这么多工作,一点展示输出都没有,怎么甘心呢?这又回到了我们的老本行,前端展示页面大家应该都很熟悉了。将数据展示出来才更直观,方便我们分析统计。

二、爬虫常用库介绍

1. Superagent

Superagent是个轻量的的http方面的库,是nodejs里一个非常方便的客户端请求代理模块,当我们需要进行get、post、head等网络请求时,尝试下它吧。

2. Cheerio

Cheerio大家可以理解成一个 Node.js 版的 jquery,用来从网页中以 css selector 取数据,使用方式跟 jquery 一模一样。

3. Async

Async是一个流程控制工具包,提供了直接而强大的异步功能mapLimit(arr, limit, iterator, callback),我们主要用到这个方法,大家可以去看看官网的API。

4. arr-del

arr-del是我自己写的一个删除数组元素方法的工具。可以通过传入待删除数组元素index组成的数组进行一次性删除。

5. arr-sort

arr-sort是我自己写的一个数组排序方法的工具。可以根据一个或者多个属性进行排序,支持嵌套的属性。而且可以再每个条件中指定排序的方向,并支持传入比较函数。

三、页面结构分析

先屡一下我们爬取的思路。立马理财线上的产品主要是定期和立马金库(最新上线的光大银行理财产品因为手续比较麻烦,而且起投金额高,基本没人买,这里不统计)。定期我们可以爬取理财页的ajax接口:https://www.lmlc.com/web/product/product_list?pageSize=10&pageNo=1&type=0。(update: 定期近期没货,可能看不到数据)数据如下图所示:

理财页ajax接口数据

这里包含了所有线上正在销售的定期产品,ajax数据只有产品本身相关的信息,比如产品id、筹集金额、当前销售额、年化收益率、投资天数等,并没有产品被哪些用户购买的信息。所以我们需要带着id参数去它的产品详情页爬取,比如立马聚财-12月期HLB01239511。详情页有一栏投资记录,里边包含了我们需要的信息,如下图所示:

详情页投资记录

但是,详情页需要我们在登录的状态下才可以查看,这就需要我们带着cookie去访问,而且cookie是有有效期限制的,如何保持我们cookie一直在登录态呢?请看后文。

其实立马金库也有类似的ajax接口:https://www.lmlc.com/web/product/product_list?pageSize=10&pageNo=1&type=1,但是里边的相关数据都是写死的,没有意义。而且金库的详情页也没有投资记录信息。这就需要我们爬取一开始说的首页的ajax接口:https://www.lmlc.com/s/web/home/user_buying。但是后来才发现这个接口是三分钟更新一次,就是说后台每隔三分钟向服务器请求一次数据。而一次是10条数据,所以如果在三分钟内,购买产品的记录数超过10条,数据就会有遗漏。这是没有办法的,所以立马金库的统计数据会比真实的偏少。

四、爬虫代码分析

1. 获取登录cookie

因为产品详情页需要登录,所以我们要先拿到登录的cookie才行。getCookie方法如下:

function getCookie() {
    superagent.post('https://www.lmlc.com/user/s/web/logon')
        .type('form')
        .send({
            phone: phone,
            password: password,
            productCode: "LMLC",
            origin: "PC"
        })
        .end(function(err, res) {
            if (err) {
                handleErr(err.message);
                return;
            }
            cookie = res.header['set-cookie']; //从response中得到cookie
            emitter.emit("setCookeie");
        })
}

phone和password参数是从命令行里传进来的,就是立马理财用手机号登录的账号和密码。我们用superagent去模拟请求立马理财登录接口:https://www.lmlc.com/user/s/web/logon。传入相应的参数,在回调中,我们拿到header的set-cookie信息,并发出一个setCookeie事件。因为我们设置了监听事件:emitter.on("setCookie", requestData),所以一旦获取cookie,我们就会去执行requestData方法。

2. 理财页ajax的爬取

requestData方法的代码如下:

function requestData() {
    superagent.get('https://www.lmlc.com/web/product/product_list?pageSize=100&pageNo=1&type=0')
    .end(function(err,pres){
        // 常规的错误处理
        if (err) {
            handleErr(err.message);
            return;
        }
        // 在这里清空数据,避免一个文件被同时写入
        if(clearProd){
            fs.writeFileSync('data/prod.json', JSON.stringify([]));
            clearProd = false;
        }
        let addData = JSON.parse(pres.text).data;
        let formatedAddData = formatData(addData.result);
        let pageUrls = [];
        if(addData.totalPage > 1){
            handleErr('产品个数超过100个!');
            return;
        }
        for(let i=0,len=addData.result.length; i<len; i++){
            if(+new Date() < addData.result[i].buyStartTime){
                if(preIds.indexOf(addData.result[i].id) == -1){
                    preIds.push(addData.result[i].id);
                    setPreId(addData.result[i].buyStartTime, addData.result[i].id);
                }
            }else{
                pageUrls.push('https://www.lmlc.com/web/product/product_detail.html?id=' + addData.result[i].id);
            }
        }
        function setPreId(time, id){
            cache[id] = setInterval(function(){
                if(time - (+new Date()) < 1000){
                    // 预售产品开始抢购,直接修改爬取频次为1s,防止丢失数据
                    clearInterval(cache[id]);
                    clearInterval(timer);
                    delay = 1000;
                    timer = setInterval(function(){
                        requestData();
                    }, delay);
                    // 同时删除id记录
                    let index = preIds.indexOf(id);
                    sort.delArrByIndex(preIds, [index]);
                }
            }, 1000)
        }
        // 处理售卖金额信息
        let oldData = JSON.parse(fs.readFileSync('data/prod.json', 'utf-8'));
        for(let i=0, len=formatedAddData.length; i<len; i++){
            let isNewProduct = true;
            for(let j=0, len2=oldData.length; j<len2; j++){
                if(formatedAddData[i].productId === oldData[j].productId){
                    isNewProduct = false;
                }
            }
            if(isNewProduct){
                oldData.push(formatedAddData[i]);
            }
        }
        fs.writeFileSync('data/prod.json', JSON.stringify(oldData));
        let time = (new Date()).format("yyyy-MM-dd hh:mm:ss");
        console.log((`理财列表ajax接口爬取完毕,时间:${time}`).warn);
        if(!pageUrls.length){
            delay = 32*1000;
            clearInterval(timer);
            timer = setInterval(function(){
                requestData();
            }, delay);
            return
        }
        getDetailData();
    });
}

代码很长,getDetailData函数代码后面分析。

请求的ajax接口是个分页接口,因为一般在售的总产品数不会超过10条,我们这里设置参数pageSize为100,这样就可以一次性获取所有产品。

clearProd是全局reset信号,每天0点整的时候,会清空prod(定期产品)和user(首页用户)数据。

因为有时候产品较少会采用抢购的方式,比如每天10点,这样在每天10点的时候数据会更新很快,我们必须要增加爬取的频次,以防丢失数据。所以针对预售产品即buyStartTime大于当前时间,我们要记录下,并设定计时器,当开售时,调整爬取频次为1次/秒,见setPreId方法。

如果没有正在售卖的产品,即pageUrls为空,我们将爬取的频次设置为最大32s。

requestData函数的这部分代码主要记录下是否有新产品,如果有的话,新建一个对象,记录产品信息,push到prod数组里。prod.json数据结构如下:

[{
  "productName": "立马聚财-12月期HLB01230901",
  "financeTotalAmount": 1000000,
  "productId": "201801151830PD84123120",
  "yearReturnRate": 6.4,
  "investementDays": 364,
  "interestStartTime": "2018年01月23日",
  "interestEndTime": "2019年01月22日",
  "getDataTime": 1516118401299,
  "alreadyBuyAmount": 875000,
  "records": [
  {
    "username": "刘**",
    "buyTime": 1516117093472,
    "buyAmount": 30000,
    "uniqueId": "刘**151611709347230,000元"
  },
  {
    "username": "刘**",
    "buyTime": 1516116780799,
    "buyAmount": 50000,
    "uniqueId": "刘**151611678079950,000元"
  }]
}]

是一个对象数组,每个对象表示一个新产品,records属性记录着售卖信息。

3. 产品详情页的爬取

我们再看下getDetailData的代码:

function getDetailData(){
    // 请求用户信息接口,来判断登录是否还有效,在产品详情页判断麻烦还要造成五次登录请求
    superagent
        .post('https://www.lmlc.com/s/web/m/user_info')
        .set('Cookie', cookie)
        .end(function(err,pres){
        // 常规的错误处理
        if (err) {
            handleErr(err.message);
            return;
        }
        let retcode = JSON.parse(pres.text).retcode;
        if(retcode === 410){
            handleErr('登陆cookie已失效,尝试重新登陆...');
            getCookie();
            return;
        }
        var reptileLink = function(url,callback){
            // 如果爬取页面有限制爬取次数,这里可设置延迟
            console.log( '正在爬取产品详情页面:' + url);
            superagent
                .get(url)
                .set('Cookie', cookie)
                .end(function(err,pres){
                    // 常规的错误处理
                    if (err) {
                        handleErr(err.message);
                        return;
                    }
                    var $ = cheerio.load(pres.text);
                    var records = [];
                    var $table = $('.buy-records table');
                    if(!$table.length){
                        $table = $('.tabcontent table');
                    }
                    var $tr = $table.find('tr').slice(1);
                    $tr.each(function(){
                        records.push({
                            username: $('td', $(this)).eq(0).text(),
                            buyTime: parseInt($('td', $(this)).eq(1).attr('data-time').replace(/,/g, '')),
                            buyAmount: parseFloat($('td', $(this)).eq(2).text().replace(/,/g, '')),
                            uniqueId: $('td', $(this)).eq(0).text() + $('td', $(this)).eq(1).attr('data-time').replace(/,/g, '') + $('td', $(this)).eq(2).text()
                        })
                    });
                    callback(null, {
                        productId: url.split('?id=')[1],
                        records: records
                    });
                });
        };
        async.mapLimit(pageUrls, 10 ,function (url, callback) {
          reptileLink(url, callback);
        }, function (err,result) {
            let time = (new Date()).format("yyyy-MM-dd hh:mm:ss");
            console.log(`所有产品详情页爬取完毕,时间:${time}`.info);
            let oldRecord = JSON.parse(fs.readFileSync('data/prod.json', 'utf-8'));
            let counts = [];
            for(let i=0,len=result.length; i<len; i++){
                for(let j=0,len2=oldRecord.length; j<len2; j++){
                    if(result[i].productId === oldRecord[j].productId){
                        let count = 0;
                        let newRecords = [];
                        for(let k=0,len3=result[i].records.length; k<len3; k++){
                            let isNewRec = true;
                            for(let m=0,len4=oldRecord[j].records.length; m<len4; m++){
                                if(result[i].records[k].uniqueId === oldRecord[j].records[m].uniqueId){
                                    isNewRec = false;
                                }
                            }
                            if(isNewRec){
                                count++;
                                newRecords.push(result[i].records[k]);
                            }
                        }
                        oldRecord[j].records = oldRecord[j].records.concat(newRecords);
                        counts.push(count);
                    }
                }
            }
            let oldDelay = delay;
            delay = getNewDelay(delay, counts);
            function getNewDelay(delay, counts){
                let nowDate = (new Date()).toLocaleDateString();
                let time1 = Date.parse(nowDate + ' 00:00:00');
                let time2 = +new Date();
                // 根据这次更新情况,来动态设置爬取频次
                let maxNum = Math.max(...counts);
                if(maxNum >=0 && maxNum <= 2){
                    delay = delay + 1000;
                }
                if(maxNum >=8 && maxNum <= 10){
                    delay = delay/2;
                }
                // 每天0点,prod数据清空,排除这个情况
                if(maxNum == 10 && (time2 - time1 >= 60*1000)){
                    handleErr('部分数据可能丢失!');
                }
                if(delay <= 1000){
                    delay = 1000;
                }
                if(delay >= 32*1000){
                    delay = 32*1000;
                }
                return delay
            }
            if(oldDelay != delay){
                clearInterval(timer);
                timer = setInterval(function(){
                    requestData();
                }, delay);
            }
            fs.writeFileSync('data/prod.json', JSON.stringify(oldRecord));
        })
    });
}

我们先去请求用户信息接口,来判断登录是否还有效,因为在产品详情页判断麻烦还要造成五次登录请求。带cookie请求很简单,在post后面set下我们之前得到的cookie即可:.set('Cookie', cookie)。如果后台返回的retcode为410表示登录的cookie已失效,需要重新执行getCookie()。这样就能保证爬虫一直在登录状态。

async的mapLimit方法,会将pageUrls进行并发请求,一次并发量为10。对于每个pageUrl会执行reptileLink方法。等所有的异步执行完毕后,再执行回调函数。回调函数的result参数是每个reptileLink函数返回数据组成的数组。

reptileLink函数是获取产品详情页的投资记录列表信息,uniqueId是由已知的username、buyTime、buyAmount参数组成的字符串,用来排重的。

async的回调主要是将最新的投资记录信息写入对应的产品对象里,同时生成了counts数组。counts数组是每个产品这次爬取新增的售卖记录个数组成的数组,和delay一起传入getNewDelay函数。getNewDelay动态调节爬取频次,counts是调节delay的唯一依据。delay过大可能产生数据丢失,过小会增加服务器负担,可能会被管理员封ip。这里设置delay最大值为32,最小值为1。

4. 首页用户ajax爬取

先上代码:

function requestData1() {
    superagent.get(ajaxUrl1)
    .end(function(err,pres){
        // 常规的错误处理
        if (err) {
            handleErr(err.message);
            return;
        }
        let newData = JSON.parse(pres.text).data;
        let formatNewData = formatData1(newData);
        // 在这里清空数据,避免一个文件被同时写入
        if(clearUser){
            fs.writeFileSync('data/user.json', '');
            clearUser = false;
        }
        let data = fs.readFileSync('data/user.json', 'utf-8');
        if(!data){
            fs.writeFileSync('data/user.json', JSON.stringify(formatNewData));
            let time = (new Date()).format("yyyy-MM-dd hh:mm:ss");
            console.log((`首页用户购买ajax爬取完毕,时间:${time}`).silly);
        }else{
            let oldData = JSON.parse(data);
            let addData = [];
            // 排重算法,如果uniqueId不一样那肯定是新生成的,否则看时间差如果是0(三分钟内请求多次)或者三分钟则是旧数据
            for(let i=0, len=formatNewData.length; i<len; i++){
                let matchArr = [];
                for(let len2=oldData.length, j=Math.max(0,len2 - 20); j<len2; j++){
                    if(formatNewData[i].uniqueId === oldData[j].uniqueId){
                        matchArr.push(j);
                    }
                }
                if(matchArr.length === 0){
                    addData.push(formatNewData[i]);
                }else{
                    let isNewBuy = true;
                    for(let k=0, len3=matchArr.length; k<len3; k++){
                        let delta = formatNewData[i].time - oldData[matchArr[k]].time;
                        if(delta == 0 || (Math.abs(delta - 3*60*1000) < 1000)){
                            isNewBuy = false;
                            // 更新时间,这样下一次判断还是三分钟
                            oldData[matchArr[k]].time = formatNewData[i].time;
                        }
                    }
                    if(isNewBuy){
                        addData.push(formatNewData[i]);
                    }
                }
            }
            fs.writeFileSync('data/user.json', JSON.stringify(oldData.concat(addData)));
            let time = (new Date()).format("yyyy-MM-dd hh:mm:ss");
            console.log((`首页用户购买ajax爬取完毕,时间:${time}`).silly);
        }
    });
}

user.js的爬取和prod.js类似,这里主要想说一下如何排重的。user.json数据格式如下:

[
{
  "payAmount": 5067.31,
  "productId": "jsfund",
  "productName": "立马金库",
  "productType": 6,
  "time": 1548489,
  "username": "郑**",
  "buyTime": 1516118397758,
  "uniqueId": "5067.31jsfund郑**"
}, {
  "payAmount": 30000,
  "productId": "201801151830PD84123120",
  "productName": "立马聚财-12月期HLB01230901",
  "productType": 0,
  "time": 1306573,
  "username": "刘**",
  "buyTime": 1516117199684,
  "uniqueId": "30000201801151830PD84123120刘**"
}]

和产品详情页类似,我们也生成一个uniqueId参数用来排除,它是payAmount、productId、username参数的拼成的字符串。如果uniqueId不一样,那肯定是一条新的记录。如果相同那一定是一条新记录吗?答案是否定的。因为这个接口数据是三分钟更新一次,而且给出的时间是相对时间,即数据更新时的时间减去购买的时间。所以每次更新后,即使是同一条记录,时间也会不一样。那如何排重呢?其实很简单,如果uniqueId一样,我们就判断这个buyTime,如果buyTime的差正好接近180s,那么几乎可以肯定是旧数据。如果同一个人正好在三分钟后购买同一个产品相同的金额那我也没辙了,哈哈。

5. 零点整合数据

每天零点我们需要整理user.json和prod.json数据,生成最终的数据。代码:

let globalTimer = setInterval(function(){
    let nowTime = +new Date();
    let nowStr = (new Date()).format("hh:mm:ss");
    let max = nowTime;
    let min = nowTime - 24*60*60*1000;
    // 每天00:00分的时候写入当天的数据
    if(nowStr === "00:00:00"){
        // 先保存数据
        let prod = JSON.parse(fs.readFileSync('data/prod.json', 'utf-8'));
        let user = JSON.parse(fs.readFileSync('data/user.json', 'utf-8'));
        let lmlc = JSON.parse(JSON.stringify(prod));
        // 清空缓存数据
        clearProd = true;
        clearUser = true;
        // 不足一天的不统计
        // if(nowTime - initialTime < 24*60*60*1000) return
        // 筛选prod.records数据
        for(let i=0, len=prod.length; i<len; i++){
            let delArr1 = [];
            for(let j=0, len2=prod[i].records.length; j<len2; j++){
                if(prod[i].records[j].buyTime < min || prod[i].records[j].buyTime >= max){
                    delArr1.push(j);
                }
            }
            sort.delArrByIndex(lmlc[i].records, delArr1);
        }
        // 删掉prod.records为空的数据
        let delArr2 = [];
        for(let i=0, len=lmlc.length; i<len; i++){
            if(!lmlc[i].records.length){
                delArr2.push(i);
            }
        }
        sort.delArrByIndex(lmlc, delArr2);

        // 初始化lmlc里的立马金库数据
        lmlc.unshift({
            "productName": "立马金库",
            "financeTotalAmount": 100000000,
            "productId": "jsfund",
            "yearReturnRate": 4.0,
            "investementDays": 1,
            "interestStartTime": (new Date(min)).format("yyyy年MM月dd日"),
            "interestEndTime": (new Date(max)).format("yyyy年MM月dd日"),
            "getDataTime": min,
            "alreadyBuyAmount": 0,
            "records": []
        });
        // 筛选user数据
        for(let i=0, len=user.length; i<len; i++){
            if(user[i].productId === "jsfund" && user[i].buyTime >= min && user[i].buyTime < max){
                lmlc[0].records.push({
                    "username": user[i].username,
                    "buyTime": user[i].buyTime,
                    "buyAmount": user[i].payAmount,
                });
            }
        }
        // 删除无用属性,按照时间排序
        lmlc[0].records.sort(function(a,b){return a.buyTime - b.buyTime});
        for(let i=1, len=lmlc.length; i<len; i++){
            lmlc[i].records.sort(function(a,b){return a.buyTime - b.buyTime});
            for(let j=0, len2=lmlc[i].records.length; j<len2; j++){
                delete lmlc[i].records[j].uniqueId
            }
        }
        // 爬取金库收益,写入前一天的数据,清空user.json和prod.json
        let dateStr = (new Date(nowTime - 10*60*1000)).format("yyyyMMdd");
        superagent
            .get('https://www.lmlc.com/web/product/product_list?pageSize=10&pageNo=1&type=1')
            .end(function(err,pres){
                // 常规的错误处理
                if (err) {
                    handleErr(err.message);
                    return;
                }
                var data = JSON.parse(pres.text).data;
                var rate = data.result[0].yearReturnRate||4.0;
                lmlc[0].yearReturnRate = rate;
                fs.writeFileSync(`data/${dateStr}.json`, JSON.stringify(lmlc));
        })
    }
}, 1000);

globalTimer是个全局定时器,每隔1s执行一次,当时间为00:00:00时,clearProd和clearUser全局参数为true,这样在下次爬取过程时会清空user.json和prod.json文件。没有同步清空是因为防止多处同时修改同一文件报错。取出user.json里的所有金库记录,获取当天金库相关信息,生成一条立马金库的prod信息并unshift进prod.json里。删除一些无用属性,排序数组最终生成带有当天时间戳的json文件,如:20180101.json。

五、前端展示

1、整体思路

前端总共就两个页面,首页和详情页,首页主要展示实时销售额、某一时间段内的销售情况、具体某天的销售情况。详情页展示某天的具体某一产品销售情况。页面有两个入口,而且比较简单,这里我们采用gulp来打包压缩构建前端工程。后台用express搭建的,匹配到路由,从data文件夹里取到数据再分析处理再返回给前端。

2、前端用到的组件介绍

  • Echarts Echarts是一个绘图利器,百度公司不可多得的良心之作。能方便的绘制各种图形,官网已经更新到4.0了,功能更加强大。我们这里主要用到的是直方图

  • DataTables Datatables是一款jquery表格插件。它是一个高度灵活的工具,可以将任何HTML表格添加高级的交互功能。功能非常强大,有丰富的API,大家可以去官网学习。

  • Datepicker Datepicker是一款基于jquery的日期选择器,需要的功能基本都有,主要样式比较好看,比jqueryUI官网的Datepicker好看太多。

3、gulp配置

gulp配置比较简单,代码如下:

var gulp = require('gulp');
var uglify = require("gulp-uglify");
var less = require("gulp-less");
var minifyCss = require("gulp-minify-css");
var livereload = require('gulp-livereload');
var connect = require('gulp-connect');
var minimist = require('minimist');
var babel = require('gulp-babel');

var knownOptions = {
  string: 'env',
  default: { env: process.env.NODE_ENV || 'production' }
};

var options = minimist(process.argv.slice(2), knownOptions);

// js文件压缩
gulp.task('minify-js', function() {
    gulp.src('src/js/*.js')
        .pipe(babel({
          presets: ['es2015']
        }))
        .pipe(uglify())
        .pipe(gulp.dest('dist/'));
});

// js移动文件
gulp.task('move-js', function() {
    gulp.src('src/js/*.js')
        .pipe(babel({
          presets: ['es2015']
        }))
        .pipe(gulp.dest('dist/'))
        .pipe(connect.reload());
});

// less编译
gulp.task('compile-less', function() {
    gulp.src('src/css/*.less')
        .pipe(less())
        .pipe(gulp.dest('dist/'))
        .pipe(connect.reload());
});

// less文件编译压缩
gulp.task('compile-minify-css', function() {
    gulp.src('src/css/*.less')
        .pipe(less())
        .pipe(minifyCss())
        .pipe(gulp.dest('dist/'));
});

// html页面自动刷新
gulp.task('html', function () {
  gulp.src('views/*.html')
    .pipe(connect.reload());
});

// 页面自动刷新启动
gulp.task('connect', function() {
    connect.server({
        livereload: true
    });
});

// 监测文件的改动
gulp.task('watch', function() {
    gulp.watch('src/css/*.less', ['compile-less']);
    gulp.watch('src/js/*.js', ['move-js']);
    gulp.watch('views/*.html', ['html']);
});

// 激活浏览器livereload友好提示
gulp.task('tip', function() {
    console.log('\n<----- 请用chrome浏览器打开 http://localhost:5000 页面,并激活livereload插件 ----->\n');
});

if (options.env === 'development') {
    gulp.task('default', ['move-js', 'compile-less', 'connect', 'watch', 'tip']);
}else{
    gulp.task('default', ['minify-js', 'compile-minify-css']);
}

开发和生产环境都是将文件打包到dist目录。不同的是:开发环境只是编译es6和less文件;生产环境会再压缩混淆。支持livereload插件,在开发环境下,文件改动会自动刷新页面。

后记

至此,一个完整的爬虫就完成了。其实我觉得最需要花时间的是在分析页面结构,处理数据还有解决各种问题,比如如何保持一直在登录状态等。

本爬虫代码只做研究学习用处,禁止用作任何商业分析。再说,统计的数据也不准确。

因为代码开源,希望大家照着代码去爬取其他网站,如果都拿立马理财来爬,估计服务器会承受不了的额。

欢迎大家star学习交流:线上地址 | github地址 | 我的博客

前端页面制作工具

$
0
0

pagemaker是一个前端页面制作工具,方便产品,运营和视觉的同学迅速开发简单的前端页面,从而可以解放前端同学的工作量。此项目创意来自网易乐得内部项目nfop中的pagemaker项目。原来项目的前端是采用jquery和模板ejs做的,每次组件的更新都会重绘整个dom,性能不是很好。因为当时react特别火,加上项目本身的适合,最后决定采用react来试试水。因为原来整个项目是包含很多子项目一起,所以后台的实现也没有参考,完全重写。

本项目只是原来项目的简单实现,去除了用的不多和复杂的组件。但麻雀虽小五脏俱全,本项目采用了react的一整套技术栈,适合那些对react有过前期学习,想通过demo来加深理解并动手实践的同学。建议学习本demo的之前,先学习/复习下相关的知识点:React 技术栈系列教程Immutable 详解及 React 中实践

线上地址

一、功能特点

  1. 组件丰富。有标题、图片、按钮、正文、音频、视频、统计、jscss输入。
  2. 实时预览。每次修改都可以立马看到最新的预览。
  3. 支持三种导入方式,支持导出配置文件。
  4. 支持Undo/Redo操作。(组件个数发生变化为触发点)
  5. 可以随时发布、修改、删除已发布的页面。
  6. 每个页面都有一个发布密码,从而可以防止别人修改。
  7. 页面前端架构采用react+redux,并采用immutable数据结构。可以将每次组件的更新最小化,从而达到页面性能的最优化。
  8. 后台对上传的图片自动进行压缩,防止文件过大
  9. 适配移动端

二、用到的技术

1. 前端

  1. React
  2. Redux
  3. React-Redux
  4. Immutable
  5. React-Router
  6. fetch
  7. es6
  8. es7

2. 后台

  1. Node
  2. Express

3. 工具

  1. Webpack
  2. Sass
  3. Pug

三、脚手架工具

因为项目用的技术比较多,采用脚手架工具可以省去我们搭建项目的时间。经过搜索,我发现有三个用的比较多:

  1. create-react-appcreate-react-app star数
  2. react-starter-kitreact-starter-kit star数
  3. react-boilerplatereact-boilerplate star数

github上的star数都很高,第一个是Facebook官方出的react demo。但是看下来,三个项目都比较庞大,引入了很多不需要的功能包。后来搜索了下,发现一个好用的脚手架工具:yeoman,大家可以选择相应的generator。我选择的是react-webpack。项目比较清爽,需要大家自己搭建redux和immutable环境,以及后台express。其实也好,锻炼下自己构建项目的能力。

四、核心代码分析

1. Store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

import { createStore } from 'redux';
import { combineReducers } from 'redux-immutable';

import unit from './reducer/unit';
// import content from './reducer/content';

let devToolsEnhancer = null;
if (process.env.NODE_ENV === 'development') {
    devToolsEnhancer = require('remote-redux-devtools');
}

const reducers = combineReducers({ unit });
let store = null;
if (devToolsEnhancer) {
    store = createStore(reducers, devToolsEnhancer.default({ realtime: true, port: config.reduxDevPort }));
}
else {
    store = createStore(reducers);
}
export default store;

Redux 提供createStore这个函数,用来生成 Store。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。Redux 提供了一个 combineReducers 方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。当然,我们这里只有一个 unit 的 Reducer ,拆不拆分都可以。

devToolsEnhancer是个中间件(middleware)。用于在开发环境时使用Redux DevTools来调试redux。

2. Action

Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。

import Store from '../store';

const dispatch = Store.dispatch;

const actions = {
    addUnit: (name) => dispatch({ type: 'AddUnit', name }),
    copyUnit: (id) => dispatch({ type: 'CopyUnit', id }),
    editUnit: (id, prop, value) => dispatch({ type: 'EditUnit', id, prop, value }),
    removeUnit: (id) => dispatch({ type: 'RemoveUnit', id }),
    clear: () => dispatch({ type: 'Clear'}),
    insert: (data, index) => dispatch({ type: 'Insert', data, index}),
    moveUnit: (fid, tid) => dispatch({ type: 'MoveUnit', fid, tid }),
};

export default actions;

State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。代码中,我们定义了actions对象,他有很多属性,每个属性都是函数,函数的输出是派发了一个action对象,通过Store.dispatch发出。action是一个包含了必须的type属性,还有其他附带的信息。

3. Immutable

Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。详细介绍,推荐知乎上的Immutable 详解及 React 中实践。我们项目里用的是Facebook 工程师 Lee Byron 花费 3 年时间打造的immutable.js库。具体的API大家可以去官网学习。

熟悉 React 的都知道,React 做性能优化时有一个避免重复渲染的大招,就是使用 shouldComponentUpdate(),但它默认返回 true,即始终会执行 render()方法,然后做 Virtual DOM 比较,并得出是否需要做真实 DOM 更新,这里往往会带来很多无必要的渲染并成为性能瓶颈。当然我们也可以在 shouldComponentUpdate()中使用使用 deepCopy 和 deepCompare 来避免无必要的 render(),但 deepCopy 和 deepCompare 一般都是非常耗性能的。

Immutable 则提供了简洁高效的判断数据是否变化的方法,只需 ===(地址比较) 和 is( 值比较) 比较就能知道是否需要执行 render(),而这个操作几乎 0 成本,所以可以极大提高性能。修改后的 shouldComponentUpdate是这样的:

import { is } from 'immutable';

shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
  const thisProps = this.props || {}, thisState = this.state || {};

  if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
      Object.keys(thisState).length !== Object.keys(nextState).length) {
    return true;
  }

  for (const key in nextProps) {
    if (thisProps[key] !== nextProps[key] || !is(thisProps[key], nextProps[key])) {
      return true;
    }
  }

  for (const key in nextState) {
    if (thisState[key] !== nextState[key] || !is(thisState[key], nextState[key])) {
      return true;
    }
  }
  return false;
}

使用 Immutable 后,如下图,当红色节点的 state 变化后,不会再渲染树中的所有节点,而是只渲染图中绿色的部分:

immutable演示

本项目中,我们采用支持 class 语法的 pure-render-decorator来实现。我们希望达到的效果是:当我们编辑组件的属性时,其他组件并不被渲染,而且preview里,只有被修改的preview组件update,而其他preview组件不渲染。为了方便观察组件是否被渲染,我们人为的给组件增加了data-id的属性,其值为Math.random()的随机值。效果如下图所示:

immutable实际效果图

4. Reducer

Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

import immutable from 'immutable';

const unitsConfig = immutable.fromJS({
    META: {
        type: 'META',
        name: 'META信息配置',
        title: '',
        keywords: '',
        desc: ''
    },
    TITLE: {
        type: 'TITLE',
        name: '标题',
        text: '',
        url: '',
        color: '#000',
        fontSize: "middle",
        textAlign: "center",
        padding: [0, 0, 0, 0],
        margin: [10, 0, 20, 0]
    },
    IMAGE: {
        type: 'IMAGE',
        name: '图片',
        address: '',
        url: '',
        bgColor: '#fff',
        padding: [0, 0, 0, 0],
        margin: [10, 0, 20, 0]
    },
    BUTTON: {
        type: 'BUTTON',
        name: '按钮',
        address: '',
        url: '',
        txt: '',
        margin: [
            0, 30, 20, 30
        ],
        buttonStyle: "yellowStyle",
        bigRadius: true,
        style: 'default'
    },
    TEXTBODY: {
        type: 'TEXTBODY',
        name: '正文',
        text: '',
        textColor: '#333',
        bgColor: '#fff',
        fontSize: "small",
        textAlign: "center",
        padding: [0, 0, 0, 0],
        margin: [0, 30, 20, 30],
        changeLine: true,
        retract: true,
        bigLH: true,
        bigPD: true,
        noUL: true,
        borderRadius: true
    },
    AUDIO: {
        type: 'AUDIO',
        name: '音频',
        address: '',
        size: 'middle',
        position: 'topRight',
        bgColor: '#9160c3',
        loop: true,
        auto: true
    },
    VIDEO: {
        type: 'VIDEO',
        name: '视频',
        address: '',
        loop: true,
        auto: true,
        padding: [0, 0, 20, 0]
    },
    CODE: {
        type: 'CODE',
        name: 'JSCSS',
        js: '',
        css: ''
    },
    STATISTIC: {
        type: 'STATISTIC',
        name: '统计',
        id: ''
    }
})

const initialState = immutable.fromJS([
    {
        type: 'META',
        name: 'META信息配置',
        title: '',
        keywords: '',
        desc: '',
        // 非常重要的属性,表明这次state变化来自哪个组件!
        fromType: ''
    }
]);


function reducer(state = initialState, action) {
    let newState, localData, tmp
    // 初始化从localstorage取数据
    if (state === initialState) {
        localData = localStorage.getItem('config');
        !!localData && (state = immutable.fromJS(JSON.parse(localData)));
        // sessionStorage的初始化
        sessionStorage.setItem('configs', JSON.stringify([]));
        sessionStorage.setItem('index', 0);
    }
    switch (action.type) {
        case 'AddUnit': {
            tmp = state.push(unitsConfig.get(action.name));
            newState = tmp.setIn([0, 'fromType'], action.name);
            break
        }
        case 'CopyUnit': {
            tmp = state.push(state.get(action.id));
            newState = tmp.setIn([0, 'fromType'], state.getIn([action.id, 'type']));
            break
        }
        case 'EditUnit': {
            tmp = state.setIn([action.id, action.prop], action.value);
            newState = tmp.setIn([0, 'fromType'], state.getIn([action.id, 'type']));
            break
        }
        case 'RemoveUnit': {
            const type = state.getIn([action.id, 'type']);
            tmp = state.splice(action.id, 1);
            newState = tmp.setIn([0, 'fromType'], type);
            break
        }
        case 'Clear': {
            tmp = initialState;
            newState = tmp.setIn([0, 'fromType'], 'ALL');
            break
        }
        case 'Insert': {
            tmp = immutable.fromJS(action.data);
            newState = tmp.setIn([0, 'fromType'], 'ALL');
            break
        }
        case 'MoveUnit':{
            const {fid, tid} = action;
            const fitem = state.get(fid);
            if (fitem && fid != tid) {
                tmp = state.splice(fid, 1).splice(tid, 0, fitem);
            } else {
                tmp = state;
            }
            newState = tmp.setIn([0, 'fromType'], '');
            break;
        }
        default:
            newState = state;
    }
    // 更新localstorage,便于恢复现场
    localStorage.setItem('config', JSON.stringify(newState.toJS()));

    // 撤销,恢复操作(仅以组件数量变化为触发点,否则存储数据巨大,也没必要)
    let index = parseInt(sessionStorage.getItem('index'));
    let configs = JSON.parse(sessionStorage.getItem('configs'));
    if(action.type == 'Insert' && action.index){
        sessionStorage.setItem('index', index + action.index);
    }else{
        if(newState.toJS().length != state.toJS().length){
            // 组件的数量有变化,删除历史记录index指针状态之后的所有configs,将这次变化的config作为最新的记录
            configs.splice(index + 1, configs.length - index - 1, JSON.stringify(newState.toJS()));
            sessionStorage.setItem('configs', JSON.stringify(configs));
            sessionStorage.setItem('index', configs.length - 1);
        }else{
            // 组件数量没有变化,index不变。但是要更新存储的config配置
            configs.splice(index, 1, JSON.stringify(newState.toJS()));
            sessionStorage.setItem('configs', JSON.stringify(configs));
        }
    }
    
    // console.log(JSON.parse(sessionStorage.getItem('configs')));
    return newState
}

export default reducer;

Reducer是一个函数,它接受Action和当前State作为参数,返回一个新的State。unitsConfig是存储着各个组件初始配置的对象集合,所有新添加的组件都从里边取初始值。State有一个初始值:initialState,包含META组件,因为每个web页面必定有一个META信息,而且只有一个,所以页面左侧组件列表里不包含它。

reducer会根据action的type不同,去执行相应的操作。但是一定要注意,immutable数据操作后要记得赋值。每次结束后我们都会去修改fromType值,是因为有的组件,比如AUDIO、CODE等修改后,预览的js代码需要重新执行一次才可以生效,而其他组件我们可以不用去执行,提高性能。

当然,我们页面也做了现场恢复功能(localStorage),也得益于immutable数据结构,我们实现了Redo/Undo的功能。Redo/Undo的功能仅会在组件个数有变化的时候计作一次版本,否则录取的的信息太多,会对性能造成影响。当然,组件信息发生变化我们是会去更新数组的。

5. 工作流程

如下图所示:
redux流程图

用户能接触到的只有view层,就是组件里的各种输入框,单选多选等。用户与之发生交互,会发出action。React-Redux提供connect方法,用于从UI组件生成容器组件。connect方法接受两个参数:mapStateToProps和mapDispatchToProps,按照React-Redux的API,我们需要将Store.dispatch(action)写在mapDispatchToProps函数里边,但是为了书写方便和直观看出这个action是哪里发出的,我们没有遵循这个API,而是直接写在在代码中。

然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。State 一旦有变化,Store 就会调用监听函数。在React-Redux规则里,我们需要提供mapStateToProps函数,建立一个从(外部的)state对象到(UI组件的)props对象的映射关系。mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发UI组件的重新渲染。大家可以看我们content.js组件的最后代码:

export default connect(
    state => ({
        unit: state.get('unit'),
    })
)(Content);

connect方法可以省略mapStateToProps参数,那样的话,UI组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。像header和footer组件,就是纯UI组件。

为什么我们的各个子组件都可以拿到state状态,那是因为我们在最顶层组件外面又包了一层<Provider> 组件。入口文件index.js代码如下:

import "babel-polyfill";
import React from 'react';
import ReactDom from 'react-dom';
import { Provider } from 'react-redux';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';

import './index.scss';

import Store from './store';

import App from './components/app';

ReactDom.render(
    <Provider store={Store}>
        <Router history={browserHistory}>
            <Route path="/" component={App}>

            </Route>
        </Router>
    </Provider>,
    document.querySelector('#app')
);

我们的react-router采用的是browserHistory,使用的是HTML5的History API,路由切换交给后台。

五、兼容性和打包优化

1. 兼容性

为了让页面更好的兼容IE9+和android浏览器,因为项目使用了babel,所以采用babel-polyfillbabel-plugin-transform-runtime插件。

2. Antd按需加载

Antd完整包特别大,有10M多。而我们项目里主要是采用了弹窗组件,所以我们应该采用按需加载。只需在.babelrc文件里配置一下即可,详见官方说明

3. webpack配置externals属性

项目最后打包的main.js非常大,有接近10M多。在网上搜了很多方法,最后发现webpack配置externals属性的方法非常好。可以利用pc的多文件并行下载,降低自己服务器的压力和流量,同时可以利用cdn的缓存资源。配置如下所示:

externals: {
    "jquery": "jQuery",
    "react": "React",
    "react-dom": "ReactDOM",
    'CodeMirror': 'CodeMirror',
    'immutable': 'Immutable',
    'react-router': 'ReactRouter'
}

externals属性告诉webpack,如下的这些资源不进行打包,从外部引入。一般都是一些公共文件,比如jquery、react等。注意,因为这些文件从外部引入,所以在npm install的时候,有些依赖这些公共文件的包安装会报warning,所以看到这些大家不要紧张。经过处理,main.js文件大小降到3.7M,然后nginx配置下gzip编码压缩,最终将文件大小降到872KB。因为在移动端,文件加载还是比较慢的,我又给页面加了loading效果。

欢迎大家star学习交流:github地址 | 我的博客

数组多重筛选条件排序方法

$
0
0

arr-sort GitHub licenseNPM versionNPM monthly downloadsNPM total downloadsWindows Build Status

根据一个或者多个属性对数组进行排序,支持嵌套的属性。而且可以在每个条件中指定排序的方向,并支持传入比较函数。

安装

采用 npm安装:

$ npm install --save arr-sort

采用 yarn安装:

$ yarn add arr-sort

用法

通过给定的对象属性进行排序:

var arrSort = require('arr-sort');

arrSort([{foo: 'y'}, {foo: 'z'}, {foo: 'x'}],[{attr:'foo'}]);
//=> [{foo: 'x'}, {foo: 'y'}, {foo: 'z'}]

逆向排序

arrSort([{foo: 'y'}, {foo: 'z'}, {foo: 'x'}],[{attr:'foo', asc: false}]);
//=> [{foo: 'z'}, {foo: 'y'}, {foo: 'x'}]

参数

arrSort(array, comparisonArgs);
  • array: { Object Array }待排序的数组
  • comparisonArgs: { Object Array }一个或者多个对象组成的数组。 结构如下:{ ‘attr’: attr, ‘asc’: asc }
    • attr: { String }对象属性
    • asc: { Boolean | Function }指定排序的方向
      • true: 升序(默认值)
      • false: 降序
      • function: 传入的比较函数

注意

  • 如何没有提供 attr属性, 则这次的排序会自动跳过
  • attr属性值类型可以是 string 或者 number
    • 如果是 string, 我们采用 localeCompare去比较排序
    • 如果是 number, 我们直接比较值的大小
  • 如果提供的比较函数没有返回值,则这次的排序会自动跳过

例子

1. 多重条件排序

var arrSort = require('arr-sort');

var array = [
  { foo: 'bbb', num: 4,  flag: 2 },
  { foo: 'aaa', num: 3,  flag: 1 },
  { foo: 'ccc', num: -6, flag: 2 },
  { foo: 'ccc', num: 8,  flag: 2 },
  { foo: 'bbb', num: 2,  flag: 4 },
  { foo: 'aaa', num: -3, flag: 4 }
];

// sort by `flag`, then `foo`, then `num`
var result = arrSort(array,
    [{
        attr: 'flag',
        asc: true
    },
    {
        attr: 'foo',
        asc: false
    },
    {
        attr: 'num',
        asc: true
    }]
);

console.log(result);
// [ { foo: 'aaa', num: 3,  flag: 1},
//   { foo: 'ccc', num: -6, flag: 2},
//   { foo: 'ccc', num: 8,  flag: 2},
//   { foo: 'bbb', num: 4,  flag: 2},
//   { foo: 'bbb', num: 2,  flag: 4},
//   { foo: 'aaa', num: -3, flag: 4} ]

2. 嵌套的属性排序

var arrSort = require('arr-sort');

var array = [
  { locals: { foo: 'bbb', num: 4 },  flag: 2},
  { locals: { foo: 'aaa', num: 3 },  flag: 1},
  { locals: { foo: 'ccc', num: -6 }, flag: 2},
  { locals: { foo: 'ccc', num: 8 },  flag: 2},
  { locals: { foo: 'bbb', num: 2 },  flag: 4},
  { locals: { foo: 'aaa', num: -3 }, flag: 4},
];

// sort by `flag`, then `locals.foo`, then `locals.num`
var result = arrSort(array,
    [{
        attr: 'flag',
        asc: true
    },
    {
        attr: 'locals.foo',
        asc: false
    },
    {
        attr: 'locals.num',
        asc: true
    }]
);

console.log(result);
// [ { locals: { foo: 'aaa', num: 3 },  flag: 1},
//   { locals: { foo: 'ccc', num: -6 }, flag: 2},
//   { locals: { foo: 'ccc', num: 8 },  flag: 2},
//   { locals: { foo: 'bbb', num: 4 },  flag: 2},
//   { locals: { foo: 'bbb', num: 2 },  flag: 4},
//   { locals: { foo: 'aaa', num: -3 }, flag: 4} ]

3. 传入比较函数排序

如果提供了比较函数,数组会根据其返回值排序。比较函数具体可以参考docs

var arrSort = require('arr-sort');

var array = [
  { locals: { foo: 'bbb', num: 4 },  flag: -2},
  { locals: { foo: 'aaa', num: 3 },  flag: 1},
  { locals: { foo: 'ccc', num: -6 }, flag: 2},
  { locals: { foo: 'ccc', num: 8 },  flag: 2},
  { locals: { foo: 'bbb', num: 2 },  flag: 4},
  { locals: { foo: 'aaa', num: -3 }, flag: 4},
];

// sort by `flag`, then `locals.foo`, then `locals.num`
var result = arrSort(array,
    [{
        attr: 'flag',
        asc: function(a,b){return (Math.abs(a) - Math.abs(b))}
    },
    {
        attr: 'locals.foo',
        asc: false
    },
    {
        attr: 'locals.num',
        asc: true
    }]
);

console.log(result);
// [ { locals: { foo: 'aaa', num: 3 },  flag: 1},
//   { locals: { foo: 'ccc', num: -6 }, flag: 2},
//   { locals: { foo: 'ccc', num: 8 },  flag: 2},
//   { locals: { foo: 'bbb', num: 4 },  flag: -2},
//   { locals: { foo: 'bbb', num: 2 },  flag: 4},
//   { locals: { foo: 'aaa', num: -3 }, flag: 4} ]

关于

相关项目

集成测试

跑集成测试是一个非常好的熟悉一个项目及其API的方法。你可以通过以下命令安装依赖并跑测试:

$ npm install && npm test

作者

tywei90

许可证

Copyright © 2018, tywei90. Released under the MIT License.

移动端布局方案探究

$
0
0

研究了淘宝,天猫和网易彩票163的wap主页样式布局,总结移动端布局方案 注意:代码运行是file协议,在chrome里不支持引用本地文件,会提示跨域错误,可以用firefox或者Safari打开

当时做的ppt下载: 2015年12月移动端布局方案探究

一、基本概念

1. 物理像素(physical pixel)

一个物理像素是显示器(手机屏幕)上最小的物理显示单元

2. 设备独立像素(density-independent pixel)

设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如: CSS像素)

3. 位图像素

一个位图像素是栅格图像(如:png, jpg, gif等)最小的数据单元。每一个位图像素都包含着一些自身的显示信息(如:显示位置,颜色值,透明度等)

4. 设备像素比(简称dpr)

设备像素比 = 物理像素 / 设备独立像素(在某一方向上,x方向或者y方向)

5. scale

缩放比:scale = 1/dpr

6. 完美视口

<meta name="viewport" content="initial-scale=1,width=device-width,user-scalable=0,maximum-scale=1" />

二、网易彩票设计方案

网易彩票

  • 采用scale = 1.0写死viewport
  • 采用媒体查询来确定html根元素的font-size值,即rem值
  • rem + 百分比布局

媒体查询的css代码如下:

//网易彩票的响应式布局是采用媒体查询来改变rem值实现的
//媒体查询css
#media-query{
    @media screen and (min-width: 240px) {
        html,body,button,input,select,textarea {
            font-size:9px!important;
        }
    }

    @media screen and (min-width: 320px) {
        html,body,button,input,select,textarea {
            font-size:12px!important;
        }
    }

    @media screen and (min-width: 374px) {
        html,body,button,input,select,textarea {
            font-size:14px!important;
        }
    }

    @media screen and (min-width: 400px) {
        html,body,button,input,select,textarea {
            font-size:15px!important;
        }
    }
    // 省略
}

三、天猫设计方案

天猫首页

  • 采用scale = 1.0写死viewport
  • 不采用rem,body的font-size=14px写死
  • px + flexbox布局

四、遇到的问题

1. 高清屏下( dpr>1 )1px线模糊问题

大多数情况下,设计师产出各种尺寸的稿子,都是先画出大尺寸(一般2x)的稿子,再去缩小尺寸,最后导出。 这样会带来问题:如果设计师在2倍稿子里画了一条1px的线(例如border:1px),这时候假如我们要在scale=1.0里呈现的话,就会变成0.5px,而很大一部分手机是无法画出0.5px的。
理论上,1个位图像素对应于1个物理像素,图片才能得到完美清晰的展示。在普通屏幕下是没有问题的,但是在retina屏幕(dpr=2)下就会出现位图像素点不够,从而导致图片模糊的情况。


2. 高清屏下( dpr>1 )高清图片模糊问题

对于dpr=2的retina屏幕而言,1个位图像素对应于4个物理像素,由于单个位图像素不可以再进一步分割,所以只能就近取色,从而导致图片模糊(注意上述的几个颜色值)。所以,对于图片高清问题,比较好的方案就是采用两倍图片。如:200×300(css pixel)img标签,就需要提供400×600的图片。
对于dpr=2的retina屏幕而言,1个位图像素对应于4个物理像素,由于单个位图像素不可以再进一步分割,所以只能就近取色,从而导致图片模糊(注意上述的几个颜色值)。所以,对于图片高清问题,比较好的方案就是采用两倍图片。如:200×300(css pixel)img标签,就需要提供400×600的图片。

五、终极解决方案–>淘宝设计方案

淘宝首页

  • 通过js处理获取手机dpr参数,然后动态生成viewpoint
  • 获取手机物理像素宽度,分成10份,每一份的宽度即是rem的尺寸。
  • 根据设计稿的尺寸(px)分三种情况进行处理,采用px + rem布局

相关的脚本如下:

$(document).ready(function(){
    var dpr, rem, scale;
    var docEl = document.documentElement;
    var fontEl = document.createElement('style');
    var metaEl = document.querySelector('meta[name="viewport"]');
    var view1 = document.querySelector('#view-1');
    if(window.screen.width < 540){
        dpr = window.devicePixelRatio || 1;
        scale = 1 / dpr;
        rem = docEl.clientWidth * dpr / 10;
    }else{
        dpr = 1;
        scale =1;
        rem = 54;
    }
//貌似最新的淘宝网站又去掉了,只是限制了主体内容的宽度

    // 设置viewport,进行缩放,达到高清效果
    metaEl.setAttribute('content', 'width=' + dpr * docEl.clientWidth + ',initial-scale=' + scale + ',maximum-scale=' + scale + ', minimum-scale=' + scale + ',user-scalable=no');
    
    // 设置整体div的宽高
    view1.setAttribute('style', 'width:'+ docEl.clientWidth+'px; height:'+ docEl.clientHeight+'px');

    // 设置data-dpr属性,留作的css hack之用
    docEl.setAttribute('data-dpr', dpr);

    // 动态写入样式
    docEl.firstElementChild.appendChild(fontEl);
    fontEl.innerHTML = 'html{font-size:' + rem + 'px!important;}';
    $('body').attr('style', 'font-size:' + dpr * 12 +'px');

    // 给js调用的,某一dpr下rem和px之间的转换函数
    window.rem2px = function(v) {
        v = parseFloat(v);
        return v * rem;
    };
    window.px2rem = function(v) {
        v = parseFloat(v);
        return v / rem;
    };

    window.dpr = dpr;
    window.rem = rem;
})

六、设计方案总结

从以上的分析我们不难看出:

  • 网易彩票的方案上手快,开发效率高,兼容性好,但是不够灵活和精细;
  • 天猫的设计思路比较简单,flexbox非常灵活,但是flexbox的兼容性方面需要好好处理,不够精细;
  • 淘宝的方案几乎解决了移动端遇到的所有问题,堪称完美的解决方案,但是开发效率低、成本比较高。

欢迎大家star学习交流:github地址 | 我的博客

参考文献

  1. 移动前端开发之viewport的深入理解
  2. 【原创】移动端高清、多屏适配方案
  3. wap手机端页面根据dpr和宽度计算出font-size对应数值
  4. less语法

在class中的方法 可以使用链式调用吗?


大家好,问个问题,有没有测试网站点击率的工具

$
0
0

举个例子。我做好了一个网站,我想测一下,当20W点击率的时候,网站会不会卡,我怎么测试,用什么工具?

还有,比如我要做一个日点击率20W的网站 我需要考虑哪些因素

唯一能于 Spring 一战的 node 框架 —— Nest.js 中文文档 翻译进行中 (80%)

ems.js是如何实现的? 看起来好屌啊

开工大吉!用React写的一个CNode社区网站

$
0
0

各位大佬都开始上班了吧。


基于React实现的一个有点相似CNode的社区网站, 支持手机端预览

源码

在线预览

技术栈

"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-router-dom": "^4.2.2",
"mobx": "^3.4.1",
"mobx-react": "^4.3.5",
"antd": "^3.1.6",
"axios": "^0.17.1",
"moment": "^2.20.1",
"webpack": "3.8.1",
"scss": "",
"ES6": "",
"flex": "",

开发目录

.
├── README.md
├── config
│   ├── env.js
│   ├── jest
│   │   ├── cssTransform.js
│   │   └── fileTransform.js
│   ├── paths.js
│   ├── polyfills.js
│   ├── webpack.config.dev.js
│   ├── webpack.config.prod.js
│   └── webpackDevServer.config.js
├── package-lock.json
├── package.json
├── public                       # 静态资源目录
│   ├── images
│   │   ├── 11.png
│   │   └── 22.png
│   ├── index.html
│   └── manifest.json
├── scripts
│   ├── build.js
│   ├── start.js
│   └── test.js
└── src                          # 开发目录,源码文件
    ├── App.jsx                  # views入口文件
    ├── api
    │   └── index.js             # API配置
    ├── assets                   # 资源目录,跟public不同的是assets会被webpack处理
    │   ├── images
    │   │   ├── app-qrcode.png
    │   │   ├── github.svg
    │   │   ├── loading.svg
    │   │   └── not-match.gif
    │   └── scss
    │       ├── _variable.scss
    │       ├── media.scss
    │       └── style.scss
    ├── components               # 存放组件
    │   ├── footer               # 底部组件
    │   │   ├── Footer.jsx
    │   │   └── footer.scss
    │   ├── header               # 头部组件
    │   │   ├── Header.jsx
    │   │   └── header.scss
    │   ├── loading              # 加载组件
    │   │   ├── Loading.jsx
    │   │   └── loading.scss
    │   └── sidebar              # 侧边栏组件
    │       ├── Sidebar.jsx
    │       └── sidebar.scss
    ├── index.js                 # 程序主入口
    ├── registerServiceWorker.js # 这个文件的作用是缓存,下次打开会更快
    ├── router                   # 路由配置
    │   └── index.jsx
    ├── store                    # mobx的状态管理
    │   └── index.jsx
    ├── utils                    # 封装的一些公用方法
    │   └── index.js
    └── views                    # 存放页
        ├── 404                  # 404页
        │   ├── 404.jsx
        │   └── 404.scss
        ├── index                # 首页
        │   ├── Index.jsx
        │   └── style.scss
        ├── login                # 登录页
        │   ├── Login.jsx
        │   └── login.scss
        └── topic                # 主题详情页
            ├── Topic.jsx
            └── style.scss

小结

  • 本来想打算把所有CNode提供的API开发完成再发布的,然而发现需要处理的东西挺多的,再加上自己并没有太多的时间,一直在拖着。
  • 现在的想法是后续会一个一个把CNode提供的API都加上,不久后就会有一个完整版。
  • React更新的太快在写的时候发现dangerouslySetHTML 已更改成 dangerouslySetInnerHTML。
  • CNode主题详情是使用markdown写的,样式可以使用github-markdown-css.
  • CNode提供的API获取主题没有返回总条数导致不能算出总页数, 所以我写死了250页。

您的一个使得我有继续开源的动力,感谢!


# install dependencies
npm install

# serve with hot reload at localhost:3888
npm start

# build for production with minification
npm run build


License

MIT

个人开源-个人简历快速部署工具

$
0
0

Mao是一款个人简历快速部署工具,采用json配置的方法,可以快速将HTML模版页进行数据填充与模块伸展,这将意味你只需要简短的HTML代码与布局方式,就可以快速 获取到精美的个人简历。

". ban":[{
			"说明":["$1里的内容" ,"$2里的内容","$3里的内容","$4的"]
			},
			{
			"说明":["#name  +title -**** -*****","",""]
			}]

通过使用.或者#来获取到DOM中的class与id,并且获取

<h2>$1</h2>
  <p>$2</p>
  <blockquote>$3$4</blockquote>
</div>

**希望有兴趣的朋友可以一起来维护,让工具更完善 这里抛出我的小链接 ** Mao个人简历快速部署

vscode不能调试程序了

$
0
0

升级了vscode和node的版本以后,vscode不能启动调试了,按F5就报错了,已经卸载vsc和node重装了,还是不行。有大神能帮帮我吗?QQ截图20180222134540.png

分享:node模拟https请求发送微博

$
0
0

新浪微博SDK需要申请App key,极其不方便,通过Node.js模拟请求发送微博,可以方便的达到某些<del>羞羞的</del>目的。

Github: weibo-post使用Node.js通过https请求发送微博。

仅供学习交流,请勿用于商业用途,并遵守新浪微博相关规定。

用法:

使用 npm 下载包:

$ npm i --save-dev weibo-post

在你的 js 文件中:

//引入
var weiboPost = require('weibo-post');

//设置你微博的cookie
weiboPost.setCookie('your weibo login cookie');

//发送微博内容(目前仅支持文本内容,欢迎Star该Github项目)
weiboPost.post('your post content');

关于怎样获取你的微博cookie?

获取cookie

  1. 使用Chrome打开并登录微博
  2. 按 F12,打开开发者工具,切换到Network,如图
  3. 发送一条微博,查看add那条请求的内容,找到cookie

reduce累加能加判断吗?应该怎么加?

$
0
0

像下面这样,有一个mainDataList对象数组,我想求数组里对象状态不为0 的transactionAmount的和,是加一个if判断吗?怎么加?加在哪里? totalBalance() { if (!this.mainDataList) { return 0 } return this.mainDataList.reduce((prev, cur) => { // if (prev.state !== 0) //不知道怎么加进去 return (parseFloat(cur.transactionAmount) + parseFloat(prev)).toFixed( 2 ) }, 0) } 请各位大神帮忙看看,谢谢

fireFox里 a标签的download属性怎么用?

$
0
0

如题,我要下载一个图片,用a标签的download属性,在chrome里可以直接下载,但是到了fireFox里面就变成打开这张图片了,请问这种情况该怎么解决? 不剩感激!

『杭州』星火矿池招聘「高级前端」and 「NodeJS 开发」

$
0
0

如果你了解区块链,或者熟悉加密货币,或者炒币,恰巧你又是程序员。那么请速度联系我们。

我们是价值网络的守护者

  1. 全球第三 ETH 矿池 https://eth.ethfans.org
  2. ethfans.org运营方
  3. zec 矿池

我们能提供:

  1. 接近 BAT 的工资待遇。15k+
  2. 标配 MacBook Pro,herman 椅子等
  3. 符合程序员氛围的工作环境
  4. 不限量零食,下午茶。
  5. 不打卡,动态上下班
  6. 每季度的优秀员工奖励 eth
  7. 等等一线互联网公司有的福利
  8. 加密货币私募额度,炒币第一手资讯

web 前端

  1. 本科及以上学历,计算机相关专业毕业
  2. 3 年以上 web 前端开发经营
  3. 精通 react,webpack 等前端技术
  4. 精通移动端 web 开发。会 native 开发的优先
  5. 对交互体验、可用性、用户体验有较好的理解

或者

NodeJS 开发

  1. 本科及以上学历,计算机相关专业毕业
  2. 2 年以上 NodeJS 开发经营
  3. 有 Koa ,express或者 eggjs 深度使用经验
  4. 熟悉常用数据库以及,常见的性能问题调优
  5. 良好的合作习惯,热爱区块链

简历和问题请联系: lx@sparkpool.com或者 telegram:https://t.me/ssstark

简历命令行代码

$
0
0

发了好多份简历了,一次都没人回啊,现在找一份工作这么难,谁快把我收了吧。我的电话18379836417 //jl.js #!/usr/bin/env node /*readme.md

简历命令行工具,要求nodejs版本>=7.6,其他都不需要,第一次运行会自动下载依赖包。

查看帮助

$ jl -h

打印姓名

$ jl -n

打印年龄

$ jl -a

打印电话号码

$ jl -t

打印学历

$ jl -e

打印简历清单

$ jl -l

把简历清单生成txt文件

$ jl write

生成证书图片

$ jl image

*/ const config = { “description”: “简历命令行工具”, “name”: ‘e69d8ee588a9e6988e’, //hex加密 "age": ‘3332’, //hex加密 "tel": ‘3138333739383336343137’, //hex加密 "education": ‘e5a4a7e5ada6e69cace7a791’, //hex加密 "devTool": ‘macbook pro’, “github”: ‘https://github.com/FengxiaoSunmoon’, “IDE”: [‘Atom’,‘Xcode’,‘Android Studio’], “dependencies”: { “chalk”: “^1.1.1”, “minimist”: “^1.2.0”, “prompt”: “^0.2.14”, “semver”: “^5.0.3” }, } const fs = require(‘fs’); const path = require(‘path’); const { execSync } = require(‘child_process’); // Use Yarn if available, it’s much faster than the npm client. const makePackage = () => { const jsonFile = path.resolve(process.cwd(),‘package.json’) if (!fs.existsSync(jsonFile)){ var packageJson = {}; packageJson[‘dependencies’] = config[‘dependencies’]; packageJson[‘description’] = config[‘description’]; fs.writeFileSync(jsonFile,JSON.stringify(packageJson)) } } const getYarnVersionIfAvailable = () => { let yarnVersion = null; try { // execSync returns a Buffer -> convert to string if (process.platform.startsWith(‘win’)) { yarnVersion = (execSync(‘yarn --version’).toString() || ‘’).trim(); } else { yarnVersion = (execSync(‘yarn --version 2>/dev/null’).toString() || ‘’).trim(); } } catch (error) { console.log(error); } return yarnVersion; } //install all packages const install = () => { const packageObj = require(path.resolve(process.cwd(),‘package.json’)).dependencies //载入json const keys = Object.keys(packageObj) var packageToInstall = ‘’; keys.forEach(function (key) { packageToInstall += key + ‘@’ + packageObj[key].split(’^’)[1] + ’ '; }) var installCommand; let pro = ‘’; //安装完重复输入的指令 process.argv.forEach((val) => { pro += val + ’ '; }); const yarnVersion = getYarnVersionIfAvailable(); if (yarnVersion) { console.log(‘Using yarn v’ + yarnVersion); installCommand = ‘yarn add ’ + packageToInstall + ’ &&’ + pro; } else { console.log('Installing ’ + packageToInstall + ‘…’); installCommand = ‘npm install --save ’ + packageToInstall + ’ &&’ + pro; } return new Promise((resolve,reject) => { try { execSync(installCommand, {stdio: ‘inherit’}); resolve; } catch (err) { console.error(err); console.error(Command ${installCommand} failed.); reject; } }); } //加密转换 const trans = (s) => { let buf = new Buffer(s,‘hex’); //原数据是hex类型 return buf.toString(‘utf8’); } const init = async () => { try { require(‘semver’); require(‘chalk’); require(‘prompt’); require(‘minimist’); } catch (e) { await install(); } } const start = async () => { makePackage(); await init(); const semver = require(‘semver’); const chalk = require(‘chalk’); const prompt = require(‘prompt’); const options = require(‘minimist’)(process.argv.slice(2));

if(options..length === 0){ if (options.n || options.name) { console.log(trans(config.name)); } if (options.a || options.age) { console.log(trans(config.age)); } if (options.t || options.tel) { console.log(trans(config.tel)); } if (options.e || options.edu) { console.log(trans(config.education)); } if (options.l || options.list) { outputList(); } if (options.h || options.help) { console.log([ ’’, ’ Options:’, ’’, ’ -h, --help output usage information’, ’ -n, --name output the name’, ’ -a, --age output the age’, ’ -t, --tel output the telephone’, ’ -e, --edu output the education’, ’ -l, --list output the CV. list’, ’ write print the CV. list into txt’, ’ image print the image of certificate’, ’’, ].join(’\n’)); process.exit(0); } }else{ switch (options.[0]) { case “write”: writeList(); base64Image(); break; case “image”: base64Image(); break; default: console.error( ‘错误’ ); process.exit(1); } } } start()

let l1 = “30342d303820e6b8a9e5b79ee5a4a7e5ada6e6b182e5ada6”; let l2 = “30382d313220e8a5bfe997a8e5ad90e695b0e68ea7e7bc96e7a88b”; let l3 = “31322d313720e887aae4b8bbe5889be4b89ae5b9b6e887aae5ada6e7bc96e7a88b”; l1 = trans(l1); l2 = trans(l2); l3 = trans(l3); let list = [ ’’, ’ List:’, ’’, ’ ’ + l1, ’ ’ + l2, ’ ’ + l3, ’’, ].join(’\n’);

const outputList = () => { console.log(list); } const writeList = () => { fs.writeFile(“list.txt”,list,function(err){ if(err) return console.log(err); }) }

const base64Image = () => { let base64Buf = "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" var bufImage = new Buffer(base64Buf,‘base64’) fs.writeFile(‘zs.jpg’,bufImage,function(err){ if(err) console.log(err); }) }

同时向node发送上千个请求,如何用代码模拟?

Viewing all 14821 articles
Browse latest View live