Blog

坑爹生活反思-买眼镜时记得别买塑料镜架

楼主去买眼镜时,一开始 买了 塑料的镜架,后来 掉皮了,然后 不小心  镜架尾 断了。所以 用 重新  买了 镜架。

===================================

相信大家都不太分得清板材眼镜架和塑料眼镜架,毕竟不是内行人。板材眼镜架和塑料眼镜架,看起来都一样,傻傻分不清楚。怎么区分眼镜架是板材的还是塑料的?小编来教大家。

宝姿 POU13501 男女通用 眼镜框 BK1黑框花腿

板材眼镜架

是由原材料加工压制板状后再重新切割打磨抛光而成,板材眼镜架密度高,由于板材眼镜架生产工艺复杂而生产成本较高,所以价格会比塑料镜架高许多。注意看,板材眼镜架在镜架边缘是没有注塑点的,而且有明显被切割过的纹理痕迹。

塑料眼镜架(又称注塑架)

博柏利 B2199-F 男士 眼镜框 3002玳瑁

是首先制作好模具,然后再注入液体塑料,冷却后再电镀上色,塑料眼镜架密度低,所以容易断裂。由于塑料眼镜架生产工艺简单,生产成本低,可以大量生产,所以款式变化很多,由于是电镀上色,所以花样繁多,色彩比板材架鲜艳多彩。请注意,塑料架一般都有注塑点,就是在镜架边缘留下一个小疤痕一样的点,而且塑料架会比板材眼镜架轻很多很多。

所以,板材眼镜架由于材料密度高,所以拿在手里比较重,扎实,手感较好。而塑料架由于材料密度低,所以比起板材架要轻很多。

同时,板材架镜腿里面的金属条,普遍较粗,而大部分塑料架的镜腿里面是没有金属条的,有少部分的塑料架镜腿里面虽然有金属条,但几乎都很细小,而且主要是圆形。

怎么区分眼镜架是板材的还是塑料的,这个新技能,你get到了吗?

=========================================

 

所有戴眼镜的人都会有种疑惑甚至愤怒:构造简单、技术成熟的眼镜,为何动辄500、高达数千??

我也疑惑,虽然我在眼镜行业好几年

但是,戴眼镜的朋友,却对眼镜知识、镜片知识、验光知识、行业情况、却又一无所知

信息不对称,造成了顾客和商家之间的鸿沟

互联网已经改变了书籍的销售模式,它会改变眼镜行业吗??

我想:首先,就是要把镜架、镜片、验光、行业内情等基本知识,准确的传达的总结给戴眼镜的人

只有信息对称,才能改变这个行业

朋友,我也是戴眼镜的人,先别忙着判断,看几条吧

如果这些知识对你有用,说到你心坎里,请给个顶字

我会写的很简明扼要,你花很小时间浏览,就能心里有数,毕竟,你无需掌握太

细:如钛架如何连铸压铸成钛条或钛片,又如何冲压、之后如何成型、如何电镀、

如何冷焊等,那就过了

放心吧,先让你很简单、很快的学完镜架镜片验光等核心知识,

我会告诉你整个行业全国的配镜现状,各类优劣,但是不能提名啊,一是避广告之嫌,二是论坛的老规矩大家都知道。
在眼镜行业工作多年后,我深知昂贵的店租和人工,是一副眼镜动则几百上千,顾

客叫苦连天的真正原因

最大的难题就是顾客大多不懂眼镜,不懂镜片,不懂镜架好坏,不懂验光

一句话,好眼镜和烂眼镜,很多顾客分辨不出来

只是店员说啥就是啥

所以我决心把那些很简单的分辨知识写出来

如果你戴眼镜,也是这样

请顶顶我,让更多人看到

第一章 眼镜知识提纲

接着我会从以下多个方面来向大家普及相关眼镜知识和一些行业现象,欢迎

各位眼镜人踊跃发问。
1 镜架目前的分类,各类镜架的材质和特点
2 一副好镜架如何辨别
3 中国几个地方产的镜架的特色
4 所谓名牌尾货的镜架(如同LV尾货)的真实内幕
5 镜片的分类
6 店里常见的镜片的特点
7 镜片好坏如何辨别
8 店里最后给你配的镜片,如何确保货要对板
9 镜片的日常维护与误区

看完这些,你就能辨别一副眼镜的好坏,而不是随便听推荐。

中间当然可能会穿插各种因为很多网友的问题而引申出来的很多信息。

第二章 中国镜架生产、质量、分布等大致情况

中国其实是眼镜生产大国,几个生产集散地的状况

—配镜时,问清楚,你的眼镜,是温州、丹阳、还是深圳横岗产的

中国是全世界最大的眼镜生产国和出口国,全球80%的眼镜,都是中国生产

但高端的眼镜镜架,设计和品牌在境外,国内的工作来加工

中国的眼镜分布在:

丹阳:江苏的丹阳,主要是生产批量大、价格低的镜架,出口到国外,国内也有大量的销售

丹阳品质就一定低吗?? 其实眼镜镜架,质量都过关,特别是镜架,主要是材质、电镀、加工等,价格低,不一定品质差,但丹阳,是低价低品质的代名词,跟事实上不是如此

温州:浙江温州,各位都会有印象,他们的路线和丹阳,基本是同类

深圳: 就是这! 生产这全球80%的高级眼镜,价格也高很多,当然,深圳就集中在横岗、有不少港资、台资的工厂,他们由于成本也高,价格也好,因此,用材比较好、技工设备也先进,因此,是全球高级眼镜的生产地

镜片:中国的镜片大厂,大部分集中在上海、江苏、浙江一带,下回重点说说镜片

因此,其实眼镜架,本身不是一个高科技的产品,而且中国工业发展这么多年,对于这类金属和塑胶加工工艺,涉笔和技术,都很成熟,所以,只是用材不一样,和材料成本不一样,以及生产手工和工艺,不同品种会有不同的区别

这是生产分布的情况—中国式眼镜大国

你的理解这些,大致就知道店里的货品情况。

