工作起步、环境初始化、SEO robots.txt
工作起步、初始化
环境变量
Docker仓库密码:DOCKER_PASSWORD、ALIYUN_IMAGE_PASSWORD
SETX DOCKER_PASSWORD "[password]"
Gradle Maven仓库:ORG_GRADLE_PROJECT_mySecureRepositoryUsername、ORG_GRADLE_PROJECT_mySecureRepositoryPassword
GitHub Actions用${{ secrets.MAVEN_REPO_PASSWORD }}
Android发布上架证书密码:GOOGLE_PLAY_KEYSTORE_PASSWORD、GOOGLE_PLAY_OLD_KEYSTORE_PASSWORD(仅得心应手App使用)
Shell、Server
Linux Shell常用命令:
df -h
free -h
history
ping congci.com
ping -4 openle.com
Shell数据传输:
curl -X POST 'https://上传服务器/main/apis/more/default/ga/anonymous/main/android/upload-file-for-share-processing?filename=x.txt' --data-binary @x.txt
[大陆用户若下载慢,后续考虑中转至中国服务器,比如有免费额度的腾讯云COS]
wget -O x.txt https://返回下载网址/...?path=%2Ftmp%2F...-x.txt
跳过密码修改规则、解决BAD PASSWORD报错(for vultr):
cp /etc/pam.d/common-password ./
sed -i 's/ enforce_for_root//g' /etc/pam.d/common-password
passwd root # 打印校验提示,但不阻止修改,终会successfully。
docker安装:因podman需额外配置Systemd才能做到开机启动,故暂用docker
sudo apt update
sudo apt install docker.io # docker-ce虽为官方但要配软件源。
docker pull busybox # 测试拉取镜像
docker login --username=wangxiaodong docker.io # 输入密码或Token
docker inspect our-auth-server # 查看容器入口点命令行
Web Server:
docker和ufw会互相覆盖规则,要么禁用 sudo ufw disable + 云平台防火墙(比如仅对外的Vultr Firewall Group)
避免通过 http://congci.com:8085/ 内部端口访问的情况;
防火墙端口白名单:
必须 - 22
可选 - ICMP(不让ping则百度提取sitemap.xml失败)
Web - 443、80
要么安装以下兼容规则。
不让docker容器端口绕过ufw:
wget -O /usr/local/bin/ufw-docker \
https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
chmod +x /usr/local/bin/ufw-docker
ufw-docker install # 安装ufw规则后,必须重启ufw方生效
systemctl restart ufw
暴露容器单个端口 ufw-docker allow c-name 80/tcp
暴露容器所有端口 ufw-docker allow c-name
不再暴露容器端口 ufw-docker delete allow c-name
注意 - 该处端口指-p 8080:80中冒号后的端口号。
OS
避免“Windows实时扫描”拉低编译速度:
排除项(需终端管理员/Google目录含Chrome和AndroidStudio2024.1等):
Add-MpPreference -ExclusionPath "$env:USERPROFILE\.gradle\"
Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\Google\"
Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\Android\Sdk\"
Add-MpPreference -ExclusionPath "$env:USERPROFILE\AndroidStudioProjects\"
Add-MpPreference -ExclusionPath "$env:USERPROFILE\Documents\NetBeansProjects\"
Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\AppData\Local\Godot\"
自建文件夹排除项:
Add-MpPreference -ExclusionPath "D:\main\"
Chrome浏览器启用并行下载 chrome://flags/#enable-parallel-downloading
WSL与宿主IP共用(.wslconfig):
[experimental]
autoMemoryReclaim=gradual
networkingMode=mirrored
dnsTunneling=true
firewall=true
autoProxy=true
启用systemd - /etc/wsl.conf:
[boot]
systemd=true
VPN代理
豁免路由:
domain:baidu.com,
domain:admob.com,
domain:registry.cn-hangzhou.aliyuncs.com,
domain:dockerauth.cn-hangzhou.aliyuncs.com
Web、SEO
扩展名首选 index.html,次选 index.htm。
HTML标签详细用法
SEO基本项:
每个页面只能存在一个h1标签。
img标签写上alt="image"属性。
图片色彩空间首选Web友好的sRGB,非Web场合可选择色彩更艳丽的Adobe RGB.
ICON尺寸原始文件为512 x 512
网址链接颜色:steelblue 或 #004080
- robots.txt
# Latest version - https://nav.congci.com/main/home/topics/work-init/#robots.txt
# RFC 9309 - https://www.robotstxt.org/
# Google 缓存 robots.txt 约 24 小时;HTTP 5xx 和 429 均解读为网站完全禁止访问“Disallow: /”。
# 可在任意位置指定多个sitemap文件。
#Sitemap: https://example.com/.well-known/static/sitemap.xml
#Sitemap: https://example.com/.well-known/static/sitemapindex.xml
# 约定:固定路径 /main/core/topics/ 之后的第一层总是带/斜杠,其他层均不带/斜杠;后台均带/斜杠。
#
# https://example.com/main/core/about/
# https://example.com/main/core/mobile/
# https://example.com/main/core/privacy/
# https://example.com/main/core/trending/
#
# https://example.com/main/core/products/
# https://example.com/main/core/products/come-in-handy
# https://example.com/main/core/products/3rd/tile-matching
#
# https://example.com/main/core/topics/
# https://example.com/main/core/topics/ai/
# https://example.com/main/core/topics/ai/chat
# https://example.com/main/core/topics/ai/chat/tool
#
# https://example.com/main/core/auth/login/
# https://example.com/main/core/auth/signup/
# https://example.com/main/core/auth/accounts/ - 禁止抓取;欢迎页结尾带斜杠可避免Servlet跳转。
# https://example.com/main/apis/core/auth/callback/ - 禁止抓取;
# 白名单机制;“/$”表示只匹配首页
User-agent: *
Disallow: /
Allow: /$
Allow: /favicon.ico
Allow: /ads.txt
# 图片
Allow: /.well-known/static/images/core/
# 约定path:
# https://example.com/.well-known/static/images/core/logo.svg
Allow: /.well-known/static/images/more/
# 正式对外核心网页(或用web)
Allow: /main/core/
Disallow: /main/core/auth/accounts/
# 正式对外非核心网页(或用any)
Allow: /main/more/
# 约定path:
# https://example.com/main/more/seo/sitemap.xml
# https://sub.example.com/main/more/seo/sub.example.com-sitemap.xml
# 正式对外核心应用接口(或用use) - /main/apis/core/pv
Disallow: /main/apis/core/
# 正式对外非核心应用接口(或用any) - /main/apis/more/x/execute
Disallow: /main/apis/more/
# 不对外、任意使用的网址前缀(ban、not、for) - /main/temp/apis/x
Disallow: /main/temp/
# 特例
Allow: /main/infomations/
Git
将当前目录纳入源码控制: git init --initial-branch=main
解决报错 - fatal: detected dubious ownership in repository at ...
右键文件夹 -> 属性 -> 安全 -> 高级 -> 所有者:更改 -> 添入当前用户 -> 勾选上 替换子容器和对象的所有者。
Gradle
创建Java应用程序项目: gradle init --type java-application --dsl kotlin
说明 - 生成的app+lib项目过于冗余(buildSrc目录等),故可手动创建以下文件即可:
gradle\libs.versions.toml 空文件; 取值:libs.versions.xName.get()
单项目:
build.gradle.kts
defaultTasks("clean","run"); //defaultTasks("projects","properties");
plugins {
id("application") // quarkus应换为java + id("io.quarkus"),并移除application {...}
}
dependencies { // kotlin不支持单引号,必须双引号括住引用库;优先用platform引用bom库。
//implementation(platform("com.google.cloud:libraries-bom:26.51.0"))
//implementation(project(":lib-module"))
}
application {
mainClass = "org.example.app.App"
}
// 可选
tasks.withType {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
}
// java.sourceCompatibility = JavaVersion.VERSION_21
java { // 不写则当前JDK版本,targetCompatibility不写则用sourceCompatibility值。
sourceCompatibility = JavaVersion.VERSION_20
//targetCompatibility = JavaVersion.VERSION_21
} println(java.targetCompatibility) // Output: 20
tasks.wrapper {
gradleVersion = "latest" // distributionUrl 优先于 gradleVersion+distributionType
//distributionUrl = "https://tsc.openle.com/main/other/go-static/v1/our-webpage/default/staticfiles.openle.com_app-tools-gradle-latest"
//distributionType = Wrapper.DistributionType.BIN
}
多项目:
settings.gradle.kts
pluginManagement { // 【可选】插件仓库声明
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
System.setProperty("qv","3.12.1"); //println(System.getProperty("qv"));
plugins { // 插件版本限定,实际项目可省略或覆盖该version,也可决定不写不用该插件
id("io.quarkus") version System.getProperty("qv") // 子项目继承版本写法 id("io.quarkus")
}
}
dependencyResolutionManagement { // 【可选】库仓库声明
//repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
rootProject.name = "java-native-apps"
include("app") //include("app", "lib-module")
app\build.gradle.kts 同单项目脚本
Java
Java程序启动推荐参数(compilerArgs无GC传参) --enable-preview -XX:+UseZGC -XX:MaxRAMPercentage=70
--release是“--source --target --system”选项的组合简化;--source指可降级至老语法,--target指可生成高于前者版本的类文件;未指定则默认为当前JDK运行时版本。
JVM(含JVM业务应用)内存分配比例,可通过 JAVA_TOOL_OPTIONS=-XX:MaxRAMPercentage=75 参数来调整,若增大了JVM内存(默认25%),则会相应减少预留给OS组件的内存量。
BufferedInputStream用多占点内存(8KiB)来换取较少的读硬盘次数,比逐字节读的InputStream更省时间。
关键之处是不对底层流调用read()单字节读取,而是只进行多字节读取: fis.read(byte[] b, int off, int len)
实例:
try (var bis = new BufferedInputStream(new FileInputStream(file));
var bos = new BufferedOutputStream(new FileOutputStream(file))) {
var buf = new byte[1024];
int len; // read首个字节时bis会从磁盘读满8KiB数据,再读时会从较快的内存中读取。
while ((len = bis.read(buf)) != -1) {
bos.write(buf); // write会存满8KiB后才flush写入较慢的磁盘。
}
}
异常、无记录、null值的区别:
说明 - 添加和更新在同一个函数,可通过empty入参明确为true来决定新增记录,特别是外键表的修改,要优先于判断id。
public static SimpleEntry webContentKV(String name) {
String refId = null;
String valueContent = null;
try (var conn = DriverManager.getConnection(url, user, password); var sta = conn.prepareStatement("select RefId,ValueContent from web_content_kv_json where RefId = ?")) {
sta.setString(1, name); // index从1数起
ResultSet rs = sta.executeQuery();
while (rs.next()) {
refId = rs.getString("RefId");
valueContent = rs.getString("ValueContent");
//return rs.getString("r");
}
} catch (SQLException ex) {
ex.printStackTrace(System.err);
return null;
}
// 整体null即报错,key为null则无记录。
return new AbstractMap.SimpleEntry<>(refId, valueContent);
}
Android
WebView推荐配置:
WebView wv = findViewById(R.id.wv); // 用var则要多做个类型转换
wv.getSettings().setTextZoom(100); // 网页字体大小不受系统影响
wv.getSettings().setJavaScriptEnabled(true); // 执行JavaScript脚本
wv.setWebChromeClient(new WebChromeClient()); // 允许弹出JS对话框
// 避免点链接或301时跳出WebView:
// 若想控制更多,可重写WebViewClient内部shouldOverrideUrlLoading(v,r);
// 返回false会自动调用wv.loadUrl(url),并呈现在WebView内,返回true则会取消网址请求。
// 页面UA判断WebView环境:navigator.userAgent.indexOf("; wv)") > -1;
wv.setWebViewClient(new WebViewClient()); // WebViewClient默认返回false
//CookieManager.getInstance().setAcceptThirdPartyCookies(wv, true); // 允许第三方存取Cookie
//WebView.setWebContentsDebuggingEnabled(true); // 启用调试
Quarkus
注意 -
JSF子目录页面index.xhtml本地测试无法默认访问,但native构建部署后却能作为welcome页面访问。
Quarkus Gradle:
implementation(enforcedPlatform("io.quarkus.platform:quarkus-bom:3.10.0"))
implementation("io.quarkus:quarkus-vertx-http") // 必选扩展 - META-INF/resources/index.html
//implementation("io.quarkus:quarkus-undertow") // 可选支持Servlet 4.0
//implementation("io.quarkus:quarkus-resteasy") // 可选支持JAX-RS
Quarkus通过GCB构建:本地构建环境过于庞大,github actions构建则需要git提交,故首选本地gcloud+GCB方式。
appengineShowConfiguration 输出 cloudSdkHome = C:\Users\person\AppData\Local\google\ct4j-cloud-sdk\466.0.0\google-cloud-sdk
cd C:\Users\person\AppData\Local\google\ct4j-cloud-sdk\473.0.0\google-cloud-sdk
./bin/gcloud auth list
./bin/gcloud auth login --cred-file=D:\main\docs\cloud\project-123-8ad030ce3d3e.json
[永久] ./bin/gcloud config set project project-123
[临时] ./bin/gcloud app describe --project project-123
[写死Dockerfile名] ./bin/gcloud builds submit --project project-123 --region=asia-northeast1 --tag asia.gcr.io/project-123/app-image .
./bin/gcloud builds submit --project project-123 --region=asia-northeast1 --config=D:\main\person\projects\java-native-apps\app\gcb.yaml D:\main\person\projects\java-native-apps\
OpenLiberty
最小特性配置:
<?xml version="1.0" encoding="UTF-8"?>
<server>
<featureManager>
<!-- 定义 platform 后可不写版本,未定义则为 jakartaee-11.0,不识别 webProfile-10.0。 -->
<platform>jakartaee-11.0</platform>
<platform>microProfile-7.0</platform>
<feature>restfulWS</feature>
<feature>cdi-4.0</feature><!-- JAX-RS范围注解 -->
<feature>jsonb-3.0</feature><!-- 解决运行时报错Could not find MessageBodyWriter -->
</featureManager>
<!-- location 指定 war 文件,即 /config/apps/x.war 或 Gradle 时(无视变量${...})的 x/build/wlp/usr/servers/defaultServer/apps/app.war.xml
若不存在则回退至 webApplication 项未配 contextRoot 的 install_apps_configuration_1491924271.xml -->
<webApplication contextRoot="/" location="x.war"/>
<!-- 或 [gradle插件无视]自动解压 war 文件至 apps/expanded/ ,直接子目录有 WEB-INF/ 等 -->
<![CDATA[ Dockerfile 嵌入Java标准网站目录: ./gradle explodedWar
COPY --chown=1001:0 x/build/exploded/x.war/ /config/apps/expanded/ ]]>
<!-- <applicationManager autoExpand="true"/> -->
</server>
liberty { // 生产环境只认:COPY --chown=1001:0 x/src/main/liberty/config/jvm.options /config/
server.stripVersion = true // 避免忘修改 server.xml 中 webApplication 的 location 属性值版本号。
}
tasks["clean"].dependsOn("libertyStop")
反直觉:
执行gradle插件libertyRun时,x.war.xml 未将 ibm-web-bnd.xml 文件定向到/WEB-INF/,而是 targetInArchive="/WEB-INF/classes/",将不会启用虚拟主机配置。
若找不到 location 属性值 x.war 文件,则加 .xml 后缀 x.war.xml 再找一次,仍未找到则回退至默认生成的 - <webApplication id="x" location="x.war" name="x"/>
Chromium Embedded Framework (CEF)
Java CEF:
实例类MainFrame.java - github.com/jcefmaven/jcefsampleapp 自用实例 - MyName/Files/SourceCode/Other/JavaGUI/always-on-top-cef.zip
Google登录页屏蔽了CEF UA,故应重写User agent值:builder.getCefSettings().user_agent = "Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0";
Docker
docker仓库网址: repo/app 等同默认域名 docker.io/repo/app
Godot
由于文档可能包含内部敏感信息,故应放入带下划线前缀的目录 _docs/README.md ,Godot打包时会忽略之。