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

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


  • 首页

  • 归档

  • 搜索

Java MD5和SHA256等常用加密算法 前言 MD5

发表于 2021-09-29

前言

我们在做java项目开发的时候,在前后端接口分离模式下,接口信息需要加密处理,做签名认证,还有在用户登录信息密码等也都需要数据加密。信息加密是现在几乎所有项目都需要用到的技术,身份认证、单点登陆、信息通讯、支付交易等场景中经常会需要用到加密算法,所谓加密算法,就是将原本的明文通过一系列算法操作变成密文。

  1. BASE 严格地说,属于编码格式,而非加密算法
    MD(Message Digest algorithm ,信息摘要算法)
    SHA(Secure Hash Algorithm,安全散列算法)
    HMAC(Hash Message Authentication Code,散列消息鉴别码)
  2. 加密算法中SHA1、SHA-224、SHA-256、SHA-384,和SHA-512,其中SHA-224、SHA-256、SHA-384,和SHA-512我们可以统称为SHA2加密算法
  3. SHA加密算法的安全性要比MD5更高,而SHA2加密算法比SHA1的要高。其中SHA后面的数字表示的是加密后的字符串长度,SHA1默认会产生一个160位的信息摘要。

MD5

MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。

MD5算法有以下特点:

  1. 压缩性:无论数据长度是多少,计算出来的MD5值长度相同
  2. 容易计算性:由原数据容易计算出MD5值
  3. 抗修改性:即便修改一个字节,计算出来的MD5值也会巨大差异
  4. 抗碰撞性:知道数据和MD5值,很小概率找到相同MD5值相同的原数据

准确来讲,MD5不是一种加密算法,而是一种摘要算法,MD5能将明文输出为128bits的字符串,这个字符串是无法再被转换成明文的。网上一些MD5解密网站也只是保存了一些字符串对应的md5串,通过已经记录的md5串来找出原文。

我做过的几个项目中经常见到MD5用在加密上的场景。比如对密码的加密,生成一个密码后,使用MD5生成一个128位字符串保存在数据库中,用户输入密码后也先生成MD5串,再去数据库里比较。因此我们在找回密码时是无法得到原来的密码的,因为明文密码根本不会被保存。

SHA系列

  1. 安全散列算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族,是FIPS所认证的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。
  2. 2005年8月17日的CRYPTO会议尾声中王小云、姚期智、姚储枫再度发表更有效率的SHA-1攻击法,能在2的63次方个计算复杂度内找到碰撞。

也就是说SHA-1加密算法有碰撞的可能性,虽然很小。

HMAC

  1. HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写,由H.Krawezyk,M.Bellare,R.Canetti于1996年提出的一种基于Hash函数和密钥进行消息认证的方法,并于1997年作为RFC2104被公布,并在IPSec和其他网络协议(如SSL)中得以广泛应用,现在已经成为事实上的Internet安全标准。它可以与任何迭代散列函数捆绑使用。
  2. HMAC算法更像是一种加密算法,它引入了密钥,其安全性已经不完全依赖于所使用的Hash算法

如果要使用加密,推荐使用SHA256、SHA384、SHA512以及HMAC-SHA256、HMAC-SHA384、HMAC-SHA512这几种算法。

对称加密算法

  1. 对称加密算法是应用比较早的算法,在数据加密和解密的时用的都是同一个密钥,这就造成了密钥管理困难的问题。常见的对称加密算法有DES、3DES、AES128、AES192、AES256 (默认安装的 JDK 尚不支持 AES256,需要安装对应的 jce 补丁进行升级 jce1.7,jce1.8)。其中AES后面的数字代表的是密钥长度。对称加密算法的安全性相对较低,比较适用的场景就是内网环境中的加解密。
  2. 所谓对称加密,就是通过密钥加密后可以再通过密钥解密。我接触过的某个国企现在内部就是采用AES的方式实现集成登陆。第三方系统提供一个接收用户信息的接口,该国企将用户信息AES加密后通过这个接口传递给第三方系统,第三方系统自行实现登陆操作。这里需要注意的是密钥十分重要,如果密钥丢失,就有信息泄漏的风险。

加密盐

  1. 加密盐也是比较常听到的一个概念,盐就是一个随机字符串用来和我们的加密串拼接后进行加密。
  2. 加盐主要是为了提供加密字符串的安全性。假如有一个加盐后的加密串,黑客通过一定手段这个加密串,他拿到的明文,并不是我们加密前的字符串,而是加密前的字符串和盐组合的字符串,这样相对来说又增加了字符串的安全性。

在线加密网站

  1. 站长工具
  2. 在线加密
  3. Java开发加密解密工具类看我这一篇文章

总结

比较推荐的几个加密算法有:

  1. 不可逆加密:SHA256、SHA384、SHA512以及HMAC-SHA256、HMAC-SHA384、HMAC-SHA512
  2. 对称加密算法:AES、3DES
  3. 非对称加密算法:RSA

参考

  1. 常用的加密算法
  2. 浅析五种最常用的Java加密算法
  3. juejin.cn/post/684490…

本文转载自: 掘金

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

IDEA + Github,打造你的协同开发环境

发表于 2021-09-29

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

在我们日常工作生活中,当我们换设备或者重装系统后,往往需要将我们之前的代码进行同步。这个时候我们常常都是采用 U 盘拷贝的方式,但是这样十分麻烦,那有没有不用通过 U 盘之类的介质拷贝就能实现的呢。今天我们看看如何利用 IDEA + Github,实现跨设备跨平台之间的同步。后续步骤均建立在安装好 git 和拥有 Github 账户的基础上,如果还没有安装 git 或者没有 Github 账户,那赶紧先去安装 git 和申请一个 Github 账户吧。

同步过程

新建一个 Java 项目

  1. 新建一个项目,如果有之前的项目,也可以直接打开,不用再新建,然后直接跳到下一小节;

  1. 选择是否基于模板创建项目,默认我们不勾选,直接下一步,创建一个空项目;

  1. 自定义项目名及存储路径,在这里指定我们的项目名,并且指定项目存放地址;

Git & Github 设置

  1. 在 IDEA 中设置 Git 安装的路径,一般 IDEA 是会自动检测你的 Git 所在目录的,如果没有自动检测出来,就需要我们自己去找到安装好的 Git 的路径;

  1. 设置好 Git 的安装路径之后,我们就可以直接去登录 Github 了,在这里直接输入你的 Github 账户及密码即可;

  1. 点击登录后,如果出现如下图中的界面,即显示你的 Github 账户头像和用户名,则说明此时登录成功;

