Java
一个 Java 程序,使用 java.net.http(Java 11 及更高版本自带)调用 API;程序的其余部分需要 Java 17。它列出模板中带标签的变量,用给定的值测量所生成的图纸,并将其导出为 PDF。 Java 没有自带的 JSON 解析器,因此程序末尾附有一个简短的解析器,足以处理这些响应,并以文本形式构建请求的 JSON;在实际程序中,请使用 Jackson 或 Gson 等库。
下载:Box.java。
// Box.java: list a template's inputs, measure it and export it as a PDF with the PiQ SaaS API.
// Java 17 or later; java.net.http, no libraries needed. Run with: java Box.java
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
import java.util.*;
public class Box {
static final String TEMPLATE =
"L=400,\"Length\"\nW=300,\"Width\"\nP1=[L,0]\nP2=[L,W]\nP3=[0,W]\nPOLYLINE @1,[P0,P1,P2,P3],TRUE,0";
static final String KEY = System.getenv("PIQ_API_KEY");
static final String BASE = Objects.requireNonNullElse(System.getenv("PIQ_URL"), "https://saas.parametriq.co.uk")
.replaceAll("/$", "");
static final HttpClient HTTP = HttpClient.newHttpClient();
public static void main(String[] args) throws Exception {
if (KEY == null || KEY.isEmpty()) { System.err.println("Set PIQ_API_KEY to your API key."); System.exit(1); }
String storage = System.getenv("PIQ_STORAGE"); // optional: a registered storage's name
// the template itself, or (optional) where it is kept in your storage
String template = storage == null ? "\"template\":" + quote(TEMPLATE)
: "\"source\":{\"storage\":" + quote(storage) + ",\"key\":\"templates/box.piq\"}";
String values = "\"values\":{\"L\":500,\"W\":250}";
// 1. The template's labelled variables
var inputs = (Map<?, ?>) Json.parse(post("/inputs", "{" + template + "}").body());
for (var item : (List<?>) inputs.get("inputs")) {
var i = (Map<?, ?>) item;
System.out.printf("%s: %s (%s, %s)%n", i.get("name"), i.get("label"), i.get("kind"), i.get("value"));
}
// 2. The drawing measured, with the values given
var m = (Map<?, ?>) Json.parse(post("/measure", "{" + template + "," + values + "}").body());
Object cut = 0;
for (var layer : (List<?>) m.get("layers"))
if ("Cut".equals(((Map<?, ?>) layer).get("layer"))) cut = ((Map<?, ?>) layer).get("length");
System.out.printf("Size: %s x %s, cut length: %s%n", m.get("width"), m.get("height"), cut);
// 3. The drawing as a PDF
var pdf = HTTP.send(request("/export/pdf", "{" + template + "," + values + "}"),
HttpResponse.BodyHandlers.ofByteArray());
if (pdf.statusCode() >= 300) fail(pdf.statusCode(), new String(pdf.body(), "UTF-8"));
Files.write(Path.of("box.pdf"), pdf.body());
System.out.printf("Saved box.pdf (%s warnings)%n", pdf.headers().firstValue("X-PiQ-Warnings").orElse("?"));
// 4. Optional: the PDF saved to your storage instead of downloaded
if (storage != null) {
String destination = "\"destination\":{\"storage\":" + quote(storage)
+ ",\"key\":\"drawings/box.pdf\"},\"overwrite\":true";
var saved = (Map<?, ?>) Json.parse(
post("/save/pdf", "{" + template + "," + values + "," + destination + "}").body());
System.out.printf("Saved %s to %s (%s bytes)%n", saved.get("key"), saved.get("storage"), saved.get("size"));
}
}
static HttpRequest request(String path, String json) {
return HttpRequest.newBuilder(URI.create(BASE + path)).header("X-API-KEY", KEY)
.header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(json)).build();
}
static HttpResponse<String> post(String path, String json) throws Exception {
var response = HTTP.send(request(path, json), HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 300) fail(response.statusCode(), response.body());
return response;
}
static void fail(int status, String body) {
System.err.println("Error " + status);
if (!body.isEmpty()) { // a 401 has no body
try {
var problem = (Map<?, ?>) Json.parse(body);
for (var part : List.of("title", "detail"))
if (problem.get(part) != null) System.err.println(problem.get(part));
if (problem.get("errors") instanceof Map<?, ?> errors)
errors.forEach((field, messages) -> System.err.println(" " + field + ": "
+ String.join(" ", ((List<?>) messages).stream().map(String::valueOf).toList())));
} catch (RuntimeException e) { System.err.println(body); }
}
System.exit(1);
}
static String quote(String s) {
var b = new StringBuilder("\"");
for (char c : s.toCharArray())
if (c == '"' || c == '\\') b.append('\\').append(c);
else if (c < 0x20) b.append(String.format("\\u%04x", (int) c));
else b.append(c);
return b.append('"').toString();
}
/** Just enough JSON for this example: in a real program use a library such as Jackson or Gson. */
static class Json {
final String s; int i;
Json(String s) { this.s = s; }
static Object parse(String s) { return new Json(s).value(); }
void skip() { while (i < s.length() && Character.isWhitespace(s.charAt(i))) i++; }
Object value() {
skip();
char c = s.charAt(i);
if (c == '{') {
var map = new LinkedHashMap<String, Object>(); i++; skip();
if (s.charAt(i) == '}') { i++; return map; }
do { skip(); String k = (String) value(); skip(); i++; map.put(k, value()); skip(); }
while (s.charAt(i++) == ',');
return map;
}
if (c == '[') {
var list = new ArrayList<Object>(); i++; skip();
if (s.charAt(i) == ']') { i++; return list; }
do list.add(value()); while (skipThen() == ',');
return list;
}
if (c == '"') {
var b = new StringBuilder(); i++;
for (char d; (d = s.charAt(i++)) != '"'; )
if (d != '\\') b.append(d);
else switch (d = s.charAt(i++)) {
case 'n' -> b.append('\n'); case 't' -> b.append('\t'); case 'r' -> b.append('\r');
case 'b' -> b.append('\b'); case 'f' -> b.append('\f');
case 'u' -> { b.append((char) Integer.parseInt(s.substring(i, i + 4), 16)); i += 4; }
default -> b.append(d);
}
return b.toString();
}
int start = i;
while (i < s.length() && ",}] \r\n\t".indexOf(s.charAt(i)) < 0) i++;
String word = s.substring(start, i);
return switch (word) { case "true" -> true; case "false" -> false; case "null" -> null;
default -> word.matches("-?\\d+") ? (Object) Long.parseLong(word) : (Object) Double.parseDouble(word); };
}
char skipThen() { skip(); return s.charAt(i++); }
}
}
工作原理
- 设置。 密钥来自
PIQ_API_KEY,地址来自PIQ_URL(未设置时为服务本身的地址),还可以选择从PIQ_STORAGE读取存储名称。没有密钥时,程序会立即停止。 - 模板。
template是每个请求 JSON 的开头:模板的文本作为template,或者在设置了PIQ_STORAGE时,以source给出模板在您存储中的保存位置。quote将文本转换为 JSON 字符串,转义其中的引号和换行符。values按名称给出L和W。 request构建一个向某个操作发送 JSON 的POST,并在X-API-KEY标头中携带密钥。post发送请求,并以文本形式交回响应;如果不成功,则转到fail。fail打印状态,然后打印问题描述:其title、detail,以及errors中的每一项,即字段及其问题。401没有正文,因此只有在有正文时才会解析;不是 JSON 的正文按原样打印。然后程序以1退出。- Inputs。
POST /inputs只需要模板。其响应被解析为映射和列表;其每个inputs都有name、label、kind和value。 - Measure。 使用这些值调用
POST /measure,可得到图纸的width和height,以及在layers中按图层统计的线长;切割长度即Cut图层上的长度。 - Export。
POST /export/pdf返回 PDF 本身而不是 JSON,因此会以字节形式读取、检查,然后写入box.pdf。X-PiQ-Warnings标头表示有多少内容无法按原样导出。 - Save(可选)。 指定了存储时,
POST /save/pdf会导出同一图纸,并将其保存到您存储中的destination,"overwrite":true表示替换已存在的文件。响应会说明保存位置及文件大小。 Json将 JSON 读取为Map、List、String、Long、Double、Boolean和null。它信任所给的内容,这对服务的响应来说没有问题,但仅限于此。
运行
将程序保存为 Box.java,然后无需先编译,直接运行:
export PIQ_API_KEY=XXXX-XXXX-XXXX-XXXXX
java Box.java
它会打印模板中带标签的变量、图纸的尺寸和切割长度,并保存 box.pdf:
L: Length (Number, 400)
W: Width (Number, 300)
D: Depth (Number, 300)
Size: 500 x 250, cut length: 1500
Saved box.pdf (0 warnings)
如果给出模板无法使用的值(例如 "L": "long"),它会打印问题并停止:
Error 400
One or more validation errors occurred.
Some of the values given can't be used.
L: L is a number.
如果密钥不正确,它只会打印 Error 401:401 没有问题描述。
要试用存储部分,请注册一个存储(请参阅注册存储),将模板以 templates/box.piq
放入其中,并将 PIQ_STORAGE 设置为该存储的名称。这样,模板将从您的存储中读取,
PDF 也会以 drawings/box.pdf 保存到其中,替换已存在的任何文件。