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

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


  • 首页

  • 归档

  • 搜索

阿里巴巴首席技术官程立:我们相信并正在践行的“好科技” 一

发表于 2021-11-04

简介: 10月21日,阿里巴巴集团首席技术官程立出席2021年云栖大会技术*可持续发展论坛,并发表《科技创新和未来生活》的主题演讲,谈及科技创新的价值,以及阿里巴巴相信并正在践行的“好科技”,本文是程立的分享。

作者 | Alibaba Tech

来源 | 阿里技术公众号

10月21日,阿里巴巴集团首席技术官程立出席2021年云栖大会技术*可持续发展论坛,并发表《科技创新和未来生活》的主题演讲,谈及科技创新的价值,以及阿里巴巴相信并正在践行的“好科技”。以下是程立的分享:

关于阿里巴巴的技术创新方向,以前我们内部常提“技术创造新商业”,技术如何带来商业文明的革新,是我们的创新方向。随着阿里巴巴的科技、业务、平台发展到现在的规模后,今年我们提出两个新的词:第一个是“高科技”,我们要深耕自立自强的高科技;还有一个是“好科技”,到底什么是好科技?有三个词是能够代表“好科技”的要求:

第一个词是人人受益。科技应该从人出发,从人的需要出发,以人为本,为人服务。面对所有的问题,当我们要判断对错和方向的时候,需要回到以人为中心的根本点,这是非常重要的依据;

第二个词是责任担当。科技必须能够承担起对未来的责任,以及对整个全人类的责任,这个责任我觉得特别重要。去年疫情爆发之后,我一直在想,如果科技没有发展到这个程度,没有这么好的数字化科技、这么强的生活科技,面对疫情我们该怎么面对?我没有答案,即使科技发展到现在这个程度,面对疫情我们仍然非常有挑战。所以我们相信未来人类再往前走,一定还会有更加挑战性的问题等着我们,我们的科技是不是足够有担当,能够为这些问题做好准备,这是非常关键的事情;

第三个词是开放共享。为了解决这些问题,没有某个个人、某个单一企业能够完成,必须靠大家共同的努力,搭建一个真正开放的、科学的平台,开放的生产力的平台,开放的创新平台,让所有人能在这个事情上付出力量、做出贡献。所以我们认为人人受益、责任担当、开放共享,是好科技的三个关键词。

接下来,我会给大家分享一些目前阿里巴巴正在做的事情。

一 人人受益的“好科技”

第一个是围绕人,我们一直把客户第一作为内部第一的价值观,但讲到客户第一的时候,我们对客户的视野正在变得越来越宽。不仅关注当下服务的客户,而是必须关注到每个人,其中弱势群体是特别需要关注的。

比如和听障人群怎么沟通?听障人群最擅长的语言是手语,手语是靠眼睛看的,如何理解视觉语言对正常人来说都是很大的挑战。所以达摩院的同学就在想,我们能不能让机器不仅会说手语,而且能看懂手语,让机器成为健听人群和听障人群之间的一个沟通的桥梁。于是我们尝试解决一个问题,即如何能让机器生成手语,同时还能让机器读懂手语。

过程中,我们发现比想象中困难很多,因为手语的丰富化、个性是非常难处理的,一个很小的手势变化,背后代表的意思就完全不一样,它是把视觉、语言理解等等全部综合在一起要解决的问题。

但这个问题非常值得解决,它很有技术挑战,一旦解决之后机器就能成为听障人群和健听人群之间非常好的沟通桥梁。我们计划尽快让这个技术迭代、成熟,让更多听障人群可以使用。未来类似这样的难题,我希望不仅阿里巴巴能够解决,我们希望把这样的技术开放出来,让所有人共同解决类似这样的难题。

目前针对这个问题我们开始取得了一定的进展。屏幕上展示的是阿里巴巴的虚拟数字人,它在跟大家说:“大家好,我是阿里巴巴达摩院的小莫,很高兴看到大家。我擅长的是能够在手语和自然语言之间做相互的翻译,欢迎大家来到2021云栖大会。”

第二类人群是我们非常关心的老年人群。这里有两个数字,一个是中国60岁及以上老人有2.64亿,这是非常大且绝不可忽视的人群,另外一个数字是,在2.64亿老年人里面有1507万或多或少地出现了老年痴呆的症状,这个比例相当于每100个人里面,大概有五六个人患有老年痴呆症。如何解决他们的问题变得非常重要。

对于老年痴呆,及早的发现和干预是非常重要的,但是筛查和及早干预一直是个难题。在这过程中,AI有没有办法解决或者助力这样的难题?达摩院有一些团队开始在做这样的尝试,能不能把原来靠人工询问、判断的问题通过机器更好地做到。这个问题同样不简单,需要能够捕捉老年人各种各样的情况并做判断,这背后需要非常综合的技术能力。

达摩院和杭州市拱墅区卫生健康局、浙江大学公共卫生学院联合打造了国内首个老年痴呆症AI筛查产品刚刚发布,最新的测试结果让我们非常有信心,机器筛查判断的可靠性基本能达到人工专业水平,还能把时间缩短到三分之一。

国家卫建委提出,到2022年,公众对老年痴呆防治知识的知晓率要达80% ,2022年社区(村)老年人认知功能筛查率要达80%。我们希望技术上的进展能够助力解决老年痴呆症大规模早筛难题,让这个事情成为可行。

第三个是阿里巴巴一直在做的,如何让数字服务不产生数字鸿沟。我们关心如何能够让更多人平等使用包括网上购物、点外卖、打车等等服务。目前,阿里巴巴12款APP加入了无障碍行动,每个重要的产品淘宝、高德、饿了么都发布了长辈版,给长辈们一个更加好用、更加符合他们需要的专门版本,让每个人都平等享受技术带来的红利。

二 责任担当的“好科技”

这部分我们来探讨下如何用科技解决未来的问题,这里我提一下目前在做的几个方面:

第一件事情我们做了大概3年时间,达摩院AI Earth团队一致在研究如何用数字技术解决包括环境、气象、水利、农业等问题。我们早期的切入点比较直接,如何用机器快速解决海量遥感的图像和信息。比如做环保检测,对各个生态红线的监管,单靠人逐一来判断的效率是很低的,机器非常适合处理海量的数据,这是我们的切入点。在这个过程中我们发现这样的技术不仅可以用于生态检测方面,还可以用在水利、农业等非常广的方面。

到现在,我们发现机器还有一个非常大的机会点,重大的问题随着技术发展可以得到很好的解法,比如我们对气象非常感兴趣,气象是一个非常大的科学问题,涉及到非常大的海量计算,需要对整个气象系统做建模,对它进行演算,在这个过程中,AI技术的发展可以大大地加速原来传统的计算方式,让复杂的求解过程变得更加高效,大幅度提升原来预报的精度和效率。

所以我们看到,由于技术发展打开了空间,不仅可以让原来处理问题的效率和质量得到提升,而且可以让原来几乎不可解决的问题有了新的解法。在这个领域,我们希望随着技术发展,能够真正把环境和地球数字化,用数字化的技术,为面临的一些重大问题提供全新解决方案,这是我们接下来会重点投入的方向。

接下来是绿色计算,双碳是需要所有人共同努力的目标,如何解决碳的问题?对整个社会来说,碳排放是由几个大的子系统构成,能源子系统和生产制造子系统的碳排放占比较大,其次是种植养殖、交通、其他行业。如何解决大的子系统的碳排放问题就变得非常关键。对计算来说,原来大家的认知是数字不会产生碳,但是数字背后是计算,计算也需要电,如何让计算变得更加绿色?这是非常有价值的一个切入点。

阿里巴巴之前提出了零碳云,云是整个未来数字基础的基建,如果云能够变得绿色、低碳,甚至未来云是零碳,基于云之上的数字商业就可以是零碳的,这是一个非常重要的解决点。如何能够让云变得更加低碳?我们所有的商业系统的计算是不是足够绿色?过去我们提出了效率目标,我们能够用多少计算资源解决多大业务量的问题,现在我们更加直接地提出低碳的目标,对单位的交易笔数,对交易的GMV碳排放是多少提出了目标。今年我们的目标是对单位的GMV碳排放下降15%,我们在内部专门发起了一个项目来做这件事情。

同时,除了电力的消耗,生产制造、工业农业也是碳排放很重要的来源,数字技术如何能够帮助这些行业做到减排减碳,这也是我们在做的事情。总体来看为了达到双碳的目标,技术是非常关键的手段,可以用技术解决这样一些非常有价值的问题。

第三个问题也非常关键,数字成员们与人类的关系越来越密切,比如我们平时花了大量的时间与手机打交道,手机在影响你,你也在影响手机,未来它能不能真正成为人类的好伙伴,具备真正的可信变地非常关键。未来,如何让我们的数字伙伴们变得更加可靠、可信、可用,是我们要解决的问题。阿里巴巴今年专门成立了人工智能治理和可持续发展实验室(AAIG),就是为了解决这些问题。

三 开放共享的“好科技”

任何一个问题的解决,都不能只靠单一一个人和企业,必须要开放。科技的开放有这么几个环节:

首先在基础领域,比如气象、环境、生物等这些领域,它必须是一个开放的体系,每个人的研究都是大的科学的部分,大家是共同开放的,达摩院也确立了这样的方向,包括下一代的AI技术,在这些领域,我们的科研成果都希望跟全社会的大科学研究的基础研究体系保持一起的开放;

第二层是所有技术软件的开放。大家来云栖大会能够听到这两个词,一个是云是未来整个数字生产力的基础,第二个是云背后的支撑是开源,开源已经成为整个数字世界真正的根,在这个过程中阿里巴巴如何把我们的技术软件和全世界的技术软件融在一起,共同构成一个全社会的数字世界的根,成为一个非常重要的部分;

第三层是我们要有一个开放的数字生产力平台,让所有人的创新变得更加简单。接下来我们也希望能用更加开放开源的方式,去解决所有跟科技相关的问题。比如无障碍的问题,我们希望阿里巴巴所有无障碍的技术,包括它的专利全部都是开放的,所有人都可以免费使用和参与。

我想特别提一下开源,阿里巴巴的开源在国内一直是比较领先的,无论从开源的数量还是活跃度一直都是最高的。但早期的话,我们的开源很多时候是同学们自发的行动,大家在工作中做到很好的技术,对工作有用,就把它开源出去了。今天思考开源的时候,我们开始把技术团队自发的行动变成更加有系统、有组织的行动,我们更加系统地去看阿里巴巴应该开源什么,尤其在重要的构成未来数字世界根的领域,我们做重点开源的部署:包括操作系统、核心数据库、大数据计算、云原生四个领域,我们所有的云原生软件必须是开源的,云原生开源有一个非常基本的理念“三位一体”,我们自己用的技术,阿里云对客户提供的技术,和社区开源的技术,我们希望这些技术软件是一个版本,之间没有代差,在此基础上我们再针对业务做差异化的提升和改进,用“三位一体”的方式希望技术软件里能够真正跟开源形成一个血脉相通的整体。

还有一点,我们希望人才培养和科技普惠能够助力解决乡村振兴的问题。这背后人才特别关键,有了人才之后我们授人以渔的才是真正三点水的“渔”。这里面,有两类人是阿里技术团队必须要帮助的。

第一个是乡村,乡村振兴方面我们做了很多事情,包括去年我们的设计师亲自下乡,帮乡村做旅游资源品牌的提升,效果特别好,但是光靠我们的力量不行,唯一的办法就是让乡村有自己核心的人才,所以我们提出乡村振兴技术官计划,让每个乡村都有自己的技术官,他能够带领乡村科技的振兴,培养乡村的科技人才。

第二个,随着整个社会的数字化,未来一定会有大量的职业被数字替代掉,但过程中我们如何创造更多新的职业,让更多人得到发展是非常关键的事情,所以我们提出技术普惠人才的计划,希望未来5年培养20万数字化人才,通过他们能解决社会未来的问题,为人们创造更好的生活。每个计划都不容易,我们还是希望努力地踏踏实实去做。