本地项目推送到远程

  1. 依次进入 VCS -> Share Project on Github;

  1. 然后填写仓库名、远程分支、仓库描述,以及选择是否私有,最后点击 Share 即可;

  1. 等待一会儿,IDEA 会指定弹出以下窗口。这里主要供我们选择所要推送到远程的文件,以及提交信息,确认好两者之后,最后点击 Add 即可;

  1. 等待完成即可;

  1. 如果遇到网络问题或者其他问题导致推送失败,可以点击下图中的按钮,然后再次提交推送即可;

  1. 最后,点击 IDEA 最下边工具栏的 Git 就可以查看我们的提交记录了;

image-20210929153511045

  1. 最后去我们的 Github 查看刚才的推送是否成功;

提交与拉取

提交

当我们的项目有所改动之后,如果我们想要将我们改动的内容推送到远程,可以通过如下过程来进行。

  1. 点击如下图中按钮(或者使用快捷键 Ctrl + K),然后在左侧选中我们改动的内容并填写提交信息,接着点击下方的 Commit 即可;

  1. 然后重复上一小节中的第 5 个步骤即可!
  2. 最后去我们的远程看看刚才提交的改动的内容。

拉取

假设有这样的场景,我们在公司的电脑上提交了我们新加的功能代码,回家之后想用自己的电脑接着开发,此时我们自己电脑上的代码还是之前的老版本,此时需要先从远程拉取我们在公司时所新加的内容。

此时需要按照如下过程操作:

  1. 点击下图中的按钮,根据自己的需要选择 Merge 还是 Rebase;

  1. 然后等待项目更新完成即可。

总结

通过上述操作,即可将 IntelliJ IDEA 与远程 Github 连接起来,随时将我们所做工作推送到 Github 保存,再也不用担心不同设备之间迁移的问题,随时随地进行开发 ~

最后,创作不易,如果你觉得对你有所帮助,那就点个赞再走吧!

本文转载自: 掘金

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

Elastic Stack (二) 核心概念-倒排索引

发表于 2021-09-29
  1. 倒排索引原理

假设一个电商网站,关于手机有如下数据(数据量为10亿条):

id product describe
1 新款小米至尊-纪念版手机 手机中的战斗机
2 新款小米全功能NFC手机 小米手机,支持全功能NFC,手机中的战斗机
3 红米手机 狂拽酷炫吊炸天
… … …
100000000 … …

当用户购买手机时,会根据需求关键字进行搜索. 如用户希望购买一台: 小米有NFC功能的手机
那么在搜索是用户搜索关键字: 小米NFC手机

该如怎么做才能够准确快速的将结果返回给用户呢?

假设上面手机相关的数据时存储在关系型数据库中(以Mysql为例), 那么SQL语句为:

1
SQL复制代码select * from table where product like "%小米NFC手机%"

该方案如下存在问题:

  1. 表中有10亿条数据, 如果不对 product 列创建索引,会造成SQL全表扫描, 该SQL的执行时间会非常长;
  2. 如果对 product 列创建索引, 那么因为 product 列数据比较长, 导致数据库索引 B+ 树的深度会非常深, 查询时需要加载的节点增多, 随机 I/O 变多, 查询时间也非常长.
  3. 而且即使 produc t列创建了索引, 上面的 SQL 条件为 “%小米NFC手机%”, 坐侧的 “%” 也会导致索引失效,无法使用索引,还是全表扫描
  4. 根据表中的数据可知, 没有匹配 “%小米NFC手机%” 的数据, 查询结果为空. 存在查询精度差的问题.

综合上面的分析可知,使用 RDB 作为该场景的搜索引擎,存在: 时间长,精度差 的问题

———-Elasticsearch(Lucene) 带着倒排索引来拯救世界了!!!————-

elasticsearch 如果组织这些数据呢?
首先 elasticsearch 会将内容进行分词, 同时记录该词对应的数据ID.分词后结果如下

关键词 包含该关键词的数据ID
新款 1, 2
小米 1, 2
至尊 1
纪念版 1
手机 1, 2, 3
全功能 2
NFC 2
红米 3

当用户搜索搜索 “小米NFC手机” 时, 执行流程如下:

  1. 对搜索语句进行分词: “小米”, “NFC”, “手机”.
  2. 依次查询每个分词, 查询的结果为
    • 小米 对应的数据ID为 [ 1, 2 ]
    • NFC 对应的数据ID为 [ 2 ]
    • 手机 对用的数据ID为 [1, 2, 3]
  3. 对根据第2步的查询结果汇总: 配置的数据ID 为 1, 2, 3
  4. 根据第2步的查询结果,对结果排序:
    • ID 为 2 的数据出现3次(匹配: 小米, NFC, 手机), 匹配度最高, 排在第一位
    • ID 为 1 的数据出现2次(匹配: 小米, NFC), 排在第二位
    • ID 为 3 的数据出现1次(匹配: 手机), 排在第三位
  5. 返回查询结果

数据的这种组织形式既为倒排索引.

  1. 倒排索引结构

在elasticsearch中,倒排索引存储结构如下:

term index(词项索引) term dictionary(词项字典) 倒排表(posting list)
新款 1, 2
小米 1, 2
至尊 1
纪念版 1
手机 1, 2, 3
全功能 2
NFC 2
红米 3

image.png

3 倒排索引核心算法

3.1 倒排表的压缩算法

倒排表(posting list)存储的为包含该关键字的所有数据的ID,且有序排列. 试想下如果包含该关键字的数据非常多,假设达到100万条, 由于 posting list 为 int 数组, 那么这100万的数据所需要的存储空间为:

100W * 4 Byte = 400W (Byte) ≈ 4 (MB)
elasticsearch 支持 PB 级数据, 那么 posting list 所占用的空间将非常的大, 那么对该数据进行压缩存储就变的非常必要.
那么就需要一个好的压缩算法, 做到压缩效率高以及快速的编码和解码速度. elasticsearch 支持两种压缩算法: FOR 和 RBM

3.1.1 FOR:Frame Of Reference

假设posting list 存储的数据都是连续的值, 既: [1,2,3,4,5…100w], 那么如果每个都以 int (4 Byte)存储, 那么100W的数据所占用的空间为:

100W * 4 Byte = 400W (Byte) ≈ 4 (MB)

———————————进入 FOR 算法原理装逼时间————————————

如果我们每项只存储其与前一项的差值, 既:

posting list[n] = value(n) - value(n-1)

那么这 100W 的数据存储形式就成了: [1,1,1,1,1……1], 既 100W 个 1, 而 1 只需要 1个bit就可以存储, 之前一个ID需要4 Byte(32 bit)存储,现在 1 bit 就可以存储, 空间节省 32 倍.
100W 所占用的空间就是:

100W * 1 bit = 100W bit = 125000 Byte ≈ 0.125M

———————————进入 FOR 算法实际存储方式分析————————————

