首页 » Web前端 » php割舍法技巧_国产后端框架 HttpEasy 横空出世基于cbrother别用php了

php割舍法技巧_国产后端框架 HttpEasy 横空出世基于cbrother别用php了

访客 2024-12-07 0

扫一扫用手机浏览

文章目录 [+]

故,HttpEasy直接割舍了视图V的存在,简化框架层次,只实现了用户掌握和数据模型,并在数据模型上支持海量数据多线程分割管理以及跨线程调用,使开拓者并不须要很深厚的编程功底,就可以开拓出支持高并发的后台接口。

HttpEasy术语

HttpEasy中用户掌握层,即吸收前端要求的处理,称为:Action。
如http://x.x.x.x/login对应一个Action,http://x.x.x.x/logout对应另一个Action

php割舍法技巧_国产后端框架 HttpEasy 横空出世基于cbrother别用php了

HttpEasy中数据模型层,即处理数据逻辑并读写数据库,称为:Data。
Data可以有多个,每个Data运行在自己的线程内,要根据自己业务需求来设计须要几个Data

php割舍法技巧_国产后端框架 HttpEasy 横空出世基于cbrother别用php了
(图片来自网络侵删)
HttpEasy分层

前端层

网页,手机,小程序等等须要调用做事器接口的终端

Action层

做事器对外供应各个接口,要卖力检测cookie的合法性后分发到各个Data去处理。
Action层运行在HttpServer线程池内,会同时有很多并发。

Data层

卖力数据逻辑的处理,在启动的时候将卖力的数据缓存进内存,定时回写改变过的数据到数据库。
每一个Data自己有自己的一个专属线程。

DB层

数据库

HttpEasy接口

HttpEasy是对HttpServer的一层封装,源码在CBrother目录下lib/httpeasy.cb。
成员变量_httpServer为HttpServer工具,可以利用该变量修正http做事的参数

函数

描述

用法

listenPort(port,CRT_PATH,KEY_PATH)

添加监听端口.port:端口,CRT_PATH,KEY_PATH:证书路径,不写证书路径启动http做事,写了证书路径启动https做事.可以调用多次同时监听多个端口

httpEasy.listenPort(80)httpEasy.listenPort(443,"xx.crt","xx.key")

addAction(actobj,name)

给已经添加的端口绑定http相应接口,actobj:相应工具,name:接口名字,可省略,省略后默认为actobj类名

httpEasy.addAction(actobj)httpEasy.addAction(actobj,name)

addData(dataobj,name)

添加Data,dataobj:Data工具,name:data名字,可省略,省略后默认为dataobj类名

httpEasy.addData(dataobj)httpEasy.addData(dataobj,name)

setRoot(path)

设置做事器跟目录,path: 根目录绝对路径

httpEasy.setRoot(path)

setThreadCount(cnt)

设置相应线程数量,默认10个设置大了并发量大但是花费资源多

httpEasy.setThreadCount(50)

setNormalAction(actName)

设置默认相应接口访问http://x.x.x.x/后面不带路径时默认实行到的界面

httpEasy.setNormalAction("hello.cb")httpServer.setNormalAction("hello.html")

set404Action(actName)

设置缺点相应接口,不设置CBrother有默认页面

httpEasy.set404Action("404.cb")

setOutTime(t)

设置要求超时时间,默认10秒,单位为秒

httpEasy.setOutTime(3)

setMaxReqDataLen(len)

设置客户机发送的最大要求数据长度,默认500K,参数单位为(字节)

httpEasy.setMaxReqDataLen(1024 1024)

openLog()

打开日志,在webroot平级建立log目录默认关闭,建议打开

httpEasy.openLog()

closeFileService()

关闭文件下载做事录

httpEasy.closeFileService()

getHttpServer(port)

获取端口对应的HttpServer工具

var httpServer = httpEasy.getHttpServer(80)

run()

启动做事

httpEasy.run()

syncCallData(dataName,dataFunc,parmArray)

同步调用Data接口,会壅塞等待函数返回值,超过3秒则超时返回nulldataName:要调用Data的名字,与addData时候名字相同dataFunc:要调用的方法名parmArray:函数参数,为一个Array工具

httpEasy.syncCallData("testData","testFunc",[1,2])

asyncCallData(dataName,dataFunc,parmArray)

异步调用Data接口,不壅塞直接返回,用于不须要吸收函数返回值时dataName:要调用Data的名字,与addData时候名字相同dataFunc:要调用的方法名parmArray:函数参数,为一个Array工具

httpEasy.asyncCallData("testData","testFunc",[1,2])

