基于MybatisPlus,thymeleaf以及MySQL的智能停车场练习

目录

前言

具体依赖

application.yml

项目结构

​编辑

1.确定实体属性并创建实体类

收费标准类PricingStandard

停车场类ParkingLot

车辆类Vehicle

订单类Order

2.创建各实体的Mapper

创建PricingStandard的Mapper

创建ParkingLot的Mapper

创建Vehicle的Mapper

创建Order的Mapper

3.创建Controller和Services

创建PricingStandard的Controller和Services

PricingStandardController

PricingStandardServices

创建ParkingLot的Controller和Services

ParkingLotController

ParkingLotServices

创建Vehicle的Controller和Services

VehicleController

VehicleService

创建Order的Controller和Services

OrderController

OrderService

各页面的html

header

order页面

4.停车业务逻辑实现

ParkingSysController

GetText类

Ocr类

home页面

entrance页面

entranceResult页面

export页面

pay页面

error页面

测试用的临时数据

schema.sql

data.sql

效果演示


前言

在课堂上,我们学习了许多关于SpringBoot的知识,这些理论知识对于我们理解框架的工作原理和使用方法非常重要,但是真正的掌握还需要通过实际项目的练习来加深理解。因此,我决定开始一个新的练手项目,基于MybatisPlus、Thymeleaf、Bootstrap以及MySQL的智能停车场练习。我想通过这个项目,模拟一个停车场场景,从而更好地了解SpringBoot在企业级应用开发中的应用。希望通过这个练习项目提高编码能力和解决问题的能力。(因为是练习所以本项目是前后端不分离)

具体依赖

         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--引入mysql依赖-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <!--引入mybatisPlus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
        <!--引入thymeleaf依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--引入Bootstrap依赖-->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>5.3.0</version>
        </dependency>
        <!--引入jquery依赖-->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.6.4</version>
        </dependency>
        <!--引入webjars依赖-->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version> <!-- 请替换为最新版本号 -->
        </dependency>

application.yml

server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/parkingmis?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: root
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
      auto-commit: true
      pool-name: dataHikariCP
      max-lifetime: 6000000
      connection-timeout: 300000

  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html

  sql:
    init:
      schema-locations: classpath:db/schema.sql
      mode: always
      continue-on-error: true
      data-locations: classpath:db/data.sql

  servlet:
    multipart:
      max-file-size: 200MB
      max-request-size: 20MB
  web:
    resources:
      static-locations: classpath:/static/,classpath:/templates/,file:${static.path}
static:
  path: D:\Spring boot\Springboot\ParkingMIS\src\main\resources\static\
save:
   path: D:\Spring boot\Springboot\ParkingMIS\src\main\resources\static\images

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    use-generated-keys: true

logging:
  level:
    com.example: debug

项目结构

user是多余的,就当它不存在就好了

1.确定实体属性并创建实体类

内容如下:

收费标准类PricingStandard
@Data
@TableName("pricing_standard")
public class PricingStandard {
    @TableId(type= IdType.AUTO)
    private Long id;
    @TableField("hourly_rate")
    private Integer hourlyRate;//小时计费
    @TableField("daily_rate")
    private Integer dailyRate;//按天计费
    @TableField("exceeding_hours")
    private Integer exceedingHours;//超时数,超过多少小时按天计费
    @TableField("update_time")
    private Date updateTime;//更新日期

}
停车场类ParkingLot
@Data
@TableName("parking_lot")
public class ParkingLot {
    @TableId(type= IdType.AUTO)
    private Long id;
    private String name;
    private int volumetric;//容积,车位数量
    private PricingStandard pricingStandard;
}
车辆类Vehicle
@Data
@TableName("vehicle")
public class Vehicle {
    @TableId(type= IdType.AUTO)
    private Long id;
    @TableField("licence_plate")
    private String licencePlate;//车牌号
    @TableField("pic_url")
    private String picUrl;//车辆入场照片存储位置
    @TableField("is_active")
    private boolean isActive;//入场、离场标记,true:入场,否则是离场
    private ParkingLot parkingLot;//关联的停车场
    public Vehicle(){
        this.isActive=true;
    }
    public Vehicle(Vehicle entity){
        this.id= entity.id;
        this.licencePlate=entity.licencePlate;
        this.isActive= entity.isActive;
        this.picUrl= entity.picUrl;
        Optional.ofNullable(entity.parkingLot)
                .ifPresent(x->{
                    this.parkingLot=new ParkingLot();
                    this.parkingLot.setId(x.getId());
                    this.parkingLot.setName(x.getName());
                    this.parkingLot.setVolumetric(x.getVolumetric());
                });

    }
}
订单类Order
@Data
@TableName("orders")
public class Order {
    private Long id;
    @TableField("in_datetime")
    private Date inDateTime;//入场时间
    @TableField("out_datetime")
    private Date outDateTime;//离场时间
    private BigDecimal price;//缴费金额
    @TableField("is_pay_completed")
    private boolean isPayCompleted;//缴费完成状态
    @TableField("is_require_invoicing")
    private boolean isRequireInvoicing;//是否开具发票

    private PricingStandard pricingStandard;//关联收费标准
    private ParkingLot parkingLot;//关联停车场
    private Vehicle vehicle;//关联车辆
}

2.创建各实体的Mapper

创建PricingStandard的Mapper

PricingStandard里面不包含其他的实体所以不需要进行自定义sql语句等操作,直接@Mapper就行了

@Mapper
public interface PricingStandardMapper extends BaseMapper<PricingStandard> {

}
创建ParkingLot的Mapper

因为ParkingLot里面有PricingStandard,所以需要进行一些自定义操作,直接调用PricingStandardMapper的selectById根据pricing_standard_id查取结果作为ParkingLot里面的PricingStandard。插入操作不能直接插入,插入pricing_standard_id就需要根据ParkingLot里面的PricingStandard的id进行插入

@Mapper
public interface ParkingLotMapper extends BaseMapper<ParkingLot> {
    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "pricing_standard_id",property = "pricingStandard",one = @One(select = "com.example.mapper.PricingStandardMapper.selectById")),
    })
    @Select("select * from parking_lot join pricing_standard on pricing_standard.id=parking_lot.pricing_standard_id")
    List<ParkingLot> getAll();
    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "pricing_standard_id",property = "pricingStandard",one = @One(select = "com.example.mapper.PricingStandardMapper.selectById")),

    })
    @Select("select * from parking_lot join pricing_standard on pricing_standard.id=parking_lot.pricing_standard_id where parking_lot.id=#{id}")
    ParkingLot getById(Long id);
    @Insert("insert into parking_lot set name=#{name},volumetric=#{volumetric},pricing_standard_id=#{pricingStandard.id}")
    int insert(ParkingLot entity);
    @Update("update parking_lot set name=#{name},volumetric=#{volumetric},pricing_standard_id=#{pricingStandard.id} where id=#{id}")
    int update (ParkingLot entity);
}
创建Vehicle的Mapper

因为Vehicle类里面有其他实体,所以需要进行一些自定义操作。因为操作跟上面都差不多,这里就不多说了

