diff --git a/src/main/java/com/example/building/controller/PrinterController.java b/src/main/java/com/example/building/controller/PrinterController.java new file mode 100644 index 0000000..382b4f4 --- /dev/null +++ b/src/main/java/com/example/building/controller/PrinterController.java @@ -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> getPrinters() { + return Result.success(printerService.list()); + } + + /** + * 获取默认打印机 + */ + @GetMapping("/default") + public Result getDefaultPrinter() { + Printer printer = printerService.getDefaultPrinter(); + return Result.success(printer); + } + + /** + * 添加打印机 + */ + @PostMapping + public Result addPrinter(@RequestBody Printer printer) { + printerService.save(printer); + return Result.success(printer); + } + + /** + * 更新打印机 + */ + @PutMapping("/{id}") + public Result updatePrinter(@PathVariable String id, @RequestBody Printer printer) { + printer.setPrinterId(id); + printerService.updateById(printer); + return Result.success(printer); + } + + /** + * 删除打印机 + */ + @DeleteMapping("/{id}") + public Result deletePrinter(@PathVariable String id) { + printerService.removeById(id); + return Result.success(); + } + + /** + * 设置默认打印机 + */ + @PutMapping("/{id}/default") + public Result setDefaultPrinter(@PathVariable String id) { + // 先取消所有默认 + List 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 printOrder(@PathVariable String orderId) { + printerService.printOrder(orderId); + return Result.success(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/building/entity/Printer.java b/src/main/java/com/example/building/entity/Printer.java new file mode 100644 index 0000000..3679857 --- /dev/null +++ b/src/main/java/com/example/building/entity/Printer.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/example/building/mapper/PrinterMapper.java b/src/main/java/com/example/building/mapper/PrinterMapper.java new file mode 100644 index 0000000..2fe74aa --- /dev/null +++ b/src/main/java/com/example/building/mapper/PrinterMapper.java @@ -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 { +} \ No newline at end of file diff --git a/src/main/java/com/example/building/service/PrinterService.java b/src/main/java/com/example/building/service/PrinterService.java new file mode 100644 index 0000000..41585b8 --- /dev/null +++ b/src/main/java/com/example/building/service/PrinterService.java @@ -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 getDefaultPrinter(); + + /** + * 打印订单小票 + */ + void printOrder(String orderId); +} \ No newline at end of file diff --git a/src/main/java/com/example/building/service/impl/PrinterServiceImpl.java b/src/main/java/com/example/building/service/impl/PrinterServiceImpl.java new file mode 100644 index 0000000..f1d7f4b --- /dev/null +++ b/src/main/java/com/example/building/service/impl/PrinterServiceImpl.java @@ -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 implements PrinterService { + + @Autowired + private OrderMapper orderMapper; + + @Override + public Printer getDefaultPrinter() { + return this.getOne(new LambdaQueryWrapper() + .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()); + } + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V6__add_printers_table.sql b/src/main/resources/db/migration/V6__add_printers_table.sql new file mode 100644 index 0000000..4e874d0 --- /dev/null +++ b/src/main/resources/db/migration/V6__add_printers_table.sql @@ -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 +); \ No newline at end of file