update
This commit is contained in:
parent
5448a04be4
commit
0f00caf23a
@ -15,8 +15,9 @@ from app.models.user import UserDB
|
|||||||
from app.api.deps import get_current_user
|
from app.api.deps import get_current_user
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from app.core.response import error_response, success_response
|
from app.core.response import error_response, success_response, ResponseModel
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from app.models.wecom_external_chat import WecomExternalChatDB, WecomExternalChatInfo, WecomExternalChatMemberDB, WecomExternalChatMemberInfo
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@ -109,43 +110,52 @@ async def wechat_corp_callback(
|
|||||||
# 解析解密后的XML
|
# 解析解密后的XML
|
||||||
msg_root = ET.fromstring(decrypted_msg)
|
msg_root = ET.fromstring(decrypted_msg)
|
||||||
|
|
||||||
print(f"企业微信回调消息:{decrypted_msg}")
|
logging.info(f"企业微信回调消息:{decrypted_msg}")
|
||||||
|
|
||||||
# 解析基本信息
|
# 解析基本信息
|
||||||
msg_type = msg_root.find('MsgType').text
|
msg_type = msg_root.find('MsgType').text
|
||||||
|
|
||||||
print(f"msg_type: {msg_type}")
|
logging.info(f"msg_type: {msg_type}")
|
||||||
|
|
||||||
# 处理事件消息
|
# 处理事件消息
|
||||||
if msg_type == 'event':
|
if msg_type == 'event':
|
||||||
event = msg_root.find('Event').text
|
event = msg_root.find('Event').text
|
||||||
|
|
||||||
print(f"event: {event}")
|
logging.info(f"event: {event}")
|
||||||
|
|
||||||
# 处理进群事件
|
# 处理外部群聊变更事件
|
||||||
if event == 'change_external_chat':
|
if event == 'change_external_chat':
|
||||||
chat_id = msg_root.find('ChatId').text
|
chat_id = msg_root.find('ChatId').text
|
||||||
change_type = msg_root.find('ChangeType').text
|
change_type = msg_root.find('ChangeType').text
|
||||||
update_detail = msg_root.find('UpdateDetail').text
|
update_detail = msg_root.find('UpdateDetail').text
|
||||||
|
|
||||||
print(f"chat_id: {chat_id}")
|
|
||||||
print(f"change_type: {change_type}")
|
|
||||||
print(f"update_detail: {update_detail}")
|
|
||||||
|
|
||||||
# 处理进群事件
|
join_user_id = None
|
||||||
|
if update_detail == 'add_member' and msg_root.find('JoinScene') is not None:
|
||||||
if update_detail == 'add_member':
|
logging.info(f"有新成员加入群聊")
|
||||||
print(f"发送欢迎消息")
|
# 获取加入的成员ID
|
||||||
# 发送欢迎消息
|
join_user_id_elem = msg_root.find('JoinUserID')
|
||||||
# await wecom_client.send_welcome_message(chat_id)
|
if join_user_id_elem is not None:
|
||||||
|
join_user_id = join_user_id_elem.text
|
||||||
|
|
||||||
|
logging.info(f"chat_id: {chat_id}, change_type: {change_type}, update_detail: {update_detail}, join_user_id: {join_user_id}")
|
||||||
|
|
||||||
|
# 处理群聊变更事件
|
||||||
|
await wecom_client.handle_chat_change_event(
|
||||||
|
chat_id=chat_id,
|
||||||
|
change_type=change_type,
|
||||||
|
update_detail=update_detail,
|
||||||
|
join_user_id=join_user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if update_detail == 'add_member' and join_user_id:
|
||||||
|
logging.info(f"发送欢迎消息到群聊:{chat_id}")
|
||||||
|
await wecom_client.send_welcome_message(chat_id)
|
||||||
|
|
||||||
return Response(content="success", media_type="text/plain")
|
return Response(content="success", media_type="text/plain")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("处理企业微信回调消息异常")
|
logging.exception("处理企业微信回调消息异常")
|
||||||
return Response(content="success", media_type="text/plain")
|
return Response(content="success", media_type="text/plain")
|
||||||
|
|
||||||
|
|
||||||
class UnionidToExternalUseridRequest(BaseModel):
|
class UnionidToExternalUseridRequest(BaseModel):
|
||||||
unionid: str
|
unionid: str
|
||||||
@ -158,4 +168,225 @@ async def unionid_to_external_userid(
|
|||||||
"""根据unionid获取external_userid"""
|
"""根据unionid获取external_userid"""
|
||||||
result = await wecom_client.unionid_to_external_userid(request.unionid, request.openid)
|
result = await wecom_client.unionid_to_external_userid(request.unionid, request.openid)
|
||||||
print(f"根据unionid获取external_userid结果: {result}")
|
print(f"根据unionid获取external_userid结果: {result}")
|
||||||
return success_response(message="获取external_userid成功", data=result)
|
return success_response(message="获取external_userid成功", data=result)
|
||||||
|
|
||||||
|
@router.get("/external-chats", response_model=ResponseModel)
|
||||||
|
async def get_external_chats(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""获取企业微信外部群聊列表"""
|
||||||
|
try:
|
||||||
|
# 检查是否为管理员
|
||||||
|
if current_user.userid != settings.PLATFORM_USER_ID:
|
||||||
|
return error_response(code=403, message="权限不足")
|
||||||
|
|
||||||
|
# 获取群聊列表
|
||||||
|
chats = db.query(WecomExternalChatDB).filter(
|
||||||
|
WecomExternalChatDB.is_active == True
|
||||||
|
).order_by(WecomExternalChatDB.update_time.desc()).all()
|
||||||
|
|
||||||
|
# 转换为Pydantic模型
|
||||||
|
chat_list = [WecomExternalChatInfo.model_validate(chat) for chat in chats]
|
||||||
|
|
||||||
|
return success_response(message="获取群聊列表成功", data=chat_list)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("获取群聊列表异常")
|
||||||
|
return error_response(code=500, message=f"获取群聊列表失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.get("/external-chats/{chat_id}/members", response_model=ResponseModel)
|
||||||
|
async def get_external_chat_members(
|
||||||
|
chat_id: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""获取企业微信外部群聊成员列表"""
|
||||||
|
try:
|
||||||
|
# 检查是否为管理员
|
||||||
|
if current_user.userid != settings.PLATFORM_USER_ID:
|
||||||
|
return error_response(code=403, message="权限不足")
|
||||||
|
|
||||||
|
# 检查群聊是否存在
|
||||||
|
chat = db.query(WecomExternalChatDB).filter(
|
||||||
|
WecomExternalChatDB.chat_id == chat_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not chat:
|
||||||
|
return error_response(code=404, message="群聊不存在")
|
||||||
|
|
||||||
|
# 获取成员列表
|
||||||
|
members = db.query(WecomExternalChatMemberDB).filter(
|
||||||
|
WecomExternalChatMemberDB.chat_id == chat_id
|
||||||
|
).order_by(WecomExternalChatMemberDB.join_time.desc()).all()
|
||||||
|
|
||||||
|
# 转换为Pydantic模型
|
||||||
|
member_list = [WecomExternalChatMemberInfo.model_validate(member) for member in members]
|
||||||
|
|
||||||
|
return success_response(message="获取群聊成员列表成功", data=member_list)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("获取群聊成员列表异常")
|
||||||
|
return error_response(code=500, message=f"获取群聊成员列表失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.post("/sync-chat/{chat_id}", response_model=ResponseModel)
|
||||||
|
async def sync_external_chat(
|
||||||
|
chat_id: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""同步企业微信外部群聊信息"""
|
||||||
|
try:
|
||||||
|
# 检查是否为管理员
|
||||||
|
if current_user.userid != settings.PLATFORM_USER_ID:
|
||||||
|
return error_response(code=403, message="权限不足")
|
||||||
|
|
||||||
|
# 获取群聊信息
|
||||||
|
result = await wecom_client.handle_chat_change_event(
|
||||||
|
chat_id=chat_id,
|
||||||
|
change_type="create",
|
||||||
|
update_detail=""
|
||||||
|
)
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
return error_response(code=500, message="同步群聊信息失败")
|
||||||
|
|
||||||
|
return success_response(message="同步群聊信息成功")
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("同步群聊信息异常")
|
||||||
|
return error_response(code=500, message=f"同步群聊信息失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.get("/chat-dashboard")
|
||||||
|
async def chat_dashboard(
|
||||||
|
current_user: UserDB = Depends(get_current_user),
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""显示企业微信外部群聊信息的HTML页面"""
|
||||||
|
# 检查是否为管理员
|
||||||
|
if current_user.userid != settings.PLATFORM_USER_ID:
|
||||||
|
return Response(content="权限不足", media_type="text/html")
|
||||||
|
|
||||||
|
# 获取群聊列表
|
||||||
|
chats = db.query(WecomExternalChatDB).filter(
|
||||||
|
WecomExternalChatDB.is_active == True
|
||||||
|
).order_by(WecomExternalChatDB.update_time.desc()).all()
|
||||||
|
|
||||||
|
# 生成HTML
|
||||||
|
html = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>企业微信外部群聊信息</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
|
||||||
|
h1, h2 { color: #333; }
|
||||||
|
table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
|
||||||
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||||
|
th { background-color: #f2f2f2; }
|
||||||
|
tr:nth-child(even) { background-color: #f9f9f9; }
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; }
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 12px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.42857143;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #337ab7;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.btn:hover { background-color: #286090; }
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
function loadMembers(chatId) {
|
||||||
|
fetch(`/api/wecom/external-chats/${chatId}/members`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
const members = data.data;
|
||||||
|
let html = '<table>';
|
||||||
|
html += '<tr><th>ID</th><th>用户ID</th><th>类型</th><th>姓名</th><th>加入时间</th><th>是否已发送欢迎</th></tr>';
|
||||||
|
|
||||||
|
members.forEach(member => {
|
||||||
|
html += `<tr>
|
||||||
|
<td>${member.id}</td>
|
||||||
|
<td>${member.user_id}</td>
|
||||||
|
<td>${member.type}</td>
|
||||||
|
<td>${member.name || '未知'}</td>
|
||||||
|
<td>${new Date(member.join_time).toLocaleString()}</td>
|
||||||
|
<td>${member.welcome_sent ? '是' : '否'}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += '</table>';
|
||||||
|
document.getElementById('members-' + chatId).innerHTML = html;
|
||||||
|
} else {
|
||||||
|
alert('获取成员列表失败: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('获取成员列表失败');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncChat(chatId) {
|
||||||
|
fetch(`/api/wecom/sync-chat/${chatId}`, {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
alert('同步成功');
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('同步失败: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('同步失败');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>企业微信外部群聊信息</h1>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not chats:
|
||||||
|
html += "<p>暂无群聊信息</p>"
|
||||||
|
else:
|
||||||
|
for chat in chats:
|
||||||
|
html += f"""
|
||||||
|
<div style="margin-bottom: 30px; border: 1px solid #ddd; padding: 15px; border-radius: 5px;">
|
||||||
|
<h2>{chat.name or '未命名群聊'} ({chat.chat_id})</h2>
|
||||||
|
<p>
|
||||||
|
<strong>创建时间:</strong> {chat.create_time.strftime('%Y-%m-%d %H:%M:%S')}<br>
|
||||||
|
<strong>更新时间:</strong> {chat.update_time.strftime('%Y-%m-%d %H:%M:%S') if chat.update_time else '无'}<br>
|
||||||
|
<strong>成员数量:</strong> {chat.member_count}<br>
|
||||||
|
<strong>群主:</strong> {chat.owner or '未知'}<br>
|
||||||
|
<strong>公告:</strong> {chat.notice or '无'}<br>
|
||||||
|
</p>
|
||||||
|
<button class="btn" onclick="loadMembers('{chat.chat_id}')">查看成员</button>
|
||||||
|
<button class="btn" onclick="syncChat('{chat.chat_id}')">同步群信息</button>
|
||||||
|
<div id="members-{chat.chat_id}" style="margin-top: 15px;"></div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
html += """
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return Response(content=html, media_type="text/html")
|
||||||
@ -112,8 +112,16 @@ class WecomClient:
|
|||||||
logging.error(f"unionid_to_external_userid异常: {str(e)}")
|
logging.error(f"unionid_to_external_userid异常: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def send_welcome_message(self, chat_id: str) -> bool:
|
async def send_welcome_message(self, chat_id: str, user_id: str = None) -> bool:
|
||||||
"""发送欢迎消息"""
|
"""发送欢迎消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 群聊ID
|
||||||
|
user_id: 用户ID,如果指定则发送私信,否则发送群消息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否发送成功
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
# 1. 获取 access_token
|
# 1. 获取 access_token
|
||||||
access_token = await self.get_access_token()
|
access_token = await self.get_access_token()
|
||||||
@ -121,24 +129,37 @@ class WecomClient:
|
|||||||
logging.error("获取access_token失败")
|
logging.error("获取access_token失败")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
welcome_text = f"""🥳 欢迎您进群,在群内可以享受📦【代取快递】跑腿服务。
|
||||||
|
|
||||||
|
‼ 微信下单,快递到家 ‼
|
||||||
|
|
||||||
|
🎁 新人礼包
|
||||||
|
𝟏 赠送𝟏𝟓张【𝟑元跑腿券】
|
||||||
|
𝟐 赠送𝟔枚鲜鸡蛋【首次下单】
|
||||||
|
━ ━ ━ ━ ━🎊━ ━ ━ ━ ━
|
||||||
|
↓点击↓小程序领券下单 &"""
|
||||||
|
|
||||||
# 2. 发送欢迎消息
|
# 2. 发送欢迎消息
|
||||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token={access_token}"
|
if user_id:
|
||||||
data = {
|
# 发送私信
|
||||||
"chatid": chat_id,
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token={access_token}"
|
||||||
"msgtype": "text",
|
data = {
|
||||||
"text": {
|
"welcome_code": user_id, # 这里使用user_id作为临时的欢迎码
|
||||||
"content": f"""🥳 欢迎您进群,在群内可以享受📦【代取快递】跑腿服务。
|
"text": {
|
||||||
|
"content": welcome_text
|
||||||
‼ 微信下单,快递到家 ‼
|
}
|
||||||
|
}
|
||||||
🎁 新人礼包
|
else:
|
||||||
𝟏 赠送𝟏𝟓张【𝟑元跑腿券】
|
# 发送群消息
|
||||||
𝟐 赠送𝟔枚鲜鸡蛋【首次下单】
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token={access_token}"
|
||||||
━ ━ ━ ━ ━🎊━ ━ ━ ━ ━
|
data = {
|
||||||
↓点击↓小程序领券下单 &"""
|
"chatid": chat_id,
|
||||||
},
|
"msgtype": "text",
|
||||||
"safe": 0
|
"text": {
|
||||||
}
|
"content": welcome_text
|
||||||
|
},
|
||||||
|
"safe": 0
|
||||||
|
}
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.post(url, json=data) as response:
|
async with session.post(url, json=data) as response:
|
||||||
@ -152,5 +173,243 @@ class WecomClient:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"发送欢迎消息异常: {str(e)}")
|
logging.error(f"发送欢迎消息异常: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def get_external_chat_info(self, chat_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""获取外部群聊信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 群聊ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 群聊信息
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
access_token = await self.get_access_token()
|
||||||
|
if not access_token:
|
||||||
|
logging.error("获取access_token失败")
|
||||||
|
return None
|
||||||
|
|
||||||
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/get?access_token={access_token}"
|
||||||
|
data = {
|
||||||
|
"chat_id": chat_id
|
||||||
|
}
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(url, json=data) as response:
|
||||||
|
result = await response.json()
|
||||||
|
if result.get("errcode") == 0:
|
||||||
|
return result.get("group_chat")
|
||||||
|
else:
|
||||||
|
logging.error(f"获取外部群聊信息失败: {result}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取外部群聊信息异常: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_external_contact_info(self, external_userid: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""获取外部联系人信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
external_userid: 外部联系人ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 外部联系人信息
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
access_token = await self.get_access_token()
|
||||||
|
if not access_token:
|
||||||
|
logging.error("获取access_token失败")
|
||||||
|
return None
|
||||||
|
|
||||||
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token={access_token}"
|
||||||
|
params = {
|
||||||
|
"external_userid": external_userid
|
||||||
|
}
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url, params=params) as response:
|
||||||
|
result = await response.json()
|
||||||
|
if result.get("errcode") == 0:
|
||||||
|
return result.get("external_contact")
|
||||||
|
else:
|
||||||
|
logging.error(f"获取外部联系人信息失败: {result}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取外部联系人信息异常: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def handle_chat_change_event(self, chat_id: str, change_type: str, update_detail: str, join_user_id: str = None) -> bool:
|
||||||
|
"""处理群聊变更事件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 群聊ID
|
||||||
|
change_type: 变更类型 create/update/dismiss
|
||||||
|
update_detail: 变更详情 add_member/del_member/change_owner/change_name/change_notice
|
||||||
|
join_user_id: 加入的用户ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 处理是否成功
|
||||||
|
"""
|
||||||
|
from app.models.wecom_external_chat import WecomExternalChatDB, WecomExternalChatMemberDB
|
||||||
|
from app.models.wecom_external_chat import WecomExternalChatCreate, WecomExternalChatMemberCreate
|
||||||
|
from app.models.database import SessionLocal
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = SessionLocal()
|
||||||
|
|
||||||
|
# 群创建事件
|
||||||
|
if change_type == "create":
|
||||||
|
# 获取群聊信息
|
||||||
|
chat_info = await self.get_external_chat_info(chat_id)
|
||||||
|
if not chat_info:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 保存群聊信息
|
||||||
|
chat_db = db.query(WecomExternalChatDB).filter(WecomExternalChatDB.chat_id == chat_id).first()
|
||||||
|
if not chat_db:
|
||||||
|
chat_create = WecomExternalChatCreate(
|
||||||
|
chat_id=chat_id,
|
||||||
|
name=chat_info.get("name"),
|
||||||
|
owner=chat_info.get("owner"),
|
||||||
|
member_count=len(chat_info.get("member_list", [])),
|
||||||
|
notice=chat_info.get("notice")
|
||||||
|
)
|
||||||
|
chat_db = WecomExternalChatDB(**chat_create.dict())
|
||||||
|
db.add(chat_db)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# 保存群成员信息
|
||||||
|
for member in chat_info.get("member_list", []):
|
||||||
|
user_id = member.get("userid")
|
||||||
|
member_type = member.get("type")
|
||||||
|
|
||||||
|
# 检查成员是否已存在
|
||||||
|
member_db = db.query(WecomExternalChatMemberDB).filter(
|
||||||
|
WecomExternalChatMemberDB.chat_id == chat_id,
|
||||||
|
WecomExternalChatMemberDB.user_id == user_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not member_db:
|
||||||
|
# 获取外部联系人详情
|
||||||
|
user_info = None
|
||||||
|
if member_type == "EXTERNAL":
|
||||||
|
user_info = await self.get_external_contact_info(user_id)
|
||||||
|
|
||||||
|
member_create = WecomExternalChatMemberCreate(
|
||||||
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
|
type=member_type,
|
||||||
|
name=user_info.get("name") if user_info else None,
|
||||||
|
unionid=user_info.get("unionid") if user_info else None
|
||||||
|
)
|
||||||
|
member_db = WecomExternalChatMemberDB(**member_create.dict())
|
||||||
|
db.add(member_db)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 群更新事件 - 添加成员
|
||||||
|
elif change_type == "update" and update_detail == "add_member":
|
||||||
|
if not join_user_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查群聊是否存在
|
||||||
|
chat_db = db.query(WecomExternalChatDB).filter(WecomExternalChatDB.chat_id == chat_id).first()
|
||||||
|
if not chat_db:
|
||||||
|
# 获取群聊信息并创建
|
||||||
|
chat_info = await self.get_external_chat_info(chat_id)
|
||||||
|
if chat_info:
|
||||||
|
chat_create = WecomExternalChatCreate(
|
||||||
|
chat_id=chat_id,
|
||||||
|
name=chat_info.get("name"),
|
||||||
|
owner=chat_info.get("owner"),
|
||||||
|
member_count=len(chat_info.get("member_list", [])),
|
||||||
|
notice=chat_info.get("notice")
|
||||||
|
)
|
||||||
|
chat_db = WecomExternalChatDB(**chat_create.dict())
|
||||||
|
db.add(chat_db)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# 检查成员是否已存在
|
||||||
|
member_db = db.query(WecomExternalChatMemberDB).filter(
|
||||||
|
WecomExternalChatMemberDB.chat_id == chat_id,
|
||||||
|
WecomExternalChatMemberDB.user_id == join_user_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not member_db:
|
||||||
|
# 判断是内部成员还是外部联系人
|
||||||
|
member_type = "EXTERNAL" # 默认为外部联系人
|
||||||
|
|
||||||
|
# 获取外部联系人详情
|
||||||
|
user_info = await self.get_external_contact_info(join_user_id)
|
||||||
|
|
||||||
|
member_create = WecomExternalChatMemberCreate(
|
||||||
|
chat_id=chat_id,
|
||||||
|
user_id=join_user_id,
|
||||||
|
type=member_type,
|
||||||
|
name=user_info.get("name") if user_info else None,
|
||||||
|
unionid=user_info.get("unionid") if user_info else None
|
||||||
|
)
|
||||||
|
member_db = WecomExternalChatMemberDB(**member_create.dict())
|
||||||
|
db.add(member_db)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# 发送欢迎消息
|
||||||
|
if member_type == "EXTERNAL":
|
||||||
|
await self.send_welcome_message(chat_id)
|
||||||
|
|
||||||
|
# 更新发送欢迎消息状态
|
||||||
|
member_db.welcome_sent = True
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# 更新群成员数量
|
||||||
|
if chat_db:
|
||||||
|
chat_db.member_count = chat_db.member_count + 1
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 群更新事件 - 移除成员
|
||||||
|
elif change_type == "update" and update_detail == "del_member":
|
||||||
|
if not join_user_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 删除成员记录
|
||||||
|
member_db = db.query(WecomExternalChatMemberDB).filter(
|
||||||
|
WecomExternalChatMemberDB.chat_id == chat_id,
|
||||||
|
WecomExternalChatMemberDB.user_id == join_user_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if member_db:
|
||||||
|
db.delete(member_db)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# 更新群成员数量
|
||||||
|
chat_db = db.query(WecomExternalChatDB).filter(WecomExternalChatDB.chat_id == chat_id).first()
|
||||||
|
if chat_db:
|
||||||
|
chat_db.member_count = max(0, chat_db.member_count - 1)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 群解散事件
|
||||||
|
elif change_type == "dismiss":
|
||||||
|
# 标记群聊为非活跃
|
||||||
|
chat_db = db.query(WecomExternalChatDB).filter(WecomExternalChatDB.chat_id == chat_id).first()
|
||||||
|
if chat_db:
|
||||||
|
chat_db.is_active = False
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"处理群聊变更事件异常: {str(e)}")
|
||||||
|
if 'db' in locals():
|
||||||
|
db.close()
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if 'db' in locals():
|
||||||
|
db.close()
|
||||||
|
|
||||||
wecom_client = WecomClient()
|
wecom_client = WecomClient()
|
||||||
98
app/models/wecom_external_chat.py
Normal file
98
app/models/wecom_external_chat.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
from sqlalchemy import Column, String, Integer, DateTime, JSON, Boolean, ForeignKey, Text
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
from .database import Base
|
||||||
|
|
||||||
|
class WecomExternalChatDB(Base):
|
||||||
|
"""企业微信外部群聊表"""
|
||||||
|
__tablename__ = "wecom_external_chats"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
chat_id = Column(String(64), nullable=False, unique=True, index=True) # 群聊ID
|
||||||
|
name = Column(String(100), nullable=True) # 群名称
|
||||||
|
owner = Column(String(64), nullable=True) # 群主ID
|
||||||
|
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
member_count = Column(Integer, nullable=False, default=0) # 成员数量
|
||||||
|
notice = Column(String(500), nullable=True) # 群公告
|
||||||
|
is_active = Column(Boolean, nullable=False, default=True) # 是否活跃
|
||||||
|
|
||||||
|
class WecomExternalChatMemberDB(Base):
|
||||||
|
"""企业微信外部群聊成员表"""
|
||||||
|
__tablename__ = "wecom_external_chat_members"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
chat_id = Column(String(64), nullable=False, index=True) # 群聊ID
|
||||||
|
user_id = Column(String(64), nullable=False, index=True) # 用户ID
|
||||||
|
type = Column(String(32), nullable=False) # 成员类型: INTERNAL(内部成员)、EXTERNAL(外部联系人)
|
||||||
|
join_time = Column(DateTime(timezone=True), server_default=func.now()) # 加入时间
|
||||||
|
unionid = Column(String(64), nullable=True) # 微信unionid
|
||||||
|
name = Column(String(100), nullable=True) # 成员名称
|
||||||
|
mobile = Column(String(20), nullable=True) # 手机号
|
||||||
|
welcome_sent = Column(Boolean, nullable=False, default=False) # 是否已发送欢迎消息
|
||||||
|
|
||||||
|
# 设置联合唯一索引
|
||||||
|
__table_args__ = (
|
||||||
|
{"mysql_charset": "utf8mb4"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pydantic 模型
|
||||||
|
class WecomExternalChatCreate(BaseModel):
|
||||||
|
chat_id: str
|
||||||
|
name: Optional[str] = None
|
||||||
|
owner: Optional[str] = None
|
||||||
|
member_count: int = 0
|
||||||
|
notice: Optional[str] = None
|
||||||
|
is_active: bool = True
|
||||||
|
|
||||||
|
class WecomExternalChatUpdate(BaseModel):
|
||||||
|
name: Optional[str] = None
|
||||||
|
owner: Optional[str] = None
|
||||||
|
member_count: Optional[int] = None
|
||||||
|
notice: Optional[str] = None
|
||||||
|
is_active: Optional[bool] = None
|
||||||
|
|
||||||
|
class WecomExternalChatInfo(BaseModel):
|
||||||
|
id: int
|
||||||
|
chat_id: str
|
||||||
|
name: Optional[str] = None
|
||||||
|
owner: Optional[str] = None
|
||||||
|
create_time: datetime
|
||||||
|
update_time: Optional[datetime] = None
|
||||||
|
member_count: int = 0
|
||||||
|
notice: Optional[str] = None
|
||||||
|
is_active: bool = True
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
class WecomExternalChatMemberCreate(BaseModel):
|
||||||
|
chat_id: str
|
||||||
|
user_id: str
|
||||||
|
type: str
|
||||||
|
unionid: Optional[str] = None
|
||||||
|
name: Optional[str] = None
|
||||||
|
mobile: Optional[str] = None
|
||||||
|
welcome_sent: bool = False
|
||||||
|
|
||||||
|
class WecomExternalChatMemberUpdate(BaseModel):
|
||||||
|
unionid: Optional[str] = None
|
||||||
|
name: Optional[str] = None
|
||||||
|
mobile: Optional[str] = None
|
||||||
|
welcome_sent: Optional[bool] = None
|
||||||
|
|
||||||
|
class WecomExternalChatMemberInfo(BaseModel):
|
||||||
|
id: int
|
||||||
|
chat_id: str
|
||||||
|
user_id: str
|
||||||
|
type: str
|
||||||
|
join_time: datetime
|
||||||
|
unionid: Optional[str] = None
|
||||||
|
name: Optional[str] = None
|
||||||
|
mobile: Optional[str] = None
|
||||||
|
welcome_sent: bool
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
BIN
jobs.sqlite
BIN
jobs.sqlite
Binary file not shown.
Loading…
Reference in New Issue
Block a user