LOADING

加载过慢请开启缓存 浏览器默认开启

峰言峰语

组建NAS(六)

记录 2025/2/25

Docker日志管理

修改Docker守护进程配置/etc/docker/daemon.json

{
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "512m",
        "max-file": "3"
    }
}

对新建容器生效

Docker IPv6

编辑/etc/sysctl.conf开启IPv6转发

echo "net.ipv6.conf.all.forwarding = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

编辑/etc/docker/daemon.json开启IPv6

{
    "ipv6": true,
    "fixed-cidr-v6": "fd13:a4f3:a0b0::/64",
    "experimental": true,
    "ip6tables": true
}

对新建容器生效

Bind9权威DNS

拉取镜像

docker pull internetsystemsconsortium/bind9:9.20

启动容器

docker run \
        --name=bind9 \
        --restart=always \
        --publish 53:53/udp \
        --publish 53:53/tcp \
        # 远程管理端口
        # --publish 127.0.0.1:953:953/tcp \
        # name.conf
        --volume /etc/bind \
        # 工作目录
        --volume /var/cache/bind \
        # 次级域
        --volume /var/lib/bind \
        # 日志
        --volume /var/log \
        internetsystemsconsortium/bind9:9.20

编辑配置文件/etc/bind/named.conf

options {
    directory "/var/cache/bind";
    listen-on { 127.0.0.1; };
    listen-on-v6 { ::1; };
    allow-recursion {
        none;
    };
    allow-update {
        none;
    };
    allow-transfer {
        none;
    };
}
include "/path/to/zone/conf/definition";

编辑include指向的配置文件

zone "xxx.xxx." {
    type primary;
    file "/path/to/zone/file";
}

编写反向域文件以便提供PTR记录

阅读全文

maven

记录 2025/2/24

Maven

为什么使用Maven

① 一个项目就是一个工程

如果项目非常庞大,就不适合使用package来划分模块,最好是每一个模块对应一个工程,利于分工协作。借助于maven就可以将一个项目拆分成多个工程

② 项目中使用jar包,需要“复制”、“粘贴”项目的lib中

同样的jar包重复的出现在不同的项目工程中,你需要做不停的复制粘贴的重复工作。借助于maven,可以将jar包保存在“仓库”中,不管在哪个项目只要使用引用即可就行。

③ jar包需要的时候每次都要自己准备好或到官网下载

借助于maven我们可以使用统一的规范方式下载jar包,规范

④ jar包版本不一致的风险

不同的项目在使用jar包的时候,有可能会导致各个项目的jar包版本不一致,导致未执行错误。借助于maven,所有的jar包都放在“仓库”中,所有的项目都使用仓库的一份jar包。

⑤ 一个jar包依赖其他的jar包需要自己手动的加入到项目中

FileUpload组件->IO组件,commons-fileupload-1.3.jar依赖于commons-io-2.0.1.jar

极大的浪费了我们导入包的时间成本,也极大的增加了学习成本。借助于maven,它会自动的将依赖的jar包导入进来。

依赖管理工具

Maven是一个项目管理工具,可以对Java项目进行构建、依赖管理、文档生成等工作。Maven的核心概念是POM(Project Object Model),通过POM文件描述项目的基本信息、依赖、构建脚本等。

POM

POM文件是Maven项目的核心文件,它是一个XML文件,描述了项目的基本信息、依赖、构建脚本等。POM文件的位置是项目根目录下的pom.xml文件。

POM文件的基本结构如下:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
    </dependencies>
</project>

maven通过groupId、artifactId、version来唯一标识一个依赖。

依赖范围

Maven的依赖范围有以下几种:

  • compile:默认范围,编译、测试、运行都有效。
  • provided:编译、测试有效,运行时无效,由JDK或容器提供。
  • runtime:测试、运行有效,编译时无效。
  • test:测试有效,编译、运行时无效。
  • system:类似provided,但需要提供systemPath指定jar包的路径。

依赖版本原则

Maven的依赖版本原则如下:

  • 最短路径优先:如果两个依赖冲突,选择最短路径的依赖。
  • 先声明优先:如果两个依赖冲突,选择先声明的依赖。

构建工具

构建是指将源代码转换为可执行程序的过程,构建工具是用来自动化构建过程的工具。Maven是一个构建工具,可以对Java项目进行构建、依赖管理、文档生成等工作。

Maven的构建过程主要包括以下几个阶段:

  • 清理:删除target目录。
  • 编译:编译源代码。
  • 测试:运行单元测试。
  • 打包:打包成jar或war文件。
  • 安装:将打包文件安装到本地仓库。
  • 部署:将打包文件部署到服务器。

Maven的构建过程是通过插件实现的,每个阶段对应一个插件。Maven的插件是一个Java类,实现了Maven的插件接口,可以在Maven的生命周期中执行特定的任务。

生命周期

Maven的生命周期是指构建过程中的一系列阶段,Maven定义了三套生命周期:

  • clean:清理生命周期,包括pre-clean、clean、post-clean三个阶段。
  • default:默认生命周期,包括compile、test、package、install、deploy五个阶段。
  • site:站点生命周期,包括pre-site、site、post-site、site-deploy四个阶段。

标签

