《解忧杂货店》

  全书共有五个章节,十一之后回来的连续五天读完,阅读时间统一为下班回家的地铁上,不由得感叹零碎时间充分的利用起来居然可以这么快的看完一本书。当然,这也离不开这是一本翻开就停不下来的书。
  在这之前,看过东野圭吾的《白夜行》与《嫌疑人X的献身》。受这两部作品的影响,以为《解忧杂货店》也是一本悬疑小说,结果完全出乎意料。
  书中,通过一封封的信件一个个的故事,看似杂乱,到后来都串成了一个统一的整体,作者的写作水平可见一斑。
  最后的一张白纸既代表着一无所有,也代表着无限的可能。
  本来想整理一下全书的时间线,结果发现知乎上已经有人整理了,在这里

一些标注:

  • 但她那略带忧郁的表情吸引了克郎。在她身上,有种不属于孩子的成熟韵味。
  • 那个女孩眼也不眨地望着他,眼神十分真挚。
  • 他们对克郎造成强烈的刺激。这种刺激用一句话概括,就是他们对音乐的热情。他们宁可牺牲一切,也要提高自己的音乐水准。
  • “歌唱得跟你一样好的人多的是,如果你的声音很有特色,自然另当别论,但你没有。”
  • “以外行来说,是还好。”评论家淡淡地答道,“不过可惜也就这个水平了。歌的旋律总有似曾相识的感觉,没有自己的新意。”
  • 人生中浪矢杂货店具有重要意义的人,也许出乎意料的多。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

解决sql脚本导入Oracle重复生成check约束问题

  之前一位细心的同事发现产品的全量sql脚本中有一些重复的check约束检查,就像下图这样的

怪异之处还在于,每次执行一遍该脚本,然后导出脚本,在导出脚本中重复的次数就会增加一遍。通过navicat,最终确认每导入一次就会新增加一条重复的check约束,如下图所示

  这个全量脚本是直接从数据库中导出的,为了方便导入其他的Oracle数据库中,从产品的出货库导出时手动去掉了服务名双引号

  通过如下步骤可复现该问题:
1.创建表

1
2
3
4
5
6
CREATE TABLE PD_WEB_FILEUPLOAD_CHUNK (
ID VARCHAR2(32 BYTE) NOT NULL ,
MD5 VARCHAR2(32 BYTE) DEFAULT NULL NULL ,
CHUNK NUMBER DEFAULT NULL NULL ,
FILE_DIR VARCHAR2(200 BYTE) NOT NULL
)

可以看到上面的脚本中有NOT NULL的标识,执行完后在navicat中可以看到结果是这样的

注意看,这里的check约束是带双引号的。

2.执行增加check约束的脚本

1
2
ALTER TABLE PD_WEB_FILEUPLOAD_CHUNK ADD CHECK (ID IS NOT NULL);
ALTER TABLE PD_WEB_FILEUPLOAD_CHUNK ADD CHECK (FILE_DIR IS NOT NULL);

执行了两遍后,结果如图所示

测试到这里,以为最终终于找到了原因,确认为双引号的问题。抱着严谨的态度,再次确认了一下

3.执行带双引号的check约束的脚本

1
2
ALTER TABLE PD_WEB_FILEUPLOAD_CHUNK ADD CHECK ("ID" IS NOT NULL);
ALTER TABLE PD_WEB_FILEUPLOAD_CHUNK ADD CHECK ("FILE_DIR" IS NOT NULL);

结果如图所示:

靠!居然还是会重复生成!

  验证要这里,算是找出了原因。在全量导出的脚本中,创建表的脚本中已经隐含了检查约束,如果再显示的添加检查约束就会重复生成。所以,解决办法为需要手动删除所有显示的检查约束。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

《精进》

  之所以找到这本书是因为在这之前在知乎看过作者关于学习半衰期理论的回答,受益匪浅。

  在看本书之前,在知乎上翻了一下对本书的评价,有人认为这是一本浓浓的鸡汤。因为作者之前答案的深度,抱着试试的心态读完的全文。看完之后觉得这本书其实满满的都是学习过程中经验感悟的总结分享。

  其实,也有看不懂的部分。但原因极有可能是没有亲自熬过鸡汤,无法理解熬制过程的曲折、经验教训与总结,无法感同身受。

