Skip to main content

· 2分钟阅读
xkyii

wsl ip

打开WSL的linux终端(以CentOs8为例)

获取ip地址 (这里是172.22.112.241):

ip a
[root@DESKTOP-21M3RR1 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:15:5d:8c:b2:a6 brd ff:ff:ff:ff:ff:ff
inet 172.22.112.241/20 brd 172.22.127.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::215:5dff:fe8c:b2a6/64 scope link
valid_lft forever preferred_lft forever

主机ip

打开Win终端,获取IP,(这里拿到局域网IP: 192.168.1.38,以及WSL的IP: 172.22.112.1)

ipconfig
以太网适配器 以太网:

连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::1eb1:c4ad:cdb1:af30%6
IPv4 地址 . . . . . . . . . . . . : 192.168.1.38
子网掩码 . . . . . . . . . . . . : 255.255.255.0

以太网适配器 vEthernet (WSL):

连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::af08:266f:d210:e545%55
IPv4 地址 . . . . . . . . . . . . : 172.22.112.1
子网掩码 . . . . . . . . . . . . : 255.255.240.0
默认网关. . . . . . . . . . . . . :

主机 ping wsl

一般是通的

ping 172.22.112.241

wsl ping 主机

  • ping 172.22.112.1如果不通,尝试用管理员权限运行:New-NetFirewallRule -DisplayName "WSL" -Direction Inbound -InterfaceAlias "vEthernet (WSL)" -Action Allow
  • ping 192.168.1.38如果不通,尝试启用防火墙的预定义入站规则:核心网络诊断 - ICMP 回显请求(ICMPv4-In)

参考

· 3分钟阅读

环境

  • Windows 10
  • Idea 2023.2.2
❯ java -version
openjdk version "11.0.19" 2023-04-18
OpenJDK Runtime Environment GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18, mixed mode, sharing)

❯ jbang version
WARNING: Windows Developer Mode is not enabled on your system, this is necessary
for JBang to be able to function correctly, see this page for more information:
https://www.jbang.dev/documentation/guide/latest/usage.html#usage-on-windows
0.110.1

安装JBang

scoop install jbang

HelloWorld

新建

❯ jbang init Hello.java
[jbang] File initialized. You can now run it with 'jbang Hello.java' or edit it using 'jbang edit --open=[editor] Hello.java' where [editor] is your editor or IDE, e.g. 'codium'. If your IDE supports JBang, you can edit the directory instead: 'jbang edit . 'Hello.java. See https://jbang.dev/ide

运行

❯ jbang .\Hello.java
Hello World

Cli

新建

❯ jbang init Cli.java
[jbang] File initialized. You can now run it with 'jbang Cli.java' or edit it using 'jbang edit --open=[editor] Cli.java' where [editor] is your editor or IDE, e.g. 'idea'. If your IDE supports JBang, you can edit the directory instead: 'jbang edit . 'Cli.java. See https://jbang.dev/ide

运行

❯ jbang .\Cli.java Tom
[jbang] Building jar for Cli.java...
Hello Tom

编辑

使用Idea

❯ jbang edit --open=idea .
[jbang] Assuming your editor have JBang support installed. See https://jbang.dev/ide
[jbang] If you prefer to open in a sandbox run with `jbang edit -b` instead.
[jbang] Starting 'idea'.If you want to make this the default, run 'jbang config set edit.open idea'

打开之后发现,并没有识别出依赖的包,红彤彤的一片:

可能是插件用得不对,一顿折腾无果之后,只能另寻他路曲线救国了;

把当前目录当作一个IDEA的项目来看,添加好相关的依赖就可以:

额,现在好像和一个普通的项目也没什么区别了,只是更方便运行发布而已。

proxy

参考这里配置,主要有三步

  1. 设置system proxy,配置两个环境变量就可以:
# 此处用的本地clash
$env:http_proxy=http://127.0.0.1:7890
$env:https_proxy=http://127.0.0.1:7890
  1. 设置JavaJAVA_TOOL_OPTIONS环境变量:
