WeChat Official Account
WeChat Official Account
The system provides comprehensive WeChat Official Account integration and management capabilities, supporting multi-account binding, message receiving/replying, material management, menu management, template messages, WeChat Pay, and more.
Feature Overview
| Module | Feature | Description |
|---|---|---|
| Account Management | Multi-account | Bind multiple official accounts, support plaintext/compatible/encrypted message modes |
| Message Management | Send/Receive logs | Complete message logs with conversation view |
| Auto Reply | Keyword/Follow reply | Support text, image, news, music reply types |
| Material Management | Temp/Permanent | Upload, sync, local storage for image/voice/video/news |
| Menu Management | Custom Menu | Visual menu editor, one-click publish/sync/delete |
| User Management | Fan Info | Track follow/unfollow, interaction time, message count |
| Template Message | Industry Templates | Sync templates from WeChat, send locally, log delivery |
| WeChat Pay | JSAPI/Native | Create orders, query, close, refund, callback handling |
Database Table Structure
All WeChat-related tables use Snowflake ID as primary key instead of auto-increment ID, ensuring ID uniqueness in distributed environments.
wx_accounts — Official Account (main table)
├── wx_auto_replies — Auto-reply rules
├── wx_materials — Material library
├── wx_menus — Custom menus
├── wx_messages — Message records
├── wx_users — Fan users
├── wx_templates — Template messages
├── wx_template_logs— Template send logs
├── wx_pay_orders — Payment orders
└── wx_pay_refunds — Refund recordswx_accounts (Official Account)
| Field | Type | Description |
|---|---|---|
| id | i64 (Snowflake) | Primary key |
| app_id | String (Unique) | Official Account AppID |
| app_secret | String | Official Account AppSecret |
| account_name | String | Account name |
| account_type | i8 | Account type |
| original_id | String | Original ID (gh_xxx) |
| wechat_id | String | WeChat ID |
| status | i8 | Status |
| message_mode | i8 | Message mode: 0=plaintext 1=compatible 2=encrypted |
| access_token | String | Cached AccessToken |
| token_expires_at | DateTime | Token expiry time |
| server_url | String | Callback server URL |
| token | String | Message verification Token |
| encoding_aes_key | String | Message encryption/decryption key |
wx_messages (Message Records)
| Field | Type | Description |
|---|---|---|
| id | i64 (Snowflake) | Primary key |
| account_id | i64 (FK) | Associated official account |
| openid | String | User OpenID |
| msg_id | i64 | WeChat message ID |
| msg_type | String | Message type: text/image/voice/video/event etc. |
| direction | i8 | Direction: 0=reply 1=received |
| content | Text | Text content |
| pic_url | String | Image URL (async download to local) |
| event_type | String | Event type: subscribe/unsubscribe/CLICK etc. |
| event_key | String | Event Key |
| is_auto_reply | i8 | Is auto-reply |
wx_auto_replies (Auto-Reply Rules)
| Field | Type | Description |
|---|---|---|
| id | i64 (Snowflake) | Primary key |
| account_id | i64 (FK) | Associated official account |
| reply_type | i8 | Rule type: 1=follow reply 2=keyword reply |
| keyword | String | Match keyword |
| match_type | i8 | Match mode: 1=exact 2=contains |
| message_type | String | Reply type: text/image/news/music |
| content | Text | Reply text content |
| media_id | String | Reply material media_id |
| priority | i32 | Priority (higher = higher priority) |
| status | i8 | Status: 0=disabled 1=enabled |
wx_materials (Material Library)
| Field | Type | Description |
|---|---|---|
| id | i64 (Snowflake) | Primary key |
| account_id | i64 (FK) | Associated official account |
| media_type | String | Material type: image/voice/video/thumb/news |
| media_id | String | WeChat material ID |
| name | String | File name |
| url | String | WeChat server URL |
| local_path | String | Local storage path |
| file_size | i64 | File size (bytes) |
| width / height | i32 | Image dimensions |
| is_permanent | i8 | 0=temporary 1=permanent |
| sync_status | i8 | Sync status: 0=not synced 1=synced |
wx_menus (Custom Menu)
| Field | Type | Description |
|---|---|---|
| id | i64 (Snowflake) | Primary key |
| account_id | i64 (FK) | Associated official account |
| parent_id | i64 | Parent menu ID (sub-menu) |
| menu_name | String | Menu name |
| menu_type | String | Menu type: click/view/scancode_push etc. |
| menu_key | String | Menu Key value |
| url | String | Redirect URL |
| media_id | String | Material media_id |
| appid | String | Mini-program appid |
| pagepath | String | Mini-program page path |
| sort_order | i32 | Sort order |
wx_users (Fan Users)
| Field | Type | Description |
|---|---|---|
| id | i64 (Snowflake) | Primary key |
| account_id | i64 (FK) | Associated official account |
| openid | String | User OpenID |
| unionid | String | UnionID |
| nickname | String | Nickname |
| headimgurl | String | Avatar |
| subscribe_status | i8 | Subscribe status: 0=not subscribed 1=subscribed |
| subscribe_time | DateTime | Subscribe time |
| unsubscribe_time | DateTime | Unsubscribe time |
| last_interact_time | DateTime | Last interaction time |
| message_count | i32 | Message count |
wx_templates (Template Messages) & wx_template_logs (Send Logs)
| Field | Type | Description |
|---|---|---|
| id | i64 (Snowflake) | Primary key |
| account_id | i64 (FK) | Associated official account |
| template_id | String | WeChat template ID |
| title | String | Template title |
| content | Text | Template content |
| status | i8 | Status |
wx_pay_orders (Payment Orders) & wx_pay_refunds (Refund Records)
| Field | Type | Description |
|---|---|---|
| id | i64 (Snowflake) | Primary key |
| account_id | i64 (FK) | Associated official account |
| out_trade_no | String | Merchant order number |
| openid | String | Payer OpenID |
| total_fee | i64 | Amount (cents) |
| trade_state | String | Trade state |
| prepay_id | String | Prepay ID |
| code_url | String | QR code URL (Native payment) |
API Endpoints
All endpoints require JWT authentication, except WeChat callback endpoints.
Authenticated Endpoints
| Path | Method | Description |
|---|---|---|
/wechat/wxaccounts/list | GET | Get account list |
/wechat/wxaccounts/add | POST | Add account |
/wechat/wxaccounts/edit | PUT | Edit account |
/wechat/wxaccounts/del | DELETE | Delete account |
/wechat/wxautoreplies/list | GET | Get auto-reply list |
/wechat/wxautoreplies/add | POST | Add auto-reply |
/wechat/wxautoreplies/edit | PUT | Edit auto-reply |
/wechat/wxautoreplies/del | DELETE | Delete auto-reply |
/wechat/wxmaterials/list | GET | Get material list |
/wechat/wxmaterials/add | POST | Add material |
/wechat/wxmaterials/edit | PUT | Edit material |
/wechat/wxmaterials/del | DELETE | Delete material |
/wechat/wxmaterials/upload_temp_media | POST | Upload temp media to WeChat |
/wechat/wxmaterials/upload_permanent_media | POST | Upload permanent media to WeChat |
/wechat/wxmaterials/upload_news | POST | Upload permanent news material |
/wechat/wxmaterials/sync_materials | POST | Sync materials from WeChat |
/wechat/wxmaterials/material_count | POST | Get WeChat material count |
/wechat/wxmaterials/delete_remote_media | POST | Delete permanent media from WeChat |
/wechat/wxmaterials/upload_file | POST | Upload file to local server |
/wechat/wxmenus/list | GET | Get menu list |
/wechat/wxmenus/add | POST | Add menu |
/wechat/wxmenus/edit | PUT | Edit menu |
/wechat/wxmenus/del | DELETE | Delete menu |
/wechat/wxmenus/pull_menu | POST | Publish menu to WeChat |
/wechat/wxmenus/sync_menu | POST | Sync menu from WeChat |
/wechat/wxmenus/delete_remote_menu | POST | Delete custom menu from WeChat |
/wechat/wxmessages/list | GET | Get message list |
/wechat/wxmessages/add | POST | Add message (customer service) |
/wechat/wxmessages/edit | PUT | Edit message |
/wechat/wxmessages/del | DELETE | Delete message |
/wechat/wxmessages/conversation | GET | Get conversation messages |
/wechat/wxmessages/reply | POST | Reply message |
/wechat/wxusers/list | GET | Get fan list |
/wechat/wxusers/add | POST | Add fan |
/wechat/wxusers/edit | PUT | Edit fan |
/wechat/wxusers/del | DELETE | Delete fan |
/wechat/wxtemplates/list | GET | Get template list |
/wechat/wxtemplates/add | POST | Add template |
/wechat/wxtemplates/edit | PUT | Edit template |
/wechat/wxtemplates/del | DELETE | Delete template |
/wechat/wxtemplates/sync | POST | Sync templates from WeChat |
/wechat/wxtemplates/send | POST | Send template message |
/wechat/wxtemplates/logs | GET | Get template send logs |
/wechat/wxpay/orders | GET | Get payment order list |
/wechat/wxpay/refunds | GET | Get refund list |
/wechat/wxpay/create_order | POST | Create payment order |
/wechat/wxpay/query_order | POST | Query order status |
/wechat/wxpay/close_order | POST | Close order |
/wechat/wxpay/refund | POST | Apply for refund |
Unauthenticated Callback Endpoints (WeChat Server Calls)
| Path | Method | Description |
|---|---|---|
/wechat/official_account/callback | GET | WeChat server verification |
/wechat/official_account/callback | POST | WeChat message/event callback |
/wechat/wxpay/notify/{account_id} | POST | WeChat Pay callback notification |
Message Processing Flow
WeChat Server
│ POST /wechat/official_account/callback
▼
┌─────────────────────────────┐
│ 1. Signature Verification │
│ verify_request_signature │
├─────────────────────────────┤
│ 2. Message Decryption │
│ (encrypted mode) │
│ WechatCrypto::decrypt │
├─────────────────────────────┤
│ 3. Store Received Message │
│ WxMessagesModel::add │
│ direction = 1 (received) │
├─────────────────────────────┤
│ 4. Async Image Download │
│ WxImageDownloadWorker │
│ (mmbiz.qpic.cn → local) │
├─────────────────────────────┤
│ 5. Message Processing & │
│ Auto Reply │
│ ┌─ Text → Keyword match │
│ ├─ Subscribe → Follow │
│ ├─ Click → Event reply │
│ └─ Unsubscribe → Update │
├─────────────────────────────┤
│ 6. Store Reply Message │
│ WxMessagesModel::add │
│ direction = 0 (reply) │
└─────────────────────────────┘
│ XML Reply
▼
WeChat ServerAuto-Reply Mechanism
The system supports three types of auto-replies, processed by priority from high to low:
Keyword Reply (reply_type = 2)
- Match by priority descending
- Support exact match (match_type = 1) and contains match (match_type = 2)
- Support reply types: text, image, news, music
Follow Reply (reply_type = 1)
- Automatically triggered when user follows the account
- Also creates/updates fan user record
Default Reply
- Returned when no rules match
Message Encryption/Decryption
The system supports three message modes:
| Mode | message_mode | Description |
|---|---|---|
| Plaintext | 0 | Messages not encrypted, parse XML directly |
| Compatible | 1 | Plaintext parsing (compatible with encrypted messages) |
| Security | 2 | AES encryption/decryption, requires EncodingAESKey |
Frontend Pages
| Page Path | Feature |
|---|---|
/wechat/wxaccounts | Account management |
/wechat/wxautoreplies | Auto-reply rule configuration |
/wechat/wxmaterials | Material management (upload/sync/preview) |
/wechat/wxmenus | Custom menu editor |
/wechat/wxmessages | Message records & conversations |
/wechat/wxusers | Fan user management |
Technical Notes
Snowflake ID
All WeChat module tables use Snowflake ID instead of auto-increment ID:
- Distributed uniqueness: No ID conflicts in multi-instance deployments
- Non-guessable: Avoids security risk of data traversal via ID
- Time-ordered: ID itself contains time information, naturally ordered
Implementation: Set auto_increment = false in SeaORM Entity, use GID() to generate ID in the Model layer add method.
UTF-8 Charset Fix
WeChat-related tables have MySQL charset fixed via Migration:
ALTER TABLE wx_materials CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Ensures Chinese content (messages, templates, logs, etc.) is correctly stored and retrieved.
Async Image Download
Image URLs (mmbiz.qpic.cn) in WeChat image messages have time limits. The system uses WxImageDownloadWorker to download images asynchronously:
Receive image message → Record message → Enqueue download task → Worker downloads image → Update local_path