mirror of
https://github.com/wangwangit/qywx-push.git
synced 2026-09-08 01:06:52 +08:00
Initial commit
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" data-theme="garden">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>企业微信通知服务 - API文档</title>
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- DaisyUI -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@3.5.0/dist/full.css" rel="stylesheet">
|
||||
<!-- Lucide Icons -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<style>
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
body {
|
||||
background: linear-gradient(135deg, #e0f2fe 0%, #bef264 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col items-center justify-center p-4">
|
||||
<div class="w-full max-w-4xl glass-card p-8 my-8">
|
||||
<header class="mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold text-primary flex items-center gap-2">
|
||||
<i data-lucide="book-open" class="h-8 w-8"></i>
|
||||
企业微信通知服务 API文档
|
||||
</h1>
|
||||
<a href="/" class="btn btn-outline btn-sm">
|
||||
<i data-lucide="arrow-left" class="h-4 w-4 mr-1"></i>
|
||||
返回首页
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-base-content/70 mt-2">详细的API使用说明文档,帮助您快速集成企业微信消息推送功能</p>
|
||||
</header>
|
||||
|
||||
<div class="space-y-8">
|
||||
<!-- API概述 -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
<i data-lucide="info" class="h-6 w-6"></i>
|
||||
API概述
|
||||
</h2>
|
||||
<div class="bg-base-100 p-6 rounded-lg shadow-sm">
|
||||
<p class="mb-4">企业微信通知服务提供了简单易用的HTTP API,用于向企业微信应用发送消息通知。通过配置您的企业微信应用信息,系统会生成一个唯一的API地址,您可以通过该地址发送消息。</p>
|
||||
<div class="alert alert-info">
|
||||
<i data-lucide="lightbulb" class="h-5 w-5"></i>
|
||||
<span>使用前,您需要先在首页创建配置并获取唯一的API地址。</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 认证与安全 -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
<i data-lucide="shield" class="h-6 w-6"></i>
|
||||
认证与安全
|
||||
</h2>
|
||||
<div class="bg-base-100 p-6 rounded-lg shadow-sm">
|
||||
<p class="mb-4">API使用唯一的配置Code作为认证凭证,该Code包含在API URL中。请妥善保管您的配置Code,不要泄露给未授权的人员。</p>
|
||||
<div class="alert alert-warning">
|
||||
<i data-lucide="alert-triangle" class="h-5 w-5"></i>
|
||||
<span>配置Code仅在创建时显示一次,请务必保存。如果遗失,您需要重新创建配置。</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 发送消息API -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
<i data-lucide="send" class="h-6 w-6"></i>
|
||||
发送消息API
|
||||
</h2>
|
||||
<div class="bg-base-100 p-6 rounded-lg shadow-sm space-y-6">
|
||||
<div>
|
||||
<h3 class="font-medium mb-2 text-lg">请求地址</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">POST /api/notify/{your_code}</code>
|
||||
</div>
|
||||
<p class="text-sm text-base-content/70 mt-1">其中 {your_code} 是您创建配置时获得的唯一Code</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2 text-lg">请求头</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">Content-Type: application/json</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2 text-lg">请求参数</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数名</th>
|
||||
<th>类型</th>
|
||||
<th>必填</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-mono">title</td>
|
||||
<td>String</td>
|
||||
<td>否</td>
|
||||
<td>消息标题,可选。如果提供,将作为消息的第一行显示,并会加粗处理。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-mono">content</td>
|
||||
<td>String</td>
|
||||
<td>是</td>
|
||||
<td>消息内容,必填。支持简单的markdown格式,如加粗、链接等。</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2 text-lg">请求示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm">curl -X POST "http://your-server.com/api/notify/your-code-here" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "服务器告警",
|
||||
"content": "CPU使用率超过90%,请及时处理!\n\n**详细信息**:\n- 服务器:web-server-01\n- 时间:2023-09-15 14:30:45\n- 当前负载:95%"
|
||||
}'</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2 text-lg">返回示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm">{
|
||||
"message": "发送成功",
|
||||
"response": {
|
||||
"errcode": 0,
|
||||
"errmsg": "ok",
|
||||
"msgid": "MSGID1234567890"
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2 text-lg">错误码说明</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP状态码</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>200</td>
|
||||
<td>请求成功</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>400</td>
|
||||
<td>参数错误,如缺少必填参数</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>404</td>
|
||||
<td>配置不存在,请检查Code是否正确</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>500</td>
|
||||
<td>服务器内部错误或企业微信API调用失败</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 集成示例 -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
<i data-lucide="code" class="h-6 w-6"></i>
|
||||
集成示例
|
||||
</h2>
|
||||
<div class="bg-base-100 p-6 rounded-lg shadow-sm space-y-6">
|
||||
<div>
|
||||
<h3 class="font-medium mb-2 text-lg">Shell脚本示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm">#!/bin/bash
|
||||
|
||||
# 监控服务器CPU使用率并发送告警
|
||||
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}')
|
||||
THRESHOLD=90
|
||||
|
||||
if (( $(echo "$CPU_USAGE > $THRESHOLD" | bc -l) )); then
|
||||
curl -X POST "http://your-server.com/api/notify/your-code-here" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"title\": \"CPU告警\",
|
||||
\"content\": \"服务器CPU使用率: ${CPU_USAGE}%,超过阈值${THRESHOLD}%\"
|
||||
}"
|
||||
fi</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2 text-lg">Python示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm">import requests
|
||||
import json
|
||||
|
||||
def send_notification(title, content):
|
||||
url = "http://your-server.com/api/notify/your-code-here"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
data = {
|
||||
"title": title,
|
||||
"content": content
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, data=json.dumps(data))
|
||||
return response.json()
|
||||
|
||||
# 使用示例
|
||||
result = send_notification(
|
||||
"数据库备份完成",
|
||||
"数据库备份已完成\n- 数据库:user_db\n- 备份大小:1.2GB\n- 备份时间:2023-09-15 02:00:00"
|
||||
)
|
||||
print(result)</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2 text-lg">Node.js示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm">const axios = require('axios');
|
||||
|
||||
async function sendNotification(title, content) {
|
||||
try {
|
||||
const response = await axios.post('http://your-server.com/api/notify/your-code-here', {
|
||||
title,
|
||||
content
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('发送通知失败:', error.response ? error.response.data : error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
sendNotification('部署完成', '项目已成功部署到生产环境\n\n版本: v1.2.3\n部署时间: 2023-09-15 15:30:00')
|
||||
.then(result => console.log('通知发送成功:', result))
|
||||
.catch(err => console.error('通知发送失败:', err));</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 常见问题 -->
|
||||
<section>
|
||||
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
<i data-lucide="help-circle" class="h-6 w-6"></i>
|
||||
常见问题
|
||||
</h2>
|
||||
<div class="bg-base-100 p-6 rounded-lg shadow-sm">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="font-medium">Q: 如何获取配置Code?</h3>
|
||||
<p>A: 在首页填写您的企业微信应用信息,验证并选择接收成员后,点击"生成通知API"按钮即可获取配置Code。</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-medium">Q: 消息发送失败怎么办?</h3>
|
||||
<p>A: 请检查以下几点:</p>
|
||||
<ul class="list-disc pl-5 mt-2">
|
||||
<li>配置Code是否正确</li>
|
||||
<li>请求参数格式是否正确</li>
|
||||
<li>企业微信应用的配置是否有效</li>
|
||||
<li>网络连接是否正常</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-medium">Q: 是否支持发送图片或文件?</h3>
|
||||
<p>A: 目前仅支持发送文本消息,不支持图片或文件附件。</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-medium">Q: 消息内容有长度限制吗?</h3>
|
||||
<p>A: 根据企业微信API的限制,消息内容不应超过2048个字符。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="text-center text-sm text-base-content/60 mb-4">
|
||||
<p class="flex items-center justify-center gap-1">
|
||||
<i data-lucide="external-link" class="h-4 w-4"></i>
|
||||
<a href="https://www.wangwangit.com/" target="_blank" class="hover:text-primary transition-colors">一只会飞的旺旺</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,164 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" data-theme="garden">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>企业微信通知服务</title>
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- DaisyUI -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@3.5.0/dist/full.css" rel="stylesheet">
|
||||
<!-- Lucide Icons -->
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<!-- GSAP -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||
<style>
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
body {
|
||||
background: linear-gradient(135deg, #e0f2fe 0%, #bef264 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col items-center justify-center p-4">
|
||||
<div class="w-full max-w-2xl glass-card p-8 my-8">
|
||||
<header class="mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-3xl font-bold text-primary flex items-center gap-2">
|
||||
<i data-lucide="bell-ring" class="h-8 w-8"></i>
|
||||
企业微信通知服务
|
||||
</h1>
|
||||
<a href="/public/api-docs.html" class="btn btn-sm btn-outline btn-primary gap-1">
|
||||
<i data-lucide="book-open" class="h-4 w-4"></i>
|
||||
API文档
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<p class="text-base-content/70">轻量级企业微信消息推送服务,一次配置,随时调用</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 第一步:生成回调URL -->
|
||||
<div id="step1-container">
|
||||
<div class="divider">第一步:生成回调URL(必须先完成)</div>
|
||||
<div class="alert alert-warning mb-4">
|
||||
<i data-lucide="alert-triangle" class="h-5 w-5"></i>
|
||||
<div>
|
||||
<div class="font-medium">重要提示</div>
|
||||
<div class="text-sm">必须先生成回调URL并在企业微信后台配置,然后配置IP白名单,最后才能获取成员列表</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="callbackForm" class="space-y-4">
|
||||
<div class="form-control">
|
||||
<label class="label font-medium">CorpID</label>
|
||||
<input type="text" name="corpid" class="input input-bordered w-full" required placeholder="请输入企业微信CorpID">
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label font-medium">回调Token</label>
|
||||
<input type="text" name="callback_token" class="input input-bordered w-full" placeholder="请输入回调验证Token" required>
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-base-content/60">用于验证回调请求的Token</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label font-medium">EncodingAESKey</label>
|
||||
<input type="text" name="encoding_aes_key" class="input input-bordered w-full" placeholder="请输入43位EncodingAESKey" required>
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-base-content/60">用于消息加解密的AES密钥</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-full flex gap-2 items-center">
|
||||
<i data-lucide="link" class="w-5 h-5"></i> 生成回调URL
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div id="callbackResult" class="hidden mt-4"></div>
|
||||
</div>
|
||||
|
||||
<!-- 第二步:完善配置 -->
|
||||
<div id="step2-container" class="hidden">
|
||||
<div class="divider">第二步:完善配置(配置IP白名单后)</div>
|
||||
<div class="alert alert-info mb-4">
|
||||
<i data-lucide="info" class="h-5 w-5"></i>
|
||||
<span>请先在企业微信后台配置回调URL和IP白名单,然后点击下方按钮获取成员列表</span>
|
||||
</div>
|
||||
|
||||
<form id="configForm" class="space-y-4">
|
||||
<div class="form-control">
|
||||
<label class="label font-medium">CorpSecret</label>
|
||||
<input type="password" name="corpsecret" class="input input-bordered w-full" required placeholder="请输入CorpSecret">
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label font-medium">AgentID</label>
|
||||
<input type="number" name="agentid" class="input input-bordered w-full" required placeholder="请输入AgentID">
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" id="validateBtn" class="btn btn-outline w-full flex gap-2 items-center">
|
||||
<i data-lucide="users" class="w-5 h-5"></i> 验证并获取成员列表
|
||||
</button>
|
||||
</div>
|
||||
<div id="userListSection" class="hidden">
|
||||
<label class="block font-medium mb-1">选择接收成员</label>
|
||||
<div id="userList" class="grid grid-cols-2 gap-2 max-h-40 overflow-y-auto bg-base-100 rounded-lg p-2 border border-base-200"></div>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label font-medium">配置描述(可选)</label>
|
||||
<input type="text" name="description" class="input input-bordered w-full" placeholder="如:服务器告警推送">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success btn-lg w-full font-bold flex gap-2 items-center">
|
||||
<i data-lucide="check" class="w-5 h-5"></i> 完成配置
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 查找配置区域 -->
|
||||
<div class="divider">查找已有配置</div>
|
||||
<div id="lookup-section">
|
||||
<form id="lookupForm" class="space-y-4">
|
||||
<div class="form-control">
|
||||
<label class="label font-medium">配置Code</label>
|
||||
<div class="flex gap-2">
|
||||
<input type="text" name="code" class="input input-bordered flex-1" required placeholder="请输入配置Code">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i data-lucide="search" class="h-5 w-5"></i>
|
||||
查找
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div id="lookup-result" class="mt-4"></div>
|
||||
</div>
|
||||
|
||||
<div id="result" class="mt-8"></div>
|
||||
</div>
|
||||
|
||||
<!-- 提示弹窗 -->
|
||||
<div id="save-alert" class="toast toast-top toast-center hidden">
|
||||
<div class="alert alert-warning">
|
||||
<i data-lucide="alert-triangle" class="h-6 w-6"></i>
|
||||
<span>请注意:Code生成后,您只有一次机会保存!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="text-center text-sm text-base-content/60 mb-4">
|
||||
<p class="flex items-center justify-center gap-1">
|
||||
<i data-lucide="external-link" class="h-4 w-4"></i>
|
||||
<a href="https://www.wangwangit.com/" target="_blank" class="hover:text-primary transition-colors">一只会飞的旺旺</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="/public/script.js"></script>
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,584 @@
|
||||
// 企业微信通知配置前端交互脚本
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// 元素引用
|
||||
const callbackForm = document.getElementById('callbackForm');
|
||||
const configForm = document.getElementById('configForm');
|
||||
const validateBtn = document.getElementById('validateBtn');
|
||||
const userListSection = document.getElementById('userListSection');
|
||||
const userList = document.getElementById('userList');
|
||||
const lookupForm = document.getElementById('lookupForm');
|
||||
const lookupResultDiv = document.getElementById('lookup-result');
|
||||
const resultDiv = document.getElementById('result');
|
||||
const saveAlert = document.getElementById('save-alert');
|
||||
const step1Container = document.getElementById('step1-container');
|
||||
const step2Container = document.getElementById('step2-container');
|
||||
const callbackResult = document.getElementById('callbackResult');
|
||||
|
||||
let usersCache = [];
|
||||
let currentCode = null; // 存储当前的code
|
||||
|
||||
// 第一步:生成回调URL
|
||||
callbackForm.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
resultDiv.innerHTML = '';
|
||||
|
||||
const corpid = callbackForm.corpid.value.trim();
|
||||
const callbackToken = callbackForm.callback_token.value.trim();
|
||||
const encodingAesKey = callbackForm.encoding_aes_key.value.trim();
|
||||
|
||||
if (!corpid || !callbackToken || !encodingAesKey) {
|
||||
showError('请填写所有必填项');
|
||||
return;
|
||||
}
|
||||
if (encodingAesKey.length !== 43) {
|
||||
showError('EncodingAESKey必须是43位字符');
|
||||
return;
|
||||
}
|
||||
|
||||
const submitBtn = callbackForm.querySelector('button[type=submit]');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '生成中...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/generate-callback', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
corpid,
|
||||
callback_token: callbackToken,
|
||||
encoding_aes_key: encodingAesKey
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || '生成失败');
|
||||
|
||||
currentCode = data.code;
|
||||
showCallbackResult(data);
|
||||
|
||||
// 显示第二步
|
||||
step2Container.classList.remove('hidden');
|
||||
gsap.from(step2Container, { opacity: 0, y: 20, duration: 0.5 });
|
||||
|
||||
// 将CorpID传递到第二步
|
||||
configForm.corpid = { value: corpid };
|
||||
|
||||
} catch (err) {
|
||||
showError('生成回调URL失败: ' + err.message);
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = '生成回调URL';
|
||||
}
|
||||
});
|
||||
|
||||
// 验证并获取成员列表
|
||||
validateBtn.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
resultDiv.innerHTML = '';
|
||||
userList.innerHTML = '';
|
||||
userListSection.classList.add('hidden');
|
||||
|
||||
const corpid = callbackForm.corpid.value.trim(); // 从第一步获取
|
||||
const corpsecret = configForm.corpsecret.value.trim();
|
||||
if (!corpid || !corpsecret) {
|
||||
showError('请填写CorpSecret');
|
||||
return;
|
||||
}
|
||||
validateBtn.disabled = true;
|
||||
validateBtn.textContent = '验证中...';
|
||||
try {
|
||||
const res = await fetch('/api/validate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ corpid, corpsecret })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || '验证失败');
|
||||
usersCache = data.users || [];
|
||||
if (usersCache.length === 0) {
|
||||
showError('未获取到任何成员');
|
||||
return;
|
||||
}
|
||||
userList.innerHTML = usersCache.map(user =>
|
||||
`<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" class="checkbox checkbox-sm" value="${user.userid}">
|
||||
<span>${user.name} <span class="text-xs text-gray-400">(${user.userid})</span></span>
|
||||
</label>`
|
||||
).join('');
|
||||
userListSection.classList.remove('hidden');
|
||||
gsap.from(userListSection, { opacity: 0, y: 20, duration: 0.5 });
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
} finally {
|
||||
validateBtn.disabled = false;
|
||||
validateBtn.textContent = '验证并获取成员列表';
|
||||
}
|
||||
});
|
||||
|
||||
// 查找配置
|
||||
lookupForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const code = lookupForm.code.value.trim();
|
||||
if (!code) return;
|
||||
|
||||
lookupResultDiv.innerHTML = '<div class="loading loading-spinner loading-md mx-auto"></div>';
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/configuration/${code}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) throw new Error(data.error || '查找配置失败');
|
||||
|
||||
// 显示配置信息
|
||||
const apiUrl = `/api/notify/${data.code}`;
|
||||
lookupResultDiv.innerHTML = `
|
||||
<div class="card bg-base-100 shadow-md">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title flex items-center gap-2">
|
||||
<i data-lucide="settings" class="h-5 w-5"></i>
|
||||
配置详情
|
||||
</h2>
|
||||
<div class="space-y-2 mt-2">
|
||||
<p><span class="font-medium">CorpID:</span> ${data.corpid}</p>
|
||||
<p><span class="font-medium">AgentID:</span> ${data.agentid}</p>
|
||||
<p><span class="font-medium">接收用户:</span> ${data.touser.join(', ')}</p>
|
||||
<p><span class="font-medium">描述:</span> ${data.description || '无'}</p>
|
||||
<p><span class="font-medium">回调状态:</span> ${data.callback_enabled ? '已启用' : '未启用'}</p>
|
||||
${data.callback_enabled ? `<p><span class="font-medium">回调Token:</span> ${data.callback_token || '未设置'}</p>` : ''}
|
||||
<p><span class="font-medium">创建时间:</span> ${new Date(data.created_at).toLocaleString()}</p>
|
||||
</div>
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<button class="btn btn-primary btn-sm" id="edit-config-btn" data-code="${data.code}">
|
||||
<i data-lucide="edit" class="h-4 w-4"></i>
|
||||
编辑配置
|
||||
</button>
|
||||
<button class="btn btn-outline btn-sm" id="copy-api-btn" data-code="${data.code}">
|
||||
<i data-lucide="copy" class="h-4 w-4"></i>
|
||||
复制API地址
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="divider">API使用说明</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求方式</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">POST ${apiUrl}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求头</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">Content-Type: application/json</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求参数</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数名</th>
|
||||
<th>类型</th>
|
||||
<th>必填</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-mono">title</td>
|
||||
<td>String</td>
|
||||
<td>否</td>
|
||||
<td>消息标题,可选</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-mono">content</td>
|
||||
<td>String</td>
|
||||
<td>是</td>
|
||||
<td>消息内容,必填</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm whitespace-pre-wrap">curl -X POST "${apiUrl}" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"title": "服务器告警",
|
||||
"content": "CPU使用率超过90%,请及时处理!"
|
||||
}'</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">返回示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm whitespace-pre-wrap">{
|
||||
"message": "发送成功",
|
||||
"response": {
|
||||
"errcode": 0,
|
||||
"errmsg": "ok",
|
||||
"msgid": "MSGID1234567890"
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
lucide.createIcons();
|
||||
gsap.from(lookupResultDiv.firstElementChild, { opacity: 0, y: 20, duration: 0.5 });
|
||||
|
||||
// 绑定编辑和复制按钮事件
|
||||
document.getElementById('edit-config-btn').addEventListener('click', (e) => {
|
||||
const code = e.currentTarget.dataset.code;
|
||||
// 这里可以添加编辑功能的实现
|
||||
showToast('编辑功能待实现');
|
||||
});
|
||||
|
||||
document.getElementById('copy-api-btn').addEventListener('click', (e) => {
|
||||
const code = e.currentTarget.dataset.code;
|
||||
navigator.clipboard.writeText(`/api/notify/${code}`);
|
||||
showToast('API地址已复制到剪贴板');
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
lookupResultDiv.innerHTML = `
|
||||
<div class="alert alert-error">
|
||||
<i data-lucide="alert-circle" class="h-5 w-5"></i>
|
||||
<span>${err.message}</span>
|
||||
</div>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
}
|
||||
});
|
||||
|
||||
// 第二步:完善配置
|
||||
configForm.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
resultDiv.innerHTML = '';
|
||||
|
||||
if (!currentCode) {
|
||||
showError('请先完成第一步生成回调URL');
|
||||
return;
|
||||
}
|
||||
|
||||
const corpsecret = configForm.corpsecret.value.trim();
|
||||
const agentid = configForm.agentid.value.trim();
|
||||
const description = configForm.description.value.trim();
|
||||
const checked = userListSection.classList.contains('hidden')
|
||||
? []
|
||||
: Array.from(userList.querySelectorAll('input[type=checkbox]:checked')).map(cb => cb.value);
|
||||
if (!corpsecret || !agentid || checked.length === 0) {
|
||||
showError('请填写所有必填项并选择至少一个成员');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
code: currentCode,
|
||||
corpsecret,
|
||||
agentid: Number(agentid),
|
||||
touser: checked,
|
||||
description
|
||||
};
|
||||
configForm.querySelector('button[type=submit]').disabled = true;
|
||||
configForm.querySelector('button[type=submit]').textContent = '完成中...';
|
||||
try {
|
||||
const res = await fetch('/api/complete-config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || '完成失败');
|
||||
showFinalResult(data);
|
||||
|
||||
// 显示一次性保存提醒
|
||||
saveAlert.classList.remove('hidden');
|
||||
gsap.from(saveAlert, { opacity: 0, y: -50, duration: 0.5 });
|
||||
setTimeout(() => {
|
||||
gsap.to(saveAlert, { opacity: 0, y: -50, duration: 0.5, onComplete: () => {
|
||||
saveAlert.classList.add('hidden');
|
||||
saveAlert.style.opacity = 1;
|
||||
saveAlert.style.transform = 'none';
|
||||
}});
|
||||
}, 5000);
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
} finally {
|
||||
configForm.querySelector('button[type=submit]').disabled = false;
|
||||
configForm.querySelector('button[type=submit]').textContent = '完成配置';
|
||||
}
|
||||
});
|
||||
|
||||
function showError(msg) {
|
||||
resultDiv.innerHTML = `<div class="alert alert-error"><span>${msg}</span></div>`;
|
||||
gsap.from(resultDiv, { opacity: 0, y: 20, duration: 0.5 });
|
||||
}
|
||||
|
||||
function showCallbackResult(data) {
|
||||
callbackResult.innerHTML = `
|
||||
<div class="card bg-base-100 shadow-md">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-primary flex items-center gap-2">
|
||||
<i data-lucide="check-circle" class="h-6 w-6"></i>
|
||||
回调URL生成成功!
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4 mt-4">
|
||||
<div>
|
||||
<div class="font-medium">您的配置Code</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.code}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">回调URL</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${window.location.origin}${data.callbackUrl}</div>
|
||||
<button class="btn btn-sm btn-outline mt-1" id="copy-callback-url-btn">
|
||||
<i data-lucide="copy" class="h-4 w-4 mr-1"></i>复制回调URL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info mt-4">
|
||||
<i data-lucide="info" class="h-5 w-5"></i>
|
||||
<div>
|
||||
<div class="font-medium">下一步操作</div>
|
||||
<div class="text-sm">
|
||||
1. 复制上方回调URL到企业微信管理后台<br>
|
||||
2. 配置IP白名单(添加您的服务器IP)<br>
|
||||
3. 完成下方第二步配置
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 绑定复制按钮事件
|
||||
document.getElementById('copy-callback-url-btn').addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(window.location.origin + data.callbackUrl);
|
||||
showToast('回调URL已复制到剪贴板');
|
||||
});
|
||||
|
||||
callbackResult.classList.remove('hidden');
|
||||
lucide.createIcons();
|
||||
gsap.from(callbackResult.firstElementChild, { opacity: 0, y: 20, duration: 0.5 });
|
||||
}
|
||||
|
||||
function showFinalResult(data) {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="card bg-base-100 shadow-md">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-success flex items-center gap-2">
|
||||
<i data-lucide="check-circle-2" class="h-6 w-6"></i>
|
||||
配置完成!
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4 mt-4">
|
||||
<div>
|
||||
<div class="font-medium">配置Code</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.code}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">通知API地址</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${window.location.origin}${data.apiUrl}</div>
|
||||
<button class="btn btn-sm btn-outline mt-1" id="copy-api-url-btn">
|
||||
<i data-lucide="copy" class="h-4 w-4 mr-1"></i>复制API地址
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">回调地址</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${window.location.origin}${data.callbackUrl}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success mt-4">
|
||||
<i data-lucide="check" class="h-5 w-5"></i>
|
||||
<span>配置已完成!您现在可以使用API发送通知,也可以接收企业微信回调消息。</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 绑定复制按钮事件
|
||||
document.getElementById('copy-api-url-btn').addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(window.location.origin + data.apiUrl);
|
||||
showToast('API地址已复制到剪贴板');
|
||||
});
|
||||
|
||||
lucide.createIcons();
|
||||
gsap.from(resultDiv.firstElementChild, { opacity: 0, y: 20, duration: 0.5 });
|
||||
}
|
||||
function showResult(data) {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="card bg-base-100 shadow-md">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-primary flex items-center gap-2">
|
||||
<i data-lucide="check-circle" class="h-6 w-6"></i>
|
||||
API生成成功!
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<div class="space-y-2">
|
||||
<div class="font-medium">您的唯一调用ID</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.code}</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="font-medium">API地址</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.apiUrl}</div>
|
||||
<button class="btn btn-sm btn-outline mt-1" id="copy-new-api-btn">
|
||||
<i data-lucide="copy" class="h-4 w-4 mr-1"></i>复制
|
||||
</button>
|
||||
</div>
|
||||
${data.callbackUrl ? `
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<div class="font-medium">回调地址</div>
|
||||
<div class="bg-base-200 p-2 rounded-md font-mono text-sm overflow-x-auto">${data.callbackUrl}</div>
|
||||
<button class="btn btn-sm btn-outline mt-1" id="copy-callback-btn">
|
||||
<i data-lucide="copy" class="h-4 w-4 mr-1"></i>复制回调地址
|
||||
</button>
|
||||
<div class="text-sm text-base-content/60 mt-1">
|
||||
<i data-lucide="info" class="h-4 w-4 inline mr-1"></i>
|
||||
在企业微信管理后台配置此回调地址以接收消息
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
<div class="divider">API使用说明</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求方式</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">POST ${data.apiUrl}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求头</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<code class="text-sm">Content-Type: application/json</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求参数</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>参数名</th>
|
||||
<th>类型</th>
|
||||
<th>必填</th>
|
||||
<th>说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-mono">title</td>
|
||||
<td>String</td>
|
||||
<td>否</td>
|
||||
<td>消息标题,可选</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-mono">content</td>
|
||||
<td>String</td>
|
||||
<td>是</td>
|
||||
<td>消息内容,必填</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">请求示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm whitespace-pre-wrap">curl -X POST "${data.apiUrl}" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"title": "服务器告警",
|
||||
"content": "CPU使用率超过90%,请及时处理!"
|
||||
}'</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="font-medium mb-2">返回示例</h3>
|
||||
<div class="bg-base-200 p-3 rounded-md">
|
||||
<pre class="text-sm whitespace-pre-wrap">{
|
||||
"message": "发送成功",
|
||||
"response": {
|
||||
"errcode": 0,
|
||||
"errmsg": "ok",
|
||||
"msgid": "MSGID1234567890"
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning mt-4">
|
||||
<i data-lucide="alert-triangle" class="h-5 w-5"></i>
|
||||
<span>请妥善保存您的配置Code,它是调用API的唯一凭证。出于安全考虑,它只会显示一次!</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 绑定复制按钮事件
|
||||
document.getElementById('copy-new-api-btn').addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(data.apiUrl);
|
||||
showToast('API地址已复制到剪贴板');
|
||||
});
|
||||
|
||||
// 绑定回调地址复制按钮事件(如果存在)
|
||||
if (data.callbackUrl) {
|
||||
document.getElementById('copy-callback-btn').addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(window.location.origin + data.callbackUrl);
|
||||
showToast('回调地址已复制到剪贴板');
|
||||
});
|
||||
}
|
||||
|
||||
// 创建图标
|
||||
lucide.createIcons();
|
||||
|
||||
gsap.from(resultDiv, { opacity: 0, y: 20, duration: 0.5 });
|
||||
}
|
||||
|
||||
// 显示提示消息
|
||||
function showToast(message) {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast toast-top toast-center';
|
||||
toast.innerHTML = `
|
||||
<div class="alert alert-info">
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
gsap.fromTo(toast,
|
||||
{ opacity: 0, y: -20 },
|
||||
{ opacity: 1, y: 0, duration: 0.3 }
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
gsap.to(toast, {
|
||||
opacity: 0,
|
||||
y: -20,
|
||||
duration: 0.3,
|
||||
onComplete: () => toast.remove()
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user