这些就是我的分享,代表我们目前的思考和行动,一切都是刚刚开始,要做到更好的科技离不开所有人的关心和合作。我也希望所有的朋友们,大家一起共同让科技变得更好,让人们的生活变得更好。

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

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

优化的扫雷 扫雷

发表于 2021-11-04

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

扫雷

还是那个熟悉的味道,优化的扫雷,无限接近真正的扫雷。

test.c

还是从main入手,本人不太喜欢直接上代码,感觉那样的话博友会看不懂,我喜欢分开来讲解。废话不多说,直接上代码(哈哈开个玩笑)。

main

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
int复制代码{
int input = 0;
int num = 3;//不可连续选错的次数
int flag = 1;//这个我不想说了,三子棋里面有,就是次数标记
srand((unsigned)time(NULL));//随机数起点,与rand()搭配使用
/*别的不说,这游戏不管你玩不玩,总之你得玩一把,先和你说一声30个雷*/
do
{
Meun();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
if (flag == 0)
{
flag = 1; //标记取反
num = 3; //次数充满
}
game(); //玩游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
if (flag == 1)
{
flag = 0; //标记警告
}
num--;
if (num == 0) //到这里你就没机会了
{
printf("你已经没有机会了。\n");
break;
}
printf("选错了,你还有%d次机会\n", num);
break;
}

} while (input&&num); //输入0或次数没了就退出游戏
return 0;
}

Meun菜单函数

1
2
3
4
5
void复制代码{
printf("****************************\n");
printf("****1.play 0.exit*****\n");
printf("****************************\n");
}

game游戏函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void复制代码{
char mine[ROWS][COLS];//地雷数组
char show[ROWS][COLS];//排查雷数组
InitBoard(mine, ROWS, COLS,'0');//初始化放雷棋盘
InitBoard(show,ROWS,COLS,'*'); //初始化排查雷棋盘

//DisplayBoard(mine, ROW, COL);//打印放雷棋盘
DisplayBoard(show, ROW, COL);//打印排雷棋盘

SetMine(mine, ROW, COL); //布置雷
//DisplayBoard(mine, ROW, COL);//打印放雷棋盘
FindBoard(mine,show, ROW, COL); //排查雷


}

game.c文件

InitBoard初始化棋盘函数

棋盘初始化,这里到底初始化是么样的字符呢?写死’0’,show数组不行。写死’*‘,mine数组也不行,那怎么办呢?所以我这里全都不要,我用一个参数set来承接,你要初始化什么样,你传给我的参数,然后再操作。

1
2
3
4
5
6
7
8
9
10
11
void复制代码{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;//set参数,你来啥我接啥
}
}
}

DisplayBoard打印棋盘参数

打印棋盘,这里打印就是我们所见的棋盘,那个为了防止数组溢出的外面一圈就不需要了。虽然我们这里虽然操作的是99的数组,但是我们传数组的时候还是1111的数组,所以用board这个11*11的数组来接收

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
void复制代码{
int i = 0;
printf("---------------------------------------\n");
/*这个循环是在打印列之前把序号打印出来*/
for (i = 0; i <= col; i++)
{
printf("%2d ", i);//为了看的方便,格式对齐所以用%2d
}
printf("\n");
for (i = 0; i <= col; i++)
{
if (0 == i)
printf(" ");
else
printf("---");
}
printf("\n");
for (i = 1; i <= row; i++)
{
int j = 0;
printf("%2d|",i);//打印行之前把序号打印出来
for (j = 1; j <= col; j++)
{
printf(" %c ",board[i][j]);
}
printf("|");//行结束后打印个|分割线
printf("\n");
}
for (i = 0; i <= col; i++)//列结束后打印分割线---
{
if (0 == i)
printf(" ");//0下面不需要分割线
else
printf("---");
}
printf("\n");
printf("---------------------------------------\n");
}

SetMine放置雷函数

布置雷,把雷放到mine数组里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void复制代码{
int count = EASY_COUNT;
while (count)
{
//布置雷生成随机下标(1-9)
int x = rand() % row + 1;
int y = rand() % col + 1;
//如果board[x][y]不是雷才能把雷放进去,不然会少雷
if (board[x][y] != '*')
{
board[x][y] = '*';
count--;
}
}
}

GetMineCount得到雷数函数

得到雷个数函数,因为我们放雷数组初始化是’0’,雷是’ ‘,但我们从排查雷及其周围是定死3*3的数组,所以我们用循环把9个字符加起来再减9个’0’就能得到总共’\‘-‘0’的整数了,再除以它就可以得到count

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int复制代码{
int i = 0;
int sum = 0;
int count = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
sum = sum + (board[i][j] - '0');
}
}
return count = sum / ('*' - '0');//我们要灵活运用字符型和整型之间的切换
}

PutOneMine放单雷函数

放一个雷,防止第一步及之后连续踩雷就被炸死,这个代码就是保护那些脸黑的,怕他们没有游戏体验

1
2
3
4
5
6
7
8
9
10
11
12
void复制代码{
while (1)
{
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (board[x][y] != '*')
{
board[x][y] = '*';//把雷放进去就跳出
break;
}
}
}

ShowSpread显示展开函数

显示展开函数,也就是递归展开排雷,这个函数大大增加了游戏的可玩性,我欲八面来风层层递归

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
void复制代码{
int count = 0;//计算雷个数
count = GetMineCount(mine, x, y);
if (count == 0)
{
/*这里非常重要不然会死递归,很像对冲击波那样*/
show[x][y] = ' ';
if ((x - 1) > 0 && (y - 1) > 0 && show[x - 1][y - 1] == '*')
ShowSpread(mine, show, x - 1, y - 1);
if((y-1)>0&&show[x][y-1] == '*')
ShowSpread(mine, show, x , y - 1);
if ((x + 1) <= ROW && (y - 1) > 0 && show[x - 1][y - 1] == '*')
ShowSpread(mine, show, x + 1, y - 1);
if ((x - 1) > 0&& show[x - 1][y] == '*')
ShowSpread(mine, show, x - 1, y);
if ((x + 1) <= ROW&& show[x + 1][y] == '*')
ShowSpread(mine, show, x + 1, y);
if ((x - 1) > 0 && (y + 1) <= COL && show[x - 1][y + 1] == '*')
ShowSpread(mine, show, x - 1, y + 1);
if ((y + 1) <= COL && show[x][y + 1] == '*')
ShowSpread(mine, show, x, y + 1);
if ((x + 1) <= ROW && (y + 1) <= COL && show[x + 1][y + 1] == '*')
ShowSpread(mine, show, x + 1, y + 1);
}
else
{
show[x][y] = count + '0';
}

}

FindBoard排查雷函数

排查雷,我们是通过排查mine数组,然后把信息传到show数组里面,我们还是操作99数组,但mine show一直都是1111的数组,为了防止查雷是溢出

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
void复制代码{
int x = 0;
int y = 0;
int num = 3;
int flag = 1;
int mine_flag = 1;
int win = 0;
while (num&&(win<row*col-EASY_COUNT))
{
printf("请输入你要排查的目标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (flag == 0)
{
flag = 1;
num = 3;
}
if (mine[x][y] == '*')
{
if (mine_flag == 1)
{
mine[x][y] = '0';
PutOneMine(mine);
//DisplayBoard(mine, ROW, COL);
/*这步是更新周围雷数的*/
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';//把得到的数给show数组,但count是整型,所以转成字符型(字符型-'0'=整型)
DisplayBoard(show, ROW, COL);

}
else
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}

}
else
{/*如果不是雷,我们就搜索该位置周围8个位置的雷的情况*/
win++;
mine_flag = 0;//到了这里雷标记就没有了

if (mine[x][y] == '0' && show[x][y] == '*')
{
ShowSpread(mine, show, x, y);//显示展开
DisplayBoard(show, ROW, COL);
}
//int count = GetMineCount(mine,x,y);
//show[x][y] = count + '0';//把得到的数给show数组,但count是整型,所以转成字符型(字符型-'0'=整型)
//DisplayBoard(show, ROW, COL);
}
}
else
{
if (flag == 1)
{
flag = 0;
}
num--;
if (num == 0)
{
printf("你已经没有机会了");
break;
}
printf("输入坐标错误,你还有%d的输入机会", num);
}
}
if (win == row * col - EASY_COUNT)
printf("恭喜你排雷成功。\n");
}

game.h

宏

上面的讲解是简易棋盘9乘9的,这里我就直接来个中等难度的15乘15的

1
2
3
4
5
6
#define复制代码#define COL 15

#define ROWS ROW+2
#define COLS COL+2

#define EASY_COUNT 30 //简单版本的地雷数

函数申明

1
2
3
4
5
6
7
8
9
10
11
12
void复制代码 
void DisplayBoard(char board[ROWS][COLS], int row, int col);

void SetMine(char board[ROWS][COLS], int row, int col);

void FindBoard(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col);

int GetMineCount(char board[ROWS][COLS], int x, int y);

void PutOneMine(char board[ROWS][COLS]);

void ShowSpread(char mine[ROWS][COLS],char show[ROWS][COLS], int x, int y);

测试图

次数限制

炸死

第一步开始连续踩雷,泉水无敌效应坐标2 3是有雷的

但为了防止刚开始就死,看两幅图2 3雷被移走了,之后只要不是连续踩雷就不会出现这个情况

赢了

9 2是唯一一个没有雷的

本文转载自: 掘金

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

【JS逆向系列】某电商网站token 前言 一、页面分析 二

发表于 2021-11-04

前言

网址:haohuo.jinritemai.com/views/produ…


一、页面分析

1.按F12,进入调试,进入链接

在这里插入图片描述

二、参数破解

1.参数加密位置查找

1.1 老规矩直接搜索token,第二个比较像,点进去,可能加载会有点慢,耐心等待。

在这里插入图片描述

1.2 点进去后再搜寻token,找到10处,不知道是哪个,在下面的两个打上断点

在这里插入图片描述

1.3 刷新网页

1.3.1 参数o是商品的id,然后我们进入这个方法

在这里插入图片描述

1.3.2 可以看到调用了两次a()方法,第一次是对t加密,然后是对a()(t)+”zd2019@@1157”加密

在这里插入图片描述

1.3.3 然后我们看下a()方法,可以看出是一个MD5的加密

在这里插入图片描述

2.参数破解测试

2.1 扣JS代码,删除无关变量

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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
javascript复制代码function Md5(t) {
if (t)
blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0,
this.blocks = blocks,
this.buffer8 = buffer8;
else if (ARRAY_BUFFER) {
var e = new ArrayBuffer(68);
this.buffer8 = new Uint8Array(e),
this.blocks = new Uint32Array(e)
} else
this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0,
this.finalized = this.hashed = !1,
this.first = !0
}

