549 lines
22 KiB
Java
549 lines
22 KiB
Java
package com.youlai.boot.device.controller;
|
||
|
||
import cn.hutool.core.util.IdUtil;
|
||
import com.youlai.boot.common.annotation.Log;
|
||
import com.youlai.boot.common.config.FilePath;
|
||
import com.youlai.boot.common.enums.ActionTypeEnum;
|
||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||
import com.youlai.boot.common.exception.BusinessException;
|
||
import com.youlai.boot.common.result.Result;
|
||
import com.youlai.boot.common.util.HashUtils;
|
||
import com.youlai.boot.device.model.entity.SnDeviceSystemInfo;
|
||
import com.youlai.boot.device.model.entity.SnDeviceHardwareInfo;
|
||
import com.youlai.boot.device.model.entity.SnDeviceNetworkInfo;
|
||
import com.youlai.boot.device.model.entity.SnDeviceSecurityInfo;
|
||
import com.youlai.boot.device.model.entity.SnDeviceOtherInfo;
|
||
import com.youlai.boot.device.model.entity.SnDeviceInfo;
|
||
import com.youlai.boot.device.model.req.ApkInstallInfoReq;
|
||
import com.youlai.boot.device.model.req.SnSystemInfoReq;
|
||
import com.youlai.boot.device.model.req.SnHardwareInfoReq;
|
||
import com.youlai.boot.device.model.req.SnNetworkInfoReq;
|
||
import com.youlai.boot.device.model.req.SnSecurityInfoReq;
|
||
import com.youlai.boot.device.model.req.SnOtherInfoReq;
|
||
import com.youlai.boot.device.model.req.SnLocationReq;
|
||
import com.youlai.boot.device.model.entity.SnLocation;
|
||
import com.youlai.boot.device.model.entity.SnScreenshot;
|
||
import com.youlai.boot.device.model.vo.DeveloperOptionsVO;
|
||
import com.youlai.boot.device.service.*;
|
||
import io.swagger.v3.oas.annotations.Operation;
|
||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||
import lombok.RequiredArgsConstructor;
|
||
import org.apache.commons.io.FilenameUtils;
|
||
import org.slf4j.Logger;
|
||
import org.slf4j.LoggerFactory;
|
||
import org.springframework.beans.BeanUtils;
|
||
import org.springframework.data.redis.core.RedisTemplate;
|
||
import org.springframework.web.bind.annotation.*;
|
||
import org.springframework.web.multipart.MultipartFile;
|
||
|
||
import java.io.File;
|
||
import java.time.LocalDateTime;
|
||
import java.util.HashMap;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.concurrent.TimeUnit;
|
||
|
||
/**
|
||
* 设备控制层
|
||
* @author TTSTD
|
||
* @since 2026/04/05
|
||
*/
|
||
@Tag(name = "16.移动设备管理")
|
||
@RestController
|
||
@RequestMapping("/api/v1/sn")
|
||
@RequiredArgsConstructor
|
||
public class MobileController {
|
||
private static final String DEVICE_SECRET_PREFIX = "device:secret:";
|
||
|
||
private final RedisTemplate<String, Object> redisTemplate;
|
||
private final DeviceService deviceService;
|
||
private final ScreenshotService screenshotService;
|
||
private final LocationService locationService;
|
||
private final DeveloperService developerService;
|
||
private final ApkInstallService apkInstallService;
|
||
private final AppIconService appIconService;
|
||
private final SystemInfoService systemInfoService;
|
||
private final HardwareInfoService hardwareInfoService;
|
||
private final NetworkInfoService networkInfoService;
|
||
private final SecurityInfoService securityInfoService;
|
||
private final OtherInfoService otherInfoService;
|
||
|
||
private final Logger logger = LoggerFactory.getLogger(MobileController.class);
|
||
|
||
|
||
@Operation(summary = "注册设备")
|
||
@PostMapping("/register")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.REGISTER)
|
||
public Map<String, Object> registerDevice(@RequestParam() String sn) {
|
||
|
||
// 生成设备密钥
|
||
String deviceSecret = IdUtil.fastSimpleUUID();
|
||
|
||
// 存储到Redis(可根据需要设置过期时间)
|
||
redisTemplate.opsForValue().set(DEVICE_SECRET_PREFIX + sn, deviceSecret, 365, TimeUnit.DAYS);
|
||
|
||
Map<String, Object> result = new HashMap<>();
|
||
result.put("deviceId", sn);
|
||
result.put("deviceSecret", deviceSecret);
|
||
result.put("message", "请妥善保管设备密钥,用于生成API签名");
|
||
|
||
return result;
|
||
}
|
||
|
||
@Operation(summary = "上传设备硬件信息")
|
||
@PostMapping("/update_system_info")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.UPDATE_SYSTEM_INFO)
|
||
public Result<Void> updateSystemInfo(
|
||
@RequestHeader(value = "X-Device-SN") String sn,
|
||
@RequestBody SnSystemInfoReq snSystemInfoReq
|
||
) {
|
||
try {
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
|
||
if (snSystemInfoReq == null) {
|
||
return Result.failed("硬件信息不能为空");
|
||
}
|
||
|
||
if (snSystemInfoReq.getDeviceModel() != null && !snSystemInfoReq.getDeviceModel().trim().isEmpty()) {
|
||
SnDeviceInfo deviceInfo = deviceService.lambdaQuery()
|
||
.eq(SnDeviceInfo::getSerialno, sn)
|
||
.one();
|
||
if (deviceInfo != null) {
|
||
deviceInfo.setSnModel(snSystemInfoReq.getDeviceModel());
|
||
deviceInfo.setUpdateTime(LocalDateTime.now());
|
||
deviceService.updateById(deviceInfo);
|
||
}
|
||
}
|
||
|
||
SnDeviceSystemInfo systemInfo = new SnDeviceSystemInfo();
|
||
BeanUtils.copyProperties(snSystemInfoReq, systemInfo);
|
||
systemInfo.setSerialno(sn);
|
||
systemInfo.setUpdateTime(LocalDateTime.now());
|
||
|
||
SnDeviceSystemInfo existingSystemInfo = systemInfoService.lambdaQuery()
|
||
.eq(SnDeviceSystemInfo::getSerialno, sn)
|
||
.one();
|
||
|
||
boolean saved;
|
||
if (existingSystemInfo != null) {
|
||
systemInfo.setId(existingSystemInfo.getId());
|
||
saved = systemInfoService.updateById(systemInfo);
|
||
} else {
|
||
systemInfo.setCreateTime(LocalDateTime.now());
|
||
saved = systemInfoService.save(systemInfo);
|
||
}
|
||
|
||
if (!saved) {
|
||
return Result.failed("保存硬件信息失败");
|
||
}
|
||
|
||
logger.info("硬件信息上传成功, sn: {}", sn);
|
||
return Result.success();
|
||
|
||
} catch (Exception e) {
|
||
logger.error("updateSystemInfo error, sn: {}", sn, e);
|
||
return Result.failed("上传硬件信息失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Operation(summary = "上传设备其他信息")
|
||
@PostMapping("/update_other_info")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.UPDATE_OTHER_INFO)
|
||
public Result<Void> updateOtherInfo(
|
||
@RequestHeader(value = "X-Device-SN") String sn,
|
||
@RequestBody SnOtherInfoReq snOtherInfoReq
|
||
) {
|
||
try {
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
|
||
if (snOtherInfoReq == null) {
|
||
return Result.failed("其他信息不能为空");
|
||
}
|
||
|
||
SnDeviceOtherInfo otherInfo = new SnDeviceOtherInfo();
|
||
BeanUtils.copyProperties(snOtherInfoReq, otherInfo);
|
||
otherInfo.setSerialno(sn);
|
||
otherInfo.setUpdateTime(LocalDateTime.now());
|
||
|
||
SnDeviceOtherInfo existingOtherInfo = otherInfoService.lambdaQuery()
|
||
.eq(SnDeviceOtherInfo::getSerialno, sn)
|
||
.one();
|
||
|
||
boolean saved;
|
||
if (existingOtherInfo != null) {
|
||
otherInfo.setId(existingOtherInfo.getId());
|
||
saved = otherInfoService.updateById(otherInfo);
|
||
} else {
|
||
otherInfo.setCreateTime(LocalDateTime.now());
|
||
saved = otherInfoService.save(otherInfo);
|
||
}
|
||
|
||
if (!saved) {
|
||
return Result.failed("保存其他信息失败");
|
||
}
|
||
|
||
// 如果推送ID不为空,则同步更新设备表的推送ID
|
||
if (snOtherInfoReq.getPushId() != null && !snOtherInfoReq.getPushId().trim().isEmpty()) {
|
||
SnDeviceInfo deviceInfo = deviceService.lambdaQuery()
|
||
.eq(SnDeviceInfo::getSerialno, sn)
|
||
.one();
|
||
if (deviceInfo != null) {
|
||
deviceInfo.setPushId(snOtherInfoReq.getPushId());
|
||
deviceInfo.setUpdateTime(LocalDateTime.now());
|
||
deviceService.updateById(deviceInfo);
|
||
}
|
||
}
|
||
|
||
logger.info("其他信息上传成功, sn: {}", sn);
|
||
return Result.success();
|
||
|
||
} catch (Exception e) {
|
||
logger.error("updateOtherInfo error, sn: {}", sn, e);
|
||
return Result.failed("上传其他信息失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Operation(summary = "上传设备硬件信息")
|
||
@PostMapping("/update_hardware_info")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.UPDATE_HARDWARE_INFO)
|
||
public Result<Void> updateHardwareInfo(
|
||
@RequestHeader(value = "X-Device-SN") String sn,
|
||
@RequestBody SnHardwareInfoReq snHardwareInfoReq
|
||
) {
|
||
try {
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
|
||
if (snHardwareInfoReq == null) {
|
||
return Result.failed("硬件信息不能为空");
|
||
}
|
||
|
||
SnDeviceHardwareInfo hardwareInfo = new SnDeviceHardwareInfo();
|
||
BeanUtils.copyProperties(snHardwareInfoReq, hardwareInfo);
|
||
hardwareInfo.setSerialno(sn);
|
||
hardwareInfo.setUpdateTime(LocalDateTime.now());
|
||
|
||
SnDeviceHardwareInfo existingHardwareInfo = hardwareInfoService.lambdaQuery()
|
||
.eq(SnDeviceHardwareInfo::getSerialno, sn)
|
||
.one();
|
||
|
||
boolean saved;
|
||
if (existingHardwareInfo != null) {
|
||
hardwareInfo.setId(existingHardwareInfo.getId());
|
||
saved = hardwareInfoService.updateById(hardwareInfo);
|
||
} else {
|
||
hardwareInfo.setCreateTime(LocalDateTime.now());
|
||
saved = hardwareInfoService.save(hardwareInfo);
|
||
}
|
||
|
||
if (!saved) {
|
||
return Result.failed("保存硬件信息失败");
|
||
}
|
||
|
||
logger.info("硬件信息上传成功, sn: {}", sn);
|
||
return Result.success();
|
||
|
||
} catch (Exception e) {
|
||
logger.error("updateHardwareInfo error, sn: {}", sn, e);
|
||
return Result.failed("上传硬件信息失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Operation(summary = "上传设备网络信息")
|
||
@PostMapping("/update_network_info")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.UPDATE_NETWORK_INFO)
|
||
public Result<Void> updateNetworkInfo(
|
||
@RequestHeader(value = "X-Device-SN") String sn,
|
||
@RequestBody SnNetworkInfoReq snNetworkInfoReq
|
||
) {
|
||
try {
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
|
||
if (snNetworkInfoReq == null) {
|
||
return Result.failed("网络信息不能为空");
|
||
}
|
||
|
||
SnDeviceNetworkInfo networkInfo = new SnDeviceNetworkInfo();
|
||
BeanUtils.copyProperties(snNetworkInfoReq, networkInfo);
|
||
networkInfo.setSerialno(sn);
|
||
networkInfo.setUpdateTime(LocalDateTime.now());
|
||
|
||
SnDeviceNetworkInfo existingNetworkInfo = networkInfoService.lambdaQuery()
|
||
.eq(SnDeviceNetworkInfo::getSerialno, sn)
|
||
.one();
|
||
|
||
boolean saved;
|
||
if (existingNetworkInfo != null) {
|
||
networkInfo.setId(existingNetworkInfo.getId());
|
||
saved = networkInfoService.updateById(networkInfo);
|
||
} else {
|
||
networkInfo.setCreateTime(LocalDateTime.now());
|
||
saved = networkInfoService.save(networkInfo);
|
||
}
|
||
|
||
if (!saved) {
|
||
return Result.failed("保存网络信息失败");
|
||
}
|
||
|
||
logger.info("网络信息上传成功, sn: {}", sn);
|
||
return Result.success();
|
||
|
||
} catch (Exception e) {
|
||
logger.error("updateNetworkInfo error, sn: {}", sn, e);
|
||
return Result.failed("上传网络信息失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Operation(summary = "上传设备安全信息")
|
||
@PostMapping("/update_security_info")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.UPDATE_SECURITY_INFO)
|
||
public Result<Void> updateSecurityInfo(
|
||
@RequestHeader(value = "X-Device-SN") String sn,
|
||
@RequestBody SnSecurityInfoReq snSecurityInfoReq
|
||
) {
|
||
try {
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
|
||
if (snSecurityInfoReq == null) {
|
||
return Result.failed("安全信息不能为空");
|
||
}
|
||
|
||
SnDeviceSecurityInfo securityInfo = new SnDeviceSecurityInfo();
|
||
BeanUtils.copyProperties(snSecurityInfoReq, securityInfo);
|
||
securityInfo.setSerialno(sn);
|
||
securityInfo.setUpdateTime(LocalDateTime.now());
|
||
|
||
SnDeviceSecurityInfo existingSecurityInfo = securityInfoService.lambdaQuery()
|
||
.eq(SnDeviceSecurityInfo::getSerialno, sn)
|
||
.one();
|
||
|
||
boolean saved;
|
||
if (existingSecurityInfo != null) {
|
||
securityInfo.setId(existingSecurityInfo.getId());
|
||
saved = securityInfoService.updateById(securityInfo);
|
||
} else {
|
||
securityInfo.setCreateTime(LocalDateTime.now());
|
||
saved = securityInfoService.save(securityInfo);
|
||
}
|
||
|
||
if (!saved) {
|
||
return Result.failed("保存安全信息失败");
|
||
}
|
||
|
||
logger.info("安全信息上传成功, sn: {}", sn);
|
||
return Result.success();
|
||
|
||
} catch (Exception e) {
|
||
logger.error("updateSecurityInfo error, sn: {}", sn, e);
|
||
return Result.failed("上传安全信息失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Operation(summary = "上传设备截图")
|
||
@PostMapping("/upload_screenshot")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.SCREENSHOT)
|
||
public Result<Void> uploadScreenshot(
|
||
@RequestPart(value = "file") MultipartFile file,
|
||
@RequestHeader(value = "X-Device-SN") String sn
|
||
) {
|
||
try {
|
||
if (file.isEmpty()) {
|
||
return Result.failed("上传文件不能为空");
|
||
}
|
||
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
|
||
String screenshotPath = FilePath.getScreenshotPath();
|
||
logger.info("uploadScreenshot, screenshotPath: {}", screenshotPath);
|
||
|
||
File fileDir = new File(screenshotPath);
|
||
if (!fileDir.exists()) {
|
||
boolean created = fileDir.mkdirs();
|
||
if (!created) {
|
||
logger.error("创建目录失败: {}", screenshotPath);
|
||
return Result.failed("创建目录失败");
|
||
}
|
||
}
|
||
|
||
String originName = file.getOriginalFilename();
|
||
if (originName == null || originName.isEmpty()) {
|
||
return Result.failed("文件名无效");
|
||
}
|
||
// 清洗文件名,去除路径穿越风险(只保留纯文件名)
|
||
String safeBaseName = FilenameUtils.getName(originName);
|
||
if (safeBaseName.isEmpty()) {
|
||
return Result.failed("文件名无效");
|
||
}
|
||
|
||
String fileExtension = FilenameUtils.getExtension(safeBaseName);
|
||
String md5 = HashUtils.calculateMultipartFileMd5(file);
|
||
String sha1 = HashUtils.calculateMultipartFileSha1(file);
|
||
String sha256 = HashUtils.calculateMultipartFileSha256(file);
|
||
String fileName = sn + "_" + System.currentTimeMillis() + "_" + md5 + "." + fileExtension;
|
||
File destFile = new File(fileDir, fileName);
|
||
|
||
file.transferTo(destFile);
|
||
|
||
SnScreenshot screenshotInfo = new SnScreenshot();
|
||
screenshotInfo.setSn(sn);
|
||
// fileName 仅存纯文件名;前端访问 URL = /static/screenshot/{fileName}(由 WebMvcConfig 静态映射)
|
||
screenshotInfo.setFileName(fileName);
|
||
// filePath 仅存相对子目录,避免耦合绝对路径
|
||
screenshotInfo.setFilePath(FilePath.TABLET_PATH + "/" + FilePath.SCREENSHOT_PATH + "/" + fileName);
|
||
screenshotInfo.setFileSize(file.getSize());
|
||
screenshotInfo.setFileMd5(md5);
|
||
screenshotInfo.setFileSha1(sha1);
|
||
screenshotInfo.setFileSha256(sha256);
|
||
screenshotInfo.setUploadTime(LocalDateTime.now());
|
||
|
||
screenshotService.save(screenshotInfo);
|
||
|
||
logger.info("截图上传成功, sn: {}, fileName: {}", sn, fileName);
|
||
return Result.success();
|
||
|
||
} catch (Exception e) {
|
||
logger.error("uploadScreenshot error, sn: {}", sn, e);
|
||
return Result.failed("上传截图失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Operation(summary = "上传设备定位信息")
|
||
@PostMapping("/upload_location")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.LOCATE)
|
||
public Result<Void> uploadLocation(
|
||
@RequestHeader(value = "X-Device-SN") String sn,
|
||
@RequestBody SnLocationReq locationReq
|
||
) {
|
||
try {
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
|
||
if (locationReq == null) {
|
||
return Result.failed("定位信息不能为空");
|
||
}
|
||
|
||
SnLocation location = new SnLocation();
|
||
BeanUtils.copyProperties(locationReq, location);
|
||
location.setSn(sn);
|
||
// 上次定位成功时间由服务端统一写入,避免移动端时间不准或 LocalDateTime 序列化格式不一致导致为空
|
||
location.setLastSuccessfulTime(LocalDateTime.now());
|
||
|
||
SnLocation existingLocation = locationService.lambdaQuery()
|
||
.eq(SnLocation::getSn, sn)
|
||
.one();
|
||
|
||
boolean saved;
|
||
if (existingLocation != null) {
|
||
location.setId(existingLocation.getId());
|
||
saved = locationService.updateById(location);
|
||
} else {
|
||
saved = locationService.save(location);
|
||
}
|
||
|
||
if (!saved) {
|
||
return Result.failed("保存定位信息失败");
|
||
}
|
||
|
||
logger.info("定位信息上传成功, sn: {}, location: {}", sn, locationReq);
|
||
return Result.success();
|
||
|
||
} catch (Exception e) {
|
||
logger.error("uploadLocation error, sn: {}", sn, e);
|
||
return Result.failed("上传定位信息失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Operation(summary = "获取开发者选项开关")
|
||
@GetMapping("/get_developer_options")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.VIEW_DEVELOPER_OPTIONS)
|
||
public Result<DeveloperOptionsVO> getDeveloperOptions(@RequestHeader(value = "X-Device-SN") String sn) {
|
||
try {
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
|
||
// 查询该设备的开发者选项配置,未配置时默认关闭
|
||
DeveloperOptionsVO vo = new DeveloperOptionsVO();
|
||
vo.setDeveloperOptions(developerService.getDeveloperOptionsBySn(sn));
|
||
// 返回开发者选项开关状态
|
||
return Result.success(vo);
|
||
|
||
} catch (Exception e) {
|
||
logger.error("getDeveloperOptions error, sn: {}", sn, e);
|
||
return Result.failed("获取开发者选项失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Operation(summary = "上传设备已安装应用列表")
|
||
@PostMapping("/upload_install_apks")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.UPLOAD_APK_LIST)
|
||
public Result<Void> uploadInstallApks(
|
||
@RequestHeader(value = "X-Device-SN") String sn,
|
||
@RequestBody List<ApkInstallInfoReq> apkInfos
|
||
) {
|
||
try {
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
|
||
if (apkInfos == null || apkInfos.isEmpty()) {
|
||
return Result.failed("应用列表不能为空");
|
||
}
|
||
|
||
boolean saved = apkInstallService.saveOrUpdateDeviceApkInfo(sn, apkInfos);
|
||
|
||
if (!saved) {
|
||
return Result.failed("保存应用信息失败");
|
||
}
|
||
|
||
logger.info("应用列表上传成功, sn: {}, count: {}", sn, apkInfos.size());
|
||
return Result.success();
|
||
|
||
} catch (Exception e) {
|
||
logger.error("uploadInstallApks error, sn: {}", sn, e);
|
||
return Result.failed("上传应用列表失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Operation(summary = "上传应用图标")
|
||
@PostMapping("/upload_app_icon")
|
||
@Log(module = LogModuleEnum.MOBILE, value = ActionTypeEnum.UPLOAD_APP_ICON)
|
||
public Result<String> uploadAppIcon(
|
||
@RequestHeader(value = "X-Device-SN") String sn,
|
||
@RequestParam String packageName,
|
||
@RequestParam(required = false) String appName,
|
||
@RequestPart(value = "file") MultipartFile file
|
||
) {
|
||
try {
|
||
if (sn == null || sn.trim().isEmpty()) {
|
||
return Result.failed("设备序列号不能为空");
|
||
}
|
||
if (packageName == null || packageName.trim().isEmpty()) {
|
||
return Result.failed("应用包名不能为空");
|
||
}
|
||
String iconUrl = appIconService.uploadIcon(packageName, appName, file, "device");
|
||
return Result.success(iconUrl);
|
||
} catch (BusinessException e) {
|
||
// 业务异常(如图标已锁定)需保留原始业务码,供设备端识别为终态、不再重试;
|
||
// 不可被下方 catch(Exception) 压平成通用系统错误码
|
||
logger.info("uploadAppIcon rejected, sn: {}, packageName: {}, reason: {}", sn, packageName, e.getMessage());
|
||
throw e;
|
||
} catch (Exception e) {
|
||
logger.error("uploadAppIcon error, sn: {}", sn, e);
|
||
return Result.failed("上传应用图标失败: " + e.getMessage());
|
||
}
|
||
}
|
||
}
|