<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>yyy</title><description/><link>https://blog.oavax.com</link><item><title>Gitea Workflow 实践：通过镜像站加速 JDK 下载</title><link>https://blog.oavax.com/posts/gitea-actions-speed-up-jdk-download</link><guid isPermaLink="true">https://blog.oavax.com/posts/gitea-actions-speed-up-jdk-download</guid><description>针对 Gitea Actions 中 setup-java 下载 JDK 龟速的难题，本文提供了一套基于 Runner 缓存和镜像站的解决方案。</description><pubDate>Mon, 25 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;Gitea Actions 的 &lt;code&gt;actions/setup-java&lt;/code&gt; 在执行时，会根据指定的 Java 版本从 Adoptium (Temurin) 的 GitHub Releases 页面下载对应的 JDK。
在国内或网络受限的环境下，通常以超时告终。&lt;/p&gt;
&lt;h3&gt;原理：利用 Runner 缓存&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;actions/setup-java&lt;/code&gt; 的核心机制在于其缓存策略。在执行下载前，它会检查 Runner 的工具缓存目录（默认为 /opt/hostedtoolcache）中是否存在所需版本的 JDK。如果缓存命中，它将直接使用本地文件，完全跳过下载步骤。&lt;/p&gt;
&lt;p&gt;利用这一点：通过手动或自动化的方式，预先将指定版本的 JDK 下载、解压并放置到正确的缓存目录中。&lt;/p&gt;
&lt;h3&gt;方法一：手动预热缓存（命令行）&lt;/h3&gt;
&lt;p&gt;对于一次性或临时的需求，我们可以直接进入 Runner 所在的容器或主机，手动下载并设置缓存。这种方法简单直接，适合快速验证。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;进入 Runner 环境&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;docker run --rm -it -v act-toolcache:/cache alpine sh&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;执行下载和缓存脚本&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;在容器内，执行以下脚本。请将 TARGET_DIR 和 DOWNLOAD_URL 中的版本号和下载链接替换为你需要的 JDK 版本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. 安装必要的工具 
apk add --no-cache wget tar 

# 2. 定义目标路径和下载地址（以 Temurin JDK 21.0.8+9 为例） 
# 缓存路径格式为：/cache/&amp;lt;工具名&amp;gt;/&amp;lt;版本号&amp;gt;/&amp;lt;架构&amp;gt; 
TARGET_DIR=&quot;/cache/Java_Temurin-Hotspot_jdk/21.0.8-9.0.LTS/x64&quot;
DOWNLOAD_URL=&quot;https://ghfast.top/https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz&quot; 

# 3. 创建目录、下载并解压 
mkdir -p &quot;${TARGET_DIR}&quot;
cd /tmp

echo &quot;Downloading from ${DOWNLOAD_URL}...&quot;
wget -q -O tool.tar.gz &quot;${DOWNLOAD_URL}&quot;

# --strip-components=1 用于去除压缩包内的顶层目录 
tar -xzf tool.tar.gz -C &quot;${TARGET_DIR}&quot; --strip-components=1 

# 4. 创建完成标记文件（重要！） 
# setup-java 通过检查此文件判断缓存是否完整 
touch &quot;${TARGET_DIR}.complete&quot; 

# 5. 清理临时文件 
rm tool.tar.gz 
echo &quot;JDK cache created successfully at ${TARGET_DIR}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法二：使用 workflow_dispatch 自动化缓存&lt;/h3&gt;
&lt;p&gt;手动操作虽然直接，但难以维护和扩展。更优雅的方式是创建一个可手动触发的 Gitea Workflow，让 Runner 自己完成缓存的下载和设置。workflow_dispatch 事件允许我们从 Gitea 界面手动触发工作流，并传入参数（如 JDK 版本），非常适合此类管理任务。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Runner Tool Cache Seeder
run-name: Seeding cache for ${{ inputs.tool_type }} version ${{ inputs.tool_version }}

env:
  # 使用镜像站代理，可替换为你自己的代理
  github_proxy: https://ghfast.top/

on:
  workflow_dispatch:
    inputs:
      tool_type:
        description: &apos;要缓存的工具类型 (目前支持 temurin-jdk)&apos;
        required: true
        type: choice
        options:
          - temurin-jdk
      tool_version:
        description: &apos;要缓存的工具确切版本 (例如: 21.0.8+9.0.LTS, 21.0.8+9, 17.0.11+9)&apos;
        required: true
        type: string