var ERROR = "input is invalid type"
, WINDOW = "object" == typeof window
, root = WINDOW ? window : {};
root.JS_MD5_NO_WINDOW && (WINDOW = !1);
var WEB_WORKER = !WINDOW && "object" == typeof self
, NODE_JS = !root.JS_MD5_NO_NODE_JS && "object" == typeof process && process.versions && process.versions.node;
NODE_JS ? root = global : WEB_WORKER && (root = self);
var COMMON_JS = !root.JS_MD5_NO_COMMON_JS && "object" == typeof module && module.exports,
ARRAY_BUFFER = !root.JS_MD5_NO_ARRAY_BUFFER && "undefined" != typeof ArrayBuffer,
HEX_CHARS = "0123456789abcdef".split(""), EXTRA = [128, 32768, 8388608, -2147483648], SHIFT = [0, 8, 16, 24],
OUTPUT_TYPES = ["hex", "array", "digest", "buffer", "arrayBuffer", "base64"],
BASE64_ENCODE_CHAR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""), blocks = [],
buffer8;
if (ARRAY_BUFFER) {
var buffer = new ArrayBuffer(68);
buffer8 = new Uint8Array(buffer),
blocks = new Uint32Array(buffer)
}
!root.JS_MD5_NO_NODE_JS && Array.isArray || (Array.isArray = function (t) {
return "[object Array]" === Object.prototype.toString.call(t)
}
),
!ARRAY_BUFFER || !root.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW && ArrayBuffer.isView || (ArrayBuffer.isView = function (t) {
return "object" == typeof t && t.buffer && t.buffer.constructor === ArrayBuffer
}
);
var createOutputMethod = function (t) {
return function (e) {
return new Md5(!0).update(e)[t]()
}
}
, createMethod = function () {
var t = createOutputMethod("hex");
NODE_JS && (t = nodeWrap(t)),
t.create = function () {
return new Md5
}
,
t.update = function (e) {
return t.create().update(e)
}
;
for (var e = 0; e < OUTPUT_TYPES.length; ++e) {
var n = OUTPUT_TYPES[e];
t[n] = createOutputMethod(n)
}
return t
}
, nodeWrap = function (method) {
var crypto = eval("require('crypto')")
, Buffer = eval("require('buffer').Buffer")
, nodeMethod = function (t) {
if ("string" == typeof t)
return crypto.createHash("md5").update(t, "utf8").digest("hex");
if (null === t || void 0 === t)
throw ERROR;
return t.constructor === ArrayBuffer && (t = new Uint8Array(t)),
Array.isArray(t) || ArrayBuffer.isView(t) || t.constructor === Buffer ? crypto.createHash("md5").update(new Buffer(t)).digest("hex") : method(t)
};
return nodeMethod
};
Md5.prototype.update = function (t) {
if (!this.finalized) {
var e, n = typeof t;
if ("string" !== n) {
if ("object" !== n)
throw ERROR;
if (null === t)
throw ERROR;
if (ARRAY_BUFFER && t.constructor === ArrayBuffer)
t = new Uint8Array(t);
else if (!(Array.isArray(t) || ARRAY_BUFFER && ArrayBuffer.isView(t)))
throw ERROR;
e = !0
}
for (var i, r, a = 0, o = t.length, s = this.blocks, c = this.buffer8; a < o;) {
if (this.hashed && (this.hashed = !1,
s[0] = s[16],
s[16] = s[1] = s[2] = s[3] = s[4] = s[5] = s[6] = s[7] = s[8] = s[9] = s[10] = s[11] = s[12] = s[13] = s[14] = s[15] = 0),
e)
if (ARRAY_BUFFER)
for (r = this.start; a < o && r < 64; ++a)
c[r++] = t[a];
else
for (r = this.start; a < o && r < 64; ++a)
s[r >> 2] |= t[a] << SHIFT[3 & r++];
else if (ARRAY_BUFFER)
for (r = this.start; a < o && r < 64; ++a)
i = t.charCodeAt(a),
i < 128 ? c[r++] = i : i < 2048 ? (c[r++] = 192 | i >> 6,
c[r++] = 128 | 63 & i) : i < 55296 || i >= 57344 ? (c[r++] = 224 | i >> 12,
c[r++] = 128 | i >> 6 & 63,
c[r++] = 128 | 63 & i) : (i = 65536 + ((1023 & i) << 10 | 1023 & t.charCodeAt(++a)),
c[r++] = 240 | i >> 18,
c[r++] = 128 | i >> 12 & 63,
c[r++] = 128 | i >> 6 & 63,
c[r++] = 128 | 63 & i);
else
for (r = this.start; a < o && r < 64; ++a)
i = t.charCodeAt(a),
i < 128 ? s[r >> 2] |= i << SHIFT[3 & r++] : i < 2048 ? (s[r >> 2] |= (192 | i >> 6) << SHIFT[3 & r++],
s[r >> 2] |= (128 | 63 & i) << SHIFT[3 & r++]) : i < 55296 || i >= 57344 ? (s[r >> 2] |= (224 | i >> 12) << SHIFT[3 & r++],
s[r >> 2] |= (128 | i >> 6 & 63) << SHIFT[3 & r++],
s[r >> 2] |= (128 | 63 & i) << SHIFT[3 & r++]) : (i = 65536 + ((1023 & i) << 10 | 1023 & t.charCodeAt(++a)),
s[r >> 2] |= (240 | i >> 18) << SHIFT[3 & r++],
s[r >> 2] |= (128 | i >> 12 & 63) << SHIFT[3 & r++],
s[r >> 2] |= (128 | i >> 6 & 63) << SHIFT[3 & r++],
s[r >> 2] |= (128 | 63 & i) << SHIFT[3 & r++]);
this.lastByteIndex = r,
this.bytes += r - this.start,
r >= 64 ? (this.start = r - 64,
this.hash(),
this.hashed = !0) : this.start = r
}
return this.bytes > 4294967295 && (this.hBytes += this.bytes / 4294967296 << 0,
this.bytes = this.bytes % 4294967296),
this
}
}
,
Md5.prototype.finalize = function () {
if (!this.finalized) {
this.finalized = !0;
var t = this.blocks
, e = this.lastByteIndex;
t[e >> 2] |= EXTRA[3 & e],
e >= 56 && (this.hashed || this.hash(),
t[0] = t[16],
t[16] = t[1] = t[2] = t[3] = t[4] = t[5] = t[6] = t[7] = t[8] = t[9] = t[10] = t[11] = t[12] = t[13] = t[14] = t[15] = 0),
t[14] = this.bytes << 3,
t[15] = this.hBytes << 3 | this.bytes >>> 29,
this.hash()
}
}
,
Md5.prototype.hash = function () {
var t, e, n, i, r, a, o = this.blocks;
this.first ? (t = o[0] - 680876937,
t = (t << 7 | t >>> 25) - 271733879 << 0,
i = (-1732584194 ^ 2004318071 & t) + o[1] - 117830708,
i = (i << 12 | i >>> 20) + t << 0,
n = (-271733879 ^ i & (-271733879 ^ t)) + o[2] - 1126478375,
n = (n << 17 | n >>> 15) + i << 0,
e = (t ^ n & (i ^ t)) + o[3] - 1316259209,
e = (e << 22 | e >>> 10) + n << 0) : (t = this.h0,
e = this.h1,
n = this.h2,
i = this.h3,
t += (i ^ e & (n ^ i)) + o[0] - 680876936,
t = (t << 7 | t >>> 25) + e << 0,
i += (n ^ t & (e ^ n)) + o[1] - 389564586,
i = (i << 12 | i >>> 20) + t << 0,
n += (e ^ i & (t ^ e)) + o[2] + 606105819,
n = (n << 17 | n >>> 15) + i << 0,
e += (t ^ n & (i ^ t)) + o[3] - 1044525330,
e = (e << 22 | e >>> 10) + n << 0),
t += (i ^ e & (n ^ i)) + o[4] - 176418897,
t = (t << 7 | t >>> 25) + e << 0,
i += (n ^ t & (e ^ n)) + o[5] + 1200080426,
i = (i << 12 | i >>> 20) + t << 0,
n += (e ^ i & (t ^ e)) + o[6] - 1473231341,
n = (n << 17 | n >>> 15) + i << 0,
e += (t ^ n & (i ^ t)) + o[7] - 45705983,
e = (e << 22 | e >>> 10) + n << 0,
t += (i ^ e & (n ^ i)) + o[8] + 1770035416,
t = (t << 7 | t >>> 25) + e << 0,
i += (n ^ t & (e ^ n)) + o[9] - 1958414417,
i = (i << 12 | i >>> 20) + t << 0,
n += (e ^ i & (t ^ e)) + o[10] - 42063,
n = (n << 17 | n >>> 15) + i << 0,
e += (t ^ n & (i ^ t)) + o[11] - 1990404162,
e = (e << 22 | e >>> 10) + n << 0,
t += (i ^ e & (n ^ i)) + o[12] + 1804603682,
t = (t << 7 | t >>> 25) + e << 0,
i += (n ^ t & (e ^ n)) + o[13] - 40341101,
i = (i << 12 | i >>> 20) + t << 0,
n += (e ^ i & (t ^ e)) + o[14] - 1502002290,
n = (n << 17 | n >>> 15) + i << 0,
e += (t ^ n & (i ^ t)) + o[15] + 1236535329,
e = (e << 22 | e >>> 10) + n << 0,
t += (n ^ i & (e ^ n)) + o[1] - 165796510,
t = (t << 5 | t >>> 27) + e << 0,
i += (e ^ n & (t ^ e)) + o[6] - 1069501632,
i = (i << 9 | i >>> 23) + t << 0,
n += (t ^ e & (i ^ t)) + o[11] + 643717713,
n = (n << 14 | n >>> 18) + i << 0,
e += (i ^ t & (n ^ i)) + o[0] - 373897302,
e = (e << 20 | e >>> 12) + n << 0,
t += (n ^ i & (e ^ n)) + o[5] - 701558691,
t = (t << 5 | t >>> 27) + e << 0,
i += (e ^ n & (t ^ e)) + o[10] + 38016083,
i = (i << 9 | i >>> 23) + t << 0,
n += (t ^ e & (i ^ t)) + o[15] - 660478335,
n = (n << 14 | n >>> 18) + i << 0,
e += (i ^ t & (n ^ i)) + o[4] - 405537848,
e = (e << 20 | e >>> 12) + n << 0,
t += (n ^ i & (e ^ n)) + o[9] + 568446438,
t = (t << 5 | t >>> 27) + e << 0,
i += (e ^ n & (t ^ e)) + o[14] - 1019803690,
i = (i << 9 | i >>> 23) + t << 0,
n += (t ^ e & (i ^ t)) + o[3] - 187363961,
n = (n << 14 | n >>> 18) + i << 0,
e += (i ^ t & (n ^ i)) + o[8] + 1163531501,
e = (e << 20 | e >>> 12) + n << 0,
t += (n ^ i & (e ^ n)) + o[13] - 1444681467,
t = (t << 5 | t >>> 27) + e << 0,
i += (e ^ n & (t ^ e)) + o[2] - 51403784,
i = (i << 9 | i >>> 23) + t << 0,
n += (t ^ e & (i ^ t)) + o[7] + 1735328473,
n = (n << 14 | n >>> 18) + i << 0,
e += (i ^ t & (n ^ i)) + o[12] - 1926607734,
e = (e << 20 | e >>> 12) + n << 0,
r = e ^ n,
t += (r ^ i) + o[5] - 378558,
t = (t << 4 | t >>> 28) + e << 0,
i += (r ^ t) + o[8] - 2022574463,
i = (i << 11 | i >>> 21) + t << 0,
a = i ^ t,
n += (a ^ e) + o[11] + 1839030562,
n = (n << 16 | n >>> 16) + i << 0,
e += (a ^ n) + o[14] - 35309556,
e = (e << 23 | e >>> 9) + n << 0,
r = e ^ n,
t += (r ^ i) + o[1] - 1530992060,
t = (t << 4 | t >>> 28) + e << 0,
i += (r ^ t) + o[4] + 1272893353,
i = (i << 11 | i >>> 21) + t << 0,
a = i ^ t,
n += (a ^ e) + o[7] - 155497632,
n = (n << 16 | n >>> 16) + i << 0,
e += (a ^ n) + o[10] - 1094730640,
e = (e << 23 | e >>> 9) + n << 0,
r = e ^ n,
t += (r ^ i) + o[13] + 681279174,
t = (t << 4 | t >>> 28) + e << 0,
i += (r ^ t) + o[0] - 358537222,
i = (i << 11 | i >>> 21) + t << 0,
a = i ^ t,
n += (a ^ e) + o[3] - 722521979,
n = (n << 16 | n >>> 16) + i << 0,
e += (a ^ n) + o[6] + 76029189,
e = (e << 23 | e >>> 9) + n << 0,
r = e ^ n,
t += (r ^ i) + o[9] - 640364487,
t = (t << 4 | t >>> 28) + e << 0,
i += (r ^ t) + o[12] - 421815835,
i = (i << 11 | i >>> 21) + t << 0,
a = i ^ t,
n += (a ^ e) + o[15] + 530742520,
n = (n << 16 | n >>> 16) + i << 0,
e += (a ^ n) + o[2] - 995338651,
e = (e << 23 | e >>> 9) + n << 0,
t += (n ^ (e | ~i)) + o[0] - 198630844,
t = (t << 6 | t >>> 26) + e << 0,
i += (e ^ (t | ~n)) + o[7] + 1126891415,
i = (i << 10 | i >>> 22) + t << 0,
n += (t ^ (i | ~e)) + o[14] - 1416354905,
n = (n << 15 | n >>> 17) + i << 0,
e += (i ^ (n | ~t)) + o[5] - 57434055,
e = (e << 21 | e >>> 11) + n << 0,
t += (n ^ (e | ~i)) + o[12] + 1700485571,
t = (t << 6 | t >>> 26) + e << 0,
i += (e ^ (t | ~n)) + o[3] - 1894986606,
i = (i << 10 | i >>> 22) + t << 0,
n += (t ^ (i | ~e)) + o[10] - 1051523,
n = (n << 15 | n >>> 17) + i << 0,
e += (i ^ (n | ~t)) + o[1] - 2054922799,
e = (e << 21 | e >>> 11) + n << 0,
t += (n ^ (e | ~i)) + o[8] + 1873313359,
t = (t << 6 | t >>> 26) + e << 0,
i += (e ^ (t | ~n)) + o[15] - 30611744,
i = (i << 10 | i >>> 22) + t << 0,
n += (t ^ (i | ~e)) + o[6] - 1560198380,
n = (n << 15 | n >>> 17) + i << 0,
e += (i ^ (n | ~t)) + o[13] + 1309151649,
e = (e << 21 | e >>> 11) + n << 0,
t += (n ^ (e | ~i)) + o[4] - 145523070,
t = (t << 6 | t >>> 26) + e << 0,
i += (e ^ (t | ~n)) + o[11] - 1120210379,
i = (i << 10 | i >>> 22) + t << 0,
n += (t ^ (i | ~e)) + o[2] + 718787259,
n = (n << 15 | n >>> 17) + i << 0,
e += (i ^ (n | ~t)) + o[9] - 343485551,
e = (e << 21 | e >>> 11) + n << 0,
this.first ? (this.h0 = t + 1732584193 << 0,
this.h1 = e - 271733879 << 0,
this.h2 = n - 1732584194 << 0,
this.h3 = i + 271733878 << 0,
this.first = !1) : (this.h0 = this.h0 + t << 0,
this.h1 = this.h1 + e << 0,
this.h2 = this.h2 + n << 0,
this.h3 = this.h3 + i << 0)
}
,
Md5.prototype.hex = function () {
this.finalize();
var t = this.h0
, e = this.h1
, n = this.h2
, i = this.h3;
return HEX_CHARS[t >> 4 & 15] + HEX_CHARS[15 & t] + HEX_CHARS[t >> 12 & 15] + HEX_CHARS[t >> 8 & 15] + HEX_CHARS[t >> 20 & 15] + HEX_CHARS[t >> 16 & 15] + HEX_CHARS[t >> 28 & 15] + HEX_CHARS[t >> 24 & 15] + HEX_CHARS[e >> 4 & 15] + HEX_CHARS[15 & e] + HEX_CHARS[e >> 12 & 15] + HEX_CHARS[e >> 8 & 15] + HEX_CHARS[e >> 20 & 15] + HEX_CHARS[e >> 16 & 15] + HEX_CHARS[e >> 28 & 15] + HEX_CHARS[e >> 24 & 15] + HEX_CHARS[n >> 4 & 15] + HEX_CHARS[15 & n] + HEX_CHARS[n >> 12 & 15] + HEX_CHARS[n >> 8 & 15] + HEX_CHARS[n >> 20 & 15] + HEX_CHARS[n >> 16 & 15] + HEX_CHARS[n >> 28 & 15] + HEX_CHARS[n >> 24 & 15] + HEX_CHARS[i >> 4 & 15] + HEX_CHARS[15 & i] + HEX_CHARS[i >> 12 & 15] + HEX_CHARS[i >> 8 & 15] + HEX_CHARS[i >> 20 & 15] + HEX_CHARS[i >> 16 & 15] + HEX_CHARS[i >> 28 & 15] + HEX_CHARS[i >> 24 & 15]
}
,
Md5.prototype.toString = Md5.prototype.hex,
Md5.prototype.digest = function () {
this.finalize();
var t = this.h0
, e = this.h1
, n = this.h2
, i = this.h3;
return [255 & t, t >> 8 & 255, t >> 16 & 255, t >> 24 & 255, 255 & e, e >> 8 & 255, e >> 16 & 255, e >> 24 & 255, 255 & n, n >> 8 & 255, n >> 16 & 255, n >> 24 & 255, 255 & i, i >> 8 & 255, i >> 16 & 255, i >> 24 & 255]
}
,
Md5.prototype.array = Md5.prototype.digest,
Md5.prototype.arrayBuffer = function () {
this.finalize();
var t = new ArrayBuffer(16)
, e = new Uint32Array(t);
return e[0] = this.h0,
e[1] = this.h1,
e[2] = this.h2,
e[3] = this.h3,
t
}
,
Md5.prototype.buffer = Md5.prototype.arrayBuffer,
Md5.prototype.base64 = function () {
for (var t, e, n, i = "", r = this.array(), a = 0; a < 15;)
t = r[a++],
e = r[a++],
n = r[a++],
i += BASE64_ENCODE_CHAR[t >>> 2] + BASE64_ENCODE_CHAR[63 & (t << 4 | e >>> 4)] + BASE64_ENCODE_CHAR[63 & (e << 2 | n >>> 6)] + BASE64_ENCODE_CHAR[63 & n];
return t = r[a],
i += BASE64_ENCODE_CHAR[t >>> 2] + BASE64_ENCODE_CHAR[t << 4 & 63] + "=="
}
;
var exports = createMethod();


