资讯管理功能设计文档.md 10 KB

资讯管理功能设计文档

本文档遵循《03-API设计规范》《06-日志和错误处理规范》《07-数据库规范》《08-安全规范》《Swagger泛型返回类型字段信息不显示问题解决方案》《前端ID精度丢失问题解决方案》等项目规范。

概述

为提升用户健康知识水平,系统需支持医生发布和管理资讯,患者和家属浏览资讯。资讯采用Markdown格式,支持图片展示,图片存储于本地磁盘,路径可配置,支持文件大小和类型限制,仅依赖MySQL+Spring Boot+本地存储。

背景和目标

  • 背景:当前APP缺乏健康资讯功能,用户无法获取权威的慢病相关知识。
  • 目标:实现资讯的发布、管理和浏览功能,支持Markdown编辑、图片上传,接口风格与现有API统一。

需求与约束

  1. 认证:接口需走Token认证(AuthInterceptor),依赖currentUserId,未认证返回401。
  2. 存储:资讯内容存储于MySQL,图片文件存储于本地磁盘,根路径通过配置文件指定。
  3. 文件限制:资讯图片仅允许图片类型(jpg/png/jpeg/webp),最大文件大小(如5MB)可配置,超限或非法类型返回400。
  4. 数据库:新增资讯相关表,存储标题、内容、概要、医生ID等。
  5. 访问控制:医生可发布和管理资讯,患者和家属可浏览。
  6. API兼容:接口风格与现有API一致,所有响应均为R<T>格式,状态码、错误码、消息体与规范一致。
  7. 日志审计:所有操作需用SLF4J记录info级日志,异常需error级,内容含userId、操作等。
  8. Swagger文档:所有接口需用@ApiResponse+@Content+@Schema标注,确保泛型返回类型文档化。
  9. ID精度:所有响应中的ID字段在VO层转为String,避免前端精度丢失。

设计要点(高层)

  1. Controller层新增接口:
    • 创建资讯:POST /news,参数包含标题、内容、概要等。
    • 更新资讯:PUT /news/{id}
    • 删除资讯:DELETE /news/{id}
    • 获取资讯列表:GET /news/list,支持分页、搜索。
    • 获取资讯详情:GET /news/{id}
    • 上传资讯图片:POST /news/upload-image,返回图片相对路径。
  2. Service层实现资讯CRUD、图片上传、Markdown解析、数据库操作、异常处理、日志记录等逻辑。新建NewsImageService(类似UserAvatarService)处理图片上传,使用NewsImageProperties配置。资讯查询时需联动UserInfoMapper查询医生用户信息,填充authorName(昵称)。
  3. 图片存储参考头像实现,使用 NewsImageProperties 配置,路径为 news-images/{医生ID}/{timestamp}.{ext}

接口清单

API设计示例

创建资讯接口

@Operation(summary = "创建资讯", description = "医生创建新资讯")
@ApiResponses(value = {
  @ApiResponse(responseCode = "200", description = "创建成功",
    content = @Content(mediaType = "application/json",
      schema = @Schema(implementation = R.class))),
  @ApiResponse(responseCode = "400", description = "参数错误",
    content = @Content(mediaType = "application/json",
      schema = @Schema(implementation = R.class))),
  @ApiResponse(responseCode = "401", description = "未认证",
    content = @Content(mediaType = "application/json",
      schema = @Schema(implementation = R.class)))
})
@PostMapping("/news")
public R<String> createNews(@RequestBody CreateNewsRequest request) {
  // 校验参数、保存资讯、返回ID(String格式)
}

上传资讯图片接口

@Operation(summary = "上传资讯图片", description = "上传资讯中使用的图片")
@ApiResponses(value = {
  @ApiResponse(responseCode = "200", description = "上传成功",
    content = @Content(mediaType = "application/json",
      schema = @Schema(implementation = R.class))),
  @ApiResponse(responseCode = "400", description = "文件类型或大小不合法",
    content = @Content(mediaType = "application/json",
      schema = @Schema(implementation = R.class)))
})
@PostMapping(value = "/news/upload-image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<String> uploadImage(@RequestPart("file") MultipartFile file) {
  // 校验文件、保存图片、返回相对路径
}

获取资讯列表接口

@Operation(summary = "获取资讯列表", description = "分页获取资讯列表")
@ApiResponses(value = {
  @ApiResponse(responseCode = "200", description = "获取成功",
    content = @Content(mediaType = "application/json",
      schema = @Schema(implementation = R.class)))
})
@GetMapping("/news/list")
public R<Page<NewsVO>> getNewsList(@RequestParam(defaultValue = "1") int page,
                                   @RequestParam(defaultValue = "10") int size) {
  // 返回分页列表,ID转为String
}

配置示例(application.yml)

news-image:
  root-path: D:/news-images/
  max-size: 5MB
  allowed-types: jpg,png,jpeg,webp

数据库字段设计与兼容性

