利他才能利己


  • 首页

  • 标签

  • 归档

  • 搜索

OpenSSL: 用VS2017创建C工程

发表于 2019-11-23 | 分类于 C/C++ |

简介

该系列文章给大家(主要是刚接触Visual Studio工具的C开发者)带来使用开发工具Visual Studio 2017进行OpenSSL编程,包括base64,AES、DES和RSA加解密等内容。

对于C/C++的编程,我工作之后基本都是在Linux上面写,很少在Windows上面耕耘。最近收到几个小伙伴的邮件,有请教如何在Windows上面进行C编程的,也有问关于OpenSSL的基础知识的。说实话我也不是很熟悉Windows上面的C编程,但凭借着一股自信还是硬着头皮实践了一把,算是给大家一个交待。

在这里非常感谢网名为“为C奋斗”的建议,也感谢网上开源代码的作者们,如果没有他们的贡献,我可能根本坚持不下来把这些知识做以总结和分享。

写文不易,挑灯夜战,如果文中有任何错误之处还望大家不吝赐教。

该系列文章中相关代码我都托管在Github上面了,点击 c-openssl 可以获取。

创建C工程

这部分介绍如何在Visual Studio 2017中创建C工程。

打开Visual Studio 2017,选择File/New/Project...,紧接着完成下面截图中的步骤即可完成工程创建。

点击OK稍等几秒就可以完成工程的创建,创建成功之后,该工程是一个空工程没有任何源文件,如图:

创建C文件

该部分介绍如何在工程中手动创建C文件。

需要手动创建C源码文件,右键点击 Source Files

注意:源码后缀是.c,不是.cpp.

创建完成后,打开 mzc_base64.c 写上一段简单的代码,测试一下是否正常。

1
2
3
4
5
6
7
8
#include <stdio.h>

int main()
{
printf("Hello OpenSSL, I'm coming.\n");

return 0;
}

直接使用快捷键 Ctrl+F5 运行程序,可以看到弹出了系统的命令行并显示运行结果,截图如下:

至此在Visual Studio 2017中成功创建了C工程 =:)

遇到的问题

有不少小伙伴跟我说,他们在创建的过程中遇到了这样或者那样的问题,我总结了一下分享给有需要的人。

1、没有找到Windows Desktop选项

这种情况应该是没有在 Visual Studio 2017 中安装桌面开发组件,如图操作:

打开 Visual Studio Inistaller 安装通用Windows平台开发组件,如图:

安装完成后,再重新创建工程就有了该选项。

2、编译错误,找不到printf函数

出现这种错误,大部分原因是没有安装 Windows10 SDK。打开 C:\Program Files (x86)\ 目录,看看是否有 Windows Kits 目录。

如果没有,就点击 Windows10 SDK 下载,安装完成后,重新创建工程就可以了。

如果有该目录,在工程属性中设置一下,如图:

打开属性视图,如下:

配置你正确的 Windows10 SDK 目录即可,我安装的是下面的版本:

3、无法访问头文件

点击系统库的头文件,显示如下提示:

在头文件上面右键,可以看到跳转的几个选项都是不可用的状态,如下图:

出现这样的情况不要怕,这是VS的病,只能等Microsoft去根治了。我们使用 Open Document 代替,快捷键是Ctrl+Shift+G,一样可以打开对应的头文件。


生命不是要超越别人,而是要超越自己~

MySQL8.0.15在Win10上的折腾记

发表于 2019-11-10 | 分类于 Server , DB |

安装和配置 MySQL

我在 Windows 10 上面使用 MySQL Instller 安装的 MySQL-8.0.15,安装完成后,启动和使用 MySQL 遇到了不少问题,特此记录踩坑记。

只要你的网络稳定,按照默认安装,整个安装过程还算顺利。

安装过程中需要设置密码,我设置的账号和密码都是 root,所以下面的操作都是基于这个账号和密码进行的。

安装完成后,将 mysql server 的 bin 目录加入到系统的 path 变量中去,如图:

1568529858131

系统环境变量 path 中加入 %MySQL_Home%\bin 即可。

加入成功之后,就可以使用 mysql 命令了。

题外话,我们在命令行使用的 mysql 命令其实是对应 MySQL Server 8.0\bin\mysql.exe,可以使用 where 命令看一下其可执行文件的位置,如下:

1
2
$ where mysql
C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe

同理, mysqld 命令对应的是 mysqld.exe可执行文件。

查看安装的 mysql-server 版本,可以执行下面的命令:

1
mysql --version

可以得到下面的输出,可以看出当前版本是 8.0.15:

1
C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe  Ver 8.0.15 for Win64 on x86_64 (MySQL Community Server - GPL)

我用的命令行终端工具是 Git bash(安装Git客户端就自带该工具,个人觉得挺好用的,[嘿哈])。

用管理员身份运行 Git bash,然后在该终端工具中执行如下命令:

1
mysqld --install

如果不用管理员身份运行终端的话,会报类似下面的错误:

1
Install/Remove of the Service Denied!

运行成功的话,可以看到如下提示:

1
Service successfully installed.

再次启动 MySQL80,一定要用管理员身份启动终端,否则会报 发生系统错误 这样的错误(如果你是使用管理员身份启动终端的还是报这个错误,也有可能是该服务已经启动了),执行:

1
net start MySQL80

注意:这里使用的是 MySQL80,这个是服务的名称 (安装时候我设置的服务名称) ,不是 mysql,下面会讲解为什么这样做,如果你没有这个 MySQL80 服务只需要启动名称为 mysql 的服务即可。

如果出现类似 “MYSQL 服务无法启动” 的提示,可以在 Windows 的任务管理器中找找是否已经启动了 mysqld 服务,可以手动关闭后再来启动。

如果始终启动失败,可以执行下面的命令:

1
mysqld --initialize

启动成功后,显示如下:

1
2
MySQL80 服务正在启动 .
MySQL80 服务已经启动成功。

可以使用下面命令查看是否成功启动 MySQL80 服务,如下:

1
netstat -aon|findstr "3306"

看到如下提示表示启动成功,如下:

1
2
3
4
5
TCP    0.0.0.0:3306           0.0.0.0:0              LISTENING       10344
TCP 0.0.0.0:33060 0.0.0.0:0 LISTENING 10344
TCP [::]:3306 [::]:0 LISTENING 10344
TCP [::]:33060 [::]:0 LISTENING 10344
TCP [::1]:3306 [::1]:56962 TIME_WAIT 0

登录 MySQL80 服务,执行如下命令:

1
2
3
$ mysql -uroot -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)

很不幸的是,登录失败(ERROR 1045)。

那我们就来解决这个问题,这个版本的 MySQL 和之前的 8.0.x 版本解决方案不同,网上找过很多解决方案有修改配置文件的,忙了半天还是没有搞定,最后在 StackOverflow 上面找到方案,结合个人实践总结了一下,还烦请大家务必往下看。

解决登录 MySQL 受限

在看下面内容之前,务必完成文中前面说的步骤,否则下面的内容对你来说没有太大价值。

新版本的 MySQL 加强了安全方便的因素,所以使用新版本比较费劲,如果你不想折腾,可以安装 MySQL 的 5.x 版本。

Step-1:停止 MySQL80 服务

使用快捷键 win+R 输入 services.msc,打开如下视图并找到 MySQL80 服务:

1568529858131

停止 MySQL80 服务,或者在命令行执行 net stop MySQL80 命令。

Step-2:另一种方式启动 MySQL80 服务

使用管理员身份运行终端1,并执行:

1
mysqld --console --skip-grant-tables --shared-memory

此时可以成功启动 MySQL80 服务,但是在 Windows 的服务中是看不到的,不用理会。继续。

Step-3:启动另一个终端2

上述步骤启动完成后,再用管理员身份运行另一个 Windows 的终端2(CMD),用系统自带的或者 Git Bash 都可以,但是如果使用 Git Bash 的话,必须在登录mysql 的时候使用 winpty(Git Bash自带的工具)命令,否则登录会卡住不动。命令使用如下:

1
winpty mysql -uroot -proot

Step-4:终端登录 mysql

执行下面命令登录服务:

1
mysql -u root -p root

如下图可以登录成功,如下截图:

1568529858131

Step-5:置空密码

在上述命令行状态下,执行:

1
2
3
use mysql

update user set authentication_string='' where user='root';

Step-6:退出 mysql 服务

1
quit

Step-7:关闭 mysqld 启动的 MySQL80 服务

需要关闭以 -console --skip-grant-tables --shared-memory 启动的 MySQL80 服务,直接在终端1中 ctrl+c 即可。

Step-8:启动 MySQL80 服务

使用Step-1步骤中的方式打开 Windows 的服务启动或者在终端1中执行 net start MySQL80 启动 MySQL80 服务。

Step-9: 无密码登录 mysql