var e = "3380284906675503740";
var t = 'hex';
var parma1 = new Md5(!0).update(e)[t](); //第一次加密
var param2 = new Md5(!0).update(parma1+'zd2019@@1157')[t]();//第二次加密
console.log(param2);

三、运行测试

可以看出我们自己解密的参数与网页上生成的参数是一致的,那么此次破解就是成功地,觉得有帮助的小伙伴,欢迎点赞关注哦~

在这里插入图片描述
在这里插入图片描述

本文转载自: 掘金

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

SpringBoot如何一分钟集成Caffeine? 引言

发表于 2021-11-04

引言

前面我们有学习Caffeine 本地缓存性能之王Caffeine,并且也提到SpringBoot默认使用的本地缓存也是Caffeine啦,今天我们来看看Caffeine如何与SpringBoot集成的。

集成caffeine

caffeine与SpringBoot集成有两种方式:

  • 一种是我们直接引入 Caffeine 依赖,然后使用 Caffeine 方法实现缓存。相当于使用原生api
  • 引入 Caffeine 和 Spring Cache 依赖,使用 SpringCache 注解方法实现缓存。SpringCache帮我们封装了Caffeine
    pom文件引入
1
2
3
4
5
6
7
8
9
java复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.6.0</version>
</dependency>

第一种方式

首先配置一个Cache,通过构造者模式构建一个Cache对象,然后后续关于缓存的增删查都是基于这个cache对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码@Configuration
public class CacheConfig {
@Bean
public Cache<String, Object> caffeineCache() {
return Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterWrite(60, TimeUnit.SECONDS)
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(1000)
.build();
}

第一种方式我们就一一不介绍了,基本上就是使用caffeineCache来根据你自己的业务来操作以下方法
在这里插入图片描述
这种方式使用的话是对代码有侵入性的。

第二种方式

  • 需要在SpingBoot启动类标上EnableCaching注解,这个玩意跟很多框架都一样,比如我们肴集成dubbo也需要标上@EnableDubbole注解等。
1
2
3
4
5
6
java复制代码    @SpringBootApplication
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
  • 在application.yml配置我们的使用的缓存类型、过期时间、缓存策略等。
1
2
3
4
5
6
7
java复制代码spring:
profiles:
active: dev
cache:
type: CAFFEINE
caffeine:
spec: maximumSize=500,expireAfterAccess=600s

如果我们不习惯使用这种方式的配置,当然我们也可以使用JavaConfig的配置方式来代替配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterAccess(600, TimeUnit.SECONDS)
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(500));
return cacheManager;
}

接下来就是代码中如何来使用这个缓存了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码
@Override
@CachePut(value = "user", key = "#userDTO.id")
public UserDTO save(UserDTO userDTO) {
userRepository.save(userDTO);
return userDTO;
}

@Override
@CacheEvict(value = "user", key = "#id")//2
public void remove(Long id) {
logger.info("删除了id、key为" + id + "的数据缓存");
}

@Override
@Cacheable(value = "user",key = "#id")
public UserDTO getUserById(Long id) {
return userRepository.findOne(id);
}

上述代码中我们可以看到有几个注解@CachePut、@CacheEvict、@Cacheable
我们只需要在方法上标上这几个注解,我们就能够使用缓存了,我们分别来介绍下这几个注解。

@Cacheable

@Cacheable它是既可以标注在类上也可以标注在方法上,当它标记在类上的时候它表述这个类上面的所有方法都会支持缓存,同样的
当它作用在法上面时候它表示这个方法是支持缓存的。比如上面我们代码中的getUserById这个方法第一次缓存里面没有数据,我们会去查询DB,但是第二次来查询的时候就不会走DB查询了,而是直接从缓存里面拿到结果就返回了。

value 属性
  • @Cacheable的value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。
key
  • @Cacheable的key 有两种方式一种是我们自己显示的去指定我们的key,还有一种默认的生成策略,默认的生成策略是SimpleKeyGenerator这个类,这个生成key的方式也比较简单我们可以看下它的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public static Object generateKey(Object... params) {
// 如果方法没有参数 key就是一个 new SimpleKey()
if (params.length == 0) {
return SimpleKey.EMPTY;
}
// 如果方法只有一个参数 key就是当前参数
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
// 如果key是多个参数,key就是new SimpleKey ,不过这个SimpleKey对象的hashCode 和Equals方法是根据方法传入的参数重写的。
return new SimpleKey(params);
}

上述代码还是非常好理解的分为三种情况:

  • 方法没有参数,那就new使用一个全局空的SimpleKey对象来作为key。
  • 方法就一个参数,就使用当前参数来作为key
  • 方法参数大于1个,就new一个SimpleKey对象来作为key,new 这个SimpleKey的时候用传入的参数重写了SimpleKey的hashCode和equals方法,
    至于为啥需要重写的原因话,就跟Map用自定义对象来作为key的时候必须要重写hashCode和equals方法原理是一样的,因为caffein也是借助了ConcurrentHashMap来实现,

小结

上述代码我们可以发现默认生成key只跟我们传入的参数有关系,如果我们有一个类里面如果存在多个没有参数的方法,然后我们使用了默认的缓存生成策略的话,就会造成缓存丢失。
或者缓存相互覆盖,或者还有可能会发生ClassCastException 因为都是使用同一个key。比如下面这代码就会发生异常(ClassCastException)

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码  @Cacheable(value = "user")
public UserDTO getUser() {
UserDTO userDTO = new UserDTO();
userDTO.setUserName("Java金融");
return userDTO;
}
@Cacheable(value = "user")
public UserDTO2 getUser1() {
UserDTO2 userDTO2 = new UserDTO2();
userDTO2.setUserName2("javajr.cn");
return userDTO2;
}

所以一般不怎么推荐使用默认的缓存生成key的策略。如果非要用的话我们最好自己重写一下,带上方法名字等。类似于如下代码:

