JoyLau's Blog

JoyLau 的技术学习与思考

步骤

有时需要进行数据的备份和恢复, 涉及到大量的小文件拷贝, 速度很慢,找了一个速度相对较快的命令来操作, 记录下

1
cd source/; tar cf - . | (cd target/; tar xvf -)

解决

问题原因是 kibana_system 的用户名密码不正确

修改密码方式

进入 elasticsearch docker 容器内部, 执行

1
2
curl -X POST --cacert config/certs/ca/ca.crt -u "elastic:${ES_PASS}" -H "Content-Type: application/json" 
http://es01:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_PASS}\"}"

修改密码

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
import com.intellij.database.model.DasTable
import com.intellij.database.model.ObjectKind
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil
import java.io.*
import java.text.SimpleDateFormat

/*
* Available context bindings:
* SELECTION Iterable<DasObject>
* PROJECT project
* FILES files helper
*/
packageName = ""
typeMapping = [
(~/(?i)tinyint|smallint|mediumint/) : "Integer",
(~/(?i)bigint/) : "Long",
(~/(?i)int/) : "Integer",
(~/(?i)bool|bit/) : "Boolean",
(~/(?i)float|double|decimal|real/) : "Double",
(~/(?i)datetime|timestamp|time/) : "LocalDateTime",
(~/(?i)date/) : "LocalData",
(~/(?i)blob|binary|bfile|clob|raw|image/): "InputStream",
(~/(?i)/) : "String"
]


FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
SELECTION.filter { it instanceof DasTable && it.getKind() == ObjectKind.TABLE }.each { generate(it, dir) }
}

def generate(table, dir) {
def className = javaClassName(table.getName(), true)
def fields = calcFields(table)
packageName = getPackageName(dir)
PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(new File(dir, className + ".java")), "UTF-8"))
printWriter.withPrintWriter {out -> generate(out, className, fields,table)}

// new File(dir, className + ".java").withPrintWriter { out -> generate(out, className, fields,table) }
}

// 获取包所在文件夹路径
def getPackageName(dir) {
return dir.toString().replaceAll("\\\\", ".").replaceAll("/", ".").replaceAll("^.*src(\\.main\\.java\\.)?", "") + ";"
}

def generate(out, className, fields,table) {
out.println "package $packageName"
out.println ""
out.println "import java.io.Serializable;"
out.println "import lombok.Getter;"
out.println "import lombok.Setter;"
Set types = new HashSet()

fields.each() {
types.add(it.type)
}

if (types.contains("LocalData")) {
out.println "import java.time.LocalDate;"
}

if (types.contains("LocalDateTime")) {
out.println "import java.time.LocalDateTime;"
}

if (types.contains("InputStream")) {
out.println "import java.io.InputStream;"
}
out.println ""
out.println "/**\n" +
" * Created by liufa on "+ new SimpleDateFormat("yyyy/MM/dd").format(new Date()) + "。\n" +
" * $table.comment \n" +
" */"
out.println "@Getter"
out.println "@Setter"
out.println "public class $className implements Serializable {"
out.println ""
out.println genSerialID()
fields.each() {
out.println ""
// 输出注释
if (isNotEmpty(it.commoent)) {
out.println "\t/**"
out.println "\t * ${it.commoent.toString()}。"
out.println "\t */"
}

if (it.annos != "") out.println " ${it.annos}"

// 输出成员变量
out.println "\tprivate ${it.type} ${it.name};"
}

// 输出get/set方法
// fields.each() {
// out.println ""
// out.println "\tpublic ${it.type} get${it.name.capitalize()}() {"
// out.println "\t\treturn this.${it.name};"
// out.println "\t}"
// out.println ""
//
// out.println "\tpublic void set${it.name.capitalize()}(${it.type} ${it.name}) {"
// out.println "\t\tthis.${it.name} = ${it.name};"
// out.println "\t}"
// }
out.println ""
out.println "}"
}

def calcFields(table) {
DasUtil.getColumns(table).reduce([]) { fields, col ->
def spec = Case.LOWER.apply(col.getDataType().getSpecification())

def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
def comm =[
colName : col.getName(),
name : javaName(col.getName(), false),
type : typeStr,
commoent: col.getComment(),
// annos: "\t@TableField(\""+col.getName()+"\")"]
annos: ""]
if("id" == col.getName())
comm.annos +="\t@TableId(type = IdType.AUTO)"
fields += [comm]
}
}

