├── 202002 ├── Flask_Restful视图函数模块化.md └── vue2实现实时生成二维码和将网页合成图片并在微信内置浏览器长按保存.md ├── 202003 ├── Flask_sqlalchemy报错MySQL server has gone away解决.md ├── v-charts添加图表标题.md ├── Flask_sqlalchemy的增删改查.md └── vue组件props双向绑定.md ├── 202006 ├── docker中php上传大小限制.md ├── Linux分享文件?快速创建静态文件服务器.md └── flask_sqlalchemy一对一、一对多、多对一、多对多.md ├── 202007 ├── SFTP部署报错解决记录.md └── vue菜单高亮.md ├── 202008 ├── Go生成6位随机数.md ├── nginx报错an upstream response is buffered to a temporary file .md ├── Vue按需引入ElementUI报错Error: Plugin:Preset files are not allowed to export objects, only functions.md ├── Go http请求报错x509 certificate signed by unknown authority.md ├── Docker修改时区.md ├── Golang使用JSON格式存取Redis.md └── Flask_RESTful解析常见类型请求数据.md ├── 202010 └── 基于python3和js的前后端aes加解密.md ├── 202011 ├── Flask设置全局错误捕获.md ├── flask_restful限制request字段长度.md ├── go单元测试.md └── gin的http单元测试.md ├── 202012 └── gin中间件和鉴权.md ├── 202104 ├── Promise inside request interceptor.md ├── Grafana+Loki+Docker Driver Client日志收集方案.md └── H5检测手机摇一摇.md ├── 202106 ├── SwiftUI MacOS项目根据屏幕大小调整窗口大小.md ├── SwiftUI项目Image点击事件.md ├── SwiftUI项目实现搜索功能.md ├── SwiftUI项目复制字符串到剪切板.md ├── SwiftUI项目判断是否为暗黑模式.md ├── Swift计算两个日期的天数差.md └── SwiftUI项目调用生物识别(Touch ID : Face ID).md ├── 202107 ├── node spawn在windows下不生效问题记录.md └── SwiftUI MacOS项目alert弹出两次问题解决.md ├── 202108 ├── go中json解析报错invalid character '\\b' after top-level value.md ├── SwiftUI+Reality开发AR项目解决全屏问题.md ├── IOS监听上下左右滑动手势.md ├── Interval计时器在tab页切换或者隐藏情况下停止运行.md ├── Golang实现农历转换阳历.md └── Golang AES-256-CBC加密和解密.md ├── 202110 ├── OSS花式解锁下载文件新姿势,你学废了吗?.md ├── 前端文件花式直传OSS!后端:那我走?.md └── 你是个成熟的代码要学会自己按需引入了.md ├── 202111 └── vue3 script setup响应式初体验.md ├── 202112 ├── electron的__dirname not defined报错解决.md ├── vue禁止遮罩层下的页面滚动.md └── vue3+ts+electron不支持require is not defined报错解决.md ├── 202206 ├── python3 socket udp example.md └── python3 socket tcp example.md ├── 202207 └── flutter3.0基础学习笔记.md ├── 202210 └── 微服务架构基于网关实现请求拦截与校验.md ├── 202212 └── Go开发APISIX插件.md ├── 202301 └── wow.js和animate css在vue3中的应用.md ├── 202303 └── 原来浏览器原生支持JS复制到剪切板.md ├── 202311 └── 如何快速入门新的编程语言和框架.md ├── 202312 ├── 程序员如何快速验证业务需求.md └── 打工太累,年前裸辞,给自己放假一年.md ├── 202403 └── 裸辞后独立开发产品上线五天开始盈利,我是怎么做的?分享我的过程(下).md ├── ESP32 ├── .DS_Store ├── Screenshots │ └── group1 │ │ ├── 测试.png │ │ ├── 烧录固件.png │ │ ├── 选择解释器.png │ │ ├── esp32.jpeg │ │ └── esp32-2.jpeg └── Mac环境下玩玩ESP32(一)——环境搭建.md ├── Examples ├── vue_i18n_demo │ ├── .browserslistrc │ ├── .env │ ├── src │ │ ├── locales │ │ │ ├── zh_CN.json │ │ │ └── en_GB.json │ │ ├── assets │ │ │ └── logo.png │ │ ├── views │ │ │ ├── About.vue │ │ │ └── Home.vue │ │ ├── shims-vue.d.ts │ │ ├── store │ │ │ └── index.ts │ │ ├── main.ts │ │ ├── components │ │ │ ├── HelloI18n.vue │ │ │ └── HelloWorld.vue │ │ ├── App.vue │ │ ├── router │ │ │ └── index.ts │ │ └── i18n.ts │ ├── babel.config.js │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── .editorconfig │ ├── vue.config.js │ ├── .gitignore │ ├── .eslintrc.js │ ├── README.md │ ├── tsconfig.json │ └── package.json ├── microFrontend │ ├── site_1 │ │ ├── .browserslistrc │ │ ├── src │ │ │ ├── App.vue │ │ │ ├── views │ │ │ │ ├── Home.vue │ │ │ │ └── About.vue │ │ │ ├── assets │ │ │ │ └── logo.png │ │ │ ├── public-path.ts │ │ │ ├── shims-vue.d.ts │ │ │ ├── store │ │ │ │ └── index.ts │ │ │ ├── router │ │ │ │ └── index.ts │ │ │ ├── main.ts │ │ │ └── components │ │ │ │ └── HelloWorld.vue │ │ ├── babel.config.js │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── index.html │ │ ├── README.md │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── .eslintrc.js │ │ ├── tsconfig.json │ │ ├── vue.config.js │ │ └── package.json │ ├── site_base │ │ ├── .browserslistrc │ │ ├── babel.config.js │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── index.html │ │ ├── src │ │ │ ├── assets │ │ │ │ └── logo.png │ │ │ ├── views │ │ │ │ ├── About.vue │ │ │ │ └── Home.vue │ │ │ ├── shims-vue.d.ts │ │ │ ├── store │ │ │ │ └── index.ts │ │ │ ├── main.ts │ │ │ ├── router │ │ │ │ └── index.ts │ │ │ ├── App.vue │ │ │ └── components │ │ │ │ └── HelloWorld.vue │ │ ├── .editorconfig │ │ ├── README.md │ │ ├── .gitignore │ │ ├── .eslintrc.js │ │ ├── tsconfig.json │ │ └── package.json │ └── site_base_optimize │ │ ├── .browserslistrc │ │ ├── babel.config.js │ │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ │ ├── src │ │ ├── assets │ │ │ └── logo.png │ │ ├── views │ │ │ ├── About.vue │ │ │ └── Home.vue │ │ ├── shims-vue.d.ts │ │ ├── store │ │ │ └── index.ts │ │ ├── main.ts │ │ ├── childNodes │ │ │ ├── apps.ts │ │ │ └── index.ts │ │ ├── router │ │ │ └── index.ts │ │ ├── App.vue │ │ └── components │ │ │ └── HelloWorld.vue │ │ ├── .editorconfig │ │ ├── README.md │ │ ├── .gitignore │ │ ├── .eslintrc.js │ │ ├── tsconfig.json │ │ └── package.json ├── unplugin_auto_import │ ├── .gitignore │ ├── .vscode │ │ └── extensions.json │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── assets │ │ │ └── logo.png │ │ ├── main.js │ │ ├── App.vue │ │ └── components │ │ │ └── HelloWorld.vue │ ├── README.md │ ├── index.html │ ├── vite.config.js │ └── package.json └── vite_electron │ ├── vite_electron_1 │ ├── .gitignore │ ├── src │ │ ├── main.js │ │ ├── assets │ │ │ └── logo.png │ │ ├── App.vue │ │ └── components │ │ │ └── HelloWorld.vue │ ├── public │ │ └── favicon.ico │ ├── README.md │ ├── vite.config.js │ ├── index.html │ ├── package.json │ ├── preload.js │ └── main.js │ ├── vite_electron_2 │ ├── .gitignore │ ├── src │ │ ├── main.js │ │ ├── assets │ │ │ └── logo.png │ │ ├── App.vue │ │ └── components │ │ │ └── HelloWorld.vue │ ├── public │ │ └── favicon.ico │ ├── README.md │ ├── vite.config.js │ ├── index.html │ ├── preload.js │ ├── package.json │ └── main.js │ └── vite_electron_3 │ ├── .gitignore │ ├── src │ ├── main.js │ ├── assets │ │ └── logo.png │ ├── App.vue │ └── components │ │ └── HelloWorld.vue │ ├── public │ └── favicon.ico │ ├── README.md │ ├── vite.config.js │ ├── index.html │ ├── electron │ ├── preload.js │ └── main.js │ └── package.json ├── assets ├── wx_qr.png ├── loki_1.png ├── loki_2.png ├── loki_3.png ├── loki_4.png ├── loki_5.png ├── nginx_高可用.png ├── wechat_qr.png ├── go_redis_pub.png ├── go_redis_sub.png ├── rust_wasm_0.png ├── go_wasm_canvas.png ├── qiankun_example.jpg ├── create_swiftui_project.png ├── qiangkun_example_result.png ├── swiftui_core_data_page.png ├── swiftui_core_data_add_item_demo.png ├── swiftui_face_privacy_info_plist.png └── swiftui_core_data_create_attributes.png ├── .gitignore ├── Nginx ├── 高可用.md ├── 动静分离.md ├── README.md ├── 反向代理.md └── 负载均衡.md ├── Electron ├── README.md ├── electron-builder踩坑系列---开发与编译后的文件路径问题.md ├── electron-builder踩坑系列---无边框.md ├── electron-builder踩坑系列---无边框窗口拖动.md ├── electron-builder踩坑系列---禁止用户调整窗口大小.md ├── electron-builder踩坑系列---无边框拖动.md ├── electron升级后在Mac上报错:Exit code: ENOENT. spawn :usr:bin:python ENOENT.md ├── electron-builder踩坑系列---tray系统托盘.md ├── electron-builder踩坑系列---mac下窗口毛玻璃效果.md └── electron-builder踩坑系列---lowdb本地存储.md ├── README.md └── MySQL ├── 字段属性.md ├── 修改删除表.md ├── 创建数据库表.md └── 数据类型.md /202212/Go开发APISIX插件.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ESP32/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/ESP32/.DS_Store -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /assets/wx_qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/wx_qr.png -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /assets/loki_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/loki_1.png -------------------------------------------------------------------------------- /assets/loki_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/loki_2.png -------------------------------------------------------------------------------- /assets/loki_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/loki_3.png -------------------------------------------------------------------------------- /assets/loki_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/loki_4.png -------------------------------------------------------------------------------- /assets/loki_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/loki_5.png -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /assets/nginx_高可用.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/nginx_高可用.png -------------------------------------------------------------------------------- /assets/wechat_qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/wechat_qr.png -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/.env: -------------------------------------------------------------------------------- 1 | VUE_APP_I18N_LOCALE=en_GB 2 | VUE_APP_I18N_FALLBACK_LOCALE=en_GB 3 | -------------------------------------------------------------------------------- /assets/go_redis_pub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/go_redis_pub.png -------------------------------------------------------------------------------- /assets/go_redis_sub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/go_redis_sub.png -------------------------------------------------------------------------------- /assets/rust_wasm_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/rust_wasm_0.png -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /assets/go_wasm_canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/go_wasm_canvas.png -------------------------------------------------------------------------------- /assets/qiankun_example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/qiankun_example.jpg -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/locales/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "中文", 3 | "message": "你好,世界!" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | */.DS_Store 4 | */*/.DS_Store 5 | */*/*/.DS_Store 6 | 7 | *.icloud 8 | 9 | -------------------------------------------------------------------------------- /ESP32/Screenshots/group1/测试.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/ESP32/Screenshots/group1/测试.png -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /ESP32/Screenshots/group1/烧录固件.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/ESP32/Screenshots/group1/烧录固件.png -------------------------------------------------------------------------------- /ESP32/Screenshots/group1/选择解释器.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/ESP32/Screenshots/group1/选择解释器.png -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["johnsoncodehk.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /assets/create_swiftui_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/create_swiftui_project.png -------------------------------------------------------------------------------- /assets/qiangkun_example_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/qiangkun_example_result.png -------------------------------------------------------------------------------- /assets/swiftui_core_data_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/swiftui_core_data_page.png -------------------------------------------------------------------------------- /ESP32/Screenshots/group1/esp32.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/ESP32/Screenshots/group1/esp32.jpeg -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/locales/en_GB.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "English", 3 | "message": "Hello, World !" 4 | } 5 | -------------------------------------------------------------------------------- /ESP32/Screenshots/group1/esp32-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/ESP32/Screenshots/group1/esp32-2.jpeg -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/vue_i18n_demo/public/favicon.ico -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/vue_i18n_demo/src/assets/logo.png -------------------------------------------------------------------------------- /assets/swiftui_core_data_add_item_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/swiftui_core_data_add_item_demo.png -------------------------------------------------------------------------------- /assets/swiftui_face_privacy_info_plist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/swiftui_face_privacy_info_plist.png -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /assets/swiftui_core_data_create_attributes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/assets/swiftui_core_data_create_attributes.png -------------------------------------------------------------------------------- /202210/微服务架构基于网关实现请求拦截与校验.md: -------------------------------------------------------------------------------- 1 | # 一. 前言 2 | 3 | 后端实现API Server时,都需要对请求进行拦截和校验,特别是当前前后端分离,更加需要API请求的校验。 4 | 5 | 平时使用的场景大都是对请求添加token进行校验,由后端中间件进行处理 -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/microFrontend/site_1/public/favicon.ico -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/microFrontend/site_1/src/assets/logo.png -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/unplugin_auto_import/public/favicon.ico -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/unplugin_auto_import/src/assets/logo.png -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /Nginx/高可用.md: -------------------------------------------------------------------------------- 1 | # 高可用 2 | 3 | 4 | 5 | ## 简介 6 | 7 | 为了防止Nginx宕机,而使用一个主Nginx,一个副Nginx。 8 | 9 | ![](../assets/nginx_高可用.png) 10 | 11 | -------------------------------------------------------------------------------- /Electron/README.md: -------------------------------------------------------------------------------- 1 | ## Electron-builder踩坑系列 2 | 3 | 最近在写一个桌面应用程序,使用技术栈为`vue`+`electron-builder`。但是网上太多`electron-vue`的文档,版本过老,坑可太多了。因此特别把踩过的坑记录下来,以免后面再踩。 -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/microFrontend/site_base/public/favicon.ico -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/microFrontend/site_base/src/assets/logo.png -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | createApp(App).mount('#app') 5 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/vite_electron/vite_electron_1/public/favicon.ico -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/vite_electron/vite_electron_1/src/assets/logo.png -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/vite_electron/vite_electron_2/public/favicon.ico -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/vite_electron/vite_electron_2/src/assets/logo.png -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/vite_electron/vite_electron_3/public/favicon.ico -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/vite_electron/vite_electron_3/src/assets/logo.png -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/microFrontend/site_base_optimize/public/favicon.ico -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-ji/Blog/HEAD/Examples/microFrontend/site_base_optimize/src/assets/logo.png -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/README.md: -------------------------------------------------------------------------------- 1 | 对应文章:[VUE3+TS+微前端实践](https://github.com/Kuari/Blog/issues/61) 2 | 3 | 公众号 4 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /Electron/electron-builder踩坑系列---开发与编译后的文件路径问题.md: -------------------------------------------------------------------------------- 1 | ## 简述 2 | 3 | 要说`electron`产品中花了时间最多的,也就是这个文件路径问题,主要是在编译后。我开发时是编译到windows平台,因此可以发现编译后的文件夹里面并没有前端相关代码,编译之后的文件路径跟开发时候并不相同,调试也比较困难。 4 | 5 | 首先, -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/README.md: -------------------------------------------------------------------------------- 1 | 对应文章:[VUE3+TS+微前端实践](https://github.com/Kuari/Blog/issues/61) 2 | 3 | 4 | 5 | 公众号 6 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/README.md: -------------------------------------------------------------------------------- 1 | 对应文章:[你是个成熟的代码要学会自己按需引入了](https://github.com/Kuari/Blog/issues/56) 2 | 3 | 4 | 5 | 公众号 6 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/README.md: -------------------------------------------------------------------------------- 1 | 对应文章:[VUE3+TS+微前端实践](https://github.com/Kuari/Blog/issues/61) 2 | 3 | 4 | 公众号 5 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/public-path.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | if ((window as any).__POWERED_BY_QIANKUN__) { 3 | __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__ 4 | } 5 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | export default createStore({ 4 | state: { 5 | }, 6 | mutations: { 7 | }, 8 | actions: { 9 | }, 10 | modules: { 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | export default createStore({ 4 | state: { 5 | }, 6 | mutations: { 7 | }, 8 | actions: { 9 | }, 10 | modules: { 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | export default createStore({ 4 | state: { 5 | }, 6 | mutations: { 7 | }, 8 | actions: { 9 | }, 10 | modules: { 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite + Electron 的基础构建 2 | 3 | 对应文章:[Vite+Electron快速构建一个VUE3桌面应用](https://github.com/Kuari/Blog/issues/52) 4 | 5 | 6 | 7 | 公众号 8 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | export default createStore({ 4 | state: { 5 | }, 6 | mutations: { 7 | }, 8 | actions: { 9 | }, 10 | modules: { 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite + Electron 的打包 2 | 3 | 对应文章:[Vite+Electron快速构建一个VUE3桌面应用(三)——打包](https://github.com/Kuari/Blog/issues/54) 4 | 5 | 6 | 7 | 公众号 8 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite + Electron 的动态模块热重载 2 | 3 | 对应文章:[Vite+Electron快速构建一个VUE3桌面应用(二)——动态模块热重载](https://github.com/Kuari/Blog/issues/53) 4 | 5 | 6 | 7 | 公众号 8 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import i18n from './i18n' 6 | 7 | createApp(App).use(i18n).use(store).use(router).mount('#app') 8 | -------------------------------------------------------------------------------- /202008/Go生成6位随机数.md: -------------------------------------------------------------------------------- 1 | ```go 2 | package main 3 | 4 | import ( 5 | "math/rand" 6 | "strconv" 7 | ) 8 | 9 | func CreateVerifyCode() (verifyCode string) { 10 | min := 100000 11 | max := 999999 12 | verifyCode = strconv.Itoa(rand.Intn(max-min) + min) 13 | return 14 | } 15 | ``` 16 | 17 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | base: "./", // 新增 9 | plugins: [vue()] 10 | }) 11 | 12 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | base: "./", // 新增 9 | plugins: [vue()] 10 | }) 11 | 12 | -------------------------------------------------------------------------------- /202008/nginx报错an upstream response is buffered to a temporary file .md: -------------------------------------------------------------------------------- 1 | ## 报错 2 | 3 | ```bash 4 | an upstream response is buffered to a temporary file 5 | ``` 6 | 7 | 8 | 9 | ## 解决 10 | 11 | `Nginx`配置加上如下配置 12 | 13 | ```bash 14 | proxy_max_temp_file_size 0; 15 | client_max_body_size 50m; 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import start from './childNodes' 6 | 7 | start() // 开启应用 8 | 9 | createApp(App).use(store).use(router).mount('#app') 10 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import path from 'path' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | base: path.resolve(__dirname, './dist/'), 8 | plugins: [vue()] 9 | }) 10 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pluginOptions: { 3 | i18n: { 4 | locale: 'en', 5 | fallbackLocale: 'en', 6 | localeDir: 'locales', 7 | enableLegacy: false, 8 | runtimeOnly: false, 9 | compositionOnly: false, 10 | fullInstall: true 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/childNodes/apps.ts: -------------------------------------------------------------------------------- 1 | const apps: any[] = [ 2 | { 3 | name: 'site1', // 应用的名字 4 | entry: 'http://localhost:9001/', // 默认加载这个html,解析里面的js动态的执行(子应用必须支持跨域,内部使用的是 fetch) 5 | container: '#site1', // 要渲染到的节点id 6 | activeRule: '/site1' // 访问子节点路由 7 | } 8 | ] 9 | 10 | export default apps 11 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 3 | 主要用来记录我的技术博客,文章在[Issues](https://github.com/Kuari/Blog/issues)里面的,嘿嘿。 4 | 5 | 一开始从wordpress,到自己开发博客,这些年也踩了不少坑,现在还是决定用GitHub来写博客,简简单单做好技术笔记。有些文章会发一些其它平台跟大家分享分享。 6 | 7 | 8 | 9 | ## 知乎专栏 10 | 11 | * [知乎专栏开发小橙](https://www.zhihu.com/column/devlittleorange) 12 | 13 | 14 | 15 | ## 公众号 16 | 17 | 微信公众号开发小橙 18 | 19 | -------------------------------------------------------------------------------- /MySQL/字段属性.md: -------------------------------------------------------------------------------- 1 | ### Unsigned 2 | 3 | * 无符号的整数 4 | * 声明了该列不能声明为负数 5 | 6 | 7 | 8 | ### zerofill 9 | 10 | * 0填充 11 | * 不足的位数,使用0来填充,int(3),如5 -> 005 12 | 13 | 14 | 15 | ### 自增 16 | 17 | * 通常理解为自增,默认自动在上一条记录的基础上 + 1 18 | * 通常用来设计唯一的主键~index,必须是整数类型 19 | * 可以自定义主键自增长的起始值和步长 20 | 21 | 22 | 23 | ### 非空 24 | 25 | * 假设设置为not null,如果不给它赋值就会报错 26 | * NULL,如果不填值,默认为null 27 | 28 | 29 | 30 | ### 默认 31 | 32 | * 设置默认的值 -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import Components from 'unplugin-vue-components/vite' 4 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | Components({resolvers: [ElementPlusResolver()]}) 11 | ] 12 | }) 13 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router' 2 | 3 | const routes: Array = [ 4 | { 5 | path: '/', 6 | name: 'Home', 7 | component: () => import('../views/Home.vue') 8 | }, 9 | { 10 | path: '/about', 11 | name: 'About', 12 | component: () => import('../views/About.vue') 13 | } 14 | ] 15 | 16 | // 直接返回routes,由其它地方处理创建路由 17 | export default routes 18 | -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unplugin_auto_import", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "vue": "^3.2.16" 11 | }, 12 | "devDependencies": { 13 | "@vitejs/plugin-vue": "^1.9.3", 14 | "element-plus": "^1.1.0-beta.24", 15 | "unplugin-vue-components": "^0.16.0", 16 | "vite": "^2.6.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/vue3-essential', 8 | '@vue/standard', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/vue3-essential', 8 | '@vue/standard', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite_electron_1", 3 | "version": "0.0.0", 4 | "main": "main.js", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "serve": "vite preview", 9 | "electron:serve": "electron ." 10 | }, 11 | "dependencies": { 12 | "vue": "^3.2.16" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^1.9.3", 16 | "electron": "^15.2.0", 17 | "vite": "^2.6.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/vue3-essential', 8 | '@vue/standard', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/vue3-essential', 8 | '@vue/standard', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/src/App.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/preload.js: -------------------------------------------------------------------------------- 1 | // preload.js 2 | 3 | // 所有Node.js API都可以在预加载过程中使用。 4 | // 它拥有与Chrome扩展一样的沙盒。 5 | window.addEventListener('DOMContentLoaded', () => { 6 | const replaceText = (selector, text) => { 7 | const element = document.getElementById(selector) 8 | if (element) element.innerText = text 9 | } 10 | 11 | for (const dependency of ['chrome', 'node', 'electron']) { 12 | replaceText(`${dependency}-version`, process.versions[dependency]) 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/preload.js: -------------------------------------------------------------------------------- 1 | // preload.js 2 | 3 | // 所有Node.js API都可以在预加载过程中使用。 4 | // 它拥有与Chrome扩展一样的沙盒。 5 | window.addEventListener('DOMContentLoaded', () => { 6 | const replaceText = (selector, text) => { 7 | const element = document.getElementById(selector) 8 | if (element) element.innerText = text 9 | } 10 | 11 | for (const dependency of ['chrome', 'node', 'electron']) { 12 | replaceText(`${dependency}-version`, process.versions[dependency]) 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /202112/electron的__dirname not defined报错解决.md: -------------------------------------------------------------------------------- 1 | ## 报错 2 | 3 | 在使用vue cli plugin electron builder开发项目,用到涉及node相关的功能,比如`fs`、`path`,出现报错`__dirname not defined `。 4 | 5 | 6 | 7 | ## 解决 8 | 9 | 该问题在于需要开启electron对于node操作的支持。 10 | 11 | ### 1. 安装@types/node 12 | 13 | ```bash 14 | yarn add @types/node -D 15 | ``` 16 | 17 | ### 2. 修改配置 18 | 19 | 修改electron的配置文件,此处我的配置文件为`src/background.ts` 20 | 21 | ```javascript 22 | webPreferences: { 23 | // ... 24 | nodeIntegration: true, 25 | nodeIntegrationInWorker: true 26 | } 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/electron/preload.js: -------------------------------------------------------------------------------- 1 | // preload.js 2 | 3 | // 所有Node.js API都可以在预加载过程中使用。 4 | // 它拥有与Chrome扩展一样的沙盒。 5 | window.addEventListener('DOMContentLoaded', () => { 6 | const replaceText = (selector, text) => { 7 | const element = document.getElementById(selector) 8 | if (element) element.innerText = text 9 | } 10 | 11 | for (const dependency of ['chrome', 'node', 'electron']) { 12 | replaceText(`${dependency}-version`, process.versions[dependency]) 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /Nginx/动静分离.md: -------------------------------------------------------------------------------- 1 | # 动静分离 2 | 3 | 4 | 5 | ## 简介 6 | 7 | 动静分离简单来说就是把动态跟静态请求分开,不能理解成只是单纯地把动态页面和静态页面物理分开。严格意义上说应该是动态请求跟静态请求分开,可以理解成使用Niginx处理静态页面,服务端处理动态页面。动静分离从目前实现角度来讲大致分为两种: 8 | 9 | * 纯粹地把静态文件独立成单独的域名,放在独立的服务器上,也是目前主流推崇的方案; 10 | * 把静态文件混合在一起发布,通过nginx分开; 11 | 12 | 通过`location`指定不同的后缀名实现不同的请求转发,通过`expiress`参数设置,可以使浏览器缓存过期时间,减少与服务器之间的请求和流量。具体`Expiress`定义:是给一个资源设定一个过期时间,也就是说无需去服务端验证,直接通过浏览器自身确认是否过期即可,所以不会产生额外的流量。此种方法非常适合不经常变动的资源。如设置`3d`,表示三天之内访问这个URL,发送一个请求,比对服务器该文件最后更新时间变化,则不会从服务器抓取,返回状态码304。如有修改,则直接从服务器重新下载,返回状态码200。 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Nginx/README.md: -------------------------------------------------------------------------------- 1 | # Nginx系列 2 | 3 | 4 | 5 | ## 功能 6 | 7 | * 正向代理 8 | * 反向代理 9 | * 负载均衡 10 | * 动静分离 11 | 12 | 13 | 14 | ## 配置文件 15 | 16 | ### 配置文件位置 17 | 18 | * /etc/nginx/nginx.conf 19 | 20 | ### 配置文件组成 21 | 22 | 1. 全局块 23 | * 从配置文件开始到events块之间的内容,主要会设置一些影响nginx服务器整体运行的配置指令; 24 | * 比如`worker_processes 1;`,`worker_processes`值越大,可以支持的并发处理量也越大; 25 | 2. events块 26 | * events块涉及的指令主要影响Nginx服务器与用户的网络连接; 27 | * 比如`worker_connections 1024;`,`worker_connections`支持的最大连接数量; 28 | 3. http块 29 | * Nginx服务配置中最频繁的部分 30 | * http块包含`http全局块`,`server块` -------------------------------------------------------------------------------- /202104/Promise inside request interceptor.md: -------------------------------------------------------------------------------- 1 | ## 问题 2 | 3 | 在使用axios的拦截器时候,需要在request中调用一个promise函数,因此需要等待其执行完成才能去进行下一步。 4 | 5 | ```js 6 | function getToken() { 7 | return new Promise(...) 8 | } 9 | 10 | // Request interceptors 11 | service.interceptors.request.use(config => { 12 | getToken() 13 | ... 14 | }) 15 | ``` 16 | 17 | 18 | 19 | ## 解决 20 | 21 | ```js 22 | function getToken() { 23 | return new Promise(...) 24 | } 25 | 26 | // Request interceptors 27 | service.interceptors.request.use(async config => { 28 | awit getToken() 29 | ... 30 | }) 31 | ``` 32 | 33 | -------------------------------------------------------------------------------- /202003/Flask_sqlalchemy报错MySQL server has gone away解决.md: -------------------------------------------------------------------------------- 1 | ### 报错 2 | 3 | 使用`flask_sqlalchemy`,服务端出现500错误,日志显示报错如下: 4 | 5 | ```bash 6 | MySQL server has gone away 7 | ``` 8 | 9 | 10 | 11 | ### 解决 12 | 13 | 添加配置: 14 | 15 | ```python 16 | SQLALCHEMY_POOL_RECYCLE = 280 17 | ``` 18 | 19 | 该配置作用是设置多少秒后回收连接,如果不提供值,默认为 2 小时。此处将其设置为280秒。 20 | 21 | 22 | 23 | 该配置原文解释: 24 | 25 | > Number of seconds after which a connection is automatically recycled. This is required for MySQL, which removes connections after 8 hours idle by default. Note that Flask-SQLAlchemy automatically sets this to 2 hours if MySQL is used. -------------------------------------------------------------------------------- /202112/vue禁止遮罩层下的页面滚动.md: -------------------------------------------------------------------------------- 1 | ## 问题 2 | 3 | 功能开发过程中写遮罩时,遇到遮罩下页面还可以滚动的问题。 4 | 5 | 6 | 7 | ## 解决 8 | 9 | 直接给遮罩下的元素套上一个样式,使其不可滚动。 10 | 11 | ```html 12 | 19 | 20 | 29 | 30 | 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/components/HelloI18n.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 23 | 24 | 25 | { 26 | "en": { 27 | "hello": "Hello i18n in SFC!" 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Electron/electron-builder踩坑系列---无边框.md: -------------------------------------------------------------------------------- 1 | ## 简述 2 | 3 | 虽然并非出于直接实现无边框的需求,但是为了实现窗口的固定大小(设置固定值不可调整情况下,mac是没问题的,但是windows一直可以调整大小)而设置了无边框。 4 | 5 | 6 | 7 | ## 官方文档 8 | 9 | ```javascript 10 | const { BrowserWindow } = require('electron') 11 | let win = new BrowserWindow({ width: 800, height: 600, frame: false }) 12 | win.show() 13 | ``` 14 | 15 | 16 | 17 | ## 实现 18 | 19 | ```javascript 20 | // background.js 21 | 22 | win = new BrowserWindow({ 23 | width: 800, 24 | height: 600, 25 | frame: false 26 | }); 27 | ``` 28 | 29 | 30 | 31 | ## 参考文档 32 | 33 | * [官方文档](https://www.electronjs.org/docs/api/frameless-window) -------------------------------------------------------------------------------- /202106/SwiftUI MacOS项目根据屏幕大小调整窗口大小.md: -------------------------------------------------------------------------------- 1 | ## 一. 代码实现 2 | 3 | ### 1. 获取屏幕对象 4 | 5 | ```swift 6 | var window = NSScreen.main?.visibleFrame 7 | ``` 8 | 9 | ### 2. 设置大小 10 | 11 | ```swift 12 | HStack { 13 | 14 | } 15 | .frame(width: window!.width / 2.0, height: window!.height / 1.5) 16 | ``` 17 | 18 | 19 | 20 | ## 二. 汇总 21 | 22 | ```swift 23 | struct Home: View { 24 | 25 | var window = NSScreen.main?.visibleFrame 26 | 27 | var body: some View { 28 | HStack { 29 | Text("Hello, World!") 30 | } 31 | .frame(width: window!.width / 2.0, height: window!.height / 1.5) 32 | } 33 | } 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/README.md: -------------------------------------------------------------------------------- 1 | # vue_i18n_demo 2 | 3 | 对应文章:[Vite+Electron快速构建一个VUE3桌面应用](https://github.com/Kuari/Blog/issues/60) 4 | 5 | 6 | 7 | 公众号 8 | 9 | 10 | 11 | ## Project setup 12 | ``` 13 | yarn install 14 | ``` 15 | 16 | ### Compiles and hot-reloads for development 17 | ``` 18 | yarn serve 19 | ``` 20 | 21 | ### Compiles and minifies for production 22 | ``` 23 | yarn build 24 | ``` 25 | 26 | ### Lints and fixes files 27 | ``` 28 | yarn lint 29 | ``` 30 | 31 | ### Customize configuration 32 | See [Configuration Reference](https://cli.vuejs.org/config/). 33 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 31 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kuari", 3 | "version": "0.0.0", 4 | "main": "main.js", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "serve": "vite preview", 9 | "electron": "wait-on tcp:3000 && electron .", 10 | "electron:serve": "concurrently -k \"yarn dev\" \"yarn electron\"" 11 | }, 12 | "dependencies": { 13 | "vue": "^3.2.16" 14 | }, 15 | "devDependencies": { 16 | "@vitejs/plugin-vue": "^1.9.3", 17 | "concurrently": "^6.3.0", 18 | "cross-env": "^7.0.3", 19 | "electron": "^15.1.2", 20 | "vite": "^2.6.4", 21 | "wait-on": "^6.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /202108/go中json解析报错invalid character '\\b' after top-level value.md: -------------------------------------------------------------------------------- 1 | ## 报错 2 | 3 | 在`Golang`中`json`解析时报错: 4 | 5 | ``` 6 | invalid character '\\b' after top-level value 7 | ``` 8 | 9 | 代码如下: 10 | 11 | ```go 12 | json.Unmarshal([]byte(result), &response) 13 | ``` 14 | 15 | 16 | 17 | ## 分析与排错 18 | 19 | 首先将`result`打印出来,发现并无异常,其标点符号也没有问题。 20 | 21 | 然后查看网上现有解决方案的帖子基本试了下,起码对于我来说并不适用,概括下方案: 22 | 23 | 1. 遍历然后过滤,最后重组; 24 | 2. 遍历,使用SetEscapeHTML(false)禁用转义符; 25 | 3. 编码; 26 | 4. ... 27 | 28 | 最后对比代码中获取到的字符产长度和手动复制所见的字符串的长度,发现确实代码中字符长度不同,其长度是80,而手动复制的字符串的长度是72。 29 | 30 | 31 | 32 | ## 解决 33 | 34 | ```go 35 | strings.ReplaceAll(result, "\b", "") 36 | ``` 37 | 38 | 就挺简单的...... -------------------------------------------------------------------------------- /202112/vue3+ts+electron不支持require is not defined报错解决.md: -------------------------------------------------------------------------------- 1 | ## 报错 2 | 3 | 在使用vue3+typescript+electron开发时,遇到一个报错为: 4 | 5 | ```bash 6 | Uncaught ReferenceError: require is not defined 7 | ``` 8 | 9 | 点进去是`module.exports = require("events")`,并不是自己的代码中的`require`,因此无法改变写法只能让项目去支持它。 10 | 11 | 12 | 13 | ## 解决 14 | 15 | 在electron的配置文件中,新增或者修改如下配置: 16 | 17 | ```javascript 18 | webPreferences: { 19 | // ... 20 | contextIsolation: false, 21 | nodeIntegration: true 22 | } 23 | ``` 24 | 25 | 26 | 27 | ## 参考文档 28 | 29 | * [Electron/Tedious: require("events") is not defined](https://stackoverflow.com/questions/64706829/electron-tedious-requireevents-is-not-defined) 30 | 31 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/childNodes/index.ts: -------------------------------------------------------------------------------- 1 | import { registerMicroApps, start } from 'qiankun' 2 | import apps from './apps' 3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 4 | // @ts-ignore 5 | import NProgress from 'nprogress' 6 | import 'nprogress/nprogress.css' 7 | 8 | registerMicroApps(apps, { 9 | // qiankun 生命周期钩子 - 子节点加载前 10 | beforeLoad: (app: any) => { 11 | NProgress.start() // 开始进度条 12 | return Promise.resolve() 13 | }, 14 | // qiankun 生命周期钩子 - 子节点挂载后 15 | afterMount: (app: any) => { 16 | NProgress.done() // 进度条结束 17 | return Promise.resolve() 18 | } 19 | }) 20 | 21 | export default start 22 | -------------------------------------------------------------------------------- /MySQL/修改删除表.md: -------------------------------------------------------------------------------- 1 | ## 修改表 2 | 3 | * 修改表明 4 | 5 | ```sql 6 | alter table `旧表名` rename as `新表名` 7 | ``` 8 | 9 | * 增加表的字段 10 | 11 | ```sql 12 | alter table `表名` add `字段名` 列属性 13 | ``` 14 | 15 | * 修改表的字段 16 | 17 | * 修改约束 18 | 19 | ```sql 20 | alter table `表名` modify `字段名` [列属性] 21 | alter table teacher1 modify age varchar(11) -- 示例 22 | ``` 23 | 24 | * 字段重命名 25 | 26 | ```sql 27 | alter table `表名` change 字段名 新字段名 [列属性] 28 | alter table teacher1 chage age age1 int(1) -- 示例 29 | ``` 30 | 31 | * 删除表的字段 32 | 33 | ```sql 34 | alter table `表名` drop `字段名` 35 | ``` 36 | 37 | 38 | 39 | ## 删除表 40 | 41 | ```sql 42 | drop table if exits `表名` 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Electron/electron-builder踩坑系列---无边框窗口拖动.md: -------------------------------------------------------------------------------- 1 | ## 简述 2 | 3 | 无边框的拖动本来很简单,只要对可以拖动的部分加上`-webkit-app-region: drag;`的样式即可。但是,当可拖动区域存在其他元素,如顶部菜单的搜索框,对mac是没有问题的,当在windows平台下拖动事件就会覆盖掉其他元素,因此需要对其他元素单独设置样式。 4 | 5 | 6 | 7 | ## 官方文档 8 | 9 | ```html 10 | 11 | 12 | ``` 13 | 14 | ```css 15 | button { 16 | -webkit-app-region: no-drag; 17 | } 18 | ``` 19 | 20 | 21 | 22 | 23 | 24 | ## 实现 25 | 26 | ```css 27 | .menu { 28 | -webkit-app-region: drag; 29 | } 30 | 31 | .menu-button { 32 | -webkit-app-region: no-drag 33 | } 34 | ``` 35 | 36 | 37 | 38 | 39 | 40 | ## 参考文档 41 | 42 | * [官方文档](https://www.electronjs.org/docs/api/frameless-window#%E5%8F%AF%E6%8B%96%E6%8B%BD%E5%8C%BA) -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /202106/SwiftUI项目Image点击事件.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | swiftui中的点击可以有两种情况: 4 | 5 | * Button 6 | * Gestures 7 | 8 | 根据不同情况可以去不同地使用。 9 | 10 | 11 | 12 | ## 二. 单纯的按钮 13 | 14 | 此处单纯的按钮即为有按钮样式和点击事件。 15 | 16 | ```swift 17 | Button(action: { 18 | ... // 点击事件触发的代码 19 | }, label: { 20 | Image(systemName: "plus") 21 | }) 22 | ``` 23 | 24 | 25 | 26 | ## 三. 无样式的按钮 27 | 28 | 即为没有按钮样式的按钮,方便直接展示`Image`。 29 | 30 | ```swift 31 | Button(action: { 32 | ... // 点击事件触发的代码 33 | }, label: { 34 | Image(systemName: "plus") 35 | }) 36 | .buttonStyle(BorderlessButtonStyle()) 37 | ``` 38 | 39 | 40 | 41 | ## 四. TapGesture事件 42 | 43 | ```swift 44 | Image(systemName: "plus") 45 | .onTapGesture { 46 | ... // 点击事件触发的代码 47 | } 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /202108/SwiftUI+Reality开发AR项目解决全屏问题.md: -------------------------------------------------------------------------------- 1 | ## 环境 2 | 3 | * Swift 5.4 4 | * Xcode 12.5.1 5 | 6 | 7 | 8 | ## 问题 9 | 10 | 在使用`Swift UI`和`Realiy`开发AR项目时,发现摄像头一直是居中的,无法全屏。 11 | 12 | 13 | 14 | ## 解决 15 | 16 | ### 1. 创建`LaunchScreen.storyboard`文件 17 | 18 | 在左侧文件列表中新建文件,名为`LaunchScreen.storyboard`。 19 | 20 | ### 2. 设置`Launch Screen File` 21 | 22 | 点击左侧文件列表中你的项目文件(最顶级文件),进入文件`[your-project].xcodeproj`文件。 23 | 24 | 在`General`中,找到`App Icons and Launch Images`,在其模块中有`Launch Screen File`选项,点击选择为`LaunchScreen.storyboard`。 25 | 26 | 总结下就是:`[yourTarget] -> General -> App Icons and Launch Images`。 27 | 28 | 29 | 30 | ## 参考文档 31 | 32 | * [How to make SwiftUI view fullscreen?](https://stackoverflow.com/questions/56733642/how-to-make-swiftui-view-fullscreen) 33 | 34 | -------------------------------------------------------------------------------- /202007/SFTP部署报错解决记录.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 部署SFTP服务器,数次遇到几个报错,特此记录 4 | 5 | 6 | 7 | ## 二. 环境 8 | 9 | * 路径 10 | 11 | ```bash 12 | home 13 | └── tom 14 | └── uploads 15 | ``` 16 | 17 | * 用户为`tom` 18 | 19 | 20 | 21 | ## 三. 报错 22 | 23 | ### 报错一 24 | 25 | ```bash 26 | permission denied 27 | ``` 28 | 29 | ### 报错二 30 | 31 | ```bash 32 | bad ownership or modes for chroot directory component "/home" 33 | ``` 34 | 35 | 36 | 37 | ## 四. 解决 38 | 39 | 以上两个报错,此处为统一解决。 40 | 41 | * 创建用户组 42 | 43 | ```bash 44 | groupadd ftp 45 | ``` 46 | 47 | * 将用户加入用户组 48 | 49 | ``` 50 | usermod -a -G ftp tom 51 | ``` 52 | 53 | * 设置权限 54 | 55 | ```bash 56 | chown root:ftp -R /home/tom 57 | chown tom:ftp -R /home/tom/uploads 58 | chmod 755 -R /home 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /202006/docker中php上传大小限制.md: -------------------------------------------------------------------------------- 1 | ## 问题 2 | 3 | 使用`docker`运行的`wordpress`,有报错`The uploaded file exceeds the upload_max_filesize directive in php.ini`。 4 | 5 | 6 | 7 | ## 过程 8 | 9 | 按照一般的方式去修改文件`php.ini`: 10 | 11 | ```bash 12 | upload_max_filesize = 30M 13 | post_max_size = 30M 14 | ``` 15 | 16 | 容器中有两个`php.ini`文件: 17 | 18 | * php.ini-development 19 | * php.ini-production 20 | 21 | 都修改之后却并不生效。 22 | 23 | 24 | 25 | ## 解决 26 | 27 | 在文件夹`conf.d`中添加文件`uploads.ini`: 28 | 29 | ```bach 30 | file_uploads = On 31 | memory_limit = 64M 32 | upload_max_filesize = 64M 33 | post_max_size = 64M 34 | max_execution_time = 600 35 | ``` 36 | 37 | 文件夹`conf.d`位置需要看具体环境: 38 | 39 | ```bash 40 | php -i | grep php.ini 41 | ``` 42 | 43 | 然后在其目录下找到文件夹`conf.d`。 44 | 45 | 也可以通过在运行容器时候直接将该文件映射进入。 -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 2 | import Home from '../views/Home.vue' 3 | 4 | const routes: Array = [ 5 | { 6 | path: '/', 7 | name: 'Home', 8 | component: Home 9 | }, 10 | { 11 | path: '/about', 12 | name: 'About', 13 | // route level code-splitting 14 | // this generates a separate chunk (about.[hash].js) for this route 15 | // which is lazy-loaded when the route is visited. 16 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') 17 | } 18 | ] 19 | 20 | const router = createRouter({ 21 | history: createWebHistory(process.env.BASE_URL), 22 | routes 23 | }) 24 | 25 | export default router 26 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import { registerMicroApps, start } from 'qiankun' 6 | 7 | const apps: any[] = [ 8 | { 9 | name: 'site1', // 应用的名字 10 | entry: 'http://localhost:9001/', // 默认加载这个html,解析里面的js动态的执行(子应用必须支持跨域,内部使用的是 fetch) 11 | container: '#site1', // 要渲染到的节点id 12 | activeRule: '/site1' // 访问子节点路由 13 | } 14 | // { 15 | // name: 'site2', 16 | // entry: 'http://localhost:9002/', 17 | // container: '#site2', 18 | // activeRule: '/site2' 19 | // } 20 | ] 21 | 22 | registerMicroApps(apps) // 注册应用 23 | start() // 开启应用 24 | 25 | createApp(App).use(store).use(router).mount('#app') 26 | -------------------------------------------------------------------------------- /202008/Vue按需引入ElementUI报错Error: Plugin:Preset files are not allowed to export objects, only functions.md: -------------------------------------------------------------------------------- 1 | ## 报错 2 | 3 | 按照ElementUI官方文档按需引入却报错,首先报错缺少`babel-preset-es2015`。安装该组件之后编译却报错。 4 | 5 | ```bash 6 | Error: Plugin/Preset files are not allowed to export objects, only functions. 7 | ``` 8 | 9 | 10 | 11 | ## 解决 12 | 13 | 该问题为`babel`版本冲突。 14 | 15 | ### 1. 安装插件 16 | 17 | ```bash 18 | yarn add @babel/preset-env 19 | ``` 20 | 21 | ### 2. 编辑`.babelrc` 22 | 23 | ```javascript 24 | { 25 | "presets": [["@babel/preset-env", { "modules": false }]], 26 | "plugins": [ 27 | [ 28 | "component", 29 | { 30 | "libraryName": "element-ui", 31 | "styleLibraryName": "theme-chalk" 32 | } 33 | ] 34 | ] 35 | } 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 2 | import Home from '../views/Home.vue' 3 | 4 | const routes: Array = [ 5 | { 6 | path: '/', 7 | name: 'Home', 8 | component: Home 9 | }, 10 | { 11 | path: '/about', 12 | name: 'About', 13 | // route level code-splitting 14 | // this generates a separate chunk (about.[hash].js) for this route 15 | // which is lazy-loaded when the route is visited. 16 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') 17 | } 18 | ] 19 | 20 | const router = createRouter({ 21 | history: createWebHistory(process.env.BASE_URL), 22 | routes 23 | }) 24 | 25 | export default router 26 | -------------------------------------------------------------------------------- /202106/SwiftUI项目实现搜索功能.md: -------------------------------------------------------------------------------- 1 | ## 一. 实现 2 | 3 | ### 1. 创建变量 4 | 5 | ```swift 6 | @State var search: String = "" 7 | ``` 8 | 9 | ### 2. 过滤 10 | 11 | 此处过滤条件为判断元素是否包含搜索的文本。 12 | 13 | ```swift 14 | .filter({"\($0)".contains(search.lowercased()) || search.isEmpty}) 15 | ``` 16 | 17 | 18 | 19 | ## 二. 汇总 20 | 21 | ```swift 22 | struct DataList: View { 23 | 24 | @State var search: String = "" 25 | @Binding var dataList: [Item] 26 | 27 | var dataSearchFilterList: [Item] { 28 | dataList.filter({"\($0)".contains(search.lowercased()) || search.isEmpty}) 29 | } 30 | 31 | var body: some View { 32 | if dataSearchFilterList.isEmpty { 33 | Text("搜索不到...") 34 | } else { 35 | ... // 展示搜索结果 36 | } 37 | } 38 | 39 | } 40 | ``` 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 2 | import Home from '../views/Home.vue' 3 | 4 | const routes: Array = [ 5 | { 6 | path: '/', 7 | name: 'Home', 8 | component: Home 9 | }, 10 | { 11 | path: '/about', 12 | name: 'About', 13 | // route level code-splitting 14 | // this generates a separate chunk (about.[hash].js) for this route 15 | // which is lazy-loaded when the route is visited. 16 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') 17 | } 18 | ] 19 | 20 | const router = createRouter({ 21 | history: createWebHistory(process.env.BASE_URL), 22 | routes 23 | }) 24 | 25 | export default router 26 | -------------------------------------------------------------------------------- /Electron/electron-builder踩坑系列---禁止用户调整窗口大小.md: -------------------------------------------------------------------------------- 1 | ## 简述 2 | 3 | 当想为程序设定一个固定的窗口大小时候,需要限制用户对程序窗口的大小进行拖动调整。但是使用官方的设定时候,在mac平台上可以禁用调整,当在windows平台上时候却依然可以。所以为了兼容两者,可以选择使用无边框来实现。 4 | 5 | 6 | 7 | ## 官方文档 8 | 9 | - `resizable` Boolean (optional) - Whether window is resizable. 默认值为 `true`。 10 | 11 | 12 | 13 | ## 实现 14 | 15 | ### mac 16 | 17 | ```javascript 18 | win = new BrowserWindow({ 19 | width: 800, 20 | height: 600, 21 | resizable: false 22 | }); 23 | ``` 24 | 25 | 26 | 27 | ### windows 28 | 29 | * 为了兼容,可选择都加上无边框 30 | 31 | ```javascript 32 | win = new BrowserWindow({ 33 | width: 800, 34 | height: 600, 35 | frame: false, 36 | resizable: false 37 | }); 38 | ``` 39 | 40 | 41 | 42 | ## 参考文档 43 | 44 | * [electron官方文档](https://www.electronjs.org/docs/api/browser-window#new-browserwindowoptions) -------------------------------------------------------------------------------- /202011/Flask设置全局错误捕获.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 代码运行过程中,意外情况会导致`500`错误,对于使用者来说体验很不好,对于开发者来说也无法及时获取错误,需要去查看日志。 4 | 5 | 并且有的插件在某些报错情况下会返回一些敏感信息,非常危险。因此需要去捕获全局错误,通知开发者,自定义错误消息等。 6 | 7 | 8 | 9 | ## 二. 实现 10 | 11 | ```python 12 | from server import app 13 | from flask import request 14 | from datetime import datetime 15 | from werkzeug.exceptions import HTTPException 16 | 17 | 18 | @app.errorhandler(Exception) 19 | def all_exception_handler(e): 20 | if isinstance(e, HTTPException): 21 | if e.code == 404: 22 | return { 23 | 'code': 40004, 24 | 'message': '404' 25 | }, 404 26 | 27 | # 通知开发者/写入日志 28 | handle(path = request.path, content = str(e)) 29 | 30 | return { 31 | 'code': 20001, 32 | 'message': 'Error' 33 | } 34 | 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /202106/SwiftUI项目复制字符串到剪切板.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 这是个比较坑的问题,我一开始开发的是macos项目,到网上搜的方案基本都是使用`UIPasteboard`方法,但是偏偏用不了。 4 | 5 | 后来开发ios项目,用macos的就不行,发现`UIPasteboard`的可行,所以这里需要清楚的是,ios和macos的复制方法是不同的...... 6 | 7 | 8 | 9 | ## 二. MacOS 10 | 11 | ### 1. 实现 12 | 13 | ```swift 14 | func copyToClipBoard(textToCopy: String) { 15 | let pasteBoard = NSPasteboard.general 16 | pasteBoard.clearContents() 17 | pasteBoard.setString(textToCopy, forType: .string) 18 | } 19 | ``` 20 | 21 | ### 2. 调用 22 | 23 | ```swift 24 | copyToClipBoard(textToCopy: "Hello,World!") 25 | ``` 26 | 27 | 28 | 29 | ## 三. IOS 30 | 31 | ### 1. 实现 32 | 33 | ```swift 34 | UIPasteboard.general.setValue(, forPasteboardType: kUTTypePlainText as String) 35 | ``` 36 | 37 | ### 2. 调用 38 | 39 | ```swift 40 | UIPasteboard.general.setValue("Hello,World!", forPasteboardType: kUTTypePlainText as String) 41 | ``` 42 | 43 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /202106/SwiftUI项目判断是否为暗黑模式.md: -------------------------------------------------------------------------------- 1 | ## 一. 实现 2 | 3 | ```swift 4 | @Environment(\.colorScheme) var colorScheme 5 | 6 | var isLight: Bool { 7 | colorScheme == .light 8 | } 9 | ``` 10 | 11 | 12 | 13 | ## 二. 调用 14 | 15 | ```swift 16 | Text("Hello, World !") 17 | .foregroundColor(isLight ? Color.red : Color.green) 18 | ``` 19 | 20 | 21 | 22 | ## 三. 完整例子 23 | 24 | ```swift 25 | import SwiftUI 26 | 27 | struct CheckIsLight: View { 28 | 29 | @Environment(\.colorScheme) var colorScheme 30 | 31 | var isLight: Bool { 32 | colorScheme == .light 33 | } 34 | 35 | var body: some View { 36 | Text("Hello, World !") 37 | .foregroundColor(isLight ? Color.red : Color.green) // 此处使用isLght实现根据暗黑模式切换字体颜色 38 | } 39 | } 40 | 41 | struct CheckIsLight_Previews: PreviewProvider { 42 | static var previews: some View { 43 | CheckIsLight() 44 | } 45 | } 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { name } = require('./package') 3 | 4 | function resolve (dir) { 5 | return path.join(__dirname, dir) 6 | } 7 | 8 | const port = 9001 9 | 10 | module.exports = { 11 | outputDir: 'dist', 12 | assetsDir: 'static', 13 | filenameHashing: true, 14 | devServer: { 15 | hot: true, 16 | disableHostCheck: true, 17 | port, 18 | overlay: { 19 | warnings: false, 20 | errors: true 21 | }, 22 | headers: { 23 | 'Access-Control-Allow-Origin': '*' 24 | } 25 | }, 26 | // 自定义webpack配置 27 | configureWebpack: { 28 | resolve: { 29 | alias: { 30 | '@': resolve('src') 31 | } 32 | }, 33 | output: { 34 | // 把子应用打包成 umd 库格式 35 | library: `${name}-[name]`, 36 | libraryTarget: 'umd', 37 | jsonpFunction: `webpackJsonp_${name}` 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /202008/Go http请求报错x509 certificate signed by unknown authority.md: -------------------------------------------------------------------------------- 1 | ## 报错 2 | 3 | 在Go中`POST`请求时报错 4 | 5 | ``` 6 | x509: certificate signed by unknown authority 7 | ``` 8 | 9 | 即无法检验证书。 10 | 11 | ```go 12 | package main 13 | 14 | import ( 15 | "net/http" 16 | ) 17 | 18 | func Handle() { 19 | 20 | ... 21 | 22 | _, err := http.Post( 23 | ... 24 | ) 25 | 26 | ... 27 | 28 | } 29 | ``` 30 | 31 | 32 | 33 | 34 | 35 | ## 解决 36 | 37 | 跳过校验即可。此处引入`"crypto/tls"`。 38 | 39 | ```go 40 | package main 41 | 42 | import ( 43 | "crypto/tls" 44 | "net/http" 45 | ) 46 | 47 | func Handle() { 48 | 49 | ... 50 | 51 | tr := &http.Transport{ 52 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 53 | } 54 | 55 | client := &http.Client{ 56 | Timeout: 15 * time.Second, 57 | Transport: tr, 58 | } 59 | 60 | _, err := client.Post( 61 | ... 62 | ) 63 | 64 | ... 65 | } 66 | ``` 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /202108/IOS监听上下左右滑动手势.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | IOS监听手势使用的方法为`UISwipeGestureRecognizer`。 4 | 5 | 6 | 7 | ## 二. 添加手势监听 8 | 9 | ```swift 10 | let gesture = UISwipeGestureRecognizer() 11 | gesture.addTarget(self, action: #selector(yourSelector(gesture:))) 12 | gesture.direction = .left // .left左滑 .right右滑 .up上滑 .down下滑 13 | self.addGestureRecognizer(gesture) 14 | ``` 15 | 16 | 17 | 18 | ## 三. 添加响应事件 19 | 20 | ```swift 21 | @objc private func leftPushEvent(){ 22 | print("响应...") 23 | } 24 | ``` 25 | 26 | 27 | 28 | ## 四. 模板 29 | 30 | 把上面的整合起来,基本可以按照这个模板来写。 31 | 32 | ```swift 33 | @objc private func leftPushEvent(){ 34 | print("响应...") 35 | } 36 | 37 | let gesture = UISwipeGestureRecognizer() 38 | gesture.addTarget(self, action: #selector(leftPushEvent(gesture:))) 39 | gesture.direction = .left 40 | self.addGestureRecognizer(gesture) 41 | ``` 42 | 43 | 44 | 45 | ## 五. 参考文档 46 | 47 | * [iOS手势识别--上下左右滑动](https://www.jianshu.com/p/30104b1c872d) -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 39 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 39 | -------------------------------------------------------------------------------- /MySQL/创建数据库表.md: -------------------------------------------------------------------------------- 1 | ## 示例 2 | 3 | ```sql 4 | create table if not exists `student` ( 5 | `id` int not null auto_increment comment '学号', 6 | `name` varchar(30) not null default '匿名' comment '姓名', 7 | `pwd` varchar(20) not null default '123456' comment '密码', 8 | `sex` varchar(2) not null default '女' comment '性别', 9 | `birthday` datetime default null comment '出生日期', 10 | `address` varchar(100) default null comment '家庭住址', 11 | `email` varchar(50) default null comment '邮箱', 12 | primary key (`id`) 13 | )engine=INNODB charset = utf8 14 | ``` 15 | 16 | 17 | 18 | ## 格式 19 | 20 | ```sql 21 | create tabel [if not exits] `表名` ( 22 | `字段名` 列类型 [属性] [索引] [注释], 23 | `字段名` 列类型 [属性] [索引] [注释], 24 | ... 25 | `字段名` 列类型 [属性] [索引] [注释] 26 | )[表类型][字符集设置][注释] 27 | ``` 28 | 29 | 30 | 31 | ## 逆向查看创建语句 32 | 33 | ```sql 34 | show create database `database_name` -- 查看创建数据库的语句 35 | show create table `table_name` -- 查看创建表的语句 36 | ``` 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/unplugin_auto_import/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /202107/node spawn在windows下不生效问题记录.md: -------------------------------------------------------------------------------- 1 | ## 问题描述 2 | 3 | 使用`electron`开发的windows桌面应用程序,在调用目标文件夹底下的exe执行文件时,开发机子上没有问题,但是其他机子使用时一直调用失败,也抓取不到日志。 4 | 5 | ```javascript 6 | spawn(path.join(remote.app.getAppPath(), "../target.exe"), [], { 7 | shell: true, 8 | detached: false, 9 | windowsHide: true 10 | }); 11 | ``` 12 | 13 | 14 | 15 | ## 原因 16 | 17 | **路径存在空格。** 18 | 19 | 也是经过各种原因排查,然后一次偶然的成功才注意到了路径问题,排查之后发现确实是这问题...... 20 | 21 | 22 | 23 | ## 解决 24 | 25 | `spawn`按照如上我的代码一定条件下可以运行,其有一个参数`cwd`,用来表明运行目录。`spawn`第一个参数必须是命令的名字,不能是路径。 26 | 27 | 所以如上代码改成这样: 28 | 29 | ```javascript 30 | spawn("target.exe", [], { // 此处直接写目标exe文件 31 | cwd: path.join(remote.app.getAppPath(), "../"), // 注意这里,使用了cwd参数来写运行目录 32 | shell: true, 33 | detached: false, 34 | windowsHide: true 35 | }); 36 | ``` 37 | 38 | 39 | 40 | ## 参考文档 41 | 42 | * [node child_process.spawn not working with spaces in path on windows](https://stackoverflow.com/questions/21356372/node-child-process-spawn-not-working-with-spaces-in-path-on-windows) -------------------------------------------------------------------------------- /Electron/electron-builder踩坑系列---无边框拖动.md: -------------------------------------------------------------------------------- 1 | ## 简述 2 | 3 | 采用无边框就自然导致拖动问题,因此需要手动去设置可拖动区域。 4 | 5 | 设置可拖动区域也会出现新的问题,如果可拖动区域上存在其他元素的事件问题。在mac上直接设置一块可拖动区域即可,而windows就会出现可拖动区域上其他元素的事件被拖动事件覆盖掉了。因此还需要对其他元素设置不可拖动。 6 | 7 | 8 | 9 | ## 官方文档 10 | 11 | 默认情况下, 无边框窗口是不可拖拽的。 应用程序需要在 CSS 中指定 `-webkit-app-region: drag` 来告诉 Electron 哪些区域是可拖拽的(如操作系统的标准标题栏),在可拖拽区域内部使用 `-webkit-app-region: no-drag` 则可以将其中部分区域排除。 请注意, 当前只支持矩形形状。 12 | 13 | 要使整个窗口可拖拽, 您可以添加 `-webkit-app-region: drag` 作为 `body` 的样式: 14 | 15 | ```html 16 | 17 | 18 | ``` 19 | 20 | 请注意,如果您使整个窗口都可拖拽,则必须将其中的按钮标记为不可拖拽,否则用户将无法点击它们: 21 | 22 | ```css 23 | button { 24 | -webkit-app-region: no-drag; 25 | } 26 | ``` 27 | 28 | 29 | 30 | ## 实现 31 | 32 | ```css 33 | .menu { 34 | -webkit-app-region: drag; 35 | } 36 | 37 | .menu-button { 38 | -webkit-app-region: no-drag; 39 | } 40 | ``` 41 | 42 | 43 | 44 | ## 参考文档 45 | 46 | * [官方文档](https://www.electronjs.org/docs/api/frameless-window#%E5%8F%AF%E6%8B%96%E6%8B%BD%E5%8C%BA) -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /202303/原来浏览器原生支持JS复制到剪切板.md: -------------------------------------------------------------------------------- 1 | ## 一. 第三方库的痛苦 2 | 3 | 在日常的前端开发中,经常需要将一些数据从网页上复制到剪切板中。而实现复制功能,第一时间想到的就是引入第三方库。 4 | 5 | 曾经过多不少第三方的剪切板的库,是真的很繁琐,又是创建对象,又是绑定DOM,头都要炸了,就个简单的复制功能,第三方库换来换去地测试...... 6 | 7 | 后来看到了vueuse可以直接用,突然觉得,哇!真棒! 8 | 9 | 直到有一天,搜到了Clipboard api...... 10 | 11 | 12 | 13 | ## 二. 原生支持 14 | 15 | ***官方文档***:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard 16 | 17 | 不管是读,还是写,统统搞定!而且都还是异步方法。 18 | 19 | 比如复制文本到剪切板: 20 | 21 | ```js 22 | navigator.clipboard.writeText("") 23 | ``` 24 | 25 | 复制canvas到剪切板: 26 | 27 | ```js 28 | function copyCanvasContentsToClipboard(canvas, onDone, onError) { 29 | canvas.toBlob((blob) => { 30 | let data = [new ClipboardItem({ [blob.type]: blob })]; 31 | 32 | navigator.clipboard.write(data).then( 33 | () => { 34 | onDone(); 35 | }, 36 | (err) => { 37 | onError(err); 38 | } 39 | ); 40 | }); 41 | } 42 | ``` 43 | 44 | 读取剪切板的文本: 45 | 46 | ```js 47 | const txt = navigator.clipboard.readText() 48 | ``` 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Nginx/反向代理.md: -------------------------------------------------------------------------------- 1 | # 反向代理 2 | 3 | 4 | 5 | ## 正则表达匹配 6 | 7 | ``` 8 | location [ = | ~ | ~* | ^~ ] uri { 9 | ... 10 | } 11 | ``` 12 | 13 | * `=`: 用户不含正则表达式的uri前,要求请求字符串与uri严格匹配,如果匹配成功,就停止继续向下搜索并立即处理该请求; 14 | * `~`: 用户表示uri包含正则表达式,并且区分大小写; 15 | * `~*`: 用户表示uri包含正则表达式,并且不区分大小写; 16 | * `^~`: 用户不含正则表达式的uri前,要求nginx服务器找到表示uri和请求字符串匹配度最高的location后,立即使用此location处理请求,而不再使用location块中的正则uri和请求字符串做匹配; 17 | > 注:如果uri包含正则表达式,则必须要有`~`和`~*`表识。 18 | 19 | 20 | 21 | 22 | 23 | ## 实例1:简单反向代理 24 | 25 | ### 实现效果 26 | 27 | * 浏览器输入`http://www.hello.com`跳转到`http://192.168.50.250:9003` 28 | 29 | 30 | 31 | ### 配置 32 | 33 | ``` 34 | server { 35 | listen 80; 36 | server_name 192.168.50.250; 37 | 38 | location / { 39 | root /usr/share/nginx/html; 40 | proxy_pass http://192.168.50.250:9003; 41 | index index.html index.htm; 42 | } 43 | 44 | } 45 | ``` 46 | 47 | 48 | 49 | ## 实例2:多个代理且包含路径 50 | 51 | ### 实现效果 52 | 53 | * `/server1` -> 'http://192.168.50.250:9003' 54 | * `/server2` -> 'http://192.168.50.250:9004' 55 | 56 | 57 | 58 | ### 配置 59 | 60 | -------------------------------------------------------------------------------- /202003/v-charts添加图表标题.md: -------------------------------------------------------------------------------- 1 | ## 一. 场景 2 | 使用 `v-charts` 做数据可视化,需要给图表添加标题。 3 | 4 | ## 二. 解决方法 5 | v-charts本身并没有提供显示标题的配置,顾需要引入 `echarts` 的 `title` 。 6 | 7 | ## 三. 实现 8 | 9 | ### 1. 引入title 10 | ```javascript 11 | import "echarts/lib/component/title"; 12 | ``` 13 | 14 | ### 2. 添加标题配置 15 | ```javascript 16 | this.chartTitle = { 17 | text: "平台用户与创客数量对比图", 18 | textStyle: { 19 | fontWeight: 600, 20 | color: "white" 21 | } 22 | }; 23 | ``` 24 | 25 | ### 3. 使用 26 | ```html 27 | 28 | ``` 29 | 30 | ### 4. 完整实现 31 | ```vue 32 | 35 | 36 | 51 | ``` 52 | 53 | ## 四. echarts配置手册 54 | * https://www.echartsjs.com/zh/option.html#title 55 | 56 | ## 五. 参考文章 57 | * https://github.com/ElemeFE/v-charts/issues/191 -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/i18n.ts: -------------------------------------------------------------------------------- 1 | import { createI18n, LocaleMessages, VueMessageType } from 'vue-i18n' 2 | 3 | /** 4 | * Load locale messages 5 | * 6 | * The loaded `JSON` locale messages is pre-compiled by `@intlify/vue-i18n-loader`, which is integrated into `vue-cli-plugin-i18n`. 7 | * See: https://github.com/intlify/vue-i18n-loader#rocket-i18n-resource-pre-compilation 8 | */ 9 | function loadLocaleMessages (): LocaleMessages { 10 | const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i) 11 | const messages: LocaleMessages = {} 12 | locales.keys().forEach(key => { 13 | const matched = key.match(/([A-Za-z0-9-_]+)\./i) 14 | if (matched && matched.length > 1) { 15 | const locale = matched[1] 16 | messages[locale] = locales(key).default 17 | } 18 | }) 19 | return messages 20 | } 21 | 22 | export default createI18n({ 23 | legacy: false, 24 | locale: process.env.VUE_APP_I18N_LOCALE || 'en_GB', 25 | fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en_GB', 26 | messages: loadLocaleMessages() 27 | }) 28 | -------------------------------------------------------------------------------- /202108/Interval计时器在tab页切换或者隐藏情况下停止运行.md: -------------------------------------------------------------------------------- 1 | ## 问题 2 | 3 | 开始是开发`electron`时遇到的问题,使用`Interval`计时器,在窗口最小化隐藏再打开,计时器在隐藏期间并没有工作。 4 | 5 | 后来网上查询相关问题,发现更多是在浏览器tab页隐藏/切换情况下,计时器就会停止。 6 | 7 | 8 | 9 | ## 解决 10 | 11 | > 在后台选项卡上运行的计时器方法可能会耗尽资源。在后台选项卡中以非常短的时间间隔运行回调的应用程序可能会消耗大量内存,以至于当前活动选项卡的工作可能会受到影响。在最坏的情况下,浏览器可能会崩溃,或者设备的电池会很快耗尽。 12 | 13 | 此限制是浏览器限制的。 14 | 15 | 无法突破限制,但是可以使用折中的方式,当然我也觉得此方式相较于一直计时会更优,即监听`visibilitychange`事件。 16 | 17 | `visibilitychange`事件可以监听tab页面的激活与失活事件,因此可以: 18 | 19 | * 在失活时,记录计时器计算的最后的值,清空计时器 20 | * 在激活时,计算失活期间应有的值,继续使用计时器计算 21 | 22 | 23 | 24 | ### 添加事件代码如下: 25 | 26 | ```javascript 27 | document.addEventListener('visibilitychange', function() { 28 | if(document.hidden) { 29 | // tab页失活 30 | 31 | } 32 | else { 33 | // tab页激活 34 | 35 | } 36 | }); 37 | ``` 38 | 39 | 40 | 41 | ## 参考文档 42 | 43 | * [https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event) 44 | 45 | * [https://usefulangle.com/post/280/settimeout-setinterval-on-inactive-tab](https://usefulangle.com/post/280/settimeout-setinterval-on-inactive-tab) -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 44 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site_1", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "vue": "^3.0.0", 13 | "vue-router": "^4.0.0-0", 14 | "vuex": "^4.0.0-0" 15 | }, 16 | "devDependencies": { 17 | "@typescript-eslint/eslint-plugin": "^4.18.0", 18 | "@typescript-eslint/parser": "^4.18.0", 19 | "@vue/cli-plugin-babel": "~4.5.0", 20 | "@vue/cli-plugin-eslint": "~4.5.0", 21 | "@vue/cli-plugin-router": "~4.5.0", 22 | "@vue/cli-plugin-typescript": "~4.5.0", 23 | "@vue/cli-plugin-vuex": "~4.5.0", 24 | "@vue/cli-service": "~4.5.0", 25 | "@vue/compiler-sfc": "^3.0.0", 26 | "@vue/eslint-config-standard": "^5.1.2", 27 | "@vue/eslint-config-typescript": "^7.0.0", 28 | "eslint": "^6.7.2", 29 | "eslint-plugin-import": "^2.20.2", 30 | "eslint-plugin-node": "^11.1.0", 31 | "eslint-plugin-promise": "^4.2.1", 32 | "eslint-plugin-standard": "^4.0.0", 33 | "eslint-plugin-vue": "^7.0.0", 34 | "typescript": "~4.1.5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Electron/electron升级后在Mac上报错:Exit code: ENOENT. spawn :usr:bin:python ENOENT.md: -------------------------------------------------------------------------------- 1 | ## 报错 2 | 3 | `electron`升级到当前最新版(13.0.0),却在mac上打包时报错,报错内容如下: 4 | 5 | ``` 6 | Exit code: ENOENT. spawn /usr/bin/python ENOENT 7 | ``` 8 | 9 | 10 | 11 | ## 环境 12 | 13 | * **macOS**: 12.3.1 14 | * **electron**: `13.0.0` 15 | * **electron-builder**: `23.0.3` 16 | * **vue-cli-plugin-electron-builder**: `2.1.1` 17 | 18 | 19 | 20 | ## 解决 21 | 22 | 在`package.json`中添加如下: 23 | 24 | ```json 25 | { 26 | // ... 27 | "resolutions": { 28 | "vue-cli-plugin-electron-builder/electron-builder": "^23.0.3" 29 | } 30 | // ... 31 | } 32 | ``` 33 | 34 | 该问题是由于mac系统升级后默认`python`命令是指向`python3`的,但是`vue-cli-plugin-electron-builder`是要求`python2`的,但是`electron-builder`是支持的,所以此处指定其使用`electron-builder v23.0.3`版本。 35 | 36 | 网上还有另一种解决方案就是将`python`重新指向`python2`,这种方案对于系统来说侵入性太强。我也考虑过起一个python2的docker continer然后临时指向,但是这样每次写代码还要设置下环境就很麻烦。 37 | 38 | 总体来说,还是觉得当前这个方案最方便了。 39 | 40 | 41 | 42 | ## 参考文档 43 | 44 | * [macOS 12.3 (21E230) Exit code: ENOENT. spawn /usr/bin/python ENOENT](https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/1701) 45 | * [Selective dependency resolutions](https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/) 46 | 47 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_1/main.js: -------------------------------------------------------------------------------- 1 | // main.js 2 | 3 | // 控制应用生命周期和创建原生浏览器窗口的模组 4 | const { app, BrowserWindow } = require('electron') 5 | const path = require('path') 6 | 7 | function createWindow () { 8 | // 创建浏览器窗口 9 | const mainWindow = new BrowserWindow({ 10 | width: 800, 11 | height: 600, 12 | webPreferences: { 13 | preload: path.join(__dirname, 'preload.js') 14 | } 15 | }) 16 | 17 | // 加载 index.html 18 | mainWindow.loadFile('dist/index.html') // 此处跟electron官网路径不同,需要注意 19 | 20 | // 打开开发工具 21 | // mainWindow.webContents.openDevTools() 22 | } 23 | 24 | // 这段程序将会在 Electron 结束初始化 25 | // 和创建浏览器窗口的时候调用 26 | // 部分 API 在 ready 事件触发后才能使用。 27 | app.whenReady().then(() => { 28 | createWindow() 29 | 30 | app.on('activate', function () { 31 | // 通常在 macOS 上,当点击 dock 中的应用程序图标时,如果没有其他 32 | // 打开的窗口,那么程序会重新创建一个窗口。 33 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 34 | }) 35 | }) 36 | 37 | // 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在 38 | // 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。 39 | app.on('window-all-closed', function () { 40 | if (process.platform !== 'darwin') app.quit() 41 | }) 42 | 43 | // 在这个文件中,你可以包含应用程序剩余的所有部分的代码, 44 | // 也可以拆分成几个文件,然后用 require 导入。 45 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site_base", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "qiankun": "^2.6.3", 13 | "vue": "^3.0.0", 14 | "vue-router": "^4.0.0-0", 15 | "vuex": "^4.0.0-0" 16 | }, 17 | "devDependencies": { 18 | "@typescript-eslint/eslint-plugin": "^4.18.0", 19 | "@typescript-eslint/parser": "^4.18.0", 20 | "@vue/cli-plugin-babel": "~4.5.0", 21 | "@vue/cli-plugin-eslint": "~4.5.0", 22 | "@vue/cli-plugin-router": "~4.5.0", 23 | "@vue/cli-plugin-typescript": "~4.5.0", 24 | "@vue/cli-plugin-vuex": "~4.5.0", 25 | "@vue/cli-service": "~4.5.0", 26 | "@vue/compiler-sfc": "^3.0.0", 27 | "@vue/eslint-config-standard": "^5.1.2", 28 | "@vue/eslint-config-typescript": "^7.0.0", 29 | "eslint": "^6.7.2", 30 | "eslint-plugin-import": "^2.20.2", 31 | "eslint-plugin-node": "^11.1.0", 32 | "eslint-plugin-promise": "^4.2.1", 33 | "eslint-plugin-standard": "^4.0.0", 34 | "eslint-plugin-vue": "^7.0.0", 35 | "typescript": "~4.1.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_2/main.js: -------------------------------------------------------------------------------- 1 | // main.js 2 | 3 | // 控制应用生命周期和创建原生浏览器窗口的模组 4 | const { app, BrowserWindow } = require('electron') 5 | const path = require('path') 6 | 7 | function createWindow () { 8 | // 创建浏览器窗口 9 | const mainWindow = new BrowserWindow({ 10 | width: 800, 11 | height: 600, 12 | webPreferences: { 13 | preload: path.join(__dirname, 'preload.js') 14 | } 15 | }) 16 | 17 | // 加载 index.html 18 | // mainWindow.loadFile('dist/index.html') 将该行改为下面这一行,加载url 19 | mainWindow.loadURL("http://localhost:3000") 20 | 21 | // 打开开发工具 22 | // mainWindow.webContents.openDevTools() 23 | } 24 | 25 | // 这段程序将会在 Electron 结束初始化 26 | // 和创建浏览器窗口的时候调用 27 | // 部分 API 在 ready 事件触发后才能使用。 28 | app.whenReady().then(() => { 29 | createWindow() 30 | 31 | app.on('activate', function () { 32 | // 通常在 macOS 上,当点击 dock 中的应用程序图标时,如果没有其他 33 | // 打开的窗口,那么程序会重新创建一个窗口。 34 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 35 | }) 36 | }) 37 | 38 | // 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在 39 | // 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。 40 | app.on('window-all-closed', function () { 41 | if (process.platform !== 'darwin') app.quit() 42 | }) 43 | 44 | // 在这个文件中,你可以包含应用程序剩余的所有部分的代码, 45 | // 也可以拆分成几个文件,然后用 require 导入。 46 | 47 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site_base", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^3.6.5", 12 | "nprogress": "^0.2.0", 13 | "qiankun": "^2.6.3", 14 | "vue": "^3.0.0", 15 | "vue-router": "^4.0.0-0", 16 | "vuex": "^4.0.0-0" 17 | }, 18 | "devDependencies": { 19 | "@typescript-eslint/eslint-plugin": "^4.18.0", 20 | "@typescript-eslint/parser": "^4.18.0", 21 | "@vue/cli-plugin-babel": "~4.5.0", 22 | "@vue/cli-plugin-eslint": "~4.5.0", 23 | "@vue/cli-plugin-router": "~4.5.0", 24 | "@vue/cli-plugin-typescript": "~4.5.0", 25 | "@vue/cli-plugin-vuex": "~4.5.0", 26 | "@vue/cli-service": "~4.5.0", 27 | "@vue/compiler-sfc": "^3.0.0", 28 | "@vue/eslint-config-standard": "^5.1.2", 29 | "@vue/eslint-config-typescript": "^7.0.0", 30 | "eslint": "^6.7.2", 31 | "eslint-plugin-import": "^2.20.2", 32 | "eslint-plugin-node": "^11.1.0", 33 | "eslint-plugin-promise": "^4.2.1", 34 | "eslint-plugin-standard": "^4.0.0", 35 | "eslint-plugin-vue": "^7.0.0", 36 | "typescript": "~4.1.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kuari", 3 | "version": "0.0.0", 4 | "main": "electron/main.js", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "serve": "vite preview", 9 | "electron": "wait-on tcp:3000 && cross-env NODE_ENV=development electron .", 10 | "electron:serve": "concurrently -k \"yarn dev\" \"yarn electron\"", 11 | "electron:build": "vite build && electron-builder" 12 | }, 13 | "dependencies": { 14 | "vue": "^3.2.16" 15 | }, 16 | "devDependencies": { 17 | "@vitejs/plugin-vue": "^1.9.3", 18 | "concurrently": "^6.3.0", 19 | "cross-env": "^7.0.3", 20 | "electron": "^15.1.2", 21 | "electron-builder": "^22.13.1", 22 | "vite": "^2.6.4", 23 | "wait-on": "^6.0.0" 24 | }, 25 | "build": { 26 | "appId": "com.my-website.my-app", 27 | "productName": "MyApp", 28 | "copyright": "Copyright © 2021 kuari", 29 | "mac": { 30 | "category": "public.app-category.utilities" 31 | }, 32 | "nsis": { 33 | "oneClick": false, 34 | "allowToChangeInstallationDirectory": true 35 | }, 36 | "files": [ 37 | "dist/**/*", 38 | "electron/**/*" 39 | ], 40 | "directories": { 41 | "buildResources": "assets", 42 | "output": "dist_electron" 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /202106/Swift计算两个日期的天数差.md: -------------------------------------------------------------------------------- 1 | ## 一. 官方方法 2 | 3 | ### **DateComponents** 4 | 5 | A date or time specified in terms of units (such as year, month, day, hour, and minute) to be evaluated in a calendar system and time zone. 6 | 7 | 以要在日历系统和时区中计算的单位(例如年、月、日、小时和分钟)指定的日期或时间。 8 | 9 | 10 | 11 | ## 二. 实现 12 | 13 | ### 1. 计算两个字符串形式的日期的天数差 14 | 15 | ```swift 16 | func dateDiff() -> Int { 17 | // 计算两个日期差,返回相差天数 18 | let formatter = DateFormatter() 19 | let calendar = Calendar.current 20 | formatter.dateFormat = "yyyy-MM-dd" 21 | let today = Date() 22 | 23 | // 开始日期 24 | let startDate = formatter.date(from: "2021-06-08") 25 | 26 | // 结束日期 27 | let endDate = formatter.date(from: "2021-06-09") 28 | let diff:DateComponents = calendar.dateComponents([.day], from: startDate!, to: endDate!) 29 | return diff.day! 30 | } 31 | ``` 32 | 33 | 34 | 35 | ### 2. 计算当天跟某一天的天数差 36 | 37 | ```swift 38 | func checkDiff() -> Int { 39 | // 计算两个日期差,返回相差天数 40 | let formatter = DateFormatter() 41 | let calendar = Calendar.current 42 | formatter.dateFormat = "yyyy-MM-dd" 43 | 44 | // 当天 45 | let today = Date() 46 | let startDate = formatter.date(from: formatter.string(from: today)) 47 | 48 | // 固定日期 49 | let endDate = formatter.date(from: "2021-06-09") 50 | 51 | let diff:DateComponents = calendar.dateComponents([.day], from: startDate!, to: endDate!) 52 | return diff.day! 53 | } 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /202107/SwiftUI MacOS项目alert弹出两次问题解决.md: -------------------------------------------------------------------------------- 1 | ## 问题 2 | 3 | 使用`Alert`时,将其用在`list`的循环视图元素中,弹出`Alert`时,一定时长不选择就会在点击后弹出第二次。 4 | 5 | 这里提一下就是之前在网上看到一个帖子说他将`Alert`放在`NavigationView`上也会出现该问题。 6 | 7 | ```swift 8 | VStask { 9 | ForEach(items, id: \.self) { item in 10 | ElementView(item: item) // 循环中的元素 11 | .alert(isPresented: $showAlert) { 12 | Alert( 13 | title: Text("删除确认"), 14 | message: Text("请问您确认删除该数据吗?"), 15 | primaryButton: .default( 16 | Text("取消"), 17 | action: { 18 | showAlert = false 19 | } 20 | ), 21 | secondaryButton: .destructive( 22 | Text("删除"), 23 | action: { 24 | deleteItems(offsets: [index]) 25 | }) 26 | ) 27 | } 28 | } 29 | } 30 | 31 | ``` 32 | 33 | 34 | 35 | ## 解决 36 | 37 | 将`Alert`放到循环之前的元素上,比如`VStack`、`List`。 38 | 39 | 40 | 41 | ## 参考 42 | 43 | * [参考帖子](https://www.reddit.com/r/swift/comments/oahztb/swiftui_alert_showing_twice_despite_only_setting/) 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test_i18n", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"" 10 | }, 11 | "dependencies": { 12 | "core-js": "^3.6.5", 13 | "vue": "^3.0.0", 14 | "vue-i18n": "^9.1.0", 15 | "vue-router": "^4.0.0-0", 16 | "vuex": "^4.0.0-0" 17 | }, 18 | "devDependencies": { 19 | "@intlify/vue-i18n-loader": "^3.0.0", 20 | "@typescript-eslint/eslint-plugin": "^4.18.0", 21 | "@typescript-eslint/parser": "^4.18.0", 22 | "@vue/cli-plugin-babel": "~4.5.0", 23 | "@vue/cli-plugin-eslint": "~4.5.0", 24 | "@vue/cli-plugin-router": "~4.5.0", 25 | "@vue/cli-plugin-typescript": "~4.5.0", 26 | "@vue/cli-plugin-vuex": "~4.5.0", 27 | "@vue/cli-service": "~4.5.0", 28 | "@vue/compiler-sfc": "^3.0.0", 29 | "@vue/eslint-config-standard": "^5.1.2", 30 | "@vue/eslint-config-typescript": "^7.0.0", 31 | "eslint": "^6.7.2", 32 | "eslint-plugin-import": "^2.20.2", 33 | "eslint-plugin-node": "^11.1.0", 34 | "eslint-plugin-promise": "^4.2.1", 35 | "eslint-plugin-standard": "^4.0.0", 36 | "eslint-plugin-vue": "^7.0.0", 37 | "typescript": "~4.1.5", 38 | "vue-cli-plugin-i18n": "~2.3.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Examples/vite_electron/vite_electron_3/electron/main.js: -------------------------------------------------------------------------------- 1 | // electron/main.js 2 | 3 | // 控制应用生命周期和创建原生浏览器窗口的模组 4 | const { app, BrowserWindow } = require('electron') 5 | const path = require('path') 6 | 7 | const NODE_ENV = process.env.NODE_ENV 8 | 9 | function createWindow () { 10 | // 创建浏览器窗口 11 | const mainWindow = new BrowserWindow({ 12 | width: 800, 13 | height: 600, 14 | webPreferences: { 15 | preload: path.join(__dirname, 'preload.js') 16 | } 17 | }) 18 | 19 | // 加载 index.html 20 | // mainWindow.loadFile('dist/index.html') 将该行改为下面这一行,加载url 21 | mainWindow.loadURL( 22 | NODE_ENV === 'development' 23 | ? 'http://localhost:3000' 24 | :`file://${path.join(__dirname, '../dist/index.html')}` 25 | ); 26 | 27 | // 打开开发工具 28 | if (NODE_ENV === "development") { 29 | mainWindow.webContents.openDevTools() 30 | } 31 | 32 | } 33 | 34 | // 这段程序将会在 Electron 结束初始化 35 | // 和创建浏览器窗口的时候调用 36 | // 部分 API 在 ready 事件触发后才能使用。 37 | app.whenReady().then(() => { 38 | createWindow() 39 | 40 | app.on('activate', function () { 41 | // 通常在 macOS 上,当点击 dock 中的应用程序图标时,如果没有其他 42 | // 打开的窗口,那么程序会重新创建一个窗口。 43 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 44 | }) 45 | }) 46 | 47 | // 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在 48 | // 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。 49 | app.on('window-all-closed', function () { 50 | if (process.platform !== 'darwin') app.quit() 51 | }) 52 | 53 | // 在这个文件中,你可以包含应用程序剩余的所有部分的代码, 54 | // 也可以拆分成几个文件,然后用 require 导入。 -------------------------------------------------------------------------------- /202008/Docker修改时区.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 在使用`Docker`时,其默认时区并非使用者所在时区,需要进行修改。对于单个容器,当前修改有几种常见方式,比如直接映射宿主机时区到容器内,而本文介绍的为使用`Dockerfile`来直接修改镜像时区。此处仅以常见几个基础容器为例来介绍。 4 | 5 | 6 | 7 | ## 二. 常见容器 8 | 9 | ### 1. **Alpine** 10 | 11 | ```Dockerfile 12 | FROM alpine:latest 13 | 14 | # 安装tzdata 15 | RUN apk add --no-cache tzdata 16 | 17 | # 设置时区 18 | ENV TZ="Asia/Shanghai" 19 | 20 | ``` 21 | 22 | * 验证 23 | 24 | ```bash 25 | docker build -t alpine:time . 26 | docker run --rm -it alpine:time date 27 | ``` 28 | 29 | 30 | 31 | ### 2. **Ubuntu** 32 | 33 | ```Dockerfile 34 | FROM ubuntu 35 | 36 | # 设置localtime 37 | # 此处需要优先设置localtime,否则安装tzdata将会进入时区选择 38 | RUN ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 39 | 40 | # 安装tzdata 41 | RUN apt-get update \ 42 | && apt-get install tzdata -y \ 43 | && apt-get clean 44 | 45 | ``` 46 | 47 | * 验证 48 | 49 | ```bash 50 | docker build -t ubuntu:time . 51 | docker run --rm -it ubuntu:time date 52 | ``` 53 | 54 | 55 | 56 | ### 3. Debian 57 | 58 | * Debian中已经安装了`tzdata`,所以跟`Ubuntu`有所不通过 59 | 60 | ```dockerfile 61 | FROM debian 62 | 63 | # 修改设置dpkg为自动配置 64 | ENV DEBIAN_FRONTEND=noninteractive 65 | 66 | RUN ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 67 | 68 | RUN dpkg-reconfigure -f noninteractive tzdata 69 | 70 | # 修改设置dpkg为手动输入选择操作 71 | ENV DEBIAN_FRONTEND=dialog 72 | ``` 73 | 74 | * 验证 75 | 76 | ```bash 77 | docker build -t debian:time . 78 | docker run --rm -it debian:time date 79 | ``` 80 | 81 | 82 | 83 | ## 三. 结语 84 | 85 | 此处不再列举太多,主要解决方式为安装`tzdata`,然后修改时区。 -------------------------------------------------------------------------------- /202006/Linux分享文件?快速创建静态文件服务器.md: -------------------------------------------------------------------------------- 1 | ## 一. 需求 2 | 3 | Linux对于开发者来说极其友好,但是由于国内主流办公产品相关的生态较为匮乏,因此如何使用Linux去分享文件是一件十分头疼的问题。 4 | 5 | 对于这个问题,可以直接使用静态文件服务器解决部分需求,如下介绍几个常见方法。 6 | 7 | 8 | 9 | ## 二. 语言类 10 | 11 | ### 一. Python 12 | 13 | 对于`Python`来说,可以直接使用内置的库来实现。 14 | 15 | * python2 16 | 17 | ```python 18 | python -m SimpleHTTPServer 8000 19 | ``` 20 | 21 | * Python3 22 | 23 | ```python 24 | python -m http.server 8000 25 | ``` 26 | 27 | 28 | 29 | ### 二. Node.js 30 | 31 | `node`生态内有一个项目`http-server`,直接`V8`引擎带你飞。 32 | 33 | 1. 安装 34 | 35 | * Npm 36 | 37 | ```bash 38 | npm install --global http-server 39 | ``` 40 | 41 | * Homebrew 42 | 43 | ```bash 44 | brew install http-server 45 | ``` 46 | 47 | 48 | 49 | 2. 运行 50 | 51 | ```bash 52 | http-server [path] [options] 53 | ``` 54 | 55 | 例如: 56 | 57 | ```bash 58 | cd exmaple/ 59 | http-server 60 | ``` 61 | 62 | 63 | 64 | 3. 项目仓库地址 65 | 66 | https://github.com/http-party/http-server 67 | 68 | 69 | 70 | ## 三. 服务类 71 | 72 | 1. Nginx/Apache 73 | 74 | `Nginx`和`Apache`本身可用于静态文件服务器,这就需要用户直接在本地安装。 75 | 76 | 当然,`nginx`需要注意配置一下,打开索引: 77 | 78 | ``` 79 | server { 80 | listen 80; 81 | ... 82 | 83 | location / { 84 | root /usr/share/nginx/html; 85 | autoindex on; 86 | } 87 | } 88 | ``` 89 | 90 | 91 | 92 | 2. Docker 93 | 94 | 使用`Docker`其实也是使用如`Nginx`来实现静态文件服务器,但是容器化在该场景存在几大优势: 95 | 96 | * 即开即用 97 | * 环境隔离 98 | 99 | 相对于直接安装`Nginx`或者`Apache`,更推荐使用`Docker`。 100 | 101 | -------------------------------------------------------------------------------- /202206/python3 socket udp example.md: -------------------------------------------------------------------------------- 1 | ## socket udp server 2 | 3 | ```python 4 | import socket 5 | 6 | 7 | def socket_udp_server(server_ip: str = '0.0.0.0', server_port: int = 9000, buffer_size: int = 1024): 8 | """ 9 | socket udp 服务端 10 | 11 | :param server_ip: 服务器的地址, 默认为0.0.0.0, 表示允许所有 12 | :param server_port: 服务器udp server接收信息的端口, 默认9000 13 | :param buffer_size: 套接字缓冲区大小, 默认1024 14 | :return: none 15 | """ 16 | udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 17 | udp_socket.bind((server_ip, server_port)) 18 | print('服务端开始运行...\n') 19 | 20 | while True: 21 | receive_data, sender_info = udp_socket.recvfrom(buffer_size) 22 | print('客户端地址: {}'.format(sender_info)) 23 | print('来自客户端的信息: {}'.format(receive_data.decode('utf-8'))) 24 | 25 | 26 | if __name__ == '__main__': 27 | socket_udp_server() 28 | 29 | ``` 30 | 31 | 32 | 33 | ## socket udp client 34 | 35 | ```python 36 | import socket 37 | 38 | 39 | def socket_udp_client_send_message(message: str, server_ip: str, server_port: int): 40 | """ 41 | socket udp 客户端发送消息 42 | 43 | :param message: 消息 44 | :param server_ip: 服务端的ip地址 45 | :param server_port: 服务端的端口号 46 | :return: none 47 | """ 48 | udp_client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) 49 | udp_client_socket.sendto(str.encode(message), (server_ip, server_port)) 50 | 51 | 52 | if __name__ == '__main__': 53 | socket_udp_client_send_message('hello,world!', '127.0.0.1', 9000) 54 | 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /Nginx/负载均衡.md: -------------------------------------------------------------------------------- 1 | # 负载均衡 2 | 3 | 4 | 5 | ## 配置 6 | 7 | ``` 8 | http { 9 | ... 10 | 11 | upstream myserver { 12 | server 192.168.50.90:8000; 13 | server 192.168.50.90:8001; 14 | server 192.168.50.90:8002; 15 | } 16 | 17 | server { 18 | listen 80; 19 | server_name 192.168.50.250; 20 | 21 | location / { 22 | proxy_pass http://myserver; 23 | } 24 | 25 | } 26 | } 27 | ``` 28 | 29 | 30 | 31 | 32 | 33 | ## Nginx分配服务器策略 34 | 35 | * 轮询(默认) 36 | 37 | * 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器挂掉,能自动剔除 38 | 39 | ``` 40 | upstream myserver { 41 | server 192.168.50.90:8000; 42 | server 192.168.50.90:8001; 43 | server 192.168.50.90:8002; 44 | } 45 | ``` 46 | 47 | 48 | 49 | * weight 50 | 51 | * weight代表权重,默认为1,权重越高被分配的客户端越多 52 | 53 | ``` 54 | upstream myserver { 55 | server 192.168.50.90:8000 weight=10; 56 | server 192.168.50.90:8001 weight=20; 57 | server 192.168.50.90:8002 weight=30; 58 | } 59 | ``` 60 | 61 | 62 | 63 | * ip hash 64 | 65 | * 每个请求按照访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可使session生效 66 | 67 | ``` 68 | upstream myserver { 69 | ip_hash; 70 | server 192.168.50.90:8000; 71 | server 192.168.50.90:8001; 72 | server 192.168.50.90:8002; 73 | } 74 | ``` 75 | 76 | 77 | 78 | * fair(第三方) 79 | 80 | * 按后端服务器的响应时间来分配请求,响应时间短的优先分配 81 | 82 | ``` 83 | upstream myserver { 84 | server 192.168.50.90:8000; 85 | server 192.168.50.90:8001; 86 | server 192.168.50.90:8002; 87 | fair; 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Electron/electron-builder踩坑系列---tray系统托盘.md: -------------------------------------------------------------------------------- 1 | ## 简述 2 | 3 | 窗口最小化或者关闭的情况下,进程未退出,需要通过系统托盘来查看,当然还需要托盘菜单。这里就用最简单的菜单实现,加上点击事件触发。 4 | 5 | 6 | 7 | ## 官方文档 8 | 9 | ```javascript 10 | const { app, Menu, Tray } = require('electron') 11 | 12 | let tray = null 13 | app.whenReady().then(() => { 14 | tray = new Tray('/path/to/my/icon') 15 | const contextMenu = Menu.buildFromTemplate([ 16 | { label: 'Item1', type: 'radio' }, 17 | { label: 'Item2', type: 'radio' }, 18 | { label: 'Item3', type: 'radio', checked: true }, 19 | { label: 'Item4', type: 'radio' } 20 | ]) 21 | tray.setToolTip('This is my application.') 22 | tray.setContextMenu(contextMenu) 23 | }) 24 | ``` 25 | 26 | 27 | 28 | ## 实现 29 | 30 | ```javascript 31 | let tray = null; 32 | app.whenReady().then(() => { 33 | const iconUrl = 34 | process.env.NODE_ENV === "development" 35 | ? path.join(__dirname, "../build/favicon.ico") 36 | : path.join(__dirname, "favicon.ico"); 37 | tray = new Tray(nativeImage.createFromPath(iconUrl)); 38 | 39 | let trayMenuTemplate = [ 40 | { 41 | label: "显示/隐藏", 42 | click: function() { 43 | return win.isVisible() ? win.hide() : win.show(); 44 | } 45 | }, 46 | { 47 | label: "退出", 48 | click: function() { 49 | app.quit(); 50 | } 51 | } 52 | ]; 53 | const contextMenu = Menu.buildFromTemplate(trayMenuTemplate); 54 | tray.setToolTip("kuari"); 55 | tray.setContextMenu(contextMenu); 56 | }); 57 | ``` 58 | 59 | 60 | 61 | ## 参考文档 62 | 63 | * [官方文档](https://www.electronjs.org/docs/api/tray) -------------------------------------------------------------------------------- /202108/Golang实现农历转换阳历.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 因为项目需求,需要去检测用户的农历生日。虽然后来找到了合适的库,但是首先先解释下`农历`的定义,也是去了解才知道,原来农历不是阴历。 4 | 5 | 农历属于阴阳合历,其年份分为平年和闰年。平年为十二个月,闰年为十三个月。月份分为大月和小月,大月三十天,小月二十九天,其平均历月等于一个朔望月。 6 | 7 | 8 | 9 | ## 二. 环境 10 | 11 | * Go 1.16 12 | * github.com/nosixtools/solarlunar 0.0.0 13 | 14 | 15 | 16 | ## 三. 库 17 | 18 | ```go 19 | github.com/nosixtools/solarlunar 20 | ``` 21 | 22 | 该库支持1900~2049年。所以项目要跑到2049年后的童鞋就要注意...... 23 | 24 | 当然,该库还支持阳历转农历、节假日计算等,有兴趣大家可以自行去了解下。 25 | 26 | 27 | 28 | ## 四. 使用 29 | 30 | ### 1. 判断闰年 31 | 32 | 该库不支持闰年判断,所以需要自己去实现闰年的判断,其参数类型为`Boolean`。 33 | 34 | ```go 35 | func IsALeapYear(year int) (result bool) { 36 | if year%4 == 0 && year%100 != 0 || year%400 == 0 { 37 | result = true 38 | return 39 | } 40 | return 41 | } 42 | ``` 43 | 44 | 45 | 46 | ### 2. 转换 47 | 48 | 需要转换的阳历日期格式是固定的,是`2006-01-02`。此处以农历`2021-07-17`为例。 49 | 50 | ```go 51 | func main() { 52 | lunarDate := "2021-07-17" 53 | fmt.Println(solarlunar.LunarToSolar(lunarDate, IsALeapYear(time.Now().Year()))) 54 | } 55 | ``` 56 | 57 | 输出为: 58 | 59 | ```go 60 | 2021-08-24 61 | ``` 62 | 63 | 64 | 65 | ## 五. 全部代码 66 | 67 | ```go 68 | package main 69 | 70 | import ( 71 | "fmt" 72 | "time" 73 | 74 | "github.com/nosixtools/solarlunar" 75 | ) 76 | 77 | func main() { 78 | lunarDate := "2021-07-17" 79 | fmt.Println(solarlunar.LunarToSolar(lunarDate, IsALeapYear(time.Now().Year()))) 80 | } 81 | 82 | func IsALeapYear(year int) (result bool) { 83 | if year%4 == 0 && year%100 != 0 || year%400 == 0 { 84 | result = true 85 | return 86 | } 87 | return 88 | } 89 | ``` 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /MySQL/数据类型.md: -------------------------------------------------------------------------------- 1 | ## 数值 2 | 3 | | 类型 | 说明 | 长度 | 备注 | 4 | | --------- | ------------------ | ------- | -------------------- | 5 | | tinyint | 十分小的数据 | 1个字节 | | 6 | | smallint | 较小的数据 | 2个字节 | | 7 | | mediumint | 中等大小的数据 | 3个字节 | | 8 | | int | 标准的整数 | 4个字节 | 常用的int | 9 | | bigint | 较大的数据 | 8个字节 | | 10 | | float | 浮点数 | 4个字节 | | 11 | | double | 浮点数 | 8个字节 | 精度问题 | 12 | | decimal | 字符串形式的浮点数 | | 金融计算时一般使用此 | 13 | 14 | 15 | 16 | ## 字符串 17 | 18 | | 类型 | 说明 | 长度 | 备注 | 19 | | -------- | ---------------- | -------- | ---------- | 20 | | char | 固定大小的字符串 | 0-255 | | 21 | | varchar | 可变字符串 | 0-65535 | 常用 | 22 | | tinytext | 微型文本 | 2^8 - 1 | | 23 | | text | 文本串 | 2^16 - 1 | 保存大文本 | 24 | 25 | 26 | 27 | ## 时间日期 28 | 29 | | 类型 | 说明 | 长度 | 备注 | 30 | | --------- | ------------------------------ | ---- | ---------------- | 31 | | date | YYYY-MM-DD 日期时间 | | | 32 | | time | HH: mm: ss 时间格式 | | | 33 | | datetime | YYYY-MM-DD HH: mm: ss | | 最常用的时间格式 | 34 | | timestamp | 时间戳,1970.1.1到现在的毫秒数 | | 较为常用 | 35 | | year | 年份表示 | | | 36 | 37 | 38 | 39 | ## null 40 | 41 | * 没有值,未知 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /202007/vue菜单高亮.md: -------------------------------------------------------------------------------- 1 | ## 一. 需求 2 | 3 | 在使用vue写菜单时,需要实现菜单上高亮与当前路由匹配。 4 | 5 | 6 | 7 | 8 | 9 | ## 二. 原生支持 10 | 11 | Vue原生支持菜单高亮,菜单中路由使用``。 12 | 13 | 添加样式`.router-link-class`来实现菜单高亮的样式。 14 | 15 | 在菜单中某一元素的``添加`exact`属性来实现默认高亮。 16 | 17 | 18 | 19 | ```html 20 | 36 | 37 | 43 | ``` 44 | 45 | 46 | 47 | 48 | 49 | ## 三. ElementUI的NavMenu组件 50 | 51 | `ElementUI`的`NavMenu`组件封装比较完善,可以直接上手使用,但是需要设置`router=true`才可开启高亮。 52 | 53 | 高亮样式可以通过官方给出的属性设置。 54 | 55 | 56 | 57 | ```html 58 | 61 | 62 | 首页 63 | 64 | 65 | 资讯 66 | 67 | 68 | 关于 69 | 70 | 71 | ``` 72 | 73 | 74 | 75 | ## 四. 参考文档 76 | 77 | * [Vue Router API Reference](https://router.vuejs.org/api/#linkactiveclass) 78 | * [Element UI NavMenu](https://element.eleme.cn/#/zh-CN/component/menu) 79 | 80 | -------------------------------------------------------------------------------- /202003/Flask_sqlalchemy的增删改查.md: -------------------------------------------------------------------------------- 1 | ## 一. 增 2 | 3 | ```python 4 | article = Article(title='article1', content='heihei') 5 | db.session.add(article) 6 | db.session.commit() 7 | ``` 8 | 9 | 若要在新增之后获取数据库中新增数据的信息,如`id`。 10 | 11 | ```python 12 | article = Article(title='article1', content='heihei') 13 | db.session.add(article) 14 | db.session.flush() # 添加这一条,用于预提交 15 | db.session.commit() 16 | 17 | # 输出新增数据的信息 18 | print(article.id) 19 | print(article.title) 20 | ``` 21 | 22 | 23 | 24 | ## 二. 删 25 | 26 | ```python 27 | # 1.把需要删除的数据查找出来 28 | article = Article.query.filter_by(content = 'heihei').first() 29 | 30 | # 2.把这条数据删除掉 31 | db.session.delete(article) 32 | 33 | # 3.提交 34 | db.session.commit() 35 | ``` 36 | 37 | 38 | 39 | ## 三. 改 40 | 41 | ```python 42 | # 1.先把你要更改的数据查找出来 43 | article = Article.query.filter(Article.title == 'article1').first() 44 | 45 | # 2.把这条数据需要修改的地方进行修改 46 | article.title = 'article2' 47 | 48 | # 3.提交 49 | db.session.commit() 50 | ``` 51 | 52 | 53 | 54 | ## 四. 查 55 | 56 | ### 1. 查询单个 57 | 58 | ```python 59 | article1 = Article.query.filter(Article.title == 'article').first() 60 | article1 = Article.query.filter_by(title = 'article').first() # 或者 61 | 62 | print(article1.title) 63 | print(article1.content) 64 | ``` 65 | 66 | ### 2. 查询所有 67 | 68 | ```python 69 | article1 = Article.query.filter_by(title = 'article').all() 70 | for item in article1: 71 | print(item.title) 72 | print(item.content) 73 | ``` 74 | 75 | ### 3. 倒叙 76 | 77 | ```python 78 | article1 = Article.query.filter_by(title = 'article').order_by(Article.id.desc()).all() 79 | ``` 80 | 81 | ### 4. 限制数量 82 | 83 | ```python 84 | article1 = Article.query.filter_by(title = 'article').limit(10).all() 85 | ``` 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /202110/OSS花式解锁下载文件新姿势,你学废了吗?.md: -------------------------------------------------------------------------------- 1 | ## 一. 简介 2 | 3 | 在上一篇文章——[前端文件花式直传OSS!后端:那我走?](https://mp.weixin.qq.com/s/qpSWVymlWtwlb5WUd_1M7Q)中聊了下文件上传的几种方案,这里我们再来聊一下文件下载的花式姿势。 4 | 5 | 6 | 7 | ## 二. 精简版 8 | 9 | 最常见的方式,莫过于后端存储文件在服务器上,然后通过后端接口传给前端,如下图所示。 10 | 11 | ![最简版](https://tva1.sinaimg.cn/large/008i3skNgy1guwoojpvg6j60lq068jrg02.jpg) 12 | 13 | 该方案对于小规模、成本较低的项目非常适用,开发也较为便捷。 14 | 15 | 而对于有性能要求的项目,可以通过砸钱加机器、分片下载等方案提升项目性能。如果可以的话,请砸钱加机器吧! 16 | 17 | 截屏2021-09-28 下午9.58.10 18 | 19 | 20 | 21 | ## 三. 中庸版 22 | 23 | 相较于上一个方案,可以砸丢丢钱整个OSS,将文件存储在OSS上,毕竟OSS上行流量不收费,如下图所示。 24 | 25 | ![中庸版直传](https://tva1.sinaimg.cn/large/008i3skNgy1guwpuow9hnj60km0dc74n02.jpg) 26 | 27 | 那么问题来了,OSS的下行流量不是收费的吗?! 28 | 29 | OK,偷偷告诉各位一个省钱小妙招/狗头,OSS内网的下行流量是不收费的!因此,可以通过后端请求OSS,获取到文件/字符串后,将其以文件流/base64数据的方式返回给前端。这样就避免了下行流量的费用。如下图所示。 30 | 31 | ![中庸版](https://tva1.sinaimg.cn/large/008i3skNgy1guwp05f04jj60r3068glu02.jpg) 32 | 33 | 不过问题又来了,这样就还是占用了后端服务器的资源,依然会是性能的一个瓶颈。 34 | 35 | 截屏2021-09-28 下午10.12.06 36 | 37 | 38 | 39 | ## 四. 性能版 40 | 41 | 基于上一个方案,可以再升级。砸丢丢钱,拉上CDN这老哥,利用CDN流量代替OSS的下行流量,既能让前端直接请求OSS资源,不占用服务器资源,也降低了成本。如图所示。 42 | 43 | ![性能版](https://tva1.sinaimg.cn/large/008i3skNgy1guwplrnpu2j60r20dc3z102.jpg) 44 | 45 | 在优先性能的情况下,该方案是较优的。 46 | 47 | 要说缺点的话,就是CDN配置吧,需要买域名和SSL证书等,不过一次购买,后续使用体验会非常棒。CDN除了可以代替OSS的下行流量外,其优点不要太多,比如说CDN可以文件缓存、可以调度至加速节点等。 48 | 49 | 涉及到OSS的私有Bucket的话,只需要使用CDN的访问控制即可。其也只需要通过后端实现加密,生成文件访问URL给前端直接访问。 50 | 51 | 或许你会存在疑问,看上去挺麻烦的啊!但是看看CDN的价格,你肯定会有不一样的想法的。 52 | 53 | 截屏2021-09-28 下午10.37.14 -------------------------------------------------------------------------------- /Electron/electron-builder踩坑系列---mac下窗口毛玻璃效果.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 3 | 一直觉得毛玻璃样式很炫,而要在`electron`中实现,本来是需要自己去写样式的,我在开发之前也去了解了下,想看看有没有大佬已经实现了,不过确实发现了一个[大佬的仓库](https://github.com/arkenthera/electron-vibrancy)分享了毛玻璃组件,但是其README也提到了官方仓库对于[mac的毛玻璃效果的pr](https://github.com/electron/electron/pull/7898),然后我去找了官方文档,已经有相关属性了,就很妙啊! 4 | 5 | 但是为什么标题要写“mac下”下呢,因为这个属性只对mac有效。(打工人落泪...) 6 | 7 | 8 | 9 | ## 官方文档 10 | 11 | ### 文档地址 12 | 13 | [https://www.electronjs.org/docs/api/browser-window](https://www.electronjs.org/docs/api/browser-window) 14 | 15 | ### 相关属性 16 | 17 | - `vibrancy` String (可选) - 窗口是否使用 vibrancy 动态效果, 仅 macOS 中有效. Can be `appearance-based`, `light`, `dark`, `titlebar`, `selection`, `menu`, `popover`, `sidebar`, `medium-light`, `ultra-dark`, `header`, `sheet`, `window`, `hud`, `fullscreen-ui`, `tooltip`, `content`, `under-window`, or `under-page`. Please note that using `frame: false` in combination with a vibrancy value requires that you use a non-default `titleBarStyle` as well. Also note that `appearance-based`, `light`, `dark`, `medium-light`, and `ultra-dark` have been deprecated and will be removed in an upcoming version of macOS. 18 | 19 | - `visualEffectState`String (optional) - Specify how the material appearance should reflect window activity state on macOS. Must be used with the`vibrancy`property. 可能的值有 20 | - `followWindow` - 当窗口处于激活状态时,后台应自动显示为激活状态,当窗口处于非激活状态时,后台应自动显示为非激活状态。 This is the default. 21 | - `active` - 后台应一直显示为激活状态。 22 | - `inactive` - 后台应一直显示为非激活状态。 23 | 24 | 25 | 26 | ## 实现 27 | 28 | 有了官方Buff加持,使起来就很方便了。 29 | 30 | ```javascript 31 | // background.js 32 | 33 | let win = new BrowserWindow({ 34 | width: 800, 35 | height: 600, 36 | vibrancy: 'dark', // 'light', 'medium-light' etc 37 | visualEffectState: "active" // 这个参数不加的话,鼠标离开应用程序其背景就会变成白色 38 | }) 39 | ``` 40 | 41 | 实现就是这么简单! 42 | 43 | 小伙伴儿们有兴趣的可以参考下我[这个项目](https://github.com/Kuari/QingKe),使用的毛玻璃样式。 -------------------------------------------------------------------------------- /202008/Golang使用JSON格式存取Redis.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 对于`Golang`操作`Redis`,此处使用`github.com/go-redis/redis`。 4 | 5 | 6 | 7 | ## 二. 操作 8 | 9 | ### 1. 连接redis服务器 10 | 11 | ```go 12 | package redis 13 | 14 | import ( 15 | "context" 16 | "os" 17 | "time" 18 | 19 | "github.com/go-redis/redis/v8" 20 | jsoniter "github.com/json-iterator/go" 21 | ) 22 | 23 | var ctx = context.Background() 24 | 25 | func BaseClient() (rdb *redis.Client) { 26 | redisServer := "redis_server" 27 | port := "redis_port" 28 | password := "redis_password" 29 | 30 | rdb = redis.NewClient(&redis.Options{ 31 | Addr: redisServer + ":" + port, 32 | Password: password, 33 | DB: 0, 34 | }) 35 | 36 | return 37 | } 38 | 39 | 40 | ``` 41 | 42 | 43 | 44 | ### 2.存储 45 | 46 | ```go 47 | func SetJson(key string, value map[string]interface{}, expiration int) (err error) { 48 | rdb := BaseClient() 49 | valueString, _ := jsoniter.MarshalToString(value) 50 | err = rdb.Set(ctx, key, valueString, time.Duration(expiration)*time.Second).Err() 51 | return 52 | } 53 | ``` 54 | 55 | * 调用 56 | 57 | ```go 58 | func main() { 59 | value, _ := redis.SetJson("user", map[string]interface{}{ 60 | "name": "tom", 61 | "age": 12 62 | }), 63 | 60, 64 | } 65 | ``` 66 | 67 | 68 | 69 | ### 3.读取 70 | 71 | ```go 72 | func Get(key string) (value string, err error) { 73 | rdb := BaseClient() 74 | value, err = rdb.Get(ctx, key).Result() 75 | return 76 | } 77 | ``` 78 | 79 | * 调用 80 | 81 | ```go 82 | type User struct { 83 | Name string 84 | Age int 85 | } 86 | 87 | func main() { 88 | value, _ := redis.Get("user") 89 | 90 | var user User 91 | json.Unmarshal([]byte(value), &user) 92 | 93 | fmt.Print(user) 94 | } 95 | ``` 96 | 97 | 98 | 99 | ## 三. 结语 100 | 101 | 使用`JSON`格式存储与读取其实就是对目标数据在存储前和读取后进行格式转换。 -------------------------------------------------------------------------------- /202206/python3 socket tcp example.md: -------------------------------------------------------------------------------- 1 | ## socket tcp server 2 | 3 | ```python 4 | import socket 5 | 6 | 7 | def socket_tcp_server(server_ip: str = '0.0.0.0', server_port: int = 9000, buffer_size: int = 1024): 8 | """ 9 | socket tcp 服务端 10 | 11 | :param server_ip: 服务器的地址, 默认为0.0.0.0, 表示允许所有 12 | :param server_port: 服务器tcp server接收信息的端口, 默认9000 13 | :param buffer_size: 套接字缓冲区大小, 默认1024 14 | :return: none 15 | """ 16 | tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 17 | tcp_socket.bind((server_ip, server_port)) 18 | tcp_socket.listen(128) 19 | print('服务端开始运行...\n') 20 | 21 | while True: 22 | client, sender_info = tcp_socket.accept() 23 | receive_data = client.recv(buffer_size) 24 | print('客户端地址: {}'.format(sender_info)) 25 | print('来自客户端的信息: {}'.format(receive_data.decode('utf-8'))) 26 | 27 | # 返回消息 28 | client.send(str.encode('response...')) 29 | 30 | 31 | if __name__ == '__main__': 32 | socket_tcp_server() 33 | 34 | ``` 35 | 36 | 37 | 38 | ## socket tcp client 39 | 40 | ```python 41 | import socket 42 | 43 | 44 | def socket_tcp_client_send_message(message: str, server_ip: str, server_port: int, buffer_size: int = 1024): 45 | """ 46 | socket tcp 客户端发送消息 47 | 48 | :param message: 消息 49 | :param server_ip: 服务端的ip地址 50 | :param server_port: 服务端的端口号 51 | :return: none 52 | """ 53 | tcp_client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) 54 | tcp_client_socket.connect((server_ip, server_port)) 55 | tcp_client_socket.send(str.encode(message)) 56 | 57 | response = tcp_client_socket.recv(buffer_size) 58 | print('response : {}'.format(response.decode())) 59 | 60 | tcp_client_socket.close() 61 | 62 | 63 | if __name__ == '__main__': 64 | socket_tcp_client_send_message('hello,world!', '127.0.0.1', 9000) 65 | 66 | ``` 67 | 68 | -------------------------------------------------------------------------------- /202011/flask_restful限制request字段长度.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 当前产品遇到一个报错,就是接口收到请求没有限制请求字段长度,导致字段长度超过数据库对应字段长度,直接报了500。因此也对此有些新的需求,需要在后端限制请求字段最大长度。 4 | 5 | 6 | 7 | ## 二. 环境 8 | 9 | * **开发语言**:Python 3.7 10 | * **后端框架**:Flask 1.1.1 11 | * **插件**:Flask-RESTful 0.3.8 12 | 13 | 14 | 15 | ## 三. 实现 16 | 17 | ```python 18 | def field_max_limit(max_length): 19 | def validate(s): 20 | if type(s) != str: 21 | raise ValidationError("The field must be String.") 22 | 23 | if len(s) <= max_length: 24 | return s 25 | raise ValidationError("The field cannot exceed %i characters." % max_length) 26 | 27 | return validate 28 | 29 | # 解析请求参数时候验证长度 30 | parse.add_argument('username', type = field_max_limit(5), required = True) 31 | ``` 32 | 33 | 34 | 35 | ## 四. 示例 36 | 37 | ```python 38 | from flask import Flask 39 | from flask_restful import Api, Resource, reqparse 40 | from werkzeug.routing import ValidationError 41 | 42 | app = Flask(__name__) 43 | api = Api(app) 44 | 45 | 46 | def field_max_limit(max_length): 47 | def validate(s): 48 | if type(s) != str: 49 | raise ValidationError("The field must be String.") 50 | 51 | if len(s) <= max_length: 52 | return s 53 | raise ValidationError("The field cannot exceed %i characters." % max_length) 54 | 55 | return validate 56 | 57 | 58 | class Login(Resource): 59 | 60 | def post(self): 61 | parse = reqparse.RequestParser() 62 | parse.add_argument('username', type = field_max_limit(5), required = True) 63 | parse.add_argument('password', type = field_max_limit(20), required = True) 64 | args = parse.parse_args() 65 | 66 | print({ 67 | 'username': args.username, 68 | 'password': args.password 69 | }) 70 | 71 | return { 72 | 'code': 20000 73 | } 74 | 75 | 76 | api.add_resource(Login, '/login') 77 | ``` 78 | 79 | -------------------------------------------------------------------------------- /202106/SwiftUI项目调用生物识别(Touch ID : Face ID).md: -------------------------------------------------------------------------------- 1 | ## 一. 设置权限 2 | 3 | 打开文件`info.plist`,在空白处右击,选择`Add Row`,输入选择`Privacy - Face ID Usage Description`,然后在`value`中写入`我们需要验证您的身份以保护数据`。 4 | 5 | ![swiftui_face_privacy_info_plist.png](../assets/swiftui_face_privacy_info_plist.png) 6 | 7 | 8 | 9 | ## 二. 代码层面接入 10 | 11 | 打开`ContentView.swift`文件,开始如下操作。 12 | 13 | ### 1. 引入相关库 14 | 15 | ```swift 16 | import LocalAuthentication 17 | ``` 18 | 19 | 20 | 21 | ### 2. 创建lock变量 22 | 23 | ```swift 24 | @State private var isUnlocked = false 25 | ``` 26 | 27 | `isUnlocked`为是否解锁,`true`表示验证完成,已解锁,`false`表示验证失败,未解锁。 28 | 29 | 30 | 31 | ### 3. 创建函数 32 | 33 | ```swift 34 | func authenticate() { 35 | let context = LAContext() 36 | var error: NSError? 37 | 38 | // 检查生物特征认证是否可用 39 | if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { 40 | // 可用,所以继续使用它 41 | let reason = "我们需要验证您的身份以保护数据" 42 | 43 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in 44 | // 身份验证现已完成 45 | DispatchQueue.main.async { 46 | if success { 47 | // 认证成功,解锁 48 | self.isUnlocked = true 49 | } else { 50 | // 发生的异常 51 | } 52 | } 53 | } 54 | } else { 55 | // 没有生物识别 56 | } 57 | } 58 | ``` 59 | 60 | 61 | 62 | ### 4. 根据isUnlocked切换View 63 | 64 | ```swift 65 | VStack { 66 | if self.isUnlocked { 67 | Text("Unlocked") 68 | } else { 69 | Text("Locked") 70 | } 71 | } 72 | .onAppear(perform: authenticate) // 该方法调用生物识别验证函数 73 | ``` 74 | 75 | 76 | 77 | ## 三. 审核问题 78 | 79 | 最近提交了ios和macos两个产品到app store,我设置的强制使用生物识别才能进入应用。但是出现的问题是macos的审核过了,而ios的审核没有过,其反馈的问题即为开始的生物识别没有过,审核人员使用的模拟器,根本不存在生物识别。 80 | 81 | 所以跟可能会踩这个坑的小伙伴儿提个醒,目前我还没有好的解决方案,正在等待新的审核中...... 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /202006/flask_sqlalchemy一对一、一对多、多对一、多对多.md: -------------------------------------------------------------------------------- 1 | ## 一对多 2 | 3 | ```python 4 | class Country(db.Model): 5 | id = db.Column(db.Integer, primary_key=True) 6 | name = db.Column(db.String(30), unique=True) 7 | capital = db.relationship('Capital', uselist=False) 8 | 9 | class Capital(db.Model): 10 | id = db.Column(db.Integer, primary_key=True) 11 | name = db.Column(db.String(30), unique=True) 12 | country_id = db.Column(db.Integer, db.ForeignKey('country.id')) 13 | country = db.relationship('Country') 14 | ``` 15 | 16 | 17 | 18 | ## 一对多 19 | 20 | ```python 21 | class Author(db.Model): 22 | id = db.Column(db.Integer, primary_key=True) 23 | name = db.Column(db.String(70), unique=True) 24 | phone = db.Column(db.String(20)) 25 | 26 | class Article(db.Model): 27 | id = db.Column(db.Integer, primary_key=True) 28 | title = db.Column(db.String(50), index=True) 29 | body = db.Column(db.Text) 30 | ``` 31 | 32 | 33 | 34 | ## 多对一 35 | 36 | ```python 37 | class Citizen(db.Model): 38 | id = db.Column(db.Integer, primary_key=True) 39 | name = db.Column(db.String(70), unique=True) 40 | city_id = db.Column(db.Integer, db.ForeignKey('city.id')) 41 | city = db.relationship('City') 42 | 43 | class City(db.Model): 44 | id = db.Column(db.Integer, primary_key=True) 45 | name = db.Column(db.String(30), unique=True) 46 | ``` 47 | 48 | 49 | 50 | ## 多对多 51 | 52 | ```python 53 | association_table = db.Table('association',db.Column('student_id', db.Integer, db.Foreign) 54 | 55 | class Student(db.Model): 56 | id = db.Column(db.Integer, primary_key=True) 57 | name = db.Column(db.String(70), unique=True) 58 | grade = db.Column(db.String(20)) 59 | teachers = db.relationship('Teacher', 60 | secondary=association_table, back_populates='students') 61 | 62 | class Teacher(db.Model): 63 | id = db.Column(db.Integer, primary_key=True) 64 | name = db.Column(db.String(70), unique=True) 65 | office = db.Column(db.String(20)) 66 | ``` 67 | 68 | -------------------------------------------------------------------------------- /202104/Grafana+Loki+Docker Driver Client日志收集方案.md: -------------------------------------------------------------------------------- 1 | ## 一. 简介 2 | 3 | 具体日志采集方案在`Grafana+Loki+Promtail日志收集方案`文章中已经介绍过,此处不再重复介绍。不太了解的小伙伴儿赶紧去复习! 4 | 5 | 此处主要是记录下`Docker Driver Client`方式的部署。 6 | 7 | 8 | 9 | ## 二. docker plugin 10 | 11 | ### 1. 安装 12 | 13 | 安装`loki`插件。 14 | 15 | ```bash 16 | docker plugin install grafana/loki-docker-driver:latest --alias loki --grant-all-permissions 17 | ``` 18 | 19 | ### 2. 验证 20 | 21 | ```bash 22 | $ docker plugin ls 23 | ID NAME DESCRIPTION ENABLED 24 | ac720b8fcfdb loki Loki Logging Driver true 25 | ``` 26 | 27 | ### 3. 开启/禁用 28 | 29 | ```bash 30 | docker plugin enable loki 31 | docker plugin disable loki --force 32 | ``` 33 | 34 | ### 4. 卸载 35 | 36 | ```bash 37 | docker plugin rm loki 38 | ``` 39 | 40 | 41 | 42 | ## 三. 部署 43 | 44 | ### 1. docker-compose.yml 45 | 46 | ```yaml 47 | version: "3" 48 | 49 | networks: 50 | loki: 51 | 52 | services: 53 | loki: 54 | image: grafana/loki:2.0.0 55 | ports: 56 | - "3100:3100" 57 | command: -config.file=/etc/loki/local-config.yaml 58 | networks: 59 | - loki 60 | 61 | grafana: 62 | image: grafana/grafana:latest 63 | ports: 64 | - "3000:3000" 65 | networks: 66 | - loki 67 | ``` 68 | 69 | ### 2. 运行 70 | 71 | ```bash 72 | docker-compose up -d 73 | ``` 74 | 75 | ### 3. 容器运行形式 76 | 77 | 容器运行时需要修改`log-driver`: 78 | 79 | ```bash 80 | --log-driver=loki 81 | --log-opt loki-url="http:///loki/api/v1/push" 82 | --log-opt loki-retries=5 83 | --log-opt loki-batch-size=400 84 | ``` 85 | 86 | 举个例子: 87 | 88 | ```bash 89 | docker run --log-driver=loki \ 90 | --log-opt loki-url="http://192.168.10.10:3100/loki/api/v1/push" \ 91 | --log-opt loki-retries=5 \ 92 | --log-opt loki-batch-size=400 \ 93 | -p 3000:3000 \ 94 | nginx 95 | ``` 96 | 97 | 由此查看日志,其`labels`只有`container_name`。 98 | 99 | 100 | 101 | ## 四. 参考文档 102 | 103 | * [官方文档](https://grafana.com/docs/loki/latest/clients/docker-driver/) -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/main.ts: -------------------------------------------------------------------------------- 1 | import './public-path' 2 | import { createApp } from 'vue' 3 | import { createRouter, createWebHistory } from 'vue-router' 4 | import App from './App.vue' 5 | import routes from './router' 6 | import store from './store' 7 | 8 | let router = null 9 | let instance: any = null 10 | let history: any = null 11 | 12 | function render (props = {}) { 13 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 14 | // @ts-ignore 15 | const { container } = props 16 | // history = createWebHistory(process.env.BASE_URL) 17 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 18 | // @ts-ignore 19 | history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/site1' : '/') 20 | router = createRouter({ 21 | history, 22 | routes 23 | }) 24 | 25 | instance = createApp(App) 26 | instance.use(router) 27 | instance.use(store) 28 | instance.mount(container ? container.querySelector('#app') : '#app') 29 | } 30 | 31 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 32 | // @ts-ignore 33 | if (!window.__POWERED_BY_QIANKUN__) { 34 | render() 35 | } 36 | 37 | export const bootstrap = async (): Promise => { 38 | console.log('%c ', 'color: green ', 'vue3.0 app bootstraped') 39 | } 40 | 41 | const storeTest = (props: any): void => { 42 | props.onGlobalStateChange && 43 | props.onGlobalStateChange( 44 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 45 | // @ts-ignore 46 | (value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), 47 | true 48 | ) 49 | props.setGlobalState && 50 | props.setGlobalState({ 51 | ignore: props.name, 52 | user: { 53 | name: props.name 54 | } 55 | }) 56 | } 57 | 58 | export const mount = async (props: any): Promise => { 59 | storeTest(props) 60 | render(props) 61 | instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange 62 | instance.config.globalProperties.$setGlobalState = props.setGlobalState 63 | } 64 | 65 | export const unmount = async (): Promise => { 66 | instance.unmount() 67 | instance._container.innerHTML = '' 68 | instance = null 69 | router = null 70 | history.destroy() 71 | } 72 | -------------------------------------------------------------------------------- /202002/Flask_Restful视图函数模块化.md: -------------------------------------------------------------------------------- 1 | Restful作为目前流行的api设计规范,在flask上也有较好的实践,即为flask_restful。我们在使用flask_restful的时候,当代码量达到一定程度,需要将视图函数模块化。然而在Flask之前一直使用Blueprint模块化,那么flask_restful如何模块化呢?底下就来瞅瞅! 2 | 3 | 4 | 5 | ## 一. 当前架构 6 | 7 | ``` 8 | server/ 9 | ├── __init__.py 10 | ├── commands.py 11 | ├── config.py 12 | └── views.py 13 | ``` 14 | 15 | * `__init__.py`:将该文件夹标示为一个模块 16 | * `commands.py`:额外命令,如初始化数据库 17 | * `config.py`:配置文件 18 | * `views.py`:视图函数 19 | 20 | 21 | 22 | 此为一个简单的flask项目架构,我们可以将flask中的各个功能拆分出来,分给不同文件,最后在`__init__.py`导入。 23 | 24 | ```python 25 | from flask import Flask 26 | from flask_cors import CORS 27 | from flask_restful import Api 28 | 29 | app = Flask('server') 30 | app.config.from_pyfile('config.py') 31 | 32 | CORS(app) 33 | 34 | api = Api(app) 35 | 36 | from server import commands, views 37 | ``` 38 | 39 | 40 | 41 | 42 | 43 | ## 二. 初步模块化 44 | 45 | 现在将视图函数改为一个文件夹`views`,然后在该文件夹下放入拆分后的文件。这里以登录和获取用户信息接口为例,架构就变成了下面这样。 46 | 47 | ``` 48 | server/ 49 | ├── __init__.py 50 | ├── commands.py 51 | ├── config.py 52 | └── views 53 | ├── __init__.py 54 | ├── login.py 55 | └── info.py 56 | ``` 57 | 58 | 此处的`__init__.py`文件功能如上,是将文件夹`views`标示为模块,但是此处的`__init__py`是个空白文件,但是`server`文件夹中的`__init__.py`的引入需要改变。 59 | 60 | ```python 61 | ... 62 | from server import commands 63 | from server.views import login, info 64 | ``` 65 | 66 | 67 | 68 | 69 | 70 | ## 三. 最终模块化 71 | 72 | 此时还不够,例如登录和获取用户信息接口是`User`模块下的,而获取文章标题接口是`Article`模块的下的,因此我们继续分。 73 | 74 | ``` 75 | server/ 76 | ├── __init__.py 77 | ├── commands.py 78 | ├── config.py 79 | └── views 80 | ├── Aricle 81 | │   ├── __init__.py 82 | │   └── title.py 83 | ├── User 84 | │   ├── __init__.py 85 | │   ├── info.py 86 | │   └── login.py 87 | └── __init__.py 88 | 89 | ``` 90 | 91 | `server`文件夹中的`__init__.py`的引入继续改变。 92 | 93 | ```python 94 | ... 95 | from server import commands 96 | from server.views.User import login, info 97 | from srever.views.Article import title 98 | ``` 99 | 100 | 101 | 102 | 103 | 104 | ## 四. 结语 105 | 106 | 至此,关于`flask_restful`的视图函数模块化结束,若有后续改进会继续分享,若大家有更好的方式请务必分享一下。 107 | 108 | 当时为了解决这个问题查了很久,但是要么是介绍blueprint的,要么就是flask_restful插件入门,简直刺激... 109 | 110 | -------------------------------------------------------------------------------- /202301/wow.js和animate css在vue3中的应用.md: -------------------------------------------------------------------------------- 1 | ## 一. 环境 2 | 3 | * vue 3.2 4 | * typescript 4.7.4 5 | * wow.js 1.2.2 6 | * animate.css 4.1.1 7 | 8 | 9 | 10 | ## 二. animate.css 11 | 12 | ### 1. 下载 13 | 14 | ``` 15 | pnpm add animate.css -D 16 | ``` 17 | 18 | ### 2. 引入 19 | 20 | 在`vue3`项目的`main.ts`中引入 21 | 22 | ```js 23 | import 'animate.css' 24 | ``` 25 | 26 | ### 3. 使用 27 | 28 | 需要注意的是,animate css在4.0之后使用`animate__`前缀 29 | 30 | ```html 31 |

An animated element

32 | ``` 33 | 34 | ### 4. 动画延迟 35 | 36 | #### 官方方法 37 | 38 | 官方给出的动画延迟是`animate__delay-2s`、`animate__delay-3s` ...... 39 | 40 | 直接在class中添加即可 41 | 42 | ```html 43 |

An animated element

44 | ``` 45 | 46 | #### 自定义延迟 47 | 48 | 特殊场景需要使用不同于官方的延迟时间,因此可以自定义延迟时间,直接声明延迟的类,然后在class上加入即可 49 | 50 | ```css 51 | .animation-delay-1 { 52 | animation-delay: 100ms; 53 | } 54 | .animation-delay-2 { 55 | animation-delay: 300ms; 56 | } 57 | .animation-delay-3 { 58 | animation-delay: 500ms; 59 | } 60 | .animation-delay-4 { 61 | animation-delay: 700ms; 62 | } 63 | .animation-delay-5 { 64 | animation-delay: 900ms; 65 | } 66 | ``` 67 | 68 | 使用 69 | 70 | ```html 71 |

An animated element

72 |

Another animated element

73 | ``` 74 | 75 | 76 | 77 | ## 三. wow.js 78 | 79 | ### 1. 下载 80 | 81 | ``` 82 | pnpm add wow.js -D 83 | ``` 84 | 85 | ### 2. 引入 86 | 87 | 在`vue3`项目的`main.ts`中引入,内容如下: 88 | 89 | ```js 90 | import WOW from 'wow.js' 91 | 92 | new WOW({ 93 | boxClass: 'wow', // 类名,在用户滚动时显示隐藏的框。 94 | animateClass: 'animate__animated', // 触发CSS动画的类名称 95 | offset: 300, // 定义浏览器视口底部与隐藏框顶部之间的距离。当用户滚动并到达此距离时,隐藏的框会显示出来。 96 | mobile: true, // 在移动设备上打开/关闭WOW.js。 97 | live: true, // 在页面上同时检查新的WOW元素。 98 | }).init() 99 | ``` 100 | 101 | ### 3. 使用 102 | 103 | 使用`wow`直接替代`animate__animated`即可 104 | 105 | ```html 106 |

An animated element

107 | ``` 108 | 109 | 110 | 111 | ## 四. 结语 112 | 113 | 由于写一个页面需要使用到wow,好多年没用过了,查了一下文档超多版本教程,使用起来各种不成功,难受...暂时也没找到可替代的方案... 114 | 115 | 116 | 117 | ## 五. 参考文档 118 | 119 | * [wow](https://github.com/graingert/wow) 120 | * [animate css](https://animate.style/) -------------------------------------------------------------------------------- /202403/裸辞后独立开发产品上线五天开始盈利,我是怎么做的?分享我的过程(下).md: -------------------------------------------------------------------------------- 1 | # 一. 前言 2 | 3 | 裸辞后,我花了一个多月开发我的第一个独立开发产品[RedisMate](https://redismate.hunterji.com/)。其MVP版本上线五天,产品从没有排名,到冲到了国区mac app store开发工具类排名榜的前十,并持续有了几笔收入。 4 | 5 | 那么,我是怎么做的呢? 6 | 7 | 我是一个正在尝试独立开发的普通程序员,在此跟各位分享我的过程,不仅是我对自己的复盘,还希望能够给一些正在或准备尝试独立开发的兄弟一点参考。所有的过程都是按照我主观的想法去推进执行的,如果有更好的建议,欢迎交流! 8 | 9 | 本次分享分为上下两篇,分别为产品设计和产品推广。 10 | 11 | 在本篇文章中,跟各位分享下我是如何推广的,准确来说是验证MVP版本。 12 | 13 | 14 | 15 | # 二. 推广之前 16 | 17 | 首先,审视RedisMate产品本身,其是一款专为开发过程中, 简化Redis调试操作而设计的macOS原生GUI应用。 18 | 19 | 那么其目标受众是**用Mac的做后端开发的程序员**, 20 | 21 | 我要推广的地方是**用Mac的做后端开发的程序员聚集的平台**。 22 | 23 | 一开始,我选择使用这个我最熟悉的领域来尝试独立开发,也是因为我就是这个圈子里的人,我认识不少这个圈子的朋友,我可以很方便地推广给我的朋友们,快速验证产品。 24 | 25 | 其次,因为是验证MVP版本,只要小规模推广一下,获取反馈验证产品方向是否正确即可。 26 | 27 | 28 | 29 | # 三. 录制介绍视频 30 | 31 | 看了别的产品的介绍视频,那些音乐、画面和节奏非常惊艳,我感觉我目前没那个能力做出来。 32 | 33 | 所以我做了一个比较朴实和真诚的视频,主要是介绍我的产品,展示其特色的功能。 34 | 35 | 中文视频我直接自己读台词,但是英文视频就直接用ai生成人声后期剪辑上去。 36 | 37 | 我尝试把空白间隙剪辑掉,稍稍加快语速,不断精炼台词和画面,使得视频控制在七八分钟内。 38 | 39 | 40 | 41 | # 四. 落地页 42 | 43 | > Landing Page,也被称作着陆页或落地页,主要用于网络营销和广告活动。它直译为“降落页面”,暗指用户通过点击广告或搜索结果后“降落”的首个页面。 44 | 45 | 我这是第一次接触落地页,别的产品都会强调有一个落地页,但是当时我并没有特别明白落地页在推广中起到什么作用,只是盲目去做了一个落地页。本来想套用模板快速生成一个,但是在没有看到有合适的模板,所以自己根据模板改了一版,保留了介绍文案和视频。 46 | 47 | ![image-20240426142103450](/Users/kuari/Library/Application Support/typora-user-images/image-20240426142103450.png) 48 | 49 | 自认为做得还不错,实际上并没有什么用😭。 50 | 51 | 52 | 53 | # 五. 社区发帖 54 | 55 | 基于上述的**用Mac的做后端开发的程序员聚集的平台**,我首先会想到的是v2ex,这是一个纯粹的程序员社区,并且对于新产品的发布非常友好,大家也都非常热情地参与和反馈,非常适合产品冷启动。 56 | 57 | 其次,我也在reddit等英文社区发了帖子。 58 | 59 | 60 | 61 | # 六. 自媒体 62 | 63 | 不同于v2ex这样的论坛,很多媒体平台比如b站等,是需要靠曝光量才能让更多目标人群看到,所以不可能一发帖就达到论坛那种效果。 64 | 65 | 在这些媒体平台上,要么花钱找相关的博主推广,要么花钱给自己的帖子买曝光量,要么就自己搞吧! 66 | 67 | 我作为刚起步的独立开发者,尽量以极低成本去验证产品。 68 | 69 | 产品的迭代是一个长期、持续的过程,因此作为独立开发者而言,自己做自媒体,一方面可以持续有稳定曝光量,另一方面可以结识一些志同道合的朋友。 70 | 71 | 因为工作这些年,我也都有在佛系写博客,维护了几个文字类平台,比如知乎、公众号等,所以稍微有一点关注量。 72 | 73 | 然后我同时注册了多个媒体平台,并在开发产品过程中,持续输出,分享一些过程。 74 | 75 | 76 | 77 | # 七. 总结 78 | 79 | 根据推广后的效果而言,v2ex的反馈是最多的,其次是b站。不得不说,这两个平台的网友真的非常热情且友善! 80 | 81 | 英文社区的推广的效果是挺差的,反馈不多,有兴趣的网友也都不是mac。 82 | 83 | 本次推广中,落地页做得很差,自以为简洁,其实缺少的元素太多,比如产品的特色、产品的定价等等,都需要添加上去。否则比如我这次,遇到最多的问题就是“你这个产品跟xxx有什么不同”。以及落地页本身获取流量等能力也没有去做。这些都是我没有做好的部分,需要后续持续学习和改进。 84 | 85 | 作为验证MVP版本,我觉得这些步骤对我来说是可以了,不仅验证了我的产品,让我第一次跑完独立开发整个流程,还结识了不少志同道合的朋友。 86 | 87 | 88 | 89 | # 八. 后续 90 | 91 | 在推广过程中,有很多细节我欠缺了太多,后来我也有在认真学习理论知识,以及看看大佬们是怎么做的,确实有学到很多。我看到有的大佬还没产品的时候就已经验证完产品且完成预售了,真的太强了...... 92 | 93 | 后续我将单独分享一下我学习到的知识,比如落地页应该应该怎么做、如何更低成本验证产品等等。 94 | 95 | -------------------------------------------------------------------------------- /202003/vue组件props双向绑定.md: -------------------------------------------------------------------------------- 1 | 在vue2中不允许子组件直接修改`props`,为单项数据流,所有若要修改只能通过额外的值,并监听`props`以改变额外的值。 2 | 3 | ## 一. 设置props 4 | 5 | ```javascript 6 | props: { 7 | dialog: { 8 | type: Boolean, 9 | default: false 10 | } 11 | } 12 | ``` 13 | 14 | ## 二. 创建额外的值 15 | 16 | 在`data`中创建一个`localDialog`,其值为`this.dialog`。 17 | 18 | ```javascript 19 | data() { 20 | return { 21 | localDialog: this.dialog 22 | } 23 | } 24 | ``` 25 | 26 | ## 三. 监听 27 | 28 | 保持同步的关键在于需要在子组件内监听`props`,即此处的`dialog`。 29 | 30 | ```javascript 31 | watch: { 32 | dialog(val) { 33 | this.localDialog = val 34 | } 35 | } 36 | ``` 37 | 38 | ## 四. 子组件向父组件传递 39 | 40 | 子组件使用`this.$emit()`即可向父组件传递变化的值。 41 | 42 | ```javascript 43 | methods: { 44 | sendToFather() { 45 | this.$emit('dialogchange', this.localDialog) 46 | } 47 | } 48 | ``` 49 | 50 | ## 五. 父组件调用 51 | 52 | ```html 53 | 54 | data() { 55 | return { 56 | dialog: false 57 | } 58 | }, 59 | methods: { 60 | dialogchange(val) { 61 | this.dialog = val 62 | } 63 | } 64 | ``` 65 | 66 | ## 六. 完整代码 67 | 68 | ### 1. 子组件 69 | 70 | ```javascript 71 | 77 | 78 | 103 | ``` 104 | 105 | ### 2. 父组件 106 | 107 | ```javascript 108 | 111 | 112 | 131 | ``` -------------------------------------------------------------------------------- /202207/flutter3.0基础学习笔记.md: -------------------------------------------------------------------------------- 1 | ### 本地图片加载 2 | 3 | #### 1. 创建images文件夹 4 | 5 | 在`lib`下创建文件夹`images`,然后放入图片,举例此处放入一张`test.png`图片文件。 6 | 7 | #### 2. 修改assets配置 8 | 9 | 在`pubspec.yaml`文件中,在`flutter`下添加如下内容: 10 | 11 | ```yaml 12 | assets: 13 | - lib/images/test.png 14 | ``` 15 | 16 | 其实可以看到在`pubspec.yaml`文件中,`flutter`下有相关注释说明: 17 | 18 | ```yaml 19 | # To add assets to your application, add an assets section, like this: 20 | # assets: 21 | # - images/a_dot_burr.jpeg 22 | # - images/a_dot_ham.jpeg 23 | ``` 24 | 25 | #### 3. 代码中使用 26 | 27 | 代码有两种写法: 28 | 29 | ```dart 30 | Image.asset('lib/images/test.png', width: 100, height: 100) 31 | 32 | const Image.asset('lib/images/test.png', width: 100, height: 100) 33 | ``` 34 | 35 | 36 | 37 | ### padding 38 | 39 | #### 四周 40 | 41 | 四周,即上、下、左、右四个方向,统一设置内边距。 42 | 43 | ```dart 44 | padding: const EdgeInsets.all(<数值>) 45 | ``` 46 | 47 | 举例: 48 | 49 | ```dart 50 | Container( 51 | padding: const EdgeInsets.all(20), // 此行设置 52 | child: ... 53 | ) 54 | ``` 55 | 56 | 57 | 58 | #### 上下、左右 59 | 60 | 上下数值一致,左右数值一致。 61 | 62 | * ***vertical***: 上下 63 | * ***horizontal***: 左右 64 | 65 | ```dart 66 | EdgeInsets.symmetric(vertical: <数值>, horizontal: <数值>}) 67 | ``` 68 | 69 | 举例: 70 | 71 | ```dart 72 | Container( 73 | padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 10), 74 | child: ... 75 | ) 76 | ``` 77 | 78 | ```dart 79 | Container( 80 | padding: const EdgeInsets.symmetric(vertical: 20.5), 81 | child: ... 82 | ), 83 | ``` 84 | 85 | 86 | 87 | #### 上、下、左、右 88 | 89 | 单独设置上、下、左、右方向的数值。 90 | 91 | 此处分为两个方法,一个是参数个数可选,参数带默认参数`0.0`,另一个是四个参数不带默认参数,都是必要参数。 92 | 93 | 首先是可选参数方法: 94 | 95 | ```dart 96 | EdgeInsets.only(top: <数值>, left: <数值>, right: <数值>, bottom: <数值>) 97 | ``` 98 | 99 | 举例: 100 | 101 | ```dart 102 | Container( 103 | padding: const EdgeInsets.only(top: 10, left: 11, right: 12, bottom: 20), 104 | child: ... 105 | ) 106 | ``` 107 | 108 | ```dart 109 | Container( 110 | padding: const EdgeInsets.only(top: 10, bottom: 20), 111 | child: ... 112 | ) 113 | ``` 114 | 115 | ```dart 116 | Container( 117 | padding: const EdgeInsets.only(top: 10), 118 | child: ... 119 | ) 120 | ``` 121 | 122 | 其次是必选参数方法: 123 | 124 | ```dart 125 | EdgeInsets.fromLTRB(, , , ) 126 | ``` 127 | 128 | 举例: 129 | 130 | ```dart 131 | Container( 132 | padding: const EdgeInsets.fromLTRB(10, 11, 12, 13), 133 | child: ... 134 | ) 135 | ``` 136 | 137 | -------------------------------------------------------------------------------- /ESP32/Mac环境下玩玩ESP32(一)——环境搭建.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 买了两块ESP32来玩玩,写点笔记记录一波。目前主要是以`microPython`来开发,先用python踩踩坑。 4 | 5 |

6 | esp32 7 | esp32 8 |

9 | 10 | 11 | 12 | ## 二. 环境 13 | 14 | * 操作系统:macOS Monterey 12.3.1 15 | * Thonny:3.3.13 16 | 17 | 18 | 19 | ## 三. 安装IDE 20 | 21 | 下载地址:[thonny.com](https://thonny.org/) 22 | 23 | 由于是esp32的microPython,不能用传统的IDE开发,此处使用的是`thonny`这个python ide。该IDE集成了连接esp32单片机、刷固件和写入文件等功能,比较方便。 24 | 25 | 在网页上选择Mac安装包,我下载时候,当前的版本是`3.3.13`。下载完成之后,将其安装。 26 | 27 | 28 | 29 | ## 四. 下载固件 30 | 31 | 下载地址:[microPython esp32 固件下载](https://micropython.org/download/esp32/) 32 | 33 | 在`Firmware`栏目中,选择最新版本的`.bin`文件下载即可。 34 | 35 | 在该页面上,还有一处教程,该教程是教大家如果使用python的`esptool`在命令行进行烧录固件,有兴趣的小伙伴儿可以去试一试。 36 | 37 | 38 | 39 | ## 五. 烧录固件 40 | 41 | ### 1. 连接esp32和mac 42 | 43 | 首先使用数据线连接esp32板子和mac,可以看到esp32亮起红灯。 44 | 45 | ### 2. 安装USB驱动 46 | 47 | 打开之前下载的thonny程序,在菜单上找到`运行`选项,选择其菜单第一行`选择解释器...`。(选项就是这么写的,带一个省略号...) 48 | 49 | ![选择解释器](./Screenshots/group1/选择解释器.png) 50 | 51 | 如图所示,第一个选择框内选择`MicroPtyhone(ESP32)`。 52 | 53 | 第二个选择框,选择esp32板子连接的端口。如果你不知道哪个是你的esp32板子的连接的端口,你可以使用如下方式去判断: 54 | 55 | * 关闭ide,拔掉esp32,然后再打开ide,进去这个设置,注意选项。接着连接esp32,关闭设置后再打开,多出来的选项就是esp32连接的端口 56 | 57 | 最后点击确定,进行安装USB驱动程序。 58 | 59 | 安装成功之后,每次打开会在IDE的下方Shell中输出一些连接信息。 60 | 61 | ### 3. 烧录固件 62 | 63 | 按照上一步骤,再次打开该`选择解释器...`,点击右下角蓝色的`Install or update firmware`,打开新的弹窗。 64 | 65 | ![烧录固件](./Screenshots/group1/烧录固件.png) 66 | 67 | 如上图所示,烧录固件设置页面。 68 | 69 | 首先在`Port`中选esp32板子连接的端口,跟上一步骤一样。 70 | 71 | 接着在`Firmware`中选择我们下载的固件文件。 72 | 73 | 然后下面的选项默认都是选上的,全都不要动,直接点击`安装`按钮。 74 | 75 | 点击完成后,就可以看到左下角有安装进度,点击安装进度的蓝色文字,会在弹窗最小方弹出安装进度。 76 | 77 | 等到安装进度最后输出`Done`,即为安装成功! 78 | 79 | 80 | 81 | ## 六. 测试 82 | 83 | 重启一下IDE,在Shell中写一个`print('Hello, World!')`,回车即可运行。 84 | 85 | 在菜单中,选择`视图`->`文件`,即可打开文件树,可以在文件树下方看到`MicreoPython设备`窗口,内有esp32板子里面的python文件。 86 | 87 | ![测试](./Screenshots/group1/测试.png) 88 | 89 | 90 | 91 | ## 七. 问题记录 92 | 93 | ### 1. 烧录固件报错 94 | 95 | #### 报错内容: 96 | 97 | ```shell 98 | A fatal error occurred: Failed to connect to Espressif device: Timed out waiting for packet header 99 | ``` 100 | 101 | #### 解决方案: 102 | 103 | 在开始烧录后,注意查看烧录日志,在开始烧录时候摁住esp32板子上的`boot`按键,知道下一步再松开。 104 | 105 | #### 解决过程: 106 | 107 | 一开始直接谷歌搜索,查出来基本都是要在esp32板子上接一个电容,瞬间我就感觉:这玩意儿对新手这么不友好的吗?!后来想想毕竟国产的esp32,到baidu上查一查,然后就找到了这个解决方案,也确实解决了我的问题。妙啊! -------------------------------------------------------------------------------- /202008/Flask_RESTful解析常见类型请求数据.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | `Flask_RESTful`是一个Flask 扩展,它添加了快速构建 REST APIs 的支持。其请求解析接口是模仿 `argparse` 接口。它设计成提供简单并且统一的访问 Flask 中 `flask.request` 对象里的任何变量的入口。 4 | 5 |
6 | 7 | ## 二. 常见类型解析 8 | 9 | ### 1. 基本参数 10 | 11 | * 请求 12 | 13 | ```json 14 | { 15 | "username": "kuari", 16 | "info": "heihei" 17 | } 18 | ``` 19 | 20 | * 解析 21 | 22 | ```python 23 | parse = reqparse.RequestParser() 24 | parse.add_argument('username', type = str) 25 | parse.add_argument('info', type = str) 26 | args = parse.parse_args() 27 | ``` 28 | 29 |
30 | 31 | ### 2. 必选参数 32 | 33 | 使用参数`required`。 34 | 35 | * 请求 36 | 37 | ```json 38 | { 39 | "username": "kuari", 40 | "info": "heihei" 41 | } 42 | ``` 43 | 44 | * 解析 45 | 46 | ```python 47 | parse = reqparse.RequestParser() 48 | parse.add_argument('username', type = str, required = True) 49 | parse.add_argument('info', type = str, required = True) 50 | args = parse.parse_args() 51 | ``` 52 | 53 |
54 | 55 | ### 3. 列表[string] 56 | 57 | 使用参数`action = 'append'` 58 | 59 | * 请求 60 | 61 | ```json 62 | { 63 | "username": "kuari", 64 | "info": [ 65 | "handsome", "cheerful", "optimism" 66 | ] 67 | } 68 | ``` 69 | 70 | * 解析 71 | 72 | ```python 73 | parse = reqparse.RequestParser() 74 | parse.add_argument('username', type = str, required = True) 75 | parse.add_argument('info', type = str, action = 'append', required = True) 76 | args = parse.parse_args() 77 | ``` 78 | 79 |
80 | 81 | ### 4. 列表[dict] 82 | 83 | 使用参数`action = 'append'` 84 | 85 | * 请求 86 | 87 | ```json 88 | { 89 | "username": "kuari", 90 | "friends": [ 91 | { 92 | "username": "tom", 93 | "age": 20 94 | }, 95 | { 96 | "username": "jerry", 97 | "age": 20 98 | } 99 | ] 100 | } 101 | ``` 102 | 103 | * 解析 104 | 105 | ```python 106 | parse = reqparse.RequestParser() 107 | parse.add_argument('username', type = str, required = True) 108 | parse.add_argument('info', type = dict, action = 'append', required = True) 109 | args = parse.parse_args() 110 | ``` 111 | 112 |
113 | 114 | ### 5. JSON 115 | 116 | * 请求 117 | 118 | ```json 119 | { 120 | "username": "kuari", 121 | "info": { 122 | "character": "optimism", 123 | "age": 20 124 | } 125 | } 126 | ``` 127 | 128 | * 解析 129 | 130 | ```python 131 | parse = reqparse.RequestParser() 132 | parse.add_argument('username', type = str, required = True) 133 | parse.add_argument('info', type = dict, required = True) 134 | args = parse.parse_args() 135 | ``` 136 | 137 | -------------------------------------------------------------------------------- /202312/程序员如何快速验证业务需求.md: -------------------------------------------------------------------------------- 1 | # 一. 前言 2 | 3 | 设想一个场景: 4 | 5 | 你作为一个程序员,听着攒劲的小曲,写着优雅的代码,加班加点终于写完了迭代中分配的模块需求。然后你跟别的同事一联调,却发现各种问题。 6 | 7 | 你跟同事A说:“你得在这种情况下给我数据A。“ 8 | 9 | 同事A一脸懵逼:“这种情况需求也没写啊,在这种情况下我也没有数据A啊!“ 10 | 11 | 然后你和同事A一起顺着业务流程,找到了负责上层模块的同事B。 12 | 13 | 同事B一脸懵逼:“这个迭代中我没有这个模块的开发需求啊,这种情况下我也没有数据A啊!” 14 | 15 | 然后你和同事A、同事B一起顺着业务流程,找到了负责上层模块的...... 16 | 17 | 最后,你和同事ABC...一起找到了产品,产品一脸懵逼:“那没办法,就改需求吧......” 18 | 19 | 你眼看着所剩无几的迭代时间,生无可恋地掏出手机,发个微信取消了这周末的相亲... 20 | 21 | 22 | 23 | 不知道程序员xdm有没有经历过这样的场景,如果各位程序员xd没有经历过这样的场景,那真的是恭喜你了,且行且珍惜!从毕业后参加工作开始,我是真的经历太多了,太痛了! 24 | 25 | 我之后不断学习各种方法,尝试去破解这样的困局。虽然说,工作中的问题很多,但是能解决一个是一个,后面再慢慢跟各位分享其他问题中我的解决方案。 26 | 27 | 所以,本篇文章将分享一波关于这类问题我的解决方案。谨代表个人主观观点。 28 | 29 | 30 | 31 | # 二. 痛点 32 | 33 | 需求是用户的痛点,但是在这个场景下,是程序员的痛点... 34 | 35 | 那么,仔细分析一下,这个场景下的痛点,到底是什么呢?我认为是如下三点: 36 | 37 | * 产品给到的需求无法自洽,且场景覆盖不够 38 | * 开发团队的技术评审和技术文档不到位 39 | * 开发各自按照模块开发,忽略外部环境,仅仅当各自开发完成后联调时候才会组装应用,发现问题 40 | 41 | 42 | 43 | 当然,可能有程序员xd会疑问:这种问题不是靠严谨的开发流程就可以避免吗? 44 | 45 | 理论上来说是这样的,但是以我个人经历而言,不管是大团队还是小团队,总有很多不稳定的因素,比如说迭代周期、协作流程的规范性等等。 46 | 47 | 特别是在创业团队,或者有些项目特别着急的时候,经常是没有足够的时间给到团队的各个角色去充分准备的,甚至为了赶时间,会精简一些环节甚至直接去掉。这种时候出现这种问题导致改需求,往往是非常可怕的,后果也往往是开发团队加班。 48 | 49 | 50 | 51 | # 三. 解决方案 52 | 53 | 我目前发现且实践后效果较好的方案,就是“曳光弹式开发”。 54 | 55 | ## 1. 什么是曳光弹 56 | 57 | 经常关注军事的小伙伴儿,应该了解,曳光弹是一种运用于军事场景的特殊子弹。 58 | 59 | 我们都知道武器发射时需要瞄准,比如枪就通过照门和准星来瞄准,而坦克和装甲车以及飞机都有专用的瞄准具,非常复杂。对于轻武器来说,一般攻击距离都比较近,使用自带的机械瞄准具或者外加的光学瞄准镜都足够,但是在距离稍远的时候瞄准镜显然不够。而对于飞机和战斗机来讲,在空中飞行时飞行姿态变化多样,**有时候进攻的时间很短,需要快速射击并且调整弹道,这时候就需要可以发光的曳光弹。** 60 | 61 | 曳光弹正如其名,可以发光而且指示弹道。曳光弹的结构比一般子弹更加复杂。子弹的弹壳部分和一般子弹一样,前半部分是钢心或者铅心的弹头,但是在后部有一个空腔,一般称作曳光管,里面填充着曳光剂。曳光剂的成分还比较复杂,一般来说主要成分是镁粉和铝镁合金粉,用来燃烧,除此之外还有硝酸锶。这样一来,燃烧的时候硝酸锶就会发出红光。大家平时看到的很多曳光弹还有黄光和绿光,加入钠盐就会发出黄光,而加入铜盐就会发出绿光。除此之外还在表面加入一层过氧化钡,以保证曳光剂被点燃。 62 | 63 | 对于机枪手来说,如果射击距离较远,自然不能选择过于精确的设计方式,也就是说不能靠瞄准具来射击。而一般的机枪主要起压制和面杀伤作用,加入曳光弹就是机枪手更加快速方便的控制弹道,随时变化攻击的方向。如果没有曳光弹的话,射手根本无法发现自己的子弹弹道,也就很难去调整弹道。毕竟枪械射击温度升高之后,弹道会有所变化,不同的弹药不同的枪管也都会改变子弹的弹道,如果仅仅依靠枪械自身的瞄准具去调整反而会适得其反,而曳光弹就很好的解决了这个问题。 64 | 65 | 66 | 67 | ## 2. 在开发中的作用 68 | 69 | 理解曳光弹本身在军事中的作用后,我们再来看曳光弹式开发如何在开发中起作用。 70 | 71 | 开发团队在接手到产品给到的业务需求后,特别是构建一些以前从未做过的东西时,对这个产品/功能的最终成效是模糊的。 72 | 73 | 程序员就像坦克上的机枪手一样,在尝试在黑暗中击中目标。但是如果像文章一开始的时候,大家先埋头写自己的模块,然后再联调,最终发现问题,感觉就像一上战场,大家都朝着各自理解的方向拼命清空弹夹,击中目标的寥寥无几,最后受伤的还是自己。 74 | 75 | 所以,此时,就需要先使用几颗曳光弹,去击中目标,在黑暗中划出轨迹,开发团队再调整方向,对着目标集中火力。 76 | 77 | 78 | 79 | ## 3. 如何应用 80 | 81 | 非常简单!步骤如下: 82 | 83 | 1. 找出级别最高的需求 84 | 2. 以完成最基础的完整功能为目标,从前端到部署,开发一个可以运行的骨架 85 | 3. 最后上相关需求的测试案例 86 | 87 | 这样,射出一颗曳光弹,穿透客户端、后端、数据库、运维、测试等不同层面。一旦击中目标——即符合用户需求,后续的任务便大都是搬砖的活儿,去丰富这个骨架。 88 | 89 | 曳光弹并不是总能击中目标的,中途若是发现未能击中目标,便可在前期以极小成本去调整曳光弹的方向,继续发射曳光弹。 90 | 91 | 如果你要问,什么是“最基础的完整功能”,那么可以这么说,那么举个例子:前端不要任何样式,直接能够满足比如表单功能即可,后端不用任何校验,能够处理和传递数据即可。 92 | 93 | 94 | 95 | # 四. 总结 96 | 97 | 为了解决文章开头的问题,需要使用曳光弹式开发,先开发一个骨架,确认满足需求后,即可继续丰富骨架,完成产品。 98 | 99 | 作为团队的一员,团队的每一个角色都很重要,程序员跟产品也是需要紧密协作,互帮互助,才能使得产品更好,也使得业绩更好。 -------------------------------------------------------------------------------- /Examples/vue_i18n_demo/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 46 | 47 | 48 | 64 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_1/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 46 | 47 | 48 | 64 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 46 | 47 | 48 | 64 | -------------------------------------------------------------------------------- /Examples/microFrontend/site_base_optimize/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 46 | 47 | 48 | 64 | -------------------------------------------------------------------------------- /202010/基于python3和js的前后端aes加解密.md: -------------------------------------------------------------------------------- 1 | ## 一. 简述 2 | 3 | 在特定敏感数据的场景需要加密,一开始采用`rsa`加密,但是`rsa`加密对性能要求较高,在解密时候对于数据量限制较大,导致加密传输的数据量上限较低。而采用`Base64`虽然简单明了但是解密过于简单。因此采用折中的对称加密`aes`。 4 | 5 | 而`aes`加密需要前后端加密类型相同,因此此处采用`CTR`,其对加密文本没有长度限制。 6 | 7 | 8 | 9 | ## 二. 前端实现 10 | 11 | ```javascript 12 | let crypto = require("crypto") 13 | 14 | export function aesEncrypted(key, text) { 15 | let iv = Buffer.concat([ crypto.randomBytes(12), Buffer.alloc(4, 0) ]) 16 | let cipher = crypto.createCipheriv("aes-128-ctr", key, iv) 17 | return iv.toString('hex') + cipher.update(text, 'utf8', 'hex') + cipher.final('hex') 18 | } 19 | ``` 20 | 21 | 22 | 23 | ## 三. 后端实现 24 | 25 | ```python 26 | def aesDecryption(key_: str, de_text: str) -> str: 27 | """ 28 | aes解密函数 29 | :param key_: aes的key 30 | :param de_text: aes加密的密文 31 | :return: 解密的文本 32 | """ 33 | ct = codecs.decode(de_text.encode(), 'hex') 34 | counter = Counter.new(32, prefix = ct[:12], initial_value = 0) 35 | cipher = AES.new(key_.encode(), AES.MODE_CTR, counter = counter) 36 | return cipher.decrypt(ct[16:]).decode() 37 | ``` 38 | 39 | 40 | 41 | ## 四. 示例代码 42 | 43 | ### 前端 44 | 45 | ```javascript 46 | let crypto = require("crypto") 47 | 48 | export function aesEncrypted(key, text) { 49 | let iv = Buffer.concat([ crypto.randomBytes(12), Buffer.alloc(4, 0) ]) 50 | let cipher = crypto.createCipheriv("aes-128-ctr", key, iv) 51 | return iv.toString('hex') + cipher.update(text, 'utf8', 'hex') + cipher.final('hex') 52 | } 53 | ``` 54 | 55 | 56 | 57 | ### 后端 58 | 59 | ```python 60 | from base64 import b64encode 61 | from Crypto.Cipher import AES 62 | from Crypto.Util import Counter 63 | from random import randint 64 | import codecs 65 | import hashlib 66 | 67 | 68 | def convert_to_md5(info: str) -> str: 69 | """ 70 | md5加密 71 | :param info: 需要加密的内容 72 | :return: md5加密密文 73 | """ 74 | md5 = hashlib.md5() 75 | md5.update(info.encode('utf-8')) 76 | return md5.hexdigest() 77 | 78 | 79 | def aesCreateKey() -> str: 80 | """ 81 | 生成aes加密的key,key的长度必须16位 82 | :return: 返回key的base64密文 83 | """ 84 | en_key = convert_to_md5(str(randint(100000, 999999)))[8:-8] 85 | return b64encode(en_key.encode()).decode() 86 | 87 | 88 | def aesDecryption(key_: str, de_text: str) -> str: 89 | """ 90 | aes解密函数 91 | :param key_: aes的key 92 | :param de_text: aes加密的密文 93 | :return: 解密的文本 94 | """ 95 | ct = codecs.decode(de_text.encode(), 'hex') 96 | counter = Counter.new(32, prefix = ct[:12], initial_value = 0) 97 | cipher = AES.new(key_.encode(), AES.MODE_CTR, counter = counter) 98 | return cipher.decrypt(ct[16:]).decode() 99 | ``` 100 | 101 | 102 | 103 | ## 五. 参考文档 104 | 105 | * https://stackoverflow.com/questions/44996742/encrypt-with-node-js-aes-ctr-and-decrypt-with-pycrypto -------------------------------------------------------------------------------- /Electron/electron-builder踩坑系列---lowdb本地存储.md: -------------------------------------------------------------------------------- 1 | ## 简述 2 | 3 | `electron`应用在开发中,需要存储数据到本地,经历了两个版本,其方案都不太一样。 4 | 5 | 一开始考虑使用cookie,在开发过程中没有任何问题,但是编译之后去使用,发现无法操作cookie。原来在开发中直接js操作的的浏览器的cookie,而在electron中需要交由底层的nodejs去操作本地的cookie,[官方](https://www.electronjs.org/docs/api/cookies)说法是通过`Session`的`cookies`属性来访问`Cookies`的实例。但是我在实践过程中确实没有成功,然后随着需求变化,数据量变大,就直接放弃了这个方案。 6 | 7 | 当时用的`electron`版本是`9.0.0`,之后才用的方案是直接文件存储,即直接`fs`读与写,毫无问题。就是注意配置文件的存放位置。 8 | 9 | 现在再去使用`electron`,版本已经到了`11.0.0`。当我去使用`fs`读写时直接给我报错`fs.writeFile is not a function`,经过一天多的排错和查找,最终放弃该方案,当然我并没有找到原因和解决方案。最后决定使用`lowdb`去实现存储。 10 | 11 | 12 | 13 | ## 官方文档 14 | 15 | ### Github仓库 16 | 17 | [https://github.com/typicode/lowdb](https://github.com/typicode/lowdb) 18 | 19 | ### 官方介绍 20 | 21 | Small JSON database for Node, Electron and the browser. Powered by Lodash. ⚡ 22 | 23 | 24 | 25 | ## 实现 26 | 27 | ### 安装 28 | 29 | ```bash 30 | npm install lowdb 31 | ``` 32 | 33 | ### 增删改查实例 34 | 35 | ```javascript 36 | const low = require('lowdb') 37 | const FileSync = require('lowdb/adapters/FileSync') 38 | 39 | const adapter = new FileSync('db.json') 40 | const db = low(adapter) 41 | 42 | // 默认初始化配置文件中 43 | db.defaults({ posts: [], user: {}, count: 0 }) 44 | .write() 45 | 46 | // 增 47 | db.get('posts') 48 | .push({ id: 1, title: 'lowdb is awesome'}) 49 | .write() 50 | 51 | // 删 52 | db.get('posts') 53 | .remove({ title: 'low!' }) 54 | .write() 55 | 56 | // 改 57 | db.set('user.name', 'typicode') 58 | .write() 59 | 60 | // 查 61 | db.get('posts[0].title') 62 | .value() 63 | ``` 64 | 65 | ### 加密 66 | 67 | 比较重要的是,`lowdb`本身支持对于配置文件的加密,但是需要自己去实现写加解密的函数。 68 | 69 | ```javascript 70 | const adapter = new FileSync('db.json', { 71 | serialize: (data) => encrypt(JSON.stringify(data)), 72 | deserialize: (data) => JSON.parse(decrypt(data)) 73 | }) 74 | ``` 75 | 76 | 如下加解密方式可以参考下: 77 | 78 | ```javascript 79 | const algorithm = "aes-256-ctr"; 80 | const ENCRYPTION_KEY = "" 81 | 82 | const IV_LENGTH = 16; 83 | 84 | // 加密 85 | function encrypt(text) { 86 | let iv = crypto.randomBytes(IV_LENGTH); 87 | let cipher = crypto.createCipheriv( 88 | algorithm, 89 | Buffer.from(ENCRYPTION_KEY, "hex"), 90 | iv 91 | ); 92 | cipher.setAutoPadding(true); 93 | let encrypted = cipher.update(text); 94 | encrypted = Buffer.concat([encrypted, cipher.final()]); 95 | return iv.toString("hex") + ":" + encrypted.toString("hex"); 96 | } 97 | 98 | // 解密 99 | function decrypt(text) { 100 | let textParts = text.split(":"); 101 | let iv = Buffer.from(textParts.shift(), "hex"); 102 | let encryptedText = Buffer.from(textParts.join(":"), "hex"); 103 | let decipher = crypto.createDecipheriv( 104 | algorithm, 105 | Buffer.from(ENCRYPTION_KEY, "hex"), 106 | iv 107 | ); 108 | let decrypted = decipher.update(encryptedText); 109 | decrypted = Buffer.concat([decrypted, decipher.final()]); 110 | return decrypted.toString(); 111 | } 112 | ``` 113 | 114 | -------------------------------------------------------------------------------- /202011/go单元测试.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 想要写出好的 Go 程序,单元测试是很重要的一部分。 `testing` 包为提供了编写单元测试所需的工具,写好单元测试后,可以通过 `go test` 命令运行测试。 4 | 5 | 6 | 7 | ## 二. 规则 8 | 9 | `testing` 为 Go 语言 package 提供自动化测试的支持。通过 `go test` 命令,能够自动执行如下形式的任何函数: 10 | 11 | ```go 12 | func TestXxx(*testing.T) 13 | ``` 14 | 15 | 要编写一个新的测试套件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 `TestXxx` 函数,如上所述。 将该文件放在与被测试文件相同的包中。该文件将被排除在正常的程序包之外,但在运行 `go test` 命令时将被包含。 16 | 17 | 18 | 19 | ## 三. 代码结构 20 | 21 | ``` 22 | . 23 | ├── go.mod 24 | ├── intMinBasicDriven_test.go 25 | ├── intMinBasic_test.go 26 | └── main.go 27 | ``` 28 | 29 | 30 | 31 | ## 四. 第一个单元测试 32 | 33 | ### 要被测试的代码 34 | 35 | ```go 36 | // main.go 37 | package main 38 | 39 | func IntMin(a, b int) int { 40 | // 返回a与b中的较小值 41 | if a < b { 42 | return a 43 | } else { 44 | return b 45 | } 46 | } 47 | ``` 48 | 49 | 50 | 51 | ### 测试代码 52 | 53 | ```go 54 | // intMinBasic_test.go 55 | package main 56 | 57 | import "testing" 58 | 59 | func TestIntMinBasic(t *testing.T) { 60 | ans := IntMin(2, -2) 61 | if ans != -2 { 62 | // t.Error* 会报告测试失败的信息,然后继续运行测试。 63 | // t.Fail* 会报告测试失败的信息,然后立即终止测试。 64 | t.Errorf("IntMin(2, -2) = %d; want -2", ans) 65 | } 66 | } 67 | ``` 68 | 69 | 70 | 71 | ### 运行测试 72 | 73 | ```bash 74 | go test 75 | // 输出 76 | ok heihei 0.385s 77 | ``` 78 | 79 | 80 | 81 | ## 五. Table-Driven Test 82 | 83 | 单元测试可以重复,所以会经常使用 *表驱动* 风格编写单元测试, 表中列出了输入数据,预期输出,使用循环,遍历并执行测试逻辑。 84 | 85 | ```go 86 | // intMinBasicDriven_test.go 87 | package main 88 | 89 | import ( 90 | "fmt" 91 | "testing" 92 | ) 93 | 94 | 95 | func TestIntMinTableDriven(t *testing.T) { 96 | var tests = []struct { 97 | a, b int 98 | want int 99 | }{ 100 | {0, 1, 0}, 101 | {1, 0, 0}, 102 | {2, -2, -2}, 103 | {0, -1, -1}, 104 | {-1, 0, -1}, 105 | } 106 | 107 | // t.Run 可以运行一个 “subtests” 子测试,一个子测试对应表中一行数据。 运行 go test -v 时,他们会分开显示。 108 | for _, tt := range tests { 109 | 110 | testname := fmt.Sprintf("%d,%d", tt.a, tt.b) 111 | t.Run(testname, func(t *testing.T) { 112 | ans := IntMin(tt.a, tt.b) 113 | if ans != tt.want { 114 | t.Errorf("got %d, want %d", ans, tt.want) 115 | } 116 | }) 117 | } 118 | } 119 | ``` 120 | 121 | 122 | 123 | ### 运行代码 124 | 125 | ```bash 126 | go test -v 127 | // 输出 128 | === RUN TestIntMinTableDriven 129 | === RUN TestIntMinTableDriven/0,1 130 | === RUN TestIntMinTableDriven/1,0 131 | === RUN TestIntMinTableDriven/2,-2 132 | === RUN TestIntMinTableDriven/0,-1 133 | === RUN TestIntMinTableDriven/-1,0 134 | --- PASS: TestIntMinTableDriven (0.00s) 135 | --- PASS: TestIntMinTableDriven/0,1 (0.00s) 136 | --- PASS: TestIntMinTableDriven/1,0 (0.00s) 137 | --- PASS: TestIntMinTableDriven/2,-2 (0.00s) 138 | --- PASS: TestIntMinTableDriven/0,-1 (0.00s) 139 | --- PASS: TestIntMinTableDriven/-1,0 (0.00s) 140 | PASS 141 | ok heihei 0.566s 142 | ``` 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /202110/前端文件花式直传OSS!后端:那我走?.md: -------------------------------------------------------------------------------- 1 | ## 一. 简介 2 | 3 | 前端还在传文件给后端吗?你们的服务器扛得住吗?什么......老板砸钱加机器?!告辞!/狗头 4 | 5 | 前后端文件传输涉及数据较大,往往会成为很多项目的性能瓶颈。常见的传输方式也有不少,相对来说,OSS直传能够减轻很大压力。 6 | 7 | 本文我们来列举下常见的和oss直传的几种传输方式,并列举其优劣。 8 | 9 | 10 | 11 | ## 二. 常见方式 12 | 13 | ### 1. 表单上传 14 | 15 | 表单上传文件是最常见的方式,前后端开发小伙伴都很轻松,前端哐哐传,后端哐哐收就成了。其过程如下图所示。 16 | 17 | ![form上传](https://tva1.sinaimg.cn/large/008i3skNgy1guw3hw7k3uj60ix06q74b02.jpg) 18 | 19 | **优势:** 20 | 21 | * 简单方便,开发量小 22 | * 前后端原生支持,无需额外第三方库支持 23 | 24 | 25 | 26 | ### 2. Base64上传 27 | 28 | Base64方式上传文件,多常见于小文件,如小图片等,前后端都可直接使用`String`类型发送和接收。不过在前端,需要将文件转成base64数据,不仅会增加些性能消耗,还会增加传输数据的体积。而对于后端,如果并不是想直接存储base64数据,也还需要将其转成文件再存储,也会增加后端的性能消耗。 29 | 30 | 该上传方式可适用于`Resetful Api`,也可适用于文件的加密、回调接口携带文件等等。其过程如下图所示。 31 | 32 | ![base64上传](https://tva1.sinaimg.cn/large/008i3skNgy1guvkwpx4d9j60ix06q74d02.jpg) 33 | 34 | 35 | 36 | **优势:** 37 | 38 | * 适用于`Resetful Api`,可用于加密、回调等场景,较为灵活 39 | 40 | **劣势:** 41 | 42 | * 前后端文件与base64数据转换需要消耗性能 43 | * 只适用于小文件 44 | 45 | 46 | 47 | ## 三. OSS直传 48 | 49 | 此处的OSS直传方案都是使用的阿里云的OSS产品,以下将介绍三个方案,可适用于不同的场景。 50 | 51 | ### 1. Browser.js SDK上传 52 | 53 | 该方案可在前端直接通过`browser.js`上传文件到OSS,可分成三步: 54 | 55 | 1. 前端使用SDK直传OSS 56 | 2. 前端上传完成后请求后端,通知上传完成 57 | 3. 后端检测OSS上该文件是否存在(可选) 58 | 59 | 其流程如下图所示。 60 | 61 | ![browser直传](https://tva1.sinaimg.cn/large/008i3skNgy1guvlvyap7zj60lc0crjru02.jpg) 62 | 63 | `Browser.js`的方式需要前端安装阿里云的库`ali-oss`,然后在前端调用。直传还需要OSS账户的Key和Secret,因此为了安全考虑,需要建立RAM账户,然后前端向后端先请求一个`STS`临时访问凭证来完成直传,其流程如下图所示。 64 | 65 | 66 | 67 | ![browser直传2](https://tva1.sinaimg.cn/large/008i3skNgy1guvlvuqm2bj60lc0cr0tb02.jpg) 68 | 69 | 70 | 71 | ### 2. Javascript客户端签名直传 72 | 73 | Javascript客户端签名直传,需要先从后端获取临时签名,其流程与上一步的`browser.js`方案大致相同,不同点在于: 74 | 75 | * 无需第三方库支持,直接表单上传 76 | * 原生支持后端上传回调(下一步骤讲述) 77 | 78 | 其流程如下图所示。 79 | 80 | ![javascript签名直传](https://tva1.sinaimg.cn/large/008i3skNgy1guvm1snim8j60lc0crgm202.jpg) 81 | 82 | 不过该方案做下来,我感觉最大的问题是权限配置有点麻烦....../泪眼 83 | 84 | 85 | 86 | ### 3. 服务端签名直传并设置上传回调 87 | 88 | 该方案其实是上面方案——Javascript客户端签名直传的升级版本,其加上了后端的上传回调功能。不错呦~ 89 | 90 | 前端需要改动的很少,只需要在请求参数中加上`callback`参数即可,该参数为后端加密,在签名请求的响应中一起返回回来,内加密了后端回调接口。在前端直传完成后,后端回调接口将会接收到相关文件参数,包括文件路径、大小、类型等。最后OSS会将回调接口response转发给前端,响应直传OSS的请求。 91 | 92 | 其流程如下图所示。 93 | 94 | ![后端签名直传且回调](https://tva1.sinaimg.cn/large/008i3skNgy1guvmceaql3j60lc0craak02.jpg) 95 | 96 | 97 | 98 | ## 四. 对比 99 | 100 | 传统方式相比直传OSS,相对来说有三个缺点: 101 | 102 | - 上传慢:用户数据需先上传到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度。 103 | - 扩展性差:如果后续用户多了,应用服务器会成为瓶颈。 104 | - 费用高:需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器。 105 | 106 | 当然,对于规模较小、成本较低的项目来说,常见的上传方式还是适合的,毕竟没有最好的,只有最适合的。 107 | 108 | 109 | 110 | ## 五. 参考文档 111 | 112 | * [Web端上传介绍](https://help.aliyun.com/document_detail/112718.html?spm=5176.22414175.sslink.2.3fc92aca2CzF4n) 113 | 114 | * [JavaScript客户端签名直传](https://help.aliyun.com/document_detail/31925.html) 115 | 116 | * [服务端签名直传并设置上传回调](https://help.aliyun.com/document_detail/267439.html?spm=a2c4g.11186623.0.0.13415d3fbA6bxA) 117 | -------------------------------------------------------------------------------- /202111/vue3 script setup响应式初体验.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 最近空下来,正好找个项目尝鲜,把vue3+ts+setup哐哐全堆上,试试最新的前端技术。 4 | 5 | 从最先体会到的变化,就是关于响应式APIs了。遇到不好问题,怪我没有理解文档/狗头。比如说: 6 | 7 | * 明明这个数据改了,怎么没渲染出来? 8 | * 同样是Arrary,怎么套了个reactive就类型不一样了? 9 | 10 | 所以这里基于遇到的几个问题,来写个笔记。 11 | 12 | 13 | 14 | ## 二. 简单对比 15 | 16 | * [响应式官方文档](https://v3.cn.vuejs.org/api/sfc-script-setup.html#%E5%93%8D%E5%BA%94%E5%BC%8F) 17 | 18 | 官方案例如下: 19 | 20 | ```vue 21 | 26 | 27 | 30 | ``` 31 | 32 | 我们可以先看看,如果是vue2,怎么做呢? 33 | 34 | ```vue 35 | 44 | 45 | 48 | ``` 49 | 50 | 可以很明显地看到此处的`count`跟上面的官方文档不同,使用了`ref`方法。这就是setup中的响应式APIs,需要预先声明响应式变量。 51 | 52 | 如果,不声明呢?那就是直接写成如下: 53 | 54 | ```javascript 55 | const count = 0 56 | ``` 57 | 58 | 运行一下,首先你会发现,没有任何报错,代码正常运行。但是当你在浏览器上查看,开始改变`count`的值时,就会发现,怎么页面没有变化? 59 | 60 | 所以,需要手动命名响应,才会在值变化时触发视图渲染。 61 | 62 | 63 | 64 | ## 三. ref 65 | 66 | 现在来详细讲讲`ref`,该方法常用于单个变量,比如: 67 | 68 | ```javascript 69 | ref(0) 70 | ref("hello") 71 | ``` 72 | 73 | 这里需要说明一个问题,那就是`ref("hello") !== "hello"`。这就是我说的,为什么同样的值,类型就不同了。那是因为ref返回的是一个`Proxy`,而非原来的值。在视图中可直接使用,但是在js/ts中操作,需要使用`.value`来操作,如下所示: 74 | 75 | ```javascript 76 | let name = ref("kuari") 77 | 78 | function changeName() { 79 | name.value = "tom" 80 | } 81 | ``` 82 | 83 | 84 | 85 | ## 四. reactive 86 | 87 | `reactive`不同于`ref`的点在于,其是“深层”的——它影响所有嵌套 property。也就是说,其可用在对象或者数组上。 88 | 89 | ```javascript 90 | let form = reactive({ 91 | name: "kuari", 92 | desc: "developer" 93 | }) 94 | ``` 95 | 96 | 其返回类型也是`Proxy`,不同点在于,可以直接修改某一个元素的,如下所示: 97 | 98 | ```javascript 99 | form.name = "tom" 100 | ``` 101 | 102 | 但是如果你想整个替换就会报错了。 103 | 104 | ```javascript 105 | form = {...} // 报错,类型不同 106 | ``` 107 | 108 | 当使用的是数组时,如果想整个替换,可以将其写成对象,代码如下所示: 109 | 110 | ```typescript 111 | let selected = reactive({ 112 | arr: [ 113 | { 114 | label: "vue", 115 | value: 0 116 | }, 117 | { 118 | label: "typescript", 119 | value: 1 120 | } 121 | ] 122 | }) 123 | 124 | // 使用 125 | console.log(selected.arr) 126 | 127 | // 整个替换 128 | selected.arr = [...] 129 | ``` 130 | 131 | 关于`reactive`跟`ref`一起使用,`reactive` 将解包所有深层的`ref`,同时维持 ref 的响应性。 132 | 133 | ```typescript 134 | const count = ref(1) 135 | const obj = reactive({ count }) 136 | 137 | // ref 会被解包 138 | console.log(obj.count === count.value) // true 139 | 140 | // 它会更新 `obj.count` 141 | count.value++ 142 | console.log(count.value) // 2 143 | console.log(obj.count) // 2 144 | 145 | // 它也会更新 `count` ref 146 | obj.count++ 147 | console.log(obj.count) // 3 148 | console.log(count.value) // 3 149 | ``` 150 | 151 | 152 | 153 | ## 五. 总结 154 | 155 | `setup`总体来说,用起来真的会更加简洁,而响应式虽然好像比之前麻烦些了,但是一定层面上让开发对对于程序有了更深入的操控。墙裂推荐一波!后面再来详细讲讲对于新特性的体验。 -------------------------------------------------------------------------------- /202108/Golang AES-256-CBC加密和解密.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 项目开发中遇到该问题,网上的文章太乱,为了节省下次踩坑时间,特此记录。 4 | 5 | 6 | 7 | ## 二. 加解密 8 | 9 | ### 1. 填充函数 10 | 11 | 该函数在加解密中都需要用到。 12 | 13 | ```go 14 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte { 15 | padding := blockSize - len(ciphertext)%blockSize 16 | padText := bytes.Repeat([]byte{byte(padding)}, padding) 17 | return append(ciphertext, padText...) 18 | } 19 | ``` 20 | 21 | 22 | 23 | ### 2. 加密 24 | 25 | ```go 26 | func Ase256Encrypt(plaintext string, key string, iv string, blockSize int) string { 27 | bKey := []byte(key) 28 | bIV := []byte(iv) 29 | bPlaintext := PKCS5Padding([]byte(plaintext), blockSize) 30 | block, _ := aes.NewCipher(bKey) 31 | ciphertext := make([]byte, len(bPlaintext)) 32 | mode := cipher.NewCBCEncrypter(block, bIV) 33 | mode.CryptBlocks(ciphertext, bPlaintext) 34 | 35 | return base64.StdEncoding.EncodeToString(ciphertext) 36 | } 37 | ``` 38 | 39 | 40 | 41 | ### 3. 解密 42 | 43 | ```go 44 | func Aes256Decrypt(cryptData, key, iv string) ([]byte, error) { 45 | ciphertext, err := base64.StdEncoding.DecodeString(cryptData) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | block, err := aes.NewCipher([]byte(key)) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | if len(ciphertext)%aes.BlockSize != 0 { 56 | err = errors.New("ciphertext is not a multiple of the block size") 57 | return nil, err 58 | } 59 | 60 | mode := cipher.NewCBCDecrypter(block, []byte(iv)) 61 | mode.CryptBlocks(ciphertext, ciphertext) 62 | 63 | return ciphertext, err 64 | } 65 | ``` 66 | 67 | 68 | 69 | ### 4. 全部代码 70 | 71 | ```go 72 | // 填充 73 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte { 74 | padding := blockSize - len(ciphertext)%blockSize 75 | padText := bytes.Repeat([]byte{byte(padding)}, padding) 76 | return append(ciphertext, padText...) 77 | } 78 | 79 | // 加密 80 | func Ase256(plaintext string, key string, iv string, blockSize int) string { 81 | bKey := []byte(key) 82 | bIV := []byte(iv) 83 | bPlaintext := PKCS5Padding([]byte(plaintext), blockSize) 84 | block, _ := aes.NewCipher(bKey) 85 | ciphertext := make([]byte, len(bPlaintext)) 86 | mode := cipher.NewCBCEncrypter(block, bIV) 87 | mode.CryptBlocks(ciphertext, bPlaintext) 88 | 89 | return base64.StdEncoding.EncodeToString(ciphertext) 90 | } 91 | 92 | // 解密 93 | func Aes256Decrypt(cryptData, key, iv string) ([]byte, error) { 94 | ciphertext, err := base64.StdEncoding.DecodeString(cryptData) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | block, err := aes.NewCipher([]byte(key)) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | if len(ciphertext)%aes.BlockSize != 0 { 105 | err = errors.New("ciphertext is not a multiple of the block size") 106 | return nil, err 107 | } 108 | 109 | mode := cipher.NewCBCDecrypter(block, []byte(iv)) 110 | mode.CryptBlocks(ciphertext, ciphertext) 111 | 112 | return ciphertext, err 113 | } 114 | ``` 115 | 116 | 117 | 118 | ## 三. 调用 119 | 120 | ### 1. 加密 121 | 122 | ```go 123 | result := Ase256Encrypt("<需要加密的数据>", "", "", aes.BlockSize) 124 | ``` 125 | 126 | 127 | 128 | ### 2. 解密 129 | 130 | ```go 131 | result, err := Aes256Decrypt("<需要解密的数据>", "", "") 132 | ``` 133 | 134 | -------------------------------------------------------------------------------- /202312/打工太累,年前裸辞,给自己放假一年.md: -------------------------------------------------------------------------------- 1 | # 一. 引言 2 | 3 | 作为一名从运维到全栈开发,最后到软件架构岗位的资深程序员,我经历了漫长的工作历程,并积累了丰富的经验。在我最后的工作中,公司具有不错的发展前景,为我提供了不错的薪资待遇,我的直属领导不仅是技术大咖,也始终致力于支持我成长。 4 | 5 | 然而,尽管如此,我仍选择在年前裸辞。 6 | 7 | 当许多朋友了解我的决定后,他们都感到非常震惊。有的朋友质疑我为何要放弃如此优质的机会,有些人更直接,认为我应当至少等到拿到年终奖后再离开。我完全理解他们的惊愕,但这确实是我经过长时间深思熟虑之后的决定。 8 | 9 | 我甚至已经决定,给自己放一整年的假。 10 | 11 | 12 | 13 | 14 | # 二. 裸辞的原因 15 | 16 | 实际上,我选择裸辞的主要原因颇为简单:我的身体已难以承受这样的压力。这也是我开始深入思考和回顾职业生涯的触发点。 17 | 18 | 因此,我选择裸辞并不是基于一个纯粹的单一原因,而是在思考完整个职业生涯之后,结合当前的情况做出的决定。 19 | 20 | ## 1. 身体原因 21 | 22 | 这些年,我一直饱受身体不适的困扰,主要表现为频繁的偏头痛和头晕。经过数年的检查,我已进行了多次CT扫描,看过超多科室,对颈椎、血管以及众多身体指标进行了检测。然而,所有专家都认为我身体状况良好,甚至有些指标显著优于其他人。 23 | 24 | 然而,往往我每天都受困于偏头痛,头晕,难以进行深入思考,走路时感觉轻飘飘的,脚底如履软棉。遇到加班繁重、压力巨大的时候,这些情况更为严重。 25 | 26 | 我大多数时候都靠药物缓解症状,有时疼痛难忍,只能疯狂地服用止疼药。所以,我的包里除了电脑,就是那堆药,随身配备的止疼药已成为常态。 27 | 28 | 特别是在最后一段工作期间,我经历巨大的压力和频繁的加班,几乎每天都得依赖止疼药。加班到深夜回家,脑袋混乱、头晕目眩,无法集中精力思考,也严重影响了我和家人的关系…经常还会在半夜疼醒... 29 | 30 | 在最后一次精神内科的诊断中,专家告诉我,所有这些症状都是过度焦虑引发的... 31 | 32 | 那么,我究竟在焦虑什么呢? 33 | 34 | 35 | ## 2. 工作状况 36 | 37 | 尽管我的最后一份工作整体上是不错的,但没有哪一份工作能称得上是“完美”。 38 | 39 | 日复一日的加班不仅让我感到身体疲惫,内心也同样承受很大压力。过于细节的我就不赘述了,但这份工作让我在技术以外的领域有了很大的成长。 40 | 41 | 然而,这份工作也让我深刻明白了一个教训:在公司的利益面前,每一个打工者都显得渺小而微不足道,如同湮灭在尘土中的一粒颗粒。 42 | 43 | 44 | ## 3. 回顾职业生涯 45 | 46 | 在回首我的职业生涯,我深刻体会到两点: 47 | 48 | * 年复一年的劳累,令我体力和精神非常疲惫 49 | * 在工作中我付出了巨大的努力,但却往往收获甚微,甚至无法看到直接的成果 50 | 51 | 52 | 实际上,直到今年之前,我从未认真考虑过裸辞的可能。我始终认为,我需要找一份工作,通过勤奋工作来维持我的生计。 53 | 54 | 然而今年,许多事情彻底颠覆了我的生活观。许多曾经遇到的事情和感受再次涌上心头,只是我现在开始重新审视它们。 55 | 56 | 我想分享一个令我记忆深刻且十分典型的经历。 57 | 58 | 多年前,就在新年过后几天,我们的客户需要一个宣传活动,涉及到一个H5页面以及后台管理系统。需求还未完全清晰,大领导就拍着胸脯,答应客户两天内完成任务。接到这个非常模糊的需求后,产品经理拉会进行了需求拆解,结果一头雾水。好不容易搞完,就将任务交给设计部门。设计完成后,所有的工作都落在了我一人身上:开发前端H5,管理后台前端,以及两个后端的开发,最后我还要负责部署上线。 59 | 60 | 因此我用了两个通宵加一天的时间完成了全部工作。半夜加班过程中发现需求逻辑不能自洽,设计又少了图等等,我只能硬着头皮给搞顺了。 61 | 62 | 最后上线后,客户说不是这样的,这里是那样,那里是这样等等。领导催促修改,强调客户很重要,需求很着急。所以我只能继续熬夜修改。 63 | 64 | 哪怕改完了,最后的结果是客户几乎没有使用我们的产品,我想寻求解释,但领导和客户都拿不出确切的回应。 65 | 66 | 我遭遇过很多类似的事情,当我回想,仍然充满了困惑。 67 | 68 | 我真的不理解,这样的工作究竟是为了什么?因为我年轻身体好能折腾?还是我想吃大饼? 69 | 70 | 很多事,别人一句“不会”,就自然而然地落在了我身上。我真的想不明白,会得越多,就得承担更多的任务吗?工资也相差没多少啊... 71 | 72 | 很多事,我发现领导的决策听起来匪夷所思,最后甚至我要背锅,我实在想不通... 73 | 74 | 很多事,为什么领导总是只相信自己相信的? 75 | 76 | 这些事情,我真的无法理解。我问我的同事和朋友,似乎大家都有过类似的经验和感受,只是他们更愿意为了工资默默忍受。 77 | 78 | 79 | ## 4. 总结 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 | 我将迎接每一个可能的未来。 -------------------------------------------------------------------------------- /202311/如何快速入门新的编程语言和框架.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 最近很多小伙伴儿通过各种途径咨询我关于学习技术方面的问题,我也都一一回复了。这里也专门出一个总结,关于我个人这些年入门新的编程语言和框架的经验和技巧,来跟大家分享一下,希望能够所有帮助。 4 | 5 | 本次分享分为两个时期: 6 | 7 | * 有编程经验时期 8 | * 新手时期 9 | 10 | 11 | ## 二. 有编程经验时期 12 | 13 | 当我有了一定开发经验后,去学习一个新的编程语言或者框架,我会注意如下两点: 14 | 15 | ### 1. 寻找共性 16 | 17 | #### 编程语言 18 | 19 | 所有的编程语言或者框架,都不是凭空产生的。当前比较流行的语言,大都是类C语言,比如我们常见的Java、Python、JavaScript、Golang等等。 20 | 21 | 所以,我们学习的编程语言,除了特有的特性,大都是相似的,也就是它们的共性。我们只要理解和掌握这些类C语言的共性,就可以很快地掌握一门新的编程语言。 22 | 23 | 那么,哪些是共性呢?比如说变量、常量、运算符、判断、循环、函数、面向对象等等。 24 | 25 | 我们在学习一门新的编程语言的时候,是不是必然要学习如上这些共性的知识点? 26 | 27 | 那么,当你已经有一个编程语言的基础后,当你在学习和写代码的时候,这些共性可以直接使用你已学会的编程语言的知识去代入。 28 | 29 | 举个例子,比如我已经会Go了(不过,当你先学Go的话,可能不太知道面向对象是什么/狗头),要去学一下Python。 30 | 31 | 我需要做的就是代入: 32 | 33 | * Go语言声明变量是`a := 1`,考虑到Python没有强类型,那么就直接`a = 1`就完事了。 34 | 35 | * Go语言声明函数是`func aFunction(username String) {}`,那么Python不过是换了个关键词,去掉了强类型,变成`def aFunctino(username):`,当然Python如果要申明类型也是可以的,写成`def login(username: str):`。 36 | 37 | * 其中,比如运算符大都是一样的,判断也是类似。 38 | 39 | * 但是突然你发现,怎么Python的循环不一样,然后发现Python的循环好方便,但是其实原理还是和Go一样的。甚至,当你遇到不知道怎么处理的时候,直接来一波`for (i := 0; i < count; i++)`。当然,Python没法这么写。 40 | 41 | 除了我举的这些例子,还有比如正则、数据类型、数据结构等等,都可以作为共性来代入。 42 | 43 | 44 | #### 框架 45 | 46 | 其实,框架也是如此。此处以web框架为例,比如Python的Flask、Django、FastApi,Go的Gin,Node的express,Java的SpringBoot等,都是比较熟知的框架。 47 | 48 | 这些Web框架,都是依托于网络的,大都是http(s)请求,甚至其他TCP请求。我们可以只在网络层面来看,它们共性就是request和response。所以在网络层面就可以理解为,后端web框架就是一个接收request和response的东西。 49 | 50 | 那么,看具体框架的功能上,可以抽象出路由、中间件。 51 | 52 | 所以,对于一个web框架,其共性为request、response、路由、中间件。 53 | 54 | 因此,我们不管是使用Flask、Gin还是SpringBootd等,可以使用我们已有的框架知识去代入: 55 | 56 | * 如何接收request,比如获取request body、headers等 57 | * 如何使用中间件处理,比如使用中间件鉴权、传递上下文等 58 | * 如何配置路由,让接口对外暴露 59 | * 如何返回response,比如返回http status code、json等 60 | 61 | 那么,就可以完成一个新的web框架的入门了。 62 | 63 | 64 | #### 其它 65 | 66 | 除了编程语言和框架,其实还有很多别的都是拥有共性,可以快入入门的,比如Nginx和Apache,比如Linux发行版本,比如Kong和ApiSix,比如不同的数据库等等。甚至最近我发现,wasm并不是前端特有的,EBPF也在用wasm。 67 | 68 | 69 | ### 2. 看官方文档 70 | 71 | 编程语言的情况还比较少,更多是学习框架时,一定要看官方文档。 72 | 73 | 网上虽然有很多教程,但是毕竟是二次加工的,可能存在信息遗漏、版本更新等等问题。 74 | 75 | 官方文档可能比较生涩,但是它是最全最新的一手文档,会非常有助于少走弯路。 76 | 77 | 再次强调,一定要看官方文档! 78 | 79 | 80 | ## 二. 新手时期 81 | 82 | 当我还是新手的时候,我其实也常常苦恼于入门——教程看了好多遍,跟着教程敲了一遍又一遍,感觉还是只会基础,自己想写点什么却不会。 83 | 84 | 后来,当我入门一段时间后,我返回去想一想,才想明白一些技巧。 85 | 86 | 所以,以我的经历,总结如下步骤: 87 | 88 | ### 1. 仅学基础 89 | 90 | 一开始的时候,我也是不停地啃教程,不管是文档还是视频,但是发现我还是只会基础理论知识,那些高级特性案例都敲了,却并不明白什么意思。 91 | 92 | 后来,我就发现,对于我而言,并不需要学太多,只要学会基础即可,甚至基础也不用太深入。 93 | 94 | 95 | ### 2. 自己构思写一个项目 96 | 97 | 跟着教程敲代码,效果甚微。 98 | 99 | 要自己构思一个项目,比如写一个TODO,写一个命令行聊天工具,写一个博客等等简单的程序。最好是自己感兴趣的领域。 100 | 101 | 但是,需要注意的是,一定要自己去构思如何写: 102 | 103 | * 你准备写一个什么程序?要达到什么效果? 104 | * 写这个程序有哪些功能? 105 | * 这些功能都应该怎么实现? 106 | 107 | 然后,直接开始写代码!哪怕只是创建了几个文件。 108 | 109 | 只有自己思考的,才能深刻理解和记忆。 110 | 111 | 112 | ### 3. 不会就去学,不懂就复制过来用 113 | 114 | 当然,因为仅仅学了基础,你会发现很多功能并不会,甚至比如如何读取一个文件都不会。 115 | 116 | 如果这时候,遇到不会的,就当场去学,哪怕是高级特性,学到把不会的这个点能做出来为止。 117 | 118 | 如果一个功能不懂什么意思,搞不明白,那么直接复制过来,调试到能满足功能为止,然后将这段代码和这个场景记住。 119 | 120 | 121 | ### 4. 多写代码,迈过门槛 122 | 123 | 我一直觉得写代码有一个门槛,在迈过门槛前,学习会非常困难,但是,一旦迈过这个门槛,你就会发现,学起来异常轻松,你所学的技术可以(相对)融会贯通。 124 | 125 | 那么迈过这个门槛的方法就是:重复2和3两个步骤,多写代码,多思考。 126 | 127 | 是的,不断重复,然后可能某一天,当你对你学的技术恍然大悟的时候,那么其实就是迈过了那个门槛。 128 | 129 | 然后,之后便是更深入地学习了。 130 | 131 | 132 | ## 三. 总结 133 | 134 | 一路走来,我觉得最重要的是“多写代码,多思考”。不少代码写到最后甚至都是肌肉记忆了,但是代码还是会变的,自己思考的方式和过程,自己沉淀的知识才是最宝贵的,足以支撑我能够不断学习新的技术,适应新的模式。 -------------------------------------------------------------------------------- /202104/H5检测手机摇一摇.md: -------------------------------------------------------------------------------- 1 | ## 一. 简介 2 | 3 | 要实现h5检测手机摇一摇动作可以直接调用h5原生api。但是在我的实践中发现在`ios`中限制条件比较多,体验还是有些区别的。 4 | 5 | 6 | 7 | ## 二. 如何监听 8 | 9 | 调用`Window: devicemotion event`即可实现监听。`devicemotion`事件以固定的时间间隔触发,并指示设备当时在接收的加速物理力量。 它还提供有关旋转速率的信息(如果有)。 10 | 11 | ```javascript 12 | function handleMotionEvent(event) { 13 | 14 | var x = event.accelerationIncludingGravity.x; 15 | var y = event.accelerationIncludingGravity.y; 16 | var z = event.accelerationIncludingGravity.z; 17 | 18 | // Do something awesome. 19 | } 20 | 21 | window.addEventListener("devicemotion", handleMotionEvent, true); 22 | ``` 23 | 24 | 25 | 26 | ## 三. 安卓机 27 | 28 | 安卓机上直接按照如上即可实现。 29 | 30 | ```html 31 | 32 | 33 | 34 | 35 | 测试摇一摇 36 | 37 | 38 |
39 |
摇一摇
40 |
41 | 42 | 43 | 54 | 55 | ``` 56 | 57 | 58 | 59 | 60 | 61 | ## 四. iPhone 62 | 63 | ### 1. 限制 64 | 65 | 在`ios`上限制有两条: 66 | 67 | * h5必须是`https`协议的 68 | * 必须用户点击授权才可以调用`devicemotion` 69 | 70 | ### 2. 授权 71 | 72 | ```javascript 73 | function getPermission() { 74 | if ( 75 | typeof window.DeviceMotionEvent !== 'undefined' && 76 | typeof window.DeviceMotionEvent.requestPermission === 'function' 77 | ) { 78 | window.DeviceMotionEvent.requestPermission() 79 | .then(function(state) { 80 | if ('granted' === state) { 81 | //用户同意授权 82 | 83 | } else { 84 | //用户拒绝授权 85 | alert('摇一摇需要授权设备运动权限,请重启应用后,再次进行授权!') 86 | } 87 | }) 88 | .catch(function(err) { 89 | alert('error: ' + err) 90 | }) 91 | } 92 | } 93 | ``` 94 | 95 | 直接调用该函数请求授权会导致报错: 96 | 97 | ```bash 98 | error: NotAllowedError: Requesting device orientation or motion access requires a user gesture to prompt 99 | ``` 100 | 101 | 需要用户主动去请求授权,因此此处需要将调用放到比如一个按钮上,让用户去点击请求授权。 102 | 103 | ```html 104 | 105 | ``` 106 | 107 | ### 3. 全部代码 108 | 109 | ```html 110 | 111 | 112 | 113 | 114 | 测试摇一摇 115 | 116 | 117 |
118 | 119 |
120 |
121 | 122 | 123 | 158 | 159 | ``` 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /202012/gin中间件和鉴权.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | `gin`的中间件的使用场景非常广泛,此处主要介绍如何使用其来完成常见场景下的鉴权。 4 | 5 | 6 | 7 | ## 二. 官方文档 8 | 9 | 官方文档列出了如下几种使用方式: 10 | 11 | * [使用中间件](https://github.com/gin-gonic/gin#using-middleware) 12 | * [定制中间件](https://github.com/gin-gonic/gin#custom-middleware) 13 | * [使用基础认证的中间件](https://github.com/gin-gonic/gin#using-basicauth-middleware) 14 | * [中间件使用协程](https://github.com/gin-gonic/gin#goroutines-inside-a-middleware) 15 | 16 | 17 | 18 | ## 三. 不同场景的鉴权实现 19 | 20 | ### 1. api key 21 | 22 | 对于`api key`的方式需要设置白名单,对白名单外的请求进行`token`检测。此中间件在处理请求被处理之前对请求进行拦截,验证token,因此可在此处利用`gin.Context`来设置上下文,如请求所属用户的用户信息等。 23 | 24 | ```go 25 | package middleware 26 | 27 | import ( 28 | "fmt" 29 | "net/url" 30 | "strings" 31 | 32 | "github.com/gin-gonic/gin" 33 | ) 34 | 35 | func whiteList() map[string]string { 36 | return map[string]string{ 37 | "/ping": "GET", 38 | } 39 | } 40 | 41 | func withinWhiteList(url *url.URL, method string) bool { 42 | target := whiteList() 43 | queryUrl := strings.Split(fmt.Sprint(url), "?")[0] 44 | if _, ok := target[queryUrl]; ok { 45 | if target[queryUrl] == method { 46 | return true 47 | } 48 | return false 49 | } 50 | return false 51 | } 52 | 53 | func Authorize() gin.HandlerFunc { 54 | return func(c *gin.Context) { 55 | 56 | type QueryToken struct { 57 | Token string `binding:"required,len=3" form:"token"` 58 | } 59 | 60 | // 当路由不在白名单内时进行token检测 61 | if !withinWhiteList(c.Request.URL, c.Request.Method) { 62 | var queryToken QueryToken 63 | if c.ShouldBindQuery(&queryToken) != nil { 64 | c.AbortWithStatusJSON(200, gin.H{ 65 | "code": 40001, 66 | }) 67 | return 68 | } 69 | 70 | c.Set("role", "user") 71 | } 72 | 73 | c.Next() 74 | } 75 | } 76 | 77 | ``` 78 | 79 | 80 | 81 | ### 2. 路由权限 82 | 83 | #### 1)说明 84 | 85 | 对于请求的处理,需要去验证是否对其请求的路径拥有访问权限。 86 | 87 | 首先看一下`gin`的路由设置: 88 | 89 | ```go 90 | func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes 91 | ``` 92 | 93 | 其参数为`...HandlerFunc`,其解释为: 94 | 95 | ```go 96 | type HandlerFunc func(*Context) 97 | HandlerFunc defines the handler used by gin middleware as return value. 98 | ``` 99 | 100 | 所以此处可以通过定制中间件的方式实现一个路由权限处理。 101 | 102 | 当然此处的权限处理比较简单,使用角色直接去判断权限。如分为两个角色,管理员`admin`和普通用户`user`。 103 | 104 | 不过此处实现有个前提条件,就是如何拿到用户的角色呢?此处需要在上一步(`api key`)的实现中加上利用`gin.Context`设置角色: 105 | 106 | ```go 107 | c.Set("role", "admin") // 可见上一步的代码,当然此处只是为了演示设置固定值 108 | ``` 109 | 110 | 然后在中间件中拿到角色并进行判断。 111 | 112 | 113 | 114 | #### 2)路由权限中间件 115 | 116 | ```go 117 | package middleware 118 | 119 | import ( 120 | "errors" 121 | "fmt" 122 | 123 | "github.com/gin-gonic/gin" 124 | ) 125 | 126 | func Permissions(roles []string) gin.HandlerFunc { 127 | return func(c *gin.Context) { 128 | 129 | permissionsErr := func() error { 130 | 131 | // 获取上下文中的用户角色 132 | roleValue, exists := c.Get("role") 133 | if !exists { 134 | return errors.New("获取用户信息失败") 135 | } 136 | role := fmt.Sprint(roleValue) 137 | 138 | // 判断请求的用户的角色是否属于设定角色 139 | noAccess := true 140 | for i := 0; i < len(roles); i++ { 141 | if role == roles[i] { 142 | noAccess = false 143 | } 144 | } 145 | if noAccess { 146 | return errors.New("权限不够") 147 | } 148 | 149 | return nil 150 | 151 | }() 152 | if permissionsErr != nil { 153 | c.AbortWithStatusJSON(200, gin.H{ 154 | "code": 40001, 155 | }) 156 | return 157 | } 158 | 159 | c.Next() 160 | } 161 | } 162 | 163 | ``` 164 | 165 | 166 | 167 | #### 3)使用 168 | 169 | 在设置路由时候,添加该中间件,并设置白名单。 170 | 171 | ```go 172 | r.POST("/todo", middleware.Permissions([]string{"admin"}), views.AddTodo) // 添加中间件将会验证角色 173 | r.PUT("/todo", views.ModifyTodo) // 未添加中间件则不会验证角色 174 | ``` 175 | 176 | -------------------------------------------------------------------------------- /202002/vue2实现实时生成二维码和将网页合成图片并在微信内置浏览器长按保存.md: -------------------------------------------------------------------------------- 1 | ## 一. url转为二维码 2 | 3 | ### 1. 需要的库 4 | 5 | [qrcodejs2](https://www.npmjs.com/package/qrcodejs2) 6 | 7 | ### 2. 安装 8 | 9 | ```bash 10 | npm install qrcodejs2 --save 11 | ``` 12 | 13 | ### 3. 引入 14 | 15 | ```javascript 16 | import QRCode from "qrcodejs2" 17 | ``` 18 | 19 | ### 4. 实现 20 | 21 | ```vue 22 | 27 | 28 | 49 | ``` 50 | 51 | 52 | 53 | ## 二. 网页保存为图片 54 | 55 | ### 1. 需要的库 56 | 57 | [html2canvas](https://github.com/hongru/canvas2image) 58 | 59 | ### 2. 安装 60 | 61 | ```bash 62 | npm install html2canvas --save 63 | ``` 64 | 65 | ### 3. 引入 66 | 67 | ```javascript 68 | import html2canvas from "html2canvas" 69 | ``` 70 | 71 | ### 4. 实现 72 | 73 | ```vue 74 | 79 | 80 | 99 | ``` 100 | 101 | 102 | 103 | ## 三. 整合 104 | 105 | 关于小程序内置浏览器的图片下载,需要一个用来生成图片的块,还需要一个`img`,先将其隐藏。实现步骤就是首先生成二维码,然后再将html生成图片,最后在html2canvas回调中替换`img`的`src`,并将生成图片的块隐藏,将`img`显示。 106 | 107 | 当然关于这个实现方式,我看到的技术分享文章中,还有两种不同的解决方式: 108 | 109 | * 不需要html来写生成图片的块,而是使用js直接创建; 110 | * 不需要替换隐藏,将生成的图片覆盖到html生成图片的块之前; 111 | 112 | 这里我只记录一下我使用的,后期会再去研究这两种实现方式。 113 | 114 | ```vue 115 | 128 | 129 | 170 | 171 | 177 | ``` 178 | 179 | 由此即可实现需要的功能了。 180 | 181 | 关于后续的优化,需要解决的图片清晰度问题、跨域图片问题等,可以参考[这篇文章](https://segmentfault.com/a/1190000011478657),这位大佬写得很详细。 182 | 183 | -------------------------------------------------------------------------------- /202110/你是个成熟的代码要学会自己按需引入了.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | 前端小伙伴儿们是不是经常遇到ui组件全局引入导致体积太大,按需引入导致不断手写会很麻烦。所以,当当!今天我们来让代码自己按需引入,解放前端小伙伴儿们的生产力,早日实现下班自由!(甲方:我要再改十个需求!) 4 | 5 | 6 | 7 | ## 二. 介绍 8 | 9 | 我们这里介绍的是`unplugin-vue-components`。该组件是由vue核心开发成员antfu开发的,尤大也是推荐的,且是该项目的金牌赞助商。 10 | 11 | 该组件主要是为了实现vue项目的组件自动引入。 12 | 13 | 官方文档:[antfu/unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) 14 | 15 | 16 | 17 | ## 三. 完整案例 18 | 19 | 此处我们以vite为例,来主要看一下其对于自定义组件和UI库组件的自动按需引入。 20 | 21 | 源码:[https://github.com/Kuari/Blog/tree/master/Examples/unplugin_auto_import](https://github.com/Kuari/Blog/tree/master/Examples/unplugin_auto_import) 22 | 23 | 24 | 25 | ### 1. 创建项目 26 | 27 | ```bash 28 | yarn create vite unplugin_auto_import --template vue 29 | ``` 30 | 31 | 然后进入文件夹安装依赖。 32 | 33 | ```bash 34 | cd unplugin_auto_import 35 | yarn install 36 | ``` 37 | 38 | ### 2. 安装unplugin-vue-components 39 | 40 | ```bash 41 | yarn add -D unplugin-vue-components 42 | ``` 43 | 44 | ### 3. 配置vite.config.js 45 | 46 | ```javascript 47 | import { defineConfig } from 'vite' 48 | import vue from '@vitejs/plugin-vue' 49 | import Components from 'unplugin-vue-components/vite' // 新增 50 | 51 | // https://vitejs.dev/config/ 52 | export default defineConfig({ 53 | plugins: [ 54 | vue(), 55 | Components({ /* options */ }) // 新增 56 | ] 57 | }) 58 | ``` 59 | 60 | ### 4. 自动引入自定义组件 61 | 62 | 我们默认模板创建的项目中,默认在`App.vue`中引入了`./components/HelloWorld.vue`。此处就可以来尝试下如何自动引入了。 63 | 64 | 在配置了`unplugin-vue-components`之后,现在只需要删除引入行(其实这时候打开vscode就会发现改行已经灰掉了),被删除行如下所示: 65 | 66 | ```javascript 67 | import HelloWorld from './components/HelloWorld.vue' 68 | ``` 69 | 70 | 删除以后完整`App.vue`如下所示: 71 | 72 | ```vue 73 | 75 | 76 | 80 | 81 | 91 | ``` 92 | 93 | 现在在命令行中运行`yarn dev`,再打开浏览器查看`http://localhost:3000`页面,是不是发现,哎?!居然引入了!(心中狂喜,要早日实现下班自由了) 94 | 95 | 截屏2021-10-27 下午2.47.55 96 | 97 | ### 5. 自动引入UI库组件 98 | 99 | 这里以element plus为例。 100 | 101 | 首先是安装`element plus`。官方文档也没说要装,我还以为内置呢,一直报错,有点懵逼,哈哈。 102 | 103 | ```bash 104 | yarn add -D element-plus 105 | ``` 106 | 107 | 引入element plus的resolver,此处编辑vite.config.js文件,修改后如下所示: 108 | 109 | ```javascript 110 | import { defineConfig } from 'vite' 111 | import vue from '@vitejs/plugin-vue' 112 | import Components from 'unplugin-vue-components/vite' 113 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' // 引入ElementPlusResolver 114 | 115 | // https://vitejs.dev/config/ 116 | export default defineConfig({ 117 | plugins: [ 118 | vue(), 119 | Components({resolvers: [ElementPlusResolver()]}) // 添加配置 120 | ] 121 | }) 122 | ``` 123 | 124 | 那么现在神奇的事情来了,就可以直接使用UI库的组件了! 125 | 126 | 我们在`App.vue`中使用一个`el-button`组件试试。我们在`App.vue`中加入如下行: 127 | 128 | ```vue 129 | Kuari 130 | ``` 131 | 132 | 添加后`App.vue`上下代码如下: 133 | 134 | ```vue 135 | 140 | ``` 141 | 142 | 现在,运行`yarn dev`,打开浏览器,可以看到,就直接可以使用UI库的组件了。 143 | 144 | 截屏2021-10-27 下午2.59.06 145 | 146 | ### 6. 打包 147 | 148 | 按需引入的功能并不是仅仅在开发时候的,在打包时,该组件也是Tree-shakable的,只会将你用了的组件打包。 149 | 150 | 按照当前教程所写的项目,当全局引入的时候,打包的dist文件夹为1.1MB,而使用该组件之后打包,其dist文件夹为202KB。 151 | 152 | 153 | 154 | ## 四. 最后 155 | 156 | 手动按需导入是不可能手动按需导入的,这辈子都不可能了/狗头。 157 | 158 | 毕竟是大佬开发的强力工具,希望前端小伙伴儿们早日实现下班自由。 -------------------------------------------------------------------------------- /202011/gin的http单元测试.md: -------------------------------------------------------------------------------- 1 | ## 一. 前言 2 | 3 | Go 标准库专门提供了 `net/http/httptest` 包专门用于进行 http Web 开发测试。 4 | 5 | 此处基于gin来实现http的单元测试。 6 | 7 | 8 | 9 | ## 二. GET请求 10 | 11 | 此处使用http单元测试对`/ping`请求测试,其正常会返回字符串`pong`,响应码为`200`。 12 | 13 | ### web应用 14 | 15 | ```go 16 | package main 17 | 18 | func setupRouter() *gin.Engine { 19 | r := gin.Default() 20 | r.GET("/ping", func(c *gin.Context) { 21 | c.String(200, "pong") 22 | }) 23 | return r 24 | } 25 | 26 | func main() { 27 | r := setupRouter() 28 | r.Run(":8080") 29 | } 30 | ``` 31 | 32 | ### http单元测试 33 | 34 | ```go 35 | package main 36 | 37 | import ( 38 | "net/http" 39 | "net/http/httptest" 40 | "testing" 41 | 42 | "github.com/stretchr/testify/assert" 43 | ) 44 | 45 | func TestPingRoute(t *testing.T) { 46 | router := setupRouter() 47 | 48 | // 创建http server并发起请求 49 | w := httptest.NewRecorder() 50 | req, _ := http.NewRequest("GET", "/ping", nil) 51 | router.ServeHTTP(w, req) 52 | 53 | assert.Equal(t, 200, w.Code) // 断言响应码为200 54 | assert.Equal(t, "pong", w.Body.String()) // 断言响应为"pong" 55 | } 56 | ``` 57 | 58 | 59 | 60 | ## 三. POST/PUT/DELETE请求 61 | 62 | post、put、delete请求处理相似,都是处理其request body,因此此处只以post为例。 63 | 64 | 此处对`/todo`进行测试,其正常返回为`json`,其中`code`为`20000`,响应码为`200`。 65 | 66 | ### web应用 67 | 68 | ```go 69 | package main 70 | 71 | func setupRouter() *gin.Engine { 72 | r := gin.Default() 73 | r.POST("/todo", func(c *gin.Context) { 74 | type ToDo struct { 75 | TodoId int `binding:"required" json:"todo_id"` 76 | } 77 | 78 | var todo ToDo 79 | if err := c.ShouldBindJSON(&todo); err != nil { 80 | c.JSON(400, gin.H{ 81 | "message": "参数不全", 82 | }) 83 | return 84 | } 85 | 86 | // 处理代码,此处以打印为例,省略处理... 87 | fmt.Println(todo.TodoId) 88 | 89 | c.JSON(200, gin.H{ 90 | "code": 20000, 91 | }) 92 | 93 | }) 94 | return r 95 | } 96 | 97 | func main() { 98 | r := setupRouter() 99 | r.Run(":8080") 100 | } 101 | ``` 102 | 103 | ### http单元测试 104 | 105 | ```go 106 | package main 107 | 108 | func TestTodoCreate(t *testing.T) { 109 | 110 | // 请求方法 111 | method := "POST" 112 | // 请求路由 113 | urlStr := "/todo" 114 | // request body 115 | body := map[string]interface{}{ 116 | "title": "hello", 117 | } 118 | 119 | jsonByte, _ := json.Marshal(body) 120 | req := httptest.NewRequest(method, tc.urlStr, bytes.NewReader(jsonByte)) 121 | w := httptest.NewRecorder() 122 | router := routers.SetupRouter() 123 | router.ServeHTTP(w, req) 124 | 125 | assert.Equal(t, 200, w.Code) // 判断响应码 126 | 127 | var response map[string]int 128 | json.Unmarshal([]byte(w.Body.String()), &response) 129 | 130 | value, exits := response["code"] 131 | assert.True(t, exits) 132 | assert.Equal(t, 20000, value) // 判断自定义状态码 133 | } 134 | ``` 135 | 136 | 137 | 138 | ## 四. 封装测试请求 139 | 140 | 由于http单元测试代码中存在较多重复,因此此处封装重复代码。 141 | 142 | ```go 143 | package tests 144 | 145 | type TestConfig struct { 146 | Url string 147 | Method string 148 | Body interface{} 149 | } 150 | 151 | func (tc *TestConfig) Request() *httptest.ResponseRecorder { 152 | 153 | var req *http.Request 154 | if tc.Body != nil { 155 | jsonByte, _ := json.Marshal(tc.Body) 156 | req = httptest.NewRequest(tc.Method, tc.Url, bytes.NewReader(jsonByte)) 157 | } else { 158 | req = httptest.NewRequest(tc.Method, tc.Url, nil) 159 | } 160 | 161 | w := httptest.NewRecorder() 162 | router := routers.SetupRouter() 163 | router.ServeHTTP(w, req) 164 | return w 165 | } 166 | ``` 167 | 168 | 169 | 170 | ## 五. 封装后的单元测试 171 | 172 | 此处以post请求为例。 173 | 174 | ```go 175 | package main 176 | 177 | func TestAddTag(t *testing.T) { 178 | // 新增tag 179 | var addTestConfig tests.TestConfig 180 | addTestConfig.Method = "POST" 181 | addTestConfig.Url = "/tag" 182 | addTestConfig.Body = map[string]string{ 183 | "tag_name": "HelloHello", 184 | } 185 | 186 | w := addTestConfig.Request() 187 | assert.Equal(t, 200, w.Code) 188 | 189 | var addResponse map[string]int 190 | json.Unmarshal([]byte(w.Body.String()), &addResponse) 191 | 192 | value, exits := addResponse["code"] 193 | assert.True(t, exits) 194 | assert.Equal(t, 20000, value) 195 | 196 | lastInsertTagId, exits := addResponse["tag_id"] 197 | assert.True(t, exits) 198 | } 199 | ``` 200 | 201 | --------------------------------------------------------------------------------