上面所假设的只是一种理想情况,所有的数据都连续. 下面以一组不连续的数据分析 FOR算法的存储形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ini复制代码posting list:
[73, 300, 302, 332, 343, 372] => 空间为 6 * 4(Byte) = 24(Byte)

差值数组为:
[73, 227, 2, 30, 11, 29] => 差值的最大数据为227, 需要8个bit(2^8=256 > 227)存储,那么所有的数据都使用8bit存储, 需要占用的空间为 8 * 6(bit) = 48(bit) = 6(Byte)

上面的数据还可以优化: 数组中227 需要 8bit存储, 但是 2 只需要 2bit 存储,如果2也用8bit存储,严重浪费空间, SO 不要懈怠,继续优化...

将上面的数组进行拆分:
[73, 227] => 最大值227, 需要8bit存储, 占用的空间为 2*8bit = 16 bit
[2, 30, 11, 29] => 最大值30, 需要5bit存储, 占用的空间为 4*5bit = 20 bit.

将数组拆分为两组, 第一组每个数据只用 8bit存储, 第二个数组每个数据占用 5bit 存储, 那么解压的时候如何知道呢?
FOR算法采用的方式为在每个数组头另外用 1Byte(8bit)存储本数组每个数据占用的空间. 那么综上所占用的空间为:

1 Byte(第一个数组头) + 16bit(2Byte)(第一个数组数据占用的空间) + 1 Byte(第二个数组头) + 20bit(3Byte, 在计算机中数据以 Byte为最小单位, 20bit 实际占用了 3Byte的空间)
= [1Byte + 2Byte](第一个数组占用空间) + [1Byte + 3Byte](第二个数组占用空间)
= 7Byte(还不如不优化-_-, 不优化只占用6Byte)

image.png

3.1.2 RBM:RoaringBitmap

FOR算法的思想是前后两个数据的差值比较小(占用的空间比较小),来达到节省空间的目的,但是如果数组的相邻项之间的差值也非常大时, 所达到的优化效果就不太明显.此时就需要 RBM 出来救场…

———————————进入 RBM 算法原理装逼时间————————————

假设如下数组:

[1000, 62101, 131385, 132052, 191173, 196658]

由于postlist为int类型的有序数组, int占用的空间为 4Byte(32bit). 那么将上面的数据拆分为高16bit和底16bit, 以上面数组最后一个数字196658为例:

196658 => 二进制为: 0000 0000 0000 0011 0000 0000 0011 0010

高16为为: 0000 0000 0000 0011 => 转换为十进制为 3

底16位为: 0000 0000 0011 0010 => 转换为十进制为 50

索引196658 可以用 (3, 50) 表示

拆分结果为:

[(0, 1000), (0, 62101), (2, 313), (2, 980), (2, 60101), (3, 50)]

那么将该数组转换为 map类型, key为第一个数字(高16位), value为第二个数字(底16位), 转换结果为:

key = 0 -> value = [1000, 62101]

key = 2 -> value = [313, 980, 60101]

key = 3 -> value = [50]

value为一个真实数据的低16位的数组, 既一个short数组(short类型为 2 Byte = 16 bit). value在 elasticsearch中存储方式有三种:

  • ArrayContainer
  • BitmapContainer
  • RunContainer

3.1.2.1 ArrayContainer

见名知意, value中的每个数据采用一个 short 存储, 既每个数据占用 16bit(2Byte). value为一个short数组:

既 value = short[n]

假设value中数据总数为 n, 那么所占用的空间为:

f(n) = 2 * n (Byte)

3.1.2.2 BitMapContainer

BitMap原理: www.cnblogs.com/cjsblog/p/1…

BitMap的原理为将所有的空间视为一个 bit 数组, 在这个 bit数组中, 第一位代表 1, 第二位代表2 … 第 N 位代表 N.

因为value中存储的为数据的低16位, 16位所能表达的最大数字为: 2^16 = 65535, 既value中可能出现 0 ~ 65535. 那么使用 BitMap存储只需要: 65536 个 bit即可存储(0~65535共 65536个数字, 因为0也算).

65536 bit = 8192 Byte = 8 KB

3.1.2.3 RunContainer

RunContainer用于处理一组特殊情况的数据, 假设数组中的数据连续: 1, 2, 3, 4 … 100W.
那么我们可以使用 2Byte 存储:

  • 第一个Byte存储连续数据的第一个数字, 在本例中第一个Byte存储 1
  • 第二个Byte存储连续数据的最后一个数字, 在本例中第二个Byte存储 100W

那么这100W个数据只需要 2 Byte就可以存储.
RunContainer 在 Lucene 5中新增.

3.1.2.4 RBM 总结

image.png

未完待续…

3.2 词项索引的检索原理

3.2.1 FST:Finit State Transducers

本文转载自: 掘金

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

计算机基础知识对程序员来说有多重要?

发表于 2021-09-29

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

马上就是国庆小长假了,对于热爱学习的小伙伴们,我建议还是多看些计算机相关的基础知识。

程序员能不能走的远,能不能写高质量的代码,能不能快速找到系统的瓶颈,能不能快速抽丝剥茧,找到系统的核心功能,肯定和咱们的专业素养有关系。

对于一个程序员来说,计算机的基础知识就是内功,就是咱们的专业素养,只有内功深厚的人行走江湖才能少翻车。

计算机的应用繁多,但归根结底还是要用数据结构和算法去做事情。如果庖丁来解代码的话,解到最后就肯定剩下一堆堆的数据,一堆堆的算法把数据串起来。

所以呀,基础肯定特别重要。因此要常去复习计算机的基础内容,多写一写基础的算法和数据结构。他们的重新组合,就能变化出万千的互联网世界。

只有足够了解基础,才能在工作中真正的游刃有余,才能更高效优雅地解决问题。

操作系统

想要学好操作系统这门课程强烈推荐去听一下下面这两个老师的操作系统课程,保证不水,B站上都有完整的视频!书籍推荐【深入理解计算机系统】。

  1. 2020 南京大学 操作系统:设计与实现 (蒋炎岩)

image-20210929112457155

  1. 操作系统(哈工大李治军老师)

个人觉得是 b 站上很不错的操作系统课程了,这门课的前几讲涉及 OS 启动,需要一点汇编知识,不过遇到不会的直接网上搜即可,大可不必因为汇编而被劝退;之后的内容如多进程、信号量、内存管理等讲得很好。

image-20210929112612670

  1. 【深入理解计算机系统】电子书籍百度云盘链接如下:

image-20210929113357484

链接:pan.baidu.com/s/1kaTZVEG7…
提取码:xh99

计算机网络

  1. 计算机网络微课堂(有字幕无背景音乐版)(陆续更新中……)_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili

学习计算机网络,我首先推荐的 UP 主湖科大教书匠,他讲的计算机网络十分通俗易懂,重点的地方讲的十分细致,并且还有一些实验,更好的是有考研 408 的难题的讲解,也是非常适合考研党,除了课程内容外还有很多习题讲解视频,特别赞的一点是每天动态里都会更新一道考研题,播放量也非常的多。

image-20210929113909320

  1. 2019 王道考研 计算机网络_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili

既然说到了考研,那我就不得不提一下王道考研了,恭喜你发现了宝藏。王道考研的计算机网络视频,播放量非常多,而且老师是一位小姐姐,声音十分动听,声音这么好听的老师给你讲课,妈妈再也不用担心我的学习了呢,总之,这个视频的质量也非常高,弹幕全是对小姐姐的高度评价。

image-20210929114039247

  1. 韩立刚计算机网络 谢希仁 第7版 2020年12月_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili

韩立刚老师所讲的计算机网络视频,内容比较多,但是讲解的通俗易懂,并且老师讲课的经验也十分的丰富。配套的教材是谢希仁老师的计算机网络教材,韩老师的最近的一个视频视频比较新,播放量还比较少,但是他讲的是真的不错,相比于王道考研所讲的计算机网络,韩老师更加细致一些。

image-20210929125056184

  1. 计算机网络(谢希仁第七版)-方老师_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili

在计算机网络方面,我还想推荐的一位老师就是方老师,也是一位小姐姐老师。

image-20210929142800379

算法

【算法图解】该书语言风趣,有比较多的插图,入门很合适。电子书籍网盘链接如下:

image-20210929125254950

链接:pan.baidu.com/s/1c9g1CK8P…
提取码:g5vz

Java编程思想

对于Java开发刚入门的同学,可以看看下面的这本书,还是很不错的。

链接:pan.baidu.com/s/1J16FkCLh…
提取码:ishy

image-20210929130308493

MYSQL数据库

这个是我自己收藏的关于MYSQL的一个视频,我感觉还是挺不错的,感兴趣的可以看看。

链接:pan.baidu.com/s/1Q2kN8S3j…
提取码:e8vg

image-20210929150750542

结语

互联网行业有个不成文的约定就是终身学习,层出不穷的框架,各显神通的新语言,不断拓展的未知领域等等,这些都是开发者们孜孜不倦地学习的动力。

作为一个程序员,我们的业余时间还是要挤出时间来学习,每个人都适自己情况而定,查缺补漏。

希望小伙伴都能有一个愉快的国庆假期,文章如果对你有帮助,看完记得点赞、关注、收藏哟。

本文转载自: 掘金

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

第二十九章 SQL命令 DISTINCT 第二十九章 SQL

发表于 2021-09-29

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

第二十九章 SQL命令 DISTINCT

指定仅返回不同值的SELECT子句。

大纲

1
2
sql复制代码SELECT [DISTINCT [BY (item {,item2})] ]  |  [ALL]
select-item {,select-item2}

参数

  • DISTINCT - 可选-返回组合选择项值唯一的行。
  • DISTINCT BY (item {,item2}) - 可选-返回按(项)值唯一的行的选择项值。
  • ALL - 可选-返回结果集中的所有行。默认设置。

描述

可选DISTINCT子句出现在SELECT关键字之后、可选TOP子句和第一个SELECT-ITEM之前。

DISTINCT子句应用于SELECT语句的结果集。它将每个不同(唯一)值返回的行数限制为一个任意行。如果未指定DISTINCT子句,则默认情况下显示满足选择条件的所有行。ALL子句与不指定DEFAULT子句相同;如果指定ALL,SELECT将返回表中满足选择条件的所有行。

DISTINCT从句有两种形式:

  • SELECT DISTINCT:为选择项值的每个唯一组合返回一行。可以指定一个或多个选择项。例如,以下查询返回一行,其中包含Home_State和Age值的每个唯一组合的Home_State和Age值:
1
sql复制代码SELECT DISTINCT Home_State,Age FROM Sample.Person
  • SELECT DISTINCT BY(Item):为项目值的每个唯一组合返回一行。可以指定单个项目或逗号分隔的项目列表。指定的项目或项目列表必须用括号括起来。可以在by关键字和圆括号之间指定或省略空格。选择项列表可以(但不一定)包括指定的项。例如,以下查询返回一行,其中包含Home_State和Age值的每个唯一组合的Name和Age值:
1
sql复制代码SELECT DISTINCT BY (Home_State,Age) Name,Age FROM Sample.Person

