Coding & Life

求知若饥,虚心若愚

pic
用ssh连接服务器经常遇到长时间不操作而被服务器踢出的情况,提示如下

Write failed: Broken pipe

方案一:客户端配置

/etc/ssh/ssh_config中添加配置

1
ServerAliveInterval 60

之后,当使用ssh时每隔60s客户端都会向服务端发送一个KeepAlive请求,避免被踢

方案二:服务端配置

/etc/ssh/sshd_config中添加配置

1
ClientAliveInterval 60

重启服务器后设置生效
注意:这种方式每一个连接服务器的客户端都受到影响,安全性会有一定的下降

pic

场景

客户端A想要免密登录服务器B

客户端A

生成SSH KEY

1
ssh-keygen -t rsa -C "your_email@example.com"

查看客户端公钥

1
cat ~/.ssh/id_rsa.pub

服务端B

生成SSH KEY

1
ssh-keygen -t rsa -C "your_email@example.com"

连接A-B

复制A的公钥到B的

1
~/.ssh/authorized_keys

重新登录

持续集成是什么?

互联网软件的开发和发布,已经形成了一套标准流程,最重要的组成部分就是持续集成(Continuous integration,简称CI)

持续集成的目的

让产品可以快速迭代,同时还能保持高质量

Jenkins是什么?

Jenkins是一个用Java编写的开源的持续集成工具,因此安装Jenkins必须有Java运行环境

Jenkins安装

官网下载Jenkins安装包,按照官网提示进行安装

安装常用插件

常用插件

创建密码

创建密码

注意:需要防火墙打开8080端口,Jenkins默认使用8080端口

Jenkins的配置

全局工具配置

  • 配置jdk
  • 配置maven

插件安装

Jenkins有很多插件已经被安装,其中Git plugin和Maven Integration plugin,publish over SSH是部署Spring Boot项目必备的插件

配置Credentials

配置成功后可以用ssh协议拉取git上的代码

问题:在mybatis中,0被认为是空字符串
解决:普通判断1. != null 2. != ‘’。当类型为Integer类型时只进行方式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
# /bin/bash
DB_NAME="exchange"
DB_USER="root"
DB_PASSWORD="abc123"
BIN_DIR="/usr/bin"
BACK_DIR="/root/data"
DATE="mysql-`date +'%Y%m%d-%H:%M:%S'`"
LogFile="$BACK_DIR"/dbbakup.log
BackNewFile=$DATE.sql

$BIN_DIR/mysqldump -u$DB_USER -p$DB_PASSWORD $DB_NAME > $BACK_DIR/$DATE.sql

echo -----------------"$(date +"%y-%m-%d %H:%M:%S")"------------------ >> $LogFile

echo createFile:"$BackNewFile" >> $LogFile

find "/root/data/" -ctime +0 -type f -name "*.sql" -print > deleted.txt

echo -e "delete files:\n" >> $LogFile

cat deleted.txt | while read LINE
do
rm -rf $LINE
echo $LINE>> $LogFile
done

echo "---------------------------------------------------------------" >> $LogFile

利用cron定时执行

利用cron服务定时执行数据备份脚本。该脚本会自动删除过期的sql文件
例如 每天12:50定时执行mysqlback.sh脚本:

1
50 12 * * * /root/mysqlback.sh

linux导入sql

1
mysql -h [host] -u [userName] -p [databaseName] < [data].sql

linux导出库

1
mysqldump -h [host] -u [username] -p --databases [databasename] > [fileName].sql

linux导出单表

1
mysqldump -h [host] -u [username] -p [dabaseName] [tableName] > [fileName].sql

更新了系统后,使用git命令,提示错误如下:

1
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

原因

因为每次更新系统之后xcode就被卸载了,因此需要重新安装一次

解决方案

终端执行

1
xcode-select --install

什么是sequence

序列,在Oracle数据库中,什么是序列呢?它的用途是什么?序列(SEQUENCE)其实是序列号生成器,可以为表中的行自动生成序列号,产生一组等间隔的数值(类型为数字)。其主要的用途是生成表的主键值,可以在插入语句中引用,也可以通过查询检查当前值,或使序列增至下一个值。

mysql没有内置sequence,需要自己实现

创建sequence表

1
2
3
4
5
6
CREATE TABLE sequence (
name VARCHAR(50) NOT NULL,
current_value INT NOT NULL,
increment INT NOT NULL DEFAULT 1,
PRIMARY KEY (name)
)

获取当前序列值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE FUNCTION currval (seq_name VARCHAR(50))
RETURNS INTEGER
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE value INTEGER;
SET value = 0;
SELECT current_value INTO value
FROM sequence
WHERE name = seq_name;
RETURN value;
END