1
2
3
4
5
6
7
8
9
java复制代码@Component
public class MyKeyGenerator extends SimpleKeyGenerator {

@Override
public Object generate(Object target, Method method, Object... params) {
Object generate = super.generate(target, method, params);
String format = MessageFormat.format("{0}{1}{2}", method.toGenericString(), generate);
return format;
}
自定义key

我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。
使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”这也是我们比较推荐的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码   @Cacheable(value="user", key="#id")
public UserDTO getUserById(Long id) {
UserDTO userDTO = new UserDTO();
userDTO.setUserName("java金融");
return userDTO;
}
@Cacheable(value="user", key="#p0")
public UserDTO getUserById1(Long id) {
return null;
}
@Cacheable(value="user", key="#userDTO.id")
public UserDTO getUserById2(UserDTO userDTO) {
return null;
}
@Cacheable(value="user", key="#p0.id")
public UserDTO getUserById3(UserDTO userDTO) {
return null;
}

@CachePut

@CachePut指定的属性是和@Cacheable一样的,但是它们两个是有区别的,@CachePut标注的方法不会先去查询缓存是否有值,而是每次都会先去执行该方法,然后把结果返回,并且结果也会缓存起来。
在这里插入图片描述

为什么是这样的一个流程我们可以去看看它的源码关键代码就是这一行,

1
java复制代码		Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

当我们使用方法上有@Cacheable注解的时候再contexts里面会把CacheableOperation加入进去,只有contexts.get(CacheableOperation.class)取到的内容不为空的话,才会去从缓存里面取内容,否则的话cacheHit会直接返回null。至于contexts什么时候加入CacheableOperation的话我们看下SpringCacheAnnotationParser#parseCacheAnnotations这个方法就会明白的。具体的源码就不展示了,感兴趣的可以自己去翻。

@CacheEvict

把缓存中数据删除,用法跟前面两个注解差不多有value和key属性,需要注意一点的是它多了一个属性beforeInvocation

  • beforeInvocation 这个属性需要注意下它的默认值是false,false代表的意思是再执调用方法之前不删除缓存,只有方法执行成功之后才会去删除缓存。设置为true的话调用方法之前会去删除一下缓存,方法执行成功之后还会去调用删除缓存这样就是双删了。如果方法执行异常的话就不会去删除缓存。
  • allEntrie 是否清空所有缓存内容,默认值为 false,如果指定为 true,则方法调用后将立即清空所有缓存

@Caching

这是一个组合注解集成了上面三个注解,有三个属性:cacheable、put和evict,分别用于来指定@Cacheable、@CachePut和@CacheEvict。

小结

第二种方式是侵入式的,它的实现原理也比较简单就是通过切面的方法拦截器来实现,拦截所有的方法,它的核心代码如下:看起来就跟我们的业务代码差不了多少,感兴趣的也可以去瞅一瞅。

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
java复制代码if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
}
catch (Cache.ValueRetrievalException ex) {
// The invoker wraps any Throwable in a ThrowableWrapper instance so we
// can just make sure that one bubbles up the stack.
throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}


// Process any early evictions
// beforeInvocation 属性是否为true,如果是true就删除缓存
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);

// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}

Object cacheValue;
Object returnValue;

if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}

// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}

// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

return returnValue;
}

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。
    站在巨人的肩膀上摘苹果:
    www.cnblogs.com/fashflying/…

本文转载自: 掘金

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

6道String练习题,你会做吗?

发表于 2021-11-04

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

前言

String字符串在我们开发中非常高频出现的一种数据结构,我这里准备了几道小题,不管你是初学者还是专家,都可以试试是否可以很容易的解决下面几道题,如果你有更好的解决办法,欢迎在评论去交流!

  1. 如何不使用Java内建方法反转一个字符串?
  2. 写一个java程序检查两个字符串是异位词?【异位词是指相同字符不同位置】
  3. 判断一个字符串中的所有字符是否只出现一次?
  4. 如何从字符串中找到重复的字符?
  5. 找到字符串的所有子字符串?
  6. 找出一个输入字符串的所有排列可能?

再开始看下面的解法前不妨自己先想一想这几道题都应该怎么解决,然后看看下面的方法是不是和你想的一致,如果你的方法更好,欢迎在评论区留言。

如何不使用Java内建方法反转一个字符串?

解决这个问题有三种方式:

  • 使用循环
  • 使用递归
  • 使用StringBuilder、StringBuffer

使用for循环

思路:

  1. 声明一个空String变量,用来保存最终反转的字符串;
  2. 使用for循环遍历要反转的字符串,从最后一个位置到第0个位置;
  3. 在遍历时将字符添加到声明的空String变量上。
1
2
3
4
5
6
7
8
9
10
java复制代码public class ReverseStringForMain {
public static void main(String[] args) {
String blogName = "小黑说Java";
String reverse = "";
for (int i = blogName.length() - 1; i >= 0; i--) {
reverse = reverse + blogName.charAt(i);
}
System.out.println("Reverse of 小黑说Java is: " + reverse);
}
}

使用递归

思路:

  1. 如果字符串长度为1,则反转结果为该字符串本身;
  2. 如果字符串长度大于1,则反转结果为该字符串的最后一个字符加剩余字符串的反转结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public class ReverseStringRecursive {
public static void main(String[] args) {
ReverseStringRecursive rsr = new ReverseStringRecursive();
String blogName = "小黑说Java";
String reverse = rsr.recursiveReverse(blogName);
System.out.println("Reverse of 小黑说Java is:" + reverse);
}

public String recursiveReverse(String orig) {
if (orig.length() == 1){
return orig;
}else{
return orig.charAt(orig.length() - 1) +
recursiveReverse(orig.substring(0, orig.length() - 1));
}
}
}

使用StringBuffer

使用StringBuffer的reverse方法,同样可以实现字符串的反转功能。

1
2
3
4
5
6
7
java复制代码public class StringBufferReverse {
public static void main(String[] args) {
String blogName = "小黑说Java";
StringBuffer sb = new StringBuffer(blogName);
System.out.println("Reverse of 小黑说Java is:" + sb.reverse());
}
}

写一个java程序检查两个字符串是异位词?

说明:异位词是指两个单词有相同的字母,但是顺序不同,如: Angel 和Angle。

这道题有多种解决方式:

  • 使用String类的方法
  • 使用Arrays.sort()
  • 使用计数数组
  • 使用Guava的Multiset

使用String类方法

思路:

  1. 建立一个方法,接收两个字符串变量,用来判断是否是异位词;
  2. 迭代第一个String单词,并使用charAt()方法从中获得char c;
  3. 如果第二个String单词的indexOf(c)的值等于-1,那么两个字符串不是字谜;
  4. 如果第二个String单词的indexOf(c)的值不等于-1,那么从第二个单词中删除字符c;
  5. 如果第二个String单词最后是个空字符串,则代表两个单词是异位词。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码public class StringAnagramMain {

public static void main(String[] args) {
String word = "小黑说Java";
String anagram = "小黑说JAVA";
System.out.println("小黑说Java and 小黑说JAVA are anagrams :" + isAnagramUsingStringMethods(word, anagram));
}

public static boolean isAnagramUsingStringMethods(String word, String anagram) {
if (word.length() != anagram.length())
return false;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
int index = anagram.indexOf(c);
if (index != -1) {
anagram = anagram.substring(0, index)
+ anagram.substring(index + 1, anagram.length());
} else{
return false;
}
}
return anagram.isEmpty();
}
}

使用Arrays.sort()

思路:直接对两个字符串排序,如果排序之后两个字符串相等,则表示两个字符串是异位词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码public class AnagramUsingSort {

public static void main(String[] args) {
String word = "小黑说Java";
String anagram = "小黑说JAVA";
System.out.println("小黑说Java and 小黑说JAVA are anagrams :" + isAnagramUsingArraySort(word, anagram));
}

public static boolean isAnagramUsingArraySort(String word, String anagram) {
String sortedWord = sortChars(word);
String sortedAnagram = sortChars(anagram);
return sortedWord.equals(sortedAnagram);
}

public static String sortChars(String word) {
char[] wordArr = word.toLowerCase().toCharArray();
Arrays.sort(wordArr);
return String.valueOf(wordArr);
}
}

使用计数数组

思路:

  1. 如果两个字符串长度不同,则不是异位词;
  2. 创建一个长度为256的数组;
  3. 迭代第一个字符串str1;
  4. 在每次迭代中,我们递增第一个String str1的计数,递减第二个String str2的计数;
  5. 如果任何字符的count在末尾不为0,则表示两个string不是字谜。

这种方法的时间复杂度是O(n),但它需要额外的空间计数数组。

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
java复制代码public class AnagramCountingMain {

public static void main(String args[])
{
boolean isAnagram = isAnagram("Angle","Angel");
System.out.println("Are Angle and Angel anangrams: "+isAnagram);
}

public static boolean isAnagram(String str1, String str2) {
if (str1.length() != str2.length()) {
return false;
}
int count[] = new int[256];
for (int i = 0; i < str1.length(); i++) {
count[str1.charAt(i)]++;
count[str2.charAt(i)]--;
}
for (int i = 0; i < 256; i++) {
if (count[i] != 0) {
return false;
}
}
return true;
}
}

使用Guava的Multiset

如果您喜欢使用Guava库,那可以使用MultiSet来检查两个String是否为异位词。

MultiSet允许每个元素出现多次,并记录每个元素的计数。

当然这种方式需要在pom.xml添加Guava依赖。

1
2
3
4
5
xml复制代码<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;

public class AnagramMultiSet {

public static void main(String args[])
{
boolean isAnagram = isAnagramMultiset("Angle","Angel");
System.out.println("Are Angle and Angel anangrams: "+isAnagram);
}

public static boolean isAnagramMultiset(String str1, String str2) {
if (str1.length() != str2.length()) {
return false;
}
Multiset<Character> ms1 = HashMultiset.create();
Multiset<Character> ms2 = HashMultiset.create();
for (int i = 0; i < str1.length(); i++) {
ms1.add(str1.charAt(i));
ms2.add(str2.charAt(i));
}
return ms1.equals(ms2);
}
}

判断一个字符串中的所有字符是否只出现一次?

说明:比如Apple单词中字符p出现多次,所以不符合;world中所有单词都只出现一次,所以符合。

这道题有以下几种解决方法:

  • 使用HashSet
  • 使用indexOf和lastIndexOf方法
  • 使用字符的ascii值

使用HashSet

依赖于HashSet的add()方法,如果已存在的元素会返回false。

步骤:

  1. 遍历每个字符,添加到HashSet;
  2. 如果HashSet的add方法返回false,那么它不是所有的唯一字符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码public class StringAllUniqueCharMain {

public static void main(String[] args) {
System.out.println("Apple has all unique chars :"+ hasAllUniqueChars("apple"));
System.out.println("index has all unique chars :"+ hasAllUniqueChars("index"));
System.out.println("world has all unique chars :"+ hasAllUniqueChars("world"));
}

public static boolean hasAllUniqueChars (String word) {
HashSet alphaSet=new HashSet();
for(int index=0;index < word.length(); index ++) {
char c =word.charAt(index);
if(!alphaSet.add(c))
return false;
}
return true;
}
}

使用indexOf和lastIndexOf方法

思路:如果indexOf和lastIndexOf对字符返回相同的值,则在该字符串中不会重复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public class StringAllUniqueCharMain {

public static void main(String[] args) {
System.out.println("Apple has all unique chars : "+ hasAllUniqueChars("apple"));
System.out.println("index has all unique chars : "+ hasAllUniqueChars("index"));
System.out.println("world has all unique chars : "+ hasAllUniqueChars("world"));
}

public static boolean hasAllUniqueChars (String word) {
for(int index=0;index < word.length(); index ++) {
char c =word.charAt(index);
if(word.indexOf(c)!=word.lastIndexOf(c))
return false;
}
return true;
}
}

使用字符的ascii值

这个方法是最高效的。

