From e28ed3508f2204dd1b513a258cef6a47c59b76d1 Mon Sep 17 00:00:00 2001 From: hanrenchun Date: Thu, 11 Dec 2025 11:17:50 +0800 Subject: [PATCH] =?UTF-8?q?kafka=E6=95=B0=E6=8D=AE=E6=8E=A5=E6=94=B6?= =?UTF-8?q?=E5=B9=B6=E5=AD=98=E5=BA=93=EF=BC=8C=E5=87=BA=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E7=94=9F=E6=88=90=E5=B7=A5=E5=85=B7=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gather-app/pom.xml | 5 + .../java/com/ruoyi/config/KafkaConfig.java | 57 ++++ .../controller/SmallProgramController.java | 26 +- .../database/domain/PassVehicleInfo.java | 130 ++++++++ .../database/domain/ReleaseRequestInfo.java | 151 ++++++++++ .../mapper/PassVehicleInfoMapper.java | 16 + .../mapper/ReleaseRequestInfoMapper.java | 16 + .../service/HikKafkaConsumerService.java | 248 ---------------- .../service/PassVehicleInfoService.java | 14 + .../service/ReleaseRequestInfoService.java | 14 + .../service/SimpleKafkaMessageService.java | 55 ++++ .../impl/PassVehicleInfoServiceImpl.java | 19 ++ .../impl/ReleaseRequestInfoServiceImpl.java | 19 ++ .../ruoyi/database/util/HikKafkaConsumer.java | 278 ++++++++++++++++++ .../util/QRCodeGenerator1080x640.java | 252 ++++++++++++++++ .../database/util/SimpleJsonConverter.java | 62 ++++ 16 files changed, 1104 insertions(+), 258 deletions(-) create mode 100644 gather-app/src/main/java/com/ruoyi/config/KafkaConfig.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/domain/PassVehicleInfo.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/domain/ReleaseRequestInfo.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/mapper/PassVehicleInfoMapper.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/mapper/ReleaseRequestInfoMapper.java delete mode 100644 gather-app/src/main/java/com/ruoyi/database/service/HikKafkaConsumerService.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/service/PassVehicleInfoService.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/service/ReleaseRequestInfoService.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/service/SimpleKafkaMessageService.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/service/impl/PassVehicleInfoServiceImpl.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/service/impl/ReleaseRequestInfoServiceImpl.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/util/HikKafkaConsumer.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/util/QRCodeGenerator1080x640.java create mode 100644 gather-app/src/main/java/com/ruoyi/database/util/SimpleJsonConverter.java diff --git a/gather-app/pom.xml b/gather-app/pom.xml index c22ecf7..32a3a42 100644 --- a/gather-app/pom.xml +++ b/gather-app/pom.xml @@ -54,6 +54,11 @@ 2.3.0 + + org.springframework.kafka + spring-kafka + + com.google.zxing core diff --git a/gather-app/src/main/java/com/ruoyi/config/KafkaConfig.java b/gather-app/src/main/java/com/ruoyi/config/KafkaConfig.java new file mode 100644 index 0000000..22946b4 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/config/KafkaConfig.java @@ -0,0 +1,57 @@ +package com.ruoyi.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Properties; + +@Configuration +public class KafkaConfig { + + private String bootstrapServers = "kafka.hikparking.com:30090,kafka.hikparking.com:30091,kafka.hikparking.com:30092"; + + private String groupId = "gp_32856UK2Rr9BT"; + + private String topic = "topic_pass32856ik8M5V"; + + private String username = "103251113CU2C82TCA6M9"; + + private String password = "ZR56NoW%qxov$Gl4"; + + /** + * 创建Kafka消费者配置 + */ + @Bean + public Properties kafkaConsumerProperties() { + Properties properties = new Properties(); + + // 基础配置 + properties.setProperty("bootstrap.servers", bootstrapServers); + properties.setProperty("group.id", groupId); + properties.setProperty("key.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + properties.setProperty("value.deserializer", + "org.apache.kafka.common.serialization.StringDeserializer"); + properties.setProperty("auto.offset.reset", "latest"); + properties.setProperty("enable.auto.commit", "true"); + properties.setProperty("auto.commit.interval.ms", "1000"); + properties.setProperty("session.timeout.ms", "30000"); + properties.setProperty("heartbeat.interval.ms", "10000"); + properties.setProperty("max.poll.records", "500"); + + // SASL认证配置 - 特别注意:SCRAM-SHA-256(不是你截图中的SCRAM-SMA-256) + properties.put("security.protocol", "SASL_PLAINTEXT"); + properties.put("sasl.mechanism", "SCRAM-SHA-256"); // 修正为SCRAM-SHA-256 + + // 注意:正确的类名是scram不是scran + String jaasConfig = String.format( + "org.apache.kafka.common.security.scram.ScramLoginModule required " + + "username=\"%s\" password=\"%s\";", + username, password + ); + properties.put("sasl.jaas.config", jaasConfig); + + return properties; + } +} \ No newline at end of file diff --git a/gather-app/src/main/java/com/ruoyi/database/controller/SmallProgramController.java b/gather-app/src/main/java/com/ruoyi/database/controller/SmallProgramController.java index 155271d..75e2285 100644 --- a/gather-app/src/main/java/com/ruoyi/database/controller/SmallProgramController.java +++ b/gather-app/src/main/java/com/ruoyi/database/controller/SmallProgramController.java @@ -48,15 +48,21 @@ public class SmallProgramController extends BaseController { @ApiOperation("根据车牌号查询缴费订单") @Transactional public AjaxResult getOrder(String plateNo) { - ParkingBillInfo b = parkingBillInfoService.saveOrUpdateParkingBillInfo(plateNo); - if (b == null) { +// ParkingBillInfo b = parkingBillInfoService.saveOrUpdateParkingBillInfo(plateNo); + ParkingBillInfo one = parkingBillInfoService.lambdaQuery() + .eq(ParkingBillInfo::getPlateNo, plateNo) + .orderByDesc(ParkingBillInfo::getCreateTime) + .last("limit 1") + .one(); + + if (one == null) { return AjaxResult.error("未查询到订单"); }else { ParkingBillInfoVO parkingBillInfoVO = new ParkingBillInfoVO(); - BeanUtils.copyProperties(b, parkingBillInfoVO); - parkingBillInfoVO.setTotalCostYuan(b.getTotalCostYuan().toString() + "元"); - parkingBillInfoVO.setPayMoneyYuan(b.getPayMoneyYuan().toString() + "元"); - parkingBillInfoVO.setDeductMoneyYuan(b.getDeductMoneyYuan().toString() + "元"); + BeanUtils.copyProperties(one, parkingBillInfoVO); + parkingBillInfoVO.setTotalCostYuan(one.getTotalCostYuan().toString() + "元"); + parkingBillInfoVO.setPayMoneyYuan(one.getPayMoneyYuan().toString() + "元"); + parkingBillInfoVO.setDeductMoneyYuan(one.getDeductMoneyYuan().toString() + "元"); return AjaxResult.success(parkingBillInfoVO); } } @@ -174,10 +180,10 @@ public class SmallProgramController extends BaseController { if (one == null) { return AjaxResult.error("订单查询失败"); } - one = parkingBillInfoService.saveOrUpdateParkingBillInfo(one.getPlateNo()); - if (one == null) { - return AjaxResult.error("订单查询失败"); - } +// one = parkingBillInfoService.saveOrUpdateParkingBillInfo(one.getPlateNo()); +// if (one == null) { +// return AjaxResult.error("订单查询失败"); +// } String generate = generate(loginUserByPhone.getPhone()); String openId = getOpenId(payFees); String clientIp = wechatMiniProgramPayService.getClientIpAddress(request); diff --git a/gather-app/src/main/java/com/ruoyi/database/domain/PassVehicleInfo.java b/gather-app/src/main/java/com/ruoyi/database/domain/PassVehicleInfo.java new file mode 100644 index 0000000..d89846c --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/domain/PassVehicleInfo.java @@ -0,0 +1,130 @@ +package com.ruoyi.database.domain; + + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 停车场过车消息表(PassVehicleInfo)实体类 + * + * @author makejava + * @since 2025-12-11 10:14:26 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName(value = "pass_vehicle_info") +@ApiModel(value = "PassVehicleInfo", description = "停车场过车消息表") +public class PassVehicleInfo { + + /** + * 主键 + */ + @ApiModelProperty("主键") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + @ApiModelProperty("创建时间") + @Excel(name = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 创建者 + */ + @ApiModelProperty("创建者") + @Excel(name = "创建者") + private Integer createBy; + + /** + * 更新时间 + */ + @ApiModelProperty("更新时间") + @Excel(name = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** + * 更新者 + */ + @ApiModelProperty("更新者") + @Excel(name = "更新者") + private Integer updateBy; + + /** + * 出入方向 + */ + @ApiModelProperty("出入方向") + @Excel(name = "出入方向") + private Integer direct; + + /** + * 车牌号 + */ + @ApiModelProperty("车牌号") + @Excel(name = "车牌号") + private String plateNo; + + /** + * 停车场编号 + */ + @ApiModelProperty("停车场编号") + @Excel(name = "停车场编号") + private String parkCode; + + /** + * 出入口编号 + */ + @ApiModelProperty("出入口编号") + @Excel(name = "出入口编号") + private String gateCode; + + /** + * 车道编号 + */ + @ApiModelProperty("车道编号") + @Excel(name = "车道编号") + private String laneNo; + + /** + * 入车唯一编号 + */ + @ApiModelProperty("入车唯一编号") + @Excel(name = "入车唯一编号") + private String inUnid; + + /** + * 出车唯一编号 + */ + @ApiModelProperty("出车唯一编号") + @Excel(name = "出车唯一编号") + private String outUnid; + + /** + * 进场时间 + */ + @ApiModelProperty("进场时间") + @Excel(name = "进场时间") + private String inTime; + + /** + * 出场时间 + */ + @ApiModelProperty("出场时间") + @Excel(name = "出场时间") + private String outTime; + + +} diff --git a/gather-app/src/main/java/com/ruoyi/database/domain/ReleaseRequestInfo.java b/gather-app/src/main/java/com/ruoyi/database/domain/ReleaseRequestInfo.java new file mode 100644 index 0000000..8aea431 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/domain/ReleaseRequestInfo.java @@ -0,0 +1,151 @@ +package com.ruoyi.database.domain; + + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 车辆请求放行表(ReleaseRequestInfo)实体类 + * + * @author makejava + * @since 2025-12-11 10:16:48 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName(value = "release_request_info") +@ApiModel(value = "ReleaseRequestInfo", description = "车辆请求放行表") +public class ReleaseRequestInfo { + + /** + * 主键 + */ + @ApiModelProperty("主键") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + @ApiModelProperty("创建时间") + @Excel(name = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 创建者 + */ + @ApiModelProperty("创建者") + @Excel(name = "创建者") + private Integer createBy; + + /** + * 更新时间 + */ + @ApiModelProperty("更新时间") + @Excel(name = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** + * 更新者 + */ + @ApiModelProperty("更新者") + @Excel(name = "更新者") + private Integer updateBy; + + /** + * 出入方向 + */ + @ApiModelProperty("出入方向") + @Excel(name = "出入方向") + private Integer direct; + + /** + * 车牌号 + */ + @ApiModelProperty("车牌号") + @Excel(name = "车牌号") + private String plateNo; + + /** + * 过车时间 + */ + @ApiModelProperty("过车时间") + @Excel(name = "过车时间") + private String passTime; + + /** + * 停车场编号 + */ + @ApiModelProperty("停车场编号") + @Excel(name = "停车场编号") + private String parkCode; + + /** + * 出入口编号 + */ + @ApiModelProperty("出入口编号") + @Excel(name = "出入口编号") + private String gateCode; + + /** + * 车道编号 + */ + @ApiModelProperty("车道编号") + @Excel(name = "车道编号") + private String laneNo; + + /** + * 入车唯一编号 + */ + @ApiModelProperty("入车唯一编号") + @Excel(name = "入车唯一编号") + private String inUnid; + + /** + * 出车唯一编号 + */ + @ApiModelProperty("出车唯一编号") + @Excel(name = "出车唯一编号") + private String outUnid; + + /** + * 停车类型 + */ + @ApiModelProperty("停车类型") + @Excel(name = "停车类型") + private Integer parkType; + + /** + * 车牌类型 + */ + @ApiModelProperty("车牌类型") + @Excel(name = "车牌类型") + private Integer plateType; + + /** + * 车牌颜色 + */ + @ApiModelProperty("车牌颜色") + @Excel(name = "车牌颜色") + private Integer plateColor; + + /** + * 车辆类型 + */ + @ApiModelProperty("车辆类型") + @Excel(name = "车辆类型") + private Integer carType; + + +} diff --git a/gather-app/src/main/java/com/ruoyi/database/mapper/PassVehicleInfoMapper.java b/gather-app/src/main/java/com/ruoyi/database/mapper/PassVehicleInfoMapper.java new file mode 100644 index 0000000..411fce8 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/mapper/PassVehicleInfoMapper.java @@ -0,0 +1,16 @@ +package com.ruoyi.database.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.database.domain.PassVehicleInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * 停车场过车消息表(PassVehicleInfo) Mapper 接口 + * + * @author makejava + * @since ${date} + */ +@Mapper +public interface PassVehicleInfoMapper extends BaseMapper { + +} diff --git a/gather-app/src/main/java/com/ruoyi/database/mapper/ReleaseRequestInfoMapper.java b/gather-app/src/main/java/com/ruoyi/database/mapper/ReleaseRequestInfoMapper.java new file mode 100644 index 0000000..e0a1d79 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/mapper/ReleaseRequestInfoMapper.java @@ -0,0 +1,16 @@ +package com.ruoyi.database.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.database.domain.ReleaseRequestInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * 车辆请求放行表(ReleaseRequestInfo) Mapper 接口 + * + * @author makejava + * @since ${date} + */ +@Mapper +public interface ReleaseRequestInfoMapper extends BaseMapper { + +} diff --git a/gather-app/src/main/java/com/ruoyi/database/service/HikKafkaConsumerService.java b/gather-app/src/main/java/com/ruoyi/database/service/HikKafkaConsumerService.java deleted file mode 100644 index 7f4d3bd..0000000 --- a/gather-app/src/main/java/com/ruoyi/database/service/HikKafkaConsumerService.java +++ /dev/null @@ -1,248 +0,0 @@ -package com.ruoyi.database.service; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.ruoyi.database.domain.VehicleRecord; -import org.apache.kafka.clients.consumer.*; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.stereotype.Component; - -import java.text.SimpleDateFormat; -import java.time.Duration; -import java.util.Arrays; -import java.util.Date; -import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; - -@Component -public class HikKafkaConsumerService implements InitializingBean, DisposableBean { - - private final AtomicBoolean running = new AtomicBoolean(false); - private ExecutorService executorService; - private KafkaConsumer consumer; - - // 配置参数 - 建议放到配置文件中 - private final String bootstrapServers = "kafka.hikparking.com:38999,kafka.hikparking.com:38991,kafka.hikparking.com:38992"; - private final String groupId = "gp_32856UK2Rr9BT"; - private final String topic = "topic_pass32856ik8M5V"; - private final String username = "103251113CU2C82TCA6M9"; - private final String password = "ZR56NoW%qxov$Gl4"; - private final ObjectMapper objectMapper = new ObjectMapper(); - - @Override - public void afterPropertiesSet() throws Exception { - startConsumer(); - } - - @Override - public void destroy() throws Exception { - stopConsumer(); - } - - private Properties createConsumerConfig() { - Properties properties = new Properties(); - - // 基础配置 - properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); - properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG, groupId); - properties.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - properties.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, - "org.apache.kafka.common.serialization.StringDeserializer"); - - // SASL认证配置 - 修正了你的配置中的拼写错误 - properties.put("security.protocol", "SASL_PLAINTEXT"); - properties.put("sasl.mechanism", "SCRAM-SHA-256"); // 修正:sssl -> sasl - - // JAAS配置 - 修正了配置格式 - String jaasConfig = String.format( - "org.apache.kafka.common.security.scram.ScramLoginModule required username=\"%s\" password=\"%s\";", - username, password - ); - properties.put("sasl.jaas.config", jaasConfig); // 修正:sssl -> sasl - - // 其他优化配置 - properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); - properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); - properties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "500"); - properties.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000"); - properties.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "10000"); - - return properties; - } - - private void startConsumer() { - try { - Properties config = createConsumerConfig(); - consumer = new KafkaConsumer<>(config); - consumer.subscribe(Arrays.asList(topic)); - - running.set(true); - executorService = Executors.newSingleThreadExecutor(); - executorService.submit(this::consumeMessages); - - System.out.println("Kafka消费者启动成功,开始监听主题: " + topic); - } catch (Exception e) { - System.err.println("Kafka消费者启动失败: " + e.getMessage()); - e.printStackTrace(); - } - } - - private void consumeMessages() { - int emptyPollCount = 0; - final int MAX_EMPTY_POLLS = 10; // 连续空轮询次数阈值 - - while (running.get()) { - try { - ConsumerRecords records = consumer.poll(Duration.ofMillis(1000)); - - if (records != null && !records.isEmpty()) { - emptyPollCount = 0; // 重置空轮询计数 - System.out.println("拉取到 " + records.count() + " 条消息"); - - for (ConsumerRecord record : records) { - processRecord(record); - } - - // 手动提交偏移量 - consumer.commitSync(); - System.out.println("偏移量提交成功"); - - } else { - emptyPollCount++; - // 如果连续多次拉取不到消息,记录日志(可选) - if (emptyPollCount >= MAX_EMPTY_POLLS) { - System.out.println("连续 " + MAX_EMPTY_POLLS + " 次未拉取到消息,消费者运行正常..."); - emptyPollCount = 0; // 重置计数 - } - } - - } catch (Exception e) { - System.err.println("消费消息时发生异常: " + e.getMessage()); - e.printStackTrace(); - - // 异常后短暂休眠 - try { - Thread.sleep(5000); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - break; - } - } - } - } - - private void processRecord(ConsumerRecord record) { - try { - // 这里实现你的业务逻辑 - System.out.printf("接收到消息: topic=%s, partition=%d, offset=%d, key=%s, value=%s%n", - record.topic(), record.partition(), record.offset(), - record.key(), record.value()); - VehicleRecord vehicleRecord = objectMapper.readValue(record.value(), VehicleRecord.class); - // 示例业务处理 - handleBusinessLogic(vehicleRecord, record); - - } catch (Exception e) { - System.err.println("处理消息失败: " + e.getMessage()); - // 根据业务需求决定是否抛出异常 - // 如果抛出异常,这批消息的偏移量不会提交,会被重新消费 - } - } - - private void handleBusinessLogic(VehicleRecord record, ConsumerRecord kafkaRecord) { - try { - // 根据方向处理不同的业务逻辑 - if (record.getDirect() == 0) { - processInRecord(record); // 进场记录 - } else if (record.getDirect() == 1) { - processOutRecord(record); // 出场记录 - } else { - System.err.println("未知的方向类型: " + record.getDirect()); - } - - } catch (Exception e) { - System.err.println("业务处理失败: " + e.getMessage()); - throw new RuntimeException("业务处理异常", e); - } - } - - - - /** - * 处理进场记录 - */ - private void processInRecord(VehicleRecord record) { - System.out.println("=== 处理进场记录 ==="); - System.out.println("停车场: " + record.getParkCode()); - System.out.println("车牌: " + record.getPlateNo()); - System.out.println("进场时间: " + record.getInTime()); - System.out.println("车道: " + record.getLaneNo()); - System.out.println("进场图片数量: " + - (record.getInImages() != null ? record.getInImages().size() : 0)); - - // 这里添加具体的进场业务逻辑 - // 例如:保存到数据库、发送通知等 - } - - /** - * 处理出场记录 - */ - private void processOutRecord(VehicleRecord record) { - System.out.println("=== 处理出场记录 ==="); - System.out.println("停车场: " + record.getParkCode()); - System.out.println("车牌: " + record.getPlateNo()); - System.out.println("出场时间: " + record.getOutTime()); - System.out.println("停车时长: " + calculateParkingDuration(record)); - System.out.println("总费用: " + record.getTotalCost() + "分"); - System.out.println("实付费用: " + record.getPayCost() + "分"); - System.out.println("出场图片数量: " + - (record.getOutImages() != null ? record.getOutImages().size() : 0)); - - // 这里添加具体的出场业务逻辑 - // 例如:计算费用、更新停车记录、扣费等 - } - - /** - * 计算停车时长(分钟) - */ - private Long calculateParkingDuration(VehicleRecord record) { - try { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - Date inTime = sdf.parse(record.getInTime()); - Date outTime = sdf.parse(record.getOutTime()); - return (outTime.getTime() - inTime.getTime()) / (1000 * 60); - } catch (Exception e) { - System.err.println("计算停车时长失败: " + e.getMessage()); - return null; - } - } - - private void stopConsumer() { - System.out.println("正在停止Kafka消费者..."); - running.set(false); - - if (executorService != null) { - executorService.shutdown(); - } - - if (consumer != null) { - consumer.close(); - System.out.println("Kafka消费者已关闭"); - } - } - - /** - * 手动重启消费者(可选) - */ - public void restartConsumer() { - stopConsumer(); - try { - Thread.sleep(3000); - startConsumer(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } -} diff --git a/gather-app/src/main/java/com/ruoyi/database/service/PassVehicleInfoService.java b/gather-app/src/main/java/com/ruoyi/database/service/PassVehicleInfoService.java new file mode 100644 index 0000000..4fd3b99 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/service/PassVehicleInfoService.java @@ -0,0 +1,14 @@ +package com.ruoyi.database.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.ruoyi.database.domain.PassVehicleInfo; + +/** + * (停车场过车消息表)Service + * + * @author makejava + * @since 2025-12-11 10:14:27 + */ +public interface PassVehicleInfoService extends IService { + +} diff --git a/gather-app/src/main/java/com/ruoyi/database/service/ReleaseRequestInfoService.java b/gather-app/src/main/java/com/ruoyi/database/service/ReleaseRequestInfoService.java new file mode 100644 index 0000000..6867e0f --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/service/ReleaseRequestInfoService.java @@ -0,0 +1,14 @@ +package com.ruoyi.database.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.ruoyi.database.domain.ReleaseRequestInfo; + +/** + * (车辆请求放行表)Service + * + * @author makejava + * @since 2025-12-11 10:16:48 + */ +public interface ReleaseRequestInfoService extends IService { + +} diff --git a/gather-app/src/main/java/com/ruoyi/database/service/SimpleKafkaMessageService.java b/gather-app/src/main/java/com/ruoyi/database/service/SimpleKafkaMessageService.java new file mode 100644 index 0000000..732d6d0 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/service/SimpleKafkaMessageService.java @@ -0,0 +1,55 @@ +package com.ruoyi.database.service; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.database.domain.PassVehicleInfo; +import com.ruoyi.database.domain.ReleaseRequestInfo; +import com.ruoyi.database.util.SimpleJsonConverter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 简单消息处理服务 + */ +@Slf4j +@Service +public class SimpleKafkaMessageService { + + @Autowired + private SimpleJsonConverter jsonConverter; + + @Autowired + private PassVehicleInfoService passVehicleInfoService; + + @Autowired + private ReleaseRequestInfoService releaseRequestInfoService; + + /** + * 处理Kafka消息 + */ + public void processKafkaMessage(String topic, String message) { + try { + JSONObject json = JSON.parseObject(message); + + if (topic.contains("topic_pass32856ik8M5V")) { + // 过车消息 + PassVehicleInfo info = jsonConverter.toPassVehicleInfo(json); + if (info != null) { + passVehicleInfoService.save(info); + log.info("保存过车消息成功, 车牌: {}", info.getPlateNo()); + } + } else if (topic.contains("topic_releaseRequest32856dSpuPV")) { + // 放行请求消息 + ReleaseRequestInfo info = jsonConverter.toReleaseRequestInfo(json); + if (info != null) { + releaseRequestInfoService.save(info); + log.info("保存放行请求成功, 车牌: {}", info.getPlateNo()); + } + } + + } catch (Exception e) { + log.error("处理Kafka消息失败, topic: {}, message: {}", topic, message, e); + } + } +} diff --git a/gather-app/src/main/java/com/ruoyi/database/service/impl/PassVehicleInfoServiceImpl.java b/gather-app/src/main/java/com/ruoyi/database/service/impl/PassVehicleInfoServiceImpl.java new file mode 100644 index 0000000..abaf5cd --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/service/impl/PassVehicleInfoServiceImpl.java @@ -0,0 +1,19 @@ +package com.ruoyi.database.service.impl; + + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.database.domain.PassVehicleInfo; +import com.ruoyi.database.mapper.PassVehicleInfoMapper; +import com.ruoyi.database.service.PassVehicleInfoService; +import org.springframework.stereotype.Service; + +/** + * (停车场过车消息表)ServiceImpl + * + * @author makejava + * @since 2025-12-11 10:14:27 + */ +@Service +public class PassVehicleInfoServiceImpl extends ServiceImpl implements PassVehicleInfoService { + +} diff --git a/gather-app/src/main/java/com/ruoyi/database/service/impl/ReleaseRequestInfoServiceImpl.java b/gather-app/src/main/java/com/ruoyi/database/service/impl/ReleaseRequestInfoServiceImpl.java new file mode 100644 index 0000000..170e3f7 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/service/impl/ReleaseRequestInfoServiceImpl.java @@ -0,0 +1,19 @@ +package com.ruoyi.database.service.impl; + + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.ruoyi.database.domain.ReleaseRequestInfo; +import com.ruoyi.database.mapper.ReleaseRequestInfoMapper; +import com.ruoyi.database.service.ReleaseRequestInfoService; +import org.springframework.stereotype.Service; + +/** + * (车辆请求放行表)ServiceImpl + * + * @author makejava + * @since 2025-12-11 10:16:48 + */ +@Service +public class ReleaseRequestInfoServiceImpl extends ServiceImpl implements ReleaseRequestInfoService { + +} diff --git a/gather-app/src/main/java/com/ruoyi/database/util/HikKafkaConsumer.java b/gather-app/src/main/java/com/ruoyi/database/util/HikKafkaConsumer.java new file mode 100644 index 0000000..e75dcb5 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/util/HikKafkaConsumer.java @@ -0,0 +1,278 @@ +package com.ruoyi.database.util; + +import com.ruoyi.database.service.SimpleKafkaMessageService; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +@Component +public class HikKafkaConsumer implements CommandLineRunner { + + private static final Logger logger = LoggerFactory.getLogger(HikKafkaConsumer.class); + + private final AtomicBoolean running = new AtomicBoolean(true); + private ExecutorService executorService; + private KafkaConsumer consumer; + + @Autowired + private Properties kafkaConsumerProperties; + + @Autowired + private SimpleKafkaMessageService simpleKafkaMessageService; + + private String topic = "topic_releaseRequest32856dSpuPV,topic_pass32856ik8M5V"; + + + + private boolean autoStart = true; + + private int consumerThreads = 1; + + @Override + public void run(String... args) throws Exception { + if (autoStart) { + startConsumer(); + } + } + + /** + * 启动消费者 + */ + public synchronized void startConsumer() { + if (executorService != null && !executorService.isShutdown()) { + logger.warn("消费者已经在运行"); + return; + } + + try { + // 创建消费者实例 + consumer = new KafkaConsumer<>(kafkaConsumerProperties); + consumer.subscribe(Arrays.asList(topic.split(","))); + + executorService = Executors.newFixedThreadPool(consumerThreads); + + for (int i = 0; i < consumerThreads; i++) { + final int threadId = i; + executorService.submit(() -> consumeMessages(threadId)); + } + + logger.info("海康Kafka消费者启动成功"); + printStartupInfo(); + + } catch (Exception e) { + logger.error("启动Kafka消费者失败", e); + System.err.println("❌ 启动Kafka消费者失败: " + e.getMessage()); + } + } + + /** + * 消费消息 + */ + private void consumeMessages(int threadId) { + String threadName = "kafka-consumer-" + threadId; + Thread.currentThread().setName(threadName); + + logger.info("消费者线程 {} 启动", threadName); + + while (running.get()) { + try { + ConsumerRecords records = consumer.poll(Duration.ofMillis(1000)); + + if (records.isEmpty()) { + // 没有消息时稍微休眠,避免CPU空转 + Thread.sleep(100); + continue; + } + + for (ConsumerRecord record : records) { + processRecord(record, threadId); + } + + // 异步提交偏移量(如果开启了自动提交则不需要) + // consumer.commitAsync(); + + } catch (Exception e) { + logger.error("消费者线程 {} 消费消息异常", threadName, e); + + // 发生异常时等待一段时间再重试 + try { + Thread.sleep(5000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + } + + logger.info("消费者线程 {} 停止", threadName); + } + + /** + * 处理单条记录 + */ + private void processRecord(ConsumerRecord record, int threadId) { + try { + // 格式化输出到控制台 + String consoleOutput = formatConsoleOutput(record, threadId); + System.out.println(consoleOutput); + + // 记录到日志文件(INFO级别) + logger.info("消费者线程 {} - 主题: {}, 分区: {}, 偏移量: {}, 键: {}, 值: {}", + threadId, record.topic(), record.partition(), + record.offset(), record.key(), record.value()); + + // 如果需要,这里可以添加业务处理逻辑 + simpleKafkaMessageService.processKafkaMessage(record.topic(), record.value()); + } catch (Exception e) { + logger.error("处理消息记录失败", e); + System.err.println("处理消息失败: " + e.getMessage()); + } + } + + /** + * 格式化控制台输出 + */ + private String formatConsoleOutput(ConsumerRecord record, int threadId) { + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + sb.append("╔══════════════════════════════════════════════════════════════════╗\n"); + sb.append(String.format("║ 【海康Kafka消息】线程: %-6d ║\n", threadId)); + sb.append("╠══════════════════════════════════════════════════════════════════╣\n"); + sb.append(String.format("║ 主题 : %-50s ║\n", record.topic())); + sb.append(String.format("║ 分区 : %-50d ║\n", record.partition())); + sb.append(String.format("║ 偏移量 : %-50d ║\n", record.offset())); + sb.append(String.format("║ 时间戳 : %-50s ║\n", new java.util.Date(record.timestamp()))); + sb.append(String.format("║ 键 : %-50s ║\n", + record.key() != null ? record.key() : "null")); + + // 处理消息值,如果太长则截断 + String value = record.value(); + if (value.length() > 50) { + value = value.substring(0, 47) + "..."; + } + sb.append(String.format("║ 值 : %-50s ║\n", value)); + sb.append("╚══════════════════════════════════════════════════════════════════╝\n"); + + return sb.toString(); + } + + /** + * 打印启动信息 + */ + private void printStartupInfo() { + String servers = kafkaConsumerProperties.getProperty("bootstrap.servers"); + String groupId = kafkaConsumerProperties.getProperty("group.id"); + String username = extractUsernameFromJaasConfig(); + + String startupInfo = String.format( + "\n" + + "██████╗ ███████╗██╗ ██╗███████╗██╗ ██╗ ██╗ ██╗██╗██╗ ██╗\n" + + "██╔══██╗██╔════╝██║ ██║██╔════╝██║ ██╔╝ ██║ ██╔╝██║██║ ██╔╝\n" + + "██████╔╝█████╗ ██║ ██║███████╗█████╔╝ █████╔╝ ██║█████╔╝ \n" + + "██╔══██╗██╔══╝ ╚██╗ ██╔╝╚════██║██╔═██╗ ██╔═██╗ ██║██╔═██╗ \n" + + "██║ ██║███████╗ ╚████╔╝ ███████║██║ ██╗ ██║ ██╗██║██║ ██╗\n" + + "╚═╝ ╚═╝╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝\n" + + "\n" + + "════════════════════ Kafka消费者启动成功 ═════════════════════\n" + + "服务器地址 : %s\n" + + "消费主题 : %s\n" + + "消费者组 : %s\n" + + "认证用户 : %s\n" + + "认证机制 : SCRAM-SHA-256\n" + + "线程数量 : %d\n" + + "═══════════════════════════════════════════════════════════\n", + servers, topic, groupId, username, consumerThreads + ); + + System.out.println(startupInfo); + logger.info(startupInfo); + } + + /** + * 从JAAS配置中提取用户名 + */ + private String extractUsernameFromJaasConfig() { + String jaasConfig = (String) kafkaConsumerProperties.get("sasl.jaas.config"); + if (jaasConfig != null) { + // 从类似 "username=\"userxxxxx\"" 的字符串中提取用户名 + int start = jaasConfig.indexOf("username=\""); + if (start != -1) { + start += "username=\"".length(); + int end = jaasConfig.indexOf("\"", start); + if (end != -1) { + return jaasConfig.substring(start, end); + } + } + } + return "未知"; + } + + /** + * 停止消费者 + */ + public synchronized void stopConsumer() { + running.set(false); + + if (executorService != null) { + executorService.shutdown(); + logger.info("正在停止消费者线程池..."); + } + + if (consumer != null) { + consumer.close(); + logger.info("Kafka消费者已关闭"); + } + + System.out.println("Kafka消费者已停止"); + } + + /** + * 重新启动消费者 + */ + public void restartConsumer() { + stopConsumer(); + running.set(true); + try { + Thread.sleep(2000); // 等待2秒再重启 + startConsumer(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + /** + * 测试消费一条消息(用于验证连接) + */ + public void testConsumeOne() { + try (KafkaConsumer testConsumer = new KafkaConsumer<>(kafkaConsumerProperties)) { + testConsumer.subscribe(Arrays.asList(topic.split(","))); + + ConsumerRecords records = testConsumer.poll(Duration.ofMillis(5000)); + + if (records.isEmpty()) { + System.out.println("测试消费:未收到任何消息(可能没有新消息)"); + } else { + System.out.println("测试消费:收到 " + records.count() + " 条消息"); + for (ConsumerRecord record : records) { + System.out.println("主题: " + record.topic() + ", 值: " + record.value()); + } + } + } catch (Exception e) { + System.err.println("测试消费失败: " + e.getMessage()); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/gather-app/src/main/java/com/ruoyi/database/util/QRCodeGenerator1080x640.java b/gather-app/src/main/java/com/ruoyi/database/util/QRCodeGenerator1080x640.java new file mode 100644 index 0000000..83ace7c --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/util/QRCodeGenerator1080x640.java @@ -0,0 +1,252 @@ +package com.ruoyi.database.util; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.client.j2se.MatrixToImageConfig; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class QRCodeGenerator1080x640 { + + + + public static void generateHighQualityQRCode(String content, String filePath) throws Exception { + Map hints = new HashMap<>(); + + // === 关键参数配置 === + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + hints.put(EncodeHintType.MARGIN, 4); // 边距 + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 高容错 + hints.put(EncodeHintType.QR_VERSION, 10); // 版本 + + // 生成大小根据内容动态调整 + int qrSize = 500; // 固定大小,确保清晰度 + + QRCodeWriter writer = new QRCodeWriter(); + BitMatrix matrix = writer.encode(content, BarcodeFormat.QR_CODE, qrSize, qrSize, hints); + + // === 提高图像质量 === + int scale = 4; // 缩放因子,提高分辨率 + BufferedImage qrImage = new BufferedImage( + qrSize * scale, + qrSize * scale, + BufferedImage.TYPE_INT_RGB + ); + + Graphics2D g = qrImage.createGraphics(); + g.setColor(Color.WHITE); + g.fillRect(0, 0, qrSize * scale, qrSize * scale); + g.setColor(Color.BLACK); + + // 绘制高分辨率二维码 + for (int x = 0; x < qrSize; x++) { + for (int y = 0; y < qrSize; y++) { + if (matrix.get(x, y)) { + g.fillRect(x * scale, y * scale, scale, scale); + } + } + } + g.dispose(); + + // === 创建1080x640画布 === + BufferedImage finalImage = new BufferedImage(1080, 640, BufferedImage.TYPE_INT_RGB); + Graphics2D finalG = finalImage.createGraphics(); + finalG.setColor(Color.WHITE); + finalG.fillRect(0, 0, 1080, 640); + + // 居中放置 + int x = (1080 - qrImage.getWidth()) / 2; + int y = (640 - qrImage.getHeight()) / 2; + finalG.drawImage(qrImage, x, y, null); + finalG.dispose(); + + // 保存 + ImageIO.write(finalImage, "PNG", new File(filePath)); + } + + /** + * 简单的调试工具:检查二维码内容 + */ + public static void testQRCode(String filePath) { + try { + System.out.println("=== 二维码生成配置 ==="); + System.out.println("1. 确保二维码核心部分是正方形"); + System.out.println("2. 有足够的边距(margin >= 2)"); + System.out.println("3. 使用高容错等级(ErrorCorrectionLevel.H)"); + System.out.println("4. 像素点清晰,无模糊"); + System.out.println("5. 背景纯白,前景纯黑(对比度高)"); + + // 加载并检查图像 + BufferedImage img = ImageIO.read(new File(filePath)); + System.out.println("\n图像信息:"); + System.out.println("尺寸: " + img.getWidth() + "x" + img.getHeight()); + System.out.println("类型: " + img.getType()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + +// public static void main(String[] args) { +// String simpleContent = "https://tingche.csckl.com"; // 先用简单内容测试 +// +// // 方法1:直接生成正方形(先确保能扫描) +// Map hints = new HashMap<>(); +// hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); +// hints.put(EncodeHintType.MARGIN, 4); +// +// QRCodeWriter writer = new QRCodeWriter(); +// BitMatrix matrix = null; +// try { +// matrix = writer.encode(simpleContent, BarcodeFormat.QR_CODE, 600, 1024, hints); +// // 直接保存正方形二维码 +// File squareFile = new File("C:\\Users\\28758\\Desktop\\test_square.jpg"); +// MatrixToImageWriter.writeToPath(matrix, "JPG", squareFile.toPath()); +// +// System.out.println("请先扫描 test_square.png"); +// System.out.println("如果能扫描,再尝试矩形版本"); +// } catch (WriterException e) { +// throw new RuntimeException(e); +// } catch (IOException e) { +// throw new RuntimeException(e); +// } +// +// +// } + + +// public static void main(String[] args) { +// int width = 600; +// int height = 1024; +// String outputPath = "hikvision_control.jpg"; +// +// try { +// createHikvisionImage(width, height, outputPath); +// System.out.println("图片生成成功: " + outputPath); +// } catch (IOException e) { +// System.err.println("图片生成失败: " + e.getMessage()); +// } +// } + + public static void createHikvisionImage(int width, int height, String outputPath) throws IOException { + // 创建BufferedImage对象 + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + // 获取Graphics2D对象 + Graphics2D g2d = image.createGraphics(); + + // 设置渲染质量 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 设置蓝色背景(海康常用蓝色) + Color hikBlue = new Color(0, 123, 184); // 海康威视标准蓝色 + g2d.setColor(hikBlue); + g2d.fillRect(0, 0, width, height); + + // 可选:添加文字或logo + addHikvisionText(g2d, width, height); + + // 释放资源 + g2d.dispose(); + + // 保存图片 + ImageIO.write(image, "jpg", new File(outputPath)); + } + + private static void addHikvisionText(Graphics2D g2d, int width, int height) { + // 设置文字颜色为白色 +// g2d.setColor(Color.WHITE); + + // 设置字体 + Font font = new Font("Microsoft YaHei", Font.BOLD, 48); + g2d.setFont(font); + + // 计算文字位置使其居中 + String text = "出入口控制"; + FontMetrics fm = g2d.getFontMetrics(); + int textWidth = fm.stringWidth(text); + int textHeight = fm.getHeight(); + + int x = (width - textWidth) / 2; + int y = (height - textHeight) / 2 + fm.getAscent(); + + // 绘制文字 +// g2d.drawString(text, x, y); + + // 添加副标题 + Font subFont = new Font("Microsoft YaHei", Font.PLAIN, 24); + g2d.setFont(subFont); + + String subText = "海康威视 HIKVISION"; + FontMetrics subFm = g2d.getFontMetrics(); +// int subTextWidth = subFm.stringWidth(subText); + +// int subX = (width - subTextWidth) / 2; + int subY = y + 80; + +// g2d.drawString(subText, subX, subY); + } + + + + + + public static void main(String[] args) { + try { + createSimpleParkingQR( + "请扫码缴费", + "https://tingche.csckl.com/wisdomPark/doc.html", + "simple_qr.jpg" + ); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void createSimpleParkingQR(String text, String qrUrl, String outputPath) throws Exception { + int width = 600; + int height = 1024; + + // 创建主图像 + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g = image.createGraphics(); + + // 设置背景色 + g.setColor(new Color(0, 123, 184)); + g.fillRect(0, 0, width, height); + + // 绘制文字 + g.setColor(Color.WHITE); + g.setFont(new Font("宋体", Font.BOLD, 50)); + FontMetrics fm = g.getFontMetrics(); + int textWidth = fm.stringWidth(text); + g.drawString(text, (width - textWidth) / 2, 200); + + // 生成二维码 + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix bitMatrix = qrCodeWriter.encode(qrUrl, BarcodeFormat.QR_CODE, 400, 400); + + BufferedImage qrImage = MatrixToImageWriter.toBufferedImage(bitMatrix); + + // 绘制二维码 + g.drawImage(qrImage, (width - 400) / 2, 300, null); + + // 保存 + ImageIO.write(image, "JPG", new File(outputPath)); + g.dispose(); + } + + } + diff --git a/gather-app/src/main/java/com/ruoyi/database/util/SimpleJsonConverter.java b/gather-app/src/main/java/com/ruoyi/database/util/SimpleJsonConverter.java new file mode 100644 index 0000000..8716536 --- /dev/null +++ b/gather-app/src/main/java/com/ruoyi/database/util/SimpleJsonConverter.java @@ -0,0 +1,62 @@ +package com.ruoyi.database.util; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.database.domain.PassVehicleInfo; +import com.ruoyi.database.domain.ReleaseRequestInfo; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * 简单JSON转换工具 + */ +@Component +public class SimpleJsonConverter { + + /** + * 将JSON转换为PassVehicleInfo + */ + public PassVehicleInfo toPassVehicleInfo(JSONObject json) { + if (json == null) return null; + + PassVehicleInfo info = new PassVehicleInfo(); + + // 直接赋值 + info.setDirect(json.getInteger("direct")); + info.setPlateNo(json.getString("plateNo")); + info.setParkCode(json.getString("parkCode")); + info.setGateCode(json.getString("gateCode")); + info.setLaneNo(json.getString("laneNo")); + info.setInUnid(json.getString("inUnid")); + info.setOutUnid(json.getString("outUnid")); + info.setInTime(json.getString("inTime")); + info.setOutTime(json.getString("outTime")); + + return info; + } + + /** + * 将JSON转换为ReleaseRequestInfo + */ + public ReleaseRequestInfo toReleaseRequestInfo(JSONObject json) { + if (json == null) return null; + + ReleaseRequestInfo info = new ReleaseRequestInfo(); + + // 直接赋值 + info.setDirect(json.getInteger("direct")); + info.setPlateNo(json.getString("plateNo")); + info.setPassTime(json.getString("passTime")); + info.setParkCode(json.getString("parkCode")); + info.setGateCode(json.getString("gateCode")); + info.setLaneNo(json.getString("laneNo")); + info.setInUnid(json.getString("inUnid")); + info.setOutUnid(json.getString("outUnid")); + info.setParkType(json.getInteger("parkType")); + info.setPlateType(json.getInteger("plateType")); + info.setPlateColor(json.getInteger("plateColor")); + info.setCarType(json.getInteger("carType")); + + return info; + } +}