下面是一些书中的摘录,整理如下:

  • 一个人如何对待他的时间,决定了他可以成为什么样的人。

    相同年龄的人到目前为止都拥有相同的时间,因为对时间的不同使用而成为了不同的人

  • 郑重是这样一种态度:不敷衍、不迟疑、不摇摆,认真地聚焦于当下的事情,自觉而专注地投入。

  • 多采纳积极过去视角、享乐主义视角和未来视角,并且在三者中取得平衡,少采纳负面作用明显的消极过去视角和宿命论视角。

  • “想想五年后你会干些什么,过什么样的生活?”

    这的确是一个十分值得思考的问题,要做一个有目标、有准备的人。

  • 在分析一件事情值不值得去做、花多少精力去做的时候,可以从两个角度来评估:一是这件事在当下将给“我”带来的收益大小,这个收益可以是心智、情感层面的,也可以是身体、物质层面的,我称之为“收益值”;二是这项收益随时间衰减的速度,我称之为“收益半衰期”,半衰期长的事件,其影响会持续较久。

  • 侯世达定律:“实际做事花费的时间总是比预期的要长,即使预期中考虑了侯世达定律。”

  • 工作要快,但生活要慢。现代人的日常生活应该有快有慢,而不是一味地和时间竞赛。什么叫有快有慢?用音乐的说法就是节奏。

  • 美学家朱光潜先生曾经说过:“做学问,做事业,在人生中都只能算是第二桩事。人生第一桩事是生活。我所谓‘生活’是‘享受’,是‘领略’,是‘培养生机’。假若为学问为事业而忘却生活,那种学问和事业在人生中便失其真正意义与价值。”

  • 我们从闲暇中获得放松和满足的程度并不取决于闲暇时间的长度,而是取决于其质量。

  • 人在面临选择时,通常会采用“满意原则”,而不是“最优原则”。

  • 一个成熟的人,他的标准来自他的内心,而大多数人,却受环境所左右。

  • 如果说在零度格局下,盲众看到的是幻象和噪声;一度格局下,逐利者看到的是自己的能力和欲望,那么理念人看到的是这个世界深处的真和美,而至善之人看到的是自我与世界、自我与整个人类之间的纽带。

  • 正如伟大的史怀哲所说: “人不能只为他自己而活。我们必须认知所有的生命都是珍贵的,而我们和所有的生命是结合在一起的。这种认知指引了我们心灵和宇宙的关系。”

  • 当我们在人生中遇到某个无法摆脱的僵局时,先不妨试试这三步: 1.找出潜意识中的隐含假设; 2.识别隐含假设中的不合理性,进行校正; 3.形成新的更灵活的思维框架,在此基础上思考出“可能选项”并进行尝试。

  • 管理学大师彼得·德鲁克认为,大多数的智力劳动者,对自己的工作都有一定的掌控权和自由度,工作并不完全是由组织分配和决定的,而是具有充分的弹性。

    比如coding,有时真的看心情…

  • 而“改造”爱好的一个常见方法,是把对一件事情的“消费型兴趣”升级为“生产型兴趣”。

  • 为自己设定更高的目标,就会发现更多更好的选项,做出更加完美的决定。

  • 一件看上去繁难的事,只要开始做了,就会变得越来越容易。

    深有体会,在面对浩大代码量、复杂逻辑的程序面前,开始写出了第一行代码就成功了一半。

  • 从一篇短文到一篇长文,从一篇长文到一本书;从单幅的漫画到多格漫画,再从多格漫画到长篇漫画连载。如此一来,你的才能、声誉、影响力和成就感都会慢慢培育出来。

  • “先做好准备再上场”观念的一个致命问题是:我们永远都无法做好“完全”的准备。

  • 正如英国作家罗根·史密斯说的:“这个宇宙上的一件古怪的事情是,虽然我们大家意见不一,我们大家却总是正确的。”

  • 管理学大师彼得·德鲁克说:“最悲哀的,莫过于用最高效的方式去做错误的事情了。”

  • 所以以精益创业的方式去走向人生的成功,便要做到这三点: 1. 克服“过度准备”的惯性,向前一步,把未完成的事情完成; 2. 克服“自我防卫”的心态,乐于接受反面意见并加以慎重地审视; 3. 克服“沉没成本”的固执,有勇气否定并重新构造自己的产品。

  • 拿到一个任务后,务必要先找到那个任务的核心思考区间,找到那块硬骨头,尽全力去啃下来,而不是先去做那些周边的打扫性的工作。

  • 作为一个读者,你总是从小说的第一句依次看到最后一句。而对一个创作者而言,小说写作的次序存在多种可能性。

  • 写一篇好散文要经过三个台阶,一个是音乐的,这时它被构思;一个是建筑的,这时它被搭建起来;最后一个是纺织的,这时它被织成。”

  • 牛人总是在前瞻性思维和总结性思维上都非常出色。

    正因为有这样的思维才牛

  • 只有最后能够作用于现实的学习,才是唯一有效的学习。

    不同意该说法

  • 就我自己来说,我的阅读和思考,都是在自己提出的问题的牵引之下、在因问题无法完美解答所形成的焦虑和不安的鞭策之下进行的。对问题的好奇、对答案的渴望,是驱动我学习和探索的主要动力。

  • 本质安全是指通过设计等手段使生产设备或生产系统本身具有安全性,即使在误操作或发生故障的情况下也不会造成事故的功能”;

  • 伟大的艺术作品,常常有很深厚的内涵和很精巧细微的技法,不论你在哪个或深或浅的层次上解读它,它都能呈现出美妙的意味,但如果你不做一番细心的努力和挖掘,就只能尝到最表层的那一小部分味道。

  • 不只要去寻结论,还要去寻过程

  • 一个艺术作品要能卖出大价钱,关键是“通过作品,创造出世界艺术史的脉络”,也就是说,“从该作品之后,是否开创了新的历史”。

  • 一幅画作的价值,不在于它的线条、颜色、构图,而在于其背后所展现出的“观念”,艺术品的价值就是观念的价值。

    耳目一新

  • 价值体现在观念的更新

  • 而加上这些副词以后,不仅信息量没有增加,还使读者原本可以有的对人物表情的想象,塌缩成了一个寻常的词汇。

  • 适度的简洁,意味着更丰富的内涵

    需谨记在心

  • 很多研究思维和创意的学者认为,先发散后收敛的顺序是最为合理的

  • 努力不是一场意志力的较量,而是一种需要学习的策略。

  • 知乎名言“以大多数人的努力程度之低,根本轮不到拼天赋。”

  • 身为音乐家,我一辈子都在追求完美,可完美总是在躲着我。所以,我有责任一次次尝试下去。

  • 挑战是设计出来的

  • 练大脑就像练肌肉,得科学“加量”才行。

  • 如果我能长期坚持去做一件事,一定是这件事带给我的丰盈感和满足感超过了我的所有付出,一定是这件事日日夜夜萦绕在我的心头让我欲罢不能,一定是这件事唤起了我内心深处最强烈的兴趣。也就是说,赐予我力量的,是激情的驱动,而不是意志力的鞭策。

  • 了解不够导致兴趣不足,而兴趣不足又无法加深对它的了解。由于了解不足而判断失误,而判断失误又妨碍了深入了解。

  • 著名书法家启功就批评过种种学习书法的教条,认为它们误事,害人。比如毛笔怎么拿,怎么拿是对的、怎么拿是错的,有严格的标准吗?没有的。又如临什么帖,学什么体,用什么纸,有什么一定之规吗?也没有的。他直言道:“写字为什么?我把字写出来,我写的字我认得,给人看人家认得,让旁人看说写得好看,这不就得了吗!你还要怎么样才算合‘法’呢?”

    《金刚经》中有这么一段话:“一切有为法。如梦幻泡影。如露亦如电。应作如是观。”

  • 每一个成功者,都是唯一的 创造成功,而不是复制成功

  • 因为你的存在,这个多元的世界又增加了一种新的可能性。

  • 学霸选择依从,学渣选择逃避,但有一点又非常相似,就是他们都没有培养出学习的自主性,并没有回答好自己到底想学什么、怎么学好的问题。很多人参加完高考,就稀里糊涂地由着各种想象选择了一个专业,没有机会和能力来想到底适不适合,然后因为路径依赖,便将就着学下去,丧失了更好地发展自己的机会。

  • 如果你总是在某个专业的壁垒里打转,视野就会变得越来越狭窄,即便本领域的提升也会越来越艰难,很容易就碰触到天花板。

  • 在新思想或新技术刚刚开始兴起无人问津之时就投入进去,成为某一个新知识领域的先驱,实现知识能力的“低买高卖”。

  • 在现实世界中思考理论问题,在理论世界中思考现实问题。

  • 很多学者认为最好的竞争策略并不是教你如何跟其他人竞争,而是开拓出一条独一无二、罕有竞争者的道路。

    何其之难…

  • 当缺少可以让内心安稳下来的独立标准时,每个人只能随波逐流。

    内心的安稳来自于精神的富足

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

Excel导入导出

  在后台管理系统中,常遇到Excel导入导出的需求,整理了如下两个工具类。

一、导入

导入工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package com.demo.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.poi.POIXMLDocument;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;