@Mapper
public interface VehicleMapper extends BaseMapper<Vehicle> {
    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "parking_lot_id", property = "parkingLot", one = @One(select = "com.example.mapper.ParkingLotMapper.getById")),
    })
    @Select("select * from vehicle join parking_lot on parking_lot.id=parking_lot_id")
    List<Vehicle> getAll();

    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "parking_lot_id", property = "parkingLot", one = @One(select = "com.example.mapper.ParkingLotMapper.getById")),
    })
    @Select("select * from vehicle join parking_lot on parking_lot.id=parking_lot_id")
    List<Vehicle> getByParkingLot();

    @Results({
            @Result(column = "id", property = "id"),
            @Result(column = "parking_lot_id", property = "parkingLot", one = @One(select = "com.example.mapper.ParkingLotMapper.getById")),
    })
    @Select("select * from vehicle join parking_lot on parking_lot_id=parking_lot.id where vehicle.id=#{id}")
    Vehicle getById(Long id);
    @Insert("insert into vehicle set licence_plate=#{licencePlate},pic_url=#{picUrl},is_active=#{isActive},parking_lot_id=#{parkingLot.id}")
    int insert(Vehicle entity);
    @Update("update vehicle set licence_plate=#{licencePlate},pic_url=#{picUrl},is_active=#{isActive},parking_lot_id=#{parkingLot.id} where id=#{id}")
    int update(Vehicle entity);
}
创建Order的Mapper

因为Order类里面有其他实体,所以需要进行一些自定义操作。因为操作跟上面都差不多,这里就不多说了

@Mapper
public interface OrderMapper extends BaseMapper<Order> {
    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "pricing_standard_id",property = "pricingStandard",one = @One(select = "com.example.mapper.PricingStandardMapper.selectById")),
            @Result(column = "parking_lot_id", property = "parkingLot", one = @One(select = "com.example.mapper.ParkingLotMapper.getById")),
            @Result(column = "vehicle_id",property = "vehicle",one = @One(select = "com.example.mapper.VehicleMapper.getById"))

    })
    @Select("select * from orders join pricing_standard on pricing_standard.id=parking_lot_id "+
            "join parking_lot on orders.parking_lot_id=parking_lot.id "+
            "join vehicle on orders.vehicle_id=vehicle.id")
    List<Order> getAll();

    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "pricing_standard_id",property = "pricingStandard",one = @One(select = "com.example.mapper.PricingStandardMapper.selectById")),
            @Result(column = "parking_lot_id", property = "parkingLot", one = @One(select = "com.example.mapper.ParkingLotMapper.getById")),
            @Result(column = "vehicle_id",property = "vehicle",one = @One(select = "com.example.mapper.VehicleMapper.getById"))

    })
    @Select("select * from orders join pricing_standard on pricing_standard.id=parking_lot_id "+
            "join parking_lot on orders.parking_lot_id=parking_lot.id "+
            "join vehicle on orders.vehicle_id=vehicle.id where orders.id=#{id}")
    Order getById(Long id);

    @Results({
            @Result(column = "id",property = "id"),
            @Result(column = "pricing_standard_id",property = "pricingStandard",one = @One(select = "com.example.mapper.PricingStandardMapper.selectById")),
            @Result(column = "parking_lot_id", property = "parkingLot", one = @One(select = "com.example.mapper.ParkingLotMapper.getById")),
            @Result(column = "vehicle_id",property = "vehicle",one = @One(select = "com.example.mapper.VehicleMapper.getById"))

    })
    @Select("select * from orders join pricing_standard on pricing_standard.id=parking_lot_id "+
            "join parking_lot on orders.parking_lot_id=parking_lot.id "+
            "join vehicle on orders.vehicle_id=vehicle.id where vehicle.licence_plate=#{licencePlate} and orders.is_pay_completed=false")
    Order getByLicencePlate(String licencePlate);

    @Insert("insert into orders set in_datetime=#{inDateTime},out_datetime=#{outDateTime},price=#{price},"+
            "is_pay_completed=#{isPayCompleted},is_require_invoicing=#{isRequireInvoicing},parking_lot_id=#{parkingLot.id},"+
            "pricing_standard_id=#{pricingStandard.id},vehicle_id=#{vehicle.id}")
    int insert(Order entity);

    @Update("update orders set in_datetime=#{inDateTime},out_datetime=#{outDateTime},price=#{price},"+
            "is_pay_completed=#{isPayCompleted},is_require_invoicing=#{isRequireInvoicing},parking_lot_id=#{parkingLot.id},"+
            "pricing_standard_id=#{pricingStandard.id},vehicle_id=#{vehicle.id} where id=#{id}")
    int update(Order entity);

}

3.创建Controller和Services

创建PricingStandard的Controller和Services
PricingStandardController
@Controller
@RequestMapping("/pricing")
public class PricingStandardController {
    PricingStandardService service;
    @Autowired
    public PricingStandardController(PricingStandardService service){
        this.service=service;
    }
    @RequestMapping("/list")
    public String list(Model model){
        model.addAttribute("list",service.getAll());//model打包成list属性完成list对象的值传递
        return "/pricing/list";
    }
    @RequestMapping(value = "/insert")
    public String insert(Model model){
        model.addAttribute("pricing",new PricingStandard());
        return "pricing/edit";
    }
    @RequestMapping(value = "/edit/{id}")
    public String edit(@PathVariable("id")Long id, Model model){
        model.addAttribute("pricing",service.getById(id));
        return "pricing/edit";
    }
    @RequestMapping(value = "/save",method = RequestMethod.POST)
    public String save(@ModelAttribute("pricing") PricingStandard entity){
        entity.setUpdateTime(new Date());
        int result=entity.getId()!=null?service.update(entity):service.insert(entity);
        return result>0?"redirect:list":"redirect:edit";
    }
    @RequestMapping(value = "/detail/{id}")
    public String detail(@PathVariable("id")Long id,Model model){
        model.addAttribute("pricing",service.getById(id));
        return "/pricing/detail";
    }
    @RequestMapping(value = "/delete/{id}")
    public String delete(@PathVariable("id")Long id){
        service.delete(id);
        return "redirect:/pricing/list";
    }
}
PricingStandardServices

Services没什么好说的,Controller调Services,Services就直接去调Mapper就行了

@Service
public class PricingStandardService extends ServiceImpl<PricingStandardMapper,PricingStandard> {
    PricingStandardMapper mapper;
    @Autowired
    public PricingStandardService(PricingStandardMapper mapper){
        this.mapper=mapper;
    }

    public List<PricingStandard> getAll() {
        return mapper.selectList(null);
    }

    public PricingStandard getById(Long id) {
        return mapper.selectById(id);
    }

    public int insert(PricingStandard entity) {
        return mapper.insert(entity);
    }

    public int update(PricingStandard entity) {
        return mapper.updateById(entity);
    }

    public boolean delete(Long id) {
        return mapper.deleteById(id)>0?true:false;
    }

}
创建ParkingLot的Controller和Services
ParkingLotController
@Controller
@RequestMapping("/parking")
public class ParkingLotController {
    ParkingLotService service;
    PricingStandardService pricingStandardService;
    @Autowired
    public ParkingLotController(ParkingLotService service,PricingStandardService pricingStandardService){
        this.service=service;
        this.pricingStandardService=pricingStandardService;
    }
    @RequestMapping("/list")
    public String list(Model model){
        model.addAttribute("list",service.getAll());//model打包成list属性完成list对象的值传递
        return "/parking/list";
    }

    @RequestMapping(value = "/insert")
    public String insert(Model model){
        model.addAttribute("parking",new ParkingLot());
        model.addAttribute("pricingList",pricingStandardService.getAll());
        return "parking/edit";
    }