思路:

  1. 创建一个长度为26的布尔数组;
  2. 将char转换为大写并获取其ascii值;
  3. 将64减去ascii值以获得0到25之间的索引;
  4. 如果字符不重复,则布尔数组中应该有false;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public class StringAllUniqueCharMain {

public static void main(String[] args) {
System.out.println("Apple has all unique chars : "+ hasAllUniqueChars("apple"));
System.out.println("index has all unique chars : "+ hasAllUniqueChars("index"));
System.out.println("world has all unique chars : "+ hasAllUniqueChars("world"));
}

public static boolean hasAllUniqueChars (String word) {
boolean[] charMap = new boolean[26];
for(int index=0;index < word.length(); index ++) {
// we are substracting char's ascii value to 64, so we get all index
// from 0 to 25.
int asciiCode = (int) word.toUpperCase().charAt(index) - 64;
if(!charMap[asciiCode])
charMap[asciiCode] = true;
else
return false;
}
return true;
}

}

如何从字符串中找到重复的字符?

思路:

  1. 创建一个HashMap,创建一个HashMap,字符串字符将作为key插入,其计数作为value插入;
  2. 如果HashMap已经包含char,则将其计数增加1,否则将char放入HashMap中;
  3. 如果Char的值大于1,这意味着它是该字符串中的重复字符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码import java.util.HashMap;

public class StringFindDuplicatesMain {
public static void main(String[] args) {
String str = "juejin.com ";
HashMap<Character, Integer> charCountMap = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (charCountMap.containsKey(c)) {
charCountMap.put(c, charCountMap.get(c) + 1);
} else {
charCountMap.put(c, 1);
}
}
for (Character c : charCountMap.keySet()) {
if (charCountMap.get(c) > 1)
System.out.println("duplicate character : " + c + " ====== " + " count : " + charCountMap.get(c));
}
}
}

找到字符串的所有子字符串?

例如:如果输入是abb,那么输出应该是a, b, b, ab, bb, abb

思路:使用String类的subString方法来查找所有subString。

1
2
3
4
5
6
7
8
9
10
11
java复制代码class SubstringsOfStringMain{
public static void main(String args[]){
String str="abbc";
System.out.println("All substring of abbc are:");
for (int i = 0; i < str.length(); i++) {
for (int j = i+1; j <= str.length(); j++) {
System.out.println(str.substring(i,j));
}
}
}
}

这个解决办法的时间复杂度为O(n^3)。因为我们有两个循环而且String的子字符串方法的时间复杂度是O(n)

如果想找到String的所有不同的子字符串,那么需要使用HashSet来删除重复的字符串。

找出一个输入字符串的所有排列可能?

比如:输入“AB”,输出[“AB”,”BA”],输入“ABC”,输出[“ABC”,”ACB”,”BAC”,”BCA”,”CBA”,”CAB”]。

思路:

  1. 取出String的第一个字符,递归输入剩余String的排列的不同位置;
  2. 假设输入String为ABC,取出第一个字符”A”;
  3. 从BC中取出B,将剩余字符串”C”递归输入;
  4. String长度为1时,返回,所以返回“C”;
  5. 将取出的“B”分别插入到递归返回结果的字符串的每一个位置;
  6. 这时得到BC,CB,返回;
  7. 将取出的字符“A”分别插入返回结果的字符串的每一个位置;
  8. 这时得到[ABC,BAC,BCA]和[ACB,CAB,CBA];
  9. 最终返回结果就是ABC的所有排序。
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
java复制代码
import java.util.HashSet;
import java.util.Set;

public class PermutationOfStringJava {

public static void main(String[] args) {
Set<String> set = permutationOfString("ABC");
System.out.println("Permutations of String ABC are:");
for (String str : set) {
System.out.println(str);
}
}

public static Set<String> permutationOfString(String str) {
Set<String> permutationSet = new HashSet<>();
if (str.length() == 1) {
permutationSet.add(str);
return permutationSet;
}
char c = str.charAt(0);
String rem = str.substring(1);
Set<String> permutatedSetForRemainingString = permutationOfString(rem);
for (String permutedString : permutatedSetForRemainingString) {
for (int j = 0; j <= permutedString.length(); j++) {
String permutation = insertFirstCharAtDiffPlaces(permutedString, c, j);
permutationSet.add(permutation);
}
}
return permutationSet;

}
public static String insertFirstCharAtDiffPlaces(String perm, char firstChar, int index) {
return perm.substring(0, index) + firstChar + perm.substring(index);
}
}

小结

以上就是本期的所有内容,怎么样,这些方法你都想到了吗?如果你有更好的方法欢迎留言交流。

如果对你有帮助,点个赞是对我最大的鼓励。

本文转载自: 掘金

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

SpringBoot整合reids之JSON序列化文件夹操作

发表于 2021-11-04

前言

最近在开发项目,用到了redis作为缓存,来提高系统访问速度和缓解系统压力,提高用户响应和访问速度,这里遇到几个问题做一下总结和整理

快速配置

SpringBoot整合redis有专门的场景启动器整合起来还是非常方便的

1
2
3
4
xml复制代码   <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

如果使用redis连接池引入

1
2
3
4
5
xml复制代码        <!-- redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

集成配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
yml复制代码#------------------redis缓存配置------------
# Redis数据库索引(默认为 0)
spring.redis.database=1
# Redis服务器地址
spring.redis.host= 127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis 密码
spring.redis.password
# 连接超时时间(毫秒)
spring.redis.timeout= 5000
# redis连接池
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=10
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle= 500
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=2000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=10000

JSON序列化

由于缓存数据默认使用的是jdk自带的序列化 二进制
需要序列化的实体类继承Serializable接口。而且序列化后的内容在redis中看起来也不是很方便。

1
java复制代码\xAC\xED\x00\x05sr\x00Lorg.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken/\xDFGc\x9D\xD0\xC9\xB7\x02\x00\x01L\x00\x0Aexpirationt\x00\x10Ljava/util/Date;xr\x00Dorg.springframework.security.oauth2.common.DefaultOAuth2RefreshTokens\xE1\x0E\x0AcT\xD4^\x02\x00\x01L\x00\x05valuet\x00\x12Ljava/lang/String;xpt\x00$805a75f7-2ee2-4a27-a598-591bfa1cf17dsr\x00\x0Ejava.util.Datehj\x81\x01KYt\x19\x03\x00\x00xpw\x08\x00\x00\x01}y\x81\xDB\x9Ax

于是萌生了需要将数据序列化成json的想法。

jackson序列化

在使用spring-data-redis,默认情况下是使用org.springframework.data.redis.serializer.JdkSerializationRedisSerializer这个类来做序列化,Jackson redis序列化是spring中自带的.我们使用jackson方式

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
java复制代码@Bean
@ConditionalOnClass(RedisOperations.class)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);

Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//序列化包括类型描述 否则反向序列化实体会报错,一律都为JsonObject
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用 String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的 key也采用 String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用 jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的 value序列化方式采用 jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();

return template;
}

序列化后存储在redis后内容

1
2
3
4
5
6
7
8
json复制代码[
"com.qhong.test.dependBean.Person",
{
"age": 20,
"name": "name0",
"iss": true
}
]
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
json复制代码[
"java.util.ArrayList",
[
[
"com.qhong.test.dependBean.Person",
{
"age": 20,
"name": "name0",
"iss": true
}
],
[
"com.qhong.test.dependBean.Person",
{
"age": 21,
"name": "name1",
"iss": true
}
],
[
"com.qhong.test.dependBean.Person",
{
"age": 22,
"name": "name2",
"iss": true
}
]
]
]

上面的不是严格符合json格式规范,虽然比默认二进制好

注意这里序列化json代类型 "com.qhong.test.dependBean.Person" 如果没有这个反序列化会报类型转换异常错误

也就是代码中这一段必须设置,我之前就是没有设置,反序列化都是JsonObject必须自己转换类型,否则会报错

1
2
3
4
5
java复制代码//序列化包括类型描述 否则反向序列化实体会报错,一律都为JsonObject
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);

Fastjson序列化

  1. 需要倒入Fastjson到依赖
1
2
3
4
5
6
xml复制代码<!-- JSON工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
  1. 实现RedisSerializer接口
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
java复制代码import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {

public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}

private final Class<T> clazz;

public FastJson2JsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}

/**
* 序列化
*/
@Override
public byte[] serialize(T t) throws SerializationException {
if (null == t) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}

/**
* 反序列化
*/
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (null == bytes || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return (T) JSON.parseObject(str, clazz);
}
}
  1. 配置redisTemplate
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
java复制代码import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheAutoConfiguration {

@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
FastJson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class);

StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);

// value序列化方式采用fastJson
template.setValueSerializer(fastJsonRedisSerializer);
// hash的value序列化方式采用fastJson
template.setHashValueSerializer(fastJsonRedisSerializer);

template.afterPropertiesSet();
return template;
}
}

注意这是一种方式自己实现RedisSerializer 序列化接口
但是FastJson 1.2.36版本以后不需要自己实现RedisSerializer

为我们提供序列化支持在com.alibaba.fastjson.support.spring中 有GenericFastJsonRedisSerializer和FastJsonRedisSerializer 两个实现类 ,

区别在于GenericFastJsonRedisSerializer 可以自动转换对象类型,FastJsonRedisSerializer 需要自定义转换需要的类型。

通常使用 GenericFastJsonRedisSerializer 即可满足大部分场景,如果你想定义特定类型专用的 RedisTemplate 可以使用 FastJsonRedisSerializer 来代替 GenericFastJsonRedisSerializer”

FastJson github有对应问题描述lssues 我已入坑 ,刚开始一直使用FastJsonRedisSerializer****无法自动反向序列化

序列化后存储在redis后内容

1
2
3
4
5
6
json复制代码{
"@type": "com.qhong.test.dependBean.Person",
"age": 20,
"iss": true,
"name": "name0"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
json复制代码[
{
"@type": "com.qhong.test.dependBean.Person",
"age": 20,
"iss": true,
"name": "name0"
},
{
"@type": "com.qhong.test.dependBean.Person",
"age": 21,
"iss": true,
"name": "name1"
},
{
"@type": "com.qhong.test.dependBean.Person",
"age": 22,
"iss": true,
"name": "name2"
}
]

正常情况是格式是正确的,但是如果你存储内容出现set或者doubble类型,会带上Set,D类型描述如下

会出现问题无法解析,但是在程序里是可以反向序列化的

分析参考对比

  1. jdkSerializationRedisSerializer: 使用JDK提供的序列化功能。 优点是反序列化时不需要提供类型信息(class),但缺点是需要实现Serializable接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。
  2. Jackson2JsonRedisSerializer: 使用Jackson库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。
  3. FastJsonRedisSerializer 性能最优号称最快的json解析库,但是反序列化后类字段顺序和原来实体类不一致发生改变,在某些set,double字段情况下json格式不正确,但是在程序可以解析

更多问题参考

RedisTemplate序列化方式解读

redis数据库操作

在整合了spring-boot-starter-data-redis后会自动帮我们注入redisTemplate 对象,专门用来操作reids数据库的

在reids中如果想用文件夹方式存储key的话类似这样

我们只需要在存储使用使用::表示文件夹就可以了

1
java复制代码redisTemplate.opsForValue().set("userLoginCache::Kenx_6003783582be4c368af14daf3495559c", "user");

如果需要模糊查询key话使用*来表示 如

  1. 获取所有key
1
2
3
4
java复制代码public static Set<String> getAllKey(String keys) {
Set<String> key = redisTemplate.keys(keys + "*");
return key;
}
  1. 模糊批量删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码 /**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public static void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(Arrays.asList(key));
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码public static void delByPrefix(String key) {
if (key != null) {
Set<String> keys = redisTemplate.keys(key + "*");
redisTemplate.delete(keys);
}
}

public static void delBySuffix(String key) {
if (key != null) {
Set<String> keys = redisTemplate.keys("*" + key);
redisTemplate.delete(keys);
}
}

public static void clean(){
Set<String> keys = redisTemplate.keys("*");
redisTemplate.delete(keys);
}

因为使用很频繁所以我写成工具库RedisUtil 通过静态方法方式去调用就可以了


基本上包含工作中用到的所有方法, 这里附上源码

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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
java复制代码package cn.soboys.kmall.cache.utils;

import cn.hutool.extra.spring.SpringUtil;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
* 定义常用的 Redis操作
*
* @author kenx
*/

public class RedisUtil {

private static final RedisTemplate<String, Object> redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class);


/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return Boolean
*/
public static Boolean expire(String key, Long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据key获取过期时间
*
* @param key 键 不能为 null
* @return 时间(秒) 返回 0代表为永久有效
*/
public static Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}

/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public static Boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public static void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(Arrays.asList(key));
}
}
}