$env:JAVA_TOOL_OPTIONS="-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=7890 -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=7890"
  1. 设置maven
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<proxies>
<!-- For HTTP -->
<proxy>
<id>http-proxy</id>
<active>true</active>
<protocol>http</protocol>
<host>127.0.0.1</host>
<port>7890</port>
</proxy>
<!-- For HTTPS -->
<proxy>
<id>https-proxy</id>
<active>true</active>
<protocol>https</protocol>
<host>127.0.0.1</host>
<port>7890</port>
</proxy>
</proxies>
</settings>

如果只是运行命令行的话,应该只需要前面两步就可以了。

· 2分钟阅读

环境

❯ zig version
0.12.0-dev.286+b0d9bb0bb

dll

新建项目

mkdir dll
cd dll
zig init-lib

核心代码

main.zig
// To compile this, you could use this command " zig build-lib -lc -dynamic -fstrip -O ReleaseSmall src/main.zig"

const std = @import("std");
const win = std.os.windows;

// Types
const WINAPI = win.WINAPI;
const HINSTANCE = win.HINSTANCE;
const DWORD = win.DWORD;
const LPVOID = win.LPVOID;
const BOOL = win.BOOL;
const HWND = win.HWND;
const LPCSTR = win.LPCSTR;
const UINT = win.UINT;

// fdwReason parameter values
const DLL_PROCESS_ATTACH: DWORD = 1;
const DLL_THREAD_ATTACH: DWORD = 2;
const DLL_THREAD_DETACH: DWORD = 3;
const DLL_PROCESS_DETACH: DWORD = 0;

extern "user32" fn MessageBoxA(hWnd: ?HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) callconv(WINAPI) i32;

pub export fn _DllMainCRTStartup(hinstDLL: HINSTANCE, fdwReason: DWORD, lpReserved: LPVOID) BOOL {
_ = lpReserved;
_ = hinstDLL;
switch (fdwReason) {
DLL_PROCESS_ATTACH => {
_ = MessageBoxA(null, "Hello World!", "Zig", 0);
},
DLL_THREAD_ATTACH => {},
DLL_THREAD_DETACH => {},
DLL_PROCESS_DETACH => {},
else => {},
}
return 1;
}

如果Dll被加载,就会弹个框。

编译

zig build-lib -lc -dynamic -fstrip -O ReleaseSmall src/main.zig

ls main*

目录: E:\xk\Code\xkyss\xkyss.zig\playground\v0.12\dll

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2023/9/8 10:30 19968 main.dll
-a---- 2023/9/8 10:30 950 main.dll.obj
-a---- 2023/9/8 10:30 1146 main.lib

loader

新建项目

mkdir loader
cd loader
zig init-exe

核心代码

main.zig
const std = @import("std");

pub fn main() !void {
const dllpath =
// 绝对路径
// \\E:\xk\Code\xkyss\xkyss.zig\playground\v0.12\dll\main.dll
// 相对路径
\\../dll/main.dll
;
std.debug.print("Dll Path: {s}\n", .{dllpath});

// 加载dll
_ = try std.DynLib.open(dllpath);
}

运行

zig build run

· 3分钟阅读

突然挺怀念jupyter的,保持运行状态这个主意真是太天才了。

环境

VsCode

> code -v
1.81.1
6c3e3dba23e8fadc360aed75ce363ba185c49794
x64

Python

> python -V
Python 3.11.4

dotnet

> dotnet --version
8.0.100-preview.7.23376.3

java

❯ java -version
openjdk version "11.0.19" 2023-04-18
OpenJDK Runtime Environment GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18, mixed mode, sharing)

Vscode插件

python

  • 需要安装python
  • 需要安装插件: PythonPylance 新建一个笔记本.ipynb,内核选择Python

运行代码时还会提示要安装个什么包,同意就行

C#

  • 安装Polyglot Notebooks插件
  • 自动安装.NET Interactive引擎,甚至不需要python就可以直接运行代码
  • 内核使用.NET

Java

参考Jupyter for Java,用jbang脚本来安装。

  • 需要安装了java(11 or later),和python
  • 需要安装了jbang
scoop install jbang

运行脚本:

# ijava (>= java9)
jbang install-kernel@jupyter-java
# 或者 ganymede (>= java11)
jbang install-kernel@jupyter-java ganymede
# 或者 rapaio (>= java21)
jbang install-kernel@jupyter-java rapaio