在步骤 Step-5 中已将密码置空,现在可以无密码状态登录 mysql 服务,输入登录命令:

1
mysql -u root -p 

登录成功后,修改密码(永远不过期的密码)执行:

1
ALTER USER 'root'@'localhost' IDENTIFIED BY 'newpwd123' PASSWORD EXPIRE NEVER;

我把原来的密码修改为 newpwd123,你可以根据自己的爱好设置为其他的密码。

如果在此步骤出现类似下面的错误:

1
ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement

请执行下面的SQL语句,如下命令:

1
flush privileges;

Step-10:退出 mysql,重新登录

退出 mysql,然后重新使用修改过的密码登录 mysql,完事。

MySQL配置文件?

这也是个神奇的问题,最后在 MySQL 的官方手册里找到了 Answer。

在 Windows 系统中 C 盘有个隐藏的文件夹,我们来揭开它的神秘面纱。

打开 C 盘文件,按照下面1、2、3顺序就可以看到 ProgramData 文件夹,如下:

1568529858131

进入该文件夹,就可以看到 MySQL 的配置文件,如下:

1568529858131

上面我们已经说过,启动 MySQL80 服务就默认使用的是该配置文件,可以在 Windows 服务中点击 MySQL80 查看其属性,可以看到下面的描述:

1
2
可执行文件的路径:
C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld.exe" --defaults-file="C:\ProgramData\MySQL\MySQL Server 8.0\my.ini" MySQL80

其中,--defaults-file="C:\ProgramData\MySQL\MySQL Server 8.0\my.ini" 就是指定配置文件路径的。

而在 Windows 服务中点击 MySQL,看到的属性是这样的:

1
2
可执行文件的路径:
"C:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld.exe" MySQL

并没有指定配置文件,这也是为什么让大家启动的时候执行 net start MySQL80 的原因。

顺便修改一下配置文件,把默认编码改为 utf-8,如下配置(记得是在[client]和[mysql]标签下):

1
2
3
4
5
6
7
8
9
10
11
[client]
# pipe=
# socket=MYSQL
# 设置mysql客户端连接服务端时默认使用的端口
port=3306
default-character-set=utf8

[mysql]
no-beep
# 设置默认编码
default-character-set=utf8

还有奇葩的错误

会持续记录各种奇葩错误。

1、ERROR 2003 (HY000): Can’t connect to MySQL server on ‘localhost’ (10061)

登录 mysql 服务,报上面的错误,说明没有启动成功 MySQL80 服务,最大的可能是你修改了配置文件要么是格式错了要么是配置的东西 mysql 不认。

只能把配置文件修改正确才能启动服务。


我们要学会做自己精神领域内的君王,不要成为别人情绪世界里面的奴隶。

微服务: 部署服务

发表于 2019-10-31 | 分类于 Server |

简介

在 微服务系列 文章中,跟大家从环境搭建、Web工程创建、提供Restful接口、热部署、Git使用以及Swagger生成文档分享了SpringBoot的入门知识。今天继续分享如何使用IDEA和maven对工程进行打包操作,然后发布代码到服务器进行部署的过程。

大家有任何问题,或者文中有不正确的地方欢迎留言评论,共同进步,再次感谢大家的阅读。

IDEA 打包

在 File/Project Structure 打开视图,或者使用快捷键 Ctrl+Alt+Shift+S 打开视图。

1568529858131

打开的视图如下:

1568529858131

按照 1、2、3 步骤打开下面视图,如下:

1568529858131

配置该视图的内容,分别设置 Main Class 和 选择 JAR files,如下:

1568529858131

这里特别要注意,选择生成 MANIFEST.MF 文件目录放到 resources 下面,默认会放在 main\java 目录下。

如果不修改这个目录,最终运行生成的 jar 文件,会报下面的错误:

1
Invalid or corrupt jarfile {project_name}.jar

配置完成后,点击 OK 关闭对话框即可。

接下来需要编译,点击 IDEA 的 Build 菜单,选择 Build Artifacts...,如下截图:

1568529858131

点击 Build Artifacts... 后出现视图

1568529858131

第一次直接选择 Build 选项,后续可以选择 Rebuild 或者 Clean 选项,根据你的需要进行选择即可。

等待 IDEA 打包完成后,在工程文件中会生成 out 目录和 resources 目录下面生成 META-INF 目录和对应的文件,如下图所示:

1568529858131

在 springboot_jar 目录下面有打包成功的 jar 文件,即 springboot.jar 文件,另外也将 tomcat 等一些其他依赖包都打包到一起了。

打开终端,执行 java -jar springboot.jar 就可以运行该项目了。

1568529858131

如果发现由于端口被占用而无法启动的情况,可以查看端口占用情况:

1
netstat -ano | findstr 8080

查到类似如下结果:

1
2
TCP    0.0.0.0:8080           0.0.0.0:0              LISTENING       18948
TCP [::]:8080 [::]:0 LISTENING 18948

这里演示的进程ID是 18948,强制关闭端口进程即可

1
taskkill -F -PID 18948

部署到服务器

我使用虚拟机(Ubuntu)来简单介绍一下部署过程。虚拟机是 VMWare 其版本是 15.0.2,Ubuntu 系统版本是 19.04。如何安装虚拟机、如何在虚拟机中安装 Ubuntu 系统以及如何在 Ubuntu 中安装 Java 环境请大家自行查找资料完成,这里不赘述。

可以通过上面的方式(IDEA打包)完成后,将生成的 springboot_jar 目录拷贝到虚拟机的用户目录下,然后通过 java -jar springboot_jar/springboot.jar 来启动服务。

我们还可以通过 IDEA 的 maven 工具进行打包,步骤如下:

1、打开工程的maven视图

1568529858131

2、按照标红的1、2、3、4操作即可

成功后在 target 目录会生成对应的可执行的 jar 文件,该 jar 包包括了所有的 SpringBoot 相关的包如下图:

1568529858131

同理,复制 springboot-0.0.1-SNAPSHOT.jar 到虚拟机的用户目录,通过 java -jar springboot-0.0.1-SNAPSHOT.jar 运行服务即可。

查看 Ubuntu 的主机IP地址,可以通过 ifconfig 来获取,也可以在设置里面查找其IP地址。 ifconfig 需要按照 net-tools,安装命令如下:

1
sudo apt install net-tools

我的虚拟机的IP地址是 192.168.142.128,即 http://192.168.142.128:8080 是 Ubuntu 的 IP 地址和 Tomcat 的运行端口,回到 Windows 主机上面访问虚拟机的服务,通过下面图可以看出是成功的。

1568529858131

这里只是简单的介绍了一下如何将自己写的 SpringBoot 服务部署到服务器上面,在你的实际项目中部署没这么简单,我算是抛砖引玉吧,后续随着学习的深入我再跟大家分享一下在实际大项目中部署遇到的一些问题。


生活并没有那么复杂,要是你喜欢,大可以说我是在探索生命。

看《银河补习班》有感

发表于 2019-10-26 | 分类于 随笔 |

简介

写这个话题,情不自禁的想起上学考试的作文题目,既有字数限制又要能表达中心思想,好不容易吭哧吭哧地写完了,结果作文分数也不一定理想,毕竟老师的心情指数变幻莫测。

看完电影《银河补习班》心中五味杂陈,想想自己陪孩子的时间屈指可数,从他出生到现在已经7个多年头了,大部分时间都是他妈妈陪着,我这个做父亲的跟马皓文比起来不止差一个银河系。

电影中搞笑片段不算多但恰到好处,马飞母亲的扮演者任素汐(看过他主演的电影《驴得水》,冷幽默风格的一部电影)和马飞后爹的扮演者梁超戏份不多但几乎担当了电影中搞笑的全部内容。特别是梁超那句 “儿子,你别的不多,就是爸爸多”,让人即感动又好笑。我相信每个人的上学生涯中都会遇到类似“闫主任”这样角色的老师,严厉的像个自私鬼,在片中简直就像一个名副其实的大反派,那句“莫名其妙、荒谬绝伦”搭配闫主任的短裤简直绝了。

电影的剧情有点理想化,但里面的很多道理值得我们去学习和反思,这是一部送给儿子和父亲的电影。

重要的是让孩子看你做什么

很多朋友觉得我的字写的还算不错,高中时候我的确经常参加书法比赛也拿过很多名次,但是我总是感觉自己的字写的很一般还需要更加努力。所以上学的时候只要有写字的机会我就会利用上,比如出黑板报,帮老师在黑板上面抄题,这些活我都很乐意干。后面参加工作了,自己也保持着写日记的习惯,用笔在笔记本上面写出来的字感觉特别亲切,那些字就好比有温度的灵魂。