<?xml version="1.0" encoding="utf-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/maven-v4_0_0.xsd">
    <!--父项目的坐标。如果项目中没有规定某个元素的值,那么父项目中的对应值即为项目的默认值。 坐标包括group ID,artifact ID和 version。-->
    <parent>
        <!--被继承的父项目的构件标识符-->
        <artifactId>spring-boot-starter-web</artifactId>
        <!--被继承的父项目的全球唯一标识符-->
        <groupId>org.springframework.boot</groupId>
        <!--被继承的父项目的版本-->
        <version>2.6.6</version>
        <!-- 父项目的pom.xml文件的相对路径。相对路径允许你选择一个不同的路径。默认值是../pom.xml。Maven首先在构建当前项目的地方寻找父项 目的pom,其次在文件系统的这个位置(relativePath位置),然后在本地仓库,最后在远程仓库寻找父项目的pom。-->
        <relativePath/>
    </parent>
    <!--声明项目描述符遵循哪一个POM模型版本。模型本身的版本很少改变,虽然如此,但它仍然是必不可少的,这是为了当Maven引入了新的特性或者其他模型变更的时候,确保稳定性。-->
    <modelVersion>4.0.0</modelVersion>
    <!--项目的全球唯一标识符,通常使用全限定的包名区分该项目和其他项目。并且构建时生成的路径也是由此生成, 如com.mycompany.app生成的相对路径为:/com/mycompany/app-->
    <groupId>asia.banseon</groupId>
    <!-- 构件的标识符,它和group ID一起唯一标识一个构件。换句话说,你不能有两个不同的项目拥有同样的artifact ID和groupID;在某个 特定的group ID下,artifact ID也必须是唯一的。构件是项目产生的或使用的一个东西,Maven为项目产生的构件包括:JARs,源 码,二进制发布和WARs等。-->
    <artifactId>banseon-maven2</artifactId>
    <!--项目产生的构件类型,例如jar、war、ear、pom。插件可以创建他们自己的构件类型,所以前面列的不是全部构件类型-->
    <packaging>jar</packaging>
    <!--项目当前版本,格式为:主版本.次版本.增量版本-限定版本号-->
    <version>1.0-SNAPSHOT</version>
    <!--项目的名称, Maven产生的文档用-->
    <name>banseon-maven</name>
    <!--项目主页的URL, Maven产生的文档用-->
    <url>http://www.baidu.com/banseon</url>
    <!-- 项目的详细描述, Maven 产生的文档用。  当这个元素能够用HTML格式描述时(例如,CDATA中的文本会被解析器忽略,就可以包含HTML标 签), 不鼓励使用纯文本描述。如果你需要修改产生的web站点的索引页面,你应该修改你自己的索引页文件,而不是调整这里的文档。-->
    <description>A maven project to study maven.</description>
    <!--描述了这个项目构建环境中的前提条件。-->
    <prerequisites>
        <!--构建该项目或使用该插件所需要的Maven的最低版本-->
        <maven/>
    </prerequisites>
    <!--项目的问题管理系统(Bugzilla, Jira, Scarab,或任何你喜欢的问题管理系统)的名称和URL,本例为 jira-->
    <issueManagement>
        <!--问题管理系统(例如jira)的名字,-->
        <system>jira</system>
        <!--该项目使用的问题管理系统的URL-->
        <url>http://jira.baidu.com/banseon</url>
    </issueManagement>
    <!--项目持续集成信息-->
    <ciManagement>
        <!--持续集成系统的名字,例如continuum-->
        <system/>
        <!--该项目使用的持续集成系统的URL(如果持续集成系统有web接口的话)。-->
        <url/>
        <!--构建完成时,需要通知的开发者/用户的配置项。包括被通知者信息和通知条件(错误,失败,成功,警告)-->
        <notifiers>
            <!--配置一种方式,当构建中断时,以该方式通知用户/开发者-->
            <notifier>
                <!--传送通知的途径-->
                <type/>
                <!--发生错误时是否通知-->
                <sendOnError>true</sendOnError>
                <!--构建失败时是否通知-->
                <sendOnFailure>false</sendOnFailure>
                <!--构建成功时是否通知-->
                <sendOnSuccess>true</sendOnSuccess>
                <!--发生警告时是否通知-->
                <sendOnWarning>true</sendOnWarning>
                <!--不赞成使用。通知发送到哪里-->
                <address/>
                <!--扩展配置项-->
                <configuration/>
            </notifier>
        </notifiers>
    </ciManagement>
    <!--项目创建年份,4位数字。当产生版权信息时需要使用这个值。-->
    <inceptionYear/>
    <!--项目相关邮件列表信息-->
    <mailingLists>
        <!--该元素描述了项目相关的所有邮件列表。自动产生的网站引用这些信息。-->
        <mailingList>
            <!--邮件的名称-->
            <name>Demo</name>
            <!--发送邮件的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建-->
            <post>banseon@126.com</post>
            <!--订阅邮件的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建-->
            <subscribe>banseon@126.com</subscribe>
            <!--取消订阅邮件的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建-->
            <unsubscribe>banseon@126.com</unsubscribe>
            <!--你可以浏览邮件信息的URL-->
            <archive>http:/hi.baidu.com/banseon/demo/dev/</archive>
        </mailingList>
    </mailingLists>
    <!--项目开发者列表-->
    <developers>
        <!--某个项目开发者的信息-->
        <developer>
            <!--SCM里项目开发者的唯一标识符-->
            <id>HELLO WORLD</id>
            <!--项目开发者的全名-->
            <name>banseon</name>
            <!--项目开发者的email-->
            <email>banseon@126.com</email>
            <!--项目开发者的主页的URL-->
            <url/>
            <!--项目开发者在项目中扮演的角色,角色元素描述了各种角色-->
            <roles>
                <role>Project Manager</role>
                <role>Architect</role>
            </roles>
            <!--项目开发者所属组织-->
            <organization>demo</organization>
            <!--项目开发者所属组织的URL-->
            <organizationUrl>http://hi.baidu.com/banseon</organizationUrl>
            <!--项目开发者属性,如即时消息如何处理等-->
            <properties>
                <dept>No</dept>
            </properties>
            <!--项目开发者所在时区, -11到12范围内的整数。-->
            <timezone>-5</timezone>
        </developer>
    </developers>
    <!--项目的其他贡献者列表-->
    <contributors>
        <!--项目的其他贡献者。参见developers/developer元素-->
        <contributor>
            <name/>
            <email/>
            <url/>
            <organization/>
            <organizationUrl/>
            <roles/>
            <timezone/>
            <properties/>
        </contributor>
    </contributors>
    <!--该元素描述了项目所有License列表。 应该只列出该项目的license列表,不要列出依赖项目的 license列表。如果列出多个license,用户可以选择它们中的一个而不是接受所有license。-->
    <licenses>
        <!--描述了项目的license,用于生成项目的web站点的license页面,其他一些报表和validation也会用到该元素。-->
        <license>
            <!--license用于法律上的名称-->
            <name>Apache 2</name>
            <!--官方的license正文页面的URL-->
            <url>http://www.baidu.com/banseon/LICENSE-2.0.txt</url>
            <!--项目分发的主要方式:
              repo,可以从Maven库下载
              manual, 用户必须手动下载和安装依赖-->
            <distribution>repo</distribution>
            <!--关于license的补充信息-->
            <comments>A business-friendly OSS license</comments>
        </license>
    </licenses>
    <!--SCM(Source Control Management)标签允许你配置你的代码库,供Maven web站点和其它插件使用。-->
    <scm>
        <!--SCM的URL,该URL描述了版本库和如何连接到版本库。欲知详情,请看SCMs提供的URL格式和列表。该连接只读。-->
        <connection>
            scm:svn:http://svn.baidu.com/banseon/maven/banseon/banseon-maven2-trunk(dao-trunk)
        </connection>
        <!--给开发者使用的,类似connection元素。即该连接不仅仅只读-->
        <developerConnection>
            scm:svn:http://svn.baidu.com/banseon/maven/banseon/dao-trunk
        </developerConnection>
        <!--当前代码的标签,在开发阶段默认为HEAD-->
        <tag/>
        <!--指向项目的可浏览SCM库(例如ViewVC或者Fisheye)的URL。-->
        <url>http://svn.baidu.com/banseon</url>
    </scm>
    <!--描述项目所属组织的各种属性。Maven产生的文档用-->
    <organization>
        <!--组织的全名-->
        <name>demo</name>
        <!--组织主页的URL-->
        <url>http://www.baidu.com/banseon</url>
    </organization>
    <!--构建项目需要的信息-->
    <build>
        <!--该元素设置了项目源码目录,当构建项目的时候,构建系统会编译目录里的源码。该路径是相对于pom.xml的相对路径。-->
        <sourceDirectory>./</sourceDirectory>
        <!--该元素设置了项目脚本源码目录,该目录和源码目录不同:绝大多数情况下,该目录下的内容 会被拷贝到输出目录(因为脚本是被解释的,而不是被编译的)。-->
        <scriptSourceDirectory>./</scriptSourceDirectory>
        <!--该元素设置了项目单元测试使用的源码目录,当测试项目的时候,构建系统会编译目录里的源码。该路径是相对于pom.xml的相对路径。-->
        <testSourceDirectory>./</testSourceDirectory>
        <!--被编译过的应用程序class文件存放的目录。-->
        <outputDirectory>./</outputDirectory>
        <!--被编译过的测试class文件存放的目录。-->
        <testOutputDirectory>./</testOutputDirectory>
        <!--使用来自该项目的一系列构建扩展-->
        <extensions>
            <!--描述使用到的构建扩展。-->
            <extension>
                <!--构建扩展的groupId-->
                <groupId>org.springframework.boot</groupId>
                <!--构建扩展的artifactId-->
                <artifactId>spring-boot-starter-web</artifactId>
                <!--构建扩展的版本-->
                <version>2.6.6</version>
            </extension>
        </extensions>
        <!--当项目没有规定目标(Maven2 叫做阶段)时的默认值-->
        <defaultGoal>install</defaultGoal>
        <!--这个元素描述了项目相关的所有资源路径列表,例如和项目相关的属性文件,这些资源被包含在最终的打包文件里。-->
        <resources>
            <!--这个元素描述了项目相关或测试相关的所有资源路径-->
            <resource>
                <!-- 描述了资源的目标路径。该路径相对target/classes目录(例如${project.build.outputDirectory})。举个例 子,如果你想资源在特定的包里(org.apache.maven.messages),你就必须该元素设置为org/apache/maven /messages。然而,如果你只是想把资源放到源码目录结构里,就不需要该配置。-->
                <targetPath>./</targetPath>
                <!--是否使用参数值代替参数名。参数值取自properties元素或者文件里配置的属性,文件在filters元素里列出。-->
                <filtering>false</filtering>
                <!--描述存放资源的目录,该路径相对POM路径-->
                <directory>/</directory>
                <!--包含的模式列表,例如**/*.xml.-->
                <includes/>
                <!--排除的模式列表,例如**/*.xml-->
                <excludes/>
            </resource>
        </resources>
        <!--这个元素描述了单元测试相关的所有资源路径,例如和单元测试相关的属性文件。-->
        <testResources>
            <!--这个元素描述了测试相关的所有资源路径,参见build/resources/resource元素的说明-->
            <testResource>
                <targetPath>./</targetPath>
                <filtering>false</filtering>
                <directory>/</directory>
                <includes/>
                <excludes/>
            </testResource>
        </testResources>
        <!--构建产生的所有文件存放的目录-->
        <directory>./</directory>
        <!--产生的构件的文件名,默认值是${artifactId}-${version}。-->
        <finalName>xxx</finalName>
        <!--当filtering开关打开时,使用到的过滤器属性文件列表-->
        <filters/>
        <!--子项目可以引用的默认插件信息。该插件配置项直到被引用时才会被解析或绑定到生命周期。给定插件的任何本地配置都会覆盖这里的配置-->
        <pluginManagement>
            <!--使用的插件列表 。-->
            <plugins>
                <!--plugin元素包含描述插件所需要的信息。-->
                <plugin>
                    <!--插件在仓库里的group ID-->
                    <groupId>org.springframework.boot</groupId>
                    <!--插件在仓库里的artifact ID-->
                    <artifactId>spring-boot-starter-web</artifactId>
                    <!--被使用的插件的版本(或版本范围)-->
                    <version>2.6.6</version>
                    <!--是否从该插件下载Maven扩展(例如打包和类型处理器),由于性能原因,只有在真需要下载时,该元素才被设置成enabled。-->
                    <extensions>false</extensions>
                    <!--在构建生命周期中执行一组目标的配置。每个目标可能有不同的配置。-->
                    <executions>
                        <!--execution元素包含了插件执行需要的信息-->
                        <execution>
                            <!--执行目标的标识符,用于标识构建过程中的目标,或者匹配继承过程中需要合并的执行目标-->
                            <id>default-cli</id>
                            <!--绑定了目标的构建生命周期阶段,如果省略,目标会被绑定到源数据里配置的默认阶段-->
                            <phase>compile</phase>
                            <!--配置的执行目标-->
                            <goals/>
                            <!--配置是否被传播到子POM-->
                            <inherited>true</inherited>
                            <!--作为DOM对象的配置-->
                            <configuration/>
                        </execution>
                    </executions>
                    <!--项目引入插件所需要的额外依赖-->
                    <dependencies>
                        <!--参见dependencies/dependency元素-->
                        <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-web</artifactId>
                            <version>2.6.6</version>
                        </dependency>
                    </dependencies>
                    <!--任何配置是否被传播到子项目-->
                    <inherited>true</inherited>
                    <!--作为DOM对象的配置-->
                    <configuration/>
                </plugin>
            </plugins>
        </pluginManagement>
        <!--使用的插件列表-->
        <plugins>
            <!--参见build/pluginManagement/plugins/plugin元素-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.6.6</version>
                <extensions>false</extensions>
                <executions>
                    <execution>
                        <id>default-cli</id>
                        <phase>compile</phase>
                        <goals/>
                        <inherited>true</inherited>
                        <configuration/>
                    </execution>
                </executions>
                <dependencies>
                    <!--参见dependencies/dependency元素-->
                    <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                        <version>2.6.6</version>
                    </dependency>
                </dependencies>
                <goals/>
                <inherited>true</inherited>
                <configuration/>
            </plugin>
        </plugins>
    </build>
    <!--在列的项目构建profile,如果被激活,会修改构建处理-->
    <profiles>
        <!--根据环境参数或命令行参数激活某个构建处理-->
        <profile>
            <!--构建配置的唯一标识符。即用于命令行激活,也用于在继承时合并具有相同标识符的profile。-->
            <id>default</id>
            <!--自动触发profile的条件逻辑。Activation是profile的开启钥匙。profile的力量来自于它
            能够在某些特定的环境中自动使用某些特定的值;这些环境通过activation元素指定。activation元素并不是激活profile的唯一方式。-->
            <activation>
                <!--profile默认是否激活的标志-->
                <activeByDefault>false</activeByDefault>
                <!--当匹配的jdk被检测到,profile被激活。例如,1.4激活JDK1.4,1.4.0_2,而!1.4激活所有版本不是以1.4开头的JDK。-->
                <jdk/>
                <!--当匹配的操作系统属性被检测到,profile被激活。os元素可以定义一些操作系统相关的属性。-->
                <os>
                    <!--激活profile的操作系统的名字-->
                    <name>Windows XP</name>
                    <!--激活profile的操作系统所属家族(如 'windows')-->
                    <family>Windows</family>
                    <!--激活profile的操作系统体系结构 -->
                    <arch>x86</arch>
                    <!--激活profile的操作系统版本-->
                    <version>5.1.2600</version>
                </os>
                <!--如果Maven检测到某一个属性(其值可以在POM中通过${名称}引用),其拥有对应的名称和值,Profile就会被激活。如果值
                字段是空的,那么存在属性名称字段就会激活profile,否则按区分大小写方式匹配属性值字段-->
                <property>
                    <!--激活profile的属性的名称-->
                    <name>mavenVersion</name>
                    <!--激活profile的属性的值-->
                    <value>2.0.3</value>
                </property>
                <!--提供一个文件名,通过检测该文件的存在或不存在来激活profile。missing检查文件是否存在,如果不存在则激活
                profile。另一方面,exists则会检查文件是否存在,如果存在则激活profile。-->
                <file>
                    <!--如果指定的文件存在,则激活profile。-->
                    <exists>/usr/local/hudson/hudson-home/jobs/maven-guide-zh-to-production/workspace/</exists>
                    <!--如果指定的文件不存在,则激活profile。-->
                    <missing>/usr/local/hudson/hudson-home/jobs/maven-guide-zh-to-production/workspace/</missing>
                </file>
            </activation>
            <!--构建项目所需要的信息。参见build元素-->
            <build>
                <defaultGoal>install</defaultGoal>
                <resources>
                    <resource>
                        <targetPath>./</targetPath>
                        <filtering>false</filtering>
                        <directory>/</directory>
                        <includes/>
                        <excludes/>
                    </resource>
                </resources>
                <testResources>
                    <testResource>
                        <targetPath>./</targetPath>
                        <filtering>false</filtering>
                        <directory>/</directory>
                        <includes/>
                        <excludes/>
                    </testResource>
                </testResources>
                <directory>/</directory>
                <finalName>xxx</finalName>
                <filters/>
                <pluginManagement>
                    <plugins>
                        <!--参见build/pluginManagement/plugins/plugin元素-->
                        <plugin>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-web</artifactId>
                            <version>2.6.6</version>
                            <extensions>false</extensions>
                            <executions>
                                <execution>
                                    <id>default-cli</id>
                                    <phase>compile</phase>
                                    <goals/>
                                    <inherited>true</inherited>
                                    <configuration/>
                                </execution>
                            </executions>
                            <dependencies>
                                <!--参见dependencies/dependency元素-->
                                <dependency>
                                    <groupId>org.springframework.boot</groupId>
                                    <artifactId>spring-boot-starter-web</artifactId>
                                    <version>2.6.6</version>
                                </dependency>
                            </dependencies>
                            <goals/>
                            <inherited>true</inherited>
                            <configuration/>
                        </plugin>
                    </plugins>
                </pluginManagement>
                <plugins>
                    <!--参见build/pluginManagement/plugins/plugin元素-->
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                        <version>2.6.6</version>
                        <extensions>false</extensions>  
                        <executions>
                            <execution>
                                <id>default-cli</id>
                                <phase>compile</phase>
                                <goals/>
                                <inherited>true</inherited>
                                <configuration/>
                            </execution>
                        </executions>
                        <dependencies>
                            <!--参见dependencies/dependency元素-->
                            <dependency>
                                <groupId>org.springframework.boot</groupId>
                                <artifactId>spring-boot-starter-web</artifactId>
                                <version>2.6.6</version>
                            </dependency>
                        </dependencies>
                        <goals/>
                        <inherited>true</inherited>
                        <configuration/>
                    </plugin>
                </plugins>
            </build>
            <!--模块(有时称作子项目) 被构建成项目的一部分。列出的每个模块元素是指向该模块的目录的相对路径-->
            <modules/>
            <!--发现依赖和扩展的远程仓库列表。-->
            <repositories>
                <!--参见repositories/repository元素-->
                <repository>
                    <releases>
                        <enabled>false</enabled>
                        <updatePolicy>never</updatePolicy>
                        <checksumPolicy>fail</checksumPolicy>
                    </releases>
                    <snapshots>
                        <enabled>false</enabled>
                        <updatePolicy>never</updatePolicy>
                        <checksumPolicy>fail</checksumPolicy>
                    </snapshots>
                    <id>bansoen-repository-proxy</id>
                    <name/>
                    <url/>
                    <layout>default</layout>
                </repository>
            </repositories>
            <!--发现插件的远程仓库列表,这些插件用于构建和报表-->
            <pluginRepositories>
                <!--包含需要连接到远程插件仓库的信息.参见repositories/repository元素-->
                <pluginRepository>
                    <releases>
                        <enabled>false</enabled>
                        <updatePolicy>never</updatePolicy>
                        <checksumPolicy>fail</checksumPolicy>
                    </releases>
                    <snapshots>
                        <enabled>false</enabled>
                        <updatePolicy>never</updatePolicy>
                        <checksumPolicy>fail</checksumPolicy>
                    </snapshots>
                    <id>bansoen-repository-proxy</id>
                    <name/>
                    <url/>
                    <layout>default</layout>
                </pluginRepository>
            </pluginRepositories>
            <!--该元素描述了项目相关的所有依赖。 这些依赖组成了项目构建过程中的一个个环节。它们自动从项目定义的仓库中下载。要获取更多信息,请看项目依赖机制。-->
            <dependencies>
                <!--参见dependencies/dependency元素-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                    <version>2.6.6</version>
                </dependency>
            </dependencies>
            <!--不赞成使用. 现在Maven忽略该元素.-->
            <reports/>
            <!--该元素包括使用报表插件产生报表的规范。当用户执行“mvn site”,这些报表就会运行。 在页面导航栏能看到所有报表的链接。参见reporting元素-->
            <reporting>
                ......
            </reporting>
            <!--参见dependencyManagement元素-->
            <dependencyManagement>
                <dependencies>
                    <!--参见dependencies/dependency元素-->
                    <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                        <version>2.6.6</version>
                    </dependency>
                </dependencies>
            </dependencyManagement>
            <!--参见distributionManagement元素-->
            <distributionManagement>
                ......
            </distributionManagement>
            <!--参见properties元素-->
            <properties/>
        </profile>
    </profiles>
    <!--模块(有时称作子项目) 被构建成项目的一部分。列出的每个模块元素是指向该模块的目录的相对路径-->
    <modules/>
    <!--发现依赖和扩展的远程仓库列表。-->
    <repositories>
        <!--包含需要连接到远程仓库的信息-->
        <repository>
            <!--如何处理远程仓库里发布版本的下载-->
            <releases>
                <!--true或者false表示该仓库是否为下载某种类型构件(发布版,快照版)开启。 -->
                <enabled>false</enabled>
                <!--该元素指定更新发生的频率。Maven会比较本地POM和远程POM的时间戳。这里的选项是:always(一直),daily(默认,每日),interval:X(这里X是以分钟为单位的时间间隔),或者never(从不)。-->
                <updatePolicy>always</updatePolicy>
                <!--当Maven验证构件校验文件失败时该怎么做:ignore(忽略),fail(失败),或者warn(警告)。-->
                <checksumPolicy>fail</checksumPolicy>
            </releases>
            <!-- 如何处理远程仓库里快照版本的下载。有了releases和snapshots这两组配置,POM就可以在每个单独的仓库中,为每种类型的构件采取不同的 策略。例如,可能有人会决定只为开发目的开启对快照版本下载的支持。参见repositories/repository/releases元素 -->
            <snapshots>
                <enabled>false</enabled>
                <updatePolicy>never</updatePolicy>
                <checksumPolicy>fail</checksumPolicy>
            </snapshots>
            <!--远程仓库唯一标识符。可以用来匹配在settings.xml文件里配置的远程仓库-->
            <id>banseon-repository-proxy</id>
            <!--远程仓库名称-->
            <name>banseon-repository-proxy</name>
            <!--远程仓库URL,按protocol://hostname/path形式-->
            <url>http://192.168.1.169:9999/repository/</url>
            <!-- 用于定位和排序构件的仓库布局类型-可以是default(默认)或者legacy(遗留)。Maven 2为其仓库提供了一个默认的布局;然 而,Maven 1.x有一种不同的布局。我们可以使用该元素指定布局是default(默认)还是legacy(遗留)。-->
            <layout>default</layout>
        </repository>
    </repositories>
    <!--发现插件的远程仓库列表,这些插件用于构建和报表-->
    <pluginRepositories>
        <!--包含需要连接到远程插件仓库的信息.参见repositories/repository元素-->
        <pluginRepository>
            <id>banseon-repository-proxy</id>
        </pluginRepository>
    </pluginRepositories>

    <!--该元素描述了项目相关的所有依赖。 这些依赖组成了项目构建过程中的一个个环节。它们自动从项目定义的仓库中下载。要获取更多信息,请看项目依赖机制。-->
    <dependencies>
        <dependency>
            <!--依赖的group ID-->
            <groupId>org.apache.maven</groupId>
            <!--依赖的artifact ID-->
            <artifactId>maven-artifact</artifactId>
            <!--依赖的版本号。 在Maven 2里, 也可以配置成版本号的范围。-->
            <version>3.6.3</version>
            <!-- 依赖类型,默认类型是jar。它通常表示依赖的文件的扩展名,但也有例外。一个类型可以被映射成另外一个扩展名或分类器。类型经常和使用的打包方式对应, 尽管这也有例外。一些类型的例子:jar,war,ejb-client和test-jar。如果设置extensions为 true,就可以在 plugin里定义新的类型。所以前面的类型的例子不完整。-->
            <type>jar</type>
            <!-- 依赖的分类器。分类器可以区分属于同一个POM,但不同构建方式的构件。分类器名被附加到文件名的版本号后面。例如,如果你想要构建两个单独的构件成 JAR,一个使用Java 1.4编译器,另一个使用Java 6编译器,你就可以使用分类器来生成两个单独的JAR构件。-->
            <classifier></classifier>
            <!--依赖范围。在项目发布过程中,帮助决定哪些构件被包括进来。欲知详情请参考依赖机制。
                - compile :默认范围,用于编译
                - provided:类似于编译,但支持你期待jdk或者容器提供,类似于classpath
                - runtime: 在执行时需要使用
                - test:    用于test任务时使用
                - system: 需要外在提供相应的元素。通过systemPath来取得
                - systemPath: 仅用于范围为system。提供相应的路径
                - optional:   当项目自身被依赖时,标注依赖是否传递。用于连续依赖时使用-->
            <scope>test</scope>
            <!--仅供system范围使用。注意,不鼓励使用这个元素,并且在新的版本中该元素可能被覆盖掉。该元素为依赖规定了文件系统上的路径。需要绝对路径而不是相对路径。推荐使用属性匹配绝对路径,例如${java.home}。-->
            <systemPath>/absolute-path</systemPath>
            <!--当计算传递依赖时, 从依赖构件列表里,列出被排除的依赖构件集。即告诉maven你只依赖指定的项目,不依赖项目的依赖。此元素主要用于解决版本冲突问题-->
            <exclusions>
                <exclusion>
                    <artifactId>spring-core</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
            <!--可选依赖,如果你在项目B中把C依赖声明为可选,你就需要在依赖于B的项目(例如项目A)中显式的引用对C的依赖。可选依赖阻断依赖的传递性。-->
            <optional>true</optional>
        </dependency>
    </dependencies>
    <!--不赞成使用. 现在Maven忽略该元素.-->
    <reports></reports>
    <!--该元素描述使用报表插件产生报表的规范。当用户执行“mvn site”,这些报表就会运行。 在页面导航栏能看到所有报表的链接。-->
    <reporting>
        <!--true,则,网站不包括默认的报表。这包括“项目信息”菜单中的报表。-->
        <excludeDefaults>false</excludeDefaults>
        <!--所有产生的报表存放到哪里。默认值是${project.build.directory}/site。-->
        <outputDirectory/>
        <!--使用的报表插件和他们的配置。-->
        <plugins>
            <!--plugin元素包含描述报表插件需要的信息-->
            <plugin>
                <!--报表插件在仓库里的group ID-->
                <groupId>org.springframework.boot</groupId>
                <!--报表插件在仓库里的artifact ID-->
                <artifactId>spring-boot-starter-web</artifactId>
                <!--被使用的报表插件的版本(或版本范围)-->
                <version>2.6.6</version>
                <!--任何配置是否被传播到子项目-->
                <inherited>true</inherited>
                <!--报表插件的配置-->
                <configuration/>
                <!--一组报表的多重规范,每个规范可能有不同的配置。一个规范(报表集)对应一个执行目标 。例如,有1,2,3,4,5,6,7,8,9个报表。1,2,5构成A报表集,对应一个执行目标。2,5,8构成B报表集,对应另一个执行目标-->
                <reportSets>
                    <!--表示报表的一个集合,以及产生该集合的配置-->
                    <reportSet>
                        <!--报表集合的唯一标识符,POM继承时用到-->
                        <id/>
                        <!--产生报表集合时,被使用的报表的配置-->
                        <configuration/>
                        <!--配置是否被继承到子POMs-->
                        <inherited/>
                        <!--这个集合里使用到哪些报表-->
                        <reports/>
                    </reportSet>
                </reportSets>
            </plugin>
        </plugins>
    </reporting>
    <!-- 继承自该项目的所有子项目的默认依赖信息。这部分的依赖信息不会被立即解析,而是当子项目声明一个依赖(必须描述group ID和 artifact ID信息),如果group ID和artifact ID以外的一些信息没有描述,则通过group ID和artifact ID 匹配到这里的依赖,并使用这里的依赖信息。-->
    <dependencyManagement>
        <dependencies>
            <!--参见dependencies/dependency元素-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.6.6</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!--项目分发信息,在执行mvn deploy后表示要发布的位置。有了这些信息就可以把网站部署到远程服务器或者把构件部署到远程仓库。-->
    <distributionManagement>
        <!--部署项目产生的构件到远程仓库需要的信息-->
        <repository>
            <!--是分配给快照一个唯一的版本号(由时间戳和构建流水号)?还是每次都使用相同的版本号?参见repositories/repository元素-->
            <uniqueVersion>false</uniqueVersion>
            <id>banseon-maven2</id>
            <name>banseon maven2</name>
            <url>file://${basedir}/target/deploy</url>
            <layout>default</layout>
        </repository>
        <!--构件的快照部署到哪里?如果没有配置该元素,默认部署到repository元素配置的仓库,参见distributionManagement/repository元素-->
        <snapshotRepository>
            <uniqueVersion>false</uniqueVersion>
            <id>banseon-maven2</id>
            <name>Banseon-maven2 Snapshot Repository</name>
            <url>scp://svn.baidu.com/banseon:/usr/local/maven-snapshot</url>
            <layout>default</layout>
        </snapshotRepository>
        <!--部署项目的网站需要的信息-->
        <site>
            <!--部署位置的唯一标识符,用来匹配站点和settings.xml文件里的配置-->
            <id>banseon-site</id>
            <!--部署位置的名称-->
            <name>business api website</name>
            <!--部署位置的URL,按protocol://hostname/path形式-->
            <url>
                scp://svn.baidu.com/banseon:/var/www/localhost/banseon-web
            </url>
        </site>
        <!--项目下载页面的URL。如果没有该元素,用户应该参考主页。使用该元素的原因是:帮助定位那些不在仓库里的构件(由于license限制)。-->
        <downloadUrl/>
        <!--如果构件有了新的group ID和artifact ID(构件移到了新的位置),这里列出构件的重定位信息。-->
        <relocation>
            <!--构件新的group ID-->
            <groupId/>
            <!--构件新的artifact ID-->
            <artifactId/>
            <!--构件新的版本号-->
            <version/>
            <!--显示给用户的,关于移动的额外信息,例如原因。-->
            <message/>
        </relocation>
        <!-- 给出该构件在远程仓库的状态。不得在本地项目中设置该元素,因为这是工具自动更新的。有效的值有:none(默认),converted(仓库管理员从 Maven 1 POM转换过来),partner(直接从伙伴Maven 2仓库同步过来),deployed(从Maven 2实例部 署),verified(被核实时正确的和最终的)。-->
        <status>deployed</status>
    </distributionManagement>
    <!--以值替代名称,Properties可以在整个POM中使用,也可以作为触发条件(见settings.xml配置文件里activation元素的说明)。格式是<name>value</name>。-->
    <properties/>