jobs:
  seed-cache:
    runs-on: ubuntu-latest # 可以指定你的 Runner 标签
    steps:
      - name: Seed Tool Cache
        shell: bash
        run: |
          set -e # 若有命令失败，立即退出
          
          GITHUB_PROXY=&quot;${{ env.github_proxy }}&quot;
          TOOL_TYPE=&quot;${{ inputs.tool_type }}&quot;
          TOOL_VERSION=&quot;${{ inputs.tool_version }}&quot;
          TOOL_CACHE_DIR=&quot;/opt/hostedtoolcache&quot; # Runner 的工具缓存目录
          
          echo &quot;Attempting to cache ${TOOL_TYPE} version ${TOOL_VERSION}...&quot;
          
          # 自动检测系统架构
          ARCH=$(uname -m)
          case &quot;$ARCH&quot; in
            x86_64)   ARCH_NAME=&quot;x64&quot; ;;
            aarch64)  ARCH_NAME=&quot;aarch64&quot; ;;
            arm64)    ARCH_NAME=&quot;aarch64&quot; ;;
            *)
            echo &quot;Unsupported architecture: $ARCH&quot; &amp;amp;&amp;amp; exit 1 ;;
          esac
          
          LTS_SUFFIX=&quot;&quot;
          # 根据 Adoptium 的命名规则，LTS 版本有特定后缀
          if [[ &quot;${TOOL_VERSION}&quot; == *&quot;.LTS&quot; ]]; then
            LTS_SUFFIX=&quot;.0.LTS&quot;
          fi
          
          # 解析版本号以构建正确的下载链接和缓存路径
          MAIN_VERSION=${TOOL_VERSION%%.*}         # 主版本, e.g., &quot;21&quot;
          VERSION_PART=${TOOL_VERSION%+*}          # 版本部分, e.g., &quot;21.0.8&quot;
          AFTER_PLUS=${TOOL_VERSION#*+}            # `+`号后的部分, e.g., &quot;9&quot; or &quot;9.0.LTS&quot;
          BUILD_PART=${AFTER_PLUS%%.*}              # 构建号, e.g., &quot;9&quot;
          
          # 拼接缓存目标路径
          TARGET_DIR=&quot;${TOOL_CACHE_DIR}/Java_Temurin-Hotspot_jdk/${VERSION_PART}-${BUILD_PART}${LTS_SUFFIX}/${ARCH_NAME}&quot;
          
          if [ -f &quot;${TARGET_DIR}.complete&quot; ]; then
            echo &quot;Java version ${TOOL_VERSION} for ${ARCH_NAME} is already cached. Skipping.&quot;
            exit 0
          fi
          
          # 拼接下载 URL
          # 示例: https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz
          
          DOWNLOAD_URL=&quot;${GITHUB_PROXY}https://github.com/adoptium/temurin${MAIN_VERSION}-binaries/releases/download/jdk-${VERSION_PART}%2B${BUILD_PART}/OpenJDK${MAIN_VERSION}U-jdk_${ARCH_NAME}_linux_hotspot_${VERSION_PART}_${BUILD_PART}.tar.gz&quot;
          
          echo &quot;Target cache directory: ${TARGET_DIR}&quot;
          echo &quot;Download URL: ${DOWNLOAD_URL}&quot;

          mkdir -p &quot;${TARGET_DIR}&quot;
          cd /tmp
          
          echo &quot;Downloading tool...&quot;
          wget -q -O tool.tar.gz &quot;${DOWNLOAD_URL}&quot;
          
          echo &quot;Extracting tool into cache directory...&quot;
          tar -xzf tool.tar.gz -C &quot;${TARGET_DIR}&quot; --strip-components=1
          
          echo &quot;Creating completion file...&quot;
          touch &quot;${TARGET_DIR}.complete&quot;
          
          rm tool.tar.gz
          echo &quot;Successfully cached ${TOOL_TYPE} version ${TOOL_VERSION} for ${ARCH_NAME}.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;使用说明&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;将以上 YAML 文件保存到你的 Gitea 仓库的 .gitea/workflows/ 目录下（例如 cache-seeder.yaml）。&lt;/li&gt;
&lt;li&gt;进入 Gitea 项目的 &lt;strong&gt;Actions&lt;/strong&gt; 页面。&lt;/li&gt;
&lt;li&gt;在左侧工作流列表中找到 &quot;Runner Tool Cache Seeder&quot;。&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Run workflow&lt;/strong&gt; 按钮。&lt;/li&gt;
&lt;li&gt;在弹出的表单中，选择工具类型（temurin-jdk）并输入你想要缓存的确切 JDK 版本（如 21.0.8+9）。&lt;/li&gt;
&lt;li&gt;点击绿色的 &lt;strong&gt;Run workflow&lt;/strong&gt; 按钮执行。&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>Bark 加密服务</title><link>https://blog.oavax.com/posts/bark-encrypt-service</link><guid isPermaLink="true">https://blog.oavax.com/posts/bark-encrypt-service</guid><description>用 Bark 推送通知很方便，但如果内容包含密码、API 密钥这类敏感信息，直接明文发送总会有些不放心。</description><pubDate>Sun, 27 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;背景&lt;/h3&gt;
&lt;p&gt;用 Bark 推送通知很方便，但如果内容包含密码、API 密钥这类敏感信息，直接明文发送总会有些不放心。&lt;/p&gt;
&lt;p&gt;Bark 本身不支持加密，但在自动化脚本或集成的第三方服务中，实现这一加密步骤通常较为复杂。为了解决这个痛点，用 Golang 写了个加密转发器，只需要把消息发给它，它就会自动帮你加密好再发给 Bark，省心又安全。&lt;/p&gt;
&lt;h3&gt;工作原理&lt;/h3&gt;
&lt;p&gt;这个服务本质上是一个反向代理，它会拦截发往特定路径（如 /push-ciphertext）的请求，提取其中的 JSON 数据，使用预设的密钥（BARK_AES_KEY）和请求中提供的初始化向量（iv）对 body 字段进行 AES-CBC 加密，然后将加密后的数据包重新组装，发送给真正的 Bark 服务器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代码实现：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package main

import (
	&quot;bytes&quot;
	&quot;crypto/aes&quot;
	&quot;crypto/cipher&quot;
	&quot;encoding/base64&quot;
	&quot;encoding/json&quot;
	&quot;io&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;net/url&quot;
	&quot;os&quot;
	&quot;strings&quot;
)

func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

func encrypt(plainText, key, iv []byte) ([]byte, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	plainText = PKCS7Padding(plainText, block.BlockSize())
	blockMode := cipher.NewCBCEncrypter(block, iv)
	encrypted := make([]byte, len(plainText))
	blockMode.CryptBlocks(encrypted, plainText)
	return encrypted, nil
}

type RequestData struct {
	DeviceKey string `json:&quot;device_key&quot;`
	Iv        string `json:&quot;iv&quot;`
}

func handler(w http.ResponseWriter, r *http.Request, aesKey []byte, barkDomain string) {
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(r.Body)

	if r.Method != http.MethodPost {
		http.Error(w, &quot;Only POST method is allowed&quot;, http.StatusMethodNotAllowed)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, &quot;Failed to read request body&quot;, http.StatusInternalServerError)
		return
	}

	var data RequestData
	if err := json.Unmarshal(body, &amp;amp;data); err != nil {
		http.Error(w, &quot;Invalid JSON&quot;, http.StatusBadRequest)
		return
	}
	deviceKey := data.DeviceKey
	iv := data.Iv

	encrypted, err := encrypt(body, aesKey, []byte(iv))
	if err != nil {
		http.Error(w, &quot;Encryption failed&quot;, http.StatusInternalServerError)
		return
	}
	ciphertextBase64 := base64.StdEncoding.EncodeToString(encrypted)

	if len(barkDomain) &amp;lt;= 0 {
		data := map[string]interface{}{
			&quot;ciphertext&quot;: ciphertextBase64,
			&quot;iv&quot;:         iv,
		}

		err := json.NewEncoder(w).Encode(data)
		if err != nil {
			http.Error(w, &quot;Error encoding JSON response&quot;, http.StatusInternalServerError)
			return
		}
		return
	}

	formData := url.Values{}
	formData.Set(&quot;ciphertext&quot;, ciphertextBase64)
	formData.Set(&quot;iv&quot;, iv)

	targetURL := barkDomain + &quot;/&quot; + deviceKey
	resp, err := http.Post(targetURL, &quot;application/x-www-form-urlencoded&quot;, strings.NewReader(formData.Encode()))
	if err != nil {
		http.Error(w, &quot;Failed to forward request to Bark API&quot;, http.StatusBadGateway)
		return
	}
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	log.Printf(&quot;Forwarded request for deviceKey ending in ...%s, status: %d&quot;, deviceKey[len(deviceKey)-4:], resp.StatusCode)

	w.WriteHeader(resp.StatusCode)
	_, _ = io.Copy(w, resp.Body)
}

