openapi: 3.0.3
info:
  title: 校园心理健康反馈平台 API
  description: |
    校园心理健康反馈平台 3.0 API 文档。

    所有响应使用 `{ success, data }` 或 `{ success, error }` 包装。
    `/v1` 是当前规范版本；旧 `/api/*` 路径通过 308 跳转兼容。
    管理端接口支持管理员 JWT，也兼容共享密钥 Bearer 认证。
  version: 3.0.0
  contact:
    name: API 支持
    email: support@example.com
servers:
  - url: /v1
    description: API v1
tags:
  - name: System
    description: 健康检查
  - name: Public Issues
    description: 公开问题提交、列表与追踪
  - name: Public Content
    description: 校园洞察与知识库
  - name: Authentication
    description: 管理员认证与密码重置
  - name: Admin Users
    description: 管理员用户管理
  - name: Admin Issues
    description: 问题处理、备注、回复与批量操作
  - name: Assignment
    description: 自动分配规则与统计
  - name: SLA
    description: SLA 规则与违规记录
  - name: Knowledge
    description: 管理端知识库
  - name: Operations
    description: 审计、导出与指标
paths:
  /api/health:
    get:
      tags: [System]
      operationId: getHealth
      summary: 获取系统健康状态
      description: 返回 D1、KV、限流、趋势与告警的脱敏状态。
      responses:
        '200':
          $ref: '#/components/responses/HealthSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/issues:
    get:
      tags: [Public Issues]
      operationId: listPublicIssues
      summary: 获取公开问题列表
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - $ref: '#/components/parameters/Status'
        - $ref: '#/components/parameters/Category'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/StartDate'
        - $ref: '#/components/parameters/EndDate'
        - $ref: '#/components/parameters/PublicSortField'
        - $ref: '#/components/parameters/SortOrder'
      responses:
        '200':
          $ref: '#/components/responses/PublicIssueListSuccess'
        default:
          $ref: '#/components/responses/Error'
    post:
      tags: [Public Issues]
      operationId: createIssue
      summary: 提交新问题
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueCreateRequest'
            example:
              name: 张三
              studentId: '2024001001001'
              email: student@example.com
              notifyByEmail: true
              category: facility
              content: 图书馆空调故障，需要尽快处理。
              isPublic: false
              isReported: false
      responses:
        '201':
          $ref: '#/components/responses/IssueCreatedSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/issues/{trackingCode}:
    get:
      tags: [Public Issues]
      operationId: getIssueByTrackingCode
      summary: 根据追踪编号查询问题
      parameters:
        - $ref: '#/components/parameters/TrackingCode'
      responses:
        '200':
          $ref: '#/components/responses/IssueTrackingSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/insights:
    get:
      tags: [Public Content]
      operationId: getPublicInsights
      summary: 获取校园心理反馈洞察
      parameters:
        - $ref: '#/components/parameters/Days'
        - $ref: '#/components/parameters/StartDate'
        - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          $ref: '#/components/responses/InsightsSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/knowledge:
    get:
      tags: [Public Content]
      operationId: listPublicKnowledge
      summary: 获取公开知识库
      responses:
        '200':
          $ref: '#/components/responses/KnowledgeListSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/auth/login:
    post:
      tags: [Authentication]
      operationId: loginAdmin
      summary: 管理员登录
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            example:
              username: admin
              password: Admin123!
              rememberMe: false
      responses:
        '200':
          $ref: '#/components/responses/LoginSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/auth/logout:
    post:
      tags: [Authentication]
      operationId: logoutAdmin
      summary: 管理员登出
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/MessageSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/auth/forgot-password:
    post:
      tags: [Authentication]
      operationId: forgotAdminPassword
      summary: 发起密码重置
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username]
              properties:
                username:
                  type: string
            example:
              username: admin
      responses:
        '200':
          $ref: '#/components/responses/MessageSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/auth/reset-password:
    post:
      tags: [Authentication]
      operationId: resetAdminPassword
      summary: 使用重置令牌更新密码
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token, newPassword]
              properties:
                token:
                  type: string
                newPassword:
                  type: string
                  format: password
                  minLength: 8
            example:
              token: reset-token-example
              newPassword: NewPass123!
      responses:
        '200':
          $ref: '#/components/responses/MessageSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/users:
    get:
      tags: [Admin Users]
      operationId: listAdminUsers
      summary: 获取管理员列表
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/UserListSuccess'
        default:
          $ref: '#/components/responses/Error'
    post:
      tags: [Admin Users]
      operationId: createAdminUser
      summary: 创建管理员用户
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserCreateRequest'
            example:
              username: handler1
              password: Handler123!
              displayName: 处理员1
              role: handler
      responses:
        '201':
          $ref: '#/components/responses/UserSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/users/{id}:
    parameters:
      - $ref: '#/components/parameters/ResourceId'
    patch:
      tags: [Admin Users]
      operationId: updateAdminUser
      summary: 更新管理员用户
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserUpdateRequest'
            example:
              displayName: 处理员一号
              role: admin
              isEnabled: true
      responses:
        '200':
          $ref: '#/components/responses/UserSuccess'
        default:
          $ref: '#/components/responses/Error'
    delete:
      tags: [Admin Users]
      operationId: deleteAdminUser
      summary: 禁用管理员用户
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/MessageSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/issues:
    get:
      tags: [Admin Issues]
      operationId: listAdminIssues
      summary: 获取后台问题列表
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - $ref: '#/components/parameters/Status'
        - $ref: '#/components/parameters/Category'
        - $ref: '#/components/parameters/Priority'
        - $ref: '#/components/parameters/Search'
        - $ref: '#/components/parameters/StartDate'
        - $ref: '#/components/parameters/EndDate'
        - $ref: '#/components/parameters/AssignedTo'
        - $ref: '#/components/parameters/SlaStatus'
      responses:
        '200':
          $ref: '#/components/responses/AdminIssueListSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/issues/{id}:
    parameters:
      - $ref: '#/components/parameters/ResourceId'
    get:
      tags: [Admin Issues]
      operationId: getAdminIssue
      summary: 获取问题完整详情
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/AdminIssueSuccess'
        default:
          $ref: '#/components/responses/Error'
    patch:
      tags: [Admin Issues]
      operationId: updateAdminIssue
      summary: 更新问题
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssueUpdateRequest'
            example:
              status: in_progress
              priority: high
              assignedTo: handler1
              publicSummary: 已安排相关部门处理
              updatedAt: '2026-06-12T08:00:00.000Z'
      responses:
        '200':
          $ref: '#/components/responses/AdminIssueSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/issues/{id}/notes:
    post:
      tags: [Admin Issues]
      operationId: createIssueNote
      summary: 添加内部备注
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ResourceId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                content:
                  type: string
                  maxLength: 2000
            example:
              content: 已联系相关老师跟进。
      responses:
        '201':
          $ref: '#/components/responses/NoteSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/issues/{id}/replies:
    post:
      tags: [Admin Issues]
      operationId: createIssueReply
      summary: 添加问题回复
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ResourceId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                content:
                  type: string
                  maxLength: 2000
                isPublic:
                  type: boolean
                  default: true
            example:
              content: 该问题已进入处理流程。
              isPublic: true
      responses:
        '201':
          $ref: '#/components/responses/ReplySuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/issues/batch:
    post:
      tags: [Admin Issues]
      operationId: batchUpdateIssues
      summary: 批量更新问题
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchIssueUpdateRequest'
            example:
              issueIds: [1, 2, 3]
              updates:
                status: in_review
                priority: high
                assignedTo: handler1
              updatedAt: '2026-06-12T08:00:00.000Z'
      responses:
        '200':
          $ref: '#/components/responses/BatchIssueSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/assign-rules:
    get:
      tags: [Assignment]
      operationId: listAssignRules
      summary: 获取自动分配规则
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/AssignRuleListSuccess'
        default:
          $ref: '#/components/responses/Error'
    post:
      tags: [Assignment]
      operationId: createAssignRule
      summary: 创建自动分配规则
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AssignRuleCreateRequest'
            example:
              name: 学业压力分配
              category: academic
              keywords: [考试, 成绩]
              assignTo: handler1
              priority: 10
              isEnabled: true
      responses:
        '201':
          $ref: '#/components/responses/AssignRuleSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/assign-rules/{id}:
    parameters:
      - $ref: '#/components/parameters/ResourceId'
    patch:
      tags: [Assignment]
      operationId: updateAssignRule
      summary: 更新自动分配规则
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AssignRuleUpdateRequest'
            example:
              keywords: [考试, 成绩, 挂科]
              priority: 20
              isEnabled: true
              updatedAt: '2026-06-12T08:00:00.000Z'
      responses:
        '200':
          $ref: '#/components/responses/AssignRuleSuccess'
        default:
          $ref: '#/components/responses/Error'
    delete:
      tags: [Assignment]
      operationId: deleteAssignRule
      summary: 删除自动分配规则
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/MessageSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/assign-stats:
    get:
      tags: [Assignment]
      operationId: getAssignStats
      summary: 获取分配统计
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      parameters:
        - name: period
          in: query
          schema:
            type: string
            enum: [week, month]
            default: week
          example: week
        - $ref: '#/components/parameters/StartDate'
        - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          $ref: '#/components/responses/AssignStatsSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/sla/rules:
    get:
      tags: [SLA]
      operationId: listSlaRules
      summary: 获取 SLA 规则
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/SlaRuleListSuccess'
        default:
          $ref: '#/components/responses/Error'
    post:
      tags: [SLA]
      operationId: createSlaRule
      summary: 创建 SLA 规则
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SLARuleCreateRequest'
            example:
              name: 普通问题 24 小时响应
              priority: normal
              responseHours: 24
              resolutionHours: 72
              isEnabled: true
      responses:
        '201':
          $ref: '#/components/responses/SlaRuleSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/sla/rules/{id}:
    patch:
      tags: [SLA]
      operationId: updateSlaRule
      summary: 更新 SLA 规则
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/ResourceId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SLARuleUpdateRequest'
            example:
              responseHours: 12
              resolutionHours: 48
              isEnabled: true
              updatedAt: '2026-06-12T08:00:00.000Z'
      responses:
        '200':
          $ref: '#/components/responses/SlaRuleSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/sla/violations:
    get:
      tags: [SLA]
      operationId: listSlaViolations
      summary: 获取 SLA 违规记录
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [warning, violated]
          example: violated
        - $ref: '#/components/parameters/StartDate'
        - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          $ref: '#/components/responses/SlaViolationListSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/knowledge:
    get:
      tags: [Knowledge]
      operationId: listAdminKnowledge
      summary: 获取全部知识库条目
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      responses:
        '200':
          $ref: '#/components/responses/KnowledgeListSuccess'
        default:
          $ref: '#/components/responses/Error'
    post:
      tags: [Knowledge]
      operationId: createKnowledgeItem
      summary: 创建知识库条目
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KnowledgeCreateRequest'
            example:
              title: 学业压力
              tag: academic_pressure
              content: 先把任务拆成今天能完成的一小步。
              sortOrder: 10
              isEnabled: true
      responses:
        '201':
          $ref: '#/components/responses/KnowledgeItemSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/knowledge/{id}:
    parameters:
      - $ref: '#/components/parameters/ResourceId'
    patch:
      tags: [Knowledge]
      operationId: updateKnowledgeItem
      summary: 更新知识库条目
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KnowledgeUpdateRequest'
            example:
              title: 学业压力应对
              content: 把任务拆成更小步骤，并给自己保留休息时间。
              sortOrder: 20
              isEnabled: true
              updatedAt: '2026-06-12T08:00:00.000Z'
      responses:
        '200':
          $ref: '#/components/responses/KnowledgeItemSuccess'
        default:
          $ref: '#/components/responses/Error'
    delete:
      tags: [Knowledge]
      operationId: deleteKnowledgeItem
      summary: 删除知识库条目
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [updatedAt]
              properties:
                updatedAt:
                  type: string
                  format: date-time
            example:
              updatedAt: '2026-06-12T08:00:00.000Z'
      responses:
        '200':
          $ref: '#/components/responses/MessageSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/actions:
    get:
      tags: [Operations]
      operationId: listAdminActions
      summary: 获取审计日志
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - name: actionType
          in: query
          schema:
            type: string
          example: issue_updated
        - name: targetId
          in: query
          schema:
            type: integer
          example: 42
      responses:
        '200':
          $ref: '#/components/responses/AdminActionListSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/export:
    get:
      tags: [Operations]
      operationId: exportIssues
      summary: 导出问题数据
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      parameters:
        - name: format
          in: query
          schema:
            type: string
            enum: [csv, json]
            default: csv
          example: json
        - $ref: '#/components/parameters/Status'
        - $ref: '#/components/parameters/Category'
        - $ref: '#/components/parameters/StartDate'
        - $ref: '#/components/parameters/EndDate'
      responses:
        '200':
          $ref: '#/components/responses/ExportSuccess'
        default:
          $ref: '#/components/responses/Error'
  /api/admin/metrics:
    get:
      tags: [Operations]
      operationId: getAdminMetrics
      summary: 获取后台运营指标
      security:
        - BearerAuth: []
        - SharedKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/StartDate'
        - $ref: '#/components/parameters/EndDate'
        - name: period
          in: query
          schema:
            type: string
            enum: [day, week, month]
            default: week
          example: week
        - name: refresh
          in: query
          schema:
            type: boolean
            default: false
          example: false
      responses:
        '200':
          $ref: '#/components/responses/MetricsSuccess'
        default:
          $ref: '#/components/responses/Error'
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 使用管理员登录返回的 JWT。
    SharedKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 备用共享密钥，格式为 `Bearer <ADMIN_SECRET_KEY>`。
  parameters:
    ResourceId:
      name: id
      in: path
      required: true
      schema:
        type: integer
        minimum: 1
      example: 42
    TrackingCode:
      name: trackingCode
      in: path
      required: true
      schema:
        type: string
        pattern: '^[A-HJ-NP-Z2-9]{8}$'
      example: ABCD23EF
    Page:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
      example: 1
    PageSize:
      name: pageSize
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
      example: 20
    Status:
      name: status
      in: query
      schema:
        $ref: '#/components/schemas/IssueStatus'
      example: in_progress
    Category:
      name: category
      in: query
      schema:
        $ref: '#/components/schemas/IssueCategory'
      example: facility
    Priority:
      name: priority
      in: query
      schema:
        $ref: '#/components/schemas/IssuePriority'
      example: high
    Search:
      name: q
      in: query
      schema:
        type: string
        maxLength: 200
      example: 图书馆
    StartDate:
      name: startDate
      in: query
      schema:
        type: string
        format: date
      example: '2026-06-01'
    EndDate:
      name: endDate
      in: query
      schema:
        type: string
        format: date
      example: '2026-06-30'
    Days:
      name: days
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 365
        default: 90
      example: 90
    PublicSortField:
      name: sortField
      in: query
      schema:
        type: string
        enum: [createdAt, updatedAt, status]
        default: updatedAt
      example: updatedAt
    SortOrder:
      name: sortOrder
      in: query
      schema:
        type: string
        enum: [asc, desc]
        default: desc
      example: desc
    AssignedTo:
      name: assignedTo
      in: query
      schema:
        type: string
      example: handler1
    SlaStatus:
      name: slaStatus
      in: query
      schema:
        type: string
        enum: [normal, warning, violated]
      example: warning
  schemas:
    IssueStatus:
      type: string
      enum: [submitted, in_review, in_progress, resolved, closed]
    IssueCategory:
      type: string
      enum: [academic, facility, service, complaint, counseling, other]
    IssuePriority:
      type: string
      enum: [low, normal, high, urgent]
    DistressType:
      type: string
      nullable: true
      enum: [academic_pressure, relationship, adaptation, mood, sleep, other]
    SceneTag:
      type: string
      nullable: true
      enum: [dormitory, classroom, library, self_study, cafeteria, playground, other]
    Pagination:
      type: object
      required: [page, pageSize, total, totalPages]
      properties:
        page:
          type: integer
        pageSize:
          type: integer
        total:
          type: integer
        totalPages:
          type: integer
    PublicIssue:
      type: object
      required: [trackingCode, content, category, priority, status, createdAt, updatedAt]
      properties:
        trackingCode:
          type: string
          example: ABCD23EF
        content:
          type: string
          example: 图书馆空调故障，需要尽快处理。
        category:
          $ref: '#/components/schemas/IssueCategory'
        distressType:
          $ref: '#/components/schemas/DistressType'
        sceneTag:
          $ref: '#/components/schemas/SceneTag'
        status:
          $ref: '#/components/schemas/IssueStatus'
        priority:
          $ref: '#/components/schemas/IssuePriority'
        publicSummary:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    Issue:
      type: object
      required: [id, trackingCode, content, category, priority, status, isPublic, createdAt, updatedAt]
      properties:
        id:
          type: integer
          example: 42
        trackingCode:
          type: string
          example: ABCD23EF
        name:
          type: string
          description: 仅管理端返回。
          example: 张三
        studentId:
          type: string
          description: 仅管理端返回。
          example: '2024001001001'
        email:
          type: string
          format: email
          nullable: true
          description: 仅管理端返回。
        content:
          type: string
          example: 图书馆空调故障，需要尽快处理。
        category:
          $ref: '#/components/schemas/IssueCategory'
        distressType:
          $ref: '#/components/schemas/DistressType'
        sceneTag:
          $ref: '#/components/schemas/SceneTag'
        priority:
          $ref: '#/components/schemas/IssuePriority'
        status:
          $ref: '#/components/schemas/IssueStatus'
        isPublic:
          type: boolean
        isReported:
          type: boolean
        assignedTo:
          type: string
          nullable: true
        assignedAt:
          type: string
          format: date-time
          nullable: true
        publicSummary:
          type: string
          nullable: true
        slaResponseDeadline:
          type: string
          format: date-time
          nullable: true
        slaResolutionDeadline:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    IssueCreateRequest:
      type: object
      required: [name, studentId, category, content]
      properties:
        name:
          type: string
          maxLength: 50
        studentId:
          type: string
          pattern: '^\d{4}$|^\d{5}$|^\d{13}$'
        email:
          type: string
          format: email
        notifyByEmail:
          type: boolean
          default: false
        category:
          $ref: '#/components/schemas/IssueCategory'
        distressType:
          $ref: '#/components/schemas/DistressType'
        sceneTag:
          $ref: '#/components/schemas/SceneTag'
        content:
          type: string
          minLength: 10
          maxLength: 2000
        isPublic:
          type: boolean
          default: false
        isReported:
          type: boolean
          default: false
    IssueUpdateRequest:
      type: object
      required: [updatedAt]
      properties:
        status:
          $ref: '#/components/schemas/IssueStatus'
        category:
          $ref: '#/components/schemas/IssueCategory'
        priority:
          $ref: '#/components/schemas/IssuePriority'
        assignedTo:
          type: string
          nullable: true
        assignedAt:
          type: string
          format: date-time
          nullable: true
        publicSummary:
          type: string
          nullable: true
        distressType:
          $ref: '#/components/schemas/DistressType'
        sceneTag:
          $ref: '#/components/schemas/SceneTag'
        isPublic:
          type: boolean
        updatedAt:
          type: string
          format: date-time
    BatchIssueUpdateRequest:
      type: object
      required: [issueIds, updates, updatedAt]
      properties:
        issueIds:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: integer
            minimum: 1
        updates:
          type: object
          properties:
            status:
              $ref: '#/components/schemas/IssueStatus'
            priority:
              $ref: '#/components/schemas/IssuePriority'
            assignedTo:
              type: string
              nullable: true
        updatedAt:
          type: string
          format: date-time
    User:
      type: object
      required: [id, username, displayName, role, isEnabled, createdAt, updatedAt]
      properties:
        id:
          type: integer
        username:
          type: string
        displayName:
          type: string
        role:
          type: string
          enum: [admin, handler]
        isEnabled:
          type: boolean
        lastLoginAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    UserCreateRequest:
      type: object
      required: [username, password, displayName, role]
      properties:
        username:
          type: string
          pattern: '^[A-Za-z0-9_]+$'
        password:
          type: string
          format: password
          minLength: 8
        displayName:
          type: string
          maxLength: 50
        role:
          type: string
          enum: [admin, handler]
    UserUpdateRequest:
      type: object
      minProperties: 1
      properties:
        displayName:
          type: string
          maxLength: 50
        role:
          type: string
          enum: [admin, handler]
        isEnabled:
          type: boolean
    LoginRequest:
      type: object
      required: [username, password]
      properties:
        username:
          type: string
        password:
          type: string
          format: password
        rememberMe:
          type: boolean
          default: false
    SLARule:
      type: object
      required: [id, name, priority, responseHours, resolutionHours, isEnabled, createdAt, updatedAt]
      properties:
        id:
          type: integer
        name:
          type: string
        priority:
          $ref: '#/components/schemas/IssuePriority'
        responseHours:
          type: integer
        resolutionHours:
          type: integer
        isEnabled:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    SLARuleCreateRequest:
      type: object
      required: [name, priority, responseHours, resolutionHours]
      properties:
        name:
          type: string
        priority:
          $ref: '#/components/schemas/IssuePriority'
        responseHours:
          type: integer
          minimum: 1
          maximum: 720
        resolutionHours:
          type: integer
          minimum: 1
          maximum: 720
        isEnabled:
          type: boolean
          default: true
    SLARuleUpdateRequest:
      type: object
      required: [updatedAt]
      properties:
        name:
          type: string
        priority:
          $ref: '#/components/schemas/IssuePriority'
        responseHours:
          type: integer
          minimum: 1
          maximum: 720
        resolutionHours:
          type: integer
          minimum: 1
          maximum: 720
        isEnabled:
          type: boolean
        updatedAt:
          type: string
          format: date-time
    AssignRule:
      type: object
      required: [id, name, keywords, assignTo, priority, isEnabled, createdAt, updatedAt]
      properties:
        id:
          type: integer
        name:
          type: string
        category:
          allOf:
            - $ref: '#/components/schemas/IssueCategory'
          nullable: true
        keywords:
          type: array
          items:
            type: string
        assignTo:
          type: string
        priority:
          type: integer
        isEnabled:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    AssignRuleCreateRequest:
      type: object
      required: [name, assignTo]
      properties:
        name:
          type: string
        category:
          allOf:
            - $ref: '#/components/schemas/IssueCategory'
          nullable: true
        keywords:
          type: array
          items:
            type: string
        assignTo:
          type: string
        priority:
          type: integer
          minimum: 0
          maximum: 100
        isEnabled:
          type: boolean
          default: true
    AssignRuleUpdateRequest:
      type: object
      required: [updatedAt]
      properties:
        name:
          type: string
        category:
          allOf:
            - $ref: '#/components/schemas/IssueCategory'
          nullable: true
        keywords:
          type: array
          items:
            type: string
        assignTo:
          type: string
        priority:
          type: integer
          minimum: 0
          maximum: 100
        isEnabled:
          type: boolean
        updatedAt:
          type: string
          format: date-time
    KnowledgeItem:
      type: object
      required: [id, title, tag, content, sortOrder, isEnabled, createdAt, updatedAt]
      properties:
        id:
          type: integer
        title:
          type: string
        tag:
          $ref: '#/components/schemas/DistressType'
        content:
          type: string
        sortOrder:
          type: integer
        isEnabled:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    KnowledgeCreateRequest:
      type: object
      required: [title, tag, content]
      properties:
        title:
          type: string
        tag:
          $ref: '#/components/schemas/DistressType'
        content:
          type: string
        sortOrder:
          type: integer
          minimum: 0
          default: 0
        isEnabled:
          type: boolean
          default: true
    KnowledgeUpdateRequest:
      type: object
      required: [updatedAt]
      properties:
        title:
          type: string
        tag:
          $ref: '#/components/schemas/DistressType'
        content:
          type: string
        sortOrder:
          type: integer
          minimum: 0
        isEnabled:
          type: boolean
        updatedAt:
          type: string
          format: date-time
    InternalNote:
      type: object
      properties:
        id:
          type: integer
        content:
          type: string
        createdBy:
          type: string
        createdAt:
          type: string
          format: date-time
    IssueReply:
      type: object
      properties:
        id:
          type: integer
        content:
          type: string
        isPublic:
          type: boolean
        createdBy:
          type: string
        createdAt:
          type: string
          format: date-time
    ErrorResponse:
      type: object
      required: [success, error]
      properties:
        success:
          type: boolean
          enum: [false]
        error:
          type: string
      example:
        success: false
        error: 请求参数无效
  responses:
    Error:
      description: |
        请求失败。常见状态码：400 参数错误、401 未认证、403 无权限或来源不受信任、
        404 资源不存在、409 并发冲突、429 请求过于频繁、500 服务器错误。
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            validation:
              summary: 参数错误
              value:
                success: false
                error: 请求参数无效
            unauthorized:
              summary: 未认证
              value:
                success: false
                error: 未授权访问
            conflict:
              summary: 并发冲突
              value:
                success: false
                error: 记录已被其他管理员更新，请刷新后重试
    HealthSuccess:
      description: 健康状态
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              status: healthy
              timestamp: '2026-06-18T08:00:00.000Z'
              version: 3.0.0
              services:
                d1:
                  status: connected
                  latency: 12
                kv:
                  status: connected
                  latency: 7
              alerts: []
    PublicIssueListSuccess:
      description: 公开问题列表
      content:
        application/json:
          schema:
            type: object
            properties:
              success:
                type: boolean
              data:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/PublicIssue'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
          example:
            success: true
            data:
              items:
                - trackingCode: ABCD23EF
                  content: 图书馆空调故障，需要尽快处理。
                  category: facility
                  distressType: null
                  sceneTag: null
                  status: in_progress
                  priority: high
                  publicSummary: 已安排后勤处理
                  createdAt: '2026-06-12T08:00:00.000Z'
                  updatedAt: '2026-06-12T09:00:00.000Z'
              pagination:
                page: 1
                pageSize: 20
                total: 1
                totalPages: 1
    IssueCreatedSuccess:
      description: 问题已创建
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              trackingCode: ABCD23EF
              status: submitted
              createdAt: '2026-06-18T08:00:00.000Z'
              message: 问题已提交，请保存追踪编号以便查询
    IssueTrackingSuccess:
      description: 公开追踪详情
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              trackingCode: ABCD23EF
              content: 图书馆空调故障，需要尽快处理。
              category: facility
              status: in_progress
              priority: high
              publicSummary: 已安排后勤处理
              publicReplies: []
              timeline: []
              createdAt: '2026-06-12T08:00:00.000Z'
              updatedAt: '2026-06-12T09:00:00.000Z'
    InsightsSuccess:
      description: 聚合洞察
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              overview:
                publicCounselingIssues: 2
              range:
                startDate: '2026-03-21'
                endDate: '2026-06-18'
                days: 90
              sceneHotspots:
                - scene: dormitory
                  total: 1
                  pending: 1
              distressTypes:
                - distressType: sleep
                  total: 1
    KnowledgeListSuccess:
      description: 知识库列表
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              items:
                - id: 1
                  title: 学业压力
                  tag: academic_pressure
                  content: 先把任务拆成今天能完成的一小步。
                  sortOrder: 10
                  isEnabled: true
                  createdAt: '2026-06-01T08:00:00.000Z'
                  updatedAt: '2026-06-01T08:00:00.000Z'
    LoginSuccess:
      description: 登录成功
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              token: eyJhbGciOiJIUzI1NiJ9.example.signature
              expiresAt: '2026-06-19T08:00:00.000Z'
              user:
                id: 1
                username: admin
                displayName: 管理员
                role: admin
    MessageSuccess:
      description: 操作成功
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              message: 操作成功
    UserListSuccess:
      description: 管理员列表
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              items:
                - id: 1
                  username: admin
                  displayName: 管理员
                  role: admin
                  isEnabled: true
                  lastLoginAt: '2026-06-18T07:00:00.000Z'
                  createdAt: '2026-06-01T08:00:00.000Z'
                  updatedAt: '2026-06-18T07:00:00.000Z'
    UserSuccess:
      description: 管理员用户
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              id: 2
              username: handler1
              displayName: 处理员1
              role: handler
              isEnabled: true
              lastLoginAt: null
              createdAt: '2026-06-18T08:00:00.000Z'
              updatedAt: '2026-06-18T08:00:00.000Z'
    AdminIssueListSuccess:
      description: 后台问题列表与统计
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              items:
                - id: 42
                  trackingCode: ABCD23EF
                  name: 张三
                  studentId: '2024001001001'
                  content: 图书馆空调故障，需要尽快处理。
                  category: facility
                  priority: high
                  status: in_progress
                  isPublic: false
                  assignedTo: handler1
                  createdAt: '2026-06-12T08:00:00.000Z'
                  updatedAt: '2026-06-12T09:00:00.000Z'
              pagination:
                page: 1
                pageSize: 20
                total: 1
                totalPages: 1
              stats:
                total: 1
    AdminIssueSuccess:
      description: 后台问题详情
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              issue:
                id: 42
                trackingCode: ABCD23EF
                name: 张三
                studentId: '2024001001001'
                content: 图书馆空调故障，需要尽快处理。
                category: facility
                priority: high
                status: in_progress
                isPublic: false
                assignedTo: handler1
                createdAt: '2026-06-12T08:00:00.000Z'
                updatedAt: '2026-06-12T09:00:00.000Z'
              internalNotes: []
              replies: []
              history: []
    NoteSuccess:
      description: 内部备注已创建
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              id: 10
              content: 已联系相关老师跟进。
              createdBy: admin
              createdAt: '2026-06-18T08:00:00.000Z'
    ReplySuccess:
      description: 回复已创建
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              id: 20
              content: 该问题已进入处理流程。
              isPublic: true
              createdBy: admin
              createdAt: '2026-06-18T08:00:00.000Z'
    BatchIssueSuccess:
      description: 批量更新结果
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              updatedCount: 2
              failedIds: [3]
    AssignRuleListSuccess:
      description: 自动分配规则列表
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              items:
                - id: 1
                  name: 学业压力分配
                  category: academic
                  keywords: [考试, 成绩]
                  assignTo: handler1
                  priority: 10
                  isEnabled: true
                  createdAt: '2026-06-01T08:00:00.000Z'
                  updatedAt: '2026-06-01T08:00:00.000Z'
    AssignRuleSuccess:
      description: 自动分配规则
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              id: 1
              name: 学业压力分配
              category: academic
              keywords: [考试, 成绩]
              assignTo: handler1
              priority: 10
              isEnabled: true
              createdAt: '2026-06-01T08:00:00.000Z'
              updatedAt: '2026-06-01T08:00:00.000Z'
    AssignStatsSuccess:
      description: 分配统计
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              summary:
                totalIssues: 10
                pending: 3
                inProgress: 2
                resolved: 5
              handlers:
                - username: handler1
                  displayName: 处理员1
                  pending: 2
                  inProgress: 1
                  resolved: 3
                  avgResponseTime: 2.5
                  avgResolutionTime: 48
              trend:
                - period: 2026-W24
                  created: 4
                  resolved: 2
    SlaRuleListSuccess:
      description: SLA 规则列表
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              items:
                - id: 1
                  name: 普通问题 24 小时响应
                  priority: normal
                  responseHours: 24
                  resolutionHours: 72
                  isEnabled: true
                  createdAt: '2026-06-01T08:00:00.000Z'
                  updatedAt: '2026-06-01T08:00:00.000Z'
    SlaRuleSuccess:
      description: SLA 规则
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              id: 1
              name: 普通问题 24 小时响应
              priority: normal
              responseHours: 24
              resolutionHours: 72
              isEnabled: true
              createdAt: '2026-06-01T08:00:00.000Z'
              updatedAt: '2026-06-01T08:00:00.000Z'
    SlaViolationListSuccess:
      description: SLA 违规记录
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              items:
                - issueId: 42
                  trackingCode: ABCD23EF
                  priority: high
                  assignedTo: handler1
                  slaStatus: violated
                  responseDeadline: '2026-06-12T10:00:00.000Z'
                  resolutionDeadline: '2026-06-13T08:00:00.000Z'
                  createdAt: '2026-06-12T08:00:00.000Z'
    KnowledgeItemSuccess:
      description: 知识库条目
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              id: 1
              title: 学业压力
              tag: academic_pressure
              content: 先把任务拆成今天能完成的一小步。
              sortOrder: 10
              isEnabled: true
              createdAt: '2026-06-01T08:00:00.000Z'
              updatedAt: '2026-06-01T08:00:00.000Z'
    AdminActionListSuccess:
      description: 审计日志
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              items:
                - id: 100
                  actionType: issue_updated
                  targetType: issue
                  targetId: 42
                  details:
                    status: in_progress
                  performedBy: admin
                  performedAt: '2026-06-18T08:00:00.000Z'
              pagination:
                page: 1
                pageSize: 20
                total: 1
                totalPages: 1
    ExportSuccess:
      description: CSV 或 JSON 导出文件
      content:
        text/csv:
          schema:
            type: string
          example: "tracking_code,category,status\nABCD23EF,facility,in_progress"
        application/json:
          schema:
            type: object
          example:
            metadata:
              format: json
              exportedAt: '2026-06-18T08:00:00.000Z'
              rowCount: 1
            issues:
              - id: 42
                trackingCode: ABCD23EF
                category: facility
                status: in_progress
    MetricsSuccess:
      description: 运营指标
      content:
        application/json:
          schema:
            type: object
          example:
            success: true
            data:
              overview:
                totalIssues: 100
                pendingIssues: 12
                resolvedIssues: 80
              statusDistribution:
                - status: resolved
                  total: 80
              trend:
                - period: 2026-W24
                  created: 10
                  resolved: 8
