zdb平台与第三方平台对接的一个范例。
基于sealink wms定义的接口实现。
接口技术要求简述如下:
l http协议
l 消息body采用json格式
l 通过url传递系统参数
zdb端实现上行部分的接口(WTE),2个平台之间彼此调用,实现业务流程的衔接。
本文用采购进货和验收确认为例描述具体的代码实现。
作为开发示例的2个接口定义如下。
zdb在采购订单审核后调用。
请求参数
名称 |
类型 |
是否必须 |
示例值 |
更多限制 |
描述 |
sheetid |
String |
是 |
|
|
|
warehouse_no |
String |
是 |
|
|
仓库编码 |
owner_no |
String |
是 |
123 |
|
货主编码 |
org_no |
String |
否 |
|
|
机构代码 |
venderid |
String |
否 |
|
|
进货供货商编码 |
purchase_no |
String |
否 |
|
|
采购单号 |
sdate |
Date |
否 |
|
|
采购单审核日期 |
checker |
String |
是 |
|
|
制单员姓名 |
class_type |
String |
否 |
|
|
类型 |
purdate |
Date |
否 |
|
|
送货日期 |
validdate |
Date |
是 |
|
|
有效日期 |
notes |
String |
是 |
|
|
订单说明 |
orderlist |
Purchase[] |
否 |
|
|
|
serialid |
Number |
否 |
|
|
单内序号 |
owner_article_no |
String |
否 |
|
|
商品编码 |
pkcount |
Number |
是 |
|
|
订货规格 |
qty |
Number |
否 |
|
|
进货总量 |
owner_cust_no |
String |
是 |
|
|
客户代码 |
cust_qty |
Number |
是 |
|
|
客户要货量 |
cust_po_no |
String |
是 |
|
|
客户订单号 |
cust_note |
String |
是 |
|
|
客户备注 |
响应参数
名称 |
类型 |
示例值 |
描述 |
flag |
String |
success |
|
code |
String |
000 |
返回状态码 |
message |
String |
|
返回状态描述 |
wms调用。
请求参数
名称 |
类型 |
是否必须 |
示例值 |
更多限制 |
描述 |
sheetid |
String |
否 |
|
|
传单单据编号 |
warehouse_no |
String |
否 |
|
|
仓库编码 |
owner_no |
String |
否 |
123 |
|
货主编码 |
po_no |
String |
否 |
|
|
进货通知单号 |
checkno |
String |
否 |
|
|
验收单号 |
supplier_no |
String |
否 |
|
|
供货商编码 |
sdate |
Date |
否 |
|
|
验收时间 |
orderlist |
Purchase[] |
否 |
|
|
|
serialid |
Number |
否 |
|
|
单内序号 |
owner_article_no |
String |
否 |
|
|
商品编码 |
prodate |
Date |
是 |
|
|
生产日期 |
qty |
Number |
否 |
|
|
验收总量 |
响应参数
名称 |
类型 |
示例值 |
描述 |
flag |
String |
success |
|
code |
String |
000 |
返回状态码 |
message |
String |
|
返回状态描述 |
ETW接口实现在client,WTE接口实现在server模块中。
根据接口定义文档定义请求业务对象和请求传输请求体对象。
请求业务对象类型:
//////////////////////////////////////////////////////////////////////////////// ///< 进货通知单 struct CPurchase { string sheetid_;/// 传单单据编号 string warehouse_no_;/// 仓库编码 string owner_no_;/// 货主编码 string org_no_; /// 机构代码 string venderid_; /// 进货供货商编码 string purchase_no_;/// 采购单号 string sdate_; /// 采购单审核日期 string checker_;/// 制单员姓名 string class_type_;/// 类型 string purdate_; /// 送货日期 string validdate_;///有效日期 string notes_;/// 订单说明 struct CItem { ///< 商品明细 int serialid_;///单内序号 string owner_article_no_; int pkcount_; ///< 订货规格 double qty_; /// 进货总量 string owner_cust_no_;/// 客户代码 double cust_qty_;///客户要货量 string cust_po_no_;/// 客户订单号 string cust_note_; /// 客户备注 CItem():serialid_(0),qty_(0),cust_qty_(0),pkcount_(1) { } MEMBER_DEFINE(CItem); int Load(mpm_ns::CPurchase::CPurchaseDetail &item); }; CAutoVector
CPurchase() { org_no_ = ORG_NO; class_type_ = "0"; }
MEMBER_DEFINE(CPurchase); int Load(mpm_ns::CPurchase &purchase); }; |
请求体类型:
///< 进货通知单请求体 struct CPurchaseRequestBody : public CMasterSlaveRequestBody MEMBER_DEFINE(CPurchaseRequestBody);
CPurchaseRequestBody():CMasterSlaveRequestBody("orderlist") { method_ = "ETW_PURCHASE"; } }; |
CMasterSlaveRequestBody为主从结构类型的请求体,模板参数CPurchase为请求业务对象类型,CPurchase::CItem为明细类型。
orderlist为明细数组名称。
ETW_PURCHASE为方法名称。
绑定建立json消息与对象成员之间的映射。
SET_MEMBER_BEGIN(CPurchase) SET_MEMBER2(CPurchase,sheetid_,"sheetid",false), SET_MEMBER2(CPurchase,warehouse_no_,"warehouse_no",false), SET_MEMBER2(CPurchase,owner_no_,"owner_no",false), SET_MEMBER2(CPurchase,org_no_,"org_no",false), SET_MEMBER2(CPurchase,venderid_,"venderid",false), SET_MEMBER2(CPurchase,purchase_no_,"purchase_no",false), SET_MEMBER2(CPurchase,sdate_,"sdate",false), SET_MEMBER2(CPurchase,checker_,"checker",true), SET_MEMBER2(CPurchase,class_type_,"class_type",false), SET_MEMBER2(CPurchase,purdate_,"purdate",false), SET_MEMBER2(CPurchase,validdate_,"validdate",true), SET_MEMBER2(CPurchase,notes_,"notes",true), SET_MEMBER2(CPurchase,purdate_,"purdate",false), SET_MEMBER2(CPurchase,validdate_,"validdate",true), SET_MEMBER_END(CPurchase)
SET_MEMBER_BEGIN(CPurchase::CItem) SET_MEMBER2(CPurchase::CItem,serialid_,"serialid",false), SET_MEMBER2(CPurchase::CItem,owner_article_no_,"owner_article_no",false), SET_MEMBER2(CPurchase::CItem,pkcount_,"pkcount",true), SET_MEMBER2(CPurchase::CItem,qty_,"qty",false), SET_MEMBER2(CPurchase::CItem,owner_cust_no_,"owner_cust_no",true), SET_MEMBER2(CPurchase::CItem,cust_qty_,"cust_qty",true), SET_MEMBER2(CPurchase::CItem,cust_po_no_,"cust_po_no",true), SET_MEMBER2(CPurchase::CItem,cust_note_,"cust_note",true), SET_MEMBER_END(CPurchase::CItem) |
SET_MEMBER2的3个参数含义分别如下:成员变量名称,接口参数名称,是否允许空。
接口转换把业务对象转换为请求对象。
本步骤基于业务相关设计人员对接口转换规则明确描述后进行。
业务对象是zdb系统内部描述业务的对象,请求对象为接口定义的用于接口之间的调用的对象。
本接口的业务对象类型为mpm_ns::CPurchase,转换为上面定义的CPurchase类型,包括明细类型转换。
下行转换方法为Load,上行转换方法为Output。
业务对象转换为请求对象
int CPurchase::Load(mpm_ns::CPurchase &purchase) { GET_SHARDINGID(purchase.eid_,_inner_env_->sharding_id_,-1); this->sheetid_ = GetSheetId(); this->warehouse_no_ = LogMsg("%d",purchase.stock_id_); this->owner_no_ = LogMsg("%d",purchase.eid_); this->org_no_ = ORG_NO; this->venderid_ = LogMsg("%d",purchase.co_eid_); this->purchase_no_ = GET_UNIQUE_SHEETID(_inner_env_->sharding_id_,purchase.sheet_id_); this->sdate_ = purchase.check_date_; this->checker_ = purchase.checker_; this->class_type_ = "0"; this->purdate_ = purchase.delivery_date_; this->notes_ = purchase.notes_;
if (purchase.details_.size()==0) return 0; vector while(iter!=purchase.details_[0]->end()) { mpm_ns::CPurchase::CPurchaseDetail *detail = (mpm_ns::CPurchase::CPurchaseDetail*)*iter; CPurchase::CItem *item = new CPurchase::CItem; item->Load(*detail); this->items_.push_back(item); iter++; }
return 0; } |
此方法从业务对象(mpm_ns::CPurchase类型)生成对应的请求对象数据。
GET_UNIQUE_SHEETID用于生成唯一单据编号,由于单据编号在zdb各分区主站内唯一,但全局不唯一,因此由分区id和原始单据编号拼接而成,分区id占起始4个字符。
上行确认时需要反向操作,拆分出分区id和原始单据编号。
明细类型转换
int CPurchase::CItem::Load(mpm_ns::CPurchase::CPurchaseDetail &item) { this->serialid_ = item.line_num_; this->owner_article_no_ = LogMsg("%lu",item.goods_id_); this->pkcount_ = item.pack_unit_qty_; this->qty_ = item.bulk_qty_ + item.pack_qty_*item.pack_unit_qty_; this->cust_note_ = item.notes_;
return 0; } |
在插件头文件中声明采购订单审核事件函数,并实现。
int CSealinkWMS::OnNewPurchase(CEvent *e) { return HandleMasterSlave } |
在event_handle_map中增加事件响应函数项。
CEventHandleMapItem event_handle_map[] = { {EVENT_NEW_PURCHASE,(EventHandleFunc)&CSealinkWMS::OnNewPurchase},///<进货通知 }; |
EVENT_NEW_PURCHASE是在采购订单审核后由业务系统激发,由wms接口的事件代理捕获,执行上面注册的函数CSealinkWMS::OnNewPurchase。
根据接口定义文档定义请求业务对象和请求传输请求体对象。
请求业务对象类型:
//////////////////////////////////////////////////////////////////////////////// ///< 验收(进货)确认单 struct CPurchaseAck : public CAckBase { string sheetid_;/// 传单单据编号 STRING(20) 不允许 string warehouse_no_;/// 仓库编码 STRING(3) 不允许 仓库编码 string po_no_; /// 进货通知单号 STRING(20) 不允许 进货通知单号 string checkno_; /// 验收单号 STRING(20) 不允许 (WMS验收单单号) string supplier_no_; /// 供货商编码 STRING(10) 不允许 进货供货商编码 string sdate_;/// 验收时间 date 日期格式:2008-10-11
struct CItem { int serialid_;///单内序号 NUMBER 不允许 采购单内序号 string owner_article_no_;/// 商品编码 STRING(20) 不允许 double qty_; /// 验收总量 Decimal(12,3) string prodate_;/// 生产日期 date 允许 CItem():qty_(0),serialid_(1) { } MEMBER_DEFINE(CItem); int Output(gsm_ns::CStorage::CStorageDetail &item); };
CAutoVector
CPurchaseAck() { } MEMBER_DEFINE(CPurchaseAck); int Output(gsm_ns::CStorage &storage); };
|
这里只实现Output方法,把请求业务对象转换为系统业务对象。
请求体类型:
//////////////////////////////////////////////////////////////////////////////// ///< 进货确认单请求体 struct CPurchaseAckRequestBody : public CMasterSlaveRequestBody MEMBER_DEFINE(CPurchaseAckRequestBody);
CPurchaseAckRequestBody():CMasterSlaveRequestBody("orderlist") { method_ = "WTE_IM_CHECK"; } }; |
CMasterSlaveRequestBody为主从结构类型的请求体,模板参数CPurchaseAck为请求业务对象类型,CPurchaseAck::CItem为明细类型。
orderlist为明细数组名称。
WTE_IM_CHECK为方法名称。
绑定建立json消息与对象成员之间的映射。
SET_MEMBER_BEGIN(CPurchaseAck) SET_MEMBER2(CPurchaseAck,sheetid_,"sheetid",false), SET_MEMBER2(CPurchaseAck,warehouse_no_,"warehouse_no",false), SET_MEMBER2(CPurchaseAck,owner_no_,"owner_no",false), SET_MEMBER2(CPurchaseAck,po_no_,"po_no",false), SET_MEMBER2(CPurchaseAck,checkno_,"checkno",false), SET_MEMBER2(CPurchaseAck,supplier_no_,"supplier_no",false), SET_MEMBER2(CPurchaseAck,sdate_,"sdate",true), SET_MEMBER_END(CPurchaseAck)
SET_MEMBER_BEGIN(CPurchaseAck::CItem) SET_MEMBER2(CPurchaseAck::CItem,owner_article_no_,"owner_article_no",false), SET_MEMBER2(CPurchaseAck::CItem,prodate_,"prodate_",true), SET_MEMBER2(CPurchaseAck::CItem,qty_,"qty",false), SET_MEMBER2(CPurchaseAck::CItem,serialid_,"serialid",false), SET_MEMBER_END(CPurchaseAck::CItem) |
把请求对象转换为业务对象。
int CPurchaseAck::Output(gsm_ns::CStorage &storage) { storage.eid_ = atoi(this->owner_no_.c_str()); string dbc_name; if (PinDB(dbc_name)) return -1; storage.dbc_name_ = dbc_name;
storage.stock_id_ = atoi(this->warehouse_no_.c_str()); SEPARATE_SHEETID(this->po_no_,storage.ref_sheet_id_,-1); storage.co_eid_ = atoi(this->supplier_no_.c_str()); storage.check_date_ = this->sdate_; storage.notes_ = ACK_NOTES_TEXT; storage.creator_ = ACK_USER; storage.oper_date_ = CDateTime::Now().GetDateTime();
if (this->items_.size()==0) return 0;
vector while(iter!=items_.end()) { CPurchaseAck::CItem *item = *iter; gsm_ns::CStorage::CStorageDetail *detail = new gsm_ns::CStorage::CStorageDetail; if (item->Output(*detail)) return -1; storage.details_[0]->push_back(detail); iter++; }
return 0; } |
PinDB根据请求对象内容的货主,确定数据库分区并存储在TLS中,获取数据库连接名。
SEPARATE_SHEETID从请求对象的单据编号,分离出原始单据编号。
//////////////////////////////////////////////////////////////////////////////// int CSealinkWMS::OnPurchaseAck(string &request,CResponseBodyBase **response) { CPurchaseAckRequestBody req; return req.Handle } |
request为请求的消息体(json)内容.
这里Handle调用的含义是对采购确认生成入库单(gsm_ns::CStorage类型),SDT_PURCHASE为入库类型,入库后自动审核,审核采用6049协议。
在GetHandleFunc函数的func_maps中增加响应函数入口项:
HandleRequestFuncPtr GetHandleFunc(string &method) { static struct CFuncMap { string method_; HandleRequestFuncPtr func_ptr_; } func_maps[] = { {"WTE_IM_CHECK",&CSealinkWMS::OnPurchaseAck},///<验收(进货)确认 }; for (unsignedint i=0;i<sizeof(func_maps)/sizeof(func_maps[0]);i++) { CFuncMap &item = func_maps[i]; if (stricmp(item.method_.c_str(),method.c_str())==0) return item.func_ptr_; } return 0; }
|
基于一份有映射关系的接口文档开发,根据接口文档声明类型;根据业务数据映射规则进行成员赋值。
所采用的实现已经尽可能简化了。
基于此方法开发,工作量容易评估,bug率及重复率低,维护扩展容易。
如增加一参数,基本就增加一个变量,绑定一下,映射一下,共3行代码就可以解决。
若接口支持xml,这些代码无需任何变动。
理论上,再简化还有2个方向:
l 应用无关:不涉及业务对象,这是不同的抽象级别,完全是另一种实现
l 代码生成工具:程序就是取代人做重复的程序化的事情的,基于接口描述生成上面的代码主干再修改。
工具的价值取决于重复使用量,低价值的工具不值得开发。
本文就如何进行此类开发进行演示说明,背后的支撑是广泛的,不在此赘言。