</project>
阅读全文

spring

记录 2025/2/24

Spring Framework

IOC

IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。

Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。

IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。

  • 控制反转是一种思想。

  • 控制反转是为了降低程序耦合度,提高程序扩展力。

  • 控制反转,反转的是什么?

    • 将对象的创建权利交出去,交给第三方容器负责。
    • 将对象和对象之间关系的维护权交出去,交给第三方容器负责。
  • 控制反转这种思想如何实现呢?

    • DI(Dependency Injection):依赖注入

依赖注入

依赖注入常见的实现方式包括两种:

  • 构造器注入
  • Setter 注入

Spring IoC 容器

Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IoC 容器。Spring 提供了IoC 容器的两种实现方式:

  • BeanFactory
  • ApplicationContext

BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身,是 Spring 框架的基础接口,不提供完整的 IoC 功能,但是 Spring 框架中的其他组件都是以 BeanFactory 为基础构建的。

ApplicationContext 是 BeanFactory 的子接口,提供了更多的高级特性,它是 Spring 框架的一个更高级的容器,它除了提供 IoC 容器的基本功能外,还提供了事件传播、国际化信息绑定等功能。

ApplicationContext的实现类有:

  • ClassPathXmlApplicationContext:从类路径下加载配置文件
  • FileSystemXmlApplicationContext:从文件系统中加载配置文件
  • XmlWebApplicationContext:在 Web 应用中加载配置文件
  • AnnotationConfigApplicationContext:基于注解的配置类加载容器

