开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Python、Java爬取个人博客信息导出到Excel

发表于 2021-11-09

一、场景分析

今天来爬一下我个人的博客,将我的文章标题和地址归纳到一个Excel中,方便查看。

请添加图片描述
wshanshi能有什么坏心思呢?她只是……想总结下自己文章和相关的地址而已…….

二、界面简单分析:

开发都懂得,打开调试模式。选择Elements,然后找到存放文章的盒子,如下图。

在这里插入图片描述
分析后发现,每个盒子对应存放一个文章相关的信息。
在这里插入图片描述
仔细再看,可以发现所有的article标签的class都是同一个。
在这里插入图片描述
哎呦,都长一样,这就好整了。通过类选择器结合标签,一下就能拿到了。
在这里插入图片描述

点开任意一个文章div,会发现里面里面有文章超链接,标题,描述信息。
在这里插入图片描述
结合此次操作的终极目标,得出了以下操作步骤。
在这里插入图片描述

场景也分析完了,就开搞白。

楼主将用两种方法(Python、Java)进行个人文章的数据收集。干就完事……
在这里插入图片描述

三、Python实现方式

示例版本:Python 3.8
安装包:requests、beautifulsoup4、pandas

库包安装命令(windows下)

1
2
3
复制代码pip install requests	
pip install beautifulsoup4
pip install pandas

3.1、常用的库说明

3.1.1、Requests

什么是Requests?Requests有什么优点、缺点?

中文网地址:docs.python-requests.org/zh_CN/lates…

3.1.2、Beautiful Soup 4.4.0

说明: 是一个可以从HTML或XML文件中提取数据的Python库。

中文网地址:beautifulsoup.readthedocs.io/zh_CN/lates…

3.1.3、Pandas

说明:强大的 Python 数据分析支持库

中文网址:www.pypandas.cn/docs/

具体使用详情认准官网happy啊,此处不多做介绍!!!

3.2、代码示例

直接贴代码:获取的文章标题、文章链接进行整合,导出到 .csv文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
python复制代码# -*- coding:UTF-8 -*-
from bs4 import BeautifulSoup
import requests
import pandas as pd
import openpyxl
import re
import os

def is_in(full_str, sub_str):
return full_str.count(sub_str) > 0

def blogOutput(df,url):
# if file does not exist write header
if not os.path.isfile(url):
df.to_csv(url, index = False)
else: # else it exists so append without writing the header
df.to_csv(url, mode = 'a', index = False, header = False)

if __name__ == '__main__':
target = 'https://blog.csdn.net/weixin_43770545'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.47'}
res = requests.get(target,headers=headers)
#div_bf = BeautifulSoup(res.text)
soup = BeautifulSoup(res.text, 'html.parser')
#定义输出数组
result=[]
for item in soup.find_all("article",{"class":"blog-list-box"}):
#提取文章标题
#print(item.find("h4").text.strip())
#提取文章地址链接
#print(item.find("a").get('href'))
data = []
data.append(item.find("h4").text.strip())
data.append(item.find("a").get('href'))
result.append(data)
df = pd.DataFrame(result,columns=['文章标题', '文章地址'])

# 调用函数将数据写入表格
blogOutput(df, 'F:/blog_result.csv')
print('输出完毕')

编码完成后,就跑起来哇。如下图所示,Run(快捷键F5)。
在这里插入图片描述

提示输出完毕,可查看导出的文件。
在这里插入图片描述

可以的,拿到数据了!!!Python方法就演示这么多(毕竟俺不擅长),下面看Java老哥。

请添加图片描述

四、Java实现方式

Java操作使用Jsoup库,实质操作Dom。

易百教程网址(友情链接):www.yiibai.com/jsoup/jsoup…

4.1、环境、库包

Jsoup Maven:

1
2
3
4
5
6
xml复制代码<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.11.2</version>
</dependency>

4.2、代码示例

定义BlogVo类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typescript复制代码public class BlogVo implements Serializable {
/**
* 文章标题
*/
private String title;
/**
* 文章地址
*/
private String url;
@Excel(colName = "文章标题", sort = 1)
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Excel(colName = "文章标题", sort = 2)
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}

service接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码/**
* 获取提取的博客信息
*
* @return
*/
List<BlogVo> getBlogList();

/**
* 导出csv文件
*
* @param httpServletResponse
* @throws Exception
*/
void export(HttpServletResponse httpServletResponse) throws Exception;