如果报错java.net.unknownhostexception: raw.githubusercontent.com,说明下载不了脚本,参考这里的proxy小节

如果在VsCode中连接不上kernel,我这里IJava/j!正常,Ganymede/j!连不上,查看日志:

21:23:23.764 [error] Disposing kernel process due to an error Error: Unable to start Kernel 'java (Ganymede/j!)' due to a timeout waiting for the ports to get used. 
View Jupyter [log](command:jupyter.viewOutput) for further details.
> Kernel Id = .jbang-ganymede.d:\Scoop\apps\jbang\current\bin\jbang.cmd.\.d:\scoop\apps\jbang\current\bin\jbang.cmd#--java#11#--ea#-r-ea#-r--add-opens#-rjava.base/jdk.internal.misc=all-unnamed#-r--illegal-access=permit#dev.hcf.ganymede:ganymede:2.1.2.20230910#-f#{connection_file}
> at D:\Scoop\persist\vscode\data\extensions\ms-toolsai.jupyter-2023.11.1003402403-win32-x64\dist\extension.node.js:259:3149
21:23:23.764 [error] [jbang] Resolving dependencies...
[jbang] dev.hcf.ganymede:ganymede:2.1.2.20230910

看起来还是jbang需要proxy

找到对应的kernel.json:

❯ jupyter kernelspec list
0.00s - Debugger warning: It seems that frozen modules are being used, which may
0.00s - make the debugger miss breakpoints. Please pass -Xfrozen_modules=off
0.00s - to python to disable frozen modules.
0.00s - Note: Debugging will proceed. Set PYDEVD_DISABLE_FILE_VALIDATION=1 to disable this validation.
Available kernels:
jbang-ganymede C:\Users\dev88\AppData\Roaming\jupyter\kernels\jbang-ganymede
python3 C:\Users\dev88\AppData\Roaming\Python\share\jupyter\kernels\python3

env增加一个JAVA_TOOL_OPTIONS字段:

C:\Users\xkyii\AppData\Roaming\jupyter\kernels\jbang-ganymede\kernel.json
{
"argv" : [
"D:\\Scoop\\apps\\jbang\\current\\bin\\jbang.cmd",
"--java",
"11",
"--ea",
"-R-ea",
"-R--add-opens",
"-Rjava.base/jdk.internal.misc=ALL-UNNAMED",
"-R--illegal-access=permit",
"dev.hcf.ganymede:ganymede:2.1.2.20230910",
"-f",
"{connection_file}"
],
"display_name" : "java (Ganymede/j!)",
"language" : "java",
"interrupt_mode" : "message",
"env" : {
"JAVA_TOOL_OPTIONS": "-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=7890 -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=7890"
}
}

在第一次下载依赖时需要,后面运行时应该是不再需要了:

08:53:45.177 [info] Launching Raw Kernel java (Ganymede/j!) # D:\Scoop\apps\jbang\current\bin\jbang.cmd
08:53:45.184 [info] Process Execution: d:\Scoop\apps\jbang\current\bin\jbang.cmd --java 11 --ea -R-ea -R--add-opens -Rjava.base/jdk.internal.misc=ALL-UNNAMED -R--illegal-access=permit dev.hcf.ganymede:ganymede:2.1.2.20230910 -f ~\AppData\Roaming\jupyter\runtime\kernel-v2-10712x2DMxDTHJu57.json
> cwd: x:\xkyii.cn\code\jupyter
08:53:45.699 [warn] StdErr from Kernel Process [jbang] Resolving dependencies...
08:53:45.824 [warn] StdErr from Kernel Process [jbang] dev.hcf.ganymede:ganymede:2.1.2.20230910
08:54:13.026 [warn] StdErr from Kernel Process [jbang] Dependencies resolved
08:54:16.194 [info] Registering Kernel Completion Provider from kernel java (Ganymede/j!) for language java
08:54:16.198 [info] Kernel acknowledged execution of cell 0 @ 1704329656198
08:54:17.549 [info] End cell 0 execution after 1.351s, completed @ 1704329657549, started @ 1704329656198

