微信推送消息配置完整指南

124 阅读8分钟

微信推送消息配置完整指南

📋 文档概述

本文档将详细介绍如何在 Next.js 项目中集成微信公众号消息推送功能,包括服务器验证、消息接收处理、用户管理等完整流程。

🎯 功能特性

  • 服务器验证 - 微信服务器URL验证
  • 消息处理 - 文本、事件、扫码等消息类型
  • 用户管理 - 关注/取消关注事件处理
  • 数据存储 - 用户信息自动保存到数据库
  • 智能回复 - 基于消息内容的自动回复
  • 错误处理 - 完善的异常处理机制

🔧 前置条件

1. 环境要求

  • Node.js 18+
  • PostgreSQL 数据库
  • 公网可访问的服务器(或内网穿透工具)
  • 已认证的微信公众号

2. 项目依赖

{
  "next": "^15.0.0",
  "@prisma/client": "latest",
  "crypto": "built-in"
}

🚀 配置步骤

步骤 1: 微信公众号基础配置

1.1 获取 AppID 和 AppSecret
  1. 登录 微信公众平台
  2. 进入 开发 → 基本配置
  3. 记录 AppIDAppSecret
  4. 记录 Token(用于服务器验证)
1.2 配置服务器地址
  1. 开发 → 基本配置 → 服务器配置
  2. 填入服务器URL:https://yourdomain.com/api/wechat/message
  3. 填入Token(与环境变量中保持一致)
  4. 选择 明文模式(推荐)
  5. 点击 提交 进行验证

步骤 2: 环境变量配置

在项目根目录创建 .env 文件:

# 微信公众号配置
WECHAT_APP_ID="wxabcdefg1234567"
WECHAT_APP_SECRET="1234567890abcdefghijklmnopqrstuv"
WECHAT_TOKEN="your_custom_token_here"

# 前端配置
NEXT_PUBLIC_WECHAT_APP_ID="wxabcdefg1234567"

# 数据库配置
DATABASE_URL="postgresql://username:password@localhost:5432/database_name"

# 认证配置
AUTH_SECRET="your-secret-key-here"
NEXTAUTH_URL="https://yourdomain.com"

步骤 3: 数据库模型配置

3.1 Prisma Schema 配置
// prisma/schema.prisma
model WechatUser {
  id              String    @id @default(cuid())
  openid          String    @unique
  unionid         String?
  nickname        String?
  headimgurl      String?
  sex             Int       @default(0)
  language        String    @default("zh_CN")
  city            String?
  province        String?
  country         String?
  isSubscribed    Boolean   @default(true)
  subscribeTime   DateTime  @default(now())
  unsubscribeTime DateTime?
  subscribeScene  String?
  qrScene         Int?
  qrSceneStr      String?
  remark          String?
  groupid         Int       @default(0)
  tagidList       Json      @default("[]")
  lastActiveAt    DateTime  @default(now())
  createdAt       DateTime  @default(now())
  updatedAt       DateTime  @updatedAt

  @@map("wechat_users")
}
3.2 数据库迁移
# 生成 Prisma 客户端
npx prisma generate

# 推送模式到数据库
npx prisma db push

# 或使用迁移(生产环境推荐)
npx prisma migrate dev --name add_wechat_user

💻 代码实现

1. 环境变量配置文件

// src/env.js
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const env = createEnv({
  server: {
    WECHAT_APP_ID: z.string().optional(),
    WECHAT_APP_SECRET: z.string().optional(),
    WECHAT_TOKEN: z.string().optional(),
    DATABASE_URL: z.string().url(),
  },
  client: {
    NEXT_PUBLIC_WECHAT_APP_ID: z.string().optional(),
  },
  runtimeEnv: {
    WECHAT_APP_ID: process.env.WECHAT_APP_ID,
    WECHAT_APP_SECRET: process.env.WECHAT_APP_SECRET,
    WECHAT_TOKEN: process.env.WECHAT_TOKEN,
    NEXT_PUBLIC_WECHAT_APP_ID: process.env.NEXT_PUBLIC_WECHAT_APP_ID,
    DATABASE_URL: process.env.DATABASE_URL,
  },
});