在写字方面特别要感谢我的父亲,记得刚上初中那会,父亲从外地打工回来特地给我买了一本字帖,最让我感动的是他刚下车就亲自送到学校来了,一直倔强且不善于表达爱的他递给我字帖的那一刻,我才明白真正的爱不是经常挂在嘴边的而是在心里在行动上,请相信不管一个多平凡的爸爸,都会以自己的方式爱你。父亲的字写的比我好,那时候我就树立了一个目标:“将来一定要像父亲一样,把字写好,不能给他丢人“,现在想想自己挺幼稚的。

电影里面有个片段,深夜里马飞突然睡醒了,看到还在工作的马皓文说:“爸爸,我可以不睡觉吗?”,马皓文说:“那是你自己的事,以后这种事不用问我”。

每个人都会经历初为人父人母,难免自己也会犯下一些错误,就像电影里面马皓文对自己的儿子马飞说:“对不起!爸爸也是第一次做爸爸”,看到这里我不禁潸然泪下,感觉特别真诚。我和老婆约定一定不要在孩子面前吵架,不要在孩子面前随便发脾气,如果那样做了会给他们心灵上面造成一定的伤害,孩子们的学习模仿能力都比较强,大人的坏脾气会影响到孩子。周末有时间,我都会故意在孩子面前看书,他看我看的那么认真也忍不住过来凑热闹,时间久了他也爱上了看书,他的第一个书架就是这么来的,书架上面的书有些是图书馆借来的,有些是我和老婆买给他的,有些是他和其他小朋友换来的。

一定要做自己做喜欢的事情

今年国庆节放假当天,我六点半起床准备做早餐,发现儿子一个人坐在客厅写作业,我当时惊呆了,这小子怎么这么早就起床了居然还在写作业,如果按照我以前的脾气一定会批评他。就悄悄走过去,轻轻的拍拍他的肩膀说:“你怎么这么勤奋呀?”,他头也不回的说:“今天你不是要带我去玩吗,我想先把作业写完”,我忽然一愣,原来我有过这个承诺,我说:“行,你写完之后给我检查一下,如果问题不大我们今天就早早出发”,他自信的说:”没问题“。

小的时候家里比较贫寒,我又比较喜欢读书,白天除了上学外其他时间几乎都需要帮助父母干农活,也只有晚上有时间来读书和写作业。北方的冬天很冷,有时候连电我都舍不得用,偷偷的点上蜡烛,母亲经常半夜起来看我,她也没有说我什么,只是告诉我不要太晚了,后来她还给我准备了一个小火炉。每次考试结果不管是好还是差,她总是告诉我要继续努力。如果你已经是一名学生的家长了,看到孩子在努力不要轻易打断他,你只需要鼓励和支持Ta就好了。喜欢电影中的一句台词:“孩子都有一个神奇的感受器,能知道你是不是真的爱TA”。

现在社会竞争压力确实很大,父母为了孩子也是操碎了心,担心孩子将来怎么办,于是各种学习资料,补习班把孩子压得气喘吁吁。作为父母不督促孩子学习和进步,是我们的失职,但是过度的压迫孩子就是在犯罪,每个孩子都有自己的爱好和梦想,我们应该教导他要努力去实现自己的目标,而不是告诉他你只有考上清华北大才能继续自己的人生,就像电影里面说的:

“你有没有想过长大要干什么”?

“清华北大啊,妈妈说的”。

“清华北大只是过程,不是目的”。

“等你长大了一定要做自己喜欢的事情”。

只有做自己喜欢的事才有可能创造奇迹!

需要独立思考的能力

我家那个小子很喜欢拼图,喜欢一个人默默无闻的在房间拯救各种卡片,仿佛只有他才能让散装的奥特曼复活。每次拼装完成他都要过来炫耀一番,当然我也会给他一个大大的赞。

我们在网上买一些东西,只要他在家他都会要求去拆装。记得上次买了一个吸尘器,我打电话给我老婆说等我回来组装。没过一会,我老婆告诉我说,你儿子已经组装好了。我回来一看组装虽然没有那么完美,但是对于一个不到6岁的孩子来说已经很不错了。之前总是担心孩子做任何事一定做不好,所以处心积虑的避开他,生怕他要自己去做,自从自己看过几期关于孩子如何独立的课程后大大的改变了我的想法。

记得自己小时候,父母都很忙。看到他们太累了就想着自己一定要学会做饭,于是自己就从下面条开始,慢慢的从洗碗洗锅变成了一个小厨师,妈妈为了这个事骄傲了很久。父母不能总是阻止孩子去做他感兴趣的事情,我们只需要做好引导即可,放心,他们很靠谱!就拿孩子跌倒这件事来说,很多爷爷奶奶看到孩子不小心摔倒就迫不及待的去抱起来,其实这是不对的,我们应该引导孩子如何自己站起来,Ta下次就知道即使跌倒也要自己爬起来。

孩子在生活中也会遇到一些问题,孩子们有自己的小世界,别以为他们就没有烦恼和困难。在遇到问题的时候,我们不要直接自以为是的给出答案,问问Ta能不能再动动脑筋想想其他办法?

我们的孩子到底需要怎么样的教育?答案是:孩子需要培养独立思考,勇敢面对这个世界、做自己喜欢事情的教育。

在马飞遇到暴雨而被困住的情况下,马皓文歇斯底里的说:“我是爸爸,动动你的脑子,看看你周围有什么,你一定能出来的”,结果奇迹出现了,马飞自救了。虽然有点夸张,但是它告诉我们如果不是父亲的引导不是儿子的自信和思考这将是一场悲剧,至少我看哭了。

银河补习班,到底给我们补了什么?

每个人心中都有自己的答案,对我来说至少让我认识到了陪伴和鼓励很重要!

记得让孩子做自己喜欢的事情,你只需要鼓励和相信他!


真正的人生难题,不会像考卷那样,会自动跳出ABCD四个选项,有且只有一种标准答案。而是会有EFGHIJK的岔路,甚至能开出XYZ的脑洞。~

微服务: Swagger生成Markdown文档

发表于 2019-10-13 | 分类于 Server |

国庆放假期间,看了一部 2018年上映的电影 《本杰明.巴顿奇事》,豆瓣评分 8.9。影片讲述了一出生便拥有80岁老人形象的本杰明·巴顿,随着岁月的推移逐渐变得年轻,最终回到婴儿形态,并在苍老的恋人黛茜怀中离世的奇异故事。 如果没有看过这部影片的小伙伴抽时间可以去看看,里面有很多关于人生的哲理。

本次文章封面图来自该电影。

1568529858131

简介

文章 微服务: Swagger让你可以多抽一支烟 给大家分享了如何在自己的 SpringBoot 工程中集成 Swagger 以及如何使用 Swagger 生成在线文档。今天跟大家分享以下如何使用 Swagger 生成离线的 Markdown 格式的文档,在阅读下面内容之前,还是希望大家能看一下 微服务: Swagger让你可以多抽一支烟 这篇文章。

本人是一个热爱 Markdown 的狂热分子,无论是写日记还是工作笔记我都会使用 Markdown工具来做,那种所见即所得的感觉有点暗爽。自己在网上也找了很多关于如何使用 Swagger 生成 Markdown 格式的文档,大多数文章都比较陈旧,好不容易找到一篇自认为还可以的文章去实践发现还是存在一些问题,于是经过摸索诞生了此文。

集成 swagger2markup

插件 Swagger2Markup 可以帮助我们将 Swagger 文档转换为离线的 Markdown 格式的文档,Swagger2Markup 介绍如下:

1
Swagger2Markup converts a Swagger JSON or YAML file into several AsciiDoc or GitHub Flavored Markdown documents which can be combined with hand-written documentation. 

可以在 mvnrepository 仓库中搜索 swagger2markup,如图:

1568529858131

我使用的是第一个,当前版本是 1.3.3,在工程的 pom 文件中,需要集成 swagger 和 swagger2markup,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--swagger2markup-->
<dependency>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup</artifactId>
<version>1.3.3</version>
<scope>test</scope>
</dependency>

如果你认为这样就可以了,那你接下来无法完成编译工作,因为根本下载不了 swagger2markup,还需要在 pom 文件中添加如下内容,示例如下:

1
2
3
4
5
6
7
8
9
10
11
<!--不加这个,swagger2markup 找不到-->
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>jcenter-releases</id>
<name>jcenter</name>
<url>http://jcenter.bintray.com</url>
</repository>
</repositories>

编写测试代码

插件集成成功之后,接下来我们就可以去实现生成 Markdown 文档的梦想了,有点小鸡冻…

单元测试代码示例,如下:

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
import io.github.swagger2markup.Swagger2MarkupConfig;
import io.github.swagger2markup.Swagger2MarkupConverter;
import io.github.swagger2markup.builder.Swagger2MarkupConfigBuilder;
import io.github.swagger2markup.markup.builder.MarkupLanguage;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import java.net.URL;
import java.nio.file.Paths;