第三章 镜架的材质和品质眼镜框材质总的来说分金属类和塑料两大类

金属类分为:钛材质和合金材料,不锈钢材质

塑胶类分为:厚重的板材、轻薄的TR材质、最近才开始的ULTEAM材料,以及其他

塑胶制品类眼镜

钛材质分为:合金钛、纯钛、B钛(超薄超轻)

总之,目前就材料档次而言,超薄B钛材的档次最高、纯钛其次、板材类为:超轻

薄的ULTEAM材料(美国通用的材料,韩国生产,国内还生产不好),板材次之

但从工艺和做工方面,又不一定最好的材质,一定是最贵的眼镜,有的名牌眼镜,

就是塑胶板材,但每个细节很完美,也很昂贵

市面上流行过一种说法,说中国丹阳的眼镜按斤卖,下面是我整理的一点信息,希

望看完后,让大家知道:

镜架是贴脸的,涉及美观、气质和健康

也让大家知道,垃圾的东西,不是最好的选择

还有,镜架,特别是好的镜架,不是传说中的那么的廉价:

第四章 镜架分类之一:钛架
我一直认为,今后金属架里面,钛架会成为主流,一是生物相容,二是轻巧,三是档次。店里开价800或1000以上的,也多是钛架

但是,钛架和钛架的差别,可是大的去了

1 目前最贵最好的钛架:B钛,超薄、超轻,但是加工难度大、成型难度大,需要

更好的设备和更高的坏品率,所以成本也相对是最高的,一个超薄超轻的钛片架子,大

点的店面,都1500以上了,如果有点品牌,更高

2 纯钛:一般以钛柱子的形式出现,比其他金属轻很多,但比钛片重,形状也没有

片状的好看,这是很多店里的主流的钛产品

3 合金钛:就是钛和其他金属的合金,很多是比较低端的钛产品,但也有做的好

的,我是说大致的情况

钛为啥贵,他的好处是什么呢?

1 轻薄: 眼镜的轻薄,一定是个趋势,毕竟戴在眼睛上的东西,但无框眼镜,又

不是每个人喜欢的,不同的气质,因此,半框和全框眼镜,钛是最好的选择之一

2 安全:生物相容性,钛是比较好的,就是对人体无害或者伤害最小—-还记得

早年我们戴的眼镜的那种铜绿吗? 其实不好的镜架,对人体是有害的

记住了,钛是金属的首选(当然你不喜欢,喜欢其他金属也没有问题),钛也最

贵,所以要搞清楚再下手!

店面贵,是因为成本太高,其实合理的价格,也不用那么贵。

第五章镜架之塑胶架

1 塑胶大类:塑胶是个很广泛的含义,非金属的镜架,基本就是塑胶大类的

A 传统的塑胶,就是板材,很厚的那种,当然做的好的,也很有气质,但

是还是很重,塑胶和金属铰链,有时候容易坏—但板材,还是目前普通眼镜的主

流之一

B TR90:是今年韩国兴起的新材料,很轻、很软、不伤人,可以是黑色的、

可以是透明的,TR90为韩国做的最好,很多韩剧里面,那些看起来很轻的眼镜

其实就是TR90的材料

—在十年面上,现在大部分是中国生产的TR90,确实工艺方面和材质方面,

比不过韩国,但是成本低,大部分店里,都卖国产的,同样配一副,利润高很多,

而顾客,根本不明白韩国产和中国产。

C ULTEM材料:是一种美国通用的航空材料,比TR更轻更柔然,很适合

接触批发,遗憾,又是韩国和日本最早生产,当然成本也比较高,大约2011年

底才开始来到中国,目前可以说很多内地的店面,根本店主都没听说过ULTEM

材料,UTLEA对人体伤害最小,当然,和皮肤接触,一般的健康材料,都问题

不大。

镜腿用塑料的(所谓的板材)我觉得相对来还是最安全,塑料这种东东做成

无毒是完全有可能的,但是你用的是不是无毒的我不知道。

第十章 怎样辨别镜片的好坏

怎么买镜片以及怎么判断镜片的质量

我们配镜的时候,很多人根本都没看镜片的包装、品名、日期、等,因为都是加

工好的
—建议下次配镜,一定好好看看,最好网络上查查,这个牌子的,哪儿产的,

是否有防伪等,

虽然你说没用,但是,多了解一下,特别是让店里把包装和查询防伪等给你

留着,也是一点监督吧

戴2年的眼镜,自己的镜片是哪儿产的、什么折射率、啥都不知道,光知道

花了400还是800

这可是有点荒唐!!

当然,肉眼是很难判断一个镜片的好坏的,只能从以下几个方面来看:

1 看看整体包装:让他给你看看镜片的整体包装,感受一下
2 看看出厂地点:出厂在中国、还是进口、是中国上海,还是中国浙江等,
3 看看防伪标志:防伪标志,不一定意味着就是好东西,但看看,把握一下
4 看看生产工厂:查询下生产工厂,地址、新闻等等

总之,镜片的判断,普通消费者很难,但你提出这些要求,店家至少会认为你比较专业,因此,不太敢给你弄低质的冒充高质的

眼镜行业是有些奇怪。顾客和商家,互相不信任,互相埋怨,甚至,顾客仇视商家

这一切,都应该从普及一些知识来开始

知己知彼,百战不殆第十一章 镜片的加工

镜片的加工:也就是打磨和安装,这点大家不要担心,现在都是比较自动化的设

备,并且,深圳大部分眼镜店,自己都不可能设独立的加工,都是第三方加工的,

所以品质都不会有什么大问题。眼镜片是否舒服其实取决于制镜师的本事。看现

在很多学生从一二百度一步步涨到六七百度,除与现在学业重有关外,与验光结

果不准,制镜不合理有绝对大的关系。

同样一副镜片,同样一个验光结果(后面我会专门讲讲验光的知识),不同

的制镜师用不同的工具做出来,效果是完全不一样的。

现在大店都是使免模的全自动机器,一体成型,制作精度基本可以满足用户