import lib/httpeasyvar HTTPEasy = new HttpEasy();function main(parm){ HTTPEasy.listenPort(8000);//http server port 8000 HTTPEasy.addAction(new HelloAction());//访问http://x.x.x.x/HelloAction会触发HelloAction的DoAction方法 HTTPEasy.addData(new HelloData());//注册HelloData HTTPEasy.run();}class HelloAction{ function DoAction(request,respon) { var res = HTTPEasy.syncCallData("HelloData","hello");//同步调用HelloData的hello方法,吸收返回值 respon.write("now time is:" + res); respon.flush(); }}class HelloData{ function onInit(t) { print "HelloData init"; } function onEnd() { print "HelloData end"; } function hello() { var myTime = new Time(); return myTime.strftime("%Y/%m/%d %H:%M:%S"); }}

调用/HelloAction时,会返回当前韶光

个中Action的用法与HttpServer须要注册的Action类完备相同,可以直接去Http模块查看详细用法,这里只讲解一下HttpEasy新增的Data类

数据相应类DataData相应类可以有如下接口

function onInit(t),Data线程初始化,入参是一个Thread工具,为自己所运行的线程。
一样平常建议在此时加载数据库数据到内存里。

function onEnd(),Data线程结束,一样平常建议此时要回写变革过的数据。

定时器的添加

在onInit方法中框架会通报所在线程工具,可以利用该工具来添加定时器。
详细可以查看多线程中Thread类的用法。
一些特殊主要的数据可以在修正后立即回写数据库,其他的一些数据可以直接在内存中修正后返回前端,然后在定时器中回写这些数据,降落数据库压力,并且提高相应效率。

class HelloData{ function onInit(t) { print "HelloData init"; t.addTimer(1000,testHeart);//添加一个定时器,每1秒实行一次testHeart方法 t.addTimer(5000,testHeart1,2);//添加一个定时器,每5秒实行一次testHeart1方法,实行两次后自动删除改定时器 } function onEnd() { print "HelloData end"; } function hello() { var myTime = new Time(); return myTime.strftime("%Y/%m/%d %H:%M:%S"); } function testHeart() { print "testHeart"; } function testHeart1() { print "testHeart111"; }}Data的利用原则

1. 每一个Data都运行在自己的线程内,以是每个Data只能操作自己的成员变量。

2. 把相互干联强的数据放到同一个Data内处理,化并行为串行,简化逻辑。

3. 一个Data也可以同步跨线程调用另一个Data的方法,但是如果调用频率过高,就把两个Data数据合并在一个Data里,会提高实行效率。

4. 只管即便避免在一个Data里同步调用另一个Data的方法,但是可以异步调用

5. Data的划分可以是数据关系纵向划分,比如用户数据一个Data,商品数据一个Data等。

6. 当数据海量往后还可以按照ID横向划分,比如500万以内的用户一个Data,500万以上的另一个Data。

7. 当你对付多个Data的线程关系一贯无法理解的时候,你所碰着的需求用一个Data绝对可以搞定,不要担心压力问题。

用HttpEasy来实现一个大略的商城后台需求

为了能深入理解HttpEasy的用法,我们来实现一个大略的商城后台,须要预留如下接口。

接口

描述

参数和返回

/RechargeAction

充值接口,留给第三方后台调用,比如用户用支付宝或者微信充值到我们平台,对方后台会调用这个接口。
(我们这个接口只是虚拟的,详细要接第三方支付的话还须要去研究第三方文档)

post数据 : {"channel":"wechat","money":1000,"userid":"11111"}成功返回 : "ok" 失落败返回 : "err"

/LoginAction

上岸接口,前端调用。

post数据 : {"account":"aaa","pwd":"bbb"}成功返回 : {userid:"111",username:"小红",sex:"女"}并添加cookie失落败返回 : "err"

/ItemListAction

查看商品列表,前端调用。

post数据 : {"type":1} //type为0表示全部类型商品成功返回 : [{"itemid":1,"itemname":"苹果","type":1,"price":10000,"count":100},......]失落败返回 : "err"

/OrderListAction

查看订单列表,前端调用。

不提交数据成功返回 : [{"orderid":"202012162020201","price":10000,"itemid":1,"count":1,"state":1},......]失落败返回 : "err"

/BuyOrderAction

下订单,前端调用。

post数据 : {"itemid":1,"count":1}成功返回 : {"orderid":"202012162020201","price":10000,"itemid":1,"count":1,"state":0}失落败返回 : "err"

/PayAction

支付订单,前端调用。

post数据 : {"orderid":"202012162020201"}成功返回 : {"orderid":"202012162020201"}失落败返回 : "err"

下面我们来设计一下数据库表构造,数据库我们利用mysql

用户表如下,ID做主键,账号加索引

并手动初始化两条用户数据

商品表如下,ID做主键

并手动初始化三条商品数据

订单表如下,ID做主键,没有人下订单,以是开始是空的

用单个Data来实现这个需求

我们先不去考虑数据的划分,直接放到同一个Data里去实现这个功能,这样比较大略,程序入口如下,注册6个Action和1个Data

import lib/httpeasyimport lib/logvar HTTPEasy = new HttpEasy();function main(parm){InitLog(GetRoot(),"httpeasy");//初始化日志路径到事情根目录HTTPEasy.listenPort(8000);//http server port 8000HTTPEasy.addAction(new RechargeAction());HTTPEasy.addAction(new LoginAction());HTTPEasy.addAction(new ItemListAction());HTTPEasy.addAction(new OrderListAction());HTTPEasy.addAction(new BuyOrderAction());HTTPEasy.addAction(new PayAction());HTTPEasy.addData(new ServerData());WriteLog("server start! port:" + 8000);HTTPEasy.run();}

Data在启动的时候把用户和商品信息加载进内存,内存中的数据紧张用Map容器来管理

class User//定义描述用户数据在内存中的类型{ var id; var account; var pwd; var userName; var sex; var money;}class Item//定义描述商品数据在内存中类型{ var itemID; var itemName; var price; var count; var type;}class ServerData{ var _mysql = new MySQL("127.0.0.1",3306,"root","123456","httpeasy"); var _userAccountMap = new Map(); //通过用户名查找到用户信息 var _userIDMap = new Map(); //通过用户ID查找到用户信息 var _itemMap = new Map(); //通过商品ID查找到商品信息 function onInit(t) { if(!_mysql.connect()) { WriteLog("mysql connect err!"); return; } initUserTable();//加载用户数据 initItemTable();//加载商品数据 } function onEnd() { } function initUserTable() { var sql = "select from usertable"; if (_mysql.query(sql)) { while (_mysql.next()) { var user = new User(); user.id = _mysql.getInt("id"); user.account = _mysql.getString("account"); user.pwd = _mysql.getString("pwd"); user.userName = _mysql.getString("userName"); user.sex = _mysql.getString("sex"); user.money = _mysql.getInt("money"); _userAccountMap.add(user.account,user); _userIDMap.add(user.id,user); } } } function initItemTable() { var sql = "select from itemtable"; if (_mysql.query(sql)) { while (_mysql.next()) { var item = new Item(); item.itemID = _mysql.getInt("itemID"); item.itemName = _mysql.getString("itemName"); item.price = _mysql.getInt("price"); item.count = _mysql.getInt("count"); item.type = _mysql.getInt("type"); _itemMap.add(item.itemID,item); } } }}

第一个接口编写上岸调用的LoginAction,最紧张一句代码是通过HTTPEasy.syncCallData方法调用ServerData的login方法

const COOKIE_PWD = "TEST_HTTPEASY"; //cookie的密码class LoginAction{ function DoAction(request,respon) { var postData = request.getData(); //获取post数据 if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var account = json.getString("account"); var pwd = json.getString("pwd"); if (account == null || pwd == null) { respon.write("err"); respon.flush(); return; } //同步调用ServerData的login方法 var resJson = HTTPEasy.syncCallData("ServerData","login",[account,pwd]); if (resJson != null) { var uid = resJson.get("userid"); var cookie = new Cookie(); cookie.setName("userid"); cookie.setValue(uid,COOKIE_PWD); respon.addCookie(cookie); //添加userid密文到cookie,不设置韶光的话关闭浏览器自动失落效 respon.write(resJson.toJsonString()); } else { respon.write("err"); } respon.flush(); } }

ServerData增加login方法

class ServerData{ ...... function login(account,pwd) { var user = _userAccountMap.get(account); if (user == null) { return null; } var tempPwd = openssl_sha1(pwd + "test"); //密码为 sha1(密码明文 + 字符串test) if (tempPwd != user.pwd) { return null; //密码缺点 } var resJson = new Json(); resJson.add("userid",user.id); resJson.add("username",user.userName); resJson.add("sex",user.sex); resJson.add("money",user.money); return resJson; } ......}

用户登录成功后,会先要求一遍商品列表,下面再实现一下ItemListAction

class ItemListAction{ function DoAction(request,respon) { //这个接口没有验证用户登录状态,由于即便用户不登录也该当有权限看到我们的商品列表 var postData = request.getData(); if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var type = json.get("type"); if (type == null) { respon.write("err"); respon.flush(); return; } //同步调用ServerData的itemList方法 var resJson = HTTPEasy.syncCallData("ServerData","itemList",[type]); if (resJson != null) { respon.write(resJson.toJsonString()); } else { respon.write("err"); } respon.flush(); } }

ServerData增加itemList方法

class ServerData{ ...... function itemList(type) { var resJson = new Json(); foreach (k,v : _itemMap) { if (type == 0 || v.type == type) { var itemObj = resJson.pushObject(); itemObj.add("itemid",v.itemID); itemObj.add("itemname",v.itemName); itemObj.add("type",v.type); itemObj.add("price",v.price); itemObj.add("count",v.count); } } return resJson; } ......}

如果用户看上了某件商品,会来下单购买这件商品,我们来实现下单接口BuyOrderAction,用户只有上岸了才可以购买物品,以是这个接口要检测上岸状态

function GetCookieUserID(request) //这个方法来查找客户机的登录cookie信息{ var cookCnt = request.getCookieCount(); for (var i = 0; i < cookCnt ; i++) { var cookie = request.getCookie(i); if (cookie.getName() == "userid") { return cookie.getValue(COOKIE_PWD); } } return null;}class BuyOrderAction{ function DoAction(request,respon) { var userid = GetCookieUserID(request); //检测上岸状态 if(userid == null) { respon.write("err"); //没有登录 respon.flush(); return; } var postData = request.getData(); if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var itemid = json.get("itemid"); var count = json.get("count"); if (itemid == null || count == null || count < 1) { respon.write("err"); respon.flush(); return; } //同步调用ServerData的buyOrder方法 var resJson = HTTPEasy.syncCallData("ServerData","buyOrder",[userid,itemid,count]); if (resJson != null) { respon.write(resJson.toJsonString()); } else { respon.write("err"); } respon.flush(); } }

ServerData增加对订单的支持

class Order //定义描述订单数据在内存中类型{ var orderID; var userID; var itemID; var count; var state; var price; var lasttime;}class UserOrder //定义描述用户自己订单列表在内存中类型{ var oldOrderList = new Array(); //已支付或关闭 var newOrderList = new Array(); //下单未支付}class ServerData{ ...... var _orderMap = new Map(); //通过用户ID查找到订单列表 var _orderIndex = 1; function buyOrder(userid,itemid,count) { var user = _userIDMap.get(userid); if (user == null) { return null; //用户不存在 } var item = _itemMap.get(itemid); if (item == null) { return null; //商品不存在 } if (item.count < count) { return null; //库存不敷 } var price = item.price count; var userOrder = _orderMap.get(userid); if (userOrder == null) { userOrder = new UserOrder(); _orderMap.add(userid,userOrder); } item.count -= count; //占用库存 var time = new Time(); var timestr = time.strftime("%Y%m%d%H%M%S"); var orderidx = _orderIndex++; var order = new Order(); order.orderID = timestr + orderidx; order.userID = userid; order.state = 0; order.itemID = itemid; order.count = count; order.price = price; order.lasttime = time(); userOrder.newOrderList.add(order); WriteLog(user.userName + " buy " + item.itemName + "X" + count + " price:" + price + " orderid:" + order.orderID); var json = new Json(); json.add("orderid",order.orderID); json.add("price",price); json.add("itemid",itemid); json.add("count",count); json.add("state",0); return json; } ......}

用户下了订单之后确认无误就要真正的支付了,下面实现一下支付的PayAction

class PayAction{ function DoAction(request,respon) { var userid = GetCookieUserID(request); if(userid == null) { respon.write("err"); //没有登录 respon.flush(); return; } var postData = request.getData(); if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var orderid = json.get("orderid"); if (orderid == null) { respon.write("err"); respon.flush(); return; } //同步调用ServerData的pay方法 var resJson = HTTPEasy.syncCallData("ServerData","pay",[userid,orderid]); if (resJson != null) { respon.write(resJson.toJsonString()); } else { respon.write("err"); } respon.flush(); } }

ServerData增加pay方法

class ServerData{ ...... function pay(userid,orderid) { var user = _userIDMap.get(userid); if (user == null) { return null; //用户不存在 } var userOrder = _orderMap.get(userid); if(userOrder == null) { return null; //用户没有任何订单信息 } var order = null; var orderIdx = -1; for (var i = 0; i < userOrder.newOrderList.size() ; i++) { if (userOrder.newOrderList[i].orderID == orderid) { order = userOrder.newOrderList[i]; orderIdx = i; break; } } if(order == null) { return null; //没有找到订单 } if(user.money < order.price) { return null; //用户钱不足 } order.state = 1; order.lasttime = time(); userOrder.newOrderList.remove(orderIdx); //从未付费列表删除 userOrder.oldOrderList.add(order); //加入支付列表 //钱很主要先扣钱 user.money -= order.price; var sql = "update usertable set money=" + user.money + " where id='" + userid + "'"; _mysql.upDate(sql); //插入数据库 var sql = "insert into orderTable (orderID,userID,itemID,count,state,price,lasttime) values('" + orderid + "'," + userid + "," + order.itemID + "," + order.count + "," + order.state + "," + order.price + "," + order.lasttime + ")"; _mysql.upDate(sql); WriteLog(user.userName + " pay price:" + order.price + " orderid:" + order.orderID + " userMoney:" + user.money); var resJson = new Json(); resJson.add("orderid",orderid); return resJson; } ......}

从下单到支付的流程就通了,但是用户数据初始化时候money字段都是0,当前端调用支付接口的时候总是由于钱不足支付失落败,先来实现一下充值接口RechargeAction

class RechargeAction{ function DoAction(request,respon) { //第三方平台调用接口,该当要有对方IP的白名单,这里是测试只许可本机调用 var targetip = request.getRemoteIP(); if(targetip != "127.0.0.1") { respon.write("err"); respon.flush(); return; } var postData = request.getData(); if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var channel = json.get("channel"); var money = json.get("money"); var userid = json.get("userid"); if (channel == null || money == null || userid == null) { respon.write("err"); respon.flush(); return; } //同步调用ServerData的recharge方法 var res = HTTPEasy.syncCallData("ServerData","recharge",[userid,money,channel]); if (res != null) { respon.write(res); } else { respon.write("err"); } respon.flush(); }}

ServerData增加recharge方法

class ServerData{ ...... function recharge(userid,money,channel) { var user = _userIDMap.get(userid); if (user == null) { return null; //用户不存在 } if(money < 0) { return null; } user.money += money; //钱实时写入 var sql = "update usertable set money=" + user.money + " where id='" + userid + "'"; _mysql.upDate(sql); WriteLog(user.userName + " recharge money:" + money + " channel:" + channel + " userMoney:" + user.money); return "ok"; } ......}

用户充值后就可以正常支付了,支付成功后用户就有了历史订单,前端要展示这些历史订单,我们再来实现返回订单列表的接口OrderListAction

class OrderListAction{ function DoAction(request,respon) { var userid = GetCookieUserID(request); if(userid == null) { respon.write("err"); //没有登录 respon.flush(); return; } //同步调用ServerData的buyOrder方法 var resJson = HTTPEasy.syncCallData("ServerData","orderList",[userid]); if (resJson != null) { respon.write(resJson.toJsonString()); } else { respon.write("err"); } respon.flush(); } }

ServerData增加orderList方法

class ServerData{ ...... function orderList(userid) { var userOrder = _orderMap.get(userid); if (userOrder == null) { return null; //用户订单不存在 } var resJson = new Json(); for (var i = userOrder.newOrderList.size() - 1 ; i >= 0 ; i--) { var order = userOrder.newOrderList[i]; var json = new Json(); json.add("orderid",order.orderID); json.add("price",order.price); json.add("itemid",order.itemID); json.add("count",order.count); json.add("state",order.state); resJson.push(json); } for (var i = userOrder.oldOrderList.size() - 1 ; i >= 0 ; i--) { var order = userOrder.oldOrderList[i]; var json = new Json(); json.add("orderid",order.orderID); json.add("price",order.price); json.add("itemid",order.itemID); json.add("count",order.count); json.add("state",order.state); resJson.push(json); } return resJson; } ......}

接口实现完了,但我们创造当做事看重启后订单数据没有加载进内存,以是要在ServerData启动时候加载订单数据,在ServerData退出时候把未完成的订单关闭并回写数据库

class ServerData{ ...... function onInit(t) { ...... initOrderTable(); //加载订单数据 } function onEnd() { foreach (k,v : _orderMap) { if(v.newOrderList.size() <= 0) { continue; } for (var i = 0; i < v.newOrderList.size(); i++) { //关闭未完成的订单 var order = v.newOrderList[i]; closeOrder(order); } } } function initOrderTable() { var sql = "select from ordertable ORDER BY lasttime"; if (_mysql.query(sql)) { while (_mysql.next()) { var order = new Order(); order.orderID = _mysql.getString("orderID"); order.userID = _mysql.getString("userID"); order.itemID = _mysql.getInt("itemID"); order.count = _mysql.getInt("count"); order.state = _mysql.getInt("state"); order.price = _mysql.getInt("price"); order.lasttime = _mysql.getLong("lasttime"); var userOrder = _orderMap.get(order.userID); if (userOrder == null) { userOrder = new UserOrder(); _orderMap.add(order.userID,userOrder); } userOrder.oldOrderList.add(order); } } } function closeOrder(order) { //关闭未完成的订单 order.state = 2; order.lasttime = time(); //插入数据库 var sql = "insert into orderTable (orderID,userID,itemID,count,state,price,lasttime) values('" + order.orderID + "'," + order.userID + "," + order.itemID + "," + order.count + "," + order.state + "," + order.price + "," + order.lasttime + ")"; _mysql.upDate(sql); var item = _itemMap.get(itemid); if (item != null) { //把商品的库存数量还回去 item.count += count; } WriteLog("close order price:" + order.price + " orderid:" + order.orderID); } ......}

为了防止用户下订单占用商品库存后一贯不支付,我们须要定时监测未支付订单,超过10分钟仍未支付的就要自动关闭,将物品库存数量还原回去让其他用户正常购买。
这里我们须要给ServerData增加定时器

class ServerData{ ...... function onInit(t) { ...... t.addTimer(1000 60,orderTimer); //增加检测订单的定时器,每60秒实行一次orderTimer方法 } function orderTimer() { foreach (k,v : _orderMap) { if(v.newOrderList.size() <= 0) { continue; } var nowTime = time(); for (var i = 0; i < v.newOrderList.size(); i++) { var order = v.newOrderList[i]; if(nowTime - order.lasttime > 60 10) { closeOrder(order); //用户未支付订单大于10分钟,删除 v.newOrderList.remove(i); i--; } } } } ......}

末了创造还忽略了一点,商品库存变革后没有回写数据库,这个数据没有必要实时回写,我们用定时器来做,首先要给Item类添加一个是否变革的属性isChange,在用户下单占用库存时和关闭订单还原库存时将这个变量赋值isChange=true

class Item{ ...... var isChange = false; //商品信息是否发生了变革}class ServerData{ ...... function onInit(t) { ...... t.addTimer(1000 60 5,itemTimer); //检测商品库存的定时器,每5分钟实行一次itemTimer方法 } function itemTimer() { foreach (k,v : _itemMap) { if(!v.isChange) { continue; } var sql = "update itemtable set count=" + v.count + " where itemID=" + v.itemID; _mysql.upDate(sql); v.isChange = false; } } ......}

这个大略的系统基本上就完成了,看完后可以创造,Action是直接和前端通讯的,卖力收发客户机提交的数据,只做一些大略的状态判断和参数判断,真正的逻辑是在Data中进行的。
理解了这一点,你就基本节制了HttpEasy的用法

上面的代码在CBrother路径下的sample/httpeasy/SingleData/SingleDataHttpEasy.cb,还有一个大略的web前端测试例子在sample/httpeasy/SingleData/webroot/testhttpeasy.html,启动SingleDataHttpEasy.cb后浏览器访问http://127.0.0.1:8000/testhttpeasy.html 可以调试一下代码

如果你没有海量数据的需求暂时可以先不用看后面的多Data实现,就按照一个Data的办法来做,这样既大略又不随意马虎出错

用多个Data来实现这个需求

当数据量在几十万条以内,一样平常来说一个Data完备可以搪塞,可是当达到了上百万数据,一个Data就可能会有性能瓶颈问题,以是我们要拆分数据,用多个Data来负载均衡。

这个例子里我们把Data划分成三个。
UserData管理用户信息,ItemData管理商品信息,OrderData管理订单信息。
程序入口如下

import lib/httpeasyimport lib/logvar HTTPEasy = new HttpEasy();function main(parm){InitLog(GetRoot(),"httpeasy");//初始化日志路径到事情根目录HTTPEasy.listenPort(8000);//http server port 8000HTTPEasy.addAction(new RechargeAction());HTTPEasy.addAction(new LoginAction());HTTPEasy.addAction(new ItemListAction());HTTPEasy.addAction(new OrderListAction());HTTPEasy.addAction(new BuyOrderAction());HTTPEasy.addAction(new PayAction());HTTPEasy.addData(new UserData());HTTPEasy.addData(new ItemData());HTTPEasy.addData(new OrderData());WriteLog("server start! port:" + 8000);HTTPEasy.run();}

UserData启动时候加载用户数据

class User{ var id; var account; var pwd; var money;}class UserData{ var _mysql = new MySQL("127.0.0.1",3306,"root","123456","test"); var _userAccountMap = new Map(); //通过用户名查找到用户信息 var _userIDMap = new Map(); //通过用户ID查找到用户信息 function onInit(t) { if(!_mysql.connect()) { WriteLog("mysql connect err!"); return; } initUserTable();//加载用户数据 } function onEnd() { } function initUserTable() { var sql = "select from usertable"; if (_mysql.query(sql)) { while (_mysql.next()) { var user = new User(); user.id = _mysql.getInt("id"); user.account = _mysql.getString("account"); user.pwd = _mysql.getString("pwd"); user.money = _mysql.getInt("money"); _userAccountMap.add(user.account,user); _userIDMap.add(user.id,user); } } }}

ItemData启动时候加载商品数据

class Item{ var itemID; var itemName; var price; var count; var type; var isChange = false;}class ItemData{ var _mysql = new MySQL("127.0.0.1",3306,"root","123456","test"); var _itemMap = new Map(); //通过商品ID查找到商品信息 function onInit(t) { if(!_mysql.connect()) { WriteLog("mysql connect err!"); return; } initItemTable();//加载商品数据 } function onEnd() { } function initItemTable() { var sql = "select from itemtable"; if (_mysql.query(sql)) { while (_mysql.next()) { var item = new Item(); item.itemID = _mysql.getInt("itemID"); item.itemName = _mysql.getString("itemName"); item.price = _mysql.getInt("price"); item.count = _mysql.getInt("count"); item.type = _mysql.getInt("type"); _itemMap.add(item.itemID,item); } } }}

OrderData启动时候加载订单数据

class Order{ var orderID; var userID; var itemID; var count; var state; var price; var lasttime;}class UserOrder{ var oldOrderList = new Array(); //已支付或关闭 var newOrderList = new Array(); //下单未支付}class OrderData{ var _mysql = new MySQL("192.168.1.25",3306,"root","root","test"); var _orderMap = new Map(); var _orderIndex = 1; function onInit(t) { WriteLog("OrderData onInit"); if(!_mysql.connect()) { WriteLog("mysql connect err!"); return; } initOrderTable(); //加载订单数据 } function onEnd() { WriteLog("OrderData onEnd"); } function initOrderTable() { var sql = "select from ordertable ORDER BY lasttime"; if (_mysql.query(sql)) { while (_mysql.next()) { var order = new Order(); order.orderID = _mysql.getString("orderID"); order.userID = _mysql.getString("userID"); order.itemID = _mysql.getInt("itemID"); order.count = _mysql.getInt("count"); order.state = _mysql.getInt("state"); order.price = _mysql.getInt("price"); order.lasttime = _mysql.getLong("lasttime"); var userOrder = _orderMap.get(order.userID); if (userOrder == null) { userOrder = new UserOrder(); _orderMap.add(order.userID,userOrder); } userOrder.oldOrderList.add(order); } } } }

我们还是先来写上岸接口,基本上和单个Data代码一样,只是调用的Data名从ServerData换成了UserData

class LoginAction{ function DoAction(request,respon) { var postData = request.getData(); if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var account = json.getString("account"); var pwd = json.getString("pwd"); if (account == null || pwd == null) { respon.write("err"); respon.flush(); return; } //同步调用UserData的login方法 var resJson = HTTPEasy.syncCallData("UserData","login",[account,pwd]); if (resJson != null) { var uid = resJson.get("userid"); var cookie = new Cookie(); cookie.setName("userid"); cookie.setValue(uid,COOKIE_PWD); respon.addCookie(cookie); //添加userid密文到cookie,不设置韶光的话关闭浏览器自动失落效 respon.write(resJson.toJsonString()); } else { respon.write("err"); } respon.flush(); } }

给UserData添加login方法

class UserData{ ...... function login(account,pwd) { var user = _userAccountMap.get(account); if (user == null) { return null; } var tempPwd = openssl_sha1(pwd + "test",); //密码为 sha1(密码明文 + 字符串test) if (tempPwd != user.pwd) { return null; //密码缺点 } var resJson = new Json(); resJson.add("userid",user.id); resJson.add("username",user.userName); resJson.add("sex",user.sex); resJson.add("money",user.money); return resJson; } ......}

商品列表接口ItemListAction也没有太大变革,只是换了调用的Data名

class ItemListAction{ function DoAction(request,respon) { //这个接口没有验证用户登录状态,由于即便用户不登录也该当有权限看到我们的商品列表 var postData = request.getData(); if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var type = json.get("type"); if (type == null) { respon.write("err"); respon.flush(); return; } //同步调用ServerData的login方法 var resJson = HTTPEasy.syncCallData("ItemData","itemList",[type]); if (resJson != null) { respon.write(resJson.toJsonString()); } else { respon.write("err"); } respon.flush(); } }

ItemData中增加itemList接口

class ItemData{...... function itemList(type) { var resJson = new Json(); foreach (k,v : _itemMap) { if (type == 0 || v.type == type) { var itemObj = resJson.pushObject(); itemObj.add("itemid",v.itemID); itemObj.add("itemname",v.itemName); itemObj.add("type",v.type); itemObj.add("price",v.price); itemObj.add("count",v.count); } } return resJson; }......}

下订单的BuyOrderAction接口跟单个Data不同了,由于要同时访问ItemData扣除商品库存并获取价格,再到OrderData里面保存订单,以是要顺序调用这两个Data的buyOrder方法

class BuyOrderAction{ function DoAction(request,respon) { var userid = GetCookieUserID(request); if(userid == null) { respon.write("err"); //没有登录 respon.flush(); return; } var postData = request.getData(); if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var itemid = json.get("itemid"); var count = json.get("count"); if (itemid == null || count == null || count < 1) { respon.write("err"); respon.flush(); return; } //先调用ItemData的buyOrder方法占用库存并获取商品信息 var itemInfo = HTTPEasy.syncCallData("ItemData","buyOrder",[itemid,count]); if (itemInfo == null) { respon.write("err"); respon.flush(); return; } //同步调用OrderData的buyOrder方法 var resJson = HTTPEasy.syncCallData("OrderData","buyOrder",[userid,itemid,count,itemInfo]); if (resJson != null) { respon.write(resJson.toJsonString()); } else { respon.write("err"); } respon.flush(); } }

ItemData中增加buyOrder方法

class ItemData{ ...... function buyOrder(itemid,count) { var item = _itemMap.get(itemid); if (item == null) { return null; //商品不存在 } if (item.count < count) { return null; //库存不敷 } item.count -= count; //占用库存 item.isChange = true; return {"price":item.price,"itemName":item.itemName}; } ......}

OrderData中增加buyOrder方法

class OrderData{ ...... function buyOrder(userid,itemid,count,itemInfo) { var price = itemInfo["price"] count; var userOrder = _orderMap.get(userid); if (userOrder == null) { userOrder = new UserOrder(); _orderMap.add(userid,userOrder); } var time = new Time(); var timestr = time.strftime("%Y%m%d%H%M%S"); var orderidx = _orderIndex++; var order = new Order(); order.orderID = timestr + orderidx; order.userID = userid; order.state = 0; order.itemID = itemid; order.count = count; order.price = price; order.lasttime = time(); userOrder.newOrderList.add(order); WriteLog(userid + " buy " + itemInfo["itemName"] + "X" + count + " price:" + price + " orderid:" + order.orderID); var json = new Json(); json.add("orderid",order.orderID); json.add("price",price); json.add("itemid",itemid); json.add("count",count); json.add("state",0); return json; } ......}

下面实现支付的PayAction,这个Action实行的任务顺序为:

1.去OrderData获取订单价格2.去UserData扣除金额3.去OrderData修正订单状态4.如果末了一步缺点了还要把扣除的钱还给UserData,从这一步可以看出异步操作虽然提升了性能,但也使代码的繁芜度增高

class PayAction{ function DoAction(request,respon) { var userid = GetCookieUserID(request); if(userid == null) { respon.write("err"); //没有登录 respon.flush(); return; } var postData = request.getData(); if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var orderid = json.get("orderid"); if (orderid == null) { respon.write("err"); respon.flush(); return; } //1.去OrderData获取订单价格 var orderPrice = HTTPEasy.syncCallData("OrderData","getOrderPrice",[userid,orderid]); if (orderPrice == null) { respon.write("err"); respon.flush(); return; } //2.去UserData扣钱 var orderPrice = HTTPEasy.syncCallData("UserData","pay",[userid,orderPrice,orderid]); if (orderPrice == null) { respon.write("err"); respon.flush(); return; } //3.到OrderData里修正订单状态 var res = HTTPEasy.syncCallData("OrderData","pay",[userid,orderid]); if (res != null) { if (res) { var resJson = new Json(); resJson.add("orderid",orderid); respon.write(resJson.toJsonString()); } else { //4.出错了,把钱还回去,异步调用就好了,不须要等结果 HTTPEasy.asyncCallData("UserData","payErr",[userid,orderPrice,orderid]); respon.write("err"); } } else { respon.write("err"); } respon.flush(); } }

UserData增加pay方法和payErr方法

class UserData{ ...... function pay(userid,price,orderid) { var user = _userIDMap.get(userid); if (user == null) { return null; //用户不存在 } if(user.money < price) { return null; //用户钱不足 } //钱很主要先扣钱 user.money -= price; var sql = "update usertable set money=" + user.money +" where id='" + userid + "'"; _mysql.upDate(sql); WriteLog(user.userName + " pay price:" + price + " orderid:" + orderid + " userMoney:" + user.money); return true; } function payErr(userid,price,orderid) { var user = _userIDMap.get(userid); if (user == null) { return null; //用户不存在 } //钱很加回来 user.money += price; var sql = "update usertable set money=" + user.money + " where id='" + userid + "'"; _mysql.upDate(sql); WriteLog(user.userName + " payErr price:" + price + " orderid:" + orderid + " userMoney:" + user.money); return true; } ......}

OrderData增加getOrderPrice方法和pay方法

class OrderData{ ...... function getOrderPrice(userid,orderid) { var userOrder = _orderMap.get(userid); if(userOrder == null) { return null; //用户没有任何订单信息 } var order = null; var orderIdx = -1; for (var i = 0; i < userOrder.newOrderList.size() ; i++) { if (userOrder.newOrderList[i].orderID == orderid) { order = userOrder.newOrderList[i]; orderIdx = i; break; } } if(order == null) { return null; //没有找到订单 } return order.price; } function pay(userid,orderid) { var userOrder = _orderMap.get(userid); if(userOrder == null) { return null; //用户没有任何订单信息 } var order = null; var orderIdx = -1; for (var i = 0; i < userOrder.newOrderList.size() ; i++) { if (userOrder.newOrderList[i].orderID == orderid) { order = userOrder.newOrderList[i]; orderIdx = i; break; } } if(order == null) { //到这一步没有订单,该当是订单被定时器关闭了,须要把钱还回去,这里概率非常低但是也要处理 return false; } order.state = 1;; order.lasttime = time(); userOrder.newOrderList.remove(orderIdx); //从未付费列表删除 userOrder.oldOrderList.add(order); //加入支付列表 //插入数据库 var sql = "insert into orderTable (orderID,userID,itemID,count,state,price,lasttime) values('" + orderid + "'," + userid + "," + order.itemID + "," + order.count + "," + order.state + "," + order.price + "," + order.lasttime + ")"; _mysql.upDate(sql); return true; } ......}

再实现一下充值接口RechargeAction,这个接口只访问UserData,没有大的变革

class RechargeAction{ function DoAction(request,respon) { //第三方平台调用接口,该当要有对方IP的白名单,这里是测试只许可本机调用 var targetip = request.getRemoteIP(); if(targetip != "127.0.0.1") { respon.write("err"); respon.flush(); return; } var postData = request.getData(); if (postData == null) { respon.write("err"); respon.flush(); return; } var json = new Json(postData); var channel = json.get("channel"); var money = json.get("money"); var userid = json.get("userid"); if (channel == null || money == null || userid == null) { respon.write("err"); respon.flush(); return; } //同步调用ServerData的pay方法 var res = HTTPEasy.syncCallData("UserData","recharge",[userid,money,channel]); if (res != null) { respon.write(res); } else { respon.write("err"); } respon.flush(); } }

UserData增加recharge方法

class UserData{ ...... function recharge(userid,money,channel) { var user = _userIDMap.get(userid); if (user == null) { return null; //用户不存在 } if(money < 0) { return null; } user.money += money; //钱实时写入 var sql = "update usertable set money=" + user.money + " where id='" + userid + "'"; _mysql.upDate(sql); WriteLog(user.userName + " recharge money:" + money + " channel:" + channel + " userMoney:" + user.money); return "ok"; } ......}

末了剩下订单列表接口OrderListAction也没有太大变革

class OrderListAction{ function DoAction(request,respon) { var userid = GetCookieUserID(request); if(userid == null) { respon.write("err"); //没有登录 respon.flush(); return; } //同步调用ServerData的buyOrder方法 var resJson = HTTPEasy.syncCallData("OrderData","orderList",[userid]); if (resJson != null) { respon.write(resJson.toJsonString()); } else { respon.write("err"); } respon.flush(); } }

OrderData增加orderList方法

class OrderData{ ...... function orderList(userid) { var userOrder = _orderMap.get(userid); if (userOrder == null) { return null; //用户订单不存在 } var resJson = new Json(); for (var i = userOrder.newOrderList.size() - 1 ; i >= 0 ; i--) { var order = userOrder.newOrderList[i]; var json = new Json(); json.add("orderid",order.orderID); json.add("price",order.price); json.add("itemid",order.itemID); json.add("count",order.count); json.add("state",order.state); resJson.push(json); } for (var i = userOrder.oldOrderList.size() - 1 ; i >= 0 ; i--) { var order = userOrder.oldOrderList[i]; var json = new Json(); json.add("orderid",order.orderID); json.add("price",order.price); json.add("itemid",order.itemID); json.add("count",order.count); json.add("state",order.state); resJson.push(json); } return resJson; } ......}

我们还须要在OrderData退出时候将未完成的订单关闭,不才订单后超过10分钟未支付也将订单关闭,关闭订单时要把商品库存还回去。
这里要把稳OrderData中调用ItemData的方法,用的是异步,而不是同步。

class OrderData{ ...... function onInit(t) { ...... t.addTimer(1000 60,orderTimer); //检测订单的定时器,每60秒实行一次 } function onEnd() { WriteLog("OrderData onEnd"); foreach (k,v : _orderMap) { if(v.newOrderList.size() <= 0) { continue; } for (var i = 0 ; i < v.newOrderList.size(); i++) { var order = v.newOrderList[i]; closeOrder(order); } } } function closeOrder(order) { //关闭未完成的订单 order.state = 2; order.lasttime = time(); //插入数据库 var sql = "insert into orderTable (orderID,userID,itemID,count,state,price,lasttime) values('" + order.orderID + "'," + order.userID + "," + order.itemID + "," + order.count + "," + order.state + "," + order.price + "," + order.lasttime + ")"; _mysql.upDate(sql); //异步关照ItemData,不壅塞,不吸收返回值,在Data内部要只管即便避免同步调用 HTTPEasy.asyncCallData("ItemData","closeOrder",[order.itemID,order.count]); WriteLog("close order price:" + order.price + " orderid:" + order.orderID); } function orderTimer() { foreach (k,v : _orderMap) { if(v.newOrderList.size() <= 0) { continue; } var nowTime = time(); for (var i = 0 ; i < v.newOrderList.size(); i++) { var order = v.newOrderList[i]; if(nowTime - order.lasttime > 60 10) { closeOrder(order); v.newOrderList.remove(i); i--; } } } } ......}

ItemData增加closeOrder方法,并增加定时回写库存的定时器

class ItemData{ ...... function onInit(t) { ...... t.addTimer(1000 60 5,itemTimer); //检测商品库存的定时器,每5分钟实行一次 } function onEnd() { WriteLog("ItemData onEnd"); itemTimer(); //退出的时候检测一边是否要回写 } function closeOrder(itemid,count) { var item = _itemMap.get(itemid); if (item != null) { //把商品的数量还回去 item.count += count; item.isChange = true; } } function itemTimer() { foreach (k,v : _itemMap) { if(!v.isChange) { continue; } var sql = "update itemtable set count=" + v.count + " where itemID=" + v.itemID; _mysql.upDate(sql); v.isChange = false; } } ......}

到这里这个别系用多个Data的办法也实现完了,理解后你会创造,Data还可以划分的更细,比如ID小于500万用UserData1大于则用UserData2,或者男性用UserData1女性用UserData2,水果用ItemData1零食用ItemData2, 其事理便是将数据的最小单元按照某一个标准再次拆分,详细如何拆分还要根据自己的业务需求来定,实在这样的拆分思想和数据库分表以及做事器分布式拆分思想是相同的。

代码在CBrother目录下sample/httpeasy/MulData目录下,这个工程我把文件拆成了多个,这是为了做一个示范,当代码量大的时候可以按照这样的目录来拆分代码。

标签:

相关文章

介绍百度码,技术革新背后的智慧之光

随着科技的飞速发展,互联网技术已经成为我们生活中不可或缺的一部分。而在这个信息爆炸的时代,如何快速、准确地获取信息,成为了人们关注...

Web前端 2025-01-03 阅读4 评论0

介绍皮箱密码,开启神秘之门的钥匙

皮箱,作为日常生活中常见的收纳工具,承载着我们的珍贵物品。面对紧闭的皮箱,许多人却束手无策。如何才能轻松打开皮箱呢?本文将为您揭秘...

Web前端 2025-01-03 阅读4 评论0

介绍盗号器,网络安全的隐忧与应对步骤

随着互联网的快速发展,网络安全问题日益突出。盗号器作为一种非法工具,对网民的个人信息安全构成了严重威胁。本文将深入剖析盗号器的原理...

Web前端 2025-01-03 阅读2 评论0