@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest
public class SpringbootApplicationTests {

@Test
public void generateMarkdownFile() throws Exception {
Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
.withMarkupLanguage(MarkupLanguage.MARKDOWN)
.build();

URL apiUrl = new URL("http://localhost:8080/v2/api-docs");
// 指定文件名称
String markdownFileName = "src/docs/markdown/generated/MSBlog_Server_API";
Swagger2MarkupConverter.from(apiUrl)
.withConfig(config)
.build()
//指定生成目录下生成指定文件
.toFile(Paths.get(markdownFileName));
}
}

编写完成后,可以运行 generateMarkdownFile 这个方法,右键该方法,出现弹框如下图:

1568529858131

直接选择 Run generateMarkdownFile 即可开始。

不出意外的话,会失败并且报如下错误:

1
Failed to read the Swagger source

我们要从 http://localhost:8080/v2/api-docs 读取内容,所以首先需要将主工程运行起来即运行项目。运行成功之后,再来执行测试代码就可以成功了。

生成的文件如下:

1568529858131

此时的我情不自禁的哼起了:“只要人人都献出一点爱,世界将变成美丽的人间” 的歌词,卧槽+n…总算可以了。

同理,也可以使用下面的方法生成 adoc 格式(这种格式的文档需要使用其他工具生成 PDF 或者 HTML 文件)的文档,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void generateDocsFile() throws Exception {
Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
.withMarkupLanguage(MarkupLanguage.ASCIIDOC)
.build();

// 该地址不要写错
URL apiUrl = new URL("http://localhost:8080/v2/api-docs");
// 指定目录
String dirName = "src/docs/markdown/generated";
Swagger2MarkupConverter.from(apiUrl)
.withConfig(config)
.build()
//指定生成目录
.toFolder(Paths.get(dirName));
}

如果你在使用 generateDocsFile() 这个方法发生如下的错误:

1
java.lang.NoClassDefFoundError: nl/jworks/markdown_to_asciidoc/Converter

需要在你的 settings.xml 文件(一般在 C:\Users\username\.m2 目录下面,如果没有就新建一个吧)中增加阿里的镜像,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>https://maven.aliyun.com/repository/jcenter</url>
</mirror>
</mirrors>

</settings>

保存,然后去 IDEA 中重新运行测试方法即可。

如果你之前修改过 settings.xml 文件的位置,可以参考 idea设置maven配置文件setting.xml的位置 这篇文章去找到该文件再修改。

打烊,手工!


一件事无论太晚或者太早,都不会阻拦你成为你想成为的那个人,这个过程没有时间的期限,只要你想,随时都可以开始~

微服务: Swagger让你可以多抽一支烟

发表于 2019-09-22 | 分类于 Server |

简介

Swagger,可用于生成、描述、调用和可视化 RESTful 风格接口的API,是一套规范和完整的开发框架,并且能对接口进行单独测试。

另外, Swagger 在 Github 上面是开源的。

无论对于后端开发,还是前端开发以及测试同事,Swagger 都可以基本满足使用需求。

在 SpringBoot 中集成 Swagger,后端同事写完接口就可以自动生成API文档,可以给到前端同事看,测试同事可以直接测试该接口。

在本篇中,跟大家分享如下内容:

1、在 SpringBoot 中项目如何集成 Swagger?

2、如何使用 Swagger,如何在不同的环境中开启和关闭 Swagger?

3、如何将同类的接口使用 Swagger 注解进行聚合?

更多关于 SpringBoot 的文章,可以点击 微服务项目系列文章 了解,完整代码示例请前往 Github 查看。

集成 Swagger

在 MavenEwpository 搜索 Springfox,可以找到 Swagger2 和 Swagger UI,如图所示。

1568529858131

截止到目前, Swagger2 和 Swagger UI 最新版本是 2.9.2 版本。

修改工程的pom文件,增加 Swagger2 和 Swagger UI 的依赖,如下:

1
2
3
4
5
6
7
8
9
10
11
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

因为我的项目已经集成了 starter-web,如下 pom 文件中的 dependency:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

这样默认会集成 com.fasterxml.jackson.core: 相关的库,不需要额外再去集成 jackson-databind 了,如下图所示:

1568529858131

否则你需要单独增加 jackson-databind 的依赖,同样的道理你可以在 MavenEwpository 搜索 jackson-databind 选择合适的版本即可。

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>

工程已经集成了 Swagger,接下来我们看看如何使用。

使用 Swagger

1、创建Swagger配置

新建一个配置类 MSSwaggerConfig,该文件名称和位置你可以放到你的工程的任意目录,根据自己的项目目录来放置即可。

MSSwaggerConfig 示例代码如下:

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
@Configuration
@EnableSwagger2
public class MSSwaggerConfig {

@Bean
public Docket msblogDocket() {
Docket docket = new Docket(DocumentationType.SWAGGER_2);
String pkgName = "com.veryitman.springboot.controller";
return docket.apiInfo(msblogAPIInfo()).select()
.apis(RequestHandlerSelectors.basePackage(pkgName))
.paths(PathSelectors.any())
.build();
}

private ApiInfo msblogAPIInfo() {
ApiInfoBuilder apiInfoBuilder = new ApiInfoBuilder();
String apiTitle = "MSBlog Server API";
String apiDes = "API for MSBlog";
String apiVersion = "1.0.0";
String homePage = "http://veryitman.com";
String emailUrl = "veryitman@126.com";
Contact contact = new Contact("itman", homePage, emailUrl);
return apiInfoBuilder.title(apiTitle).description(apiDes)
.version(apiVersion)
.contact(contact)
.license("Apache 2.0")
.licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html")
.build();
}
}

记得添加 @Configuration、 @EnableSwagger2 这两个注解。

对于 apis(RequestHandlerSelectors.basePackage(pkgName)) 目的是只扫描指定包名下面 Controller 的 Swagger 注解,这样就不会去扫描其他包下面的类了。

对于这个配置类,主要是用来生成一些摘要信息,如图:

1568529858131

2、注解 Controller

在 微服务-简单的用户名注册和登录 给大家分享了如何写注册和登录的API,今天还是拿注册的例子来进行 Swagger 的演示。

下面代码是用户注册的示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping(value = "signup")
public class MSSignupController {

@CrossOrigin(origins = {"*"})
@PostMapping(value = "/name")
@ApiOperation(value = "用户注册", httpMethod = "POST", notes = "用户名和密码注册")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "注册的用户名", required = true),
@ApiImplicitParam(name = "userpwd", value = "注册的密码", required = true)
})
public MSResponse signup(@RequestParam(value = "username") String userName, @RequestParam(value = "userpwd") String userPwd) {
// 省略
}
}

可以看到如下的注解:

1
2
3
4
5
@ApiOperation(value = "用户注册", httpMethod = "POST", notes = "用户名和密码注册")
@ApiImplicitParams({
@ApiImplicitParam(name = "username", value = "注册的用户名", required = true),
@ApiImplicitParam(name = "userpwd", value = "注册的密码", required = true)
})

这些注解就是 Swagger 的注解,@ApiOperation 说明了该接口的用途,@ApiImplicitParams 中有两个 @ApiImplicitParam 用来对接口的参数进行说明。

如果接口只有一个参数,可以直接使用 @ApiImplicitParam 注解即可。

3、在线文档

配置完成之后,可以启动 SpringBoot 项目,在浏览器中打开下面地址,就可以看到生成的在线文档了。

1
http://localhost:8080/swagger-ui.html#/

1568529858131

点击对应的 Controller 就可以看到对应接口的详细说明了,并且还可以对接口进行测试。

分环境开启 Swagger

在实际项目中,我们一般只会在开发和测试环境使能 Swagger,在沙盒和生成环境会关闭 Swagger,那如何控制呢?

在工程的 Resources 目录下面,新建几个跟环境相关的 properties 文件。

这些文件的命令要满足 application-{profile}.properties 的格式,其中 {profile} 你可以自定义名字,如下我自定义了4个环境,分别是开发、生产、沙盒和测试环境。

1568529858131

我们知道,在 application-{profile}.properties 中配置的选项与在 application.properties 中配置的选项如果名称相同,优先会使用 application-{profile}.properties 中配置。

在 application.properties 中配置如下:

1
2
3
4
# 配置使用哪个环境
spring.profiles.active=dev
# 默认不启用 swagger
swagger.enable=false

对应的在 application-dev.properties 和 application-test.properties 中开启 swagger,如下:

1
2
# 启用 swagger
swagger.enable=true

