Wednesday, June 2, 2021

V2EX - 技术

V2EX - 技术


卷比到什么程度,会在代码里写位运算

Posted: 02 Jun 2021 04:00 AM PDT

代码看的火大,乘除都看不懂了,业务代码用位运算,这人是写个框架还不直接字节码啊

Android 开发的入门问题:有必要应该直接学习 Jetpack 和 Flutter 吗?

Posted: 02 Jun 2021 03:56 AM PDT

主题并非引战,基于自己的认知提出疑问。

学习 Android 开发时,发现 Jetpack 和 Flutter 的存在,通过官网得知其为生产力助手以及主流之选。

那么 Android 开发入门建议直接从这俩入手学习吗?避开传统的 xml 界面编写等。

也许问题描述的并不合理,我想用 Spring 来举个例子:

对于 Java Web 的业余爱好者,我会推荐其从 Spring Boot 直接开始,而不是 Spring 以及 Spring MVC 。 

现在对于入门 Android 的我来说,单纯为爱好学习,手里已经有了《第一行代码 2&3 版》,但是其中没有关于 Flutter 的介绍,所以很是困惑,想知道目前的主流学习路径。

担心出现学习弯路,比如从 Spring 、Spring MVC 以及 Spring Boot 一路学习过去,但是发现最后仅仅使用 Spring Boot 。

做基础架构方向,是不是更高大上?

Posted: 02 Jun 2021 03:52 AM PDT

感觉做基础架构涉及的技术都好高端呀。 高可用啊,异地多活,流量调度,微服务框架,监控报警 tracking,异步一致性,限流熔断降级,等高大上的名字好像和我们 CURD body 无关呢。

Namecheap 的优势在哪里呢?

Posted: 02 Jun 2021 03:50 AM PDT

发现很多大佬网站的域名都在这家注册。但感觉这家价格一点都不 cheap,面板还不太好用。

最近公司 Android 开发招人, JD 定了需要 Kotlin 经验, HR 抱怨

Posted: 02 Jun 2021 03:48 AM PDT

HR 抱怨说 kotlin 这条规定筛不到可以面试的开发,我比较好奇,现在安卓开发的简历不提 Kotlin 吗?

这个价格你会心动吗?

Posted: 02 Jun 2021 03:45 AM PDT

DS220+ 优惠价格:2179
DS920+ 优惠价格:3840

部分安装我们公司安卓 App 的用户收到了派出所的电话,说可能安装了诈骗 App

Posted: 02 Jun 2021 03:39 AM PDT

我们公司的 App 刚上线,用户是通过我们自己的链接下载的 App,这是什么原因呢?有遇到过的吗?
App 是销售建筑辅材的商城

青云科技 云计算技术上 有啥优势啊

Posted: 02 Jun 2021 03:26 AM PDT

青云科技 在云计算技术上有啥优势

我理解的云计算不就是分割出容器

在有限的磁盘空间以及硬件能力下 为客户提供虚拟主机(类似 vps ) 同时提供类似算力模板类型的东西 为客户提供服务

他们是 IO 读写能力强吗

想了解了解这家公司 大佬们 说说呗

百万数量级小文件迁移怎么做?

Posted: 02 Jun 2021 03:26 AM PDT

服务器上有 3 个 t 的数据大概由百万级别数量的小文件构成,现在挂了一个 5 个 t 的硬盘想把 3 个 t 的数据备份到硬盘上有啥好方法么 cp mv rsync tar 都试过了 不太好使

各位大佬, LocalDateTime 的副本指的是什么?

Posted: 02 Jun 2021 03:24 AM PDT

Google Photos 已经不再提供无限存储的第二天,大家都还好吗?

Posted: 02 Jun 2021 03:16 AM PDT

我反正将桌面版的照片备份客户端都删掉了……

Google 居然没直接砍掉这产品,不像它之前的风格啊!

你们一般怎么做秒杀地址隐藏的

Posted: 02 Jun 2021 03:13 AM PDT

看网上的方案先输入验证码获取地址 path,然后把 path 拼接到真正的秒杀地址,总感觉有点多余,没啥意义,我把验证码直接放到秒杀接口不就行了吗

v 站底部的在线人数是如何实现的?

Posted: 02 Jun 2021 03:09 AM PDT

xxx 人在线 最高纪录 xxx

[分享]跨平台同步笔记资料可选方案

Posted: 02 Jun 2021 03:08 AM PDT

昨天试了一下,用的 Insync (收费,非广告,只是觉得还不错,大家有好的希望能推荐)在 Windows 和 Linux 上同步文件,体验觉得不错,说一下优点:
1. 跨平台,Linux 下 google drive 、onedrive 的客户端一是 cli 的,2 是需要手动 sync,要是自动需要写些脚本,而且我装了好几次都不太好使。
2. 设置成完全同步(它里面有个 Base Directory 的概念,反正我就是设置的那个)本地删除之后,我是用的 goole drive,google drive 上面也会删除,不过是放到回收站( 30 天后 delete forever ),这个舒服啊。如果不小心本地误删了,可以通过 google drive 找回,给本地文件加了一层保障。这个地方也是无缝的,你在本地做的更改都会及时同步到云端。
3. 可以同时用多个 google drive 、onedrive 。

缺点:
1. 收费,不过可以一次性买断 40 刀,听说有 50%的折扣,这个不太清楚什么时间或什么方式搞,看之前的帖子有说直接给客服发邮件可以给折扣的,有点东西哈。
2. Base Directory 只能设置一个,没找到能同步多个指定文件夹的办法,目前是把需要同步的都放到一个路径。

坑点:
1. 软件需要挂代理,你们懂的,要不连不上 google drive 、onedrive
2. 我看价格那里需要 60 刀才能使用 Ignore rules,但是我记得我试用的是 40 刀的软件,好像也能用,目前还在试用。

最后就是用 Baidu 网盘同步一些大的文件,不需要经常同步的。

推荐周志明大大的一片文章《云原生时代, Java 的危与机》

Posted: 02 Jun 2021 02:58 AM PDT

不懂就问,这个判断是啥意思?

Posted: 02 Jun 2021 02:50 AM PDT

