JoyLau's Blog

JoyLau 的技术学习与思考

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 来回滚事务

配置

通常情况下 Spring Boot 使用 Undertow 容器开启访问日志功能,是记录到本地日志文件的, 我这里有个需求是需要记录到控制台上

配置如下:

1
2
3
4
5
6
7
8
server:
undertow:
accesslog:
enabled: true
dir: /dev
prefix: stdout
suffix:
rotate: false

这样日志会写到 /dev/stdout 上,也就会在服务的控制台打印

改方法适用于 Linux 和 MacOS

语法

XML 示例

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
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<definitions xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="definitions_a563b995-6727-44a6-b7c9-613f54d9145c" targetNamespace="1" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<process id="process_SBJY" isExecutable="true" name="设备借用">
<documentation id="documentation_6f4c7594-bfd9-4b51-8477-a8165c33ca19">{"setting":{"name":"设备借用","categoryId":1,"desc":"","repeatType":0,"autoPass":true,"enableUndo":true,"enableTransfer":true,"enableAuth":true,"id":"process_SBJY:24:c5e034aa-253a-11ee-b160-1aa02d19e831"},"form":[{"title":"单行输入框","type":"单行输入框","name":"TextInput","icon":"icon-danhangwenben","value":"","valueType":"String","props":{"placeholder":"请输入","required":false,"mobileShow":false,"pcShow":true},"id":"field3251030156446","err":false},{"title":"视频","type":"视频","name":"VideoUpload","icon":"icon-shipin","value":[],"valueType":"Array","props":{"required":false,"hiddenMobileShow":true,"maxSize":200,"maxNumber":1,"enableZip":true},"id":"field5186540608388","isInTable":false,"err":false},{"title":"数字","type":"数字","name":"NumberInput","icon":"icon-shuzi","value":null,"valueType":"Number","props":{"placeholder":"请输入","required":true,"mobileShow":true,"pcShow":true},"id":"field2918600070275","isInTable":false,"err":false},{"title":"人员","type":"人员","name":"UserPicker","icon":"icon-renyuan","value":[],"valueType":"User","props":{"placeholder":"请选择人员","required":true,"mobileShow":true,"pcShow":true,"multiple":true},"id":"field2758549148097","isInTable":false,"err":false},{"title":"部门","type":"部门","name":"DeptPicker","icon":"icon-bumen","value":[],"valueType":"Dept","props":{"placeholder":"请选择部门","required":true,"mobileShow":false,"pcShow":true},"id":"field3836364467190","isInTable":false,"err":false}],"process":{"id":"root","parentId":null,"type":"ROOT","name":"发起人","desc":"任何人","props":{"assignedUser":[],"formPerms":[]},"children":{"id":"node_616935911517","parentId":"root","props":{"assignedType":["ASSIGN_USER"],"mode":"AND","sign":false,"nobody":{"handler":"TO_PASS","assignedUser":[]},"assignedUser":[{"id":"1585090515499008001","name":"蜀山分局","orgName":null,"type":2,"number":2,"photo":null},{"id":"1585090627415621634","name":"蜀山分局2","orgName":null,"type":2,"number":5,"photo":null},{"id":"1585109286318030850","name":"地方监狱-ch","orgName":null,"type":2,"number":3,"photo":null},{"id":"1602586682327564289","name":"11112342","orgName":null,"type":2,"number":3,"photo":null},{"id":"1635892057621479425","name":"狱政科","orgName":null,"type":2,"number":4,"photo":null},{"id":"1578658896143728642","name":"科室1","orgName":null,"type":2,"number":8,"photo":null},{"id":"1625451599632105474","name":"分分分","orgName":null,"type":2,"number":2,"photo":null},{"id":"1572024105919086593","name":"001","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1574934048770945026","name":"ch","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1579683243000184834","name":"90","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1580726129128992770","name":"186-超级管理员","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582182526693732354","name":"测试1","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582267287462277121","name":"李聪","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582268293298319361","name":"张爱婷","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582268293659029506","name":"李聪","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582268294598553601","name":"李聪婷","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582268295139618817","name":"张婷聪","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582268295470968833","name":"张婷","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582268295823290369","name":"张爱聪","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582268296012034050","name":"任婷","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582268296188194818","name":"张婷聪","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1582938632181313538","name":"李婷","orgName":"省局","type":1,"number":null,"photo":""}],"roleOrg":{"role":[],"roleOrgType":"","org":[]}},"type":"CC","name":"抄送人","children":{"id":"node_926398936983","parentId":"node_616935911517","props":{"assignedType":["ASSIGN_USER"],"mode":"NEXT","sign":false,"nobody":{"handler":"TO_EXCEPTIONAL","assignedUser":[]},"assignedUser":[{"id":"1580726129128992770","name":"186-超级管理员","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1659398930563702785","name":"刘法手机","orgName":"科室1","type":1,"number":null,"photo":"168673478113543988cbfefe15f74157569bb3107d652"},{"id":"1585094725347098626","name":"11111","orgName":null,"type":2,"number":0,"photo":null}],"roleOrg":{"role":[],"roleOrgType":"ALL","org":[]}},"type":"APPROVAL","name":"审批人","children":{"id":"node_839328175114","parentId":"node_926398936983","props":{"assignedType":["ASSIGN_USER"],"mode":"OR","sign":false,"nobody":{"handler":"TO_PASS","assignedUser":[]},"assignedUser":[{"id":"1580726129128992770","name":"186-超级管理员","orgName":"省局","type":1,"number":null,"photo":""},{"id":"1659398930563702785","name":"刘法手机","orgName":"科室1","type":1,"number":null,"photo":""},{"id":"1572769134128214018","name":"张柱11","orgName":"科室1","type":1,"number":null,"photo":"1684823575798a655866a187cf9c8d995b8dae79fb77a"},{"id":"1613445750961299458","name":"洪小霞","orgName":"科室1","type":1,"number":null,"photo":""}],"roleOrg":{"role":[],"roleOrgType":"","org":[]}},"type":"APPROVAL","name":"审批人","children":{}}}}}}</documentation>
<extensionElements>
<camunda:properties>
<camunda:property name="createBy" value="1580726129128992770"/>
<camunda:property name="createAt" value="2023-07-19T17:56:37.413"/>
</camunda:properties>
</extensionElements>
<startEvent id="root" name="发起人">
<documentation id="documentation_3c5043d1-e67d-45df-bfe3-89abc6b1f1ba">{"assignedUser":[],"formPerms":[]}</documentation>
<extensionElements>
<camunda:executionListener class="com.hfky.workflow.server.module.camunda.listener.start.StartEventStartListener" event="start"/>
<camunda:executionListener class="com.hfky.workflow.server.module.camunda.listener.start.StartEventEndListener" event="end"/>
</extensionElements>
<outgoing>sequenceFlow_76b564f1-d4ad-4d53-900e-947df8979783</outgoing>
</startEvent>
<sendTask camunda:class="com.hfky.workflow.server.module.camunda.delegate.CCTaskDelegate" id="node_616935911517" name="抄送人">
<documentation id="documentation_bf70d26d-f7fe-4547-b27b-142ab7c9b613">{"assignedType":["ASSIGN_USER"],"assignedUser":[{"id":"1585090515499008001","name":"蜀山分局","type":2},{"id":"1585090627415621634","name":"蜀山分局2","type":2},{"id":"1585109286318030850","name":"地方监狱-ch","type":2},{"id":"1602586682327564289","name":"11112342","type":2},{"id":"1635892057621479425","name":"狱政科","type":2},{"id":"1578658896143728642","name":"科室1","type":2},{"id":"1625451599632105474","name":"分分分","type":2},{"id":"1572024105919086593","name":"001","type":1},{"id":"1574934048770945026","name":"ch","type":1},{"id":"1579683243000184834","name":"90","type":1},{"id":"1580726129128992770","name":"186-超级管理员","type":1},{"id":"1582182526693732354","name":"测试1","type":1},{"id":"1582267287462277121","name":"李聪","type":1},{"id":"1582268293298319361","name":"张爱婷","type":1},{"id":"1582268293659029506","name":"李聪","type":1},{"id":"1582268294598553601","name":"李聪婷","type":1},{"id":"1582268295139618817","name":"张婷聪","type":1},{"id":"1582268295470968833","name":"张婷","type":1},{"id":"1582268295823290369","name":"张爱聪","type":1},{"id":"1582268296012034050","name":"任婷","type":1},{"id":"1582268296188194818","name":"张婷聪","type":1},{"id":"1582938632181313538","name":"李婷","type":1}],"roleOrg":{"role":[],"roleOrgType":"UNKNOWN","org":[]},"selfSelect":null,"formUser":null}</documentation>
<extensionElements>
<camunda:executionListener class="com.hfky.workflow.server.module.camunda.listener.task.sendtask.start.SendTaskStartListener" event="start"/>
<camunda:executionListener class="com.hfky.workflow.server.module.camunda.listener.task.sendtask.end.SendTaskEndListener" event="end"/>
</extensionElements>
<incoming>sequenceFlow_76b564f1-d4ad-4d53-900e-947df8979783</incoming>
<outgoing>node_926398936983_sequence</outgoing>
</sendTask>
<sequenceFlow id="sequenceFlow_76b564f1-d4ad-4d53-900e-947df8979783" sourceRef="root" targetRef="node_616935911517"/>
<sequenceFlow id="node_926398936983_sequence" sourceRef="node_616935911517" targetRef="node_926398936983">
<extensionElements>
<camunda:executionListener class="com.hfky.workflow.server.module.camunda.listener.sequence.UserTaskIncomingListener" event="take"/>
</extensionElements>
</sequenceFlow>
<userTask camunda:assignee="${user}" id="node_926398936983" name="审批人">
<documentation id="documentation_dc99fedc-fc7b-4985-b80a-bea805b02699">{"assignedType":["ASSIGN_USER"],"mode":"NEXT","sign":false,"formPerms":null,"nobody":{"handler":"TO_EXCEPTIONAL","assignedUser":[]},"timeLimit":null,"assignedUser":[{"id":"1580726129128992770","name":"186-超级管理员","type":1},{"id":"1659398930563702785","name":"刘法手机","type":1},{"id":"1585094725347098626","name":"11111","type":2}],"selfSelect":null,"leaderTop":null,"leader":null,"roleOrg":{"role":[],"roleOrgType":"ALL","org":[]},"refuse":null,"formUser":null}</documentation>
<extensionElements>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.create.UserTaskCreateListener" event="create"/>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.complete.UserTaskCompleteListener" event="complete"/>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.delete.UserTaskDeleteListener" event="delete"/>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.assignment.UserTaskAssignmentListener" event="assignment"/>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.update.UserTaskUpdateListener" event="update"/>
<camunda:inputOutput>
<camunda:inputParameter name="approvalMode">NEXT</camunda:inputParameter>
<camunda:inputParameter name="userTaskType">APPROVAL</camunda:inputParameter>
<camunda:inputParameter name="nobodyHandler">TO_EXCEPTIONAL</camunda:inputParameter>
</camunda:inputOutput>
</extensionElements>
<incoming>node_926398936983_sequence</incoming>
<outgoing>node_839328175114_sequence</outgoing>
<multiInstanceLoopCharacteristics camunda:collection="${users}" camunda:elementVariable="user" id="multiInstanceLoopCharacteristics_55826d3e-2ded-467b-a8ea-3be1ae6e102e" isSequential="true">
<completionCondition id="completionCondition_08ebe6ea-749f-4dc8-bd8b-55b0428db83b">${nrOfCompletedInstances == nrOfInstances}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="node_839328175114_sequence" sourceRef="node_926398936983" targetRef="node_839328175114">
<extensionElements>
<camunda:executionListener class="com.hfky.workflow.server.module.camunda.listener.sequence.UserTaskIncomingListener" event="take"/>
</extensionElements>
</sequenceFlow>
<userTask camunda:assignee="${user}" id="node_839328175114" name="审批人">
<documentation id="documentation_621470e6-df17-4fb1-8fad-86011e0b344c">{"assignedType":["ASSIGN_USER"],"mode":"OR","sign":false,"formPerms":null,"nobody":{"handler":"TO_PASS","assignedUser":[]},"timeLimit":null,"assignedUser":[{"id":"1580726129128992770","name":"186-超级管理员","type":1},{"id":"1659398930563702785","name":"刘法手机","type":1},{"id":"1572769134128214018","name":"张柱11","type":1},{"id":"1613445750961299458","name":"洪小霞","type":1}],"selfSelect":null,"leaderTop":null,"leader":null,"roleOrg":{"role":[],"roleOrgType":"UNKNOWN","org":[]},"refuse":null,"formUser":null}</documentation>
<extensionElements>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.create.UserTaskCreateListener" event="create"/>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.complete.UserTaskCompleteListener" event="complete"/>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.delete.UserTaskDeleteListener" event="delete"/>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.assignment.UserTaskAssignmentListener" event="assignment"/>
<camunda:taskListener class="com.hfky.workflow.server.module.camunda.listener.task.usertask.update.UserTaskUpdateListener" event="update"/>
<camunda:inputOutput>
<camunda:inputParameter name="approvalMode">OR</camunda:inputParameter>
<camunda:inputParameter name="userTaskType">APPROVAL</camunda:inputParameter>
<camunda:inputParameter name="nobodyHandler">TO_PASS</camunda:inputParameter>
</camunda:inputOutput>
</extensionElements>
<incoming>node_839328175114_sequence</incoming>
<outgoing>sequenceFlow_39966a65-6409-4301-bd22-566da3f0abc5</outgoing>
<multiInstanceLoopCharacteristics camunda:collection="${users}" camunda:elementVariable="user" id="multiInstanceLoopCharacteristics_2ee36c6f-a743-44e8-96f1-5a9153434859" isSequential="false">
<completionCondition id="completionCondition_4c099868-903c-4c86-b1b3-7e60f78c96e6">${nrOfCompletedInstances == 1}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<endEvent id="end" name="结束">
<extensionElements>
<camunda:executionListener class="com.hfky.workflow.server.module.camunda.listener.end.EndEventEndListener" event="end"/>
</extensionElements>
<incoming>sequenceFlow_39966a65-6409-4301-bd22-566da3f0abc5</incoming>
</endEvent>
<sequenceFlow id="sequenceFlow_39966a65-6409-4301-bd22-566da3f0abc5" sourceRef="node_839328175114" targetRef="end"/>
<textAnnotation id="textAnnotation_e2d2a588-caf6-466e-8b93-29cdb683f4fd">
<text>审批节点/顺序依次审批/流程异常</text>
</textAnnotation>
<association id="association_6f8c3357-dce9-4d5a-9596-720975abad36" sourceRef="node_926398936983" targetRef="textAnnotation_e2d2a588-caf6-466e-8b93-29cdb683f4fd"/>
<textAnnotation id="textAnnotation_84c4a034-2871-4275-affd-4111c89f2f48">
<text>审批节点/或签/直接通过</text>
</textAnnotation>
<association id="association_6138d9c4-bf25-4f0d-b7ea-eadf4b06233a" sourceRef="node_839328175114" targetRef="textAnnotation_84c4a034-2871-4275-affd-4111c89f2f48"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_18a5de55-9957-40e6-b43e-5ebde913cdf5">
<bpmndi:BPMNPlane bpmnElement="process_SBJY" id="BPMNPlane_4328db3b-a050-4677-b4d6-22f5148b8739">
<bpmndi:BPMNShape bpmnElement="root" id="BPMNShape_df1df4ed-7fcc-4d16-95e2-996bdab5097e">
<dc:Bounds height="36.0" width="36.0" x="100.0" y="100.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="node_616935911517" id="BPMNShape_20d93b31-b628-4844-b9e6-f95aaa374d26">
<dc:Bounds height="80.0" width="100.0" x="186.0" y="78.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_76b564f1-d4ad-4d53-900e-947df8979783" id="BPMNEdge_7ce6d515-d414-470c-a10d-4faf4e827a9f">
<di:waypoint x="136.0" y="118.0"/>
<di:waypoint x="186.0" y="118.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="node_926398936983" id="BPMNShape_9a09848e-47b5-4cf9-ba4d-202fca0076eb">
<dc:Bounds height="80.0" width="100.0" x="336.0" y="78.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="node_926398936983_sequence" id="BPMNEdge_6c227e09-8505-411f-b218-cccf6c7f6b62">
<di:waypoint x="286.0" y="118.0"/>
<di:waypoint x="336.0" y="118.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="textAnnotation_e2d2a588-caf6-466e-8b93-29cdb683f4fd" id="BPMNShape_9778beb2-f56e-4525-adeb-55521420d4ae">
<dc:Bounds height="30.0" width="150.0" x="436.0" y="178.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="association_6f8c3357-dce9-4d5a-9596-720975abad36" id="BPMNEdge_9cf34fdc-3cda-4465-8bae-461a4b97d085">
<di:waypoint x="386.0" y="158.0"/>
<di:waypoint x="436.0" y="193.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="node_839328175114" id="BPMNShape_2adea32c-7b35-4ed9-93dd-5d4d9b5c42e4">
<dc:Bounds height="80.0" width="100.0" x="486.0" y="78.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="node_839328175114_sequence" id="BPMNEdge_cb1c6de5-6bf5-496e-bf67-577bee2c033c">
<di:waypoint x="436.0" y="118.0"/>
<di:waypoint x="486.0" y="118.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="textAnnotation_84c4a034-2871-4275-affd-4111c89f2f48" id="BPMNShape_ad901588-3b14-4870-9b4a-8061053332e5">
<dc:Bounds height="30.0" width="150.0" x="586.0" y="178.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="association_6138d9c4-bf25-4f0d-b7ea-eadf4b06233a" id="BPMNEdge_5da81668-e368-42a5-90b7-7751795cd44e">
<di:waypoint x="536.0" y="158.0"/>
<di:waypoint x="586.0" y="193.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_6f6c1e50-adde-4c61-8f9b-6f043ebda337">
<dc:Bounds height="36.0" width="36.0" x="636.0" y="100.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow_39966a65-6409-4301-bd22-566da3f0abc5" id="BPMNEdge_41edea4e-b4c5-42db-9787-e5a2faf04c20">
<di:waypoint x="586.0" y="118.0"/>
<di:waypoint x="636.0" y="118.0"/>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