选项配置完成后,我们在代码中使用该选项,修改 MSSwaggerConfig 如下(只列出了修改部分的关键代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
@EnableSwagger2
public class MSSwaggerConfig {

@Value("${swagger.enable}")
private boolean enableSwagger;

@Bean
public Docket msblogDocket() {
Docket docket =
new Docket(DocumentationType.SWAGGER_2).enable(enableSwagger);
String pkgName = "com.veryitman.springboot.controller";
return docket.apiInfo(msblogAPIInfo())
.select()
// 只扫描指定包名下面的Controller中的Swagger注解
.apis(RequestHandlerSelectors.basePackage(pkgName))
.paths(PathSelectors.any())
.build();
}

在下面的代码中读取配置

1
2
@Value("${swagger.enable}")
private boolean enableSwagger;

然后调用 Docket 的 enable(enableSwagger) 方法来决定是否开启 Swagger,如果配置不开启,效果图如下:

1568529858131

还有其他方式来控制在不同环境下配置 Swagger 是否开启,比如可以结合注解 @Profile 通过不同的 profile 给 Swagger 的依赖设置不同的 scope,还可以使用注解 @ConditionalOnProperty(name = "swagger.enable", havingValue = "true"),大家自行选择适合自己项目的方案即可,目的都是一样。

聚合接口

在上面实例中,我们可以看到登录和注册都属于用户模块,使用注解 @API 可以聚合显示这些接口。

在登录和注册的 Controller 中可以添加下面注解,从而来聚合显示接口。

1
@Api(value="xxx", tags="用户模块")

注册 Controller 添加注解,如下:

1
2
3
4
5
@Api(value="signup", tags="用户模块")
@RestController
@RequestMapping(value = "signup")
public class MSSignupController {
}

登录 Controller 添加注解,如下:

1
2
3
4
5
@Api(value="signin", tags="用户模块")
@RestController
@RequestMapping(value = "signin")
public class MSSigninController {
}

显示效果如下:

1568529858131


你眼睛看到的不一定是事实的全部~

微服务: 简单的用户名注册和登录

发表于 2019-09-15 | 分类于 Server |

内容概要

在本篇中我们要完成一个目标:提供注册、登录的接口给前端或者客户端来使用。

涉及到的内容主要有下面几个:

1、SpringBoot 中常用的注解如何使用?

2、 如何对接口进行单元测试?

其他相关的微服务文章,可以点击 微服务项目系列文章 了解。

常用注解

控制器 Controller 是 Spring 中最基本的组件,主要是处理跟用户交互的,一般每个业务逻辑都会有一个 Controller,提供 HTTP 请求接口,用户请求接口进行数据访问。

跟 Controller 相关的几个注解主要有 @Controller,@RestController,@RequestMapping,@PathVariable,@RequestParam,@GetMapping 等。

  • @Controller:标注 Controller 类,处理 HTTP 请求。
  • @RestController:标注 Controller 类,Spring 4 新加注解,相当于 @Controller + @ResponseBody ,主要是为了使 HTTP 请求返回数据格式为 json 格式,正常情况下都是使用这个注解。
  • @RequestMapping:配置 URL 映射,即请求的地址。
  • @PathVariable,@RequestParam ,@QueryParam ,@PathParam 注解,可以参考 微服务: 学习几个容易混淆的URL注解 这篇文章。
  • @GetMapping,@PostMapping,@PutMapping 称之为组合注解,它们等价于 @RequestMapping 单独指定映射再指定请求方法。举个例子,如下:
1
@RequestMapping(value = "/signup/name", method = RequestMethod.POST)

等价于下面的注解:

1
@PostMapping(value = "/signup/name")

编写实体

不管是登录还是注册,都是每个用户进行的操作,我们先定义用户实体类 MSUser,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import lombok.Data;
import java.io.Serializable;

@Data
public class MSUser implements Serializable {
private String userID;

// 账号名称,用于登录,不可以修改
private String accountName;

// 账号密码
private String accountPwd;

// 昵称默认和accountName一致,可以修改
private String nickName;

private Integer age;

private Integer gender;

// 座右铭;格言;箴言
private String motto;
}

大家可以看到,在实体类 MSUser 中,我们用到了注解 @Data ,该注解来源于 lombok,需要自己在IDEA中安装该插件,安装方法请大家自行搜索解决。

注解 @Data 相当于给我们自动实现了 Get 、Setter、toString、 equals 和 hashCode 方法以及构造方法,不需要我们自己再去写这些代码。

HTTP 的请求响应结果,也可以被封装为实体类,如下 MSResponse 就是用来承载HTTP返回的响应。

1
2
3
4
5
6
7
8
import lombok.Data;

@Data
public class MSResponse<T> {
private int code;
private String msg;
private T results;
}

编写接口

一般来说,用户注册需要将用户的信息存下来,存到数据库中方便后续使用这份数据,这个行为称之为 数据持久化 ,现在我们不做这个操作,只是纯粹的提供接口让用户可以注册、登录我们的系统,后续再来考虑和实现数据持久化。

这个阶段注册功能只提供 用户名+密码 的方式,该请求是个 POST 请求,主要核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
@RequestMapping(value = "signup")
public class MSSignupController {

@CrossOrigin(origins = {"*"})
@PostMapping(value = "/name")
public MSResponse signup(@RequestParam(value = "username") String userName, @RequestParam(value = "userpwd") String userPwd) {
MSResponse response = new MSResponse();
MSUser user = null;
if (null == userName || null == userPwd || userName.length() <= 0 || userPwd.length() <= 0) {
MSResponseEnum signupError = MSResponseEnum.SignupInvalidInfo;
response.setMsg(signupError.getMsg());
response.setCode(signupError.getCode());
} else {
response.setCode(MSResponseEnum.SUCCESS.getCode());
response.setMsg(MSResponseEnum.SUCCESS.getMsg());
user = MSUserUtil.createUser(userName, userPwd);
}

response.setResults(user);

return response;
}
}

同理,登录功能也只提供 用户名+密码 的方式,该请求是个 GET 请求,主要核心代码如下:

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
@RestController
@RequestMapping(value = "signin")
public class MSSigninController {

@CrossOrigin(origins = {"*", "http://localhost:8082"})
@RequestMapping(value = "/name", method = RequestMethod.GET)
public MSResponse sigin(@RequestParam(value = "username") String userName, @RequestParam(value = "userpwd") String userPwd) {
MSResponse response = new MSResponse();
MSUser user = null;
if (null == userName || null == userPwd || userName.length() <= 0 || userPwd.length() <= 0) {
MSResponseEnum responseEnum = MSResponseEnum.Login4SiginInvalidInfo;
response.setCode(responseEnum.getCode());
response.setMsg(responseEnum.getMsg());
} else {
user = MSUserUtil.createUser(userName, userPwd);
MSResponseEnum rspEnum = MSResponseEnum.SUCCESS;
response.setCode(rspEnum.getCode());
response.setMsg(rspEnum.getMsg());
}

response.setResults(user);

return response;
}
}

单元测试

在 IDEA 中新建 SpringBoot 工程后,默认就会创建一个测试目录。

即 test 目录下会有 SpringbootApplicationTests 类文件,其中 pom 文件中已经添加好了 spring-boot-starter-test 启动器。

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

springboot-test 包中已经包括了 junit 和 mockito 类库,不需要我们额外再去添加这些库。

借助于 MockMvc 我们可以对接口进行简单的单元测试了。主要的测试核心代码如下:

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
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest
public class SpringbootApplicationTests {

@Autowired
private MockMvc mvc;

/* 登录的接口测试,GET 请求 */
@Test
public void signinTest() throws Exception {
//直接写接口的映射地址就可以了,不需要写host和port
String url = "/signin/name?username=itman&userpwd=123";

MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(url)
.accept(MediaType.APPLICATION_JSON)) //断言返回结果是json
.andReturn();

MockHttpServletResponse response = mvcResult.getResponse();
//HTTP响应的返回码
int status = response.getStatus();
//HTTP响应的内容
String contentAsString = response.getContentAsString();

System.err.println(status);
System.err.println(contentAsString);
}

/* 注册的接口测试,POST 请求 */
@Test
public void signupTest() throws Exception {
String url = "/signup/name";
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post(url)
.accept(MediaType.APPLICATION_JSON)
.param("username", "itman")
.param("userpwd", "123567"))
.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
int status = response.getStatus();
String contentAsString = response.getContentAsString();
System.err.println(status);
System.err.println(contentAsString);
}
}

在上面编写的测试代码中,解释一下几个用到的注解和类。

1、@RunWith(SpringRunner.class) ,SpringRunner 是 SpringJUnit4ClassRunner 的简写,用于提供测试时的 Spring 应用上下文信息。

2、MockMvc ,MockMvc 是测试 Spring MVC 应用程序的主要入口,为我们的测试提供了一个模拟的应用上下文的环境。

3、@Autowired,可以对类成员变量、方法及构造函数进行标注,完成自动装配。不需要手动创建该对象,这个跟 Spring 的 IOC 机制有关。

