最近因为我的游戏本放在宿舍,在实验室只留了一个轻薄本用来远程。所以推视觉小说比较多。

有的时候在想现在的经历,觉得如果有回档就好了。我想要回到之前的某一个时间节点去做另外一个选择。

但是又转念一想,或许不同的线,命运都给我书写好了,现在做的无非就是最后到达某一个结局而已。除了有非常明确的目标,不然最后打出的都是”无好感线“,对应到现实可能就是什么都想做,却没有个明确的主线去串联起我的行为,最后就是平平庸庸泯然众人。

但是平庸也没什么不好,我现在这样并不是上天不公,或者社会不公平,可能仅仅是某一个晚上,我选择打伞去逛操场。但是在另外一条线或者另外的很多线里面,很多事情都是发生的。对于目前来说,专注于现在就好了。结局有很多,对于我个人在哲学意义上来说,单纯的为了拥有某个结局而去努力是没有意义的,因为最后的最后结局都有的。所以,不管是有明确的目标也好,还是没有,都是没关系的。

在去往某条线上的经历,那些体验过的美好,看过的风景才是重要的。

先是指定版本

version: '3'
现在已被废除,不需要写了

然后指定 service
每个service 都指定build字段或者image字段看是从dockerfile上构建镜像还是从拉取镜像
服务的名称直接缩进一格就好
container_name指定容器名称
ports指定开放的端口
depends_on指定该服务需要在哪些服务启动之后启动
environment指定环境变量
volumes指定数据卷

就是数据和容器内数据的映射
比如将容器内的某个文件夹保存在宿主机内的某个地方
也可以用docker的数据卷做持久化,在最外层需要多一个volumes字段,缩进一格放数据卷名称

在配置mysql的时候可以将sql文件映射到容器内/docker-entrypoint-initdb.d目录
映射好之后,容器启动会自动运行目录里面的sql文件。需要注意的是只有在首次创建数据库的时候会执行。所有如果对mysql做了持久化,就是将容器内/var/lib/mysql的东西映射到了本地或者数据卷,后面再次启动就不会执行,除非将持久化的内容删了,然后重新创建
mysql环境变量

  1. 环境变量优先级:

environment:

MYSQL_ROOT_PASSWORD: root123      # root密码(必需)
MYSQL_DATABASE:         # 创建的数据库名
MYSQL_USER:      # 创建的用户名
MYSQL_PASSWORD: # 用户密码

容器间通信需要使用networks来指定一个网络,和volume一样最后在配置url的时候将localhost或者127.0.0.1变成对应的服务名。
但是好像不用network指定网络也行。不过就是确实需要对应为服务名就是了。

更正:
可能是windows上的docker比较新,可以不指定network就默认为一个network,但是我部署到linux上的时候,ubuntu上装的一个docker compose就不支持,加上networks字段之后才正常工作

如果就是容器内自己用的话像是mysql,redis这种的端口应该就不用暴露出去,因为暴露出去是给宿主机用的。如果只是容器之间用的话就不暴露也行。

可以使用环境变量的方式配置application.propertiess里面的配置。将字段改为全大写,点变为短下划线即可。

最后使用

docker compose up -d

来后台启动

如果想要重新构建镜像可以使用

docker compose build
后面可以跟上对应的服务名表示单独构建某一个镜像

使用

docker compose down

来停止和删除容器

等于 stop + rm

示例:

services:
  qiannian:
    build: ./qiannian
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redisL
    networks:
      - qiannian_network
  
  ocr:
    build: ./ocr
    networks:
      - qiannian_network

  aiservice:
    build: ./ai_service
    networks:
      - qiannian_network

  mysql:
    image: mysql:8.0
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: qiannian
    networks:
      - qiannian_network
    volumes:
      - qiannian_mysql:/var/lib/mysql
      - ./qiannian/script.sql:/docker-entrypoint-initdb.d/script.sql

  redisL:
    image: redis:6.2-alpine
    container_name: redis
    networks:
      - qiannian_network

volumes:
  qiannian_mysql:

networks:
  qiannian_network:
    driver: bridge

选用的基础镜像
python:3.11
python:3.11-slim
python:3.11-alpine

alpine对PaddlePaddle不兼容

复制文件到 /app 文件夹下面

docker里面每一行命令都是基于前一个命令执行之后的容器文件新开一个容器

使用WORKDIR可以将后面的容器目录cd到该目录后面

opencv系统依赖

