# 日历记录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} 返回操作结果 */ 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} 返回查询结果 */ 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 ``` ### 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 (

{currentMonth.getFullYear()}年{currentMonth.getMonth() + 1}月日历

{hasRecord ? (
{calendarContent.map((item, index) => (
{item.date}
{item.events?.map((event, idx) => ( {event} ))}
{item.notes}
))}
) : (

本月暂无日历记录

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