区分环境

This commit is contained in:
aaron 2025-02-27 15:24:56 +08:00
parent 78e744143f
commit 381a95654a
8 changed files with 188 additions and 97 deletions

0
.env Normal file
View File

0
.env.dev Normal file
View File

0
.env.prd Normal file
View File

1
.gitignore vendored
View File

@ -38,6 +38,5 @@ ENV/
*.log *.log
# Local development # Local development
.env
.env.local .env.local
*.db *.db

View File

@ -66,17 +66,17 @@ async def wechat_phone_login(
if not phone: if not phone:
return error_response(code=400, message="手机号为空") return error_response(code=400, message="手机号为空")
# # 获取企业微信的 userid # # # 获取企业微信的 userid
wecom_client = WecomClient() # wecom_client = WecomClient()
wecom_info = await wecom_client.miniprogram_to_userid(openid=openid) # wecom_info = await wecom_client.miniprogram_to_userid(openid=openid)
print(f"获取到的企业微信用户信息: {wecom_info}") # print(f"获取到的企业微信用户信息: {wecom_info}")
wecom_userid = None # wecom_userid = None
wecom_pending_id = None # wecom_pending_id = None
if wecom_info: # if wecom_info:
wecom_userid = wecom_info.get("userid") # wecom_userid = wecom_info.get("userid")
wecom_pending_id = wecom_info.get("pending_id") # wecom_pending_id = wecom_info.get("pending_id")
# 查找或创建用户 # 查找或创建用户
@ -93,8 +93,8 @@ async def wechat_phone_login(
password=get_password_hash("123456"), password=get_password_hash("123456"),
openid=openid, # 保存 openid openid=openid, # 保存 openid
unionid=unionid, # 保存 unionid unionid=unionid, # 保存 unionid
wecom_userid=wecom_userid, # wecom_userid=wecom_userid,
wecom_pending_id=wecom_pending_id # wecom_pending_id=wecom_pending_id
) )
db.add(user) db.add(user)
db.flush() db.flush()
@ -106,8 +106,8 @@ async def wechat_phone_login(
# 更新现有用户的 openid 和 unionid # 更新现有用户的 openid 和 unionid
user.openid = openid user.openid = openid
user.unionid = unionid user.unionid = unionid
user.wecom_userid = wecom_userid # user.wecom_userid = wecom_userid
user.wecom_pending_id = wecom_pending_id # user.wecom_pending_id = wecom_pending_id
db.commit() db.commit()
# 创建访问令牌 # 创建访问令牌

View File

@ -1,5 +1,7 @@
from typing import Optional from typing import Optional
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
import os
from functools import lru_cache
class Settings(BaseSettings): class Settings(BaseSettings):
DEBUG: bool = True # 开发模式标志 DEBUG: bool = True # 开发模式标志
@ -108,4 +110,49 @@ class Settings(BaseSettings):
env_file = ".env" env_file = ".env"
extra = "allow" # 允许额外的环境变量 extra = "allow" # 允许额外的环境变量
settings = Settings() class DevSettings(Settings):
DEBUG: bool = True # 开发模式标志
API_V1_STR: str = "/api/v1"
PROJECT_NAME: str = "FastAPI 项目 (开发环境)"
MYSQL_HOST: str = "gz-cynosdbmysql-grp-2j1cnopr.sql.tencentcdb.com"
MYSQL_PORT: int = 27469
MYSQL_USER: str = "root"
MYSQL_PASSWORD: str = "Aa#223388"
MYSQL_DB: str = "beefast"
class Config:
env_file = ".env.dev"
class ProdSettings(Settings):
DEBUG: bool = False # 生产模式标志
API_V1_STR: str = "/api/v1"
PROJECT_NAME: str = "FastAPI 项目 (生产环境)"
MYSQL_HOST: str = "cd-cynosdbmysql-grp-7kdd8qe4.sql.tencentcdb.com"
MYSQL_PORT: int = 26558
MYSQL_USER: str = "root"
MYSQL_PASSWORD: str = "Ss@123!@#"
MYSQL_DB: str = "beefast"
class Config:
env_file = ".env.prod"
@lru_cache()
def get_settings() -> BaseSettings:
"""
获取配置实例
使用环境变量 APP_ENV 来决定使用哪个配置
"""
env = os.getenv("APP_ENV", "dev").lower()
config_map = {
"dev": DevSettings,
"prod": ProdSettings
}
config_class = config_map.get(env, DevSettings)
return config_class()
# 导出配置实例
settings = get_settings()

View File

@ -1,93 +1,130 @@
import aiohttp import requests
from app.core.config import settings import json
import time
import logging import logging
from typing import Optional, Dict from app.core.config import settings
from typing import Dict, Any, Optional, List
class WecomClient: class WecomClient:
"""企业微信客户端"""
def __init__(self): def __init__(self):
self.corpid = settings.WECHAT_CORP_ID self.corp_id = settings.WECHAT_CORP_ID
self.secret = settings.WECHAT_CORP_SECRET # 注意:这里使用普通的 secret不是 provider_secret self.corp_secret = settings.WECHAT_CORP_SECRET
self.access_token = None self.access_token = None
self.token_expires_at = 0
async def get_access_token(self) -> str: async def get_access_token(self) -> str:
"""获取企业微信普通access_token""" """获取访问令牌"""
try: # 检查令牌是否过期
url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken" if self.access_token and time.time() < self.token_expires_at:
params = { return self.access_token
"corpid": self.corpid,
"corpsecret": self.secret
}
async with aiohttp.ClientSession() as session: # 获取新令牌
async with session.get(url, params=params) as response: url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corp_id}&corpsecret={self.corp_secret}"
result = await response.json()
try:
response = requests.get(url)
result = response.json()
if result.get("errcode") == 0: if result.get("errcode") == 0:
self.access_token = result["access_token"] self.access_token = result.get("access_token")
print(f"企业微信access_token: {self.access_token}") self.token_expires_at = time.time() + result.get("expires_in") - 200 # 提前200秒过期
return self.access_token return self.access_token
else: else:
logging.error(f"获取access_token返回: {result}") logging.error(f"获取企业微信访问令牌失败: {result}")
raise Exception(f"获取access_token失败: {result}") return None
except Exception as e: except Exception as e:
logging.error(f"获取access_token失败: {str(e)}") logging.exception(f"获取企业微信访问令牌异常: {str(e)}")
raise return None
async def miniprogram_to_userid(self, openid: str) -> Optional[Dict]: async def miniprogram_to_userid(self, openid: str) -> Optional[Dict[str, Any]]:
""" """
转换小程序身份到企业微信 小程序openid转换为企业微信userid
可以使用code或openid
Args:
openid: 小程序用户的openid
Returns:
Dict: 包含userid或pending_id的字典
""" """
try: token = await self.get_access_token()
# 构建请求参数 if not token:
url = "https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session" return None
params = {
"access_token": await self.get_access_token(), url = f"https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session?access_token={token}"
"appid": settings.WECHAT_APPID,
data = {
"code": openid,
"grant_type": "authorization_code"
} }
# 根据传入参数类型选择 try:
params["openid"] = openid response = requests.post(url, json=data)
result = response.json()
async with aiohttp.ClientSession() as session:
async with session.get(url, params=params) as response:
result = await response.json()
logging.info(f"转换结果: {result}")
if result.get("errcode") == 0: if result.get("errcode") == 0:
return { return {
"userid": result.get("userid"), # 如果是企业成员返回userid "userid": result.get("userid"),
"pending_id": result.get("pending_id"), # 如果是外部联系人返回pending_id "pending_id": result.get("pending_id")
"openid": result.get("openid"),
"session_key": result.get("session_key")
} }
else: else:
logging.error(f"身份转换失败: {result}") logging.error(f"小程序openid转换失败: {result}")
return None
except Exception as e:
logging.error(f"身份转换失败: {str(e)}")
return None return None
async def get_user_info(self, userid: str) -> Optional[Dict]: except Exception as e:
"""获取企业成员信息""" logging.exception(f"小程序openid转换异常: {str(e)}")
return None
async def get_user_in_group_chat(self, userid: str) -> List[str]:
"""
获取用户所在的群聊列表
Args:
userid: 企业微信用户ID
Returns:
List[str]: 群聊ID列表
"""
token = await self.get_access_token()
if not token:
return []
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list_group_chat?access_token={token}"
# 先获取所有群聊
try: try:
url = "https://qyapi.weixin.qq.com/cgi-bin/user/get" response = requests.post(url, json={})
params = { result = response.json()
"access_token": await self.get_access_token(),
"userid": userid
}
async with aiohttp.ClientSession() as session: if result.get("errcode") != 0:
async with session.get(url, params=params) as response: logging.error(f"获取群聊列表失败: {result}")
result = await response.json() return []
if result.get("errcode") == 0: chat_ids = []
return result for chat in result.get("group_chat_list", []):
else: chat_id = chat.get("chat_id")
logging.error(f"获取用户信息失败: {result}")
return None # 获取群聊详情
detail_url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/get?access_token={token}"
detail_response = requests.post(detail_url, json={"chat_id": chat_id})
detail_result = detail_response.json()
if detail_result.get("errcode") == 0:
group_chat = detail_result.get("group_chat", {})
members = group_chat.get("member_list", [])
# 检查用户是否在群内
for member in members:
if member.get("userid") == userid:
chat_ids.append(chat_id)
break
return chat_ids
except Exception as e: except Exception as e:
logging.error(f"获取用户信息失败: {str(e)}") logging.exception(f"获取用户群聊异常: {str(e)}")
return None return []
wecom_client = WecomClient()

View File

@ -35,6 +35,14 @@ app.add_middleware(
# 添加请求日志中间件 # 添加请求日志中间件
app.add_middleware(RequestLoggerMiddleware) app.add_middleware(RequestLoggerMiddleware)
@app.get("/api/info")
async def get_info():
"""获取当前环境信息"""
return {
"project": settings.PROJECT_NAME,
"debug": settings.DEBUG
}
# 添加用户路由 # 添加用户路由
app.include_router(dashboard.router, prefix="/api/dashboard", tags=["仪表盘"]) app.include_router(dashboard.router, prefix="/api/dashboard", tags=["仪表盘"])
app.include_router(wechat.router,prefix="/api/wechat",tags=["微信"]) app.include_router(wechat.router,prefix="/api/wechat",tags=["微信"])