Bean 的生命周期

Spring 容器管理 Bean 的生命周期,Spring 容器负责创建 Bean 实例,Spring 容器负责 Bean 的初始化和销毁。

Bean 的生命周期包括以下阶段:

  • 实例化 Bean:容器根据配置文件中的信息创建 Bean 实例。
  • 设置对象属性:容器在创建 Bean 的时候,会通过 set 方法设置 Bean 的属性值。
  • Bean 的后置处理器:容器会调用 Bean 的后置处理器方法。(postProcessBeforeInitialization)
  • Bean 的初始化:容器会调用 Bean 的初始化方法。
  • Bean 的后置处理器:容器会再次调用 Bean 的后置处理器方法。(postProcessAfterInitialization)
  • Bean 的销毁:容器会调用 Bean 的销毁方法。

Bean 的作用域

Bean 的作用域是指 Bean 实例的作用范围,Spring 容器支持以下几种作用域:

  • singleton:单例模式,一个 Bean 容器只有一个实例。
  • prototype:原型模式,每次从容器中获取 Bean 时,都会创建一个新的实例。
  • request:每次 HTTP 请求都会创建一个新的 Bean 实例。
  • session:每次 HTTP Session 都会创建一个新的 Bean 实例。
  • global-session:全局 Session 作用域,仅在基于 Servlet 的 Web 应用中有效。
  • application:全局作用域,整个应用中只有一个实例。
  • websocket:WebSocket 作用域,仅在基于 WebSocket 的 Web 应用中有效。
  • custom:自定义作用域。

AOP

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP 的术语

  • Aspect:切面,是一个类,它包含了一些方法,这些方法可以在切入点之前或之后执行。
  • Joinpoint:连接点,是程序执行过程中的一个点,比如方法的调用或异常的处理。
  • Pointcut:切入点,是一组连接点的集合,可以通过表达式来描述。
  • Advice:通知,是切面在连接点执行的动作。
  • Introduction:引介,是一种特殊的通知,它向现有的类添加新方法和属性。
  • Target:目标对象,是被一个或多个切面所通知的对象。
  • Weaving:织入,是把切面连接到目标对象并创建新的代理对象的过程。
  • Proxy:代理,是一个对象,它通过连接到目标对象的方式间接控制对目标对象的访问。

AOP 的实现方式

  • 代理对象有接口:JDK 动态代理
  • 代理对象没有接口:CGLIB 动态代理

Resources

Spring用于访问资源的接口是 org.springframework.core.io.Resource,它是一个接口,Spring 提供了多种实现类,用于访问不同的资源。

实现类

  • UrlResource:用于访问 URL 资源。
  • ClassPathResource:用于访问类路径下的资源。
  • FileSystemResource:用于访问文件系统资源。
  • ServletContextResource:用于访问 ServletContext 资源。
  • InputStreamResource:用于访问输入流资源。
  • ByteArrayResource:用于访问字节数组资源。

加载资源

Spring 提供了 ResourceLoader 接口,用于加载资源。该接口实现类的实例可以获得一个Resource实例。ResourceLoaderAware接口是一个回调接口,用于设置ResourceLoader实例。如果 Bean 实例需要访问资源,有如下两种解决方案:

  • 获取 ResourceLoader 实例,然后调用 getResource() 方法。
  • 使用依赖注入

对于第一种方式,当程序获取 Resource 实例时,总需要提供 Resource 所在的位置,不管通过 FileSystemResource 创建实例,还是通过 ClassPathResource 创建实例,或者通过 ApplicationContext 的 getResource() 方法获取实例,都需要提供资源位置。这意味着:资源所在的物理位置将被耦合到代码中,如果资源位置发生改变,则必须改写程序。因此,通常建议采用第二种方法,让 Spring 为 Bean 实例依赖注入资源。

Validation

在开发中,我们经常遇到参数校验的需求,比如用户注册的时候,要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式,我们会把校验的代码和真正的业务处理逻辑耦合在一起,而且如果未来要新增一种校验逻辑也需要在修改多个地方。而spring validation允许通过注解的方式来定义对象校验规则,把校验和业务逻辑分离开,让代码编写更加方便。Spring Validation其实就是对Hibernate Validator进一步的封装,方便在Spring中使用。一般有多种校验方式:

  • 基于Validator接口的校验
  • 基于注解的校验
  • 基于自定义注解的校验

Spring Data

Transactions

Spring框架中的事务是通过AOP来实现的,Spring事务管理的底层实现是通过对DataSource和JDBC的封装来实现的。

基于注解的事务管理

Spring 通过 @Transactional 注解来实现事务管理,@Transactional 注解可以加在类上,也可以加在方法上。

事务属性

@Transactional 注解有以下几个属性:

  • propagation:事务的传播行为
    • Propagation.REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
    • Propagation.SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
    • Propagation.MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
    • Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
    • Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    • Propagation.NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
    • Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则新建一个事务。
  • isolation:事务的隔离级别
  • timeout:事务的超时时间
  • readOnly:事务是否只读
  • rollbackFor:发生哪些异常回滚
  • noRollbackFor:发生哪些异常不回滚

Spring Boot

Spring Boot 是 Spring 的一个项目,它简化了 Spring 应用的初始搭建,提供了一些默认配置,可以快速地开发 Spring 应用。约定大于配置,Spring Boot 通过自动配置和起步依赖简化了项目的配置。

Spring Boot Starter

Spring Boot Starter 是 Spring Boot 的一个重要特性,它是一个依赖描述符,用于简化 Maven 或 Gradle 的配置。Spring Boot Starter 可以让你不需要关心 Spring Boot 的版本,只需要引入 Starter,Spring Boot 会自动配置项目。将功能场景提取出来,打包成一个 Starter,然后在项目中引入 Starter,就可以使用这个功能场景。

Spring Boot AutoConfiguration

版本

spring boot父项目spring-boot-starter-parent的父项目spring-boot-dependencies中定义了大量的依赖版本,这些版本是经过测试的,可以放心使用。如果我们在项目中引入了spring-boot-dependencies,就可以不用再指定版本号了。

注解

  • @SpringBootApplication:Spring Boot 应用的入口,它是一个组合注解,包含了 @Configuration@EnableAutoConfiguration@ComponentScan 注解。
  • @EnableAutoConfiguration:启用自动配置,Spring Boot 会根据项目中的依赖自动配置项目。
  • @Configuration:配置类,相当于 Spring 中的 XML 配置文件。
  • @AutoConfigurationPackage:自动配置包,用于自动配置类的扫描。将主配置类(@SpringBootApplication 注解的类)所在包及其子包下的所有类都注册到 Spring 容器中。
  • @Import:导入其他配置类。
  • @SpringBootConfiguration:Spring Boot 的配置类,它是 @Configuration 的派生注解。
  • @ConfigurationProperties:绑定配置文件中的属性值。
  • @Value:获取配置文件中的属性值。
  • @PropertySource:加载指定的配置文件。
  • @ImportResource:导入 Spring 的 XML 配置文件。

自动配置原理

spring boot启动时找到@SpringBootApplication注解的类,然后开启@EnableAutoConfiguration。其中在@EnableAutoConfiguration注解中,有一个@Import注解,这个注解导入了AutoConfigurationImportSelector类,这个类中有一个selectImports方法,这个方法会根据META-INF/spring.factories文件中的配置,获取EnableAutoConfiguration注解的值,然后根据这个值,导入对应的自动配置类。自动配置类中有@EnableConfigurationProperties注解,这个注解会导入ConfigurationPropertiesBindingPostProcessor类,这个类会将@ConfigurationProperties注解的类绑定到配置文件中的属性。自动配置类中还有很多@ConditionalOn注解,这些注解会根据条件来判断是否需要导入这个自动配置类。自动配置类中有Properties类,这个类中有很多属性,这些属性会从配置文件中读取,然后根据这些属性来配置项目。

配置加载位置

Spring Boot 会按照以下顺序加载配置文件,优先级从高到低:

  • 当前目录下的 config 目录
  • 当前目录
  • classpath 下的 config 目录
  • classpath 根目录
  • @PropertySource 注解指定的位置
  • spring.config.location 环境变量指定的位置

外部配置加载顺序

Spring Boot 会按照以下顺序加载外部配置,优先级从高到低:

  • 命令行参数
  • 来自 java:comp/env 的 JNDI 属性
  • Java 系统属性(System.getProperties())
  • 操作系统环境变量
  • RandomValuePropertySource 中的属性
  • application.propertiesapplication.yml 文件中的属性
  • @PropertySource 注解指定的属性
  • 默认属性
  • SpringApplication.setDefaultProperties 指定的属性

bootstrap和application

Spring Boot 有两个配置文件,一个是 bootstrap.propertiesbootstrap.yml,另一个是 application.propertiesapplication.ymlbootstrap 配置文件用于 Spring Boot 应用的引导阶段,它是 Spring Cloud 的配置文件,用于配置应用的上下文信息。application 配置文件用于 Spring Boot 应用的运行阶段,它是 Spring Boot 的配置文件,用于配置应用的运行信息。

加载顺序:

  • bootstrap.propertiesbootstrap.yml
  • bootstrap-{profile}.propertiesbootstrap-{profile}.yml
  • application.propertiesapplication.yml
  • application-{profile}.propertiesapplication-{profile}.yml

运行流程

Spring Boot 应用的运行流程如下:

  1. 创建 Spring Boot 应用。
    • 保存主配置类
    • 判断是否为 Web 应用
    • 获取所有的 ApplicationContextInitializer
    • 获取所有的 ApplicationListener
    • 从多个配置类中找到主配置类
  2. 调用 SpringApplication.run() 方法
    • 获取SpringApplicationRunListeners,并调用starting方法
    • 准备环境,创建环境后调用监听器environmentPrepared方法
    • 创建应用上下文(IOC容器)
    • 准备上下文,调用Initializer的初始化方法,调用监听器contextPrepared方法,最后调用监听器的contextLoaded方法
  3. 刷新应用上下文
    • 刷新应用上下文,IOC容器初始化
    • 回调事件,调用监听器的started方法
  4. 运行应用
    • callRunners方法,调用所有的CommandLineRunnerApplicationRunner的run方法
    • 回调事件ready,调用监听器的ready方法