    @RequestMapping(value = "/save",method = RequestMethod.POST)
    public String save(@ModelAttribute("parking") ParkingLot entity, @RequestParam("pricingId") Long pricingId){
        entity.setPricingStandard(pricingStandardService.getById(pricingId));
        int result=entity.getId()!=null?service.update(entity):service.insert(entity);
        return result>0?"redirect:list":"redirect:edit";
    }
    @RequestMapping(value = "/edit/{id}")
    public String edit(@PathVariable("id")Long id, Model model){
        model.addAttribute("parking",service.getById(id));
        model.addAttribute("pricingList",pricingStandardService.getAll());
        return "parking/edit";
    }
    @RequestMapping(value = "/detail/{id}")
    public String detail(@PathVariable("id")Long id, Model model){
        model.addAttribute("parking",service.getById(id));
        return "/parking/detail";
    }
    @RequestMapping(value = "/delete/{id}")
    public String delete(@PathVariable("id")Long id){
        service.delete(id);
        return "redirect:/parking/list";
    }
}
ParkingLotServices

跟之前一样,Controller调Services,Services去调Mapper就行了

@Service
public class ParkingLotService extends ServiceImpl<ParkingLotMapper, ParkingLot> {
    ParkingLotMapper mapper;
    @Autowired
    public ParkingLotService(ParkingLotMapper mapper){
        this.mapper=mapper;
    }
    public List<ParkingLot> getAll(){
        return mapper.getAll();
    }
    public ParkingLot getById(Long id) {
        return mapper.getById(id);
    }
    public int update(ParkingLot entity) {
        return mapper.update(entity);
    }

    public int insert(ParkingLot entity) {
        return mapper.insert(entity);
    }

    public boolean delete(Long id) {
        return mapper.deleteById(id)>0?true:false;
    }
}
创建Vehicle的Controller和Services
VehicleController
@Controller
@RequestMapping("/vehicle")
public class VehicleController {
    @Value("${save.path}")
    String savePath;//图片存储路径
    private VehicleService service;
    private ParkingLotService parkingLotService;
    public VehicleController(VehicleService service,ParkingLotService parkingLotService){
        this.service=service;
        this.parkingLotService=parkingLotService;
    }
    @RequestMapping("/list")
    public String list(Model model){
        model.addAttribute("list",service.getAll());//model打包成list属性完成list对象的值传递
        return "/vehicle/list";
    }

    @RequestMapping(value = "/insert")
    public String insert(Model model){
        model.addAttribute("vehicle",new Vehicle());
        model.addAttribute("parkingLots",parkingLotService.getAll());
        return "vehicle/edit";
    }

    @RequestMapping(value = "/edit/{id}")
    public String edit(@PathVariable("id")Long id, Model model){
        model.addAttribute("vehicle",service.getById(id));
        model.addAttribute("parkingLots",parkingLotService.getAll());
        return "vehicle/edit";
    }

    @RequestMapping(value = "/save",method = RequestMethod.POST)
    public String save(@ModelAttribute("Vehicle") Vehicle entity,
                       @RequestParam("") boolean isActive ,
                       @RequestParam("parkingLotId") Long parkingLotId,
                       @RequestParam("file")MultipartFile file) throws Exception{
        entity.setActive(isActive);
        entity.setParkingLot(parkingLotService.getById(parkingLotId));

        //上传图片到指定目录
        String fileName=System.currentTimeMillis()+"-"+file.getOriginalFilename();//生成文件名
        Path saveTo= Paths.get(savePath,fileName);//设置文件存储路径
        Files.write(saveTo,file.getBytes());//存储文件

        //存图片Url
        entity.setPicUrl("/images/"+fileName);

        int result=entity.getId()!=null?service.update(entity):service.insert(entity);
        return result>0?"redirect:list":"redirect:edit";
    }
    @RequestMapping(value = "/detail/{id}")
    public String detail(@PathVariable("id")Long id, Model model){
        model.addAttribute("vehicle",service.getById(id));
        return "/vehicle/detail";
    }
    @RequestMapping(value = "/delete/{id}")
    public String delete(@PathVariable("id")Long id){
        service.delete(id);
        return "redirect:/vehicle/list";
    }
}
VehicleService

跟之前一样,Controller调Services,Services去调Mapper就行了

@Service
public class VehicleService {
    VehicleMapper mapper;
    @Autowired
    public VehicleService(VehicleMapper mapper){
        this.mapper=mapper;
    }
    public List<Vehicle> getAll(){
        return mapper.getAll();
    }
    public Vehicle getById(Long id) {
        return mapper.getById(id);
    }

    public int update(Vehicle entity) {
        return mapper.update(entity);
    }

    public int insert(Vehicle entity) {
        return mapper.insert(entity);
    }

    public boolean delete(Long id) {
        return mapper.deleteById(id)>0?true:false;
    }
}
创建Order的Controller和Services
OrderController
@Controller
@RequestMapping("/order")
public class OrderController {
    private final OrderService service;
    private final PricingStandardService pricingStandardService;
    private final ParkingLotService parkingLotService;
    private final VehicleService vehicleService;
    @Autowired
    public OrderController(OrderService service,PricingStandardService pricingStandardService,ParkingLotService parkingLotService,VehicleService vehicleService){
        this.service=service;
        this.pricingStandardService=pricingStandardService;
        this.parkingLotService=parkingLotService;
        this.vehicleService=vehicleService;
    }
    @RequestMapping("/list")
    public String list(Model model){
        model.addAttribute("list",service.getAll());//model打包成list属性完成list对象的值传递
        return "/order/list";
    }

    @RequestMapping(value = "/insert")
    public String insert(Model model){
        model.addAttribute("order",new Order());
        model.addAttribute("pricingList",pricingStandardService.getAll());
        model.addAttribute("parkingLots",parkingLotService.getAll());
        model.addAttribute("vehicles",vehicleService.getAll());
        return "order/edit";
    }

    @RequestMapping(value = "/edit/{id}")
    public String edit(@PathVariable("id")Long id, Model model){
        model.addAttribute("order",service.getById(id));
        model.addAttribute("pricingList",pricingStandardService.getAll());
        model.addAttribute("parkingLots",parkingLotService.getAll());
        model.addAttribute("vehicles",vehicleService.getAll());
        return "order/edit";
    }

    @RequestMapping(value = "/save",method = RequestMethod.POST)
    public String save(@ModelAttribute("Order") Order entity,
                       @RequestParam("isPayCompleted") boolean isPayCompleted ,
                       @RequestParam("isRequireInvoicing") boolean isRequireInvoicing,
                       @RequestParam("parkingLotId") Long parkingLotId,
                       @RequestParam("pricingStandardId") Long pricingStandardId,
                       @RequestParam("vehicleId") Long vehicleId) throws Exception{
        entity.setPayCompleted(isPayCompleted);
        entity.setRequireInvoicing(isRequireInvoicing);
        entity.setParkingLot(parkingLotService.getById(parkingLotId));
        entity.setPricingStandard(pricingStandardService.getById(pricingStandardId));
        entity.setVehicle(vehicleService.getById(vehicleId));
        if(entity.getId()==null){
            entity.setInDateTime(new Date());
            service.insert(entity);
            return "redirect:list";
        }
        Optional.ofNullable(entity.getId()).orElse(null);
        //order更新
        Optional.ofNullable(entity.getId()).ifPresent(order->{
            Order od=service.getById(order);
            od.setOutDateTime(new Date());
            LocalDateTime in=LocalDateTime.ofInstant(od.getInDateTime().toInstant(), ZoneId.systemDefault());
            LocalDateTime out=LocalDateTime.ofInstant(od.getOutDateTime().toInstant(), ZoneId.systemDefault());
            Long durationHour= Duration.between(in,out).toHours();//停车时间(小时)
            od.setPayCompleted(isPayCompleted);
            od.setRequireInvoicing(isRequireInvoicing);
            od.setParkingLot(parkingLotService.getById(parkingLotId));
            od.setPricingStandard(pricingStandardService.getById(pricingStandardId));
            od.setVehicle(vehicleService.getById(vehicleId));
            if(durationHour<24&&durationHour<od.getPricingStandard().getExceedingHours()){
                od.setPrice(BigDecimal.valueOf(od.getPricingStandard().getHourlyRate()*durationHour));
            }else{
                Long durationDay=durationHour/24;
                durationHour=durationHour%24;
                od.setPrice(BigDecimal.valueOf(od.getPricingStandard().getDailyRate()*durationDay+od.getPricingStandard().getHourlyRate()*durationHour));
            }
            service.update(od);
        });
        return "redirect:list";
    }

