1 Commits
1.0.2 ... 1.0.3

Author SHA1 Message Date
zsc
bcdf903945 已完成全部功能 2026-05-16 15:54:25 +08:00
5 changed files with 124 additions and 2 deletions

View File

@@ -5,6 +5,11 @@ from flask import Blueprint, render_template, jsonify, current_app, request
dingtalk_bp = Blueprint('dingtalk', __name__)
# 延迟导入避免循环引用
def get_user_model():
from models import User
return User
def get_dingtalk_config():
print('[后端] [1] ============== get_dingtalk_config 开始 ==============')
app_key = current_app.config.get('DINGTALK_APP_KEY', '')
@@ -166,6 +171,113 @@ def get_dept_name(dept_id):
print(f'[后端] [5] 异常:', e)
return None
def send_dingtalk_card_to_user(user_id, title, content, jump_url):
"""发送钉钉消息卡片给指定用户"""
print(f'[后端] [N] ============== send_dingtalk_card_to_user 开始, user_id={user_id} ==============')
access_token = get_access_token()
if not access_token:
print('[后端] [N] 获取 access_token 失败')
return False
url = f'https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token={access_token}'
headers = {'Content-Type': 'application/json'}
agent_id = current_app.config.get('DINGTALK_AGENT_ID', '')
data = {
'agent_id': int(agent_id) if agent_id else 0,
'userid_list': user_id,
'msg': {
'msgtype': 'link',
'link': {
'title': title,
'text': content,
'picUrl': 'https://img.alicdn.com/tfs/TB1NwmBkeL2gK0jSZPhXXahviXa-72-72.png',
'messageUrl': jump_url
}
}
}
print(f'[后端] [N] 发送消息卡片: {data}')
try:
response = requests.post(url, json=data, headers=headers, timeout=10)
result = response.json()
print(f'[后端] [N] 钉钉响应:', result)
if result.get('errcode') == 0:
print(f'[后端] [N] 消息发送成功')
return True
else:
print(f'[后端] [N] 消息发送失败: {result.get("errmsg", "未知错误")}')
return False
except Exception as e:
print(f'[后端] [N] 异常:', e)
return False
def notify_admins_new_demand(demand, base_url):
"""通知所有管理员有新需求提交"""
print(f'[后端] [N] ============== notify_admins_new_demand 开始 ==============')
# 获取所有管理员用户
from models import User
admins = User.query.filter_by(role='admin').all()
print(f'[后端] [N] 找到 {len(admins)} 个管理员')
if not admins:
print('[后端] [N] 没有管理员用户,跳过通知')
return
# 构建消息内容
title = f'📋 新需求提交:{demand.title}'
content = f'''需求标题:{demand.title}
提交者:{demand.contact or "未知"}
分会:{demand.branch}
点击查看并回答'''.strip()
# 回答页面的跳转链接
jump_url = f'{base_url}/demand/{demand.id}/answer'
success_count = 0
for admin in admins:
if admin.dingtalk_userid:
if send_dingtalk_card_to_user(admin.dingtalk_userid, title, content, jump_url):
success_count += 1
print(f'[后端] [N] 消息发送完成,成功: {success_count}/{len(admins)}')
def notify_asker_answer(demand, base_url):
"""通知提问者问题已被回答"""
print(f'[后端] [N] ============== notify_asker_answer 开始 ==============')
# 获取提问者
User = get_user_model()
asker = User.query.get(demand.user_id)
if not asker:
print('[后端] [N] 未找到提问者,跳过通知')
return
if not asker.dingtalk_userid:
print(f'[后端] [N] 提问者 {asker.username} 没有钉钉userid跳过通知')
return
# 构建消息内容
title = f'✅ 您的需求已被回答:{demand.title}'
content = f'''需求标题:{demand.title}
管理员已回复您的问题,点击查看详情'''.strip()
# 跳转链接 - 指向首页
jump_url = f'{base_url}' + '/dingtalk'
print(f'[后端] [N] 准备通知提问者: {asker.username}, dingtalk_userid: {asker.dingtalk_userid}')
if send_dingtalk_card_to_user(asker.dingtalk_userid, title, content, jump_url):
print(f'[后端] [N] 提问者通知发送成功')
else:
print(f'[后端] [N] 提问者通知发送失败')
@dingtalk_bp.route('/')
def dingtalk_entry():
print('[后端] [0] ============== dingtalk_entry 请求到达 ==============')

View File

@@ -19,7 +19,6 @@ tar -czvf "$OUTPUT_DIR/${PROJECT_NAME}_$(date +%Y%m%d_%H%M%S).tar.gz" \
--exclude='.git' \
--exclude='.codebuddy' \
--exclude='node_modules' \
--exclude='*.db' \
--exclude='venv' \
--exclude='env' \
--exclude='.env' \

View File

@@ -4,6 +4,7 @@ from flask_login import login_user, logout_user, login_required, current_user
from __init__ import app, db
from models import User, Demand, now_shanghai
from forms import DemandForm, AnswerForm, LoginForm, RegisterForm
from dingtalk import notify_admins_new_demand, notify_asker_answer
BRANCH_NAMES = {
'comprehensive': '综合分会',
@@ -125,6 +126,11 @@ def new_demand():
)
db.session.add(demand)
db.session.commit()
# 通知管理员有新需求提交
base_url = request.host_url.rstrip('/').replace('http://', 'https://') + '/requirement-collection'
notify_admins_new_demand(demand, base_url)
flash('需求提交成功', 'success')
return redirect(url_for('index'))
return render_template('demand_form.html', form=form, title='提交新需求')
@@ -164,6 +170,11 @@ def answer_demand(id):
demand.answer = form.answer.data
demand.answered_at = now_shanghai()
db.session.commit()
# 通知提问者问题已被回答
base_url = request.host_url.rstrip('/').replace('http://', 'https://') + '/requirement-collection'
notify_asker_answer(demand, base_url)
flash('回答已保存', 'success')
return redirect(url_for('index'))
return render_template('answer_form.html', form=form, demand=demand)

0
start_dingtalk.sh Normal file → Executable file
View File

View File

@@ -55,7 +55,7 @@
</head>
<body>
<div class="container">
<img src="/static/logo.png" alt="logo" class="logo">
<img src="{{ url_for('static', filename='logo.png') }}" alt="logo" class="logo">
<div class="spinner"></div>
<p class="loading">正在跳转...</p>
</div>