0%

一、安装GO

1.1 使用Homebrew安装go环境(如果很慢,可以换个源)

1
brew install go

1.2 查看安装信息

1
go env

主要关注如下输出

1
GOROOT="/usr/local/Cellar/go/1.10.3/libexec" # 安装目录

1.3 配置环境变量

1
vi ~/.bash_profile # 没有的话会新建一个文件

输入如下内容,第一行是安装的目录,第三行是工作目录(可以改成自己喜欢的路径)

1
2
3
4
5
GOROOT=/usr/local/Cellar/go/1.10.3/libexec
export GOROOT
export GOPATH=/Users/hisenyuan/golang
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN:$GOROOT/bin

1.4 让配置文件生效并且查看环境变量

1
2
source ~/.bash_profile
go env # 这时你会发现环境变量已经有改变

二、安装GoLand

我是习惯了用jetbrains的idea

发现它家也有go语言的IDE GoLand

于是就去官网下载,安装,找个注册码,修改一下host防止注册码失效

这里就不再累赘了

三、运行需要输入的程序

买了一本《Go语言程序设计》

Read more »

一、背景描述

在支付业务当中,每一笔交易都得进行记账。

两种情况:

  1. 第一步先冻结,交易成功,解冻并扣款(A账户->B账户->C账户);
  2. 第一步先冻结,交易失败,解冻并归还(A账户->B账户->A账户);

上面的两种情况各自都是在一个事物当中。

二、问题描述

在同一个商户进行并发操作的时候,交易有成功有失败;

  1. 成功的时候钱是:A账户->B账户;
  2. 失败的时候钱是:B账户->A账户;

因为在各自的事物当中更新两条记录的信息,并且使用了for update(innodb引擎)
ß
在某一瞬间:成功的先锁A账户,失败的先锁了B记录

接下来就两个事物各自持有对方想要的资源,并且不释放已经占有的资源,就造成了死锁

三、解决方法

在程序里面,更新两个账户的钱的时候,始终先更新ID更小的那条记录,那样不管多少个事务同时进来

都会按照固定的顺序去持有资源,比如先A再B,这样就不会出现各自持有对方想要的资源

1
2
3
ID ACCOUNT
10 A
11 B

每个事务都是先锁定A再锁定B,拿不到锁就一直等待

一、背景说明

最近在做app后台相关接口

自建通知中心目前不能很好的支持给APP推送消息

长连接可以保持推送速度,目前app中内嵌了H5,所以考虑使用websocket

之前没有接触过websocket,百度了一堆之后,页面上可以正常使用

但是没有发现可用使用Java后台进行消息的发送,于是乎就琢磨了一上午,解决了这个问题

现在把这个小工程分享给大家,少走点弯路==

ps:很多不能在后台发送消息,是因为缺少Java的客户端

二、准备工作

建立一个maven web 工程

添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>Javax.websocket</groupId>
<artifactId>Javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>com.neovisionaries</groupId>
<artifactId>nv-websocket-client</artifactId>
<version>1.13</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>

三、主要代码

websocket服务端主逻辑

为了实现简单的非群发操作,在连接websocket的时候,加上了一些get参数

例如:ws://localhost:8080/websocket?sendTo=hisen&method=methodSingle&user=hisenyuan

然后在后端判断,根据参数做出不同的动作

demo完整工程:https://github.com/hisenyuan/IDEAPractice/tree/master/websocket-demo

配置完Tomcat,即可使用,在Java后台运行测试类(com.hisen.ws.client.ClientApp4Java)可发送消息到页面

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
package com.hisen.ws.server;

import com.hisen.ws.util.Constants;

import Javax.websocket.*;
import Javax.websocket.server.ServerEndpoint;
import Java.io.IOException;
import Java.util.ArrayList;
import Java.util.List;
import Java.util.Map;
import Java.util.Optional;
import Java.util.concurrent.ConcurrentHashMap;