public class ExcelToEntityList {

private Logger log = (Logger) LoggerFactory.getLogger(this.getClass());
private BeanStorage storage = new BeanStorage();
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
private StringBuffer error = new StringBuffer(0);

/**
* 包含headMapping信息已经前端传过来的扩展信息
* @param entity 一行excel内容要转换为何种对象
* @param excel excel输入流
* @param titleToAttr key为excel的中文title,value为该中文title对于的entity属性名
* @return
* @throws IOException
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws InvalidFormatException
*/
public <T> ArrayList<T> transform(Class<?> entity,InputStream excel,Map<String,String> titleToAttr) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvalidFormatException{
ArrayList<T> result = new ArrayList<T>();

Workbook book = create(excel);
Sheet sheet = book.getSheetAt(0);
int rowCount = sheet.getLastRowNum();
if(rowCount < 1){
return result;
}
//加载标题栏数据,以此和headMapping对应
Map<Integer, String> headTitle = loadHeadTitle(sheet);
for(int i=1;i<=rowCount;i++){
Row row = sheet.getRow(i);
//空行跳过
if(row==null){
continue;
}
int cellCount = row.getLastCellNum();
@SuppressWarnings("unchecked")
T instance = (T) entity.newInstance();
int col = 0;
try {
for(;col<cellCount;col++){
String cellValue = getCellValue(row.getCell(col));
if(null!=cellValue){
this.setEntity(entity, instance,titleToAttr.get(headTitle.get(col)), cellValue);
}
}
result.add(instance);
} catch (Exception e) {
//excel.close();
e.printStackTrace();
this.error.append("第"+ (i+1) +"行,"+ headTitle.get(col)+"字段,数据错误,跳过!").append("<br>");
log.error("第"+ (i+1) +"行,"+ headTitle.get(col)+"字段,数据错误,跳过!");
}
}
excel.close();
return result;
}

/**
* 加载Excel的标题栏
* @param sheet
* @return 返回列序号和对于的标题名称Map
*/
private Map<Integer,String> loadHeadTitle(Sheet sheet){
Map<Integer,String> map = new HashMap<Integer, String>();
Row row = sheet.getRow(0);
int cellCount= row.getLastCellNum();
for(int i = 0; i < cellCount; i++){
String value = row.getCell(i).getStringCellValue();
if(null == value){
throw new RuntimeException("Excel导入:标题栏不能为空!");
}
map.put(i, value);
}
return map;
}

/**
* 获取表格列的值
* @param cell
* @return
*/
private String getCellValue(Cell cell){
if(null==cell){return "";}
String value = null;
switch (cell.getCellType()){
case XSSFCell.CELL_TYPE_BOOLEAN:
value = String.valueOf(cell.getBooleanCellValue());
break;
case XSSFCell.CELL_TYPE_NUMERIC:
// 判断当前的cell是否为Date
if (DateUtil.isCellDateFormatted(cell)){
value = dateFormat.format(cell.getDateCellValue());
}else{
value = String.valueOf((long) cell.getNumericCellValue());
}
break;
case XSSFCell.CELL_TYPE_STRING:
value = cell.getStringCellValue();
break;
case XSSFCell.CELL_TYPE_FORMULA:
log.debug("不支持函数!");
break;
}

return value;
}

private <T> void setEntity(Class<?> clazz, T instance, String pro, String value) throws SecurityException, NoSuchMethodException, Exception{
String innerPro = null;
String outterPro = null;
if (pro.contains(".")){
String[] pros = pro.split("\\.");
outterPro = pros[0];
innerPro = pros[1];
// 将成员变量的类型存储到仓库中
storage.storeClass(instance.hashCode() + outterPro, clazz.getDeclaredMethod(this.initGetMethod(outterPro), null).getReturnType());
}
String getMethod = this.initGetMethod(outterPro!=null?outterPro:pro);
Class<?> type = clazz.getDeclaredMethod(getMethod, null).getReturnType();
Method method = clazz.getMethod(this.initSetMethod(outterPro!=null?outterPro:pro), type);
if (type == String.class){
method.invoke(instance, value);
}else if (type == int.class || type == Integer.class){
method.invoke(instance, Integer.parseInt("".equals(value) ? "0" : value));
}else if (type == long.class || type == Long.class){
method.invoke(instance, Long.parseLong("".equals(value) ? "0" : value));
}else if (type == float.class || type == Float.class){
method.invoke(instance, Float.parseFloat("".equals(value) ? "0" : value));
}else if (type == double.class || type == Double.class){
method.invoke(instance, Double.parseDouble("".equals(value) ? "0" : value));
}else if (type == Date.class){
method.invoke(instance, dateFormat.parse(value));
}else if (type == boolean.class|| type == Boolean.class){
method.invoke(instance, Boolean.parseBoolean("".equals(value) ? "false" : value));
}else if (type == byte.class|| type == Byte.class){
method.invoke(instance, Byte.parseByte(value));
}else{
// 引用类型数据
Object ins = storage.getInstance(instance.hashCode() + outterPro);
this.setEntity(ins.getClass(), ins, innerPro, value);
method.invoke(instance, ins);
}
}

public String initSetMethod(String field)
{
return "set" + field.substring(0, 1).toUpperCase() + field.substring(1);
}

public String initGetMethod(String field)
{
return "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
}

/**
* @return true 存在错误,false 不存在错误
*/
public boolean hasError()
{
return error.capacity() > 0;
}

public StringBuffer getError()
{
return error;
}
/**
* 存储bean中的bean成员变量
*/
private class BeanStorage
{
private Map<String, Object> instances = new HashMap<String, Object>();
public void storeClass(String key, Class<?> clazz) throws Exception{
if (!instances.containsKey(key)){
instances.put(key, clazz.newInstance());
}
}
public Object getInstance(String key){
return instances.get(key);
}
}

//2003、2007兼容处理
public Workbook create(InputStream inp) throws IOException,InvalidFormatException {
if (!inp.markSupported()) {
inp = new PushbackInputStream(inp, 8);
}
if (POIFSFileSystem.hasPOIFSHeader(inp)) {
return new HSSFWorkbook(inp);
}
if (POIXMLDocument.hasOOXMLHeader(inp)) {
return new XSSFWorkbook(OPCPackage.open(inp));
}
throw new IllegalArgumentException("你的excel版本目前poi解析不了");
}
}

使用方式

1
2
3
4
5
6
//前端部分提交excel文件与该对应关系到后端
var mapping = {
"EntityClassName":"com.demo.entity.User",
"用户编号":"userCode","用户名称":"userName",
"电话":"phone","地址":"address"
}
1
2
3
4
5
6
7
/*后端部分在servlet中接收excel与excel头与entity属性的对应关系*/
//接收对应关系参数
HashMap<String, String> param = JSON.parseObject(URLDecoder.decode(request.getParameter("mapping"), "utf-8"), HashMap.class);
ExcelToEntityList excel = new ExcelToEntityList();
//把excel文件内容转换为List对象
ArrayList<?> list = excel.transform(Class.forName(param.get("EntityClassName")), request.getPart("file").getInputStream(), param);

