Skip to content

消息通知 API

消息通知系统提供完整的消息管理功能,包括系统通知、公告、提醒和审批消息的发送、接收、查看和管理。支持个人消息和全局消息,具备消息状态管理和统计分析功能。

功能概述

  • 消息发送:支持发送给指定用户或全局广播
  • 消息类型:支持通知、公告、提醒、审批四种消息类型
  • 优先级管理:支持低、普通、高、紧急四个优先级
  • 状态管理:支持已读/未读、归档状态管理
  • 权限控制:管理员管理所有消息,用户只能查看自己的消息
  • 统计分析:提供消息统计和阅读率分析

消息类型说明

类型英文标识描述用途
通知notification系统通知系统功能更新、维护通知等
公告announcement公告消息公司公告、政策发布等
提醒reminder提醒消息会议提醒、任务提醒等
审批approval审批消息请假审批、报销审批等

优先级说明

优先级英文标识描述显示效果
low低优先级普通显示
普通normal普通优先级默认显示
high高优先级突出显示
紧急urgent紧急红色高亮显示

管理员接口

创建消息

管理员创建新消息,可发送给指定用户或设为全局消息。

  • URL: /api/v1/admin/messages
  • 方法: POST
  • 内容类型: application/json
  • 认证: 需要管理员权限

请求参数

字段类型必填描述示例
titlestring消息标题"部门会议提醒"
contentstring消息内容"明天上午10点部门会议,请准时参加"
typestring消息类型"reminder"
prioritystring消息优先级"normal"
recipient_idsuint[]接收者ID列表[1, 2, 3]
is_globalbool是否全局消息false
expires_atstring过期时间"2024-12-31"
published_atstring发布时间"2024-01-01T00:00:00Z"

请求示例

json
{
  "title": "部门会议提醒",
  "content": "明天上午10点部门会议,请准时参加。会议地点:3楼会议室。",
  "type": "reminder",
  "priority": "normal",
  "recipient_ids": [1, 2, 3],
  "is_global": false,
  "expires_at": "2024-12-31",
  "published_at": "2024-01-01T09:00:00Z"
}

响应示例

成功响应 (201)