2. 微信用户服务

// src/server/services/wechat-user.ts
import { db } from "@/server/db";

export interface WechatUserInfo {
  openid: string;
  unionid?: string;
  nickname?: string;
  headimgurl?: string;
  sex?: number;
  city?: string;
  province?: string;
  country?: string;
}

export interface SubscribeEventInfo {
  openid: string;
  eventKey?: string;
  ticket?: string;
}

export class WechatUserService {
  /**
   * 处理用户关注事件
   */
  static async handleSubscribeEvent(eventInfo: SubscribeEventInfo): Promise<void> {
    try {
      console.log('处理用户关注事件:', eventInfo);

      const existingUser = await db.wechatUser.findUnique({
        where: { openid: eventInfo.openid }
      });

      if (existingUser) {
        // 用户重新关注,更新状态
        await db.wechatUser.update({
          where: { openid: eventInfo.openid },
          data: {
            isSubscribed: true,
            subscribeTime: new Date(),
            unsubscribeTime: null,
            subscribeScene: eventInfo.eventKey ? `qr_scene_${eventInfo.eventKey}` : 'ADD_SCENE_OTHERS',
            qrScene: eventInfo.eventKey ? parseInt(eventInfo.eventKey) : null,
            qrSceneStr: eventInfo.eventKey || '',
            lastActiveAt: new Date(),
            updatedAt: new Date()
          }
        });
      } else {
        // 新用户关注,创建记录
        await db.wechatUser.create({
          data: {
            openid: eventInfo.openid,
            isSubscribed: true,
            subscribeTime: new Date(),
            subscribeScene: eventInfo.eventKey ? `qr_scene_${eventInfo.eventKey}` : 'ADD_SCENE_OTHERS',
            qrScene: eventInfo.eventKey ? parseInt(eventInfo.eventKey) : null,
            qrSceneStr: eventInfo.eventKey || '',
            lastActiveAt: new Date()
          }
        });
      }

      // 异步更新用户详细信息
      setTimeout(() => {
        WechatUserService.updateUserInfo(eventInfo.openid).catch(error => {
          console.error('异步更新用户信息失败:', error);
        });
      }, 1000);

    } catch (error) {
      console.error('处理用户关注事件失败:', error);
      throw error;
    }
  }

  /**
   * 处理用户取消关注事件
   */
  static async handleUnsubscribeEvent(openid: string): Promise<void> {
    try {
      await db.wechatUser.updateMany({
        where: { openid },
        data: {
          isSubscribed: false,
          unsubscribeTime: new Date(),
          lastActiveAt: new Date()
        }
      });
    } catch (error) {
      console.error('处理用户取消关注事件失败:', error);
      throw error;
    }
  }

  /**
   * 更新用户详细信息
   */
  static async updateUserInfo(openid: string): Promise<void> {
    try {
      const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';
      const response = await fetch(
        `${baseUrl}/api/wechat/user-info?openid=${openid}&app_type=official_account`
      );

      if (!response.ok) {
        throw new Error(`获取用户信息失败: ${response.status}`);
      }

      const result = await response.json();
      
      if (!result.success || !result.data?.user_info) {
        console.warn(`获取用户详细信息失败: ${result.error || '未知错误'}`);
        return;
      }

      const userInfo = result.data.user_info as WechatUserInfo;

      await db.wechatUser.update({
        where: { openid },
        data: {
          unionid: userInfo.unionid || null,
          nickname: userInfo.nickname || null,
          headimgurl: userInfo.headimgurl || null,
          sex: userInfo.sex || 0,
          city: userInfo.city || null,
          province: userInfo.province || null,
          country: userInfo.country || null,
          lastActiveAt: new Date(),
          updatedAt: new Date()
        }
      });

      console.log(`用户信息更新成功: ${userInfo.nickname} (${openid})`);
    } catch (error) {
      console.error('更新用户信息失败:', error);
    }
  }
}