日志

日志由日志门面(抽象层)和日志实现构成,Spring Boot 默认使用 SLF4J 作为日志门面,Logback 作为日志实现,并且引入了其他日志框架的适配器,可以使用其他日志框架。在导入依赖时,需排除默认的非 SLF4J 日志框架。

阅读全文

数据库

记录 2025/2/24

外键

  1. 企业级项目一般不建立外键,因为外键会导致数据库的耦合性增加,而且外键存在级联约束,会导致数据库的性能下降。主要影响写入操作,对子表进行写入的时候会对父表加共享锁,在高并发的情况下会导致数据库的性能下降。
  2. 阿里巴巴Java开源手册——不得使用外键与级联,一切外键关联一律在应用层处理。

事务

  1. 数据库事务是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
  2. 事务的四个特性:原子性A、一致性C、隔离性I、持久性D。
    • 原子性:事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
    • 一致性:事务执行前后,数据库的完整性约束没有被破坏。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
    • 隔离性:多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
    • 持久性:事务一旦提交,对数据库的改变是永久性的。
阅读全文

跳表

数据结构 2025/2/23

结构

跳表的期望空间复杂度为O(n),跳表的查询,插入和删除操作的期望时间复杂度均为O(logn)。跳表实际为一种多层的有序链表,跳表的每一层都为一个有序链表,且满足每个位于第i层的节点有p的概率出现在第i+1层,其中p为常数。

查询

从跳表的当前的最大层数level层开始查找,在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层进行查找,重复这个过程直至到达第1层。此时,若第1层的下一个节点的值等于target,则返回true;反之,则返回false。如图所示:

添加

从跳表的当前的最大层数level层开始查找,在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层进行查找,重复这个过程直至到达第1层。设新加入的节点为newNode,我们需要计算出此次节点插入的层数lv,如果level小于lv,则同时需要更新level。我们用数组update保存每一层查找的最后一个节点,第i层最后的节点为update[i]。我们将newNode的后续节点指向update[i]下一个节点,同时更新update[i]的后续节点为newNode。如图所示:

删除

首先我们需要查找当前元素是否存在跳表中。从跳表的当前的最大层数level层开始查找,在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层进行查找,重复这个过程直至到达第1层。如果第1层的下一个节点不等于num时,则表示当前元素不存在直接返回。我们用数组update保存每一层查找的最后一个节点,第i层最后的节点为update[i]。此时第i层的下一个节点的值为num,则我们需要将其从跳表中将其删除。由于第i层的以p的概率出现在第i+1层,因此我们应当从第1层开始往上进行更新,将numupdate[i]下一跳中删除,同时更新update[i]的后续节点,直到当前层的链表中没有出现num的节点为止。最后我们还需要更新跳表中当前的最大层数level。如图所示:

空间复杂度分析

每次添加节点时,节点出现在第i层的概率为\((1-p)×{p}^{i-1}\),跳表插入时的期望层数为:
\[ E(L)=\sum_{i=1}^{\infin}i×(1-p)×{p}^{i-1}=\frac{1}{1-p} \]

如果节点的目标层数为L,则此时需要的空间为O(L),因此总的空间复杂度为\(O(n×E(L))=O(n×\frac{1}{1-p})=O(n)\)

时间复杂度分析

在含有n个节点的跳表中,当前最大层数L(n)包含的元素个数期望为\(\frac{1}{p}\),根据跳表的定义可以知道第1层的每个元素出现在L(n)的概率为\({p}^{L(n)-1}\),则此时我们可以推出如下:
\[ \frac{1}{p}=n{p}^{L(n)-1} \]
根据以上结论可以知道在含有n个节点的跳表中,当前最大层数期望\(L(n)={log}_{p}\frac{1}{n}\)

C(i)为在一个无限长度的跳表中向上爬i层的期望代价,最小代价即为往左上角爬,根据定义,则知道:
\[ \begin{gather} C(0)=0 \\ C(i)=(1-p)(1+C(i))+p(1+C(i-1)) \\ C(i)=\frac{i}{p} \end{gather} \]
在含有n个元素的跳表中,从第1层爬到第L(n)层的期望步数存在上界\(\frac{L(n)-1}{p}\),当达到第L(n)层后,我们需要向左走。我们已知L(n)层的节点总数的期望存在上界为\(\frac{1}{p}\),所以平均查询时间复杂度为\(\frac{L(n)-1}{p}+\frac{1}{p}=\frac{log_{\frac{1}{p}}n}{p}=O(log\ n)\)

实现细节

每个跳表节点保存一个值以及下一列的地址

class Skiplist {
    
    // 最大层数
    static final int MAX_LEVEL = 32;
    // 下一层留存概率
    static final double P = 0.5;
    // 哨兵节点
    private SkiplistNode head;
    // 当前最大层数
    private int level;
    // 用于插入时随机分配层数
    private Random random;

    public Skiplist() {
        this.head = new SkiplistNode(-1, MAX_LEVEL);
        this.level = 0;
        this.random = new Random();
    }
    
    public boolean search(int target) {
        SkiplistNode cur = this.head;
        // 从最高层开始向下查找
        for (int i = this.level - 1; i >= 0; i--) {
            // 在当前层从左往右查找不大于target的最大节点
            while (cur.forward[i] != null && cur.forward[i].val < target) {
                cur = cur.forward[i];
            }
        }
        // 下一节点
        cur = cur.forward[0];
        if (cur != null && cur.val == target) {
            return true;
        }
        return false;
    }
    
    public void add(int num) {
        // 保存每层需要更新下一节点的列地址
        SkiplistNode[] update = new SkiplistNode[MAX_LEVEL];
        Arrays.fill(update, this.head);
        SkiplistNode cur = this.head;
        for (int i = this.level - 1; i >= 0; i--) {
            while (cur.forward[i] != null && cur.forward[i].val < num) {
                cur = cur.forward[i];
            }
            update[i] = cur;
        }
        int lv = this.randomLevel();
        // 更新层数
        this.level = Math.max(this.level, lv);
        SkiplistNode newNode = new SkiplistNode(num, lv);
        // 更新列
        for (int i = 0; i < lv; i++) {
            newNode.forward[i] = update[i].forward[i];
            update[i].forward[i] = newNode;
        }
    }
    
    public boolean erase(int num) {
        SkiplistNode[] update = new SkiplistNode[MAX_LEVEL];
        SkiplistNode cur = this.head;
        for (int i = this.level - 1; i >= 0; i--) {
            while (cur.forward[i] != null && cur.forward[i].val < num) {
                cur = cur.forward[i];
            }
            update[i] = cur;
        }
        cur = cur.forward[0];
        // 不在跳表里
        if (cur == null || cur.val != num) {
            return false;
        }
        for (int i = 0; i < this.level; i++) {
            if (update[i].forward[i] != cur) {
                break;
            }
            update[i].forward[i] = cur.forward[i];
        }
        // 更新层数
        while (this.level > 1 && this.head.forward[this.level - 1] == null) {
            this.level--;
        }
        return true;
    }

    private int randomLevel() {
        int lv = 1;
        while (random.nextDouble() < P && lv < MAX_LEVEL) {
            lv++;
        }
        return lv;
    }
    
}

class SkiplistNode {
    
    // 节点值
    int val;
    // 下一列的地址
    SkiplistNode[] forward;

    public SkiplistNode(int val, int maxLevel) {
        this.val = val;
        this.forward = new SkiplistNode[maxLevel];
    }
    
}
阅读全文

简历优化

求职 2024/12/28

优化目标——让HR在短时间内看出你和岗位的匹配度

核心

  1. 我是什么人/角色(who),我做了什么工作(what),做出了什么结果(how)
  2. 2-3行一段,使用短句
  3. 量化所作的工作细节以及取得成果
  4. 围绕岗位关键词展开

内容


个人信息

  • 姓名
  • 性别
  • 年龄(视情况)
  • 联系方式
  • 电子邮箱(正式邮箱,不要使用QQ邮箱

教育背景

  • 时间
  • 院校
  • 专业
  • 学历
  • 成绩(视情况)

大学及以后教育经历,时间由近到远列出


项目经历

STAR法则

Situation:什么场景/背景

Task:什么任务/目标

Action:做了什么措施

Result:取得什么结果


工作经历

项目经历,应用STAR法则


在校经历(视情况)


个人技能


获奖情况


自我评价(视情况)

要写就要博人眼球,突出自己的特点

阅读全文

组建NAS(五)

记录 2024/10/8

使用TailScale

虽然NAS已经可以正常使用,但是ZeroTier对手机端的支持并没有想象中好,于是选择更换组网框架——TailScale

安装TailScale

  1. 停止并删除ZeroTier相关容器,删除相关数据卷和数据文件,删除相关镜像

  2. 到官网Tailscale · Best VPN Service for Secure Networks创建一个网络,并生成auth-key

  3. 拉取TailScale镜像

    docker pull tailscale/tailscale:latest
  4. 运行容器

    sudo docker run -d --name tailscale --restart always --cap-add NET_ADMIN --cap-add SYS_MODULE -v /dev/net/tun:/dev/net/tun -v tailscale-state:/var/lib/tailscale -e TS_AUTHKEY=<auth-key> -e TS_STATE_DIR=/var/lib/tailscale -e TS_USERSPACE=false -e TS_ACCEPT_DNS=true --hostname <hostname> --network host tailscale/tailscale:latest
  5. 访问网络控制面板,解除对设备的有期限授权

  6. 在控制面板关闭MagicDNS,添加自定义DNS服务器

  7. 在DNS服务器添加本地DNS记录

  8. 其他客户端只需下载相应操作系统客户端并使用同一账户登录即可

使用LDAP

Nextcloud本身在图片和影音上并不出色,只是集成方便,于是我打算使用专门的软件对图片、影音进行管理,因此就需要用到统一验证

安装OpenLDAP、Authelia

  • 拉取镜像

    sudo docker pull osixia/openldap
  • 运行容器

    sudo docker run -d \
        -e LDAP_DOMAIN=<域名> \
        -e LDAP_ORGANISATION=nas \
        -e LDAP_ADMIN_PASSWORD=<password> \
        --name openldap \
        --restart always \
        osixia/openldap
  • 拉取UI镜像

    sudo docker pull osixia/phpldapadmin
  • 运行UI,登录用户cn=admin,dc=二级域名,dc=顶级域名

    sudo docker run -d \
        -e PHPLDAPADMIN_LDAP_HOSTS=<ldap-server-ip> \
        -e PHPLDAPADMIN_HTTPS=false \
        -e PHPLDAPADMIN_TRUST_PROXY_SSL=true \
        -v pla-data:/var/www/phpldapadmin \
        --name pla \
        --restart always \
        osixia/phpldapadmin
  • 拉取镜像

    sudo docker pull authelia/authelia
  • 拉取postgres

    sudo docker pull postgres
  • 编写配置configuration.yml

    default_2fa_method: 'totp'
    server:
      address: 'tcp://:9091/'
    totp:
      issuer: '域名'
    identity_validation:
      reset_password:
        jwt_secret: '密钥'
    authentication_backend:
      ldap:
        address: 'ldap://openldap:389'
        base_dn: 'dc=二级域名,dc=顶级域名'
        user: 'cn=admin,dc=二级域名,dc=顶级域名'
        password: 'admin-password'
        users_filter: '(&({username_attribute}={input})(objectClass=inetOrgPerson))'
        groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
    access_control:
      rules:
      - domain: 'auth.域名'
        policy: 'bypass'
    session:
      secret: '密钥'
      cookies:
        - domain: '域名'
          authelia_url: 'https://auth.域名'
    storage:
      encryption_key: '密钥>32'
      postgres:
        address: 'tcp://authelia_postgres:5432'
        database: 'authelia'
        username: 'authelia'
        password: 'database-password'
    notifier:
      filesystem:
        filename: '/config/notification.txt'
    identity_providers:
      oidc:
        hmac_secret: '密钥64'
        jwks:
          - key: '密钥'
        clients:
          - client_id: 'immich'
            client_name: 'immich'
            client_secret: '密钥'
            public: false
            authorization_policy: 'one_factor'
            redirect_uris:
              - 'https://immich.域名/auth/login'
              - 'https://immich.域名/user-settings'
              - 'app.immich:///oauth-callback'
            scopes:
              - 'openid'
              - 'profile'
              - 'email'
            userinfo_signed_response_alg: 'none'
          - client_id: 'jellyfin'
            client_name: 'jellyfin'
            client_secret: ''
            public: false
            authorization_policy: 'one_factor'
            require_pkce: true
            pkce_challenge_method: 'S256'
            redirect_uris:
              - 'https://jellyfin.域名/sso/OID/redirect/Authelia'
              - 'https://jellyfin.域名/sso/OID/r/Authelia'
            scopes:
              - 'openid'
              - 'profile'
              - 'groups'
            userinfo_signed_response_alg: 'none'
            token_endpoint_auth_method: 'client_secret_post'
          - client_id: 'nextcloud'
            client_name: 'nextcloud'
            client_secret: ''
            public: false
            authorization_policy: 'one_factor'
            require_pkce: true
            pkce_challenge_method: 'S256'
            redirect_uris:
              - 'https://nextcloud.域名/apps/user_oidc/code'
            scopes:
              - 'openid'
              - 'profile'
              - 'email'
              - 'groups'
            userinfo_signed_response_alg: 'none'
            token_endpoint_auth_method: 'client_secret_post'
  • 编写docker-compose.yml

    services:
      authelia:
        container_name: authelia
        image: authelia/authelia:latest
        restart: always
        environment:
          TZ: Asia/Shanghai
        volumes:
          - /path/to/authelia/config:/config
        depends_on:
          - database
          - ldap
    
      database:
        container_name: authelia_postgres
        image: postgres:latest
        restart: always
        environment:
          POSTGRES_PASSWORD: <password>
          POSTGRES_USER: authelia
          POSTGRES_DB: authelia
        volumes:
          - ./postgres:/var/lib/postgresql/data
    
      ldap:
        container_name: openldap
        image: osixia/openldap:latest
        restart: always
        environment:
          LDAP_DOMAIN: <域名>
          LDAP_ORGANISATION: nas
          LDAP_ADMIN_PASSWORD: <password>
        volumes:
          - ./ldap/data:/var/lib/ldap
          - ./ldap/config:/etc/ldap/slapd.d
    
      pla:
        container_name: pla
        image: osixia/phpldapadmin:latest
        restart: always
        environment:
          PHPLDAPADMIN_LDAP_HOSTS: openldap
          PHPLDAPADMIN_HTTPS: false
          PHPLDAPADMIN_TRUST_PROXY_SSL: true
        volumes:
          - ./pla:/var/www/phpldapadmin
        depends_on:
          - ldap
  • 启动编排

    sudo docker compose up -d
  • 配置反向代理

    https://ldap.域名:443 {
      reverse_proxy http://pla-ip:80
    }
    https://auth.域名:443 {
        reverse_proxy http://authelia-ip:9091
    }

