feat: 添加打印机管理模块,支持局域网打印
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -0,0 +1,96 @@
|
|||||||
|
package com.example.building.controller;
|
||||||
|
|
||||||
|
import com.example.building.common.Result;
|
||||||
|
import com.example.building.entity.Printer;
|
||||||
|
import com.example.building.service.PrinterService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印机控制器
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/printers")
|
||||||
|
public class PrinterController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PrinterService printerService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取打印机列表
|
||||||
|
*/
|
||||||
|
@GetMapping
|
||||||
|
public Result<List<Printer>> getPrinters() {
|
||||||
|
return Result.success(printerService.list());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认打印机
|
||||||
|
*/
|
||||||
|
@GetMapping("/default")
|
||||||
|
public Result<Printer> getDefaultPrinter() {
|
||||||
|
Printer printer = printerService.getDefaultPrinter();
|
||||||
|
return Result.success(printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加打印机
|
||||||
|
*/
|
||||||
|
@PostMapping
|
||||||
|
public Result<Printer> addPrinter(@RequestBody Printer printer) {
|
||||||
|
printerService.save(printer);
|
||||||
|
return Result.success(printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新打印机
|
||||||
|
*/
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public Result<Printer> updatePrinter(@PathVariable String id, @RequestBody Printer printer) {
|
||||||
|
printer.setPrinterId(id);
|
||||||
|
printerService.updateById(printer);
|
||||||
|
return Result.success(printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除打印机
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public Result<Void> deletePrinter(@PathVariable String id) {
|
||||||
|
printerService.removeById(id);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置默认打印机
|
||||||
|
*/
|
||||||
|
@PutMapping("/{id}/default")
|
||||||
|
public Result<Void> setDefaultPrinter(@PathVariable String id) {
|
||||||
|
// 先取消所有默认
|
||||||
|
List<Printer> printers = printerService.list();
|
||||||
|
for (Printer p : printers) {
|
||||||
|
if (p.getIsDefault() != null && p.getIsDefault() == 1) {
|
||||||
|
p.setIsDefault(0);
|
||||||
|
printerService.updateById(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 设置新的默认
|
||||||
|
Printer printer = printerService.getById(id);
|
||||||
|
if (printer != null) {
|
||||||
|
printer.setIsDefault(1);
|
||||||
|
printerService.updateById(printer);
|
||||||
|
}
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印订单
|
||||||
|
*/
|
||||||
|
@PostMapping("/print/{orderId}")
|
||||||
|
public Result<Void> printOrder(@PathVariable String orderId) {
|
||||||
|
printerService.printOrder(orderId);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/main/java/com/example/building/entity/Printer.java
Normal file
48
src/main/java/com/example/building/entity/Printer.java
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package com.example.building.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印机实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("printers")
|
||||||
|
public class Printer {
|
||||||
|
|
||||||
|
@TableId(type = IdType.ASSIGN_UUID)
|
||||||
|
private String printerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印机名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP地址
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 端口
|
||||||
|
*/
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否默认 0-否 1-是
|
||||||
|
*/
|
||||||
|
private Integer isDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态 0-禁用 1-启用
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.example.building.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.example.building.entity.Printer;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PrinterMapper extends BaseMapper<Printer> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.example.building.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.example.building.entity.Printer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印机服务接口
|
||||||
|
*/
|
||||||
|
public interface PrinterService extends IService<Printer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取默认打印机
|
||||||
|
*/
|
||||||
|
Printer getDefaultPrinter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印订单小票
|
||||||
|
*/
|
||||||
|
void printOrder(String orderId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package com.example.building.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.example.building.entity.Order;
|
||||||
|
import com.example.building.entity.Printer;
|
||||||
|
import com.example.building.mapper.OrderMapper;
|
||||||
|
import com.example.building.mapper.PrinterMapper;
|
||||||
|
import com.example.building.service.PrinterService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印机服务实现
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class PrinterServiceImpl extends ServiceImpl<PrinterMapper, Printer> implements PrinterService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderMapper orderMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Printer getDefaultPrinter() {
|
||||||
|
return this.getOne(new LambdaQueryWrapper<Printer>()
|
||||||
|
.eq(Printer::getIsDefault, 1)
|
||||||
|
.eq(Printer::getStatus, 1)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printOrder(String orderId) {
|
||||||
|
Printer printer = getDefaultPrinter();
|
||||||
|
if (printer == null) {
|
||||||
|
throw new RuntimeException("未找到默认打印机,请先配置打印机");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取订单信息
|
||||||
|
Order order = orderMapper.selectById(orderId);
|
||||||
|
if (order == null) {
|
||||||
|
throw new RuntimeException("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成ESC/POS指令
|
||||||
|
byte[] data = buildReceipt(order);
|
||||||
|
|
||||||
|
// 发送到打印机
|
||||||
|
sendToPrinter(printer.getIp(), printer.getPort(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成小票ESC/POS指令
|
||||||
|
*/
|
||||||
|
private byte[] buildReceipt(Order order) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 初始化打印机
|
||||||
|
sb.append("\u001B@"); // ESC @
|
||||||
|
|
||||||
|
// 居中对齐
|
||||||
|
sb.append("\u001Ba\u0001"); // ESC a 1
|
||||||
|
|
||||||
|
// 打印标题
|
||||||
|
sb.append("\u001B!\u0038"); // ESC ! 56 大字+居中
|
||||||
|
sb.append("【订单小票】\n");
|
||||||
|
sb.append("\u001B!\u0000"); // ESC ! 0 恢复正常
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
sb.append("--------------------------------\n");
|
||||||
|
|
||||||
|
// 左对齐
|
||||||
|
sb.append("\u001Ba\u0000"); // ESC a 0
|
||||||
|
|
||||||
|
// 订单信息
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
||||||
|
sb.append("订单号: ").append(order.getOrderNo()).append("\n");
|
||||||
|
sb.append("时间: ").append(order.getCreatedAt().format(formatter)).append("\n");
|
||||||
|
sb.append("客户: ").append(order.getCustomerName() != null ? order.getCustomerName() : "散客").append("\n");
|
||||||
|
sb.append("电话: ").append(order.getCustomerPhone() != null ? order.getCustomerPhone() : "-").append("\n");
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
sb.append("--------------------------------\n");
|
||||||
|
|
||||||
|
// 商品表头
|
||||||
|
sb.append(String.format("%-10s %4s %8s %8s\n", "商品", "数量", "单价", "小计"));
|
||||||
|
sb.append("--------------------------------\n");
|
||||||
|
|
||||||
|
// TODO: 获取订单明细,这里暂时用固定内容演示
|
||||||
|
sb.append(String.format("%-10s %4d %8.2f %8.2f\n",
|
||||||
|
"商品示例", 1, order.getTotalAmount().doubleValue(), order.getActualAmount().doubleValue()));
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
sb.append("--------------------------------\n");
|
||||||
|
|
||||||
|
// 金额信息
|
||||||
|
sb.append(String.format("%-16s %10s\n", "原价合计:", "¥" + order.getTotalAmount()));
|
||||||
|
sb.append(String.format("%-16s %10s\n", "优惠金额:", "-¥" + order.getDiscountAmount()));
|
||||||
|
sb.append("\u001B!\u0008"); // ESC ! 8 放大
|
||||||
|
sb.append(String.format("%-16s %10s\n", "实付金额:", "¥" + order.getActualAmount()));
|
||||||
|
sb.append("\u001B!\u0000"); // ESC ! 0 恢复正常
|
||||||
|
|
||||||
|
// 支付方式
|
||||||
|
String paymentMethod = getPaymentText(order.getPaymentMethod());
|
||||||
|
sb.append("支付方式: ").append(paymentMethod).append("\n");
|
||||||
|
|
||||||
|
// 备注
|
||||||
|
if (order.getRemark() != null && !order.getRemark().isEmpty()) {
|
||||||
|
sb.append("备注: ").append(order.getRemark()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尾部
|
||||||
|
sb.append("--------------------------------\n");
|
||||||
|
sb.append("\u001Ba\u0001"); // 居中
|
||||||
|
sb.append("谢谢惠顾,欢迎下次光临!\n");
|
||||||
|
sb.append("\n\n\n");
|
||||||
|
|
||||||
|
// 切纸指令
|
||||||
|
sb.append("\u001Bd\u0003"); // ESC d 3 走纸3行
|
||||||
|
sb.append("\u001Bv\u0000"); // GS v 0 切纸
|
||||||
|
|
||||||
|
return sb.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPaymentText(String method) {
|
||||||
|
if (method == null) return "-";
|
||||||
|
switch (method) {
|
||||||
|
case "cash": return "现金";
|
||||||
|
case "wechat": return "微信";
|
||||||
|
case "alipay": return "支付宝";
|
||||||
|
default: return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送数据到打印机
|
||||||
|
*/
|
||||||
|
private void sendToPrinter(String ip, int port, byte[] data) {
|
||||||
|
try (Socket socket = new Socket(ip, port);
|
||||||
|
OutputStream out = socket.getOutputStream()) {
|
||||||
|
out.write(data);
|
||||||
|
out.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("打印失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/main/resources/db/migration/V6__add_printers_table.sql
Normal file
11
src/main/resources/db/migration/V6__add_printers_table.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-- 打印机表
|
||||||
|
CREATE TABLE IF NOT EXISTS printers (
|
||||||
|
printer_id VARCHAR(36) PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
ip VARCHAR(50) NOT NULL,
|
||||||
|
port INTEGER DEFAULT 9100,
|
||||||
|
is_default INTEGER DEFAULT 0,
|
||||||
|
status INTEGER DEFAULT 1,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user