3. 消息推送API接口

// src/app/api/wechat/message/route.ts
import { type NextRequest, NextResponse } from "next/server";
import { env } from "@/env";
import { WechatUserService } from '@/server/services/wechat-user';
import crypto from "crypto";

// 微信消息类型定义
interface WeChatMessage {
  ToUserName: string;
  FromUserName: string;
  CreateTime: number;
  MsgType: string;
  MsgId?: string;
  Content?: string;
  Event?: string;
  EventKey?: string;
  [key: string]: string | number | undefined;
}

// 验证微信服务器签名
function verifySignature(params: {
  signature: string;
  timestamp: string;
  nonce: string;
}, token: string): boolean {
  const { signature, timestamp, nonce } = params;
  const tmpArr = [token, timestamp, nonce].sort();
  const tmpStr = tmpArr.join("");
  const hash = crypto.createHash("sha1").update(tmpStr).digest("hex");
  return hash === signature;
}

// 处理文本消息
function handleTextMessage(message: WeChatMessage): string {
  const content = message.Content ?? "";
  let replyContent = "";

  // 智能回复逻辑
  if (content.includes("帮助") || content.includes("help")) {
    replyContent = `🤖 智能助手为您服务

可用功能:
• 发送"阿姨" - 查看阿姨信息
• 发送"雇主" - 查看雇主需求
• 发送"招聘" - 查看最新招聘
• 发送"联系" - 获取联系方式

💡 您也可以直接描述需求,我会为您提供相关信息。`;
  } else if (content.includes("阿姨")) {
    replyContent = `👩‍🦳 阿姨服务信息

我们有专业的家政阿姨:
• 月嫂 - 专业护理新生儿和产妇
• 育儿嫂 - 科学育儿指导
• 保姆 - 家务料理和老人照护
• 钟点工 - 灵活时间服务

如需了解详情,请发送具体服务类型。`;
  } else if (content.includes("雇主") || content.includes("招聘")) {
    replyContent = `👔 雇主招聘信息

当前热门需求:
• 住家保姆 - 月薪8000-12000元
• 育儿嫂 - 月薪10000-15000元
• 月嫂 - 月薪15000-25000元
• 钟点工 - 时薪50-80元

📞 如需发布招聘或应聘,请联系客服。`;
  } else if (content.includes("联系") || content.includes("客服")) {
    replyContent = `📞 联系方式

客服热线:400-123-4567
服务时间:09:00-18:00
微信客服:ayibang_service
在线咨询:www.ayibang.com

💬 您也可以直接在此对话,我们会及时回复。`;
  } else {
    replyContent = `收到您的消息:${content}

感谢您的关注!如需帮助请发送"帮助"查看可用功能。

当前时间:${new Date().toLocaleString('zh-CN')}`;
  }

  return `<xml>
    <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
    <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
    <CreateTime>${Math.floor(Date.now() / 1000)}</CreateTime>
    <MsgType><![CDATA[text]]></MsgType>
    <Content><![CDATA[${replyContent}]]></Content>
  </xml>`;
}

