YunaiV/yudao-cloud · error · RuntimeException

计算文件签名失败:

Error message

计算文件签名失败: 

What it means

Thrown by IotOtaFirmwareServiceImpl.createOtaFirmware when the private helper calculateFileDigest(firmware) fails. That helper downloads the firmware file with HttpUtil.downloadBytes(fileUrl), sets fileSize, then computes an MD5 digest. Any exception from the download (bad/unreachable URL, 404 from the file service, I/O timeout) or the digest step is caught, logged, and rethrown as a generic RuntimeException whose message is '计算文件签名失败: ' plus the root cause's message.

Source

Thrown at yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaFirmwareServiceImpl.java:61

    private IotProductService productService;

    @Override
    public Long createOtaFirmware(IotOtaFirmwareCreateReqVO saveReqVO) {
        // 1.1 校验固件产品 + 版本号不能重复
        if (otaFirmwareMapper.selectByProductIdAndVersion(saveReqVO.getProductId(), saveReqVO.getVersion()) != null) {
            throw exception(OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE);
        }
        // 1.2 校验产品存在
        productService.validateProductExists(saveReqVO.getProductId());

        // 2. 构建对象 + 存储
        IotOtaFirmwareDO firmware = BeanUtils.toBean(saveReqVO, IotOtaFirmwareDO.class);
        // 2.1 计算文件签名等属性
        try {
            calculateFileDigest(firmware);
        } catch (Exception e) {
            log.error("[createOtaFirmware][url({}) 计算文件签名失败]", firmware.getFileUrl(), e);
            throw new RuntimeException("计算文件签名失败: " + e.getMessage());
        }
        otaFirmwareMapper.insert(firmware);
        return firmware.getId();
    }

    @Override
    public void updateOtaFirmware(IotOtaFirmwareUpdateReqVO updateReqVO) {
        // 1. 校验存在
        validateFirmwareExists(updateReqVO.getId());

        // 2. 更新数据
        IotOtaFirmwareDO updateObj = BeanUtils.toBean(updateReqVO, IotOtaFirmwareDO.class);
        otaFirmwareMapper.updateById(updateObj);
    }

    @Override
    public IotOtaFirmwareDO getOtaFirmware(Long id) {
        return otaFirmwareMapper.selectById(id);

View on GitHub (pinned to 477be9dd49)

Solutions

  1. Decode the root cause: read the log line '[createOtaFirmware][url({}) 计算文件签名失败]' and the cause after '计算文件签名失败: ' — a 404 means the file is not at that URL, a connection error means the file server is unreachable.
  2. Verify the URL by fetching it directly (curl -I) from the app server host; fix the upload or the file-service base URL in configuration if it 404s or cannot connect.
  3. Ensure the file upload completes before createOtaFirmware is called (upload first, then create with the returned URL); re-run the create request.
  4. If the file is large, raise the HTTP client timeout used by HttpUtil.downloadBytes (e.g. HttpUtil.downloadBytes(url, timeout) or global hutool HTTP timeouts).
  5. Replace the raw RuntimeException with the project's error-code pattern (e.g. OTA_FIRMWARE_FILE_DIGEST_CALCULATE_FAILED) so clients get a structured GlobalExceptionHandler response instead of a 500.

Example fix

// before
} catch (Exception e) {
    log.error("[createOtaFirmware][url({}) 计算文件签名失败]", firmware.getFileUrl(), e);
    throw new RuntimeException("计算文件签名失败: " + e.getMessage());
}

// after (structured error code + preserved cause)
} catch (Exception e) {
    log.error("[createOtaFirmware][url({}) 计算文件签名失败]", firmware.getFileUrl(), e);
    throw exception(OTA_FIRMWARE_FILE_DIGEST_CALCULATE_FAILED, firmware.getFileUrl());
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the firmware URL is reachable before calling createOtaFirmware
String fileUrl = saveReqVO.getFileUrl();
try {
    HttpResponse resp = HttpRequest.head(fileUrl).timeout(10_000).execute();
    if (!resp.isOk()) {
        throw new IllegalArgumentException("Firmware file not available at " + fileUrl + ": HTTP " + resp.getStatus());
    }
} catch (HttpException e) {
    throw new IllegalArgumentException("Cannot reach firmware file URL " + fileUrl, e);
}

Try / catch

try {
    firmwareService.createOtaFirmware(saveReqVO);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("计算文件签名失败")) {
        // file download/digest failed: verify the file URL and file service, then retry
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling createOtaFirmware (or the POST /iot/ota/firmware/create endpoint) with a fileUrl that: points to a host the server cannot reach, returns 404 because the file was not yet uploaded / was deleted, has a typo or wrong scheme, or when the file storage (e.g. MinIO/S3/local file service) is down. The exception message after the colon is whatever downloadBytes/digest threw (e.g. '404: Not Found', 'Connection refused', 'timeout').

Common situations: Client uploaded the file asynchronously and created the firmware record before the file was actually available; wrong file-server base URL in config; file service credentials/bucket misconfigured; network egress blocked from the app server; very large firmware files hitting the HTTP download timeout; URL stored without host (relative path) so downloadBytes fails.

Related errors


AI-assisted analysis of YunaiV/yudao-cloud@477be9dd49 (2026-08-14). Data as JSON: /api/errors/6ed6a6adebfee32a. Report an issue: GitHub.