星轨

感谢deepseek

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
import cv2
import numpy as np
import glob
import sys
def pre_process(img):
# 降噪处理(示例使用非局部均值去噪)
img = cv2.fastNlMeansDenoisingColored(img, None, 10, 10, 7, 21)

# 对比度增强(示例使用CLAHE)
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
l = clahe.apply(l)
img = cv2.cvtColor(cv2.merge((l,a,b)), cv2.COLOR_LAB2BGR)
return img

def create_star_trail(input_path, output_file):
"""
合成星轨图像
:param input_path: 输入图像路径(支持通配符,如 'input/*.jpg')
:param output_file: 输出文件名
"""
# 获取按文件名排序的图像文件列表
img_files = sorted(glob.glob(input_path))
if not img_files:
print("未找到输入图像,请检查路径是否正确")
return

# 初始化星轨合成图像
star_trail = None

for idx, file in enumerate(img_files):
# 读取图像
img = cv2.imread(file)
# pre_process(img)
if img is None:
print(f"警告:无法读取图像 {file},已跳过")
continue

# 首次初始化星轨图像
if star_trail is None:
star_trail = img.copy().astype(np.float32)
else:
# 最大值叠加法(保留每个像素最亮值)
star_trail = np.maximum(star_trail, img.astype(np.float32))

# 显示处理进度
print(f"已处理 {idx+1}/{len(img_files)} 张图像", end='\r')

temp = np.clip(star_trail, 0, 255).astype(np.uint8)
cv2.imwrite(sys.path[0] + f'/temp/{idx}.jpg', temp)

if star_trail is None:
print("错误:未找到有效输入图像")
return

# 转换为8位无符号整型并保存结果
star_trail = np.clip(star_trail, 0, 255).astype(np.uint8)
cv2.imwrite(output_file, star_trail)
print(f"\n星轨合成完成!结果已保存至 {output_file}")

if __name__ == "__main__":
# 使用示例
create_star_trail(
input_path='/Volumes/backup/星空2/修改/*.jpg', # 输入图像路径(按文件名排序)
output_file='star_trail1.jpg'
)

drools的DMN-javaparser编译

1 between 0 and 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
26
27
28
29
30
31
32
33
34
package org.kie.dmn.feel.codegen.feel11.genadeb1f468481413bbc5bfd45c3422978;

import static org.kie.dmn.feel.codegen.feel11.CompiledFEELSemanticMappings.*;
import org.kie.dmn.feel.codegen.feel11.CompiledCustomFEELFunction;
import org.kie.dmn.feel.codegen.feel11.CompiledFEELExpression;
import org.kie.dmn.feel.codegen.feel11.CompiledFEELSupport;
import org.kie.dmn.feel.lang.EvaluationContext;

public class TemplateCompiledFEELExpression implements org.kie.dmn.feel.codegen.feel11.CompiledFEELExpression {

/**
* FEEL: 1 between 0 and 4
*/
@Override
public Object apply(EvaluationContext feelExprCtx) {
return between(feelExprCtx, K___1, K___0, K___4);
}

private static TemplateCompiledFEELExpression INSTANCE;

public static TemplateCompiledFEELExpression getInstance() {
if (INSTANCE == null) {
INSTANCE = new TemplateCompiledFEELExpression();
}
return INSTANCE;
}

public static final java.math.BigDecimal K___1 = new java.math.BigDecimal(1, java.math.MathContext.DECIMAL128);

public static final java.math.BigDecimal K___0 = new java.math.BigDecimal(0, java.math.MathContext.DECIMAL128);

public static final java.math.BigDecimal K___4 = new java.math.BigDecimal(4, java.math.MathContext.DECIMAL128);
}

between调用的方法是org.kie.dmn.feel.codegen.feel11.CompiledFEELSemanticMappings.between方法,是预先写好的。
我觉得相比于ast的方式,把一些变量放在类里面了。编译完之后也可以直接加载使用。