// 处理关注事件
async function handleSubscribeEvent(message: WeChatMessage): Promise<string> {
  const fromUserName = message.FromUserName ?? "";
  const eventKey = message.EventKey ?? "";
  
  console.log('用户关注事件:', { fromUserName, eventKey });

  // 异步处理数据库操作,不阻塞消息回复
  setTimeout(() => {
    void (async () => {
      try {
        await WechatUserService.handleSubscribeEvent({
          openid: fromUserName,
          eventKey: eventKey || undefined
        });
        console.log(`用户 ${fromUserName} 关注事件处理成功`);
      } catch (error) {
        console.error('处理关注事件失败:', error);
      }
    })();
  }, 100);

  const welcomeMessage = `🎉 欢迎关注阿姨帮!

感谢您的关注,我们将为您提供优质的家政服务信息。

您可以:
• 发送"帮助" - 查看功能介绍
• 发送"阿姨" - 了解阿姨信息
• 发送"雇主" - 了解雇主需求
• 发送"招聘" - 查看最新职位

🔥 优质阿姨推荐,专业服务保障
如有任何问题,请随时联系我们!`;

  return `<xml>
    <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
    <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
    <CreateTime>${Math.floor(Date.now() / 1000)}</CreateTime>
    <MsgType><![CDATA[text]]></MsgType>
    <Content><![CDATA[${welcomeMessage}]]></Content>
  </xml>`;
}

// 处理取消关注事件
async function handleUnsubscribeEvent(message: WeChatMessage): Promise<string> {
  const fromUserName = message.FromUserName ?? "";
  
  console.log('用户取消关注事件:', { fromUserName });

  setTimeout(() => {
    void (async () => {
      try {
        await WechatUserService.handleUnsubscribeEvent(fromUserName);
        console.log(`用户 ${fromUserName} 取消关注事件处理成功`);
      } catch (error) {
        console.error('处理取消关注事件失败:', error);
      }
    })();
  }, 100);

  // 取消关注不需要回复
  return "";
}

// 处理事件
async function handleEvent(message: WeChatMessage): Promise<string> {
  const event = message.Event ?? "";

  switch (event) {
    case "subscribe":
      return handleSubscribeEvent(message);
    case "unsubscribe":
      return handleUnsubscribeEvent(message);
    case "SCAN":
      // 处理扫码事件
      return `<xml>
        <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
        <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
        <CreateTime>${Math.floor(Date.now() / 1000)}</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[扫码成功!欢迎使用阿姨帮服务。]]></Content>
      </xml>`;
    default:
      return `<xml>
        <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
        <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
        <CreateTime>${Math.floor(Date.now() / 1000)}</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[收到事件:${event}]]></Content>
      </xml>`;
  }
}

// 处理消息
async function handleMessage(message: WeChatMessage): Promise<string> {
  console.log("收到微信消息:", message);

  switch (message.MsgType) {
    case "text":
      return handleTextMessage(message);
    case "event":
      return await handleEvent(message);
    default:
      return `<xml>
        <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
        <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
        <CreateTime>${Math.floor(Date.now() / 1000)}</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[暂不支持此类型消息,请发送文字消息。]]></Content>
      </xml>`;
  }
}

// 解析XML消息
function parseXMLMessage(xmlString: string): WeChatMessage {
  const message: WeChatMessage = {} as WeChatMessage;

  const patterns = {
    ToUserName: /<ToUserName><!\[CDATA\[(.*?)\]\]><\/ToUserName>/,
    FromUserName: /<FromUserName><!\[CDATA\[(.*?)\]\]><\/FromUserName>/,
    CreateTime: /<CreateTime>(.*?)<\/CreateTime>/,
    MsgType: /<MsgType><!\[CDATA\[(.*?)\]\]><\/MsgType>/,
    Content: /<Content><!\[CDATA\[(.*?)\]\]><\/Content>/,
    Event: /<Event><!\[CDATA\[(.*?)\]\]><\/Event>/,
    EventKey: /<EventKey><!\[CDATA\[(.*?)\]\]><\/EventKey>/,
    MsgId: /<MsgId>(.*?)<\/MsgId>/
  };

  Object.entries(patterns).forEach(([key, pattern]) => {
    const match = pattern.exec(xmlString);
    if (match) {
      if (key === 'CreateTime') {
        message[key] = parseInt(match[1]!);
      } else {
        message[key] = match[1]!;
      }
    }
  });

  return message;
}

