文章标签 ‘nodejs’
2016五月18

Node.js 企业级开发框架

基于Sails.js MVC框架,路由自动映射,完善的权限控制体系,优美的后台界面。

集成最基础的通用功能:

系统(机构管理、用户管理、角色管理、菜单管理、定时任务、数据库备份、IP访问控制、登录日志等)

CMS(站点管理、栏目管理、内容管理、广告及链接等)

微信(会员列表、微信消息、群发消息、自动回复、关键词回复、帐号配置、菜单配置等)

 

演示地址:http://www.nodeshop.cn

联系方式:QQ  1162-4317   (备注nodejs)

 

界面截图:

QQ截图20160518095645

2

2016五月4

Node.js:集成QQ信任登录

1、申请AppId、AppKey和验证字符串

http://connect.qq.com/manage/login

网站首页头文件添加验证字符串,如:

<meta property=”qc:adminscontent=”765754250763563070636” />

填写回调地址:

必须是公网地址,可以填写多个,注意 /xxx 和 http://wizzer.cn/xxx 是两个地址,两个都需要配置。

2、开发完成

登录页面:

<span id=”qqLoginBtn”></span>

<script type="text/javascript" src="http://qzonestyle.gtimg.cn/qzone/openapi/qc_loader.js" data-appid="<%=qq_appid||''%>" data-redirecturi="<%=req.baseUrl%>/public/shop/pc/account/oauthQq" charset="utf-8" ></script>
<script type="text/javascript">
  QC.Login({
    btnId:"qqLoginBtn",    //插入按钮的节点id
    scope: "get_user_info"
  },function(oInfo, oOpts){
    //登陆成功执行
    var nickname=QC.String.escHTML(oInfo.nickname);//获取QQ会员名
    var info={
      nickname:nickname,
      gender:oInfo.gender,
      headimgurl:oInfo.figureurl_qq_1 //头像40X40
    };//封装对象
    if(QC.Login.check()){
      QC.Login.getMe(function(openId, accessToken){
        info.openid=openId;//传递openid及昵称头像等,业务逻辑自动注册会员或登录
        $.post(
          "/public/shop/pc/account/oauthQqStatus",
          info,
          function(result){
            console.log(result);
            if(result.code==0){
              window.location.href=$("#r").val()||'/member';//登录成功跳转
            }else{
              alert('登录失败');
            }
          },'json'
        );
      });
    }
  });</script>

回调页面:

<script type="text/javascript" src="http://qzonestyle.gtimg.cn/qzone/openapi/qc_loader.js" charset="utf-8" data-callback="true"></script>

退出登录:

<%if(sails.config.system.ShopConfig.oauth_open&&sails.config.system.ShopConfig.pay_wxpay&&sails.config.system.ShopConfig.oauth_qq){
var qq_appid='';
  if(sails.config.system.ShopConfig.oauth_qq_info){
    qq_appid=sails.config.system.ShopConfig.oauth_qq_info.qq_appid;
}
%>
<script type="text/javascript" src="http://qzonestyle.gtimg.cn/qzone/openapi/qc_loader.js" data-appid="<%=qq_appid||''%>" data-redirecturi="http://<%=sails.config.system.AppDomain%>/public/shop/pc/account/oauthQq" charset="utf-8" ></script>
<script type="text/javascript">
  QC.Login.signOut();
  if(QC.Login.check()==false){
    window.location.href='/public/shop/pc/account/logout';//先QQ登出,再清除session
  }else{
    window.location.reload();
  }
</script>
<%}else{%>
<script type="text/javascript">
  window.location.href='/public/shop/pc/account/logout';
</script>
<%}%>

3、申请审核

审核条件:登录页面有QQ登录图标、使用申请的QQ或测试QQ号,测试可以正常登录后提交申请,否则肯定是不通过的。

 

2016三月2

Node.js 商城系统开发进展

商品规格、会员价、商品相册、图片缩放、二维码等等,用其他语言实现起来可能比较简单的功能,用node.js 实现起来,毕竟全靠自己摸索,花了好多时间,,好在做好了,泪奔。。

 

1 2 3 4

2015十一月20

Node.js:集成百度编辑器的上传功能

Ueditor1.4.3.1版本,仅贴了图片上传代码,其他自己加。。。

/**
 * Created by root on 11/16/15.
 */
