Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
X
xqh_temu_api
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
lqy
xqh_temu_api
Commits
6cc3f91f
提交
6cc3f91f
authored
2月 10, 2026
作者:
刘擎阳
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
1.优化
上级
e094e2ca
显示空白字符变更
内嵌
并排
正在显示
4 个修改的文件
包含
90 行增加
和
128 行删除
+90
-128
temu_api.py
app/api/temu_api.py
+5
-5
util.py
app/util.py
+70
-111
create_pdf.py
services/create_pdf.py
+6
-3
temu_service.py
services/temu_service.py
+9
-9
没有找到文件。
app/api/temu_api.py
浏览文件 @
6cc3f91f
...
...
@@ -21,7 +21,7 @@ from nameko.standalone.rpc import ClusterRpcProxy
from
config
import
NAMEKO_CONFIG
from
.
import
api
from
__init__
import
rpc
from
util
import
check_
customer
from
util
import
check_
sign
CONFIG
=
NAMEKO_CONFIG
_logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -39,7 +39,7 @@ _logger = logging.getLogger(__name__)
@api.route
(
'/temu/create/order'
,
methods
=
[
'post'
])
# @check_
customer
# @check_
sign
def
temu_create_order
():
"""创建订单"""
res
=
{
...
...
@@ -90,7 +90,7 @@ def temu_create_order():
@api.route
(
'/temu/update/order'
,
methods
=
[
'post'
])
# @check_
customer
# @check_
sign
def
temu_update_order
():
"""更新订单"""
# 接收提单信息、大包与小包的关联信息
...
...
@@ -144,7 +144,7 @@ def temu_update_order():
@api.route
(
'/temu/query/order'
,
methods
=
[
'post'
])
# @check_
customer
# @check_
sign
def
temu_query_order
():
"""查询箱贴"""
res
=
{
...
...
@@ -195,7 +195,7 @@ def temu_query_order():
@api.route
(
'/temu/cancel/order'
,
methods
=
[
'post'
])
# @check_
customer
# @check_
sign
def
temu_cancel_order
():
# 接收取消订单
res
=
{
...
...
app/util.py
浏览文件 @
6cc3f91f
...
...
@@ -6,132 +6,91 @@ from datetime import timedelta, datetime
import
jwt
from
db_service
import
DbService
#
from db_service import DbService
import
functools
import
logging
import
pandas
as
pd
from
flask
import
request
,
jsonify
from
config
import
NAMEKO_CONFIG
TIMEOUT_TIME
=
1000
db_handle
=
DbService
()
.
conn_engine
#
db_handle = DbService().conn_engine
_logger
=
logging
.
getLogger
(
__name__
)
# 假设这是你的 App Secret
APP_SECRET
=
"your_app_secret_here"
def
check_customer
(
func
):
@functools.wraps
(
func
)
def
wrapper
(
*
args
,
**
kw
):
res
=
{
"code"
:
0
,
"msg"
:
"success"
,
"requestID"
:
"202312251715021060522200417739B9"
,
"ts"
:
"2023-12-25 17:15:03"
}
request_time
=
datetime
.
utcnow
()
current_timestamp
=
int
(
time
.
time
())
res
[
'ts'
]
=
request_time
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
)
res
[
'requestID'
]
=
request_time
.
strftime
(
"
%
Y
%
m
%
d
%
H
%
M
%
S"
)
+
str
(
current_timestamp
)
# 获取传输的值
sign
=
kw
[
'sign'
]
if
kw
.
get
(
'sign'
)
else
""
param_json_str
=
kw
[
'param_json'
]
if
kw
.
get
(
'param_json'
)
else
"{}"
timestamp
=
kw
[
'timestamp'
]
if
kw
.
get
(
'timestamp'
)
else
""
version
=
kw
[
'version'
]
if
kw
.
get
(
'version'
)
else
""
app_key
=
kw
[
'app_key'
]
if
kw
.
get
(
'app_key'
)
else
""
app_secret
=
request
.
env
[
"ir.config_parameter"
]
.
sudo
()
.
get_param
(
'tt_app_secret'
)
or
''
_logger
.
info
(
'request_data:
%
s'
%
kw
)
if
kw
.
get
(
'param_json'
):
# param_json = json.loads(kw['param_json'])
# param_json_str = json.dumps(param_json, ensure_ascii=False)
check_sign
=
request
.
env
[
"ao.tt.api"
]
.
sudo
()
.
make_sign
(
app_key
,
app_secret
,
version
,
timestamp
,
param_json_str
)
# print(check_sign)
if
sign
!=
check_sign
:
res
[
'code'
]
=
1004
res
[
'msg'
]
=
'验证失败'
# 检查时间的有效性
if
res
[
'code'
]
==
0
:
# try:
millis
=
time
.
time
()
_logger
.
info
(
u'时间戳:
%
s'
%
millis
)
now
=
datetime
.
now
()
timestamp1
=
int
(
kw
[
'timestamp'
])
_logger
.
info
(
u'时间戳:
%
s'
%
timestamp1
)
datetime_timestamp
=
datetime
.
fromtimestamp
(
timestamp1
)
_logger
.
info
(
u'datetime_timestamp:
%
s'
%
datetime_timestamp
)
_logger
.
info
(
u'now:
%
s'
%
now
)
if
now
>
datetime_timestamp
:
difference
=
(
now
-
datetime_timestamp
)
.
seconds
else
:
difference
=
0
_logger
.
info
(
u'时间差:
%
s'
%
difference
)
if
difference
>
TIMEOUT_TIME
:
res
[
'code'
]
=
1005
res
[
'msg'
]
=
'请求过期'
_logger
.
warning
(
u'时间戳已过期'
)
# _logger.info('response:%s' % rets['response'])
if
res
[
'code'
]
==
0
:
return
func
(
*
args
,
**
kw
)
def
sort_json_obj
(
obj
):
"""
递归对 JSON 对象进行排序 (对应 Java 的 sortJsonNode)
Python 字典本身无序,所以这里返回排序后的字典或列表,
后续 json.dumps 时需要指定 sort_keys=True
"""
if
isinstance
(
obj
,
dict
):
# 对字典的 Key 进行排序,并递归处理 Value
return
{
k
:
sort_json_obj
(
v
)
for
k
,
v
in
sorted
(
obj
.
items
())}
elif
isinstance
(
obj
,
list
):
# 对列表中的每个元素递归处理
return
[
sort_json_obj
(
x
)
for
x
in
obj
]
else
:
# return func(*args, **kw)
return
json
.
JSONEncoder
()
.
encode
(
res
)
return
wrapper
# jwt auth方式生成token
def
get_token
(
username
):
payload
=
{
'exp'
:
datetime
.
datetime
.
now
()
+
timedelta
(
hours
=
24
),
# 令牌过期时间
'username'
:
str
(
username
)
# 想要传递的信息,如用户名ID
}
key
=
'yunhangji'
# 基本类型直接返回
return
obj
encoded_jwt
=
jwt
.
encode
(
payload
,
key
,
algorithm
=
'HS256'
)
return
encoded_jwt
def
calculate_sign
(
json_data
,
app_secret
):
"""
计算签名核心逻辑 (对应 Java 的 signRequest + buildSignSource)
"""
# 1. 递归排序 JSON
sorted_data
=
sort_json_obj
(
json_data
)
# token解码 {'exp': 1603984192, 'username': 'BigFish'}
def
check_token
(
func
):
@functools.wraps
(
func
)
def
wrapper
(
*
args
,
**
kw
):
rets
=
{
"status"
:
"1"
,
'error_message'
:
''
}
request_headers
=
request
.
headers
logging
.
info
(
'check_token_request_method:
%
s'
%
request
.
method
)
logging
.
info
(
'check_token_request_headers:
%
s'
%
request_headers
)
if
request_headers
.
get
(
'token'
):
# 检查是否存在这个客户
logging
.
info
(
'token:
%
s '
%
request_headers
[
'token'
])
try
:
res
=
jwt
.
decode
(
request_headers
[
'token'
],
'yunhangji'
,
algorithms
=
[
'HS256'
])
logging
.
info
(
'check_token_res:
%
s'
%
res
)
if
request
.
method
!=
'GET'
and
request
.
path
!=
'/v1/servicer/info/update'
:
request_data
=
request
.
json
else
:
request_data
=
request
.
args
logging
.
info
(
'check_token_request_data:
%
s'
%
request_data
)
if
request_data
.
get
(
'servicer_id'
):
if
res
[
'username'
]
!=
str
(
request_data
[
'servicer_id'
]):
rets
[
'status'
]
=
103
rets
[
'error_message'
]
=
'token不属于当前用户'
else
:
rets
[
'status'
]
=
105
rets
[
'error_message'
]
=
'请传入参数名为servicer_id的服务商id'
except
Exception
as
ex
:
rets
[
'status'
]
=
102
rets
[
'error_message'
]
=
str
(
ex
)
# 2. 扁平化处理:提取第一层 Key-Value
# Java 代码中先把 JSON 扁平化到了 signMap,遇到容器类型(Dict/List)转为字符串
sign_map
=
{}
if
isinstance
(
sorted_data
,
dict
):
for
k
,
v
in
sorted_data
.
items
():
if
k
==
"sign"
:
# 剔除 sign 字段
continue
# 对应 Java: if (subNode.isContainerNode()) ...
if
isinstance
(
v
,
(
dict
,
list
)):
# 复杂类型转为 JSON 字符串,注意去空格,确保格式一致
# separators=(',', ':') 用于去除 json.dumps 默认添加的空格
sign_map
[
k
]
=
json
.
dumps
(
v
,
sort_keys
=
True
,
separators
=
(
','
,
':'
))
else
:
rets
[
'status'
]
=
101
rets
[
'error_message'
]
=
u'请在请求头中传入token参数.'
logging
.
warning
(
u'请传入token参数'
)
logging
.
info
(
'status:
%
s'
%
rets
[
'status'
])
# 对应 Java: else { signMap.put(..., entry.getValue().asText()); }
# 简单类型转字符串
sign_map
[
k
]
=
str
(
v
)
# 3. 拼接字符串 (对应 Java 的 buildSignSource)
# 逻辑: appSecret + key1 + value1 + key2 + value2 ... + appSecret
# Java 使用了 TreeMap 自动排序,这里我们需要对 sign_map 的 Key 再次排序
raw_str
=
app_secret
for
k
in
sorted
(
sign_map
.
keys
()):
raw_str
+=
f
"{k}{sign_map[k]}"
raw_str
+=
app_secret
# 4. MD5 加密 (对应 Java 的 md5)
md5_hash
=
hashlib
.
md5
(
raw_str
.
encode
(
'utf-8'
))
.
hexdigest
()
.
upper
()
return
md5_hash
if
rets
[
'status'
]
==
"1"
:
return
func
(
*
args
,
**
kw
)
else
:
return
rets
return
wrapper
def
check_sign
(
f
):
@functools.wraps
(
f
)
def
decorated_function
(
*
args
,
**
kwargs
):
try
:
# 获取原始请求体 JSON
data
=
request
.
get_json
(
force
=
True
,
silent
=
True
)
if
not
data
:
return
jsonify
({
"code"
:
400
,
"msg"
:
"Invalid JSON"
}),
400
# 获取请求中的签名
client_sign
=
data
.
get
(
'sign'
,
''
)
# 计算服务端签名
server_sign
=
calculate_sign
(
data
,
APP_SECRET
)
# 对比签名
if
client_sign
!=
server_sign
:
# 实际开发建议记录日志: print(f"Client: {client_sign}, Server: {server_sign}")
return
jsonify
({
"code"
:
401
,
"msg"
:
"Invalid Signature"
}),
401
return
f
(
*
args
,
**
kwargs
)
except
Exception
as
e
:
return
jsonify
({
"code"
:
500
,
"msg"
:
str
(
e
)}),
500
return
decorated_function
services/create_pdf.py
浏览文件 @
6cc3f91f
...
...
@@ -10,6 +10,7 @@ import logging
from
dependence.services.util
import
YhjCommon
,
Order_dispose
from
dependence.services
import
config
import
redis
import
math
logging
.
basicConfig
(
handlers
=
[
logging
.
FileHandler
(
'logs/create_pdf.log'
,
'a'
,
'utf-8'
)],
format
=
'
%(asctime)
s
%(levelname)
s
%(message)
s'
,
level
=
logging
.
INFO
)
...
...
@@ -22,16 +23,18 @@ odoo_conn = Order_dispose()
class
CreateCartonPdf
(
object
):
def
start_worker
(
self
,
order_no
):
def
start_worker
(
self
,
order_no
,
count
):
"""
启动守护进程循环
:param interval: 每次循环的休眠时间(秒)
"""
_logger
.
info
(
f
">>>订单{order_no} PDF 生成服务已启动 <<<"
)
_logger
.
info
(
f
">>>订单{order_no}
{count}个大箱
PDF 生成服务已启动 <<<"
)
try
:
# --- 执行业务逻辑 ---
# 调用 Odoo 端的方法 (假设 cron_create_pdf 内部会自动查找 draft 状态的记录并处理)
# 注意:这里保留了您原本的传参 ["self"],请确保 Odoo 端方法能接收此参数
page
=
math
.
ceil
(
count
/
200
)
for
i
in
range
(
page
):
result
=
odoo_conn
.
odoo_db
.
execute
(
"temu.order.carton"
,
"cron_create_pdf"
,
[
"self"
])
# 如果 Odoo 返回了处理的数量,可以打个日志
if
result
:
...
...
@@ -54,7 +57,7 @@ try:
data1
=
result
[
1
]
task_data
=
json
.
loads
(
data1
)
if
result
:
pdf_obj
.
start_worker
(
task_data
.
get
(
'order_no'
))
pdf_obj
.
start_worker
(
task_data
.
get
(
'order_no'
)
,
task_data
.
get
(
'count'
)
)
else
:
logging
.
error
(
'未找到数据类型'
)
except
Exception
as
e
:
...
...
services/temu_service.py
浏览文件 @
6cc3f91f
...
...
@@ -76,7 +76,7 @@ class TemuService(object):
# 1. 检查订单状态 & 锁行 (FOR UPDATE)
# -------------------------------------------------------
cr
.
execute
(
"""
SELECT id, version_no, logistics_order_no, delivery_mode, deliver_method
SELECT id, version_no,
state,
logistics_order_no, delivery_mode, deliver_method
FROM temu_order
WHERE temu_delivery_no =
%
s
"""
,
(
order_no
,))
...
...
@@ -85,16 +85,16 @@ class TemuService(object):
logistics_order_no
=
None
incoming_seq
=
int
(
kw
.
get
(
'sequence'
,
0
))
if
existing
:
order_id
,
current_seq
,
logistics_order_no
,
d_mode
,
d_method
=
existing
order_id
,
current_seq
,
state
,
logistics_order_no
,
d_mode
,
d_method
=
existing
# === 场景 A: 版本过低 ===
current_seq
=
int
(
current_seq
)
if
incoming_seq
<
(
current_seq
or
0
):
current_seq
=
int
(
current_seq
or
0
)
if
incoming_seq
<
(
current_seq
or
0
)
and
state
!=
'cancel'
:
return_res
[
'msg'
]
=
'下单失败:版本号低于当前系统版本'
return_res
[
'result'
]
=
{
'orderNo'
:
order_no
,
'logisticsOrderNo'
:
logistics_order_no
}
self
.
_log_api
(
cr
,
kw_data
,
order_no
,
return_res
[
'msg'
])
return
return_res
# === 场景 B: 版本一致 (直接返回) ===
elif
incoming_seq
==
(
current_seq
or
0
):
elif
incoming_seq
==
(
current_seq
or
0
)
and
state
!=
'cancel'
:
# 如果需要返回箱号,查一下
carton_res
=
[]
if
str
(
d_mode
)
!=
'1'
or
str
(
d_method
)
!=
'3'
:
...
...
@@ -226,7 +226,7 @@ class TemuService(object):
'cartonInfo'
:
final_cartons
}
# 记录日志
redis_obj
.
lpush
(
'create_pdf_data'
,
json
.
dumps
({
'order_no'
:
order_no
}))
redis_obj
.
lpush
(
'create_pdf_data'
,
json
.
dumps
({
'order_no'
:
order_no
,
'count'
:
len
(
final_cartons
)
}))
self
.
_log_api
(
cr
,
kw_data
,
order_no
,
""
)
except
Exception
as
e
:
...
...
@@ -287,7 +287,7 @@ class TemuService(object):
def
_update_order
(
self
,
cr
,
order_id
,
kw
):
sql
=
"""
UPDATE temu_order SET
channel_code=
%
s, warehouse_name=
%
s, version_no=
%
s, deliver_method=
%
s,
state=
%
s,
channel_code=
%
s, warehouse_name=
%
s, version_no=
%
s, deliver_method=
%
s,
delivery_mode=
%
s, is_electrified=
%
s, predict_charge=
%
s, predict_charge_currency=
%
s,
export_license_source=
%
s,
company_name=
%
s, company_contact_name=
%
s, company_phone=
%
s, company_email=
%
s,
...
...
@@ -299,7 +299,7 @@ class TemuService(object):
WHERE id=
%
s
"""
params
=
(
kw
.
get
(
'channelCode'
),
kw
.
get
(
'warehouse'
),
kw
.
get
(
'sequence'
),
kw
.
get
(
'deliverMethod'
),
'progress'
,
kw
.
get
(
'channelCode'
),
kw
.
get
(
'warehouse'
),
kw
.
get
(
'sequence'
),
kw
.
get
(
'deliverMethod'
),
kw
.
get
(
'deliveryMode'
),
kw
.
get
(
'electrified'
),
kw
.
get
(
'predictCharge'
),
kw
.
get
(
'predictChargeCurrency'
),
kw
.
get
(
'exportLicenseSource'
),
kw
.
get
(
'companyInfo'
,
{})
.
get
(
'companyName'
),
kw
.
get
(
'companyInfo'
,
{})
.
get
(
'fullName'
),
kw
.
get
(
'companyInfo'
,
{})
.
get
(
'phone'
),
kw
.
get
(
'companyInfo'
,
{})
.
get
(
'email'
),
kw
.
get
(
'shippingInfo'
,
{})
.
get
(
'fullName'
),
kw
.
get
(
'shippingInfo'
,
{})
.
get
(
'regionName1'
),
kw
.
get
(
'shippingInfo'
,
{})
.
get
(
'regionName2'
),
kw
.
get
(
'shippingInfo'
,
{})
.
get
(
'regionName3'
),
kw
.
get
(
'shippingInfo'
,
{})
.
get
(
'regionName4'
),
...
...
@@ -418,7 +418,7 @@ class TemuService(object):
kw
=
{}
utc_time
=
''
try
:
print
(
kws
)
#
print(kws)
kw
=
kws
[
'data'
]
order_no
=
kw
[
'orderNo'
]
logistics_order_no
=
kw
[
'logisticsOrderNo'
]
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论