update
This commit is contained in:
parent
db6a9af7c0
commit
2b0f4aacef
83
app/api/endpoints/mp.py
Normal file
83
app/api/endpoints/mp.py
Normal file
@ -0,0 +1,83 @@
|
||||
from fastapi import APIRouter, Request, Response
|
||||
from app.core.mpclient import mp_client
|
||||
from app.core.response import success_response, error_response
|
||||
from app.models.database import get_db
|
||||
from app.models.user import UserDB
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import time
|
||||
from app.core.config import settings
|
||||
import logging
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
def check_signature(signature: str, timestamp: str, nonce: str) -> bool:
|
||||
"""验证微信服务器签名"""
|
||||
token = settings.MP_TOKEN # 在配置文件中添加 MP_TOKEN
|
||||
# 按字典序排序
|
||||
temp_list = [token, timestamp, nonce]
|
||||
temp_list.sort()
|
||||
# 拼接字符串
|
||||
temp_str = "".join(temp_list)
|
||||
# SHA1加密
|
||||
sign = hashlib.sha1(temp_str.encode('utf-8')).hexdigest()
|
||||
# 与微信发送的签名对比
|
||||
return sign == signature
|
||||
|
||||
@router.get("")
|
||||
async def verify_server(
|
||||
signature: str,
|
||||
timestamp: str,
|
||||
nonce: str,
|
||||
echostr: str
|
||||
):
|
||||
"""验证服务器地址的有效性"""
|
||||
if check_signature(signature, timestamp, nonce):
|
||||
return Response(content=echostr, media_type="text/plain")
|
||||
return Response(status_code=403)
|
||||
|
||||
@router.post("")
|
||||
async def handle_server(request: Request):
|
||||
"""处理微信服务器推送的消息和事件"""
|
||||
try:
|
||||
# 读取原始XML数据
|
||||
body = await request.body()
|
||||
root = ET.fromstring(body)
|
||||
|
||||
logging.info(f"微信公众号消息:{root}")
|
||||
|
||||
# 解析基本信息
|
||||
msg_type = root.find('MsgType').text
|
||||
from_user = root.find('FromUserName').text
|
||||
to_user = root.find('ToUserName').text
|
||||
create_time = int(root.find('CreateTime').text)
|
||||
|
||||
# 获取数据库会话
|
||||
db = next(get_db())
|
||||
|
||||
# 处理不同类型的消息和事件
|
||||
if msg_type == 'event':
|
||||
event = root.find('Event').text.lower()
|
||||
|
||||
if event == 'subscribe': # 关注事件
|
||||
# 获取用户信息
|
||||
user_info = await mp_client.get_user_info(from_user)
|
||||
if user_info:
|
||||
# 查找或创建用户
|
||||
user = db.query(UserDB).filter(
|
||||
UserDB.unionid == user_info.get('unionid')
|
||||
).first()
|
||||
|
||||
if user:
|
||||
# 更新用户信息
|
||||
user.mp_openid = from_user
|
||||
db.commit()
|
||||
|
||||
|
||||
return Response(content="", media_type="text/plain")
|
||||
|
||||
except Exception as e:
|
||||
logging.exception("处理微信消息异常")
|
||||
# 返回空字符串表示接收成功
|
||||
return Response(content="", media_type="text/plain")
|
||||
@ -48,6 +48,7 @@ async def wechat_phone_login(
|
||||
# 获取用户 openid
|
||||
session_info = await wechat.code2session(request.login_code)
|
||||
openid = session_info["openid"]
|
||||
unionid = session_info.get("unionid")
|
||||
|
||||
# 获取用户手机号
|
||||
phone_info = await wechat.get_phone_number(request.phone_code)
|
||||
@ -73,20 +74,22 @@ async def wechat_phone_login(
|
||||
phone=phone,
|
||||
user_code=user_code,
|
||||
referral_code=request.referral_code,
|
||||
openid=openid # 保存 openid
|
||||
openid=openid, # 保存 openid
|
||||
unionid=unionid # 保存 unionid
|
||||
)
|
||||
db.add(user)
|
||||
db.flush()
|
||||
|
||||
# 发放优惠券
|
||||
from app.api.endpoints.user import issue_register_coupons
|
||||
issue_register_coupons(db, user.userid)
|
||||
# from app.api.endpoints.user import issue_register_coupons
|
||||
# issue_register_coupons(db, user.userid)
|
||||
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
else:
|
||||
# 更新现有用户的 openid
|
||||
# 更新现有用户的 openid 和 unionid
|
||||
user.openid = openid
|
||||
user.unionid = unionid
|
||||
db.commit()
|
||||
|
||||
# 创建访问令牌
|
||||
|
||||
@ -69,6 +69,11 @@ class Settings(BaseSettings):
|
||||
WECHAT_API_V3_KEY: str = "OAhAqXqebeT4ZC9VTYFkSWU0CENEahx5" # API v3密钥
|
||||
WECHAT_PLATFORM_CERT_PATH: str = "app/cert/platform_key.pem" # 平台证书路径
|
||||
|
||||
MP_APPID: str = "wxa9db2cc7868dfefd"
|
||||
MP_SECRET: str = "3eed9a717654d6460ba9afda3b0f6be2"
|
||||
MP_TOKEN: str = "yORAT7RL9I3sux7uc4PbMEEHT1xowc6H" # 用于验证服务器配置
|
||||
MP_AES_KEY: str = "XDc2mG1tWNikTithcSy66oD3fP5XXFasSeRk6ulicye" # 用于解密消息
|
||||
|
||||
# 微信模板消息ID
|
||||
#配送订单创建成功
|
||||
WECHAT_DELIVERY_ORDER_CREATED_TEMPLATE_ID: str = "-aFOuC2dv1E6Opn9auB39bSiU4p0DbKOrUtOFgS-AaA"
|
||||
|
||||
109
app/core/mpclient.py
Normal file
109
app/core/mpclient.py
Normal file
@ -0,0 +1,109 @@
|
||||
import aiohttp
|
||||
import json
|
||||
from app.core.config import settings
|
||||
import redis
|
||||
import time
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
class MPClient:
|
||||
"""微信公众号客户端"""
|
||||
|
||||
def __init__(self):
|
||||
self.appid = settings.MP_APPID
|
||||
self.secret = settings.MP_SECRET
|
||||
|
||||
async def get_access_token(self) -> str:
|
||||
"""获取访问令牌"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={self.appid}&secret={self.secret}"
|
||||
async with session.get(url) as response:
|
||||
result = await response.json()
|
||||
|
||||
if "access_token" not in result:
|
||||
raise Exception(f"获取access_token失败: {result}")
|
||||
|
||||
access_token = result["access_token"]
|
||||
|
||||
return access_token
|
||||
|
||||
async def send_template_message(
|
||||
self,
|
||||
openid: str,
|
||||
template_id: str,
|
||||
data: Dict[str, Any],
|
||||
url: Optional[str] = None,
|
||||
miniprogram: Optional[Dict[str, str]] = None
|
||||
) -> bool:
|
||||
"""
|
||||
发送模板消息
|
||||
:param openid: 用户openid
|
||||
:param template_id: 模板ID
|
||||
:param data: 模板数据
|
||||
:param url: 点击跳转的链接(可选)
|
||||
:param miniprogram: 跳转小程序信息(可选)
|
||||
:return: 发送是否成功
|
||||
"""
|
||||
try:
|
||||
access_token = await self.get_access_token()
|
||||
api_url = f"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={access_token}"
|
||||
|
||||
message = {
|
||||
"touser": openid,
|
||||
"template_id": template_id,
|
||||
"data": {
|
||||
key: {
|
||||
"value": value
|
||||
} for key, value in data.items()
|
||||
}
|
||||
}
|
||||
|
||||
# 添加跳转链接
|
||||
if url:
|
||||
message["url"] = url
|
||||
|
||||
# 添加小程序跳转信息
|
||||
if miniprogram:
|
||||
message["miniprogram"] = miniprogram
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(api_url, json=message) as response:
|
||||
result = await response.json()
|
||||
|
||||
if result.get("errcode") == 0:
|
||||
return True
|
||||
|
||||
print(f"发送模板消息失败: {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送模板消息异常: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
# 根据unionid获取用户信息
|
||||
async def get_user_info(self, unionid: str) -> Optional[Dict]:
|
||||
"""
|
||||
获取用户基本信息
|
||||
:param unionid: 用户unionid
|
||||
:return: 用户信息字典
|
||||
"""
|
||||
try:
|
||||
access_token = await self.get_access_token()
|
||||
url = f"https://api.weixin.qq.com/cgi-bin/user/info?access_token={access_token}&unionid={unionid}&lang=zh_CN"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
result = await response.json()
|
||||
|
||||
if "errcode" in result:
|
||||
print(f"获取用户信息失败: {result}")
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取用户信息异常: {str(e)}")
|
||||
return None
|
||||
|
||||
# 创建全局实例
|
||||
mp_client = MPClient()
|
||||
@ -1,6 +1,6 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app.api.endpoints import wechat,user, address, community, station, order, coupon, community_building, upload, merchant, merchant_product, merchant_order, point, config, merchant_category, log, account,merchant_pay_order, message, bank_card, withdraw, subscribe
|
||||
from app.api.endpoints import wechat,user, address, community, station, order, coupon, community_building, upload, merchant, merchant_product, merchant_order, point, config, merchant_category, log, account,merchant_pay_order, message, bank_card, withdraw, mp
|
||||
from app.models.database import Base, engine
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
@ -34,7 +34,7 @@ app.add_middleware(RequestLoggerMiddleware)
|
||||
|
||||
# 添加用户路由
|
||||
app.include_router(wechat.router,prefix="/api/wechat",tags=["微信"])
|
||||
app.include_router(subscribe.router, prefix="/api/subscribe", tags=["小程序订阅消息"])
|
||||
app.include_router(mp.router, prefix="/api/mp", tags=["微信公众号"])
|
||||
app.include_router(user.router, prefix="/api/user", tags=["用户"])
|
||||
app.include_router(bank_card.router, prefix="/api/bank-cards", tags=["用户银行卡"])
|
||||
app.include_router(withdraw.router, prefix="/api/withdraw", tags=["提现"])
|
||||
|
||||
@ -26,6 +26,8 @@ class UserDB(Base):
|
||||
|
||||
userid = Column(Integer, primary_key=True,autoincrement=True, index=True)
|
||||
openid = Column(String(64), unique=True, nullable=True)
|
||||
unionid = Column(String(64), unique=True, nullable=True)
|
||||
mp_openid = Column(String(64), unique=True, nullable=True)
|
||||
nickname = Column(String(50))
|
||||
phone = Column(String(11), unique=True, index=True)
|
||||
user_code = Column(String(6), unique=True, nullable=False)
|
||||
@ -53,6 +55,8 @@ class UserLogin(BaseModel):
|
||||
class UserInfo(BaseModel):
|
||||
userid: int
|
||||
openid: Optional[str] = None
|
||||
unionid: Optional[str] = None
|
||||
mp_openid: Optional[str] = None
|
||||
nickname: str
|
||||
phone: str
|
||||
user_code: str
|
||||
|
||||
Loading…
Reference in New Issue
Block a user