小不的笔记

时间之外的往事

项目实体状态管理说明文档

1. 简介

本项目的实体数据管理采用双状态模式:草稿(Draft)与快照(Snapshot)。
这种模式能够在保证数据版本可追溯的同时,为业务方提供灵活的编辑与发布流程。
核心思想:草稿用于编辑,快照用于发布与留存历史版本。


2. 实体结构

  • 继承关系
    所有实体均继承自 AirBeanBase 基类。
  • 分类层级
    分类按从小到大顺序排列:moduledomainprogramproduct
  • 状态对象
    • 草稿(Draft):编辑中的版本
    • 快照(Snapshot):发布后的版本,可作为历史记录保存

3. 状态说明

状态 说明
草稿 Draft 编辑中的实体版本,可随时修改;可以发布为快照
快照 Snapshot 已发布版本,不可直接修改(只能通过草稿再次发布)
RELEASED 草稿成功发布为快照
UPDATED 已发布的草稿被修改
DELETED 已发布的草稿被标记删除,待再次发布时彻底删除

4. 数据存储规则

  1. 表命名
    • 草稿表:xx_draft(如 tb_program_draft
    • 快照表:xx(如 tb_program
  2. ID 规则
    • 草稿 ID:dxxxxxxxx
    • 快照 ID:sxxxxxxxx
  3. Branch 规则
    • 草稿的 branch 值 = 草稿的 id
    • 快照的 branch 值 = 对应草稿的 id
  4. Previous 规则
    • 草稿的 previous 值 = 最新快照的 ID(便于版本追踪)

5. 状态流转规则

  1. 发布(Release)
    • 将草稿生成快照
    • 草稿状态变为 RELEASED
    • 一份草稿可以生成多份快照
  2. 修改(Update)
    • 修改已发布的草稿时,状态变为 UPDATED
  3. 删除(Delete)
    • 从未发布的草稿:直接删除
    • 已发布的草稿:状态变为 DELETED,再次发布时彻底删除

6. 示例场景

场景 1:首次发布

  1. 创建草稿(ID = d0001
  2. 发布 → 生成快照(ID = s1001branch = d0001

场景 2:修改已发布的草稿

  1. 草稿(d0001)状态从 RELEASEDUPDATED
  2. 再次发布 → 生成新快照(ID = s1002branch = d0001

场景 3:删除未发布的草稿

  1. 创建草稿(ID = d0002
  2. 删除 → 数据直接物理删除

场景 4:删除已发布的草稿

  1. 草稿(d0001)状态变为 DELETED
  2. 再次发布 → 对应快照删除

7. 状态流转流程图

1
2
3
4
5
6
7
flowchart TD
A[创建草稿 Draft] -->|发布 Release| B[生成快照 Snapshot 状态: RELEASED]
B -->|修改草稿| C[状态: UPDATED]
C -->|发布| D[新快照 Snapshot]
A -->|删除草稿(未发布)| E[直接删除]
B -->|删除草稿(已发布)| F[状态: DELETED]
F -->|再次发布| G[快照删除]

简单流程图示例

1
2
3
graph TD
A[开始] -->|准备中| B[处理中]
B -->|完成| C[结束]

安装

虽然Nginx从源码编译也很简单,但是建议新手还是使用系统预构建的包。

RedHat系

CentOS/RHEL/ Oracle Linux/AlmaLinux/Rocky Linux

1
2
3
sudo yum install epel-release
sudo yum update
sudo yum install nginx

Debian系

Debian/Ubuntu

1
2
sudo apt-get update
sudo apt-get install nginx

Installing NGINX Open Source

确认版本及配置

查看版本号

1
nginx -v

nginx version: nginx/1.23.2

查看版本及配置

1
nginx -V
1
2
3
4
5
nginx version: nginx/1.23.2
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
built with OpenSSL 1.0.2k-fips 26 Jan 2017
TLS SNI support enabled
configure arguments: --user=www --group=www --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_auth_request_module --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-google_perftools_module --with-debug --with-stream_ssl_preread_module

这里我们主要关注--prefix--conf-path配置

--prefix=/usr/share/nginx--conf-path=/etc/nginx/nginx.conf

prefix 决定了nginx里相对路径配置的路径前缀。
conf-path 决定了nginx配置文件位置。不同的Linux发行版配置文件位置都可能不一样,可以通过这个配置快速定位配置位置。

查看JAVA阻塞方法

1
jstack -l 1 | awk -v RS='' '/BLOCKED/' | sort | uniq -c | awk '$1 >= 5' | sort -nr

查看JAVA阻塞方法

1
jstack -l 1 | awk -v RS='' '/BLOCKED/' | sort | uniq -c | awk '$1 >= 5' | sort -nr

拉取老版本Docker镜像报unsupported manifest解决方案

0x01 问题描述

需要从私有仓库拉取一个老版本的 Docker 镜像,直接 docker pull 报错:

1
docker pull hub.shuyun.com/newbi4/app:4.8.16-420260330170921
1
Error response from daemon: unsupported manifest media type and no default available: application/json

镜像的 manifest 格式是 application/json,而新版 Docker (25+) 只接受标准的 application/vnd.docker.distribution.manifest.v2+json 等格式,直接拒绝了。

0x02 原因分析

这个问题通常出现在以下场景:

  1. 镜像是用较老版本的 Docker 或非标准工具推送的,manifest 格式不符合当前 Docker 的要求
  2. 新版 Docker 对 manifest media type 的校验更严格,不再兼容非标准格式

本质上是客户端与 registry 之间的格式协商失败。

0x03 解决方案:使用 skopeo

skopeo 是一个专门用于镜像搬运的工具,对各种 manifest 格式兼容性很强,可以在不同存储之间复制镜像。

3.1 安装 skopeo

skopeo 不支持 Windows 原生安装,在 WSL 中安装即可:

1
2
wsl
sudo apt update && sudo apt install skopeo

3.2 登录私有仓库

skopeo 可以直接读取 Docker 的登录凭证,如果已经在 WSL 中 docker login 过就不需要再登录。否则手动登录:

1
skopeo login hub.shuyun.com -u docker -p docker

3.3 拉取镜像

先保存为 tar 文件,再用 docker load 导入:

1
skopeo copy --src-tls-verify=false docker://hub.shuyun.com/newbi4/app:4.8.16-420260330170921 docker-archive:/tmp/app.tar

然后导入 Docker:

1
docker load -i /tmp/app.tar

docker load 只是解压导入 tar 包中的镜像层,不走 registry API 协商,所以不受 manifest 格式限制。

注意:不要用 docker-daemon: 作为目标,WSL 中的 skopeo 版本可能因为 Docker Engine API 版本不匹配而报错:
client version 1.22 is too old. Minimum supported API version is 1.44
docker-archive + docker load 是最稳的方案。

0x04 过程中的其他坑

4.1 WSL 中 docker login 报 credential-desktop.exe 错误

在 WSL 中执行 docker login 时可能报错:

1
error saving credentials: error storing credentials - err: fork/exec /usr/bin/docker-credential-desktop.exe: exec format error

这是因为 WSL 继承了 Windows 侧 Docker 的 ~/.docker/config.json,其中 credsStore 指向了 Windows 的凭据管理器。

解决方法是把 credsStore 置空:

1
sed -i 's/"credsStore": "desktop"/"credsStore": ""/' ~/.docker/config.json

之后重新 docker login 即可,凭证会以 base64 明文存在 config.json 中。

4.2 Windows 凭据管理器中找回 Docker 密码

如果之前在 Windows 侧 docker login 过但忘记了密码,凭证存在 Windows 凭据管理器中(credsStore: "desktop")。

控制面板的凭据管理器界面可以看到条目但密码可能无法直接显示。可以通过 PowerShell 调用 Win32 API 读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Add-Type -Namespace Win32 -Name Credman -MemberDefinition @'
[DllImport("advapi32.dll",CharSet=CharSet.Unicode,SetLastError=true)]
public static extern bool CredRead(string target,int type,int flags,out IntPtr cred);

[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
public struct CREDENTIAL{
public int Flags;
public int Type;
public string TargetName;
public string Comment;
public long LastWritten;
public int CredentialBlobSize;
public IntPtr CredentialBlob;
public int Persist;
public int AttributeCount;
public IntPtr Attributes;
public string TargetAlias;
public string UserName;
}
'@

$ptr = [IntPtr]::Zero
[Win32.Credman]::CredRead('hub.shuyun.com',1,0,[ref]$ptr)
$cred = [Runtime.InteropServices.Marshal]::PtrToStructure($ptr,[type][Win32.Credman+CREDENTIAL])
$bytes = New-Object byte[] $cred.CredentialBlobSize
[Runtime.InteropServices.Marshal]::Copy($cred.CredentialBlob, $bytes, 0, $cred.CredentialBlobSize)
$pass = [Text.Encoding]::UTF8.GetString($bytes)
Write-Host "User:" $cred.UserName
Write-Host "Pass:" $pass

将以上内容保存为 .ps1 文件执行,即可拿到用户名和密码。

0x05 不启动容器提取镜像内文件

如果只是想从镜像中提取某个文件,不需要创建或启动容器,直接解压 tar 包即可。

Docker 镜像本质上是分层的 tar 包,docker load 用的 tar 文件里包含了每一层的 layer.tar

5.1 解压镜像 tar

1
2
3
mkdir -p /tmp/app_extracted
cd /tmp/app_extracted
tar -xf /tmp/app.tar

5.2 找到目标层

解压后会看到多个 *.tar 文件,每个对应镜像的一层。按大小排序找到应用层:

1
ls -lhS /tmp/app_extracted/*.tar

5.3 查看层内容并提取

1
2
3
4
5
# 列出层内文件
tar -tf /tmp/app_extracted/<最大的那个>.tar

# 提取指定文件,比如 WAR 包
tar -xf /tmp/app_extracted/<最大的那个>.tar -C /tmp/ var/lib/jetty/webapps/ROOT.war

文件会被解压到 /tmp/var/lib/jetty/webapps/ROOT.war

如果是在 Windows 侧操作,可以通过 UNC 路径访问 WSL 中的文件:
\\wsl.localhost\Ubuntu-22.04\tmp\app_extracted\

0x06 完整操作步骤

汇总一下从零开始的完整流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 进入 WSL
wsl

# 2. 安装 skopeo
sudo apt update && sudo apt install skopeo

# 3. 修复 credential 问题
sed -i 's/"credsStore": "desktop"/"credsStore": ""/' ~/.docker/config.json

# 4. 登录私有仓库
skopeo login hub.shuyun.com -u <username> -p <password>

# 5. 拉取镜像到 tar
skopeo copy --src-tls-verify=false docker://hub.shuyun.com/newbi4/app:4.8.16-420260330170921 docker-archive:/tmp/app.tar

# 6. 导入 Docker
docker load -i /tmp/app.tar

# 7. 验证
docker images | grep newbi4/app

# 8. (可选) 不建容器直接提取文件
mkdir -p /tmp/app_extracted && tar -xf /tmp/app.tar -C /tmp/app_extracted
ls -lhS /tmp/app_extracted/*.tar
tar -xf /tmp/app_extracted/<目标层>.tar -C /tmp/ <目标文件路径>

0x07 总结

新版 Docker 对 manifest 格式校验越来越严格,遇到老镜像无法直接 pull 时,skopeo 是最靠谱的替代方案。核心思路就是绕开 Docker 客户端的格式校验,用 skopeo 先存为 tar,再通过 docker load 导入。整个过程中 Windows + WSL 环境下还需要注意 credential helper 和凭据管理器的兼容问题。