从此
📄文章 #️⃣专题 🌐酷站 👨‍💻技术 📺 📱

工作起步、环境初始化、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打包时会忽略之。