目录
创建PricingStandard的Controller和Services
创建ParkingLot的Controller和Services
前言
在课堂上,我们学习了许多关于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
这就没什么好说的了,直接看代码吧
header
<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>
footer
<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++ 编译。https://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页面
为了计算出金额,我这里就直接修改系统时间了
因为这是模拟,所以就没做支付,直接显示结果了