代码写好之后,就可以进行单元测试了,直接在 signinTest 或者 signupTest 右键运行即可,不需要启动整个工程。如下图所示:

1568529858131

运行之后,可以在输出的控制台中看到信息:

1568529940948

前端请求

在 跨域和OPTIONS这对欢喜冤家 和 减少跨域中的OPTIONS请求 这两篇文章中,已经跟大家分享过如何在前端页面中进行接口访问了,其中也重点讲解了如何在 SpringBoot 中解决跨域问题,这里不再赘述,感兴趣的朋友可以去看看这两篇文章。

文中的完整示例代码都在 Github 上面,需要的可以自行clone,感谢您的阅读。


临渊羡鱼不如按退而结网~

减少跨域中的OPTIONS请求

发表于 2019-09-08 | 分类于 Server , 网络协议 |

简介

这篇是继 跨域和OPTIONS这对欢喜冤家 后的一篇文章,在本篇中我们继续探索跨域中的 OPTIONS 请求,主要分享一下:

  • SpringBoot 中除了 CrossOrigin 注解外还有哪些方式可以解决跨域问题?

  • 如何使用 SpringBoot 结合 CORS 减少 OPTIONS 请求?

阅读下面内容之前,强烈建议先阅读之前的 跨域和OPTIONS这对欢喜冤家 这篇文章。

文中使用的代码都可以在 Github 找到,大家根据需要自行采纳。

用上 Nginx

这次使用 Nginx 作为 Web 容器,在本地将HTML跑起来,上次是在 Chrome 中直接打开 HTML 文件的方式来验证跨域问题的。我们知道只要端口不同也会造成跨域问题,那么只需要在 Nginx 中配置一个端口不同于服务端口的虚拟主机就可以达到目的了。

由于我是在 Win10 上面做的例子,包括 Nginx 也是 Windows 版本的,如果没有安装的小伙伴请去 下载Nginx 直接解压即可。

我把 Nginx 解压放到:D:\portable\nginx-1.15.12 这个目录,你可以解压到你认为比较合适的地方。

打开 Nginx 的配置文件 nginx.conf 即在 安装目录\conf 下面,增加一个虚拟主机配置项,修改完成后保存配置文件即可。

1
2
3
4
5
6
7
8
9
10
11
# 自定义虚拟主机,可以同时配置多个虚拟主机
server {
listen 8082; # 不同于服务端口
server_name localhost;
location / {
# 文件路径,注意路径分隔符是 `/` 不是 `\`
root E:/examples/cors-options;
# 默认页面
index index.html index.htm;
}
}

注意:E:/examples/cors-options; 是我的 HTML 文件(options.html )路径,你要根据自己实际HTML路径来配置这个选项,否则后面无法打开该文件。

这里我把这个文件命名为 options.html,HTML文件内容大致如下:

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
<html>
<head>
<meta charset="utf-8">
<title>options-demo</title>
<script type="text/javascript" src="./jquery-3.4.1.min.js"></script>
<script type="text/javascript" src="./jquery.json.js"></script>
<script>
function getReq() {
var url = "http://localhost:8080/signin/name?username=jack&userpwd=123";
$.ajax({
url: url,
type: 'GET',
dataType: 'json',
contentType: 'application/json',
headers: {
token: "yu7rX98xxxx_iii^ddd",
userId: 123,
openid: 231232
}
}).done(function (result) {
console.log("success");
console.log(result);
}).fail(function () {
console.log("error");
})
}
</script>
</head>
<body>
<button onclick="getReq()">用户名登录-GET</button>
</html>

现在启动 Nginx,启动 Nginx 很简单,打开 Windows 终端或者 Git 的终端(如果你安装了 Git 的话,即使没有安装我也强烈建议你安装,因为太好用了),然后 cd到 Nginx 的安装目录。

1
start nginx.exe

打开 Chrome 浏览器,输入下面的网址进行访问:

1
http://localhost:8082/options.html

不出意外的话,可以看到显示一个按钮的视图,顺便把 Chrome 的审查视图(Ctrl+Shift+i)打开,大概是下面截图的样子。

指定域名列表

Nginx 配置完成,文件也可以正常的打开,那我们就来试试是否跟预想的一致?

SpringBoot 关键示例代码如下:

1
2
3
4
5
6
7
8
@RestController
@RequestMapping(value = "signin") // 注意这里不要在signin前后加"/"
public class MSSigninController {
@RequestMapping(value = "/name", method = RequestMethod.GET)
public MSResponse sigin(@RequestParam(value = "username") String userName, @RequestParam(value = "userpwd") String userPwd) {
// 省略
}
}

启动 SpringBoot 服务(默认运行在8080端口不同于网页运行端口8082)完成后,点击 Chrome 视图中的按钮进行请求,截图如下:

可以看到跟我们预期一致,的确造成了跨域请求,并且进行了 OPTIONS 请求。

在 SpringBoot 的某个方法上面添加 CrossOrigin 注解可以解决跨域问题,并且可以指定域名列表,示例代码如下:

1
2
3
4
5
6
7
8
public class MSSigninController {
// 指定域名列表
@CrossOrigin(origins = {"http://localhost:8082"})
@RequestMapping(value = "/name", method = RequestMethod.GET)
public MSResponse sigin(@RequestParam(value = "username") String userName, @RequestParam(value = "userpwd") String userPwd) {
// 省略
}
}

重启服务后,再去验证一下发现请求就可以通过了。

如果你感兴趣,可以修改一下 CrossOrigin 注解中的域名列表端口号,再去请求就会失败。

使用 CrossOrigin 注解指定域名列表,可以从更小的粒度上面控制跨域请求。

那么除了使用 CrossOrigin 注解意外,在 SpringBoot 中还可以怎样解决跨域问题呢?

解决跨域的其他计策

目前除了使用 CrossOrigin 注解,还可以使用下面几种方法来解决跨域问题。

1、自定义Filter

自定义 Filter 可以解决跨域问题,示例代码如下:

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
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Component
public class MSCorsFilter implements Filter {

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
if ("OPTIONS".equals(request.getMethod())) {
System.out.println("HTTP's OPTIONS Coming");
}

HttpServletResponse response = (HttpServletResponse) res;

// 设置所有的请求域名都可以
response.setHeader("Access-Control-Allow-Origin", "*");

// 设置允许的请求方法
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
// 设置缓存时间单位为秒,在改时间内不需要再发送预检验请求,即缓存该结果
// 设置为0就相当于不设置缓存,即每次都会有OPTIONS请求
response.setHeader("Access-Control-Max-Age", "0");

// 设置允许跨域请求包含content-type头
response.setHeader("Access-Control-Allow-Headers", "*");

System.out.println("Filter has been used.");

chain.doFilter(req, res);
}

public void init(FilterConfig filterConfig) {
}

public void destroy() {
}
}

这里我们是通过设置 Access-Control-Allow-Origin 允许所有的域名(通配符*)都可以访问,如下:

1
response.setHeader("Access-Control-Allow-Origin", "*");

也可以设置指定域名才可以,示例如下:

1
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8082");

那我们需要思考一下了,setHeader 方法只能设置一个指定的域名,如果我想设置多个域名怎么办?

首先告诉你通过下面的方式肯定不行,如下:

1
2
response.addHeader("Access-Control-Allow-Origin", "http://localhost:8082");
response.addHeader("Access-Control-Allow-Origin", "http://localhost:8083");

具体原因大家可以看一下源码就秒懂了。

有一个解决方案,把可以通过跨域访问的域名做成数组也是大家在业务上面经常说的白名单,示例如下:

1
2
3
4
5
6
7
8
// 设置多个域名支持,类似白名单
String[] allowDomain = {"http://localhost:8082", "http://localhost:8083", "http://localhost:8085", "http://localhost:8087"};
Set allowedOrigins = new HashSet(Arrays.asList(allowDomain));
String originHeader = ((HttpServletRequest) req).getHeader("Origin");
System.out.println("originHeader: " + originHeader);
if (allowedOrigins.contains(originHeader)) {
response.setHeader("Access-Control-Allow-Origin", originHeader);
}

再来思考一个问题,上面那种方式自定义Filter会对所有URL即全局的请求都起作用了,能否对指定URL进行过滤呢?

做过Spring的同学肯定知道,我们可以设置 WebFilter,示例如下:

1
@WebFilter(urlPatterns = { "/signin/name" })

这样修改后,需要修改一下SpringBoot相关的代码,首先去掉 Component 注解,示例如下:

1
2
3
4
//@Component
@WebFilter(urlPatterns = { "/signin/name" })
public class MSCorsFilter implements Filter {
}

然后需要在 Application 中添加 ServletComponentScan 注解,示例如下:

1
2
3
4
@ServletComponentScan
@SpringBootApplication
public class SpringbootApplication {
}

