顺丰丰桥对接笔记
下单、筛单、路由查询、运单打印
city_code.json
(点我下载)
import os
import json
import codecs
import base64
import requests
import hashlib
from xml.etree import ElementTree
from xml.etree.ElementTree import Element, SubElement
from django.conf import settings
from user.models import Image
from django.core.files.base import ContentFile
# logger
import logging
logger = logging.getLogger("info")
class Singleton(type):
def __init__(cls, name, bases, dict):
super(Singleton, cls).__init__(name, bases, dict)
cls.instance = None
def __call__(cls, *args, **kw):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kw)
return cls.instance
class SF(metaclass=Singleton):
domain = 'https://bsp-oisp.sf-express.com/bsp-oisp/sfexpressService'
client_code = ''
check_word = ''
city_data = None
def call_service(self, xml):
# 拼接校验码
combine_str = xml + self.check_word
# md5
md5 = hashlib.md5()
md5.update(combine_str.encode('utf-8'))
md5_str = md5.digest()
# base64
verify_code = base64.b64encode(md5_str).decode('utf-8')
# call service
data = {"xml": xml, "verifyCode": verify_code}
response = requests.post(self.domain, data=data)
return response
def order_create(self, order_id, month_card, province, city, district, detailed_address, company, contact, tel,
paster, packet):
"""
快速下单
返回参数依次为 发货状态 快递单号 错误信息
"""
root = Element('Request', service='OrderService', lang="zh-CN")
head = SubElement(root, 'Head')
head.text = self.client_code
body = SubElement(root, 'Body')
order_attributes = {
'orderid': order_id,
'is_gen_bill_no': '1',
'express_type': '2',
# 'j_province': '江苏省',
# 'j_city': '南京市',
# 'j_company': '雨花台区',
'j_address': '软件谷科创城D2南',
'j_contact': '殷先生',
'j_tel': '17749503263',
'd_province': province,
'd_city': city,
'd_county': district,
'd_address': detailed_address,
'd_company': company,
'd_contact': contact,
'd_tel': tel,
'parcel_quantity': '1',
'custid': month_card,
'pay_method': '1',
'customs_batchs': ''
}
order = SubElement(body, 'Order', attrib=order_attributes)
SubElement(order, 'Cargo', attrib={'name': '中药(帖)', 'count': str(paster), 'unit': '帖'})
SubElement(order, 'Cargo', attrib={'name': '中药(包)', 'count': str(packet), 'unit': '包'})
xml = ElementTree.tostring(root, encoding='utf8').decode()
response = self.call_service(xml)
if response.status_code == 200:
et = ElementTree.fromstring(response.text)
head = et.find('Head').text
if head == 'OK':
return True, et.find('Body').find('OrderResponse').attrib['mailno']
else:
return False, et.find('ERROR').text
else:
return False, '顺丰服务器无响应'
def order_filter(self, province, city, district, address, tel):
"""筛单 需要开启订单筛选接口 判断判断客户的收、派地址是否属于顺丰的收派范围"""
root = Element('Request', service='OrderFilterService', lang="zh-CN")
head = SubElement(root, 'Head')
head.text = self.client_code
body = SubElement(root, 'Body')
SubElement(body, 'OrderFilter', attrib={
'filter_type': '2',
'd_address': address
})
SubElement(body, 'OrderFilterOption', attrib={
'd_country': '中国',
'd_province': province,
'd_city': city,
'd_county': district,
'd_tel': tel
})
xml = ElementTree.tostring(root, encoding='utf8').decode()
response = self.call_service(xml)
if response.status_code == 200:
et = ElementTree.fromstring(response.text)
head = et.find('Head').text
if head == 'OK':
return et.find('Body').find('OrderFilterResponse').attrib['filter_result']
return None
def router_query(self, order_id):
"""路由查询 快递信息查询"""
root = Element('Request', service='RouteService', lang="zh-CN")
head = SubElement(root, 'Head')
head.text = self.client_code
body = SubElement(root, 'Body')
SubElement(body, 'RouteRequest', attrib={
'tracking_type': '2',
'tracking_number': order_id,
'method_type': '1'
})
xml = ElementTree.tostring(root, encoding='utf8').decode()
response = self.call_service(xml)
if response.status_code == 200:
et = ElementTree.fromstring(response.text)
head = et.find('Head').text
if head == 'OK':
routers = et.find('Body').find('RouteResponse').findall('Route')
data = []
for router in routers:
router_data = {}
for attr in router.attrib:
router_data[attr] = router.attrib[attr]
data.append(router_data)
return data
return None
def city_to_code(self, city):
"""城市转顺丰编码"""
if self.city_data is None:
path = os.path.join(settings.BASE_DIR, 'common/city_code.json')
with codecs.open(path, 'r', 'utf-8') as f:
self.city_data = json.load(f)
for x in self.city_data:
if x['city'] == city:
return x['code']
return None
def waybill(self, mail_no, month_card, province, city, district, detailed_address, company, contact, tel,
paster, packet):
"""电子运单图片下载"""
data = {
'mailNo': mail_no,
'expressType': 2,
'payMethod': 1,
'returnTrackingNo': None,
'monthAccount': month_card,
'orderNo': None,
'zipCode': self.city_to_code('南京市'),
'destCode': self.city_to_code(city),
'payArea': None,
# 寄件人信息
'deliverCompany': '南京诗远启科技有限公司',
'deliverName': '殷先生',
'deliverMobile': '17749503263',
'deliverTel': None,
'deliverProvince': '江苏省',
'deliverCity': '南京市',
'deliverCounty': '雨花台区',
'deliverAddress': '大周路软件谷科创城D2南',
'deliverShipperCode': None,
# 收件人信息
'consignerCompany': company,
'consignerName': contact,
'consignerMobile': tel,
'consignerTel': None,
'consignerProvince': province,
'consignerCity': city,
'consignerCounty': district,
'consignerAddress': detailed_address,
'consignerShipperCode': None,
# logo相关
'logo': None,
'sftelLogo': None,
'topLogo': None,
'topsftelLogo': None,
# 其他信息
'totalFee': None,
'appId': '必填',
'appKey': '必填',
'electric': 'E',
'insureValue': None,
'insureFee': None,
'codValue': None,
'codMonthAccount': None,
# 丰密运单相关配置
'abFlag': None,
'xbFlag': None,
'proCode': None,
'destRouteLabel': None,
'destTeamCode': None,
'codingMapping': None,
'codingMappingOut': None,
'sourceTransferCode': None,
'printIcon': None,
'qrcode': None,
# 加密
'encryptMobile': False,
'encryptCustName': False,
# 货物信息
'cargoInfoDtoList': [
{
'cargo': '中药(帖)',
'parcelQuantity': None,
'cargoCount': paster,
'cargoUnit': '帖',
'cargoWeight': None,
'cargoAmount': None,
'cargoTotalWeight': None,
'remark': '内有液体 小心轻放',
'sku': None
},
{
'cargo': '中药(包)',
'parcelQuantity': None,
'cargoCount': packet,
'cargoUnit': '包',
'cargoWeight': None,
'cargoAmount': None,
'cargoTotalWeight': None,
'remark': '',
'sku': None
}
]
}
url = 'http://localhost:4040/sf/waybill/print?type=V2.1_poster_100mm150mm&output=image'
response = requests.post(url, data='[{}]'.format(json.dumps(data)))
if response.json()['code'] == 'SYS_CODE_QIAO_0200':
images = []
for image_base64 in response.json()['result']:
_image = ContentFile(base64.b64decode(image_base64), name='express_waybill.jpg')
image = Image.objects.create(image=_image, purpose=2)
images.append(image)
return images
return None
Ubuntu14.04安装JDK1.7
官网下载安装包
前往官网下载安装包,将安装包拷贝至主机待用。
创建文件夹、解压文件
创建文件夹
解压压缩包至文件夹
设置环境变量
文件结尾加入环境变量内容:
#set java environment
export JAVA_HOME=/usr/lib/java/jdk1.7.0_80
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
设置完环境变量之后执行命令让设置生效
设置默认的JDK
sudo update-alternatives --install /usr/bin/java java /usr/lib/java/jdk1.7.0_80/bin/java 300
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/java/jdk1.7.0_80/bin/javac 300
检查Ubuntu的JDK版本
dreamgo@dreamgo:/usr/lib/java$ java -version
java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
运单面单自助打印SDK
运行服务
- 拷贝
csim_waybill_print_service_V1.0.5.jar
至服务器 chmod -R 777 csim_waybill_print_service_V1.0.5.jar
对文件授权,以确保该jar能正确生成日志文件。sudo java -jar csim_waybill_print_service_V1.0.5.jar 8888
#端口号不填时默认为4040,请检查防火墙状态。(多次反复启动\关闭会导致服务开启是卡在第3步,已提交顺丰技术,等待验证解决。)
python请求接口
使用的100x150mm,转base64的接口,代码参见最上方。可结合需求和nodejs以及java版本demo自行修改。
安装字体
安装字体是为了解决在Linux系统上,生成的快递面单无法正常显示目的地编码的问题。
下载字体
字体包simhei.ttf
可在SF-CSIM-PRINTER-SDK-V1.0.5
压缩包中的linux系统启动注意事项
文件夹中找到。
查看当前系统中已安装的中文字体
复制simsun.ttf
字体文件
拷贝字体文件至该文件夹
cd /usr/share/fonts/my_fonts
apt-get install xfonts-utils
mkfontscale
mkfontdir #注意 是 mkfontdir 不是 mkfontsdir,此处顺丰文档有错误。
ll # 应该可以看到对应的fonts.dir 和 fonts.scale文件
再次查看已安装的字体
自动开启自助面单打印服务
supervisord
fengqiao.conf
:
[program:feng_qiao]
command=java -jar csim_waybill_print_service_V1.0.5.jar
directory=/django/fengqiao
stdout_logfile=/django/fengqiao/out.log
stderr_logfile=/django/fengqiao/error.log
autostart=true
autorestart=true
user=root
startsecs=10
用户user
必须是root
,超管名称不是root
的也写root
,不然会权限不足。 supervisord
相关操作,部署请参考Supervisor使用笔记及celery生产部署。
作为服务 暂未测试通过
#!/bin/sh
SERVICE_NAME=fengqiao
PATH_TO_JAR=/django/fengqiao/csim_waybill_print_service_V1.0.5.jar
PID_PATH_NAME=/tmp/fengqiao-pid
case $1 in
start)
echo "Starting $SERVICE_NAME ..."
if [ ! -f $PID_PATH_NAME ]; then
nohup java -jar $PATH_TO_JAR /tmp 2>> /dev/null >> /dev/null &
echo $! > $PID_PATH_NAME
echo "$SERVICE_NAME started ..."
else
echo "$SERVICE_NAME is already running ..."
fi
;;
stop)
if [ -f $PID_PATH_NAME ]; then
PID=$(cat $PID_PATH_NAME);
echo "$SERVICE_NAME stoping ..."
kill $PID;
echo "$SERVICE_NAME stopped ..."
rm $PID_PATH_NAME
else
echo "$SERVICE_NAME is not running ..."
fi
;;
restart)
if [ -f $PID_PATH_NAME ]; then
PID=$(cat $PID_PATH_NAME);
echo "$SERVICE_NAME stopping ...";
kill $PID;
echo "$SERVICE_NAME stopped ...";
rm $PID_PATH_NAME
echo "$SERVICE_NAME starting ..."
nohup java -jar $PATH_TO_JAR /tmp 2>> /dev/null >> /dev/null &
echo $! > $PID_PATH_NAME
echo "$SERVICE_NAME started ..."
else
echo "$SERVICE_NAME is not running ..."
fi
;;
esac