二、导出

导入工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package com.demo.util;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFComment;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFPatriarch;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;

public class EntityListToExcel {
private StringBuffer error = new StringBuffer(0);

/**
* 将实体类列表entityList转换成excel
* @param param 包含headMapping信息,key为属性名,value为列名<br>
* @param entityList
* @param excel
* @return
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws IOException
*/
public <T> boolean transform(Map<String, String> param, List<T> entityList,OutputStream excel) throws NoSuchMethodException,
SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, IOException {
// 声明一个工作薄
HSSFWorkbook workbook = new HSSFWorkbook();
// 生成一个表格
HSSFSheet sheet = workbook.createSheet();
// 设置表格默认列宽度为15个字节
sheet.setDefaultColumnWidth(15);
// 声明一个画图的顶级管理器
HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
// 定义注释的大小和位置,详见文档
HSSFComment comment = patriarch.createComment(new HSSFClientAnchor(0,
0, 0, 0, (short) 4, 2, (short) 6, 5));
// 设置注释内容
comment.setString(new HSSFRichTextString("可以在POI中添加注释!"));
// 设置注释作者,当鼠标移动到单元格上是可以在状态栏中看到该内容.
comment.setAuthor("admin");

// 产生表格标题行
HSSFRow row = sheet.createRow(0);
int i = 0;
List<String> proList = new ArrayList<String>();
HSSFFont blueFont = workbook.createFont();
blueFont.setColor(HSSFColor.BLUE.index);
for (Map.Entry<String, String> entry : param.entrySet()) {
HSSFCell cell = row.createCell(i);
HSSFRichTextString text = new HSSFRichTextString(entry.getValue());
text.applyFont(blueFont);
cell.setCellValue(text);
proList.add(entry.getKey());
i++;
}
// 遍历集合数据,产生数据行
Iterator<T> it = entityList.iterator();
int index = 0;
while (it.hasNext()) {
index++;
row = sheet.createRow(index);
T t = (T) it.next();
// 利用反射,根据javabean属性的先后顺序,动态调用getXxx()方法得到属性值
for (i = 0; i < proList.size(); i++) {
HSSFCell cell = row.createCell(i);
String propertyName = proList.get(i);
String textValue = null;
try {
textValue = this.getPropertyValue(t, propertyName);
} catch (Exception e) {
e.printStackTrace();
this.error.append("第").append(index+1).append("行,列名:").append(param.get(propertyName)).append(",字段:").append(propertyName).append(",数据错误,跳过!").append("<br>");
}
// 利用正则表达式判断textValue是否全部由数字组成
if (textValue != null) {
Pattern p = Pattern.compile("^//d+(//.//d+)?$");
Matcher matcher = p.matcher(textValue);
if (matcher.matches()) {
// 是数字当作double处理
cell.setCellValue(Double.parseDouble(textValue));
} else {
HSSFRichTextString richString = new HSSFRichTextString(
textValue);
cell.setCellValue(richString);
}
}
}

}
workbook.write(excel);
workbook.close();
return true;
}

/**
* 获取实体instance的propertyName属性的值
* @param instance
* @param propertyName
* @return
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
private <T> String getPropertyValue(T instance, String propertyName)
throws NoSuchMethodException, SecurityException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException {

String getMethodName = this.initGetMethod(propertyName);
Class<?> tCls = instance.getClass();
Method getMethod = null;
Object value = null;

getMethod = tCls.getMethod(getMethodName, new Class[] {});
value = getMethod.invoke(instance, new Object[] {});

String returnType = getMethod.getReturnType().getName();

// 判断值的类型后进行强制类型转换
SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String textValue = null;
if ("java.util.Date".equals(returnType)) {
textValue = dateFormat.format(value);
}else{
textValue = value.toString();
}
return textValue;
}
/**
* 返回fiel属性的getXXX方法字符串
* @param field
* @return
*/
private String initGetMethod(String field) {
return "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
}

/**
* @return true 存在错误,false 不存在错误
*/
public boolean hasError() {
return error.capacity() > 0;
}

/**
* 获得错误信息
* @return
*/
public StringBuffer getError() {
return error;
}
}

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Servlet中设置返回类型
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
//这里也可以通过前端传过来
LinkedHashMap<String, String> header = new LinkedHashMap<String, String>();
header.put("userCode", "用户编码");
header.put("userName", "用户名");
header.put("phone", "电话");
header.put("address", "地址");

//数据导出为excel
EntityListToExcel excel = = new EntityListToExcel();
//这里的entityList为要转换为excel行的数据列表
excel.transform(header, entityList, response.getOutputStream());

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

排序算法

最近需要写一个排序算法,很久没写发现有些生疏了,抽时间用JavaScript实现了几种常用的排序算法,以备不时之需。

一、快速排序

步骤:

  1. 在数据集之中,选择一个元素作为”基准”(pivot)。
  2. 所有小于”基准”的元素,都移到”基准”的左边;所有大于”基准”的元素,都移到”基准”的右边。
  3. 对”基准”左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
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
/**
* 快速排序
* @param arr
* @returns Array
*/
var count = 0;
var arr = [55,1,22,77,99,11,88,33,66,44];
function quickSort(arr){
//递推跳出
if (arr.length <= 1) { return arr; }
//首先定义“基准”左右数据的数组
var left = [];
var right = [];
//选择第一个元素作为基准元素(基准元素可以为任意一个元素)
var pivot = arr[0];
var len = arr.length;
//由于取了第一个元素,所以从第二个元素开始循环
for(var i=1;i<len;i++){
var item = arr[i];
//大于基准的放右边,小于基准的放左边
item>pivot ? right.push(item) : left.push(item);
count++;
}
//递归左右数组
return quickSort(left).concat([pivot],quickSort(right));
}
console.log(quickSort(arr));
console.log(count); //21

二、插入排序

步骤:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果被扫描的元素(已排序)大于新元素,将该元素后移一位
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 插入排序
* @param arr
* @returns Array
*/
var count = 0;
var arr = [55,1,22,77,99,11,88,33,66,44];
function insertSort(arr){
var sorted = [];
//把数组中的第一个元素定义为已排序元素
sorted.push(arr[0]);
//不断取未排序的元素插入到已排序元素中
var len = arr.length;
for(var i= 1;i<len;i++){
var item = arr[i];
//在已经排序的元素序列中从后向前扫描
var flag = true;
var j = sorted.length-1;
for(j;j>=0;j--){
//直到比自己小的元素,就把自己放在该元素后面
if(item>sorted[j]){
//插入到元素之后
sorted.splice(j+1,0,item);
flag = false;
break
}
count++;
}
//自己最小,直接放到第一个位置
if(flag){sorted.splice(0,0,item);}
}
return sorted;
}
console.log(insertSort(arr));
console.log(count); //19

