浏览代码

暂存修改版的打票机打印

liudongzhi 1 周之前
父节点
当前提交
19faf6a20a

+ 7 - 0
alien-entity/src/main/java/shop/alien/entity/store/PrinterBrand.java

@@ -0,0 +1,7 @@
+package shop.alien.entity.store;
+
+public enum PrinterBrand {
+    XINYE,    // 芯烨云打印机
+    JIAHANG,  // 佳航云打印机
+    SHANGPENG // 商鹏云打印机
+}

+ 42 - 0
alien-entity/src/main/java/shop/alien/entity/store/PrinterDO.java

@@ -0,0 +1,42 @@
+package shop.alien.entity.store;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 打印机数据库实体
+ * 对应数据库表:printer
+ */
+@Data
+@TableName("printer") // 绑定数据库表名
+public class PrinterDO {
+
+    // 主键自增ID
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    // 品牌:XINYE/JIAHANG/SHANGPENG
+    private String brand;
+
+    // 打印机名称(前台/后厨)
+    private String printerName;
+
+    // 设备编号SN(每台打印机唯一)
+    private String sn;
+
+    // 开发者ID / 商户ID(芯烨=user,佳航=merchantId)
+    private String userId;
+
+    // API密钥 / UserKey / Token
+    private String apiKey;
+
+    // 备注
+    private String remark;
+
+    // 创建时间
+    private LocalDateTime createTime;
+}

+ 13 - 0
alien-entity/src/main/java/shop/alien/mapper/PrinterMapper.java

@@ -0,0 +1,13 @@
+package shop.alien.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import shop.alien.entity.store.PrinterDO;
+
+/**
+ * 邀请活动配置mapper
+ */
+@Mapper
+public interface PrinterMapper extends BaseMapper<PrinterDO> {
+
+}

+ 7 - 0
alien-store/pom.xml

@@ -234,6 +234,13 @@
             <artifactId>fastjson</artifactId>
             <version>2.0.32</version>
         </dependency>
+
+<!--        <dependency>-->
+<!--            <groupId>com.alibaba.fastjson2</groupId>-->
+<!--            <artifactId>fastjson2</artifactId>-->
+<!--            <version>2.0.23</version>-->
+<!--        </dependency>-->
+
         <!-- 用于 HMAC-SHA1 签名 -->
         <dependency>
             <groupId>commons-codec</groupId>

+ 54 - 0
alien-store/src/main/java/shop/alien/store/controller/PrintController.java

@@ -0,0 +1,54 @@
+package shop.alien.store.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import shop.alien.entity.store.PrinterDO;
+import shop.alien.mapper.PrinterMapper;
+import shop.alien.store.service.impl.PrintService;
+import shop.alien.store.service.impl.PrintTemplate;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@RestController
+@Slf4j
+@RequestMapping("/print")
+public class PrintController {
+    // 注入数据库Mapper
+    @Resource
+    private PrinterMapper printerMapper;
+
+    // 注入打印服务
+    @Resource
+    private PrintService printService;
+
+    /**
+     * 一键打印所有打印机
+     * 访问地址:http://localhost:8080/print/all
+     */
+    @GetMapping("/all")
+    public String printAll() {
+        // 查询数据库所有打印机
+        List<PrinterDO> list = printerMapper.selectList(new LambdaQueryWrapper<PrinterDO>().isNotNull(PrinterDO::getSn));
+        // 生成小票内容
+        String content = PrintTemplate.buildOrder("ORDER20260406001",
+                "可乐 3.00 x2 6.00\n方便面 5.00 x1 5.00\n",
+                "11.00");
+        // 批量打印
+        log.info("打印内容:"+ content );
+        printService.printAll(list, content);
+        return "已下发打印指令,共" + list.size() + "台打印机";
+    }
+
+    /**
+     * 查询所有打印机
+     * 访问地址:http://localhost:8080/print/list
+     */
+    @GetMapping("/list")
+    public Object list() {
+        return printerMapper.selectList(null);
+    }
+}

+ 134 - 0
alien-store/src/main/java/shop/alien/store/service/impl/PrintService.java