项目字段必须按列名指定。有效值包括以下值:列名(DISTINCT BY(City));%ID(返回所有行);指定列名的标量函数(DISTINCT BY(ROUND(Age,-1);指定列名的排序函数(DISTINCT BY(%Exact(City)。不能按列别名指定字段;尝试这样做会生成SQLCODE-29错误。不能按列号指定字段;这将被解释为文字,并返回一行。将文字指定为DISTINCT子句中的项值将返回1行;返回哪行是不确定的。因此,指定7、‘Chicago’、‘’、0或NULL都返回1行。但是,如果将文字指定为逗号分隔列表中的项值,则该文字将被忽略,并且DISTINCT将为指定字段名的每个唯一组合选择一行。

DISTINCT子句在TOP子句之前应用。如果两者都指定,则SELECT只返回具有唯一值的行,唯一值是在TOP子句中指定的唯一值行数。

如果DISTINCT子句中指定的列包含NULL(不包含值)行,则DISTINCT将返回一行作为DISTINCT(唯一)值的NULL,如以下示例所示:

1
sql复制代码SELECT DISTINCT FavoriteColors FROM Sample.Person
1
2
sql复制代码SELECT DISTINCT BY (FavoriteColors) Name,FavoriteColors FROM Sample.Person
ORDER BY FavoriteColors

image.png

DISTINCT子句在嵌入式SQL简单查询中没有意义,因为在这种类型的嵌入式SQL中,SELECT始终只返回一行数据。但是,嵌入式SQL基于游标的查询可以返回多行数据;在基于游标的查询中,DISTINCT子句只返回唯一值行。

DISTINCT和ORDER BY

DISTINCT子句在ORDER BY子句之前应用。因此,DISTINCT和ORDER BY的组合将首先选择满足DISTINCT子句的任意行,然后根据ORDER BY子句对这些行进行排序。

DISTINCT和GROUP BY

DISTINCT和GROUP BY这两个记录按指定字段(或多个字段)分组,并为该字段的每个唯一值返回一条记录。它们之间的一个重要区别是DISTINCT在分组之前计算聚合函数。GROUP BY计算分组后的聚合函数。以下示例显示了这种差异:

1
2
sql复制代码SELECT DISTINCT BY (ROUND(Age,-1)) Age,AVG(Age) AS AvgAge FROM Sample.Person
/* Avg(Age)返回表中所有年龄的平均值 */
1
2
sql复制代码SELECT Age,AVG(Age) AS AvgAge FROM Sample.Person GROUP BY ROUND(Age,-1)
/* Avg(Age)返回每个年龄组的平均年龄 */

DISTINCT子句可以用一个或多个聚合函数字段指定,尽管这很少有意义,因为聚合函数返回单个值。因此,下面的示例返回单行:

1
sql复制代码SELECT DISTINCT BY (AVG(Age)) Name,Age,AVG(Age) FROM Sample.Person

注意:如果将聚合函数作为唯一项或选择项的DISTINCT子句与GROUP BY子句一起使用,则DISTINCT子句将被忽略。可以使用子查询实现DISTINCT、聚合函数和GROUP BY的预期组合。

字母大小写与DISTINCT优化

根据为字段定义的排序规则类型,将字符串值不同地分组在一起。默认情况下,字符串数据类型字段使用SQLUPPER排序规则定义,该排序规则不区分大小写。

如果字段/特性排序规则类型为SQLUPPER,则分组的字段值将全部以大写字母返回。要按原始字母大小写对值进行分组,或以原始字母大小写显示分组字段的返回值,请使用%Exact排序规则函数。以下示例显示了这一点,这些示例假设Home_City字段是使用排序规则类型SQLUPPER定义的,并且包含值‘New York’和‘New York’:

1
2
sql复制代码SELECT DISTINCT BY (Home_City) Name,Home_City FROM Sample.Person
/* 将Home_City值按其大写字母值组合在一起将以大写字母返回每个分组城市的名称。因此,返回‘new york’. */
1
2
sql复制代码SELECT DISTINCT BY (Home_City) Name,%EXACT(Home_City) FROM Sample.Person
/* 将Home_City值按其大写字母值组合在一起将返回每个分组的城市的名称(原始字母大小写)。因此,可以返回‘New York’或‘new York’,但不能同时返回两者。 */
1
2
3
4
sql复制代码SELECT DISTINCT BY (%EXACT(Home_City)) Name,Home_City FROM Sample.Person
/* 将Home_City值按其原始字母大小写组合在一起将返回每个分组的城市的名称(原始字母大小写)。
因此,‘New York’和‘new York’都会返回。
未使用优化. */

可以使用管理门户优化包含DISTINCT子句的查询的查询性能。依次选择系统管理、配置、SQL和对象设置、SQL。查看和编辑GROUP BY和DISTINCT查询必须生成原始值选项。(此优化也适用于GROUP BY子句。)。默认值为“否”。

此默认设置按字母值的大写排序规则对字母值进行分组。此优化利用选定字段的索引。因此,只有在一个或多个选定字段存在索引时才有意义。它对存储在索引中的字段值进行排序;字母字符串以全部大写字母返回。您可以设置此系统范围的选项,然后使用%exact排序规则函数为特定查询覆盖它以保留字母大小写。

也可以使用$SYSTEM.SQL.Util.SetOption()方法快速区分选项在系统范围内设置此选项。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings(),它显示打开的不同优化设置;默认值为1。

DISTINCT的其他用法

  • 流字段:DISTINCT对流字段的OID进行操作,而不是对其实际数据进行操作。因为所有流字段OID都是唯一值,所以DISTINCT对实际流字段重复数据值没有影响。DISTINCT BY(StreamField)将流字段为空的记录数减少到一个空记录。
  • 星号语法:DISTINCT*语法是合法的,但没有意义,因为根据定义,所有行都包含一些不同的唯一标识符。不同于(*)的语法不合法。
  • 子查询:在子查询中使用DISTINCT子句是合法的,但没有意义,因为子查询返回单个值。
  • 未选择行数据:DISTINCT子句可以与不访问任何表数据的SELECT一起使用。如果SELECT包含FROM子句,则在一行中指定DISTINCT结果将包含这些非表值;如果未指定DISTINCT(或TOP),则SELECT将产生与FROM子句表中的行数相同的行数。如果SELECT不包含FROM子句,则DISTINCT是合法的,但没有意义。
  • 聚合函数:可以在聚合函数中使用DISTINCT子句,以仅选择要包含在聚合中的不同(唯一)字段值。与SELECT DISTINCT子句不同,聚合函数中的DISTINCT不包括NULL作为DISTINCT(唯一)值。请注意,MAX和MIN聚合函数分析DISTINCT子句语法没有错误,但此语法不执行任何操作。

DISTINCT和%ROWID

指定DISTINCT关键字会导致基于游标的嵌入式SQL查询不设置%ROWID变量。即使DISTINCT不限制返回的行数,也不设置%ROWID。下面的示例显示了这一点:

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复制代码ClassMethod Distinct()
{
s %ROWID = 999
&sql(
DECLARE EmpCursor3 CURSOR FOR
SELECT DISTINCT Name, Home_State
INTO :name,:state FROM Sample.Person
WHERE Home_State %STARTSWITH 'M'
)
&sql(
OPEN EmpCursor3
)
q:(SQLCODE '= 0)
for {
&sql(
FETCH EmpCursor3
)
q:SQLCODE
w !,"RowID: ",%ROWID," row count: ",%ROWCOUNT
w " Name=",name," State=",state
}
&sql(
CLOSE EmpCursor3
)
}

查询行为的这种更改仅适用于基于游标的嵌入式SQL SELECT查询。动态SQL SELECT查询和非游标嵌入式SQL SELECT查询从未设置%ROWID。

DISTINCT和事务处理

指定DISTINCT关键字会导致查询检索所有当前数据,包括当前事务尚未提交的数据。忽略事务的READ COMMITTED隔离模式参数(如果设置);在READ UNCOMMITTED模式下检索所有数据。

示例

以下查询为每个不同的Home_State值返回一行:

1
2
sql复制代码SELECT DISTINCT Home_State FROM Sample.Person
ORDER BY Home_State

以下查询为每个不同的Home_State值返回一行,但返回该行的其他字段。无法预测检索到的是哪一行:

1
2
sql复制代码SELECT DISTINCT BY (Home_State) %ID,Name,Home_State,Office_State FROM Sample.Person
ORDER BY Home_State

以下查询为Home_State和Office_State值的每个不同组合返回一行。根据数据的不同,它要么返回更多行,要么返回与上一个示例相同的行数:

1
2
sql复制代码SELECT DISTINCT BY (Home_State,Office_State) %ID,Name,Home_State,Office_State FROM Sample.Person
ORDER BY Home_State,Office_State

以下查询使用DISTINCT BY为每个不同的名称长度返回一行:

1
2
3
sql复制代码SELECT DISTINCT BY ($LENGTH(Name)) Name,$LENGTH(Name) AS lname
FROM Sample.Person
ORDER BY lname

image.png

下面的查询使用DISTINCT BY为FavoriteColors %List值的每个不同的第一个元素返回一行。它列出FavoriteColors为空的不同行:

1
2
sql复制代码SELECT DISTINCT BY ($LIST(FavoriteColors,1)) Name,FavoriteColors,$LIST(FavoriteColors,1) AS FirstColor
FROM Sample.Person

image.png

以下查询按排序规则升序返回从Sample.Person检索到的前20个不同的Home_State值。“top”行反映Sample.Person中所有行的ORDER BY子句排序。

1
sql复制代码SELECT DISTINCT TOP 20 Home_State FROM Sample.Person ORDER BY Home_State

以下查询在主查询和WHERE子句子查询中都使用DISTINCT。它返回Sample.Person中的前20个不同的Home_State值,这些值也在Sample.Employee中。如果未提供子查询DISTINCT,它将检索Sample.Person中与Sample.Employee中随机选择的Home_State值匹配的DISTINCT Home_State值:

1
2
3
sql复制代码SELECT DISTINCT TOP 20 Home_State FROM Sample.Person 
WHERE Home_State IN(SELECT DISTINCT TOP 20 Home_State FROM Sample.Employee)
ORDER BY Home_State

以下查询返回前20个不同的FavoriteColore值。这反映了Sample.Person中所有行的ORDER BY子句排序。众所周知,FavoriteColors字段具有NULL,因此FavoriteColors为NULL的不同行出现在排序规则序列的顶部。

1
2
sql复制代码SELECT DISTINCT BY (FavoriteColors) TOP 20 FavoriteColors,Name FROM Sample.Person 
ORDER BY FavoriteColors

还要注意,在前面的示例中,因为FavoriteColors是一个列表字段,所以归类序列包括元素长度字节。因此,以三个字母元素(红色)开头的不同列表值列在以四个字母元素(蓝色)开头的列表值之前。

本文转载自: 掘金

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

netty系列之 分离websocket处理器 简介 net

发表于 2021-09-29

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

简介

在上一篇文章中,我们使用了netty构建了可以处理websocket协议的服务器,在这个服务器中,我们构建了特制的handler用来处理HTTP或者websocket请求。

在一个handler中处理两种不同的请求,对于某些有代码洁癖的人可能忍受不了。那么,有没有可能将普通的HTTP请求和websocket请求使用不同的handler来进行处理呢?答案是肯定的。

netty的消息处理

我们知道netty中所有的消息处理都是通过handler来实现的,为了方便起见,netty提供了一个简单的消息处理类SimpleChannelInboundHandler,大家通过继承它来重写channelRead0方法即可:

1
java复制代码protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;

我们再看一下SimpleChannelInboundHandler的定义:

1
scala复制代码public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter

可以看到SimpleChannelInboundHandler本身是带有泛型I的,而这个I就是我们要探讨的方向。

如果我们要使用这个handler来处理所有的消息,那么可以将I取值为Object。

如果我们只需要处理String消息,那么可以这样:

1
2
3
4
5
6
7
8
9
scala复制代码       public class StringHandler extends
SimpleChannelInboundHandler<String> {

@Override
protected void channelRead0(ChannelHandlerContext ctx, String message)
throws Exception {
System.out.println(message);
}
}

同样的,如果要同时处理HTTP和WebSocket消息,只需要将I设置为不同的类型即可。

对于WebSocketFrame,我们有:

1
scala复制代码public class Server2FrameHandler extends SimpleChannelInboundHandler<WebSocketFrame>

对于FullHttpRequest,我们有:

1
scala复制代码public class Server2HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest>

处理WebSocketFrame

对于WebSocketFrame消息,从上一节我们知道它有6种类型,分别是:

1
2
3
4
5
6
复制代码BinaryWebSocketFrame
CloseWebSocketFrame
ContinuationWebSocketFrame
PingWebSocketFrame
PongWebSocketFrame
TextWebSocketFrame

其中真正包含内容的是TextWebSocketFrame和BinaryWebSocketFrame,这里我们对TextWebSocketFrame进行专门处理:

1
2
3
4
5
6
7
8
9
10
11
java复制代码    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {

if (frame instanceof TextWebSocketFrame) {
// 将接收到的消息转换成为大写
String request = ((TextWebSocketFrame) frame).text();
ctx.channel().writeAndFlush(new TextWebSocketFrame(request.toUpperCase(Locale.CHINA)));
} else {
String message = "不支持的Frame类型: " + frame.getClass().getName();
throw new UnsupportedOperationException(message);
}
}

处理HTTP

对于HTTP请求中的FullHttpRequest,我们就安装正常的HTTP服务请求的处理流程来就行。

这里不做过多阐述。

编码和解码器

等等,我们是不是忘记了什么东西?对,那就是编码和解码器。

在上一节中,我们使用的是WebSocketServerHandshaker来对websocket消息进行编码和解码。不过其实是放在我们自定义的hadler代码里面的,使用起来略显不优雅。

没关系,netty为我们提供了一个WebSocketServerProtocolHandler类,专门负责websocket的编码和解码问题。

除了处理正常的websocket握手之外,WebSocketServerProtocolHandler类还为我们处理了Close, Ping, Pong这几种通用的消息类型。而我们只需要专注于真正的业务逻辑消息即可,十分的方便。

对于剩下的Text或者Binary frame数据,会被交由pipline中的下一个handler进行处理。

其中Handshake有两个状态,分别是:

HANDSHAKE_COMPLETE 和 HANDSHAKE_TIMEOUT。

而HandshakeComplete又包含了requestUri,requestHeaders和selectedSubprotocol这三个方面的信息。

最后,将WebSocketServerProtocolHandler加入到pipeline中,最终得到:

1
2
3
4
5
6
7
8
9
10
java复制代码    public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();

pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536));
pipeline.addLast(new WebSocketServerCompressionHandler());
pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, null, true));
pipeline.addLast(new Server2HttpHandler());
pipeline.addLast(new Server2FrameHandler());
}