// GET请求 - 微信服务器验证
export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const signature = searchParams.get("signature") ?? "";
    const timestamp = searchParams.get("timestamp") ?? "";
    const nonce = searchParams.get("nonce") ?? "";
    const echostr = searchParams.get("echostr") ?? "";

    console.log("微信服务器验证请求:", {
      signature,
      timestamp,
      nonce,
      echostr,
    });

    // 验证签名
    const token = env.WECHAT_TOKEN ?? "your-wechat-token";
    const isValid = verifySignature({ signature, timestamp, nonce }, token);
    
    if (isValid) {
      console.log("微信服务器验证成功");
      return new NextResponse(echostr, { status: 200 });
    } else {
      console.log("微信服务器验证失败");
      return new NextResponse("验证失败", { status: 403 });
    }
  } catch (error) {
    console.error("微信服务器验证错误:", error);
    return new NextResponse("服务器错误", { status: 500 });
  }
}

// POST请求 - 接收微信消息推送
export async function POST(request: NextRequest) {
  try {
    const xmlData = await request.text();
    console.log("收到微信推送消息:", xmlData);

    // 解析XML消息
    const message = parseXMLMessage(xmlData);
    console.log("解析后的消息:", message);

    // 处理消息并生成回复
    const reply = await handleMessage(message);

    if (reply) {
      console.log("回复消息:", reply);
      return new NextResponse(reply, {
        status: 200,
        headers: {
          "Content-Type": "application/xml; charset=utf-8",
        },
      });
    } else {
      // 不需要回复的消息(如取消关注)
      return new NextResponse("success", { status: 200 });
    }
  } catch (error) {
    console.error("处理微信消息推送错误:", error);
    return new NextResponse("success", { status: 200 });
  }
}

🧪 测试验证

1. 服务器验证测试

# 测试GET请求(服务器验证)
curl -X GET "https://yourdomain.com/api/wechat/message?signature=test&timestamp=1234567890&nonce=abc123&echostr=hello"

2. 消息推送测试

# 测试POST请求(消息推送)
curl -X POST "https://yourdomain.com/api/wechat/message" \
  -H "Content-Type: application/xml" \
  -d "<xml>
    <ToUserName><![CDATA[gh_test123456]]></ToUserName>
    <FromUserName><![CDATA[o_test123456]]></FromUserName>
    <CreateTime>1234567890</CreateTime>
    <MsgType><![CDATA[text]]></MsgType>
    <Content><![CDATA[测试消息]]></Content>
    <MsgId>123456789</MsgId>
  </xml>"

3. 功能测试页面

创建测试页面 /wechat-message-test

// src/app/wechat-message-test/page.tsx
"use client";

import { useState } from "react";

