talent-card-server/src/main/java/com/talentcard/server/service/BenefitVerificationCodeService.java
@@ -27,12 +27,14 @@
import com.talentcard.server.entity.BenefitFreeStayVoucher;
import com.talentcard.server.entity.BenefitMerchant;
import com.talentcard.server.entity.TicketPackageCode;
import com.talentcard.server.entity.TicketPackageClaim;
import com.talentcard.server.mapper.BenefitCinemaCouponClaimMapper;
import com.talentcard.server.mapper.BenefitCinemaCouponMapper;
import com.talentcard.server.mapper.BenefitFreeStayVerificationCodeMapper;
import com.talentcard.server.mapper.BenefitFreeStayVoucherMapper;
import com.talentcard.server.mapper.BenefitMerchantMapper;
import com.talentcard.server.mapper.TicketPackageCodeMapper;
import com.talentcard.server.mapper.TicketPackageClaimMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -50,12 +52,14 @@
    private final BenefitFreeStayVoucherMapper voucherMapper;
    private final BenefitFreeStayVerificationCodeMapper freeStayCodeMapper;
    private final TicketPackageCodeMapper codeMapper;
    private final TicketPackageClaimMapper ticketPackageClaimMapper;
    private final TicketPackagePortalService ticketPackageService;
    public BenefitVerificationCodeService(
            BenefitQuotaService quotaService,
            BenefitMerchantMapper merchantMapper,
            TicketPackageCodeMapper codeMapper,
            TicketPackageClaimMapper ticketPackageClaimMapper,
            TicketPackagePortalService ticketPackageService,
            BenefitCinemaCouponMapper couponMapper,
            BenefitCinemaCouponClaimMapper claimMapper,
@@ -65,6 +69,7 @@
        this.quotaService = quotaService;
        this.merchantMapper = merchantMapper;
        this.codeMapper = codeMapper;
        this.ticketPackageClaimMapper = ticketPackageClaimMapper;
        this.ticketPackageService = ticketPackageService;
        this.couponMapper = couponMapper;
        this.claimMapper = claimMapper;
@@ -78,14 +83,41 @@
        if (!activeMovieCode(claim)) {
            throw new BusinessException(ErrorCode.CONFLICT, "电影票兑换码不可用");
        }
        return qrPng(claim.getExchangeCode(), "电影票二维码生成失败");
    }
    /**
     * 观影权益(票包兑换码)的二维码:内容是后台批量生成、绑定影城的那串兑换码。
     *
     * <p>与电影券的二维码分成两个入口,因为两者的领取记录不在同一张表——同一个 id 可能两边都存在,
     * 合并成一个接口会张冠李戴。
     */
    @Transactional(readOnly = true)
    public byte[] ticketPackageCodeImage(Long claimId) {
        TicketPackageClaim claim = ticketPackageClaimMapper.selectById(claimId);
        if (claim == null || (claim.getDeleted() != null && claim.getDeleted() != 0)) {
            throw new BusinessException(ErrorCode.NOT_FOUND, "观影权益领取记录不存在");
        }
        // 已核销的观影券仍然返回二维码:使用记录详情要按原型继续展示这张码(核销时服务端会拒掉重复核销),
        // 只有过期/作废才不给。
        if (!"ISSUED".equals(upper(claim.getStatus())) && !"USED".equals(upper(claim.getStatus()))) {
            throw new BusinessException(ErrorCode.CONFLICT, "观影权益已失效");
        }
        if (expiredDate(claim.getValidEnd())) {
            throw new BusinessException(ErrorCode.CONFLICT, "观影权益已过期");
        }
        return qrPng(claim.getExchangeCode(), "观影权益二维码生成失败");
    }
    private byte[] qrPng(String content, String failureMessage) {
        try {
            var matrix = new QRCodeWriter().encode(claim.getExchangeCode(), BarcodeFormat.QR_CODE, 480, 480);
            var matrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, 480, 480);
            BufferedImage image = MatrixToImageWriter.toBufferedImage(matrix);
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            ImageIO.write(image, "PNG", output);
            return output.toByteArray();
        } catch (Exception ex) {
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, "电影票二维码生成失败");
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, failureMessage);
        }
    }