    @RequestMapping(value = "/detail/{id}")
    public String detail(@PathVariable("id")Long id, Model model){
        model.addAttribute("order",service.getById(id));
        return "/order/detail";
    }

    @RequestMapping(value = "/delete/{id}")
    public String delete(@PathVariable("id")Long id){
        service.delete(id);
        return "redirect:/order/list";
    }
}
OrderService

跟之前一样,Controller调Services,Services去调Mapper就行了

@Service
public class OrderService extends ServiceImpl<OrderMapper,Order> {
    OrderMapper mapper;
    @Autowired
    public OrderService(OrderMapper mapper){
        this.mapper=mapper;
    }

    public List<Order> getAll(){
        return mapper.getAll();
    }

    public Order getById(Long id) {
        return mapper.getById(id);
    }

    public int update(Order entity) {
        return mapper.update(entity);
    }

    public int insert(Order entity) {
        return mapper.insert(entity);
    }

    public boolean delete(Long id) {
        return mapper.deleteById(id) > 0;
    }

    public Order getByLicencePlate(String licencePlate) {
        return mapper.getByLicencePlate(licencePlate);
    }
}

各页面的html

这就没什么好说的了,直接看代码吧

<div th:fragment="header">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
    <title>List</title>
    <div th:fragment="header-css">
        <link rel="stylesheet" href="/webjars/bootstrap/5.3.0/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/5.3.0/css/bootstrap.min.css}">
    </div>
    <div th:fragment="header-js">
        <script src="/webjars/jquery/3.6.4/jquery.min.js" th:href="@{/webjars/jquery/3.6.4/jquery.min.js}"></script>
        <script src="/webjars/bootstrap/5.3.0/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/5.3.0/js/bootstrap.min.js}"></script>
        <script src="/webjars/bootstrap/5.3.0/js/bootstrap.bundle.js" th:src="@{/webjars/bootstrap/5.3.0/js/bootstrap.bundle.js}"></script>
    </div>
</div>

<div th:fragment="header-nav">
    <nav class="navbar navbar-expand-lg bg-body-tertiary">
        <div class="container-fluid">
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav">
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">系统模块</a>
                        <ul class="dropdown-menu">
                            <li><a class="dropdown-item" href="/parkingSys/home">home</a></li>
                            <li><a class="dropdown-item" href="/pricing/list">pricing</a></li>
                            <li><a class="dropdown-item" href="/parking/list">parking</a></li>
                            <li><a class="dropdown-item" href="/vehicle/list">vehicle</a></li>
                            <li><a class="dropdown-item" href="/order/list">order</a></li>
                        </ul>
                    </li>
                </ul>
            </div>
        </div>
    </nav>
</div>
<div th:fragment="footer">
    <div class="nav justify-content-center">
        ©2023 xxx版权所有
    </div>
</div>
order页面

由于内容都差不多,这里就只展示order的页面代码了(body外面本不该出现div,但是为了方便我自己看就用了,好像也没什么影响,反正也不需要显示出来)

list

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <div th:replace="~{/fragment/header :: header}"></div>
    <div th:replace="~{/fragment/header :: header-css}"></div>
    <div th:replace="~{/fragment/header :: header-nav}"></div>
</head>
<body>
<div class="container">
    <a th:href="@{/order/insert}" class="btn btn-success">新增</a>
    <table class="table table-striped">
        <thead>
            <tr>
                <th>id</th>
                <th>入场时间</th>
                <th>离场时间</th>
                <th>缴费金额</th>
                <th>缴费完成状态</th>
                <th>是否开具发票</th>
            </tr>
        </thead>
        <tbody>
        <tr th:each="order : ${list}">
            <td th:text="${order.id}"></td>
            <td th:text="*{#dates.format(order.inDateTime,'yyy-MM-dd HH:mm:ss')}"></td>
            <td th:text="*{#dates.format(order.outDateTime,'yyy-MM-dd HH:mm:ss')}"></td>
            <td th:text="${order.price}"></td>
            <td th:text="${order.isPayCompleted()}"></td>
            <td th:text="${order.isRequireInvoicing()}"></td>
            <td>
                <a th:href="@{/order/edit/{id}(id=${order.id})}" class="btn btn-info">修改</a>
                <a th:href="@{/order/detail/{id}(id=${order.id})}" class="btn btn-info">详情</a>
                <a th:href="@{/order/delete/{id}(id=${order.id})}" class="btn btn-danger">删除</a>
            </td>
        </tr>
        </tbody>
    </table>
</div>
</body>
<div th:replace="~{/fragment/footer :: footer}"></div>
<div th:replace="~{/fragment/header :: header-js}"></div>
</html>

edit

<!DOCTYPE html>
<html>
<head>
    <div th:replace="~{/fragment/header :: header}"></div>
    <div th:replace="~{/fragment/header :: header-css}"></div>
    <div th:replace="~{/fragment/header :: header-nav}"></div>
</head>
<body>
<form action="/order/save" th:action="@{/order/save}" th:object="${order}" method="post">
    <input th:field="*{id}" type="hidden"/>
    <div class="mb-3">
        <label class="form-label">缴费状态:</label>
        <select name="isPayCompleted" id="isPayCompleted">
            <option value="true">true</option>
            <option value="false">false</option>
        </select>
    </div>
    <div class="mb-3">
        <label class="form-label">发票状态:</label>
        <select name="isRequireInvoicing" id="isRequireInvoicing">
            <option value="true">true</option>
            <option value="false">false</option>
        </select>
    </div>
    <div class="dropdown">
        <span class="input-group-text">收费标准</span>
        <select name="pricingStandardId" id="pricingStandardId">
            <option th:each="pricing:${pricingList}" th:value="${pricing.id}" th:text="${#dates.format(pricing.updateTime, 'yyyy-MM-dd HH:mm:ss')}"></option>
        </select>
    </div>
    <div class="dropdown">
        <span class="input-group-text">停车场</span>
        <select name="parkingLotId" id="parkingLotId">
            <option th:each="parking:${parkingLots}" th:value="${parking.id}" th:text="${parking.name}"></option>
        </select>
    </div>
    <div class="dropdown">
        <span class="input-group-text">车辆</span>
        <select name="vehicleId" id="vehicleId">
            <option th:each="vehicle:${vehicles}" th:value="${vehicle.id}" th:text="${vehicle.licencePlate}"></option>
        </select>
    </div>
    <div class="mb-3 form-check">
        <input type="checkbox" class="form-check-input" id="exampleCheck1">
        <label class="form-check-label" for="exampleCheck1">检查</label>
    </div>
    <button type="submit" class="btn btn-primary">提交</button>
    <a th:href="@{/order/list}" class="btn btn-secondary">返回</a>
