提交 e2472b49 authored 作者: 刘擎阳's avatar 刘擎阳

1.xqh_temu服务

上级 470d7909
.idea/
*.pyc
FROM coding-public-docker.pkg.coding.net/public/docker/python:3.8
COPY . /app
WORKDIR /app
RUN /usr/local/bin/python3 -m pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN ls
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN pip install flask_nameko==1.4.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
ENTRYPOINT ["python3", "manage.py"]
# -*- coding: utf-8 -*-
from logging.handlers import RotatingFileHandler
from flask import Flask
import logging
from flask_nameko import FlaskPooledClusterRpcProxy
from config import NAMEKO_CONFIG
rpc = FlaskPooledClusterRpcProxy()
def create_app():
app = Flask(__name__)
app.config.update(dict(
NAMEKO_AMQP_URI=NAMEKO_CONFIG['AMQP_URI'],
NAMEKO_MAX_CONNECTIONS=20,
))
rpc.init_app(app)
# 蓝图注册
from api import api as api_blueprint
app.register_blueprint(api_blueprint)
# 设置日志的记录等级
logging.basicConfig(level=logging.DEBUG) # 调试debug级
# 创建日志记录器,指明日志保存的路径、每个日志文件的最大大小、保存的日志文件个数上限
file_log_handler = RotatingFileHandler("logs/log.log", maxBytes=1024 * 1024 * 100, backupCount=10, encoding='utf-8')
# 创建日志记录的格式 日志等级 输入日志信息的文件名 行数 日志信息
formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')
# 为刚创建的日志记录器设置日志记录格式
file_log_handler.setFormatter(formatter)
# 为全局的日志工具对象(flask app使用的)添加日志记录器
logging.getLogger().addHandler(file_log_handler)
return app
import requests
import json
def test_create_order():
# 1. 接口地址
# 注意:根据你的描述,路由是 /temu/create/order,基础地址是 http://127.0.0.1:5000
url = "http://127.0.0.1:5000/temu/create/order"
# 2. 请求头
headers = {
"Content-Type": "application/json"
}
# 3. 请求数据 (你提供的数据)
payload = {
"sign": "2DE2BA70E44A1F64855FE5B4D404EC13",
"orderNo": "2343155111",
"channelCode": "channelCode",
"warehouse": "warehouse",
"sequence": 9,
"deliverMethod": 1,
"deliveryMode": 1,
"electrified": 0,
"exportLicenseSource": 1,
"companyInfo": {
"companyName": "gsmccs",
"phone": "111",
"email": "111@163.com"
},
"shippingInfo": {
"fullName": "发仓联系人",
"regionName1": "CN",
"regionName2": "上海市",
"regionName3": "上海市",
"regionName4": "长宁区",
"detailedAddress": "上海长宁大厦",
"email": "111@163.com",
"phone": "111"
},
"destinationInfo": {
"fullName": "海外仓联系人",
"regionName1": "US",
"regionName2": "California",
"regionName3": "Acton",
"regionName4": "",
"detailedAddress": "test1",
"postcode": "111",
"email": "111@163.com",
"phone": "111",
"warehouseNo": "12689079385"
},
"cartonInfo": [
{
"cartonNo": "2125",
"weight": 2,
"weightUnit": "KG",
"length": 100,
"width": 100,
"height": 100,
"lengthUnit": "CM",
"electrified": 0,
"skuInfo": [
{
"id": "111",
"quantity": 1
}
]
}
],
"containerInfo": [
{
"containerType": "abc",
"containerCnt": 1,
"containerWeight": 123.4,
"containerWeightUnit": "kg",
"containerVolume": 432.1,
"containerVolumeUnit": "CBM"
}
],
"interface_type": "bg.intligst.semihead.create",
"scene_id": 7017820,
"source_app_key": "f219476226359894f8f3a2b70568e1cf",
"target_app_key": "demo-e1a4a8688f0678ef3de8e2b4877"
}
try:
# 4. 发送 POST 请求
print(f"正在发送 POST 请求到: {url}")
response = requests.post(url, headers=headers, json=payload)
# 5. 打印响应状态码和内容
print(f"响应状态码: {response.status_code}")
# 尝试解析 JSON 响应
try:
resp_json = response.json()
print("响应内容 (JSON):")
print(json.dumps(resp_json, indent=4, ensure_ascii=False))
# 简单的结果判断
if resp_json.get("success"):
print(">>> 测试通过:下单成功")
else:
print(f">>> 测试失败:{resp_json.get('errorMsg')}")
except json.JSONDecodeError:
print("响应内容 (非 JSON):")
print(response.text)
except requests.exceptions.ConnectionError:
print(">>> 连接失败:请确保 Flask 服务 (127.0.0.1:5000) 已启动")
except Exception as e:
print(f">>> 发生错误: {str(e)}")
if __name__ == "__main__":
test_create_order()
# -*- coding: utf-8 -*-
# __author__ = 'holly'
# 蓝图注册
from flask import Blueprint
api = Blueprint('api', __name__)
from . import temu_api
# !/usr/bin/python
# -*- coding:utf-8 -*-
# Author: Zhichang Fu
# Created Time: 2019-03-27 07:58:11
'''
BEGIN
function:
Comment API
return:
code:0 success
END
'''
import time
import json
import requests
import logging
from datetime import datetime, timedelta
from flask import request, jsonify
from nameko.standalone.rpc import ClusterRpcProxy
from config import NAMEKO_CONFIG
from . import api
from __init__ import rpc
from util import check_customer
CONFIG = NAMEKO_CONFIG
_logger = logging.getLogger(__name__)
# from redis_conn import redis_connection
# r_conn = redis_connection()
# from services.tasks import tiktok_package_declare
# schema:
# id: data
# properties:
# verification_code:
# type: string
# description: 验证码
# example: 1234
@api.route('/temu/create/order', methods=['post'])
# @check_customer
def temu_create_order():
res = {
"success": True,
"errorCode": 0,
"errorMsg": "success",
"requestID": "202312251715021060522200417739B9",
"serverTimeMs": int(time.time() * 1000),
"result": None,
}
request_time = datetime.utcnow()
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(timestamp)
request_data = request.get_json()
_logger.info('temu_create_order:%s' % request_data)
result = {}
if request_data:
# print(type(request_data))
data = request_data
result['request_id'] = res['requestID']
result['data'] = data
provider_order_no = data['orderNo'] if data.get('orderNo') else ''
sequence = data['sequence'] if data.get('sequence') else ''
if not provider_order_no:
res['errorCode'] = 1007
res['errorMsg'] = 'TEMU发货单号必填'
if not sequence:
res['errorCode'] = 1007
res['errorMsg'] = '版本号必填'
if res['errorCode'] == 0:
# logging.info('推入redis')
# push_data = {'type': 'package', 'result': result}
# r_conn.lpush('tiktok_parcel_data', json.dumps(push_data))
# tiktok_package_declare.delay(**result)
return_res = rpc.temu_service.temu_create_order_service(**result)
if return_res:
if return_res['msg']:
res['success'] = False
res['errorCode'] = 1008
res['errorMsg'] = return_res['msg']
else:
res['result'] = return_res['result']
else:
res['errorCode'] = 5000
res['errorMsg'] = '参数未传'
return jsonify(res)
@api.route('/temu/update/order', methods=['post'])
# @check_customer
def temu_update_order():
# 接收提单信息、大包与小包的关联信息
res = {
"code": 0,
"msg": "success",
"requestID": "202312251715021060522200417739B9",
"ts": "2023-12-25 17:15:03"
}
request_time = datetime.utcnow()
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(timestamp)
request_data = request.form['param_json']
_logger.info('mawb_declare kw:%s' % request_data)
if request_data:
request_data = json.loads(request_data)
request_data['request_id'] = res['requestID']
master_waybill_no = request_data['master_waybill_no'] if request_data.get('master_waybill_no') else ''
mwb_info = request_data['mwb_info'] if request_data.get('mwb_info') else ''
if not master_waybill_no:
res['code'] = 1007
res['msg'] = '提单号必填'
if not mwb_info:
res['code'] = 1007
res['msg'] = '提单信息必传'
if res['code'] == 0:
logging.info('推入redis')
push_data = {'type': 'mawb', 'result': request_data}
r_conn.lpush('tiktok_parcel_data', json.dumps(push_data))
# return_res = rpc.customs_tiktok.mawb_declare(**request_data)
# if return_res:
# if return_res['err_msg']:
# res['code'] = 5000
# res['msg'] = return_res['err_msg']
else:
res['code'] = 5000
res['msg'] = '参数未传'
return jsonify(res)
@api.route('/temu/query/order', methods=['post'])
# @check_customer
def temu_query_order():
# 接收提单的附件信息
res = {
"code": 0,
"msg": "success",
"requestID": "202312251715021060522200417739B9",
"ts": "2023-12-25 17:15:03"
}
request_time = datetime.utcnow()
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(timestamp)
request_data = request.form['param_json']
_logger.info('mawb_copy_upload kw:%s' % request_data)
if request_data:
request_data = json.loads(request_data)
request_data['request_id'] = res['requestID']
master_waybill_no = request_data['master_waybill_no'] if request_data.get('master_waybill_no') else ''
if not master_waybill_no:
res['code'] = 1007
res['msg'] = '提单号必填'
if res['code'] == 0:
return_res = rpc.customs_tiktok.mawb_copy_upload(**request_data)
if return_res:
if return_res['msg']:
res['code'] = return_res['code']
res['msg'] = return_res['msg']
else:
res['code'] = 5000
res['msg'] = '参数未传'
return jsonify(res)
@api.route('/temu/cancel/order', methods=['post'])
# @check_customer
def temu_cancel_order():
# 接收取消订单
res = {
"success": True,
"errorCode": 0,
"errorMsg": "success",
"requestID": "202312251715021060522200417739B9",
"serverTimeMs": int(time.time() * 1000)
}
request_time = datetime.utcnow()
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(timestamp)
request_data = request.form['param_json']
_logger.info('temu_cancel_order kw:%s' % request_data)
result = {}
if request_data:
data = json.loads(request_data)
result['request_id'] = res['requestID']
result['data'] = data
provider_order_no = data['orderNo'] if data.get('orderNo') else ''
logistics_order_no = data['logisticsOrderNo'] if data.get('logisticsOrderNo') else ''
if not provider_order_no:
res['errorCode'] = 1007
res['errorMsg'] = 'TEMU发货单号必填'
if not logistics_order_no:
res['errorCode'] = 1007
res['errorMsg'] = '服务商发货单号必填'
if res['code'] == 0:
return_res = rpc.temu_service.temu_cancel_order_service(**result)
if return_res:
if return_res['msg']:
res['errorCode'] = 1008
res['errorMsg'] = return_res['msg']
else:
res['code'] = 5000
res['msg'] = '参数未传'
return jsonify(res)
@api.route('/logistics/provider/customs/mawb_cancel', methods=['post'])
# @check_customer
def mawb_cancel():
# 接收提单取消
res = {
"code": 0,
"msg": "success",
"requestID": "202312251715021060522200417739B9",
"ts": "2023-12-25 17:15:03"
}
request_time = datetime.utcnow()
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(timestamp)
request_data = request.form['param_json']
_logger.info('mawb_cancel kw:%s' % request_data)
if request_data:
request_data = json.loads(request_data)
request_data['request_id'] = res['requestID']
master_waybill_no = request_data['master_waybill_no'] if request_data.get('master_waybill_no') else ''
if not master_waybill_no:
res['code'] = 1007
res['msg'] = '提单号必传'
if res['code'] == 0:
return_res = rpc.customs_tiktok.mawb_cancel(**request_data)
if return_res:
if return_res['msg']:
res['code'] = return_res['code']
res['msg'] = return_res['msg']
else:
res['code'] = 5000
res['msg'] = '参数未传'
return jsonify(res)
# !/usr/bin/python
# -*- coding:utf-8 -*-
# Author: holly
# Created Time: 2019-03-26 23:15:39
"""
BEGIN
function:
config
END
"""
import os
# rabbit_ip = os.environ.get('RABBIT_IP', 'rabbit')
# # # 正式
# # NAMEKO_CONFIG = {'AMQP_URI': "amqp://hh_guest:guest@%s" % rabbit_ip}
# #
# # db_ip = os.environ.get('DB_IP', 'db')
# # db_port = "5432"
# # db_name = "hh_airorder"
# # db_user = "hh"
# # db_password = "hh123../"
# rabbit_ip = os.environ.get('RABBIT_IP', 'rabbit')
# # 测试环境
# NAMEKO_CONFIG = {'AMQP_URI': "amqp://guest:guest@%s" % rabbit_ip}
#
# db_ip = 'db'
# db_port = "5432"
# db_name = "airorder_tiktok"
# db_user = "khg"
# db_password = "khg"
# 本地
NAMEKO_CONFIG = {'AMQP_URI': "amqp://guest:guest@localhost"}
# 数据库配置
db_ip = "127.0.0.1"
db_port = "5431"
db_name = "test_xqhwaybill"
db_user = "odoo14"
db_password = "qq166349"
# swagger配置
title = 'Temu API'
description = ''
#
# odoo_url = "https://otm.f.yizuo.ltd"
redis_options = dict(
host='127.0.0.1',
port=6379,
# password='topodoo1314',
decode_responses=True,
db=0
)
import config
import psycopg2
from sqlalchemy import create_engine
class DbService(object):
def __init__(self):
print('new connection')
self.conn_engine = create_engine('postgresql+psycopg2://{username}:{password}@{host}:{port}/{database}'.format(
username=config.db_user, password=config.db_password, host=config.db_ip, port=config.db_port,
database=config.db_name))
self.pg_conn = psycopg2.connect(database=config.db_name, user=config.db_user,
password=config.db_password, host=config.db_ip, port=config.db_port)
# -*- coding:utf-8 -*-
import argparse
from flasgger import Swagger
from flask_cors import CORS
from __init__ import create_app
import config
parser = argparse.ArgumentParser()
parser.add_argument("--port", help="app running port", type=int, default=5000)
parse_args = parser.parse_args()
app = create_app()
swagger_config = Swagger.DEFAULT_CONFIG
swagger_config['title'] = config.title
swagger_config['description'] = config.description
Swagger(app)
CORS(app, supports_credentials=True)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=int(parse_args.port), debug=True)
# -*- coding: utf-8 -*-
import config
import redis
import logging
import psycopg2
_logger = logging.getLogger(__name__)
def redis_connection():
# 连接redis
redis_config = config.redis_options
if redis_config:
try:
redis_options = dict(
host=redis_config.get('host'),
port=redis_config.get('port'),
# password=redis_config.get('password'),
decode_responses=True,
db=redis_config.get('db'),
)
# print(redis_options)
pool = redis.ConnectionPool(**redis_options)
r = redis.Redis(connection_pool=pool)
return r
except Exception as e:
_logger.error(u'连接redis失败,原因:%s' % str(e))
return ''
else:
_logger.error(u'conf文件中未配置redis连接信息')
return
pytest-runner
flask
flasgger
nameko
flask_cors
psycopg2
sqlalchemy
pandas
PyJWT
import time
import hashlib
import base64
import json
from datetime import timedelta, datetime
import jwt
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
_logger = logging.getLogger(__name__)
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)
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'
encoded_jwt = jwt.encode(payload, key, algorithm='HS256')
return encoded_jwt
# 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)
else:
rets['status'] = 101
rets['error_message'] = u'请在请求头中传入token参数.'
logging.warning(u'请传入token参数')
logging.info('status:%s' % rets['status'])
if rets['status'] == "1":
return func(*args, **kw)
else:
return rets
return wrapper
{
"sign": "2DE2BA70E44A1F64855FE5B4D404EC13",
"orderNo": "2343155111",
"channelCode": "channelCode",
"warehouse": "warehouse",
"sequence": 9,
"deliverMethod": 1,
"deliveryMode": 1,
"electrified": 0,
"exportLicenseSource": 1,
"companyInfo": {
"companyName": "gsmccs",
"phone": "111",
"email": "111@163.com"
},
"shippingInfo": {
"fullName": "发仓联系人",
"regionName1": "CN",
"regionName2": "上海市",
"regionName3": "上海市",
"regionName4": "长宁区",
"detailedAddress": "上海长宁大厦",
"email": "111@163.com",
"phone": "111"
},
"destinationInfo": {
"fullName": "海外仓联系人",
"regionName1": "US",
"regionName2": "California",
"regionName3": "Acton",
"regionName4": "",
"detailedAddress": "test1",
"postcode": "111",
"email": "111@163.com",
"phone": "111",
"warehouseNo": "12689079385"
},
"cartonInfo": [
{
"cartonNo": "2125",
"weight": 2,
"weightUnit": "KG",
"length": 100,
"width": 100,
"height": 100,
"lengthUnit": "CM",
"electrified": 0,
"skuInfo": [
{
"id": "111",
"quantity": 1
}
]
}
],
"containerInfo": [
{
"containerType": "abc",
"containerCnt": 1,
"containerWeight": 123.4,
"containerWeightUnit": "kg",
"containerVolume": 432.1,
"containerVolumeUnit": "CBM"
}
],
"interface_type": "bg.intligst.semihead.create",
"scene_id": 7017820,
"source_app_key": "f219476226359894f8f3a2b70568e1cf",
"target_app_key": "demo-e1a4a8688f0678ef3de8e2b4877"
}
\ No newline at end of file
from .redis_service import RedisService
# from .db_service import DbService
from .db_service import db_handle
# !/usr/bin/python
# -*- coding:utf-8 -*-
# Author: holly
# Created Time: 2021-08-02
"""
BEGIN
function:
config
END
"""
import os
# 生产环境
# redis配置
# REDIS_NAME = "nameko-redis"
# REDIS_HOST = os.environ.get("REDIS_HOST", "some-redis")
# REDIS_PORT = int(os.environ.get("REDIS_PORT", 32768))
# REDIS_DB = int(os.environ.get("REDIS_DB", 1))
# postgresql数据库配置
db_ip = '127.0.0.1'
db_port = "5431"
db_name = "test_xqhwaybill"
db_user = "odoo14"
db_password = "qq166349"
# odoorpc配置
rpc_db_ip = '127.0.0.1'
rpc_db_port = "8069"
rpc_db_name = "hh_ccs_425"
rpc_db_user = "admin"
rpc_db_password = "admin"
# 测试环境
# redis配置
# REDIS_NAME = "nameko-redis"
# REDIS_HOST = os.environ.get("REDIS_HOST", "redis")
# REDIS_PORT = int(os.environ.get("REDIS_PORT", 6379))
# REDIS_DB = int(os.environ.get("REDIS_DB", 9))
# postgresql数据库配置
# db_ip = os.environ.get('DB_IP', 'db')
# db_port = "5432"
# db_name = "airorder_tiktok"
# db_user = "khg"
# db_password = "khg"
# 本地
# redis配置
REDIS_NAME = "nameko-redis"
REDIS_HOST = os.environ.get("REDIS_HOST", "127.0.0.1")
REDIS_PORT = int(os.environ.get("REDIS_PORT", 6379))
REDIS_DB = int(os.environ.get("REDIS_DB", 0))
#
# # postgresql数据库配置
# db_ip = "127.0.0.1"
# db_port = "5432"
# db_name = "yhj"
# db_user = "odoo"
# db_password = "odoo"
redis_options = dict(
host='127.0.0.1',
port=6379,
# password='topodoo1314',
decode_responses=True,
db=0
)
# !/usr/bin/python
# -*- coding:utf-8 -*-
# Author: Zhichang Fu
# Created Time: 2019-03-26 23:14:11
'''
BEGIN
function:
db Service
return:
code:0 success
END
'''
# import json
# from . import config
# import psycopg2
# from sqlalchemy import create_engine
#
#
# class DbService(object):
# def __init__(self):
# print('new connection')
# self.conn_engine = create_engine('postgresql+psycopg2://{username}:{password}@{host}:{port}/{database}'.format(
# username=config.db_user, password=config.db_password, host=config.db_ip, port=config.db_port,
# database=config.db_name), pool_size=40)
#
# self.pg_conn = psycopg2.connect(database=config.db_name, user=config.db_user,
# password=config.db_password, host=config.db_ip, port=config.db_port)
#
#
from sqlalchemy import create_engine
from . import config # 假设你有这个配置文
from contextlib import contextmanager
class DbService(object):
def __init__(self):
# 创建连接池 (SQLAlchemy 默认包含 QueuePool)
# pool_size=40: 保持40个连接
# max_overflow=10: 负载高时允许额外再创10个
self.conn_engine = create_engine(
'postgresql+psycopg2://{username}:{password}@{host}:{port}/{database}'.format(
username=config.db_user, password=config.db_password,
host=config.db_ip, port=config.db_port, database=config.db_name
),
pool_size=15,
max_overflow=5,
pool_recycle=3600 # 1小时回收连接,防止 MySQL/PG 断连
)
@contextmanager
def get_cursor(self):
"""
上下文管理器:自动获取连接、创建游标、提交事务、关闭连接
"""
# 从连接池获取一个原生 psycopg2 连接
conn = self.conn_engine.raw_connection()
cursor = conn.cursor()
try:
yield cursor
conn.commit() # 成功则提交
except Exception as e:
conn.rollback() # 出错回滚
raise e
finally:
cursor.close()
conn.close() # 归还连接给连接池 (不是真的关闭 TCP)
# 初始化全局 DB 实例
db_handle = DbService()
# !/usr/bin/python
# -*- coding:utf-8 -*-
# Author: Zhichang Fu
# Created Time: 2019-03-26 23:14:11
'''
BEGIN
function:
Redis Service
return:
code:0 success
END
'''
import redis
import json
from . import config
class RedisClient(object):
"""
redis client
"""
redis_client = {}
@staticmethod
def reload_redis(host, port, select_db):
"""
function: reload redis object
"""
return redis.StrictRedis(
host=host,
port=port,
db=select_db,
password="",
decode_responses=True)
@classmethod
def get_redis(cls, redis_name, host, port, select_db):
"""
function: get redis client
"""
if redis_name not in cls.redis_client:
cls.redis_client[redis_name] = cls.reload_redis(
host, port, select_db)
return cls.redis_client.get(redis_name)
class RedisService(object):
def __init__(self):
self.redis_instance = RedisClient.get_redis(
config.REDIS_NAME, config.REDIS_HOST, config.REDIS_PORT,
config.REDIS_DB)
self.users_key = "users"
self.users_data_key = "users_data"
def check_registered_and_get_info(self, u_id):
"""
Check if the user is registered and return user information \
if registered.
"""
user_data = self.redis_instance.hget(self.users_data_key, u_id)
if not user_data:
return False, None
return True, json.loads(user_data)
def check_email_is_registered(self, email):
u_id = self.redis_instance.hget(self.users_key, email)
return u_id
def register(self, u_id, email, data):
self.redis_instance.hset(self.users_key, email, u_id)
result = self.redis_instance.hset(self.users_data_key, u_id,
json.dumps(data))
return result
\ No newline at end of file
# !/usr/bin/python
# -*- coding:utf-8 -*-
import datetime
from random import Random
import pytz
from . import config
import odoorpc
week_day_dict = {
0: 'Mon',
1: 'Tue',
2: 'Wed',
3: 'Thu',
4: 'Fri',
5: 'Sat',
6: 'Sun',
}
class YhjCommon(object):
"""
common
"""
@classmethod
def get_location_time(cls):
"""
获取当前时区时间(带时区)
:return:
"""
now_time = datetime.datetime.utcnow()
return now_time
@classmethod
def get_add_time(cls, parse_time):
"""
把时间增加8小时
:return:
"""
dt = datetime.datetime.strptime(parse_time, "%Y-%m-%d %H:%M:%S")
d = dt + datetime.timedelta(hours=8)
nTime = d.strftime("%Y-%m-%d %H:%M:%S")
return nTime
@classmethod
def get_item_time(cls, parse_time, days, way):
"""
时间增加还是减少指定天数
:return:
"""
dt = datetime.datetime.strptime(parse_time.strftime("%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S")
if way == 'add':
d = dt + datetime.timedelta(days=days)
else:
d = dt - datetime.timedelta(days=days)
expire_date = d.strftime("%Y-%m-%d %H:%M:%S")
return expire_date
@classmethod
def get_time_zone_week(cls, parse_time, hour='', time_zone=''):
"""
根据时区获取时间,转化为轨迹所需的格式
:return:
"""
hours = hour
if '+' in hours:
hours = hours.replace('+', '')
if '-' in hours:
hours = -int(hours.replace('-', ''))
dt = datetime.datetime.strptime(parse_time.strftime("%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S")
if hours:
d = dt + datetime.timedelta(hours=int(hours))
else:
d = dt
day = d.weekday()
week = week_day_dict[day]
nTime = d.strftime("%m-%d %H:%M")
Time = '%s,' % week + nTime + time_zone
return Time
@classmethod
def get_time_zone_current(cls, parse_time, hour='', is_second=True):
"""
根据时区获取时间,转化为轨迹所需的格式
:return:
"""
hours = hour
if '+' in hours:
hours = hours.replace('+', '')
if '-' in hours:
hours = -int(hours.replace('-', ''))
dt = datetime.datetime.strptime(parse_time.strftime("%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S")
if hours:
d = dt + datetime.timedelta(hours=int(hours))
else:
d = dt
if is_second:
nTime = d.strftime("%Y-%m-%d %H:%M:%S")
else:
nTime = d.strftime("%Y-%m-%d %H:%M")
Time = nTime + '(%s)' % hour
return Time
@classmethod
def get_time_zone_current_str(cls, parse_time, hour=''):
"""
根据0时区时间和时区获取当地时间
:return:
"""
hours = hour
if '+' in hours:
hours = hours.replace('+', '')
if '-' in hours:
hours = -int(hours.replace('-', ''))
dt = datetime.datetime.strptime(parse_time, "%Y-%m-%d %H:%M:%S")
if hours:
d = dt + datetime.timedelta(hours=int(hours))
else:
d = dt
nTime = d.strftime("%Y-%m-%d %H:%M:%S")
Time = nTime + '(%s)' % hour
return Time
@classmethod
def get_time(cls, parse_time, way, hour=0):
"""
时间增加或者减少小时
:return:
"""
hours = hour
dt = datetime.datetime.strptime(parse_time.strftime("%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S")
if way == 'add':
d = dt + datetime.timedelta(hours=hours)
else:
d = dt - datetime.timedelta(hours=hours)
expire_date = d.strftime("%Y-%m-%d %H:%M:%S")
return expire_date
@classmethod
def get_utc_time(cls, parse_time, time_zone):
"""
根据时区 时间获取零时区的时间
:return:
"""
if not time_zone:
time_zone = ''
if '-' in time_zone:
time_zone = time_zone.replace('-', '')
if '+' in time_zone:
time_zone = -int(time_zone.replace('+', ''))
dt = datetime.datetime.strptime(parse_time, "%Y-%m-%d %H:%M:%S")
if time_zone:
d = dt + datetime.timedelta(hours=int(time_zone))
nTime = d.strftime("%Y-%m-%d %H:%M:%S")
else:
nTime = dt.strftime("%Y-%m-%d %H:%M:%S")
return nTime
@classmethod
def get_time_zone(cls, parse_time, time_zone):
"""
根据零时区时间转化成当前时区的时间
:return:
"""
if not time_zone:
time_zone = ''
if '+' in time_zone:
time_zone = time_zone.replace('+', '')
if '-' in time_zone:
time_zone = -int(time_zone.replace('-', ''))
dt = datetime.datetime.strptime(parse_time.strftime("%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S")
if time_zone:
d = dt + datetime.timedelta(hours=int(time_zone))
nTime = d.strftime("%Y-%m-%d %H:%M:%S")
else:
nTime = dt.strftime("%Y-%m-%d %H:%M:%S")
return nTime
@classmethod
def random_str(cls, randomlength=8):
str = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(randomlength):
str += chars[random.randint(0, length)]
return str
@classmethod
def state_seq(cls, state):
state_list = ['已下单', '客户发货', '仓库收货', '货物安检', '货物入库', '发出', '到达', '尾程入仓', '卡运揽收', '卡运签收', '提货', '完成', '取消']
if state in state_list:
return state_list.index(state)
else:
return 14
@classmethod
def get_time_str(self, parse_time):
"""
把时间减去8小时
:return:
"""
dt = datetime.datetime.strptime(parse_time, "%Y-%m-%d %H:%M:%S")
nTime = dt.strftime("%Y-%m-%d %H:%M:%S")
return nTime
# class Order_dispose(object):
#
# def __init__(self):
# # rpc连接
# self.odoo_db = odoorpc.ODOO(config.rpc_db_ip, port=config.rpc_db_port)
# self.odoo_db.login(config.rpc_db_name, config.rpc_db_user, config.rpc_db_password)
\ No newline at end of file
# foobar.yaml
# 本地/测试环境
#AMQP_URI: pyamqp://guest:guest@${RABBIT_IP:rabbit}
# 生产环境
AMQP_URI: pyamqp://hh_guest:guest@${RABBIT_IP:rabbit}
#WEB_SERVER_ADDRESS: '0.0.0.0:8000'
#rpc_exchange: 'nameko-rpc'
#max_workers: 10
#parent_calls_tracked: 10
LOGGING:
version: 1
handlers:
console:
class: logging.StreamHandler
# root:
# level: DEBUG
# handlers: [console]
\ No newline at end of file
2026-02-05 17:06:22,165 INFO: starting services: temu_service [in D:\Python\Python39\lib\site-packages\nameko\runners.py:64]
2026-02-05 17:06:22,173 INFO: Connected to amqp://guest:**@127.0.0.1:5672// [in D:\Python\Python39\lib\site-packages\kombu\mixins.py:228]
2026-02-05 17:17:12,625 ERROR: temu_create_order_service Error: 2343155111 [in D:\Documents\xqh_temu_api\services\.\temu_service.py:245]
Traceback (most recent call last):
File "D:\Documents\xqh_temu_api\services\.\temu_service.py", line 75, in temu_create_order_service
cr.execute("""
psycopg2.errors.UndefinedTable: 错误: 关系 "temu_order" 不存在
LINE 3: FROM temu_order
^
2026-02-05 17:17:40,506 INFO: stopping services: temu_service [in D:\Python\Python39\lib\site-packages\nameko\runners.py:75]
2026-02-05 17:17:49,028 INFO: starting services: temu_service [in D:\Python\Python39\lib\site-packages\nameko\runners.py:64]
2026-02-05 17:17:49,037 INFO: Connected to amqp://guest:**@127.0.0.1:5672// [in D:\Python\Python39\lib\site-packages\kombu\mixins.py:228]
2026-02-05 17:44:15,863 INFO: stopping services: temu_service [in D:\Python\Python39\lib\site-packages\nameko\runners.py:75]
2026-02-05 17:44:17,851 INFO: killing services: temu_service [in D:\Python\Python39\lib\site-packages\nameko\runners.py:86]
2026-02-05 17:44:17,852 INFO: killing <ServiceContainer [temu_service] at 0x20049e82a60> [in D:\Python\Python39\lib\site-packages\nameko\containers.py:284]
This source diff could not be displayed because it is too large. You can view the blob instead.
redis
nameko
pandas==1.1.1
pytest
sqlalchemy==1.4.35
psycopg2-binary
apscheduler
requests
psycopg2
wechatpy
pycryptodome
odoorpc
差异被折叠。
FROM coding-public-docker.pkg.coding.net/public/docker/python:3.8
RUN mkdir /etc/supervisor
ADD . /mnt/extra-addons
RUN pip3 install -r /mnt/extra-addons/requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
RUN mkdir -p /var/log/supervisor/
RUN pip install supervisor -i https://mirrors.aliyun.com/pypi/simple/
ADD ./supervisord_conf/supervisord.conf /etc/supervisor/
EXPOSE 9001
CMD ["supervisord","-n","-c","/etc/supervisor/supervisord.conf"]
[program:tiktok_queue_1]
process_name=%(program_name)s_%(process_num)02d ; 进程名称
directory = /mnt/extra-addons ; 程序的启动目录
command = /usr/local/bin/python3 /mnt/extra-addons/tiktok_queue.py ; 启动命令
autostart = true ; 在 supervisord 启动的时候也自动启动
startsecs = 5 ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true ; 程序异常退出后自动重启
startretries = 3 ; 启动失败自动重试次数,默认是 3
user = root ; 用哪个用户启动
numprocs=1 ; 进程数
redirect_stderr = true ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 20MB ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 20 ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /var/log/supervisor/tiktok_queue.log
\ No newline at end of file
[program:tiktok_service_1]
process_name=%(program_name)s_%(process_num)02d ; 进程名称
directory = /mnt/extra-addons ; 程序的启动目录
command = nameko run tiktok_service --config foobar.yaml ; 启动命令
autostart = true ; 在 supervisord 启动的时候也自动启动
startsecs = 5 ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true ; 程序异常退出后自动重启
startretries = 3 ; 启动失败自动重试次数,默认是 3
user = root ; 用哪个用户启动
numprocs=2 ; 进程数
redirect_stderr = true ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 20MB ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 20 ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /var/log/supervisor/tiktok_service.log
; supervisor config file
[unix_http_server]
file=/var/run/supervisor.sock ; (the path to the socket file)
chmod=0700 ; sockef file mode (default 0700)
[supervisord]
nodaemon=true
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP)
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[inet_http_server]
port=9001
username=admin
password=admin
[include]
files = /mnt/extra-addons/supervisord_conf/conf.d/*.conf
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论