// 处理类名(这里是因为我的表都是以t_命名的,所以需要处理去掉生成类名时的开头的T,
// 如果你不需要那么请查找用到了 javaClassName这个方法的地方修改为 javaName 即可)
def javaClassName(str, capitalize) {
def s = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str)
.collect { Case.LOWER.apply(it).capitalize() }
.join("")
.replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
// 去除开头的T http://developer.51cto.com/art/200906/129168.htm
// s = s[1..s.size() - 1]
capitalize || s.length() == 1? s : Case.LOWER.apply(s[0]) + s[1..-1]
}

def javaName(str, capitalize) {
// def s = str.split(/(?<=[^\p{IsLetter}])/).collect { Case.LOWER.apply(it).capitalize() }
// .join("").replaceAll(/[^\p{javaJavaIdentifierPart}]/, "_")
// capitalize || s.length() == 1? s : Case.LOWER.apply(s[0]) + s[1..-1]
def s = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str)
.collect { Case.LOWER.apply(it).capitalize() }
.join("")
.replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
capitalize || s.length() == 1? s : Case.LOWER.apply(s[0]) + s[1..-1]
}

def isNotEmpty(content) {
return content != null && content.toString().trim().length() > 0
}

static String changeStyle(String str, boolean toCamel){
if(!str || str.size() <= 1)
return str

if(toCamel){
String r = str.toLowerCase().split('_').collect{cc -> Case.LOWER.apply(cc).capitalize()}.join('')
return r[0].toLowerCase() + r[1..-1]
}else{
str = str[0].toLowerCase() + str[1..-1]
return str.collect{cc -> ((char)cc).isUpperCase() ? '_' + cc.toLowerCase() : cc}.join('')
}
}

static String genSerialID()
{
return "\tprivate static final long serialVersionUID = "+Math.abs(new Random().nextLong())+"L;"
}

生成的实体类预览

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
package com.hfky.pmms.workbench.patrol.group.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

/**
* Created by liufa on 2023/08/24。
* 巡更-巡更组成员
*/
@Getter
@Setter
public class PatrolGroupNumber implements Serializable {

private static final long serialVersionUID = 6241830744323505145L;

@TableId(type = IdType.AUTO)
private Integer id;

/**
* 巡更组ID。
*/
private Integer groupId;

/**
* 用户 ID。
*/
private Long userId;

}

方案

  1. 短轮询
  2. 长轮询: 可以使用 Spring 提供的 DeferredResult 实现
  3. iframe流
  4. SSE: 服务器响应 text/event-stream 类型的数据流信息,思路类似于在线视频播放
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
<script>
let source = null;
let userId = 7777
if (window.EventSource) {
// 建立连接
source = new EventSource('http://localhost:7777/sse/sub/'+userId);
setMessageInnerHTML("连接用户=" + userId);
/**
* 连接一旦建立,就会触发open事件
* 另一种写法:source.onopen = function (event) {}
*/
source.addEventListener('open', function (e) {
setMessageInnerHTML("建立连接。。。");
}, false);
/**
* 客户端收到服务器发来的数据
* 另一种写法:source.onmessage = function (event) {}
*/
source.addEventListener('message', function (e) {
setMessageInnerHTML(e.data);
});
} else {
setMessageInnerHTML("你的浏览器不支持SSE");
}
</script>
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
private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();

/**
* 创建连接
*
* @date: 2022/7/12 14:51
* @auther: 程序员小富
*/
public static SseEmitter connect(String userId) {
try {
// 设置超时时间,0表示不过期。默认30秒
SseEmitter sseEmitter = new SseEmitter(0L);
// 注册回调
sseEmitter.onCompletion(completionCallBack(userId));
sseEmitter.onError(errorCallBack(userId));
sseEmitter.onTimeout(timeoutCallBack(userId));
sseEmitterMap.put(userId, sseEmitter);
count.getAndIncrement();
return sseEmitter;
} catch (Exception e) {
log.info("创建新的sse连接异常,当前用户:{}", userId);
}
return null;
}

/**
* 给指定用户发送消息
*
* @date: 2022/7/12 14:51
* @auther: 程序员小富
*/
public static void sendMessage(String userId, String message) {

if (sseEmitterMap.containsKey(userId)) {
try {
sseEmitterMap.get(userId).send(message);
} catch (IOException e) {
log.error("用户[{}]推送异常:{}", userId, e.getMessage());
removeUser(userId);
}
}
}
  1. MQTT
  2. WebSocket