public static void delByPrefix(String key) {
if (key != null) {
Set<String> keys = redisTemplate.keys(key + "*");
redisTemplate.delete(keys);
}
}

public static void delBySuffix(String key) {
if (key != null) {
Set<String> keys = redisTemplate.keys("*" + key);
redisTemplate.delete(keys);
}
}

public static void clean(){
Set<String> keys = redisTemplate.keys("*");
redisTemplate.delete(keys);
}

/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public static Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}

/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public static Boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public static Boolean set(String key, Object value, Long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return Long
*/
public static Long incr(String key, Long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}

/**
* 递减
*
* @param key 键
* @param delta 要减少几
* @return Long
*/
public static Long decr(String key, Long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}

/**
* HashGet
*
* @param key 键 不能为 null
* @param item 项 不能为 null
* @return 值
*/
public static Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

/**
* 获取 hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public static Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}

/**
* 获取 hashKey对应的所有键
*
* @param key 键
* @return 对应的多个键
*/
public static Set<String> hmgetKey(String key) {
Map map = redisTemplate.opsForHash().entries(key);
return map.keySet();
}

/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public static Boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public static Boolean hmset(String key, Map<String, Object> map, Long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public static Boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public static Boolean hset(String key, String item, Object value, Long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 删除hash表中的值
*
* @param key 键 不能为 null
* @param item 项 可以使多个不能为 null
*/
public static void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}

/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为 null
* @param item 项 不能为 null
* @return true 存在 false不存在
*/
public static Boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}

/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return Double
*/
public static Double hincr(String key, String item, Double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}

/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return Double
*/
public static Double hdecr(String key, String item, Double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}

/**
* 根据 key获取 Set中的所有值
*
* @param key 键
* @return Set
*/
public static Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public static Boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public static Long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}

/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public static Long sSetAndTime(String key, Long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}

/**
* 获取set缓存的长度
*
* @param key 键
* @return Long
*/
public static Long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}

/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public static Long setRemove(String key, Object... values) {
try {
return redisTemplate.opsForSet().remove(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}

/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return List
*/
public static List<Object> lGet(String key, Long start, Long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 获取list缓存的长度
*
* @param key 键
* @return Long
*/
public static Long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}

/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;
* index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return Object
*/
public static Object lGetIndex(String key, Long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return Boolean
*/
public static Boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return Boolean
*/
public static Boolean lSet(String key, Object value, Long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return Boolean
*/
public static Boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return Boolean
*/
public static Boolean lSet(String key, List<Object> value, Long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return Boolean
*/
public static Boolean lUpdateIndex(String key, Long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public static Long lRemove(String key, Long count, Object value) {
try {
return redisTemplate.opsForList().remove(key, count, value);
} catch (Exception e) {
e.printStackTrace();
return 0L;
}
}

public static Set<String> getAllKey(String keys) {
Set<String> key = redisTemplate.keys(keys + "*");
return key;
}


}

本文转载自: 掘金

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

LeetCode 213 打家劫舍 II【c++/java

发表于 2021-11-04

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

1、题目

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

1
2
3
ini复制代码输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

1
2
3
4
ini复制代码输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

1
2
ini复制代码输入:nums = [0]
输出:0

2、思路

给定一个代表金额的非负整数数组nums,相邻房间不可偷并且房间是围成一圈的,让我们输出可以偷窃到的最高金额。

样例:


如样例所示,nums = [1,2,3,1],偷窃1,3,号房间可以获得最高金额4。

打家劫舍 I

我们先来看看「198. 打家劫舍」房间单排排列的动态规划的做法。

状态表示:f[i]表示偷窃1号到i号房间所能获得的最高金额。那么,f[n]就表示偷窃1号到n号房间所能获得的最高金额,即为答案。

状态计算:

假设有i间房间,考虑最后一间偷还是不偷房间,有两种选择方案:

  • 1、偷窃前i-1间房间,不偷窃最后一间房间,那么问题就转化为了偷窃1号到i- 1号房间所能获得的最高金额,即f[i] = f[i-1] 。

  • 2、偷窃前i - 2间房间和最后一间房间 (相邻的房屋不可闯入),那么问题就转化为了偷窃1号到i- 2号房间所能获得的最高金额再加上偷窃第i号房间的金额,即f[i] = f[i - 2] + nums[i]。 (下标均从1开始)


两种方案,选择其中金额最大的一个。因此状态转移方程为: f[i] = max(f[i - 1], f[i - 2] + nums[i])。 (下标均从1开始)

打家劫舍 II

我们已经知道了房间单排排列的状态转移方程,接下来思考房间环状排列的做法。

房间环状排列 意味着第一间和最后一间不能同时选择,因此我们可以分成两种情况来讨论:

  • 1、不偷窃最后一间房间,那么问题转化为偷窃1号到i - 1号房间所能获得的最高金额。
  • 2、不偷窃第一间房间,那么问题转化为偷窃2号到i号房间所能获得的最高金额。

两种情况中取最大值,这样我们就把环状排列问题转化为了两个单排排列的子问题。

我们定义两个数组f[]和g[],分别用f[n-1]和g[n]两个数组值来表示区间[1, n - 1]和[2, n]的最大金额值,图示过程如下:


初始化:

f[1] = nums[0],只偷窃1号房间所能获得的最高金额为nums[0]。

g[2] = nums[1],把第二间房间当成房间单排排列的起点,只偷窃2号房间所能获得的最高金额为nums[1]。

实现细节:

我们定义的状态表示f[]、g[]数组以及nums[]数组下标均是从1开始的,而题目给出的nums[]数组下标是从0开始的。为了一 一对应,状态转移方程中的nums[i]的值要往前错一位,取nums[i - 1],这点细节希望大家可以注意一下。

时间复杂度分析: O(n)O(n)O(n),其中 nnn是数组长度。需要对数组遍历一次。

3、c++代码

1
2
3
4
5
6
7
8
9
10
11
12
c复制代码class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n == 1) return nums[0]; //只有一间房间,返回nums[0]
vector<int>f(n + 1), g(n + 1);
f[1] = nums[0], g[2] = nums[1]; //初始化
for(int i = 2; i <= n - 1; i++) f[i] = max(f[i - 1], f[i - 2] + nums[i - 1]); //区间[1,n-1]最大值
for(int i = 3; i <= n; i++) g[i] = max(g[i - 1], g[i - 2] + nums[i - 1]); //区间[2,n]最大值
return max(f[n - 1], g[n]);
}
};

4、java代码

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码class Solution {
public int rob(int[] nums) {
int n = nums.length;
if(n == 1) return nums[0]; //只有一间房间,返回nums[0]
int[] f = new int[n + 1], g = new int[n + 1];
f[1] = nums[0]; //初始化
g[2] = nums[1];
for(int i = 2; i <= n - 1; i++) f[i] = Math.max(f[i - 1], f[i - 2] + nums[i - 1]);
for(int i = 3; i <= n; i++) g[i] = Math.max(g[i - 1], g[i - 2] + nums[i - 1]);
return Math.max(f[n - 1], g[n]);
}
}

原题链接:213. 打家劫舍 II

在这里插入图片描述

本文转载自: 掘金

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

Spring Cloud Gateway 整合阿里 Sent

发表于 2021-11-04

大家好,我是不才陈某~

这是《Spring Cloud 进阶》第八篇文章,往期文章如下:

  • 五十五张图告诉你微服务的灵魂摆渡者Nacos究竟有多强?
  • openFeign夺命连环9问,这谁受得了?
  • 阿里面试这样问:Nacos、Apollo、Config配置中心如何选型?这10个维度告诉你!
  • 阿里面试败北:5种微服务注册中心如何选型?这几个维度告诉你!
  • 阿里限流神器Sentinel夺命连环 17 问?
  • 对比7种分布式事务方案,还是偏爱阿里开源的Seata,真香!(原理+实战)
  • Spring Cloud Gateway夺命连环10问?

前一篇文章介绍了Spring Cloud Gateway的一些基础知识点,今天陈某就来唠一唠网关层面如何做限流?

文章目录如下:

网关如何限流?

Spring Cloud Gateway本身自带的限流实现,过滤器是RequestRateLimiterGatewayFilterFactory,不过这种上不了台面的就不再介绍了,有兴趣的可以实现下。

今天的重点是集成阿里的Sentinel实现网关限流,sentinel有不懂的可以看陈某的文章:阿里限流神器Sentinel夺命连环 17 问?

从1.6.0版本开始,Sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流:

  • route维度:即在配置文件中配置的路由条目,资源名为对应的routeId,这种属于粗粒度的限流,一般是对某个微服务进行限流。
  • 自定义API维度:用户可以利用Sentinel提供的API来自定义一些API分组,这种属于细粒度的限流,针对某一类的uri进行匹配限流,可以跨多个微服务。

sentinel官方文档:github.com/alibaba/Sen…

Spring Cloud Gateway集成Sentinel实现很简单,这就是阿里的魅力,提供简单、易操作的工具,让程序员专注于业务。

新建项目

新建一个gateway-sentinel9026模块,添加如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
xml复制代码<!--nacos注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!--spring cloud gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- spring cloud gateway整合sentinel的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

<!-- sentinel的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

注意:这依然是一个网关服务,不要添加WEB的依赖

配置文件

配置文件中主要指定以下三种配置:

  • nacos的地址
  • sentinel控制台的地址
  • 网关路由的配置

配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
yaml复制代码spring:
cloud:
## 整合sentinel,配置sentinel控制台的地址
sentinel:
transport:
## 指定控制台的地址,默认端口8080
dashboard: localhost:8080
nacos:
## 注册中心配置
discovery:
# nacos的服务地址,nacos-server中IP地址:端口号
server-addr: 127.0.0.1:8848
gateway:
## 路由
routes:
## id只要唯一即可,名称任意
- id: gateway-provider
uri: lb://gateway-provider
## 配置断言
predicates:
## Path Route Predicate Factory断言,满足/gateway/provider/**这个请求路径的都会被路由到http://localhost:9024这个uri中
- Path=/gateway/provider/**

上述配置中设置了一个路由gateway-provider,只要请求路径满足/gateway/provider/**都会被路由到gateway-provider这个服务中。

限流配置

经过上述两个步骤其实已经整合好了Sentinel,此时访问一下接口:http://localhost:9026/gateway/provider/port

然后在sentinel控制台可以看到已经被监控了,监控的路由是gateway-provider,如下图:

此时我们可以为其新增一个route维度的限流,如下图:

上图中对gateway-provider这个路由做出了限流,QPS阈值为1。

此时快速访问:http://localhost:9026/gateway/provider/port,看到已经被限流了,如下图:

以上route维度的限流已经配置成功,小伙伴可以自己照着上述步骤尝试一下。

API分组限流也很简单,首先需要定义一个分组,API管理-> 新增API分组,如下图:

匹配模式选择了精确匹配(还有前缀匹配,正则匹配),因此只有这个uri:http://xxxx/gateway/provider/port会被限流。

第二步需要对这个分组添加流控规则,流控规则->新增网关流控,如下图:

API名称那里选择对应的分组即可,新增之后,限流规则就生效了。

陈某不再测试了,小伙伴自己动手测试一下吧……………

陈某这里只是简单的配置一下,至于限流规则持久化一些内容请看陈某的Sentinel文章,这里就不再过多的介绍了。

如何自定义限流异常信息?

从上面的演示中可以看到默认的异常返回信息是:”Block………”,这种肯定是客户端不能接受的,因此需要定制自己的异常返回信息。

下面介绍两种不同的方式定制异常返回信息,开发中自己选择其中一种。

直接配置文件中定制

开发者可以直接在配置文件中直接修改返回信息,配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yaml复制代码spring:
cloud:
## 整合sentinel,配置sentinel控制台的地址
sentinel:
#配置限流之后,响应内容
scg:
fallback:
## 两种模式,一种是response返回文字提示信息,
## 一种是redirect,重定向跳转,需要同时配置redirect(跳转的uri)
mode: response
## 响应的状态
response-status: 200
## 响应体
response-body: '{"code": 200,"message": "请求失败,稍后重试!"}'

上述配置中mode配置的是response,一旦被限流了,将会返回JSON串。

1
2
3
4
json复制代码{
"code": 200,
"message": "请求失败,稍后重试!"
}

重定向的配置如下:

1
2
3
4
5
6
7
8
9
10
11
yaml复制代码spring:
cloud:
## 整合sentinel,配置sentinel控制台的地址
sentinel:
#配置限流之后,响应内容
scg:
fallback:
## 两种模式,一种是response返回文字提示信息,一种是redirect,重定向跳转,需要同时配置redirect(跳转的uri)
mode: redirect
## 跳转的URL
redirect: http://www.baidu.com

一旦被限流,将会直接跳转到:www.baidu.com

编码定制

这种就不太灵活了,通过硬编码的方式,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码@Configuration
public class GatewayConfig {
/**
* 自定义限流处理器
*/
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockHandler = (serverWebExchange, throwable) -> {
Map map = new HashMap();
map.put("code",200);
map.put("message","请求失败,稍后重试!");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(map));
};
GatewayCallbackManager.setBlockHandler(blockHandler);
}
}