var fs = require('fs-extra');
var moment = require("moment");
module.exports = {
  index: function (req, res) {
    if (req.session.auth && !req.session.user.disabled) {
      var action = req.query.action;
      //加载配置文件
      fs.readFile(sails.config.appPath + '/assets/plugins/ueditor/node/config.json', 'utf8', function (err, config_txt) {
        var config = JSON.parse(config_txt);
        switch (action) {
          case 'config':
            return res.send(config_txt);
            break;
          case 'uploadimage':

            req.file('Filedata').upload({
              maxBytes: config.imageMaxSize
            }, function (err, uploadedFiles) {
              if (err)return res.json({state: sails.__('file.upload.err')});
              var filename = uploadedFiles[0].filename;
              var type = uploadedFiles[0].type;
              var fd = uploadedFiles[0].fd;
              var size = uploadedFiles[0].size;
              console.log('uploadimage:::' + JSON.stringify(uploadedFiles));
              if(config.imageAllowFiles.indexOf(fd.substring(fd.lastIndexOf('.')))<0)
                return res.json({state: sails.__('file.upload.err')});
              var file = fd.substring(fd.lastIndexOf('/'));
              var newPath = sails.config.system.AppBase + sails.config.system.UploadPath + "/image/" + moment().format("YYYYMMDD") + file;

              fs.copy(fd, sails.config.appPath + newPath, function (err) {
                if (err)return res.json({state: sails.__('file.upload.err')});
                return res.json({state: 'SUCCESS', url: newPath, title: filename, original: filename, type: type, size: size});
              })

            });
            break;
        }
      });
    } else {
      return res.json({state: sails.__('private.forbidden'), url: '', title: '', original: '', type: '', size: 0});
    }
  }
};
2015十月27

Node.js:微信消息回复、推送的实现

适应场景:Sails框架、微信公众号数据配置在数据库中,支持多个公众号。
1、Model定义(Waterline)
文件路径:/api/models/wx/Wx_config.js

/**
 * Created by root on 10/25/15.
 */
var moment = require('moment');
module.exports = {
  schema: true,
  autoCreatedAt: false,
  autoUpdatedAt: false,
  attributes: {
    id: {
      type: 'integer',
      autoIncrement: true,
      primaryKey: true
    },
    appname: {
      type: 'string',
      required: true
    },
    ghid: {//原始ID
      type: 'string',
      unique: true,
      required: true
    },
    appid: {
      type: 'string',
      unique: true,
      required: true
    },
    appsecret: {
      type: 'string',
      unique: true,
      required: true
    },
    encodingAESKey:{
      type: 'string',
      unique: true,
      required: true
    },
    token: {
      type: 'string',
      unique: true,
      required: true
    },
    access_token: {
      type: 'string'
    },
    expire_time: {
      type:'integer',
      defaultsTo:function(){
        return 0;
      }
    },
    createdBy:{
      type: 'integer'
    },
    createdAt:{
      type:'integer',
      defaultsTo:function(){
        return moment().format('X');
      }
    }
  }
};

2、Sails默认不支持微信xml post,改造之
文件路径:/config/http.js

rawParser: function(req, res, next) {
      switch(req.headers['content-type']) {
        case 'text/xml':
          require('body-parser').raw({ type: 'text/xml' })(req, res, next);
          break;
        default :
          next();
      }
    },
    order: [
      'startRequestTimer',
      'cookieParser',
      'session',
      'myRequestLogger',
      'rawParser',
      'bodyParser',
      'handleBodyParserError',
      'compress',
      'methodOverride',
      'poweredBy',
      '$custom',
      'router',
      'www',
      'favicon',
      '404',
      '500'
    ],

3、控制类
文件路径:/api/controllers/open/WeixinController.js
微信公众号后台配置路径:http://127.0.0.1/open/weixin/api?wxid=1

/**
 * Created by root on 10/25/15.
 */
module.exports = {
  api: function (req, res) {
    var id = req.query.wxid;
    Wx_config.findOne(id).exec(function (err, conf) {
      if (err)return res.send(200, 'fail');
      if (req.body) {
        WeixinService.loop(req, function (data) {
          if (data.type = 'text') {//用户发送纯文本
            var msg = {toUserName: data.openid, fromUserName: conf.ghid, content: 'Node.js Test...'};
            WeixinService.sendTextMsg(res, msg);//向用户回复消息
          }
        });
      }
      //签名
      if (WeixinService.checkSignature(req, conf.token)) {
        res.send(200, req.query.echostr);
      } else {
        res.send(200, 'fail');
      }
    });
  }
};

4、微信服务工具类
文件路径:/api/services/WeixinService.js

/**
 * Created by root on 10/26/15.
 */