转载地址: https://mp.weixin.qq.com/s/DlW5XnpG7v0eIiIz9XzDvg

背景

家里的主路由是 iKuai 的,无法安装科学插件,于是想到使用 OpenWrt 作为旁路网关供家里的一些设备上网

我的方案是在群晖上安装 OpenWrt 系统虚拟机来做旁路由

折腾过的方案

一开始使用过群晖的 Docker 套件部署 clash-premium 容器用来做旁路网关
这种方法需要注意的是需要使用 macvlan 创建一个独立的 docker 网络,用来的到和局域网同网段的新的 IP
最终失败了,提示内核不支持的 tun 模式的功能

后来我找了一个旧的笔记本直接运行编译后的二进制的 clash-premium 版本, 也是提示内核不支持,系统是 CentOS 7.2, clash-premium 版本是最新版

安装虚拟机

选择的 OpenWrt 固件: https://openwrt.mpdn.fun:8443/?dir=lede/x86_64
选择 【高大全版】

在群晖里安装套件 Virtual Machine Manager
之后

  • 新增硬盘映像,选择下载并解压出来的 img 文件
  • 到【虚拟机】栏,选择【导入】虚拟机,选择从 【硬盘映像导入】
  • 创建虚拟机, 这里注意在创建虚拟机时网卡记得选 e1000, 否则虚拟机运行后, OpenWrt 界面显示网络接口是 半双工的

修改 OpenWrt 配置

  • 修改 /etc/config/network 网络配置文件,修改 “lan” 一栏的 IP 地址,网关,DNS, 修改完成重启
  • 登录 OpenWrt, 用户名密码 root/password
  • 来到【服务】- 【OpenClash】- 【配置订阅】导入配置信息,并配置自动更新
  • 再来到 【插件设置】 - 【版本更新】,这里要下载 TUN 内核, 如果下载失败,点下载到本地,手动下载,并通过 【系统】- 【文件管理】上传到 /etc/openclash/core/ 目录下, 并授权可执行, chmod +x /etc/openclash/core/clash_tun
  • 再来到 【模式设置】选择 Fake-IP (TUN) 模式运行
  • 再来到 【网络】- 【接口】LAN 接口设置, 基本设置, 关闭最下方的 DHCP 服务器, 选择 【忽略此接口】
  • 最后来到 【DHCP/DNS】- 【高级设置】, 滚到最下方,添加 【自定义挟持域名】, 添加一条记录 time.android.com 203.107.6.88 安卓时间服务器

修改客户端配置

有 3 中方法可以配置

  1. 可以到 iKuai 的 【网络设置 > DHCP设置 > DHCP静态分配】手动下发网关地址
  2. 可以在 iKuai 的 【网络设置 > DHCP设置 > DHCP服务端】设置网关地址和首选 DNS 地址
  3. 手动修改 Google TV 的网络设置为静态

因为我这里是首次激活,需要采取第一种方式或者第二种方式,后面激活成功后,可以还原配置,使用第三种方式

坑记录

Google TV 首次激活会联机安卓的时间服务器进行校时,不通的话会无法连接 WiFi,这就要求 WiFi 能科学上网并且能正确访问 time.android.com
域名解析请求是 UDP 访问方式,需要旁路网关支持 UDP 转发
而满足这个要求需要 DNS 劫持
这里有个重要的点就是,OpenClash 需要开启 Fake-IP (TUN) 模式运行,
否则的话域名劫持无法解析,使用 ntpdate time.android.com 会提示 no server suitable for synchronization found 错误

背景

@Transactional是一种基于注解管理事务的方式,spring通过动态代理的方式为目标方法实现事务管理的增强。

@Transactional使用起来方便,但也需要注意引起@Transactional失效的场景,本文总结了七种情况,下面进行逐一分析。

场景

  • 异常被捕获后没有抛出
  • 抛出非 RuntimeException 异常
  • 方法内部直接调用
  • 新开启一个线程
  • 注解到 private 方法上
  • 数据库本身不支持 (mysql数据库,必须设置数据库引擎为InnoDB)
  • 事务传播属性设置错误

转载自 https://mp.weixin.qq.com/s/f9oYSo68ZNkEj9g8cXb9yA

手动回滚事务和提交事务

  1. 回滚

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

  1. 提交
    引入 PlatformTransactionManager Bean
    1
    platformTransactionManager.commit(TransactionAspectSupport.currentTransactionStatus());

也可以使用 platformTransactionManager 来回滚事务

0%