【React】使用 Nginx 部署 React + Router 项目

【React】使用 Nginx 部署 React + Router 项目 项目编译 & 打包 npm run build 编译好的静态文件会在 build 目录下。 React 项目 Nginx 配置 server { server_name web.vksir.zone; location / { root /var/www/ns-web; index index.html; } } React + Router 项目 Nginx 配置 如果使用了 react-router,则 Nginx 配置会多出一项: server { server_name web.vksir.zone; location / { root /var/www/ns-web; index index.html; try_files $uri /index.html; } } 配置代理 如果开发时使用了 http-proxy-middleware,打包成静态文件后,这个包将会失去作用,需要使用 Nginx 代替其提供代理功能。 如果 src/setupProxy.js 文件内容如下: const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = function(app) { app.use('/ns_server', createProxyMiddleware({ target: 'http://server.vksir.zone', changeOrigin: true, pathRewrite: { '^/ns_server': '' } })); }; 那么 Nginx 配置应当如下: ...

六月 5, 2022  |  300 字  |  总阅读

【Go】利用 reflect 实现结构体设置默认值

【Go】利用 reflect 实现结构体设置默认值 写 API 时经常会需要结构体中某个参数拥有默认值。但如 Gin 只有 ShouldBindQuery 这种 form 类型支持设置默认值,常用的 ShouldBindJSON 这种 json 类型却不支持,很奇怪。 Gin 中 bind 结构体设置默认值 package main import ( "fmt" "github.com/gin-gonic/gin" ) type FormData struct { Name string `form:"name,default=vksir"` Age int `form:"age,default=18"` } type JsonData struct { Name string `json:"name,default=vksir"` Age int `json:"age,default=18"` } func main() { e := gin.Default() e.GET("/", func(c *gin.Context) { var fd FormData c.ShouldBindQuery(&fd) var jd JsonData c.ShouldBindJSON(&jd) fmt.Printf("FormData: %+v\n", fd) fmt.Printf("JsonData:%+v\n", jd) }) e.Run() } 请求会打印如下结果: FormData: {Name:vksir Age:18} JsonData:{Name: Age:0} 也就是说 JsonData 的默认值没有生效,如果看源码也可以发现 ShouldBindJSON 是没有设置默认值的动作的。 简单实现结构体设置默认值 编辑 structutil/struct.go: package structutil import ( "reflect" "strconv" ) func SetDefault(v any) error { typeOf := reflect.TypeOf(v).Elem() valueOf := reflect.ValueOf(v).Elem() return subSetDefault(typeOf, valueOf) } func subSetDefault(typeOf reflect.Type, valueOf reflect.Value) error { for i := 0; i < typeOf.NumField(); i++ { tField := typeOf.Field(i) vField := valueOf.Field(i) switch tField.Type.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: defaultVal, ok := tField.Tag.Lookup("default") if !ok { continue } defaultValInt, err := strconv.ParseInt(defaultVal, 10, 64) if err != nil { return err } vField.SetInt(defaultValInt) case reflect.String: defaultVal, ok := tField.Tag.Lookup("default") if !ok { continue } vField.SetString(defaultVal) case reflect.Struct: err := subSetDefault(tField.Type, vField) if err != nil { return err } } } return nil } 简单使用: ...

六月 4, 2022  |  263 字  |  总阅读

【Go】实现分级日志