The retrieved CompiledFEELExpression could be a statically-interpreted InterpretedExecutableExpression (that wraps the original BaseNode ast) or could be a dynamically-code-generated CompiledExecutableExpression.
In the first case, evaluation is executed by the DMN code as it is statically defined.
In the latter case, code is generated out of the given model. In that code, some variable will be directly written in the generated, speeding up its execution.

sentinel实现分析

系统自适应保护是什么意思?

https://sentinelguard.io/zh-cn/docs/system-adaptive-protection.html

1
2
3
4
5
6
7
8
9
10
// total thread
int currentThread = Constants.ENTRY_NODE.curThreadNum();
...
private static boolean checkBbr(int currentThread) {
if (currentThread > 1 &&
currentThread > Constants.ENTRY_NODE.maxSuccessQps() * Constants.ENTRY_NODE.minRt() / 1000) {
return false;
}
return true;
}

qps * rt==当前的线程数就是最佳的负载。如果当前线程数据>qps * rt可以认为新进来的请求会产生积压。
qps * rt 就是当前系统的消费能力。线程数就是当前的生产能力。

正常还是需要组合其他策略使用。

只能在入口使用是因为需要当前cpu负载激发。

1
2
3
4
5
if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) {
if (!checkBbr(currentThread)) {
throw new SystemBlockException(resourceWrapper.getName(), "load");
}
}

但实际上是不是也可以对下游也这么干?现在都是微服务架构,如何根据下游的能力实时的进行出口限流?

DMN FEEL脚本实现分析

Drools的实现

https://github.com/kiegroup/drools/tree/main/kie-dmn/kie-dmn-feel:

antlr4词法解析,通过vistor生成抽象语法树,执行。但是可以用javaparser将ast生成java代码,再编译成class执行。其原理效果还不太明确。

例子:
https://stackoverflow.com/questions/29971097/how-to-create-ast-with-antlr4

camunda的实现

https://camunda.github.io/feel-scala/:

用的scala实现
readme里有一段最近被删掉了。
The following resources can help to understand some general concepts behind the implementation:

日本旅行

2024-08-30/2024-09-03

  • day1: 中午到达关西国际机场。第一顿是松屋。下午大阪城公园。
  • day2: 台风袭来,全天下雨。花费三百多报了一个小团,京都伏见稻荷,清水寺等。下午奈良公园喂小鹿。
  • day3: 脚底起泡,上午是大阪本地寺庙,中午做近铁重新到奈良,爬若草山。
  • day4: 阪急六甲由马周游券,完整一圈。缆车体验很好。下午神户港。
  • day5: 休息。在凌空城outlets坐了一下午。

总自费3000元左右。

设备:A6700 18-135






















































DMN(Decision Model and Notation)

什么是DMN

决策模型和符号(DMN™)是由 OMG® 制定的一项标准,它提供了一种通用且可视化的符号,所有用户和角色都可以轻松理解。借助 DMN,业务分析师可以定义初始决策需求,然后将更详细的决策模型形式化;技术开发人员可以使用可移植的执行语义来自动执行任何流程中的决策,而业务利益相关者可以管理和监控这些决策。
https://www.omg.org/spec/DMN/1.5/Beta1

使用样例





在spring中使用drools导入DMN并调用

1

DMN的编辑软件

https://marketplace.visualstudio.com/items?itemName=kie-group.dmn-vscode-extension
https://bpmn.io/toolkit/dmn-js/
@kogito-tooling/kie-editors-standalone

DMN 相比于 DRl 等的优势

DMN 更易于理解和维护,更适合业务人员参与
DMN 支持图形化的决策模型,更直观
DMN 提供了更丰富的决策元素和功能,更灵活
DMN 标准化程度更高,更易于与其他系统集成

excel流式生成

无论是easyExcel或者poi等excel处理框架,在生成excel时都需要同步等待操作。但如果数据量较大,生成excel的时间会比较长,对于用户来说需要等待,影响体验。这时候就需要考虑使用流式生成excel。

下面提供了几种方案实现流失生成,能够在页面上直接开启下载。

  1. 后端处理