两种方式介绍完了,根据业务需求自己选择适合的方式,当然陈某更喜欢第一种,理由:约定>配置>编码。

网关限流了,服务就安全了吗?

很多人认为只要网关层面做了限流,躲在身后的服务就可以高枕无忧了,你是不是也有这种想法?

很显然这种想法是错误的,复杂的微服务架构一个独立服务不仅仅被一方调用,往往是多方调用,如下图:

商品服务不仅仅被网关层调用,还被内部订单服务调用,这时候仅仅在网关层限流,那么商品服务还安全吗?

一旦大量的请求订单服务,比如大促秒杀,商品服务不做限流会被瞬间击垮。

因此需要根据公司业务场景对自己负责的服务也要进行限流兜底,最常见的方案:网关层集群限流+内部服务的单机限流兜底,这样才能保证不被流量冲垮。

总结

文章介绍了Spring Cloud Gateway整合Sentinel对网关层进行限流,以及关于限流的一些思考。如有错误之处,欢迎留言指正。

项目源码已经上传Github,公众号【码猿技术专栏】回复关键词:9528获取!

最后说一句(求关注,别白嫖我)

陈某每一篇原创文章都是精心输出,尤其是《Spring Cloud 进阶》专栏的文章,知识点太多,要想讲的细,必须要花很多时间准备,从知识点到源码demo。

如果这篇文章对你有所帮助,或者有所启发的话,帮忙点赞、在看、转发、收藏,你的支持就是我坚持下去的最大动力!

本文转载自: 掘金

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

Java17快了多少?JDK 17、16和11的性能比较和分

发表于 2021-11-04

Java 17 已正式发布,新版本提供了不少新特性和功能增强。不过对于大多数项目而言,往往需要更改代码才能利用到这些新变化,但性能除外 —— 开发者只需要升级 JDK 版本,就能免费获得性能提升。

规划调度引擎 OptaPlanner 项目负责人对 JDK 17、JDK 16 和 JDK 11 的性能基准测试进行了对比,看看 Java 17 的性能提升是否值得我们去升级。

测试环境和流程

1、硬件:稳定的机器,没有任何其他计算要求的进程在运行。

配置 Intel® Xeon® Silver 4116 @ 2.1 GHz (12 cores total / 24 threads) ,128 GiB RAM ,RHEL 8 x86_64

2、JDKs(用于编译和运行)

  • JDK 11
1
scss复制代码openjdk 11.0.12 2021-07-20OpenJDK Runtime Environment Temurin-11.0.12+7 (build 11.0.12+7)OpenJDK 64-Bit Server VM Temurin-11.0.12+7 (build 11.0.12+7, mixed mode)
  • JDK 16
1
java复制代码openjdk 16.0.2 2021-07-20OpenJDK Runtime Environment (build 16.0.2+7-67)OpenJDK 64-Bit Server VM (build 16.0.2+7-67, mixed mode, sharing)
  • JDK 17 (下载日期为 2021-09-06)
1
java复制代码openjdk 17 2021-09-14OpenJDK Runtime Environment (build 17+35-2724)OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)

3、JVM 选项:启用-Xmx3840M并明确指定垃圾回收器:

  • -XX:+UseG1GC for G1GC,低延迟垃圾回收器(三个 JDK 版本的默认项)
  • -XX:+UseParallelGC for ParallelGC,高吞吐量垃圾回收器

4、Main class:org.optaplanner.examples.app.GeneralOptaPlannerBenchmarkApp,来自 OptaPlanner 8.10.0.Final中的 optaplanner-examples模块

  • 每次运行都使用 OptaPlanner 解决 11 个规划问题,例如员工排班、学校时间表和云优化。每个规划问题运行 5 分钟。日志记录设置为 INFO。基准测试以 30 秒的 JVM 预热开始。
  • 解决规划问题不涉及 IO(除了在启动期间加载输入的几毫秒)。单个 CPU 完全饱和。它会不断地创建许多短期存在的对象,然后 GC 将它们收集起来。
  • 基准测试会衡量每秒计算的分数数量,分数越高代表性能越好。为提议的规划解决方案计算分数并非易事:它涉及许多计算,包括检查每个实体与每个其他实体之间的冲突。

5、运行次数:每个 JDK 和每个垃圾回收器组合按顺序运行 3 次。下面的结果是这 3 次运行的平均值。

测试结果

Java 11 (LTS) and Java 16 versus Java 17 (LTS)

图片

图片

图片

图片

G1GC versus ParallelGC on Java 17

图片

图片

总结

基于 OptaPlanner 用例,这些基准测试表明:

  • 对于 G1GC(默认),Java 17 比 Java 11 快 8.66%,比 Java 16 快 2.41%
  • 对于 ParallelGC,Java 17 比 Java 11 快 6.54%,比 Java 16 快 0.37%
  • Parallel GC 比 G1 GC 快 16.39%

简而言之,最新的 JDK 更快,高吞吐量垃圾回收器比低延迟垃圾回收器更快。

因此,Java 17 带来的性能提升非常值得升级,更重要的是它可以免费商用,而且还是 LTS 版本。所以你还要坚持 Java 8 一万年不动摇吗?

本文转载自: 掘金

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

聊聊Jhipster,强烈推荐Java开发看看,节省很多时间

发表于 2021-11-04

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

为什么想聊聊Jhipster呢?Jhipster我用了将近半年了,说说一些感受吧。

为什么要用Jhipster呢?Leader让用的呗,我开始用真的是不习惯,生成一堆文件,好多依赖,都不知道是干啥的。没办法啊,我又不是Leader,技术选型还轮不到我说了算,不习惯也要用啊,网上也找了相关资料,说真的,都不是很全。下面我们来聊聊Jhipster吧,你们可以去官网看看。

有些博主这方面讲的还是很不错的,如:zhuanlan.zhihu.com/c_100829618…

Jhipster的官网:www.jhipster.tech/

image-20211103223507935

在了解JHipster之前,首先明确几个误区:

1、JHipster不是框架,而算是Boilerplate。这个Boilerplate整合了前后端各种当前主流的技术、框架、工具、架构、代码规范、开发过程、最佳实践。当然JHipster也有自己的创新,例如JDL。

2、JHipster适合创建新的企业级应用。在4及以前的版本中,服务器端支持Hibernate,前端支持Angularjs/Angular,所以更适合做企业应用。又由于从JDL生成原型这个特性,JHipster不适合做已存在数据库表结构的系统,特别是原来的数据库设计非常不合理(命名不规范、表结构不合理、主外键约束不严格)的情况。

3、如果说Spring Boot给Java服务器端带来变革,让项目配置搭建更方便。那么JHipster就是一个跨越前后端的全栈Boot。

介绍

Jhipster这个名词可以拆开成:Java + hipster ,hipster是潮人的意思,加起来就是Java潮人? 没错,她所涉及的技术栈确实是紧跟时代潮流的。

那么她的具体定义是啥呢?用官网的话说:JHipster是一个开发平台,可以快速生成,开发和部署现代Web应用程序+微服务架构。

快速入门

1、安装 Java、 Git 和 Node.js

2、安装JHipster npm install -g generator-jhipster

3、创建一个新目录并进入 mkdir myApp && cd myApp

4、运行Jhipster并根据屏幕指示操作 jhipster

5、使用 JDL Studio 设计您的实体类后, 下载jhipster-jdl.jh文件

6、生成实体类代码 jhipster jdl jhipster-jdl.jh

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
java复制代码1. What is the base name of your application? (您的应用程序的基础名是什么?)
这是您应用程序的名称。
2. What is your default Java package name? (您的默认Java软件包名称是什么?)
您的Java应用程序将以此为包的根名称。
3. Do you want to use the JHipster Registry to configure, monitor and scale your application?
JHipster Registry是一个开源工具,用于管理您在运行中的应用程序。可不选。
4. Which type of authentication would you like to use?
选择认证方式,如JWT,OAuth 2.0,HTTP会话等。
5. Which type of database would you like to use?
选择数据库类型,提供了sql的,nosql的,供你选择。
6. Which production database would you like to use?
您要使用哪个 生产 数据库
7. Which development database would you like to use?
您要使用哪个 开发 数据库?一般选h2-disk
8. Do you want to use the Spring cache abstraction?
您是否要使用Spring抽象缓存?
9. Would you like to use Maven or Gradle?
您要使用Maven还是Gradle?
10. Which other technologies would you like to use?
您还想使用哪些其他技术?根据需要选择即可
11. Which Framework would you like to use for the client?
您想为前端选择使用哪个框架 ?给出的选项是angular,react,vue
12. Would you like to use a Bootswatch theme?
选择你想要使用的前端题。
13. Would you like to use the Sass stylesheet preprocessor for your CSS?
您想为CSS使用Sass样式表预处理器吗?
14. Which testing frameworks would you like to use?
您想使用哪些测试框架?可选的是Gatling,Cucumber等。
15. Would you like to install other generators from the JHipster Marketplace?
您是否要从JHipster市场安装其他生成器? 选否就行。

生成的结构目录大概如下图:

image-20211104090003360

视频教程

从0开始,5分钟创建一个Spring Boot + Angular/React应用

image-20211103224806684

结语

当我们写了很多代码之后,还去写很多的胶水代码其实就没多大意义了。我们完全可以借助一些工具,帮我们生产代码。解放我们的双手,释放我们的时间,或者只是单纯的投个懒,有何不可呢?当我们解放了更多的时间之后,可以将更多的时间关注在设计上关注在其他方面上。实际上,jhipster写的代码还是可以的,简单的crud应用完全没问题,他生成的和亲自去写是一样的。

我一直都说,从事软件开发,我们就是需要不断学习,感兴趣的可以尝试去用一用,还是非常不错的,具体怎么一步一步操作,网上教程很多,不感兴趣的可以直接跳过。从事 Java开发的小伙伴,我还是推荐你去学一下。

本文转载自: 掘金

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

1…423424425…956

开发者博客

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