Docker 学习篇(七)| 实战 — 用 Docker 构建 SpringBoot + Vue 全栈项目

Docker 学习篇(七)| 实战 --- 用 Docker 构建 SpringBoot + Vue 全栈项目

    • [1. 前置准备](#1. 前置准备)
      • [1.1 确认 Docker 装好了](#1.1 确认 Docker 装好了)
      • [1.2 配置镜像加速器](#1.2 配置镜像加速器)
    • [2. 拉取中间件镜像](#2. 拉取中间件镜像)
    • [3. 后端:blog-server 的 Dockerfile](#3. 后端:blog-server 的 Dockerfile)
      • [3.1 分析项目](#3.1 分析项目)
      • [3.2 在项目根目录创建文件](#3.2 在项目根目录创建文件)
      • [3.3 构建镜像](#3.3 构建镜像)
    • [4. 前端:blog-ui 的 Dockerfile](#4. 前端:blog-ui 的 Dockerfile)
      • [4.1 分析项目](#4.1 分析项目)
      • [4.2 创建文件](#4.2 创建文件)
      • [4.3 构建镜像](#4.3 构建镜像)
    • [5. docker-compose.yml(全家桶一键启动)](#5. docker-compose.yml(全家桶一键启动))
      • [5.1 环境变量的几种管理方式](#5.1 环境变量的几种管理方式)
        • [方式一:全部写在 yml 里(上面就是)](#方式一:全部写在 yml 里(上面就是))
        • [方式二:抽离到 `.env` 文件(推荐日常开发)](#方式二:抽离到 .env 文件(推荐日常开发))
        • [方式三:CI/CD 变量注入(上服务器推荐)](#方式三:CI/CD 变量注入(上服务器推荐))
        • [方式四:Secrets Manager(大厂生产环境)](#方式四:Secrets Manager(大厂生产环境))
        • 总结:你该用哪种
    • [6. 启动与验证](#6. 启动与验证)
      • [6.1 如果你本机已装了 MySQL / Redis](#6.1 如果你本机已装了 MySQL / Redis)
      • [6.2 构建并启动](#6.2 构建并启动)
      • [6.3 验证](#6.3 验证)
      • [6.4 最终效果:四个容器各司其职](#6.4 最终效果:四个容器各司其职)
    • [7. 部署到服务器](#7. 部署到服务器)
      • [7.1 导出镜像](#7.1 导出镜像)
      • [7.2 上传到服务器](#7.2 上传到服务器)
      • [7.3 服务器上导入并启动](#7.3 服务器上导入并启动)
    • [8. 命令速查](#8. 命令速查)
    • [9. 常见问题](#9. 常见问题)

1. 前置准备

1.1 确认 Docker 装好了

bash 复制代码
docker version

输出中有 Client 和 Server 两段,Server 有版本号就是 OK。

1.2 配置镜像加速器

国内直连 Docker Hub 几乎不可用,必须配镜像加速。

Docker Desktop → 设置 → Docker Engine → 修改 registry-mirrors

json 复制代码
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.m.daocloud.io"
  ]
}

点击 Apply & Restart 重启 Docker 后生效。

验证:

bash 复制代码
docker pull hello-world

能拉下来就说明配好了。拉完删掉:docker rmi hello-world

⚠️ docker.xuanyuan.me 在 Docker Desktop 29.x 上不兼容(报 content size of zero),不要加。


2. 拉取中间件镜像

项目需要的中间件只有 MySQL 和 Redis。Nginx 不需要单独拉------它会在构建前端镜像时从 Docker Hub 自动拉取(FROM nginx:alpine)。

bash 复制代码
docker pull mysql:8.0
docker pull redis:7-alpine

确认:

bash 复制代码
docker images

看到 mysql:8.0redis:7-alpine 即可。


3. 后端:blog-server 的 Dockerfile

3.1 分析项目

blog-server 是一个 Spring Boot 3.2.5 + Java 21 + Maven 多模块项目:

复制代码
blog-server/
├── pom.xml                  ← 父 POM
├── blog-bootstrap/          ← 启动模块(有 main 方法)
├── blog-module-common/
├── blog-module-article/
├── blog-module-comment/
├── blog-module-media/
├── blog-module-auth/
├── blog-module-site/
└── blog-module-message/

关键配置(application.yml):

配置项 环境变量 application.yml 默认值 docker-compose 中覆盖为
数据库地址 DB_HOST localhost mysql(容器名)
数据库端口 DB_PORT 3307 3306(容器内端口)
数据库名 --- blog ---
用户名 DB_USERNAME root root
密码 DB_PASSWORD root root
Redis 地址 REDIS_HOST localhost redis(容器名)
Redis 端口 REDIS_PORT 6380 6379(容器内端口)
Redis 密码 --- 123456 ---
服务端口 SERVER_PORT 8080 8080
上传目录 UPLOAD_PATH 本地 Windows 路径 /app/upload(容器内路径)

application.yml 默认值已设为连 Docker 容器(宿主机端口 3307/6380),方便 IDEA 直接跑。docker-compose 会用自己的环境变量覆盖为容器内端口(3306/6379)。

3.2 在项目根目录创建文件

blog-server/ 下创建 .dockerignore

复制代码
target/
.git/
.idea/
*.md
*.log
upload/
logs/

blog-server/ 下创建 Dockerfile

dockerfile 复制代码
# ===== 第一阶段:编译 =====
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app

# 直接复制所有源码,单步编译
# 注意:多模块 Maven 项目不建议用 mvn dependency:go-offline 分层------
# 内部模块间依赖无法从本地仓库解析,会导致构建失败
COPY . .
RUN mvn clean package -DskipTests -pl blog-bootstrap -am

# ===== 第二阶段:运行 =====
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app

# 从编译阶段只拿 jar 包
COPY --from=builder /app/blog-bootstrap/target/*.jar app.jar

# 创建上传目录
RUN mkdir -p /app/upload

EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

多阶段构建原理见第四篇第 3 节和第 17 节。简单说:该处 maven:3.9-eclipse-temurin-21(包含Maven + JDK) 编译完就丢弃,不进入最终镜像。

复制代码
blog-server 最终镜像里只有:
✅ JRE 21(运行 Java)
✅ app.jar(我们的代码)
✅ /app/upload 目录
❌ Maven / JDK / 源码(编译完就不要了)

3.3 构建镜像

bash 复制代码
cd blog-server
docker build -t blog-server:latest .

4. 前端:blog-ui 的 Dockerfile

4.1 分析项目

blog-ui 是 Vue 3 + Vite + Element Plus,构建后生成 dist/ 静态文件。

4.2 创建文件

blog-ui/ 下创建 .dockerignore

复制代码
node_modules/
dist/
.git/
*.md

blog-ui/ 下创建 nginx.conf

nginx 复制代码
server {
    listen       80;
    server_name  _;
    root         /usr/share/nginx/html;
    index        index.html;

    # ========== 关键:API 请求代理到后端 ==========
    location /api/ {
        proxy_pass http://blog-server:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # Swagger / Knife4j API 文档
    location /swagger-ui.html {
        proxy_pass http://blog-server:8080;
    }
    location /v3/api-docs {
        proxy_pass http://blog-server:8080;
    }
    location /webjars/ {
        proxy_pass http://blog-server:8080;
    }

    # 上传文件
    location /upload/ {
        proxy_pass http://blog-server:8080;
    }

    # Vue Router history 模式:找不到文件就回退到 index.html
    location / {
        try_files $uri $uri/ /index.html;
    }
}

为什么需要 nginx 代理 /api/

Vue 项目里的 Axios 发请求是从用户浏览器 发出的,不是从 Docker 容器里发出的。如果你在 .env 里写 VITE_API_BASE_URL=http://localhost:8080,那浏览器就真的去访问用户自己电脑的 localhost:8080------这在部署到服务器上时完全不对。

正确做法:前端请求全部发到同源 (同一个域名/端口),由 nginx 根据路径前缀把 /api/ 转发给后端容器:

复制代码
浏览器 → http://服务器/api/v1/articles
      ↓ nginx (blog-ui 容器)
      ↓ proxy_pass http://blog-server:8080
      ↓ blog-server 容器处理请求

blog-ui/ 下创建 Dockerfile

dockerfile 复制代码
# ===== 第一阶段:构建 =====
FROM node:20-alpine AS builder
WORKDIR /app

# 先复制依赖描述文件(package.json + package-lock.json)
# 这样改源码不改依赖时,npm install 这层走缓存
COPY package*.json .
RUN npm config set registry https://registry.npmmirror.com  # 国内加速,海外可删
RUN npm install

# VITE_API_BASE_URL 设为空:浏览器发请求到同源,由 nginx 代理到后端
ENV VITE_API_BASE_URL=""

COPY . .
# build-only 是该项目跳过了类型检查(vue-tsc),普通项目用 npm run build 即可
RUN npm run build-only

# ===== 第二阶段:托管 =====
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

VITE_API_BASE_URL 为什么设空? Vite 在构建时会把 import.meta.env.VITE_API_BASE_URL 替换为实际值,写进 JS 文件里。设为空字符串后,Axios 的 baseURL 为空,所有请求变成相对路径 (如 /api/v1/articles),浏览器自动发到当前页面的域名。然后 nginx 根据 /api/ 前缀转发给后端。

复制代码
blog-ui 最终镜像里的东西:
✅ Nginx(Web 服务器)
✅ dist/ 静态文件(HTML + JS + CSS)
✅ nginx.conf(代理规则)
❌ Node.js(不需要了)
❌ node_modules(不需要了)
❌ 源代码(不需要了)
❌ npm(不需要了)

4.3 构建镜像

bash 复制代码
cd blog-ui
docker build -t blog-ui:latest .

5. docker-compose.yml(全家桶一键启动)

在项目根目录创建 docker-compose.yml

yaml 复制代码
services:
  mysql:
    image: mysql:8.0
    container_name: blog-mysql
    ports:
      - "3307:3306"
    volumes:
      - D:/Develop/DockerData/Personal/docker-mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: blog
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - blog-net

  redis:
    image: redis:7-alpine
    container_name: blog-redis
    ports:
      - "6380:6379"
    volumes:
      - D:/Develop/DockerData/Personal/docker-redis:/data
    command: redis-server --requirepass 123456
    restart: unless-stopped
    networks:
      - blog-net

  blog-server:
    image: blog-server:latest     # 本地 docker build -t blog-server .,服务器 docker load,同一份 compose 两边通用
    container_name: blog-server
    ports:
      - "8081:8080"               # 8081 避免和 IDEA 里跑的冲突
    volumes:
      - blog-upload:/app/upload   # 上传的文件持久化
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - DB_HOST=mysql             # 用容器名当域名!
      - DB_PORT=3306              # 容器内端口,不是映射端口
      - DB_USERNAME=root
      - DB_PASSWORD=root
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - REDIS_PASSWORD=123456
      - UPLOAD_PATH=/app/upload
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    networks:
      - blog-net

  blog-ui:
    image: blog-ui:latest
    container_name: blog-ui
    ports:
      - "80:80"
    depends_on:
      - blog-server
    restart: unless-stopped
    networks:
      - blog-net

volumes:
  blog-upload:                    # 命名卷:Docker 管理,不用关心路径

networks:
  blog-net:
    driver: bridge                # 自定义桥接网络:容器间用容器名互访

5.1 环境变量的几种管理方式

上面这个 docker-compose.yml 把密码直接写在文件里------对入门来说最直观,但有两个问题:一是提交到 Git 会泄露密码,二是项目多了改密码要到处翻。实际开发中,根据场景有不同的做法。


方式一:全部写在 yml 里(上面就是)

适合本地快速验证,复制粘贴就能跑。缺点也明显------密码明文,不能提交 Git。


方式二:抽离到 .env 文件(推荐日常开发)

把敏感变量抽到 .env 文件里,docker-compose.yml${变量名} 引用。

项目根目录创建 .env

bash 复制代码
# 数据库
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=blog

# Redis
REDIS_PASSWORD=123456

# blog-server
DB_HOST=mysql
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=root
REDIS_HOST=redis
REDIS_PORT=6379
UPLOAD_PATH=/app/upload
JWT_SECRET=LuckyBlogSecretKeyForJWTTokenGenerationMustBe256BitsLong

docker-compose.yml 改为引用变量:

yaml 复制代码
services:
  mysql:
    image: mysql:8.0
    container_name: blog-mysql
    ports: ["3307:3306"]
    volumes: [D:/Develop/DockerData/Personal/docker-mysql:/var/lib/mysql]
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
    networks: [blog-net]

  redis:
    image: redis:7-alpine
    container_name: blog-redis
    ports: ["6380:6379"]
    volumes: [D:/Develop/DockerData/Personal/docker-redis:/data]
    command: redis-server --requirepass ${REDIS_PASSWORD}
    restart: unless-stopped
    networks: [blog-net]

  blog-server:
    image: blog-server:latest
    container_name: blog-server
    ports: ["8081:8080"]
    volumes: [blog-upload:/app/upload]
    environment:
      SPRING_PROFILES_ACTIVE: docker
      DB_HOST: ${DB_HOST}
      DB_PORT: ${DB_PORT}
      DB_USERNAME: ${DB_USERNAME}
      DB_PASSWORD: ${DB_PASSWORD}
      REDIS_HOST: ${REDIS_HOST}
      REDIS_PORT: ${REDIS_PORT}
      REDIS_PASSWORD: ${REDIS_PASSWORD}
      UPLOAD_PATH: ${UPLOAD_PATH}
      JWT_SECRET: ${JWT_SECRET}
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    networks: [blog-net]

  blog-ui:
    image: blog-ui:latest
    container_name: blog-ui
    ports: ["80:80"]
    depends_on: [blog-server]
    restart: unless-stopped
    networks: [blog-net]

volumes:
  blog-upload:

networks:
  blog-net:
    driver: bridge

一定要把 .env 加到 .gitignore,避免提交到 Git:

gitignore 复制代码
# .gitignore
.env

同时提交一个 .env.example 给其他人参考(里面填假值或不填):

bash 复制代码
# .env.example
MYSQL_ROOT_PASSWORD=你的密码
MYSQL_DATABASE=blog
REDIS_PASSWORD=你的密码
# ... 其他变量

docker compose 启动时自动读取 同目录下的 .env 文件,不需要额外配置。环境变量只在容器首次启动时生效,启动后改 .env 需要 docker compose down && docker compose up -d 重建容器。


方式三:CI/CD 变量注入(上服务器推荐)

.env 文件虽然解决了提交 Git 的问题,但服务器上仍然存了一个明文文件。更好的做法是:

yaml 复制代码
# docker-compose.yml 里依然用 ${变量}
# 但不依赖 .env 文件,而是由 CI/CD 系统在部署时注入
yaml 复制代码
# GitHub Actions 示例
- name: Deploy
  run: docker compose up -d
  env:
    MYSQL_ROOT_PASSWORD: ${{ secrets.MYSQL_ROOT_PASSWORD }}
    REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD }}

密码存在 GitHub Secrets / GitLab CI Variables 里,加密存储,部署时注入到容器的环境变量。服务器上没有任何文件包含明文密码。


方式四:Secrets Manager(大厂生产环境)

大厂会用专门的密钥管理服务(AWS Secrets Manager、HashiCorp Vault、Azure Key Vault),密码加密存储 + 访问审计 + 自动轮换。部署工具在启动容器时调用 API 拉取密码,直接注入环境变量,密码不落在任何文件里


总结:你该用哪种
方式 适用场景 安全等级
方式一:全部写 yml 里 本地快速验证
方式二:.env 文件抽离 日常开发、个人项目 ⭐⭐
方式三:CI/CD 注入 上服务器的项目 ⭐⭐⭐⭐
方式四:Secrets Manager 大厂生产、合规要求 ⭐⭐⭐⭐⭐

当前项目推荐方式二.env + .gitignore),平衡了安全性和操作复杂度。等真正部署到服务器时再升级到方式三。


关键知识点:

1. 为什么统一用 image: --- build: vs image: 的详细对比、docker 与 docker compose 在各阶段的完整分析,见第五篇:docker 与 docker compose 对比

2. 容器间通信用容器名 --- DB_HOST=mysql 用的是 compose 服务名。原理见第四篇第 12 节

3. 四个镜像来源:

复制代码
远程拉取(Docker Hub)        本地构建(我们的 Dockerfile)
┌─────────────────┐         ┌──────────────────┐
│  mysql:8.0      │         │  blog-server     │
│  redis:7-alpine │         │  blog-ui         │
└─────────────────┘         └──────────────────┘
     ↓                            ↓
4 个镜像 → docker compose up → 4 个容器

6. 启动与验证

6.1 如果你本机已装了 MySQL / Redis

两种方案,挑一个:

方案 A:停掉本机服务(简单省事)

powershell 复制代码
# 管理员 PowerShell
net stop MySQL80        # 服务名可能不同,去 services.msc 确认
net stop Redis            # 服务名也可能是 RedisService 或其他,去 services.msc 确认

此时 3306 和 6379 空闲,但 compose 仍然走 3307/6380,IDEA 连接也不用变。

方案 B:本机服务留着,Docker 换端口(推荐,已配好)

本文的 docker-compose.yml 已经用了岔开端口的方案:

复制代码
Windows 本机                      Docker 容器
├── MySQL  → localhost:3306       ├── MySQL  → localhost:3307  ← compose 默认
├── Redis  → localhost:6379       ├── Redis  → localhost:6380  ← compose 默认

不需要改任何配置,直接 docker compose up -d 就能两边同时跑。

⚠️ 数据目录绝对不能共用! 两个 MySQL 实例指向同一份数据文件会锁表甚至损坏数据。Docker 的数据目录 D:/Develop/DockerData/Personal/docker-mysql 必须是空的独立目录。

IDEA 里切着连:

  • 连 Windows MySQL → localhost:3306,root / root
  • 连 Docker MySQL → localhost:3307,root / root

6.2 构建并启动

bash 复制代码
# 1. 构建镜像(docker build → 读 Dockerfile,打出镜像)
docker build -t blog-server:latest ./blog-server
docker build -t blog-ui:latest ./blog-ui

# 2. 启动(docker compose → 读 docker-compose.yml,镜像跑成容器)
docker compose up -d

# 之后改代码,只需重建镜像并重启对应服务
docker build -t blog-server:latest ./blog-server && docker compose up -d

6.3 验证

bash 复制代码
# 看四个容器是否都在跑
docker compose ps

# 看后端日志
docker logs -f blog-server

# 测试 API(通过 nginx 代理)
curl http://localhost/api/v1/site/config

# 浏览器访问
# 前端:        http://localhost
# API 文档:    http://localhost/swagger-ui.html(通过 nginx 代理)
# 后端直连:    http://localhost:8081(绕过 nginx,调试用)

所有访问都走前端 80 端口,nginx 根据路径自动转发 /api/ 到后端。后端 8081 只给开发者本地调试用。

6.4 最终效果:四个容器各司其职

复制代码
浏览器访问 http://localhost
         │
         ▼
    ┌──────────────┐
    │   blog-ui    │  Nginx 容器
    │   :80        │  /          → 静态文件 (Vue)
    └──────┬───────┘  /api/*     → 代理给 blog-server:8080
           │          /upload/*  → 代理给 blog-server:8080
           ▼
    ┌──────────────┐
    │  blog-server │  JRE 容器
    │  :8080       │  Spring Boot 应用
    └──┬─────┬─────┘
       │     │
       ▼     ▼
    ┌────┐ ┌────┐
    │mysql│ │redis│  中间件容器
    │:3306│ │:6379│
    └────┘ └────┘

用代码验证 Redis 连接(可选):

在 blog-server 里加一个测试接口,确认 Redis 容器能正常读写:

java 复制代码
@RestController
public class RedisTestController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @GetMapping("/redis/set")
    public String setRedisData() {
        stringRedisTemplate.opsForValue().set("docker-test", "Docker Redis 连接成功!");
        return "已存入 Redis";
    }

    @GetMapping("/redis/get")
    public String getRedisData() {
        return stringRedisTemplate.opsForValue().get("docker-test");
    }
}

访问 /redis/set 存数据,/redis/get 取数据,能取到说明 Redis 通了。


7. 部署到服务器

7.1 导出镜像

bash 复制代码
docker save -o blog-server.tar blog-server:latest
docker save -o blog-ui.tar blog-ui:latest

7.2 上传到服务器

通过 FinalShell 或宝塔面板,把 blog-server.tarblog-ui.tardocker-compose.yml 传到服务器上。

7.3 服务器上导入并启动

服务器上需要拉取中间件镜像(MySQL、Redis):

bash 复制代码
# 拉取中间件镜像(服务器也要配镜像加速器)
docker pull mysql:8.0
docker pull redis:7-alpine

# 导入你自己的镜像
docker load -i blog-server.tar
docker load -i blog-ui.tar

# 修改 docker-compose.yml 中卷路径为 Linux 路径(如 /data/docker-mysql)
# docker-compose.yml 用的是 image: 不是 build:,本地服务器同一份,不用改
docker compose up -d

服务器部署时,建议删掉 docker-compose.yml 中 MySQL 和 Redis 的 ports 映射------生产环境只暴露前端 80/443 就够了。

注意 .tar vs .tar.gz 如果镜像文件后缀是 .tar.gz,两种方式导入:

bash 复制代码
# 方式一:管道解压导入(最可靠)
gunzip -c blog-server.tar.gz | docker load

# 方式二:直接用 load(部分新版 Docker 支持)
docker load -i blog-server.tar.gz

.tar 被改后缀成 .tar.gz 会导致 gunzip -c 失败,用 file 文件名 可查看真实类型。

docker cp:容器和宿主机互传文件

bash 复制代码
# 从宿主机复制到容器
docker cp app.jar blog-server:/app/app.jar

# 从容器复制到宿主机(比如导出日志)
docker cp blog-server:/app/logs ./logs

# 容器内目录结构参考:
# 后端应用:取决于 Dockerfile 的 WORKDIR(本例为 /app)
# 前端静态文件:/usr/share/nginx/html(Nginx 默认)

8. 命令速查

完整命令手册见 第六篇:常用命令速查


9. 常见问题

问题 解决
端口被占用 停掉 Windows 本机的 MySQL/Redis 服务,或岔开端口映射(如 3307)
容器启动后立即退出 docker logs 容器名 看错误日志,通常缺环境变量或端口冲突
后端连不上数据库 检查 DB_HOST=mysql(容器名)且 DB_PORT=3306(容器内端口),不是宿主机端口 3307
构建慢 检查 .dockerignore 有没有排除 node_modules / target
镜像拉不下来 检查镜像加速器配了没有,重启 Docker
改代码不生效 docker build -t 镜像名 . 重建镜像,再 docker compose up -d 重启
Redis 客户端连不上 localhost,端口填宿主机映射端口(如 6380),不是容器 IP
容器名重复 容器名同一机器唯一,先 docker rm 容器名 再重建

容器卡死,stop/restart/kill 都不响应: 详见第四篇第 11 节方法。

相关推荐
南境十里·墨染春水1 小时前
linux 学习进展 网络编程 ——TCP 协议 TIME_WAIT 状态详解
linux·网络·学习
薛定e的猫咪1 小时前
(AAMAS 2023)基于广义策略改进优先级的高效多目标学习 GPI - LS/PD
人工智能·学习·机器学习
@杰克成3 小时前
Java学习22
java·python·学习·idea
树下水月3 小时前
docker 常用命令
docker·容器·eureka
Hello_Embed3 小时前
串口硬件结构与三种编程方式
笔记·stm32·学习·ai编程
经济元宇宙3 小时前
2026 工厂搬运自动化:主流 AMR 品牌技术与应用深度测评
数码相机·学习
好奇的菜鸟3 小时前
Java开发常用中间件,Docker安装。
java·docker·中间件
HalvmånEver4 小时前
MySQL事务(一)
linux·数据库·学习·mysql