重写复现方法

This commit is contained in:
2025-08-28 09:13:05 +08:00
parent c55871d134
commit f3f919053f
6 changed files with 274 additions and 185 deletions

View File

@@ -1,20 +1,15 @@
package com.mini.capi.job; package com.mini.capi.job;
import com.mini.capi.biz.domain.*;
import com.mini.capi.biz.service.*;
import com.mini.capi.model.ApiResult; import com.mini.capi.model.ApiResult;
import com.mini.capi.sys.service.DbService;
import com.mini.capi.sys.service.DockerService;
import com.mini.capi.utils.*; import com.mini.capi.utils.*;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping("/Sys/jobs") @RequestMapping("/Sys/jobs")
@@ -22,23 +17,10 @@ public class taskEnable {
@Resource @Resource
private SshInfoService sshInfoService; private DockerService dockerService;
@Resource @Resource
private SshUserService sshUserService; private DbService dbService;
@Resource
private DockerHostService dockerHostService;
@Resource
private DiskMountService diskMountService;
@Resource
private SysHostService sysHostService;
@Resource
private Executor hostExecutor;
/** /**
@@ -49,75 +31,20 @@ public class taskEnable {
if (!vToken.isValidToken(token)) { if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌"); return ApiResult.error(401, "无效的访问令牌");
} }
try { return dockerService.jobHostDisk();
List<DockerHost> dockerHosts = dockerHostService.list();
List<String> errorList = Collections.synchronizedList(new ArrayList<>());
// 并行处理所有宿主机
CompletableFuture<?>[] futures = dockerHosts.stream()
.map(host -> CompletableFuture.runAsync(() -> handleSingleHost(host, errorList), hostExecutor))
.toArray(CompletableFuture[]::new);
// 等待全部完成
CompletableFuture.allOf(futures).join();
return errorList.isEmpty()
? ApiResult.success()
: ApiResult.error();
} catch (Exception e) {
return ApiResult.error(101, e.getMessage());
}
} }
private void handleSingleHost(DockerHost host, List<String> errorList) { /**
try { * 运行全部任务数据同步
SshUser sshUser = sshUserService.getById(host.getUserId()); */
SshInfo sshInfo = sshInfoService.getById(host.getHostId()); @GetMapping("/getTaskSyncDbInfo")
/* 1. 采集实时数据 */ public ApiResult<?> jobSyncAllTask(String token) {
HostInfo.Result r = HostInfo.collect( if (!vToken.isValidToken(token)) {
sshInfo.getHostIp(), return ApiResult.error(401, "无效的访问令牌");
Integer.parseInt(sshInfo.getHostPort()),
sshUser.getCUsername(),
sshUser.getCPassword());
/* 2. 主机维度 saveOrUpdate */
SysHost sysHost = r.host;
sysHost.setSysHostId(host.getHostId());
sysHost.setUpdateTime(vDate.getNow());
sysHost.setDokerHostId(host.getDokerHostId());
sysHostService.saveOrUpdate(sysHost);
/* 3. 处理磁盘:先查库做索引,再比对 */
List<DiskMount> dbDisks = diskMountService.lambdaQuery()
.eq(DiskMount::getSysHostId, host.getHostId())
.list();
Map<String, DiskMount> dbDiskMap = dbDisks.stream()
.collect(Collectors.toMap(DiskMount::getMountPoint, Function.identity()));
List<DiskMount> toSaveOrUpdate = new ArrayList<>();
Set<String> liveMountPoint = new HashSet<>();
for (DiskMount d : r.disks) {
liveMountPoint.add(d.getMountPoint());
DiskMount exist = dbDiskMap.get(d.getMountPoint());
if (exist != null) {
d.setDiskMountId(exist.getDiskMountId());
} else {
d.setDiskMountId(vId.getUid());
}
d.setSysHostId(host.getHostId());
d.setUpdateTime(vDate.getNow());
toSaveOrUpdate.add(d);
}
/* 4. 批量保存/更新 */
diskMountService.saveOrUpdateBatch(toSaveOrUpdate);
/* 5. 删除实时已消失的盘 */
List<String> delIds = dbDisks.stream()
.filter(d -> !liveMountPoint.contains(d.getMountPoint()))
.map(DiskMount::getDiskMountId)
.collect(Collectors.toList());
if (!delIds.isEmpty()) {
diskMountService.removeByIds(delIds);
}
} catch (Exception e) {
// 仅记录异常,不中断其它任务
errorList.add(String.format("hostId=%s, error=%s", host.getHostId(), e.getMessage()));
} }
return dbService.jobSyncAllTask();
} }
} }