需要。

而大多数中档店都是使的手工模板的全自动机器,日本产的还行,国产的基

本上都有跑轴,左右不对称的现象,做出的镜片不敢否定也不敢肯定,碰吧。

至于小店拿手工轮,钳子做出的镜片,那你就别想了,想准那才怪呢。有人

说了,那小店不错呀,我都做好多年了,也挺舒服的。

你知道吗,人眼是人体内调节力最强的一个器官,你的眼睛适应他的眼镜了,

呵呵。

要想准,特别是有散光的,你一定要选半框拉丝的镜子,因为那是一体成型,

可以保障一点误差没有的。打孔的需要手工装配,极易偏差,全框很多时候也需

要手工磨边,有错的可能性。

因此,眼镜加工,也是眼镜的重要环节

有时候,你如果问店家,你什么时候可以做好,他说一天后,肯定是送到加工中心去加工,其实那里有最好的设备,最有保障

他如果说立等可取,一个店面都配一个20万的依视路全自动仪是不切实际的,他用什么加工我不敢推测,但是个人觉得,还是要求他去加工中心去加工

第十二章 镜片的维护和保养

你的镜片关键在保养和维护,保养好了,一般的镜片,2年是必须更换的,保养

不好的一年就会影响视力,应该更换了。你要是不换,磨花严重的后果,就是每

天跟随你的–杀手!

清洗:注意清洗的方式,超声波天天清洗是不现实的,就用水洗,但擦拭多注意

放置:最好不要让镜片接触硬物,侧面放置在桌台上,这个道理都懂

如果路过大店,有空的话,请他们洗洗眼镜,他们都是超声波清洗,比较彻底,基

本都是免费的

眼镜一旦刮花了,一定要注意,如果刮花严重,一定要及早更换,可以少吃一次西

餐,但千万不能忽视镜片

—镜片:灵魂、灵魂、灵魂

多关心下每天跟着你的那个镜片吧

 第十四章镜片、镜架、验光大总结

写了很多天了,我把之前的东西总结一下
1 中国眼镜大国,理论上供应资源丰富,但眼镜很贵,都是门店和人工、加上门店太多,每家店面做的生意太少,这就是真正的原因

2 好的镜架,要看材质、看做工、看细节、看整体风格设计等,比如,一个LOGO印刷的非常精美的镜架,他的材质做工,基本都不会差,而一个OSS镜架(BOSS没印刷好,B掉了,成了OSS),这样的山寨假名牌,他品质好不到哪儿去—明白了吗?

3 镜片好坏,只能问指标,肉眼很难辨别,但可以通过细节,看包装、看印刷、看厂家状态等,来基本判断你的镜片,还有一半的度数,无需过高折射率的镜片

4 验光是核心的环节,千万不能马虎,争取找有经验的人给你验光,而有经验的验光师,专业的人,在哪儿,如果店面你有把握,当然可以,医院个人觉得更靠谱一些,但是医生也有不良的,自己多当心

5 眼镜的保养,特别是镜片的保养,很重要,刮花了之后,一定要重视,严重的,要更换,刮花的眼镜,对自己视力伤害太大了

6 眼镜这个行业,和其他行业一样,有黑幕、有猫腻,也有真诚、有诚信,关键看你自己懂不懂,如果自己基本懂,懂行情,走到哪儿,都吃不了亏

mac踩坑-maven环境变量因文件夹隐藏后缀而写错

在mac 下 按照正常方式:配置maven 环境变量。

vim ~/.bash_profile

export JAVA_6_HOME=`/usr/libexec/java_home -v 1.6`
# 设置 JDK 7
export JAVA_7_HOME=`/usr/libexec/java_home -v 1.7`
# 设置 JDK 8
export JAVA_8_HOME=`/usr/libexec/java_home -v 1.8`

#默认JDK 7
export JAVA_HOME=$JAVA_7_HOME

#alias命令动态切换JDK版本
#alias jdk6="export JAVA_HOME=$JAVA_6_HOME"
alias jdk7="export JAVA_HOME=$JAVA_7_HOME"
#alias jdk8="export JAVA_HOME=$JAVA_8_HOME"

export M3_HOME=/Users/toy/Documents/maven/apache-maven-3.5.3
export PATH=$PATH:$M3_HOME/bin

export PATH=$PATH:"/Library/Tomcat/my_script_diy":"/Library/Tomcat/home/bin":"/usr/local/mysql/bin":"/Users/toy/Library/Android/sdk/platform-tools"
~                                                                               
~

注意配置路径的时候,路径名可以加分号,也可以不加。
export PATH 可以操作多次的。

编辑完maven后,保存。用命令行查看效果。

echo $PATH

/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/toy/Documents/maven/apache-maven-3.5.3/bin:/Library/Tomcat/my_script_diy:/Library/Tomcat/home/bin:/usr/local/mysql/bin:/Users/toy/Library/Android/sdk/platform-tools

然后 立即生效环境变量:
source ~/.bash_profile


正常情况下,应该没有问题了。

但是出现了 command not found 问题。我看 了一下,系统环境变量 路径都对的 啊。然后 进行排错。

1、文件权限,改完没有用

2、文件路径 改成了 Library 库目录里面,没有用。

3、利用Finder   go to folder  居然可以找到的。感觉路径的确没有问题啊 。

 

4.将 执行文件托人 terminal 中 发现 路径居然 有问题啊,问题的 关键找到了,

 

当时 原来是隐藏了 后缀名啊,巨坑。没想到 隐藏后缀名也能 用 Finder 搜索的到。坑爹。

mac常用操作-配置环境变量

经过测试 环境变量 配置时, 填写应用路径时 加双引号和 不加 双引号 都没有问题。
一、打开terminal,输入  echo $PATH  ,按回车执行命令查看当前变量值,这里我们将修改PATH变量来测试。