例如有上面的示例 XML
现在想获取 id 为 node_839328175114 的 userTask 的 documentation 的值
使用

1
select extractvalue(@xml, '/definitions/process/userTask[@id="node_839328175114"]/documentation');

这里我的场景是官方文档没有提到的, 做个简记
官方文档: https://dev.mysql.com/doc/refman/8.0/en/xml-functions.html

背景

有时我们想在同一个流程实例中查看流程从上到下的流转顺序, 效果像这样

Camunda-1

这时会到 ACT_HI_ACTINST 表里去查流程实例的节点信息

但是我们根据流程是 ID 去查数据的时候发现, 并没有很好的根据时间顺序进行排列,就比如上面的数据,在数据库反应的情况如下:

Camunda-2

可以看到 发起人和抄送人是几乎同时进行的,因为发起人发起后,第一个是抄送人任务, sendTask 的操作很快,创建时间 START_TIME 都是 2023-07-20 09:11:21
于是同一流程实例根据创建时间 START_TIME 排序就会出现问题,上面的数据就是抄送人跑到发起人前面去了

于是想着用没用其他的方法,来根据其他字段进行排序

解决方案

被我找到一个方案, 就是表里的主键 UUID

Camunda 默认主键策略是 UUID 这个配置可以在配置文件中修改

