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
|
# 获取用户 openid
|
||||||
session_info = await wechat.code2session(request.login_code)
|
session_info = await wechat.code2session(request.login_code)
|
||||||
openid = session_info["openid"]
|
openid = session_info["openid"]
|
||||||
|
unionid = session_info.get("unionid")
|
||||||
|
|
||||||
# 获取用户手机号
|
# 获取用户手机号
|
||||||
phone_info = await wechat.get_phone_number(request.phone_code)
|
phone_info = await wechat.get_phone_number(request.phone_code)
|
||||||
@ -73,20 +74,22 @@ async def wechat_phone_login(
|
|||||||
phone=phone,
|
phone=phone,
|
||||||
user_code=user_code,
|
user_code=user_code,
|
||||||
referral_code=request.referral_code,
|
referral_code=request.referral_code,
|
||||||
openid=openid # 保存 openid
|
openid=openid, # 保存 openid
|
||||||
|
unionid=unionid # 保存 unionid
|
||||||
)
|
)
|
||||||
db.add(user)
|
db.add(user)
|
||||||
db.flush()
|
db.flush()
|
||||||
|
|
||||||
# 发放优惠券
|
# 发放优惠券
|
||||||
from app.api.endpoints.user import issue_register_coupons
|
# from app.api.endpoints.user import issue_register_coupons
|
||||||
issue_register_coupons(db, user.userid)
|
# issue_register_coupons(db, user.userid)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(user)
|
db.refresh(user)
|
||||||
else:
|
else:
|
||||||
# 更新现有用户的 openid
|
# 更新现有用户的 openid 和 unionid
|
||||||
user.openid = openid
|
user.openid = openid
|
||||||
|
user.unionid = unionid
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# 创建访问令牌
|
# 创建访问令牌
|
||||||
|
|||||||
@ -69,6 +69,11 @@ class Settings(BaseSettings):
|
|||||||
WECHAT_API_V3_KEY: str = "OAhAqXqebeT4ZC9VTYFkSWU0CENEahx5" # API v3密钥
|
WECHAT_API_V3_KEY: str = "OAhAqXqebeT4ZC9VTYFkSWU0CENEahx5" # API v3密钥
|
||||||
WECHAT_PLATFORM_CERT_PATH: str = "app/cert/platform_key.pem" # 平台证书路径
|
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
|
# 微信模板消息ID
|
||||||
#配送订单创建成功
|
#配送订单创建成功
|
||||||
WECHAT_DELIVERY_ORDER_CREATED_TEMPLATE_ID: str = "-aFOuC2dv1E6Opn9auB39bSiU4p0DbKOrUtOFgS-AaA"
|
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 import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
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 app.models.database import Base, engine
|
||||||
from fastapi.exceptions import RequestValidationError
|
from fastapi.exceptions import RequestValidationError
|
||||||
from fastapi.responses import JSONResponse
|
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(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(user.router, prefix="/api/user", tags=["用户"])
|
||||||
app.include_router(bank_card.router, prefix="/api/bank-cards", tags=["用户银行卡"])
|
app.include_router(bank_card.router, prefix="/api/bank-cards", tags=["用户银行卡"])
|
||||||
app.include_router(withdraw.router, prefix="/api/withdraw", 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)
|
userid = Column(Integer, primary_key=True,autoincrement=True, index=True)
|
||||||
openid = Column(String(64), unique=True, nullable=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))
|
nickname = Column(String(50))
|
||||||
phone = Column(String(11), unique=True, index=True)
|
phone = Column(String(11), unique=True, index=True)
|
||||||
user_code = Column(String(6), unique=True, nullable=False)
|
user_code = Column(String(6), unique=True, nullable=False)
|
||||||
@ -53,6 +55,8 @@ class UserLogin(BaseModel):
|
|||||||
class UserInfo(BaseModel):
|
class UserInfo(BaseModel):
|
||||||
userid: int
|
userid: int
|
||||||
openid: Optional[str] = None
|
openid: Optional[str] = None
|
||||||
|
unionid: Optional[str] = None
|
||||||
|
mp_openid: Optional[str] = None
|
||||||
nickname: str
|
nickname: str
|
||||||
phone: str
|
phone: str
|
||||||
user_code: str
|
user_code: str
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user