serviceImpl实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
ini复制代码@Override
public List<BlogVo> getBlogList() {
List<BlogVo> list = new ArrayList<>();
try {
Document document = Jsoup.connect("https://blog.csdn.net/weixin_43770545").timeout(20000).get();

Elements e = document.getElementsByClass("blog-list-box");
Elements h4 = e.select(".blog-list-box-top").select("h4");
Elements a = e.select(".blog-list-box").select("a");
List<String> h4List = new ArrayList<>();
List<String> aList = new ArrayList<>();
h4.forEach(item -> {
h4List.add(item.text());
});
a.forEach(item -> {
String href = item.attr("href");
aList.add(href);
});
for (int i = 0; i < h4List.size(); i++) {
BlogVo blogVo = new BlogVo();
blogVo.setTitle(h4List.get(i));
blogVo.setUrl(aList.get(i));
list.add(blogVo);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}

@Override
public void export(HttpServletResponse httpServletResponse) throws Exception {
new ExcelExportUtils().export(BlogVo.class, getBlogList(), httpServletResponse, "blog");
}

controller控制层

1
2
3
4
5
6
7
8
9
10
java复制代码/**
* 导出csv文件
*
* @param response
* @throws Exception
*/
@GetMapping("/getExport")
public void getExport(HttpServletResponse response) throws Exception {
demoService.export(response);
}

自定义注解类(Excel )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码package com.wshanshi.test.entity;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Excel {
public String colName(); //列名

public int sort(); //顺序
}

工具类(导出)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
ini复制代码package com.wshanshi.test.util;

import com.wshanshi.test.entity.Excel;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.functions.T;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
* 导出工具类
*/
public class ExcelExportUtils {
public void ResponseInit(HttpServletResponse response, String fileName) {
response.reset();
//设置content-disposition响应头控制浏览器以下载的形式打开文件
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".csv");
//让服务器告诉浏览器它发送的数据属于excel文件类型
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Prama", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
}

public void POIOutPutStream(HttpServletResponse response, HSSFWorkbook wb) {

try {
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
wb.write(out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}

@SuppressWarnings({"unchecked", "rawtypes"})
public void export(Class<?> objClass, List<?> dataList, HttpServletResponse response, String fileName) throws Exception {

ResponseInit(response, fileName);

Class excelClass = Class.forName(objClass.toString().substring(6));
Method[] methods = excelClass.getMethods();

Map<Integer, String> mapCol = new TreeMap<>();
Map<Integer, String> mapMethod = new TreeMap<>();

for (Method method : methods) {
Excel excel = method.getAnnotation(Excel.class);
if (excel != null) {
mapCol.put(excel.sort(), excel.colName());
mapMethod.put(excel.sort(), method.getName());
}
}
HSSFWorkbook wb = new HSSFWorkbook();
POIBuildBody(POIBuildHead(wb, "sheet1", mapCol), excelClass, mapMethod, (List<T>) dataList);

POIOutPutStream(response, wb);
}

public HSSFSheet POIBuildHead(HSSFWorkbook wb, String sheetName, Map<Integer, String> mapCol) {
HSSFSheet sheet01 = wb.createSheet(sheetName);
HSSFRow row = sheet01.createRow(0);
HSSFCell cell;
int i = 0;
for (Map.Entry<Integer, String> entry : mapCol.entrySet()) {
cell = row.createCell(i++);
cell.setCellValue(entry.getValue());
}
return sheet01;
}

public void POIBuildBody(HSSFSheet sheet01, Class<T> excelClass, Map<Integer, String> mapMethod, List<T> dataList) throws Exception {

HSSFRow r = null;
HSSFCell c = null;

if (dataList != null && dataList.size() > 0) {
for (int i = 0; i < dataList.size(); i++) {
r = sheet01.createRow(i + 1);
int j = 0;
for (Map.Entry<Integer, String> entry : mapMethod.entrySet()) {
c = r.createCell(j++);
Object obj = excelClass.getDeclaredMethod(entry.getValue()).invoke(dataList.get(i));
c.setCellValue(obj == null ? "" : obj + "");
}
}
}
}

}

PostMan测试导出,效果如下。

在这里插入图片描述

哎呦,可以。

请添加图片描述

数据虽然导出了,但是发现一个问题。数据少了?之前用python取到的数据明明是九十多条。为啥这次只请求到了20多条?有点怪哦。

请添加图片描述

仔细看了下界面,原来是因为页面换为了慢加载。当你滑到最下方的时候请求分页的。

在这里插入图片描述
嗷嗷,原来是这样啊。不过,既然看到了接口,那就…..用postMan调下白。

在这里插入图片描述

我giao,这径直拿到数据了啊…..

请添加图片描述

So,还有一种方法。就是直接Http请求这个接口,把分页的条数设置大点就好了。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
ini复制代码public List<BlogVo> blogHttp() {
List<BlogVo> list = new ArrayList<>();
String s = HttpClientUtils.doGetRequest("https://blog.csdn.net/community/home-api/v1/get-business-list?page=1&size=300&businessType=blog&orderby=&noMore=false&username=weixin_43770545", null, null, null);
RespDTO blogDTO = JSON.parseObject(s, RespDTO.class);
DataEntity data = blogDTO.getData();
data.getList().forEach(item -> {
BlogVo blogVo = new BlogVo();
blogVo.setUrl(item.getUrl());
blogVo.setTitle(item.getTitle());
list.add(blogVo);
});
return list;
}

效果如下,自己尝试哈。你懂得…….
图片: https://uploader.shimo.im/f/5izQH7nnnTtvnzj4.png!thumbnail?accessToken=eyJhbGciOiJIUzI1NiIsImtpZCI6ImRlZmF1bHQiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJhY2Nlc3NfcmVzb3VyY2UiLCJleHAiOjE2MzYxMDI1NDMsImciOiJ3OVJ3VHRXd3dUeFFYdGRLIiwiaWF0IjoxNjM2MTAyMjQzLCJ1c2VySWQiOjY1NTk2MjQ5fQ.eYh-SFaKMI89DUbndePbvgqOQlWwfZopzhepCy1I0Pw

害,就先这样吧!别学坏哈…..

在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go语言重新开始,Go Modules 的前世今生与基本使用

发表于 2021-11-09

点击一键订阅《云荐大咖》专栏,获取官方推荐精品内容,学技术不迷路!

1李保坤.jpg

导语 | 随着Go语言发展与场景变化, GOPATH 引起的编译依赖、内部自签发证书、安全审计等问题相继出现,随着官方推出的Go Modules逐渐完善,用户有了新的选择。本文将会带领大家从0开始,认识并使用Go Modules。

213.png

2020 年腾讯内部的一份开发者报告显示,Go 语言已经成为腾讯内部第二大后端开发语言,在腾讯每天有大量的 Go 开发者在做业务和平台开发,大量的团队和项目使用也暴露出一些问题,随着 Go Modules 的出现,类似于内部自签发证书、安全审计等这些问题也逐渐得到解决。

笔者在腾讯当前负责腾讯云在 Go 编程语言使用上的一些问题, 2021年初开始负责内部 goproxy 服务并推广Go Modules使用,这些技术支撑了腾讯云、微信、腾讯视频、腾讯游戏、腾讯音乐、腾讯会议等明星产品,并与公司内部软件源团队、工蜂团队、TRPC 团队以及各个 CI 团队达成密切的合作。在本系列文章中,笔者就将由浅入深帮助大家开始学习和了解 Go Modules。

Golang 开发的模式演进

从 Go 出生开始,用户就一直在使用 GOPATH 这个环境变量,随着 Go 语言的快速发展和不断壮大,由 GOPATH 引起的编译依赖问题也开始逐渐出现。终于在2019 年, Golang 迎来 10 周年之际,Google Go 团队终于开始把目光投向了这一伴随了 Golang 十年的环境变量。

GOPATH的使用

目前在 Go 中存在两种开发模式,GOPATH mode 和 Go modules mode。

在 Go modules 之前,Go 开发中的依赖管理使用 GOPATH 开发模式。在 GOPATH 开发模式中,Go 命令使用 GOPATH 环境变量来实现以下几个功能:

  1. go install 命令安装二进制库到 GOBIN,其默认路径为GOBIN, 其默认路径为 GOBIN,其默认路径为GOPATH/bin。
  1. go install 命令安装编译好的包到 GOPATH/pkg/中,例如将example.com/y/z安装到GOPATH/pkg/ 中,例如将 example.com/y/z 安装到 GOPATH/pkg/中,例如将example.com/y/z安装到GOPATH/pkg/example.com/y/z.a。
  1. go get 命令下载源码包到 GOPATH/src/中,例如将example.com/y/z下载到GOPATH/src/ 中,例如将 example.com/y/z 下载到 GOPATH/src/中,例如将example.com/y/z下载到GOPATH/src/example。

Go modules的发展历程

GOPATH mode 开发模式是终将要被淘汰的,Go 官方在整个 Go 开发生态系统中添加 package version 这一概念,进而引入 Go modules 开发模式。从 GOPATH mode 开发模式变换到 Go modules 是一个漫长的过程,它已经经历了数个 Go 的发行版本:

  • Go 1.11 (2018 年 8 月) 引入了 GO111MODULE 环境变量,其默认值为 auto。如果设置该变量 GO111MODULE=off, 那么 go 命令将始终使用 GOPATH mode 开发模式。如果设置该变量 GO111MODULE=on,go 命令将始终使用 Go modules 开发模式。如果设置该变量 GO111MODULE=auto (或者不设置),go 命令行会根据当前工作目录来决定使用哪种模式,如果当前目录在 GOPATH/src以外,并且在根目录下存在go.mod文件,那么go命令会启用Gomodule模式,否则使用 GOPATH开发模式。这个规则保证了所有在GOPATH/src 以外,并且在根目录下存在 go.mod 文件,那么 go 命令会启用 Go module 模式,否则使用 GOPATH 开发模式。这个规则保证了所有在 GOPATH/src以外,并且在根目录下存在go.mod文件,那么go命令会启用Gomodule模式,否则使用 GOPATH开发模式。这个规则保证了所有在GOPATH/src 中使用 auto 值时原有编译不受影响,并且还可以在其他目录中来体验最新的 Go module 开发模式。
  • Go 1.13 (2019 年 8 月) 调整了 GO111MODULE=auto 模式中对 GOPATH/src的限制,如果一个代码库在GOPATH/src 的限制,如果一个代码库在 GOPATH/src的限制,如果一个代码库在GOPATH/src 中,并且有 go.mod 文件的存在, go 命令会启用 module 开发模式。这允许用户继续在基于导入的层次结构中组织他们的检出代码,但使用模块进行个别仓库的导入。
  • Go 1.16 (2021 年 2 月) 会将 GO111MODULE=on 做为默认值,默认启用 go module 开发模式,也就是说,默认情况下 GOPATH 开发模式将被彻底关闭。如果用户需要使用 GOPATH 开发模式可以指定环境变量 GO111MODULE=auto 或者 GO111MODULE=off。
  • Go 1.NN (???) 将会废弃 GO111MODULE 环境变量和 GOPATH 开发模式,默认完全使用 module 开发模式。

GOPATH 与Go modules的相爱想杀

针对大家关心的几个问题,笔者对此作出如下回答:

Q1:GOPATH 变量会被移除吗?

A:不会,GOPATH 变量不会被移除。未来废弃 GOPATH 开发模式并不是指删除 GOPATH 环境变量,它会继续保留,主要作用如下:

  • go install 命令安装二进制到 GOBIN目录,其默认位置为GOBIN 目录,其默认位置为 GOBIN目录,其默认位置为GOPATH/bin。
  • go get 命令缓存下载的 modules 到 GOMODCACHE目录,默认位置为GOMODCACHE 目录,默认位置为 GOMODCACHE目录,默认位置为GOPATH/pkg/mod。
  • go get 命令缓存下载的 checksum 数据到 $GOPATH/pkg/sumdb 目录。

Q2:我还可以继续在 GOPATH/src/import/path 中创建代码库吗?

A:可以,很多开发者以这样的文件结构来组织自己的仓库,你只需要在自己创建的仓库中添加 go.mod 文件。

Q3: 如果我想测试修改一个我需要的依赖库,我改怎么做?

A:如果你编译自己的项目时依赖了一些未发布的变更,你可以使用 go.mod 的 replace来实现你的需求。

举个例子,如果你已经将 golang.org/x/website 和 golang.org/x/tools 下载到 GOPATH/src/目录下,那么你可以在GOPATH/src/ 目录下,那么你可以在 GOPATH/src/目录下,那么你可以在GOPATH/src/golang.org/x/website/go.mod 中添加下面的指令来完成替换:

replace golang.org/x/tools => $GOPATH/src/golang.org/x/tools

当然,replace 指令是不感知 GOPATH 的,将代码下载到其他目录也一样可以。

从0开始使用 Go Modules

  1. 创建一个新的 Go module

首先创建一个新目录 /home/gopher/hello,然后进入到这个目录中,接着创建一个新文件,

1
2
3
4
5
6
7
8
9
10
11
12
go复制代码
hello.go:

package hello

 

func Hello() string {

    return "Hello, world."

}

然后再写个对应的测试文件 hello_test.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
go复制代码
package hello

 

import "testing"

 

func TestHello(t *testing.T) {

    want := "Hello, world."

    if got := Hello(); got != want {

        t.Errorf("Hello() = %q, want %q", got, want)

    }

}

现在我们拥有了一个 package,但它还不是一个 module,因为还没有创建 go.mod 文件。如果在 /home/gopher/hello 目录中执行 go test,则可以看到:

1
2
3
4
go复制代码
$ go test

go: go.mod file not found in current directory or any parent directory; see 'go help modules'

可以看到 Go 命令行提示没有找到 go.mod 文件,可以参考 go help modules。这样的话可以使用 Go mod init 来初始化一下,然后再执行 Go test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vbnet复制代码
 

$ go mod init example.com/hello

go: creating new go.mod: module example.com/hello

go: to add module requirements and sums:

 

go mod tidy

$go mod tidygo

$ go test

PASS

ok      example.com/hello   0.020s

$

这样的话,module 测试就完成了。

然后执行的 go mod init 命令创建了一个 go.mod 文件:

1
2
3
4
5
6
7
8
bash复制代码
$ cat go.mod

module example.com/hello

 

go 1.17
  1. 给 module 添加依赖

Go modules 的主要亮点在于在编程时使用别人写的代码,即引入一个依赖库时能有非常好的体验。首先更新一下 hello.go,引入 rsc.io/quote 来实现一些新的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go复制代码
package hello

 

import "rsc.io/quote"

 

func Hello() string {

    return quote.Hello()

}

然后,再测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
bash复制代码
$ go test

hello.go:3:8: no required module provides package rsc.io/quote; to add it:

 

go get rsc.io/quote

$ go get rsc.io/quote

go: downloading rsc.io/quote v1.5.2

go: downloading rsc.io/sampler v1.3.0

go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

go: added golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

go: added rsc.io/quote v1.5.2

go: added rsc.io/sampler v1.3.0

$ go test

PASS

ok      example.com/hello   1.401s

从 Go 1.7开始,Go modules 开始使用 lazyloading 加载机制,依赖库的更新需要根据提示手动进行相应的更新。

Go 命令会根据 go.mod 的文件来解析拉取指定的依赖版本。如果在 go.mod 中没有找到指定的版本,会提示相应的命令引导用户添加,然后 Go 命令会去解析最新的稳定版本(Latest),并且添加到 go.mod 文件中。 在这个例子中可以看到,第一次执行的 Go test 运行需要 rsc.io/quote 这个依赖,但是在 go.mod 文件中并没有找到,于是引导用户去获取 latest 版本,用户通过 go get rsc.io/quote 获取了最新版本 v1.5.2,而且还另外下载了另外两个 rsc.io/quote 需要的依赖:rsc.io/sampler 和 golang.org/x/text。间接依赖引用也会记录在 go.mod 文件中,使用 indirect 注释进行标记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
arduino复制代码
$ cat go.mod

module example.com/hello

 

go 1.17

 

require (

    golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect

    rsc.io/quote v1.5.2 // indirect

    rsc.io/sampler v1.3.0 // indirect

)

再一次运行 go test 命令不会重复上面的工作,因为 go.mod 已经是最新的,并且所需的依赖包已经下载到本机中了(在 $GOPATH/pkg/mod 中):

1
2
3
4
5
6
bash复制代码
$ go test

PASS

ok      example.com/hello   0.020s

注意,虽然 Go 命令可以快速轻松地添加新的依赖项,但并非没有代价。

如上面所提到,在项目中添加一个直接依赖可能会引入其他间接依赖。go list -m all 命令可以列出当前项目所依赖的所有依赖:

1
2
3
4
5
6
7
8
9
10
bash复制代码
$ go list -m all

example.com/hello

golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c

rsc.io/quote v1.5.2

rsc.io/sampler v1.3.0

在 go list 的输出结果中,可以看到当前的 module,也被称为 main module 会展示在第一行,然后其他的会按照 module path 进行排序。其中依赖 golang.org/x/text 的版本号 v0.0.0-20170915032832-14c0d48ead0c 是一个伪版本号, 它是 Go 版本的一种,指向了一个没有打 tag 的 commit 上。

另外除了 go.mod 文件,go 命令还维护了一个叫做 go.sum 的文件,这个文件包含了每个版本对应的加密哈希值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bash复制代码
$ cat go.sum

golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZO...

golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:Nq...

rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3...

rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPX...

rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/Q...

rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9...

Go 命令使用 go.sum 来保证每次下载的依赖库代码和第一次都是一致的,进而来保证项目不会出现一些异常情况,所以 go.mod 和 go.sum 都应该上传到 git 等版本控制系统中。

  1. 更新依赖

从上面 go list -m all 命令的输出中,可以看到在库 golang.org/x/text 中使用了一个伪版本号。首先把这个版本好更新到最新的稳定版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vbnet复制代码
 

$ go get golang.org/x/text

go: downloading golang.org/x/text v0.3.7

go: upgraded golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c => v0.3.7

 

$ go test

PASS

ok      example.com/hello   0.013s

测试仍然可以通过,然后再执行一次 go list -m all:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
bash复制代码
$ go list -m all

example.com/hello

golang.org/x/text v0.3.7

rsc.io/quote v1.5.2

rsc.io/sampler v1.3.0

 

$ cat go.mod

module example.com/hello

 

go 1.17

 

require (

    golang.org/x/text v0.3.7 // indirect

    rsc.io/quote v1.5.2 // indirect

    rsc.io/sampler v1.3.0 // indirect

)

结语

Go 通过 Go modules 的依赖管理统一了 Go 生态中众多的第三方的依赖管理,并且高度集成在 Go 命令行中,无需开发者们额外安装使用,目前在当前维护的 Go 版本中都已经支持了 Go modules。还没有切换到 Go modules 的用户,强烈建议大家开始使用它,这无论是从团队开发体验、性能还是安全等方面,都能提供诸多的优质特性和保障。

1李保坤.jpg

《云荐大咖》是腾讯云加社区精品内容专栏。云荐官特邀行业佼者,聚焦于前沿技术的落地及理论实践之上,持续为您解读云时代热点技术、探索行业发展新机。点击一键订阅,我们将为你定期推送精品内容。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Van ♂ Python 用docx、PDF保存爬到的数

发表于 2021-11-09

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

0x1、引言

上节《Van♂Python | 某星球的简单爬取》 有读者在后台私聊我说:

爬取结果保存到Markdown中,不方便在手机上看,

我:

离谱,咳咳,说不定自己以后也会用到,意思意思折腾下吧,接着下午摸鱼的时间,利用搜了一波关键字后,看到了两种常规玩法,都来试试,先来折腾下看似简单的库 ↓

pandoc

0x2、pandoc库初体验

支持 超!超!超! 多类型的互相转换,有下面这一大坨:

好的,不用去看具体支持哪些文件格式,你能想到的基本都有,我们这里是想把 Markdown转成PDF。

怎么安装的话可以看 INSTALL.md,像笔者中Windows的直接下载压缩包或用choco进行安装都可以,这里采用前者,直接下载 免安装包:

接着尝试 配置环境变量,以便到处都可以执行pandoc命令:

解压压缩包 → 进入文件夹 → 选中 pandoc.exe → 按住shift右键 → 复制路径 → 此电脑 → 右键打开属性 → 找到高级系统设置 → 环境变量 → 在系统变量(S)处 → 找到PATH → 编辑 → 新建 → 把刚复制的路径贴到这里:

配置完后,打开cmd,键入:pandoc -v,如果不是输出:pandoc 命令找不到之类的,而出现下面这种的:

说明配置成功,如果确定路径没错,又经过了开机关机等,依旧无效的话,可以跟笔者一样直接把解压后的文件丢到Python的Scripts目录下,通过where命令获取Python的安装目录:

来到下图路径,直接把文件都丢这里就好了

配置完,接着看下怎么用:

比较简单,就是命令行执行下:

1
bash复制代码pandoc -o 待转换文件 转换后的文件

试试直接把txt转成pdf:

需要指定 latex引擎,有下述可选项:

有点麻烦了,有些还要单独下个Latex镜像,4个多G,所以直接还是直接转换成Word文档的格式 → docx了:

1
复制代码pandoc 123.txt -o test.docx

打开看看效果:

还行,接着就是一些遍历文件夹的常规操作了,拼接cmd字符串,利用subprocess执行命令,示例代码如下:

1
2
3
4
5
6
7
8
9
10
python复制代码def md_to_doc(file_path):
cmd = "pandoc {} -o {}"
sep_split = file_path.split(os.path.sep)
# 切换到图片所在目录
os.chdir(output_root_dir)
# 判断文件夹是否存在,不在创建
cp_file_utils.is_dir_existed(os.path.join(doc_save_dir, sep_split[-2]))
doc_file_path = os.path.join(doc_save_dir, '{}{}{}.docx'.format(sep_split[-2], os.path.sep, sep_split[-1][:-4]))
subprocess.call(cmd.format(file_path, doc_file_path), shell=True)
print("生成文件:", doc_file_path)

文件生成完毕了,接着来再写脚本把那么多的Word文档合成一个,需要用到下述库 (直接pip装即可):

1
2
bash复制代码pip install python-docx
pip install docxcompose

接着直接肝代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python复制代码from docx import Document
from docxcompose.composer import Composer

def compose_docx(docx_list):
# 第一个文件
master = Document(docx_list[0])
master.add_page_break() # 强制新一页
composer = Composer(master)
# 后续文件追加合并
for docx in docx_list[1:]:
print("当前处理文件:", docx)
temp = Document(docx)
temp.add_page_break()
composer.append(temp)
composer.save("result.docx")
print("文件合并完毕...")

运行后等坐等程序跑完即可,因为默认的合并顺序是按照文件名的,而我们创建文件时采用的是时间戳方式,所以不用担心顺序问题。看看合成后的文档,可以:

841mb,2927页,WPS打开当场卡死:

0x3、稍微麻烦点的方案

接着说说第二个方案,就是先将Markdown渲染成HTML,然后再转换成PDF,用到这两个库:

1
2
复制代码pip install markdown
pip install pdfkit

还有:wkhtmltopdf,同样下载压缩包,配置环境变量的方式:

命令行键入:wkhtmltopdf -V 查看配置是否生效~

接着就可以直接搞了,写出下面的测试demo:

1
2
3
4
5
6
python复制代码import pdfkit
from markdown import markdown

def md_to_pdf(file_path):
html = markdown(cp_file_utils.read_file_text_content(file_path), output_format='html')
pdfkit.from_string(html, "out.pdf", options={'encoding': 'utf-8'})

传入md文件路径,如果运行后出现:

Pdfkit OSError: No wkhtmltopdf executable found

就是上面的环境变量没生效,重开一个窗口,或者通过下述代码指定路径亦可:

1
2
3
python复制代码import pdfkit
config = pdfkit.configuration(wkhtmltopdf=r"D:\xxx\bin\wkhtmltopdf.exe")
pdfkit.from_url(html, filename, configuration=config)

接着运行还报下述错误:

就是引用到了外部资源,但是却没找着,可以try-except捕获一波异常:

1
2
3
4
5
6
7
8
9
python复制代码def md_to_pdf(file_path):
html = markdown(cp_file_utils.read_file_text_content(file_path), output_format='html')
try:
pdfkit.from_string(html, "out.pdf", options={'encoding': 'utf-8'})
except IOError as e:
# 直接忽略掉异常
pass
finally:
print("文件生成完毕...")

运行后打开生成的PDF,看看效果:

还行,不过这种默认的渲染, 不支持标注、表格、LaTeX、代码块、流程图、序列图和甘特图 ,需要引入更多的扩展。

  • markdown模块扩展

示例如下:

1
2
python复制代码# 启用tables扩展
html = markdown(text, output_format='html', extensions=['tables'])
  • 第三方扩展

示例如下:

1
2
3
4
5
bash复制代码# 安装数学包
pip install python-markdown-math

# 启用数学包扩展
text = markdown(text, output_format='html', extensions=['mdx_math'])
  • 使用第三方Markdown渲染HTML的工具导出HTML (如作业部落),再转成pdf

示例如下:

1
python复制代码pdfkit.from_file('test.html', 'test.pdf', options={'encoding': 'utf-8'})

更多具体内容可参考:Python将MarkDown转PDF(完美转换HTML,含LaTeX、表格等渲染)

0x4、小结

看着都挺简单的,实际上,有深度定制样式需求的,有得折腾,不过巧了,笔者没有,能看就行,感兴趣的读者可以自行摸索下~

爬取数据保存方式技巧+1,阅读体验也更加了,23333,以上就是本文的全部内容,有问题欢迎评论区指出,感谢~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

JAVA使用JAVACV实现图片合成短视频,并给视频添加音频

发表于 2021-11-09

玩抖音的时候,发现可以根据图片生成视频,并添加音频,同时刚好在项目当中也遇到需要利用多张图片生成视频的操作,特此记录下实现的过程!!!

JAVA来实现图片合成视频这个需求,想想还是非常少见的,在网上找了很久资料,基本只找到一个开源库:JAVACV 可以进行操作。并且在网上查找资料的时候也是发现,这方面的资料也是非常少的。有点难受哎!!!

什么是JAVACV?

JavaCV 是一款开源的视觉处理库,基于Apache License Version 2.0协议和GPLv2两种协议 [1] ,对各种常用计算机视觉库封装后的一组jar包,封装了OpenCV、libdc1394、OpenKinect、videoInput和ARToolKitPlus等计算机视觉编程人员常用库的接口。
JavaCV通过其中的utility类方便的在包括Android在内的Java平台上调用这些接口。

GITHUB项目地址:github.com/bytedeco/ja…
GITEE地址:gitee.com/hjljy/javac… (非官方,自己fork的一份)
最重要的是这个项目现在还在维护当中:无论是GITHUB地址,还是Maven仓库,都可以看到代码或者JAR包近期有过更新!!!
Maven仓库地址:mvnrepository.com/search?q=ja…

相关JAR包

下载这个jar非常耗时。难受!!! 建议切换到阿里云仓库,下载要快很多

1
2
3
4
5
6
7
8
9
10
xml复制代码<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.2</version>
</dependency>

图片合成视频

视频都是一张一张图片组成的,每秒的视频都是由25张以上的图片组成的,这个在视频术语里面叫做帧!!! 具体的合成代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
java复制代码package cn.hjljy.javacv;

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.HashMap;
import java.util.Map;

/**
* @author 海加尔金鹰 www.hjljy.cn
* @version V1.0
* @email hjljy@outlook.com
* @description: 图片合成MP4
* @since 2020/5/16 18:00
**/
public class Image2Mp4 {
public static void main(String[] args) throws Exception {
//合成的MP4
String mp4SavePath = "D:\\javacv\\mp4\\img.mp4";
//图片地址 这里面放了22张图片
String img = "D:\\javacv\\img";
int width = 1600;
int height = 900;
//读取所有图片
File file = new File(img);
File[] files = file.listFiles();
Map<Integer, File> imgMap = new HashMap<Integer, File>();
int num = 0;
for (File imgFile : files) {
imgMap.put(num, imgFile);
num++;
}
createMp4(mp4SavePath, imgMap, width, height);
}

private static void createMp4(String mp4SavePath, Map<Integer, File> imgMap, int width, int height) throws FrameRecorder.Exception {
//视频宽高最好是按照常见的视频的宽高 16:9 或者 9:16
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(mp4SavePath, width, height);
//设置视频编码层模式
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
//设置视频为25帧每秒
recorder.setFrameRate(25);
//设置视频图像数据格式
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
recorder.setFormat("mp4");
try {
recorder.start();
Java2DFrameConverter converter = new Java2DFrameConverter();
//录制一个22秒的视频
for (int i = 0; i < 22; i++) {
BufferedImage read = ImageIO.read(imgMap.get(i));
//一秒是25帧 所以要记录25次
for (int j = 0; j < 25; j++) {
recorder.record(converter.getFrame(read));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//最后一定要结束并释放资源
recorder.stop();
recorder.release();
}
}
}

在合成完毕之后,正常打开可以看到一个22秒的视频,可以正常播放,里面的画面也是图片文件夹里面的图片。
几个需要注意的点:
1 建议合成的图片宽高要一致,并且视频的宽高还是要符合一定比例,不然会合成失败!!!
2 一定要释放资源,这个非常占内存
3 H264和YUV420P 都是视频的一些属性,具体作用百度一下你就知道。反正我不是很清楚!!!
4 合成完毕后,会打印合成信息,里面有合成的视频的详细信息,可以仔细看看!!!

视频融合音频

上面合成的视频没有声音,需要将音频融合到视频里面。形成一个完整的视频!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
java复制代码public static boolean mergeAudioAndVideo(String videoPath, String audioPath, String outPut) throws Exception {
boolean isCreated = true;
File file = new File(videoPath);
if (!file.exists()) {
return false;
}
FrameRecorder recorder = null;
FrameGrabber grabber1 = null;
FrameGrabber grabber2 = null;
try {
//抓取视频帧
grabber1 = new FFmpegFrameGrabber(videoPath);
//抓取音频帧
grabber2 = new FFmpegFrameGrabber(audioPath);
grabber1.start();
grabber2.start();
//创建录制
recorder = new FFmpegFrameRecorder(outPut,
grabber1.getImageWidth(), grabber1.getImageHeight(),
grabber2.getAudioChannels());

recorder.setFormat("mp4");
recorder.setFrameRate(grabber1.getFrameRate());
recorder.setSampleRate(grabber2.getSampleRate());
recorder.start();

Frame frame1;
Frame frame2 ;
//先录入视频
while ((frame1 = grabber1.grabFrame()) != null ){
recorder.record(frame1);
}
//然后录入音频
while ((frame2 = grabber2.grabFrame()) != null) {
recorder.record(frame2);
}
grabber1.stop();
grabber2.stop();
recorder.stop();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (recorder != null) {
recorder.release();
}
if (grabber1 != null) {
grabber1.release();
}
if (grabber2 != null) {
grabber2.release();
}
} catch (FrameRecorder.Exception e) {
e.printStackTrace();
}
}
return isCreated;

}

到这里一个完整的视频就合成出来了!!!。但是在视频融合音频的过程当中还是有一些比较需要注意的点:
1 视频长度和音频长度尽量保持一致,如果不一致,合成的视频长度会以最长的为准,音频短,后面就自然缺失音频,视频短,后面的视频会呈现视频的最后一帧。
2 不建议录一帧视频然后录一帧音频,音频的后半段会丢失,比例差不多是1:1.6!!!

最后总结

这个功能是非常耗时与耗内存的一个操作,所以一定要注意服务器的内存问题。
推荐一些其他人的操作文章:
音频与视频合成技术
javaCV入门指南:序章
javacv opencv 多图片合成视频 并加入mp3的音频 控制视频秒数

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【腾讯面试题】青蛙跳跳跳

发表于 2021-11-09

青蛙的故事

青蛙公主是远近闻名的大美蛙🐸,她一心想找一个聪明的老公,于是设下擂台,比武招亲:有连续的木板,因为材质不同,每个木板能跳的最大距离有限,来应招的青年才俊,需要判断,是否能跳到最后一块板子。

让我们来看看输入参数:一个非负整数数组,青蛙位于数组的第一个位置,数组中的每个元素代表你在该位置可以跳跃的最大长度。输出就更简单:能否跳到。

解题思路

按照一般的解题思维,DFS肯定可以做,但效率应该不是最高。递推的话,动态规划可以考虑,同时看看有没有使用贪心算法的可能性,如果能贪心,那可能就是最简路径。

很幸运,这道题无论是DFS,还是动态规划,亦或是贪心算法都可以做,我比较喜欢这类题,从不同思路,开展不同做法,互相比较,归纳复盘,容易得到提高。话不多说,我们来逐个看看。

花式吊打

DFS深度优先算法

最直接的解法,就是基于每种可能,即每块板子可能的跳跃距离,进行DFS递归搜索。这种算法思路比较无脑,缺点显而易见:时间复杂度很高,大概率会超时。

换个角度:目标是最后一个木板。那么往前找到所有能跳到目标的木板,然后以这些木板为目标,递归查找下去。这种算法速率也一般,算是优化版的DFS。

果不其然,运行超时,蛙蛙流泪。

动态规划

我们定义一个数组dp,dp[i]表示当前可以覆盖的最大范围。当前位置能否可达很简单,判断dp[i-1]是否小于i,如果小于就不可达。如果可达,更新dp[i] = Max(dp[i-1], i+nums[i])。

由上图执行结果可以看到,按照动态规划执行的数据,相比DFS是飞跃,耗时减少2/3,内存消耗减少100%以上,时间复杂度应该已经是最优,后面就看空间上是否可以优化。

贪心算法

贪心算法,顾名思义,就是足够贪心。如果当前木板能跳到的距离比当前能达的距离更远,就跳到当前木板,否则就先停留在当前木板。按照这样的规则,一直跳到最后一块板,其间永远站在能跳的最远的木板。如果中间有位置不可达了,那就是跳不到,因为在贪心的基础上,当前点已经是覆盖范围最大的了,如果这样都跳不到,其他方式也不行的。

我们能看到这样写代码,简洁优雅,耗时满足预期,写完给自己点个赞👍,青蛙王子也得感谢我!

但是仔细想想,上面的代码其实可以更简洁明了。因为在本题中跳点不是必要项,换句话说,我们可以从关注跳点,变成关注距离。优化后的代码如下,是不是更加清晰?

把简单的事情做复杂不算什么。把复杂的事情越做越简单,才是能力提升的关键!

逆推法

实不相瞒,这题做到这里,还做出感觉了。我就想,还有没有其他解决方法?

换个角度思考一下,之前大多为正向推导,但是DFS是逆向行走,说明逆推是可行的,只是我们嫌弃DFS效率太低。

对于一个目标位置,我们往前找能跳到该位置的最近的木板,将这个木板作为我们新的目标。这样持续倒推,如果最终推导到了第一块木板,也就是我们的起点,那么就证明可以从起点跳到终点。

注意,我们来仔细思考下这种方式和DFS的不同,DFS是基于当前位置,从后往前递归所有能跳到当前木板的情况,会走完多条分支路径;而这种逆推方式,只会随遇而安地往前挪挪,是O(n)的复杂度。

蛙蛙复盘

横看成岭侧成峰,条条大路通罗马,一个简单的跳板运动,居然有这么多思维方式。把一件事情做到极致,我们也一定会收获多多。做算法题,不仅要尝试beats 100%,更进一步的追求是将各种方式都做透彻,每种方式可能有自己的优缺点,很可能加一个限制条件,当前最优解就成死路了。

当然,最重要的是,这样的做题习惯训练了我们不同思维方式。无论是在学习还是工作中,一个问题,你能给出好几种解决方案,还不能成为这条街上最靓的仔?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【收藏夹吃灰系列】MySQL 常用函数细致总结

发表于 2021-11-09

前言

这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战 。MySQL 常用函数不熟练?看我这篇就够了!!

▶ MySQL 常用函数总览

MySQL 常用函数.png

★ 字符串函数

LENGTH(str)

掌握指数:★★★★

函数说明:

返回 str 字符串字节长度

注意:

  • 英文的一个字符为 1 个字节
  • GBK 编码的为 2 个字节
  • UTF-8 编码的汉字为 3 个字节

SQL 语句示例:

1
2
3
4
5
sql复制代码
# 英文字符串字节长度
select length('HUALEI'); # 6

select length('大家好'); # 9

INSERT(str,pos,len,newstr)

掌握指数:★★★

函数说明:

向 str 中第 pos 位置开始插入长度为 len 的 newStr 字符串

SQL 语句示例:

如果我想给 str = 'HUALEI' ,想用 insert() 函数将其拼接成 “HUALEI is a hansome boy.”,那我该怎么做呢?

1
2
3
4
5
6
7
8
9
10
sql复制代码
select insert('HUALEI', length('HUALEI')+1, length('is a hansome boy.'), 'is a hansome boy.'); # HUALEI

or

select insert('HUALEI', length('HUALEI')+2, length('is a hansome boy.'), 'is a hansome boy.'); # HUALEI

but answer:

select insert('HUALEI ', length('HUALEI')+2, length('is a hansome boy.'), 'is a hansome boy.'); # HUALEI is a hansome boy.

注意: MySQL 中从 1 开始算下标,插入要满足 pos <= length(str) 。

那如果是中文汉字字符串呢,如何让 “青花瓷” 变成 “青花烤瓷”,“苏格拉底广场” 变成 “苏格拉底广场舞” ?

1
2
3
4
sql复制代码    
select insert('青花瓷', 3, length('烤瓷'), '烤瓷'); # 青花烤瓷

select insert('苏格拉底广场', 7, length('舞'), '舞'); # 苏格拉底广场舞

第一个 SQL 中 pos => 3 为汉字字符串的下标索引,从第三个汉字字符开始插入长度为 length('烤瓷') 的 '烤瓷' ;再看第二个 SQL, 7 大于字符个数,但是并不大于 length('苏格拉底广场') => 18 ,所以新字符串在最后一个位置上插入成功。

LEFT(str,len)

掌握指数:★★★

函数说明:

从字符串 str 左边开始截取长度为 len 的字符串

SQL 语句示例:

1
2
sql复制代码
select left('HUALEI', 3); # HUA

那 “大家好” 这个汉字字符串我想左截取出 “大家”,该如何做呢?

1
2
sql复制代码
select left('大家好', length('大家')); # 大家好

奇怪,截取的结果和预期不一样,为什么呢?

注意: 这里的 len 还是并不是通过 length() 而是通过 char_length() 得到的,从 1 开始到 len 即为截取的目标字符串。

所以,对于中文字符串来说,正确的写法应该是:

select left('大家好', 2); # 大家

RIGHT(str,len)

掌握指数:★★★

函数说明:

从字符串 str 右边开始截取长度为 len 的字符串

SQL 语句示例:

1
2
3
4
sql复制代码
select right('HUALEI', 3); # LEI

select right('大家好', 1); # 好

不赘述,和 LEFT() 一个道理。

SUBSTR(str FROM pos) <=> SUBSTR(str,pos) SUBSTR(str FROM pos FOR len) <=> SUBSTR(str,pos,len)

掌握指数:★★★★

函数说明:

从 str 的第 pos 个位置开始截取长度为 len 的子串,如果没有 len 参数则截取到末尾

SQL 语句示例:

1
2
3
4
5
6
7
8
9
10
11
sql复制代码
select substr('abcdefg' from 2); # bcdefg
# 等价于
select substr('abcdefg', 2); # bcdefg

select substr('abcd' from 1 for 3); # abc
# 等价于
select substr('abcd', 1, 3); # abc

# 这里的 len 理解为从 pos 开始截取几个汉字
select substr('大家好', 2, 1); # 家

STRCMP(expr1,expr2)

掌握指数:★★★

函数说明:

expr1 > expr2 => 返回 1;expr1 = expr2 => 返回 0;expr1 < expr2 => 返回 -1

SQL 语句示例:

1
2
3
4
5
6
7
8
sql复制代码
select strcmp('bbcd','bacd'); # 对应位置上的字符对应相比,返回 1

select strcmp('你好', '你好'); # 两个字符串一模一样,返回 0

select strcmp('ABC','abc'); # 不区分大小写,返回 0

select strcmp('abadf','abadfe'); # 后面大,返回-1

CONCAT(str1,str2,…)

掌握指数:★★★★

函数说明:

将 str1、str2 … 等字符串连接成一个新的字符串

SQL 语句示例:

1
2
3
4
sql复制代码
select concat('hel','llo'); # hello

select concat('大家','好'); # 大家好

注意: 只要连接的字符串中存在一个 null 值,最终结果也将是 null 。

1
2
3
4
sql复制代码
select concat(null, 'abc'); # null

select concat('abc',null); # null

LOCATE(substr,str) | POSITION(substr IN str) | INSTR(str,substr)

掌握指数:★★★★

函数说明:

返回子串 substr 在父串 str 中的开始位置,如果父串中压根就不包含子串那么返回 0

SQL 语句示例:

1
2
3
4
5
6
7
8
9
10
sql复制代码
select locate('LEI', 'HUALEI'); # 子串 'LEI' 在父串 'HUALEI' 中的开始位置是 4
select locate('LEI ', 'HUALEI'); # 子串不存在,返回 0

select position('LEI' in 'HUALEI'); # 子串 'LEI' 在父串 'HUALEI' 中的开始位置是 4
select position('LEI ' in 'HUALEI'); # 子串不存在,返回 0

select instr('HUALEI', 'LEI'); # 4

select instr('HUALEI', 'LEI '); # 0

小结:

  • 三个函数都能拿到子串在父串中的开始位置
  • locate() 和 position() 函数基本类似,就是参数列表不同,后者使用 in 表示子串在父串里面中的 position 位置,写起来比较好理解,更推荐使用
  • instr() 和 locate() 唯一不同的就是参数位置交换了,注意别搞混淆写反了

LOWER(str) | UPPER(str)

掌握指数:★★★

函数说明:

将 str 字符串全部小写(LOWER)/大写(UPPER)

SQL 语句示例:

1
2
3
4
5
6
sql复制代码
# 小写化
select lower('HUALEI'); # hualei

# 大写话
select upper('hualei'); # HUALEI

注意: 仅对英文字符串有效,中文字符串无效。

1
2
sql复制代码
select lower('大家好,我是 HUALEI'); # 大家好,我是 hualei

LTRIM(str) | RTRIM(str) | TRIM([remstr FROM] str)

掌握指数:★★★★

函数说明:

去除 str 字符串中的空格

SQL 语句示例:

1
2
3
4
5
6
sql复制代码
select ltrim(' HUALEI'); # HUALEI

select rtim('大家好! '); # 大家好!

select trim(' HUALEI '); # HUALEI

注意: trim() 函数只会去除 str 字符串前后的空格并不会去除所有空格!

1
2
sql复制代码
select trim(' HUA LEI '); # HUA LEI

REPEAT(str,count)

掌握指数:★★

函数说明:

返回 str 重复 count 遍后的结果

SQL 语句示例:

1
2
3
4
sql复制代码
select repeat('HUALEI ', 5); # HUALEI HUALEI HUALEI HUALEI HUALEI

select repeat('雷猴啊', 2); # 雷猴啊雷猴啊

REVERSE(str)

掌握指数:★★

函数说明:

将字符串 str 按倒序反过来

SQL 语句示例:

1
2
3
4
sql复制代码
select reverse('我被反过来了'); # 了来过反被我

select reverse('HUALEI'); # IELAUH

RPAD(str,len,padstr) | LPAD(str,len,padstr)

掌握指数:★★★

函数说明:

指定 str 字符串长度 len, len > length(str) 不足用 padstr 向右/左填充;len < length(str) 充足则根据指定长度进行截取。

SQL 语句示例:

1
2
3
4
5
6
7
8
9
10
sql复制代码
select rpad('大家', 3, '好'); # 大家好

select rpad('HUALEI', length('HUALEI')+length(' NB'), ' NB'); # HUALEI NB

select length('大家好'); # 9

select lpad('SQL', 3, 'My'); # SQL

select lpad('SQL', length('MySQL'), 'My'); # MySQL

★ 数学函数

FORMAT(X,D) | ROUND(X) | ROUND(X,D)

掌握指数:★★★★

函数说明:

对 X 四舍五入保留小数点后 D 位

SQL 语句示例:

1
2
3
4
5
6
7
sql复制代码
select format(3.1415926,3); # 3.142
# 等价于
select ROUND(3.1415926, 3); # 3.142

# 无保留位数时,取整
select round(3.1415926); # 3

CEIL(X) | FLOOR(X)

掌握指数:★★★★

函数说明:

ceil 天花板(向更大值方向)取整;floor 地板(向更小值方向)取整

SQL 语句示例:

1
2
3
4
5
6
7
8
sql复制代码
# 向上取整
select ceil(3.5); # 4
select ceil(-3.5); # -3

# 向下取整
select floor(3.5); # 3
select floor(-3.5); # -4

MOD(N,M)

掌握指数:★★★

函数说明:

取 N / M 的余数,等价于 N % M

SQL 语句示例:

1
2
3
4
sql复制代码
select mod(10, 3); # 1
# 等价于
select 10 % 3; # 1

POW(X,Y) | POWER(X,Y)

掌握指数:★★★

函数说明:

返回 X 的 Y 次方

SQL 语句示例:

1
2
3
4
sql复制代码
select pow(2, 10); # 2^10 = 1024
# 也可以写作
select power(2, 10); # 2^10 = 1024

SQRT(X)

掌握指数:★★★

函数说明:

返回 X 的平方根,也就是对 X 开平方

SQL 语句示例:

1
2
3
4
sql复制代码
select sqrt(100); # 10

select sqrt(2); # 根号 2 => 1.4142135623730951

GREATEST(expr1, expr2, expr3, …) | LEAST(expr1, expr2, expr3, …)

掌握指数:★★★

函数说明:

返回参数列表中最大/最小值

注意: 参数列表可以是字符序列。

SQL 语句示例:

1
2
3
4
5
6
7
8
sql复制代码
select greatest(1,2,3,4,51,6,7,8); # 51

select greatest('Java', 'MySQL', 'JavaScript'); # MySQL

select least(-1,2,3,4,5,6,7,8); # -1

select least('Java', 'MySQL', 'JavaScript'); # Java

RAND()

掌握指数:★★★★★

函数说明:

返回 (0, 1) 之间的随机数

SQL 语句示例:

1
2
3
4
5
6
7
sql复制代码
select rand(CURRENT_TIMESTAMP); # 给定种子值,这里给当前时间戳作为值,保证相对随机

select format(rand()*100, 0); # 产生 (0, 100) 之间的随机整数
select round(rand()*100); # 产生 (0, 100) 之间的随机整数

select ... order by rand() limit N; # 随机查询取 N 条记录

聚合函数 MAX(expr) | MIN(expr) | SUM(expr) | COUNT(expr) | AVG([DISTINCT] expr)

掌握指数:★★★★★

函数说明:

聚合函数,配合 group by 使用,用来求最大、小值/求和/计数/求平均值

SQL 语句示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sql复制代码
# 查询男同事里薪水最高的人
select max(salary) from emp where sex = '男';

# 查询女同事里薪水最少的人
select min(salary) from emp where sex = '女';

# 查询所有姓王的学生的个人总成绩
select sum(sscore) 姓王的学生的个人总成绩 from score where sid in (select sid from student stu where stu.sname like '王%') group by sid;

# 查询女同事的人数
select count(id) 女同事的人数 from emp group by sex having sex = '女';

# 查询平均薪水
select avg(salary) 平均薪水 from emp;

注意:

  • 除非另有说明,否则聚合函数会忽略 null 值
  • 如果在不包含 group by 子句的语句中使用聚合函数,就等效于对所有行进行分组,结果总是有一行
  • 时间类型的值对 sum() 和 avg() 无效!它们会将其换成数字,丢弃第一个非数字字符后的所有信息

另外,聚合函数可以传入独立的表达式作为参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sql复制代码
# 查询分数在 80 分及以上人数
select count(1) from score where sscore >= 80;
# 等价于
select count(if(sscore >= 80, 1, null)) from score;

# 查询分数在 80 分及以上总分
select sum(sscore) from score where sscore >= 80;
# 等价于
select sum(if(sscore>=80, sscore, null)) from score;

# 查询分数在 80 分及以上平均分
select avg(sscore) from score where sscore >= 80;
# 等价于
select avg(if(sscore>=80, sscore, null)) from score;

★ 日期函数

CURDATE() <=> CURRENT_DATE

掌握指数:★★★★

函数说明:

返回当前日期,格式为 YYYY-MM-dd

SQL 语句示例:

1
2
3
4
sql复制代码
select curdate(); # 2021-11-07

select current_date; # 2021-11-07

CURTIME() <=> CURRENT_TIME

掌握指数:★★★★

函数说明:

返回当前时间,格式为 HH:mm:ss

SQL 语句示例:

1
2
3
4
sql复制代码
select curtime(); # 10:31:23

select current_time; # 10:31:23

NOW() <=> CURRENT_TIMESTAMP

掌握指数:★★★★

函数说明:

返回当前日期时间,格式为 YYYY-MM-dd HH:mm:ss

SQL 语句示例:

1
2
3
4
sql复制代码
select now(); # 2021-11-07 10:31:46

select current_timestamp; # 2021-11-07 10:31:46

DAY(date) | DAYOFWEEK(date) | DAYOFMONTH(date) | DAYOFYEAR(date)

掌握指数:★★★★

函数说明:

返回 date 中 dd / 这一天是这一周/月/年中的第几天

SQL 语句示例:

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码
# 取出 date 中的天
select day('2021-11-07'); # 7

# 一周中的第几天
select dayofweek(now()); # 星期天(Sunday) -> 返回 1

# 一个月中的第几天
select dayofmonth(now()); # 7

# 一年中的第几天
select dayofyear(now()); # 311

WEEK(date[,mode]) | WEEKOFYEAR(date)

掌握指数:★★★

函数说明:

Mode First day of week
0 Sunday => 1
1 Monday => 1

使用 mode 指定星期天是一周中的第一天还是星期一,然后根据这个标准判断 date 是一年的第几周,返回结果 (0, 52) ;WEEKOFYEAR() 总是以星期一作为一周的开始,即 mode 固定就是 1

SQL 语句示例:

1
2
3
4
5
6
7
8
9
sql复制代码
# 默认,mode => 0,星期天(Sunday)=> 1
select week('2021-11-07'); # 45
select week('2021-11-07', 0); # 45

# 星期一(Monday)=> 1
select week('2021-11-07', 1); # 44
# 等价于
select weekofyear('2021-11-07'); # 44

MONTH(date) | QUARTER(date)

掌握指数:★★★

函数说明:

返回 date 中的月份/所属季度

SQL 语句示例:

1
2
3
4
5
sql复制代码
select month(now()); # 11

# 第一季度(1, 2, 3)第二季度(4, 5, 6)第三季度(7, 8, 9)第四季度(10, 11, 12)
select quarter(curdate()); # 4

YEAR(date) | YEARWEEK(date,mode)

掌握指数:★★★★

函数说明:

返回 date 中的年份/年份+第几周

SQL 语句示例:

1
2
3
4
5
6
7
8
9
10
sql复制代码
select year(curdate()); # 2021

# 默认 mode = 0,星期天(Sunday)=> 1
select yearweek('2021-11-07'); # 202145
# 等价于
select YEARWEEK('2021-11-07', 0); # 202145

# 星期一(Monday) => 1
select YEARWEEK('2021-11-07', 1); # 202144

DAYNAME(date) | MONTHNAME(date)

掌握指数:★★

函数说明:

返回该天/月英文名

SQL 语句示例:

1
2
3
4
sql复制代码
select dayname('2021-11-07'); # Sunday 星期天

select monthname('2021-11-07'); # November 11 月

STR_TO_DATE(str,format) | DATE_FORMAT(date,format)

掌握指数:★★★★★

函数说明:

根据 date 字符串的格式,转换成日期,相反地,可以将 date 转换成指定格式的字符串

SQL 语句示例:

1
2
3
4
5
6
7
sql复制代码
# 字符串转 date 类型
select str_to_date('2021年11月07日', '%Y年%m月%d日'); # 2021-11-07
select str_to_date('2021年11月07日 12点28分34秒', '%Y年%m月%d日 %H点%i分%s秒'); # 2021-11-07 12:28:34

# date 转指定格式的字符串
select date_format(now(), '%Y年%m月%d日 %H点%i分%s秒'); # 2021年11月07日 11点29分56秒

DATEDIFF(expr1,expr2)

掌握指数:★★★★★

函数说明:

返回两个 date 相隔的天数

SQL 语句示例:

1
2
3
4
sql复制代码
select concat(datediff(curdate(), '2021-01-01'), '天') as '距离开年以来已经过了'; # 312天

select concat(datediff(str_to_date(concat(year(now()), '/12/31'), '%Y/%m/%d'), now()), '天') as '距离年末仅剩'; # 52天

DATE_ADD(date,INTERVAL expr unit) | DATE_SUB(date,INTERVAL expr unit)

掌握指数:★★★★★

函数说明:

对 date 做加减法

SQL 语句示例:

1
2
3
4
5
6
7
8
9
10
11
sql复制代码
select ceil(rand()*31); # (0, 31]
# 将当前时间戳加上 (0, 31] 区间中的随机 DAY 天数
select date_add(CURRENT_TIMESTAMP, interval ( ceil(rand()*31) ) DAY );

select ceil(rand()*4); # [1, 4]
# 将当前时间戳加上 [1, 4] 区间中的随机 WEEK 周数
select date_add(now(), interval ( ceil(rand()*4) ) WEEK );

# 相反地,对指定 date 做减法,将当前时间戳减去 [0, 10] 区间中的随机 YEAR 年数
select date_sub(CURRENT_TIMESTAMP, interval ( round(rand(CURRENT_TIME)*11) ) YEAR );

TO_DAYS(date) | FROM_DAYS(N)

掌握指数:★★

函数说明:

给定一个 date ,返回从公元 0 年到 date 的天数

小知识:历史并不存在公元 0 年,但 0 年是公元位数对齐的基础

SQL 语句示例:

1
2
3
4
5
6
7
8
sql复制代码
select to_days('2021-11-07'); # 738466
# 并不完全等价,to_days() 包含当天,而 datediff() 不然
select datediff('2021-11-07','0-01-01'); # 738465

# to_days() 的逆运用,给定一个从公元 0 年开始的天数,返回一个 date
select from_days(737515); # 2019-4-1
select from_days(to_days('2021-11-07')); # 2021-11-07

☛ 流程控制函数

IF(expr1,expr2,expr3)

掌握指数:★★★★

函数说明:

判断 expr1 表达式真假,真返回 expr2,否则返回 expr3

SQL 语句示例:

1
2
sql复制代码
select if(10 > 5, '10 更大', '5 更大'); # 10 更大

IFNULL(expr1,expr2) | NULLIF(expr1,expr2)

掌握指数:★★★

函数说明:

IFNULL 用来判断 expr1 是否为 null,如果不是则返回 expr2 ,否则返回 expr1 ;NULLIF 则是用来判断 expr1、2 是否相等,相等则返回 null,否则返回 expr1

SQL 语句示例:

1
2
3
4
5
6
7
8
9
10
11
12
sql复制代码
# expr1 is not null.return expr1
select ifnull('exp1 is not null', null); # exp1 is not null
# expr1 is null.return expr2
select ifnull(null, 'exp1 is null'); # exp1 is null

select nullif('HUALEI', 'hualei'); # 相等返回 null
# 等价于(不区分大小写)
select nullif('HUALEI', 'HUALEI'); # 相等返回 null

# expr1 != expr2
select nullif('HUALEI', null); # HUALEI

注意: expr1 != null ,否则返回 null 。

1
sql复制代码select nullif(null, 'HUALEI'); # null

IF … ELSE 语句

掌握指数:★★★★

函数说明:

写法不同于 IF() 函数

语法:

1
2
3
4
5
6
sql复制代码
IF search_condition THEN
statement_list
ELSE
statement_list
END IF;

SWITCH … CASE 语句

掌握指数:★★★★

函数说明:

开关语句

语法:

1
2
3
4
5
6
7
sql复制代码
CASE case_value
WHEN when_value THEN
  statement_list
ELSE
  statement_list
END;

☛ 消息摘要函数

PASSWORD(str)

掌握指数:★★★

函数说明:

计算并返回密码字符串

SQL 语句示例:

1
2
3
4
sql复制代码
select PASSWORD('abc') # *0D3CED9BEC10A777AEC23CCC353A8C08A633045E

select PASSWORD('ABC') # *71B101096C51D03995285042443F5C44D59C8A31

注意: 该函数在 MySQL8.0.11 版本中被移除了。

MD5(str)

掌握指数:★★★★

函数说明:

计算 MD5 总和校验码

SQL 语句示例:

1
2
sql复制代码
select MD5("abc"); # 900150983cd24fb0d6963f7d28e17f72

SHA(str) | SHA1(str)

掌握指数:★★★★

函数说明:

计算 SHA/SHA1 总和校验码

SQL 语句示例:

1
2
3
4
sql复制代码
select SHA('abc') # a9993e364706816aba3e25717850c26c9cd0d89d
# 等价于
select SHA1('abc') # a9993e364706816aba3e25717850c26c9cd0d89d

☛ 对称加密函数

ENCODE(str,pass_str) | DECODE(crypt_str,pass_str)

掌握指数:★★★★

函数说明:

通过公共密钥加密(Encode 编码)/解密(Decode 解码)

SQL 语句示例:

1
2
3
4
5
6
sql复制代码
# 'password' 作为公钥加密字符串信息 'HUALEI'
select encode('HUALEI', 'password'); # ���e

# 将加密后的密文通过公钥 'password' 进行解密,从而得到加密前的明文
select decode(encode('HUALEI', 'password'), 'password'); # HUALEI

AES_ENCRYPT(str,key_str) | AES_DECRYPT(crypt_str,key_str) | DES_ENCRYPT(str[,{key_num|key_str}]) | DES_DECRYPT(crypt_str[,key_str])

掌握指数:★★★★

函数说明:

通过额 AES(Advanced Encryption Standard 高级加密标准,作为 DES 算法的替代品) / DES(Data Encryption Standard 数据加密标准) 算法对称加密信息

SQL 语句示例:

1
2
3
4
5
6
7
8
9
10
sql复制代码
# AES 算法加密,公钥为 'salt'
select aes_encrypt('HUALEI', 'salt'); # �ɫD�b*�ճ�ϐe�
# AES 算法解密
select aes_decrypt(aes_encrypt('HUALEI', 'salt'), 'salt'); # abc

# DES 算法加密,公钥为 'password'
select des_encrypt('HUALEI', 'password'); # ��o�#�
# DES 算法解密
select des_decrypt(des_encrypt('HUALEI', 'password'), 'password'); # HUALEI

☛ 系统信息函数

VERSION()

掌握指数:★★★★

函数说明:

返回当前 MySQL 版本号

SQL 语句示例:

1
2
sql复制代码
select version(); # 5.7.31-log

USER() | CURRENT_USER

掌握指数:★★★★

函数说明:

返回当前用户角色

SQL 语句示例:

1
2
3
4
sql复制代码
select user(); # root@localhost
# 等价于
select current_user; # root@localhost

DATABASE()

掌握指数:★★★★

函数说明:

返回当前所在数据库名

SQL 语句示例:

1
2
sql复制代码
select database(); # mysql

CONNECTION_ID()

掌握指数:★★★

函数说明:

返回当前用户连接数

SQL 语句示例:

1
2
sql复制代码
select connect_id(); # 38

函数全概述

以上全部函数,我将它们归纳成一张思维导图,非常直观,供大家学习参考:

MySQL 常用函数.jpg

结尾

撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

开发笔记-java解决并发请求下数据插入重复问题

发表于 2021-11-09

前段时间发现数据库里经常会存在两条相同的用户数据,导致数据查询异常。查了原因,发现前端微信小程序在授权登录时,有时会出现同时发送了两条一模一样的请求(也就是常说的并发)。虽然后端代码有做防重复的判断,但是避免不了并发时候的重复性操作。于是就开始考虑并发的解决方案,解决方案有很多,从拦截请求到数据库层面都可以入手。

我们采用了对请求报文生成摘要信息+Redis分布式锁的方案。运行了一段时间,功能很可靠,代码也很简洁。于是上来做下记录以便后续参考。

解决方案说明:
系统架构用的Spring boot,定义一个Filter过滤器对请求进行过滤,然后对请求报文生成摘要信息并设置Redis分布式锁。通过摘要和锁判断是否为同一请求。

分布式锁工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
ini复制代码public class ContextLJ {

private static final Integer JD = 0;

/**
* 上锁 使用redis 为分布式项目 加锁
* @param sign
* @param tiD
* @return
* @throws Exception
*/
public static boolean lock(String sign, String tiD) {
synchronized (JD) { // 加锁
Cache<String> cache = CacheManager.getCommonCache(sign);
if(cache == null || StringUtils.isBlank(cache.getValue())) {
CacheManager.putCommonCacheInfo(sign, tiD, 10000);
return true;
}
return false;
}
}

/**
* 锁验证
* @param sign
* @param tiD
* @return
*/
public static boolean checklock(String sign, String tiD){
Cache<String> cache = CacheManager.getCommonCache(sign);
String uTid = StringUtils.replace(cache.getValue(), "\"", "");
return tiD.equals(uTid);
}

/**
* 去掉锁
* @param sign
* @param tiD
*/
public static void clent (String sign, String tiD){
if (checklock(sign, tiD)) {
CacheManager.clearOnly(sign);
}
}

/**
* 获取摘要
* @param request
*/
public static String getSign(ServletRequest request){
// 此工具是将 request中的请求内容 拼装成 key=value&key=value2 的形式 源码在线面
String sign = null;
try {
Map<String, String> map = getRequstMap((HttpServletRequest) request);
// 生成摘要
sign = buildRequest(map);
} catch (Exception e) {
e.printStackTrace();
}
return sign;
}

public static Map<String, String> getRequstMap(HttpServletRequest req) throws Exception{
Map<String,String> params = new HashMap<String,String>();
params.put("uri", req.getRequestURI());
Map<String, String[]> requestParams = req.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
return params;
}

private static String buildRequest(Map<String, String> map) {
List<String> signList = new ArrayList<>();
for(Entry<String, String> entry : map.entrySet()) {
signList.add(entry.getKey() + "=" + entry.getValue());
}
String sign = StringUtils.join(signList, "&");
return DigestUtils.md5Hex(sign);
}

}

在过滤器实现请求拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
typescript复制代码/**
* 过滤频繁请求
*/
@Slf4j
@Component
public class MyFilter implements Filter{

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse myResp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
Boolean isDict = StringUtils.contains(req.getRequestURI(), "/dict/getDatas");
Boolean isFile = StringUtils.contains(req.getRequestURI(), "/files/file");
if(isDict || isFile) {
chain.doFilter(request, myResp); // 查询数据字典或者文件,直接放行
return;
}
String sign = "sign_" + ContextLJ.getSign(request); // 生成摘要
String tiD = RandomUtils.randomCode(3) + "_" + Thread.currentThread().getId(); // 当前线程的身份
try {
if (!ContextLJ.lock(sign, tiD)) {
Map<String,String> map = ContextLJ.getRequstMap((HttpServletRequest)request);
log.warn("放弃相同并发请求【" + sign+ "】【" + tiD+"】"+JSON.toJSONString(map));
frequentlyError(myResp);
return;
}
if (!ContextLJ.checklock(sign, tiD)) {
Map<String,String> map = ContextLJ.getRequstMap((HttpServletRequest)request);
log.warn("加锁验证失败 【" + sign+ "】【" + tiD+"】"+JSON.toJSONString(map));
frequentlyError(myResp);
return;
}
chain.doFilter(request, myResp); // 放行
} catch (Exception e) { // 捕获到异常 进行异常过滤
log.error("", e);
myResp.getWriter().write(JSON.toJSONString(ApiRs.asError("服务器繁忙,请重试")));
} finally {
ContextLJ.clent(sign, tiD);
}
}

@Override
public void destroy() {

}

/**
* 频繁请求
*/
private void frequentlyError(ServletResponse myResp) throws IOException {
((HttpServletResponse) myResp).setHeader("Content-type", "text/html;charset=UTF-8");
myResp.getWriter().write(JSON.toJSONString(ApiRs.asError("稍安勿躁,不要频繁请求")));
}

}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java基础知识-10-异常 try cach finall

发表于 2021-11-09

用于解决程序在运行过程中产生的意外情况。语法错误或逻辑错误不算是异常。

一提到异常,大家难免会想到几个关键字。分别是:

try cach finally throw throws

他们都分别代表什么意思呢

try cach finally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码try{
可能会产生异常代码块
}catch(异常类型 ex){
对异常进行处理
//调试时一般会打印出调试信息
//ex.printStackTrace();
}catch(异常类型 ex){
对异常进行处理
//调试时一般会打印出调试信息
//ex.printStackTrace();
}finally{
//无论前面异常执行不执行,这里一定会被执行的代码
//除非前面强制终止,System.exit(1) //终止java虚拟机
//这里不要用return,否则会屏蔽前面的return
//一般在这里进行清理、释放资源等工作
}

throw throws

throws:

声明将要抛出何种类型的异常

1
2
3
4
java复制代码//下面的函数可能会抛出 异常类型1,异常类型2 这两种类型的异常.
public void 方法名() throws 异常类型1,异常类型2{
可能产生异常的代码
}

其本身不处理异常,只负责声明和抛出,使用它的人来处理异常

1
2
3
4
5
6
java复制代码调用存在异常声明的方法时
try {
方法名();//方法调用
}catch(异常类型 ex){
处理异常
}

throw:

主动/手动抛出一些业务逻辑上的异常,自己手动创建要抛出的异常

1
2
3
4
5
6
7
8
java复制代码public void 方法名(){
try {
if(业务异常)
throw new Exception("业务异常描述");
}catch(Exception ex){//捕获异常
//处理异常
}
}
1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public void 方法名() throws Exception{
if(业务异常){
throw new Exception("业务异常描述");
}
}

//调用
try {
方法名();
}catch(Exception ex){//捕获异常
//处理异常
}

⚠️:throw和throws有本质的区别。不要混为一谈。

throw是抛出异常阶段 的方案:

抛出异常有两种:一种是系统自动产生异常并抛出,一种是手动创建异常抛出(throw)

throws是捕获异常阶段 的方案:

捕获异常处理的方案有两种,一种是try/catch直接处理,一种是抛给别人处理(throws)

自定义异常

定义一个类,继承自异常类

1
2
3
4
5
6
java复制代码public class MyException extends Exception{

public MyException(){//构造方法
super("异常描述");//调用父类构造方法
}
}

之后就可以在业务代码中 throw自己定义的异常了。

异常选择

  1. 如果父类中的方法没有throws异常,那么,子类中重写该方法时,也不能throws异常。对自己编写的可能出现异常的代码,只能自己try/catch
  2. 一个方法A中调用了方法 a1,a2,a3。那么,一般情况下a1,a2,a3建议用throws抛出异常,A中对异常进行try/catch。

异常链

多重嵌套抛出异常时,将异常信息一个一个的串联起来抛出的机制。否则只能看到最后一个异常。

  • 方式1
    throw Exception("本异常描述信息",捕获的异常)
  • 方式2
1
2
3
php复制代码    Exception ex = new Exception("本异常描述信息");
ex.initCause(捕获的异常);
throw ex;

异常实际案例

接下来,举一个实际项目中的异常捕获方式。但并不是唯一方式。

1.自定义一个全局异常捕获类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
java复制代码/**
* 全局异常捕获
*/
@ControllerAdvice
public class GlobalExceptionInterceptor {

private Logger log = LoggerFactory.getLogger(getClass());


/**
* 处理ServiceException异常 //自定义异常
*/
@ExceptionHandler(value = ServiceException.class)
@ResponseBody
public RespResult serviceExceptionHandle(ServiceException se) {
log.error("自定义异常堆栈:"+ ThrowableUtil.getStackTrace(se));
//对自定义异常处理的业务
return respBody;
}


/**
* 在 @RequestBody 上 加validate 校验参数失败后抛出的异常是
* MethodArgumentNotValidException异常。
* 参数为空,或没传参数都会抛此异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public RespResult MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
//参数校验异常处理逻辑
}

/**
* 在@RequestParam的参数没传时 抛这个异常
* MissingServletRequestParameterException 。
* 注意:是参数没传会抛,不是参数为空会抛
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseBody
public RespResult MissingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
//参数校验异常处理逻辑
}

/**
* 处理请求json格式不成器的异常:HttpMessageNotReadableException
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseBody
public RespResult HttpMessageNotReadableExceptionHandler(HttpMessageNotReadableException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
//请求体校验异常处理逻辑
}


/**
* 唯一索引重复异常:MySQLIntegrityConstraintViolationException
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
@ResponseBody
public RespResult SQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
String message = e.getMessage();
//唯一索引异常处理逻辑
}

...
其他类型异常捕获及处理
}

2.自定义异常类

我们看到,在上面全局异常捕获类中,有一个自定义异常类。具体内容如下:

在业务处理中,遇到需要抛异常的地方,你就可以构造一个这样的异常,然后抛出去,就会被全局异常捕获并处理了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public interface ExceptionEnumInterface {

/**
* 错误码,为200表示正常
* @return
*/
String getCode();

/**
* 错误说明,code为200时返回null,否则返回错误说明<br/>
* 可包含占位符,占位符格式为中括号加数字表示,如{0}, {1},表示替换为第0个、第1个参数
* @return
*/
String getMsg();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
java复制代码public class ServiceException extends RuntimeException{
private ExceptionEnumInterface errorEnum;
private Object[] args;
private Exception e;

/**
* 构造一个包含特定异常码的业务异常,当异常描述中饮食可替换的占位符时,可用args中参数来替换占位符<br/>
* 占位符格式为中括号加数字表示,如{0}, {1},表示替换为第0个、第1个参数
* @param errorEnum
* @param args
* @return
*/
public static ServiceException of(ExceptionEnumInterface errorEnum, Object...args) {
return new ServiceException(errorEnum, args);
}

/**
* 构造一个包含特定异常码的业务异常,当异常描述中饮食可替换的占位符时,可用args中参数来替换占位符<br/>
* 占位符格式为中括号加数字表示,如{0}, {1},表示替换为第0个、第1个参数
* @param errorEnum
* @param args
* @return
*/
public static ServiceException of(ExceptionEnumInterface errorEnum, Exception e, Object...args) {
return new ServiceException(errorEnum,e,args);
}

/**
* 通过异常枚举接口来返回特定的业务异常
* @param errorEnum
* @param args
*/
private ServiceException(ExceptionEnumInterface errorEnum, Object...args) {
super(ExceptionEnumUtil.getErrorEnumMsg(errorEnum, args));
this.errorEnum = errorEnum;
this.args = args;
}

/**
* 通过异常枚举接口来返回特定的业务异常
* @param errorEnum
* @param args
*/
private ServiceException(ExceptionEnumInterface errorEnum, Exception e, Object...args) {
super(ExceptionEnumUtil.getErrorEnumMsg(errorEnum, args));
this.errorEnum = errorEnum;
this.args = args;
this.e = e;
}

public ExceptionEnumInterface getErrorEnum() {
return errorEnum;
}

public Object[] getArgs() {
return args;
}

public Exception getE() {
return e;
}
}
  1. 抛异常

1
2
3
4
5
java复制代码try {
//业务代码
} catch (Exception e) {
throw ServiceException.of(参数);
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

swagger导出离线API文档 swagger导出离线PD

发表于 2021-11-09

swagger导出离线PDF API文档

基于swagger2makeup插件和asciidoctor来生成离线PDF Restful API文档。

起因

项目中使用swagger2来生成开发API文档,但是涉及到跨团队或者项目交付的时候,需要提供离线API文档,在尝试使用swagger导出离线文档的时候,发现baidu的很多文章基本上大同小异。

而且很多都要在项目中生成对应的离线asciidoc,在开发一个独立的Test case来进行,同时引入其他依赖到项目中,感觉对代码有侵入,本身业务也不需要这些依赖,发现结合自己项目很多东西都要修改,后面发现可以通过swagger2makeup来在客户端通过命令行来实现,也是非常简单同时还不用侵入代码,特意记录起来分享。

离线文档生成

根据swagger2markup在当前目录下生成文件名为“swagger-doc.adoc”的asciidoc文件。本文中使用docker镜像来创建adoc文件,也可以参考官网来通过jar来生成。

swagger2markup jar生成文档

swagger2markup cli地址

1
sh复制代码docker run --rm -v $(pwd):/opt swagger2markup/swagger2markup convert -i "http://192.168.101.6:8016/v2/api-docs" -f /opt/swagger-doc

如果是已经生成的本地json文件也可以替换url链接为json文件,例如本地/tmp/swagger.json

1
sh复制代码docker run --rm -v $(pwd):/opt swagger2markup/swagger2markup convert -i /tmp/swagger.json -f /opt/swagger-doc

根据产生的adoc文件在当前目录下生成名为“swagger-doc.pdf”的文档。

1
sh复制代码docker run -it -v $(pwd):/documents/ asciidoctor/docker-asciidoctor asciidoctor-pdf swagger-doc.adoc

http://192.168.101.6:8016/v2/api-docs 地址内容这里只展示部分内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
json复制代码{

swagger: "2.0",

info:- {

description: "Api Documentation",

version: "1.0",

title: "Api Documentation",

termsOfService: "urn:tos",

contact:{},

license:- {

name: "Apache 2.0",

url: "<http://www.apache.org/licenses/LICENSE-2.0>"

}

},

host: "192.168.101.6:8016",

basePath: "/",

tags:+ [... ],

执行完成效果查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
sh复制代码qujianfei@troyMac  ~/docker/swagger_dir  ls
qujianfei@troyMac  ~/docker/swagger_dir  docker run --rm -v $(pwd):/opt swagger2markup/swagger2markup convert -i "http://192.168.101.6:8016/v2/api-docs" -f /opt/swagger-doc
06:09:13.641 [main] INFO io.swagger.parser.Swagger20Parser - reading from http://192.168.101.6:8016/v2/api-docs
06:09:15.965 [main] DEBUG i.g.s.i.document.PathsDocument - Generate examples is disabled.
06:09:15.965 [main] DEBUG i.g.s.i.document.PathsDocument - Create separated operation files is disabled.
06:09:15.971 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Create separated definition files is disabled.
06:09:16.836 [main] INFO i.g.s.m.b.i.asciidoc.AsciiDocBuilder - Markup document written to: /opt/swagger-doc.adoc
06:09:17.237 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'linksUsingGET' (normalized id = 'linksUsingGET')
06:09:17.288 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'handleUsingGET_2' (normalized id = 'handleUsingGET_2')
06:09:17.333 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'handleUsingGET_1' (normalized id = 'handleUsingGET_1')
06:09:17.455 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'handleUsingGET' (normalized id = 'handleUsingGET')
06:09:17.554 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'handleUsingGET_3' (normalized id = 'handleUsingGET_3')
06:09:17.680 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'addMonitorPluginUsingPUT_1' (normalized id = 'addMonitorPluginUsingPUT_1')
06:09:17.738 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'tokenValidateUsingPOST_1' (normalized id = 'tokenValidateUsingPOST_1')
06:09:17.774 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'errorHtmlUsingPOST' (normalized id = 'errorHtmlUsingPOST')
06:09:17.807 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'errorHtmlUsingGET' (normalized id = 'errorHtmlUsingGET')
06:09:17.841 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'errorHtmlUsingPUT' (normalized id = 'errorHtmlUsingPUT')
06:09:17.884 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'errorHtmlUsingDELETE' (normalized id = 'errorHtmlUsingDELETE')
06:09:17.907 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'errorHtmlUsingPATCH' (normalized id = 'errorHtmlUsingPATCH')
06:09:17.931 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'errorHtmlUsingHEAD' (normalized id = 'errorHtmlUsingHEAD')
06:09:17.963 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'errorHtmlUsingOPTIONS' (normalized id = 'errorHtmlUsingOPTIONS')
06:09:18.068 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'getAgentUsingGET' (normalized id = 'getAgentUsingGET')
06:09:18.121 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'uploadAgentUsingPOST' (normalized id = 'uploadAgentUsingPOST')
06:09:18.157 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'uploadProxyUsingPOST' (normalized id = 'uploadProxyUsingPOST')
06:09:18.200 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'getProxyUsingGET' (normalized id = 'getProxyUsingGET')
06:09:18.243 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'updateAgentStatusUsingPOST' (normalized id = 'updateAgentStatusUsingPOST')
06:09:18.291 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'createAgentsUsingPUT' (normalized id = 'createAgentsUsingPUT')
06:09:18.343 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'listAgentsUsingGET_2' (normalized id = 'listAgentsUsingGET_2')
06:09:18.374 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'createAgentUsingPUT' (normalized id = 'createAgentUsingPUT')
06:09:18.414 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'addMonitorPluginUsingPUT' (normalized id = 'addMonitorPluginUsingPUT')
06:09:18.443 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'getPluginListUsingGET' (normalized id = 'getPluginListUsingGET')
06:09:18.498 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'tokenValidateUsingPOST' (normalized id = 'tokenValidateUsingPOST')
06:09:18.535 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'heartBeatUsingPOST' (normalized id = 'heartBeatUsingPOST')
06:09:18.590 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'getInstallerUsingGET' (normalized id = 'getInstallerUsingGET')
06:09:18.624 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'listAgentsUsingGET' (normalized id = 'listAgentsUsingGET')
06:09:18.649 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'listAllPluginInstallerUsingGET' (normalized id = 'listAllPluginInstallerUsingGET')
06:09:18.671 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'updatePluginsUsingPATCH' (normalized id = 'updatePluginsUsingPATCH')
06:09:18.733 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'getAgentDetailByResIdUsingGET' (normalized id = 'getAgentDetailByResIdUsingGET')
06:09:18.780 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'deleteAgentByResourceIdsUsingDELETE' (normalized id = 'deleteAgentByResourceIdsUsingDELETE')
06:09:18.796 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'listAgentStatesUsingGET' (normalized id = 'listAgentStatesUsingGET')
06:09:18.845 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'getTopUpTimeUsingGET' (normalized id = 'getTopUpTimeUsingGET')
06:09:18.863 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'agentDetailUsingGET' (normalized id = 'agentDetailUsingGET')
06:09:18.900 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'deleteAgentUsingDELETE' (normalized id = 'deleteAgentUsingDELETE')
06:09:18.927 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'listAgentsUsingGET_1' (normalized id = 'listAgentsUsingGET_1')
06:09:18.958 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'cancelUsingGET' (normalized id = 'cancelUsingGET')
06:09:19.027 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'runUsingPOST' (normalized id = 'runUsingPOST')
06:09:19.088 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'countUsingGET' (normalized id = 'countUsingGET')
06:09:19.105 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'countsUsingGET' (normalized id = 'countsUsingGET')
06:09:19.127 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'listUsingGET' (normalized id = 'listUsingGET')
06:09:19.145 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'detailUsingGET' (normalized id = 'detailUsingGET')
06:09:19.174 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'deleteUsingDELETE' (normalized id = 'deleteUsingDELETE')
06:09:19.197 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'cancelUsingPOST' (normalized id = 'cancelUsingPOST')
06:09:19.234 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'createUsingPUT' (normalized id = 'createUsingPUT')
06:09:19.282 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'countUsingGET_1' (normalized id = 'countUsingGET_1')
06:09:19.319 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'detailUsingGET_1' (normalized id = 'detailUsingGET_1')
06:09:19.351 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'deleteUsingDELETE_1' (normalized id = 'deleteUsingDELETE_1')
06:09:19.406 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'updateUsingPATCH' (normalized id = 'updateUsingPATCH')
06:09:19.468 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'cancelUsingPOST_1' (normalized id = 'cancelUsingPOST_1')
06:09:19.529 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'runUsingPOST_1' (normalized id = 'runUsingPOST_1')
06:09:19.572 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'listUsingGET_1' (normalized id = 'listUsingGET_1')
06:09:19.633 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'getExecutionLogUsingGET' (normalized id = 'getExecutionLogUsingGET')
06:09:19.743 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'createUsingPUT_1' (normalized id = 'createUsingPUT_1')
06:09:19.800 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'changeFileToStringUsingGET' (normalized id = 'changeFileToStringUsingGET')
06:09:19.841 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'checkedListUsingGET' (normalized id = 'checkedListUsingGET')
06:09:19.956 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'runScriptUsingPOST' (normalized id = 'runScriptUsingPOST')
06:09:20.011 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'detailUsingGET_2' (normalized id = 'detailUsingGET_2')
06:09:20.074 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'deleteUsingDELETE_2' (normalized id = 'deleteUsingDELETE_2')
06:09:20.111 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'updateUsingPATCH_1' (normalized id = 'updateUsingPATCH_1')
06:09:20.126 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'contentUsingGET' (normalized id = 'contentUsingGET')
06:09:20.152 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'checkUsingPOST' (normalized id = 'checkUsingPOST')
06:09:20.163 [main] DEBUG i.g.s.i.document.PathsDocument - Operation processed : 'listAllScriptsUsingGET' (normalized id = 'listAllScriptsUsingGET')
06:09:20.218 [main] INFO i.g.s.m.b.i.asciidoc.AsciiDocBuilder - Markup document written to: /opt/swagger-doc.adoc
06:09:20.235 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'Agent'
06:09:20.255 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'AgentParam'
06:09:20.257 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'AgentPlugin'
06:09:20.266 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'AgentPluginBinding'
06:09:20.271 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ExecDetailView'
06:09:20.279 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ExecutionConditionCounts'
06:09:20.280 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ExecutionItem'
06:09:20.291 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ExecutionListView'
06:09:20.302 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'HeartBeat'
06:09:20.304 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'JobDetailView'
06:09:20.308 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'JobItem'
06:09:20.314 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'JobListView'
06:09:20.317 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'JobNode'
06:09:20.320 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'JobRequest'
06:09:20.335 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'Link'
06:09:20.336 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'LogEntry'
06:09:20.339 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'LogView'
06:09:20.341 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'Map«string,Link»'
06:09:20.343 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'Map«string,long»'
06:09:20.345 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'Map«string,string»'
06:09:20.350 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ModelAndView'
06:09:20.354 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'Node'
06:09:20.361 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'NodeStepState'
06:09:20.368 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'Option'
06:09:20.373 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'PagedResultBean«SmartAgent»'
06:09:20.375 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'PluginParam'
06:09:20.378 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean'
06:09:20.388 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«AgentPluginBinding»'
06:09:20.392 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«AgentPlugin»'
06:09:20.393 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«Agent»'
06:09:20.394 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«ExecDetailView»'
06:09:20.399 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«ExecutionItem»'
06:09:20.407 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«ExecutionListView»'
06:09:20.410 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«JobDetailView»'
06:09:20.411 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«JobListView»'
06:09:20.413 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«List«AgentPlugin»»'
06:09:20.415 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«List«ExecutionConditionCounts»»'
06:09:20.420 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«List«Map«string,long»»»'
06:09:20.424 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«List«Script»»'
06:09:20.428 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«List«SmartAgent»»'
06:09:20.433 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«List«string»»'
06:09:20.436 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«LogView»'
06:09:20.440 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«Map«string,string»»'
06:09:20.444 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«ScriptContent»'
06:09:20.446 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«ScriptDetailView»'
06:09:20.450 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«ScriptListView»'
06:09:20.452 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«SmartAgent»'
06:09:20.454 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«int»'
06:09:20.455 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«long»'
06:09:20.458 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ResultBean«string»'
06:09:20.459 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'RunCmdParam'
06:09:20.463 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'RunScriptParam'
06:09:20.475 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'Script'
06:09:20.484 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ScriptContent'
06:09:20.487 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ScriptDetailView'
06:09:20.492 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ScriptItem'
06:09:20.501 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'ScriptListView'
06:09:20.510 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'SmartAgent'
06:09:20.516 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'TokenValidateParam'
06:09:20.522 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'View'
06:09:20.529 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'Workflow'
06:09:20.531 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'WorkflowStep'
06:09:20.537 [main] DEBUG i.g.s.i.document.DefinitionsDocument - Definition processed : 'WorkflowWorkflowStep'
06:09:20.562 [main] INFO i.g.s.m.b.i.asciidoc.AsciiDocBuilder - Markup document written to: /opt/swagger-doc.adoc
06:09:20.573 [main] INFO i.g.s.m.b.i.asciidoc.AsciiDocBuilder - Markup document written to: /opt/swagger-doc.adoc
qujianfei@troyMac  ~/docker/swagger_dir  ls
swagger-doc.adoc
qujianfei@troyMac  ~/docker/swagger_dir  docker run -it -v $(pwd):/documents/ asciidoctor/docker-asciidoctor asciidoctor-pdf swagger-doc.adoc
qujianfei@troyMac  ~/docker/swagger_dir  ls
swagger-doc.adoc swagger-doc.pdf
qujianfei@troyMac  ~/docker/swagger_dir 

查看pdf文件

swagger.png

swagger配置

build.gradle

1
2
yaml复制代码    implementation 'io.springfox:springfox-swagger2:2.9.2'
compile group: 'com.github.xiaoymin', name: 'swagger-bootstrap-ui', version: '1.9.6'

spring配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
java复制代码package com.troy.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;


@Configuration
@EnableSwagger2
public class SwaggerConfig {

@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}

protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}

swagger配置这里只粘贴了项目中的部分代码,实际使用中不需要和保持一致,只要引入swagger2可以生成swagger json文件即可。

结尾

整体使用下来发现,基于swagger2makeup插件和asciidoctor生成离线文档的方式操作简单并且代码侵入性小,而且几乎没有学习成本,可以做到即插即用,可以完全满足一般需求。但是本次示例也有一些遗留问题,就是对于pdf的字体的配置,这个本次就不在做过多探究了,后面如果有时间在继续探索字体的设置,也欢迎哪位同学如果有方案也可以一起探讨。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

C语言03-函数(下)

发表于 2021-11-09

「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」

最近,想复习一下C语言,所以笔者将会在掘金每天更新一篇关于C语言的文章! 各位初学C语言的大一新生,以及想要复习C语言/C++知识的不要错过哦! 夯实基础,慢下来就是快!

4.写一个函数判断是不是闰年

1
c复制代码闰年:能被4整除&&不能被100整除 ||能被400整除

摘自百度百科
闰年是历法中的名词,分为普通闰年和世纪闰年。

闰年(Leap Year)是为了弥补因人为历法规定造成的年度天数与地球实际公转周期的时间差而设立的。补上时间差的年份为闰年。闰年共有366天(1月~12月分别为31天、29天、31天、30天、31天、30天、31天、31天、30天、31天、30天、31天)。

凡阳历中有闰日(2月29日)的年份,闰余(岁余置闰。阴历每年与回归年相比所差的时日)。

注意闰年(公历中的名词)和闰月(农历中的名词)并没有直接的关联,公历只分闰年和平年,平年有365天,闰年有366天(2月中多一天);平年中也可能有闰月(如2017年是平年,农历有闰月,闰六月)。


1
2
3
4
5
6
7
8
9
10
c复制代码//方法1
int is_leap_year(int year)
{
if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
return 1;
}
else
return 0;
}
1
2
3
4
5
c复制代码//巨秀的写法
int is_leap_year(int year)
{
return (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0));
}

5.实现一个整形有序数组的二分查找

二分查找法的前提:数组是有序的
每次可以缩减一遍的区间,是一个高效的算法!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
c复制代码//二分法
int BinarySearch(int* arr, int sz,int k)
{
int right = sz - 1; //右下标
int left = 0; //左下标
while (left <= right)
{
int mid = (right + left) >> 1; //中间值
//mid = (right + left)/2
if (arr[mid] > k)
{
right = mid - 1; //右下标 变成中间值-1位置
}
else if (arr[mid] < k)
{
left = mid + 1; //左下标 变成中间值+1位置
}
else
return mid;
}
return -1; //找不到
}
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
int k = 7; //要找的数字
int ret = BinarySearch(arr, sz,k);
if (ret != -1)
{
printf("找到了,下标为:%d\n", ret);
}
else
{
printf("找不到\n");
}
return 0;
}

TDD:

TDD:在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行


返回参数为void型:

若函数返回类型为void :则最后可以不写return 或者写成 return;


6.每调用一次这个函数,num的值就增加一次

写法1:因为函数要改变参数的值,所以要传地址过去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
c复制代码void Add(int* p)
{
(*p)++;
//也可写成
// *p++ 后置++,先使用p再++ 相当于(*p)++
return;
}
int main()
{
int num = 0;
printf("num的值为:%d\n", num);
Add(&num);
printf("num的值为:%d\n", num);
Add(&num);
printf("num的值为:%d\n", num);
return 0;
}

写法2:传值-接收返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
c复制代码int Add(int n )
{
return n+1;
}
int main()
{
int num = 0;
printf("num的值为:%d\n", num); //0
num = Add(num);
printf("num的值为:%d\n", num); //1
num = Add(num);
printf("num的值为:%d\n", num); //2
return 0;
}

3.函数的链式访问

链式访问:把一个函数的返回值作为另一个函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
c复制代码int main()
{
int len = strlen("abc");
printf("%d\n",len); //3
//链式访问
printf("%d\n",strlen("abc")); //3

char arr1[20] = "xxxxxx";
char arr2[20] = "abc";
printf("%s\n",strcpy(arr1,arr2)); //abc

printf("%d",printf("%d",printf("%d",43))); //4321
//printf返回的值为打印字符的个数
return 0;
}

1.分块写文件


分块去写的好处:1.多人协作 2.封装和隐藏


2.如何导入静态库

1
2
c复制代码导入静态库:#pragma comment(lib,”add.lib”)  
此时静态库的名字为add.lib

将add.c和add.h 在debug文件中可以找到add.lib文件


3.防止头文件重复包含

//方法1:
#pragma once

//方法2:
#ifdef .h文件的文件名
#define .h文件的文件名
最后结尾加上#endif

#ifdef Add.h
#define Add.h
#endif


今天就先到这吧~感谢你能看到这里!希望对你有所帮助!欢迎老铁们点个关注订阅这个专题! 同时欢迎大佬们批评指正!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…387388389…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%