获取下一个序列

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE FUNCTION nextval (seq_name VARCHAR(50))
RETURNS INTEGER
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
UPDATE sequence
SET current_value = current_value + increment
WHERE name = seq_name;
RETURN currval(seq_name);
END

重置序列值

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE FUNCTION setval (seq_name VARCHAR(50), value INTEGER)
RETURNS INTEGER
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
UPDATE sequence
SET current_value = value
WHERE name = seq_name;
RETURN currval(seq_name);
END

应用

1
2
3
4
5
6
7
// 在序列表中新建一条序列(参数依次为:序列名称、序列开始值、序列递增步长)
INSERT INTO sequence VALUES ('TestSeq', 0, 1);
// 设置序列开始值(参数依次为:序列名称、序列开始值)
SELECT SETVAL('TestSeq', 10);
SELECT CURRVAL('TestSeq');
// 获得下一个序列值
SELECT NEXTVAL('TestSeq');

server服务端

主线程

构建页面

创建serverSocket

添加send按钮的点击事件

子线程

死循环接收消息

服务端代码如下:

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
/**
* 主线程发送消息
* 子线程接收消息
*/
package com.yatai.web;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

/**
* @author 王伟业 2014年5月28日
*/
public class ChatServer implements ActionListener, Runnable {
// 用来存放客户端连接数量
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
// 显示历史聊天记录
private JTextArea showArea;
// 待发送的字符区域
private JTextField msgText;
// 窗口
private JFrame mainJframe;
// 发送按钮
private JButton sentBtn;
// 滚动面板
private JScrollPane JSPane;
// 普通面板
private JPanel pane;
// 最大的容器
private Container con;
// 线程处理信息
private Thread thread = null;
private ServerSocket serverSocket;
private Socket connectToClient;
private DataInputStream inFromClient;
private DataOutputStream outToClient;

/**
* 构造函数用来设置界面,处理事件
*/
public ChatServer() {
// TODO Auto-generated constructor stub
// 设置页面
mainJframe = new JFrame("服务器端");
// 初始化容器
con = mainJframe.getContentPane();
showArea = new JTextArea();
showArea.setEditable(false); // 历史聊天窗口中的文字域不能编辑,只供查看
showArea.setLineWrap(true); // 自动换行
JSPane = new JScrollPane(showArea);
// 待发送文字区域
msgText = new JTextField();
msgText.setColumns(35);
// 事件监听
msgText.addActionListener(this);
sentBtn = new JButton("Send");
sentBtn.addActionListener(this);
// 界面下部
pane = new JPanel();
pane.setLayout(new FlowLayout());
pane.add(msgText);
pane.add(sentBtn);

con.add(JSPane, BorderLayout.CENTER);
con.add(pane, BorderLayout.SOUTH);
mainJframe.setSize(500, 400);
mainJframe.setLocation(600, 200);
mainJframe.setVisible(true);
mainJframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

try {
serverSocket = new ServerSocket(8888);
showArea.append(" 正在等待对话请求\n");
// 监听端口
connectToClient = serverSocket.accept();
inFromClient = new DataInputStream(connectToClient.getInputStream());
outToClient = new DataOutputStream(connectToClient.getOutputStream());
// 启动线程
thread = new Thread(this);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
} catch (IOException e) {
// TODO Auto-generated catch block
// 出现此异常说明服务器未创建成功
showArea.append(" 对不起,不能创建服务器\n");
// 待发送文字区域不能编辑
msgText.setEditable(false);
// 发送按钮不可用
sentBtn.setEnabled(false);
}

}

/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
// TODO Auto-generated method stub
// 此线程用来接收客户端传来的信息
while (true) {
try {
showArea.append(" 对方说:" + inFromClient.readUTF() + "\n");
Thread.sleep(1000);
} catch (IOException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

/*
* (non-Javadoc)
*
* @see
* java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
// 响应按钮事件
String s = msgText.getText();
// 如果待发送文字区域存在文字
if (s.length() > 0) {
try {
// 将文字写入到流中
outToClient.writeUTF(s);
outToClient.flush();
// 历史聊天记录增添内容
showArea.append(" 我说:" + msgText.getText() + "\n");
// 待发送文字区域设为空
msgText.setText(null);
} catch (IOException e1) {
// TODO Auto-generated catch block
// 出现此异常说明消息未发送成功
showArea.append(" 你的消息:" + "“" + msgText.getText() + "”" + "未能发送成功\n");
}
}
}

public static void main(String[] args) {
// 主线程用来发送消息
new ChatServer();
}

}

client客户端

主线程

构建页面

连接serverSocket

添加send按钮的监听事件

子线程

死循环接收消息

客户端代码如下:

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
/**
*
*/
package com.yatai.web;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

/**
* @author 王伟业 2014年5月28日
*/
public class ChatClient implements ActionListener, Runnable {
// 相同的界面形式
private JTextArea showArea;
private JTextField msgText;
private JFrame mainJframe;
private JButton sentBtn;
private JScrollPane JSPane;
private JPanel pane;
private Container con;
// 相似的处理方法
private Thread thread = null;
private Socket connectToServer;
private DataInputStream inFromServer;
private DataOutputStream outToServer;

/**
*
*/
public ChatClient() {
// TODO Auto-generated constructor stub
// 构造函数下完成以下内容
mainJframe = new JFrame("客户端");
con = mainJframe.getContentPane();
showArea = new JTextArea();
showArea.setEditable(false);
showArea.setLineWrap(true);
showArea.setWrapStyleWord(true);
JSPane = new JScrollPane(showArea);
msgText = new JTextField();
msgText.setColumns(35);
msgText.addActionListener(this);
sentBtn = new JButton("Send");
sentBtn.addActionListener(this);
pane = new JPanel();
pane.setLayout(new FlowLayout());
pane.add(msgText);
pane.add(sentBtn);
con.add(JSPane, BorderLayout.CENTER);
con.add(pane, BorderLayout.SOUTH);
mainJframe.setSize(500, 400);
mainJframe.setLocation(80, 200);
mainJframe.setVisible(true);
mainJframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

try {
connectToServer = new Socket("localhost", 8888);
inFromServer = new DataInputStream(connectToServer.getInputStream());
outToServer = new DataOutputStream(connectToServer.getOutputStream());
showArea.append(" 连接成功,可以通信\n");

// 创建线程
thread = new Thread(this);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
} catch (IOException e) {
// TODO Auto-generated catch block
// 出现异常说明连接失败
// 向历史聊天区域打印提示信息
showArea.append(" 对不起,连接服务器失败\n");
// 异常连接时输入框不可用
msgText.setEditable(false);
msgText.setEnabled(false);
}
}

/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
// TODO Auto-generated method stub
// 该线程用来接收传来的消息
while (true) {
try {
showArea.append(" 对方说:" + inFromServer.readUTF() + "\n");
Thread.sleep(1000);
} catch (IOException | InterruptedException e) {
// TODO Auto-generated catch block
// 此处异常处理。。。。
e.printStackTrace();
}
}
}

/*
* (non-Javadoc)
*
* @see
* java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
// 响应事件
String s = msgText.getText();
// 如果待发送文字区域存在文字
if (s.length() > 0) {
try {
// 将文字写入到流中
outToServer.writeUTF(s);
outToServer.flush();
// 历史聊天记录增添内容
showArea.append(" 我说:" + msgText.getText() + "\n");
// 待发送文字区域设为空
msgText.setText(null);
} catch (IOException e1) {
// TODO Auto-generated catch block
// 出现此异常说明消息未发送成功
showArea.append(" 你的消息:" + "“" + msgText.getText() + "”" + "未能发送成功\n");
}
}
}

public static void main(String[] args) {
new ChatClient();
}
}


我们在开发Spring Boot应用时,通常同一套程序会被应用和安装到几个不同的环境,比如:开发、测试、生产等。其中每个环境的数据库地址、服务器端口等等配置都会不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常繁琐且容易发生错误的事。

对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包,Spring Boot也不例外,或者说更加简单。

在Spring Boot中多环境配置文件名需要满足 application-{profile}.properties的格式,其中{profile}对应你的环境标识,比如:

1
2
3
application-dev.properties:开发环境 
application-test.properties:测试环境
application-prod.properties:生产环境

至于哪个具体的配置文件会被加载,需要在application.properties文件中通过spring.profiles.active属性来设置,其值对应{profile}值。

如:spring.profiles.active=test就会加载application-test.properties配置文件内容

下面,以不同环境配置不同的服务端口为例,进行样例实验。

针对各环境新建不同的配置文件application-dev.propertiesapplication-test.propertiesapplication-prod.properties

测试不同配置的加载:
执行java -jar xxx.jar --spring.profiles.active=prod也就是生产环境的配置(prod)按照上面的实验,可以如下总结多环境的配置思路:

application.properties中配置通用内容,并设置spring.profiles.active=dev,以开发环境为默认配置
application-{profile}.properties中配置各个环境不同的内容
通过命令行方式去激活不同环境的配置

转载自程序猿DD-翟永超

0%