fix: 成长记录 images 序列化统一 + 租户套餐移除确认流程

- 成长记录: images 统一为 string[],修复 OpenAPI/Java DTO/前端类型
- 租户更新: TenantUpdateRequest 新增 forceRemove,ErrorCode 新增 REMOVE_PACKAGE_HAS_SCHEDULES
- 异常处理: BusinessException 支持附加 data,GlobalExceptionHandler 返回 data 供前端确认弹窗

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-23 14:47:01 +08:00
parent da415703cf
commit 029881f09f
11 changed files with 61 additions and 14 deletions

View File

@ -26910,8 +26910,9 @@
"description": "内容"
},
"images": {
"type": "string",
"description": "图片JSON 数组)"
"type": "array",
"items": { "type": "string", "description": "图片 URL" },
"description": "图片 URL 列表"
},
"recordDate": {
"type": "string",
@ -30152,8 +30153,9 @@
"description": "内容"
},
"images": {
"type": "string",
"description": "图片JSON 数组)"
"type": "array",
"items": { "type": "string", "description": "图片 URL" },
"description": "图片 URL 列表"
},
"recordDate": {
"type": "string",

View File

@ -31,6 +31,8 @@ async function fetchAndFix() {
if (spec.components?.schemas) {
delete spec.components.schemas['ResultObject[]'];
}
// 修复成长记录 images 字段:统一为 array of string避免 SpringDoc 误生成为 string
fixGrowthRecordImagesSchema(spec.components?.schemas);
writeFileSync(OUTPUT, JSON.stringify(spec, null, 2));
console.log('OpenAPI spec written to:', OUTPUT);
@ -61,6 +63,22 @@ function fixSchema(schema) {
if (schema.items) fixSchema(schema.items);
}
/** 将 GrowthRecordCreateRequest/GrowthRecordUpdateRequest 的 images 统一为 array of string */
function fixGrowthRecordImagesSchema(schemas) {
if (!schemas) return;
const arrayOfString = {
type: 'array',
items: { type: 'string', description: '图片 URL' },
description: '图片 URL 列表',
};
for (const name of ['GrowthRecordCreateRequest', 'GrowthRecordUpdateRequest']) {
const s = schemas[name];
if (s?.properties?.images?.type === 'string') {
schemas[name].properties.images = arrayOfString;
}
}
}
function inlineResultObjectArrayRef(paths) {
const inlineSchema = {
type: 'object',

View File

@ -18,8 +18,8 @@ export interface GrowthRecordCreateRequest {
title: string;
/** 内容 */
content?: string;
/** 图片JSON 数组) */
images?: string;
/** 图片 URL 列表 */
images?: string[];
/** 记录日期 */
recordDate?: string;
/** 标签 */

View File

@ -16,8 +16,8 @@ export interface GrowthRecordUpdateRequest {
title?: string;
/** 内容 */
content?: string;
/** 图片JSON 数组) */
images?: string;
/** 图片 URL 列表 */
images?: string[];
/** 记录日期 */
recordDate?: string;
/** 标签 */

View File

@ -37,6 +37,7 @@ public enum ErrorCode {
// Package Errors (3100+)
PACKAGE_NOT_FOUND(3101, "Package not found"),
REMOVE_PACKAGE_HAS_SCHEDULES(3102, "该套餐下有排课计划"),
// User Errors (4000+)
USER_NOT_FOUND(4001, "User not found"),

View File

@ -11,29 +11,41 @@ public class BusinessException extends RuntimeException {
private final Integer code;
private final String message;
private final Object data;
public BusinessException(String message) {
super(message);
this.code = 500;
this.message = message;
this.data = null;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
this.data = null;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.message = errorCode.getMessage();
this.data = null;
}
public BusinessException(ErrorCode errorCode, String message) {
super(message);
this.code = errorCode.getCode();
this.message = message;
this.data = null;
}
public BusinessException(ErrorCode errorCode, String message, Object data) {
super(message);
this.code = errorCode.getCode();
this.message = message;
this.data = data;
}
public static BusinessException of(String message) {

View File

@ -33,9 +33,9 @@ public class GlobalExceptionHandler {
private String activeProfile;
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
public Result<Object> handleBusinessException(BusinessException e, HttpServletRequest request) {
log.warn("业务异常 at {}: {}", request.getRequestURI(), e.getMessage());
return Result.error(e.getCode(), e.getMessage());
return Result.error(e.getCode(), e.getMessage(), e.getData());
}
@ExceptionHandler(MethodArgumentNotValidException.class)

View File

@ -84,6 +84,17 @@ public class Result<T> implements Serializable {
return result;
}
/**
* 错误响应带错误码消息和附加数据
*/
public static <T> Result<T> error(Integer code, String message, T data) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
/**
* 错误响应默认 500
*/

View File

@ -29,13 +29,13 @@ public class GrowthRecordCreateRequest {
@Schema(description = "内容")
private String content;
@Schema(description = "图片 URL 列表")
@Schema(description = "图片 URL 列表", type = "array", implementation = String.class)
private List<String> images;
@Schema(description = "记录日期")
private LocalDate recordDate;
@Schema(description = "标签")
@Schema(description = "标签", type = "array", implementation = String.class)
private List<String> tags;
}

View File

@ -19,13 +19,13 @@ public class GrowthRecordUpdateRequest {
@Schema(description = "内容")
private String content;
@Schema(description = "图片 URL 列表")
@Schema(description = "图片 URL 列表", type = "array", implementation = String.class)
private List<String> images;
@Schema(description = "记录日期")
private LocalDate recordDate;
@Schema(description = "标签")
@Schema(description = "标签", type = "array", implementation = String.class)
private List<String> tags;
}

View File

@ -36,6 +36,9 @@ public class TenantUpdateRequest {
@Schema(description = "课程套餐ID用于三层架构")
private List<Long> collectionIds;
@Schema(description = "是否强制移除套餐(即便套餐下有排课计划)")
private Boolean forceRemove;
@Schema(description = "教师配额")
private Integer teacherQuota;