1
2
3
camunda:
bpm:
id-generator: strong

可以修改为 simplestrongprefixed, 默认是 strong

strong 策略的实现是 StrongUuidGenerator, 源码位置在 org.camunda.bpm.engine.impl.persistence.StrongUuidGenerator

其中生成 ID 的 getNextId 方法是使用的 fasterxmlTimeBasedGenerator, 源码位置在 com.fasterxml.uuid.impl.TimeBasedGenerator

从名字可以看出是基于时间生成的,实际上生成的是 UUID 的 version 1 版本

于是就是可以根据时间来排序了

具体操作

继续翻源码, 我们来到 java-uuid-generator-3.2.0.jar 这个依赖包, 其中 2 个类引起了我的注意
UUIDUtilUUIDComparator

那么排序就很简单了, 如下代码就可轻松排序:

1
2
3
4
5
6
7
8
9
public class UUIDTimeOrderTest {
public static void main(String[] args) {
String[] demos = ("c0767f45-0367-11ee-9698-8edff34f00ff,c07aec28-0367-11ee-9698-8edff34f00ff,c167211a-0367-11ee-9698-8edff34f00ff,c1687fb5-0367-11ee-9698-8edff34f00ff,c169b840-0367-11ee-9698-8edff34f00ff,c16b8d0b-0367-11ee-9698-8edff34f00ff").split(",");
Arrays.stream(demos)
.sorted((s1, s2) -> UUIDComparator.staticCompare(UUIDUtil.uuid(s1), UUIDUtil.uuid(s2)))
.forEach(s -> System.out.println(s));
}

}