func main() {
	aesKeyStr := os.Getenv(&quot;BARK_AES_KEY&quot;)
	if len(aesKeyStr) != 16 {
		log.Fatal(&quot;Fatal: BARK_AES_KEY environment variable not set or not 16 characters long.&quot;)
	}

	barkDomain := os.Getenv(&quot;BARK_DOMAIN&quot;)

	aesKeyBytes := []byte(aesKeyStr)

	http.HandleFunc(&quot;/push-ciphertext&quot;, func(w http.ResponseWriter, r *http.Request) {
		handler(w, r, aesKeyBytes, barkDomain)
	})

	log.Println(&quot;Starting encryption service on :9090&quot;)
	if err := http.ListenAndServe(&quot;:9090&quot;, nil); err != nil {
		log.Fatalf(&quot;Failed to start server: %v&quot;, err)
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;反向代理加密服务&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;bark.example.com {
        encode zstd gzip 

        # (可选) 为你的服务增加一层基础认证，防止被滥用
        basic_auth {
                user xxxx
        }

		# 处理加密请求的路由, 所有发往 /push-ciphertext 的请求，都转发给加密服务
        handle /push-ciphertext* {
                reverse_proxy bark-encrypt:9090
        }

        # 处理普通未加密请求的路由 (可选), 这样你的域名也能兼容普通的 Bark 请求
        handle {
                reverse_proxy bark-server:8080
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用&lt;/h3&gt;
&lt;p&gt;配置环境变量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BARK_AES_KEY=1234567890123456 # 自行替换
BARK_DOMAIN=bark.example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;路径 &lt;code&gt;push&lt;/code&gt; 改为 &lt;code&gt;push-ciphertext&lt;/code&gt; 参数加上&lt;code&gt;iv&lt;/code&gt; 则可加密发送&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST --location &quot;https://user:password@bark.example.com/push-ciphertext&quot; \
    -H &quot;Content-Type: application/json&quot; \
    -d &apos;{
          &quot;title&quot;: &quot;test&quot;,
          &quot;device_key&quot;: &quot;xxx&quot;,
          &quot;sound&quot;: &quot;newsflash&quot;,
          &quot;iv&quot;: &quot;1234567890123456&quot;
        }&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注：这里假设你启用了 basic_auth，所以 URL 中包含了 user:password@。如果没启用，可以去掉。&lt;/p&gt;
&lt;h3&gt;参考&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bark.day.app/#/encryption&quot;&gt;Bark 官方加密文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;感谢 Bark 开源，也欢迎大家试用我的小工具！&lt;/p&gt;
</content:encoded></item><item><title>公交事件</title><link>https://blog.oavax.com/posts/bus-incident</link><guid isPermaLink="true">https://blog.oavax.com/posts/bus-incident</guid><description>公交事件</description><pubDate>Tue, 15 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;夜幕笼罩下的热门景点外，早已水泄不通。出租车踪影难觅，公交站台上，攒动的人头黑压压一片，焦灼弥漫在闷热的空气里。&lt;/p&gt;
&lt;p&gt;姗姗来迟的公交车，如同绝望中的一根救命稻草。人群瞬间沸腾，蜂拥而上。最后挤上车的是一对老夫妇，拖着行李箱，带着一个约莫八岁的孙子。一个年轻小伙觑见还有一丝空隙，也奋力挤了上去。&lt;/p&gt;
&lt;p&gt;车厢瞬间饱和。小伙紧挨着前门第一个座位站定。老妇人则带着孙子，勉强挤在稍后位置。&lt;/p&gt;
&lt;p&gt;公交车如负重的老牛，龟速前行。抵达下一站时，下车者寥寥无几，而上车的人潮却汹涌依旧。新乘客全被堵在车门处，动弹不得。司机站起身，一遍遍机械地吆喝：“往里走一走，里面还挺空！往里走一走，里面还挺空…！”&lt;/p&gt;
&lt;p&gt;小伙闻声，立刻扭头向后张望——确实有空间！他心急火燎地试图挤过去，示意挡在前方的老妇人让道。不知是对方反应迟缓还是不愿挪动，就在这电光火石的一瞬，小伙猛地一窜，为了通过这狭窄的缝隙，他下意识地侧过身子，殊不知，他背上那个斜挎包，正不偏不倚地，挤压在了孩子的下巴上。&lt;/p&gt;
&lt;p&gt;“啊——！啊啊啊——！”一声撕心裂肺的童音骤然响起，瞬间压过了车厢所有嘈杂。众人目光齐刷刷聚焦——只见老妇人身旁的孩子，正紧紧捂住下巴，小脸痛苦地皱成一团。&lt;/p&gt;
&lt;p&gt;车厢陷入死寂。&lt;/p&gt;
&lt;p&gt;“你干什么呀！”老妇人又惊又怒，矛头直指小伙，“挤到孩子了！这么小的孩子，挤倒了就要踩过去了啊？！”&lt;/p&gt;
&lt;p&gt;小伙也急了：“后面空着，我让你们往后走你们不动啊！我也没看到这孩子！”&lt;/p&gt;
&lt;p&gt;“没看见？没看见就能硬挤啊？有个三长两短怎么办！”老妇人不依不饶。&lt;/p&gt;
&lt;p&gt;“我又不是故意的！我叫你让让了！”小伙争辩着。&lt;/p&gt;
&lt;p&gt;几轮交锋，火药味渐浓。后座一位大爷看不下去了，低声示意小伙：“行了行了，孩子没什么事，赶紧道个歉吧。”&lt;/p&gt;
&lt;p&gt;小伙深吸一口气，对着孩子方向连声道：“对不起，对不起！真不是故意的，实在没看到。”&lt;/p&gt;
&lt;p&gt;老妇人依旧对着周围的乘客诉絮叨着不满，声音却小了下去。&lt;/p&gt;
&lt;p&gt;这时，靠近车厢后门乘客开口了：“来，小朋友，别挤在过道中间了，太危险。靠着这扶手，稳当点。” 他一边说，一边侧了侧身，腾出旁一小块相对稳固的“安全区”，并用手指了指。&lt;/p&gt;
&lt;p&gt;老妇人一愣，脸上紧绷的线条松弛下来。她连声道谢，拉着孙子的小手，把他安置在了那个安全角落。孩子靠着冰凉的金属扶手，小声地抽噎着。&lt;/p&gt;
&lt;p&gt;公交车再次启动，车厢里先前凝固的空气仿佛重新开始流动，人们的窃窃私语也变成了正常的交谈声。&lt;/p&gt;
&lt;p&gt;小伙挤着往车厢后方挪去，低着头，耳根发烫。他远远望着那个孩子，心里五味杂陈，既有愧疚，也有一丝被陌生人的善意所触动的温暖。他没再出声，只是默默地看着窗外流光溢彩的夜景。公交车晃晃悠悠，载着一车厢的疲惫、争执与和解，继续在都市的夜色中平稳前行。&lt;/p&gt;
</content:encoded></item><item><title>Drone 部署 Spring boot 服务多环境配置</title><link>https://blog.oavax.com/posts/drone-deploying-springboot</link><guid isPermaLink="true">https://blog.oavax.com/posts/drone-deploying-springboot</guid><pubDate>Thu, 25 Aug 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;环境准备&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Docker + Docker-Compose&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gitea.com/&quot;&gt;Gitea&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.drone.io/&quot;&gt;Drone&lt;/a&gt;  + &lt;a href=&quot;https://www.drone-runner.com/&quot;&gt;Drone-Runner&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sonatype.com/products/nexus-repository&quot;&gt;Nexus&lt;/a&gt; Maven 仓库&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://goharbor.io/&quot;&gt;Harbor&lt;/a&gt; 镜像仓库（也可以直接使用 Nexus）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;流程&lt;/h2&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt; 流程图 &amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt; mermaid &amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graph TB

st(开始)
e(结束)

st--&amp;gt;push(提交代码)
st--&amp;gt;tag(提交 Tag)
push--&amp;gt;op1(Maven 打包项目 生成 Jar 包文件)
tag--&amp;gt;op1
op1--&amp;gt;condService(是否是服务)
condService--&amp;gt;|否|sub1(发布 Jar 包到私服仓库)
condService--&amp;gt;|是|condTag(&quot;构建发布镜像&quot;)
condTag--&amp;gt;|是|op2(发布 Git 版本)
condTag--&amp;gt;|否|op3(拷贝 docker-compose.yml 到目标服务器)
op2--&amp;gt;op3
op3--&amp;gt;op4(连接目标服务器运行)

sub1--&amp;gt;e
op4--&amp;gt;e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;h2&gt;环境配置&lt;/h2&gt;
&lt;h3&gt;Maven 配置&lt;/h3&gt;
&lt;p&gt;开发过程中可能回有多个 Java 版本的情况，以及会使用一些代理仓库加速下载依赖，构建一些内部常用的 Maven 环境&lt;/p&gt;
&lt;p&gt;本文以 eclipse-temurin-8-alpine 为例，&lt;a href=&quot;https://github.com/carlossg/docker-maven&quot;&gt;GitHub 查看更多版本&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;下载 &lt;code&gt;Dockerfile&lt;/code&gt;，&lt;code&gt;mvn-entrypoint.sh&lt;/code&gt; 文件，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget https://raw.githubusercontent.com/carlossg/docker-maven/master/eclipse-temurin-8-alpine/Dockerfile
wget https://raw.githubusercontent.com/carlossg/docker-maven/master/eclipse-temurin-8-alpine/mvn-entrypoint.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;settings.xml&lt;/code&gt; 添加自定义设置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;settings xmlns=&quot;http://maven.apache.org/SETTINGS/1.0.0&quot;
          xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
          xsi:schemaLocation=&quot;http://maven.apache.org/SETTINGS/1.0.0
                      https://maven.apache.org/xsd/settings-1.0.0.xsd&quot;&amp;gt;
    &amp;lt;localRepository&amp;gt;/usr/share/maven/ref/repository&amp;lt;/localRepository&amp;gt;

    &amp;lt;servers&amp;gt;
        &amp;lt;server&amp;gt;
            &amp;lt;id&amp;gt;private-repo&amp;lt;/id&amp;gt; &amp;lt;!-- 仓库 ID，与 pom.xml 中配置的 repository id 匹配 --&amp;gt;
            &amp;lt;username&amp;gt;your-username&amp;lt;/username&amp;gt;
            &amp;lt;password&amp;gt;your-password&amp;lt;/password&amp;gt;
        &amp;lt;/server&amp;gt;
    &amp;lt;/servers&amp;gt;
    &amp;lt;mirrors&amp;gt;
        &amp;lt;mirror&amp;gt;
            &amp;lt;id&amp;gt;mirror-repo&amp;lt;/id&amp;gt;
            &amp;lt;mirrorOf&amp;gt;*&amp;lt;/mirrorOf&amp;gt;
            &amp;lt;name&amp;gt;mirror repo&amp;lt;/name&amp;gt;
            &amp;lt;url&amp;gt;http://nexus.domain.com/repository/maven-mirror-repo/&amp;lt;/url&amp;gt;
        &amp;lt;/mirror&amp;gt;
    &amp;lt;/mirrors&amp;gt;
    &amp;lt;profiles&amp;gt;
        &amp;lt;profile&amp;gt;
            &amp;lt;id&amp;gt;private-repo-profile&amp;lt;/id&amp;gt;
            &amp;lt;repositories&amp;gt;
                &amp;lt;repository&amp;gt;
                    &amp;lt;id&amp;gt;private-repo&amp;lt;/id&amp;gt; &amp;lt;!-- 仓库 ID，与 servers 中的 id 匹配 --&amp;gt;
                    &amp;lt;url&amp;gt;http://nexus.domain.com/repository/maven-private-repo/&amp;lt;/url&amp;gt;
                    &amp;lt;releases&amp;gt;&amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;&amp;lt;/releases&amp;gt;
                    &amp;lt;snapshots&amp;gt;&amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;&amp;lt;/snapshots&amp;gt;
                &amp;lt;/repository&amp;gt;
            &amp;lt;/repositories&amp;gt;
        &amp;lt;/profile&amp;gt;
    &amp;lt;/profiles&amp;gt;
    &amp;lt;activeProfiles&amp;gt;
        &amp;lt;activeProfile&amp;gt;private-repo-profile&amp;lt;/activeProfile&amp;gt;
    &amp;lt;/activeProfiles&amp;gt;
&amp;lt;/settings&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 &lt;code&gt;Dockerfile&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM eclipse-temurin:8-jdk-alpine

# 国内网络问题，使用 aliyun 源
RUN sed -i &apos;s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g&apos; /etc/apk/repositories

RUN apk add --no-cache curl tar bash procps

# ...

COPY mvn-entrypoint.sh /usr/local/bin/mvn-entrypoint.sh
# 添加自定义配置文件
COPY settings.xml /usr/share/maven/conf/

ENTRYPOINT [&quot;/usr/local/bin/mvn-entrypoint.sh&quot;]
CMD [&quot;mvn&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打包成镜像&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker build -t ci/maven:8-jdk .
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置部署模板文件&lt;/h3&gt;
&lt;p&gt;生成 Dockerfile，docker-compose.yml 等文件相关脚本，可以更具自己实际需求更改&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dockerfile.tmpl&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM &amp;lt;% ${FROM_IMAGE} %&amp;gt;

ADD &amp;lt;% ${SERVICE_JAR} %&amp;gt; app.jar

ENTRYPOINT [&quot;java&quot;, &quot;-Duser.timezone=GMT+08&quot;,&quot;-jar&quot;, &quot;/app.jar&quot; &amp;lt;% ${SERVICE_PARAMS} %&amp;gt; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;docker-compose.yml.tmpl&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3&apos;
services:
  &amp;lt;% ${SERVICE_NAME} %&amp;gt;:
    restart: always
    image: &amp;lt;% ${SERVICE_IMAGE_NAME} %&amp;gt;:&amp;lt;% ${vTag} %&amp;gt;
    container_name: &amp;lt;% ${SERVICE_NAME} %&amp;gt;
    ports: # - &amp;lt;% ${SERVICE_PORT} %&amp;gt;:&amp;lt;% ${SERVICE_PORT} %&amp;gt; 可能多个情况使用sed插入
    deploy:
      resources:
        limits:
          memory: &amp;lt;% ${SERVICE_MEMORY} %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;convert_param.sh&lt;/code&gt;：构造 Dockerfile 中 SERVICE_PARAMS 参数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
params=&quot;&quot;
d=&quot;,&quot;
paramCount=$#
for (( i = 1; i &amp;lt;= paramCount; i++ )); do
    params=&quot;$params $d \\&quot;\&quot;${!i}\\&quot;\&quot;&quot;
done
echo &quot;$params&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;template.sh&lt;/code&gt;：使用环境参数生成文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh
EOF=EOF
exec cat &amp;lt;&amp;lt;EOF | sh
cat &amp;lt;&amp;lt;EOF
$(cat $1 | \
    sed &apos;s|`|\\`|g&apos; | \
    sed &apos;s|\$|\\\$|g&apos; | \
    sed &quot;s|${OPEN:-&amp;lt;%}|\`eval echo |g&quot; | \
    sed &quot;s|${CLOSE:-%&amp;gt;}|\`|g&quot;)
$EOF
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看 
sh template.sh Dockerfile.tmpl
# 使用生成
sh template.sh Dockerfile.tmpl &amp;gt; Dockerfile
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置 Drone 模板&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;mvn-deploy.yml&lt;/code&gt;：Maven 打包私服模板，工具类等不需要部署的项目&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kind: pipeline # 定义对象类型，还有secret和signature两种类型
type: docker # 定义流水线类型，还有kubernetes、exec、ssh等类型
name: default # 定义流水线名称

steps: # 定义流水线执行步骤，这些步骤将顺序执行
  - name: mvn-deploy # 流水线名称
    pull: if-not-exists
    image: {{ .input.buildImage }} # 定义创建容器的Docker镜像
    network_mode: host
    volumes: # 将容器内目录挂载到宿主机，仓库需要开启Trusted设置
      - name: maven-cache
        path: /usr/share/maven/ref/repository # 这个需要 Maven settings.xml 中一致
    commands:
      - mvn clean deploy

volumes: # 定义流水线挂载目录，用于共享数据
  - name: maven-cache
    host:
      path: /data/maven/cache # 主机目录缓存依赖

trigger:
  event:
    - push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mvn-build-push-image-run.yaml&lt;/code&gt;：Maven 打包部署模板&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kind: pipeline
type: docker
name: mvn-build-push-image-run-start
steps:
  - name: mvn-build
    pull: if-not-exists
    image: {{ .input.buildImage }}
    network_mode: host
    volumes:
      - name: maven-cache
        path: /usr/share/maven/ref/repository
      - name: ci-env
        path: /app/ci-env
    commands: # 定义在Docker容器中执行的shell命令
      - mvn clean package
      - rm target/*sources.jar &amp;amp;&amp;amp; cp target/*.jar . &amp;amp;&amp;amp; cp /app/ci-env/* .
      - chmod +x template.sh convert_param.sh
      - export vTag=`ls *.jar | awk -F {{ .input.name }}- &apos;{print $2}&apos; | awk -F .jar &apos;{print $1}&apos;`
      - export SERVICE_JAR=`ls *.jar`
      - export SERVICE_PARAMS=`./convert_param.sh {{ range .input.deploy.params }} {{ . }}{{ end }}`
      - sh template.sh Dockerfile.tmpl &amp;gt; Dockerfile
      - sh template.sh docker-compose.yml.tmpl &amp;gt; docker-compose.yml
      - sed -i &apos;5i\{{ range .input.ports }}EXPOSE {{ . }}\n{{ end }}&apos; Dockerfile
      - sed -i &apos;8i\{{ range .input.ports }}      - {{ . }}:{{ . }}\n{{ end }}&apos; docker-compose.yml
      - echo -n $vTag,latest &amp;gt; .tags
    environment:
      FROM_IMAGE: {{ .input.deploy.fromImage }}
      SERVICE_NAME: {{ .input.name }}
      SERVICE_MEMORY: {{ .input.memory }}
      SERVICE_IMAGE_NAME: {{ .input.image.registry }}/{{ .input.image.name }}

  - name: push-image
    pull: if-not-exists
    image: plugins/docker
    network_mode: host
    settings:
      registry: {{ .input.image.registry }} # if not provided index.docker.io is supposed
      repo: {{ .input.image.registry }}/{{ .input.image.name }}
      cache: true
      username:
        from_secret: {{ .input.image.docker_username_key }}
      password:
        from_secret: {{ .input.image.docker_password_key }}

  - name: gitea release
    image: plugins/gitea-release
    pull: if-not-exists
    network_mode: host
    settings:
      api_key:
        from_secret: git_release
      prerelease: true
      base_url: https://git.domain.com
      title: ${DRONE_TAG}
      files:
        - ./*.jar
        - ./*docker-compose.yml
        - ./*Dockerfile
      checksum:
        - md5
        - sha1
    when:
      event: tag

  - name: scp files
    image: appleboy/drone-scp
    pull: if-not-exists
    settings:
      host: dev.domain.com # 远程连接地址
      port: 22 # 远程连接端口
      username: root # 远程连接账号
      password:
        from_secret: dev_password # 从Secret中读取SSH密码
      target: {{ .input.workdir }}
      overwrite: true
      source:
        - docker-compose.yml

  - name: run-start
    pull: if-not-exists
    network_mode: host
    image: appleboy/drone-ssh # SSH工具镜像
    settings:
      host: dev.domain.com # 远程连接地址
      port: 22 # 远程连接端口
      username: root # 远程连接账号
      password:
        from_secret: dev_password # 从Secret中读取SSH密码
      command_timeout: 5m # 远程执行命令超时时间
      envs:
        - DOCKER_USERNAME
        - DOCKER_PASSWORD
      script:
        - cd {{ .input.workdir }}
        - echo &quot;$DOCKER_PASSWORD&quot; | docker login {{ .input.image.registry }} -u $DOCKER_USERNAME --password-stdin
        - docker-compose down &amp;amp;&amp;amp; docker-compose pull &amp;amp;&amp;amp; docker-compose up -d
    environment:
      DOCKER_USERNAME:
        from_secret: {{ .input.image.docker_username_key }}
      DOCKER_PASSWORD:
        from_secret: {{ .input.image.docker_password_key }}

volumes: # 定义流水线挂载目录，用于共享数据
  - name: maven-cache
    host:
      path: /data/maven/cache
  - name: ci-env
    host:
      path: /data/ci-env

trigger:
  event:
    - push
    - tag
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;编写 &lt;code&gt;.drone.yml&lt;/code&gt; 脚本&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;kind: template
load: mvn-build-push-image-run.yaml
name: mvn-build-push-image-run
data:
  name: drone-demo # 这里需要注意需要和 Jar 包名一致，drone-demo-0.0.1.jar
  ports:
    - 8080
    # - 9090
  memory: 1G
  workdir: /data/service/drone-demo
  buildImage: ci/maven:8-jdk

  image:
    registry: harbor.domain.com
    name: service/drone-demo
    # docker 用户名密码前缀 需要 secrets 添加 xxx_docker_username
    docker_username_key: docker_username
    docker_password_key: docker_password
  # Dockerfile 配置
  deploy:
    fromImage: eclipse-temurin:8-jdk
    params:
      - --spring.profiles.active=dev
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Idea 常用设置及插件</title><link>https://blog.oavax.com/posts/idea-setting</link><guid isPermaLink="true">https://blog.oavax.com/posts/idea-setting</guid><description>Idea Java 开发常用设置</description><pubDate>Wed, 10 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Java 开发常用设置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;提示忽略大小写&lt;/strong&gt;     &lt;strong&gt;path&lt;/strong&gt;: File | Settings | Editor | General | Code Completion&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[x] Mach case&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自动导入包&lt;/strong&gt;   &lt;strong&gt;path&lt;/strong&gt;: File | Settings | Editor | General | Auto Import&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[x] Add unambiguous imports on the fly&lt;/li&gt;
&lt;li&gt;[x] Optimize imports on the fly&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关闭标签页显示Tabs (简洁 一般使用 ctrl + e 打开最近文件)&lt;/strong&gt;
&lt;strong&gt;path&lt;/strong&gt;: File | Settings | Editor | General | Editor Tabs&lt;/p&gt;
&lt;p&gt;Tab pLacement: None&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;&lt;em&gt;设置超过指定 import 个数，改为 * (不推荐使用&lt;/em&gt;)&lt;/em&gt;*
&lt;strong&gt;path&lt;/strong&gt;: File | Settings | Editor | Code Style | Java | Imports&lt;/p&gt;
&lt;p&gt;CLass count to use import with * : 99
Names count to use static import with * : 99&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;设置项目文件编码&lt;/strong&gt;(通常UTF-8)  &lt;strong&gt;path&lt;/strong&gt;: File | Settings | Editor | File Encodings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;启动选择项目打开&lt;/strong&gt;  &lt;strong&gt;path&lt;/strong&gt;: File | Settings | Appearance &amp;amp; Behavior | System Settings&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Reopen projects on startup&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;常用插件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/15740-stopcoding&quot;&gt;Stopcoding&lt;/a&gt; 防止沉迷于代码无法自拔&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/4441-jrebel-and-xrebel-for-intellij&quot;&gt;JRebel&lt;/a&gt;  热部署插件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/10292-restfultoolkit&quot;&gt;RestfulToolkit&lt;/a&gt; 一套 RESTful 服务开发辅助工具集&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/2162-string-manipulation&quot;&gt;String Manipulation&lt;/a&gt; 字符串操作: 大小写切换，排序，过滤，递增，对齐列，grepping，转义，编码...&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/8579-translation&quot;&gt;Translation&lt;/a&gt; 翻译&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7125-grep-console&quot;&gt;Grep Console&lt;/a&gt;  控制台不同级别 log 的字体颜色和背景色&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://plugins.jetbrains.com/plugin/7275-codeglance&quot;&gt;CodeGlance&lt;/a&gt;  类似于 Sublime 编辑区缩略图、&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>