话不多说直接上代码: package log import ( "errors" "fmt" "log" "os" ) const ( LevelDebug = (1 + iota) * 10 LevelInfo LevelWaring LevelError LevelCritical ) var debugLogger *log.Logger var debugFileLogger *log.Logger var infoLogger *log.Logger var infoFileLogger *log.Logger var warningLogger *log.Logger var warningFileLogger *log.Logger var errorLogger *log.Logger var errorFileLogger *log.Logger var criticalLogger *log.Logger var criticalFileLogger *log.Logger var flag = log.Ldate | log.Ltime | log.Lshortfile | log.Lmsgprefix var logLevel = LevelInfo func SetLevel(level int) error { if level != LevelDebug && level != LevelInfo && level != LevelWaring && level != LevelError && level != LevelCritical { return errors.New(fmt.Sprintf("invalid level: %d", level)) } else { logLevel = level return nil } } func AddFileOutput(filePath string) error { logWriter, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0655) if err != nil { return err } else { debugFileLogger = log.New(logWriter, "[DEBUG] ", flag) infoFileLogger = log.New(logWriter, "[INFO] ", flag) warningFileLogger = log.New(logWriter, "[WARNING] ", flag) errorFileLogger = log.New(logWriter, "[ERROR] ", flag) criticalFileLogger = log.New(logWriter, "[CRITICAL] ", flag) return nil } } func Debug(format string, v ...any) { logWithLevel(debugLogger, LevelDebug, format, v) logWithLevel(debugFileLogger, LevelDebug, format, v) } func Info(format string, v ...any) { logWithLevel(infoLogger, LevelDebug, format, v) logWithLevel(infoFileLogger, LevelDebug, format, v) } func Waring(format string, v ...any) { logWithLevel(warningLogger, LevelDebug, format, v) logWithLevel(warningFileLogger, LevelDebug, format, v) } func Error(format string, v ...any) { logWithLevel(errorLogger, LevelDebug, format, v) logWithLevel(errorFileLogger, LevelDebug, format, v) } func Critical(format string, v ...any) { logWithLevel(criticalLogger, LevelDebug, format, v) logWithLevel(criticalFileLogger, LevelDebug, format, v) } func init() { debugLogger = log.New(os.Stderr, "[DEBUG] ", flag) infoLogger = log.New(os.Stderr, "[INFO] ", flag) warningLogger = log.New(os.Stderr, "[WARNING] ", flag) errorLogger = log.New(os.Stderr, "[ERROR] ", flag) criticalLogger = log.New(os.Stderr, "[CRITICAL] ", flag) } func logWithLevel(logger *log.Logger, level int, format string, v []any) { if logger != nil && logLevel >= level { logger.Printf(format, v...) } } 简单使用如下: ...

五月 30, 2022  |  317 字  |  总阅读

【React】React + Router + MUI 工程搭建

初始化 create-react-app npx create-react-app my-app 安装 MUI 在工程路径下执行: npm install @mui/material @emotion/react @emotion/styled npm install @mui/material @mui/styled-engine-sc styled-components npm install @mui/icons-material 在 public/index.html 中引入: <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" /> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" /> 引入一个按钮。修改 src/App.js 如下: import './App.css'; import {Button} from "@mui/material"; function App() { return ( <> <Button variant="contained">你好,世界</Button> </> ); } export default App; 修改 src/App.css 如下: body { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; } 启动 npm start 如此打开 http://localhost:3000 可看到简单页面: 跨域请求 配置跨域 npm install http-proxy-middleware 创建文件 src/setupProxy.js 如下: ...

五月 29, 2022  |  440 字  |  总阅读

【Python】日志 logging

记录 logging 简单用法。 #!/usr/bin/env python # coding=utf-8 import time import logging LOG_PATH = f'./{time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())}.log' LOG = logging.getLogger(__name__) formatter = logging.Formatter('[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d][%(threadName)s][%(funcName)s] %(message)s') LOG.setLevel(logging.DEBUG) def init_log(): sh = logging.StreamHandler() sh.setFormatter(formatter) sh.setLevel(logging.DEBUG) fh = logging.FileHandler(LOG_PATH, 'w', encoding='utf-8') fh.setFormatter(formatter) fh.setLevel(logging.DEBUG) LOG.addHandler(sh) LOG.addHandler(fh) init_log() 更多 Formatter 格式配置见 [logging — Python 的日志记录工具 — Python 3.11.1 文档](https://docs.python.org/zh-cn/3/library/logging.html?highlight=logging formatter#logging.LogRecord)。

五月 8, 2022  |  52 字  |  总阅读

【Python】命令行解析器 argparse

记录 argparse 简单用法。 #!/usr/bin/env python # coding=utf-8 import argparse parser = argparse.ArgumentParser() parser.add_argument('-n', '--name', metavar='<NAME>', required=True) parser.add_argument('-a', '--age', metavar='<AGE>', required=True, type=int) args = parser.parse_args() if __name__ == '__main__': print(args) $ ./script.py -n vksir -a 18 Namespace(age=18, name='vksir')

五月 8, 2022  |  37 字  |  总阅读

【MFC】第一次使用 MFC 开发桌面小程序