总结

一个分离了HTTP请求和webSocket请求的服务器就完成了。简单直观才是一个程序员追求的世界!

本文的例子可以参考:learn-netty4

本文已收录于 www.flydean.com/24-netty-we…

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

本文转载自: 掘金

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

腾讯云短信服务

发表于 2021-09-29

发送短信工具类:

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


import com.github.qcloudsms.SmsSingleSender;
import com.github.qcloudsms.SmsSingleSenderResult;
import org.json.JSONException;

import javax.xml.ws.http.HTTPException;
import java.io.IOException;
import java.util.Random;

/**
* @author zagwk
* @version 1.0
* @date 2020/7/25 0025 14:00
*/
public class SMSUtil {

//短信应用SDKAppId
private int appid = 111111111;
// 短信应用SDKAppKey
private String appkey = "ajdhfa546afe654fa";

// 短信模板ID,需要在短信应用中申请
private int templateId = 456489;

// 签名
// NOTE:真实的签名需要在短信控制台中申请,另外签名参数使用的是`签名内容`,而不是`签名ID`
private String smsSign = "xxxxx";


/**
*     * 发送短信验证码
*     *
*     * @paramtel 电话号码
*     * @paramverifyCode 验证码
*     * @return
*    
*/
public String sendCaptcha(String tel, String verifyCode) {
try {
// 需要发送短信的手机号码
String phoneNumber = tel;

// 单发短信
// SmsSingleSender ssender = new SmsSingleSender(appid, appkey);
// SmsSingleSenderResult result = ssender.send(0, "86", phoneNumber, "您正在注册成为好学堂用户,您的验证码为:" + verifyCode + ",请在10分钟内完成验证,感谢您的支持!", "", "");

// 指定模板ID单发短信
String[] params = {verifyCode};
SmsSingleSender ssender = new SmsSingleSender(appid, appkey);
SmsSingleSenderResult result = ssender.sendWithParam("86", phoneNumber, templateId, params, smsSign, "", "");
//返回验证码
return verifyCode;
// 签名参数未提供或者为空时,会使用默认签名发送短信
// System.out.print(result);

} catch (HTTPException e) {
// HTTP响应码错误
e.printStackTrace();
} catch (JSONException e) {
// json解析错误
e.printStackTrace();
} catch (IOException e) {
// 网络IO错误
e.printStackTrace();
}catch (com.github.qcloudsms.httpclient.HTTPException e){
e.printStackTrace();
}
return null;
}


}
1
2
3
4
5
arduino复制代码腾讯云控制台:
https://console.cloud.tencent.com/