参考

· 1分钟阅读

环境

❯ dotnet --version
8.0.100-preview.7.23376.3

账户

打包

  • 填一些项目的Package配置:
  • 打包
dotnet pack -c Release ./src/Kx.Toolx.Clicky

发布

手动apikey

  • 取得前面在nuget.org生成的Api Key
  • 发布:
❯ dotnet nuget push .\Kx.Toolx.Clicky.0.1.4.nupkg --api-key $ApiKey --source https://api.nuget.org/v3/index.json
正在将 Kx.Toolx.Clicky.0.1.4.nupkg 推送到 'https://www.nuget.org/api/v2/package'...
PUT https://www.nuget.org/api/v2/package/
warn : License missing. See how to include a license within the package: https://aka.ms/nuget/authoring-best-practices#licensing.,Readme missing. Go to https://aka.ms/nuget-include-readme learn How to include a readme file within the package.
Created https://www.nuget.org/api/v2/package/ 29282 毫秒
已推送包。

自动apikey

  • 给指定服务器保存ApiKey
nuget setapikey $ApiKey -source https://api.nuget.org/v3/index.json
  • 然后发布的时候可以省略ApiKey
nuget push .\Kx.Toolx.Clicky.0.1.4.nupkg -source https://api.nuget.org/v3/index.json

· 4分钟阅读

环境

  • dotnet
❯ dotnet --list-sdks
7.0.400 [C:\Program Files\dotnet\sdk]
8.0.100-preview.7.23376.3 [C:\Program Files\dotnet\sdk]
  • Microsoft Visual Studio Professional 2022 (64-bit) - Current Version 17.7.2

准备

目前.net8还是preview状态,需要在VS中开启,否则无法选用.net8版本的SDK。

Sample

Kx.Toolx.Clicky.csproj
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<!-- AOT -->
<PublishAot>true</PublishAot>
<IsAotCompatible>true</IsAotCompatible>
<InvariantGlobalization>true</InvariantGlobalization>

<!-- NuGet -->
<PackAsTool>true</PackAsTool>
<ToolCommandName>clicky</ToolCommandName>
<RootNamespace>Kx.Toolx.Clicky</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.7.23375.6" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.2.0-dev-00918" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.18-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>

选项模式的简易替代方案

直接使用选项模式会报警告:

原因是Configure方法已经标了RequiresDynamicCode特性:

        [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class
=> services.Configure<TOptions>(Options.Options.DefaultName, config);

跟进代码后发现核心代码如下:

        [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string? name, IConfiguration config, Action<BinderOptions>? configureBinder)
where TOptions : class
{
ThrowHelper.ThrowIfNull(services);
ThrowHelper.ThrowIfNull(config);

services.AddOptions();
services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
}

真正受影响的是NamedConfigureFromConfigurationOptions这个类:

        [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)]
public NamedConfigureFromConfigurationOptions(string? name, IConfiguration config, Action<BinderOptions>? configureBinder)
: base(name, options => config.Bind(options, configureBinder))
{
ThrowHelper.ThrowIfNull(config);
}

其核心作用是将config中的配置信息填充给被注入的options对象。

我们做如下替换:

  1. 初始化

await Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
// 服务注册
services.AddHostedService<HostedService>();

// Options

// 原配置
// services.Configure<ClickyConfig>(hostContext.Configuration.GetSection(ClickyConfig.KEY));

// 替换后
services.AddOptions();
var config = hostContext.Configuration.GetSection(ClickyConfig.KEY);
services.AddSingleton<IOptionsChangeTokenSource<ClickyConfig>>(new ConfigurationChangeTokenSource<ClickyConfig>(string.Empty, config));
// 使用 ClickyConfigureNamedOptions 替换 NamedConfigureFromConfigurationOptions
services.AddSingleton<IConfigureOptions<ClickyConfig>>(new ClickyConfigureNamedOptions(string.Empty, config));
})
.UseConsoleLifetime()
.Build()
.RunAsync()
;
  1. ClickyConfigureNamedOptions
using System.Drawing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace Kx.Toolx.Clicky;