排序演示:

三、冒泡排序

步骤:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对第0个到第n-1个数据做同样的工作。这时,最大的数就“浮”到了数组最后的位置上。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 冒泡排序
* @param arr
* @returns Array
*/
var count = 0;
var arr = [55,1,22,77,99,11,88,33,66,44];
function bubbleSort(arr){
var len = arr.length;
var temp= [];
for(var i=0;i<len;i++){
//每一次的最后结果都是最大的元素放到了最后
for(var j=0;j<len-i-1;j++){
if(arr[j]>arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1]= temp;
}
count++;
}
}
return arr;
}
console.log(bubbleSort(arr));
console.log(count); //45

四、时间复杂度

来自于这里

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

《黄金时代》-王小波

  在这之前没有看过王小波的书,只闻其名。看过这本书之后,的确眼前一亮,文章原来还能这么写!
  这本书是由几篇文章组合而成,但是每篇文章的都有一个叫“王二”的主人公。
  另外,从某个角度看,这还是一本小黄书,不乏血液喷张的内容,笑点还很多。但深思之后就会发现那是一个混乱、无奈、扭曲的时代。一想到这,就怎么也笑不起来。

下面放一些看书过程中的一些摘录整理:

  • 那一天我二十一岁,在我一生的黄金时代,我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云。后来我才知道,生活就是个缓慢受槌的过程,人一天天老下去,奢望也一天天消失,最后变得像挨了槌的牛一样。可是我过二十一岁生日时没有预见到这一点。我觉得自己会永远生猛下去,什么也捶不了我。

    这句话应该是本书流传得最广的一句话,年轻时都有一段青葱岁月,然而始终会有挨捶地一天。

  • 我坐在小屋里,听着满山树叶哗哗响,终于到了物我两忘的境界。

    要达到物我两忘的境界很难

  • 仿佛在不久之前,我还是初一的学生。放学时在校门口和同学们打书包仗。我的书包打在人身上一声闷响,把人家摔出一米多远。原来我的书包里不光有书,还有一整块板砖。

    从这句话就能看出整本书的风格了

  • 你一天天老下去,牛皮一天天紧起来。

    慢慢就到了挨捶地时候

  • 眼前就是罗得岛,我就在这里跳跃——我这么做什么都不为,这就是存在本身。

    一个哲学问题

  • 我要抱着草长马发情的伟大真诚去做一切事,而不是在人前羞羞答答地表演。在我看来,人都是为了要表演,失去了自己的存在。我说了很多,可一样也没照办。这就是我不肯想起那篇论文的原因。

    又一个我要成为谁的问题。

  • 忽然之间心底涌起强烈的渴望,前所未有:我要爱、要生活,把眼前的一世当做一百世一样。这里的道理很明白:我思故我在,既然我存在,就不能装作不存在。无论如何,我要对自己负起责任。

    生而为人,是一件很幸运的事情

  • 她尖叫一声,拿被子蒙上头,就在床上游仰泳。

    形象生动!

  • 人生是一条寂寞的路,要有一本有趣的书来消磨旅途。

    这本书就是”自己”

  • 我向来不怕得罪朋友,因为既是朋友。就不怕得罪,不能得罪的就不是朋友,这是我的一贯作风。由这一点你也可猜出,我的朋友为什么这么少。

  • 他一定能体会到死亡的惨烈,也一定能体会死去时那种空前绝后的快感。

    大部分人往往不是“快感”,而是“恐惧”

  • 在此之前首先要解释一下什么叫似水流年。普鲁斯特写了一本书,谈到自己身上发生过的事。这些事看起来就如一个人中了邪躺在河底,眼看潺潺流水,粼粼流光,落叶,浮木,空玻璃瓶,一样一样从身上流过去。这个书名怎么译,翻译家大费周章。最近的译法是追忆似水年华。听上去普鲁斯特写书时已经死了多时,又诈了尸。

  • 似水流年是一个人所有的一切,只有这个东西,才真正归你所有。其余的一切,都是片刻的欢娱和不幸,转眼间就已跑到那似水流年里去了。我所认识的人,都不珍视自己的似水流年。他们甚至不知道,自己还有这么一件东西,所以一个个像丢了魂一样。

  • 人就是四十岁时最难过。那时候脑子很清楚,可以发现自己在变老。以后就糊里糊涂,不知老之将至。

  • 叔本华说:人在四十岁之前,过得很慢,过了四十岁,过得就快了。

  • 我一直在干这件事,可是线条说,我写的小说中只有好的事,回避了坏的事,不是似水流年的全貌,算不得直笔。如果真的去写似水流年,就必须把一切事都写出来,包括乍看不可置信的事,不敢写出这样的事情,就是媚俗。比如不敢写这样的事,就是媚俗。

  • 小时候和王二一起玩的孩子各有各的毛病,有人喜欢掐别人的脖子,有的喜欢朝别人裆下踢,不知他们的毛病都好了没有。

  • 我后来到美国留学时,给×教授编软件,文件名总叫“caonima”,caonima.1,caonima.2,等等。但是他总把第一个音节念成“考”,给我打电话说:考你妈一可以了,考你妈二还得往短里改。我就纠正他道:不是考你妈,操你妈。我们一共是四个研究生给他编程序,人人都恨他。这是因为按行算钱,他又不让编长。这种情形就叫做受压迫。毛主席教导我们说,有压迫就有反抗。所以就考你妈,就射精,就吐吐沫。

  • 我们生活在漫漫寒夜,人生好似长途旅行。仰望天空寻找方向,天际却无引路的明星!

  • 我觉得自己是个不会种地的农民,总是赶不上节气。

    这种体会很难受

  • 于是她就揉起眼睛来,那架势活像是猫洗脸;

    形象生动!

  • 走进了寂寞里,你就变成了黑夜里的巨灵神,想干啥就干啥,效率非常之高。你可以夜以继日地干任何事,不怕别人打断,直到事情干成。但是寂寞中也有让人不能忍受的时刻,那就是想说话时没有人听。

  • 我以为自己的本分就是把小说写得尽量好看,而不应在作品里夹杂某些刻意说教。我的写作态度是写一些作品给读小说的人看,而不是去教诲不良的青年。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

安全算法梳理

  经常见到RSA、MD5、SHA系列算法,另外还有数字摘要、数字签名、数字证书等名词,以及对称加密与非对称加密。他们之间到底是什么关系呢?下面就通过这篇文章来梳理一下。

一、加密算法

  加密算法,简而言之就是一种能够把原始内容加密,然后再解密获得原始内容的算法。