短信应用SDKAppId

1
复制代码
1
复制代码短信应用SDKAppKey

1
复制代码
1
复制代码短信模板ID,需要在短信应用中申请

1
复制代码
1
go复制代码签名参数使用的是`签名内容`,而不是`签名ID`

1
复制代码

本文转载自: 掘金

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

使用ThreadLocal在线程间传递参数

发表于 2021-09-29

使用场景

用户参数透传,如租户ID,tid,userID,Token等。这些参数与具体业务无关,但是又是必须的(如:租户ID,在数据落库时需要传入,但是在其他业务代码中基本用不到)如果作为方法的参数一层一层向下传递,必然造成代码的冗余和扩展性查等影响。

整片文章就以租户ID为例子说明吧。

1.使用ThreadLocal结合AOP

大致思路:在请求接口时,租户ID往往可以从Token或用户信息中取出,这时,在请求拦截器中,将用户TenantID取出,放入线程中。后面在需要TenantID时,直接从线程中取出,这样,既保证了简洁性也增加了易用性。

2.Pom依赖

本文使用阿里线程工具。

1
2
3
4
5
6
xml复制代码<!--TTL-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.4</version>
</dependency>

3.RequestFilter

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
java复制代码import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @author litongzero
*/
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TenantContextHolderFilter extends GenericFilterBean {

@Override
@SneakyThrows
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 从Token/Header/param中获取租户ID
String headerTenantId = request.getHeader("TENANT_ID");

log.debug("TenantContextHolderFilter|header中的TENANT_ID为:{}", headerTenantId);

// 判断逻辑,自行定义
if (headerTenantId != null && headerTenantId != "" && !headerTenantId.equals("null")) {
TenantContextHolder.setTenantId(Integer.parseInt(headerTenantId));
} else {
// 默认值
TenantContextHolder.setTenantId(0);
}
filterChain.doFilter(request, response);
// 请求结束,一定要清除线程中自己设置的参数。
TenantContextHolder.clear();
}

}

4.TenantContextHolder(线程参数设置工具)

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复制代码/**
* @author litongzero
*/
@UtilityClass
public class TenantContextHolder {

private final ThreadLocal<Integer> THREAD_LOCAL_TENANT = new TransmittableThreadLocal<>();

/**
* TTL 设置租户ID<br/>
* @param tenantId
*/
public void setTenantId(Integer tenantId) {
THREAD_LOCAL_TENANT.set(tenantId);
}

/**
* 获取TTL中的租户ID
* @return
*/
public Integer getTenantId() {
return THREAD_LOCAL_TENANT.get();
}

/**
* 清除当前线程中的租户
* 慎用
*/
public void clear() {
THREAD_LOCAL_TENANT.remove();
}

}

5.使用

在具体的Controller,Service,Mapper中,只要是当前请求的线程,都是可以直接使用TenantContextHolder.getTenantId()获取租户ID的。

本文转载自: 掘金

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

JAVA后台生成echarts图片保存

发表于 2021-09-29

​

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。​

1
arduino复制代码业务场景,用户每月或者每天会有对应的用户画像生成的报告,word需要引入echarts图表,而数据是实时分析的,所以需要在进行word报告导出之前先生成对应的echarts数据图表的图片,以便对word文档进行图片写入,后台需要根据数据主动生成echarts数据图表存放到某一地址或者文件服务下,word文档导出时在图表部位引入实时echarts数据生成的图片导出观看

实现文章:java后台生成echarts图表并保存图片

博主就是使用了echarts-convert1.js和phantomjs-2.1.1通过cmd调用生成echarts再进行下载,里面使用了PhantomJS工具和echarts-convert.js

下载链接:

链接:pan.baidu.com/s/1NX9pf77S…

提取码:wli7

但是在测试过程中也遇到了一些问题:

1、PhantomJS工具的安装

下载地址:Download PhantomJS

这是官网的下载地址,但在上面的百度云网盘里应该已经有了,但是是windows的,官网里分别提供了Windows、Mac、以及Linux 的安装包,根据自己的需要下载即可。下载完成后,将其解压到容易找到的文件夹中,打开并找到bin文件夹里的 phantomjs.exe,点击运行,出现如下界面,说明安装成功,可以使用了。

)​

2、环境配置

找到bin文件下的phantomjs.exe,复制文件夹路径,比如我就是:C:\Users\Administrator\Desktop\me\reference\phantomjs-2.1.1-windows\bin

)​

然后打开计算机的属性界面,并按照下图标记进行操作:

)​

)​打开环境变量找到path属性

)​

把我们刚才粘贴的bin的地址加到path里

)​

然后保存,环境变量就好了,打开cmd窗口验证:输入phantomjs

)​

出现红框里的东西就好了

)​

3、开发问题

当我配置环境时,在java中执行cmd命令的时候报错

java.io.IOException: Cannot run program “phantomjs”: CreateProcess error=2, 系统找不到指定的文件

这个问题是说当前环境没有配置这个命令,我在cmd中执行java执行的命令是可以生成图片到指定地址,我在考虑java环境的配置是否更新,最后排除测试还是无效,无奈重启,发现执行成功,在使用开发程序时环境可能不能及时更新环境变量,需重启更新。