</form>

</body>
<div th:replace="~{/fragment/footer :: footer}"></div>
<div th:replace="~{/fragment/header :: header-js}"></div>
</html>

detail

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <div th:replace="~{/fragment/header :: header}"></div>
  <div th:replace="~{/fragment/header :: header-css}"></div>
  <div th:replace="~{/fragment/header :: header-nav}"></div>
</head>
<body>
<h3>id:<span th:text="${order.id}"></span></h3>
<h3>入场时间:<span th:text="*{#dates.format(order.inDateTime,'yyy-MM-dd HH:mm:ss')}"></span></h3>
<h3>离场时间:<span th:text="*{#dates.format(order.outDateTime,'yyy-MM-dd HH:mm:ss')}"></span></h3>
<h3>缴费金额:<span th:text="${order.price}"></span></h3>
<h3>缴费完成状态:<span th:text="${order.isPayCompleted}"></span></h3>
<h3>是否开票:<span th:text="${order.isRequireInvoicing}"></span></h3>
<a th:href="@{/order/list}" class="btn btn-secondary">返回</a>
</body>
<div th:replace="~{/fragment/footer :: footer}"></div>
<div th:replace="~{/fragment/header :: header-js}"></div>
</html>

4.停车业务逻辑实现

ParkingSysController

图片识别这里我调用的是PaddleOCR-jsonGitHub - jerrylususu/PaddleOCR-json: OCR离线图片文字识别命令行程序,以JSON字符串形式输出结果,方便别的程序调用。由 PaddleOCR C++ 编译。icon-default.png?t=N7T8https://github.com/jerrylususu/PaddleOCR-json

@Controller
@RequestMapping("/parkingSys")
public class ParkingSysController {
    @Value("${save.path}")
    String savePath;//图片存储路径
    private VehicleService vehicleService;
    private ParkingLotService parkingLotService;
    private OrderService orderService;
public ParkingSysController(VehicleService vehicleService,ParkingLotService parkingLotService,OrderService orderService){
    this.vehicleService=vehicleService;
    this.parkingLotService=parkingLotService;
    this.orderService=orderService;
}
    @RequestMapping(value = "/home")
    public String home(){
    try{
        Runtime.getRuntime().exec("cmd.exe /k del /q \""+savePath+"\\temp\\*\"");//利用cmd删除临时文件
    }catch (Exception e){
        e.printStackTrace();
    }
        return "parkingSys/home";
    }

    @RequestMapping(value = "/entrance")
    public String insert(Model model){
        model.addAttribute("vehicle",new Vehicle());
        List<ParkingLot> parkingLots=parkingLotService.getAll();
        for(int i=0;i<parkingLots.size();i++){
            if(parkingLots.get(i).getVolumetric()>200);
        }
        model.addAttribute("parkingLots",parkingLots);
        return "parkingSys/entrance";
    }

    @RequestMapping(value = "/export")
    public String update(){
        return "parkingSys/export";
    }

    @RequestMapping(value = "/entranceSave",method = RequestMethod.POST)
    public String entrance(@ModelAttribute("Vehicle") Vehicle entity,
                       @RequestParam("parkingLotId") Long parkingLotId,
                       @RequestParam("file") MultipartFile file,Model model) throws Exception{
        entity.setActive(false);
        entity.setParkingLot(parkingLotService.getById(parkingLotId));

        //上传图片到指定目录
        String fileName=System.currentTimeMillis()+"-"+file.getOriginalFilename();//生成文件名
        Path saveTo= Paths.get(savePath,fileName);//设置文件存储路径
        Files.write(saveTo,file.getBytes());//存储文件
        entity.setLicencePlate(new GetText().getText(savePath+"\\"+fileName));//扫描车牌
        //存图片Url
        entity.setPicUrl("/images/"+fileName);

        vehicleService.insert(entity);
        entity.setId(vehicleService.getAll().get(vehicleService.getAll().size()-1).getId());
        Order order=new Order();
        order.setInDateTime(new Date());
        order.setVehicle(entity);
        order.setParkingLot(entity.getParkingLot());
        order.setPricingStandard(entity.getParkingLot().getPricingStandard());
        orderService.insert(order);
        model.addAttribute("order",orderService.getById(orderService.getAll().get(orderService.getAll().size()-1).getId()));
        return "/parkingSys/entranceResult";
    }

    @RequestMapping(value = "/exportPay",method = RequestMethod.POST)
    public String pay(@RequestParam("file") MultipartFile file,
                         @RequestParam("isRequireInvoicing") boolean isRequireInvoicing,Model model) throws Exception{
        //上传图片到指定目录
        String fileName=System.currentTimeMillis()+"-"+file.getOriginalFilename();//生成文件名
        Path saveTo= Paths.get(savePath+"\\temp",fileName);//设置文件存储路径
        Files.write(saveTo,file.getBytes());//存储文件
        String licencePlate=new GetText().getText(savePath+"\\temp\\"+fileName);//扫描车牌
        Order od=orderService.getByLicencePlate(licencePlate);
        if(od==null){
            return "/parkingSys/error";
        }
        od.setOutDateTime(new Date());
        LocalDateTime in=LocalDateTime.ofInstant(od.getInDateTime().toInstant(), ZoneId.systemDefault());
        LocalDateTime out=LocalDateTime.ofInstant(od.getOutDateTime().toInstant(), ZoneId.systemDefault());
        Long durationHour= Duration.between(in,out).toHours();//停车时间(小时)
        od.setPayCompleted(true);
        od.setRequireInvoicing(isRequireInvoicing);
        if(durationHour<24&&durationHour<od.getPricingStandard().getExceedingHours()){
            od.setPrice(BigDecimal.valueOf(od.getPricingStandard().getHourlyRate()*durationHour));
        }else{
            Long durationDay=durationHour/24;
            durationHour=durationHour%24;
            od.setPrice(BigDecimal.valueOf(od.getPricingStandard().getDailyRate()*durationDay+od.getPricingStandard().getHourlyRate()*durationHour));
        }
        orderService.update(od);
        model.addAttribute("order",orderService.getById(od.getId()));
        model.addAttribute("licencePlate",licencePlate);
        model.addAttribute("tempImgUrl","/images/temp/"+fileName);
        return "/parkingSys/pay";
    }
}
GetText类

我这里是随便改造了一下PaddleOCR-json的示例

