- GCP
谷歌云免费主机实例账单中,用量项目名为"E2 Instance Core running in Americas",赠金折扣名则为"E2 Instance Core running free tier discount"。
- GAR
登录:
https://cloud.google.com/artifact-registry/docs/docker/pushing-and-pulling?hl=zh-cn#token
命令:
临时推拉(有效期1小时)
进入 Google Cloud Shell 执行: gcloud auth print-access-token 或 gcloud auth print-access-token | docker login ...
echo "" | docker login --username=oauth2accesstoken --password-stdin asia-east1-docker.pkg.dev
docker pull asia-east1-docker.pkg.dev/project-123456/temp-repo/gcp-faas 或 $GOOGLE_CLOUD_PROJECT
【GCP中】 sudo gcloud auth configure-docker; sudo gcloud container images list
[永远有效/json key file]
mv project-123456-1234567890ab.json keyfile.json
docker login -u _json_key -p "$(cat keyfile.json)" asia-east1-docker.pkg.dev
- GAE
GAE控制台设置里,可以启用名为"project-123456.appspot.com"的"默认 Cloud Storage 存储桶",属于单区域,我的是东京asia-northeast1,跟GAE一致且无法修改,免费额度为存储5GiB,流出1GiB;同时,还拥有一个上限为5GiB的暂存桶staging.project-123456.appspot.com,虽然免费但按周清空(我是2周),存储桶误删可通过 gcloud beta app repair 重建。
自定义网域:
域名必须经过 Google Search Console 的验证,才会出现在同一 Google 账号 GAE 的添加列表里,只需添加4个 A 记录就能自动生成 SSL 证书,AAAA 记录则可选。
GAE Gradle Plugin:
默认用 Jetty 方式的 com.google.cloud.tools.appengine ,其会通过 appengineStage 任务将 appengine-web.xml 生成 app.yaml。
若用了 apply plugin: 'com.google.cloud.tools.appengine-appyaml',则需要配置 src\main\appengine\app.yaml。
app.yaml 用于配置非 Jetty 方式网站,比如SpringBoot等,不想写 entrypoint: java -Xmx64m -jar x.war 就得指定下 jar.manifest.attributes.Main-Class。
session.setAttribute() 后无法 get 解决:true
发邮件每天免费额度5000起步的方法:
MailServiceFactory.getMailService().sendToAdmins(msg);
说明 - 入参msg字段to会自动置为null,或用100额度但能指定接收邮箱的send(msg);
- GCS
综述:
自动创建文件夹 - 对象名若包含了目录,不需要专门创建再写入;挂载目录也会自动创建,但子目录则file.exists()返回false。
对象名长度 - 512 字节(采用 UTF-8 编码),每个汉字占3-4字节,全中文大约128个。
GCS CORS: [解决javascript fetch跨域/ServiceException: 401 Anonymous caller]
[可选] gcloud auth application-default login
gcloud storage buckets update gs://project-123456.appspot.com --cors-file=cors.json
vim cors.json
[{
"origin": ["https://staticfiles.openle.com", "https://passed.app"],
"method": ["GET", "HEAD", "POST", "PUT", "DELETE"],
"responseHeader": ["Content-Type", "x-goog-acl", "x-goog-resumable"],
"maxAgeSeconds": 3600
}]
gcloud storage buckets describe gs://project-123456.appspot.com --format="default(cors_config)"
gsutil cors get gs://project-123456.appspot.com
JS上传示例:Storage.SignUrlOption.withExtHeaders(Map.of("x-goog-acl", "public-read"));
var signedUrl = "https://storage.googleapis.com/...";
fetch(signedUrl, {method: "PUT", body: input.files[0],
headers: {"x-goog-acl": "public-read"}
}).then(function () { alert("上传完毕!"); });
JS上传示例:可续传 - Storage.SignUrlOption.withExtHeaders(Map.of("x-goog-resumable", "start"));
fetch(sUrl, {method: "POST", headers: {'x-goog-resumable': 'start'
, 'Content-type': 'application/vnd.android.package-archive' // 必设且跟signUrl保持一致。
}}).then(async resp => { var sUri = resp.headers.get('location');
for (const chunk of ac) {
fetch(sUri, {method: "PUT", headers: {'Content-Length': chunk.cFile.size, 'Content-Range': cr}, body: chunk.cFile });
}
});
GCS流式响应下载:
StreamingOutput so = (os) -> {
try (os) {
try (var gcs = GcsCommon.gcs()) {
var bId = BlobId.of("project-123456.appspot.com", objName);
var blob = gcs.get(bId);
if (blob != null) {
try (var r = gcs.reader(bId)) {
Channels.newInputStream(r).transferTo(os);
} catch (StorageException se) { se.printStackTrace(System.err); }
}
} catch (Exception ex) { ex.printStackTrace(System.err); }
}
}; return Response.ok(so).build();
- Google Cloud Build
价格 - 每月免费2500分钟,合每天80分钟。
构建:
说明 - images 所列镜像只会在所有步骤均执行成功后才会推送;若下一步就要使用,可在前一步添加命令 docker push。
① 仅 Dockerfile 构建:
FROM busybox
CMD ["echo", "$符号无效,只能写死字符串!"]
gcloud builds submit --tag asia-east1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/temp-repo/temp-pkg:cloud-build-test
docker run asia-east1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/temp-repo/temp-pkg:cloud-build-test
② yaml配置文件构建(仅build命令(无需push)+images项即自动推送):
vim cloudbuild.yaml # 就近访问可改asia.gcr.io
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/our-x:1.0.1', '.']
# script: |
# docker run ...
images:
- 'gcr.io/$PROJECT_ID/our-x:1.0.1'
gcloud builds submit --tag asia-east1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/temp-repo/temp-pkg:cloud-build-test --config=cloudbuild.yaml
- Google Cloud Run
容器->请求超时:默认500秒,可修改为最高60分钟。
专用环境变量:System.getenv("CLOUD_RUN_TIMEOUT_SECONDS")
账单SKU - CPU Allocation Time(4856-B847-F1EB)、Memory Allocation Time(02A2-9231-36A6)
- Google Firestore
综合:
只有 (default) 数据库有资格使用免费配额:1GiB存储。
字段值的总大小上限:1 MiB - 89 字节(1048487 字节/汉字349000+个) 在线计算文档总字节数
字段保留名字前后缀:__.*__
文档Id字段名:FieldPath.documentId() == "__name__"
服务器当前时间值:FieldValue.serverTimestamp() 对应文档字段类型为timestamp。
TTL存留时间字段入参:var dt = OffsetDateTime.now().plusWeeks(1); com.google.cloud.Timestamp.ofTimeSecondsAndNanos(dt.toEpochSecond(), dt.getNano());
文档创建时间:QueryDocumentSnapshot.getCreateTime()
根据docId前缀列出:db.collection("c").orderBy(FieldPath.documentId()).startAt(keyPrefix).endAt(keyPrefix + "\uf8ff").get();
内存中排序:
var sortList = new ArrayList>();
var docs = doc.getDocuments(); // 不支持排序,故通过getCreateTime排序后用index索引定位。
IntStream.range(0, docs.size()).forEach(i -> {
sortList.add(Map.entry(i, docs.get(i).getCreateTime()));
});
sortList.sort(Comparator.comparing(obj -> ((Map.Entry) obj).getValue()).reversed());
sortList.forEach(kv -> {
var d = docs.get(kv.getKey());
});
- Gemini
SSE响应流JSON解析:
String line; // data: {"candidates": [{"content": {"role": "model","parts": [{"text": "The"}]}}]}
if (line.isBlank() || !line.startsWith("data:")) {
continue;
}
var dataString = line.replaceFirst("data:", "").trim();
//System.out.println("dataString - " + dataString);
if (!dataString.isBlank()) {
try {
var el = JsonParser.parseString(dataString);
//System.out.println("el - " + el);
var rel = el.getAsJsonObject().getAsJsonArray("candidates");
//System.out.println("rel - " + rel);
if (rel != null && !rel.isEmpty()) {
var perts = rel.get(0).getAsJsonObject().getAsJsonObject("content").getAsJsonArray("parts");
//System.out.println("perts - " + perts);
if (perts != null && !perts.isEmpty()) {
var text = perts.get(0).getAsJsonObject().get("text").getAsString();
System.out.println("text: " + text);
}
}
} catch (JsonSyntaxException jse) {
jse.printStackTrace(System.err);
continue;
}
}
/* 或 非严谨的正则匹配:
var pattern = Pattern.compile("\"text\"\\s*:\\s*\"([^\"]+)\"");
var matcher = pattern.matcher(line.substring(5));
if (matcher.find()) { System.out.println(matcher.group(1)); }
*/