资讯表 (t_news_info)

  • id: BIGINT PRIMARY KEY AUTO_INCREMENT(雪花算法生成,响应时转为String)
  • title: VARCHAR(30) NOT NULL, 资讯标题
  • content: TEXT NOT NULL, Markdown内容
  • summary: VARCHAR(50), 资讯概要
  • author_id: BIGINT NOT NULL, 医生ID
  • publish_time: DATETIME DEFAULT CURRENT_TIMESTAMP, 发布时间
  • create_time: DATETIME DEFAULT CURRENT_TIMESTAMP(BaseEntity字段)
  • update_time: DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(BaseEntity字段)
  • create_user: BIGINT(BaseEntity字段,创建者ID,转为String)
  • update_user: BIGINT(BaseEntity字段,更新者ID,转为String)
  • version: INT DEFAULT 0(BaseEntity字段,乐观锁版本号)
  • remark: VARCHAR(255)(BaseEntity字段,可选备注)

注:实体类继承BaseEntity,自动包含上述公共字段。id、create_user、update_user在JSON响应中通过ToStringSerializer转为String。

数据传输对象设计

请求VO

CreateNewsRequest(创建资讯请求)

@Schema(description = "创建资讯请求")
public class CreateNewsRequest {
    @Schema(description = "资讯标题", required = true, maxLength = 30)
    @NotBlank(message = "标题不能为空")
    @Size(max = 30, message = "标题长度不能超过30字符")
    private String title;

    @Schema(description = "资讯内容(Markdown格式)", required = true)
    @NotBlank(message = "内容不能为空")
    private String content;

    @Schema(description = "资讯概要", maxLength = 50)
    @Size(max = 50, message = "概要长度不能超过50字符")
    private String summary;
}

UpdateNewsRequest(更新资讯请求)

@Schema(description = "更新资讯请求")
public class UpdateNewsRequest {
    @Schema(description = "资讯标题", required = true, maxLength = 30)
    @NotBlank(message = "标题不能为空")
    @Size(max = 30, message = "标题长度不能超过30字符")
    private String title;

    @Schema(description = "资讯内容(Markdown格式)", required = true)
    @NotBlank(message = "内容不能为空")
    private String content;

    @Schema(description = "资讯概要", maxLength = 50)
    @Size(max = 50, message = "概要长度不能超过50字符")
    private String summary;
}

响应VO

NewsVO(资讯信息响应)

@Schema(description = "资讯信息")
public class NewsVO {
    @Schema(description = "资讯ID", example = "1234567890123456789")
    private String id;

    @Schema(description = "资讯标题")
    private String title;

    @Schema(description = "资讯概要")
    private String summary;

    @Schema(description = "资讯内容(Markdown格式)")
    private String content;

    @Schema(description = "作者ID", example = "1234567890123456789")
    private String authorId;

    @Schema(description = "作者姓名(从UserInfo.nickname获取)")
    private String authorName;

    @Schema(description = "发布时间", example = "2023-12-01 10:00:00")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime publishTime;

    @Schema(description = "封面图片URL(从内容中提取的第一张图片)")
    private String coverImage;
}

注:所有ID字段在VO中定义为String类型,避免前端精度丢失。时间字段使用@JsonFormat格式化。authorName通过注入UserInfoMapper查询UserInfo.nickname填充。

联动查询设计

资讯模块涉及跨表查询用户信息,为填充NewsVO中的authorName字段:

  • 注入依赖:NewsServiceImpl注入UserInfoMapper。
  • 查询逻辑:查询资讯列表或详情时,根据news.author_id调用userInfoMapper.selectById(authorId),获取UserInfo对象。
  • 字段映射:将userInfo.getNickname()赋值给newsVO.setAuthorName()。
  • 异常处理:若用户信息不存在,authorName设为空字符串或默认值,避免查询失败。
  • 性能优化:对于列表查询,可批量查询用户信息,或使用缓存减少数据库访问。

权限与安全

  • 创建/更新/删除接口仅医生角色可操作。
  • 浏览接口公开,但需认证。
  • 图片上传防路径穿越,文件名唯一。

日志与审计

  • 操作日志:[NewsOperation] userId={}, action={}, newsId={}
  • 异常日志:[NewsError] userId={}, error={}
  • 使用SLF4J,日志级别INFO/ERROR。
  • ID在日志中以字符串记录。

测试建议

  1. 单元测试:CRUD逻辑、图片上传校验。
  2. 集成测试:接口联调、Markdown解析。
  3. 性能测试:列表分页、图片访问。

兼容性与前端要求

  • 前端需支持Markdown编辑器、图片上传。
  • 列表显示标题、封面、概要。
  • 详情页解析Markdown展示。
  • 响应中ID字段为String。

迁移/变更管理

  • 先实现后端接口,测试通过后前端接入。
  • 图片存储路径可配置。
接口路径 方法 描述 权限要求 请求体/参数 响应
/news POST 创建资讯 医生 CreateNewsRequest (title, content, summary) R (资讯ID)
/news/{id} PUT 更新资讯 医生 UpdateNewsRequest (title, content, summary) R (资讯ID)
/news/{id} DELETE 删除资讯 医生 路径参数: id R
/news/{id} GET 获取资讯详情 患者/家属 路径参数: id R
/news/list GET 获取资讯列表(分页) 患者/家属 查询参数: page, size R>
/news/upload-image POST 上传资讯图片 医生 MultipartFile (file) R (相对路径)