maxiaolong 1 місяць тому
батько
коміт
d93085ecf3

+ 222 - 37
app/api/data_parse/routes.py

@@ -60,7 +60,9 @@ from app.core.data_parse.calendar import (
     login_wechat_user,
     logout_wechat_user,
     get_wechat_user_info,
-    update_wechat_user_info
+    update_wechat_user_info,
+    save_calendar_record,
+    get_calendar_record
 )
 from app.config.config import DevelopmentConfig, ProductionConfig
 import logging
@@ -2424,13 +2426,14 @@ def wechat_register_api():
     
     Request Body:
     {
-        "wechat_code": "wx_openid_123",      // 必填:微信授权码/openid
-        "phone_number": "13800138000",       // 可选:手机号码
-        "id_card_number": "110101199001011234" // 可选:身份证号码
+        "wechat_code": "wx_code_12345",         // 必填:微信授权码(15分钟有效期)
+        "phone_number": "13800138000",          // 可选:手机号码
+        "id_card_number": "110101199001011234", // 可选:身份证号码
+        "platform": "miniprogram"              // 可选:微信平台类型,默认为小程序
     }
     
     Returns:
-        JSON: 包含注册结果的响应数据
+        JSON: 包含注册结果的响应数据,成功时返回用户openid等信息
     """
     try:
         # 获取请求数据
@@ -2458,12 +2461,13 @@ def wechat_register_api():
         # 获取可选参数
         phone_number = data.get('phone_number')
         id_card_number = data.get('id_card_number')
+        platform = data.get('platform', 'miniprogram')
         
         # 记录请求日志
-        logger.info(f"收到微信用户注册请求,wechat_code: {wechat_code}")
+        logger.info(f"收到微信用户注册请求,wechat_code: {wechat_code}, platform: {platform}")
         
         # 调用核心业务逻辑
-        result = register_wechat_user(wechat_code, phone_number, id_card_number)
+        result = register_wechat_user(wechat_code, phone_number, id_card_number, platform)
         
         # 根据返回结果设置HTTP状态码
         status_code = result.get('return_code', 500)
@@ -2501,11 +2505,12 @@ def wechat_login_api():
     
     Request Body:
     {
-        "wechat_code": "wx_openid_123"  // 必填:微信授权码/openid
+        "wechat_code": "wx_code_12345",  // 必填:微信授权码(15分钟有效期)
+        "platform": "miniprogram"       // 可选:微信平台类型,默认为小程序
     }
     
     Returns:
-        JSON: 包含登录结果的响应数据
+        JSON: 包含登录结果的响应数据,成功时返回用户openid等信息
     """
     try:
         # 获取请求数据
@@ -2530,11 +2535,14 @@ def wechat_login_api():
                 'error': '缺少必填参数: wechat_code'
             }), 400
         
+        # 获取可选参数
+        platform = data.get('platform', 'miniprogram')
+        
         # 记录请求日志
-        logger.info(f"收到微信用户登录请求,wechat_code: {wechat_code}")
+        logger.info(f"收到微信用户登录请求,wechat_code: {wechat_code}, platform: {platform}")
         
         # 调用核心业务逻辑
-        result = login_wechat_user(wechat_code)
+        result = login_wechat_user(wechat_code, platform)
         
         # 根据返回结果设置HTTP状态码
         status_code = result.get('return_code', 500)
@@ -2572,7 +2580,7 @@ def wechat_logout_api():
     
     Request Body:
     {
-        "wechat_code": "wx_openid_123"  // 必填:微信授权码/openid
+        "openid": "wx_openid_abcd1234567890"  // 必填:微信用户openid
     }
     
     Returns:
@@ -2592,30 +2600,30 @@ def wechat_logout_api():
             }), 400
         
         # 验证必填参数
-        wechat_code = data.get('wechat_code')
-        if not wechat_code:
+        openid = data.get('openid')
+        if not openid:
             return jsonify({
                 'reason': 'failed',
                 'return_code': 400,
                 'result': None,
-                'error': '缺少必填参数: wechat_code'
+                'error': '缺少必填参数: openid'
             }), 400
         
         # 记录请求日志
-        logger.info(f"收到微信用户登出请求,wechat_code: {wechat_code}")
+        logger.info(f"收到微信用户登出请求,openid: {openid}")
         
         # 调用核心业务逻辑
-        result = logout_wechat_user(wechat_code)
+        result = logout_wechat_user(openid)
         
         # 根据返回结果设置HTTP状态码
         status_code = result.get('return_code', 500)
         
         # 记录处理结果日志
         if result.get('return_code') == 200:
-            logger.info(f"微信用户登出成功,wechat_code: {wechat_code}")
+            logger.info(f"微信用户登出成功,openid: {openid}")
         else:
             error_msg = result.get('error', '未知错误')
-            logger.warning(f"微信用户登出失败,wechat_code: {wechat_code},错误: {error_msg}")
+            logger.warning(f"微信用户登出失败,openid: {openid},错误: {error_msg}")
         
         # 返回结果
         return jsonify(result), status_code
@@ -2639,42 +2647,42 @@ def wechat_get_user_info_api():
     """
     获取微信用户信息接口
     
-    GET /api/parse/wechat-user?wechat_code=wx_openid_123
+    GET /api/parse/wechat-user?openid=wx_openid_abcd1234567890
     
     Args:
-        wechat_code (str): 微信授权码/openid,作为查询参数
+        openid (str): 微信用户openid,作为查询参数
         
     Returns:
         JSON: 包含用户信息的响应数据
     """
     try:
         # 获取查询参数
-        wechat_code = request.args.get('wechat_code')
+        openid = request.args.get('openid')
         
         # 验证必填参数
-        if not wechat_code:
+        if not openid:
             return jsonify({
                 'reason': 'failed',
                 'return_code': 400,
                 'result': None,
-                'error': '缺少必填参数: wechat_code'
+                'error': '缺少必填参数: openid'
             }), 400
         
         # 记录请求日志
-        logger.info(f"收到获取微信用户信息请求,wechat_code: {wechat_code}")
+        logger.info(f"收到获取微信用户信息请求,openid: {openid}")
         
         # 调用核心业务逻辑
-        result = get_wechat_user_info(wechat_code)
+        result = get_wechat_user_info(openid)
         
         # 根据返回结果设置HTTP状态码
         status_code = result.get('return_code', 500)
         
         # 记录处理结果日志
         if result.get('return_code') == 200:
-            logger.info(f"获取微信用户信息成功,wechat_code: {wechat_code}")
+            logger.info(f"获取微信用户信息成功,openid: {openid}")
         else:
             error_msg = result.get('error', '未知错误')
-            logger.warning(f"获取微信用户信息失败,wechat_code: {wechat_code},错误: {error_msg}")
+            logger.warning(f"获取微信用户信息失败,openid: {openid},错误: {error_msg}")
         
         # 返回结果
         return jsonify(result), status_code
@@ -2702,7 +2710,7 @@ def wechat_update_user_info_api():
     
     Request Body:
     {
-        "wechat_code": "wx_openid_123",        // 必填:微信授权码/openid
+        "openid": "wx_openid_abcd1234567890",  // 必填:微信用户openid
         "phone_number": "13900139000",         // 可选:要更新的手机号码
         "id_card_number": "110101199001011234" // 可选:要更新的身份证号码
     }
@@ -2724,16 +2732,16 @@ def wechat_update_user_info_api():
             }), 400
         
         # 验证必填参数
-        wechat_code = data.get('wechat_code')
-        if not wechat_code:
+        openid = data.get('openid')
+        if not openid:
             return jsonify({
                 'reason': 'failed',
                 'return_code': 400,
                 'result': None,
-                'error': '缺少必填参数: wechat_code'
+                'error': '缺少必填参数: openid'
             }), 400
         
-        # 构建更新数据,排除wechat_code
+        # 构建更新数据,排除openid
         update_data = {}
         if 'phone_number' in data:
             update_data['phone_number'] = data['phone_number']
@@ -2750,20 +2758,20 @@ def wechat_update_user_info_api():
             }), 400
         
         # 记录请求日志
-        logger.info(f"收到更新微信用户信息请求,wechat_code: {wechat_code}, 更新字段: {list(update_data.keys())}")
+        logger.info(f"收到更新微信用户信息请求,openid: {openid}, 更新字段: {list(update_data.keys())}")
         
         # 调用核心业务逻辑
-        result = update_wechat_user_info(wechat_code, update_data)
+        result = update_wechat_user_info(openid, update_data)
         
         # 根据返回结果设置HTTP状态码
         status_code = result.get('return_code', 500)
         
         # 记录处理结果日志
         if result.get('return_code') == 200:
-            logger.info(f"更新微信用户信息成功,wechat_code: {wechat_code}")
+            logger.info(f"更新微信用户信息成功,openid: {openid}")
         else:
             error_msg = result.get('error', '未知错误')
-            logger.warning(f"更新微信用户信息失败,wechat_code: {wechat_code},错误: {error_msg}")
+            logger.warning(f"更新微信用户信息失败,openid: {openid},错误: {error_msg}")
         
         # 返回结果
         return jsonify(result), status_code
@@ -2773,6 +2781,183 @@ def wechat_update_user_info_api():
         error_msg = f"更新微信用户信息接口失败: {str(e)}"
         logger.error(error_msg, exc_info=True)
         
