# 日历记录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)
- 初始版本发布
- 支持保存日历记录功能
- 支持获取日历记录功能
- 提供完整的前端集成示例
- 包含错误处理和性能优化建议
