踩坑总结
1.接口都是使用的表单数据、且推送接口表单数据带空格需要处理一下、(当时怎么都获取不到数据)
2.地图并不是查询的实时的、而是根据你订阅接口发送的发货地和收货地来绘制的、如果想要获取准确的地图需要额外处理。
3.推送接口推的数据经常不返回地图的url、这里可以处理一下、增加个判断、这样快递100会等待半个小时之后再推送一次。
4.顺丰快递需要手机号才能查到
5.订阅的时候resultv2的值别用默认的、可以用5或者7
6.我们使用的快递100的api、地图url在未签收前有三天有效期、签收后就只有15天了、如果想要持久化物流信息的话、最好还是拿他们返回的地理位置自己进行渲染、如果觉得不影响、可以直接使用他们返回的url。
javascript
if param.LastResult_.TrailUrl == "" {
ctx.JSON(500, gin.H{
"result": false,
"returnCode": "500",
"message": "trailUrl数据为空",
})
1、基本信息
接口文档
api.kuaidi100.com/document/60...
一个订阅接口一个推送接口

1.1订阅接口
需要发送字段
参数名 | 是否必填 | 类型 | 说明 |
---|---|---|---|
key | 是 | string | 授权码,请申请企业版获取 |
company | 是 | string | 订阅的快递公司的编码,一律用小写字母 |
number | 是 | string | 订阅的快递单号,单号的最大长度是40个字符 |
from | 是 | string | 快递寄件地址 |
to | 是 | string | 快递收件地址 |
parameters | 是 | Object | 辅助参数 |
parameters数据结构:
参数名 | 是否必填 | 类型 | 说明 |
---|---|---|---|
callbackurl | 是 | string | 回调接口的地址 |
salt | 否 | string | 签名用随机字符串 |
phone | 否 | string | 收寄件人的移动电话号码(只能填写一个,顺丰速运和丰网速运必填,其他快递公司选填) |
ordertime | 否 | string | 订单下单时间,格式"yyyy-MM-dd HH" |
resultv2 | 否 | string | 添加此字段表示开通行政区域解析功能。0:关闭(默认),1:开通行政区域解析功能以及物流轨迹增加物流状态值,2:开通行政解析功能以及物流轨迹增加物流状态值并且返回出发、目的及当前城市信息 4:开通行政解析功能以及物流轨迹增加物流高级状态名称并且返回出发、目的及当前城市信息 6:开通行政解析功能以及物流轨迹增加物流高级状态名称、状态值并且返回出发、目的及当前城市信息 |
1.2推送接口
接收的字段
项目 | 值/说明 |
---|---|
请求方式 | POST |
Header参数 | Content-Type: application/x-www-form-urlencoded |
Body参数 | 见下表 |
Body参数
参数名 | 必填 | 类型 | 说明 |
---|---|---|---|
sign | 否 | string | 加密签名(需转32位大写),算法:md5(param+salt) (仅当salt值非空时推送) |
param | 是 | Object | 主体参数对象 |
param对象结构
字段名 | 必填 | 类型 | 说明 | 可选值 |
---|---|---|---|---|
status | 是 | String | 监控状态 | polling (监控中)、shutdown (结束)、abort (中止)、updateall (重新推送) |
billstatus | 是 | String | 已弃用,忽略即可 | got , sending , check |
message | 是 | String | 状态消息(如:3天查询无记录 , 60天无变化 ) |
|
autoCheck | 是 | String | 快递公司编码纠错标记 | 0 (原始编码)、1 (纠正后的编码) |
comOld | 是 | String | 原始快递公司编码(当autoCheck=1或status=abort时有效;国际版接口无此字段) | 如:yuantong |
comNew | 是 | String | 纠正后的快递公司编码(当autoCheck=1时有效;国际版接口无此字段) | 如:ems |
lastResult | 是 | Object | 最新查询结果数据 |
lastResult对象结构
字段名 | 类型 | 说明 | 特殊条件 |
---|---|---|---|
message | String | 消息体(忽略) | |
state | String | 核心状态:0在途, 1揽收, 2疑难, 3签收, 4退签, 5派件, 8清关, 14拒签 | |
status | String | 通讯状态(忽略) | |
condition | String | 明细状态标记(未实现) | |
ischeck | Integer | 签收标记(0未签收, 1已签收,建议用state字段替代) | |
com | String | 快递公司编码(小写) | |
nu | String | 快递单号 | |
trailUrl | String | 轨迹地图链接 | resultv2=7时值为null |
arrivalTime | String | 预计到达时间 | |
totalTime | String | 平均耗时 | |
remainTime | String | 剩余时间 | |
isLoop | Boolean | 是否存在运输环路 | |
routeInfo | Object | 行政区划路由信息 | |
data | Array | 物流轨迹详情(倒序排列) | |
predictedRoute | Array | 物流节点预测数据 | 需传参resultv2=7 |
routeInfo(行政区划路由)
字段名 | 子字段 | 类型 | 说明 |
---|---|---|---|
from | number | String | 出发地行政区编码 |
name | String | 出发地名称 | |
cur | number | String | 当前地行政区编码 |
name | String | 当前地名称 | |
to | number | String | 目的地行政区编码 |
name | String | 目的地名称 |
data(物流轨迹详情)
字段名 | 类型 | 说明 | 触发条件 |
---|---|---|---|
context | String | 物流信息内容 | |
time | String | 原始时间(如:2025-04-16 10:36:46) | |
ftime | String | 格式化后时间 | |
status | String | 物流状态名称 | resultv2=3或5 |
areaCode | String | 行政区域编码 | resultv2=3或5 |
areaName | String | 行政区域名称 | resultv2=3或5 |
statusCode | String | 高级物流状态值 | resultv2=5 |
areaCenter | String | 行政区域经纬度(高德坐标) | resultv2=5 |
location | String | 快件当前位置 | resultv2=5 |
areaPinYin | String | 行政区域拼音 | resultv2=5 |
predictedRoute(物流节点预测)
字段名 | 类型 | 说明 | 示例值 |
---|---|---|---|
arriveTime | String | 到达节点时间 | 2025-04-16 10:36:46 |
leaveTime | String | 离开节点时间 | 2025-04-16 10:39:03 |
province | String | 节点所在省 | 湖北 |
city | String | 节点所在市 | 宜昌市 |
district | String | 节点所在区 | 点军区 |
name | String | 节点名称 | 武汉转运中心 |
state | String | 节点状态:已经过 /当前停留 /预估途径 |
|
type | String | 节点类型:转运中心 /网点 |
|
location | String | 经纬度坐标(高德) |
1.2.1关键业务逻辑说明(如果业务要求不高就暂时不需要处理)
- 状态关联
- 当快递签收 →
status="shutdown"
- 当连续60天无变化/3天无记录 →
status="abort"
(需特殊处理)
- 公司编码纠错
- 连续3天查不到结果时:
- 若原始编码正确 → 推送
autoCheck=0
- 若编码错误 → 自动用新编码订阅,并推送:
autoCheck=1, comOld=原编码, comNew=新编码
- 高级字段触发
- 需传参
resultv2=3/5/7
才会返回高级字段(如statusCode/predictedRoute)。
2、表结构
2.1订阅接口
为什么要这么设计
一般来说、当我们把商品出仓或者入仓的时候、需要追踪物流、来查看当前商品的运输情况、这里只讨论出仓的时候、入仓也是一样的逻辑。
2.1.1.基础字段
key、company、number、from、to、parameters、Object、callbackurl(地址我做了三级下拉框所以一共是6个字段)
这几个是必填的、需要我们发送到快递100那边、对快递进行订阅、
id 、created_at 、updated_at 、created_user_id 、created_user_nickname 、updated_user_id 、updated_user_nickname 、deleted 、organization_id
这几个字段是基础字段、一般创建表的时候都要包含这几个字段
2.1.2特殊字段
另外还有一些特殊字段、因为出库单的上游可能是不同类型的单据、比如销售的、采购退货的、所以需要一个type来表示不同的单据、一个relevance_id来跟其他单据做关联、另外需要记录一下你推送给快递100的推送状态、所以我设计了map_tracker_status 这个字段、下图为出库单的表结构(按需修改)
java
create table sims_enter_leave_inventory_bill
(
id bigint auto_increment
primary key,
created_at datetime default CURRENT_TIMESTAMP null,
updated_at datetime default CURRENT_TIMESTAMP null,
created_user_id bigint null,
created_user_nickname varchar(500) null,
updated_user_id bigint null,
updated_user_nickname varchar(500) null,
deleted int(1) default 0 null,
organization_id bigint null,
name varchar(200) null comment '单据名称',
code varchar(500) null,
direction varchar(10) null comment '0:入库 1:出库',
type varchar(10) null comment '【入库】0:进销存售后单 1:采购订单 2:人工新批次 3:人工已出批次 4:组合单 5:销售订单耗用 6:商城售后单 【出库】0:进销存订单 1:采购售后 2:人工出库 3:组合单 5:销售订单耗用 6:商城订单',
relevance_id bigint null,
relevance_code varchar(500) null,
counterparty_id bigint null comment '供应商Id',
status varchar(10) null comment '【入库】0:待确认 1:待入库 2:已完成 3:取消 4:失败 【出库】0:待确认 1:待出库 2:待收货 3:已完成 4:取消 5:失败',
warehouse_id bigint null,
product_num bigint null,
delivery_company varchar(500) null,
delivery_num varchar(500) null,
map_tracker_status varchar(20) default '0' null comment '地图轨迹推送状态: 0=未推送, 1=已推送, 2=推送失败',
message text null,
remark varchar(200) null comment '备注',
operate_date varchar(50) null comment '操作时间',
price_tax_expenses decimal(18, 2) default 0.00 null comment '整单分摊的商品价格+税',
additional_tax_expenses decimal(18, 2) default 0.00 null comment '整单分摊的附加费+税',
product_tax_expenses decimal(18, 2) default 0.00 null comment '整单分摊的商品税',
purchase_tax_expenses decimal(18, 2) default 0.00 null comment '整单分摊的附加费税',
sale_price varchar(250) default '0.00' null comment '销售单价',
sale_price_decimal decimal(18, 2) default 0.00 null comment '售后-销售总价',
income_expend_type varchar(4) null comment '类型 1-非订单收入 2-股东投入分配 3-非订单费用支出',
total_amount decimal(18, 2) default 0.00 null comment '人工出入库单据总金额',
product_total_amount decimal(18, 2) default 0.00 null comment '出入库单据商品总金额',
order_expense_category varchar(255) null comment '订单支出类别',
delivery_province_code varchar(255) null comment '收货地址省份编码',
delivery_city_code varchar(255) null comment '收货地址城市编码',
delivery_area_code varchar(255) null comment '收货地址区县编码',
ship_province_code varchar(255) null comment '发货地址省份编码',
ship_city_code varchar(255) null comment '发货地址城市编码',
ship_area_code varchar(255) null comment '发货地址区县编码',
phone_number varchar(255) null comment '物流电话'
)
charset = utf8mb4;
create index relevance_type_idx
on sims_enter_leave_inventory_bill (direction, type, relevance_id);
2.2推送接口
2.2.1基础字段
同订阅接口
2.2.2特殊字段
java
create table soms.express_tracking
(
id bigint unsigned auto_increment comment '主键ID'
primary key,
express_no varchar(40) not null comment '快递单号',
express_company varchar(20) not null comment '快递公司编码',
request_sign varchar(64) null comment '请求签名(md5(param+salt))',
auto_check tinyint default 0 null comment '公司编码是否纠正(0:未纠正,1:已纠正)',
original_express_company varchar(20) null comment '原始快递公司编码',
corrected_express_company varchar(20) null comment '纠正后的快递公司编码',
track_status enum ('polling', 'shutdown', 'abort', 'updateall') default 'polling' null comment '跟踪状态',
track_message varchar(255) null comment '跟踪状态消息',
express_state int null comment '运单状态值',
advanced_status varchar(50) null comment '高级状态名称',
advanced_status_code int null comment '高级状态值',
current_location varchar(100) null comment '当前位置描述',
area_code varchar(20) null comment '行政区编码',
area_name varchar(50) null comment '行政区名称',
route_from_code varchar(20) null comment '出发地行政区编码',
route_from_name varchar(50) null comment '出发地行政区名称',
route_to_code varchar(20) null comment '目的地行政区编码',
route_to_name varchar(50) null comment '目的地行政区名称',
route_current_code varchar(20) null comment '当前行政区编码',
route_current_name varchar(50) null comment '当前行政区名称',
estimated_arrival_time datetime null comment '预计到达时间',
tracking_updated_at datetime not null comment '最后跟踪时间',
trail_url varchar(255) null comment '轨迹地图URL',
created_at datetime default CURRENT_TIMESTAMP null comment '创建时间',
updated_at datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新时间',
created_user_id bigint null comment '创建人ID',
updated_user_id bigint null comment '更新人ID',
created_user_nickname varchar(100) null comment '创建人昵称',
updated_user_nickname varchar(100) null comment '更新人昵称',
deleted tinyint default 0 null comment '删除标记(0:未删除,1:已删除)'
)
comment '快递轨迹跟踪表' charset = utf8mb4;
create index idx_express_company
on soms.express_tracking (express_company);
create index idx_express_no
on soms.express_tracking (express_no);
create index idx_track_status
on soms.express_tracking (track_status);
sql
create table soms.tracking_points
(
id bigint unsigned auto_increment comment '主键ID'
primary key,
tracking_id bigint unsigned not null comment '关联跟踪记录ID',
point_description text not null comment '轨迹点描述',
point_time datetime null comment '轨迹点时间',
point_status varchar(50) null comment '状态名称',
status_code int null comment '状态代码',
area_code varchar(20) null comment '区域编码',
area_name varchar(50) null comment '区域名称',
created_at datetime default CURRENT_TIMESTAMP null comment '创建时间',
updated_at datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新时间',
created_user_id bigint null comment '创建人ID',
updated_user_id bigint null comment '更新人ID',
created_user_nickname varchar(100) null comment '创建人昵称',
updated_user_nickname varchar(100) null comment '更新人昵称',
deleted tinyint default 0 null comment '删除标记(0:未删除,1:已删除)',
constraint tracking_points_ibfk_1
foreign key (tracking_id) references soms.express_tracking (id)
on delete cascade
)
comment '快递轨迹点明细表' charset = utf8mb4;
create index idx_point_time
on soms.tracking_points (point_time);
create index idx_tracking_id
on soms.tracking_points (tracking_id);
sql
create table soms.tracking_special_status
(
id bigint unsigned auto_increment comment '主键ID'
primary key,
tracking_id bigint unsigned not null comment '关联跟踪记录ID',
status_type enum ('abort-3day', 'company-corrected', 'suspicious') null comment '特殊状态类型',
action_taken enum ('resubmitted', 'marked-fake', 'updated-company') null comment '已采取的行动',
action_date datetime null comment '行动日期',
resubmit_count tinyint default 0 null comment '重新提交次数',
created_at datetime default CURRENT_TIMESTAMP null comment '创建时间',
updated_at datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment '更新时间',
created_user_id bigint null comment '创建人ID',
updated_user_id bigint null comment '更新人ID',
created_user_nickname varchar(100) null comment '创建人昵称',
updated_user_nickname varchar(100) null comment '更新人昵称',
deleted tinyint default 0 null comment '删除标记(0:未删除,1:已删除)',
constraint tracking_special_status_ibfk_1
foreign key (tracking_id) references soms.express_tracking (id)
on delete cascade
)
comment '特殊状态处理记录表' charset = utf8mb4;
create index idx_status_type
on soms.tracking_special_status (status_type);
create index tracking_id
on soms.tracking_special_status (tracking_id);
3.接口的实现
3.1订阅接口
订阅接口很简单、从表里面查出需要的数据进行组装、发送给快递100、发送形式为表单类型的数据、订阅成功之后快递100官方会调用你的回调地址把物流相关信息返回给你、可以查看官方文档。
需要注意的点:
顺丰快递需要发手机号、salt表示使用加密签名、resultv2(3、5、7返回的信息越来越详细)
go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"time"
)
// 快递100订阅接口配置
const (
SubscribeURL = "http://poll.kuaidi100.com/pollmap"
APIKey = "YOUR_API_KEY_HERE" // 替换为实际API密钥
CallbackURL = "https://yourdomain.com/callback"
Salt = "your_random_salt"
ResultV2 = "5" // 使用5获取更多物流详情
)
// 订阅请求数据结构
type SubscribeRequest struct {
Key string `json:"key"`
Company string `json:"company"`
Number string `json:"number"`
From string `json:"from"`
To string `json:"to"`
Params SubscribeParams `json:"parameters"`
}
type SubscribeParams struct {
CallbackURL string `json:"callbackurl"`
Salt string `json:"salt,omitempty"`
Phone string `json:"phone,omitempty"` // 顺丰快递需要此参数
ResultV2 string `json:"resultv2,omitempty"`
}
// 订阅响应数据结构
type SubscribeResponse struct {
Result bool `json:"result"`
ReturnCode string `json:"returnCode"`
Message string `json:"message"`
}
func main() {
// 模拟需要订阅的订单数据
orders := []struct {
ID int
DeliveryCompany string
DeliveryNum string
FromAddress string
ToAddress string
Phone string
}{
{
ID: 1,
DeliveryCompany: "yuantong",
DeliveryNum: "YT1234567890",
FromAddress: "上海市浦东新区",
ToAddress: "北京市朝阳区",
Phone: "13800138000", // 顺丰快递需要手机号
},
// 可以添加更多订单...
}
// 遍历订单进行订阅
for _, order := range orders {
err := subscribeExpress(order)
if err != nil {
log.Printf("订单 %d 订阅失败: %v", order.ID, err)
continue
}
log.Printf("订单 %d 订阅成功", order.ID)
}
}
// 订阅快递单号
func subscribeExpress(order struct {
ID int
DeliveryCompany string
DeliveryNum string
FromAddress string
ToAddress string
Phone string
}) error {
// 构建订阅请求
req := SubscribeRequest{
Key: APIKey,
Company: order.DeliveryCompany,
Number: order.DeliveryNum,
From: order.FromAddress,
To: order.ToAddress,
Params: SubscribeParams{
CallbackURL: CallbackURL,
Salt: Salt,
Phone: order.Phone,
ResultV2: ResultV2,
},
}
// 转换为JSON
paramJSON, err := json.Marshal(req)
if err != nil {
return fmt.Errorf("JSON编码失败: %v", err)
}
// 构建表单数据
formData := url.Values{}
formData.Set("schema", "json")
formData.Set("param", string(paramJSON))
// 发送HTTP请求
resp, err := http.PostForm(SubscribeURL, formData)
if err != nil {
return fmt.Errorf("HTTP请求失败: %v", err)
}
defer resp.Body.Close()
// 解析响应
var result SubscribeResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("响应解析失败: %v", err)
}
// 检查订阅结果
if result.ReturnCode != "200" {
return fmt.Errorf("订阅失败: %s", result.Message)
}
return nil
}
// 注意:实际应用中应该添加重试机制和更完善的错误处理
3.2推送接口
推送接口就是快递100把物流相关信息推送过来、trailUrl就是我们需要的地图url、可以直接引入。
因为推送数据包含基础信息、和物流轨迹信息、还有一个特殊状态、所以我是设计了3个表(根据官方文档按需要设计自己的表)
go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// 推送数据结构
type PushData struct {
Sign string `json:"sign"`
Param string `json:"param"`
}
type PushParam struct {
Status string `json:"status"`
AutoCheck string `json:"autoCheck"`
ComOld string `json:"comOld,omitempty"`
ComNew string `json:"comNew,omitempty"`
LastResult LastResult `json:"lastResult"`
}
type LastResult struct {
TrailUrl string `json:"trailUrl"`
// 其他字段根据需要添加
}
func main() {
r := gin.Default()
// 推送回调接口
r.POST("/callback", handleCallback)
log.Println("启动快递100推送服务,监听端口:8080")
log.Fatal(r.Run(":8080"))
}
// 回调处理函数
func handleCallback(c *gin.Context) {
// 1. 读取原始请求体
body, err := io.ReadAll(c.Request.Body)
if err != nil {
log.Printf("读取请求体失败: %v\n", err)
c.JSON(400, gin.H{"error": "读取请求体失败"})
return
}
// 打印原始请求体(调试用)
log.Printf("原始请求体: %s\n", string(body))
// 重置请求体,以便后续处理
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
// 2. 尝试获取sign和param参数
sign := c.PostForm("sign")
paramStr := c.PostForm("param")
// 3. 兼容字段名带空白符的情况
if paramStr == "" || sign == "" {
for key, values := range c.Request.Form {
trimmedKey := strings.TrimSpace(key)
if trimmedKey == "param" && paramStr == "" && len(values) > 0 {
paramStr = values[0]
log.Printf("通过遍历Form找到param: %s\n", paramStr)
}
if trimmedKey == "sign" && sign == "" && len(values) > 0 {
sign = values[0]
log.Printf("通过遍历Form找到sign: %s\n", sign)
}
}
}
// 4. 尝试其他方式获取参数
if paramStr == "" {
// 尝试从Query参数获取
paramStr = c.Query("param")
log.Printf("从Query参数获取param: %s\n", paramStr)
}
if paramStr == "" {
// 尝试从JSON body获取
var jsonBody map[string]interface{}
if err := c.ShouldBindJSON(&jsonBody); err == nil {
if param, ok := jsonBody["param"].(string); ok {
paramStr = param
log.Printf("从JSON body获取param: %s\n", paramStr)
}
}
}
// 5. 尝试从原始body中解析
if paramStr == "" && len(body) > 0 {
log.Printf("尝试从原始body解析form数据\n")
// 手动解析form数据
if err := c.Request.ParseForm(); err == nil {
paramStr = c.Request.FormValue("param")
sign = c.Request.FormValue("sign")
log.Printf("从手动解析的form获取: sign=%s, param=%s\n", sign, paramStr)
}
}
// 6. 检查参数是否存在
if paramStr == "" {
log.Printf("param参数缺失,返回错误\n")
c.JSON(400, gin.H{"msg": "param参数缺失"})
return
}
// 7. 解析param字段(JSON字符串)
var param PushParam
if err := json.Unmarshal([]byte(paramStr), ¶m); err != nil {
log.Printf("param解析失败: %v, paramStr: %s\n", err, paramStr)
c.JSON(400, gin.H{"msg": "param解析失败"})
return
}
// 8. 打印解析后的完整数据结构
log.Printf("解析后的快递100推送数据:sign=%s, param=%+v\n", sign, param)
// 9. 业务处理(这里简化为打印和保存)
processPushData(sign, param)
// 10. 检查地图URL是否存在
if param.LastResult.TrailUrl == "" {
log.Println("trailUrl数据为空")
c.JSON(500, gin.H{
"result": false,
"returnCode": "500",
"message": "trailUrl数据为空",
})
} else {
log.Println("处理成功")
// 返回成功响应
c.JSON(200, gin.H{
"result": true,
"returnCode": "200",
"message": "success",
})
}
}
// 处理推送数据
func processPushData(sign string, param PushParam) {
// 这里可以:
// 1. 验证签名(param+Config.Salt)
// 2. 保存物流信息到数据库
// 3. 处理特殊状态(签收/中止等)
log.Printf("收到推送: sign=%s, status=%s, trailUrl=%s",
sign, param.Status, param.LastResult.TrailUrl)
// 示例:打印所有数据
log.Printf("完整推送数据: %+v", param)
// 公司编码纠错处理
if param.AutoCheck == "1" {
log.Printf("公司编码纠错: %s -> %s", param.ComOld, param.ComNew)
}
// 处理特殊状态
switch param.Status {
case "shutdown":
log.Println("快递已签收")
case "abort":
log.Println("快递状态中止(60天无变化/3天无记录)")
case "updateall":
log.Println("重新推送所有数据")
}
// 保存到数据库(伪代码)
// saveToDatabase(param)
}
// 模拟保存到数据库
func saveToDatabase(param PushParam) {
log.Printf("保存物流信息到数据库: 单号=%s, 状态=%s", param.LastResult.TrailUrl, param.Status)
// 实际实现数据库保存逻辑
}
需要注意的点:
推送接口的数据的数据不是使用的json、是表单格式、单纯请求表单请求不到、需要兼容带空格的数据
重点是下面这一段
ini
// 兼容字段名带空白符的情况
if paramStr == "" || sign == "" {
for key, values := range ctx.Request.Form {
trimmedKey := strings.TrimSpace(key)
if trimmedKey == "param" && paramStr == "" && len(values) > 0 {
paramStr = values[0]
log.Printf("通过遍历Form找到param: %s\n", paramStr)
}
if trimmedKey == "sign" && sign == "" && len(values) > 0 {
sign = values[0]
log.Printf("通过遍历Form找到sign: %s\n", sign)
}
}
}
3.3获取物流信息
这个就很简单了、在单据上面加一个按钮、点击按钮获取物流信息跟物流轨迹、地图直接嵌入trailurl(都是从表里面拿的)在页面上显示
go
// 1. 查询所有与商城订单ID相关的出库单
param := &repo.SimsEnterLeaveInventoryBillDBDataParam{
SimsEnterLeaveInventoryBillDBData: repo.SimsEnterLeaveInventoryBillDBData{
Type: "6",
RelevanceId: orderId,
},
}
dbList, count, err := s.billRepo.List(ctx, "", 0, 0, param)
if err != nil {
return nil, err
}
resp := &api.BatchGetExpressTrackingResponse{
Code: "200",
Message: "success",
}
if dbList == nil || len(*dbList) == 0 {
log.Printf("GetExpressTrackingByOrderId: 未查到出库单")
resp.Code = "404"
resp.Message = "未查到出库单"
resp.Data = nil
return resp, nil
}
var data []*api.ExpressTrackingInfo
var failedNos []string
var successCount, failureCount int32
for _, dbData := range *dbList {
if dbData.DeliveryNum == "" {
log.Printf("GetExpressTrackingByOrderId: 出库单ID=%d 未填写快递单号", dbData.Id)
failedNos = append(failedNos, "")
failureCount++
continue
}
// 2. 根据快递单号获取物流信息
expressData, err := s.trackingRepo.GetByExpressNo(ctx, dbData.DeliveryNum)
if err != nil || expressData == nil {
log.Printf("GetExpressTrackingByOrderId: 出库单ID=%d 未查到物流信息", dbData.Id)
failedNos = append(failedNos, dbData.DeliveryNum)
failureCount++
continue
}
// 3. 组装数据
pointsData, _ := s.pointsRepo.GetByTrackingID(ctx, expressData.Id)
specialData, _ := s.specialRepo.GetByTrackingID(ctx, expressData.Id)
info := stru.ConvertToExpressTrackingInfo(expressData, pointsData, specialData)
data = append(data, info)
successCount++
}
resp.Data = data
resp.SuccessCount = successCount
resp.FailureCount = failureCount
resp.FailedExpressNos = failedNos
resp.ResponseAt = time.Now().Format("2006-01-02 15:04:05")
resp.ProcessingTimeMs = 0 // 可选
if len(data) == 0 {
resp.Code = "404"
resp.Message = "未查到任何物流信息"
}
return resp, nil
}