+        # 返回错误响应
+        return jsonify({
+            'reason': 'failed',
+            'return_code': 500,
+            'result': None,
+            'error': error_msg
+        }), 500
+
+
+# ================================
+# 日历记录相关API路由
+# ================================
+
+@bp.route('/save-calendar-record', methods=['POST'])
+def save_calendar_record_api():
+    """
+    保存日历记录接口
+    
+    POST /api/parse/save-calendar-record
+    
+    Request Body:
+    {
+        "openid": "wx_openid_abcd1234567890123456",  // 必填:微信用户openid
+        "month_key": "2024-01",                      // 必填:月份标识(YYYY-MM格式)
+        "calendar_content": [                        // 必填:日历内容(JSON数组)
+            {
+                "date": "2024-01-01",
+                "events": ["元旦节"],
+                "notes": "新年快乐"
+            },
+            {
+                "date": "2024-01-15",
+                "events": ["会议", "约会"],
+                "notes": "重要日程"
+            }
+        ]
+    }
+    
+    Returns:
+        JSON: 包含保存结果的响应数据
+    """
+    try:
+        # 获取请求数据
+        data = request.get_json()
+        
+        # 验证请求数据
+        if not data:
+            return jsonify({
+                'reason': 'failed',
+                'return_code': 400,
+                'result': None,
+                'error': '请求体不能为空'
+            }), 400
+        
+        # 验证必填参数
+        openid = data.get('openid')
+        month_key = data.get('month_key')
+        calendar_content = data.get('calendar_content')
+        
+        if not openid:
+            return jsonify({
+                'reason': 'failed',
+                'return_code': 400,
+                'result': None,
+                'error': '缺少必填参数: openid'
+            }), 400
+        
+        if not month_key:
+            return jsonify({
+                'reason': 'failed',
+                'return_code': 400,
+                'result': None,
+                'error': '缺少必填参数: month_key'
+            }), 400
+        
+        if calendar_content is None:
+            return jsonify({
+                'reason': 'failed',
+                'return_code': 400,
+                'result': None,
+                'error': '缺少必填参数: calendar_content'
+            }), 400
+        
+        # 记录请求日志
+        logger.info(f"收到保存日历记录请求,openid: {openid}, month_key: {month_key}")
+        
+        # 调用核心业务逻辑
+        result = save_calendar_record(data)
+        
+        # 根据返回结果设置HTTP状态码
+        status_code = result.get('return_code', 500)
+        
+        # 记录处理结果日志
+        if result.get('return_code') == 200:
+            logger.info(f"保存日历记录成功,openid: {openid}, month_key: {month_key}")
+        else:
+            error_msg = result.get('error', '未知错误')
+            logger.warning(f"保存日历记录失败,openid: {openid}, month_key: {month_key},错误: {error_msg}")
+        
+        # 返回结果
+        return jsonify(result), status_code
+        
+    except Exception as e:
+        # 记录错误日志
+        error_msg = f"保存日历记录接口失败: {str(e)}"
+        logger.error(error_msg, exc_info=True)
+        
+        # 返回错误响应
+        return jsonify({
+            'reason': 'failed',
+            'return_code': 500,
+            'result': None,
+            'error': error_msg
+        }), 500
+
+
+@bp.route('/get-calendar-record', methods=['GET'])
+def get_calendar_record_api():
+    """
+    获取日历记录接口
+    
+    GET /api/parse/get-calendar-record?openid=wx_openid_abcd1234567890123456&month_key=2024-01
+    
+    Args:
+        openid (str): 微信用户openid,作为查询参数
+        month_key (str): 月份标识(YYYY-MM格式),作为查询参数
+        
+    Returns:
+        JSON: 包含查询结果的响应数据
+    """
+    try:
+        # 获取查询参数
+        openid = request.args.get('openid')
+        month_key = request.args.get('month_key')
+        
+        # 验证必填参数
+        if not openid:
+            return jsonify({
+                'reason': 'failed',
+                'return_code': 400,
+                'result': None,
+                'error': '缺少必填参数: openid'
+            }), 400
+        
+        if not month_key:
+            return jsonify({
+                'reason': 'failed',
+                'return_code': 400,
+                'result': None,
+                'error': '缺少必填参数: month_key'
+            }), 400
+        
+        # 记录请求日志
+        logger.info(f"收到获取日历记录请求,openid: {openid}, month_key: {month_key}")
+        
+        # 调用核心业务逻辑
+        result = get_calendar_record(openid, month_key)
+        
+        # 根据返回结果设置HTTP状态码
+        status_code = result.get('return_code', 500)
+        
+        # 记录处理结果日志
+        if result.get('return_code') == 200:
+            has_content = result.get('result', {}).get('id') is not None
+            logger.info(f"获取日历记录成功,openid: {openid}, month_key: {month_key}, 有记录: {has_content}")
+        else:
+            error_msg = result.get('error', '未知错误')
+            logger.warning(f"获取日历记录失败,openid: {openid}, month_key: {month_key},错误: {error_msg}")
+        
+        # 返回结果
+        return jsonify(result), status_code
+        
+    except Exception as e:
+        # 记录错误日志
+        error_msg = f"获取日历记录接口失败: {str(e)}"
+        logger.error(error_msg, exc_info=True)
+        
         # 返回错误响应
         return jsonify({
             'reason': 'failed',

Різницю між файлами не показано, бо вона завелика
+ 881 - 76
app/core/data_parse/calendar.py


+ 213 - 0
app/core/data_parse/wechat_api.py

@@ -0,0 +1,213 @@
+"""
+微信API服务
+提供微信小程序和公众号相关的API调用功能
+"""
+
+import requests
+import json
+import logging
+from typing import Optional, Dict, Any, Tuple
+from .wechat_config import get_wechat_config, get_error_message, validate_wechat_config
+
+logger = logging.getLogger(__name__)
+
+
+class WechatApiService:
+    """
+    微信API服务类
+    提供微信相关API的调用功能
+    """
+    
+    def __init__(self, platform: str = 'miniprogram'):
+        """
+        初始化微信API服务
+        
+        Args:
+            platform (str): 微信平台类型,'miniprogram' 或 'official_account'
+        """
+        self.platform = platform
+        self.config = get_wechat_config(platform)
+        
+        # 验证配置
+        if not validate_wechat_config(platform):
+            logger.warning(f"微信{platform}配置不完整,请检查环境变量")
+    
+    def code_to_openid(self, code: str) -> Tuple[bool, Optional[str], Optional[str]]:
+        """
+        使用微信授权码换取用户openid
+        
+        Args:
+            code (str): 微信授权码(有效期15分钟)
+            
+        Returns:
+            Tuple[bool, Optional[str], Optional[str]]: 
+            (是否成功, openid, 错误信息)
+        """
+        try:
+            # 验证配置
+            if not validate_wechat_config(self.platform):
+                return False, None, "微信API配置不完整"
+            
+            # 构建请求参数
+            if self.platform == 'miniprogram':
+                params = {
+                    'appid': self.config['app_id'],
+                    'secret': self.config['app_secret'],
+                    'js_code': code,
+                    'grant_type': self.config['grant_type']
+                }
+                url = self.config['code2session_url']
+            else:  # official_account
+                params = {
+                    'appid': self.config['app_id'],
+                    'secret': self.config['app_secret'],
+                    'code': code,
+                    'grant_type': self.config['grant_type']
+                }
+                url = self.config['oauth2_url']
+            
+            # 记录请求日志
+            logger.info(f"请求微信API获取openid,平台: {self.platform}")
+            
+            # 发起API请求
+            response = requests.get(
+                url,
+                params=params,
+                timeout=self.config['timeout']
+            )
+            
+            if response.status_code != 200:
+                error_msg = f"微信API请求失败,HTTP状态码: {response.status_code}"
+                logger.error(error_msg)
+                return False, None, error_msg
+            
+            # 解析响应
+            result = response.json()
+            logger.debug(f"微信API响应: {json.dumps(result, ensure_ascii=False)}")
+            
+            # 检查是否有错误
+            if 'errcode' in result:
+                error_code = result['errcode']
+                if error_code != 0:
+                    error_msg = get_error_message(error_code)
+                    logger.error(f"微信API返回错误: {error_code} - {error_msg}")
+                    return False, None, error_msg
+            
+            # 获取openid
+            openid = result.get('openid')
+            if not openid:
+                error_msg = "微信API返回数据中缺少openid"
+                logger.error(error_msg)
+                return False, None, error_msg
+            
+            logger.info(f"成功获取openid: {openid}")
+            return True, openid, None
+            
+        except requests.exceptions.Timeout:
+            error_msg = "微信API请求超时"
+            logger.error(error_msg)
+            return False, None, error_msg
+            
+        except requests.exceptions.RequestException as e:
+            error_msg = f"微信API请求异常: {str(e)}"
+            logger.error(error_msg)
+            return False, None, error_msg
+            
+        except json.JSONDecodeError as e:
+            error_msg = f"微信API响应解析失败: {str(e)}"
+            logger.error(error_msg)
+            return False, None, error_msg
+            
+        except Exception as e:
+            error_msg = f"获取openid时发生未知错误: {str(e)}"
+            logger.error(error_msg, exc_info=True)
+            return False, None, error_msg
+    
+    def code_to_openid_with_retry(self, code: str, max_retries: Optional[int] = None) -> Tuple[bool, Optional[str], Optional[str]]:
+        """
+        带重试机制的openid获取
+        
+        Args:
+            code (str): 微信授权码
+            max_retries (int, optional): 最大重试次数,默认使用配置中的值
+            
+        Returns:
+            Tuple[bool, Optional[str], Optional[str]]: 
+            (是否成功, openid, 错误信息)
+        """
+        if max_retries is None:
+            max_retries = self.config.get('max_retries', 3) or 3
+        
+        retry_delay = self.config.get('retry_delay', 1)
+        
+        # 确保 max_retries 是整数
+        assert isinstance(max_retries, int)
+        
+        for attempt in range(max_retries + 1):
+            success, openid, error_msg = self.code_to_openid(code)
+            
+            if success:
+                return True, openid, None
+            
+            # 如果是最后一次尝试,直接返回错误
+            if attempt == max_retries:
+                return False, None, error_msg
+            
+            # 某些错误不需要重试
+            if error_msg and any(keyword in error_msg for keyword in ['无效', 'invalid', '配置不完整']):
+                return False, None, error_msg
+            
+            # 等待后重试
+            logger.warning(f"第{attempt + 1}次获取openid失败,{retry_delay}秒后重试: {error_msg}")
+            import time
+            time.sleep(retry_delay)
+        
+        return False, None, "重试次数已用尽"
+
+
+# 便捷函数
+def get_openid_from_code(code: str, platform: str = 'miniprogram') -> Tuple[bool, Optional[str], Optional[str]]:
+    """
+    从微信授权码获取openid的便捷函数
+    
+    Args:
+        code (str): 微信授权码
+        platform (str): 微信平台类型,默认为小程序
+        
+    Returns:
+        Tuple[bool, Optional[str], Optional[str]]: 
+        (是否成功, openid, 错误信息)
+    """
+    service = WechatApiService(platform)
+    return service.code_to_openid_with_retry(code)
+
+
+def validate_openid(openid: str) -> bool:
+    """
+    验证openid格式是否正确
+    
+    Args:
+        openid (str): 微信用户openid
+        
+    Returns:
+        bool: 格式是否正确
+    """
+    if not openid or not isinstance(openid, str):
+        return False
+    
+    # 微信openid通常是28位字符串,由字母和数字组成
+    if len(openid) != 28:
+        return False
+    
+    # 检查是否只包含字母、数字、下划线和短横线
+    import re
+    pattern = r'^[a-zA-Z0-9_-]+$'
+    return bool(re.match(pattern, openid))
+
+
+# 导出主要类和函数
+__all__ = [
+    'WechatApiService',
+    'get_openid_from_code',
+    'validate_openid'
+]

+ 89 - 0
app/core/data_parse/wechat_config.py

@@ -0,0 +1,89 @@
+"""
+微信API配置文件
+用于配置微信小程序/公众号的API相关参数
+"""
+
+import os
+from typing import Dict, Any
+
+# 微信API配置
+WECHAT_API_CONFIG: Dict[str, Any] = {
+    # 微信小程序配置
+    'miniprogram': {
+        'app_id': os.environ.get('WECHAT_MINIPROGRAM_APP_ID', ''),
+        'app_secret': os.environ.get('WECHAT_MINIPROGRAM_APP_SECRET', ''),
+        'code2session_url': 'https://api.weixin.qq.com/sns/jscode2session',
+        'grant_type': 'authorization_code'
+    },
+    
+    # 微信公众号配置(如果需要)
+    'official_account': {
+        'app_id': os.environ.get('WECHAT_OFFICIAL_APP_ID', ''),
+        'app_secret': os.environ.get('WECHAT_OFFICIAL_APP_SECRET', ''),
+        'oauth2_url': 'https://api.weixin.qq.com/sns/oauth2/access_token',
+        'grant_type': 'authorization_code'
+    },
+    
+    # 请求配置
+    'request': {
+        'timeout': 10,  # 请求超时时间(秒)
+        'max_retries': 3,  # 最大重试次数
+        'retry_delay': 1  # 重试延迟(秒)
+    }
+}
+
+# 微信错误码映射
+WECHAT_ERROR_CODES: Dict[int, str] = {
+    -1: '系统繁忙,此时请开发者稍候再试',
+    0: '请求成功',
+    40013: 'invalid appid',
+    40029: 'code 无效',
+    45011: 'API 调用太频繁,请稍候再试',
+    40226: '高风险等级用户,小程序登录拦截',
+    # 可以根据需要添加更多错误码
+}
+
+def get_wechat_config(platform: str = 'miniprogram') -> Dict[str, Any]:
+    """
+    获取微信平台配置
+    
+    Args:
+        platform (str): 平台类型,'miniprogram' 或 'official_account'
+        
+    Returns:
+        Dict[str, Any]: 配置信息
+    """
+    if platform not in WECHAT_API_CONFIG:
+        raise ValueError(f"不支持的微信平台类型: {platform}")
+    
+    config = WECHAT_API_CONFIG[platform].copy()
+    config.update(WECHAT_API_CONFIG['request'])
+    
+    return config
+
+def validate_wechat_config(platform: str = 'miniprogram') -> bool:
+    """
+    验证微信配置是否完整
+    
+    Args:
+        platform (str): 平台类型
+        
+    Returns:
+        bool: 配置是否完整
+    """
+    config = WECHAT_API_CONFIG.get(platform, {})
+    required_keys = ['app_id', 'app_secret']
+    
+    return all(config.get(key) for key in required_keys)
+
+def get_error_message(error_code: int) -> str:
+    """
+    根据错误码获取错误信息
+    
+    Args:
+        error_code (int): 微信API返回的错误码
+        
+    Returns:
+        str: 错误信息
+    """
+    return WECHAT_ERROR_CODES.get(error_code, f'未知错误码: {error_code}')

+ 212 - 0
app/scripts/create_calendar_records_table.py

@@ -0,0 +1,212 @@
+#!/usr/bin/env python3
+"""
+日历内容记录表创建脚本
+用于创建、检查和删除calendar_records表
+"""
+
+import sys
+import os
+import logging
+from datetime import datetime
+
+# 添加项目根目录到路径
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
+
+from app import create_app, db
+from sqlalchemy import text
+
+# 配置日志
+logging.basicConfig(
+    level=logging.INFO,
+    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+    handlers=[
+        logging.FileHandler('calendar_records_migration.log', encoding='utf-8'),
+        logging.StreamHandler()
+    ]
+)
+
+logger = logging.getLogger(__name__)
+
+
+def create_calendar_records_table():
+    """
+    创建日历内容记录表
+    
+    Returns:
+        bool: 创建成功返回True,失败返回False
+    """
+    try:
+        app = create_app()
+        with app.app_context():
+            logger.info("开始创建日历内容记录表...")
+            
+            # 读取DDL脚本
+            sql_file_path = os.path.join(
+                os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
+                'database', 'create_calendar_records.sql'
+            )
+            
+            if not os.path.exists(sql_file_path):
+                logger.error(f"DDL脚本文件不存在: {sql_file_path}")
+                return False
+            
+            with open(sql_file_path, 'r', encoding='utf-8') as f:
+                sql_content = f.read()
+            
+            # 执行DDL脚本
+            with db.engine.connect() as connection:
+                # 分割SQL语句并逐个执行
+                sql_statements = [stmt.strip() for stmt in sql_content.split(';') if stmt.strip()]
+                
+                for statement in sql_statements:
+                    if statement:
+                        logger.debug(f"执行SQL: {statement[:100]}...")
+                        connection.execute(text(statement))
+                
+                connection.commit()
+            
+            logger.info("日历内容记录表创建成功")
+            return True
+            
+    except Exception as e:
+        logger.error(f"创建日历内容记录表失败: {str(e)}", exc_info=True)
+        return False
+
+
+def check_calendar_records_table():
+    """
+    检查日历内容记录表是否存在
+    
+    Returns:
+        bool: 表存在返回True,不存在返回False
+    """
+    try:
+        app = create_app()
+        with app.app_context():
+            logger.info("检查日历内容记录表是否存在...")
+            
+            with db.engine.connect() as connection:
+                result = connection.execute(text("""
+                    SELECT EXISTS (
+                        SELECT FROM information_schema.tables 
+                        WHERE table_schema = 'public' 
+                        AND table_name = 'calendar_records'
+                    );
+                """))
+                
+                exists = result.scalar()
+                
+                if exists:
+                    logger.info("日历内容记录表已存在")
+                    
+                    # 获取表结构信息
+                    result = connection.execute(text("""
+                        SELECT column_name, data_type, is_nullable, column_default
+                        FROM information_schema.columns
+                        WHERE table_schema = 'public' AND table_name = 'calendar_records'
+                        ORDER BY ordinal_position;
+                    """))
+                    
+                    columns = result.fetchall()
+                    logger.info("表结构:")
+                    for col in columns:
+                        logger.info(f"  {col[0]}: {col[1]} ({'NULL' if col[2] == 'YES' else 'NOT NULL'}) {col[3] or ''}")
+                        
+                    # 获取索引信息
+                    result = connection.execute(text("""
+                        SELECT indexname, indexdef
+                        FROM pg_indexes
+                        WHERE tablename = 'calendar_records' AND schemaname = 'public';
+                    """))
+                    
+                    indexes = result.fetchall()
+                    if indexes:
+                        logger.info("索引:")
+                        for idx in indexes:
+                            logger.info(f"  {idx[0]}: {idx[1]}")
+                else:
+                    logger.info("日历内容记录表不存在")
+                
+                return exists
+                
+    except Exception as e:
+        logger.error(f"检查日历内容记录表失败: {str(e)}", exc_info=True)
+        return False
+
+
+def drop_calendar_records_table():
+    """
+    删除日历内容记录表
+    
+    Returns:
+        bool: 删除成功返回True,失败返回False
+    """
+    try:
+        app = create_app()
+        with app.app_context():
+            logger.info("开始删除日历内容记录表...")
+            
+            with db.engine.connect() as connection:
+                # 删除触发器
+                connection.execute(text("DROP TRIGGER IF EXISTS trigger_update_calendar_records_updated_at ON public.calendar_records;"))
+                
+                # 删除触发器函数
+                connection.execute(text("DROP FUNCTION IF EXISTS update_calendar_records_updated_at();"))
+                
+                # 删除表
+                connection.execute(text("DROP TABLE IF EXISTS public.calendar_records CASCADE;"))
+                
+                connection.commit()
+            
+            logger.info("日历内容记录表删除成功")
+            return True
+            
+    except Exception as e:
+        logger.error(f"删除日历内容记录表失败: {str(e)}", exc_info=True)
+        return False
+
+
+def main():
+    """
+    主函数
+    """
+    if len(sys.argv) != 2:
+        print("使用方法:")
+        print("  python create_calendar_records_table.py create   # 创建表")
+        print("  python create_calendar_records_table.py check    # 检查表")
+        print("  python create_calendar_records_table.py drop     # 删除表")
+        sys.exit(1)
+    
+    action = sys.argv[1].lower()
+    
+    if action == 'create':
+        success = create_calendar_records_table()
+        if success:
+            print("✅ 日历内容记录表创建成功")
+        else:
+            print("❌ 日历内容记录表创建失败")
+            sys.exit(1)
+            
+    elif action == 'check':
+        exists = check_calendar_records_table()
+        if exists:
+            print("✅ 日历内容记录表存在")
+        else:
+            print("❌ 日历内容记录表不存在")
+            
+    elif action == 'drop':
+        success = drop_calendar_records_table()
+        if success:
+            print("✅ 日历内容记录表删除成功")
+        else:
+            print("❌ 日历内容记录表删除失败")
+            sys.exit(1)
+            
+    else:
+        print(f"未知操作: {action}")
+        print("支持的操作: create, check, drop")
+        sys.exit(1)
+
+
+if __name__ == '__main__':
+    main()

+ 68 - 0
database/create_calendar_records.sql

@@ -0,0 +1,68 @@
+-- 日历内容记录表DDL脚本
+-- 用于存储用户的日历内容记录信息
+
+create table public.calendar_records
+(
+    id              serial
+        primary key,
+    openid          varchar(255) not null,
+    month_key       varchar(7)   not null,
+    calendar_content jsonb       not null,
+    created_at      timestamp with time zone default current_timestamp not null,
+    updated_at      timestamp with time zone default current_timestamp not null
+);
+
+comment on table public.calendar_records is '日历内容记录表';
+
+comment on column public.calendar_records.id is '主键ID';
+
+comment on column public.calendar_records.openid is '微信用户openid';
+
+comment on column public.calendar_records.month_key is '月份标识,格式为YYYY-MM';
+
+comment on column public.calendar_records.calendar_content is '日历内容,JSON数组格式';
+
+comment on column public.calendar_records.created_at is '记录创建时间';
+
+comment on column public.calendar_records.updated_at is '记录更新时间';
+
+-- 创建索引以提高查询性能
+create index idx_calendar_records_openid on public.calendar_records(openid);
+create index idx_calendar_records_month_key on public.calendar_records(month_key);
+create index idx_calendar_records_openid_month on public.calendar_records(openid, month_key);
+create index idx_calendar_records_created_at on public.calendar_records(created_at);
+create index idx_calendar_records_updated_at on public.calendar_records(updated_at);
+
+-- 创建更新时间触发器函数
+create or replace function update_calendar_records_updated_at()
+returns trigger as $$
+begin
+    new.updated_at = current_timestamp;
+    return new;
+end;
+$$ language plpgsql;
+
+-- 创建触发器
+create trigger trigger_update_calendar_records_updated_at
+    before update on public.calendar_records
+    for each row
+    execute function update_calendar_records_updated_at();
+
+-- 创建唯一约束(一个用户在同一个月份只能有一条记录)
+create unique index idx_calendar_records_openid_month_unique 
+    on public.calendar_records(openid, month_key);
+
+-- 添加约束检查月份格式
+alter table public.calendar_records 
+add constraint chk_calendar_records_month_format 
+check (month_key ~ '^\d{4}-\d{2}$');
+
+-- 添加约束检查openid格式(微信openid通常是28位字符串)
+alter table public.calendar_records 
+add constraint chk_calendar_records_openid_format 
+check (length(openid) = 28 and openid ~ '^[a-zA-Z0-9_-]+$');
+
+-- 添加约束检查JSON内容不为空
+alter table public.calendar_records 
+add constraint chk_calendar_records_content_not_empty 
+check (jsonb_array_length(calendar_content) >= 0);

+ 3 - 3
database/create_wechat_users.sql

@@ -5,7 +5,7 @@ create table public.wechat_users
 (
     id              serial
         primary key,
-    wechat_code     varchar(255) not null unique,
+    openid          varchar(255) not null unique,
     phone_number    varchar(20),
     id_card_number  varchar(18),
     login_status    boolean default false not null,
@@ -19,7 +19,7 @@ comment on table public.wechat_users is '微信用户信息表';
 
 comment on column public.wechat_users.id is '主键ID';
 
-comment on column public.wechat_users.wechat_code is '微信授权码/openid,唯一标识';
+comment on column public.wechat_users.openid is '微信用户openid,唯一标识';
 
 comment on column public.wechat_users.phone_number is '用户手机号码';
 
@@ -36,7 +36,7 @@ comment on column public.wechat_users.created_at is '账户创建时间';
 comment on column public.wechat_users.updated_at is '信息更新时间';
 
 -- 创建索引以提高查询性能
-create index idx_wechat_users_wechat_code on public.wechat_users(wechat_code);
+create index idx_wechat_users_openid on public.wechat_users(openid);
 create index idx_wechat_users_phone_number on public.wechat_users(phone_number);
 create index idx_wechat_users_login_status on public.wechat_users(login_status);
 create index idx_wechat_users_user_status on public.wechat_users(user_status);

+ 35 - 0
database/migrate_wechat_code_to_openid.sql

@@ -0,0 +1,35 @@
+-- 微信用户表字段迁移脚本
+-- 将 wechat_code 字段重命名为 openid
+-- 注意:执行前请备份数据!
+
+-- 检查表是否存在
+DO $$
+BEGIN
+    IF EXISTS (SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'wechat_users') THEN
+        -- 检查是否已经有openid字段
+        IF NOT EXISTS (SELECT FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'wechat_users' AND column_name = 'openid') THEN
+            -- 如果存在wechat_code字段,则重命名
+            IF EXISTS (SELECT FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'wechat_users' AND column_name = 'wechat_code') THEN
+                -- 重命名字段
+                ALTER TABLE public.wechat_users RENAME COLUMN wechat_code TO openid;
+                
+                -- 更新字段注释
+                COMMENT ON COLUMN public.wechat_users.openid IS '微信用户openid,唯一标识';
+                
+                -- 重命名相关索引
+                IF EXISTS (SELECT FROM pg_class WHERE relname = 'idx_wechat_users_wechat_code') THEN
+                    DROP INDEX IF EXISTS public.idx_wechat_users_wechat_code;
+                    CREATE INDEX idx_wechat_users_openid ON public.wechat_users(openid);
+                END IF;
+                
+                RAISE NOTICE '成功将 wechat_code 字段重命名为 openid';
+            ELSE
+                RAISE NOTICE 'wechat_code 字段不存在,无需迁移';
+            END IF;
+        ELSE
+            RAISE NOTICE 'openid 字段已存在,无需迁移';
+        END IF;
+    ELSE
+        RAISE NOTICE 'wechat_users 表不存在,请先创建表';
+    END IF;
+END $$;

+ 1185 - 0
docs/calendar-records-api-guide.md

@@ -0,0 +1,1185 @@
+# 日历记录API接口 - 前端开发指南
+
+## 版本信息
+- **版本**: v1.0.0
+- **更新日期**: 2024-01-15
+- **适用范围**: DataOps平台日历记录功能
+
+## 概述
+
+本文档提供日历记录相关API接口的详细说明,包括保存日历记录和获取日历记录功能。这些接口用于管理用户的月度日历内容,支持插入新记录和更新现有记录。
+
+## 重要说明
+
+1. **认证方式**: 使用微信用户的 `openid` 作为用户标识
+2. **数据格式**: 所有请求和响应均使用 JSON 格式
+3. **字符编码**: UTF-8
+4. **时区**: 所有时间字段均为东八区时间 (UTC+8)
+5. **月份格式**: 统一使用 `YYYY-MM` 格式(如:2024-01)
+
+## API接口列表
+
+| 接口名称 | 请求方法 | 路径 | 功能描述 |
+|---------|---------|------|---------|
+| 保存日历记录 | POST | `/api/parse/save-calendar-record` | 保存或更新用户日历记录 |
+| 获取日历记录 | GET | `/api/parse/get-calendar-record` | 查询用户指定月份的日历记录 |
+
+---
+
+## 1. 保存日历记录接口
+
+### 基本信息
+- **接口路径**: `/api/parse/save-calendar-record`
+- **请求方法**: `POST`
+- **Content-Type**: `application/json`
+- **功能**: 保存用户的日历内容记录,如果记录已存在则更新,不存在则创建新记录
+
+### 请求参数
+
+#### 请求头 (Headers)
+```http
+Content-Type: application/json
+```
+
+#### 请求体 (Request Body)
+| 参数名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| openid | string | 是 | 微信用户openid,28位字符串 | "wx_openid_abcd1234567890123456" |
+| month_key | string | 是 | 月份标识,格式为YYYY-MM | "2024-01" |
+| calendar_content | array | 是 | 日历内容,JSON数组格式 | 见下方示例 |
+
+#### calendar_content 数组元素结构
+| 字段名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| date | string | 否 | 日期,格式为YYYY-MM-DD | "2024-01-01" |
+| events | array | 否 | 事件列表,字符串数组 | ["会议", "约会"] |
+| notes | string | 否 | 备注信息 | "重要日程" |
+
+### 请求示例
+
+#### JavaScript (Fetch API)
+```javascript
+async function saveCalendarRecord(openid, monthKey, calendarContent) {
+    const url = '/api/parse/save-calendar-record';
+    
+    const requestData = {
+        openid: openid,
+        month_key: monthKey,
+        calendar_content: calendarContent
+    };
+    
+    try {
+        const response = await fetch(url, {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json',
+            },
+            body: JSON.stringify(requestData)
+        });
+        
+        const result = await response.json();
+        
+        if (result.return_code === 200) {
+            console.log('保存成功:', result.result);
+            return { success: true, data: result.result };
+        } else {
+            console.error('保存失败:', result.error);
+            return { success: false, error: result.error };
+        }
+    } catch (error) {
+        console.error('请求异常:', error);
+        return { success: false, error: '网络请求失败' };
+    }
+}
+
+// 使用示例
+const calendarData = [
+    {
+        date: "2024-01-01",
+        events: ["元旦节"],
+        notes: "新年快乐"
+    },
+    {
+        date: "2024-01-15",
+        events: ["会议", "约会"],
+        notes: "重要日程"
+    }
+];
+
+saveCalendarRecord("wx_openid_abcd1234567890123456", "2024-01", calendarData);
+```
+
+#### jQuery Ajax
+```javascript
+function saveCalendarRecord(openid, monthKey, calendarContent) {
+    $.ajax({
+        url: '/api/parse/save-calendar-record',
+        type: 'POST',
+        contentType: 'application/json',
+        data: JSON.stringify({
+            openid: openid,
+            month_key: monthKey,
+            calendar_content: calendarContent
+        }),
+        success: function(result) {
+            if (result.return_code === 200) {
+                console.log('保存成功:', result.result);
+            } else {
+                console.error('保存失败:', result.error);
+            }
+        },
+        error: function(xhr, status, error) {
+            console.error('请求失败:', error);
+        }
+    });
+}
+```
+
+#### 微信小程序
+```javascript
+// 微信小程序保存日历记录
+function saveCalendarRecord(openid, monthKey, calendarContent) {
+    wx.request({
+        url: 'https://your-domain.com/api/parse/save-calendar-record',
+        method: 'POST',
+        header: {
+            'content-type': 'application/json'
+        },
+        data: {
+            openid: openid,
+            month_key: monthKey,
+            calendar_content: calendarContent
+        },
+        success: function(res) {
+            if (res.data.return_code === 200) {
+                console.log('保存成功:', res.data.result);
+                wx.showToast({
+                    title: '保存成功',
+                    icon: 'success'
+                });
+            } else {
+                console.error('保存失败:', res.data.error);
+                wx.showToast({
+                    title: '保存失败',
+                    icon: 'error'
+                });
+            }
+        },
+        fail: function(error) {
+            console.error('请求失败:', error);
+            wx.showToast({
+                title: '网络错误',
+                icon: 'error'
+            });
+        }
+    });
+}
+```
+
+### 响应结果
+
+#### 成功响应 (HTTP 200)
+```json
+{
+    "reason": "successed",
+    "return_code": 200,
+    "result": {
+        "id": 1,
+        "openid": "wx_openid_abcd1234567890123456",
+        "month_key": "2024-01",
+        "calendar_content": [
+            {
+                "date": "2024-01-01",
+                "events": ["元旦节"],
+                "notes": "新年快乐"
+            },
+            {
+                "date": "2024-01-15",
+                "events": ["会议", "约会"],
+                "notes": "重要日程"
+            }
+        ],
+        "created_at": "2024-01-15T10:30:00+08:00",
+        "updated_at": "2024-01-15T14:30:00+08:00"
+    }
+}
+```
+
+#### 错误响应
+```json
+{
+    "reason": "failed",
+    "return_code": 400,
+    "result": null,
+    "error": "缺少必填参数: openid"
+}
+```
+
+### 返回码说明
+| 返回码 | 说明 | 处理建议 |
+|--------|------|---------|
+| 200 | 成功 | 继续后续操作 |
+| 400 | 请求参数错误 | 检查请求参数格式和必填项 |
+| 500 | 服务器内部错误 | 联系技术支持或稍后重试 |
+
+---
+
+## 2. 获取日历记录接口
+
+### 基本信息
+- **接口路径**: `/api/parse/get-calendar-record`
+- **请求方法**: `GET`
+- **功能**: 查询用户指定月份的日历记录
+
+### 请求参数
+
+#### 查询参数 (Query Parameters)
+| 参数名 | 类型 | 必填 | 说明 | 示例值 |
+|--------|------|------|------|--------|
+| openid | string | 是 | 微信用户openid | wx_openid_abcd1234567890123456 |
+| month_key | string | 是 | 月份标识,格式为YYYY-MM | 2024-01 |
+
+### 请求示例
+
+#### JavaScript (Fetch API)
+```javascript
+async function getCalendarRecord(openid, monthKey) {
+    const url = `/api/parse/get-calendar-record?openid=${encodeURIComponent(openid)}&month_key=${encodeURIComponent(monthKey)}`;
+    
+    try {
+        const response = await fetch(url, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json',
+            }
+        });
+        
+        const result = await response.json();
+        
+        if (result.return_code === 200) {
+            console.log('查询成功:', result.result);
+            return { success: true, data: result.result };
+        } else {
+            console.error('查询失败:', result.error);
+            return { success: false, error: result.error };
+        }
+    } catch (error) {
+        console.error('请求异常:', error);
+        return { success: false, error: '网络请求失败' };
+    }
+}
+
+// 使用示例
+getCalendarRecord("wx_openid_abcd1234567890123456", "2024-01")
+    .then(result => {
+        if (result.success) {
+            // 处理成功结果
+            const calendarData = result.data;
+            if (calendarData.id) {
+                console.log('找到日历记录:', calendarData.calendar_content);
+            } else {
+                console.log('该月份暂无记录');
+            }
+        } else {
+            // 处理错误
+            console.error('获取失败:', result.error);
+        }
+    });
+```
+
+#### jQuery Ajax
+```javascript
+function getCalendarRecord(openid, monthKey) {
+    $.ajax({
+        url: '/api/parse/get-calendar-record',
+        type: 'GET',
+        data: {
+            openid: openid,
+            month_key: monthKey
+        },
+        success: function(result) {
+            if (result.return_code === 200) {
+                console.log('查询成功:', result.result);
+                if (result.result.id) {
+                    // 有记录
+                    displayCalendarData(result.result.calendar_content);
+                } else {
+                    // 无记录
+                    displayEmptyCalendar();
+                }
+            } else {
+                console.error('查询失败:', result.error);
+            }
+        },
+        error: function(xhr, status, error) {
+            console.error('请求失败:', error);
+        }
+    });
+}
+```
+
+#### 微信小程序
+```javascript
+// 微信小程序获取日历记录
+function getCalendarRecord(openid, monthKey) {
+    wx.request({
+        url: 'https://your-domain.com/api/parse/get-calendar-record',
+        method: 'GET',
+        data: {
+            openid: openid,
+            month_key: monthKey
+        },
+        success: function(res) {
+            if (res.data.return_code === 200) {
+                console.log('查询成功:', res.data.result);
+                const calendarData = res.data.result;
+                
+                if (calendarData.id) {
+                    // 有记录,显示日历内容
+                    this.setData({
+                        calendarContent: calendarData.calendar_content,
+                        hasRecord: true
+                    });
+                } else {
+                    // 无记录,显示空日历
+                    this.setData({
+                        calendarContent: [],
+                        hasRecord: false
+                    });
+                }
+            } else {
+                console.error('查询失败:', res.data.error);
+                wx.showToast({
+                    title: '查询失败',
+                    icon: 'error'
+                });
+            }
+        },
+        fail: function(error) {
+            console.error('请求失败:', error);
+            wx.showToast({
+                title: '网络错误',
+                icon: 'error'
+            });
+        }
+    });
+}
+```
+
+### 响应结果
+
+#### 有记录时的成功响应 (HTTP 200)
+```json
+{
+    "reason": "successed",
+    "return_code": 200,
+    "result": {
+        "id": 1,
+        "openid": "wx_openid_abcd1234567890123456",
+        "month_key": "2024-01",
+        "calendar_content": [
+            {
+                "date": "2024-01-01",
+                "events": ["元旦节"],
+                "notes": "新年快乐"
+            },
+            {
+                "date": "2024-01-15",
+                "events": ["会议", "约会"],
+                "notes": "重要日程"
+            }
+        ],
+        "created_at": "2024-01-15T10:30:00+08:00",
+        "updated_at": "2024-01-15T14:30:00+08:00"
+    }
+}
+```
+
+#### 无记录时的成功响应 (HTTP 200)
+```json
+{
+    "reason": "successed",
+    "return_code": 200,
+    "result": {
+        "id": null,
+        "openid": "wx_openid_abcd1234567890123456",
+        "month_key": "2024-01",
+        "calendar_content": [],
+        "created_at": null,
+        "updated_at": null
+    }
+}
+```
+
+#### 错误响应
+```json
+{
+    "reason": "failed",
+    "return_code": 400,
+    "result": null,
+    "error": "缺少必填参数: month_key"
+}
+```
+
+---
+
+## 前端集成指南
+
+### 1. 完整的日历管理类
+
+```javascript
+class CalendarRecordManager {
+    constructor(baseUrl = '') {
+        this.baseUrl = baseUrl;
+    }
+    
+    /**
+     * 保存日历记录
+     * @param {string} openid - 微信用户openid
+     * @param {string} monthKey - 月份标识 (YYYY-MM)
+     * @param {Array} calendarContent - 日历内容数组
+     * @returns {Promise<Object>} 返回操作结果
+     */
+    async saveRecord(openid, monthKey, calendarContent) {
+        const url = `${this.baseUrl}/api/parse/save-calendar-record`;
+        
+        try {
+            const response = await fetch(url, {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+                body: JSON.stringify({
+                    openid: openid,
+                    month_key: monthKey,
+                    calendar_content: calendarContent
+                })
+            });
+            
+            const result = await response.json();
+            return {
+                success: result.return_code === 200,
+                data: result.result,
+                error: result.error,
+                code: result.return_code
+            };
+        } catch (error) {
+            return {
+                success: false,
+                data: null,
+                error: error.message,
+                code: -1
+            };
+        }
+    }
+    
+    /**
+     * 获取日历记录
+     * @param {string} openid - 微信用户openid
+     * @param {string} monthKey - 月份标识 (YYYY-MM)
+     * @returns {Promise<Object>} 返回查询结果
+     */
+    async getRecord(openid, monthKey) {
+        const url = `${this.baseUrl}/api/parse/get-calendar-record?openid=${encodeURIComponent(openid)}&month_key=${encodeURIComponent(monthKey)}`;
+        
+        try {
+            const response = await fetch(url, {
+                method: 'GET',
+                headers: {
+                    'Content-Type': 'application/json',
+                }
+            });
+            
+            const result = await response.json();
+            return {
+                success: result.return_code === 200,
+                data: result.result,
+                error: result.error,
+                code: result.return_code,
+                hasRecord: result.result && result.result.id !== null
+            };
+        } catch (error) {
+            return {
+                success: false,
+                data: null,
+                error: error.message,
+                code: -1,
+                hasRecord: false
+            };
+        }
+    }
+    
+    /**
+     * 格式化月份键
+     * @param {Date} date - 日期对象
+     * @returns {string} 格式化的月份键 (YYYY-MM)
+     */
+    formatMonthKey(date = new Date()) {
+        const year = date.getFullYear();
+        const month = String(date.getMonth() + 1).padStart(2, '0');
+        return `${year}-${month}`;
+    }
+    
+    /**
+     * 验证月份键格式
+     * @param {string} monthKey - 月份键
+     * @returns {boolean} 是否有效
+     */
+    validateMonthKey(monthKey) {
+        const pattern = /^\d{4}-\d{2}$/;
+        return pattern.test(monthKey);
+    }
+    
+    /**
+     * 验证openid格式
+     * @param {string} openid - 微信openid
+     * @returns {boolean} 是否有效
+     */
+    validateOpenid(openid) {
+        return openid && typeof openid === 'string' && openid.length === 28;
+    }
+}
+
+// 使用示例
+const calendarManager = new CalendarRecordManager();
+
+// 保存记录
+async function saveCurrentMonthRecord(openid, events) {
+    const monthKey = calendarManager.formatMonthKey(new Date());
+    const result = await calendarManager.saveRecord(openid, monthKey, events);
+    
+    if (result.success) {
+        console.log('保存成功:', result.data);
+    } else {
+        console.error('保存失败:', result.error);
+    }
+}
+
+// 获取记录
+async function loadCurrentMonthRecord(openid) {
+    const monthKey = calendarManager.formatMonthKey(new Date());
+    const result = await calendarManager.getRecord(openid, monthKey);
+    
+    if (result.success) {
+        if (result.hasRecord) {
+            console.log('找到记录:', result.data.calendar_content);
+            return result.data.calendar_content;
+        } else {
+            console.log('暂无记录');
+            return [];
+        }
+    } else {
+        console.error('获取失败:', result.error);
+        return null;
+    }
+}
+```
+
+### 2. Vue.js 集成示例
+
+```vue
+<template>
+  <div class="calendar-container">
+    <h2>{{ currentMonthText }}日历</h2>
+    
+    <!-- 日历内容显示 -->
+    <div v-if="hasRecord" class="calendar-content">
+      <div v-for="item in calendarContent" :key="item.date" class="calendar-item">
+        <div class="date">{{ item.date }}</div>
+        <div class="events">
+          <span v-for="event in item.events" :key="event" class="event-tag">
+            {{ event }}
+          </span>
+        </div>
+        <div class="notes">{{ item.notes }}</div>
+      </div>
+    </div>
+    
+    <!-- 无记录提示 -->
+    <div v-else class="no-record">
+      <p>本月暂无日历记录</p>
+    </div>
+    
+    <!-- 操作按钮 -->
+    <div class="actions">
+      <button @click="loadRecord" :disabled="loading">刷新</button>
+      <button @click="saveRecord" :disabled="loading">保存</button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'CalendarRecord',
+  data() {
+    return {
+      openid: 'wx_openid_abcd1234567890123456', // 从登录状态获取
+      currentMonth: new Date(),
+      calendarContent: [],
+      hasRecord: false,
+      loading: false
+    };
+  },
+  computed: {
+    currentMonthText() {
+      return `${this.currentMonth.getFullYear()}年${this.currentMonth.getMonth() + 1}月`;
+    },
+    monthKey() {
+      const year = this.currentMonth.getFullYear();
+      const month = String(this.currentMonth.getMonth() + 1).padStart(2, '0');
+      return `${year}-${month}`;
+    }
+  },
+  mounted() {
+    this.loadRecord();
+  },
+  methods: {
+    async loadRecord() {
+      this.loading = true;
+      
+      try {
+        const response = await fetch(`/api/parse/get-calendar-record?openid=${this.openid}&month_key=${this.monthKey}`);
+        const result = await response.json();
+        
+        if (result.return_code === 200) {
+          this.hasRecord = result.result.id !== null;
+          this.calendarContent = result.result.calendar_content || [];
+        } else {
+          this.$message.error('获取日历记录失败: ' + result.error);
+        }
+      } catch (error) {
+        this.$message.error('网络请求失败: ' + error.message);
+      } finally {
+        this.loading = false;
+      }
+    },
+    
+    async saveRecord() {
+      // 这里应该从表单或编辑器获取要保存的数据
+      const dataToSave = [
+        {
+          date: "2024-01-15",
+          events: ["会议", "约会"],
+          notes: "重要日程"
+        }
+      ];
+      
+      this.loading = true;
+      
+      try {
+        const response = await fetch('/api/parse/save-calendar-record', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+          body: JSON.stringify({
+            openid: this.openid,
+            month_key: this.monthKey,
+            calendar_content: dataToSave
+          })
+        });
+        
+        const result = await response.json();
+        
+        if (result.return_code === 200) {
+          this.$message.success('保存成功');
+          this.loadRecord(); // 重新加载数据
+        } else {
+          this.$message.error('保存失败: ' + result.error);
+        }
+      } catch (error) {
+        this.$message.error('网络请求失败: ' + error.message);
+      } finally {
+        this.loading = false;
+      }
+    }
+  }
+};
+</script>
+```
+
+### 3. React 集成示例
+
+```jsx
+import React, { useState, useEffect, useCallback } from 'react';
+
+const CalendarRecord = ({ openid }) => {
+    const [calendarContent, setCalendarContent] = useState([]);
+    const [hasRecord, setHasRecord] = useState(false);
+    const [loading, setLoading] = useState(false);
+    const [currentMonth, setCurrentMonth] = useState(new Date());
+    
+    // 格式化月份键
+    const formatMonthKey = useCallback((date) => {
+        const year = date.getFullYear();
+        const month = String(date.getMonth() + 1).padStart(2, '0');
+        return `${year}-${month}`;
+    }, []);
+    
+    const monthKey = formatMonthKey(currentMonth);
+    
+    // 加载日历记录
+    const loadRecord = useCallback(async () => {
+        setLoading(true);
+        
+        try {
+            const response = await fetch(`/api/parse/get-calendar-record?openid=${openid}&month_key=${monthKey}`);
+            const result = await response.json();
+            
+            if (result.return_code === 200) {
+                setHasRecord(result.result.id !== null);
+                setCalendarContent(result.result.calendar_content || []);
+            } else {
+                console.error('获取失败:', result.error);
+            }
+        } catch (error) {
+            console.error('网络错误:', error);
+        } finally {
+            setLoading(false);
+        }
+    }, [openid, monthKey]);
+    
+    // 保存日历记录
+    const saveRecord = async (dataToSave) => {
+        setLoading(true);
+        
+        try {
+            const response = await fetch('/api/parse/save-calendar-record', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+                body: JSON.stringify({
+                    openid: openid,
+                    month_key: monthKey,
+                    calendar_content: dataToSave
+                })
+            });
+            
+            const result = await response.json();
+            
+            if (result.return_code === 200) {
+                console.log('保存成功');
+                loadRecord(); // 重新加载
+            } else {
+                console.error('保存失败:', result.error);
+            }
+        } catch (error) {
+            console.error('网络错误:', error);
+        } finally {
+            setLoading(false);
+        }
+    };
+    
+    useEffect(() => {
+        if (openid) {
+            loadRecord();
+        }
+    }, [openid, loadRecord]);
+    
+    return (
+        <div className="calendar-container">
+            <h2>{currentMonth.getFullYear()}年{currentMonth.getMonth() + 1}月日历</h2>
+            
+            {hasRecord ? (
+                <div className="calendar-content">
+                    {calendarContent.map((item, index) => (
+                        <div key={index} className="calendar-item">
+                            <div className="date">{item.date}</div>
+                            <div className="events">
+                                {item.events?.map((event, idx) => (
+                                    <span key={idx} className="event-tag">{event}</span>
+                                ))}
+                            </div>
+                            <div className="notes">{item.notes}</div>
+                        </div>
+                    ))}
+                </div>
+            ) : (
+                <div className="no-record">
+                    <p>本月暂无日历记录</p>
+                </div>
+            )}
+            
+            <div className="actions">
+                <button onClick={loadRecord} disabled={loading}>
+                    {loading ? '加载中...' : '刷新'}
+                </button>
+            </div>
+        </div>
+    );
+};
+
+export default CalendarRecord;
+```
+
+---
+
+## 错误处理指南
+
+### 常见错误码及处理方案
+
+| 错误码 | 错误信息 | 原因 | 解决方案 |
+|--------|---------|------|---------|
+| 400 | 请求体不能为空 | POST请求未提供请求体 | 检查请求体格式 |
+| 400 | 缺少必填参数: openid | 未提供openid参数 | 确保传递有效的openid |
+| 400 | 缺少必填参数: month_key | 未提供month_key参数 | 确保传递正确格式的月份键 |
+| 400 | 缺少必填参数: calendar_content | 未提供calendar_content参数 | 确保传递日历内容数组 |
+| 400 | 无效的openid格式 | openid格式不正确 | 检查openid是否为28位字符串 |
+| 400 | 无效的月份格式 | month_key格式错误 | 确保使用YYYY-MM格式 |
+| 500 | 数据库连接失败 | 服务器数据库问题 | 联系技术支持 |
+| 500 | 服务器内部错误 | 其他服务器错误 | 稍后重试或联系技术支持 |
+
+### 错误处理最佳实践
+
+```javascript
+async function handleApiCall(apiFunction) {
+    try {
+        const result = await apiFunction();
+        
+        switch (result.code) {
+            case 200:
+                return { success: true, data: result.data };
+                
+            case 400:
+                // 参数错误,显示具体错误信息
+                console.warn('参数错误:', result.error);
+                return { success: false, error: result.error, needsRetry: false };
+                
+            case 500:
+                // 服务器错误,可以重试
+                console.error('服务器错误:', result.error);
+                return { success: false, error: '服务器暂时不可用,请稍后重试', needsRetry: true };
+                
+            default:
+                console.error('未知错误:', result.error);
+                return { success: false, error: '操作失败,请重试', needsRetry: true };
+        }
+    } catch (error) {
+        console.error('网络错误:', error);
+        return { success: false, error: '网络连接失败,请检查网络', needsRetry: true };
+    }
+}
+
+// 带重试的API调用
+async function apiCallWithRetry(apiFunction, maxRetries = 3) {
+    for (let attempt = 1; attempt <= maxRetries; attempt++) {
+        const result = await handleApiCall(apiFunction);
+        
+        if (result.success || !result.needsRetry) {
+            return result;
+        }
+        
+        if (attempt < maxRetries) {
+            console.log(`第${attempt}次尝试失败,${1000 * attempt}ms后重试...`);
+            await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
+        }
+    }
+    
+    return { success: false, error: '多次重试失败,请稍后再试' };
+}
+```
+
+---
+
+## 开发环境配置
+
+### 环境变量配置
+
+在开发环境中,需要配置以下环境变量:
+
+```bash
+# .env.development
+REACT_APP_API_BASE_URL=http://localhost:5000
+VUE_APP_API_BASE_URL=http://localhost:5000
+
+# .env.production  
+REACT_APP_API_BASE_URL=https://your-production-domain.com
+VUE_APP_API_BASE_URL=https://your-production-domain.com
+```
+
+### 代理配置
+
+#### Webpack Dev Server (React/Vue)
+```javascript
+// webpack.config.js 或 vue.config.js
+module.exports = {
+  devServer: {
+    proxy: {
+      '/api': {
+        target: 'http://localhost:5000',
+        changeOrigin: true,
+        secure: false
+      }
+    }
+  }
+};
+```
+
+#### Create React App
+```javascript
+// package.json
+{
+  "name": "your-app",
+  "proxy": "http://localhost:5000",
+  // ...
+}
+```
+
+---
+
+## 测试用例
+
+### 单元测试示例
+
+```javascript
+// calendar-api.test.js
+import { CalendarRecordManager } from './calendar-manager';
+
+describe('CalendarRecordManager', () => {
+    let manager;
+    const mockOpenid = 'wx_openid_test1234567890123456';
+    const mockMonthKey = '2024-01';
+    
+    beforeEach(() => {
+        manager = new CalendarRecordManager();
+        // Mock fetch
+        global.fetch = jest.fn();
+    });
+    
+    afterEach(() => {
+        jest.resetAllMocks();
+    });
+    
+    test('保存日历记录 - 成功', async () => {
+        const mockResponse = {
+            reason: 'successed',
+            return_code: 200,
+            result: {
+                id: 1,
+                openid: mockOpenid,
+                month_key: mockMonthKey,
+                calendar_content: [
+                    {
+                        date: '2024-01-01',
+                        events: ['测试事件'],
+                        notes: '测试备注'
+                    }
+                ]
+            }
+        };
+        
+        fetch.mockResolvedValueOnce({
+            json: async () => mockResponse
+        });
+        
+        const result = await manager.saveRecord(mockOpenid, mockMonthKey, [
+            {
+                date: '2024-01-01',
+                events: ['测试事件'],
+                notes: '测试备注'
+            }
+        ]);
+        
+        expect(result.success).toBe(true);
+        expect(result.data.id).toBe(1);
+        expect(fetch).toHaveBeenCalledWith(
+            '/api/parse/save-calendar-record',
+            expect.objectContaining({
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+                body: JSON.stringify({
+                    openid: mockOpenid,
+                    month_key: mockMonthKey,
+                    calendar_content: [
+                        {
+                            date: '2024-01-01',
+                            events: ['测试事件'],
+                            notes: '测试备注'
+                        }
+                    ]
+                })
+            })
+        );
+    });
+    
+    test('获取日历记录 - 有记录', async () => {
+        const mockResponse = {
+            reason: 'successed',
+            return_code: 200,
+            result: {
+                id: 1,
+                openid: mockOpenid,
+                month_key: mockMonthKey,
+                calendar_content: [
+                    {
+                        date: '2024-01-01',
+                        events: ['测试事件'],
+                        notes: '测试备注'
+                    }
+                ]
+            }
+        };
+        
+        fetch.mockResolvedValueOnce({
+            json: async () => mockResponse
+        });
+        
+        const result = await manager.getRecord(mockOpenid, mockMonthKey);
+        
+        expect(result.success).toBe(true);
+        expect(result.hasRecord).toBe(true);
+        expect(result.data.calendar_content).toHaveLength(1);
+    });
+    
+    test('获取日历记录 - 无记录', async () => {
+        const mockResponse = {
+            reason: 'successed',
+            return_code: 200,
+            result: {
+                id: null,
+                openid: mockOpenid,
+                month_key: mockMonthKey,
+                calendar_content: [],
+                created_at: null,
+                updated_at: null
+            }
+        };
+        
+        fetch.mockResolvedValueOnce({
+            json: async () => mockResponse
+        });
+        
+        const result = await manager.getRecord(mockOpenid, mockMonthKey);
+        
+        expect(result.success).toBe(true);
+        expect(result.hasRecord).toBe(false);
+        expect(result.data.calendar_content).toEqual([]);
+    });
+    
+    test('参数验证 - 无效的openid', async () => {
+        const isValid = manager.validateOpenid('invalid_openid');
+        expect(isValid).toBe(false);
+    });
+    
+    test('参数验证 - 有效的openid', async () => {
+        const isValid = manager.validateOpenid(mockOpenid);
+        expect(isValid).toBe(true);
+    });
+    
+    test('月份键格式化', () => {
+        const date = new Date('2024-01-15');
+        const monthKey = manager.formatMonthKey(date);
+        expect(monthKey).toBe('2024-01');
+    });
+    
+    test('月份键验证 - 有效格式', () => {
+        expect(manager.validateMonthKey('2024-01')).toBe(true);
+        expect(manager.validateMonthKey('2024-12')).toBe(true);
+    });
+    
+    test('月份键验证 - 无效格式', () => {
+        expect(manager.validateMonthKey('2024-1')).toBe(false);
+        expect(manager.validateMonthKey('24-01')).toBe(false);
+        expect(manager.validateMonthKey('2024/01')).toBe(false);
+    });
+});
+```
+
+---
+
+## 性能优化建议
+
+### 1. 请求优化
+- 使用防抖(debounce)避免频繁保存
+- 实现本地缓存减少重复请求
+- 使用 Loading 状态提升用户体验
+
+### 2. 数据处理优化
+- 对大量日历数据进行分页处理
+- 使用虚拟滚动处理长列表
+- 实现增量更新而非全量替换
+
+### 3. 缓存策略
+```javascript
+class CachedCalendarManager extends CalendarRecordManager {
+    constructor(baseUrl = '', cacheTimeout = 5 * 60 * 1000) { // 5分钟缓存
+        super(baseUrl);
+        this.cache = new Map();
+        this.cacheTimeout = cacheTimeout;
+    }
+    
+    getCacheKey(openid, monthKey) {
+        return `${openid}:${monthKey}`;
+    }
+    
+    async getRecord(openid, monthKey) {
+        const cacheKey = this.getCacheKey(openid, monthKey);
+        const cached = this.cache.get(cacheKey);
+        
+        // 检查缓存是否有效
+        if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
+            return cached.data;
+        }
+        
+        // 从服务器获取数据
+        const result = await super.getRecord(openid, monthKey);
+        
+        // 缓存成功结果
+        if (result.success) {
+            this.cache.set(cacheKey, {
+                data: result,
+                timestamp: Date.now()
+            });
+        }
+        
+        return result;
+    }
+    
+    async saveRecord(openid, monthKey, calendarContent) {
+        const result = await super.saveRecord(openid, monthKey, calendarContent);
+        
+        // 保存成功后清除缓存
+        if (result.success) {
+            const cacheKey = this.getCacheKey(openid, monthKey);
+            this.cache.delete(cacheKey);
+        }
+        
+        return result;
+    }
+    
+    clearCache(openid = null, monthKey = null) {
+        if (openid && monthKey) {
+            // 清除特定缓存
+            const cacheKey = this.getCacheKey(openid, monthKey);
+            this.cache.delete(cacheKey);
+        } else {
+            // 清除所有缓存
+            this.cache.clear();
+        }
+    }
+}
+```
+
+---
+
+## 联系与支持
+
+如有技术问题或需要支持,请联系:
+
+- **技术支持邮箱**: support@dataops-platform.com
+- **开发文档**: https://docs.dataops-platform.com
+- **API更新日志**: https://docs.dataops-platform.com/changelog
+
+---
+
+## 更新日志
+
+### v1.0.0 (2024-01-15)
+- 初始版本发布
+- 支持保存日历记录功能
+- 支持获取日历记录功能
+- 提供完整的前端集成示例
+- 包含错误处理和性能优化建议

+ 217 - 41
docs/wechat-auth-api-guide.md

@@ -4,6 +4,14 @@
 
 本文档为前端开发工程师提供微信认证相关API的详细使用说明,包括路由路径、请求参数、响应格式、错误码说明以及前端示例代码。
 
+**重要说明**:本系统已升级为符合微信官方规范的授权流程,使用15分钟有效期的微信授权码换取长期有效的openid作为用户唯一标识。
+
+## 微信授权流程说明
+
+1. **前端获取授权码**:通过微信小程序API获取临时授权码(15分钟有效期)
+2. **后端换取openid**:使用授权码调用微信API获取用户openid
+3. **用户身份管理**:以openid作为用户唯一标识进行注册、登录等操作
+
 ## 基础信息
 
 - **API基础路径**: `/api/parse`
@@ -51,17 +59,19 @@
 
 | 参数名 | 类型 | 必填 | 说明 | 示例 |
 |--------|------|------|------|------|
-| wechat_code | string | 是 | 微信授权码/openid | "wx_openid_123456" |
+| wechat_code | string | 是 | 微信授权码(15分钟有效期) | "wx_code_12345abcde" |
 | phone_number | string | 否 | 手机号码 | "13800138000" |
 | id_card_number | string | 否 | 身份证号码 | "110101199001011234" |
+| platform | string | 否 | 微信平台类型,默认为小程序 | "miniprogram" |
 
 ### 请求示例
 
 ```json
 {
-  "wechat_code": "wx_openid_123456",
+  "wechat_code": "wx_code_12345abcde",
   "phone_number": "13800138000",
-  "id_card_number": "110101199001011234"
+  "id_card_number": "110101199001011234",
+  "platform": "miniprogram"
 }
 ```
 
@@ -74,7 +84,7 @@
   "return_code": 201,
   "result": {
     "id": "1",
-    "wechat_code": "wx_openid_123456",
+    "openid": "wx_openid_abcd1234567890123456",
     "phone_number": "13800138000",
     "id_card_number": "110101199001011234",
     "login_status": false,
@@ -90,7 +100,7 @@
   "reason": "failed",
   "return_code": 409,
   "result": null,
-  "error": "用户已存在,微信授权码: wx_openid_123456"
+  "error": "用户已存在,openid: wx_openid_abcd1234567890123456"
 }
 ```
 
@@ -98,7 +108,7 @@
 
 #### JavaScript (Fetch API)
 ```javascript
-async function registerWechatUser(wechatCode, phoneNumber = null, idCardNumber = null) {
+async function registerWechatUser(wechatCode, phoneNumber = null, idCardNumber = null, platform = 'miniprogram') {
   try {
     const response = await fetch('/api/parse/wechat-register', {
       method: 'POST',
@@ -108,7 +118,8 @@ async function registerWechatUser(wechatCode, phoneNumber = null, idCardNumber =
       body: JSON.stringify({
         wechat_code: wechatCode,
         phone_number: phoneNumber,
-        id_card_number: idCardNumber
+        id_card_number: idCardNumber,
+        platform: platform
       })
     });
     
@@ -128,7 +139,7 @@ async function registerWechatUser(wechatCode, phoneNumber = null, idCardNumber =
 }
 
 // 使用示例
-registerWechatUser('wx_openid_123456', '13800138000', '110101199001011234')
+registerWechatUser('wx_code_12345abcde', '13800138000', '110101199001011234')
   .then(result => {
     if (result.success) {
       // 注册成功处理
@@ -175,20 +186,22 @@ function registerWechatUser(wechatCode, phoneNumber, idCardNumber) {
 
 ### 接口信息
 - **路径**: `POST /api/parse/wechat-login`
-- **功能**: 微信用户登录,更新登录状态和时间
+- **功能**: 微信用户登录,通过授权码获取openid并更新登录状态和时间
 - **认证**: 无需认证
 
 ### 请求参数
 
 | 参数名 | 类型 | 必填 | 说明 | 示例 |
 |--------|------|------|------|------|
-| wechat_code | string | 是 | 微信授权码/openid | "wx_openid_123456" |
+| wechat_code | string | 是 | 微信授权码(15分钟有效期) | "wx_code_12345abcde" |
+| platform | string | 否 | 微信平台类型,默认为小程序 | "miniprogram" |
 
 ### 请求示例
 
 ```json
 {
-  "wechat_code": "wx_openid_123456"
+  "wechat_code": "wx_code_12345abcde",
+  "platform": "miniprogram"
 }
 ```
 
@@ -201,7 +214,7 @@ function registerWechatUser(wechatCode, phoneNumber, idCardNumber) {
   "return_code": 200,
   "result": {
     "id": "1",
-    "wechat_code": "wx_openid_123456",
+    "openid": "wx_openid_abcd1234567890123456",
     "phone_number": "13800138000",
     "id_card_number": "110101199001011234",
     "login_status": true,
@@ -225,7 +238,7 @@ function registerWechatUser(wechatCode, phoneNumber, idCardNumber) {
 
 #### JavaScript (Fetch API)
 ```javascript
-async function loginWechatUser(wechatCode) {
+async function loginWechatUser(wechatCode, platform = 'miniprogram') {
   try {
     const response = await fetch('/api/parse/wechat-login', {
       method: 'POST',
@@ -233,7 +246,8 @@ async function loginWechatUser(wechatCode) {
         'Content-Type': 'application/json',
       },
       body: JSON.stringify({
-        wechat_code: wechatCode
+        wechat_code: wechatCode,
+        platform: platform
       })
     });
     
@@ -255,7 +269,7 @@ async function loginWechatUser(wechatCode) {
 }
 
 // 使用示例
-loginWechatUser('wx_openid_123456')
+loginWechatUser('wx_code_12345abcde')
   .then(result => {
     if (result.success) {
       // 登录成功处理
@@ -303,13 +317,13 @@ async function loginWechatUser(wechatCode) {
 
 | 参数名 | 类型 | 必填 | 说明 | 示例 |
 |--------|------|------|------|------|
-| wechat_code | string | 是 | 微信授权码/openid | "wx_openid_123456" |
+| openid | string | 是 | 微信用户openid | "wx_openid_abcd1234567890123456" |
 
 ### 请求示例
 
 ```json
 {
-  "wechat_code": "wx_openid_123456"
+  "openid": "wx_openid_abcd1234567890123456"
 }
 ```
 
@@ -340,7 +354,7 @@ async function loginWechatUser(wechatCode) {
 
 #### JavaScript (Fetch API)
 ```javascript
-async function logoutWechatUser(wechatCode) {
+async function logoutWechatUser(openid) {
   try {
     const response = await fetch('/api/parse/wechat-logout', {
       method: 'POST',
@@ -348,7 +362,7 @@ async function logoutWechatUser(wechatCode) {
         'Content-Type': 'application/json',
       },
       body: JSON.stringify({
-        wechat_code: wechatCode
+        openid: openid
       })
     });
     
@@ -370,7 +384,7 @@ async function logoutWechatUser(wechatCode) {
 }
 
 // 使用示例
-logoutWechatUser('wx_openid_123456')
+logoutWechatUser('wx_openid_abcd1234567890123456')
   .then(result => {
     if (result.success) {
       // 登出成功处理
@@ -395,12 +409,12 @@ logoutWechatUser('wx_openid_123456')
 
 | 参数名 | 类型 | 必填 | 传递方式 | 说明 | 示例 |
 |--------|------|------|----------|------|------|
-| wechat_code | string | 是 | Query参数 | 微信授权码/openid | "wx_openid_123456" |
+| openid | string | 是 | Query参数 | 微信用户openid | "wx_openid_abcd1234567890123456" |
 
 ### 请求示例
 
 ```
-GET /api/parse/wechat-user?wechat_code=wx_openid_123456
+GET /api/parse/wechat-user?openid=wx_openid_abcd1234567890123456
 ```
 
 ### 响应示例
@@ -412,7 +426,7 @@ GET /api/parse/wechat-user?wechat_code=wx_openid_123456
   "return_code": 200,
   "result": {
     "id": "1",
-    "wechat_code": "wx_openid_123456",
+    "openid": "wx_openid_abcd1234567890123456",
     "phone_number": "13800138000",
     "id_card_number": "110101199001011234",
     "login_status": true,
@@ -430,7 +444,7 @@ GET /api/parse/wechat-user?wechat_code=wx_openid_123456
   "reason": "failed",
   "return_code": 404,
   "result": null,
-  "error": "未找到微信授权码为 wx_openid_123456 的用户"
+  "error": "未找到openid为 wx_openid_abcd1234567890123456 的用户"
 }
 ```
 
@@ -438,9 +452,9 @@ GET /api/parse/wechat-user?wechat_code=wx_openid_123456
 
 #### JavaScript (Fetch API)
 ```javascript
-async function getWechatUserInfo(wechatCode) {
+async function getWechatUserInfo(openid) {
   try {
-    const url = `/api/parse/wechat-user?wechat_code=${encodeURIComponent(wechatCode)}`;
+    const url = `/api/parse/wechat-user?openid=${encodeURIComponent(openid)}`;
     const response = await fetch(url, {
       method: 'GET',
       headers: {
@@ -464,7 +478,7 @@ async function getWechatUserInfo(wechatCode) {
 }
 
 // 使用示例
-getWechatUserInfo('wx_openid_123456')
+getWechatUserInfo('wx_openid_abcd1234567890123456')
   .then(result => {
     if (result.success) {
       // 显示用户信息
@@ -484,11 +498,11 @@ function displayUserInfo(userInfo) {
 
 #### jQuery
 ```javascript
-function getWechatUserInfo(wechatCode) {
+function getWechatUserInfo(openid) {
   $.ajax({
     url: '/api/parse/wechat-user',
     type: 'GET',
-    data: { wechat_code: wechatCode },
+    data: { openid: openid },
     success: function(data) {
       if (data.return_code === 200) {
         console.log('获取用户信息成功:', data.result);
@@ -519,7 +533,7 @@ function getWechatUserInfo(wechatCode) {
 
 | 参数名 | 类型 | 必填 | 说明 | 示例 |
 |--------|------|------|------|------|
-| wechat_code | string | 是 | 微信授权码/openid | "wx_openid_123456" |
+| openid | string | 是 | 微信用户openid | "wx_openid_abcd1234567890123456" |
 | phone_number | string | 否 | 要更新的手机号码 | "13900139000" |
 | id_card_number | string | 否 | 要更新的身份证号码 | "110101199001011234" |
 
@@ -527,7 +541,7 @@ function getWechatUserInfo(wechatCode) {
 
 ```json
 {
-  "wechat_code": "wx_openid_123456",
+  "openid": "wx_openid_abcd1234567890123456",
   "phone_number": "13900139000",
   "id_card_number": "110101199001011234"
 }
@@ -542,7 +556,7 @@ function getWechatUserInfo(wechatCode) {
   "return_code": 200,
   "result": {
     "id": "1",
-    "wechat_code": "wx_openid_123456",
+    "openid": "wx_openid_abcd1234567890123456",
     "phone_number": "13900139000",
     "id_card_number": "110101199001011234",
     "login_status": true,
@@ -560,7 +574,7 @@ function getWechatUserInfo(wechatCode) {
   "reason": "failed",
   "return_code": 404,
   "result": null,
-  "error": "未找到微信授权码为 wx_openid_123456 的用户"
+  "error": "未找到openid为 wx_openid_abcd1234567890123456 的用户"
 }
 ```
 
@@ -568,10 +582,10 @@ function getWechatUserInfo(wechatCode) {
 
 #### JavaScript (Fetch API)
 ```javascript
-async function updateWechatUserInfo(wechatCode, updateData) {
+async function updateWechatUserInfo(openid, updateData) {
   try {
     const requestData = {
-      wechat_code: wechatCode,
+      openid: openid,
       ...updateData
     };
     
@@ -604,7 +618,7 @@ const updateData = {
   id_card_number: '110101199001011234'
 };
 
-updateWechatUserInfo('wx_openid_123456', updateData)
+updateWechatUserInfo('wx_openid_abcd1234567890123456', updateData)
   .then(result => {
     if (result.success) {
       // 更新成功处理
@@ -625,7 +639,7 @@ export default {
   data() {
     return {
       userForm: {
-        wechatCode: 'wx_openid_123456',
+        openid: 'wx_openid_abcd1234567890123456',
         phoneNumber: '',
         idCardNumber: ''
       },
@@ -643,7 +657,7 @@ export default {
             'Content-Type': 'application/json',
           },
           body: JSON.stringify({
-            wechat_code: this.userForm.wechatCode,
+            openid: this.userForm.openid,
             phone_number: this.userForm.phoneNumber,
             id_card_number: this.userForm.idCardNumber
           })
@@ -676,6 +690,136 @@ export default {
 
 ---
 
+## 前端集成指南
+
+### 微信小程序获取授权码
+
+在微信小程序中,需要先获取授权码,然后传递给后端API:
+
+```javascript
+// 微信小程序端代码
+wx.login({
+  success: function(res) {
+    if (res.code) {
+      // 将授权码发送给后端
+      const wechatCode = res.code;
+      
+      // 调用注册或登录API
+      registerOrLogin(wechatCode);
+    } else {
+      console.error('获取微信授权码失败');
+    }
+  },
+  fail: function(err) {
+    console.error('微信登录失败:', err);
+  }
+});
+
+// 注册或登录函数
+async function registerOrLogin(wechatCode) {
+  // 先尝试登录
+  const loginResult = await loginWechatUser(wechatCode);
+  
+  if (loginResult.success) {
+    // 登录成功,保存用户信息
+    wx.setStorageSync('user_info', loginResult.data);
+    wx.showToast({ title: '登录成功', icon: 'success' });
+  } else if (loginResult.error && loginResult.error.includes('不存在')) {
+    // 用户不存在,尝试注册
+    wx.showModal({
+      title: '首次使用',
+      content: '检测到您是首次使用,是否注册账号?',
+      success: async function(modalRes) {
+        if (modalRes.confirm) {
+          const registerResult = await registerWechatUser(wechatCode);
+          if (registerResult.success) {
+            wx.setStorageSync('user_info', registerResult.data);
+            wx.showToast({ title: '注册成功', icon: 'success' });
+          } else {
+            wx.showToast({ title: '注册失败', icon: 'error' });
+          }
+        }
+      }
+    });
+  } else {
+    wx.showToast({ title: '登录失败', icon: 'error' });
+  }
+}
+```
+
+### 用户状态管理
+
+```javascript
+// 检查用户登录状态
+function checkUserLoginStatus() {
+  const userInfo = wx.getStorageSync('user_info');
+  if (userInfo && userInfo.openid) {
+    // 验证用户状态是否仍然有效
+    return getWechatUserInfo(userInfo.openid)
+      .then(result => {
+        if (result.success && result.data.login_status) {
+          return { isLoggedIn: true, userInfo: result.data };
+        } else {
+          // 清除无效的本地数据
+          wx.removeStorageSync('user_info');
+          return { isLoggedIn: false };
+        }
+      });
+  }
+  return Promise.resolve({ isLoggedIn: false });
+}
+
+// 用户登出
+async function logoutUser() {
+  const userInfo = wx.getStorageSync('user_info');
+  if (userInfo && userInfo.openid) {
+    const result = await logoutWechatUser(userInfo.openid);
+    if (result.success) {
+      wx.removeStorageSync('user_info');
+      wx.showToast({ title: '已退出登录', icon: 'success' });
+      // 跳转到登录页面
+      wx.redirectTo({ url: '/pages/login/login' });
+    }
+  }
+}
+```
+
+### 环境配置
+
+确保在不同环境中正确配置API基础路径:
+
+```javascript
+// config.js
+const config = {
+  development: {
+    apiBaseUrl: 'http://localhost:5000/api/parse'
+  },
+  production: {
+    apiBaseUrl: 'https://your-domain.com/api/parse'
+  }
+};
+
+const currentEnv = 'development'; // 根据实际环境设置
+export const API_BASE_URL = config[currentEnv].apiBaseUrl;
+
+// 在API调用中使用
+async function registerWechatUser(wechatCode, phoneNumber, idCardNumber, platform = 'miniprogram') {
+  const response = await fetch(`${API_BASE_URL}/wechat-register`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({
+      wechat_code: wechatCode,
+      phone_number: phoneNumber,
+      id_card_number: idCardNumber,
+      platform: platform
+    })
+  });
+  return response.json();
+}
+```
+
+---
+
 ## 错误处理最佳实践
 
 ### 1. 统一错误处理函数
@@ -881,10 +1025,42 @@ autoLogin().then(isLoggedIn => {
 
 ## 版本信息
 
-- **文档版本**: v1.0.0
-- **API版本**: v1.0.0
-- **最后更新**: 2025-09-02
+- **文档版本**: v2.0.0
+- **API版本**: v2.0.0  
+- **最后更新**: 2025-01-15
 - **维护者**: DataOps团队
+- **更新内容**: 升级为基于openid的微信认证系统,符合微信官方授权规范
+
+## 环境配置要求
+
+### 后端环境变量
+
+在部署前,请确保设置以下环境变量:
+
+```bash
+# 微信小程序配置(必需)
+WECHAT_MINIPROGRAM_APP_ID=your_miniprogram_app_id_here
+WECHAT_MINIPROGRAM_APP_SECRET=your_miniprogram_app_secret_here
+
+# 微信公众号配置(可选)
+WECHAT_OFFICIAL_APP_ID=your_official_account_app_id_here  
+WECHAT_OFFICIAL_APP_SECRET=your_official_account_app_secret_here
+
+# 数据库配置
+DATABASE_URL=postgresql://username:password@localhost:5432/dataops_db
+```
+
+### 数据库初始化
+
+首次部署时,请执行以下SQL脚本:
+
+```bash
+# 创建微信用户表
+psql -U username -d database_name -f database/create_wechat_users.sql
+
+# 如果从旧版本升级,执行迁移脚本
+psql -U username -d database_name -f database/migrate_wechat_code_to_openid.sql
+```
 
 ---
 

Деякі файли не було показано, через те що забагато файлів було змінено