闲话 2020WUHAN,闲在家没事做,之前也用C语言写了一个小的游戏脚本,只有黑框框,趁这个时间就想给它弄个界面出来。 笔者其实只有非常粗略的编程技术,仅仅略懂C语言和Java。游戏脚本需要非常精准的时间控制函数,而且需要模拟键盘输出,响应键盘输入,运行在windows上,考虑到java的效率问题,选择了C++进行开发。 听说QT和MFC都不错,其实也考虑过imgui,但是那个教程资源很少,有也基本都是英文的,看起来着实费劲。 QT 感觉qt很适合新手,折腾了一番,没一天的时间就把界面折腾出来了,也基本完善了所有功能。但是,qt很不靠谱!做好了程序没法导出exe。用windeployqt工具导出错,查了一下,貌似是现在的版本5.14.0,有这个bug,没法导出。 卸了,装上5.13.3,可以成功导出。但是!我是高分屏,5.13.3界面会自动启用win10缩放,而且禁用缩放也没用。这导致编辑gui的界面被放得过大,不仅操作不方便,而且生成的程序窗口大小和那个不一致,放大ui也不放大字体。5.13.3也不能用。 各种难受。(刚看了眼,5.14.1发布了,想来是可以导出程序了,但怎么说,这种开源项目,出这种莫名其妙的bug,其实很难受。弃了) MFC 听说mfc并不适合新手,确实,但是,visual studio 2019 为mfc 开发提供了很多便捷操作,查查资料,其实并没有那么难(笔者可是0基础,C++都是现学的) vs2019 提供了哪些便捷操作呢?拖放编辑UI(这个和qt一样),类向导可以直接添加消息函数、重载函数,全图形交互式操作(这就不叫敲代码了吧……一键添加代码?) 当然,比起来MFC还是更难,但是mfc发展至今,资料也非常多,常见问题都能找得到,且微软不会像qt这样出一些莫名其妙的bug,mfc发展非常完善。vs2019也比qt creator好用得多(开发qt时我用的vscode写代码),调式功能非常不错,不像qt,出个错误又没有提示信息,有时候摸不着头脑。 学习c++ 菜鸟驿站 非常棒的网站,快速学习主要知识,直接上手,不懂的地方再找资料细看。 学习mfc开发 VC驿站 里面有很多C++相关的教程,笔者专注于看MFC开发那一块。 实用VC++编程之玩转控件 站长在b站上发的mfc教程视频,非常详细,讲解也很清楚。 mfc小程序需要学哪些 仅对于笔者需要的功能来说啊: 创建对话框 使用按钮、文本框、Spin、ComboBox等基础控件 控件设置 添加控件响应 添加菜单 禁止对话框窗口缩放 重载函数使得F1,Esc,Enter键失效 弹出第二个窗口 注册全局热键 读写文件 一一说来。 创建对话框程序 使用vs2019模板创建即可,运行,弹出一个窗口。 基于对话框 + 无增强的MFC控件 + 在静态库中使用MFC 没有mfc模板可能是没有装这个组件,百度即可。具体创建跟着vc驿站的教程视频来。 添加控件 图形交互式操作 设置控件变量名:右键控件——添加变量——设置名称 (变量名更易于自己识别,编写控件事件更方便) 添加响应 - 右键控件——添加事件处理程序——选择类列表:CXXXDlg——更改函数名——选择事件类型 下面有事件说明,大多数时候默认事件是最合适的 添加完成后,会直接跳转到cpp文件中,函数声明、创建,vs2019全部为你做好了,直接在里面添事件代码就行。 控件设置 Spin Control //设置DamageA和DamageB的Spin UDACCEL aclA, aclB; aclA.nInc = 2; aclB.nInc = 10; m_Spin_DamageA.SetRange(0, 9999); m_Spin_DamageA.SetAccel(2, &aclA); m_Spin_DamageA.SetBuddy(GetDlgItem(IDC_EDIT1)); m_Spin_DamageB.SetRange(0, 9999); m_Spin_DamageB.SetAccel(2, &aclB); m_Spin_DamageB.SetBuddy(GetDlgItem(IDC_EDIT2)); 说明: ...

四月 23, 2022  |  655 字  |  总阅读

【Go】实现 requests 库

