修复设备模块导出报 TemplateRenderPolicy render error (500):签名图片缺失时用透明图兜底

- UserUtils 新增 orTransparentPicture / getFinalUserSignatureOrTransparent(按id/按姓名),签名未配置或签名文件缺失时返回全透明图片,原方法保留不动
- cnas-device 13 个 ServiceImpl 共 31 处签名渲染调用换用兜底方法,覆盖校准计划、核查计划及明细/对比、保养计划、使用授权、利用外部仪器、事故报告、报废、故障维修、点检、量值溯源等全部 Word 导出
- DeviceStateServiceImpl(停用/启用导出)4 处"签名缺失塞 null"改为透明图兜底,移除旧人名查询与中断抛出
已修改15个文件
178 ■■■■■ 文件已修改
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceAccidentReportServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBreakdownMaintenanceServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceCalibrationPlanServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordContrastServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExternalApplyServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceImpowerServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceInspectionRecordServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceScrappedServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceStateServiceImpl.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceTraceabilityManagementServiceImpl.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/util/UserUtils.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceAccidentReportServiceImpl.java
@@ -159,15 +159,15 @@
                    put("deviceAccidentReport", deviceAccidentReport);
                    put("device", finalDevice);
                    // 报告人签名
                    put("reportUserUrl", UserUtils.getFinalUserSignatureUrl(deviceAccidentReport.getReportUserId()));
                    put("reportUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getReportUserId()));
                    // 评估人签名
                    put("assessorUserUrl", UserUtils.getFinalUserSignatureUrl(deviceAccidentReport.getAssessorUserId()));
                    put("assessorUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getAssessorUserId()));
                    // 部门负责人签名
                    put("departmentHeadUserUrl", UserUtils.getFinalUserSignatureUrl(deviceAccidentReport.getDepartmentHeadUserId()));
                    put("departmentHeadUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getDepartmentHeadUserId()));
                    // 技术负责人签名
                    put("technicalDirectorUserUrl", UserUtils.getFinalUserSignatureUrl(deviceAccidentReport.getTechnicalDirectorUserId()));
                    put("technicalDirectorUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getTechnicalDirectorUserId()));
                    // 主任签名
                    put("directorHeadUserUrl", UserUtils.getFinalUserSignatureUrl(deviceAccidentReport.getDepartmentHeadUserId()));
                    put("directorHeadUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getDepartmentHeadUserId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBreakdownMaintenanceServiceImpl.java
@@ -128,9 +128,9 @@
                    put("deviceBreakdownMaintenance", deviceBreakdownMaintenance);
                    put("device", finalDevice);
                    // 申请人签名
                    put("applicantUrl", UserUtils.getFinalUserSignatureUrl(deviceBreakdownMaintenance.getApplicantUserId()));
                    put("applicantUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceBreakdownMaintenance.getApplicantUserId()));
                    // 部门负责人签名
                    put("headUrl", UserUtils.getFinalUserSignatureUrl(deviceBreakdownMaintenance.getDepartmentHeadUserId()));
                    put("headUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceBreakdownMaintenance.getDepartmentHeadUserId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceCalibrationPlanServiceImpl.java
@@ -286,9 +286,9 @@
                    put("deviceCalibrationPlan", deviceCalibrationPlanDto);
                    put("deviceCalibrationPlanDetailDtoList", deviceCalibrationPlanDetailDtoList);
                    //获取编制人的签名地址
                    put("organizationUrl", UserUtils.getFinalUserSignatureUrl(deviceCalibrationPlan.getRatifyUserId()));
                    put("organizationUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceCalibrationPlan.getRatifyUserId()));
                    //获取批准人的签名地址
                    put("approvedUrl", UserUtils.getFinalUserSignatureUrl(deviceCalibrationPlan.getWriteUserId()));
                    put("approvedUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceCalibrationPlan.getWriteUserId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java
@@ -289,9 +289,9 @@
                    put("deviceExaminePlan", deviceExaminePlanDto);
                    put("deviceExaminePlanDetailsDtoList", deviceExaminePlanDetailsDtoList);
                    //获取核查人的签名地址
                    put("writeUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExaminePlanDto.getWriteUserId()));
                    put("writeUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExaminePlanDto.getWriteUserId()));
                    //获取审查人的签名地址
                    put("reviewUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExaminePlanDto.getRatifyUserId()));
                    put("reviewUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExaminePlanDto.getRatifyUserId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordContrastServiceImpl.java
@@ -170,9 +170,9 @@
                    put("deviceExamineRecordContrast", deviceExamineRecordContrast);
                    put("deviceExamineRecordContrastDetailsDtoList", deviceExamineRecordContrastDetailsDtoList);
                    //获取核查人的签名地址
                    put("checkerUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExamineRecordContrast.getCheckerUserId()));
                    put("checkerUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExamineRecordContrast.getCheckerUserId()));
                    //获取审查人的签名地址
                    put("reviewUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExamineRecordContrast.getReviewUserId()));
                    put("reviewUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExamineRecordContrast.getReviewUserId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java
@@ -213,9 +213,9 @@
                    put("deviceExamineRecordDto", deviceExamineRecordDto);
                    put("deviceExamineRecordDetailList", deviceExamineRecordDetailList);
                    //获取核查人的签名地址
                    put("checkerUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExamineRecordDto.getCheckerUserId()));
                    put("checkerUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExamineRecordDto.getCheckerUserId()));
                    //获取审查人的签名地址
                    put("reviewUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExamineRecordDto.getReviewUserId()));
                    put("reviewUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExamineRecordDto.getReviewUserId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExternalApplyServiceImpl.java
@@ -134,13 +134,13 @@
                new HashMap<String, Object>() {{
                    put("deviceAccidentReport", deviceAccidentReport);
                    // 申请人签名
                    put("applicantUserUrl", UserUtils.getFinalUserSignatureUrl(deviceAccidentReport.getApplicantUserId()));
                    put("applicantUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getApplicantUserId()));
                    // 部门负责人签名
                    put("departmentHeadUserUrl", UserUtils.getFinalUserSignatureUrl(deviceAccidentReport.getDepartmentHeadUserId()));
                    put("departmentHeadUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getDepartmentHeadUserId()));
                    // 计量室人签名
                    put("meteringRoomUserUrl", UserUtils.getFinalUserSignatureUrl(deviceAccidentReport.getMeteringRoomUserId()));
                    put("meteringRoomUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getMeteringRoomUserId()));
                    // 批准人签名
                    put("approverUserUrl", UserUtils.getFinalUserSignatureUrl(deviceAccidentReport.getApproverUserId()));
                    put("approverUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getApproverUserId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceImpowerServiceImpl.java
@@ -277,7 +277,7 @@
                    put("deviceImpower", deviceImpowerDto);
                    put("deviceImpowerDetailsDtoList", deviceImpowerDetailsDtoList);
                    // 授权人地址
                    put("auditUrl", UserUtils.getFinalUserSignatureUrl(deviceImpower.getAuditId()));
                    put("auditUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceImpower.getAuditId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceInspectionRecordServiceImpl.java
@@ -201,8 +201,8 @@
                    put("deviceInspectionRecord", deviceInspectionRecordDto);
                    put("deviceInspectionRecordDetailsList", deviceInspectionRecordDetailsList);
                    put("device", device);
                    put("recorderUrl", UserUtils.getFinalUserSignatureUrl(deviceInspectionRecordDto.getRecorderId()));
                    put("reviewerUrl", UserUtils.getFinalUserSignatureUrl(deviceInspectionRecordDto.getReviewerId()));
                    put("recorderUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceInspectionRecordDto.getRecorderId()));
                    put("reviewerUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceInspectionRecordDto.getReviewerId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceImpl.java
@@ -109,7 +109,7 @@
            }
            deviceMaintenance.setMaintenanceContent(maintenanceContent.toString());
            // 添加维护人图片
            deviceMaintenance.setMaintenanceUserUrlRender(UserUtils.getFinalUserSignatureUrl(deviceMaintenance.getMaintenanceUserId()));
            deviceMaintenance.setMaintenanceUserUrlRender(UserUtils.getFinalUserSignatureOrTransparent(deviceMaintenance.getMaintenanceUserId()));
        }
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java
@@ -181,9 +181,9 @@
                    put("deviceMaintenancePlan", deviceMaintenancePlan);
                    put("deviceMaintenancePlanDetailsDtoList", deviceMaintenancePlanDetailsDtoList);
                    // 编制人签名地址
                    put("compilerUrl", UserUtils.getFinalUserSignatureUrl(deviceMaintenancePlan.getCompilerId()));
                    put("compilerUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceMaintenancePlan.getCompilerId()));
                    // 审核人签名地址
                    put("auditUrl", UserUtils.getFinalUserSignatureUrl(deviceMaintenancePlan.getAuditId()));
                    put("auditUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceMaintenancePlan.getAuditId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceScrappedServiceImpl.java
@@ -150,13 +150,13 @@
                    put("deviceScrapped", deviceScrapped);
                    put("device", finalDevice);
                    // 申请人签名
                    put("applicantUrl", UserUtils.getFinalUserSignatureUrl(deviceScrapped.getApplicantUserId()));
                    put("applicantUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceScrapped.getApplicantUserId()));
                    // 部门负责人签名
                    put("headUrl", UserUtils.getFinalUserSignatureUrl(deviceScrapped.getDepartmentHeadUserId()));
                    put("headUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceScrapped.getDepartmentHeadUserId()));
                    // 计量室签名
                    put("metrologyRoomUrl", UserUtils.getFinalUserSignatureUrl(deviceScrapped.getMeteringRoomUserId()));
                    put("metrologyRoomUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceScrapped.getMeteringRoomUserId()));
                    // 批准人签名
                    put("approverUrl", UserUtils.getFinalUserSignatureUrl(deviceScrapped.getApproverUserId()));
                    put("approverUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceScrapped.getApproverUserId()));
                }});
        try {
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceStateServiceImpl.java
@@ -7,17 +7,13 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.data.Pictures;
import com.ruoyi.basic.mapper.LaboratoryMapper;
import com.ruoyi.basic.pojo.Laboratory;
import com.ruoyi.common.core.domain.entity.User;
import com.ruoyi.device.dto.DeviceStateDto;
import com.ruoyi.device.mapper.DeviceStateMapper;
import com.ruoyi.device.pojo.DeviceState;
import com.ruoyi.device.service.DeviceStateService;
import com.ruoyi.framework.exception.ErrorException;
import com.ruoyi.system.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Value;
import com.ruoyi.inspect.util.UserUtils;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
@@ -41,12 +37,6 @@
    @Resource
    private LaboratoryMapper laboratoryMapper;
    @Resource
    private UserMapper userMapper;
    @Value("${file.path}")
    private String imgUrl;
    @Override
    public IPage<DeviceStateDto> getDeviceStatePage(Integer deviceId, Page page, String processNumber) {
@@ -78,66 +68,18 @@
            laboratoryName = "";
        }
        //todo: 设备状态查询签名地址 暂时人名查询
        //获取申请人的签名地址
        String applicantUrl = null;
        if (deviceStateDto.getSubmitOperatingPersonnel() != null) {
            applicantUrl = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                            .eq(User::getName, deviceStateDto.getSubmitOperatingPersonnel()))
                    .getSignatureUrl();
            if (StringUtils.isBlank(applicantUrl)) {
                throw new ErrorException("找不到申请人的签名");
            }
        }
        //获取部门负责人的签名地址
        String headOfDepartmentUrl = null;
        if (deviceStateDto.getDepartmentNextPesponsible() != null) {
            headOfDepartmentUrl = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                    .eq(User::getName, deviceStateDto.getDepartmentNextPesponsible()))
                    .getSignatureUrl();
            if (StringUtils.isBlank(headOfDepartmentUrl)) {
                throw new ErrorException("找不到部门负责人的签名");
            }
        }
        //获取计量室复测人的签名地址
        String measurementRoomUrl = null;
        if (deviceStateDto.getMeasuringRoomNextPesponsible() != null) {
            measurementRoomUrl = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                            .eq(User::getName, deviceStateDto.getMeasuringRoomNextPesponsible()))
                    .getSignatureUrl();
            if (StringUtils.isBlank(measurementRoomUrl)) {
                throw new ErrorException("找不到计量室复测人的签名");
            }
        }
        //获取批准人的签名地址
        String approvedUrl = null;
        if (deviceStateDto.getApprovalNextPesponsible() != null) {
            approvedUrl = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                            .eq(User::getName, deviceStateDto.getApprovalNextPesponsible()))
                    .getSignatureUrl();
            if (StringUtils.isBlank(approvedUrl)) {
                throw new ErrorException("找不到批准人的签名");
            }
        }
        // 获取路径
        InputStream inputStream = this.getClass().getResourceAsStream("/static/word/device-status.docx");
        Configure configure = Configure.builder()
                .build();
        String finalApplicantUrl = applicantUrl; // 申请人的签名地址
        String finalHeadOfDepartmentUrl = headOfDepartmentUrl; // 部门负责人的签名地址
        String finalMeasurementRoomUrl = measurementRoomUrl; // 计量室复测人的签名地址
        String finalApprovedUrl = approvedUrl; // 批准人的签名地址
        XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render(
                new HashMap<String, Object>() {{
                    put("deviceStateDto", deviceStateDto);
                    put("submitOperatingPersonnelUrl", StringUtils.isNotBlank(finalApplicantUrl) ? Pictures.ofLocal(imgUrl + "/" + finalApplicantUrl).create() : null);
                    put("departmentNextPesponsibleUrl", StringUtils.isNotBlank(finalHeadOfDepartmentUrl) ? Pictures.ofLocal(imgUrl + "/" + finalHeadOfDepartmentUrl).create() : null);
                    put("measuringRoomNextPesponsibleUrl", StringUtils.isNotBlank(finalMeasurementRoomUrl) ? Pictures.ofLocal(imgUrl + "/" + finalMeasurementRoomUrl).create() : null);
                    put("approvalNextPesponsibleUrl", StringUtils.isNotBlank(finalApprovedUrl) ? Pictures.ofLocal(imgUrl + "/" + finalApprovedUrl).create() : null);
                    // 签名兜底: 人员未配置签名或签名文件缺失时返回透明图片, 保证导出不因空图片报错
                    put("submitOperatingPersonnelUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceStateDto.getSubmitOperatingPersonnel()));
                    put("departmentNextPesponsibleUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceStateDto.getDepartmentNextPesponsible()));
                    put("measuringRoomNextPesponsibleUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceStateDto.getMeasuringRoomNextPesponsible()));
                    put("approvalNextPesponsibleUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceStateDto.getApprovalNextPesponsible()));
                    put("laboratory", laboratoryName);
                }});
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceTraceabilityManagementServiceImpl.java
@@ -265,9 +265,9 @@
                    put("deviceTraceabilityManagement", deviceTraceabilityManagement);
                    put("deviceTraceabilityManagementDetailsDtoList", deviceTraceabilityManagementDetailsDtoList);
                    // 编制人签名地址
                    put("compilerUrl", UserUtils.getFinalUserSignatureUrl(deviceTraceabilityManagement.getCompilerId()));
                    put("compilerUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceTraceabilityManagement.getCompilerId()));
                    // 审核人签名地址
                    put("auditUrl", UserUtils.getFinalUserSignatureUrl(deviceTraceabilityManagement.getAuditId()));
                    put("auditUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceTraceabilityManagement.getAuditId()));
                }});
        try {
inspect-server/src/main/java/com/ruoyi/inspect/util/UserUtils.java
@@ -11,6 +11,8 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Base64;
/**
 * Author: yuan
 * Date: 2024-12-17 星期二 10:35:50
@@ -87,4 +89,48 @@
        }
        return StringUtils.isNotBlank(userSignatureUrl) ? Pictures.ofLocal(imgUrl + "/" + userSignatureUrl).create() : null;
    }
    /**
     * 全透明 1x1 PNG, 签名未配置/签名文件缺失时的兜底图片
     */
    private static final byte[] TRANSPARENT_PNG = Base64.getDecoder().decode(
            "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAANSURBVBhXY2BgYGAAAAAFAAGKM+MAAAAAAElFTkSuQmCC");
    /**
     * 图片值兜底: null 或本地图片读取失败(如签名文件缺失)时返回透明图片
     */
    public static PictureRenderData orTransparentPicture(PictureRenderData picture) {
        if (picture != null) {
            try {
                byte[] data = picture.readPictureData();
                if (data != null && data.length > 0) {
                    return picture;
                }
            } catch (Exception ignored) {
            }
        }
        return Pictures.ofBytes(TRANSPARENT_PNG).create();
    }
    /**
     * 渲染Word用户签名对象(按人员id, 带兜底): 人员不存在或签名未配置/文件缺失时返回透明图片, 不抛错
     */
    public static PictureRenderData getFinalUserSignatureOrTransparent(Integer userId) {
        try {
            return orTransparentPicture(getFinalUserSignatureUrl(userId));
        } catch (Exception e) {
            return Pictures.ofBytes(TRANSPARENT_PNG).create();
        }
    }
    /**
     * 渲染Word用户签名对象(按人员姓名, 带兜底): 人员不存在或签名未配置/文件缺失时返回透明图片, 不抛错
     */
    public static PictureRenderData getFinalUserSignatureOrTransparent(String userName) {
        try {
            return orTransparentPicture(getFinalUserSignatureUrl(userName));
        } catch (Exception e) {
            return Pictures.ofBytes(TRANSPARENT_PNG).create();
        }
    }
}