/**
* @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
*/
@ServerEndpoint("/websocket")
public class WebSocketServer {
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;

// 实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
// 当前用户
private String user;

/**
* 客户端可以是web页面,也可以是Java后台
* <p>
* 通过连接或者message可以控制发送给谁
*
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
// 获取url传过来的参数
Map<String, List<String>> parameterMap = session.getRequestParameterMap();
// 发送方式
String method = null;
// 发送给哪些人
List<String> receivers = new ArrayList<>();
// 发送者
String sernder = null;
if (parameterMap.containsKey(Constants.METHOD)) {
method = parameterMap.get(Constants.METHOD).get(0);
}
if (parameterMap.containsKey(Constants.SEND_TO)) {
receivers = parameterMap.get(Constants.SEND_TO);
}
if (parameterMap.containsKey(Constants.USER)) {
sernder = parameterMap.get(Constants.USER).get(0);
}

System.out.println("sender:" + sernder + ",receivers:" + receivers.toString() + ",method:" + method);
if (method == null || method.equals(Constants.METHOD_ALL)) {
//发送所有
send2All(message);
} else {
//单发
send2Users(receivers, message);
}

}

/**
* 连接建立成功调用的方法
*
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
this.user = session.getRequestParameterMap().get(Constants.USER).get(0);
// 放入map
webSocketMap.put(user, this);
//在线数加1
addOnlineCount();
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount() + ",session:" + session.getId() + ",user:" + this.user);
}

/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
// 移除
webSocketMap.remove(this.user);
//在线数减1
subOnlineCount();
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount() + ",user:" + this.user);
}


private void send2Users(List<String> receivers, String message) {
receivers.forEach(e -> {
System.out.println("single receiver:" + e);
Optional.ofNullable(webSocketMap.get(e))
.filter(webSocketServer -> webSocketServer.session.isOpen())
.ifPresent(webSocketServer -> sendOnce(message, e, webSocketServer));
});
}

private void send2All(String message) {
webSocketMap.forEach((key, value) -> {
sendOnce(message, key, value);
});
}

private void sendOnce(String message, String e, WebSocketServer webSocketServer) {
try {
webSocketServer.sendMessage(message);
} catch (IOException exp) {
System.out.println("发送出错,receiver:" + e);
}
}

/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误,user:" + this.user);
error.printStackTrace();
}

/**
* 自定义的方法
*
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}

public static synchronized int getOnlineCount() {
return onlineCount;
}

public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}

public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}

一、背景简介

今天头给我们开会,说到团队对外沟通的问题。

谈到对外需要积极给人解决问题,而不是各种推脱,即使自己不知道,也可以给个眼神找到对的人。

继而谈到需要安排人轮流负责跟外部接洽

由于这个活呢,大伙儿认为不是什么好差事,那就抓阄决定吧

于是乎就感觉可以写一个简单的排班系统小bug,不过我这里只是提供一个简单的思路

二、程序代码

主要的逻辑在这,当然并没有考虑数据持久化的问题

性能等其他的问题,纯粹是一个思路,用hashCode取模主要是打的比较散,很均匀

加上日期什么的,就一个排班表出来了。

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
/**
*
* @param person 目前所有的人
* @param lucklys 这一轮已经值班了的人
*/

private static void findLucklyOneByHashCode(List<String> person, ArrayList<String> lucklys) {
List<String> result = new ArrayList<>(person);
result.removeAll(lucklys);
if (result.size() == 0) {
lucklys.clear();
result = new ArrayList<>(person);
}
String code = System.nanoTime() + UUID.randomUUID().toString();
int index = Math.abs(code.hashCode()) % result.size();
String lucklyOne = result.get(index);
System.out.println("lucklyOne:" + lucklyOne);
lucklys.add(lucklyOne);
}
/**
* hashCode随机取出来的数据
* lucklyOne:G
* lucklyOne:B
* lucklyOne:C
* lucklyOne:D
* lucklyOne:H
* lucklyOne:F
* lucklyOne:E
* lucklyOne:A
*/

mac自带的终端总感觉不大好用,于是乎就被安利了iTerm2

然后就搜了下自动连接远程服务,于是就发现了一个不错的脚步

整个设置过程还是比较顺畅,

操作步骤:

  1. 新建一个sh文件
1
vi auto_ssh.sh
  1. 输入如下内容,[lindex $argv 0] 这个为第一个参数的占位符
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/expect

set timeout 30
spawn ssh -p[lindex $argv 0] [lindex $argv 1]@[lindex $argv 2]
expect {
"(yes/no)?"
{send "yes\n";exp_continue}
"password:"
{send "[lindex $argv 3]\n"}
}
interact
  1. 复制脚本到bin下并且赋予执行权限
1
2
3
sudo cp auto_ssh.sh /usr/local/bin/
cd /usr/local/bin/
sudo chmod +x auto_ssh.sh
  1. 设置
    在iTerm2中Command+o呼出profile
1
2
3
4
5
6
7
8
9
10
11
12
13
# 界面右下角,点击:
edit profiles
# 界面左下角,点击:
+
# 界面上方,点击:
General
# 界面靠上,写这个登录的名字:
name
# 界面中间,点击选择:
Login shell
# 找到这个提示符:Send text at start,输入如下字符
auto_ssh.sh 8022 root 10.10.20.20 hisenyuan
# 脚本名称 端口 用户名 ip地址 密码
  1. 运行
    设置完了之后,关闭窗口,重新command+o呼出配置文件,双击刚刚配置的即可自动登录

重温经典,特意看了下为什么读文件非得不让等于 -1

fileChannelIn.read(byteBuffer) != -1

EOF = End Of File,其默认值就是-1

这是一个约定好的结束符,不是认为改变的

代码

建议使用大文件进行测试,看效果比较明显

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
@Test
public void testCopy() {
String oldFileName = "/Users/hisenyuan/hisen/blog/source/_posts/Test-Java-Code.md";
String newFileName = "/Users/hisenyuan/hisen/test/Test-Java-Code.md";
nioCopy(oldFileName, newFileName);
ioCopy(oldFileName, newFileName.replace(".md", ".md.bak"));
ioCopyByLine(oldFileName,newFileName.replace(".md",".bak." + System.currentTimeMillis()));
}

/**
* 利用NIO进行读写文件
*
* @param oldFileName 原文件的路径
* @param newFileName 新文件的路径
*/
public static void nioCopy(String oldFileName, String newFileName) {
try {
FileChannel fileChannelIn = new FileInputStream(new File(oldFileName)).getChannel();
FileChannel fileChannelOut = new FileOutputStream(new File(newFileName)).getChannel();
//获取文件大小
long size = fileChannelIn.size();
System.out.printf("文件大小为:%s byte \n", size);
//缓冲
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

long start = System.currentTimeMillis();
while (fileChannelIn.read(byteBuffer) != -1) {
//准备写
byteBuffer.flip();
fileChannelOut.write(byteBuffer);
//准备读
byteBuffer.clear();
}
long end = System.currentTimeMillis();
System.out.printf("NIO方式复制完成,耗时 %s 秒\n", (end - start) / 1000);
//关闭
fileChannelIn.close();
fileChannelOut.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* IO方式复制文件
*
* @param oldFileName
* @param newFileName
*/
public static void ioCopy(String oldFileName, String newFileName) {
try {
FileInputStream fileInputStream = new FileInputStream(new File(oldFileName));
FileOutputStream fileOutputStream = new FileOutputStream(new File(newFileName));

long length = new File(oldFileName).length();

System.out.printf("文件大小为:%s byte \n", length);
byte[] buffer = new byte[1024];

long start = System.currentTimeMillis();
int len = 0;
//EOF = End Of File,其默认值就是-1
while ((len = fileInputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
}
long end = System.currentTimeMillis();
System.out.printf("IO方式复制完成,耗时 %s 秒\n", (end - start) / 1000);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

public static void ioCopyByLine(String oldFileName, String newFileName) {
try {
BufferedReader reader = new BufferedReader(new FileReader(oldFileName));
BufferedWriter writer = new BufferedWriter(new FileWriter(newFileName));
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
writer.flush();
}
} catch (IOException e) {
System.out.println("error:" + e);
}
}

最近在慢慢适应mbp

然后就在研究怎么方便的写博客

首先就是找了几款markdown编辑器,发现GitHub出品的Atom还不错

插件很丰富,然而下载了第一个terminal插件但是无效,一度折腾了很久

后来搜索了一下,发现这个插件很不错:platformio-ide-terminal

利用快捷键:control + 点(1左边那个)

就可以在当前界面呼出终端,而且是当前目录

也就是在_post目录下,写完了之后执行命令就上传了

还是很方便的。后续还需要慢慢熟悉更多的插件与工具

很多时候写功能或者接口需要进行压力测试,
今天发现jwt在生成token的时候,如果输入都是一样的
仅有一个签发时间不一样,生成的token是有可能是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void testCreate() {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("hisenyuan").build();
ExecutorService pool = new ThreadPoolExecutor(
20,
50,
10000L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10240),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 50; i++) {
// 需要提交的内容
pool.execute(this::createTokenTest);
}
pool.shutdown();
try {
while (!pool.awaitTermination(500, TimeUnit.MILLISECONDS)) {
LOGGER.debug("Waiting for terminate");
}
} catch (InterruptedException e) {
LOGGER.error(e);
}
}

springmvc正常情况下redirect并且设置指定响应码,异常情况下返回json数据

背景介绍

需求就是正常情况下能redirect到指定的页面
异常的情况下,能够返回JSON格式的错误信息
正常情况和异常情况都需要设置HTTP Code

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RequestMapping(value = "/test", method = RequestMethod.POST)
public String webCharge(HttpServletRequest request, HttpServletResponse response) {
if (1==1) {
response.setStatus(200);
return "redirect:https://github.com/hisenyuan";
} else {
try {
response.setStatus(405);
response.getWriter().write(JSON.toJSONString("hisenyuan"));
return null;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}

一、背景

在各种系统需要加签的时
一般都会把参与签名的数据以get请求参数拼接起来
并且要求有序,这个方法会比较方便

二、实现

2.1 拼接为有序的get请求类字符串

1
2
3
4
5
6
7
8
9
10
public String getSortedStr(Map<String, String> unSortedStr) {
String sortedStr= unSortedStr
.entrySet()
.stream()
.filter(entry -> !StringUtil.isEmpty(entry.getValue()))
.sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
return sortedStr;
}

2.2 把get类参数字符串转为map

1
2
3
4
5
6
7
8
9
private Map<String,String> getMapData(String getStr){
String[] strs = getStr.split("&");
HashMap<String, String> dataMap = new HashMap<>(16);
for (int i = 0; i < strs.length; i++) {
String[] str = strs[i].split("=");
dataMap.put(str[0], str[1]);
}
return dataMap;
}

对于get类字符串没有发现比较好的方法转换为map