String origin = request.getHeader("Origin"); if (!"".equals(origin) && origin != null) {   response.setHeader("Access-Control-Allow-Origin", "xxx");   response.setHeader("Access-Control-Allow-Methods", "POST, GET, HEAD, OPTIONS, DELETE");   response.setHeader("Access-Control-Max-Age", "3600");   response.setHeader("Access-Control-Allow-Credentials", "true");   response.setHeader("Access-Control-Allow-Headers",                      "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); } filterChain.doFilter(request, response); 

CORS 相关的代码,这里为啥要判断 origin 不等于空?

请教,有没有离线工具,可以直接将英文 pdf 转化为中文

Posted: 02 Jun 2021 02:48 AM PDT

我使用 wkhtmltopdf 将网页转化为 pdf,想使用类似的离线工具,将英文 pdf 转化为中文

弄了几天了,求问那个安卓框架可以获取点击获取 pdf 文件的内容

Posted: 02 Jun 2021 02:44 AM PDT

要想再安卓点击选择 pdf 文件获取内容然后翻译,找了现成的第三方框架都无法实现,求推荐一个

请问 CMD 中如何把一段 SQL 作为 mysql 的输入,并将查询结果输出到文件?

Posted: 02 Jun 2021 02:43 AM PDT

现在得实现是两步;
1.echo sql 语句 > 文件
2.mysql -u -p < 文件 > 结果文件
请问怎么合并成一步完成?谢谢。

调试的时候,怎么 debug 进入到第三方的 library 里面去?

Posted: 02 Jun 2021 02:37 AM PDT

我有一个项目,A,最后编译成 一个 package, 可以 publish 出去的。

现在,我需要写一个集成测试,就是新建立一个项目 B,里面都是测试,用 jest 跑,主要就是调用包 A 里面的函数,测试它。

现在,我 B 运行时,调用 A 的函数返回值不是预期的,我想 debug 进入到 A 里面。但是 Debug jest,B 里面断点都是有效的,能正常工作。但是我通过"node_modules"里面打开 A 的.js 文件(编译后的文件,安装 A 以后,有 a.js 还有 a.js.map, a.d.ts ),在里面打断点,运行起来后,无效。断点不会打断。

这个是怎么回事?有相关办法么?

谢谢!

求助帖,应用集群个别实例 young gc 时间突然飙升该如何排查?

Posted: 02 Jun 2021 02:31 AM PDT

问题描述

一个线上 16 个节点的服务,突然有两个节点 young gc 时间异常。从原先的 10ms 飙升到 1000+ms,到后面的 3s 以上。 目前解决方法是先把这两个节点给剔除了,但是没有下线;


gc-log 分析链接

应用配置和状态

JVM 参数

-Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=5000 -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=80 -XX:+DisableExplicitGC -verbose:gc -Xloggc:/data/logs/app-gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Dfile.encoding=UTF-8 -Djava.awt.headless=true -XX:+UseCompressedOops -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/app.dump -XX:MaxDirectMemorySize=256m -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExplicitGCInvokesConcurrent 

容器配置

  • 4C8g

内存监控

提问

想请教一下这种 young gc 时间突然上升的,而且是个别节点的出现的问题该如何排查?正常节点的 young gc 频率和异常节点大致相同,但是时间相差巨大。

大佬们,有没有优秀的 SVG 教程推荐一波?

Posted: 02 Jun 2021 02:08 AM PDT

MDN 上那个教程有点短,好多想知道的知识点没有。

发现我的红米 K20 在 Google Photo 里被识别为 Pixel,可以无限量使用高画质备份

Posted: 02 Jun 2021 02:04 AM PDT

突然白嫖

[api 监控] 想统计 flask 服务每个 url 的访问次数, 500, 200 的频率,有什么现成的解决方案吗?

Posted: 02 Jun 2021 01:50 AM PDT

最终想要呈现的效果是,在 grafana dashboard 里面显示 top10 访问次数最多的 url

目前已有 influxdb, 系统监控用的是 telegraf,里面还集成了 statsd 插件。
但是 statsd 似乎只能监控一个 url,没办法 group by,然后取 top10

目前就这条件,想空手求个开源插件或者轮子

google photos 新政策启用了吗?

Posted: 02 Jun 2021 01:42 AM PDT

刚想起来 61 开始不免费了,但昨天 61 晚上我还备份了两个 1G 的视频,今天看看居然占用空间还是 0 ? 而且之前好像根本没有这个 0,直接没有这行的我记得, Imgur

关于 aop 记录日志的一个问题

Posted: 02 Jun 2021 01:16 AM PDT

  • 希望通过 aop 在接口被调用时打印日志
  • 现在有两个模块,一个 common,一个 business,切面类写在 common 模块下,execution 表达式匹配的是 business 下的 controller ;现在的问题是 common 是不应该依赖 business 的,这导致 execution 表达式匹配不到 business 下的 controller,我应该把这个切面类移到 business 下吗,这样的话有多个业务模块得重写多次切面类吗

请教大家一个关于 spring security 鉴权的问题

Posted: 02 Jun 2021 01:12 AM PDT

我想将一个方法中权限验证与业务尽可能地解耦

例如下面这样一个方法

deleteDocument(String docId) { ... }

通过 docId 查找数据库中的 document 表,确认其中的 owner 是否是当前登录用户。 如果:1.是当前登录用户 2.不是当前登录用户,但当前登录用户是管理员 即判断有删除文档的权限,之后就进行纯粹的业务逻辑的代码。

请问上述操作 spring security 有实现的办法吗。或者说有其他的方法可以实现?谢谢大家!

腾讯这个网页刷新技术怎么实现的?

Posted: 02 Jun 2021 01:01 AM PDT

请看: https://work.weixin.qq.com/api/doc/90001/90144/92388

切换文档时顶部和左边的菜单都不会刷新,只有文档内容区域刷新了,表现很像 vue 的单页应用,局部刷新内容。

但是右键查看网页源码又可以看到所有的内容,这样对搜索引擎就很友好。

这种可以做到局部刷新,但是又可以查看到源码的是怎么实现的?有人知道吗?

另外我看他切换文档时接口请求回来的数据有 html 的,有 md 格式的,还有 text 格式的,这是为什么

有没有 Windows 下好用的 samba 客户端/浏览器

Posted: 02 Jun 2021 12:55 AM PDT

Windows 自带的有几个痛点: 1 、不能用非标端口 2 、不能映射根目录(如 \file.com\ 这种) 3 、只能使用一个账号

网上找了一圈好像都没有解决的办法,问一下大家都是怎么解决这个问题的,或有没有好用的客户端推荐

app 可以配置 referer 吗

Posted: 02 Jun 2021 12:38 AM PDT

事情是这样的

现在公司 app 使用的是七牛云 oss 对象存储 (公共 bucket )

然后源地址被盗刷。每天多跑 1T 下载流量。

所以想问下大家,有什么思路。

通过七牛日志是可以排查到一些 IP

但是手动添加 IP 也不高效。

所以请问下有什么解决方案

Android 如何限制指定 app 的 cpu 使用率或频率?

Posted: 02 Jun 2021 12:25 AM PDT

很多人吐槽安卓卡或者发热严重, 几乎可以 100%肯定出现这种情况必然是有 app 在疯狂占用 cpu 。
我有几次出现这种突然变卡和发热的情况,然后使用 top 命令后发现好几次是微信在后台长时间占用 50%以上的 cpu 使用率。

如果能做到的话可以利用一些自动化的方式让 app 在后台的时候严格限制 cpu 的使用率,前台的时候可以不限制,以免前台的时候操作 app 很卡。

google photos 存储空间问题

Posted: 01 Jun 2021 11:36 PM PDT

我想问下,这个 1.99 刀 /月,100G, 这个是每个月给你 100G 么?

前端如何对网络请求返回的数据进行“预处理”?

Posted: 01 Jun 2021 11:00 PM PDT

当前端拿到请求返回的数据之后需要进行一下处理,数据才能用于通用组件。 比如给 list 中的 item 添加一个属性、 给 item 的某个属性改个键名等等。 这种情况有什么最佳实践,或者说好的范式吗? 还是只能一点点扣

请教一个 centos7 默认防火墙问题

Posted: 01 Jun 2021 10:20 PM PDT

各位大佬 ,请教个问题
Centos7 默认防火墙是 firewalld,防火墙开启后,只开放了 80 端口和 SSH 端口,用 firewall-cmd --list-all 也只能看到 80 和 SSH 端口,但服务器上装了 PHP-FPM ( 9000 端口),9000 端口没允许通过防火墙,为什么在外面机器上还是能 telnet 通呢?怎样让 9000 端口在外面机器上 telnet 不通呢

Android 为什么没有像 iOS 点击屏幕上方自动返回页面顶端的手势?

Posted: 01 Jun 2021 10:20 PM PDT

从 iPhone 换到 Pixel,不习惯的地方很少,这点算一个

Vite-babysitter 像月嫂?保姆?照顾孩子一般为你讲解 Vite 源码。

Posted: 01 Jun 2021 09:46 PM PDT

在老家 v2 发个看看,求点小星星!

之前刷到有帖子介绍 Vite,我就去看了下源码然后记录下来分享一下,

然后发现不能全部 copy 过来超字数了。。看线上文档吧。

查看线上文档体验更佳 查看文档 Powered by dumi

前言

该项目如名,像月嫂?保姆?照顾孩子一般为你讲解Vite源码。

  • NPM 依赖解析和预构建: 全面提升页面重载速度和强缓存依赖。

  • Plugins 插件:可以利用 Rollup 插件的强大生态系统,同时根据需要也能够扩展开发服务器和 SSR 功能。

  • 动态模块热重载( HMR ):Vite 提供了一套原生 ESM 的 HMR API 。 具有 HMR 功能的框架可以利用该 API 提供即时、准确的更新,而无需重新加载页面或删除应用程序状态。

目前 [依赖解析和预构建] 章节已经施工完毕,插件和 HMR 的源码解析将在两周内上线。

Vite 的版本为 2.3.3 。

看完有帮助的可以进入github给我一个🌟小星星 谢谢!

NPM 依赖解析和预构建

目录

  1. 代码入口
  2. 预构建对象和前期准备
  3. 构建和插件
  4. 最后

1. 代码入口

在 cli.ts 文件中,接收命令行的运行参数。

// 命令行输入命令启动 vite npm run dev // 根据 package 调用 vite 并获取命令参数 如--force build... vite xxxx xxx xxx 

vite 运行的第一步,获取命令参数,最后创建 server 并运行 listen 函数。

//cli.ts  .action(async (root: string, options: ServerOptions & GlobalCLIOptions) => {         const { createServer } = await import('./server')         try {                 const server = await createServer({                 ...                 })                 await server.listen()         } catch (e) {                 ...         } }) 

listen 函数中,runOptimize 函数就是预构建的核心代码。

// server/index.ts => listen if (!middlewareMode && httpServer) {   // overwrite listen to run optimizer before server start   const listen = httpServer.listen.bind( httpServer);   httpServer.listen = (async (port: number, ...args: any[]) => {     try {       await container.buildStart({});       await runOptimize();     } catch (e) {       httpServer.emit('error', e);       return;     }     return listen(port, ...args);   }) as any;   ... } else {   await container.buildStart({});   await runOptimize(); }  // server/index.ts import { DepOptimizationMetadata, optimizeDeps } from '../optimizer'  const runOptimize = async () => {   if (config.cacheDir) {     server._isRunningOptimizer = true;     try {       server._optimizeDepsMetadata = await optimizeDeps(config);     } finally {       server._isRunningOptimizer = false;     }     server._registerMissingImport = createMissingImporterRegisterFn(server);   } }; 
// server/index.ts import { DepOptimizationMetadata, optimizeDeps } from '../optimizer'  const runOptimize = async () => {   if (config.cacheDir) {     server._isRunningOptimizer = true;     try {       server._optimizeDepsMetadata = await optimizeDeps(config);     } finally {       server._isRunningOptimizer = false;     }     server._registerMissingImport = createMissingImporterRegisterFn(server);   } }; 

入口代码很简单,获取了 vite 命令行参数后,创建内部 server,触发各个功能的构建。

接下来进入详解 optimizeDeps 的章节。

预构建对象和前期准备

首先获取预缓存(metadata.json)的路径,以及预构建的 hash 值,以便后续比对。

这个 json 文件为 vite 处理后导出的数据信息,当此文件存在时,会比对 hash 值,如果相同就会直接读取此文件中的依赖。

// /optimizer.ts async function optimizeDeps(   config: ResolvedConfig,   force = config.server.force,   asCommand = false,   newDeps?: Record<string, string>, ) {   const { root, logger, cacheDir } = config    // 这边第三个 args 为 asCommand, 是否是命令行运行的    // 为了讲述的流畅性,在上一章节代码入口没有提到, 在 vite --force 后,会直接运行 optimizeDeps 函数,因此需要区分 log 的输出方式    // vite --force    =>    await optimizeDeps(config, options.force, true)   const log = asCommand ? logger.info : debug    if (!cacheDir) {     log(`No cache directory. Skipping.`)     return null    //这边首先获取 预构建模块路径   const dataPath = path.join(cacheDir, '_metadata.json'); //预缓存路径   // /.../my-vue-app/node_modules/.vite/_metadata.json   const mainHash = getDepHash(root, config);   // 创建一个 data 的对象,后面会用到   const data: DepOptimizationMetadata = {     hash: mainHash,     browserHash: mainHash,     optimized: {},   }; 

如何获取 hash 值?

首先获取了预构建模块的路径,默认情况为 node_modules/.vite 。

以下为 metadata.json 的数据结构, 后续会说到。

// node_modules/.vite/_metadata.json {   "hash": "9a4fa980",   "browserHash": "6f00d484",   "optimized": {     "vue": {       "file": "/.../my-vue-app/node_modules/.vite/vue.js",       "src": "/.../my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js",       "needsInterop": false     },     "axios": {       "file": "/.../new/my-vue-app/node_modules/.vite/axios.js",       "src": "/.../new/my-vue-app/node_modules/axios/index.js",       "needsInterop": true     }   } } 

接着我们看 getDepHash 函数。 官方文档中描述,Vite 在预构建之前,根据以下源来确定是否要重新运行预构建。

  • package.json 中的 dependencies 列表
  • 包管理器的 lockfile,例如 package-lock.json, yarn.lock,或者 pnpm-lock.yaml
  • 可能在 vite.config.js 相关字段中配置过的

以下代码中,变量 lockfileFormats 就是包管理器的 locakfile 。

// /optimizer.ts  const lockfileFormats = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'];  // /optimizer.ts => getDepHash let cachedHash: string | undefined;  function getDepHash(root: string, config: ResolvedConfig): string {   if (cachedHash) {     return cachedHash;   }   let content = lookupFile(root, lockfileFormats) || ''; //往下滑会有 lookupFile 函数的解释。   // 这边已经获取了所有 local file array 内的文件内容    // also take config into account   // only a subset of config options that can affect dep optimization    content += JSON.stringify(     {       mode: config.mode,       root: config.root,       resolve: config.resolve,       assetsInclude: config.assetsInclude,       plugins: config.plugins.map((p) => p.name),       optimizeDeps: {         include: config.optimizeDeps?.include, // null         exclude: config.optimizeDeps?.exclude, //null       },     },     (_, value) => {       if (typeof value === 'function' || value instanceof RegExp) {         return value.toString();       }       return value;     },   );   //这里不说了  最终返回 "9a4fa980" 八位数 hash 值。   return createHash('sha256').update(content).digest('hex').substr(0, 8); }  // /optimizer.ts => lookupFile function lookupFile(   dir: string,   formats: string[],   pathOnly = false, ): string | undefined {   for (const format of formats) {     const fullPath = path.join(dir, format); //获取 root + format 路径     // 路径对象是否存在 并且是文件     // pathOnly 为 true 就只返回路径,不然就都默认返回 utf-8 的文件内容     if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {       return pathOnly ? fullPath : fs.readFileSync(fullPath, 'utf-8');     }   }   const parentDir = path.dirname(dir);   if (parentDir !== dir) {     return lookupFile(parentDir, formats, pathOnly);   } } 

是否强制优化并处理.vite 文件夹

获取了预构建的 hash 值后,让我退回到 optimizeDeps 函数中,继续往下看。

通过参数 force 来判断是否需要强制优化,如果不需要那就对比老 hash 值,如果相等就返回老的 metadata.json 文件内容。

最后处理.vite 文件夹,为后续做准备。

// /optimizer.ts ... const data: DepOptimizationMetadata = {     hash: mainHash, //"9a4fa980"     browserHash: mainHash, //"9a4fa980"     optimized: {},   };   // 是否强制预先优化 不管是否已经更改。 // force = config.server.force 来源于 cli.ts ,获取命令行参数中是否有 --force if (!force) {   let prevData;   try {     // 尝试解析已经存在的 metadata 数据, 获取 /.vite/metadata.json 中的内容     prevData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));   } catch (e) {}   // hash is consistent, no need to re-bundle   // 如果预 dep 数据的 hash 相同,那就直接跳过,如果需要覆盖就使用 --force   if (prevData && prevData.hash === data.hash) {     log('Hash is consistent. Skipping. Use --force to override.');     return prevData;   } } //如果 node_modules/.vite 存在,那就清空。 if (fs.existsSync(cacheDir)) {   emptyDir(cacheDir); } else {   // 要不然就创建文件夹, 并且 recursive:true 返回创建文件夹的路径   fs.mkdirSync(cacheDir, { recursive: true }); } 

获取需要编译依赖关系的模块路径

解决.vite 文件夹后,我们跟着代码处理.vite 中的内容文件。

这边创建了两个变量 deps 和 missing 。

deps: 需要处理依赖关系的路径对象。

missing: 需要处理依赖关系但在 node_modules 中没有找到来源的数组对象。

//deps {   "vue": "/.../my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js",   "axios": "/.../my-vue-app/node_modules/axios/index.js" } 

需要提前知道的是,newDeps 这个 args 参数区分了第一次编译和已启动后遇到新依赖关系导入重写运行的编译。

// /optimizer.ts  let deps: Record<string, string>, missing: Record<string, string>; // 在服务器已经启动之后,如果遇到一个新的依赖关系导入, // 而这个依赖关系还没有在缓存中,Vite 将重新运行依赖构建进程并重新加载页面。 // 如上官方文档所述,最终会得出 deps 和 missing if (!newDeps) {   // scanImports 这里就不展开了,他的作用就是获取导入源,用正则检测后,使用 esbuild 编译所有的入口依赖( entries)   ({ deps, missing } = await scanImports(config)); } else {   deps = newDeps;   missing = {}; } // 重写更新了浏览器的哈希 // update browser hash data.browserHash = createHash('sha256')   .update(data.hash + JSON.stringify(deps))   .digest('hex')   .substr(0, 8); 

没有找到来源的模块处理(missing)

下面代码很简单,处理在 node_modules 中没有找到来源的模块。

// /optimizer.ts  // missing 是一个储存需要处理依赖关系但在 node_modules 中没有找到来源的数组对象,如果有的话直接 error 提醒一波。 const missingIds = Object.keys(missing); if (missingIds.length) {   throw new Error(     `The following dependencies are imported but could not be resolved:\n\n  ${missingIds       .map(         (id) =>           `${chalk.cyan(id)} ${chalk.white.dim(             `(imported by ${missing[id]})`,           )}`,       )       .join(`\n  `)}\n\nAre they installed?`,   ); } 

获取并导入 自定义的强制预构建(include)

接着处理在 vite.config.js 中 optimizeDeps.include 。

如官方文档 API 所述,

optimizeDeps.include: 默认情况下,不在 node_modules 中的,链接的包不会被预构建。使用此选项可强制预构建链接的包

// /optimizer.ts  //config 中是否有需要强制构建的依赖项, 处理后再 deps 中加入 const include = config.optimizeDeps?.include; if (include) {   const resolve = config.createResolver({ asSrc: false });   for (const id of include) {     if (!deps[id]) {       const entry = await resolve(id);       if (entry) {         deps[id] = entry;       } else {         throw new Error(           `Failed to resolve force included dependency: ${chalk.cyan(id)}`,         );       }     }   } } 

命令行打印需要构建模块的信息

// /optimizer.ts  const qualifiedIds = Object.keys(deps); //不用说很简单,没有需要依赖的 dep 就跳过 if (!qualifiedIds.length) {   writeFile(dataPath, JSON.stringify(data, null, 2));   log(`No dependencies to bundle. Skipping.\n\n\n`);   return data; }  // 这里也不用解释太多,基本上就是打印出信息的逻辑,然后绿色高亮告诉你要预缓存巴拉巴拉 const total = qualifiedIds.length; const maxListed = 5; const listed = Math.min(total, maxListed); const extra = Math.max(0, total - maxListed); const depsString = chalk.yellow(   qualifiedIds.slice(0, listed).join(`\n  `) +     (extra > 0 ? `\n  (...and ${extra} more)` : ``), ); if (!asCommand) {   if (!newDeps) {     // This is auto run on server start - let the user know that we are     // pre-optimizing deps     logger.info(       chalk.greenBright(`Pre-bundling dependencies:\n  ${depsString}`),     );     logger.info(       `(this will be run only when your dependencies or config have changed)`,     );   } } else {   logger.info(chalk.greenBright(`Optimizing dependencies:\n  ${depsString}`)); } 

创建预构建对象

使用 es-module-lexer 模块获取每个 deps 中的预构建模块文件,输出引入和导出的数据并保存。

// /optimizer.ts  import { ImportSpecifier, init, parse } from 'es-module-lexer';  // esbuild generates nested directory output with lowest common ancestor base // this is unpredictable and makes it difficult to analyze entry / output // mapping. So what we do here is: // 1. flatten all ids to eliminate slash // 2. in the plugin, read the entry ourselves as virtual files to retain the //    path. const flatIdDeps: Record<string, string> = {}; const idToExports: Record<string, ExportsData> = {}; const flatIdToExports: Record<string, ExportsData> = {}; // 运行 es-module-lexer 的初始化函数,后续会用到 await init;  for (const id in deps) {   // 替换 id 中的斜杠变成下划线 node/abc => node_abc   const flatId = flattenId(id);   flatIdDeps[flatId] = deps[id];   // 获取每个依赖源的文件内容   //{ vue: '/.../my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js',   // 'element-plus': '/.../my-vue-app/node_modules/element-plus/lib/index.esm.js',   //  axios: '/.../my-vue-app/node_modules/axios/index.js' }   const entryContent = fs.readFileSync(deps[id], 'utf-8');   // parse 出自 es-module-lexer,这个包是一个 js 模块语法词法分析器,体积非常小   // 解析出后的 ExportsData 是一个数组,[0]是 imports, [1]是 exports   const exportsData = parse(entryContent) as ExportsData;    /*     ss/se => statement start/end 缩写, {number} import 的开始和结束 index     这里以 vue 举例,parse 返回的值 =>  ss = 0 se = 60     entryContent.slice(0, 60) => "import { initCustomFormatter, warn } from '@vue/runtime-dom'"     entryContent.slice(62, 94) => "export * from '@vue/runtime-dom"     最后标注需要特殊处理的 export from   */   for (const { ss, se } of exportsData[0]) {     const exp = entryContent.slice(ss, se);     if (/export\s+\*\s+from/.test(exp)) {       exportsData.hasReExports = true; //待定     }   }   // 分别记录以 id flatId 的 exportsData   // exportsData 数据太多这里就不贴了,总之里面包含每个构建模块中的 import 和 export 的数据。   idToExports[id] = exportsData;   flatIdToExports[flatId] = exportsData;  } 

总结

上述描述代码中,我们理一下当前的逻辑。

  1. 获取了预构建模块的内容( hash 值,优化对象等)。
  2. 获取包管理器的 lockfile 转换的 hash 值,判断是否需要重新运行预构建。
  3. 获取需要编译依赖关系的模块路径( deps )和需要编译但没找到来源的模块( missing)。
  4. 处理 missing 数组,打印 error 提示是否已安装来源。
  5. 获取 vite.config.js 中自定义强制预构建的模块路径(include),加入 deps 对象中。
  6. 命令行打印需要构建模块的信息。
  7. 创建预构建对象,获取预构建对象中的引入导出数据并记录。

处理完各种琐事之后,我们获取了需要构建的 deps 对象,接下来进入下一章节来解析 deps 对象。

3. 构建和插件

此章节准备介绍构建和 vite 的自定义插件。

构建(build)

需要注意的几个参数:

  1. format设为esm,是 Vite 的目的之一,将所有的代码视为原生 ES 模块。

    CommonJS 和 UMD 兼容性: 开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM 。

  2. splitting设为true,仅适用于 esm 输出下,拆分多个文件引入的模块至单独文件,浏览页面 a 时,加载了 axios,再进入页面 b 时,直接调用已经加载后的 axios,省去了再次请求 axios 的操作。

    Code shared between multiple entry points is split off into a separate shared file that both entry points import. That way if the user first browses to one page and then to another page, they don't have to download all of the JavaScript for the second page from scratch if the shared part has already been downloaded and cached by their browser.

    Code referenced through an asynchronous import() expression will be split off into a separate file and only loaded when that expression is evaluated. This allows you to improve the initial download time of your app by only downloading the code you need at startup, and then lazily downloading additional code if needed later.

  3. plugins含有 Vite 插件esbuildDepPlugin: 下面会详细解释此插件。

  4. treeShaking设为ignore-annotations, 文档中提到的忽略无用的代码,以便减轻模块的体积。

// /optimizer/index.ts  // 最核心的地方,使用 esBuild 打包了 const result = await build({   entryPoints: Object.keys(flatIdDeps),   bundle: true, //任何导入的依赖一起打包   format: 'esm', // 符合 vite 转换成 esm   external: config.optimizeDeps?.exclude, //不需要处理的模块   logLevel: 'error', //日志级别,只显示错误   //拆分代码,简单来说就是拆分入口内的共享 import 文件,在访问 a 页面时加载了 axios,   //进入了 b 页面直接使用 a 页面加载的 axios 省去了再次请求的过程。   splitting: true,   sourcemap: true, //这个不用多说哈   outdir: cacheDir, //vite 自定义的默认缓存文件夹,node_modules/.vite   //修剪树枝? 默认删除无用的代码,ignore-annotations 的话指忽略那些删掉会损坏包的无用代码   treeShaking: 'ignore-annotations',   metafile: true, // 生成 meta json   define, // 替换标识符   plugins: [...plugins, esbuildDepPlugin(flatIdDeps, flatIdToExports, config)],   ...esbuildOptions, }); 

esbuild 插件

了解 esbuild 的插件的可以直接跳过这节,此节简单解释了下插件的结构:

(1) esbuild plugin 是一个包含namesetup的对象结构。 name为插件名,setup是一个接收build的函数。

(2) 主要的逻辑在setup函数中,分别为build.onResolvebuild.onLoad

build.onResolve: 此函数拦截相应的导入路径,修改路径并标记特定的命名空间。

build.onLoad: 此函数接收并筛选所有标记命名空间为env-ns的传入项,告诉 esbuild 该如何处理。

let envPlugin = {   name: 'env',   setup(build) {     // 第一个参数为拦截规则。如下示例,用正则拦截了名为`env`的路径。     // 第二个参数为函数,返回对象中包含路径(这里可以对路径修改并返回)和标记`env-ns`命名空间。     build.onResolve({ filter: /^env$/ }, (args) => ({       path: args.path,       namespace: 'env-ns',     }));      // 第一个参数为接收命名空间为 env-ns 的路径并通过 filter 筛选。     // 第二个参数为函数,告诉 esbuild 在 env-ns 命名空间中要返回 json 格式的环境变量。     build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({       contents: JSON.stringify(process.env),       loader: 'json',     }));   }, };  require('esbuild')   .build({     entryPoints: ['app.js'],     bundle: true,     outfile: 'out.js',     plugins: [envPlugin],   })   .catch(() => process.exit(1)); 

esbuildDepPlugin

首先需要看下 Vite 插件的一些用到的函数:

// /optimizer/esbuildDepPlugin.ts  export function esbuildDepPlugin(   qualified: Record<string, string>,   exportsData: Record<string, ExportsData>,   config: ResolvedConfig, ): Plugin; 

(1) 创建了两个解析器,分别对应 esmcommonjs

// /optimizer/esbuildDepPlugin.ts  // default resolver which prefers ESM const _resolve = config.createResolver({ asSrc: false });  // cjs resolver that prefers Node const _resolveRequire = config.createResolver({   asSrc: false,   isRequire: true, }); 

(2) 创建 resolve 函数,主要用来解决判断是什么类型的模块,并且返回相应的解析器结果。

// /optimizer/esbuildDepPlugin.ts  const resolve = (   id: string,   importer: string,   kind: ImportKind,   resolveDir?: string, ): Promise<string | undefined> => {   let _importer;   // explicit resolveDir - this is passed only during yarn pnp resolve for   // entries   // 传如果传入文件夹,那就获取绝对路径的文件夹路径   if (resolveDir) {     _importer = normalizePath(path.join(resolveDir, '*'));   } else {     // map importer ids to file paths for correct resolution     /**      * mporter 是否在外部传入的 flatIdDeps 中,      * {      *  vue: '/Users/kev1nzh/Desktop/new/my-vue-app/node_modules/vue/dist/vue.runtime.esm-bundler.js',      *  axios: '/Users/kev1nzh/Desktop/new/my-vue-app/node_modules/axios/index.js'      * }      * 如果在获取 value 的路径      */     _importer = importer in qualified ? qualified[importer] : importer;   }   //判断是否时以 require 开头,为了筛选出 kind 为 require-resolve, require-call 的模块,调用 resolveRequire   const resolver = kind.startsWith('require') ? _resolveRequire : _resolve;   // 返回解决完的路径,这个函数的代码后续会有章节详细讲   return resolver(id, _importer); }; 

(3) 创建resolveEntry函数,根据传入类型返回命名空间。

function resolveEntry(id: string, isEntry: boolean, resolveDir: string) {   const flatId = flattenId(id);   if (flatId in qualified) {     return isEntry       ? {           path: flatId,           namespace: 'dep',         }       : {           path: require.resolve(qualified[flatId], {             paths: [resolveDir],           }),         };   } } 

怪事,一部分用户的证书不对?

Posted: 01 Jun 2021 09:44 PM PDT

最近发现有一些用户请求网络异常
根据上传的 log,他们请求的证书 VeriSign Class 3 Public Primary Certification Authority - G5
sha1:a546086a79aee93637e1e81d68b46d24439e7826
而且只获取到这一个证书,并不是一个证书链,正常情况下,比如 Charles,你设置一个证书 A
他会根据这个证书签发一个和访问域名对应的证书下发
A -> *.yourdomain.com

去 digicert 发现 sha1 也对不上。

Subject DN: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5 Operational Start Date: Nov 8 00:00:00 2006 GMT Operational End Date: Jul 16 23:59:59 2036 GMT Key Size: 2048 bit Signature Algorithm: sha1WithRSAEncryption Serial Number: 18 da d1 9e 26 7d e8 bb 4a 21 58 cd cc 6b 3b 4a SHA-1 Thumbprint: 4E B6 D5 78 49 9B 1C CF 5F 58 1E AD 56 BE 3D 9B 67 44 A5 E5 Hierarchy: Public TLS / SSL, CodeSigning, Client Auth/Email Root Download Link: https://www.websecurity.digicert.com/content/dam/websitesecurity/digitalassets/desktop/pdfs/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem 

而且这些用户 ip 全国都有,应该不是一个个例,巧合他们的 sha1 都是这个吗?
还是他们用的同一款抓包软件?

根据这个 sha1 也搜不到任何内容
google 结果

[国内收款真正开源 BSD 的多商户系统] Fecbbc 多商户正式开源免费, BSD 开源协议 [Fecmall]

Posted: 01 Jun 2021 09:27 PM PDT

国内收款真正开源 BSD 的多商户系统, 商用免费授权

Fecbbc 多商户系统正式开源免费,BSD 开源协议,和 yii2 框架一样的开源协议,真正商用免费授权。

做国内首款真正开源 BSD 的多商户系统,欢迎大家研究学习,商用项目使用。

国内多商户商城系统资料:

多商户应用市场地址: http://addons.fecmall.com/42575492

多商户简介: https://www.fecmall.com/fecbbc

多商户详细文档: https://www.fecmall.com/doc/fecmall-guide/instructions/cn-1.0/guide-README.html

二:多商户 demo

国内多商户测试地址:

1.pc 地址:fecbbc.fecpx.com

2.h5: fecbbc.fecpx.com

3.平台后台:fecbbcadmin.fecpx.com
测试账户:test testfecbbc123

4.经销商后台:fecbbcbdmin.fecpx.com 测试账户:fec fec123s

三:资料

1.fecmall 官网: https://www.fecmall.com/

2.fecmall Github 地址: https://github.com/fecshop/yii2_fecshop

独行者速,众行者远。fecmall 开源之路离不开大家的参与,勤勉相加,欢迎大家后面使用 fecmall 做自己的项目,互助你我,持之以恒。

如何管理多个环境下的配置文件?

Posted: 01 Jun 2021 09:26 PM PDT

目前开发环境 1 台服务器,测试环境 2 台服务器,生产环境 4 台服务器,不同的服务器上的配置文件中的 IP 都会有变化,并且服务一部分是 jar 包,配置文件在 jar 外边,一部分是 war 包,配置文件打在里面。这种情况下,SVN 上需要维护几份配置文件?如何完成不同环境下全量代码的自动化编译和部署呢,有没有现成的工具可以管理不同环境的配置文件?还是需要自己定制开发?

dapr self-hosted 如何平滑升级

Posted: 01 Jun 2021 08:26 PM PDT

看到一个 issue 写了缺乏这方面文档
The current page is here: https://docs.dapr.io/operations/hosting/self-hosted/self-hosted-upgrade/, which still directs to do a full uninstall and reinstall. Need to write docs on zero-downtime upgrades (if possible)

https://github.com/dapr/docs/issues/760
翻源码也没有头绪

顺便问一下如何在没有 docker 的 slim mode 使用 actor

clop 新版本来了, 让你快速从 flag 包迁移过来, 一行命令的事

Posted: 01 Jun 2021 07:26 PM PDT

clop

一款小巧, 追求开发效果的工具命令行工具包

地址

https://github.com/guonaihong/clop

本次特性

加入代码生成的思路, 可以快速帮你从 flag 包迁移至 clop

1.安装 clop 命令

go get github.com/guonaihong/clop/cmd/clop 

2.使用 clop 解析包含 flag 包的代码

就可以把 main.go 里面的 flag 库转成 clop 包的调用方式

clop -f main.go 

main.go代码如下

package main  import "flag"  func main() { 	s := flag.String("string", "", "string usage") 	i := flag.Int("int", "", "int usage") 	flag.Parse() } 

输出代码如下

package main  import ( 	"github.com/guonaihong/clop" )  type flagAutoGen struct { 	Flag string `clop:"--string" usage:"string usage" ` 	Flag int    `clop:"--int" usage:"int usage" ` }  func main() { 	var flagVar flagAutoGen 	clop.Bind(&flagVar) } 

完整文档如下

clop

Go codecov Go Report Card

clop 是一款基于 struct 的命令行解析器,麻雀虽小,五脏俱全。(从零实现) clop.png

feature

  • 支持环境变量绑定 env DEBUG=xx ./proc
  • 支持参数搜集 cat a.txt b.txt,可以把a.txt, b.txt散装成员归归类,收集到你指定的结构体成员里
  • 支持短选项proc -d 或者长选项proc --debug不在话下
  • posix 风格命令行支持,支持命令组合ls -ltrls -l -t -r简写形式,方便实现普通 posix 标准命令
  • 子命令支持,方便实现 git 风格子命令git add ,简洁的子命令注册方式,只要会写结构体就行,3,4,5 到无穷尽子命令也支持,只要你喜欢,用上 clop 就可以实现
  • 默认值支持default:"1",支持多种数据类型,让你省去类型转换的烦恼
  • 贴心的重复命令报错
  • 严格的短选项,长选项报错。避免二义性选项诞生
  • 效验模式支持,不需要写一堆的if x!= "" or if y!=0浪费青春的代码
  • 可以获取命令优先级别,方便设置命令别名
  • 解析 flag 包代码生成 clop 代码

内容

Installation

go get github.com/guonaihong/clop 

Quick start

package main  import ( 	"fmt" 	"github.com/guonaihong/clop" )  type Hello struct { 	File string `clop:"-f; --file" usage:"file"` }  func main() {  	h := Hello{} 	clop.Bind(&h) 	fmt.Printf("%#v\n", h) } // ./one -f test // main.Hello{File:"test"} // ./one --file test // main.Hello{File:"test"}  

example

base type

int

package main  import (         "fmt"          "github.com/guonaihong/clop" )  type IntDemo struct {         Int int `clop:"short;long" usage:"int"` }  func main() {         id := &IntDemo{}         clop.Bind(id)         fmt.Printf("id = %v\n", id) } //  ./int -i 3 // id = &{3} // ./int --int 3 // id = &{3} 

float64

package main  import (         "fmt"          "github.com/guonaihong/clop" )  type Float64Demo struct {         Float64 float64 `clop:"short;long" usage:"float64"` }  func main() {         fd := &Float64Demo{}         clop.Bind(fd)         fmt.Printf("fd = %v\n", fd) } // ./float64 -f 3.14 // fd = &{3.14} // ./float64 --float64 3.14 // fd = &{3.14} 

duration

package main  import (         "fmt"         "time"          "github.com/guonaihong/clop" )  type DurationDemo struct {         Duration time.Duration `clop:"short;long" usage:"duration"` }  func main() {         dd := &DurationDemo{}         clop.Bind(dd)         fmt.Printf("dd = %v\n", dd) } // ./duration -d 1h // dd = &{1h0m0s} // ./duration --duration 1h // dd = &{1h0m0s} 

string

package main  import (         "fmt"          "github.com/guonaihong/clop" )  type StringDemo struct {         String string `clop:"short;long" usage:"string"` }  func main() {         s := &StringDemo{}         clop.Bind(s)         fmt.Printf("s = %v\n", s) } // ./string --string hello // s = &{hello} // ./string -s hello // s = &{hello} 

array

similar to curl command

package main  import (         "fmt"          "github.com/guonaihong/clop" )  type ArrayDemo struct {         Header []string `clop:"-H;long" usage:"header"` }  func main() {         h := &ArrayDemo{}         clop.Bind(h)         fmt.Printf("h = %v\n", h) } // ./array -H session:sid --header token:my // h = &{[session:sid token:my]} 

similar to join command

加上 greedy 属性,就支持数组贪婪写法。类似 join 命令。

package main  import (     "fmt"      "github.com/guonaihong/clop" )  type test struct {     A []int `clop:"-a;greedy" usage:"test array"`     B int   `clop:"-b" usage:"test int"` }  func main() {     a := &test{}     clop.Bind(a)     fmt.Printf("%#v\n", a) }  /* 运行 ./use_array -a 12 34 56 78 -b 100 输出 &main.test{A:[]int{12, 34, 56, 78}, B:100} */  

required flag

package main  import ( 	"github.com/guonaihong/clop" )  type curl struct { 	Url string `clop:"-u; --url" usage:"url" valid:"required"` }  func main() {  	c := curl{} 	clop.Bind(&c) }  // ./required  // error: -u; --url must have a value! // For more information try --help 

set default value

可以使用 default tag 设置默认值,普通类型直接写,复合类型用 json 表示

package main  import (     "fmt"     "github.com/guonaihong/clop" )  type defaultExample struct {     Int          int       `default:"1"`     Float64      float64   `default:"3.64"`     Float32      float32   `default:"3.32"`     SliceString  []string  `default:"[\"one\", \"two\"]"`     SliceInt     []int     `default:"[1,2,3,4,5]"`     SliceFloat64 []float64 `default:"[1.1,2.2,3.3,4.4,5.5]"` }  func main() {     de := defaultExample{}     clop.Bind(&de)     fmt.Printf("%v\n", de)  } // run //         ./use_def // output: //         {1 3.64 3.32 [one two] [1 2 3 4 5] [1.1 2.2 3.3 4.4 5.5]} 

Support environment variables

// file name use_env.go package main  import ( 	"fmt" 	"github.com/guonaihong/clop" )  type env struct { 	OmpNumThread string `clop:"env=omp_num_thread" usage:"omp num thread"` 	Path         string `clop:"env=XPATH" usage:"xpath"` 	Max          int    `clop:"env=MAX" usage:"max thread"` }  func main() { 	e := env{} 	clop.Bind(&e) 	fmt.Printf("%#v\n", e) } // run // env XPATH=`pwd` omp_num_thread=3 MAX=4 ./use_env  // output // main.env{OmpNumThread:"3", Path:"/home/guo", Max:4} 

subcommand

package main  import ( 	"fmt" 	"github.com/guonaihong/clop" )  type add struct { 	All      bool     `clop:"-A; --all" usage:"add changes from all tracked and untracked files"` 	Force    bool     `clop:"-f; --force" usage:"allow adding otherwise ignored files"` 	Pathspec []string `clop:"args=pathspec"` }  type mv struct { 	Force bool `clop:"-f; --force" usage:"allow adding otherwise ignored files"` }  type git struct { 	Add add `clop:"subcommand=add" usage:"Add file contents to the index"` 	Mv  mv  `clop:"subcommand=mv" usage:"Move or rename a file, a directory, or a symlink"` }  func main() { 	g := git{} 	clop.Bind(&g) 	fmt.Printf("git:%#v\n", g) 	fmt.Printf("git:set mv(%t) or set add(%t)\n", clop.IsSetSubcommand("mv"), clop.IsSetSubcommand("add"))  	switch { 	case clop.IsSetSubcommand("mv"): 		fmt.Printf("subcommand mv\n") 	case clop.IsSetSubcommand("add"): 		fmt.Printf("subcommand add\n") 	} }  // run: // ./git add -f  // output: // git:main.git{Add:main.add{All:false, Force:true, Pathspec:[]string(nil)}, Mv:main.mv{Force:false}} // git:set mv(false) or set add(true) // subcommand add  

Get command priority

package main  import ( 	"fmt" 	"github.com/guonaihong/clop" )  type cat struct { 	NumberNonblank bool `clop:"-b;--number-nonblank"                              usage:"number nonempty output lines, overrides"`  	ShowEnds bool `clop:"-E;--show-ends"                        usage:"display $ at end of each line"` }  func main() {  	c := cat{} 	clop.Bind(&c)  	if clop.GetIndex("number-nonblank") < clop.GetIndex("show-ends") { 		fmt.Printf("cat -b -E\n") 	} else { 		fmt.Printf("cat -E -b \n") 	} } // cat -be  // 输出 cat -b -E // cat -Eb // 输出 cat -E -b 

Can only be set once

指定选项只能被设置一次,如果命令行选项,使用两次则会报错。

package main  import (     "github.com/guonaihong/clop" )  type Once struct {     Debug bool `clop:"-d; --debug; once" usage:"debug mode"` }  func main() {     o := Once{}     clop.Bind(&o) } /* ./once -debug -debug error: The argument '-d' was provided more than once, but cannot be used multiple times For more information try --help */ 

quick write

快速写法,通过使用固定的 short, long tag 生成短,长选项。可以和 cat 例子直观比较下。命令行选项越多,越能节约时间,提升效率。

package main  import (     "fmt"     "github.com/guonaihong/clop" )  type cat struct { 	NumberNonblank bool `clop:"-c;long"  	                     usage:"number nonempty output lines, overrides"`  	ShowEnds bool `clop:"-E;long"  	               usage:"display $ at end of each line"`  	Number bool `clop:"-n;long"  	             usage:"number all output lines"`  	SqueezeBlank bool `clop:"-s;long"  	                   usage:"suppress repeated empty output lines"`  	ShowTab bool `clop:"-T;long"  	              usage:"display TAB characters as ^I"`  	ShowNonprinting bool `clop:"-v;long"  	                      usage:"use ^ and M- notation, except for LFD and TAB" `  	Files []string `clop:"args=files"` }  func main() {  	c := cat{} 	err := clop.Bind(&c)  	fmt.Printf("%#v, %s\n", c, err) } 

Advanced features

高级功能里面有一些 clop 包比较有特色的功能

Parsing flag code to generate clop code

让你爽翻天, 如果你的 command 想迁移至 clop, 但是全面众多的 flag 代码, 又不想花费太多时间在无谓的人肉 code 转换上, 这时候你就需要 clop 命令, 一行命令解决你的痛点.

1.安装 clop 命令

go get github.com/guonaihong/clop/cmd/clop 

2.使用 clop 解析包含 flag 包的代码

就可以把 main.go 里面的 flag 库转成 clop 包的调用方式

clop -f main.go 

main.go代码如下

package main  import "flag"  func main() { 	s := flag.String("string", "", "string usage") 	i := flag.Int("int", "", "int usage") 	flag.Parse() } 

输出代码如下

package main  import ( 	"github.com/guonaihong/clop" )  type flagAutoGen struct { 	Flag string `clop:"--string" usage:"string usage" ` 	Flag int    `clop:"--int" usage:"int usage" ` }  func main() { 	var flagVar flagAutoGen 	clop.Bind(&flagVar) } 

Implementing linux command options

cat

package main  import ( 	"fmt" 	"github.com/guonaihong/clop" )  type cat struct { 	NumberNonblank bool `clop:"-c;--number-nonblank"  	                     usage:"number nonempty output lines, overrides"`  	ShowEnds bool `clop:"-E;--show-ends"  	               usage:"display $ at end of each line"`  	Number bool `clop:"-n;--number"  	             usage:"number all output lines"`  	SqueezeBlank bool `clop:"-s;--squeeze-blank"  	                   usage:"suppress repeated empty output lines"`  	ShowTab bool `clop:"-T;--show-tabs"  	              usage:"display TAB characters as ^I"`  	ShowNonprinting bool `clop:"-v;--show-nonprinting"  	                      usage:"use ^ and M- notation, except for LFD and TAB" `  	Files []string `clop:"args=files"` }  func main() {  	c := cat{} 	err := clop.Bind(&c)  	fmt.Printf("%#v, %s\n", c, err) }  /* Usage:     ./cat [Flags] <files>   Flags:     -E,--show-ends           display $ at end of each line      -T,--show-tabs           display TAB characters as ^I      -c,--number-nonblank     number nonempty output lines, overrides      -n,--number              number all output lines      -s,--squeeze-blank       suppress repeated empty output lines      -v,--show-nonprinting    use ^ and M- notation, except for LFD and TAB   Args:     <files> */ 

扩充了一下 awesome-go,加上了 repo 的基本信息

Posted: 01 Jun 2021 07:03 PM PDT

https://github.com/xwjdsh/awesome-go-extra

解析 awesome-go 的 README 文件,然后调 GitHub API 获取 repo 信息,最后生成一个新的 README

请教大家如何存储国际手机号数据?

Posted: 01 Jun 2021 03:58 PM PDT

有一个手机号控件(对应表里的一个字段 telephone ),如下图。 存储内容是区号+文本形式(比如:+1-123456789 )。 同一个区号可能有多个国家,比如,+1 可能属于美国、加拿大。 https://i.imgur.com/SEERndK.png

问题: 后端返回控件内容(+1-189008732 )给到前端,此时前端并不知道这里的+1 是属于美国还是加拿大,因此无法定位控件左边的区号。请问大家这种情况应该如何处理?

如何修改一个大文件?

Posted: 01 Jun 2021 03:12 PM PDT

假设我有一个 1G 的大文件,我要修改中间某个地方的 1 个 byte,这个时候可以用 file.seek 定位到具体的位置,再用 append 模式写。
但如果我要删除一段内容,再添加一段新的内容呢?(内容的长度不确定),这个时候是怎么做的?
一个编辑器如何做到这个功能?
一个像 sqlite 这样涉及到大量读写的数据库系统又是如何做到的?
照 posix 现有的 api 来看要么只能全读内存再调用 fsync 写入。或者用 mmap 这种底层的 api 提高写入速度。有相关的书籍介绍这个问题的吗?

Python 的 logging 不是实时的?

Posted: 01 Jun 2021 02:50 PM PDT

头次用这个 发现日志文件里记录的时间点都那么接近 都是差不多程序运行结束时的时刻
这是怎么回事?之前用 js,java 啥的不是这样啊

一有振动手机就亮屏,这是安卓 11 的特性还是 bug?不耗电吗?

Posted: 01 Jun 2021 02:49 PM PDT

新手机是安卓 11 的,放桌上拿起来就自动亮屏,它不知道我是要用它还是只想把它放进兜里;放沙发上或者床上,人坐上去或者起来,它也自动亮屏;晚上睡觉放床头我动一动它也亮,这简直有点惊悚了。有面部解锁功能但是我关闭着。我还是希望传统一点双击亮屏好了,怎么设置能让它不自动亮屏???

No comments:

Post a Comment