json
{
  "code": 200,
  "message": "创建成功",
  "data": {
    "id": 1,
    "title": "部门会议提醒",
    "content": "明天上午10点部门会议,请准时参加。会议地点:3楼会议室。",
    "type": "reminder",
    "priority": "normal",
    "sender_id": 1,
    "sender": {
      "id": 1,
      "username": "admin",
      "real_name": "管理员"
    },
    "is_global": false,
    "expires_at": "2024-12-31T23:59:59Z",
    "published_at": "2024-01-01T09:00:00Z",
    "is_active": true,
    "is_expired": false,
    "is_published": true,
    "created_at": "2024-01-01T08:30:00Z",
    "updated_at": "2024-01-01T08:30:00Z"
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

更新消息

更新指定消息的信息(只有发送者可以更新)。

  • URL: /api/v1/admin/messages/{id}
  • 方法: PUT
  • 内容类型: application/json
  • 认证: 需要管理员权限

请求参数

路径参数

参数类型必填描述示例
iduint消息ID1

请求体参数

字段类型必填描述示例
titlestring消息标题"更新后的会议提醒"
contentstring消息内容"会议时间改为下午2点"
prioritystring消息优先级"high"
is_activebool是否激活true

请求示例

json
{
  "title": "更新后的会议提醒",
  "content": "会议时间改为下午2点,地点不变。",
  "priority": "high",
  "is_active": true
}

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "更新消息成功",
  "data": {
    "id": 1,
    "title": "更新后的会议提醒",
    "content": "会议时间改为下午2点,地点不变。",
    "type": "reminder",
    "priority": "high",
    "sender_id": 1,
    "sender": {
      "id": 1,
      "username": "admin",
      "real_name": "管理员"
    },
    "is_global": false,
    "expires_at": "2024-12-31T23:59:59Z",
    "published_at": "2024-01-01T09:00:00Z",
    "is_active": true,
    "is_expired": false,
    "is_published": true,
    "created_at": "2024-01-01T08:30:00Z",
    "updated_at": "2024-01-01T09:15:00Z"
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

删除消息

删除指定消息(只有发送者可以删除)。

  • URL: /api/v1/admin/messages/{id}
  • 方法: DELETE
  • 认证: 需要管理员权限

请求参数

参数类型必填描述示例
iduint消息ID1

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "删除消息成功",
  "data": null,
  "timestamp": "2024-01-01T12:00:00Z"
}

获取管理员消息列表

获取系统中所有消息列表(管理员权限)。

  • URL: /api/v1/admin/messages
  • 方法: GET
  • 认证: 需要管理员权限

请求参数

参数类型必填描述示例
pageint页码,默认11
page_sizeint每页数量,默认1020
typestring消息类型"reminder"
prioritystring消息优先级"high"
sender_iduint发送者ID1
is_activebool是否激活true
is_globalbool是否全局消息false
searchstring搜索关键词"会议"
date_fromstring开始日期"2024-01-01"
date_tostring结束日期"2024-12-31"

请求示例

GET /api/v1/admin/messages?page=1&page_size=20&type=reminder&priority=high&search=会议

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "获取消息列表成功",
  "data": {
    "messages": [
      {
        "id": 1,
        "title": "更新后的会议提醒",
        "content": "会议时间改为下午2点,地点不变。",
        "type": "reminder",
        "priority": "high",
        "sender_id": 1,
        "sender": {
          "id": 1,
          "username": "admin",
          "real_name": "管理员"
        },
        "is_global": false,
        "expires_at": "2024-12-31T23:59:59Z",
        "published_at": "2024-01-01T09:00:00Z",
        "is_active": true,
        "is_expired": false,
        "is_published": true,
        "created_at": "2024-01-01T08:30:00Z",
        "updated_at": "2024-01-01T09:15:00Z"
      }
    ],
    "pagination": {
      "current_page": 1,
      "page_size": 20,
      "total_items": 1,
      "total_pages": 1,
      "has_next": false,
      "has_prev": false
    }
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

获取消息详情

获取消息的详细信息,包括接收者信息。

  • URL: /api/v1/admin/messages/{id}/detail
  • 方法: GET
  • 认证: 需要管理员权限

请求参数

参数类型必填描述示例
iduint消息ID1

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "获取消息详情成功",
  "data": {
    "id": 1,
    "title": "更新后的会议提醒",
    "content": "会议时间改为下午2点,地点不变。",
    "type": "reminder",
    "priority": "high",
    "sender_id": 1,
    "sender": {
      "id": 1,
      "username": "admin",
      "real_name": "管理员"
    },
    "is_global": false,
    "expires_at": "2024-12-31T23:59:59Z",
    "published_at": "2024-01-01T09:00:00Z",
    "is_active": true,
    "is_expired": false,
    "is_published": true,
    "created_at": "2024-01-01T08:30:00Z",
    "updated_at": "2024-01-01T09:15:00Z",
    "recipients": [
      {
        "id": 1,
        "message_id": 1,
        "recipient_id": 1,
        "recipient": {
          "id": 1,
          "username": "user1",
          "real_name": "张三"
        },
        "is_read": true,
        "read_at": "2024-01-01T10:00:00Z",
        "is_archived": false,
        "archived_at": null,
        "created_at": "2024-01-01T08:30:00Z",
        "updated_at": "2024-01-01T10:00:00Z"
      }
    ]
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

添加消息接收者

为指定消息添加接收者(只有发送者可操作)。

  • URL: /api/v1/admin/messages/{id}/recipients
  • 方法: POST
  • 内容类型: application/json
  • 认证: 需要管理员权限

请求参数

路径参数

参数类型必填描述示例
iduint消息ID1

请求体参数

字段类型必填描述示例
recipient_idsuint[]接收者ID列表[4, 5, 6]

请求示例

json
{
  "recipient_ids": [4, 5, 6]
}

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "添加接收者成功",
  "data": null,
  "timestamp": "2024-01-01T12:00:00Z"
}

发送全局消息

发送全局消息给所有用户。

  • URL: /api/v1/admin/messages/global
  • 方法: POST
  • 内容类型: application/json
  • 认证: 需要管理员权限

请求参数

字段类型必填描述示例
titlestring消息标题"系统维护通知"
contentstring消息内容"系统将于今晚22:00进行维护,预计2小时"
typestring消息类型"announcement"
prioritystring消息优先级"high"
expires_atstring过期时间"2024-12-31"
published_atstring发布时间"2024-01-01T20:00:00Z"

请求示例

json
{
  "title": "系统维护通知",
  "content": "系统将于今晚22:00进行维护,预计2小时,请提前做好相关工作安排。",
  "type": "announcement",
  "priority": "high",
  "expires_at": "2024-12-31",
  "published_at": "2024-01-01T20:00:00Z"
}

响应示例

成功响应 (201)

json
{
  "code": 200,
  "message": "发送全局消息成功",
  "data": {
    "id": 2,
    "title": "系统维护通知",
    "content": "系统将于今晚22:00进行维护,预计2小时,请提前做好相关工作安排。",
    "type": "announcement",
    "priority": "high",
    "sender_id": 1,
    "sender": {
      "id": 1,
      "username": "admin",
      "real_name": "管理员"
    },
    "is_global": true,
    "expires_at": "2024-12-31T23:59:59Z",
    "published_at": "2024-01-01T20:00:00Z",
    "is_active": true,
    "is_expired": false,
    "is_published": true,
    "created_at": "2024-01-01T19:30:00Z",
    "updated_at": "2024-01-01T19:30:00Z"
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

用户接口

获取用户消息列表

获取当前用户的消息列表。

  • URL: /api/v1/messages
  • 方法: GET
  • 认证: 需要用户权限

请求参数

参数类型必填描述示例
pageint页码,默认11
page_sizeint每页数量,默认1010
typestring消息类型"reminder"
prioritystring消息优先级"high"
is_readbool是否已读false
is_archivedbool是否归档false
sender_iduint发送者ID1
searchstring搜索关键词"会议"

请求示例

GET /api/v1/messages?page=1&page_size=10&is_read=false&search=会议

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "获取消息列表成功",
  "data": {
    "messages": [
      {
        "id": 1,
        "title": "更新后的会议提醒",
        "content": "会议时间改为下午2点,地点不变。",
        "type": "reminder",
        "priority": "high",
        "sender_id": 1,
        "sender": {
          "id": 1,
          "username": "admin",
          "real_name": "管理员"
        },
        "is_global": false,
        "expires_at": "2024-12-31T23:59:59Z",
        "published_at": "2024-01-01T09:00:00Z",
        "is_active": true,
        "is_expired": false,
        "is_published": true,
        "is_read": false,
        "read_at": null,
        "is_archived": false,
        "archived_at": null,
        "created_at": "2024-01-01T08:30:00Z",
        "updated_at": "2024-01-01T09:15:00Z"
      }
    ],
    "pagination": {
      "current_page": 1,
      "page_size": 10,
      "total_items": 1,
      "total_pages": 1,
      "has_next": false,
      "has_prev": false
    }
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

获取消息详情

获取单个消息的详细信息。

  • URL: /api/v1/messages/{id}
  • 方法: GET
  • 认证: 需要用户权限

请求参数

参数类型必填描述示例
iduint消息ID1

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "获取消息成功",
  "data": {
    "id": 1,
    "title": "更新后的会议提醒",
    "content": "会议时间改为下午2点,地点不变。",
    "type": "reminder",
    "priority": "high",
    "sender_id": 1,
    "sender": {
      "id": 1,
      "username": "admin",
      "real_name": "管理员"
    },
    "is_global": false,
    "expires_at": "2024-12-31T23:59:59Z",
    "published_at": "2024-01-01T09:00:00Z",
    "is_active": true,
    "is_expired": false,
    "is_published": true,
    "is_read": false,
    "read_at": null,
    "is_archived": false,
    "archived_at": null,
    "created_at": "2024-01-01T08:30:00Z",
    "updated_at": "2024-01-01T09:15:00Z"
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

标记消息为已读

标记一个或多个消息为已读状态。

  • URL: /api/v1/messages/read
  • 方法: POST
  • 内容类型: application/json
  • 认证: 需要用户权限

请求参数

字段类型必填描述示例
message_idsuint[]消息ID列表[1, 2, 3]

请求示例

json
{
  "message_ids": [1, 2, 3]
}

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "标记消息已读成功",
  "data": null,
  "timestamp": "2024-01-01T12:00:00Z"
}

归档消息

标记一个或多个消息为归档状态。

  • URL: /api/v1/messages/archive
  • 方法: POST
  • 内容类型: application/json
  • 认证: 需要用户权限

请求参数

字段类型必填描述示例
message_idsuint[]消息ID列表[1, 2, 3]

请求示例

json
{
  "message_ids": [1, 2, 3]
}

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "归档消息成功",
  "data": null,
  "timestamp": "2024-01-01T12:00:00Z"
}

获取未读消息数量

获取当前用户的未读消息数量。

  • URL: /api/v1/messages/unread-count
  • 方法: GET
  • 认证: 需要用户权限

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "获取未读消息数量成功",
  "data": {
    "unread_count": 5
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

获取用户消息统计

获取当前用户的消息统计信息。

  • URL: /api/v1/messages/stats
  • 方法: GET
  • 认证: 需要用户权限

响应示例

成功响应 (200)

json
{
  "code": 200,
  "message": "获取消息统计成功",
  "data": {
    "total_count": 100,
    "unread_count": 5,
    "read_count": 95,
    "archived_count": 10,
    "notification_count": 80,
    "announcement_count": 10,
    "reminder_count": 8,
    "approval_count": 2
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

数据模型

MessageResponse

typescript
interface MessageResponse {
  id: number;                    // 消息ID
  title: string;                 // 消息标题
  content: string;               // 消息内容
  type: MessageType;             // 消息类型
  priority: MessagePriority;     // 消息优先级
  sender_id: number;             // 发送者ID
  sender?: UserResponse;         // 发送者信息
  is_global: boolean;            // 是否全局消息
  expires_at?: string;           // 过期时间
  published_at?: string;         // 发布时间
  is_active: boolean;            // 是否激活
  is_expired: boolean;           // 是否过期
  is_published: boolean;         // 是否已发布
  created_at: string;            // 创建时间
  updated_at: string;            // 更新时间
}

UserMessageResponse

typescript
interface UserMessageResponse extends MessageResponse {
  is_read: boolean;              // 是否已读
  read_at?: string;              // 阅读时间
  is_archived: boolean;          // 是否归档
  archived_at?: string;          // 归档时间
}

MessageDetailResponse

typescript
interface MessageDetailResponse extends MessageResponse {
  recipients: MessageRecipientResponse[];  // 接收者列表
}

MessageStatsResponse

typescript
interface MessageStatsResponse {
  total_count: number;            // 总消息数
  unread_count: number;           // 未读消息数
  read_count: number;             // 已读消息数
  archived_count: number;         // 归档消息数
  notification_count: number;     // 通知消息数
  announcement_count: number;     // 公告消息数
  reminder_count: number;         // 提醒消息数
  approval_count: number;         // 审批消息数
}

MessageType

typescript
type MessageType = 'notification' | 'announcement' | 'reminder' | 'approval';

MessagePriority

typescript
type MessagePriority = 'low' | 'normal' | 'high' | 'urgent';

错误代码

错误代码描述解决方案
400请求参数错误检查请求参数格式和必填字段
401未认证检查 JWT token 是否有效
403无权限确认用户具有相应权限
404消息不存在检查消息ID是否正确
500服务器内部错误联系系统管理员

消息状态管理

读取状态

  • 未读:用户尚未查看的消息
  • 已读:用户已查看的消息,自动记录阅读时间

归档状态

  • 未归档:正常显示在消息列表中
  • 已归档:已归档的消息,不在常规列表中显示

发布状态

  • 未发布:消息创建但未到发布时间
  • 已发布:消息已到发布时间,用户可见
  • 已过期:消息已过过期时间,不再显示

权限说明

管理员权限

  • 创建、更新、删除所有消息
  • 查看所有消息和接收者信息
  • 发送全局消息
  • 管理消息接收者

用户权限

  • 查看发送给自己的消息
  • 标记消息为已读/归档
  • 查看自己的消息统计
  • 不能修改或删除消息

集成示例

JavaScript 消息管理组件

javascript
import React, { useState, useEffect } from 'react';

const MessageCenter = () => {
  const [messages, setMessages] = useState([]);
  const [unreadCount, setUnreadCount] = useState(0);
  const [loading, setLoading] = useState(false);
  const [selectedType, setSelectedType] = useState('all');
  const [selectedPriority, setSelectedPriority] = useState('all');

  const fetchMessages = async (type = 'all', priority = 'all') => {
    setLoading(true);
    try {
      const params = new URLSearchParams();
      if (type !== 'all') params.append('type', type);
      if (priority !== 'all') params.append('priority', priority);

      const response = await fetch(`/api/v1/messages?${params}`, {
        headers: {
          'Authorization': `Bearer ${localStorage.getItem('token')}`
        }
      });
      const data = await response.json();

      if (data.code === 200) {
        setMessages(data.data.messages);
      }
    } catch (error) {
      console.error('获取消息列表失败:', error);
    } finally {
      setLoading(false);
    }
  };

  const fetchUnreadCount = async () => {
    try {
      const response = await fetch('/api/v1/messages/unread-count', {
        headers: {
          'Authorization': `Bearer ${localStorage.getItem('token')}`
        }
      });
      const data = await response.json();

      if (data.code === 200) {
        setUnreadCount(data.data.unread_count);
      }
    } catch (error) {
      console.error('获取未读消息数量失败:', error);
    }
  };

  const markAsRead = async (messageIds) => {
    try {
      const response = await fetch('/api/v1/messages/read', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('token')}`
        },
        body: JSON.stringify({ message_ids: messageIds })
      });

      if (response.ok) {
        // 更新本地状态
        setMessages(prev => prev.map(msg =>
          messageIds.includes(msg.id)
            ? { ...msg, is_read: true, read_at: new Date().toISOString() }
            : msg
        ));
        setUnreadCount(prev => Math.max(0, prev - messageIds.length));
      }
    } catch (error) {
      console.error('标记已读失败:', error);
    }
  };

  const archiveMessages = async (messageIds) => {
    try {
      const response = await fetch('/api/v1/messages/archive', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${localStorage.getItem('token')}`
        },
        body: JSON.stringify({ message_ids: messageIds })
      });

      if (response.ok) {
        // 从列表中移除已归档的消息
        setMessages(prev => prev.filter(msg => !messageIds.includes(msg.id)));
      }
    } catch (error) {
      console.error('归档消息失败:', error);
    }
  };

  const getPriorityColor = (priority) => {
    const colors = {
      low: '#6c757d',
      normal: '#28a745',
      high: '#ffc107',
      urgent: '#dc3545'
    };
    return colors[priority] || '#6c757d';
  };

  const getTypeIcon = (type) => {
    const icons = {
      notification: '🔔',
      announcement: '📢',
      reminder: '⏰',
      approval: '✅'
    };
    return icons[type] || '📧';
  };

  const formatDate = (dateString) => {
    return new Date(dateString).toLocaleString('zh-CN');
  };

  useEffect(() => {
    fetchMessages();
    fetchUnreadCount();
  }, []);

  useEffect(() => {
    fetchMessages(selectedType, selectedPriority);
  }, [selectedType, selectedPriority]);

  const handleMarkAsRead = (messageId) => {
    markAsRead([messageId]);
  };

  const handleArchiveMessage = (messageId) => {
    archiveMessages([messageId]);
  };

  const handleMarkAllAsRead = () => {
    const unreadIds = messages.filter(msg => !msg.is_read).map(msg => msg.id);
    if (unreadIds.length > 0) {
      markAsRead(unreadIds);
    }
  };

  return (
    <div className="message-center">
      <div className="message-header">
        <h2>消息中心</h2>
        <div className="header-actions">
          <span className="unread-badge">
            未读消息: {unreadCount}
          </span>
          <button
            className="mark-all-read-btn"
            onClick={handleMarkAllAsRead}
            disabled={unreadCount === 0}
          >
            全部标记为已读
          </button>
        </div>
      </div>

      <div className="filters">
        <div className="filter-group">
          <label>消息类型:</label>
          <select
            value={selectedType}
            onChange={(e) => setSelectedType(e.target.value)}
          >
            <option value="all">全部</option>
            <option value="notification">通知</option>
            <option value="announcement">公告</option>
            <option value="reminder">提醒</option>
            <option value="approval">审批</option>
          </select>
        </div>

        <div className="filter-group">
          <label>优先级:</label>
          <select
            value={selectedPriority}
            onChange={(e) => setSelectedPriority(e.target.value)}
          >
            <option value="all">全部</option>
            <option value="low">低</option>
            <option value="normal">普通</option>
            <option value="high">高</option>
            <option value="urgent">紧急</option>
          </select>
        </div>
      </div>

      {loading ? (
        <div className="loading">加载中...</div>
      ) : (
        <div className="message-list">
          {messages.length === 0 ? (
            <div className="empty-state">
              <p>暂无消息</p>
            </div>
          ) : (
            messages.map(message => (
              <div
                key={message.id}
                className={`message-item ${!message.is_read ? 'unread' : ''}`}
              >
                <div className="message-main">
                  <div className="message-header-info">
                    <div className="message-meta">
                      <span className="message-icon">
                        {getTypeIcon(message.type)}
                      </span>
                      <h3 className="message-title">{message.title}</h3>
                      <span
                        className="priority-badge"
                        style={{ backgroundColor: getPriorityColor(message.priority) }}
                      >
                        {message.priority}
                      </span>
                      {!message.is_read && <span className="unread-indicator">未读</span>}
                    </div>
                    <div className="message-info">
                      <span className="sender">
                        发送者: {message.sender?.real_name || '系统'}
                      </span>
                      <span className="time">
                        {formatDate(message.created_at)}
                      </span>
                    </div>
                  </div>

                  <div className="message-content">
                    <p>{message.content}</p>
                  </div>

                  {message.expires_at && (
                    <div className="message-expiry">
                      过期时间: {formatDate(message.expires_at)}
                    </div>
                  )}
                </div>

                <div className="message-actions">
                  {!message.is_read && (
                    <button
                      className="action-btn mark-read-btn"
                      onClick={() => handleMarkAsRead(message.id)}
                    >
                      标记为已读
                    </button>
                  )}
                  <button
                    className="action-btn archive-btn"
                    onClick={() => handleArchiveMessage(message.id)}
                  >
                    归档
                  </button>
                </div>
              </div>
            ))
          )}
        </div>
      )}

      <style jsx>{`
        .message-center {
          max-width: 900px;
          margin: 0 auto;
          padding: 20px;
        }

        .message-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 20px;
          padding-bottom: 15px;
          border-bottom: 1px solid #e0e0e0;
        }

        .message-header h2 {
          margin: 0;
          color: #333;
        }

        .header-actions {
          display: flex;
          align-items: center;
          gap: 15px;
        }

        .unread-badge {
          background: #dc3545;
          color: white;
          padding: 4px 8px;
          border-radius: 12px;
          font-size: 12px;
          font-weight: bold;
        }

        .mark-all-read-btn {
          padding: 6px 12px;
          background: #28a745;
          color: white;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          font-size: 12px;
        }

        .mark-all-read-btn:disabled {
          background: #6c757d;
          cursor: not-allowed;
        }

        .filters {
          display: flex;
          gap: 20px;
          margin-bottom: 20px;
          padding: 15px;
          background: #f8f9fa;
          border-radius: 8px;
        }

        .filter-group {
          display: flex;
          align-items: center;
          gap: 8px;
        }

        .filter-group label {
          font-weight: bold;
          color: #495057;
        }

        .filter-group select {
          padding: 6px 12px;
          border: 1px solid #ced4da;
          border-radius: 4px;
          background: white;
        }

        .message-list {
          display: flex;
          flex-direction: column;
          gap: 15px;
        }

        .message-item {
          border: 1px solid #e0e0e0;
          border-radius: 8px;
          padding: 20px;
          background: white;
          box-shadow: 0 1px 3px rgba(0,0,0,0.1);
          transition: all 0.2s ease;
        }

        .message-item.unread {
          border-left: 4px solid #007bff;
          background: #f8f9ff;
        }

        .message-item:hover {
          box-shadow: 0 2px 8px rgba(0,0,0,0.15);
        }

        .message-header-info {
          margin-bottom: 12px;
        }

        .message-meta {
          display: flex;
          align-items: center;
          gap: 10px;
          margin-bottom: 8px;
        }

        .message-icon {
          font-size: 18px;
        }

        .message-title {
          margin: 0;
          color: #333;
          font-size: 16px;
          flex: 1;
        }

        .priority-badge {
          padding: 2px 8px;
          border-radius: 12px;
          font-size: 11px;
          font-weight: bold;
          color: white;
        }

        .unread-indicator {
          background: #dc3545;
          color: white;
          padding: 2px 8px;
          border-radius: 12px;
          font-size: 11px;
          font-weight: bold;
        }

        .message-info {
          display: flex;
          gap: 15px;
          font-size: 12px;
          color: #6c757d;
        }

        .message-content {
          margin-bottom: 12px;
          line-height: 1.5;
          color: #495057;
        }

        .message-content p {
          margin: 0;
        }

        .message-expiry {
          font-size: 11px;
          color: #6c757d;
          margin-bottom: 12px;
        }

        .message-actions {
          display: flex;
          gap: 10px;
          justify-content: flex-end;
        }

        .action-btn {
          padding: 6px 12px;
          border: none;
          border-radius: 4px;
          cursor: pointer;
          font-size: 12px;
          transition: background-color 0.2s;
        }

        .mark-read-btn {
          background: #28a745;
          color: white;
        }

        .mark-read-btn:hover {
          background: #218838;
        }

        .archive-btn {
          background: #6c757d;
          color: white;
        }

        .archive-btn:hover {
          background: #5a6268;
        }

        .empty-state {
          text-align: center;
          padding: 40px;
          color: #6c757d;
        }

        .loading {
          text-align: center;
          padding: 40px;
          color: #6c757d;
        }
      `}</style>
    </div>
  );
};

export default MessageCenter;

Python 消息管理脚本

python
import requests
import json
from datetime import datetime
from typing import List, Optional, Dict, Any

class MessageManager:
    def __init__(self, base_url: str, admin_token: str, user_token: str):
        self.base_url = base_url
        self.admin_token = admin_token
        self.user_token = user_token
        self.admin_headers = {
            'Authorization': f'Bearer {admin_token}',
            'Content-Type': 'application/json'
        }
        self.user_headers = {
            'Authorization': f'Bearer {user_token}',
            'Content-Type': 'application/json'
        }

    def create_message(self, title: str, content: str, message_type: str,
                      priority: str, recipient_ids: Optional[List[int]] = None,
                      is_global: bool = False, expires_at: Optional[str] = None,
                      published_at: Optional[str] = None) -> Dict[str, Any]:
        """创建消息"""
        data = {
            'title': title,
            'content': content,
            'type': message_type,
            'priority': priority,
            'is_global': is_global
        }

        if recipient_ids:
            data['recipient_ids'] = recipient_ids
        if expires_at:
            data['expires_at'] = expires_at
        if published_at:
            data['published_at'] = published_at

        response = requests.post(
            f'{self.base_url}/api/v1/admin/messages',
            json=data,
            headers=self.admin_headers
        )
        response.raise_for_status()
        return response.json()

    def send_global_message(self, title: str, content: str, message_type: str,
                           priority: str, expires_at: Optional[str] = None,
                           published_at: Optional[str] = None) -> Dict[str, Any]:
        """发送全局消息"""
        data = {
            'title': title,
            'content': content,
            'type': message_type,
            'priority': priority
        }

        if expires_at:
            data['expires_at'] = expires_at
        if published_at:
            data['published_at'] = published_at

        response = requests.post(
            f'{self.base_url}/api/v1/admin/messages/global',
            json=data,
            headers=self.admin_headers
        )
        response.raise_for_status()
        return response.json()

    def get_user_messages(self, message_type: Optional[str] = None,
                         priority: Optional[str] = None,
                         is_read: Optional[bool] = None,
                         search: Optional[str] = None,
                         page: int = 1, page_size: int = 20) -> Dict[str, Any]:
        """获取用户消息列表"""
        params = {
            'page': page,
            'page_size': page_size
        }

        if message_type:
            params['type'] = message_type
        if priority:
            params['priority'] = priority
        if is_read is not None:
            params['is_read'] = is_read
        if search:
            params['search'] = search

        response = requests.get(
            f'{self.base_url}/api/v1/messages',
            params=params,
            headers=self.user_headers
        )
        response.raise_for_status()
        return response.json()

    def get_admin_messages(self, message_type: Optional[str] = None,
                          priority: Optional[str] = None,
                          sender_id: Optional[int] = None,
                          is_active: Optional[bool] = None,
                          is_global: Optional[bool] = None,
                          search: Optional[str] = None,
                          date_from: Optional[str] = None,
                          date_to: Optional[str] = None,
                          page: int = 1, page_size: int = 20) -> Dict[str, Any]:
        """获取管理员消息列表"""
        params = {
            'page': page,
            'page_size': page_size
        }

        if message_type:
            params['type'] = message_type
        if priority:
            params['priority'] = priority
        if sender_id:
            params['sender_id'] = sender_id
        if is_active is not None:
            params['is_active'] = is_active
        if is_global is not None:
            params['is_global'] = is_global
        if search:
            params['search'] = search
        if date_from:
            params['date_from'] = date_from
        if date_to:
            params['date_to'] = date_to

        response = requests.get(
            f'{self.base_url}/api/v1/admin/messages',
            params=params,
            headers=self.admin_headers
        )
        response.raise_for_status()
        return response.json()

    def mark_as_read(self, message_ids: List[int]) -> Dict[str, Any]:
        """标记消息为已读"""
        response = requests.post(
            f'{self.base_url}/api/v1/messages/read',
            json={'message_ids': message_ids},
            headers=self.user_headers
        )
        response.raise_for_status()
        return response.json()

    def archive_messages(self, message_ids: List[int]) -> Dict[str, Any]:
        """归档消息"""
        response = requests.post(
            f'{self.base_url}/api/v1/messages/archive',
            json={'message_ids': message_ids},
            headers=self.user_headers
        )
        response.raise_for_status()
        return response.json()

    def get_unread_count(self) -> Dict[str, Any]:
        """获取未读消息数量"""
        response = requests.get(
            f'{self.base_url}/api/v1/messages/unread-count',
            headers=self.user_headers
        )
        response.raise_for_status()
        return response.json()

    def get_user_stats(self) -> Dict[str, Any]:
        """获取用户消息统计"""
        response = requests.get(
            f'{self.base_url}/api/v1/messages/stats',
            headers=self.user_headers
        )
        response.raise_for_status()
        return response.json()

    def add_recipients(self, message_id: int, recipient_ids: List[int]) -> Dict[str, Any]:
        """添加消息接收者"""
        response = requests.post(
            f'{self.base_url}/api/v1/admin/messages/{message_id}/recipients',
            json={'recipient_ids': recipient_ids},
            headers=self.admin_headers
        )
        response.raise_for_status()
        return response.json()

    def update_message(self, message_id: int, **kwargs) -> Dict[str, Any]:
        """更新消息"""
        response = requests.put(
            f'{self.base_url}/api/v1/admin/messages/{message_id}',
            json=kwargs,
            headers=self.admin_headers
        )
        response.raise_for_status()
        return response.json()

    def delete_message(self, message_id: int) -> Dict[str, Any]:
        """删除消息"""
        response = requests.delete(
            f'{self.base_url}/api/v1/admin/messages/{message_id}',
            headers=self.admin_headers
        )
        response.raise_for_status()
        return response.json()

    def generate_message_report(self, date_from: Optional[str] = None,
                               date_to: Optional[str] = None) -> str:
        """生成消息报告"""
        messages_data = self.get_admin_messages(
            date_from=date_from,
            date_to=date_to,
            page_size=1000
        )

        stats_data = self.get_admin_messages(page_size=1)

        total_messages = len(messages_data['data']['messages'])

        # 统计各类消息数量
        type_counts = {}
        priority_counts = {}

        for message in messages_data['data']['messages']:
            msg_type = message['type']
            priority = message['priority']

            type_counts[msg_type] = type_counts.get(msg_type, 0) + 1
            priority_counts[priority] = priority_counts.get(priority, 0) + 1

        report = f"""
# 消息系统统计报告

## 基本统计
- 总消息数: {total_messages}
- 统计时间范围: {date_from or '开始'}{date_to or '现在'}

## 消息类型分布
"""

        for msg_type, count in type_counts.items():
            report += f"- {msg_type}: {count} 条 ({count/total_messages*100:.1f}%)\n"

        report += "\n## 优先级分布\n"
        for priority, count in priority_counts.items():
            report += f"- {priority}: {count} 条 ({count/total_messages*100:.1f}%)\n"

        return report


# 使用示例
def main():
    # 初始化消息管理器
    message_manager = MessageManager(
        base_url='http://localhost:8080',
        admin_token='your_admin_token',
        user_token='your_user_token'
    )

    print("=== 消息管理示例 ===")

    # 管理员操作
    print("\n--- 管理员操作 ---")

    # 创建消息
    try:
        result = message_manager.create_message(
            title="系统维护通知",
            content="系统将于今晚22:00进行维护,预计2小时。",
            message_type="announcement",
            priority="high",
            is_global=True,
            expires_at="2024-12-31"
        )
        print(f"创建消息成功: {result['message']}")
        message_id = result['data']['id']
    except Exception as e:
        print(f"创建消息失败: {e}")
        message_id = 1

    # 发送全局消息
    try:
        result = message_manager.send_global_message(
            title="春节放假通知",
            content="根据国家规定,春节放假时间为2月10日至2月17日。",
            message_type="announcement",
            priority="high"
        )
        print(f"发送全局消息成功: {result['message']}")
    except Exception as e:
        print(f"发送全局消息失败: {e}")

    # 获取管理员消息列表
    try:
        admin_messages = message_manager.get_admin_messages(
            message_type="announcement",
            priority="high"
        )
        print(f"高优先级公告消息数: {len(admin_messages['data']['messages'])}")
    except Exception as e:
        print(f"获取管理员消息列表失败: {e}")

    # 用户操作
    print("\n--- 用户操作 ---")

    # 获取用户消息列表
    try:
        user_messages = message_manager.get_user_messages(is_read=False)
        print(f"未读消息数: {len(user_messages['data']['messages'])}")
    except Exception as e:
        print(f"获取用户消息列表失败: {e}")

    # 获取未读消息数量
    try:
        unread_data = message_manager.get_unread_count()
        print(f"未读消息数量: {unread_data['data']['unread_count']}")
    except Exception as e:
        print(f"获取未读消息数量失败: {e}")

    # 获取用户消息统计
    try:
        stats = message_manager.get_user_stats()
        print("消息统计:")
        print(f"  总消息数: {stats['data']['total_count']}")
        print(f"  已读消息数: {stats['data']['read_count']}")
        print(f"  未读消息数: {stats['data']['unread_count']}")
        print(f"  归档消息数: {stats['data']['archived_count']}")
    except Exception as e:
        print(f"获取消息统计失败: {e}")

    # 标记消息为已读
    if user_messages['data']['messages']:
        unread_ids = [msg['id'] for msg in user_messages['data']['messages']]
        try:
            result = message_manager.mark_as_read(unread_ids[:1])  # 标记第一条为已读
            print(f"标记已读成功: {result['message']}")
        except Exception as e:
            print(f"标记已读失败: {e}")

    # 生成消息报告
    try:
        report = message_manager.generate_message_report(
            date_from="2024-01-01",
            date_to="2024-12-31"
        )
        print(report)
    except Exception as e:
        print(f"生成报告失败: {e}")


if __name__ == '__main__':
    main()

相关链接

基于 MIT 许可发布