var sha1 = require('sha1'), xml2js = require('xml2js');
module.exports = {
  /**
   * 签名验证
   * @param req
   * @param token
   * @returns {boolean}
   */
  checkSignature: function (req, token) {
    var signature = req.query.signature,
      timestamp = req.query.timestamp,
      nonce = req.query.nonce,
      echostr = req.query.echostr;
    // 按照字典排序
    var array = [token, timestamp, nonce];
    array.sort();
    // 连接
    var str = sha1(array.join(""));
    // 对比签名
    return str == signature;
  },
  /**
   * 监听用户消息
   * @param req
   * @param callback
   */
  loop: function (req, callback) {
    var body = req.body.toString('utf-8');
    var data = {};
    xml2js.parseString(body, function (err, json) {
      if (err) {
      } else {
        console.log('json::' + JSON.stringify(json));
        data.type = json.xml.MsgType;
        data.openid = json.xml.FromUserName;
        if (data.type == 'text') {
          data.txt = json.xml.Content;
        } else if (data.type == 'image') {
          data.pic = json.xml.PicUrl;
        }
        return callback(data);
      }
    });
  },
  /**
   * 发送文本消息
   * @param res
   * @param msg
   */
  sendTextMsg: function (res, msg) {
    var time = Math.round(new Date().getTime() / 1000);

    var funcFlag = msg.funcFlag ? msg.funcFlag : 0;

    var output = "" +
      "<xml>" +
      "<ToUserName><![CDATA[" + msg.toUserName + "]]></ToUserName>" +
      "<FromUserName><![CDATA[" + msg.fromUserName + "]]></FromUserName>" +
      "<CreateTime>" + time + "</CreateTime>" +
      "<MsgType><![CDATA[text]]></MsgType>" +
      "<Content><![CDATA[" + msg.content + "]]></Content>" +
      "<FuncFlag>" + funcFlag + "</FuncFlag>" +
      "</xml>";

    res.type('xml');
    res.send(output);
  },
  /**
   * 发送图文消息
   * @param res
   * @param msg
   */
  sendNewsMsg: function (res, msg) {
    var time = Math.round(new Date().getTime() / 1000);
    var articlesStr = "";
    for (var i = 0; i < msg.articles.length; i++) {
      articlesStr += "<item>" +
        "<Title><![CDATA[" + msg.articles[i].title + "]]></Title>" +
        "<Description><![CDATA[" + msg.articles[i].description + "]]></Description>" +
        "<PicUrl><![CDATA[" + msg.articles[i].picUrl + "]]></PicUrl>" +
        "<Url><![CDATA[" + msg.articles[i].url + "]]></Url>" +
        "</item>";
    }

    var funcFlag = msg.funcFlag ? msg.funcFlag : 0;
    var output = "" +
      "<xml>" +
      "<ToUserName><![CDATA[" + msg.toUserName + "]]></ToUserName>" +
      "<FromUserName><![CDATA[" + msg.fromUserName + "]]></FromUserName>" +
      "<CreateTime>" + time + "</CreateTime>" +
      "<MsgType><![CDATA[news]]></MsgType>" +
      "<ArticleCount>" + msg.articles.length + "</ArticleCount>" +
      "<Articles>" + articlesStr + "</Articles>" +
      "<FuncFlag>" + funcFlag + "</FuncFlag>" +
      "</xml>";

    res.type('xml');
    res.send(output);
  }
};

Nodejs版本:0.12.7
Sails版本:0.11.0
require相关组件,记得安装。
参考项目1:https://github.com/node-webot/wechat-api
参考项目2:https://github.com/JeremyWei/weixin_api

2015十月16

jquery.datatables 使用方法:Ajax分页、字段排序、传递参数等

1

Html:

姓名状态是否在线操作
用户名

datatables初始化:

var datatable;
  function initDatatable() {
    datatable = $('.datatable').DataTable({
      "processing": false,
      "serverSide": true,
      "select": true,
      "ordering": true,
      "language": {
        "url": "/plugins/datatables/cn.json"
      },
      "preDrawCallback": function () {
        sublime.closeLoadingbar($(".main-content"));
        sublime.showLoadingbar($(".main-content"));
      },
      "drawCallback": function () {
        sublime.closeLoadingbar($(".main-content"));
      },
      "ajax": {
        "url": "/private/sys/user/data",
        "type": "post",
        "data": function (d) {
          d.unitid = $('#unitid').val();
          d.loginname=$('#loginname').val();
          d.nickname=$('#nickname').val();
        }
      },
      "order":[[0,"desc"]],
      "columns": [
        {"data": "loginname", "bSortable": true},
        {"data": "nickname", "bSortable": true},
        {"data": "disabled", "bSortable": true},
        {"data": "online", "bSortable": true}
      ],
      "columnDefs": [
        {
          "render": function (data, type, row) {
            return 'html.....';
          },
          "targets": 4
        },
        {
          "render": function (data, type, row) {
            if (data) {
              return '';
            } else {
              return '';
            }
          },
          "targets": 3
        },
        {
          "render": function (data, type, row) {
            if (!data) {
              return '';
            } else {
              return '';
            }
          },
          "targets": 2
        }
      ]
    });
    datatable.on('click', 'tr', function () {
      $(this).toggleClass('selected');
    });
    $("#searchBtn").on('click',function(){
      datatable.ajax.reload(null, false);
    });
  }