internal class ClickyConfigureNamedOptions : ConfigureNamedOptions<ClickyConfig>
{
public ClickyConfigureNamedOptions(string? name, IConfiguration configuration)
: base(name, cc => Bind(cc, configuration))
{
}

private static void Bind(ClickyConfig config, IConfiguration configuration)
{
if (config == null)
{
return;
}

if (configuration == null)
{
return;
}

// 填充config的具体字段
config.Hwnd = configuration[nameof(config.Hwnd)] ?? string.Empty;
config.TimerPeriod = double.TryParse(configuration[nameof(config.TimerPeriod)], out var tp) ? tp : 5;
config.Points = configuration.GetSection(nameof(config.Points))
.GetChildren()
.Select(p => new Point((int.TryParse(p["X"], out var x) ? x : 0), (int.TryParse(p["Y"], out var y) ? y : 0)))
.ToList()
;
}
}

相当于手动解析了一下配置,目前项目配置不复杂还可以接受,复杂的话就够呛。

不过我相信官方不久就应该要解决这个问题了,毕竟选型模式挺好用且挺好用的。躺着等就好。

参考

· 7分钟阅读

环境

> java -version
openjdk version "11.0.19" 2023-04-18
OpenJDK Runtime Environment GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18, mixed mode, sharing)

> quarkus -version
3.2.2.Final

quarkus extensions

<dependency>  
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache</artifactId>
</dependency>

诉求

整体项目是基于reactive方式,查询数据库通过Hibernate Reactive,所以调用链都是通过Uni<?>来传递;

具体来说,在登录成功后,需要进行登录信息的入库,而这个入库操作成功与否并不是很重要,完全可以作为一个异步操作;

这只是一个小的应用场景,不排除有其他异步操作,像异步日志,异步通知之类。

相关类

SysLoginInfoService.java
package com.xkyii.spry.web.service;  

import com.xkyii.spry.web.entity.SysLoginInfo;
import com.xkyii.spry.web.entity.SysUser;
import com.xkyii.spry.web.repository.SysLoginInfoRepository;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import io.vertx.core.Vertx;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;

import java.util.Date;

@ApplicationScoped
public class SysLoginInfoService {

@Inject
Logger logger;

@Inject
SysLoginInfoRepository loginInfoRepository;

public Uni<SysLoginInfo> create(SysUser user) {

SysLoginInfo info = new SysLoginInfo();
info.setUserName(user.getUserName());
info.setIpaddr("127.0.0.1");
info.setLoginLocation("内网IP");
info.setBrowser("Chrome 11");
info.setOs("Unknown");
info.setStatus("1");
info.setMsg("成功");
info.setLoginTime(new Date());

logger.info("创建登录日志");

return loginInfoRepository.persist(info);
}
}

尝试

Java方式 ❌

尝试了几种:

import java.util.concurrent.Executor;  
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;