excel2017版本之后,文件本身其实是一个zip文件,里面包含了多个xml文件,这些xml文件描述了excel的内容。所以我们可以通过流式生成xml文件,然后将这些xml文件打包成zip文件,最后返回给浏览器。

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
public ExcelGenerator(InputStream template, Integer dataIndex, OutputStream outputStream) throws IOException {
this.index = dataIndex;
zos = new ZipOutputStream(outputStream);
try (ZipInputStream zipInputStream = new ZipInputStream(template)) {
ZipEntry ze = null;
String sheet1 = null;
while ((ze = zipInputStream.getNextEntry()) != null) {
if (StringUtils.equals(ze.getName(), "xl/worksheets/sheet1.xml")) {
sheet1 = IOUtils.toString(zipInputStream, StandardCharsets.UTF_8);
} else {
ZipEntry zeOut = new ZipEntry(ze.getName());
zeOut.setSize(ze.getSize());
zeOut.setTime(ze.getTime());
zos.putNextEntry(zeOut);
IOUtils.copy(zipInputStream, zos);
zos.flush();
zos.closeEntry();

}
}
//准备sheet1的数据
//FIXME: 玩弄xml
ZipEntry entry = new ZipEntry("xl/worksheets/sheet1.xml");
zos.putNextEntry(entry);
int sheetDataIndex = sheet1.indexOf("</sheetData>");
zos.write(sheet1.substring(0, sheetDataIndex).getBytes(StandardCharsets.UTF_8));
finishStr = sheet1.substring(sheetDataIndex);
}
}

//只修改sheetData -> row 的数据
public void render(List<T> views) throws IOException {
for (T t : views) {
zos.write(renderRowView(t));
}
zos.flush();
}

private byte[] renderRowView(T t) {
Map<Integer, String> cells = new TreeMap<>();
ReflectionUtils.doWithFields(t.getClass(), f -> {
ColumnMeta meta = f.getAnnotation(ColumnMeta.class);
try {
Object value = PropertyUtils.getProperty(t, f.getName());
ConvertMeta convert = f.getAnnotation(ConvertMeta.class);
if (convert != null) {
ConvertInfo convertInfo = new ConvertInfo();
convertInfo.setDefaultValue(convert.defaultVaule());
convertInfo.setTargetType(f.getType());
convertInfo.setFormat(convert.format());
//FIXME: 性能问题 缓存实例
IConvert instance = convert.convert().newInstance();
instance.setConvertInfo(convertInfo);
value = instance.convert(value);
}
cells.put(meta.index(), value.toString());
} catch (Exception e) {
}
}, f -> null != f.getAnnotation(ColumnMeta.class));
byte[] row = ("<row r=\"" + index + "\">" +
cells.entrySet().stream().map(e -> {
return "<c r=\"" + getColName(e.getKey()) + index + "\" t=\"inlineStr\"><is><t>" + e.getValue() + "</t></is></c>";
}).collect(Collectors.joining("")) + "</row>").getBytes(StandardCharsets.UTF_8);
index++;
return row;
}

private String getColName(Integer index) {
if (index <= 26) {
return String.valueOf((char) ('A' + index - 1));
} else {
return String.valueOf((char) ('A' + index / 26 - 1)) + String.valueOf((char) ('A' + index % 26 - 1));
}
}


public void finish() throws IOException {
//完成sheet1的xml
zos.write(finishStr.getBytes(StandardCharsets.UTF_8));
zos.flush();
zos.closeEntry();
zos.finish();
}
  1. 前端处理

前端可以通过restful接口获取到excel内容,然后通过blob对象生成excel文件,最后通过a标签的download属性下载excel文件。

https://medium.com/@Nopziiemoo/create-excel-files-using-javascript-without-all-the-fuss-2c4aa5377813

Examination

使用dify.ai构建考试问答机器人

发现问卷星没有强制使用https,本地配置hosts

1
127.0.0.1	kaoshi.wjx.top

实现一个代理服务,注入我们的js代码,实现答题时访问dify api 获取答案,将答案拼接在问题下面。将原来的地址改成http访问,也省需要mitm了。
当然也需要将代理返回内容里的https改成http,否则就访问不了了。

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
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.request as urllib2
import logging
import re
import gzip
import logging
import sys
from bs4 import BeautifulSoup

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')

