在使用 Spring Boot 构建一套 RESTful 接口的时候经常需要手工维护一份接口文档以提供给不同的客户端使用,有的时候手工维护成本太高,今天发现了一套自动化生成 RESTful 接口文档的工具 Swagger 。
Swagger 能根据 Spring Controller 接口自动生成一个文档页面,在代码中使用注解将接口文档注释,非常方便。 Swagger 整合到 Spring boot 项目中也非常方便。
io.springfox >= 3.0
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
访问地址是:http://localhost:8080/swagger-ui/#/
在 pom.xml
中添加
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
最新的版本可以在 mvnrepository 上查到,或者上官网或者 github。
在项目 package 根下新建如下 Class:
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket helloApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("info.einverne.springboot.demo"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
// 文档标题
.title("API 文档")
// 文档描述
.description("https://github.com/einverne/thrift-swift-demo/tree/master/spring-boot-demo")
.termsOfServiceUrl("https://github.com/einverne/thrift-swift-demo/tree/master/spring-boot-demo")
.version("v1")
.build();
}
}
通过@Configuration
注解,让 Spring 来加载该类配置。再通过@EnableSwagger2
注解来启用 Swagger2。
apiInfo()
用来创建 API 的基本信息,展现在文档页面中。select()
函数返回一个 ApiSelectorBuilder
实例用来控制哪些接口暴露给 Swagger ,这里使用定义扫描包路径来定义, Swagger 会扫描包下所有 Controller 的定义并产生文档内容,除了被 @ApiIgnore
注解的接口。@ApiOperation
注解来给 API 增加说明、通过 @ApiImplicitParams
、@ApiImplicitParam
注解来给参数增加说明。
一个简单的注释
@ApiOperation(value = "创建用户", notes = "根据 User 对象创建用户")
@ApiImplicitParam(name = "user", value = "用户详细实体 user", required = true, dataType = "User")
@RequestMapping(value = "", method = RequestMethod.POST)
public String postUser(@RequestBody User user) {
users.put(user.getId(), user);
return "success";
}
详细的例子可以参考源代码 https://github.com/einverne/thrift-swift-demo
再添加注释后启动 Spring boot, 访问 http://localhost:8080/swagger-ui.html 即可看到 API 文档
@Api
注解用于类上,说明类作用
标记在方法上,对一个操作或 HTTP 方法进行描述。具有相同路径的不同操作会被归组为同一个操作对象。不同的 HTTP 请求方法及路径组合构成一个唯一操作。此注解的属性有:
注解 ApiImplicitParam 的容器类,以数组方式存储。
对 API 的单一参数进行注解。虽然注解 @ApiParam 同 JAX-RS 参数相绑定,但这个 @ApiImplicitParam 注解可以以统一的方式定义参数列表,也是在 Servelet 及非 JAX-RS 环境下,唯一的方式参数定义方式。注意这个注解 @ApiImplicitParam 必须被包含在注解 @ApiImplicitParams 之内。可以设置以下重要参数属性:
@RequestBody 这样的场景请求参数无法使用 @ApiImplicitParam 注解进行描述
增加对参数的元信息说明。这个注解只能被使用在 JAX-RS 1.x/2.x 的综合环境下。其主要的属性有:
提供对 Swagger model 额外信息的描述。在标注 @ApiOperation 注解的操作内,所有的类将自动被内省(introspected),但利用这个注解可以做一些更加详细的 model 结构说明。主要属性有:
对 model 属性的注解,主要的属性值有:
响应配置
多个 Response
考虑到安全的问题,每次请求 API 需要对用户进行验证与授权。目前主流的验证方式采用请求头部(request header)传递 token,即用户登录之后获取一个 token,然后每次都使用这个 token 去请求 API。如果想利用 swagger-UI 进行 API 测试,必须显式为每个需要验证的 API 指定 token 参数。这时可以为每个操作添加一个注解 @ApiImplicitParams,具体代码如下:
@ApiImplicitParams({@ApiImplicitParam(name = "TOKEN", value = "Authorization token", required = true, dataType = "string", paramType = "header")})
Swagger 提供了 enable
方法,可以通过设置该方法来选择开启 Swagger 来在线上环境禁用 Swagger。
@Bean
public Docket customImplementation(){
return new Docket(SWAGGER_2)
.apiInfo(apiInfo())
.enable(environmentSpeficicBooleanFlag) //<--- Flag to enable or disable possibly loaded using a property file
.includePatterns(".*pet.*");
}
如果使用 Spring @Profile 也可以
@Bean
@Profile("production")
public Docket customImplementation(){
return new Docket(SWAGGER_2)
.apiInfo(apiInfo())
.enable(false) //<--- Flag set to false in the production profile
.includePatterns(".*pet.*");
}
From: https://stackoverflow.com/a/27976261/1820217
平时在 Linux 上查看磁盘信息都使用 df -lh
, -l
显示本地文件系统, -h
来表示 human readable 。虽然 df 在一定程度上能够满足查询磁盘剩余空间的需求,但是这里要介绍一款强大于 df 的命令 —- di 。
使用如下命令安装
sudo apt install di
di 命令是 disk information 的缩写,直接运行 di 会有如下结果
di
Filesystem Mount Size Used Avail %Used fs Type
/dev/sda1 / 901.2G 188.3G 667.1G 26% ext4
tmpfs /dev/shm 7.8G 0.1G 7.6G 2% tmpfs
tmpfs /run 1.6G 0.1G 1.5G 4% tmpfs
cgmfs /run/cgmanager/ 0.1M 0.0M 0.1M 0% tmpfs
tmpfs /run/lock 5.0M 0.0M 5.0M 0% tmpfs
tmpfs /run/user/0 1.6G 0.0G 1.6G 0% tmpfs
tmpfs /run/user/1000 1.6G 0.0G 1.6G 0% tmpfs
tmpfs /sys/fs/cgroup 7.8G 0.0G 7.8G 0% tmpfs
/dev/sda1 /var/lib/docker 901.2G 188.3G 667.1G 26% ext4
di
默认的输出就是比较人性化的了。
看 di 的使用介绍 man di
就会发现 di 是这么介绍自己的
> di Displays usage information on mounted filesystems. Block values are reported in a human readable format. If the user or group has a disk quota, the values reported are adjusted according the quotas that apply to the user.
di -A
Mount fs Type Filesystem
Options
Size Used Free %Used %Free
Size Used Avail %Used %Free
Size Used Avail %Used
Inodes IUsed IFree %IUsed
/ ext4 /dev/sda1
rw,relatime,errors=remount-ro,data=ordered
901.2G 188.3G 712.9G 21% 79%
901.2G 234.1G 667.1G 26% 74%
855.4G 188.3G 667.1G 22%
60014592 1372538 58642054 2%
/dev/shm tmpfs tmpfs
rw,nosuid,nodev
7.8G 0.1G 7.6G 2% 98%
7.8G 0.1G 7.6G 2% 98%
7.8G 0.1G 7.6G 2%
2036725 741 2035984 0%
/run tmpfs tmpfs
rw,nosuid,noexec,relatime,size=1629380k,mode=755
1.6G 0.1G 1.5G 4% 96%
1.6G 0.1G 1.5G 4% 96%
1.6G 0.1G 1.5G 4%
2036725 777 2035948 0%
/run/cgmanager/fs tmpfs cgmfs
rw,relatime,size=100k,mode=755
0.1M 0.0M 0.1M 0% 100%
0.1M 0.0M 0.1M 0% 100%
0.1M 0.0M 0.1M 0%
2036725 14 2036711 0%
/run/lock tmpfs tmpfs
rw,nosuid,nodev,noexec,relatime,size=5120k
5.0M 0.0M 5.0M 0% 100%
5.0M 0.0M 5.0M 0% 100%
5.0M 0.0M 5.0M 0%
2036725 4 2036721 0%
/run/user/0 tmpfs tmpfs
rw,nosuid,nodev,relatime,size=1629380k,mode=700
1.6G 0.0G 1.6G 0% 100%
1.6G 0.0G 1.6G 0% 100%
1.6G 0.0G 1.6G 0%
2036725 4 2036721 0%
/run/user/1000 tmpfs tmpfs
rw,nosuid,nodev,relatime,size=1629380k,mode=700,uid=1000,gid=1000
1.6G 0.0G 1.6G 0% 100%
1.6G 0.0G 1.6G 0% 100%
1.6G 0.0G 1.6G 0%
2036725 36 2036689 0%
/sys/fs/cgroup tmpfs tmpfs
rw,mode=755
7.8G 0.0G 7.8G 0% 100%
7.8G 0.0G 7.8G 0% 100%
7.8G 0.0G 7.8G 0%
2036725 18 2036707 0%
/var/lib/docker/aufs ext4 /dev/sda1
rw,relatime,errors=remount-ro,data=ordered
901.2G 188.3G 712.9G 21% 79%
901.2G 234.1G 667.1G 26% 74%
855.4G 188.3G 667.1G 22%
60014592 1372538 58642054 2%
使用 -c
选项分割输出
di -c
s,m,b,u,v,p,T
"/dev/sda1","/","901.2G","188.3G","667.1G",26%,"ext4"
"tmpfs","/dev/shm","7.8G","0.1G","7.6G",2%,"tmpfs"
"tmpfs","/run","1.6G","0.1G","1.5G",4%,"tmpfs"
"cgmfs","/run/cgmanager/fs","0.1M","0.0M","0.1M",0%,"tmpfs"
"tmpfs","/run/lock","5.0M","0.0M","5.0M",0%,"tmpfs"
"tmpfs","/run/user/0","1.6G","0.0G","1.6G",0%,"tmpfs"
"tmpfs","/run/user/1000","1.6G","0.0G","1.6G",0%,"tmpfs"
"tmpfs","/sys/fs/cgroup","7.8G","0.0G","7.8G",0%,"tmpfs"
"/dev/sda1","/var/lib/docker/aufs","901.2G","188.3G","667.1G",26%,"ext4"
c 是 --csv-output
的缩写,为了便于程序解析
-t
参数在最后增加统计行
di -t
Filesystem Mount Size Used Avail %Used fs Type
/dev/sda1 / 901.2G 188.4G 667.0G 26% ext4
tmpfs /dev/shm 7.8G 0.1G 7.6G 2% tmpfs
tmpfs /run 1.6G 0.1G 1.5G 4% tmpfs
cgmfs /run/cgmanager/ 0.1M 0.0M 0.1M 0% tmpfs
tmpfs /run/lock 5.0M 0.0M 5.0M 0% tmpfs
tmpfs /run/user/0 1.6G 0.0G 1.6G 0% tmpfs
tmpfs /run/user/1000 1.6G 0.0G 1.6G 0% tmpfs
tmpfs /sys/fs/cgroup 7.8G 0.0G 7.8G 0% tmpfs
/dev/sda1 /var/lib/docker 901.2G 188.4G 667.0G 26% ext4
Total 1.8T 0.4T 1.3T 26%
di -s
默认更具 mount point 输出
di -sm
默认 mount pontdi -sn
不排序,按照挂载表 /etc/fstab 中顺序di -ss
按照特殊设备di -st
根据 filesystem typedi -sr
逆序输出排序方式可以组合使用,如:di –stsrm
:按照类型、设备、挂载点逆序排序。
di –strsrm
:按照类型、设备逆序、挂载点逆序排序。
di -fM
Mount
/
/dev/shm
/run
/run/cgmanager/fs
/run/lock
/run/user/0
/run/user/1000
/sys/fs/cgroup
/var/lib/docker/aufs
只打印 mount point
更多的 f 的选项可以直接参看 man di
虽然 di 提供了比 df 更多更强大的功能,但是也有其缺点,大部分的 Linux 发行版默认是没有预装的。
关键字提取,开源,代码质量管理,多语言支持。
SonarQube is an open-source platform developed by SonarSource for continuous inspection of code quality to perform automatic reviews with static analysis of code to detect bugs, code smells, and security vulnerabilities on 20+ programming languages.
开源协议:Lesser GNU General Public License
SonarQube 可以从以下七个维度检测代码质量,而作为开发人员至少需要处理前 5 种代码质量问题
Jenkins 是一个独立的开源自动化服务器,可用于自动化各种任务,如构建,测试和部署软件。Jenkins 可以通过本机系统包 Docker 安装,甚至可以通过安装 Java Runtime Environment 的任何机器独立运行。
用于持续、自动构建,测试项目,监控外部任务运行等。
新建模板类型
单元测试的目的是在不涉及依赖的情况下测试代码(隔离)。一个设计良好的系统需要遵循 SOLID 原则。
Unit test 目标是针对一个模块或者一段代码隔离测试,应该消除其他类,或者系统依赖带来的副作用。
测试替身(Test Doubles)用来消除这类副作用,test Doubles 可以分为:
通常情况下可以通过手工代码来 mock objects 或者使用 mock framework 来模拟类的行为。Mockito 是一个非常流行的 Mock framework,使用 Mockito 的三段论:
在 http://search.maven.org 中搜索 a:"mockito-core"
或者 g:"org.mockito"
比如说增加:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
mockito-all 是一个包含了所有依赖 (hamcrest 和 objenesis) 的单一 jar 包。
mockito-core 不包含 hamcrest 和 objenesis 依赖,可以自己来控制依赖的版本。
第一种方法,使用 JUnit 的 @RunWith
@RunWith(MockitoJUnitRunner.class)
public class MockitoAnnotationTest {
...
}
或者用代码启用
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
最常用的注解就是 @Mock
注解,使用 @Mock
注解来创建和插入 mocked 实例,这样就省去了手动调用 Mockito.mock()
方法。
@Spy
注解可以 mock 真实对象的方法,让真实对象方法被调用时,就像 mock object 一样可以被配置。
参数捕获器,用于捕获 mock 方法参数,进行验证使用
@InjectMocks
注解会自动将 mock fields 注入到被测试的 object 中。
需要注意的是,在 JUnit 4 中必须使用 @RunWith(MockitoJUnitRunner.class)
或 MockitoAnnotations.initMocks(this)
来初始化 mocks 并注入。
在 JUnit 5 中必须使用 @ExtendWith(MockitoExtension.class)
@RunWith(MockitoJUnitRunner.class) // JUnit 4
// @ExtendWith(MockitoExtension.class) for JUnit 5
public class SomeManagerTest {
@InjectMocks
private SomeManager someManager;
@Mock
private SomeDependency someDependency; // this will be injected into someManager
// tests...
}
通过 when().thenReturn()
方法链可以用来指定一个方法调用预先定义好的返回值。这个方法也可以指定抛出一个异常
Properties properties = mock(Properties.class);
when(properties.get(”Anddroid”)).thenThrow(new IllegalArgumentException(...));
try {
properties.get(”Anddroid”);
fail(”Anddroid is misspelled”);
} catch (IllegalArgumentException ex) {
// good!
}
当 mock 方法是知道确定的返回值,那么可以使用 thenReturn
或者 doReturn
,方法会 mock 一个确定的返回值。
thenReturn(T value) Sets a return value to be returned when the method is called.
Answer
当需要根据不同条件来 mock 方法并且返回不同返回值时需要 Answer
,比如需要根据方法传入参数来返回对应的返回值的情况。
Mockito 暂时还不支持 Mock 静态方法,所以需要借助 jmockit 完成静态方法的 Mock:
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.30</version>
<scope>test</scope>
</dependency>
new MockUp<StaticClass>() {
@mockit.Mock
public boolean isXXX() {
return true;
}
};
《Mockito Cookbook》参考代码:https://github.com/marcingrzejszczak/mockito-cookbook
最近又一次使用 Clonezilla 来克隆系统,和以往不同的是,这一次我是备份了整块硬盘到镜像,然后从镜像恢复系统到另外一块硬盘,而不是以往是复制一个分区,所以又产生了一些问题,所以有了这篇文章,一方面来记录一下中间遇到的问题,另一方面也学习巩固一下关于 Linux 启动过程中的必要流程。
关于最基础的计算机启动过程也就不展开说,阮一峰,和网上大部分文章已经讲的非常清晰了,这里只简单的列举一些基础名词,必须要知道的概念。
全称是 Basic Input Output System,也就是一块被写入开机程序的只读内存,电脑通电之后第一时间会读取的芯片。
POST 不是 HTTP 请求方法的 POST,而是 Power On Self Test,开机自检,在通电之后 BIOS 加载后会自动执行。
[[MBR]] 是主引导分区,全称是 Master Boot Record,该分区决定该设备是否能够启动。如果设备能够启动,那么该设备第一个扇区,512 字节就需要表明该设备能够启动。
主引导记录告诉计算机去哪一块硬盘寻找操作系统,主引导记录由三部分组成
[[GRUB]] 是 Linux 下最流行的启动管理器(Boot Loader),全称是 GRand Unified Bootloader。计算机在读取 MBR 前 446 字节机器码之后,将运行事先安装的启动管理器 boot loader 也就是 GRUB。
GRUB 设计兼容 multiboot specification,为了使得 GRUB 能够引导启动各种版本的 Linux。GRUB 也能够让用户选择从不同的 kernels 启动。Grub 的配置文件地址在 /boot/grub/grub.conf
.
GRUB1 现在正在被 GRUB2 代替,Grub2 在 /boot/grub2/grub.cfg
配置。
所以总结来看,计算机通电之后,先通过 BIOS,到 MBR,通过 GRUB 引导操作系统启动。以上就是计算机 boot sequence 的过程。
通过 GRUB 引导启动操作系统,之后操作系统就会接管系统启动,操作系统启动过程也分为很多步骤。
BIOS 在 MBR 中搜索 boot record,因为主引导分区必须保证尽量小,所以通过 stage 1.0 找到 1.5 GRUB 引导,GRUB 引导必须在 boot record 和第一个分区之间,将 GRUB 加载到内存之后进入 stage 1.5.
上面提到 GRUB 必须在引导记录和磁盘第一个分区之间,这一块区域因为历史原因被预留,磁盘的第一个分区从 sector 63 开始,MBR 在 sector 0,所以留下了 62512 byte sectors,大概 31774 bytes,用来保存 core.img, core.img 只有 25389 字节,所以完全足够。
需要注意的是,/boot
目录需要存放在支持 GRUB 的文件系统上。stage 1.5 在找到 /boot
文件和加载必要的驱动之后执行。
GRUB stage 2 阶段会加载 Linux kernel 到内存中,kernel 和相关的文件在 /boot
目录中。kernel 文件都以 vmlinuz
开头。
kernel 都是压缩的格式以节省空间,当计算机解压并加载内核到内存之后,会加载 systemd,自此系统启动过程结束,内核和 systemd 都已经在运行。
systemd 是所有进程的创始者,他负责将系统启动至一个可工作的状态,他负责包括挂载系统文件,启动管理系统服务等等工作。
首先 systemd 会尝试使用 /etc/fstab
中定义的内容挂载文件系统,包括定义的根分区,swap 分区文件等等。
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
# / was on /dev/sda1 during installation
UUID=3d1b7e3e-c184-4664-9555-2b088997f2c8 / ext4 errors=remount-ro 0 1
# swap was on /dev/sda5 during installation
UUID=b99bf592-a25b-4ca0-b597-fc62e121aae1 none swap sw 0 0
关于 systemd 的启动顺序,可以参考阮一峰的这篇
默认情况下 GRUB 会有 10 秒时间来给用户选择系统,这个设置可以通过修改 /etc/default/grub
来修改。
GRUB_DEFAULT=0 # 默认启动的系统序号
GRUB_TIMEOUT=4 # 默认等待多久以预设系统开机
更新之后使用 sudo update-grub
来更新 GRUB。
GRUB Manual 手册中提供了完整的参数解释。
使用 Clonezilla 复制硬盘所有分区到另外一块硬盘,而我这边遇到的情况是复制结束之后硬盘没有 boot 分区,导致 BIOS 无法找到主引导分区。
解决办法是使用 Clonezilla 的专家(高级)模式,在高级模式中会自动修复 grub 的问题
这个问题表现形式可能是各种各样的,开机黑屏,或者在 grub 引导之后出现各种乱码命令。对于这个问题的解决方法可能需要是修改 /etc/fstab
,将其中硬盘的 UUID ,通过 sudo blkid 查看获取后保证 /etc/fstab
中启动的硬盘是一块。可以参考之前的文章
一直都知道 grep 很强大,但是一直都没有办法来定义它,直到看到 man 介绍第一行,非常简洁精炼 “print lines matching a pattern”,一下子就知道了 grep 的作用。
grep 全称是 Global Regular Expression Print。grep 的工作方式是这样的,它在一个或多个文件中搜索字符串模板。如果模板包括空格,则必须被引用,模板后的所有字符串被看作文件名。搜索的结果被送到标准输出,不影响原文件内容。
grep 可用于 shell 脚本,因为 grep 通过返回一个状态值来说明搜索的状态,如果模板搜索成功,则返回 0,如果搜索不成功,则返回 1,如果搜索的文件不存在,则返回 2。我们利用这些返回值就可进行一些自动化的文本处理工作。
在 UNIX
早期的编辑器中,如果要查找比如 junk
这个单词,需要输入 /junk/p
来打印找到的一个行,如果要找所有行,就使用 g/junk/p
,g 是 global search 的意思。
这个功能被独立出来作为了命令,只做一件事情,就是全局搜索,并打印,叫做 grep,其实是 g/regular expression/p
的缩写,可以理解为 g/re/p
,在 vi
或者 sed
中类似的功能也经常能见到。
下面这些选项非常常用,记住:
-i 忽略大小写
-v invert match
-n 行号
-B NUM -A NUM
-C NUM
比如说在当前目录下,搜索文件中包含的 password 词
grep password *
grep 会自动在当前目录下搜索并将包含 password
行打印出来,所以千万不要在本地文件中存放密码一类的敏感信息,这一无异于将密码写在显示器上。
使用 -i
来忽略大小写
grep -i password *
这一行命令会匹配,比如 Password,PASSWORD 等等。
将 grep 作为过滤器来过滤标准输出的内容
cat /etc/passwd | grep 'sshd'
强制让 grep 输出文件名
grep 'sshd' /etc/passwd /dev/null
这时 grep 会打印文件名,冒号,结果
-n
参数会显示行号
grep -n pattern file.txt
正常情况下 grep 会过滤出匹配 正则的行,使用 -v
参数反之,显示不包含正则的行
grep -v 'junk' *
再比如,如果想要设置 every
但是不想搜索 everyone
,everybody
和 everywhere
可以使用
grep every * | grep -v one | grep -v body | grep -v where
grep -B 10 -A 20 pattern file.txt
输出匹配 PATTERN 行的前 10 行和 后 20 行。
很多时候一行日志中有非常多的内容,我们往往只关心特定的部分,这个时候可以使用 grep 的正则来过滤出我们关心的部分,比如日志中的耗时我们可能会打印出 cost 10ms
这样的内容
grep -o -P "cost [0-9]+ms"
说明:
-o
表示只输出匹配到的内容,每一行显示一个-P "regex"
表示开启正则过滤当然如果组合使用 sed 也能够做到同样的事情。
在 grep 之上,后人又开发了很多很有用的命令,比如不解压的情况下搜索压缩包中的内容,再比如简便版的 ack-grep
jinja2 是基于 Python 的模板引擎。
注意下面的 \ 是因为模板需要转义,使用时需要去掉,另外大括号和 % 之间的空格也需要去掉。
\{\% ... \%\} Statements 控制结构,比如 if/elif/else for-loops,macros,block 等等
\{\{ ... \}\} Expressions 表达式,用来输出结果
\{\# ... \#\} Comments 注释
\# ... \#\# Line Statements
\{\{ name \}\}
结构表示一个变量
过滤器用法
Hello, \{\{ name|capitalize \}\}
常用的过滤器
过滤器 | 说明 |
---|---|
safe | 渲染值时不转义 |
capitalize | 把值的首字母转换成大写,其他字母转换成小写 |
lower | 把值转换成小写形式 |
upper | 把值转换成大写形式 |
title | 把值中每个单词的首字母都转换成大写 |
trim | 把值的首尾空格去掉 |
striptags | 渲染之前把值中所有的 HTML 标签都删掉 |
length | 输出长度 |
完整的过滤器清单可以在文档中找到:http://jinja.pocoo.org/docs/templates/#builtin-filters
用来控制流程
常见的 if-else
{ % if user % }
Hello, !
{ % else % }
Hello, Stranger!
{ % endif % }
for 循环
<ul>
{ % for comment in comments % }
<li></li>
{ % endfor % }
</ul>
宏,类似方法
{ % macro render_comment(comment) % }
<li></li>
{ % endmacro % }
<ul>
{ % for comment in comments % }
{ % endfor % }
</ul>
保存到文件中使用 import
{ % import 'macros.html' as macros % }
<ul>
{ % for comment in comments % }
{ % endfor % }
</ul>
需要多次使用的模板可以单独放在文件中,然后 使用 include
{ % include 'common.html' % }
模板继承,先定义父模板
<html>
<head>
{ % block head % }
<title>{ % block title % }{ % endblock % } - My Application</title>
{ % endblock % }
</head>
<body>
{ % block body % }
{ % endblock % }
</body>
</html>
然后继承
{ % extends "base.html" % }
{ % block title % }Index{ % endblock % }
{ % block head % }
<style>
</style>
{ % endblock % }
{ % block body % }
<h1>Hello, World!</h1>
{ % endblock % }
WTForms 支持的字段类型
字段类型 | 说明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密码文本字段 |
HiddenField | 隐藏文本字段 |
DateField | 文本字段,值为 datetime.date 格式 |
DateTimeField | 文本字段,值为 datetime.datetime 格式 |
IntegerField | 文本字段,值为整数 |
DecimalField | 文本字段,值为 decimal.Decimal |
FloatField | 文本字段,值为浮点数 |
BooleanField | 复选框,值为 True 和 False |
RadioField | 一组单选框 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表,可选择多个值 |
FileField | 文件上传字段 |
SubmitField | 表单提交按钮 |
FormField | 把表单作为字段嵌入另一个表单 |
FieldList | 一组指定类型的字段 |
WTForms 支持的验证函数
验证函数 | 说明 |
---|---|
验证电子邮件地址 | |
EqualTo | 比较两个字段的值;常用于要求输入两次密码进行确认的情况 |
IPAddress | 验证 IPv4 网络地址 |
Length | 验证输入字符串的长度 |
NumberRange | 验证输入的值在数字范围内 |
Optional | 无输入值时跳过其他验证函数 |
Required | 确保字段中有数据 |
Regexp | 使用正则表达式验证输入值 |
URL | 验证 URL |
AnyOf | 确保输入值在可选值列表中 |
NoneOf | 确保输入值不在可选值列表中 |
在模板中使用
<form method="POST">
</form>
赋值语句比较简单,Learning Python 这本书中对赋值语句介绍比较详细,分类也讲述的比较细,这篇文章就只简单的记录一些容易混乱的知识点,并不记录所有赋值语句需要注意的点。
在之前的文章中就交代过共享引用是需要特别注意的,在 augmented assignment 中也是
>>> L = [1, 2]
>>> M = L # L M 共享同一个对象
>>> L = L + [3, 4] # Concatenation 会创建新的对象
>>> L, M # 所以他们的值不相同
([1, 2, 3, 4], [1, 2])
>>> L = [1, 2]
>>> M = L # Share
>>> L += [3, 4] # 但是 `+=` 其实用的是 extend 方法,所以是原地修改
>>> L, M
([1, 2, 3, 4], [1, 2, 3, 4])
表达式同样也分为很多种
>>> L = [1,2]
>>> L.append(3)
>>> L
[1, 2, 3]
不过需要注意的是,比如 append()
方法并没有返回值,所有返回的 L 会是 None
>>> L = L.append(4)
>>> L
None
Maven 是一个项目管理工具,主要用于项目构建,依赖管理,项目信息管理。自动化构建过程,从清理、编译、测试和生成报告、再到打包和部署。Maven 通过一小段描述信息来管理项目。
安装之前先把 JDK 环境配置好。
Debian/Ubuntu/Linux Mint 下
sudo apt install maven
如果要手动安装则按照下面步骤,选择一个合适的版本
tar zxvf apache-Maven-3.3.9-bin.tar.gz
sudo mv apache-maven-3.3.9/ /opt/
sudo ln -s /opt/apache-maven-3.3.9/ /opt/maven
root 身份修改配置命令 sudo vi ~/.bashrc
在文件最后添加:
#set Maven environment
#export Maven_OPTS="-Xms256m -Xmx512m"
export M2_HOME=/opt/maven
export M2=$M2_HOME/bin
export PATH=$M2:$PATH
保存并关闭。
mvn -version
如果进行了上面步骤在任意目录中 mvn 命令不能使用,可以在 /etc/profile
文件后面加入下面三行 sudo vim ~/.bashrc
然后输入以下内容
Maven_HOME=/usr/local/apache-maven-3.3.9
export Maven_HOME
export PATH=${PATH}:${Maven_HOME}/bin
设置好 Maven 的路径之后,需要运行下面的命令 source ~/.bashrc
使刚刚的配置生效
Maven 最熟悉的一个概念就是 POM,Maven 项目会有一个 pom.xml 文件, 在这个文件里面添加相应配置,Maven 就会自动帮你下载相应 jar 包
<dependency>
<groupId>com.google.firebase</groupId> 项目名
<artifactId>firebase-admin</artifactId> 项目模块
<version>5.3.1</version> 项目版本
</dependency>
项目名 - 项目模块 - 项目版本
三个坐标定义了项目在 Maven 世界中的基本坐标,任何 jar,pom, war 都是基于这些坐标进行区分的。
groupId
定义了项目组,组和项目所在组织或公司,或者开源项目名称,一般为公司域名反写,比如 com.google.firebase 等等。Maven 项目和实际项目并不一定是一对一关系,比如 SpringFramework 实际项目,对应的 Maven 项目会有很多,spring-core, spring-context 等等,更进一步推荐 groupId 应当定义项目隶属的实际项目,如果定义到组织或者公司,那么一个组织下可能会有很多实际项目,造成混乱artifactId
定义了 Maven 项目的名称,在组中的唯一 ID,在同一个项目中可能有不同的子项目,可以定义不同的 artifactId,可以理解为 Maven 项目的模块。artifactId 也是构建完成项目后生成的 jar 包或者 war 包的文件名的一部分。version
顾名思义,就是项目的版本号,如果项目维发布,一般在开发中的版本号习惯性加上 SNAPSHOT, 比如 1.0-SNAPSHOT根据上面的例子,比如上面定义的 Maven 坐标,可以在对应的中央仓库中 https://repo1.maven.org/maven2/com/google/firebase/firebase-admin/5.3.1/
目录下找到对应的文件。
scope
定义了依赖范围,如果依赖范围为 test ,那么该依赖只对测试有效,也就是说在测试代码中引入 junit 有效,在主代码中用 junit 会造成编译错误。如果不声明依赖范围则默认为 compile ,表示该依赖对主代码和测试代码都有效。Maven 有以下几种依赖范围:
依赖范围和 classpath 的关系
依赖范围 Scope | 编译 classpath 有效 | 测试 classpath 有效 | 运行 classpath 有效 | 例子 |
---|---|---|---|---|
compile | Y | Y | Y | spring-core |
test | - | Y | - | junit |
provided | Y | Y | - | servlet-api |
runtime | - | - | Y | JDBC 驱动 |
system | Y | Y | - | 本地的, Maven 仓库之外的类库 |
Maven 依赖调解 Dependency Mediation ,第一原则:路径最近者优先;第二原则:第一声明者优先。
SNAPSHOT 快照版本只应该在组织内部的项目或者模块之间的依赖使用,组织对于这些快照版本的依赖具有完全的理解和控制权。项目不应该依赖于任何组织外部的快照版本,由于快照的不稳定性,依赖会产生潜在的危险,即使项目构建今天是成功的,由于外部快照版本可能变化,而导致未来构建失败。
在上面介绍 Maven 的作用的时候提到了 Maven 的两个核心概念:坐标和依赖,这也是 Maven 首要解决的问题。这里要引入 Maven 另外一个核心概念:仓库。 Maven 时间中通过坐标来定位依赖,Maven 通过仓库来统一管理这些依赖。
Maven 项目的每一个构件对应着仓库的路径为: groupId/artifactId/version/artifactId-version.packageing
Maven 的仓库分为远程仓库和本地仓库,当第一次运行 Maven 命令时,需要网络连接,从远程仓库下载可用的依赖和插件。而当以后在运行 Maven 命令的时候,Maven 会自动先检查本地 ~/.m2/repository
目录下的依赖,如果本地有缓存优先从本地获取,在找不到的情况下去远程仓库寻找。
常用的在线 maven 依赖查看网站
Maven 的生命周期是抽象的,实际行为都有插件完成。
清理项目,包含三个阶段
pre-clean
执行清理前需要完成的工作clean
清理上一次构建生成的文件post-clean
执行一次清理后需要完成的工作定义了真正构建时所需要执行的步骤
建立和发布项目站点
具体的流程可以参考下图
下面是一个标准的 Maven 工程
src/main/java - 存放项目.java 文件;
src/main/resources - 存放项目资源文件;
src/test/java - 存放测试类.java 文件;
src/test/resources - 存放测试资源文件;
target - 项目输出目录;
pom.xml - Maven 核心文件(Project Object Model);
mvn archetype:create 创建 Maven 项目
mvn compile 编译源代码
mvn deploy 发布项目
mvn test-compile 编译测试源代码
mvn test 运行应用程序中的单元测试
mvn site 生成项目相关信息的网站
mvn clean 清除项目目录中的生成结果
mvn package 根据项目生成的 jar
mvn install 在本地 Repository 中安装 jar
mvn eclipse:eclipse 生成 eclipse 项目文件
mvn jetty:run 启动 jetty 服务
mvn tomcat:run 启动 tomcat 服务
mvn clean package -DMaven.test.skip=true 清除以前的包后重新打包,跳过测试类
mvn clean package 清除以前的包后重新打包
创建一个 Maven 项目
mvn -B archetype:generate
-DarchetypeGroupId=org.apache.maven.archetypes
-DgroupId=com.log4j.maven
-DartifactId=dependency-example