NextCloud配置

  • 安装OpenID Connect user backend应用

  • 管理设置里的OpenID Connect启用OpenID

  • 添加Authelia

    • Identifier: Authelia
    • Client ID: nextcloud
    • Client secret: insecure_secret
    • Discovery endpoint: https://auth.example.com/.well-known/openid-configuration
    • Scope: openid email profile

安装jellyfin

  • 拉取镜像

    sudo docker pull jellyfin/jellyfin
  • 运行镜像

    # 查询render组id,用于硬件加速
    getent group render | cut -d: -f3
    getent group video | cut -d: -f3
    sudo docker run -d \
        --name jellyfin \
        --user 33:33 \
        -v /path/to/jellyfin/config:/config \
        -v /path/to/jellyfin/cache:/cache \
        -v /path/to/media:/media \
        --restart always \
        --net host \
        --group-add="render-group-id" \
        --device /dev/dri/renderD128:/dev/dri/renderD128 \
        jellyfin/jellyfin
    https://jellyfin.域名:443 {
      reverse_proxy jellyfin.域名:8096
    }
  • 在控制面板-常规-品牌添加以下内容

    <form action="https://jellyfin.域名/sso/OID/start/服务提供商id">
      <button class="raised block emby-button button-submit">Login with 服务提供商id</button>
    </form>
    #loginPage .readOnlyContent {
      display: flex;
      flex-direction: column-reverse;
    }
    
    .loginDisclaimerContainer {
      margin-top: 0;
      margin-bottom: 1em;
    }
    
    .loginDisclaimer {
      width: 100%;
      height: 100%;
    }
  • 安装jellyfin sso插件

  • 填写配置

    1. Visit the Jellyfin Administration Dashboard.
    2. Visit the Plugins section.
    3. Visit the Repositories tab.
    4. Click the + to add a repository.
    5. Enter the following details:
      1. Repository Name: Jellyfin SSO
      2. Repository URL: https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json
    6. Click Save.
    7. Click Ok to confirm the repository installation.
    8. Visit the Catalog tab.
    9. Select SSO Authentication from the Authentication section.
    10. Click Install.
    11. Click Ok to confirm the plugin installation.
    12. Once installed restart Jellyfin.
    13. Complete steps 1 and 2 again.
    14. Click the SSO-Auth plugin.
    15. Add a provider with the following settings:
      1. Name of the OID Provider: Authelia
      2. OID Endpoint: https://auth.example.com
      3. OpenID Client ID: jellyfin
      4. OID Secret: insecure_secret
      5. Enabled: Checked
      6. Enable Authorization by Plugin: Checked
      7. Enable All Folders: Checked
      8. Roles: jellyfin-users
      9. Admin Roles: jellyfin-admins
      10. Role Claim: groups
      11. Request Additional Scopes: groups
      12. Set default username claim: preferred_username
    16. All other options may remain unchecked or unconfigured.
    17. Click Save.

安装immich

  • 获取docker-compose.yml和example.env

    wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
    wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
    # 可选,获取硬件转码配置
    wget -O hwaccel.transcoding.yml https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml
  • 编辑.env

    # You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables
    
    # The location where your uploaded files are stored
    UPLOAD_LOCATION=/custom/path/immich/
    # The location where your database files are stored
    DB_DATA_LOCATION=数据库数据位置
    
    # To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
    TZ=Asia/Shanghai
    
    # The Immich version to use. You can pin this to a specific version like "v1.71.0"
    IMMICH_VERSION=release
    
    # Connection secret for postgres. You should change it to a random password
    # Please use only the characters `A-Za-z0-9`, without special characters or spaces
    DB_PASSWORD=密码
    
    # The values below this line do not need to be changed
    ###################################################################################
    DB_USERNAME=postgres
    DB_DATABASE_NAME=immich
  • 编辑docker-compose.yml

    #
    # WARNING: Make sure to use the docker-compose.yml of the current release:
    #
    # https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
    #
    # The compose file on main may not be compatible with the latest release.
    #
    
    name: immich
    
    services:
      immich-server:
        container_name: immich_server
        image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
        extends:
          file: hwaccel.transcoding.yml
          service: 加速后端 # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
        volumes:
          # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
          - ${UPLOAD_LOCATION}:/usr/src/app/upload/upload
          - /etc/localtime:/etc/localtime:ro
        env_file:
          - .env
        ports:
          - 2283:3001
        depends_on:
          - redis
          - database
        restart: always
        healthcheck:
          disable: false
    
      immich-machine-learning:
        container_name: immich_machine_learning
        # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
        # Example tag: ${IMMICH_VERSION:-release}-cuda
        image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
        # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
        #   file: hwaccel.ml.yml
        #   service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
        volumes:
          - model-cache:/cache
        env_file:
          - .env
        restart: always
        healthcheck:
          disable: false
    
      redis:
        container_name: immich_redis
        image: docker.io/redis:6.2-alpine@sha256:2d1463258f2764328496376f5d965f20c6a67f66ea2b06dc42af351f75248792
        healthcheck:
          test: redis-cli ping || exit 1
        restart: always
    
      database:
        container_name: immich_postgres
        image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
        environment:
          POSTGRES_PASSWORD: ${DB_PASSWORD}
          POSTGRES_USER: ${DB_USERNAME}
          POSTGRES_DB: ${DB_DATABASE_NAME}
          POSTGRES_INITDB_ARGS: '--data-checksums'
        volumes:
          # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
          - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
        healthcheck:
          test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
          interval: 5m
          start_interval: 30s
          start_period: 5m
        command: ["postgres", "-c", "shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
        restart: always
    
    volumes:
      model-cache:
  • 启动compose

    sudo docker compose up -d
  • 配置反向代理

    sudo docker network connect immich_default caddy
    https://immich.域名:443 {
        reverse_proxy http://immich-server-ip:3001 # 或http://localhost:2283
    }
  • 进入服务器设置-Oauth设置

    • Issuer URL: https://auth.example.com/.well-known/openid-configuration.
    • Client ID: immich.
    • Client Secret: insecure_secret.
    • Scope: openid profile email.
    • Button Text: Login with Authelia.
    • Auto Register: Enable if desired.

使用Authentik替换Authelia

  • 获取docker-compose配置

    wget https://goauthentik.io/docker-compose.yml
  • 编辑.env

    PG_PASS=密码
    AUTHENTIK_SECRET_KEY=密钥
    
    AUTHENTIK_ERROR_REPORTING__ENABLED=false
    
    # SMTP Host Emails are sent to
    #AUTHENTIK_EMAIL__HOST=localhost
    #AUTHENTIK_EMAIL__PORT=25
    # Optionally authenticate (don't add quotation marks to your password)
    #AUTHENTIK_EMAIL__USERNAME=
    #AUTHENTIK_EMAIL__PASSWORD=
    # Use StartTLS
    #AUTHENTIK_EMAIL__USE_TLS=false
    # Use SSL
    #AUTHENTIK_EMAIL__USE_SSL=false
    #AUTHENTIK_EMAIL__TIMEOUT=10
    # Email address authentik will send from, should have a correct @domain
    #AUTHENTIK_EMAIL__FROM=authentik@localhost
    
    #COMPOSE_PORT_HTTP=80
    #COMPOSE_PORT_HTTPS=443
    docker compose pull
    docker compose up -d
  • 访问http://server-ip:9000/if/flow/initial-setup/开启初始化流程

系统监控

安装Portainer

  • 拉取镜像

    docker pull portainer/portainer-ce
  • 启动容器

    docker run -d --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest

安装Netdata

  • 拉取镜像

    docker pull netdata/netdata
  • 运行容器

    docker run -d --name=netdata \
      --pid=host \
      --network=host \
      -v netdataconfig:/etc/netdata \
      -v netdatalib:/var/lib/netdata \
      -v netdatacache:/var/cache/netdata \
      -v /:/host/root:ro,rslave \
      -v /etc/passwd:/host/etc/passwd:ro \
      -v /etc/group:/host/etc/group:ro \
      -v /etc/localtime:/etc/localtime:ro \
      -v /proc:/host/proc:ro \
      -v /sys:/host/sys:ro \
      -v /etc/os-release:/host/etc/os-release:ro \
      -v /var/log:/host/var/log:ro \
      -v /var/run/docker.sock:/var/run/docker.sock:ro \
      -v /run/dbus:/run/dbus:ro \
      --restart unless-stopped \
      --cap-add SYS_PTRACE \
      --cap-add SYS_ADMIN \
      --security-opt apparmor=unconfined \
      netdata/netdata

使用UIforFreedom替换clash

安装

docker pull ui4freedom/uif:latest # 拉取最新镜像
docker run --network host --name uif --privileged --restart unless-stopped -d ui4freedom/uif:latest

配置

docker logs -f uif
# Password: 92c204a9-3934-4976-96f2-7bbcb338ccf0
# Web Address: 0.0.0.0:9527
# API Address: 0.0.0.0:9413

打开网址ip:9527配置api后端为ip:9413