@ApplicationScoped
public class SysUserService {

@Inject
Executor executor;

@Inject
ExecutorService executorService;

@Inject
ScheduledExecutorService scheduledExecutorService;

@WithTransaction
public Uni<LoginOutput> login(LoginCommand input) {

String username = input.getUsername();
return userRepository.find("userName", username).firstResult()
// 省略其他步骤
// ...
// 保存登录日志 (想要个异步效果)
.onItem().invoke(u -> {

// java.lang.IllegalStateException: HR000069: Detected use of the reactive Session from a different Thread than the one which was used to open the reactive Session - this suggests an invalid integration; original thread [245]: 'vert.x-eventloop-thread-3' current Thread [2456]: 'executor-thread-1'
executor.execute(() -> {
loginInfoService.create(u)
.subscribe().with(x -> logger.info("创建登录日志 with executor"), Throwable::printStackTrace);
});

// java.lang.IllegalStateException: HR000069: Detected use of the reactive Session from a different Thread than the one which was used to open the reactive Session - this suggests an invalid integration; original thread [244]: 'vert.x-eventloop-thread-2' current Thread [2589]: 'executor-thread-1'
executorService.execute(() -> {
loginInfoService.create(u)
.subscribe().with(x -> logger.info("创建登录日志 with executorService"), Throwable::printStackTrace);
});

// java.lang.IllegalStateException: HR000068: This method should exclusively be invoked from a Vert.x EventLoop thread; currently running on thread 'executor-thread-1'
scheduledExecutorService.schedule(() -> {
loginInfoService.create(u)
.subscribe().with(x -> logger.info("创建登录日志 with scheduledExecutorService"), Throwable::printStackTrace);
}, 10, TimeUnit.MILLISECONDS);
}
// 生成token
.onItem().transform(u -> new LoginOutput(tokenService.generateToken(u)))
;
}
}

这几种方式,用来运行常规的异步任务应该是没问题的,但是与hibernate reactive配合并不成功;

Vertx方式 ❌

@ApplicationScoped  
public class SysUserService {

@WithTransaction
public Uni<LoginOutput> login(LoginCommand input) {

String username = input.getUsername();
return userRepository.find("userName", username).firstResult()
// 省略其他步骤
// ...
// 保存登录日志 (想要个异步效果)
.onItem().invoke(u -> {

// 1
// 运行在同一个线程,没报错,但是也没有保存成功。
Vertx.currentContext().runOnContext(e -> {
loginInfoService.create(u)
.subscribe().with(x -> logger.info("创建登录日志 with Vertx.currentContext()"), Throwable::printStackTrace);
});

// 2
// java.lang.IllegalStateException: HR000069: Detected use of the reactive Session from a different Thread than the one which was used to open the reactive Session - this suggests an invalid integration; original thread [245]: 'vert.x-eventloop-thread-3' current Thread [1056]: 'vert.x-worker-thread-2'
vertx.executeBlocking(e -> {
loginInfoService.create(u)
.emitOn(Infrastructure.getDefaultWorkerPool())
.subscribe().with(x -> logger.info("创建登录日志 with vertx.executeBlocking"), Throwable::printStackTrace);
});
}
// 生成token
.onItem().transform(u -> new LoginOutput(tokenService.generateToken(u)))
;
}
}
  1. 没有报错,但是数据并没有成功入库,应该是没有正确开启session的缘故,并且由于和当前的调用一定会运行在同一个线程,所以并不能达到效果
  2. 报错了

Smallrye方式 ❌

@ApplicationScoped  
public class SysUserService {

@WithTransaction
public Uni<LoginOutput> login(LoginCommand input) {

String username = input.getUsername();
return userRepository.find("userName", username).firstResult()
// 省略其他步骤
// ...
// 保存登录日志 (想要个异步效果)
.onItem().invoke(u -> {

// 1
// 保存成功,但是实际上没有达到异步效果,只是subscribe在另一个线程而已
loginInfoService.create(u)
.emitOn(Infrastructure.getDefaultWorkerPool())
.subscribe().with(x -> logger.info("创建登录日志 emitOn Infrastructure.getDefaultWorkerPool()"), Throwable::printStackTrace);

// 2
// java.lang.IllegalStateException: HR000069: Detected use of the reactive Session from a different Thread than the one which was used to open the reactive Session - this suggests an invalid integration; original thread [244]: 'vert.x-eventloop-thread-2' current Thread [1911]: 'executor-thread-1'
loginInfoService.create(u)
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
.subscribe().with(x -> logger.info("创建登录日志 runSubscriptionOn Infrastructure.getDefaultWorkerPool()"), Throwable::printStackTrace);
}
// 生成token
.onItem().transform(u -> new LoginOutput(tokenService.generateToken(u)))
;
}
}
  1. 不是异步效果,执行仍然在当前线程,只是订阅结果到worker线程了
  2. 执行在异步线程,但是报错了

EventLoop ✔

经过前面的尝试,以及对报错内容分析,其实已经大致找到了原因,简单来说就是hibernate reactive session不是线程安全的,所以必须运行在Vert.xEventLoop thread,不能是worker thread或者其他线程;

整理了一个方案,有点蹩脚,但是简单测试一下是可以用:

  
import io.vertx.core.Vertx;
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.impl.EventLoopContext;
import io.vertx.core.impl.VertxInternal;

import static io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.setContextSafe;

@ApplicationScoped
public class SysUserService {

@WithTransaction
public Uni<LoginOutput> login(LoginCommand input) {

String username = input.getUsername();
return userRepository.find("userName", username).firstResult()
// 省略其他步骤
// ...
// 保存登录日志 (想要个异步效果)
.onItem().invoke(u -> {

// 既然只能运行在EventLoop thread,而当前eventloop thread又达不到异步效果,就尝试在别的eventloop thread上运行
VertxInternal vxi = (VertxInternal) vertx;
Executor delegate = vertx.nettyEventLoopGroup();
EventLoopContext context = vxi.createEventLoopContext();
ContextInternal internal = (ContextInternal) VertxContext.getOrCreateDuplicatedContext(context);
setContextSafe(internal, true);
delegate.execute(() -> internal.dispatch(() -> {
loginInfoService.create(u)
.subscribe().with(x -> logger.info("创建登录日志 with getOrCreateDuplicatedContext"), Throwable::printStackTrace);
}));
}
// 生成token
.onItem().transform(u -> new LoginOutput(tokenService.generateToken(u)))
;
}
}

EventBus ✔

这是推荐方式了,业务分离,实现简单