结合 Filter 我们可以做出更细粒度更多功能来解决和控制跨域问题。Filter 这种方式对跨域的 GET 和 POST 请求都是支持的。

2、WebMvcConfigurationSupport

在 SpringBoot 中还可以自定义配置来解决跨域问题,通过继承 WebMvcConfigurationSupport 配置 CROS,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MSCorsConfig extends WebMvcConfigurationSupport {

@Override
protected void addCorsMappings(CorsRegistry registry) {
super.addCorsMappings(registry);
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600);
}
}

还可以指定 URL 和域名,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MSCorsConfig extends WebMvcConfigurationSupport {

@Override
protected void addCorsMappings(CorsRegistry registry) {
super.addCorsMappings(registry);
registry.addMapping("/signin/name")
.allowedOrigins("http://localhost:8082", "http://localhost:8083")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600);
}
}

这样配置后,只允许请求域名是 http://localhost:8082 和 http://localhost:8082 并且请求服务端URL是 /signin/name 的请求才可以使用 CORS 机制。

3、CorsFilter

这种方法早在 SpringBoot1.x 版本中使用,使用方法如下:

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
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class MSCorsFilterConfig {

private CorsConfiguration getConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 允许任何域名使用
corsConfiguration.addAllowedOrigin("*");
// 允许任何头
corsConfiguration.addAllowedHeader("*");
// 允许任何HTTP方法
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setMaxAge(60L);

return corsConfiguration;
}

@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source;
source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", getConfig());
return new CorsFilter(source);
}
}

这种方式跟上面的解决方法基本都是大同小异,大家根据实际情况选择使用,对提供的方法举一反三即可。

减少OPTIONS请求

虽然我们支持网页对服务端进行 OPTIONS 请求,但是请求如果多了势必会影响服务器性能。

如果在没有必要的情况下尽量减少由于跨域请求带来的 OPTIONS 请求,我们可以通过设置缓存时间来解决这个问题。

比如使用自定义 Filter 的方式设置600秒的缓存时间,示例代码如下:

1
response.setHeader("Access-Control-Max-Age", "600");

在大家进行测试的时候,记得不要勾选 Chrome 审查视图中 Network 选项中 Disable cache 这一项,否则每次都会进行 OPTIONS 请求,给你造成设置服务端缓存时间没有效果的假象。

常用Nginx命令

下面给出Windows版本的Nginx常用命令。

1、启动 Nginx

1
start nginx.exe

2、验证配置是否正确

1
nginx.exe -t

3、修改配置文件后,重新加载

1
nginx.exe -s reload

4、快速关闭

1
nginx.exe -s stop

5、正常关闭退出

1
nginx.exe -quit

6、查看Nginx版本

1
nginx.exe -V

坚持做好一件事,需要付出比常人更多的努力~

跨域和OPTIONS这对欢喜冤家

发表于 2019-08-31 | 分类于 Server , 网络协议 |

简介

相信做过前端开发的同事,包括做小程序或者小游戏的码友们应该都看过类似下面的错误,这个错误是由于 JavaScript 代码向服务器发送了 HTTP 请求引起的。

1
2
3
Access to XMLHttpRequest at 'http://www.xxx.com/yyy' from origin 'null' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

如果是第一次遇到,你肯定会觉得很好奇,忍不住会去一探究竟~

还有些同事会遇到另一个神奇的错误,即发送GET或者POST请求之前,居然先给服务器发送了一个 OPTIONS 请求,让人不可思议的是这个 OPTIONS 请求是自动发的,服务器在没有任何设置的条件下直接将这个请求夭折掉,如下返回 403 错误,也可能是其他错误。

1
OPTIONS http://www.xxx.com/yyy 403

引起这些问题的罪魁祸首就是 跨域 ,今天我跟大家一起以实际的例子来看看这个神奇的 跨域 问题。

文中使用的代码都可以在 Github 找到,大家根据需要自行采纳。

同源策略

域,是指由 协议 + 域名 + 端口号 组成的一个虚拟概念。

如果两个域的 协议、域名、端口号 都一样,就称他们为同域,但是只要三者之中有一个不一样,就不是同域。

那么 跨域请求 简单来说,就是在一个域内请求了另一个域的资源,由于域不一致会有安全隐患如 CSRF (Cross-site request forgery)攻击。

在百度百科里面是这样定义 同源策略 的,如下:

1
2
3
同源策略(SOP,Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。

可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

听起来,这玩意挺高大上的,简单理解 同源策略 就是一种安全策略,为了安全而生的一种限制措施。它是由 Netscape 提出的一个著名的安全策略,现在所有支持 JavaScript 的浏览器都会使用这个策略,也是必须遵守的一个策略。

那么 同源策略 中的 同源 是指 域名,协议,端口 三者必须相同,如果有任何一个不同就会引起跨域。

下表给出了相对 http://a.xx.com/yy/zz.html 同源检测的示例:

URL 结果 原因
http://a.xxx.com/ff/other.html 成功 域名、协议、端口(默认80)一致
http://a.xxx.com/gg/hh/another.html 成功 域名、协议、端口(默认80)一致
https://a.xxx.com/secure.html 失败 不同协议 ( HTTPS和HTTP )
http://a.xxx.com:81/dir/etc.html 失败 不同端口 ( 81和80)
http://a.wpq.com/yy/other.html 失败 不同域名 ( xxx和wpq)
http://123.21.122.12/dir 失败 域名IP不等同于域名
http://xx.xxx.com/dir2/ 失败 主域相同,子域不同

简单来说,HTML 代码运行在一个web主机上面(假设域名是 http://a.xx.com/yy/zz.html),而HTML代码中有需要请求服务器某 API 接口(http://api.user.com/name)的,那么就会造成跨域问题。

同源策略会影响:

(1) Cookie、LocalStorage 和 IndexDB 无法读取;

(2) DOM 无法获得;

(3) AJAX 请求不能正常发送,有可能还会引起 OPTIONS 请求;

OPTIONS请求

大家所熟知的HTTP请求最多的应该就是 GET 和 POST 请求,这两种请求也是软件开发中用的最多的。

GET:向特定的资源发出请求,一般对服务器来说是一个只读的请求,不会对资源进行写操作。

POST:向指定资源提交数据进行处理请求,例如提交表单或者上传文件,数据被包含在请求体(body)中,该请求可能会对服务器资源进行读写操作。

除了这两种请求外,HTTP还有其他种类的请求,如下:

PUT:向指定资源位置上传其最新内容,一般用于资源的整体更新,而下面的 PATCH 用于资源的部分更新。

DELETE:请求服务器删除所标识的资源。

HEAD:向服务器索要与 GET 请求相一致的响应,只不过响应体将不会被返回,可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。

TRACE:回显服务器收到的请求,主要用于测试或诊断。

OPTIONS:返回服务器针对特定资源所支持的 HTTP 请求方法。也可以利用向Web服务器发送 ‘*’ 的请求来测试服务器的功能性。该请求不会修改服务器资源,相对比较安全。

CONNECT:是 HTTP/1.1 协议预留的,能够将连接改为管道方式的代理服务器。通常用于 SSL 加密服务器的链接与非加密的 HTTP 代理服务器的通信。

PATCH:是对 PUT 方法的补充,用来对已知资源进行局部更新。当资源不存在时,PATCH 会创建一个新的资源,而 PUT 只会对已存在的资源进行更新。

其中 GET, POST 和 HEAD 方法是 HTTP1.0 定义的三种请求方法,在 HTTP1.1 又新增了六种请求方法,即 OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。如果想了解更多 HTTP 历史的朋友,可以阅读我之前的写的一篇文章 HTTP 演进史,嘿哈🙋‍。

再说一下 OPTIONS 请求,该请求与 HEAD 请求有点类似,一般也是用于客户端查看服务器的性能。

OPTIONS 方法会请求服务器返回该资源所支持的所有HTTP请求方法,该方法会用来代替资源名称,向服务器发送 OPTIONS 请求,可以测试服务器功能是否正常。JavaScript 的 XMLHttpRequest 对象进行CORS跨域资源共享时,就是使用 OPTIONS 方法发送嗅探请求,以判断是否有对指定资源的访问权限。

那么需要满足哪些条件才会触发 OPTINS 请求呢?

实例验证

在没有回答上面的问题之前,我们还是来做个实验吧~

你需要将 Chrome 浏览器的审查视图打开,最好把 Disable Cache 也勾选上禁止 Chrome 使用网络缓存,这样才不会影响下面的实验。

img

下面是 Springboot 关于登录的一个示例代码,如下:

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
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "signin")
public class MSSigninController {
@RequestMapping(value = "/name", method = RequestMethod.GET)
public MSResponse sigin(@RequestParam(value = "username") String userName, @RequestParam(value = "userpwd") String userPwd) {
MSResponse response = new MSResponse();
MSUser user = null;

if (null == userName || null == userPwd || userName.length() <= 0 || userPwd.length() <= 0) {
MSResponseEnum responseEnum = MSResponseEnum.Login4SiginInvalidInfo;
response.setCode(responseEnum.getCode());
response.setMsg(responseEnum.getMsg());
} else {
user = MSUserUtil.createDefaultUser(userName, userPwd);
MSResponseEnum rspEnum = MSResponseEnum.SUCCESS;
response.setCode(rspEnum.getCode());
response.setMsg(rspEnum.getMsg());
}

response.setResults(user);

return response;
}
}

你大可不必去了解这个代码的具体逻辑,现在你只需要知道他是用来给 JavaScript 调用的一个登录API即可。

再来一个 HTML 文件,模拟请求登录的API,请求 HTTP 使用 Ajax,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
function getReq() {
var url = "http://localhost:8080/signin/name?username=jack&userpwd=123";

$.ajax({
url: url,
type: 'GET',
dataType: 'json',
}).done(function (result) {
console.log("success");
console.log(result);
}).fail(function () {
console.log("error");
})
}
</script>

使用 Chrome 浏览器直接打开这个HTML文件即可,然后启动 Java 服务,在浏览器中点击按钮进行 GET 请求。

此时请求会报下面的错误:

1
Access to XMLHttpRequest at 'http://localhost:8080/signin/name?username=jack&userpwd=123' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

确实是造成了跨域请求,导致请求失败。

但是令人遗憾的是并没有看到发出 OPTIONS 请求,使用 Fiddler 抓包,可以看到只有 GET 请求,如图所示:

img

难道是自己写代码的姿势不对吗?!

其实,在 HTML 中使用 HTTP 请求,发生 OPTIONS 请求是需要几个条件的:

  • 1、必须是跨域请求
  • 2、自定义了请求头
  • 3、请求头中的 content-type 是 application/x-www-form-urlencoded,multipart/form-data,text/plain 之外的格式

满足1和2或者满足1和3就会发生 OPTIONS 请求,首先我们确定了上面的示例是跨域请求,但是不满足后面的两个条件之一。

我们修改一下HTML代码增加一个 content-type,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
function getReq() {
var url = "http://localhost:8080/signin/name?username=jack&userpwd=123";

$.ajax({
url: url,
type: 'GET',
dataType: 'json',
contentType: 'application/json',
}).done(function (result) {
console.log("success");
console.log(result);
}).fail(function () {
console.log("error");
})
}
</script>

此时在浏览器中(需要使用 Chrome 的审查视图)可以看到报错信息:

1
2
3
OPTIONS http://localhost:8080/signin/name?username=jack&userpwd=123 403

Access to XMLHttpRequest at 'http://localhost:8080/signin/name?username=jack&userpwd=123' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

抓包工具中也可以看到发生了 OPTIONS 请求,如下图:

img

也可以自定义 Header 头来进行验证,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
function getReq() {
var url = "http://localhost:8080/signin/name?username=jack&userpwd=123";

$.ajax({
url: url,
type: 'GET',
dataType: 'json',
headers: {
token: "yu7rX98xxxx_iii^ddd",
userId: 123,
openid: 231232
}
}).done(function (result) {
console.log("success");
console.log(result);
}).fail(function () {
console.log("error");
})
}
</script>

验证结果和上面一致,也会发生 OPTIONS 请求。

再聊OPTIONS

在 RFC2616-HTTP/1.1 中关于 OPTIONS 有详细的描述,感兴趣的可以看一下 9.2 OPTIONS 小节。

OPTIONS 请求方法的主要用途有两个:

1、获取服务器支持的 HTTP 请求方法;

2、用来检查服务器的性能,如上面例子中的AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个 HTTP OPTIONS 请求头,用以判断实际发送的请求是否安全;

HTT P的 OPTIONS 请求,有很多地方也被称之为预请求或者预检请求,换句话说就是试探性的请求不算是正式请求。

为了避免对服务器产生一些副作用,类似上面例子中的网页中的请求就会产生 OPTIONS 请求,也算是一种对服务器的保护。只有当服务器允许后,浏览器才会发出正式的请求,否则不发送正式请求。

我们可以使用 curl 模拟 OPTIONS 请求,例如下面请求谷歌:

1
curl -i -v -X OPTIONS https://www.google.com

可以看到请求的响应情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
< HTTP/2 405
< allow: GET, HEAD
< date: Sat, 31 Aug 2019 02:17:03 GMT
< content-type: text/html; charset=UTF-8
< server: gws
< content-length: 1592
< x-xss-protection: 0
< x-frame-options: SAMEORIGIN
< alt-svc: quic=":443"; ma=2592000; v="46,43,39"
<
{ [5 bytes data]
100 1592 100 1592 0 0 13606 0 --:--:-- --:-- 13606HTTP/2 405
allow: GET, HEAD
date: Sat, 31 Aug 2019 02:17:03 GMT
content-type: text/html; charset=UTF-8
server: gws
content-length: 1592
x-xss-protection: 0
x-frame-options: SAMEORIGIN
alt-svc: quic=":443"; ma=2592000; v="46,43,39"

SpringBoot 解决跨域

话说,同源策略引起了跨域问题,本身是为了安全起见为何我们还要去解决这个问题呢?这是因为 Web 前端是我们自己开发的,也就是说我们是知道自己的 Web 请求是安全的(类似于白名单客户),就需要让它顺利访问后端服务,所以解决这个跨域问题势在必行。

解决跨越的问题,在网上有很多的路子,目前大概有下面几种解决方案,如下:

  • JSONP
    • 简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。但它仅支持GET方法不支持POST等其他请求方法,而且可能会遭受XSS攻击。
  • CORS
  • postMessage
  • websocket
  • Node 中间件代理
  • Nginx 反向代理
  • window.name+iframe
  • location.hash+iframe
  • document.domain+iframe

今天我们使用 SpringBoot 自带的注解来解决这个问题😃。

在说解决方案之前,还是先了解一下 CORS(Cross-origin resource sharing),其全称是”跨域资源共享”,是 W3C 的一个标准。

CORS 允许浏览器向跨源服务器发出 XMLHttpRequest 请求,从而克服了AJAX只能同源使用的限制。

CORS 需要浏览器和服务器同时支持。幸运的是目前几乎所有的浏览器都支持该功能,唯一美中不足的是IE浏览器的版本不能低于IE10。

实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。SpringBoot 自带注解 CrossOrigin 可以用来解决跨域问题。

修改一下 Controller 的代码,增加 CrossOrigin 注解,示例代码如下:

1
2
3
4
5
@CrossOrigin
@RequestMapping(value = "/name", method = RequestMethod.GET)
public MSResponse sigin(@RequestParam(value = "username") String userName, @RequestParam(value = "userpwd") String userPwd) {
// 省略
}

重新启动服务,抓包工具可以看到 OPTIONS 和 GET 请求都正常执行,返回码都是200。

img

可以针对某个方法添加 CrossOrigin 注解,也可以对整个 Controller 添加该注解。

关于 CrossOrigin 注解,大家可以自行实践,这里不再赘述。


一直坚持在学习的路上努力~

img

跳绳的故事

发表于 2019-08-24 | 分类于 随笔 |

夏天的太阳永远都是那么强劲有力,南方尤为突出。整个地面都冒着热气,像一双无形的双手紧紧地抱着你,马路上反射的光纵然你火眼金金也会心生敬畏。不知道你有没有发现,即使这么热的天,篮球场上还有很多“战士”奋不顾身的拼个你死我活。倒不是他们不怕热不怕累,而是因为他们喜欢这项运动更愿意为了这项运动而付出。

1

讲一个听来的故事~

公司将要进行跳绳比赛,各个小组积极备战,需要大家一起努力配合才能把绳跳好。

组长发现刚开始大家都很难保持动作的一致性,经过一番练习之后,大部分人能动作一致了,还有一小部分人做起来有点困难。

组长试着让有经验的人带着那些跳不好节奏的人,这样又练习了一段时间,绝大部分人已经能很好的配合了,其余跟不少节奏或者怎么都调教不好的人只能去啦啦队那里呐喊助威了。


在我们团队中也一样,有人能带着大家往前走,自然就会有人拖住大家的后腿,我曾今把这种现象叫做“相对论”。在团队中,发现有人掉队了或者很吃力的在前进,试着去调整一下他的工作或者找人帮帮他,经过一段时间的调整如果还是没有好转,只能让他另谋高就了,对大家都好。

虽然很残忍,但事实确实如此!

我们能做的就是找好自己的位置,并且在这个位置上发挥自己最大的价值。


时间可以改变一切,但你得做点什么!

<1…567…20>

193 日志
16 分类
163 标签
© 2024 veryitman