加密算法可以划分为如下两类:

1.对称加密

  对称加密可以理解为加密与解密都使用同一套算法(即密钥)。

  上图是一个经典对称加密算法。采用字母移位的办法对原始字符串进行转换,解密时再根据之前的移位,转换为原始字符串。可以根据此种算法,设计一个密码机,输入移位n,自动进行编码与解码。
  在谍战电视剧中经常出现你争我抢的密码本,加密与解密双方使用同一个密码本对要传递的内容进行加解密,这也是一种对称加密算法。
  对于对称加密在实际使用中的问题,可以通过如下样例来说明。
  假如淘宝网的登录密码传输部分使用的是对称加密算法。此时,张三、李四、还有王五同学需要访问淘宝。那么,服务器就需要生成3个不同的密钥,比如张三的密钥为移5位,李四的密钥为移6位。如果全部都用一个密钥,显然张三用自己的密钥可以解密其它人加密后的密码。对于服务器来讲,有多少个用户就得维护多少组密钥,这样做显然是不合理的。

  1. 常用的对称加密算法有:DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES等。
  2. 百度百科:对称加密

2.非对称加密

  针对于对称加密存在的问题,使用非对称加密即可完美的解决。

  由上图可以看出,左侧的对称加密为A、B、C、D四个用户分别分配了一个密钥,而右侧的非对称加密(也叫公开密钥加密技术)为A、B、C、D四个用户分配了相同的密钥。
  非对称加密的密钥分为公钥私钥,其中公钥和私钥都可以加密与解密。特别的是,公钥所加密的内容只有私钥能够解密。这样,对于服务器来讲,只需要把公钥分发给所有客户,自己保存好私钥即可。

  1. 常用的非对称加密算法有:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)等。
  2. 百度百科:非对称加密

二、摘要算法

  首先需要指明的是,摘要算法不属于加解密算法。
  对摘要两字可以理解为对信息主体的浓缩。这种浓缩是一个不可逆的过程。
  可以在这些地方使用摘要算法:明文密码取摘要后把摘要存入数据库、文件断点续传与秒传(用文件摘要确定唯一性)、不在网络中传输密码(密码做摘要后传输,在服务器端取出密码用相同的摘要算法计算摘要后再跟客户端传过来的摘要比对)

  1. 常用的摘要算法有:MD5、SHA1、SHA256、SHA384、SHA512
  2. 百度百科:摘要算法

三、数字签名

  数字签名可以理解为摘要算法非对称加密的综合使用。
  数字签名可以对应为经常见到的骑缝章(经常有人喜欢把名字写在书的侧面)。数字签名需要说明是谁编写的报文,同时证明报文未被篡改过。

  通过上图,可以清楚的看出,使用摘要算法来证明报文未被篡改过,使用非对称加密来说明是谁写的报文。

百度百科:数字签名

四、数字证书

  我们可以通过非对称加密可以解决报文裸奔的问题。
  试想,有这么一个场景,服务器A分发一个公钥给客户端B,正常情况下很OK。此时A和B之前有个中间代理C,A和B之间的所有数据传输都要经过C来中转。C被黑客控制,C把自己的公钥发给B(B误以为是A的),C收到B传输给A的消息,先用自己的私钥解密,获得明文。然后用A的公钥加密密文再转发给A。
  这里就牵涉到一个问题,B怎么确定收到的公钥的确是A的,而不是别人伪造的?现实中的身份证,要想确定身份证是否是真实的,去权威机构(也就是公安局)查一下就行了。
  数字证书也是这个原理,需要一个权威机构来颁发。以此来证明这个公钥到底是谁的。

  上图是百度的数字证书,详细标明了颁发者、使用者、有效期、加密算法等信息。

资料:“数字签名”(digital signature)和”数字证书”(digital certificate)到底是什么?

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

RSA加密解密样例

场景:当未启用HTTPS时,用户的登录密码,以及当用户修改密码时,密码在网络中需要加密传输。

一、交互逻辑

  上图中,前端部分运行在浏览器上,所以需要用JavaScript来加密需要传输的密码,后端部分使用Java来实现。

