尝试一下写些技术的东西, 之前在公司一直是用C#写的WCF后台. 对于跨域调用API虽然也支持, 但是需要做的配置也挺繁琐, 所以试下不一样的后台技术, NodeJs .
由于只做API调用, 所以用了Restify 插件而不是Express .
所以先全局安装下 restify : npm install restify -g (-g就是全局,不然就是当前路径下安装).
装好nodejs后第一步是创建项目环境:
在电脑新建文件夹(F:\NodeTest) 在NodeJs的Console端CD进去,
输入npm init ,配置下项目信息
npm init
然后在项目里安装下数据库连接件 node-mysql (当然全局安装也没问题)
npm install node-mysql -save (写到刚刚配置的package.json里)
然后是刚刚在init上的Entry Point 写的入口点(首先启动的js)
用文本编辑器新建app.js 内容:
var server=require("./JsModule/RestServer.js");
server.start();
看到require 的路径了吧,对了,接下来就是再创建它. sever是什么, start是什么方法, 都在这个js文件里, 至于require, 则是Nodejs自带, 可以理解为JAVA的import包.
新建文件夹JsModule, 新建js RestServer.js
现在的项目结构
var restify = require('restify');
var Guest=require("./Controll/GuestControll.js");
var MySql=require("./MysqlHelper.js");
function start(){
var server = restify.createServer();
server.use(restify.bodyParser());
server.use(restify.queryParser());
server.use(restify.CORS());
var SqlPool=Object.create(MySql.MysqlHelper);
SqlPool.InitConnect();
Guest.GuestControll(server,SqlPool);
server.get("/getservertime",function(req,res,next)
{
var timestamp = date.parse(new date());
res.send(timestamp);
})
server.listen(5203, function() { console.log('%s listening at %s', server.name, server.url); });}
exports.start=start;
第一句require没有路径, 因为是全局安装的module,可以用名称直接搜索到restify,
第二句Guest是一个MVC式的controller. 负责将发过来的请求转发处理, 我们这次的例子是查询一个住客信息.
第三句Mysql则是调用一个数据连接类.
主程序就是 start 这个函数, 在最尾一句export出来. 呼应App.js 的新建的 var obj=XXX; obj.start();
那start函数在做的是 ,前四句使用restify提供的server, 并添加参数处理,跨域支持.
第五,六句则是初始化一个数据连接池
第七句 是将服务和连接池传到Controller去加工.
第八句 server.get() 就是注册一个新的请求监听, 其实在Controller里我们也是做这种请求的注册, 只是为了封装起来,分散开方便阅读和处理. 这里是请求服务器时间, 直接回传一个UTC值.
然后最后 调用restify包装过的 server.listen() Web服务就会启动, 5203 就是部署的端口号, 后面的function是成功后的回调方法
接下来我们先看看数据连接的类, 这是我试错后自己写的哦. 由require路径看出他和RestSever.js是同个文件夹的.
var mysql=require('mysql');
var fs = require('fs');
var MysqlHelper=
{
SqlPool:false,
InitConnect:function()
{
fs.readFile('./Mysql.json','utf8',function(err,data){
if(err) throw err;
var jsonObj = JSON.parse(data);
SqlPool= mysql.createPool({
connectionLimit : 3,
host : jsonObj.host,
port : jsonObj.port,
user : jsonObj.user,
password :jsonObj.password,
database:jsonObj.database,
supportBigNumbers:jsonObj.supportBigNumbers,
multipleStatements:jsonObj.multipleStatements
});
});
},
SelectTable:function(aTableName,aSelCols,aWhere,aParam,aDataCallback)
{
if(SqlPool===false)return false;
if(aSelCols==="")aSelCols="*";
if(aWhere==="")aWhere="1=1";
var selectSQL= ["SELECT "];
selectSQL.push(aSelCols);
selectSQL.push(" FROM ");
selectSQL.push(aTableName);
selectSQL.push(" WHERE ");
selectSQL.push(aWhere);
var runSql=selectSQL.join("");
console.log(runSql);
SqlPool.getConnection(function (err, conn) {
if(aParam){
conn.query(runSql,aParam,function(err,rows){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(false):0;
}
var Datas=rows;
conn.release();
aDataCallback?aDataCallback(Datas):0;
});
}else{
conn.query(runSql,function(err,rows){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(false):0;
}
var Datas=rows;
conn.release();
aDataCallback?aDataCallback(Datas):0;
});
}
});
},
InsertTableRow:function(aTableName,aInsertCols,aInsertValueArray,aParam,aDataCallback)
{
if(SqlPool===false)return false;
var InsertSQL= ["INSERT INTO "];
InsertSQL.push(aTableName);
InsertSQL.push("(");
for(var i=0; i
{
if(i>0){InsertSQL.push(',');}
InsertSQL.push(aInsertCols[i]);
}
InsertSQL.push(")");
InsertSQL.push(" VALUES(");
for(var i=0; i
{
if(i>0){InsertSQL.push(',');}
InsertSQL.push(aInsertValueArray[i]);
}
InsertSQL.push(");");
var runSql=InsertSQL.join('');
console.log(runSql);
SqlPool.getConnection(function (err, conn) {
if(aParam){
conn.query(runSql,aParam,function(err,result){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(-1):0;
}
var NewId=result.insertId;
conn.release();
aDataCallback?aDataCallback(NewId):0;
});
}else{
conn.query(runSql,function(err,result){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(-1):0;
}
var NewId=result.insertId;
conn.release();
aDataCallback?aDataCallback(NewId):0;
});
}
});
},
DeleteTable:function(aTableName,aWhere,aParam,aDataCallback)
{
if(SqlPool===false)return false;
var runSQL= "DELETE FROM ?? WHERE ";
runSQL = mysql.format(runSQL, [aTableName]);
runSQL+=aWhere;
console.log(runSQL);
SqlPool.getConnection(function (err, conn) {
if(aParam){
conn.query(runSQL,aParam,function(err,result){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(-1):0;
}
var ARows=result.affectedRows;
conn.release();
aDataCallback?aDataCallback(ARows):0;
});
}else{
conn.query(runSQL,function(err,result){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(-1):0;
}
var ARows=result.affectedRows;
conn.release();
aDataCallback?aDataCallback(ARows):0;
});
}
});
},
UpdateTable:function(aTableName,aUpdateColArray,aUpdateValueArray,aWhere,aParam,aDataCallback)
{
if(SqlPool===false)return false;
var UpdateSQL= [" UPDATE "];
UpdateSQL.push(aTableName);
UpdateSQL.push(" SET ");
for(var i=0; i
{
if(i>0){UpdateSQL.push(',');}
UpdateSQL.push(aUpdateColArray[i]);
UpdateSQL.push('=');
UpdateSQL.push(aUpdateValueArray[i]);
}
UpdateSQL.push(" WHERE ");
UpdateSQL.push(aWhere);
var runSql=UpdateSQL.join('');
console.log(runSql);
SqlPool.getConnection(function (err, conn) {
if(aParam){
conn.query(runSql,aParam,function(err,result){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(-1):0;
}
var ARows=result.affectedRows;
conn.release();
aDataCallback?aDataCallback(ARows):0;
});
}else{
conn.query(runSql,function(err,result){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(-1):0;
}
var ARows=result.affectedRows;
conn.release();
aDataCallback?aDataCallback(ARows):0;
});
}
});
},
ExecuteNoqueryCmd:function(aSql,aParam,aDataCallback)
{
if(SqlPool===false)return false;
SqlPool.getConnection(function (err, conn) {
if(aParam){
conn.query(aSql,aParam,function(err,result){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(false):0;
}
var ARows=result.affectedRows;
conn.release();
aDataCallback?aDataCallback(ARows):0;
});
}else{
conn.query(aSql,function(err,result){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(false):0;
}
var ARows=result.affectedRows;
conn.release();
aDataCallback?aDataCallback(ARows):0;
});
}
});
},
ExecuteQueryCmd:function(aSql,aParam,aDataCallback)
{
if(SqlPool===false)return false;
SqlPool.getConnection(function (err, conn) {
if(aParam){
conn.query(aSql,aParam,function(err,rows){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(false):0;
}
var Datas=rows;
conn.release();
aDataCallback?aDataCallback(Datas):0;
});
}else{
conn.query(aSql,function(err,rows){
if (err)
{
console.log("Con ==> "+err);
aDataCallback?aDataCallback(false):0;
}
var Datas=rows;
conn.release();
aDataCallback?aDataCallback(Datas):0;
});
}
});
}
}
exports.MysqlHelper=MysqlHelper;
第二句require('fs')是nodejs提供的内部读写文件的包, 我们需要有一个数据的配置文件去存放连接和路径,在InitConnect 里读取 ./Mysql.json 初始了一个连接池 然后这个Pool就在RestServer里初始并一直存在了. 至于怎么关闭池 我还没写. 应该就是 RestSever再响应个Server.close 然后调用这个类的ClosePool 这样吧.
Mysql.json 内容:
{
"host":"127.0.0.1",
"port":"3306",
"user":"root",
"password":"test",
"database":"basicdb",
"supportBigNumbers":"true",
"multipleStatements":"true"
}
连接类里面的增删查改方法都要用回调函数才能获取的执行结果,然后这个回调在后面的代码还会多加一个回调aDataCallback 才能对执行结果进行精加工, 这就是NodeJs的事件触发机制, 回调吧少年.
因为是API后台, 没有V只剩MC, 我们先看看Controller吧.也是简单的处理代码.
由require看出,我们需要在JsModule下再建一个文件夹Controll,再在里面新建GuestControll.js
var Guest=require("../Model/Guest.js");
function GuestControll(app,Mysql)
{
var GuestModel=Object.create(Guest.GuestModel);
app.get('/guest/gettodayguest', GetTodayGuests); //查询今天客人
app.get('/guest/getroombooking', GetRoomBooking); //查询房间预订信息
app.get('/guest/getguestinfobyid', GetGuestInfoById); //根据客人id获取信息
app.post('/guest/bookroom', NewGuest); //预订客房
app.post('/guest/checkin', Checkin); //入住客房
app.post('/guest/checkout', Checkout); //退房
app.post('/guest/cancelbooking', CancelBooking); //取消预订
app.post('/guest/overstay', OverStay); //续住
function GetTodayGuests(req, res, next) {
console.log("GetTodayGuests query:"+JSON.stringify(req.query));
var Today=req.params.today;
GuestModel.GetTodayGuests(Mysql,Today,function(Data){
res.charSet('utf-8');
res.send(Data);
});
}
function NewGuest(req, res, next){
res.send('0');
}
function Checkin(req, res, next) {
console.log("Checkin param:"+JSON.stringify(req.params));
var RoomId=req.params.roomid;
GuestModel.Checkin(Mysql,RoomId,function(Rst){
res.charSet('utf-8');
var SR={iRst:Rst};
res.send(SR);
});
}
function Checkout(req, res, next) {
console.log("Checkout param:"+JSON.stringify(req.params));
var RoomId=req.params.roomid;
GuestModel.Checkout(Mysql,RoomId,function(Rst){
res.charSet('utf-8');
var SR={iRst:Rst};
res.send(SR);
});
}
function CancelBooking(req, res, next) {
console.log("CancelBooking param:"+JSON.stringify(req.params));
var RoomId=req.params.roomid;
var GuestStr=req.params.guestStr;
var GuestArr=eval_r(GuestStr);
console.log(GuestArr);
GuestModel.CancelBooking(Mysql,RoomId,GuestArr,function(Rst){
res.charSet('utf-8');
var SR={iRst:Rst};
res.send(SR);
});
}
function OverStay(req, res, next) {
console.log("OverStay param:"+JSON.stringify(req.params));
var RoomId=req.params.roomid;
var StayTime=req.params.staytime;
GuestModel.OverStay(Mysql,RoomId,StayTime,function(Rst){
res.charSet('utf-8');
var SR={iRst:Rst};
res.send(SR);
});
}
function GetRoomBooking(req, res, next)
{
console.log("GetRoomBooking query:"+JSON.stringify(req.query));
var RoomId=req.params.roomid;
var NowTime=req.params.thistime;
GuestModel.GetRoomBooking(Mysql,RoomId,NowTime,function(Data){
res.charSet('utf-8');
res.send(Data);
});
}
function GetGuestInfoById(req, res, next)
{
console.log("GetGuestInfoById query:"+JSON.stringify(req.query));
var UserId=req.params.uid;
GuestModel.GetGuestInfoById(Mysql,UserId,function(Data){
res.charSet('utf-8');
res.send(Data);
});
}
}
exports.GuestControll=GuestControll;
Controller都是中转控制啦, 业务逻辑都在Module层里, 第一句就告诉我们又要再建一个Module文件夹和Guest.js 的Module. 唯一让我觉得不够爽的地方是注册各个请求的接收参数名, 这个必须由后台私下和前端事先约定, 在nodejs上无法自描述...
var JsOper=require("../Tool/JsOper.js");
var DataTable=require("../Tool/CDataTableName.js");
var GuestModel={
GetTodayGuests:function(MySql,aUTCDate,aResponse) {
var QueryCmd=['SELECT t1.ROOM_ID,t1.GASTNR,t1.GASTNAME,t2.SEX,t2.VIP,t1.ANREISE,t1.ABREISE,t1.CHECK_FLAG '];
QueryCmd.push(' FROM ',DataTable.TableGuestCheckIn,' t1, ',DataTable.TableGuestInfo,' t2 ' );
QueryCmd.push(' WHERE t1.GASTNR=t2.NUMMER AND ((t1.ANREISE<? AND t1.ABREISE>=?) OR CHECK_FLAG=?) AND CHECK_FLAG<>? ' );
QueryCmd.push(' ORDER BY t1.ROOM_ID ' );
var NowDate=JsOper.GetDateStr(aUTCDate,0);
var NextDate=JsOper.GetDateStr(aUTCDate,1);
var QueryPara=[NextDate,NowDate,DataTable.FlagCheckIn,DataTable.FlagCheckOut];
var runCmd=QueryCmd.join("");
MySql.ExecuteQueryCmd(runCmd,QueryPara
,function(data){
if(data===false){
var DataRst={iRst:-1,iGuestList:[]};
aResponse?aResponse(DataRst):0;
}
var LastRid=-1,ThisRid=0,GuestArray=[],RoomGuest=[];
for(var i in data)
{
ThisRid = data[i].ROOM_ID;
if (LastRid == -1) LastRid = ThisRid;
if (LastRid != ThisRid)
{
GuestArray.push({iRoomId:LastRid,iGuest:RoomGuest});
RoomGuest = [];
LastRid = ThisRid;
}
RoomGuest.push(
{
iGuestNo :data[i].GASTNR,
iGuestName :data[i].GASTNAME,
iSex : GetSexName(data[i].SEX),
iVip : data[i].VIP,
iCheckFlag :data[i].CHECK_FLAG,
iCheckOutTime :Date.parse(data[i].ABREISE)/1000,
iCheckInTime :Date.parse(data[i].ANREISE)/1000
});
}
if (LastRid > 0) GuestArray.push({iRoomId:LastRid,iGuest:RoomGuest});
var DataRst={iRst:0,iGuestList:GuestArray};
aResponse?aResponse(DataRst):0;
});
},
GetRoomBooking:function(MySql,aRoomId,aNowTime,aResponse) {
var QueryCmd=['SELECT t1.GASTNR,t1.GASTNAME,t2.SEX,t2.VIP,t1.ANREISE,t1.ABREISE,t1.CHECK_FLAG '];
QueryCmd.push(' FROM ',DataTable.TableGuestCheckIn,' t1, ',DataTable.TableGuestInfo,' t2 ' );
QueryCmd.push(' WHERE t1.GASTNR=t2.NUMMER AND ROOM_ID=? AND t1.ABREISE>=? AND CHECK_FLAG=? ' );
var ThisDate=new Date(aNowTime*1000);
var NowDate=JsOper.ToLongDateStr(ThisDate);
var QueryPara=[aRoomId,NowDate,DataTable.FlagEmpty];
var runCmd=QueryCmd.join("");
MySql.ExecuteQueryCmd(runCmd,QueryPara
,function(data){
if(data===false){
var DataRst={iRst:-1,iGuest:[]};
aResponse?aResponse(DataRst):0;
}
var RoomGuest=[];
for(var i in data)
{
RoomGuest.push(
{
iGuestNo :data[i].GASTNR,
iGuestName :data[i].GASTNAME,
iSex : GetSexName(data[i].SEX),
iVip : data[i].VIP,
iCheckFlag :data[i].CHECK_FLAG,
iCheckOutTime :Date.parse(data[i].ABREISE)/1000,
iCheckInTime :Date.parse(data[i].ANREISE)/1000
});
}
var DataRst={iRst:0,iGuest:RoomGuest};
aResponse?aResponse(DataRst):0;
});
},
GetGuestInfoById:function(MySql,aUserId,aResponse)
{
MySql.SelectTable(DataTable.TableGuestInfo,"","NUMMER="+aUserId,false,
,function(data){
if(data===false){
var DataRst={iRst:-1,iGuest:[]};
aResponse?aResponse(DataRst):0;
}
var Guest=[];
for(var i in data)
{
Guest.push(
{
iGuestNo :data[i].GASTNR,
iGuestName :data[i].GASTNAME,
iCity:data[i].ORT,
iEmail:data[i].EMAIL,
iLang:data[i].SPRACHE,
iMobile:data[i].TELEX,
iNation:data[i].NATION,
iSex : GetSexName(data[i].SEX),
iVip : data[i].VIP,
iCardNo:data[i].PASSPORT
});
}
var DataRst={iRst:0,iGuest:Guest};
aResponse?aResponse(DataRst):0;
});
},
NewGuest:function(req, res, next){
res.send('0');
},
Checkin:function(MySql,aRoomId) {
var Today = JsOper.GetDateStr(0,0); //今天
var Tomorrow=JsOper.GetDateStr(0,1); //明天
var QPara=[aRoomId,Today,Tomorrow,,DataTable.FlagEmpty];
MySql.SelectTable(DataTable.TableGuestCheckIn,"1"," ROOM_ID=? AND ANREISE <? AND ABREISE>? AND CHECK_FLAG=? ",QPara
,function(data){
if(data===false){aResponse?aResponse(-1):0;return;}
if(data.length===0){aResponse?aResponse(-4):0;return;}
var updateCol=['CHECK_FLAG','ANREISE'];
var updateVal=['?','NOW()'];
var UPara=[DataTable.FlagCheckIn,aRoomId,Today,Tomorrow,DataTable.FlagEmpty];
MySql.UpdateTable(DataTable.TableGuestCheckIn,updateCol,updateVal," ROOM_ID=? AND ANREISE <? AND ABREISE>? AND CHECK_FLAG=? ",UPara,
function(Rst)
{
if(Rst===false)Rst=-1;
aResponse?aResponse(Rst):0;
});
});
},
Checkout:function(MySql,aRoomId) {
var updateCol=['CHECK_FLAG','ABREISE'];
var updateVal=['?','NOW()'];
var UPara=[DataTable.FlagCheckOut];
MySql.UpdateTable(DataTable.TableGuestCheckIn,updateCol,updateVal,"ROOM_ID="+aRoomId,UPara,
function(Rst)
{
if(Rst===false)Rst=-1;
aResponse?aResponse(Rst):0;
});
},
OverStay:function(MySql,aRoomId,aStayTime,aResponse){
var StayDate=new Date(aStayTime*1000);
var updateCol=['ABREISE'];
var updateVal=['?'];
var UPara=[JsOper.ToLongDateStr(StayDate),DataTable.FlagCheckIn];
MySql.UpdateTable(DataTable.TableGuestCheckIn,updateCol,updateVal," CHECK_FLAG=? AND ROOM_ID="+aRoomId,UPara,
function(Rst)
{
if(Rst===false)Rst=-1;
aResponse?aResponse(Rst):0;
});
},
CancelBooking:function(MySql,aRoomId,aGuestArr,aResponse) {
if(aGuestArr.length===0)aResponse?aResponse(-4):0;
var WhereSql=[' ROOM_ID='+aRoomId];
WhereSql.push(" AND CHECK_FLAG='' AND ( ")
for(var i in aGuestArr)
{
if(i>0)WhereSql(' OR ')
WhereSql.push(' ( GASTNR='+aGuestArr[i].id+' AND UNIX_TIMESTAMP(ANREISE)='+aGuestArr[i].in_time+' )' );
}
WhereSql.push(" ) ")
console.log(WhereSql.join(''));
MySql.DeleteTable(DataTable.TableGuestCheckIn,WhereSql.join(''),false,
function(Rst){
if(Rst===false)Rst=-1;
aResponse?aResponse(Rst):0;
});
},
}
function GetSexName(sex)
{
if (sex||sex==="")
{
return "";
}
else if (sex.toUpperCase()==="M")
{
return "(男)";
}
else
{
return "(女)";
}
}
exports.GuestModel=GuestModel;
Module层require了日期处理的公共函数和数据库表名的字符串.这就不多说了, 反而是这里响应了SQL查询的callback后, 还要继续触发回Controller传入的Response,两重callback, 不知道是不是有更优雅的实现方法呢....
现在的项目已经能run起来了
再附上runbat.bat 双击则可启动后台了.(F:\nodejs 是我安装nodejs的目录,因为没有设环境变量, 这里就直接写了.)
@echo off
cd /d %~dp0
set fpath=%~dp0
set npath=F:\nodejs
echo on
%npath%\node.exe %fpath%app.js
pause
接口的测试就不赋调用代码了, Get和Post请求都是用一般的Httpget和HttpPost格式, 即使是跨域也不须用jsonp什么的了.