toydeMacBook-Pro:~ toy$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/toy/Library/maven/apache-maven-3.5.3/bin:/Library/Tomcat/my_script_diy:/Library/Tomcat/home/bin:/usr/local/mysql/bin:/Users/toy/Library/Android/sdk/platform-tools
toydeMacBook-Pro:~ toy$

二、先科普一下

mac 一般使用bash作为默认shell

Mac系统的环境变量,加载顺序为:
/etc/profile /etc/paths ~/.bash_profile ~/.bash_login ~/.profile ~/.bashrc
当然/etc/profile和/etc/paths是系统级别的,系统启动就会加载,后面几个是当前用户级的环境变量。后面3个按照从前往后的顺序读取,如果~/.bash_profile文件存在,则后面的几个文件就会被忽略不读了,如果~/.bash_profile文件不存在,才会以此类推读取后面的文件。~/.bashrc没有上述规则,它是bash shell打开的时候载入的。

如果没特殊说明,设置PATH的语法都为:
——————————————————-

# 中间用冒号隔开
 export PATH=$PATH:<PATH 1>:<PATH 2>:<PATH 3>:------:<PATH N>

三、(一)全局设置
下面的几个文件设置是全局的,修改时需要root权限

1)/etc/paths (全局建议修改这个文件 )
编辑 paths,将环境变量添加到 paths文件中 ,一行一个路径
Hint:输入环境变量时,不用一个一个地输入,只要拖动文件夹到 Terminal 里就可以了。

2)/etc/profile (建议不修改这个文件 )
全局(公有)配置,不管是哪个用户,登录时都会读取该文件。

3)/etc/bashrc (一般在这个文件中添加系统级环境变量)
全局(公有)配置,bash shell执行时,不管是何种方式,都会读取此文件。

4)
1.创建一个文件:
sudo touch /etc/paths.d/mysql
2.用 vim 打开这个文件(如果是以 open -t 的方式打开,则不允许编辑):
sudo vim /etc/paths.d/mysql
3.编辑该文件,键入路径并保存(关闭该 Terminal 窗口并重新打开一个,就能使用 mysql 命令了)
/usr/local/mysql/bin
据说,这样可以自己生成新的文件,不用把变量全都放到 paths 一个文件里,方便管理。

(二)单个用户设置

1)~/.bash_profile (任意一个文件中添加用户级环境变量)
(注:Linux 里面是 .bashrc 而 Mac 是 .bash_profile)
若bash shell是以login方式执行时,才会读取此文件。该文件仅仅执行一次!默认情况下,他设置一些环境变量
设置命令别名alias ll=’ls -la’
设置环境变量:

1

export PATH=/opt/local/bin:/opt/local/sbin:$PATH

2)~/.bashrc 同上

四、如果想立刻生效,则可执行下面的语句:
$ source 相应的文件
一般环境变量更改后,重启后生效。

如果你重启后生效了那就不用往下看了。
按照如上修改了~/.bash_profile文件发现无效,并且提示 zsh: command not found: homestead 那么肯定是安装了zsh的缘故,因为安装zsh,~/.bash_profile就不会被执行,解决办法如下:

1.打开vim ~/.zshrc 将你要配置到环境变量配置到该文件中即可

2.打开vim ~/.zshrc 添加source ~/.bash_profile ,这样~/.bash_profile配置的环境变量同样有效

zsh环境变量失效问题
新版的zsh在登录的时候默认只会加载~/.zshrc的配置文件,以前配置的环境变量会无效,尝试网上的各种方法均无效
发现配置文件有这样一条说明

If you come from bash you might have to change your $PATH.

于是简单粗暴的解决方案:
编辑~/.zshrc文件
在开头添加一下配置

export PATH=$HOME/bin:/usr/local/bin:$PATH
source $HOME/.bashrc
source $HOME/.bash_profile

参考:https://www.jianshu.com/p/8ef18883c958

五、以maven 作为环境变量 来举例 配置

  1. 打开terminel输入以下命令:

    vim ~/.bash_profile 打开.bash_profile文件,在次文件中添加设置环境变量的命令
    export M2_HOME=/Users/xxx/Documents/maven/apache-maven-3.5.0
    export PATH=$PATH:$M2_HOME/bin
    添加之后保存并推出,执行以下命令使配置生效:
    source ~/.bash_profile

  2. 查看配置是否生效

    1、输入:mvn -v命令,输入如下:

    Apache Maven 3.5.0 (ff8f5e7444045639af65f6095c62210b5713f426; 2017-04-04T03:39:06+08:00)
    Maven home: /Users/xxx/Documents/maven/apache-maven-3.5.0
    Java version: 1.8.0_121, vendor: Oracle Corporation
    Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre
    Default locale: zh_CN, platform encoding: UTF-8
    OS name: "mac os x", version: "10.12.6", arch: "x86_64", family: "mac"

    则配置成功。

参考自:https://blog.csdn.net/hlllmr1314/article/details/52228672

windows常用操作-配置环境变量

在安装软件时,有时候 我们需要配置一下 环境变量。

Windows系统中,只要  在 桌面 计算机图标 上 鼠标 右键  -》计算机属性

安装好后,重新退出 ,在配置一下,就能使环境变量生效了。或者重启计算机也能生效环境变量。(上图以配置maven环境变量为例)

windows命令行-概述-cmd文件和bat文件的区别

本质上没有区别,都是简单的文本编码方式,都可以用记事本创建、编辑和查看。两者所用的命令行代码也是共用的,只是cmd文件中允许使用的命令要比bat文件多。cmd文件只有在windows2000以上的系统中才能运行,而bat文件则没有这个限制。从它们的文件描述中也可以看出以上的区别:cmd文件的描述是“windows nt命令脚本”bat文件的描述是“ms dos批处理文件”

一个使用cmd运行的
一个用dos解释器解释运行

cmd.exe是XP下的命令行模式32位的(所以一些DOS命令无法使用)
cmd拿到DOS环境下运行不了,而bat在这些环境下都可以运行,因为cmd仅仅基于windows环境下的假DOS

maven安装与配置

一、需要准备的东西

1. JDK

2. Eclipse

3. Maven程序包