在入站规则中关闭系统代理,根据自己的需要配置入站规则,即连接协议和端口等

在出站规则中添加订阅链接,启用节点,完成

使用coredns做docker服务发现

安装

docker pull kevinjqiu/coredns-dockerdiscovery:latest
docker run -d --name coredns --restart=always -v /path/to/Corefile:/etc/Corefile -v /var/run/docker.sock:/var/run/docker.sock -p 8053:53/udp kevinjqiu/coredns-dockerdiscovery -conf /etc/Corefile

配置

.:53 { # 监听53端口
    docker { # 使用docker服务发现模块
        domain docker.loc # 服务域名
    }
    log # 使用日志模块
    errors # 使用错误流模块
}

如果启用dae,需要在dae中配置dns和拦截规则

阅读全文

组建NAS(四)

记录 2024/9/25

前言

之前捣鼓了很久,NAS基本运行起来了,但是还存在证书信任等问题,非常烦。问了GPT,提供了一个ZeroTier + Pi-Hole + Caddy的网络方案(不得不说,科技改变生活)

重置服务器

  • 删除证书

    mkcert -uninstall
    
    cd /etc/ssl/certs
    rm xxxx.pem # 删除空的根证书软链接
    rm xxxx.0 # 删除空的根证书hash软链接
    
    rm -r ~/.local/share/mkcert
  • 清理docker

    sudo docker stop xxx # 停止所有容器(除了dae)
    sudo docker container prune # 删除停止的容器
    sudo docker network rm nextcloud-aio # 删除虚拟网卡
    sudo docker volume prune --filter all=1 # 删除空挂载卷
    sudo docker image prune -a # 删除无活动镜像
  • 清理文件

    sudo rm -r /mnt/ncdata/ftp
    sudo rm -r /mnt/ncdata/nextcloud
    
    sudo rm -r ~/xxxx # 清理home目录下相关文件

安装ZeroTier

  • 介绍

    ZeroTier是P2P VPN,利用在互联网中构建一个虚拟局域网来实现组网。

    一般情况下ZeroTier使用UDP进行打洞(先利用共同的通信服务器交换两台设备的链路信息,然后两台设备利用信息实现直连),实现内网直连;UDP打洞失败情况下会使用TCP中继。

    ZeroTier除了官方实现外,还有很多第三方控制器,用以解除对设备数量的限制。这里我选择的是imashen/zerotier-aio,集成了ZeroTier 1.14.0、ztnui和planet构建工具

  • 安装

  • 拉取镜像

    sudo docker pull imashen/zerotier-aio
  • 运行容器

    sudo docker run -d -p 9993:9993/udp -p 3443:3443 -p 3180:3180 \
        -v zerotier-one:/var/lib/zerotier-one \
        -v zerotier-webui:/www/zerotier-webui/etc \
        -v zerotier-logs:/logs \
        -e ZEROTIER-WEBUI_PASSWD=<password> \
        -e MYADDR=<outter-network-ip> \
        -e MYDOMAIN=site.home \
        --name zerotier-aio \
        --restart always \
        imashen/zerotier-aio
    # 9993是ZeroTier打洞端口,3443是面板https端口,3180是http服务器端口
    # 该容器需要有公网ip,作为整个局域网的控制器和根节点(可选)
    # 迁移只需复制三个卷
  • 访问https://本机ip:3443

    默认用户名admin,密码为设置的密码,如果密码太弱会被自动更换,需要进入容器日志查看

    sudo docker exec -it zerotier-aio /bin/bash
    cd /logs
    cat zerotier-webui.log

    新建一个网络->easy setup,生成网络IP池

    进入IP池,创建一个/80的ipv6 IP池

    进入ipv6选项,勾选自动分配及6plane

    进入路由选项,填入ipv6子网,路由留空

  • 在其他ZeroTier客户端,加入该网络,启用DNS,全局ip和默认路由

    zerotier-cli join <networkID>
    zerotier-cli set <networkID> allowDNS=1
    zerotier-cli set <networkID> allowGlobal=1
    zerotier-cli set <networkID> allowDefault=1
  • 加速

  • 进入zerotier-zio容器,配置planet

    sudo docker exec -it zerotier-aio /bin/bash
    mkplanet -b2j
    # 在roots中配置,配置项为{"identity": "zerotier-id", "stableEndpoints": [ipv4/zerotier-udp-or-tcp-port, ipv6/zerotier-udp-or-tcp-port]},最多四个
    vim planet.json
    mkplanet -j2b

    将生成的planet文件替换客户端下planet文件,重启zerotier服务,可以将planet文件放在/www/zerotier-webui/etc/myfs/下供客户端下载

    Linux:/var/lib/zerotier-one

    Windows:C:

    MacOS:/Library/Application Support/ZeroTier/One

安装Pi-hole

  • 安装

  • 拉取镜像

    sudo docker pull pihole/pihole
  • 停止系统默认域名解析服务

    sudo systemctl stop systemd-resolved
    vim /etc/systemd/resolved.conf # 解除注释DNSStubListener,并改为no
    sudo systemctl restart systemd-resolved
    # 用/run/systemd/resolve/resolv.conf替换软链接/run/systemd/resolve/stub-resolv.conf
    sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
  • 运行容器

    sudo docker run -d \
        --name pihole \
        --cap-add=NET_ADMIN \
        --restart always \
        -p 53:53/tcp -p 53:53/udp -p 8000:80/tcp \
        -e TZ=Asia/Shanghai -e WEBPASSWORD=<password> \
        -v etc-pihole:/etc/pihole -v etc-dnsmasq_d:/etc/dnsmasq.d \
        pihole/pihole
    # 80是面板入口,需要修改映射以防端口冲突
  • 访问http://宿主机ip或zerotier虚拟ip:8000/admin,登录面板

    在Local DNS->DNS Records中添加自定义域名(节点名称.域名)及对应的zerotier虚拟ip4和ipv6(dae需要)

    在Settings->DNS->Interface settings中,选择Permit all origins

  • 在ZeroTier自建控制器中设置网络的dns,域名填写和pihole中的一致,ip填写pihole所在节点的虚拟ip

  • 在dae中配置代理自建DNS

    # 添加合并内容
    dns {
      upstream {
          piholedns: 'tcp+udp://zerotier虚拟ip:53'
      }
      routing {
          request {
              qname(suffix: 你的域名) -> piholedns
          }
          response {
              upstream(piholedns) -> accept
          }
      }
    }
    routing {
      sip(dns源填的ip) && l4proto(udp) && port(53) -> must_direct
    }

获取域名

  • 免费域名

  • 访问getlocalcert,使用GitHub登录注册

  • 注册一个免费域名,记得选择.localcert.net,.localhostcert.net只能用于本机测试

  • 生成API Key,保存json文件

  • zerotier网络配置的域名需要与该域名一致,否则可能需要额外dns配置才能实现访问

安装Caddy

  • 安装

  • 拉取镜像

    sudo docker pull caddy:<version>-builder
    sudo docker pull caddy:<version>
  • 构建DNS质询caddy镜像

    sudo vim Dockerfile
    
    # 内容
    # FROM caddy:<version>-builder AS builder
    #
    # RUN xcaddy build --with github.com/caddy-dns/acmedns
    #
    # FROM caddy:<version>
    #
    # COPY --from=builder /usr/bin/caddy /usr/bin/caddy
    
    sudo docker build -t caddy:<tag> .
  • 运行容器

    sudo docker run -d \
        --restart always \
        --name caddy \
        -p 80:80 -p 443:443 -p 443:443/udp -p 8443:8443 \
        -v caddy-data:/data \
        -v caddy-config:/config \
        -v /path/to/Caddyfile/Dir:/etc/caddy \
        caddy
  • Caddyfile配置

    <yourSubdomain>.localcert.net {
      tls {
        # ca https://acme-staging-v02.api.letsencrypt.org/directory
        dns acmedns <creds.json>
      }
      respond "Hello from Caddy"
    }

    可以先解除注释,尝试获取证书,确认无误后再使用正式环境获取证书

安装NextCloud AIO

  • 安装

  • 拉取镜像

    sudo docker pull nextcloud/all-in-one
  • 配置反向代理

    # Caddyfile
    https://<yourSubdomain>.localcert.net:443 {
      reverse_proxy nextcloud-aio网卡的ip:11000
    }
    https://<your-nc-domain>:8443 {
        reverse_proxy https://nextcloud-aio网卡的ip:8080 {
            transport http {
                tls_insecure_skip_verify
            }
        }
    }
  • 运行容器

    sudo docker run -d \
                             --init \
                             --sig-proxy=false \
                             --name nextcloud-aio-mastercontainer \
                             --restart always \
                             --publish 8080:8080 \
                             --env APACHE_PORT=11000 \
                             --env APACHE_IP_BINDING=0.0.0.0 \
                             --env NEXTCLOUD_DATADIR="data-dir" \
                             --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \
                             --volume /var/run/docker.sock:/var/run/docker.sock:ro \
                             nextcloud/all-in-one:latest
  • 给caddy添加nextcloud-aio网卡,dae代理nextcloud-aio网卡

后续

  • 内网DNS太容易断线了,把pihole服务器放到了有公网ip的云服务器上
阅读全文

组建NAS(三)

记录 2024/9/23

使用dae做网卡级代理

  1. 删除docker关于代理的配置

  2. 停止所有容器

  3. 删除所有容器

  4. 删除nextcloud-aio网络

  5. 删除所有卷

  6. 拉取dae镜像

    sudo docker pull daeuniverse/dae
  7. 启动容器

    sudo docker run -d \
        --restart always \
        --network host \
        --pid host \
        --privileged \
        -v /sys:/sys \
        -v /etc/dae:/etc/dae \
        --name dae \
        daeuniverse/dae:latest
  8. 在/etc/dae目录下创建config.dae文件,修改权限为640

    global {
        log_level: info
        lan_interface: docker0
        wan_interface: auto
        auto_config_kernel_parameter: true
    }
    node {
        sock: 'socks5://localhost:7890'
        http: 'http://localhost:7890'
    }
    dns {
        upstream {
            alidns: 'udp://dns.alidns.com:53'
            googledns: 'tcp+udp://dns.google.com:53'
        }
        routing {
            request {
                qname(geosite:cn) -> alidns
                fallback: googledns
            }
            response {
                upstream(googledns) -> accept
                ip(geoip:private) && !qname(geosite:cn) -> googledns
                fallback: accept
            }
        }
    }
    group {
        my_group {
        policy: min_moving_avg
    }
    }
    routing {
        pname(clash) -> must_direct
        dip(geoip:private) -> direct
        dip(geoip:cn) -> direct
        domain(geosite:cn) -> direct
        domain(home.arpa) -> direct
        fallback: my_group
    }
  9. 运行zerotier容器、zeronsd容器

  10. 运行nginx proxy manager容器

  11. 运行nextcloud-aio容器,将nextcloud-aio网卡加入dae

安装ftp服务器

  1. 拉取镜像

    sudo docker pull kibatic/proftpd
  2. 运行镜像

    sudo docker run -d --net host \
        -e FTP_LIST="admin:<password>" \
        -e MASQUERADE_ADDRESS=<serverIP> \
        -v /path_to_ftp_dir:/home/admin \
        --name proftpd \
        kibatic/proftpd:latest

切换zerotier-aio

  1. 拉取镜像

    sudo docker pull imashen/zerotier-aio
  2. 运行容器

    sudo docker run -d -p 9993:9993/udp -p 3443:3443 -p 3180:3180 \
        -v zerotier-one:/var/lib/zerotier-one \
        -v zerotier-webui:/www/zerotier-webui/etc \
        -v zerotier-logs:/logs \
        -e NODE_ENV=production \
        -e ZEROTIER-WEBUI_PASSWD=<password> \
        -e HTTPS_PORT=3443 \
        --name zerotier-aio \
        imashen/zerotier-aio
  3. 额,由于该镜像不支持zeronsd做DNS,所有最后只是取了容器中mkplanet工具生成planet文件,分发给各个客户端做加速

阅读全文

组建NAS(二)

记录 2024/9/14