export default function WeChatMessageTestPage() {
  const [webhookUrl, setWebhookUrl] = useState("");
  const [testResult, setTestResult] = useState("");

  const generateWebhookUrl = () => {
    const url = `${window.location.origin}/api/wechat/message`;
    setWebhookUrl(url);
  };

  const testServerVerification = async () => {
    try {
      const params = new URLSearchParams({
        signature: "test_signature",
        timestamp: Date.now().toString(),
        nonce: "test_nonce",
        echostr: "test_echo"
      });
      
      const response = await fetch(`/api/wechat/message?${params}`);
      const result = await response.text();
      
      setTestResult(`服务器验证测试结果: ${response.status} - ${result}`);
    } catch (error) {
      setTestResult(`测试失败: ${error}`);
    }
  };

  const testMessagePush = async () => {
    try {
      const xmlMessage = `<xml>
        <ToUserName><![CDATA[gh_test123456]]></ToUserName>
        <FromUserName><![CDATA[o_test123456]]></FromUserName>
        <CreateTime>${Math.floor(Date.now() / 1000)}</CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[帮助]]></Content>
        <MsgId>123456789</MsgId>
      </xml>`;

      const response = await fetch("/api/wechat/message", {
        method: "POST",
        headers: { "Content-Type": "application/xml" },
        body: xmlMessage,
      });

      const result = await response.text();
      setTestResult(`消息推送测试结果: ${response.status} - ${result}`);
    } catch (error) {
      setTestResult(`测试失败: ${error}`);
    }
  };

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-2xl font-bold mb-6">微信消息推送测试</h1>
      
      <div className="space-y-6">
        <div className="bg-white p-6 rounded-lg shadow">
          <h2 className="text-xl font-semibold mb-4">Webhook URL</h2>
          <div className="flex gap-2">
            <input
              type="text"
              value={webhookUrl}
              readOnly
              placeholder="点击生成按钮获取URL"
              className="flex-1 px-3 py-2 border rounded"
            />
            <button
              onClick={generateWebhookUrl}
              className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
            >
              生成URL
            </button>
          </div>
        </div>

        <div className="bg-white p-6 rounded-lg shadow">
          <h2 className="text-xl font-semibold mb-4">功能测试</h2>
          <div className="space-y-4">
            <button
              onClick={testServerVerification}
              className="w-full px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
            >
              测试服务器验证
            </button>
            
            <button
              onClick={testMessagePush}
              className="w-full px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
            >
              测试消息推送
            </button>
          </div>
        </div>

        {testResult && (
          <div className="bg-gray-100 p-4 rounded-lg">
            <h3 className="font-semibold mb-2">测试结果:</h3>
            <pre className="text-sm">{testResult}</pre>
          </div>
        )}
      </div>
    </div>
  );
}

🔍 故障排除

常见错误及解决方案

1. 服务器验证失败

错误现象: 微信公众平台显示"Token验证失败"

可能原因:

  • Token不匹配
  • 签名算法错误
  • 服务器不可访问

解决方案:

// 检查Token配置
console.log("环境变量Token:", process.env.WECHAT_TOKEN);

// 检查签名算法
function debugSignature(timestamp: string, nonce: string, token: string) {
  const tmpArr = [token, timestamp, nonce].sort();
  console.log("排序后数组:", tmpArr);
  const tmpStr = tmpArr.join("");
  console.log("拼接字符串:", tmpStr);
  const hash = crypto.createHash("sha1").update(tmpStr).digest("hex");
  console.log("生成签名:", hash);
  return hash;
}
2. 消息无响应

错误现象: 用户发送消息后无回复

可能原因:

  • XML解析错误
  • 消息处理异常
  • 响应格式错误

解决方案:

// 添加详细日志
console.log("原始XML:", xmlData);
console.log("解析消息:", message);
console.log("生成回复:", reply);

// 验证XML格式
function validateXMLResponse(xml: string): boolean {
  return xml.includes('<xml>') && 
         xml.includes('</xml>') && 
         xml.includes('<ToUserName>') && 
         xml.includes('<FromUserName>');
}
3. 数据库连接错误

错误现象: 用户信息保存失败

解决方案:

// 添加错误处理
try {
  await WechatUserService.handleSubscribeEvent(eventInfo);
} catch (error) {
  console.error('数据库操作失败:', error);
  // 继续返回欢迎消息,不影响用户体验
}
4. IP白名单问题

错误现象: 获取access_token失败

解决方案:

  1. 检查服务器公网IP
  2. 在微信公众平台添加IP到白名单
  3. 使用IP检查接口验证:/api/wechat/check-ip

调试工具

1. 日志查看
# 查看服务器日志
tail -f logs/app.log

# 查看特定错误
grep "微信" logs/app.log
2. 接口测试
# 健康检查
curl https://yourdomain.com/api/health

# IP检查
curl https://yourdomain.com/api/wechat/check-ip

📈 性能优化

1. 异步处理

// 不阻塞主流程的异步处理
setTimeout(() => {
  void (async () => {
    await heavyOperation();
  })();
}, 100);

2. 缓存机制