  1. 建立一个事件消费端,这里直接把create方法调整了一下
package com.xkyii.spry.web.service;  

import com.xkyii.spry.web.entity.SysLoginInfo;
import com.xkyii.spry.web.entity.SysUser;
import com.xkyii.spry.web.repository.SysLoginInfoRepository;
import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.quarkus.vertx.ConsumeEvent;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;

import java.util.Date;

@ApplicationScoped
public class SysLoginInfoService {

@Inject
Logger logger;

@Inject
SysLoginInfoRepository loginInfoRepository;

@ConsumeEvent("SysLoginInfoService-create-with-SysUser")
@WithTransaction
public Uni<SysLoginInfo> create(SysUser user) {

SysLoginInfo info = new SysLoginInfo();
// info.setInfoId(180L);
info.setUserName(user.getUserName());
info.setIpaddr("127.0.0.1");
info.setLoginLocation("内网IP");
info.setBrowser("Chrome 11");
info.setOs("Unknown");
info.setStatus("1");
info.setMsg("成功");
info.setLoginTime(new Date());


logger.info("创建登录日志");

return loginInfoRepository.persist(info);
}
}
  1. 通过EventBus调用即可:
  
import io.vertx.core.Vertx;

@ApplicationScoped
public class SysUserService {
@Inject
Vertx vertx;

@WithTransaction
public Uni<LoginOutput> login(LoginCommand input) {

String username = input.getUsername();
return userRepository.find("userName", username).firstResult()
// 省略其他步骤
// ...
// 保存登录日志 (想要个异步效果)
.onItem().invoke(u -> {
vertx.eventBus().publish("SysLoginInfoService-create-with-SysUser", u);
}
// 生成token
.onItem().transform(u -> new LoginOutput(tokenService.generateToken(u)))
;
}
}

参考

· 2分钟阅读

环境

> java -version
openjdk version "11.0.19" 2023-04-18
OpenJDK Runtime Environment GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18, mixed mode, sharing)

> mvn --version
Apache Maven 3.9.3 (21122926829f1ead511c958d89bd2f672198ae9f)

> quarkus -version
3.2.2.Final

方式一

POM依赖

<dependency>  
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>

添加错误消息文件

resources/ValidationMessages.properties

# @Length
org.hibernate.validator.constraints.Length.message = 长度区间 {min} and {max}

使用

@Path("/demo")  
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class DemoResource {

@GET
@Operation(summary = "Get Demo")
public Response get(@Length(min=2, max=20) String name) {
return Response.ok(JsonObject.of("name", name)).build();
}
}

方式二

有时候需要自己规划配置文件位置和名字,而这个ValidationMessages.properties固定的。

quarkus里面,提供了一种方式,如果提供了jakarta.validation.MessageInterpolator,会被自动注入到ValidatorFactory中,像这样:

// KsMessageInterpolator 
// -> ResourceBundleMessageInterpolator
// -> AbstractMessageInterpolator
// -> MessageInterpolator
public class KsMessageInterpolator extends ResourceBundleMessageInterpolator {
public KsMessageInterpolator() {
super(new PlatformResourceBundleLocator("i18n/validation"));
}
}

这样,就可以用resources/i18n/validation.properties来代替ValidationMessages.properties

方式三

基于方式二,在自定义的extension里面也可以达到这个效果:

runtime

定义一个Recorder

@Recorder  
public class KsServerRecorder {

public Supplier<MessageInterpolator> messageInterpolatorSupplier() {
return () -> new ResourceBundleMessageInterpolator(
new PlatformResourceBundleLocator("i18n/validation"));
}
}

deployment

增加一个BuildStep

@BuildStep  
@Record(ExecutionTime.STATIC_INIT)
SyntheticBeanBuildItem registerMessageInterpolator(KsServerRecorder ksServerRecorder) {
return SyntheticBeanBuildItem.configure(MessageInterpolator.class)
.scope(Singleton.class)
.supplier(ksServerRecorder.messageInterpolatorSupplier())
.done();
}

这样,引用的项目也可以用resources/i18n/validation.properties来代替ValidationMessages.properties

参考

· 1分钟阅读

环境

> java -version
openjdk version "11.0.19" 2023-04-18
OpenJDK Runtime Environment GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.2 (build 11.0.19+7-jvmci-22.3-b18, mixed mode, sharing)

> mvn --version
Apache Maven 3.9.3 (21122926829f1ead511c958d89bd2f672198ae9f)

> quarkus -version
3.2.2.Final

POM依赖

<dependency>  
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt-build</artifactId>
</dependency>

密钥

生成密钥

# 建立涉密资源目录
cd src/main/resources
mkdir security
cd security

# 生成密钥
openssl genrsa -out rsaPrivateKey.pem 2048
openssl rsa -pubout -in rsaPrivateKey.pem -out publicKey.pem
openssl pkcs8 -topk8 -nocrypt -inform pem -in rsaPrivateKey.pem -outform pem -out privateKey.pem

配置

application.properties

# key  
smallrye.jwt.sign.key.location=security/privateKey.pem
smallrye.jwt.encrypt.key.location=security/publicKey.pem
quarkus.native.resources.includes=${smallrye.jwt.encrypt.key.location}
mp.jwt.verify.publickey.location=${smallrye.jwt.encrypt.key.location}

参考

· 3分钟阅读

环境