View File

@@ -1,20 +1,15 @@
package com.mini.capi.sys.controller; package com.mini.capi.sys.controller;
import com.mini.capi.biz.domain.DbConfig;
import com.mini.capi.biz.service.DbConfigService;
import com.mini.capi.config.DataSourceConfig;
import com.mini.capi.model.ApiResult; import com.mini.capi.model.ApiResult;
import com.mini.capi.model.TabResult; import com.mini.capi.model.TabResult;
import com.mini.capi.sys.service.DbService;
import com.mini.capi.utils.vToken; import com.mini.capi.utils.vToken;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
@RestController @RestController
@RequestMapping("/Sys/dbs") @RequestMapping("/Sys/dbs")
@@ -22,7 +17,7 @@ public class dbController {
@Resource @Resource
private DbConfigService dbConfigService; private DbService dbService;
/** /**
* 获取MySQL的当前连接下的所有数据表 * 获取MySQL的当前连接下的所有数据表
@@ -33,45 +28,18 @@ public class dbController {
if (!vToken.isValidToken(token)) { if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌"); return ApiResult.error(401, "无效的访问令牌");
} }
return dbService.listSourceTables(dbId);
// 2. 校验dbId参数
if (dbId == null || dbId.trim().isEmpty()) {
return ApiResult.error(400, "数据库ID不能为空");
}
// 3. 查询数据库配置并校验
DbConfig dbConfig = dbConfigService.getById(dbId);
if (dbConfig == null) {
return ApiResult.error(404, "未找到ID为[" + dbId + "]的数据库配置");
}
try {
JdbcTemplate jdbcTemplate = DataSourceConfig.createJdbcTemplate(dbConfig);
// 补充参数传递
String querySql = "SELECT TABLE_NAME,TABLE_COMMENT FROM information_schema.tables WHERE TABLE_SCHEMA = ?";
List<Map<String, Object>> result = jdbcTemplate.queryForList(querySql, dbConfig.getDbName());
List<TabResult> data = result.stream()
.map(row -> {
String tableName = row.get("TABLE_NAME") != null ? row.get("TABLE_NAME").toString() : "";
String tableDesc = row.get("TABLE_COMMENT") != null ? row.get("TABLE_COMMENT").toString() : "";
return new TabResult(tableName, getComment(tableName, tableDesc));
})
.sorted(Comparator.comparing(TabResult::getTableName)) // 按表名排序
.toList();
return ApiResult.success(data);
} catch (Exception e) {
return ApiResult.error(101, e.getMessage());
}
} }
private String getComment(String tableName, String tableDesc) { /**
boolean hasTableDesc = tableDesc != null && !tableDesc.trim().isEmpty(); * 运行单个任务
// 根据表描述是否存在返回不同格式 */
if (hasTableDesc) { @GetMapping("/getTaskSyncDbByInfo")
return String.format("%s(%s)", tableDesc.trim(), tableName); public ApiResult<?> jobSyncOneTask(String token, String taskId) {
} else { if (!vToken.isValidToken(token)) {
return tableName; return ApiResult.error(401, "无效的访问令牌");
} }
return dbService.jobSyncOneTask(taskId);
} }
} }

View File

@@ -0,0 +1,70 @@
package com.mini.capi.sys.controller;
import com.mini.capi.model.ApiResult;
import com.mini.capi.sys.service.HostService;
import com.mini.capi.utils.vToken;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
@RestController
@RequestMapping("/Sys/hosts")
public class hostController {
@Resource
private HostService hostService;
@GetMapping("/getApiInfo")
public ApiResult<List<HostService.SnapshotDTO>> getApiInfo(String token) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
return hostService.getApiInfo();
}
/**
* 获取容器列表
*/
@GetMapping("/getApiDockerInfo")
public ApiResult<?> getDockerInfo(String token) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
return hostService.getDockerInfo();
}
/**
* 启动容器
*/
@GetMapping("/getApiStartDockerInfo")
public ApiResult<?> startDockerInfo(String id, String token) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
return hostService.startDockerInfo(id);
}
/**
* 停止容器
*/
@GetMapping("/getApiStopDockerInfo")
public ApiResult<?> stopDockerInfo(String id, String token) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
return hostService.stopDockerInfo(id);
}
}