// 用户信息缓存
const userCache = new Map();

async function getCachedUserInfo(openid: string) {
  if (userCache.has(openid)) {
    return userCache.get(openid);
  }
  
  const userInfo = await fetchUserInfo(openid);
  userCache.set(openid, userInfo);
  
  // 设置过期时间
  setTimeout(() => {
    userCache.delete(openid);
  }, 30 * 60 * 1000); // 30分钟
  
  return userInfo;
}

3. 错误重试

async function retryOperation(operation: () => Promise<any>, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await operation();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

🛡️ 安全考虑

1. 签名验证

// 严格验证微信签名
function strictSignatureVerification(params: any, token: string): boolean {
  const { signature, timestamp, nonce } = params;
  
  // 验证时间戳(避免重放攻击)
  const now = Math.floor(Date.now() / 1000);
  const timeGap = Math.abs(now - parseInt(timestamp));
  if (timeGap > 300) { // 5分钟超时
    return false;
  }
  
  // 验证签名
  const tmpArr = [token, timestamp, nonce].sort();
  const tmpStr = tmpArr.join("");
  const hash = crypto.createHash("sha1").update(tmpStr).digest("hex");
  
  return hash === signature;
}

2. 输入过滤

// 过滤恶意输入
function sanitizeInput(input: string): string {
  return input
    .replace(/<script[^>]*>.*?<\/script>/gi, '')
    .replace(/<[^>]+>/g, '')
    .trim()
    .substring(0, 1000); // 限制长度
}

3. 频率限制

// 简单的频率限制
const rateLimiter = new Map();

function checkRateLimit(openid: string): boolean {
  const key = `rate_limit_${openid}`;
  const now = Date.now();
  const lastRequest = rateLimiter.get(key) || 0;
  
  if (now - lastRequest < 1000) { // 1秒内只能发送一条
    return false;
  }
  
  rateLimiter.set(key, now);
  return true;
}

🚀 部署指南

1. 环境变量检查

# 生产环境必要变量
export WECHAT_APP_ID="your_app_id"
export WECHAT_APP_SECRET="your_app_secret"
export WECHAT_TOKEN="your_token"
export DATABASE_URL="your_database_url"
export NEXTAUTH_URL="https://yourdomain.com"

2. 数据库迁移

# 生产环境迁移
npx prisma migrate deploy

# 验证数据库连接
npx prisma db pull

3. 服务启动

# 构建项目
npm run build

# 启动服务
npm run start

# 使用PM2管理
pm2 start npm --name "wechat-app" -- start

4. 监控配置

// 健康检查接口
export async function GET() {
  try {
    // 检查数据库连接
    await db.$queryRaw`SELECT 1`;
    
    // 检查微信配置
    const hasWechatConfig = !!(process.env.WECHAT_APP_ID && process.env.WECHAT_APP_SECRET);
    
    return NextResponse.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      database: 'connected',
      wechat: hasWechatConfig ? 'configured' : 'missing_config'
    });
  } catch (error) {
    return NextResponse.json({
      status: 'unhealthy',
      error: error.message
    }, { status: 500 });
  }
}

📚 最佳实践

1. 消息回复策略

  • 5秒内必须响应
  • 回复内容要有帮助性
  • 支持关键词智能匹配
  • 提供清晰的菜单指引

2. 用户体验优化

  • 快速响应用户操作
  • 提供有意义的错误提示
  • 支持多轮对话
  • 个性化推荐内容

3. 数据管理

  • 定期清理过期数据
  • 备份用户重要信息
  • 监控系统性能指标
  • 记录关键操作日志

4. 运维监控

  • 设置告警机制
  • 监控接口响应时间
  • 统计用户活跃度
  • 分析消息类型分布

📖 参考资料


💡 提示: 本文档基于实际项目经验编写,涵盖了从配置到部署的完整流程。如有疑问,请参考项目中的实际代码实现或联系技术支持。