粗略实现了一下 requests 库。但后来想想,也必要自己造轮子,还是 resty 香! package requests import ( "encoding/json" "errors" "fmt" "io" "log" "net/http" "net/url" "reflect" "strings" ) type Client struct { Url string Headers map[string][]string Params map[string][]string Content string Data interface{} Json interface{} } type Response struct { Status string StatusCode int Body string } func (c Client) newRequest(method string) (req *http.Request, err error) { // url if reflect.ValueOf(c.Url).IsZero() { err = errors.New(fmt.Sprintf("url is needed: client=%+v", c)) return } u, err := url.ParseRequestURI(c.Url) if err != nil { return } // params if !reflect.ValueOf(c.Params).IsNil() { var params url.Values = c.Params u.RawQuery = params.Encode() } // body, headers body := "" if reflect.ValueOf(c.Headers).IsNil() { c.Headers = make(map[string][]string) } if !reflect.ValueOf(c.Content).IsZero() { body = c.Content } else if !reflect.ValueOf(c.Data).IsValid() { c.Headers["Content-Type"] = []string{"application/x-www-form-urlencoded"} var bodyBytes []byte bodyBytes, err = json.Marshal(c.Json) if err != nil { return } body = string(bodyBytes) } else if !reflect.ValueOf(c.Json).IsValid() { c.Headers["Content-Type"] = []string{"application/json"} var bodyBytes []byte bodyBytes, err = json.Marshal(c.Json) if err != nil { return } body = string(bodyBytes) } req, err = http.NewRequest(method, u.String(), strings.NewReader(body)) if err != nil { return } req.Header = c.Headers return } func (c Client) parseResponse(resp *http.Response) (response *Response, err error) { defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { log.Printf("close response body failed: err=%s", err) } }(resp.Body) body, err := io.ReadAll(resp.Body) if err != nil { return } return &Response{ resp.Status, resp.StatusCode, string(body), }, nil } func (c Client) Request(method string) (response *Response, err error) { req, err := c.newRequest(method) if err != nil { return } client := http.Client{} resp, err := client.Do(req) if err != nil { return } return c.parseResponse(resp) } func (c Client) Get() (response *Response, err error) { return c.Request("GET") } func (c Client) Post() (response *Response, err error) { return c.Request("POST") }

四月 23, 2022  |  295 字  |  总阅读

【Python】Float 运算精度

如果有一天,有人告诉你三个 0.1 加起来不等于 0.3 你会不会觉得很惊讶?但事实如此: >>> 0.1 + 0.1 + 0.1 == 0.3 False 这是为什么呢?这就涉及到了浮点运算的精度问题。 Why 计算机一般以二进制存储数据。在十进制里,0.1 是个精确值,但在二进制里,0.1 是一个无限循环小数: a = 0.1 print('0.', end='') for _ in range(50): a *= 2 print(int(a // 1), end='') a -= a // 1 0.00011001100110011001100110011001100110011001100110 所以从存入的那一刻,其数值就已经从精确值变为了不定值。 在某些需要精确计算的场合,这非常致命。 How 如何解决? 其一是使用 decimal 模块,这里也仅介绍 decimal 模块,在我看来,它几乎满足所有要求。 decimal Decimal is based on a floating-point model which was designed with people in mind. 它模仿人类十进制计算方法进行计算: 绝对精确:0.1 + 0.1 + 0.1 = 0.3 有效位保留:0.30 * 0.200 = 0.06000 四舍五入 Decimal class decimal.Decimal(value="0", context=None) value 可以是整数、字符串、浮点数等。 >>> Decimal(1) # 一般用于表示大数字 Decimal(1) >>> Decimal('0.1') # 用于精确表示浮点数 Decimal('0.1') >>> Decimal(0.1) # 一般无用 Decimal('0.1000000000000000055511151231257827021181583404541015625') >>> Decimal('inf') >>> Decimal('-inf') context ...

八月 7, 2021  |  121 字  |  总阅读

【Python】进程管理之 subprocess

非常强大的子进程管理模块,你想要的它都有。 环境:Windows 10 上一篇文章讲的 Pexpect,功能是不错,但它有的 Subprocess 都能做到,且更加完美。 一个好的子进程管理需要满足什么功能需求? 无阻塞 / 阻塞 标准输入 / 输出 信号发送 / kill 其实也不多。 开始 import subprocess proc = subprocess.Popen('ping 127.0.0.1', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE) print(proc.stdout.read().decode('gbk')) # 因为是 windows 系统,默认编码是 ‘gbk’ 正在 Ping 127.0.0.1 具有 32 字节的数据: 来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128 来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128 来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128 来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128 127.0.0.1 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失), 往返行程的估计时间(以毫秒为单位): 最短 = 0ms,最长 = 0ms,平均 = 0ms subprocess 主要有两个运行命令的方法: ...

八月 7, 2021  |  786 字  |  总阅读