重新加载数据:

datatable.ajax.reload(null, false);//当前页
datatable.ajax.reload();//回到第一页

排序:通过后台参数 order数组得到dir:asc/desc,通过order的column下标,获取columns数组中的data字段名,两者结合写排序条件
后台把所有参数打印出来,你就知道代码应该怎么写了……over

响应的数据格式:

{
              "draw": draw,
              "recordsTotal": pageSize,
              "recordsFiltered": count,
              "data": list
            }

Nodejs 后端完整代码:

data: function (req, res) {
    var pageSize = parseInt(req.body.length);
    var start = parseInt(req.body.start);
    var page = start / pageSize + 1;
    var draw = parseInt(req.body.draw);
    var unitid = req.body.unitid || 1;
    var loginname = req.body.loginname || '';
    var nickname = req.body.nickname || '';
    var order = req.body.order || [];
    var columns = req.body.columns || [];
    var sort = {};
    var where = {unitid: unitid};
    if (loginname) {
      where.loginname = {'like':'%'+loginname+'%'};
    }
    if (nickname) {
      where.nickname = {'like':'%'+nickname+'%'};
    }
    if (order.length > 0) {
      sort[columns[order[0].column].data] = order[0].dir;
    }
    Sys_user.count(where).exec(function (err, count) {
      if (!err && count > 0) {
        Sys_user.find(where)
          .sort(sort)
          .paginate({page: page, limit: pageSize})
          .exec(function (err, list) {
            return res.json({
              "draw": draw,
              "recordsTotal": pageSize,
              "recordsFiltered": count,
              "data": list
            });
          });
      } else {
        return res.json({
          "draw": draw,
          "recordsTotal": pageSize,
          "recordsFiltered": 0,
          "data": []
        });
      }
    });
  },
2015九月11

Sails:Waterline多对多查询

Sys_user.js

module.exports = {
  schema: true,
  autoCreatedAt: false,
  autoUpdatedAt: false,
  attributes: {
    id: {
      type: 'integer',
      autoIncrement: true,
      primaryKey: true
    },
    roles: {
      collection: 'Sys_role',
      via: 'users'
    }
  }
};

Sys_role.js

users: {
      collection: 'Sys_user',
      via: 'roles'
      //dominant: true 
    },

 

使用populate查询

    Sys_role.findOne(1).populate('users').populate('menus').exec(function(err, role) {
      console.log(':::'+JSON.stringify(role.users));
      console.log(':::'+JSON.stringify(role.menus));
    });

 

2015九月10

Node:加盐salt密码验证

npm install bcrypt –save

 

var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync(‘pass’, salt);
console.log(‘加密::’+hash);
console.log(‘解密::’+bcrypt.compareSync(‘pass’,hash));

2015九月9

Node:搭建sails.js开发环境

友情提示:do not 在windows下开发node.js应用,否则你会浪费很多时间并且会pia撞墙的……
1.CentOS6.5 安装 nodejs 并配置到环境
———————————–
>>gedit /etc/profile
export NODE_HOME=/soft/node-v0.12.7-linux-x64
export PATH=$NODE_HOME/bin:$PATH
>>source /etc/profile
>>node -v

 

2.npm 设置为国内源
———————————–
>>npm config set registry https://registry.npm.taobao.org

 

3.安装必备模块 node-gyp (CentOS6.5 自带Python2.6.6)
———————————–
>>npm install node-gyp -g

 

4.安装WebStorm 并安装JDK7
———————————–
>>rpm -ivh jdk-7u79-linux-x64.rpm
>>gedit /etc/profile
JAVA_HOME=/usr/java/jdk1.7.0_79
CLASSPATH=.:$JAVA_HOME/lib.tools.jar
PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME CLASSPATH PATH
>>source /etc/profile

 

5.安装Git 并生成ssh密钥,github帐号设置中添加密钥
———————————–
>>yum install git
>>cd ~/.ssh
>>ssh-keygen -t rsa -C “wizzer@qq.com”
>>cat ~/.ssh/id_rsa.pub
>>git clone git@github.com:Wizzercn/nodeshop.git

>>git pull
>>git commit -am ‘note’
>>git push

 

6.验证码组件ccap,需安装python2.7
————————————
http://blog.csdn.net/tiantiandjava/article/details/17242345
>>npm install ccap –save