fh = logging.FileHandler(sys.path[0] + '/考试.log',mode='a', encoding='utf-8')
fh.setLevel(logging.INFO)
fh.setFormatter(formatter)
logger.addHandler(fh)

ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(formatter)
logger.addHandler(ch)

# logging.basicConfig()

class ProxyHandler(BaseHTTPRequestHandler):
target_host = 'kaoshi.wjx.top'

def proxy(self):
header = dict(self.headers)
logger.info('header: %s', header)
# print(header)
if self.path == '/npm/eruda':
self.send_response(200)
self.end_headers()
with open(sys.path[0] + '/eruda.js', 'r', encoding='utf-8') as f:
self.wfile.write(f.read().encode('utf-8'))
return
elif self.path == '/script.js':
self.send_response(200)
self.end_headers()
with open(sys.path[0] + '/script.js', 'r', encoding='utf-8') as f:
self.wfile.write(f.read().encode('utf-8'))
return
else:
header['Host'] = 'kaoshi.wjx.top'
request = urllib2.Request(url='http://' + "101.37.44.53" + self.path, headers=header, method=self.command)
# print(request.full_url)
logger.info('request: %s', request.full_url)
with urllib2.urlopen(request) as f:
# print(f.status, f.headers)
logger.info("status: %s", f.status)
logger.info("headers: %s", f.headers)

self.send_response(f.status)
for (key, value) in f.headers.items():
if key == 'Content-Type':
self.send_header(key, value)
self.end_headers()
data = f.read()
if f.headers['Content-Encoding'] == 'gzip':
data = gzip.decompress(data)
data = data.decode('utf-8')
# print(data)
if 'https' in data:
data = data.replace('https://', 'http://')
logger.info("data: %s", data)
logger.info('<<<<: %s', self.path)
data = get_answer(data)
self.wfile.write(data.encode('utf-8'))


def do_GET(self):
self.proxy()


def do_POST(self):
self.proxy()

def get_answer(data: str):
if '</body>' in data:
i = data.index('</body>')
script = '''
<script src="//kaoshi.wjx.top/npm/eruda"></script><script>setTimeout(() => eruda.init(), 3000);</script>
'''
script = script + '''<script src="//kaoshi.wjx.top/script.js"></script>'''
data = data[:i] + script + data[i:]
return data

def run():
server_address = ('', 80)
httpd = HTTPServer(server_address, ProxyHandler)
httpd.serve_forever()

if __name__ == '__main__':
run()
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
document.addEventListener("DOMContentLoaded", (event) => {
let next = document.querySelector('a[onclick="show_next_page();"]');
next.addEventListener('click', (event) => {
event.preventDefault();
show_next_page();
let q = document.querySelector('fieldset[style=""]');
if (q.page != 1) {
t = q.querySelector('div.topichtml').innerHTML;
o = Array.from(q.querySelectorAll('div.label')).map((e) => e.innerHTML);
console.log(t);
console.log(o);
fetch('https://api.dify.ai/v1/chat-messages', {
method: 'POST',
headers: {
'Authorization': 'Bearer xxxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"inputs": {},
// "query": "党员领导干部的配偶、子女及其配偶,违反有关规定在该党员领导干部管辖的地区和业务范围内从事可能影响其公正执行公务的经营活动,或者有其他违反经商办企业禁止规定行为的,该党员领导干部应当按照规定予以纠正;拒不纠正的,()。\nA.其本人应当辞去现任职务\nB.由组织予以调整职务\nC.其本人应当辞去现任职务或者由组织予以调整职务\nD.直接撤销党内职务处分\n答案是什么?",
"query": t + '\n' + o.join('\n') + '\n答案是什么?',
"response_mode": "blocking",
"conversation_id": "",
"user": "abc-123",
"files": [
]
})
})
.then(response => response.json())
.then(data => {
console.log(data);
r = JSON.parse(data)['answers'];
p = document.createElement('p');
p.innerHTML = r;
q.querySelector('div.topichtml').append(p);
});
}
});
});