​

本文转载自: 掘金

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

一位程序员社畜的2021闲读书单! 写在前面 读书规划和进度

发表于 2021-09-29

写在前面

其实这篇我很早就想写了,但一直拖着,周末码几个字,一直拖到了现在。

今年年初的时候,我就给自己定了两个今年必须长期坚持的大目标,一个是坚持锻炼,另外一个则是坚持读书,当然这里指的是非技术类的书。

关于锻炼的事情一言难尽,先不聊了。今天不妨先来聊一聊关于读书计划(非技术篇)的事情。

俗话说得好,业宜精专,书宜读杂。从去年开始,主要我就想多看一点技术以外的杂书,最好是文学、传记、管理、心理、哲学、理财等方面都涉及一点。

我还是觉得理工科生抽空读点技术专业以外的杂书也挺好,最主要是能开阔开阔思维。

程序员的思维是严谨倒没错,但有时候却不一定开阔。所以如果总僵化地守着自己那一亩三分地,不开阔一下思维,路可能会越走越窄,思维也会变得僵化,从而可能愈发焦虑和迷茫。

所以很多时候我们有必要换一些角度来看生活,这样也就不至于把自己某些小事看得太过于纠结。

而读书恰好则是一个性价比极高的低门槛途径。

本文在GitHub开源仓库「编程之路」 github.com/rd2coding/R… 中已经收录,里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记,以及我的程序员人生,欢迎鉴赏。


读书规划和进度

之前的想法是每天晚上睡前抽个四十分钟看一看,但是实际操作下来发现不一定每天都能控制得这么好,尤其是最近两个月由于太忙,进度又慢下来了。

有时候回来太晚或者实在不想看也就停两天,主要是我也不想这事成为一种心里上的负担,不然就适得其反了。

我给自己定的目标是很佛系的,总体计划是一个月能完整读完一到两本技术之外的书就很满意了,这样一年下来也能读完十几本。如果都能认真看完,并且有所理解和心得,那就非常值得了。

实际情况是,半年过去了,进度总体比预计的要慢一些,不过没啥压力,属于休闲看的那一种。

下面分享一下今年的书单,年初的时候做计划时列的,这些书籍基本都来源于后台小伙伴的安利推荐,以及羊嫂给我的任务安排,我从中选的十几本。

也欢迎大家在评论区分享出你们觉得不错的书籍或者书单,一起交流看看。


书单

  • 《南京传》(已完成)
  • 《正面管教》(已完成)
  • 《养育女孩》(已完成)
  • 《数学之美》(已完成)
  • 《非暴力沟通》(已完成)
  • 《软技能·代码之外的生存指南》(已完成)
  • 《诗经》(循环看)
  • 《软技能2·软件开发者职业生涯指南》(已完成)
  • 《债务危机》(进行中…)
  • 《怪诞心理学》(进行中…)
  • 《弱传播》
  • 《被讨厌的勇气》
  • 《知行·技术人的管理之路》
  • 《偏见》
  • 《复杂生命的起源》

目前看完的几本包括:

  • 《软技能系列》

正如标题所言,这是两本和程序员自身多元化发展,以及软件开发者职业生涯息息相关的书。

书里面讲到了很多有关职场和程序员职业生涯的分析,各种自我营销途径的解释,个人的持续学习和成长,甚至还聊到了理财、健身、找工作、谈薪、心态调整…等诸多问题,不少看起来都是代码之外的东西。

然而往往就是这些代码之外的软技能,会让人变得更加自信和有竞争力。

诚然,我觉得程序员应该重视代码,但不应该只关注代码,很多代码之外的东西对现如今的生存也非常重要。

  • 《南京传》

作为一个新南京人,听听老南京人讲南京故事就挺好,这本书满足了我的这个愿望。

当你了解了你所在城市的历史,生活于其中的感受是完全不同的。不得不说,是一本非常优秀的城市传记,作者的文字读起来更是令人酣畅淋漓。

  • 《正面管教》& 《养育女孩》

虽然表面看起来都是育儿方面的书籍,但是看了之后才发现这说的不就是当年的自己嘛。

读完才惊奇地发现原来成年人世界的很多性格缺陷以及做事的方式、态度,都无一不和幼时的管教环境有关,甚至那些一直操纵着成年人,让我们有时候无法好好生活的不良行为、性格、态度、习惯,居然都能溯源。

  • 《数学之美》

这本书我应该是三刷了,而且我读的这本还是吴军先生的签名版,所以每次都颇有仪式感。作为一个通信专业出来的同学,里面提到的几十个技术科普主题的理论部分大部分其实都接触过,但是不少确实都已经忘了,所以看起来总有一种既熟悉又陌生的感觉。

但每次刷这本书给我的感觉就是,作者确实高屋建瓴、化繁为简,能够将当年学起来感觉非常难懂晦涩的数学问题解释得通俗易懂,算是深入浅出了。除此之外也结合了很多工程应用方面的东西,也算是更加开阔了眼界。

  • 《非暴力沟通》

这是一本非常热门的心理学图书,很多人应该都看过。顾名思义,就是使用非暴力的方式进行沟通。

暴力沟通的方式和场景在平时的家庭、生活以及工作中不经意间出现的频率还是很高的。

“暴力褪去,流露出来的将会是爱”。

这本书里所讲的关于非暴力沟通的分析和要素适用于日常生活以及工作。个人感觉,尤其是在生活中,我觉得更有用,因为我们总习惯于将好的一面给别人,而把最差的一面却留了自己最亲近的人。所以无论何时,多一点倾听、多一点理解、多一点真诚、也多一点爱。

  • 《诗经》

作为中国最早诗歌总集,《诗经》所承载的经典和意义想必就不用多说了,满满的都是优美的文字,有时间读读古诗美文也挺好的,就当陶冶情操了,没事读读也的确心静。

这地方顺便多提一点就是(一般人我都不告诉他),多读读诗经,说不定以后给孩子起名字的时候都能用得到呢,岂不美哉。


后 记

所以今天就先聊到这里吧,等年末的时候,会再来回顾一年的读书情况,做一个年度的读书复盘。

不得不说,读书的时间确实会让人内心感到十分平静和踏实,忘了压力和焦虑。这种感觉对于当下竞争激烈的程序员们来说真的是非常珍贵了。

最后也欢迎大家分享出你们觉得不错的书籍或者书单,一起交流提高!

本文在GitHub开源仓库「编程之路」 github.com/rd2coding/R… 中已经收录,里面有我整理的6大编程方向(岗位)的自学路线+知识点大梳理、面试考点、我的简历、几本硬核pdf笔记,以及我的程序员人生,欢迎star。

本文转载自: 掘金

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

1…513514515…956

开发者博客

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