# 安装OpenCV依赖的系统库
RUN apt-get update && apt-get install -y \
    libgl1  \
    libglib2.0-0 \
    libsm6 \
    libxext6 \
    libgomp1 \
    libjpeg-dev \
    libpng-dev \
    libtiff-dev \
    libwebp-dev \
    && rm -rf /var/lib/apt/lists/*

关键依赖解释:

  • libgl1 - OpenGL支持(GUI功能)
  • libglib2.0-0 - GLib库
  • libsm6, libxext6, libxrender-dev - X11图形库
  • libgomp1 - OpenMP并行计算
  • libgtk-3-dev - GTK库(GUI界面)
  • libavcodec-dev, libavformat-dev, libswscale-dev - 视频编解码库
  • libjpeg-dev, libpng-dev, libtiff-dev, libwebp-dev - 图像格式支持

最小化安装(如果不需要GUI功能):

RUN apt-get update && apt-get install -y \
    libgl1 \
    libglib2.0-0 \
    libsm6 \
    libxext6 \
    libgomp1 \
    && rm -rf /var/lib/apt/lists/*

可以先创建虚拟环境,然后用
然后安装一下需要的包,最好使用

pip freeze > requirements.txt

保存下依赖

之后在dockerfile里面可以在copy完之后将依赖下载一下
最后可以使用CMD命令作为启动容器的默认命令,来起一个程序

使用

docker build -t name:tag .

来构建镜像
使用

docker images

来查看镜像
使用

docker run -d -p port:port --name name image:tag

来启动镜像

常用参数:

  • -d:后台运行
  • -p 8000:8000:端口映射(主机端口:容器端口)
  • --name my-container:给容器命名
  • -v /host/path:/container/path:挂载目录
  • -e ENV_VAR=value:设置环境变量

示例:

FROM python:3.11-slim

COPY . /app

WORKDIR /app

# 安装opencv依赖系统库
RUN apt-get update && apt-get install -y \
    libgl1 \
    libglib2.0-0 \
    libsm6 \
    libxext6 \
    libgomp1 \
    libjpeg-dev \
    libpng-dev \
    libtiff-dev \
    libwebp-dev \
    && rm -rf /var/lib/apt/lists/*

RUN pip install -r requirements.txt

EXPOSE 18689

CMD ["python", "ocr_service.py"]
FROM python:3.11-slim

COPY . /app

WORKDIR /app

RUN pip install -r requirements.txt

EXPOSE 7373

CMD ["python", "main.py"]

先是看下pom.xml里面的java版本,然后选择基础镜像

# 官方OpenJDK镜像(推荐)
FROM openjdk:17-jdk-slim
FROM openjdk:11-jdk-slim
FROM openjdk:8-jdk-slim

# Eclipse Temurin(更好的性能)
FROM eclipse-temurin:17-jdk
FROM eclipse-temurin:11-jdk

可以使用多阶段构建
如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。

使用

FROM maven:3.8.5-openjdk-17 AS builder

作为构建jar包时的镜像
https://hub.docker.com/_/maven/tags
这里可以搜索一下tag,主要就是看下jdk版本。

后面使用

FROM openjdk:17-jdk-slim

作为运行环境
两个镜像分隔,可以使最后的的最终镜像大小变小

可以使用

COPY --from=builder

复制前一阶段的文件

如果使用springboot插件需要删除<skip>true</skip>
这个会使SpringBoot插件跳过打包过程
导致没有main-class,运行jar包时报错

no main manifest attribute, in /app/xxx.jar

示例:

FROM maven:3.8.5-openjdk-17 AS builder

COPY . ./app

WORKDIR /app

RUN mvn clean package -DskipTests

FROM openjdk:17-jdk-slim

COPY --from=builder /app/target/xxx-0.0.1-SNAPSHOT.jar /app/xxx.jar

EXPOSE 8080

CMD ["java", "-jar", "/app/xxx.jar"]

基本语法

每个 @RequestParam 都可以单独设置 defaultValue 属性:
例程:

package com.example.demo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;

@RestController
public class testController {
    // 多个参数,都有默认值
    @RequestMapping("/test")
    public String test(
            @RequestParam(value = "name", defaultValue = "World") String name,
            @RequestParam(value = "age", defaultValue = "18") Integer age,
            @RequestParam(value = "city", defaultValue = "Beijing") String city) {
        return String.format("Hello %s, age %d, from %s", name, age, city);
    }
    
    // 简化写法(参数名与变量名相同时)
    @RequestMapping("/test2")
    public String test2(
            @RequestParam(defaultValue = "Guest") String name,
            @RequestParam(defaultValue = "0") Integer score) {
        return String.format("User: %s, Score: %d", name, score);
    }
}

详细说明

1. 完整写法

@RequestParam(value = "参数名", defaultValue = "默认值") 数据类型 变量名

2. 简化写法(当参数名与变量名相同)

@RequestParam(defaultValue = "默认值") 数据类型 变量名

实际使用示例

// 搜索接口示例
@RequestMapping("/search")
public String search(
        @RequestParam(defaultValue = "") String keyword,           // 搜索关键词,默认空
        @RequestParam(defaultValue = "1") Integer page,            // 页码,默认第1页
        @RequestParam(defaultValue = "10") Integer size,           // 每页大小,默认10条
        @RequestParam(defaultValue = "createTime") String sortBy,  // 排序字段,默认按创建时间
        @RequestParam(defaultValue = "desc") String sortOrder) {   // 排序方向,默认降序
    
    return String.format("搜索: %s, 第%d页, 每页%d条, 按%s %s排序", 
                        keyword, page, size, sortBy, sortOrder);
}

// 用户信息接口示例
@RequestMapping("/userInfo")
public String getUserInfo(
        @RequestParam(defaultValue = "0") Long userId,
        @RequestParam(defaultValue = "false") Boolean includeDetails,
        @RequestParam(defaultValue = "json") String format) {
    
    return String.format("用户ID: %d, 包含详情: %s, 格式: %s", 
                        userId, includeDetails, format);
}

注意事项

  1. 数据类型转换:Spring会自动进行类型转换
  2. 必需参数:如果不设置 defaultValue,参数就是必需的
  3. 可选参数:设置了 defaultValue 的参数变为可选
  4. 布尔值默认值"true""false"(字符串形式)
  5. 数字默认值:用字符串表示,如 "0""100"

进阶用法

@RequestMapping("/advanced")
public String advanced(
        @RequestParam(value = "q", defaultValue = "") String query,
        @RequestParam(defaultValue = "1") @Min(1) Integer page,
        @RequestParam(defaultValue = "10") @Range(min = 1, max = 100) Integer size,
        @RequestParam(required = false) String category,  // 可选参数,无默认值时为null
        @RequestParam(defaultValue = "false") Boolean exact) {
    
    // 处理逻辑
    return "搜索结果";
}