# 资讯管理功能设计文档 > 本文档遵循《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`格式,状态码、错误码、消息体与规范一致。 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}`。 ## 接口清单 | 接口路径 | 方法 | 描述 | 权限要求 | 请求体/参数 | 响应 | |----------|------|------|----------|-------------|------| | `/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 (相对路径) | ## API设计示例 ### 创建资讯接口 ```java @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 createNews(@RequestBody CreateNewsRequest request) { // 校验参数、保存资讯、返回ID(String格式) } ``` ### 上传资讯图片接口 ```java @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 uploadImage(@RequestPart("file") MultipartFile file) { // 校验文件、保存图片、返回相对路径 } ``` ### 获取资讯列表接口 ```java @Operation(summary = "获取资讯列表", description = "分页获取资讯列表") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "获取成功", content = @Content(mediaType = "application/json", schema = @Schema(implementation = R.class))) }) @GetMapping("/news/list") public R> getNewsList(@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { // 返回分页列表,ID转为String } ``` ## 配置示例(application.yml) ```yaml 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(创建资讯请求) ```java @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(更新资讯请求) ```java @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(资讯信息响应) ```java @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。 ## 迁移/变更管理 - 先实现后端接口,测试通过后前端接入。 - 图片存储路径可配置。