从Golang的开源项目中学习不同的功能实现

缘起 最近看到有些go开源项目中的代码,看到其中的功能,故整理备用。 密码 阈值解密(Threshold Decryption) 来源 取自公众号文章 package main import ( "crypto/rand" "fmt" "github.com/hashicorp/vault/shamir" ) func main() { // 1. 生成一个随机密钥(如AES-256密钥) secretKey := make([]byte, 32) _, err := rand.Read(secretKey) if err != nil { panic(err) } fmt.Printf("原始密钥: %x\n", secretKey) // 2. 分片为3份,至少需要2份恢复(阈值k=2,总分片n=3) shards, err := shamir.Split(secretKey, 3, 2) if err != nil { panic(err) } // 3. 模拟分片存储(实际中分发给不同机构) shard1, shard2, shard3 := shards[0], shards[1], shards[2] fmt.Printf("分片1: %x\n分片2: %x\n分片3: %x\n", shard1, shard2, shard3) // 4. 恢复密钥(任意2个分片) recoveredKey, err := shamir.Combine([][]byte{shard1, shard2}) // 使用分片1和2 if err != nil { panic(err) } fmt.Printf("恢复的密钥: %x\n", recoveredKey) } 数据结构 优先级队列 项目 https://github.com/tigrisdata/tigris ...

2022-11-25 · 62 分钟 · czyt

C#扫码枪输入Hook

摘自网络,测试可用 public class ScanerHook { public delegate void ScanerDelegate(ScanerCodes codes); public event ScanerDelegate ScanerEvent; //private const int WM_KEYDOWN = 0x100;//KEYDOWN //private const int WM_KEYUP = 0x101;//KEYUP //private const int WM_SYSKEYDOWN = 0x104;//SYSKEYDOWN //private const int WM_SYSKEYUP = 0x105;//SYSKEYUP //private static int HookProc(int nCode, Int32 wParam, IntPtr lParam); private int hKeyboardHook = 0;//声明键盘钩子处理的初始值 private ScanerCodes codes = new ScanerCodes();//13为键盘钩子 //定义成静态,这样不会抛出回收异常 private static HookProc hookproc; delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] //设置钩子 private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] //卸载钩子 private static extern bool UnhookWindowsHookEx(int idHook); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] //继续下个钩子 private static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); [DllImport("user32", EntryPoint = "GetKeyNameText")] private static extern int GetKeyNameText(int IParam, StringBuilder lpBuffer, int nSize); [DllImport("user32", EntryPoint = "GetKeyboardState")] //获取按键的状态 private static extern int GetKeyboardState(byte[] pbKeyState); [DllImport("user32", EntryPoint = "ToAscii")] //ToAscii职能的转换指定的虚拟键码和键盘状态的相应字符或字符 private static extern bool ToAscii(int VirtualKey, int ScanCode, byte[] lpKeySate, ref uint lpChar, int uFlags); //int VirtualKey //[in] 指定虚拟关键代码进行翻译。 //int uScanCode, // [in] 指定的硬件扫描码的关键须翻译成英文。高阶位的这个值设定的关键,如果是(不压) //byte[] lpbKeyState, // [in] 指针,以256字节数组,包含当前键盘的状态。每个元素(字节)的数组包含状态的一个关键。如果高阶位的字节是一套,关键是下跌(按下)。在低比特,如/果设置表明,关键是对切换。在此功能,只有肘位的CAPS LOCK键是相关的。在切换状态的NUM个锁和滚动锁定键被忽略。 //byte[] lpwTransKey, // [out] 指针的缓冲区收到翻译字符或字符。 //uint fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise. [DllImport("kernel32.dll")] //使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效 public static extern IntPtr GetModuleHandle(string name); public ScanerHook() { } public bool Start() { if (hKeyboardHook == 0) { hookproc = new HookProc(KeyboardHookProc); //GetModuleHandle 函数 替代 Marshal.GetHINSTANCE //防止在 framework4.0中 注册钩子不成功 IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName); //WH_KEYBOARD_LL=13 //全局钩子 WH_KEYBOARD_LL // hKeyboardHook = SetWindowsHookEx(13, hookproc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0); hKeyboardHook = SetWindowsHookEx(13, hookproc, modulePtr, 0); } return (hKeyboardHook != 0); } public bool Stop() { if (hKeyboardHook != 0) { bool retKeyboard = UnhookWindowsHookEx(hKeyboardHook); hKeyboardHook = 0; return retKeyboard; } return true; } private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) { EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam, typeof(EventMsg)); codes.Add(msg); if (ScanerEvent != null && msg.message == 13 && msg.paramH > 0 && !string.IsNullOrEmpty(codes.Result)) { ScanerEvent(codes); } return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); } public class ScanerCodes { private int ts = 100; // 指定输入间隔为300毫秒以内时为连续输入 private List<List<EventMsg>> _keys = new List<List<EventMsg>>(); private List<int> _keydown = new List<int>(); // 保存组合键状态 private List<string> _result = new List<string>(); // 返回结果集 private DateTime _last = DateTime.Now; private byte[] _state = new byte[256]; private string _key = string.Empty; private string _cur = string.Empty; public EventMsg Event { get { if (_keys.Count == 0) { return new EventMsg(); } else { return _keys[_keys.Count - 1][_keys[_keys.Count - 1].Count - 1]; } } } public List<int> KeyDowns { get { return _keydown; } } public DateTime LastInput { get { return _last; } } public byte[] KeyboardState { get { return _state; } } public int KeyDownCount { get { return _keydown.Count; } } public string Result { get { if (_result.Count > 0) { return _result[_result.Count - 1].Trim(); } else { return null; } } } public string CurrentKey { get { return _key; } } public string CurrentChar { get { return _cur; } } public bool isShift { get { return _keydown.Contains(160); } } public void Add(EventMsg msg) { #region 记录按键信息 // 首次按下按键 if (_keys.Count == 0) { _keys.Add(new List<EventMsg>()); _keys[0].Add(msg); _result.Add(string.Empty); } // 未释放其他按键时按下按键 else if (_keydown.Count > 0) { _keys[_keys.Count - 1].Add(msg); } // 单位时间内按下按键 else if ((DateTime.Now - _last).TotalMilliseconds < ts) { _keys[_keys.Count - 1].Add(msg); } // 从新记录输入内容 else { _keys.Add(new List<EventMsg>()); _keys[_keys.Count - 1].Add(msg); _result.Add(string.Empty); } #endregion _last = DateTime.Now; #region 获取键盘状态 // 记录正在按下的按键 if (msg.paramH == 0 && !_keydown.Contains(msg.message)) { _keydown.Add(msg.message); } // 清除已松开的按键 if (msg.paramH > 0 && _keydown.Contains(msg.message)) { _keydown.Remove(msg.message); } #endregion #region 计算按键信息 int v = msg.message & 0xff; int c = msg.paramL & 0xff; StringBuilder strKeyName = new StringBuilder(500); if (GetKeyNameText(c * 65536, strKeyName, 255) > 0) { _key = strKeyName.ToString().Trim(new char[] { ' ', '\0' }); GetKeyboardState(_state); if (_key.Length == 1 && msg.paramH == 0)// && msg.paramH == 0 { // 根据键盘状态和shift缓存判断输出字符 _cur = ShiftChar(_key, isShift, _state).ToString(); _result[_result.Count - 1] += _cur; } else if (_key.Length == 5 && msg.paramH == 0 && msg.paramL == 78 && msg.message == 107)// && msg.paramH == 0 { // 根据键盘状态和shift缓存判断输出字符 _cur = Convert.ToChar('+').ToString(); _result[_result.Count - 1] += _cur; } else { _cur = string.Empty; } } #endregion } private char ShiftChar(string k, bool isShiftDown, byte[] state) { bool capslock = state[0x14] == 1; bool numlock = state[0x90] == 1; bool scrolllock = state[0x91] == 1; bool shiftdown = state[0xa0] == 1; char chr = (capslock ? k.ToUpper() : k.ToLower()).ToCharArray()[0]; if (isShiftDown) { if (chr >= 'a' && chr <= 'z') { chr = (char)((int)chr - 32); } else if (chr >= 'A' && chr <= 'Z') { if (chr == 'Z') { string s = ""; } chr = (char)((int)chr + 32); } else { string s = "`1234567890-=[];',./"; string u = "~!@#$%^&*()_+{}:\"<>?"; if (s.IndexOf(chr) >= 0) { return (u.ToCharArray())[s.IndexOf(chr)]; } } } return chr; } } public struct EventMsg { public int message; public int paramL; public int paramH; public int Time; public int hwnd; } } 某些情况下,特别是使用类似像微软拼音之类的中文输入法时,扫码枪会出现字符被输入法截获,显示不是很友好,所以需要使用InputMethod.SetPreferredImeState(target, InputMethodState.Off)来关闭输入法,只能输入英文,待业务调用完成后再切换回来。target可以是业务的主界面窗口,也可以是foucus的输入框。

2022-11-01 · 4 分钟 · czyt

使用protoc-gen-star编写protoc插件

预备知识 需要安装的软件 protoc golang go 软件包 github.com/lyft/protoc-gen-star 插件调用步骤 protoc,PB编译器,使用一组标志(记录在protoc -h下)进行配置,并将一组文件作为参数交给它。在这种情况下,I标志可以被多次指定,是它在proto文件中用于导入依赖关系的查找路径。默认情况下,官方描述符protos已经被包含在内。 myplugin_out 告诉 protoc 使用 protoc-gen-myplugin protoc-plugin。这些插件会从系统的 PATH 环境变量中自动解析,或者可以用另一个标志明确指定。官方的protoc-plugins (例如,protoc-gen-python) 已经在protoc注册了。该标志的值是特定于特定插件的,但 :…/generated 后缀除外。这个后缀表示protoc将把该包生成的文件放在哪个根目录下(相对于当前工作目录)。然而,这个生成的输出目录不会传播给 protoc-gen-myplugin,所以它需要在标志的左边重复。PG* 通过一个 output_path 参数支持这一点。 protoc 解析传入的 proto 文件,确保它们在语法上是正确的,并加载任何导入的依赖项。它将这些文件和依赖关系转换成描述符 (它们本身就是 PB 消息),并创建一个 CodeGeneratorRequest (又是一个 PB)。protoc 将这个请求序列化,然后执行每个配置的 protoc-plugin,通过 stdin 发送有效载荷。 protoc-gen-myplugin 启动,接收请求的有效载荷,并将其解密。一个基于 PG* 的 protoc-plugin 有两个阶段。首先,PG* 对从 protoc 收到的 CodeGeneratorRequest 进行解密,并为每个文件和其包含的所有实体创建一个完全连接的抽象语法树 (AST)。为这个插件指定的任何参数也会被解析,以便以后使用。 当这一步完成后,PG*就会执行任何注册的模块,把构建的AST交给它。模块可以被写成生成人工制品(例如,文件),或者只是对所提供的图进行某种形式的验证而没有任何其他副作用。模块在针对PB的操作方面提供了极大的灵活性。 一旦所有的模块都被运行,PG*会将任何自定义的工件写入文件系统,或者将生成器特定的工件序列化为CodeGeneratorResponse并将数据发送到其stdout。这整个流程看起来像这样。 foo.proto → protoc → CodeGeneratorRequest → protoc-gen-myplugin → CodeGeneratorResponse → protoc → foo.pb.go 假设插件名称为diy,则需要编译程序为protoc-gen-diy,并将程序加入系统Path变量,通过下面的命令调用插件。 protoc -I . --diy_out=./gen/ xxxx.proto 使用protoc-gen-star包 模块 Modules PG*模块将被交付一个完整的AST,用于生成目标文件以及所有依赖项。然后,模块可以将文件添加到协议CodeGeneratorResponse或将文件作为组件直接写入磁盘。 ...

2022-10-29 · 3 分钟 · czyt

C# dllimport 备忘录

dllImport的入口点问题 通过Dependencies 查询Dll对应方法的EntryPoint 然后在dllimport的attribute中显式申明EntryPoint [DllImport("demo.dll", SetLastError = true,EntryPoint ="??0DemoManager@EcgParser@Gfeit@@AEAA@XZ")] public static extern IntPtr DemoManager(); 导入类方法的问题 最好的方式还是使用C++构造wrapper,然后通过windowsApi的方式调用Pinvoke 参考链接 swig nuget swig C++/C# interoperability Working with C++ Interface Classes from C# Call function in unmanaged DLL from C# and pass custom data types [Marshal] SWIG and C# Example for SWIG to wrap C++ library in .Net 6

2022-10-09 · 1 分钟 · czyt

golang http Reverse Proxy使用备忘

创建 一般使用 使用 httputil.NewSingleHostReverseProxy 即可 返回Response 当我们想实现获取通过ReverseProxy的请求结果时,可以使用自定义的 responsewriter 来实现。参考定义 func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { .... } type ResponseWriter interface { // Header returns the header map that will be sent by // WriteHeader. The Header map also is the mechanism with which // Handlers can set HTTP trailers. // // Changing the header map after a call to WriteHeader (or // Write) has no effect unless the modified headers are // trailers. // // There are two ways to set Trailers. The preferred way is to // predeclare in the headers which trailers you will later // send by setting the "Trailer" header to the names of the // trailer keys which will come later. In this case, those // keys of the Header map are treated as if they were // trailers. See the example. The second way, for trailer // keys not known to the Handler until after the first Write, // is to prefix the Header map keys with the TrailerPrefix // constant value. See TrailerPrefix. // // To suppress automatic response headers (such as "Date"), set // their value to nil. Header() Header // Write writes the data to the connection as part of an HTTP reply. // // If WriteHeader has not yet been called, Write calls // WriteHeader(http.StatusOK) before writing the data. If the Header // does not contain a Content-Type line, Write adds a Content-Type set // to the result of passing the initial 512 bytes of written data to // DetectContentType. Additionally, if the total size of all written // data is under a few KB and there are no Flush calls, the // Content-Length header is added automatically. // // Depending on the HTTP protocol version and the client, calling // Write or WriteHeader may prevent future reads on the // Request.Body. For HTTP/1.x requests, handlers should read any // needed request body data before writing the response. Once the // headers have been flushed (due to either an explicit Flusher.Flush // call or writing enough data to trigger a flush), the request body // may be unavailable. For HTTP/2 requests, the Go HTTP server permits // handlers to continue to read the request body while concurrently // writing the response. However, such behavior may not be supported // by all HTTP/2 clients. Handlers should read before writing if // possible to maximize compatibility. Write([]byte) (int, error) // WriteHeader sends an HTTP response header with the provided // status code. // // If WriteHeader is not called explicitly, the first call to Write // will trigger an implicit WriteHeader(http.StatusOK). // Thus explicit calls to WriteHeader are mainly used to // send error codes. // // The provided code must be a valid HTTP 1xx-5xx status code. // Only one header may be written. Go does not currently // support sending user-defined 1xx informational headers, // with the exception of 100-continue response header that the // Server sends automatically when the Request.Body is read. WriteHeader(statusCode int) } 例子 package main import ( "fmt" "log" "net/http" "net/http/httputil" "net/url" "github.com/gin-gonic/gin" ) type customWriter struct { http.ResponseWriter } func NewCustomWriter(w http.ResponseWriter) *customWriter { return &customWriter{w} } func (c *customWriter) Header() http.Header { return c.ResponseWriter.Header() } func (c *customWriter) Write(data []byte) (int, error) { fmt.Println(string(data)) //get response here return c.ResponseWriter.Write(data) } func (c *customWriter) WriteHeader(i int) { c.ResponseWriter.WriteHeader(i) } func main() { router := gin.Default() router.GET("/", func(ctx *gin.Context) { targetURL, err := url.Parse("http://192.168.0.70:8090") if err != nil { log.Println(err) } proxy := httputil.NewSingleHostReverseProxy(targetURL) proxy.ServeHTTP(NewCustomWriter(ctx.Writer), ctx.Request) }) router.Run(":8899") } 封装个函数 ...

2022-09-30 · 4 分钟 · czyt

tailscale的泛型SingleFlight

源地址 tailscale仓库 完整代码如下 // Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package singleflight provides a duplicate function call suppression // mechanism. // // This is a Tailscale fork of Go's singleflight package which has had several // homes in the past: // // - https://github.com/golang/go/commit/61d3b2db6292581fc07a3767ec23ec94ad6100d1 // - https://github.com/golang/groupcache/tree/master/singleflight // - https://pkg.go.dev/golang.org/x/sync/singleflight // // This fork adds generics. package singleflight // import "tailscale.com/util/singleflight" import ( "bytes" "errors" "fmt" "runtime" "runtime/debug" "sync" ) // errGoexit indicates the runtime.Goexit was called in // the user given function. var errGoexit = errors.New("runtime.Goexit was called") // A panicError is an arbitrary value recovered from a panic // with the stack trace during the execution of given function. type panicError struct { value interface{} stack []byte } // Error implements error interface. func (p *panicError) Error() string { return fmt.Sprintf("%v\n\n%s", p.value, p.stack) } func newPanicError(v interface{}) error { stack := debug.Stack() // The first line of the stack trace is of the form "goroutine N [status]:" // but by the time the panic reaches Do the goroutine may no longer exist // and its status will have changed. Trim out the misleading line. if line := bytes.IndexByte(stack[:], '\n'); line >= 0 { stack = stack[line+1:] } return &panicError{value: v, stack: stack} } // call is an in-flight or completed singleflight.Do call type call[V any] struct { wg sync.WaitGroup // These fields are written once before the WaitGroup is done // and are only read after the WaitGroup is done. val V err error // forgotten indicates whether Forget was called with this call's key // while the call was still in flight. forgotten bool // These fields are read and written with the singleflight // mutex held before the WaitGroup is done, and are read but // not written after the WaitGroup is done. dups int chans []chan<- Result[V] } // Group represents a class of work and forms a namespace in // which units of work can be executed with duplicate suppression. type Group[K comparable, V any] struct { mu sync.Mutex // protects m m map[K]*call[V] // lazily initialized } // Result holds the results of Do, so they can be passed // on a channel. type Result[V any] struct { Val V Err error Shared bool } // Do executes and returns the results of the given function, making // sure that only one execution is in-flight for a given key at a // time. If a duplicate comes in, the duplicate caller waits for the // original to complete and receives the same results. // The return value shared indicates whether v was given to multiple callers. func (g *Group[K, V]) Do(key K, fn func() (V, error)) (v V, err error, shared bool) { g.mu.Lock() if g.m == nil { g.m = make(map[K]*call[V]) } if c, ok := g.m[key]; ok { c.dups++ g.mu.Unlock() c.wg.Wait() if e, ok := c.err.(*panicError); ok { panic(e) } else if c.err == errGoexit { runtime.Goexit() } return c.val, c.err, true } c := new(call[V]) c.wg.Add(1) g.m[key] = c g.mu.Unlock() g.doCall(c, key, fn) return c.val, c.err, c.dups > 0 } // DoChan is like Do but returns a channel that will receive the // results when they are ready. // // The returned channel will not be closed. func (g *Group[K, V]) DoChan(key K, fn func() (V, error)) <-chan Result[V] { ch := make(chan Result[V], 1) g.mu.Lock() if g.m == nil { g.m = make(map[K]*call[V]) } if c, ok := g.m[key]; ok { c.dups++ c.chans = append(c.chans, ch) g.mu.Unlock() return ch } c := &call[V]{chans: []chan<- Result[V]{ch}} c.wg.Add(1) g.m[key] = c g.mu.Unlock() go g.doCall(c, key, fn) return ch } // doCall handles the single call for a key. func (g *Group[K, V]) doCall(c *call[V], key K, fn func() (V, error)) { normalReturn := false recovered := false // use double-defer to distinguish panic from runtime.Goexit, // more details see https://golang.org/cl/134395 defer func() { // the given function invoked runtime.Goexit if !normalReturn && !recovered { c.err = errGoexit } c.wg.Done() g.mu.Lock() defer g.mu.Unlock() if !c.forgotten { delete(g.m, key) } if e, ok := c.err.(*panicError); ok { // In order to prevent the waiting channels from being blocked forever, // needs to ensure that this panic cannot be recovered. if len(c.chans) > 0 { go panic(e) select {} // Keep this goroutine around so that it will appear in the crash dump. } else { panic(e) } } else if c.err == errGoexit { // Already in the process of goexit, no need to call again } else { // Normal return for _, ch := range c.chans { ch <- Result[V]{c.val, c.err, c.dups > 0} } } }() func() { defer func() { if !normalReturn { // Ideally, we would wait to take a stack trace until we've determined // whether this is a panic or a runtime.Goexit. // // Unfortunately, the only way we can distinguish the two is to see // whether the recover stopped the goroutine from terminating, and by // the time we know that, the part of the stack trace relevant to the // panic has been discarded. if r := recover(); r != nil { c.err = newPanicError(r) } } }() c.val, c.err = fn() normalReturn = true }() if !normalReturn { recovered = true } } // Forget tells the singleflight to forget about a key. Future calls // to Do for this key will call the function rather than waiting for // an earlier call to complete. func (g *Group[K, V]) Forget(key K) { g.mu.Lock() if c, ok := g.m[key]; ok { c.forgotten = true } delete(g.m, key) g.mu.Unlock() }

2022-09-23 · 5 分钟 · czyt

Rasp3b 安装Postgresql

安装 系统信息 使用命令安装 yay -S postgresql 初始化及配置 启用数据库服务 sudo systemctl enable --now postgresql 开启数据库服务 sudo systemctl start postgresql 初始化数据 su - postgres -c "initdb --locale en_US.UTF-8 -D '/var/lib/postgres/data'" 查询配置文件路径 su - postgres [postgres@homeserver ~]$ ls data [postgres@homeserver ~]$ psql psql (14.5) 输入 "help" 来获取帮助信息. postgres=# SHOW config_file; config_file ---------------------------------------- /var/lib/postgres/data/postgresql.conf (1 行记录) 修改监听 修改配置/var/lib/postgres/data/postgresql.conf 文件中的listen_addresses = '*'监听所有地址,重启服务sudo systemctl restart postgresql生效。 允许远程访问 修改配置文件同级目录下的pg_hba.conf,添加一行 # TYPE DATABASE USER CIDR-ADDRESS METHOD host all all 0.0.0.0/0 md5 默认pg只允许本机通过密码认证登录,修改为上面内容后即可以对任意IP访问进行密码验证。 ...

2022-09-17 · 1 分钟 · czyt

.NET6 从JSON获取配置

环境准备 nuget包 Microsoft.Extensions.Configuration Microsoft.Extensions.Configuration.Binder Microsoft.Extensions.Configuration.Json (当需要从Json文件添加记录时,安装此nuget包) Microsoft.Extensions.Configuration.EnvironmentVariables (当需要从环境变量添加记录时,安装此nuget包) C#开发环境 visual studio 2019 + visual Code 示例代码 // See https://aka.ms/new-console-template for more information using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; Console.WriteLine("Hello, World!"); ConfigurationBuilder configurationBuilder = new ConfigurationBuilder(); IConfiguration c = configurationBuilder.AddJsonFile("appsettings.json").AddEnvironmentVariables().Build(); var k = c.GetRequiredSection("Settings").Get<Settings>().KeyOne; var n = 1; public class NestedSettings { public string Message { get; set; } = null!; } public class Settings { public int KeyOne { get; set; } public bool KeyTwo { get; set; } public NestedSettings KeyThree { get; set; } = null!; } JSON 文件 (appsettings.json.ps:配置文件属性建议按下面属性设置) { "Settings": { "KeyOne": 1, "KeyTwo": true, "KeyThree": { "Message": "Oh, that's nice..." } } } 常见问题 如何自定义配置项目的Key 使用 [ConfigurationKeyName]属性进行设置,如下面的配置: public class ApiConfig { [ConfigurationKeyName("endpoint")] public string Endpoint { get; set; } } 配置热重载 配置热重载可以使用ChangeToken.OnChange来实现,参考微软官方文档 下面是一个例子: ...

2022-09-06 · 1 分钟 · czyt

浅谈windows默认Shell的替换

Windows XP时代 Xp时代提供的是通过注册表来自定义shell 设置所有用户的shell 注册表键HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell 设置当前用户的shell注册表键 HKEY_Current_User\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell 注册表键值类型 REG_SZ 值修改为你要自定义为shell的程序的完整路径。 在windows10下使用该技巧可能会出现黑屏的现象,参考stackoverflow的回答 Simply replacing the “explorer.exe” (HKLM\SOFTWARE\Microsoft\Window NT\Winlogon\Shell) with a custom app location provided a black screen. A much simpler way, and it works great, was to create a BATCH script to call the custom app through elevated powershell… powershell -nologo -noprofile -executionpolicy bypass -command "start-process -verb 'runas' -filepath <full path of custom app executable>" By replacing “explorer.exe” with this batch script I was able to successfully create a kiosk style lockdown under Windows 10 PRO with a non-UWP app. ...

2022-08-31 · 2 分钟 · czyt

Golang 默认的CGO参数编译导致的GLIBC错误

问题描述 使用go正常编译了Linux下的程序,放到服务器上报错 ./app: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by ./app) 解决 Google了下,发现相同的Issue ,于是通过go env检查本机golang运行环境,发现CGO默认启用而且程序也不涉及CGO相关的东西,于是设置CGO参数为关闭。然后编译程序 CGO_ENABLED="0" go build -v 重新上传,运行OK.

2022-08-30 · 1 分钟 · czyt