View File

@@ -1,4 +1,4 @@
package com.mini.capi.job; package com.mini.capi.sys.service;
import com.mini.capi.biz.domain.DbConfig; import com.mini.capi.biz.domain.DbConfig;
import com.mini.capi.biz.domain.SyncTask; import com.mini.capi.biz.domain.SyncTask;
@@ -8,28 +8,22 @@ import com.mini.capi.biz.service.SyncTaskLogService;
import com.mini.capi.biz.service.SyncTaskService; import com.mini.capi.biz.service.SyncTaskService;
import com.mini.capi.config.DataSourceConfig; import com.mini.capi.config.DataSourceConfig;
import com.mini.capi.model.ApiResult; import com.mini.capi.model.ApiResult;
import com.mini.capi.utils.vToken; import com.mini.capi.model.TabResult;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.sql.*; import java.sql.*;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@RestController @Service
@RequestMapping("/Sys/dbs") public class DbService {
public class taskDbSync {
@Resource @Resource
private SyncTaskService syncTaskService; private SyncTaskService syncTaskService;
@@ -43,17 +37,57 @@ public class taskDbSync {
@Resource @Resource
private Executor hostExecutor; private Executor hostExecutor;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final String dsValue = LocalDate.now().format(DATE_FORMATTER); private static final String dsValue = LocalDate.now().format(DATE_FORMATTER);
public ApiResult<List<TabResult>> listSourceTables(String dbId) {
// 1. 校验dbId参数
if (dbId == null || dbId.trim().isEmpty()) {
return ApiResult.error(400, "数据库ID不能为空");
}
// 2. 查询数据库配置并校验
DbConfig dbConfig = dbConfigService.getById(dbId);
if (dbConfig == null) {
return ApiResult.error(404, "未找到ID为[" + dbId + "]的数据库配置");
}
try {
JdbcTemplate jdbcTemplate = DataSourceConfig.createJdbcTemplate(dbConfig);
// 补充参数传递
String querySql = "SELECT TABLE_NAME,TABLE_COMMENT FROM information_schema.tables WHERE TABLE_SCHEMA = ?";
List<Map<String, Object>> result = jdbcTemplate.queryForList(querySql, dbConfig.getDbName());
List<TabResult> data = result.stream()
.map(row -> {
String tableName = row.get("TABLE_NAME") != null ? row.get("TABLE_NAME").toString() : "";
String tableDesc = row.get("TABLE_COMMENT") != null ? row.get("TABLE_COMMENT").toString() : "";
return new TabResult(tableName, getComment(tableName, tableDesc));
})
.sorted(Comparator.comparing(TabResult::getTableName)) // 按表名排序
.toList();
return ApiResult.success(data);
} catch (Exception e) {
return ApiResult.error(101, e.getMessage());
}
}
private String getComment(String tableName, String tableDesc) {
boolean hasTableDesc = tableDesc != null && !tableDesc.trim().isEmpty();
// 根据表描述是否存在返回不同格式
if (hasTableDesc) {
return String.format("%s(%s)", tableDesc.trim(), tableName);
} else {
return tableName;
}
}
/** /**
* 运行全部任务 * 运行全部任务
*/ */
@GetMapping("/getTaskSyncDbInfo") public ApiResult<?> jobSyncAllTask() {
public ApiResult<?> jobSyncAllTask(String token) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
List<SyncTask> syncTasks = syncTaskService.list(); List<SyncTask> syncTasks = syncTaskService.list();
// 记录是否有任务失败仅用于后台日志不影响接口返回 // 记录是否有任务失败仅用于后台日志不影响接口返回
List<String> errorMessages = new ArrayList<>(); List<String> errorMessages = new ArrayList<>();
@@ -69,11 +103,7 @@ public class taskDbSync {
/** /**
* 运行单个任务 * 运行单个任务
*/ */
@GetMapping("/getTaskSyncDbByInfo") public ApiResult<?> jobSyncOneTask( String taskId) {
public ApiResult<?> jobSyncOneTask(String token, String taskId) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
try { try {
SyncTask task = syncTaskService.getById(taskId); SyncTask task = syncTaskService.getById(taskId);
// 记录是否有任务失败仅用于后台日志不影响接口返回 // 记录是否有任务失败仅用于后台日志不影响接口返回

View File

@@ -0,0 +1,117 @@
package com.mini.capi.sys.service;
import com.mini.capi.biz.domain.*;
import com.mini.capi.biz.service.*;
import com.mini.capi.model.ApiResult;
import com.mini.capi.utils.HostInfo;
import com.mini.capi.utils.vDate;
import com.mini.capi.utils.vId;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
public class DockerService {
@Resource
private SshInfoService sshInfoService;
@Resource
private SshUserService sshUserService;
@Resource
private DockerHostService dockerHostService;
@Resource
private DiskMountService diskMountService;
@Resource
private SysHostService sysHostService;
@Resource
private Executor hostExecutor;
/**
* 获取容器主机的磁盘使用情况
*/
public ApiResult<?> jobHostDisk() {
try {
List<DockerHost> dockerHosts = dockerHostService.list();
List<String> errorList = Collections.synchronizedList(new ArrayList<>());
// 并行处理所有宿主机
CompletableFuture<?>[] futures = dockerHosts.stream()
.map(host -> CompletableFuture.runAsync(() -> handleSingleHost(host, errorList), hostExecutor))
.toArray(CompletableFuture[]::new);
// 等待全部完成
CompletableFuture.allOf(futures).join();
return errorList.isEmpty()
? ApiResult.success()
: ApiResult.error();
} catch (Exception e) {
return ApiResult.error(101, e.getMessage());
}
}
private void handleSingleHost(DockerHost host, List<String> errorList) {
try {
SshUser sshUser = sshUserService.getById(host.getUserId());
SshInfo sshInfo = sshInfoService.getById(host.getHostId());
/* 1. 采集实时数据 */
HostInfo.Result r = HostInfo.collect(
sshInfo.getHostIp(),
Integer.parseInt(sshInfo.getHostPort()),
sshUser.getCUsername(),
sshUser.getCPassword());
/* 2. 主机维度 saveOrUpdate */
SysHost sysHost = r.host;
sysHost.setSysHostId(host.getHostId());
sysHost.setUpdateTime(vDate.getNow());
sysHost.setDokerHostId(host.getDokerHostId());
sysHostService.saveOrUpdate(sysHost);
/* 3. 处理磁盘:先查库做索引,再比对 */
List<DiskMount> dbDisks = diskMountService.lambdaQuery()
.eq(DiskMount::getSysHostId, host.getHostId())
.list();
Map<String, DiskMount> dbDiskMap = dbDisks.stream()
.collect(Collectors.toMap(DiskMount::getMountPoint, Function.identity()));
List<DiskMount> toSaveOrUpdate = new ArrayList<>();
Set<String> liveMountPoint = new HashSet<>();
for (DiskMount d : r.disks) {
liveMountPoint.add(d.getMountPoint());
DiskMount exist = dbDiskMap.get(d.getMountPoint());
if (exist != null) {
d.setDiskMountId(exist.getDiskMountId());
} else {
d.setDiskMountId(vId.getUid());
}
d.setSysHostId(host.getHostId());
d.setUpdateTime(vDate.getNow());
toSaveOrUpdate.add(d);
}
/* 4. 批量保存/更新 */
diskMountService.saveOrUpdateBatch(toSaveOrUpdate);
/* 5. 删除实时已消失的盘 */
List<String> delIds = dbDisks.stream()
.filter(d -> !liveMountPoint.contains(d.getMountPoint()))
.map(DiskMount::getDiskMountId)
.collect(Collectors.toList());
if (!delIds.isEmpty()) {
diskMountService.removeByIds(delIds);
}
} catch (Exception e) {
// 仅记录异常,不中断其它任务
errorList.add(String.format("hostId=%s, error=%s", host.getHostId(), e.getMessage()));
}
}
}

View File

@@ -1,4 +1,4 @@
package com.mini.capi.sys.controller; package com.mini.capi.sys.service;
import com.mini.capi.biz.domain.DockerContainerInfo; import com.mini.capi.biz.domain.DockerContainerInfo;
import com.mini.capi.biz.domain.DockerHost; import com.mini.capi.biz.domain.DockerHost;
@@ -12,11 +12,8 @@ import com.mini.capi.model.ApiResult;
import com.mini.capi.utils.HostRuntime; import com.mini.capi.utils.HostRuntime;
import com.mini.capi.utils.docker; import com.mini.capi.utils.docker;
import com.mini.capi.utils.vDate; import com.mini.capi.utils.vDate;
import com.mini.capi.utils.vToken;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
@@ -26,9 +23,8 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@RestController @Service
@RequestMapping("/Sys/hosts") public class HostService {
public class sysController {
@Resource @Resource
@@ -57,7 +53,7 @@ public class sysController {
public String memUsage; public String memUsage;
public String swapTotal; public String swapTotal;
public String swapUsed; public String swapUsed;
public List<DiskDTO> disks; public List<SnapshotDTO.DiskDTO> disks;
public String netRxBytes; public String netRxBytes;
public String netTxBytes; public String netTxBytes;
public double load1; public double load1;
@@ -78,7 +74,7 @@ public class sysController {
dto.swapTotal = humanBytes(s.swapTotal); dto.swapTotal = humanBytes(s.swapTotal);
dto.swapUsed = humanBytes(s.swapUsed); dto.swapUsed = humanBytes(s.swapUsed);
dto.disks = s.disks.stream() dto.disks = s.disks.stream()
.map(d -> new DiskDTO(d.path, .map(d -> new SnapshotDTO.DiskDTO(d.path,
humanBytes(d.total), humanBytes(d.total),
humanBytes(d.free), humanBytes(d.free),
humanBytes(d.used), humanBytes(d.used),
@@ -253,12 +249,7 @@ public class sysController {
} }
@GetMapping("/getApiInfo") public ApiResult<List<SnapshotDTO>> getApiInfo() {
public ApiResult<List<SnapshotDTO>> getApiInfo(String token) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
try { try {
// 1. 新建一个一次性 List // 1. 新建一个一次性 List
List<HostRuntime.Snapshot> snapshots = List<HostRuntime.Snapshot> snapshots =
@@ -280,11 +271,7 @@ public class sysController {
/** /**
* 获取容器列表 * 获取容器列表
*/ */
@GetMapping("/getApiDockerInfo") public ApiResult<?> getDockerInfo() {
public ApiResult<?> getDockerInfo(String token) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
try { try {
List<DockerHost> dockerHosts = dockerHostService.list(); List<DockerHost> dockerHosts = dockerHostService.list();
List<String> errorList = Collections.synchronizedList(new ArrayList<>()); List<String> errorList = Collections.synchronizedList(new ArrayList<>());
@@ -306,11 +293,7 @@ public class sysController {
/** /**
* 启动容器 * 启动容器
*/ */
@GetMapping("/getApiStartDockerInfo") public ApiResult<?> startDockerInfo(String id) {
public ApiResult<?> startDockerInfo(String id, String token) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
try { try {
DockerContainerInfo cur = dockerInfoService.getById(id); DockerContainerInfo cur = dockerInfoService.getById(id);
DockerHost host = dockerHostService.getById(cur.getDokerHostId()); DockerHost host = dockerHostService.getById(cur.getDokerHostId());
@@ -337,11 +320,7 @@ public class sysController {
/** /**
* 停止容器 * 停止容器
*/ */
@GetMapping("/getApiStopDockerInfo") public ApiResult<?> stopDockerInfo(String id) {
public ApiResult<?> stopDockerInfo(String id, String token) {
if (!vToken.isValidToken(token)) {
return ApiResult.error(401, "无效的访问令牌");
}
try { try {
DockerContainerInfo cur = dockerInfoService.getById(id); DockerContainerInfo cur = dockerInfoService.getById(id);
DockerHost host = dockerHostService.getById(cur.getDokerHostId()); DockerHost host = dockerHostService.getById(cur.getDokerHostId());
@@ -363,6 +342,4 @@ public class sysController {
return ApiResult.error(101, e.getMessage()); return ApiResult.error(101, e.getMessage());
} }
} }
} }