  • Windows 10
  • VsCode 1.80.1
  • zig 0.11.0-dev.4229+f1bd59876

安装

zig

scoop install zig-dev
zig version
# 0.11.0-dev.4229+f1bd59876

VsCode

scoop install vscode
code -v
# 1.80.1
# 74f6148eb9ea00507ec113ec51c489d6ffb4b771
# x64

VsCode插件

zls

理论上应该使用跟zig版本相符的zls,这里偷懒就直接使用Zig Language插件提示安装的版本了。

调试

建立测试项目

mkdir hello
cd hello
zig init-exe
code .

调试

VsCode中按下Ctrl+Shift+D打开调试页面,随意新建一个配置,会新建一个文件.vscode/launch.json,替换文件内容为:

{
  "version": "0.2.0",
  "configurations": [
      {
          "name": "Zig Launch (Windows)",
          "type": "cppvsdbg",
          "request": "launch",
          "program": "${workspaceFolder}/zig-out/bin/hello.exe",
          "args": [],
          "stopAtEntry": false,
          "cwd": "${workspaceFolder}",
          "environment": []
      }
  ]
}

打开main.zig,如果不能下断点,就勾选上VsCode的配置项Debug: Allow Breakpoints Everywhere

手动编译一下项目:

zig build

随意下个断点,F5启动,应该可以了。

前置编译

每次调试前都要手动编译是挺呆的,作如下调整:

Ctrl+Shift+P -> Task: Configure Task -> Create task.json fromtemplate -> Other,然后修改.vscode/tasks.json的内容为:

{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "zig build",
"group": "build",
"problemMatcher": [
"$gcc"
]
}
]
}

再修改.vscode/launch.json为:

{
"version": "0.2.0",
"configurations": [
{
"name": "Zig Launch (Windows)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/zig-out/bin/hello.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"preLaunchTask": "build" // 前置编译
}
]
}

再次F5启动,就会先行编译了。

参考