安装Docker

  1. 清理旧版本

    for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
  2. 添加Docker的apt仓库

    # Add Docker's official GPG key:
    sudo apt-get update
    sudo apt-get install ca-certificates curl
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc
    
    # Add the repository to Apt sources:
    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
      $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
      sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    sudo apt-get update
  3. 安装Docker

    sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  4. 开机自启动

    sudo systemctl enable docker
  5. 编辑~/.docker/config.json添加代理,会被加入容器

    {
     "proxies": {
            "default": {
                "httpProxy": "http://dockerIP:vpnPort",
                "httpsProxy": "http://dockerIP:vpnPort",
                "noPorxy": "localhost,127.0.0.1,zeronsd域名"
            }
        }
    }

安装ZeroTier

Web端

  1. 注册ZeroTier账号、登录
  2. 创建一个网络
  3. 创建账户的API Access Token
  4. 等待客户端接入
  5. 对接入的客户端进行授权和命名

NAS

  1. 拉取或从Dockerfile构建ZeroTier镜像

    sudo docker pull zerotier/zerotier
  2. 运行容器

    sudo docker run -d \
                             --name zerotier \
                             --device=/dev/net/tun \
                             --net=host \
                             --cap-add=NET_ADMIN \
                             --cap-add=SYS_ADMIN \
                             --restart=always \
                             -v ~/zerotier-one/:/var/lib/zerotier-one/ \
                             zerotier/zerotier:latest
  3. 进入容器,加入网络,开启DNS

    sudo docker exec -it zerotier /bin/bash
    # 容器内
    zerotier-cli join networkID
    zerotier-cli set networkID allowDNS=1
  4. 拉取或者构建ZeroNSD镜像,用来搭建ZeroTier的DNS

    sudo docker pull zerotier/zeronsd
  5. 运行容器

    sudo docker run -d \
                             --net=host \
                             -v ~/zerotier-one/authtoken.secret:/authtoken.secret \
                             --restart=always \
                             -e ZEROTIER_CENTRAL_TOKEN=<token> \
                             --name zeronsd \
                             zerotier/zeronsd:latest start -s /authtoken.secret \
                             <networkID>

云服务器

  1. 安装ZeroTier

    curl -s https://install.zerotier.com | sudo bash
  2. 加入网络,开启DNS

    zerotier-cli join networkID
    zerotier-cli set networkID allowDNS=1
  3. 进入ZeroTier配置文件夹,配置Moon服务器

    cd /var/lib/zerotier-one
    zerotier-idtool initmoon identity.public >> moon.json
  4. 查看moon配置,并在roots.stableEndpoints填入"IP/端口",可以使用公网IP,端口默认9993

        {
          "id": "deadbeef00",
          "objtype": "world",
          "roots": [
            {
              "identity": "deadbeef00:0:34031483094...",
              "stableEndpoints": ["IP/PORT"]
            }
          ],
          "signingKey": "b324d84cec708d1b51d5ac03e75afba501a12e2124705ec34a614bf8f9b2c800f44d9824ad3ab2e3da1ac52ecb39ac052ce3f54e58d8944b52632eb6d671d0e0",
          "signingKey_SECRET": "ffc5dd0b2baf1c9b220d1c9cb39633f9e2151cf350a6d0e67c913f8952bafaf3671d2226388e1406e7670dc645851bf7d3643da701fd4599fedb9914c3918db3",
          "updatesMustBeSignedBy": "b324d84cec708d1b51d5ac03e75afba501a12e2124705ec34a614bf8f9b2c800f44d9824ad3ab2e3da1ac52ecb39ac052ce3f54e58d8944b52632eb6d671d0e0",
          "worldType": "moon"
        }
  5. 生成moon,重启服务

    zerotier-idtool genmoon moon.json
    mkdir moons.d
    mv 0000worldID.moon moons.d/0000worldID.moon
    /etc/init.d/zerotier-one restart
  6. 其他客户端订阅moon服务器

    zerotier-cli orbit worldID rootID

安装Nignx Proxy Manager

  1. 拉取镜像

    sudo docker pull jc21/nginx-proxy-manager
  2. 运行容器

    sudo docker run -d \
                             --restart=always \
                             -p 80:80 -p 443:443 -p 81:81 \
                             -v ~/nignx-proxy-manager/data:/data -v ~/nignx-proxy-manager/letsencrypt:/etc/letsencrypt \
                             --name nignx-proxy-manager \
                             jc21/nginx-proxy-manager:latest

安装硬盘

  1. 将两块机械硬盘插入机箱

  2. 查看硬盘

    sudo fdisk -l
    Disk /dev/nvme0n1:238.47 GiB,256060514304 字节,500118192 个扇区
    Disk model: aigo NVMe SSD P3500 256GB 
    单元:扇区 / 1 * 512 = 512 字节
    扇区大小(逻辑/物理):512 字节 / 512 字节
    I/O 大小(最小/最佳):512 字节 / 512 字节
    磁盘标签类型:gpt
    磁盘标识符:89E7ED5D-5DDD-4A92-8808-CAC413B4C5EE
    
    设备              起点      末尾      扇区   大小 类型
    /dev/nvme0n1p1    2048   2203647   2201600     1G EFI 系统
    /dev/nvme0n1p2 2203648   6397951   4194304     2G Linux 文件系统
    /dev/nvme0n1p3 6397952 500115455 493717504 235.4G Linux 文件系统
    
    
    Disk /dev/mapper/ubuntu--vg-ubuntu--lv:235.42 GiB,252782313472 字节,493715456 个扇区
    单元:扇区 / 1 * 512 = 512 字节
    扇区大小(逻辑/物理):512 字节 / 512 字节
    I/O 大小(最小/最佳):512 字节 / 512 字节
    
    
    Disk /dev/sda:1.82 TiB,2000398934016 字节,3907029168 个扇区
    Disk model: ST2000VN004-2E41
    单元:扇区 / 1 * 512 = 512 字节
    扇区大小(逻辑/物理):512 字节 / 4096 字节
    I/O 大小(最小/最佳):4096 字节 / 4096 字节
    
    
    Disk /dev/sdb:1.82 TiB,2000398934016 字节,3907029168 个扇区
    Disk model: ST2000VN004-2E41
    单元:扇区 / 1 * 512 = 512 字节
    扇区大小(逻辑/物理):512 字节 / 4096 字节
    I/O 大小(最小/最佳):4096 字节 / 4096 字节
  3. 对硬盘进行分区、更改分区类型、保存

    sudo fdisk /dev/sda # /dev/sdb同理
    命令(输入 m 获取帮助): n
    # 后续使用默认选项即可
    
    # 分区完成后
    命令(输入 m 获取帮助): t
    Hex code or alias (type L to list all): L
    Hex code or alias (type L to list all): 8E # 改成LVM
    
    # 写入磁盘保存
    命令(输入 m 获取帮助): w
    sudo fdisk -l
    Disk /dev/sda:1.82 TiB,2000398934016 字节,3907029168 个扇区
    Disk model: ST2000VN004-2E41
    单元:扇区 / 1 * 512 = 512 字节
    扇区大小(逻辑/物理):512 字节 / 4096 字节
    I/O 大小(最小/最佳):4096 字节 / 4096 字节
    磁盘标签类型:dos
    磁盘标识符:0x18312fd8
    
    设备       启动  起点       末尾       扇区  大小 Id 类型
    /dev/sda1        2048 3907029167 3907027120  1.8T 8e Linux LVM
    
    
    Disk /dev/sdb:1.82 TiB,2000398934016 字节,3907029168 个扇区
    Disk model: ST2000VN004-2E41
    单元:扇区 / 1 * 512 = 512 字节
    扇区大小(逻辑/物理):512 字节 / 4096 字节
    I/O 大小(最小/最佳):4096 字节 / 4096 字节
    磁盘标签类型:dos
    磁盘标识符:0xbbb3186f
    
    设备       启动  起点       末尾       扇区  大小 Id 类型
    /dev/sdb1        2048 3907029167 3907027120  1.8T 8e Linux LVM
  4. 创建PV

    sudo pvcreate /dev/sda1 # 另一块同理
  5. 创建VG

    sudo vgcreate nas-storage /dev/sda1 # 用一块PV创建VG,建一个就好
    sudo vgextend nas-storage /dev/sdb1 # 扩容VG
  6. 创建LV

    sudo lvcreate -l 100%FREE --name nas-storage-lv nas-storage
  7. 格式化LV

    sudo mkfs --type=ext4 /dev/nas-storage/nas-storage-lv
  8. 挂载LV

    mkdir /mnt/ncdata
    sudo mount /dev/nas-storage/nas-storage-lv /mnt/ncdata
    sudo df -Th
    文件系统                                  类型   大小  已用  可用 已用% 挂载点
    tmpfs                                     tmpfs  3.1G  1.7M  3.1G    1% /run
    /dev/mapper/ubuntu--vg-ubuntu--lv         ext4   232G   14G  207G    7% /
    tmpfs                                     tmpfs   16G     0   16G    0% /dev/shm
    tmpfs                                     tmpfs  5.0M     0  5.0M    0% /run/lock
    /dev/nvme0n1p2                            ext4   2.0G  253M  1.6G   14% /boot
    /dev/nvme0n1p1                            vfat   1.1G  6.1M  1.1G    1% /boot/efi
    tmpfs                                     tmpfs  3.1G     0  3.1G    0% /run/user/1000
    /dev/mapper/nas--storage-nas--storage--lv ext4   3.6T   28K  3.4T    1% /mnt/ncdata
  9. 配置开机自动挂载

    # 查看文件系统UUID
    sudo blkid
    
    sudo vim /etc/fstab
    
    # 添加一行
    UUID=8aba1ccb-750e-4d00-9566-a786537bee30 /mnt/ncdata ext4 defaults 0 0
    
    # 重新挂载(读取/etc/fstab)
    sudo mount -a
    sudo df -h

安装NextCloud AIO

  1. 拉取镜像

    sudo docker pull nextcloud/all-in-one
  2. 运行容器

    sudo docker run -d \
                             --init \
                             --sig-proxy=false \
                             --name nextcloud-aio-mastercontainer \
                             --restart always \
                             --publish 8080:8080 \
                             --env APACHE_PORT=11000 \
                             --env APACHE_IP_BINDING=0.0.0.0 \
                             --env NEXTCLOUD_DATADIR="data-dir" \
                             --add-host zeronsd域名:zerotierIP \
                             --env NEXTCLOUD_TRUSTED_CACERTS_DIR=ca-certificates-dir \
                             --volume /etc/ssl/certs:/etc/ssl/certs \
                             --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \
                             --volume /var/run/docker.sock:/var/run/docker.sock:ro \
                             nextcloud/all-in-one:latest

获取证书

  1. 安装mkcert

    sudo apt install libnss3-tools
    curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
    chmod +x mkcert-v*-linux-amd64
    sudo mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert
  2. 生成本地CA证书

    mkcert -install
  3. 生成服务器证书

    mkcert "*.home.arpa"
  4. 容器信任证书,将主机/etc/ssl/certs映射到容器

进行配置

  1. 访问Nignx Proxy Manager管理网站http://nas服务器ip:81,连接了ZeroTier可以使用ZeroTier内网ip

    ,密码changeme

  2. 填写好基本信息后回到首页,选择Proxy Hosts->Add Proxy Host

    # 给nignx proxy manager添加nextcloud aio的网卡
    sudo docker network connect nextcloud-aio nignx-proxy-manager

    填写ZeroNSD生成的域名,ip填写网卡nextcloud-aio的ip,端口填写aio启动参数apache_port端口

  3. 转到Advanced,填写下面配置,保存

    client_body_buffer_size 512k;
    proxy_read_timeout 86400s;
    client_max_body_size 0;
  4. 转到SSL Certificates,添加mkcert生成的证书

  5. 回到Proxy Hosts,编辑,选择SSL,选择已有证书

  6. 访问https://nas服务器ip:8080到达NextCloud网站,默认密码在下方,记得复制

  7. 填写nignx中配置的域名

  8. 选择所需容器,等待安装完成

阅读全文
avatar
周文峰

疯疯癫癫,一笑一天
(笑不出来版)