二、前端部分

  前端部分的加密,选择jsencrypt来实现,代码如下:

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
//用户修改密码样例
$("#ModifyPasswordBtn").bind("click",function(){
if($("#ModifyPasswordForm").valid()){
//这里的RSA是使用的模块化加载的入口
var encrypt = new RSA.JSEncrypt();
//KEY为公钥
encrypt.setPublicKey(KEY);
var data = {
userUuid:USER.userUuid,
oldPassword:encrypt.encrypt($('#oldPassword').val()),
newPassword:encrypt.encrypt($('#newPassword').val())
};
$.ajax({
url:getServer()+"your action",
type:"post",
data:data,
success:function(data){
var status = data.modifyStatus;
if(status==1){
Util.alert("修改成功");
}else if(status==0){
Util.alert("原密码不正确.");
}else{
Util.alert("修改失败");
}
}
});
}

三、后端部分

  后端部分使用Java来实现

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package com.share.util;

import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import java.util.HashMap;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;


public class RSATools {

/**
* 生成RAS公钥与私钥字符串,直接返回
* @return
*/
public static HashMap<String,String> getKeys(){
HashMap<String,String> map = new HashMap<String,String>();
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024,new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
//得到公钥字符串
String publicKey = base64ToStr(keyPair.getPublic().getEncoded());
//得到私钥字符串
String privateKey = base64ToStr(keyPair.getPrivate().getEncoded());
map.put("publicKey", publicKey);
map.put("privateKey", privateKey);
return map;
}

/**
* 从字符串中加载公钥
* @param publicKeyStr 公钥字符串
* @return
* @throws Exception
*/
public static RSAPublicKey loadPublicKey(String publicKeyStr) throws Exception {
try {
byte[] buffer = javax.xml.bind.DatatypeConverter.parseBase64Binary(publicKeyStr);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("公钥非法");
} catch (NullPointerException e) {
throw new Exception("公钥数据为空");
}
}

/**
* 从字符串中加载私钥
* @param privateKeyStr 私钥字符串
* @return
* @throws Exception
*/
public static RSAPrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
try {
byte[] buffer = javax.xml.bind.DatatypeConverter.parseBase64Binary(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("私钥非法");
} catch (NullPointerException e) {
throw new Exception("私钥数据为空");
}
}

/**
* 公钥加密过程
* @param publicKey 公钥
* @param plainTextData 明文数据
* @return
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt(RSAPublicKey publicKey, byte[] plainTextData)throws Exception {
if (publicKey == null) {
throw new Exception("加密公钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(plainTextData);
return base64ToStr(output);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此加密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("加密公钥非法,请检查");
} catch (IllegalBlockSizeException e) {
throw new Exception("明文长度非法");
} catch (BadPaddingException e) {
throw new Exception("明文数据已损坏");
}
}

/**
* 私钥加密过程
*
* @param privateKey 私钥
* @param plainTextData 明文数据
* @return
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt(RSAPrivateKey privateKey, byte[] plainTextData) throws Exception {
if (privateKey == null) {
throw new Exception("加密私钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] output = cipher.doFinal(plainTextData);
return base64ToStr(output);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此加密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("加密私钥非法,请检查");
} catch (IllegalBlockSizeException e) {
throw new Exception("明文长度非法");
} catch (BadPaddingException e) {
throw new Exception("明文数据已损坏");
}
}

/**
* 私钥解密过程
*
* @param privateKey 私钥
* @param cipherData 密文数据
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(RSAPrivateKey privateKey, byte[] cipherData) throws Exception {
if (privateKey == null) {
throw new Exception("解密私钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] output = cipher.doFinal(cipherData);
return new String(output);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此解密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("解密私钥非法,请检查");
} catch (IllegalBlockSizeException e) {
throw new Exception("密文长度非法");
} catch (BadPaddingException e) {
throw new Exception("密文数据已损坏");
}
}

/**
* 公钥解密过程
* @param publicKey 公钥
* @param cipherData 密文数据
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(RSAPublicKey publicKey, byte[] cipherData) throws Exception {
if (publicKey == null) {
throw new Exception("解密公钥为空, 请设置");
}
Cipher cipher = null;
try {
// 使用默认RSA
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(cipherData);
return new String(output);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此解密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
throw new Exception("解密公钥非法,请检查");
} catch (IllegalBlockSizeException e) {
throw new Exception("密文长度非法");
} catch (BadPaddingException e) {
throw new Exception("密文数据已损坏");
}
}

public static String base64ToStr(byte[] b){
return javax.xml.bind.DatatypeConverter.printBase64Binary(b);
}

public static byte[] strToBase64(String str){
return javax.xml.bind.DatatypeConverter.parseBase64Binary(str);
}
}

生成公钥/私钥

  在第一次请求时生成公钥与私钥,并放到当前线程上。返回公钥给客户端。

1
2
HashMap<String, String> map = RSATools.getKeys();
session.setAttribute("PD_CurrentRSAKey", map);

私钥解密

1
2
3
4
5
6
7
//从session上拿到上次生成的密钥
HashMap<String, String> map = (HashMap<String, String>) session.getAttribute("PD_CurrentRSAKey");
RSAPrivateKey privateKey = RSATools.loadPrivateKey(map.get("privateKey"));
//使用私钥解密传输过来的密码
String userUuid = request.getParameter("userUuid");
String oldPassword = RSATools.decrypt(privateKey, RSATools.strToBase64(request.getParameter("oldPassword")));
String newPassword = RSATools.decrypt(privateKey, RSATools.strToBase64(request.getParameter("newPassword")));

四、可运行的样例

  后续提供github地址

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

《嫌疑人X的献身》

黑色的封面,代表着这是一个悲伤的故事。

  紧接上一篇,看完《白夜行》后,发现节奏带太快,收不住。忍不住搜一搜东野圭吾的其他小说,最后确定目标:《嫌疑人X的献身》。就是想一窥到底,看看东野圭吾是不是像网评的那么神!
  之所以会盯上这本书,是因为大家都说这本书的故事神奇。一开始,读者便知道了作案凶手以及详细的作案细节!我勒个去!这么搞那还有什么悬念可言?在平生所看的悬疑类小说中,通常一开始就埋坑,然后到结尾才知道答案,抓住凶手。东野圭吾这是要搞哪样,太嚣张了!
  这本书讲述的是一个理工男暗恋上隔壁女邻居的故事。这个女邻居离异,和女儿一起生活。前任老公一直缠着她不放,经常找她要钱。一次来找她,她和她女儿一起把前任老公杀死了。住在隔壁的理工男协助掩盖真相,故事由此展开。
  最多剧透到此,不能再剧透了!照例放点儿书中的内容片段勾起大家的想要一睹为快的冲动!

  • 石神并不排斥森冈这种质疑的态度,对于为何要学习某种东西抱有疑问,本是理所当然。唯有疑问解除了,才会产生求知的欲望,才能走上理解数学本质之路。可惜太多老师不愿回答这种单纯的疑问。不,是答不出,石神知道,他们也没真正理解数学,只是按照既定的教材照本宣科,只想着让学生拿到好分数。对森冈提出的这种质疑,恐怕只会觉得不耐烦。

    可惜自己开窍得太晚!当需要用知识去赚钱生活时才发现上学那会儿的课程是多么的重要!线性代数、统计、积分……想想都是泪!

  • 这个世上没有无用的齿轮,只有齿轮自身才能决定自己的用途

    人也一样,需要自己成全自己。(突然想到《霸王别姬》中大师兄让小师弟自己成全自己。假霸王,真虞姬,哎!)

  • 草薙告诉我一件有趣的事,是关于你出考题的方式,针对自以为是的盲点。比方说看起来是几何问题,其实是函数问题,我听了恍然大悟。对那种不懂数学的本质、早已习惯根据思维定势解答的学生来说,这种题目想必很有效。乍看之下是几何问题,学生拼命朝那个方向想,却解不出来,唯有时间分秒流逝。要说是坏心眼,确实有点儿过分,但用来测试真正的实力,诚然有效。”

    上学时,就经常碰到这样坏心眼儿的老师!好像暴露了什么……强行逆转,看问题不应该被问题的表象所蒙蔽,应该直达问题的本质。解决程序Bug也一样。再次成功穿越……

  • “最后一次见到石神时,他问了我一个问题——P≠NP。自己想出答案和判断别人的答案是否正确,何者较容易——这是著名的数学难题。”   草薙皱起眉头。   “那是数学?听起来像哲学。”   “你明白吗?石神给你们提出了一个答案,也就是这次的自首、供述内容。这一自白怎么看都像正确无误的解答,是他充分发挥智慧想出来的。如果就这么乖乖相信,那就表示你们输了。你们正受到来自他的挑战和考验!接下来,该轮到你们全力以赴,判断他提供的答案是否正确。”   “我们已经作了各种证实。”   “你们做的,只是按照他的证明方法走。你们该做的,是探寻有没有别的答案。除了他提供的答案之外别无可能——唯有证明到这个地步,才能断言,那个答案是唯一的答案!”

    由此可见,判断别人给的答案是否正确远比自己想出答案更难。自信的数学家,妙!

  • 指示之后,还有这么一段。工藤邦明先生是个诚实可靠的人。和他结婚,你和美里获得幸福的几率较高。把我完全忘记,不要有任何负罪感。如果你过得不幸福,我所做的一切才是徒劳。她看了又看,再次落泪。

    这就是一个数学家,一个理工男对爱的表达。

  • 心如明镜不带丝毫阴霾的,世上只有石神。

    理工男往往是比较单纯的,内心世界只有逻辑。

  • 他再次感到,自己并不需要任何人的肯定。他也有发表论文、受人重视的欲望,但那非关数学本质。让别人知道是谁第一个爬上山顶固然重要,但只要当事人自己明白其中的真味,也就足够了。

    很理解这种乐在其中的快乐。

  • 他已毫无留恋。没有理由寻死,也没有理由活着,如此而已。   他站上台子,正要把脖子套进绳索时,门铃响了。   是扭转命运方向的门铃。   他没有置之不理,他不想给任何人添麻烦。门外的某人,说不定有急事。   开门一看,门外站着两个女子,是一对母女。   母亲自我介绍说她们刚搬来隔壁,女儿在一旁鞠躬。看到两人时,石神的身体仿佛猛然被某种东西贯穿。   怎么会有眼睛如此美丽的母女?在那之前,他从未被任何东西的美丽吸引、感动过,也不了解艺术的意义。然而这一瞬间,他全都懂了,他发觉这和求解数学的美感在本质上乃是殊途同归。

    真、善、美其实是共通的,殊途同归。

  • 石神早已忘记她们是怎么打招呼的,但两人凝视他的明眸如何流转,至今仍清晰烙印在记忆深处。

    喜欢上一个人往往只需要一瞬!

  看完了全书,由衷的赞叹本田圭吾能写出如此神作,故事架构精巧慎密,看到最后,当知道实情后眼前一亮,虎躯一震,惊叹万分。
  整本书的感受只能用Perfect、Amazing、Heartbreaking 来形容。

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。

《白夜行》

好东西需要分享,所以今天来义务当一回五毛。

  前段时间休了一次长假,长假回来发现状态全无,晚上下班回家无所事事,什么事儿没干一晃就到了睡觉时间,这种状态让人感觉很不踏实。为了找找学习状态,重找晚上回家看书的习惯,需要带一波节奏。
  据以往经验,想要把节奏带起来,不能来得太陡,必须循序渐进。如果一上来就找本技术大部头来啃绝对容易打瞌睡!很早之前在知乎就看到有人推荐东野圭吾的《白夜行》,说这本小说很不错,属于推理悬疑小说。看到推理悬疑,想到这种小说肯定属于不断挖坑,引人入胜,打开就停不下来的小说。每天晚上回家来上一章,绝对停不下来!带节奏神器!
  下面,就来先见见这本书的封面。

  从封面上看,可能会觉得很普通,老实说,当时看到的第一眼也是这么认为的。不过当我看完全书再回过头来着封面,觉得设计得真TMD的有深意!
  作为摇旗呐喊的五毛,当然是不可能剧透的!不过倒是可以放点儿书中的内容勾引勾引….

  • 她的眼神里有一种微妙得难以言喻的刺。但那并不是社交舞社社长无视她的存在,只顾和朋友讲话而自尊受伤的样子。那双眼睛里栖息的光并不属于那种类型。 那是更危险的光——这才是一成的感觉,可以说是隐含了卑劣下流的光。他认为真正的名门闺秀,眼神里不应栖息着那样的光。

      公司最近招了一拨实习生,好怀念自己当年初入职场时那单纯、清澈的眼神!随着阅历的增加,眼神中的那股清流慢慢的消失。如果能一直保持住像三四岁小孩儿那种干净清澈的眼神该多好!已入职场6年,希望能不断学习成长,通过智慧让自己的眼神不至于渐渐变得黯淡无光,不求清澈见底,至少炯炯、明亮。

  • “捡别人丢的东西不还,跟偷别人随意放置的东西,并没有什么差别。有错的难道不是把装了钱的包随便放的人吗?这个社会上,让别人有机可乘的人注定要吃亏。

  • “我很不想承认,可你的确是本世纪最幸运的男人。娶到那么美的老婆就该偷笑了,她竟然还烧得一手好菜!一想到我跟你活在同一个世界上,实在很难不嫌弃自己。

  • 看着她,总会感到一种莫名的诡异,我实在不认为她只是个坚强的女子。”

    坚强往往是需要某种力量来支撑的!

  • 一双看尽人性丑恶的眼睛,一种堪称真正冷静清澈的光静静地栖息其中。

    这句话是形容书中一直追踪命案二十年一直不放弃的老警察

  • “枪虾会挖洞,住在洞里。可有个家伙却要去住在它的洞里,那就是虾虎鱼。不过虾虎鱼也不白住,它会在洞口巡视,要是有外敌靠近,就摆动尾鳍通知洞里的枪虾。它们合作无间,这好像叫互利共生。”

    仔细想想,这种相互依偎取暖也挺可怜的!

  • “有一株芽应该在那时就摘掉,因为没摘,芽一天天成长茁壮,长大了还开了花,而且是作恶的花。”

    恶之花一开始就应该摘掉,越晚摘除后果越大。写代码也一样,问题代码一开始就应该干掉,而不应该一味的容忍做兼容!神转折吧!

  • 曾经拥有的东西被夺走,并不代表就会回到原来没有那种东西的时候。

    这句话很有深意啊!值得仔细思考!

  • “那时,我比现在的你更小,真的还是小孩子。但是,恶魔不会因为你是小孩子就放过你。而且,恶魔还不止一个。”

    遇到恶魔就不要指望得到恶魔的同情。

  • “喏,夏美,一天当中,有太阳升起的时候,也有下沉的时候。人生也一样,有白天和黑夜,只是不会像真正的太阳那样,有定时的日出和日落。看个人,有些人一辈子都活在太阳的照耀下,也有些人不得不一直活在漆黑的深夜里。人害怕的,就是本来一直存在的太阳落下不再升起,也就是非常害怕原本照在身上的光芒消失,现在的夏美就是这样。” 夏美听不懂老板在说什么,只好点头。 “我呢,从来就没有生活在太阳底下过。” “怎么会!”夏美笑了,“社长总是如日中天呢。” 她摇头。她的眼神是那么真挚,夏美的笑容也不由得消失了。 “我的天空里没有太阳,总是黑夜,但并不暗,因为有东西代替了太阳。虽然没有太阳那么明亮,但对我来说已经足够。凭借着这份光,我便能把黑夜当成白天。你明白吧?我从来就没有太阳,所以不怕失去。”“代替太阳的东西是什么呢?” “你说呢?也许夏美以后会有明白的一天。”

    看到这儿,终于明白书名为何取为《白夜行》。这样的人生居然还能如此坚强!在白夜中行走需要多大的勇气!

  看完书的最后一个字,合上书,静静的看着书的封面,思绪万千!

留言

欢迎交流想法。留言会通过 GitHub Issues 保存,首次使用需要登录 GitHub。