public class GetText {
    public String getText(String imgPath) {
        // 可选的配置项
        Map<String, Object> arguments = new HashMap<>();
        // arguments.put("use_angle_cls", true);

        // 初始化 OCR:使用本地进程或者套接字服务器
        // 本地进程: new Ocr(new File(exePath), arguments)
        //String exePath = "D:\\Spring boot\\Springboot\\ParkingMIS\\src\\main\\resources\\static\\PaddleOCR-json\\PaddleOCR_json.exe"; // paddleocr_json 的可执行文件所在路径
        String exePath = System.getProperty("user.dir")+"\\ParkingMIS\\src\\main\\resources\\PaddleOCR-json\\PaddleOCR_json.exe"; // paddleocr_json 的可执行文件所在路径
        try (Ocr ocr = new Ocr(new File(exePath), arguments)) {
//        使用套接字服务器(仅作为客户端,不启动服务)
//        try (Ocr ocr = new Ocr(serverAddr, serverPort, arguments)) {

            // 对一张图片进行 OCR(使用路径)
            OcrResponse resp = ocr.runOcr(new File(imgPath));

            // 或者使用图片数据(二进制或 base64)
//            byte[] fileBytes = Files.readAllBytes(Paths.get(imgPath));
//            OcrResponse resp = ocr.runOcrOnImgBytes(fileBytes);

//            OcrResponse resp = ocr.runOcrOnImgBase64("base64img");

            // 或者直接识别剪贴板中的图片
//             OcrResponse resp = ocr.runOcrOnClipboard();

            // 读取结果
            if (resp.code == OcrCode.OK) {
                for (OcrEntry entry : resp.data) {
                    return entry.text;
                }
            } else {
                System.out.println("error: code=" + resp.code + " msg=" + resp.msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
return "";
    }
}
Ocr类

这个可以在PaddleOCR-json的github那里找到

package com.example;

import com.google.gson.Gson;

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.Writer;

// from: https://github.com/soot-oss/soot/blob/3966f565db6dc2882c3538ffc39e44f4c14b5bcf/src/main/java/soot/util/EscapedWriter.java
/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 1997 - 1999 Raja Vallee-Rai
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
 * #L%
 */
/**
 * A FilterWriter which catches to-be-escaped characters (<code>\\unnnn</code>) in the input and substitutes their escaped
 * representation. Used for Soot output.
 */
class EscapedWriter extends FilterWriter {
    /** Convenience field containing the system's line separator. */
    public final String lineSeparator = System.getProperty("line.separator");
    private final int cr = lineSeparator.charAt(0);
    private final int lf = (lineSeparator.length() == 2) ? lineSeparator.charAt(1) : -1;

    /** Constructs an EscapedWriter around the given Writer. */
    public EscapedWriter(Writer fos) {
        super(fos);
    }

    private final StringBuffer mini = new StringBuffer();

    /** Print a single character (unsupported). */
    public void print(int ch) throws IOException {
        write(ch);
        throw new RuntimeException();
    }

    /** Write a segment of the given String. */
    public void write(String s, int off, int len) throws IOException {
        for (int i = off; i < off + len; i++) {
            write(s.charAt(i));
        }
    }

    /** Write a single character. */
    public void write(int ch) throws IOException {
        if (ch >= 32 && ch <= 126 || ch == cr || ch == lf || ch == ' ') {
            super.write(ch);
            return;
        }

        mini.setLength(0);
        mini.append(Integer.toHexString(ch));

        while (mini.length() < 4) {
            mini.insert(0, "0");
        }

        mini.insert(0, "\\u");
        for (int i = 0; i < mini.length(); i++) {
            super.write(mini.charAt(i));
        }
    }
}

enum OcrMode {
    LOCAL_PROCESS,  // 本地进程
    SOCKET_SERVER  // 套接字服务器
}

class OcrCode {
    public static final int OK = 100;
    public static final int NO_TEXT = 101;
}

class OcrEntry {
    String text;
    int[][] box;
    double score;

    @Override
    public String toString() {
        return "RecognizedText{" +
                "text='" + text + '\'' +
                ", box=" + Arrays.toString(box) +
                ", score=" + score +
                '}';
    }
}

class OcrResponse {
    int code;
    OcrEntry[] data;
    String msg;
    String hotUpdate;

    @Override
    public String toString() {
        return "OcrResponse{" +
                "code=" + code +
                ", data=" + Arrays.toString(data) +
                ", msg='" + msg + '\'' +
                ", hotUpdate='" + hotUpdate + '\'' +
                '}';
    }

    public OcrResponse() {
    }

    public OcrResponse(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

public class Ocr implements AutoCloseable {
    // 公共
    Gson gson;
    boolean ocrReady = false;
    Map<String, Object> arguments;
    BufferedReader reader;
    BufferedWriter writer;
    OcrMode mode;

    // 本地进程模式
    Process process;
    File exePath;


    // 套接字服务器模式
    String serverAddr;
    int serverPort;
    Socket clientSocket;
    boolean isLoopback = false;

    /**
     * 使用套接字模式初始化
     * @param serverAddr
     * @param serverPort
     * @param arguments
     * @throws IOException
     */
    public Ocr(String serverAddr, int serverPort, Map<String, Object> arguments) throws IOException {
        this.mode = OcrMode.SOCKET_SERVER;
        this.arguments = arguments;
        this.serverAddr = serverAddr;
        this.serverPort = serverPort;
        checkIfLoopback();
        initOcr();
    }

    /**
     * 使用本地进程模式初始化
     * @param exePath
     * @param arguments
     * @throws IOException
     */
    public Ocr(File exePath, Map<String, Object> arguments) throws IOException {
        this.mode = OcrMode.LOCAL_PROCESS;
        this.arguments = arguments;
        this.exePath = exePath;
        initOcr();
    }

    private void initOcr() throws IOException {
        gson = new Gson();

        String commands = "";
        if (arguments != null) {
            for (Map.Entry<String, Object> entry : arguments.entrySet()) {
                String command = "--" + entry.getKey() + "=";
                if (entry.getValue() instanceof String) {
                    command += "'" + entry.getValue() + "'";
                } else {
                    command += entry.getValue().toString();
                }
                commands += ' ' + command;
            }
        }

        if (!StandardCharsets.US_ASCII.newEncoder().canEncode(commands)) {
            throw new IllegalArgumentException("参数不能含有非 ASCII 字符");
        }

        System.out.println("当前参数:" + (commands.isEmpty() ? "空": commands));


        switch (this.mode) {
            case LOCAL_PROCESS: {
                File workingDir = exePath.getParentFile();
                ProcessBuilder pb = new ProcessBuilder(exePath.toString(), commands);
                pb.directory(workingDir);
                pb.redirectErrorStream(true);
                process = pb.start();

                InputStream stdout = process.getInputStream();
                OutputStream stdin = process.getOutputStream();
                reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
                writer = new BufferedWriter(new OutputStreamWriter(stdin, StandardCharsets.UTF_8));
                String line = "";
                ocrReady = false;
                while (!ocrReady) {
                    line = reader.readLine();
                    if (line.contains("OCR init completed")) {
                        ocrReady = true;
                    }
                }
                System.out.println("初始化OCR成功");
                break;
            }
            case SOCKET_SERVER: {
                clientSocket = new Socket(serverAddr, serverPort);
                reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8));
                writer = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream(), StandardCharsets.UTF_8));
                ocrReady = true;
                System.out.println("已连接到OCR套接字服务器,假设服务器已初始化成功");
                break;
            }
        }


    }

    /**
     * 使用图片路径进行 OCR
     * @param imgFile
     * @return
     * @throws IOException
     */
    public OcrResponse runOcr(File imgFile) throws IOException {
        if (mode == OcrMode.SOCKET_SERVER && !isLoopback) {
            System.out.println("套接字模式下服务器不在本地,发送路径可能失败");
        }
        Map<String, String> reqJson = new HashMap<>();
        reqJson.put("image_path", imgFile.toString());
        return this.sendJsonToOcr(reqJson);
    }

    /**
     * 使用剪贴板中图片进行 OCR
     * @return
     * @throws IOException
     */
    public OcrResponse runOcrOnClipboard() throws IOException {
        if (mode == OcrMode.SOCKET_SERVER && !isLoopback) {
            System.out.println("套接字模式下服务器不在本地,发送剪贴板可能失败");
        }
        Map<String, String> reqJson = new HashMap<>();
        reqJson.put("image_path", "clipboard");
        return this.sendJsonToOcr(reqJson);
    }

    /**
     * 使用 Base64 编码的图片进行 OCR
     * @param base64str
     * @return
     * @throws IOException
     */
    public OcrResponse runOcrOnImgBase64(String base64str) throws IOException {
        Map<String, String> reqJson = new HashMap<>();
        reqJson.put("image_base64", base64str);
        return this.sendJsonToOcr(reqJson);
    }

    /**
     * 使用图片 Byte 数组进行 OCR
     * @param fileBytes
     * @return
     * @throws IOException
     */
    public OcrResponse runOcrOnImgBytes(byte[] fileBytes) throws IOException {
        return this.runOcrOnImgBase64(Base64.getEncoder().encodeToString(fileBytes));
    }

    private OcrResponse sendJsonToOcr(Map<String, String> reqJson) throws IOException {
        if (!isAlive()) {
            throw new RuntimeException("OCR进程已经退出或连接已断开");
        }
        StringWriter sw = new StringWriter();
        EscapedWriter ew = new EscapedWriter(sw);
        gson.toJson(reqJson, ew);
        writer.write(sw.getBuffer().toString());
        writer.write("\r\n");
        writer.flush();
        String resp = reader.readLine();
        System.out.println(resp);

        Map rawJsonObj = gson.fromJson(resp, Map.class);
        if (rawJsonObj.get("data") instanceof String) {
            return new OcrResponse((int)Double.parseDouble(rawJsonObj.get("code").toString()), rawJsonObj.get("data").toString());
        }

        return gson.fromJson(resp, OcrResponse.class);
    }


    private void checkIfLoopback() {
        if (this.mode != OcrMode.SOCKET_SERVER) return;
        try {
            InetAddress address = InetAddress.getByName(serverAddr);
            NetworkInterface networkInterface = NetworkInterface.getByInetAddress(address);
            if (networkInterface != null && networkInterface.isLoopback()) {
                this.isLoopback = true;
            } else {
                this.isLoopback = false;
            }
        } catch (Exception e) {
            // 非关键路径
            System.out.println("套接字模式,未能确认服务端是否在本地");
        }
        System.out.println("套接字模式下,服务端在本地:" + isLoopback);
    }

    private boolean isAlive() {
        switch (this.mode) {
            case LOCAL_PROCESS:
                return process.isAlive();
            case SOCKET_SERVER:
                return clientSocket.isConnected();
        }
        return false;
    }


    @Override
    public void close() {
        if (isAlive()) {
            switch (this.mode) {
                case LOCAL_PROCESS: {
                    process.destroy();
                    break;
                }
                case SOCKET_SERVER: {
                    try {
                        clientSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    break;
                }
            }
        }
    }

}
home页面
<!DOCTYPE html>
<html>
<head>
    <div th:replace="~{/fragment/header :: header}"></div>
    <div th:replace="~{/fragment/header :: header-css}"></div>
    <div th:replace="~{/fragment/header :: header-nav}"></div>
</head>
<body>
<form>
    <a th:href="@{/parkingSys/entrance}" class="btn btn-secondary">进入</a>
    <a th:href="@{/parkingSys/export}" class="btn btn-secondary">离开</a>
</form>

</body>
<div th:replace="~{/fragment/footer :: footer}"></div>
<div th:replace="~{/fragment/header :: header-js}"></div>
</html>
entrance页面
<!DOCTYPE html>
<html>
<head>
    <div th:replace="~{/fragment/header :: header}"></div>
    <div th:replace="~{/fragment/header :: header-css}"></div>
    <div th:replace="~{/fragment/header :: header-nav}"></div>
</head>
<body>
<form action="/parkingSys/entranceSave" th:action="@{/parkingSys/entranceSave}" th:object="${vehicle}" method="post" enctype="multipart/form-data">
    <input th:field="*{id}" type="hidden"/>
    <div class="mb-3">
        <label class="form-label">车牌图片:</label>
        <input type="file" name="file">
    </div>
    <div class="dropdown">
        <label class="form-label">停车场:</label>
        <select name="parkingLotId" id="parkingLotId">
            <option th:each="parkingLot:${parkingLots}" th:value="${parkingLot.id}" th:text="${parkingLot.name}"></option>
        </select>
    </div>
    <div class="mb-3 form-check">
        <input type="checkbox" class="form-check-input" id="exampleCheck1">
        <label class="form-check-label" for="exampleCheck1">检查</label>
    </div>

    <button type="submit" class="btn btn-primary">提交</button>
    <a th:href="@{/parkingSys/home}" class="btn btn-secondary">返回</a>
</form>

</body>
<div th:replace="~{/fragment/footer :: footer}"></div>
<div th:replace="~{/fragment/header :: header-js}"></div>
</html>
entranceResult页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <div th:replace="~{/fragment/header :: header}"></div>
  <div th:replace="~{/fragment/header :: header-css}"></div>
  <div th:replace="~{/fragment/header :: header-nav}"></div>
</head>
<body>
<h3>订单id:<span th:text="${order.id}"></span></h3>
<h3>入场时间:<span th:text="*{#dates.format(order.inDateTime,'yyy-MM-dd HH:mm:ss')}"></span></h3>
<h3>车牌照片:</h3>
<img th:src="${order.vehicle.picUrl}">
<h3>识别结果:<span th:text="${order.vehicle.licencePlate}"></span></h3>
<a th:href="@{/parkingSys/home}" class="btn btn-secondary">返回</a>
</body>
<div th:replace="~{/fragment/footer :: footer}"></div>
<div th:replace="~{/fragment/header :: header-js}"></div>
</html>
export页面
<!DOCTYPE html>
<html>
<head>
    <div th:replace="~{/fragment/header :: header}"></div>
    <div th:replace="~{/fragment/header :: header-css}"></div>
    <div th:replace="~{/fragment/header :: header-nav}"></div>
</head>
<body>
<form action="/parkingSys/exportPay" th:action="@{/parkingSys/exportPay}" method="post" enctype="multipart/form-data">
    <div class="mb-3">
        <label class="form-label">车牌图片:</label>
        <input type="file" name="file">
    </div>
    <div class="mb-3">
        <label class="form-label">是否需要发票:</label>
        <select name="isRequireInvoicing" id="isRequireInvoicing">
            <option value="true">true</option>
            <option value="false">false</option>
        </select>
    </div>
    <div class="mb-3 form-check">
        <input type="checkbox" class="form-check-input" id="exampleCheck1">
        <label class="form-check-label" for="exampleCheck1">检查</label>
    </div>

    <button type="submit" class="btn btn-primary">提交</button>
    <a th:href="@{/parkingSys/home}" class="btn btn-secondary">返回</a>
</form>
</body>
<div th:replace="~{/fragment/footer :: footer}"></div>
<div th:replace="~{/fragment/header :: header-js}"></div>
</html>
pay页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <div th:replace="~{/fragment/header :: header}"></div>
  <div th:replace="~{/fragment/header :: header-css}"></div>
  <div th:replace="~{/fragment/header :: header-nav}"></div>
</head>
<body>
<h3>订单id:<span th:text="${order.id}"></span></h3>
<h3>入场车牌照片:</h3>
<img th:src="${order.vehicle.picUrl}">
<h3>入场识别结果:<span th:text="${order.vehicle.licencePlate}"></span></h3>
<h3>当前车牌照片:</h3>
<img th:src="${tempImgUrl}">
<h3>离场识别结果:<span th:text="${licencePlate}"></span></h3>
<h3>入场时间:<span th:text="*{#dates.format(order.inDateTime,'yyy-MM-dd HH:mm:ss')}"></span></h3>
<h3>离场时间:<span th:text="*{#dates.format(order.outDateTime,'yyy-MM-dd HH:mm:ss')}"></span></h3>
<h3>缴费金额:<span th:text="${order.price}"></span></h3>
<a th:href="@{/parkingSys/home}" class="btn btn-secondary">返回</a>
</body>
<div th:replace="~{/fragment/footer :: footer}"></div>
<div th:replace="~{/fragment/header :: header-js}"></div>
</html>
error页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <div th:replace="~{/fragment/header :: header}"></div>
  <div th:replace="~{/fragment/header :: header-css}"></div>
  <div th:replace="~{/fragment/header :: header-nav}"></div>
</head>
<body>
<h3>error:订单不存在</h3>
<a th:href="@{/parkingSys/home}" class="btn btn-secondary">返回</a>
</body>
<div th:replace="~{/fragment/footer :: footer}"></div>
<div th:replace="~{/fragment/header :: header-js}"></div>
</html>

测试用的临时数据

schema.sql
drop table if exists sys_user;
create table sys_user(
    id bigint(12) auto_increment primary key comment '主键',
    username varchar(300) comment '用户账号',
    password varchar(300) comment '用户密码',
    salt varchar(300) comment '盐'
) engine=InnoDB;

drop table if exists pricing_standard;
create table pricing_standard(
    id bigint(12) auto_increment primary key comment '主键',
    hourly_rate int comment '小时计费',
    daily_rate int comment '按天计费',
    exceeding_hours int comment '超时数',
    update_time TIMESTAMP default CURRENT_TIMESTAMP comment '更新日期'
) engine=InnoDB;

drop table if exists parking_lot;
create table parking_lot(
    id bigint(12) auto_increment primary key comment '主键',
    name varchar(300) comment '停车场名称',
    volumetric int comment '停车场容积',
    pricing_standard_id bigint comment '资费标准'
) engine=InnoDB;

drop table if exists vehicle;
create table vehicle(
    id bigint(12) auto_increment primary key comment '主键',
    licence_plate varchar(100) comment '车牌号',
    pic_url varchar(200) comment '车牌图片url',
    is_active tinyint(2) comment '是否在场',
    parking_lot_id bigint(12) comment '停车场id'
) engine=InnoDB;

drop table if exists orders;
create table orders(
    id bigint(12) auto_increment primary key comment '主键',
    in_datetime TIMESTAMP default CURRENT_TIMESTAMP comment '入场时间',
    out_dateTime TIMESTAMP comment '出场时间',
    price double comment '金额',
    is_pay_completed tinyint(2) comment '缴费状态',
    is_require_invoicing tinyint(2) comment '开票状态',
    pricing_standard_id bigint comment '资费标准',
    parking_lot_id bigint(12) comment '停车场id',
    vehicle_id bigint(12) comment '车辆id'
) engine=InnoDB
data.sql
insert into sys_user (username,password) values ('aaa','123456');
insert into sys_user (username,password) values ('bbb','123456');
insert into sys_user (username,password) values ('ccc','123456');
insert into sys_user (username,password) values ('ddd','123456');
insert into sys_user (username,password) values ('eee','123456');
insert into sys_user (username,password) values ('fff','123456');

insert into pricing_standard (hourly_rate,daily_rate,exceeding_hours,update_time) values (1,2,3,'2023-10-21');
insert into pricing_standard (hourly_rate,daily_rate,exceeding_hours,update_time) values (1,2,3,'2023-10-22');
insert into pricing_standard (hourly_rate,daily_rate,exceeding_hours,update_time) values (2,3,4,'2023-10-23');
insert into pricing_standard (hourly_rate,daily_rate,exceeding_hours,update_time) values (2,3,4,'2023-10-24');
insert into pricing_standard (hourly_rate,daily_rate,exceeding_hours,update_time) values (3,4,5,'2023-10-25');
insert into pricing_standard (hourly_rate,daily_rate,exceeding_hours,update_time) values (3,4,5,'2023-10-26');

insert into parking_lot (name,volumetric,pricing_standard_id) values ('A区',200,1);
insert into parking_lot (name,volumetric,pricing_standard_id) values ('B区',200,2);
insert into parking_lot (name,volumetric,pricing_standard_id) values ('B区',200,3);
insert into parking_lot (name,volumetric,pricing_standard_id) values ('C区',200,4);
insert into parking_lot (name,volumetric,pricing_standard_id) values ('D区',200,5);

insert into vehicle (licence_plate,pic_url,is_active,parking_lot_id) values ('aaa','/images/1.png',true,1);
insert into vehicle (licence_plate,pic_url,is_active,parking_lot_id) values ('bbb','/images/2.png',true,1);
insert into vehicle (licence_plate,pic_url,is_active,parking_lot_id) values ('ccc','/images/3.png',true,1);
insert into vehicle (licence_plate,pic_url,is_active,parking_lot_id) values ('ddd','/images/4.png',true,1);
insert into vehicle (licence_plate,pic_url,is_active,parking_lot_id) values ('eee','/images/5.png',true,1);
insert into vehicle (licence_plate,pic_url,is_active,parking_lot_id) values ('fff','/images/6.png',true,1);

insert into orders (id,in_datetime,out_dateTime,price,is_pay_completed,is_require_invoicing,pricing_standard_id,parking_lot_id,vehicle_id) values (1,'2023-10-6','2023-10-8',10.6,true,true,1,1,1);
insert into orders (id,in_datetime,out_dateTime,price,is_pay_completed,is_require_invoicing,pricing_standard_id,parking_lot_id,vehicle_id) values (2,'2023-10-7','2023-10-9',10.6,true,true,2,2,2);
insert into orders (id,in_datetime,out_dateTime,price,is_pay_completed,is_require_invoicing,pricing_standard_id,parking_lot_id,vehicle_id) values (3,'2023-10-8','2023-10-10',10.6,true,true,3,3,3);
insert into orders (id,in_datetime,out_dateTime,price,is_pay_completed,is_require_invoicing,pricing_standard_id,parking_lot_id,vehicle_id) values (4,'2023-10-9','2023-10-11',10.6,true,true,4,4,4);
insert into orders (id,in_datetime,out_dateTime,price,is_pay_completed,is_require_invoicing,pricing_standard_id,parking_lot_id,vehicle_id) values (5,'2023-10-10','2023-10-12',10.6,true,true,5,5,5);
insert into orders (id,in_datetime,out_dateTime,price,is_pay_completed,is_require_invoicing,pricing_standard_id,parking_lot_id,vehicle_id) values (6,'2023-10-11','2023-10-13',10.6,true,true,6,5,6);

效果演示

这里就只演示ParkingSys的页面,其他的就不演示了

home页面

车辆入场时:点击进入后来到entrance页面,选择车牌图片文件,选择停车场

提交后来到entranceSave页面

车辆离场时:点击home页面的离开

来到export页面,选择车牌图片文件,选择是否开发票

提交后来到exportPay页面

为了计算出金额,我这里就直接修改系统时间了

因为这是模拟,所以就没做支付,直接显示结果了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值