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": "内容" "description": "内容"
}, },
"images": { "images": {
"type": "string", "type": "array",
"description": "图片JSON 数组)" "items": { "type": "string", "description": "图片 URL" },
"description": "图片 URL 列表"
}, },
"recordDate": { "recordDate": {
"type": "string", "type": "string",
@ -30152,8 +30153,9 @@
"description": "内容" "description": "内容"
}, },
"images": { "images": {
"type": "string", "type": "array",
"description": "图片JSON 数组)" "items": { "type": "string", "description": "图片 URL" },
"description": "图片 URL 列表"
}, },
"recordDate": { "recordDate": {
"type": "string", "type": "string",

View File

@ -31,6 +31,8 @@ async function fetchAndFix() {
if (spec.components?.schemas) { if (spec.components?.schemas) {
delete spec.components.schemas['ResultObject[]']; delete spec.components.schemas['ResultObject[]'];
} }
// 修复成长记录 images 字段:统一为 array of string避免 SpringDoc 误生成为 string
fixGrowthRecordImagesSchema(spec.components?.schemas);
writeFileSync(OUTPUT, JSON.stringify(spec, null, 2)); writeFileSync(OUTPUT, JSON.stringify(spec, null, 2));
console.log('OpenAPI spec written to:', OUTPUT); console.log('OpenAPI spec written to:', OUTPUT);
@ -61,6 +63,22 @@ function fixSchema(schema) {
if (schema.items) fixSchema(schema.items); 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) { function inlineResultObjectArrayRef(paths) {
const inlineSchema = { const inlineSchema = {
type: 'object', type: 'object',

View File

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

View File

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

View File

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

View File

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

View File

@ -33,9 +33,9 @@ public class GlobalExceptionHandler {
private String activeProfile; private String activeProfile;
@ExceptionHandler(BusinessException.class) @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()); 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) @ExceptionHandler(MethodArgumentNotValidException.class)

View File

@ -84,6 +84,17 @@ public class Result<T> implements Serializable {
return result; 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 * 错误响应默认 500
*/ */

View File

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

View File

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

View File

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