@@ -0,0 +1,134 @@
+package shop.alien.store.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import org.springframework.stereotype.Service;
+import shop.alien.entity.store.PrinterDO;
+import shop.alien.store.util.Base64Util;
+import shop.alien.store.util.ShaUtil;
+
+import java.util.List;
+
+@Slf4j
+@Service
+public class PrintService {
+    // HTTP请求工具
+    private final OkHttpClient client = new OkHttpClient();
+    // JSON请求头
+    private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+
+    /**
+     * 批量打印:给所有打印机发打印指令
+     * @param list 打印机列表
+     * @param content 打印内容
+     */
+    public void printAll(List<PrinterDO> list, String content) {
+        for (PrinterDO p : list) {
+            // 异步打印:不阻塞,多打印机同时打印
+            new Thread(() -> printWithRetry(p, content, 3)).start();
+        }
+    }
+
+    /**
+     * 打印重试机制
+     * @param printer 打印机
+     * @param content 内容
+     * @param retry 重试次数
+     * @return 是否成功
+     */
+    public boolean printWithRetry(PrinterDO printer, String content, int retry) {
+        while (retry-- > 0) {
+            try {
+                boolean ok = printSingle(printer, content);
+                if (ok) return true;
+            } catch (Exception e) {
+                System.err.println("打印失败,剩余重试:" + retry);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 单台打印机打印
+     * 根据品牌自动分发
+     */
+    public boolean printSingle(PrinterDO printer, String content) {
+        String brand = printer.getBrand();
+        if ("XINYE".equalsIgnoreCase(brand)) {
+            return printXinYe(printer, content);
+        } else if ("JIAHANG".equalsIgnoreCase(brand)) {
+            return printJiaHang(printer, content);
+        } else if ("SHANGPENG".equalsIgnoreCase(brand)) {
+            return printShangPeng(printer, content);
+        }
+        return false;
+    }
+
+    // ==================== 芯烨打印实现 ====================
+    private boolean printXinYe(PrinterDO p, String content) {
+        // 时间戳(秒)
+        long ts = System.currentTimeMillis() / 1000;
+        // SHA1签名:user + userKey + timestamp
+        String sign = ShaUtil.sha1(p.getUserId() + p.getApiKey() + ts);
+
+        // 构造API参数
+        JSONObject req = new JSONObject();
+        req.put("user", p.getUserId());
+        req.put("sn", p.getSn());
+//        req.put("content", Base64Util.encode(content));
+        req.put("content", content);
+        req.put("copies", 1);
+        req.put("timestamp", ts);
+        req.put("sign", sign);
+        req.put("mode", 1);
+
+        // 发送请求
+        return post("https://open.xpyun.net/api/openapi/xprinter/print", req);
+    }
+
+    // ==================== 佳航打印实现 ====================
+    private boolean printJiaHang(PrinterDO p, String content) {
+        JSONObject req = new JSONObject();
+        req.put("merchantId", p.getUserId());
+        req.put("deviceNo", p.getSn());
+        req.put("key", p.getApiKey());
+        req.put("printContent", Base64Util.encode(content));
+        req.put("copy", 1);
+        return post("http://api.jhprt.com/api/print", req);
+    }
+
+    // ==================== 商鹏打印实现 ====================
+    private boolean printShangPeng(PrinterDO p, String content) {
+        JSONObject req = new JSONObject();
+        req.put("sn", p.getSn());
+        req.put("token", p.getApiKey());
+        req.put("content", Base64Util.encode(content));
+        req.put("times", 1);
+        return post("https://api.spyun.net/api/print", req);
+    }
+
+    /**
+     * 统一发送POST请求
+     */
+    private boolean post(String url, JSONObject req) {
+        try {
+            RequestBody body = RequestBody.create(JSON, req.toJSONString());
+            Request request = new Request.Builder().url(url).post(body).build();
+            try (Response res = client.newCall(request).execute()) {
+                String str = res.body().string();
+                System.out.println("打印响应:" + str);
+                JSONObject json = JSONObject.parseObject(str);
+                // 通用成功判断:code=0 或 success=true
+                return json.getIntValue("code") == 0 || json.getBooleanValue("success");
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+}

+ 26 - 0
alien-store/src/main/java/shop/alien/store/service/impl/PrintTemplate.java

@@ -0,0 +1,26 @@
+package shop.alien.store.service.impl;
+
+import java.time.LocalDateTime;
+
+public class PrintTemplate {
+
+    /**
+     * 生成订单小票
+     * @param orderNo 订单号
+     * @param items 商品列表
+     * @param total 总金额
+     * @return 打印文本
+     */
+    public static String buildOrder(String orderNo, String items, String total) {
+        return "========== 测试门店 ==========\n" +
+                "订单号:" + orderNo + "\n" +
+                "时间:" + LocalDateTime.now() + "\n" +
+                "----------------------------\n" +
+                items +
+                "----------------------------\n" +
+                "合计:" + total + " 元\n" +
+                "支付方式:微信支付\n" +
+                "----------------------------\n" +
+                "欢迎再次光临\n\n\n";
+    }
+}

+ 17 - 0
alien-store/src/main/java/shop/alien/store/util/Base64Util.java

@@ -0,0 +1,17 @@
+package shop.alien.store.util;
+import org.apache.commons.codec.binary.Base64;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Base64编码工具
+ * 云打印API要求打印内容必须用Base64传输
+ */
+public class Base64Util {
+
+    /**
+     * 字符串转Base64
+     */
+    public static String encode(String str) {
+        return Base64.encodeBase64String(str.getBytes(StandardCharsets.UTF_8));
+    }
+}

+ 29 - 0
alien-store/src/main/java/shop/alien/store/util/ShaUtil.java

@@ -0,0 +1,29 @@
+package shop.alien.store.util;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+public class ShaUtil {
+    /**
+     * SHA1加密
+     * @param s 待加密字符串
+     * @return 小写32位加密结果
+     */
+    public static String sha1(String s) {
+        try {
+            // 获取SHA1算法实例
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            // 加密
+            byte[] bytes = md.digest(s.getBytes(StandardCharsets.UTF_8));
+            // 转16进制
+            StringBuilder sb = new StringBuilder();
+            for (byte b : bytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) sb.append('0');
+                sb.append(hex);
+            }
+            // 返回小写
+            return sb.toString().toLowerCase();
+        } catch (Exception e) {
+            return "";
+        }
+    }
+}