二、官网下载:
http://maven.apache.org/download.cgi

System Requirements

Java Development Kit (JDK) Maven 3.3+ require JDK 1.7 or above to execute – they still allows you to build against 1.3 and other JDK versions by Using Toolchains
Memory No minimum requirement
Disk Approximately 10MB is required for the Maven installation itself. In addition to that, additional disk space will be used for your local Maven repository. The size of your local repository will vary depending on usage but expect at least 500MB.
Operating System No minimum requirement. Start up scripts are included as shell scripts and Windows batch files.

因为 安装包是 java 写的,里面的启动文件 即有 Windows的mvn.cmd 也有linux的mvn  所以没有 按不同的系统发布不同的安装包。

 

Maven概述

由于学习大数据需要Maven,所以先简单学习一下Maven。Hadoop的MapReduce环境是一个复杂的编程环境,所以我们要尽可能地简化构建MapReduce项目的过程。

Maven是一个很不错的自动化项目构建工具,通过Maven来帮助我们从复杂的环境配置中解脱出来,从而标准化开发过程。所以,写MapReduce之前,让我们先花点时间把刀磨快!!当然,除了Maven还有其他的选择Gradle(推荐), Ivy….

摘自百科:

Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具
Maven 除了以程序构建能力为特色之外,还提供高级项目管理工具。由于 Maven 的缺省构建规则有较高的可重用性,所以常常用两三行 Maven 构建脚本就可以构建简单的项目。由于 Maven 的面向项目的方法,许多 Apache Jakarta 项目发文时使用 Maven,而且公司项目采用 Maven 的比例在持续增长。
Maven这个单词来自于意第绪语(犹太语),意为知识的积累,最初在Jakata Turbine项目中用来简化构建过程。当时有一些项目(有各自Ant build文件),仅有细微的差别,而JAR文件都由CVS来维护。于是希望有一种标准化的方式构建项目,一个清晰的方式定义项目的组成,一个容易的方式发布项目的信息,以及一种简单的方式在多个项目中共享JARs。
Maven是一个项目管理工具,它包含了一个项目对象模型 (Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。当你使用Maven的时候,你用一个明确定义的项目对象模型来描述你的项目,然后Maven可以应用横切的逻辑,这些逻辑来自一组共享的(或者自定义的)插件。
Maven 有一个生命周期,当你运行 mvn install 的时候被调用。这条命令告诉 Maven 执行一系列的有序的步骤,直到到达你指定的生命周期。遍历生命周期旅途中的一个影响就是,Maven 运行了许多默认的插件目标,这些目标完成了像编译和创建一个 JAR 文件这样的工作。
此外,Maven能够很方便的帮你管理项目报告,生成站点,管理JAR文件,等等。

科学上网-零基础搭建个人专线shadowsocks

一、首先购买一个海外vps ,安装centos7系统

VPS有:BandwagonHost,dataocean,vultr,linode。选择服务器地址时,可以先测试一下速度,选速度快的机房。

二、登录vps 并加固

1、利用SSH远程 登入 centos7,方便后续操作。(有些vps的ip可能被屏蔽了,所以需要换一个vps试试看)
SSH远程连接入门

2、加固 centos7 安全性:

(1)、更新centos7系统
(2)、解决系统编码问题
(3)、开启 防火墙。

systemctl start firewalld

(4)、修改SSH默认端口SSH只用密钥登录

3、安装VNC软件,进行远程访问。(可选操作,如果不适应用命令行操作,可以借助vnc 进行远程桌面操作)
(为了提高安全性,使用SSH端口转发,登入服务器)
vnc远程连接配置与入门

三、安装shadowsocks服务端

我们安装的是python版本的。因为centos7 默认自带了 python,所以不需要安装了。如果没有安装的话,可以运行

yum install python

这样子 也可以检查出,是否安装了python。

安装 pip :
1、可以用 VNC 远程登录 vps 主机,然后用浏览器 下载 pip 安装包。
下载页面:https://pypi.org/project/pip/9.0.1/
文件链接地址:https://files.pythonhosted.org/packages/11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/pip-9.0.1.tar.gz

为什么不选最新版,因为最新版pip 和 python 有一点 兼容性问题。
也可以在SSH中用命令行的方式 下载文件:

curl https://files.pythonhosted.org/packages/11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/pip-9.0.1.tar.gz -o pip.tar.gz --progress

文件下载完,可以用 VNC 桌面方式进行 解压。或者 在 SSH 中 用命令行 进行解压。

tar zxvf pip-9.0.1.tar.gz -C /root/mydir

将文件 解压到了mydir目录下面。
解压完成后,进入 mydir 文件夹。可以用vnc 直接进入。也可以用命令行

cd /root/mydir

只有进入到 mydir文件夹以后,下面的安装才能成功。
在SSH下或者vnc的终端运行命令行:

python setup.py install

(其实 用命令行 可以直接装 pip,但是安装的是最新版的pip,好像与python存在兼容性问题)

curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
python get-pip.py

2.安装 shadowsocks
在SSH或者VNC的终端下运行如下代码:

安装 加密软件 库 用于支持 aes-256-gcm 加密方式 (带#的,都是注释,不需要打 )
 yum install epel-release
 
yum install libsodium
 安装 或升级 最新 软件 版本
 pip install --upgrade git+https://github.com/shadowsocks/shadowsocks.git@master

3.配置shadowsocks
SSH下:

[bash] # 创建 /etc/shadowsocks.json vim /etc/shadowsocks.json [/bash]

上面就创建了配置文件,然后用 vim 编辑 下面内容就行了。

{
"server": "0.0.0.0",
"server_port": 3678,
"password": "sdfsndsfoni",
"method": "aes-256-cfb"
}

多余多个端口可以如下:(顺便说了一下,可以添加PID_FILE和LOG_FILE)

pidfile

{
  "server": "0.0.0.0",
  "port_password": {
        "8850":"hehe2016"
 },
  "method": "aes-256-gcm",
  "PID_FILE": "/var/run/shadowsocks_ssserver.pid",
  "LOG_FILE": "/var/run/shadowsocks_ssserver.log"
}

添加PID_FILE,是因为 执行下面这句话时,需要PID 文件,否则会报错。

ssserver -c /etc/shadowsocks.json -d  stop

这个时候,服务器就可以使用了:

##### 开启 shadowsocks  服务
ssserver -c /etc/shadowsocks.json -d  stop

##### 关闭 shadowsocks  服务
ssserver -c /etc/shadowsocks.json -d  start

后面提到的shadowsocks.service是另一种启动方式。

##### 查看 shadowsocks 状态 
#####(如果 我用ssserver -c /etc/shadowsocks.json -d start 启动,那么这里查看的启动状态是 dead 的,两种启动方法 应该隶属于 不同的管理方式中)
systemctl status shadowsocks

##### 启动 shadowsocks 服务
systemctl start shadowsocks

##### 关闭 shadowsocks 服务
systemctl stop shadowsocks

注意: 加密方式(method),密码(password),服务器端口(server-port)都可以自己设置,只要客户端相同就行。服务器端口 1024以下是系统保留的,从1024-65535是用户使用的。密码 建议选择 aes 系列。method为加密方法,可选aes-128-cfb, aes-192-cfb, aes-256-cfb, bf-cfb, cast5-cfb, des-cfb, rc4-md5, chacha20, salsa20, rc4, table

vim编辑器用法可以查看:vim入门基础
VNC桌面环境就直接创建json文件就行了。

4.配置开机自启动。SSH和VNC操作都行。

新建启动脚本文件/etc/systemd/system/shadowsocks.service,内容如下:

[Unit]
Description=Shadowsocks
[Service]
TimeoutStartSec=0
ExecStart=/usr/bin/ssserver -c /etc/shadowsocks.json
[Install]
WantedBy=multi-user.target

然后 , 在SSH或者VNC终端下:输入命令

systemctl enable shadowsocks
systemctl start shadowsocks

为了检查 shadowsocks 服务是否已成功启动,可以继续执行以下命令查看服务的状态:

systemctl status shadowsocks -l 

如果服务启动成功,则控制台显示的信息可能类似这样:

● shadowsocks.service - Shadowsocks
 Loaded: loaded (/etc/systemd/system/shadowsocks.service; enabled; vendor preset: disabled)
 Active: active (running) since Mon 2015-12-21 23:51:48 CST; 11min ago
 Main PID: 19334 (ssserver)
 CGroup: /system.slice/shadowsocks.service
 └─19334 /usr/bin/python /usr/bin/ssserver -c /etc/shadowsocks.json
 Dec 21 23:51:48 morning.work systemd[1]: Started Shadowsocks.
 Dec 21 23:51:48 morning.work systemd[1]: Starting Shadowsocks…
 Dec 21 23:51:48 morning.work ssserver[19334]: INFO: loading config from /etc/shadowsocks.json
 Dec 21 23:51:48 morning.work ssserver[19334]: 2015-12-21 23:51:48 INFO loading libcrypto from libcrypto.so.10
 Dec 21 23:51:48 morning.work ssserver[19334]: 2015-12-21 23:51:48 INFO starting server at 0.0.0.0:8388

不过此时还连接不上服务器,因为CentOS 7默认防火墙是阻止SS端口的,这里需要放行SS端口,由于CentOS 7的防火墙升级为Firewall了,操作方式与之前的iptables有所不同(3678为文中shadowsocks服务器配置端口号,这里要改为你安装SS时设置的端口):
firewall-cmd –permanent –add-port=3678/tcp
firewall-cmd –reload

四、安装kcp加速插件

由于原生shadowsocks速度慢,所以用kcp 插件加速。
访问链接,选择linux-amd64 下载,centos7系统 是 64位的linux系统。
https://github.com/shadowsocks/shadowsocks-windows/releases

解压后有两个文件,其中类似server_linux_amd64这样的文件就是服务端程序了,另一个类似client_linux_amd64的就是客户端程序了。如果客户端在Windows上使用要下载Windows版本的哦。

SSH 里面可以用 tar命令进行解压(前面解压pip文件已经提到过了), ls 命令可以查看 当前目录所有文件 cd 命令表示进入某目录。可以搜索一下bash常用命令操作。
VNC里面直接可视化操作就行了。

先来创建服务端配置文件,创建开机启动项如下供参考:

[bash] vim /etc/systemd/system/kcptun.service [/bash]

然后 配置启动文件如下:

[Unit]
 Description=kcptun
 [Service]
 TimeoutStartSec=20
 ExecStart=/usr/bin/server_linux_amd64 -t "0.0.0.0:3678" -l ":5678" -mode fast2
 [Install]
 WantedBy=multi-user.target

其中TimeoutStartSec为延时启动的时间,这里由于需要等待ShadowSocks启动完成后再启动,所以配置20秒的延迟。ExecStart后的server_linux_amd64为你刚刚下载的文件的位置,建议放到相应的文件夹以便管理。-t后的是服务器的IP和ShadowSocks服务的端口,这里IP配置为本机IP即可。-l表示KCP服务使用的端口,-mode表示速度的模式,从快到慢依次是fast3 > fast2 > fast > normal > default,fast3一般用于游戏,不想要任何的延迟(no lag),但这也意味浪费的流量会增多,这里我选择fast2就够用了。

[bash] systemctl daemon-reload # 创建开机启动 systemctl enable kcptun # 启动kcp服务。 systemctl start kcptun # 防火墙 开放 kcp 端口 (kcp 是 udp 协议,shadowsocks 是tcp协议) firewall-cmd –permanent –add-port=5678/udp # 防火墙 重新 加载 配置 firewall-cmd –reload [/bash]

五、安装客户端

安卓版本:(个人用 3.3.1 版本,因为 好像 不需要 google play 插件吧。并且内置kcp 插件)
https://github.com/shadowsocks/shadowsocks-android/releases
windows版本:
https://github.com/shadowsocks/shadowsocks-windows/releases
需要下载kcp客户端单独进行集成。(Windows-386 32位系统,Windows-amd64 64位系统)
https://github.com/xtaci/kcptun/releases
集成方法可以参考链接:
(总体思路是本地shadowsocks先连接本地的kcptun,然后kcptun 连接 代理服务器的 kcptun,
接着 代理服务器的kcptun 连接 代理服务器的 shadowsocks)
https://blog.csdn.net/farawayzheng_necas/article/details/63255799

mac版本:(内置kcp插件)
https://github.com/shadowsocks/ShadowsocksX-NG
ios版本:
因为大陆已经下架,需要在其他地区搜索:Shadowrocket 等 支持shadowsocks的 应用软件。

之前3.几的安卓客户端,kcp与客户端是集成的,配置文件中,shadowsocks服务器端口就填shadowsocks端口。kcp配置:需要配置kcp端口,kcp参数:

--crypt aes-192 --key "hello"

到了安卓4.x版本,kcp是插件化了。无法启动kcp,可能是1、shadowsocks卸载重装2、手机管家的自启动的链式启动,允许kcptun。

如果要添加kcp插件,那么shadowsocks的端口就要填 kcp的端口,kcp的插件需要配置:[注意与3.x的配置不同,这里的密码,不再需要添加引号了]

key=hello;crypt=aes-192

六、安装obfs 混淆,这个只能与 kcp 二选一

服务器:centos

sudo yum install gcc autoconf libtool automake make zlib-devel openssl-devel asciidoc xmlto

git clone https://github.com/shadowsocks/simple-obfs.git
cd simple-obfs
git submodule update --init --recursive
./autogen.sh
./configure && make
sudo make install

### 会报错 缺少 libev,于是 猜测 shadowsocks libev版本有,
### 于是安装 shadowsocks libev 的依赖库
yum install gcc gettext autoconf libtool automake make pcre-devel asciidoc xmlto c-ares-devel libev-devel libsodium-devel mbedtls-devel -y

服务器配置:

因为是独立模式:vim /etc/systemd/system/obfs-server.service

[Unit]
Description=Simple-obfs
After=network.target

[Service]
Type=simple-obfs
User=nobody
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
ExecStart=/usr/local/bin/obfs-server -s 0.0.0.0 -p 443 --obfs tls -r 127.0.0.1:8866

[Install]
WantedBy=multi-user.target

监听 1024以下端口 需要 root 用户权限,所以采用下面的方法:(应该是绑定服务吧)

setcap cap_net_bind_service=+ep /usr/local/bin/obfs-server
### 最后更新服务并启动:
sudo systemctl enable obfs-server
sudo systemctl daemon-reload
sudo systemctl restart obfs-server

特别注意:复制粘贴时的 问题:【正常情况应该不会出现这个问题,然后我把vim后面的空格给删了,然后用打了一个空格,结果就好了,可以新建文件了】

[root@superguy ~]# vim /etc/systemd/system/obfs-server.service
-bash: vim /etc/systemd/system/obfs-server.service: No such file or directory

这里有编译好的软件可直接放到目标位置  obfs-localobfs-server

有可能 443端口被占用了,导致绑定出错,可以换其他端口再重试。

# 查看端口被占用方法

lsof -i:443

客户端:因为是集成插件模式:

1、客户端连接 服务器 端口 就是 443

2、指定 插件为:  obfs-local【有些地方填:simple-obfs】  ;  并配置插件参数 ,如下面的示例。

--plugin-opts "obfs=tls;obfs-host=www.bing.com"

备注:obfs 类型有 tls和http  主要是 服务器 和客户端 类型要统一。

参考教材:

###Plugin mode with shadowsocks
###Add respective item to --plugin and --plugin-opts arg or as value of plugin and plugin_opts in JSON.

### On the client:

ss-local -c config.json --plugin obfs-local --plugin-opts "obfs=http;obfs-host=www.bing.com"
### On the server:

ss-server -c config.json --plugin obfs-server --plugin-opts "obfs=http"
### Standalone mode
### On the client:

obfs-local -s server_ip -p 8139 -l 1984 --obfs http --obfs-host www.bing.com
ss-local -c config.json -s 127.0.0.1 -p 1984 -l 1080
### On the server:

obfs-server -s server_ip -p 8139 --obfs http -r 127.0.0.1:8388
ss-server -c config.json -s 127.0.0.1 -p 8388

七、nginx与shadowsocks共用443端口伪装

1、shadowsocks 转发给 nginx

最近突发奇想把网站改为https,不可避免的遇到了与shadowsocks的443端口冲突问题(443端口不容易被检出)。废了好大劲才发现原来ss的simple-obfs已经带有非ss流量转发功能。

shadowsocks

# HTTP only with plugin mode
ss-server -c config.json --plugin obfs-server --plugin-opts "obfs=http;failover=example.com:80"

# Both HTTP and HTTPS with standalone mode
obfs-server -s server_ip -p 80 --obfs http -r 127.0.0.1:8388 --failover example.com:80
obfs-server -s server_ip -p 443 --obfs tls -r 127.0.0.1:8388 --failover example.com:443

# suppose you have an HTTP webserver (apache/nginx/whatever) listening on localhost:8080 and HTTPS on 8443
# (you probably shouldn't expose these ports)
obfs-server -s server_ip -p 80 --obfs http -r 127.0.0.1:8388 --failover 127.0.0.1:8080
obfs-server -s server_ip -p 443 --obfs tls -r 127.0.0.1:8388 --failover 127.0.0.1:8443

这样便将web流量转发到了8443端口

nginx

user xx xx;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    types_hash_max_size 1024;

    server {
        listen       80;
        server_name  localhost;
        return       301    https://$host$request_uri;  #redir to https
   #     #charset koi8-r;

   #     #access_log  logs/host.access.log  main;

        # redirect server error pages to the static page /50x.html
        #
        #error_page   500 502 503 504  /50x.html;
        #location = /50x.html {
        #    root   /usr/share/nginx/html;
        #}

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    server {
        listen       8443 ssl http2;
        server_name  localhost;

        ssl_certificate      /etc/letsencrypt/live/cclin.net/fullchain.pem; #managed by Certbot
        ssl_certificate_key  /etc/letsencrypt/live/cclin.net/privkey.pem; #managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf;
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

        location / {
            uwsgi_pass 127.0.0.1:3031;
            include uwsgi_params;
        }

        }
    #   ssl_session_cache    shared:SSL:1m;
    #   ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    }
}

2、利用nginx转发到shadowsocks

下面的方法 可以 ,即访问网站,同时又 ss。 也就是说 simple-obfs 支持 http代理 转发
server {
    listen       80;
     server_name  localhost;
  # server_name  www.huaijiujia.com;
    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;

        if ($http_upgrade = "websocket") {
             proxy_pass http://127.0.0.1:8443;
        }
        proxy_http_version 1.1;
        proxy_set_header Upgrade "websocket";
        proxy_set_header Connection "upgrade";
    }

…..

}

下面的 方法 不行,只能访问 网路,但是无法 使用  ss。也就是说 simple-obfs 不支持 tls 代理 转发

server {
    listen   443  default_server;
    server_name  _;
    ssl_certificate     https_cert/huaijiujia_ca_chained.crt;
    ssl_certificate_key https_cert/huaijiujia_private.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
  #  return 404;
   #   return 301 https://www.amazon.com;
      location = / {
        if ($http_upgrade = "websocket") {
             proxy_pass https://127.0.0.1:8443;
        }
        proxy_http_version 1.1;
        proxy_set_header Upgrade "websocket";
        proxy_set_header Connection "upgrade";
    }
   return 301 https://www.amazon.com;
}

因为,安卓手机的功能不完善,不完全支持 simpobfs功能,所以需要重新下个 改进版的。点击下载: Simple-obfuscation-fix


补充:

如果 shadowsocks需要支持 aes-256-gcm加密的话:

For CentOS 7, if you need AEAD ciphers(支持aes-256-gcm加密), you need install libsodium

yum install libsodium python34-pip
pip3 install  git+https://github.com/shadowsocks/shadowsocks.git@master

 

wordpress-上传文件类型限制

楼主想上传一个脚本文件到wordpress,但是 发现 由于安全限制,无法上传。

文件类型不符合安全规则。试试别的文件。

这种错误是由于WordPress中做了文件上传格式的限制,这种限制可以在WordPress中的wp-include/functions.php的get_allowed_mime_types函数中找到,函数如下:

function get_allowed_mime_types() {
static $mimes = false;
if ( !$mimes ) {
// Accepted MIME types are set here as PCRE unless provided.
$mimes = apply_filters( ‘upload_mimes’, array(
‘jpg|jpeg|jpe’ => ‘image/jpeg’,
‘gif’ => ‘image/gif’,
‘png’ => ‘image/png’,
‘bmp’ => ‘image/bmp’,
‘tif|tiff’ => ‘image/tiff’,
‘ico’ => ‘image/x-icon’,
‘asf|asx|wax|wmv|wmx’ => ‘video/asf’,
‘avi’ => ‘video/avi’,
‘divx’ => ‘video/divx’,
‘flv’ => ‘video/x-flv’,
‘mov|qt’ => ‘video/quicktime’,
‘mpeg|mpg|mpe’ => ‘video/mpeg’,
‘txt|c|cc|h’ => ‘text/plain’,
‘rtx’ => ‘text/richtext’,
‘css’ => ‘text/css’,
‘htm|html’ => ‘text/html’,
‘mp3|m4a’ => ‘audio/mpeg’,
‘mp4|m4v’ => ‘video/mp4′,
‘ra|ram’ => ‘audio/x-realaudio’,
‘wav’ => ‘audio/wav’,
‘ogg’ => ‘audio/ogg’,
‘mid|midi’ => ‘audio/midi’,
‘wma’ => ‘audio/wma’,
‘rtf’ => ‘application/rtf’,
‘js’ => ‘application/javascript’,
‘pdf’ => ‘application/pdf’,
‘doc|docx’ => ‘application/msword’,
‘pot|pps|ppt|pptx’ => ‘application/vnd.ms-powerpoint’,
‘wri’ => ‘application/vnd.ms-write’,
‘xla|xls|xlsx|xlt|xlw’ => ‘application/vnd.ms-excel’,
‘mdb’ => ‘application/vnd.ms-access’,
‘mpp’ => ‘application/vnd.ms-project’,
’swf’ => ‘application/x-shockwave-flash’,
‘class’ => ‘application/java’,
‘tar’ => ‘application/x-tar’,
‘zip’ => ‘application/zip’,
‘gz|gzip’ => ‘application/x-gzip’,
‘exe’ => ‘application/x-msdownload’,
// openoffice formats
‘odt’ => ‘application/vnd.oasis.opendocument.text’,
‘odp’ => ‘application/vnd.oasis.opendocument.presentation’,
‘ods’ => ‘application/vnd.oasis.opendocument.spreadsheet’,
‘odg’ => ‘application/vnd.oasis.opendocument.graphics’,
‘odc’ => ‘application/vnd.oasis.opendocument.chart’,
‘odb’ => ‘application/vnd.oasis.opendocument.database’,
‘odf’ => ‘application/vnd.oasis.opendocument.formula’,
) );
}
return $mimes;
}

以上传入 apply_filters函数中的Array,即使允许上传的类型列表,我们可以修改这个地方,以禁止或允许可以上传的文件类型。

例如我们想要允许rar文件上传,只需添加一行格式说明即可:

‘rar’ => ‘application/rar’,

除了直接修改这个Array外,还有另外一种方法,可以修改wp-config.php文件,这个文件在WordPress的根目录下,在其中增加一行语句:
define(‘ALLOW_UNFILTERED_UPLOADS’, true);

这条语句将去除上传的文件类型限制,一劳永逸。

转载自:https://www.cnblogs.com/kedarui/p/3627307.html