背景

特斯拉开了将近 2 年了, 没有进行任何保养过, 最近想去保养下, 发下没有什么值得保养的

就我目前收集的信息, 目前可以保养的有

  1. 空调滤芯, 官方建议每年更换一次
  2. 空调管道支架,很早之前有发召回的公告,需要加装支架
  3. 前支臂,原地方向盘打满的时候, 前轮会发出哒哒哒的异响,不是刹车片摩擦的声音,我查询了下,应该通病

其实前 2 个问题, 并没有什么安全隐患,也并不是强制性的, 然后我就看了下空调滤芯的事情, 发现如果到官方的售后去更换的话, 材料费加上人工工时费,大概在 340
我在京东买个 2 个前置的曼牌空调滤芯, 打算自己动手更换

准备工具

  1. T20 螺丝刀
  2. 撬棒,家里没有,我拿了个金属勺子代替

步骤

  1. 将副驾驶的座位移到最后面
  2. 拿掉副驾驶脚垫
  3. 使用 T20 螺丝刀拧下副驾驶下方右侧的螺丝, 左侧的没有螺丝, 是个塑料的膨胀螺丝, 拿东西撬一下就行
  4. 沿着手套箱下面的边缝撬起,去下下方的塑料壳盖板,注意有 2 根排线相连,取得时候用力不能过大,否则会扯断排线
  5. 我这里为了顺利拆掉侧方的挡板, 我拔掉了喇叭的排线,这样能够把整个塑料壳放到傍边,也可以把氛围灯的排线一并拆掉,这样可以整体拿下来
  6. 使用撬棒沿着侧边挡板的缝隙慢慢撬起, 随后用手沿着挡板把整个挡板拽下来,要用点力气, 这里注意拔下来的挡板白色卡扣,有时候不会被一起带出来,需要手动去下来,再装回卡勾上, 不然还原装回去的时候容易掉下去,导致车里异响

挡板

  1. 拆下挡板后可以看到最里面的滤芯槽盖板, 上方有一个 T20 的螺丝, 拆下他, 可以去除盖板

滤芯槽盖板
滤芯槽盖板2

  1. 取出 2 片空调滤芯,再放入新买的

新旧对比1
新旧对比2

对比了下新旧, 发现我之前旧的滤芯并不是很脏,但是有点潮湿了,还有异味

  1. 还原

我的 Tesla

0%