【译】React 服务器组件中的关键安全漏洞

重要提醒:React 服务器组件曝光了一枚未认证的远程代码执行(RCE)漏洞,只要项目启用了 RSC 支持,就可能被远程掌控。建议所有依赖 React 19 的项目立即排查并升级。 本文根据 React 团队于 2025 年 12 月 3 日发布的「Critical Security Vulnerability in React Server Components」翻译整理。 🚨 漏洞概述 12 月 3 日,React 官方团队发布最高级别安全通告:React 服务器组件(RSC)存在未认证的远程代码执行漏洞。 漏洞已被登记为 CVE-2025-55182,CVSS 得分 10.0(危害最高级别)。 攻击者无需通过身份验证,只要能向 Server Function 端点发送恶意 HTTP 请求,就可能在服务器上执行任意代码,直接接管你的后端环境。 即便你尚未实现任何 React Server Function,只要你的框架或构建工具启用了 RSC 能力,就可能处于风险之中。 ⚠️ 你是否受到影响? 受影响的 React 版本 19.0 19.1.0 19.1.1 19.2.0 受影响的框架与工具 Next.js React Router Waku @parcel/rsc @vitejs/plugin-rsc rwsdk 以下情况暂不受影响 ✅ 完全运行在客户端、没有任何服务端代码的 React 应用 ✅ 没有使用支持 React 服务器组件的框架或打包工具 特别注意:只要支持 React 服务器组件,即便没有配置任何 Server Function 端点,也可能遭到攻击! 🛡️ 紧急修复方案 React 团队已在 19.0.1、19.1.2、19.2.1 中修复,请立即升级到对应分支的安全版本。 若你使用的是 canary 版本(如 Next.js 14.3.0-canary.77+),请降级到最新稳定版,再等待后续补丁。 一些托管服务商已在 React 团队指导下部署临时缓解,但请勿依赖临时方案,必须升级依赖。 基础升级命令示例: 1 2 3 npm install react@19.2.1 react-dom@19.2.1 # 或 yarn add react@19.2.1 react-dom@19.2.1 📅 漏洞时间线 11 月 29 日:安全研究员 Lachlan Davidson 通过 Meta Bug Bounty 报告漏洞。 11 月 30 日:Meta 安全团队确认漏洞,并与 React 团队展开修复。 12 月 1 日:修复方案完成,同时与托管服务商和生态项目联动部署缓解措施。 12 月 3 日:补丁发布到 npm,漏洞以 CVE-2025-55182 正式披露。 💡 技术背景 React Server Functions 允许客户端通过 HTTP 请求调用运行在服务器上的函数,React 会负责序列化与反序列化过程。漏洞恰恰出在服务端解码载荷的环节: 客户端发起的函数调用被转换为 HTTP 请求。 服务端解析载荷并执行对应的函数。 攻击者可以伪造恶意载荷,诱使 React 在反序列化过程中执行任意代码。 在官方确认补丁完全部署之前,更多技术细节将保持保密,以免漏洞被大规模利用。 🎯 行动建议 立即确认项目所用的 React 与框架版本。 马上升级 React 核心依赖以及框架/打包器提供的 RSC 支持包。 同步通知团队、合作伙伴及客户,确保所有部署都得到修复。 监控服务器日志与入侵检测,关注是否存在可疑请求。 持续关注官方公告(React、Next.js、Expo、Redwood 等),获取最新补丁状态。 升级指南(框架 & 构建工具) Next.js 1 2 3 4 5 6 7 npm install next@15.0.5 # 15.0.x npm install next@15.1.9 # 15.1.x npm install next@15.2.6 # 15.2.x npm install next@15.3.6 # 15.3.x npm install next@15.4.8 # 15.4.x npm install next@15.5.7 # 15.5.x npm install next@16.0.7 # 16.0.x 若使用 14.3.0-canary.77 或更高的 canary,请降级至最新 14.x 稳定版: 1 npm install next@14 详见 Next.js 安全公告。 React Router(不稳定 RSC API) 1 2 3 4 5 npm install react@latest npm install react-dom@latest npm install react-server-dom-parcel@latest npm install react-server-dom-webpack@latest npm install @vitejs/plugin-rsc@latest Expo 1 npm install react@latest react-dom@latest react-server-dom-webpack@latest Redwood SDK 1 2 npm install rwsdk@latest npm install react@latest react-dom@latest react-server-dom-webpack@latest 更多迁移说明见 Redwood 文档。 Waku 1 npm install react@latest react-dom@latest react-server-dom-webpack@latest waku@latest 详情参考 Waku 官方讨论。 @vitejs/plugin-rsc 1 npm install react@latest react-dom@latest @vitejs/plugin-rsc@latest react-server-dom-parcel 1 npm install react@latest react-dom@latest react-server-dom-parcel@latest react-server-dom-turbopack 1 npm install react@latest react-dom@latest react-server-dom-turbopack@latest react-server-dom-webpack 1 npm install react@latest react-dom@latest react-server-dom-webpack@latest 🙏 致谢 感谢安全研究员 Lachlan Davidson 发现并报告漏洞,也向在补丁发布期间提供临时缓解与验证的托管服务商、框架团队和社区贡献者致以谢意。 📢 重要提醒 安全无小事! 该漏洞的严重性不容忽视,请务必在今天完成升级,保障用户数据与业务连续性。 转发给你的技术团队,让更多开发者看到这条重要信息!

2025/12/4
articleCard.readMore

Vercel收购NuxtLabs,我和尤雨溪一样心情复杂

2025年7月8日,前端领域迎来一次重磅整合:以托管和优化Next.js闻名的Vercel公司,宣布收购Nuxt.js背后的团队实体NuxtLabs。这一举动不仅标志着Nuxt发展进入新阶段,也引发了整个社区对前端生态未来走向的深度思考。 Nuxt 的新篇章:独立精神与商业支持的融合 根据 Nuxt 核心维护者 Daniel Roe 在 GitHub 上的公告,核心团队加入 Vercel: Nuxt 的创始人 Sébastien Chopin (@atinux)、核心开发者 Pooya Parsa (@pi0)、Anthony Fu (@antfu) 以及 Daniel Roe (@danielroe) 等人将加入 Vercel,继续全职投入运行 Nuxt 的开源开发。 保持独立性: 公告特别强调,Nuxt 作为一个开源项目,将保留其独立性。团队将继续拥有其公开的技术路线图、开放的治理模式,并维持其核心价值观。Daniel Roe 将继续领导 Nuxt 的技术和战略方向。 拥抱选择的多样性: Nuxt 始终坚持“选择权”的核心理念。这意味着 Nuxt 将继续支持开发者自由选择模块、数据库、打包工具、路由方案,当然,也包括部署平台。它不会成为 Vercel 独占的框架。 更可持续的未来: Vercel 的投资为 Nuxt 团队提供了更稳定的财务支持。这使得核心开发者能更专注于打磨框架、赋能社区,并将社区赞助的资金用于支持更广泛的贡献者,形成一个更健康、可持续的开源生态。 Vue之父:尤雨溪大大的反应 我想,无数Nuxt老用户的心情应该都和我类似。而Vue的作者尤雨溪(Evan You),则用几条动态,无比精准地把我们这种复杂的心绪摆到了台面上。 一个值得细品的“心情复杂”:在收购官宣之后,尤雨溪第一时间向Nuxt团队送上了祝福。作为NuxtLabs的天使投资人,他比任何人都清楚这个团队一路走来的不易——既要维护一个庞大的开源项目,又要想办法让公司活下去。现在团队被Vercel收购,核心成员可以直接全职为爱发电,这无疑是算是比较好的结局之一。 但同时,他也坦率地写下了那句话,说自己“算不上是Vercel最大的粉丝”(may not be the biggest Vercel fan)。 这句话简练地概括了我们这些旁观者的矛盾心态。我们由衷佩服Vercel的平台能力和技术远见,但又对它日益强大的“虹吸效应”抱有一丝天然的警惕。在官宣前几天,他就曾发帖说自己“第一笔一级市场投资退出,心情有点复杂”,现在回头看,一切都顺理成章了。 这份“复杂”到底从何而来? 作为“大家长”的守护心态:Vue是他一手打造的生态。Nuxt作为Vue生态中最成功的全栈框架,就像一个极其优秀的孩子。现在,这个孩子被隔壁家特别有钱、还主推另一个孩子(React/Next.js)的家庭“收养”了,心情自然微妙。 作为投资人的务实:他深知开源项目的商业化有多难。Nuxt团队在“免费奉献”和“商业求生”之间走了太久钢丝。现在Vercel的收购,让团队成员的价值得到了认可,让项目有了持续的生命力。从务实的角度看,这是一次成功的退出。 作为战略家的隐忧:当一个平台通过收购,将多个主流框架的“大脑”都收归麾下时,生态的中心化风险就在增加。一个健康的市场,需要有多个实力相当的玩家来回“掰手腕”。尤雨溪的表态,正是对这种潜在失衡的温和提醒。 所以,他的“心情复杂”,并非个人好恶,而是站在整个前端生态高度的一种清醒与期许。 Nuxt的未来:是如虎添翼,还是会被“温水煮青蛙”? 冷静下来分析,这次收购对Nuxt本身,短期内是利大于弊。 Nuxt团队的公告给我们吃了定心丸:项目会继续保持开源和独立,技术路线图不变,开发者仍然可以自由选择部署到任何平台。这打消了我们最大的顾虑。 最直接的好处是,像Sébastien Chopin (atinux)、Pooya Parsa (pi0)这些大神,终于可以全职、专注地投入到开发中了。这意味着我们可以期待更快的Bug修复、更雄心勃勃的新功能、更完善的文档和社区支持。这对于我们这些一线开发者来说,是实实在在的红利。 但隐忧也并非空穴来风。这种风险是温和而长期的。比如,未来的新功能,会不会在设计上不自觉地优先适配Vercel的架构?官方文档和教程的示例,会不会越来越多地以Vercel为“最佳实践”?这就是开发者们最担心的“软锁定”——它不会强迫你,但会通过潜移默化的方式,让你觉得“用Vercel才是最顺滑的”。 再看Vercel,它的野心早已不加掩饰。从亲手打造Next.js这个“样板间”,到把Svelte的作者Rich Harris请入麾下,再到如今将Nuxt的核心团队收入囊中,它的目标绝不只是当一个“前端托管服务商”。 Vercel想做的,是成为“前端开发与部署的操作系统”。它提供基础设施(OS),同时又掌控着OS上最流行的应用软件(各类框架)。这步棋下得很大,也很有远见。它所创造的从代码提交到全球部署的丝滑体验,确实是革命性的。 对于我们开发者来说,这当然是好事。工具越来越强大,部署越来越简单,我们可以把更多精力放在业务逻辑和用户体验的创造上。但同时,我们也需要保持一份清醒:当选择越来越少,当“最优解”变得越来越唯一时,我们是否会失去一部分选择的自由和议价的权利? 一个老用户的缘分 作为一名从2016年就开始接触Vue的开发者,我与Nuxt的渊源,是无数Vue开发者成长路径的缩影。2018年,我在为学校开发官方网站时,首次面临了服务端渲染和SEO的硬性需求。在短暂调研过百度推出的亲儿子“Lavas”框架后,我惊喜地发现了Nuxt.js,那种“拨云见日”的狂喜。它“约定优于配置”的理念、清晰的工程化结构以及对SSR场景的专注,几乎完美解决了当时Vue生态一个的痛点。我毫不犹豫地选择了它。一个优秀的“元框架”对于提升开发效率、拓展应用场景是何其重要。从那以后,Nuxt就成了我工具箱里的“SSR利器”。它陪我走过了很多需要考虑首屏加载和SEO的项目,也让我对Vue的全栈能力有了全新的认识。它就像那个你从一个小众音乐节上发现的宝藏乐队,你希望它火,但又怕它火了之后就“变味”了。这也是我看到该消息想写点什么的缘故。希望它被收编后,不会失去那份独特的、纯粹的“为Vue生态而生”的气质。

2025/7/9
articleCard.readMore

免费平替Claude-Code:Google Gemini CLI深度解析与实践指南

最近,Claude-Code刷屏朋友圈!紧接着昨晚Google推出的开源免费的Gemini CLI让AI强大能力直接走进开发者的终端界面,这款免费开源工具正彻底改变开发者与AI的交互方式。无需切换应用,直接在命令行中完成内容生成、代码辅助与复杂问题解决,开创了终端交互新时代。 为何选择Gemini CLI?四大核心优势 开源免费,透明可定制 作为开源项目,Gemini CLI不仅免费使用,更重要的是提供了完全的代码透明性和可定制性。这种开放模式激发了全球开发者社区的贡献,确保工具能快速迭代、持续进化。 强大的AI代理能力 基于”Reason and Act (ReAct)”循环机制,Gemini CLI远超普通聊天机器人,可自主分解任务、调用工具、执行动作并自我修正。这种代理能力可自动化复杂工作流,从代码错误修复到功能模块创建,大幅提升开发效率。 无缝终端集成体验 将AI能力直接融入开发者最熟悉的终端环境,最大限度减少上下文切换开销,让开发流程更专注、更高效。 突破性的多模态与大上下文处理 能处理超越1M token上下文窗口限制的大型代码库,支持PDF文档处理、视频分析和媒体生成,拓展了AI在创意与开发领域的应用边界。 三步上手,即刻体验 环境准备 Node.js: v18+(推荐LTS版本) 1 2 # 检查版本 node -v 网络连接: 确保可访问Google服务 1 2 # 会返回一个html curl www.google.com 简单安装,两种方式 通过 npx 快速运行 (推荐初次尝试体验使用这种方法): 1 npx https://github.com/google-gemini/gemini-cli 全局安装 (长期使用推荐) 1 npm install -g @google/gemini-cli 安装好后输入/help可以看到命令列表 认证配置 使用个人 Google 账户 (快速上手): 可使用 Gemini 2.5 Pro 模型的权限,并享有每分钟最多 60 个模型请求和每天 1,000 个模型请求的额度 使用 API Key (高级用途与更高额度):如果有更高需求可以采用这种,但是注意费用⚠️ 1 2 3 - 生成 API Key: 首先,你需要前往 Google AI Studio 生成你的专属 API Key 。   - 设置环境变量: 将生成的 API Key 设置为终端的环境变量。在终端中执行以下命令(请务必将 YOUR_API_KEY 替换为你的实际密钥):export GEMINI_API_KEY="YOUR_API_KEY" - 持久化配置:放进配置文件里:~/.zshrc 或 ~/.bashrc 其他方式:谷歌workspace账户或者是授权code assist用户,Vertex AI用户 实战应用场景 代码开发助手 1 2 3 4 5 6 7 8 # 快速解析代码库架构 gemini "分析这个代码库的核心模块和交互方式" # 智能错误排查 gemini "解释为什么这段代码会产生内存泄漏并提供修复方案" # 代码生成与优化 gemini "创建一个带缓存的用户认证API" 创意内容生成 利用多模态能力,从简单文本创作到复杂媒体内容分析与生成,一条命令轻松完成。创建文本肯定没问题,但是创建图片和视频是需要额外工具的。理解图片是没有什么问题的。 高效工作流自动化 1 2 3 4 5 # Git历史分析 gemini "总结过去一周的代码提交,按功能分类" # 项目文档生成 gemini "为这个API模块生成完整的使用文档" 这样终于可以看懂同事写什么了 高级技巧与故障排除 1 2 3 参数调优: 使用--model、--temperature等参数精确控制AI行为 工具链集成: 通过管道连接其他命令行工具,如git log | gemini "总结这些提交" 调试技巧: 遇到问题时使用DEBUG=true gemini "你的提示"获取详细日志 实战让Gemini Cli帮你做产品 创建一个简单的网页:高考能力小测试 告诉gemini-cli我的需求是什么 然后告诉它你的更细致需求,一步步调试,最后的到下面的结果,也可以扫码浏览:https://gkquiz.vadxq.com/ 创建一个工程化的nextjs项目 首先告诉gemini-cli,我们需要创建一个nextjs项目,是关于什么内容,有什么要求 它还会自己安装依赖,写完后会自己debug,并且运行起来 当运行后出现问题,需要取消运行后告诉它debug,这里有点不智能了,需要改进的地方 最后的目录结构和粒子特效如下 结语:AI终端新时代 Gemini CLI将强大的AI能力直接融入开发工作流,极大提升开发效率。随着持续进化,它将带来更强大的多模态能力和更深度的系统集成,成为每位开发者不可或缺的智能助手。立即尝试Gemini CLI,开启你的终端AI革命! 最后!建了一个AI交流群,欢迎诸君前来交流!有任何关于文章的疑问也可以在群里交流哟 回复公众号【aiqun】或者【ai群】访问 https://aiqun.vadxq.com 参考来源 [1] https://github.com/google-gemini/gemini-cli [2] https://blog.google/technology/developers/introducing-gemini-cli-open-source-ai-agent

2025/6/24
articleCard.readMore

Newsletter逆天福利:三种方式获取全年免费 AI/SaaS 工具包(含团体购买兑换防坑指南)

最近,Lenny Rachitsky 的 Lenny’s Newsletter推出了一个真正让人 “难以置信的优惠”。简单来说:$200就可以体验10个知名AI科技产品工具。只要订阅他的 Newsletter 一年,你就能免费获得一整套价值上千美元的顶级 SaaS 和 AI 工具,使用期限同样是一整年!这简直就是给产品人和创业者的及时雨。 捆绑包内含工具详解 (截至 2025年4月) 这套工具组合简直是为现代知识工作者量身打造的,从编程到会议记录,从产品设计到项目管理,应有尽有: Cursor: AI 加持的代码编辑器 - 专业版一年 (年价值 $240) Bolt: 智能会议助手 - 专业版一年 (年价值 $240) Lovable: AI 驱动的用户研究平台 - 入门版一年 (年价值 $240) Replit: 强大的在线 IDE - 核心版一年 (年价值 $240) v0 (by Vercel): AI 生成 UI 界面 - 高级版一年 (年价值 $240) Granola: 会议摘要分析工具 - 商业版一年 (支持100席位,团队福利) Notion: 全能工作空间 - Plus 版一年及额度 (价值 $2000+,团队适用) Linear: 高效项目管理工具 - 商业版一年 (价值 $336+,团队适用) Superhuman: 效率邮件客户端 - 入门版一年 (年价值 $300) Perplexity: 对话式 AI 搜索 - 专业版一年 (年价值 $240) 粗略一算,这套工具包的总价值轻松超过$4000美元,而你只需支付Lenny 的订阅费(个人$200,团购$170,学生约$100)就能全部拿下。老实说,这种性价比,不趁手抓住简直对不起自己的钱包。 入口地址,可以复制打开:https://www.lennysnewsletter.com/p/an-unbelievable-offer-now-get-one 如何获得这个超值捆绑包?三种主要途径详解 方式一:直接个人订阅 适合人群: 普通个人用户 价格: $200/年 操作步骤: 访问 Lenny’s Newsletter 主订阅页面 购买,支付后会收到兑换指引邮件。 方式二:学生优惠订阅 适合人群: 学生党们,是时候发挥你学生身份的价值了! 价格: 半价约 $100 美元/年,简直是白菜价 操作步骤: 需要通过学生专属优惠链接订阅,需要验证学生邮箱。专属链接如下:https://www.lennysnewsletter.com/subscribe?coupon=a29ec9b5,coupon这个代码目前不清楚能够切换别的。 相关信息:目前测试的国内学生邮箱都失败了,美国教育邮箱部分可以 方式三:团体订阅(最划算) 适合人群: 公司团队、创业伙伴、同学或朋友小组 最大优势: 买 N 个席位,得 N 份完整的工具包!每个成员都能获得全套福利 价格: 阶梯定价,人均可能比个人订阅更便宜 管理员操作: 团队负责人在官网选择 "Group Subscription",选择席位数并支付 在团体管理页面 https://www.lennysnewsletter.com/account/group 添加成员邮箱 大家要组团购买的话,可以私聊公众号【lenny】或者访问https://lenny-ai.vadxq.com 即可得到入群二维码 获取和激活你的捆绑包兑换码 无论选择哪种方式,激活流程都大同小异: 查收第一封邮件: 支付成功后收到确认邮件,点击捆绑包链接 小提示: 别忘了检查推广和垃圾邮件文件夹,有时好东西就藏在那里 登录 Lenny’s Bundle 网站: 输入邮箱进行登录/绑定 查收第二封邮件: 获取专属登录链接 访问兑换码页面: 点击登录链接后,即可看到所有工具及兑换码 团队成员注意: 需使用被管理员添加的那个邮箱完成上述步骤,获取属于你自己的那份独立激活码。 详细兑换示例:以 Cursor Pro 为例(附支付验证技巧) 以下是激活 Cursor Pro 的详细步骤: 找到并点击兑换: 在 Lenny’s Bundle 页面找到 Cursor Pro 卡片,点击”Redeem” 前往 Cursor 官网: 系统会自动跳转到带有你激活码的专属页面 注册/登录账号: 关键提示! 必须使用全新账号,否则可能无法享受福利 建议使用 Gmail 等国际邮箱注册,避免不必要的麻烦 绑定支付方式: 虽然实际不扣费,但需要验证支付方式 推荐使用支付宝:填写姓名和地址,扫描二维码签约”订阅代扣” 也可绑定国际信用卡 设置取消订阅提醒: 这绝对是最重要的一步!立即在手机日历设置提醒,免费期结束前取消自动续费 例如:2025年4月激活,就设置2026年4月初的提醒”取消 Cursor Pro 订阅” 请注意:目前cursor激活后都是提示一个月,所以目前还不清楚cursor是什么一个情况,目前有三种猜测:一种是需要手动激活续费,一种是可能是一个号只能试用一个月,然后下个月就新注册账号,还有一种就是会自动激活续费直到一年期满 重要前提条件 每个工具的福利都仅限于该工具的”新付费客户”。如果你之前已为某工具付过费,那么这次活动无法获得该工具的免费使用权。 但是可以新注册一个账号即可解决。 潜在问题与实用提示 支付验证问题: Lenny’s Newsletter 订阅支持国内信用卡 兑换各工具时,遇到验证错误可尝试支付宝或联系客服 邮箱接收问题: 务必检查所有邮箱文件夹 注册工具网站时,建议使用 Gmail 等国际邮箱 留心二手市场 这种捆绑包的最容易流入到二手市场,如果想捡漏或者回本,可以留心一下二手市场,比如闲鱼等平台,如果你只需个别工具,可以关注,但请注意交易风险。 这个 Lenny’s Newsletter 捆绑包简直是一场及时雨,为我们这些对效率和创新工具有强烈需求的人带来了巨大福利。只要注意”新客户”条款,并记得设置取消续费提醒,就能安全享受这波惊人的优惠。个人认为,这种模式也为国内内容创作者与工具服务商的合作提供了新思路,期待看到更多类似的创新合作出现。 建了一个小群,大家可以组团或者交换不需要的工具激活码:关注公众号【显林叔】,回复【lenny】或者访问:https://lenny-ai.vadxq.com 即可得到入群二维码

2025/4/16
articleCard.readMore

Google发布Agent2Agent(A2A)协议:开启协作式 AI 代理的新时代

引言 我们正处在一个 AI 智能体(Agent)蓬勃发展的时代,它们在各种应用中大显身手。但你有没有想过,如果这些聪明的“助手”们能够跨越框架和供应商的限制,更好地沟通协作,是不是能发挥出更大的潜力呢? 现实是,各自为战的 AI 智能体难以实现无缝的信息共享和行动协调,这就像一个团队里大家各说各话,难以完成复杂任务。为了打破这种壁垒,Google 在 Cloud Next 大会上正式发布并开源了 Agent2Agent (A2A) 协议! 这项举措旨在为不同来源、不同架构的 AI 智能体提供一个通用的“交流平台”,促进它们之间的无缝互操作。更令人兴奋的是,A2A 一经推出就获得了 50 多家行业领先企业的积极响应和支持,这预示着 AI 代理生态系统即将迎来一个全新的协作时代,有点像当年安卓系统重塑移动生态!今天,我们就来深入了解 A2A 协议,看看它如何让 AI 伙伴们协同工作。 认识 A2A:AI 伙伴间的通用语言 A2A 协议的核心目标就是实现智能体之间的互操作性,它建立在一系列清晰的设计原则之上: 🤝 视代理为伙伴 (Agent-First): 超越简单的“工具调用”思维,让智能体即使没有共享内存或工具也能自主协作。 🔧 基于成熟标准 (Standards-Based): 构建在大家熟悉的 HTTP、SSE(服务器发送事件)、JSON-RPC 之上,极大简化集成,降低开发者门槛。 🛡️ 安全第一 (Secure by Default): 内置企业级身份验证和授权机制,符合 OpenAPI 标准,确保通信安全可靠。 ⏳ 支持长时任务 (Long-Running Tasks): 轻松处理从快速查询到数小时甚至数天的复杂研究,并能提供实时状态更新和反馈。 🗣️ 多种沟通方式 (Modality-Agnostic): 不仅限于文本,原生支持音频、视频流、文件、表单等多种交互模态。 为了实现这些,A2A 定义了一些关键的技术组件: 代理卡片 (Agent Card): 每个智能体都有一个标准化的 JSON 文件(可以参考github上的https://github.com/google/A2A),像它的“数字名片”或“黄页”,公开自己的功能、技能、地址和认证需求,让其他智能体能动态发现它。 任务 (Task): A2A 中的核心工作单元,拥有从启动到完成(或失败/取消)的完整生命周期,用于管理和跟踪协作流程。 消息 (Message) & 部件 (Part): 智能体间通过“消息”沟通,每条消息可包含一个或多个“部件”。部件可以是文本 (TextPart)、文件 (FilePart) 或结构化数据 (DataPart),灵活支持多模态交互。 工件 (Artifact): 代表智能体完成任务后生成的输出成果,如报告、文件或数据,同样可包含多个部件。 A2A 与 MCP:强强联手,各有侧重 提到智能体协议,大家可能也会想到 Anthropic 的模型上下文协议 (MCP)。A2A 和 MCP 并非竞争关系,而是互补的! MCP (模型上下文协议): 主要关注 单个 AI 智能体如何更方便地调用外部工具和获取上下文信息。你可以把它想象成给智能体配上各种趁手的 “工具”或“扳手” 🔧,让它能连接 API、访问数据库等。 A2A (Agent2Agent 协议): 则专注于 多个 自主 AI 智能体之间的 直接通信和协作。它更像是智能体之间的 “对话机制” 💬,定义了它们如何找到彼此、协商任务、交换信息、协同行动。 它们如何协作? 想象一个招聘流程 : 招聘经理的智能体 (Agent A) 可能使用 MCP 连接内部简历数据库,筛选候选人。 Agent A 筛选出合格者后,通过 A2A 将信息传递给负责安排面试的智能体 (Agent B)。 Agent B (可能也通过 A2A 与日历智能体交互) 完成面试安排。 整个过程流畅高效,每个智能体发挥专长,通过 A2A 无缝衔接。Google 的 Agent Development Kit (ADK) 甚至同时支持这两种协议 。 【A2A 的价值:不止于连接】 A2A 协议的开放性和互操作性带来了巨大的潜力: 🚀 提升效率与生产力: 自动化跨智能体、跨系统的复杂工作流。 🔗 打破数据与应用孤岛: 实现不同平台和服务(如 CRM、ERP、HR 系统)间智能体的协作与信息共享。 💡 促进创新: 允许企业自由组合来自不同供应商、基于不同框架(如 ADK, LangGraph, CrewAI )构建的专业智能体,打造定制化、更强大的 AI 解决方案,避免供应商锁定。 🌐 应用场景广泛: 企业自动化: 订单处理、客户服务升级、供应链管理优化。 客户服务: 构建多层级、专业化的智能客服体系。 科学研究: 连接数据源、模拟工具,加速新药研发、材料发现等。 构建更强 AI 应用: 将具备不同能力的智能体连接起来,处理更复杂、更高级的任务。 拥抱 AI 协作新时代 总而言之,Google 的 Agent2Agent (A2A) 协议是 AI 发展中的一个重要里程碑。它直面日益复杂的 AI 环境中对互操作性和协作的关键需求。 通过为 AI 智能体提供标准化的“通用语言”,A2A 有望打破现有壁垒,开启一个功能更强大、应用更广泛、协作更顺畅的 AI 新时代。对于开发者和企业而言,这意味着能够构建更复杂、更集成、更智能的自动化解决方案,真正释放 AI 的潜力。 让我们一起期待 A2A 协议为 AI 生态带来的变革! 参考新闻: [1] Google blog: https://developers.googleblog.com/en/a2a-a-new-era-of-agent-interoperability [2] A2A Docs:  https://google.github.io/A2A [3] A2A Github: https://github.com/google/A2A 有趣的灵魂在等你 长按二维码识别

2025/4/10
articleCard.readMore

颠覆传统搜索!全球AI搜索工具正在攻城略地

还在被传统搜索的广告和无效信息折磨?是时候告别低效时代了!AI正以前所未有的力量颠覆搜索体验,一场信息革命悄然来临。从古老的关键词匹配到如今能“读懂你心”的智能引擎,搜索引擎的进化速度超乎想象。大型语言模型等AI技术的崛起,正以前所未有的效率和精准度,重塑我们获取信息的方式。面对信息爆炸、广告围剿和复杂查询的困境,AI凭借自然语言处理和语义搜索,精准捕捉你的意图,提供直达答案的个性化结果。在全球数字化浪潮中,英语和中文无疑是两大引擎。那么,针对这两大语种优化的AI搜索引擎,谁能更胜一筹?它们又将如何塑造未来的信息世界?本报告将深入对比分析中英文AI搜索引擎,揭秘其功能、优劣势,并洞察跨语言动态与特定语言环境下的独特考量,究竟谁能引领未来? 一、AI搜索革命:从「关键词猜谜」到「读心术」 对比以上两个流程图可以看出:传统搜索流程像一个迷宫,让用户在信息海洋中迷失。而AI搜索像是把答案一条直线连接到用户需求,可以突出其智能分析和直接给出答案的特点 ▶ 传统搜索三大酷刑 关键词猜谜:”周杰伦 歌词”搜出演唱会票务广告,“想找附近的咖啡馆,搜‘咖啡’结果全是连锁品牌广告,根本找不到特色小店。” 信息过载:找论文先翻10页垃圾网站,一项调查显示,用户平均需要花费很长时间才能在传统搜索引擎上找到所需信息。 复杂需求抓瞎:”对比iPhone15和小米15Ultra夜景拍摄效果”,想了解‘2024年全球新能源汽车销量前十名及各自的技术特点’,传统搜索需要打开无数个网页进行对比分析。 ▶ AI搜索杀手锏 语义理解:听懂潜台词(”找许嵩 拆了东墙补西墙是哪首歌”),不再需要精确的关键词,像和朋友聊天一样提问,AI也能秒懂。 智能摘要:3秒提取20页PDF核心结论,AI不仅能提取核心结论,还能标注信息来源,避免虚假信息。 跨模态搜索:用图片搜论文,语音问财报,AI搜索不再局限于文字,而是可以处理和理解多种模态的信息。 英语区顶流工具优缺点 下面是我列出的市面上主要的优缺点表格 对比维度说明 核心功能:突出各工具最具特色的技术实现和主要应用场景 主要优势:强调差异化竞争优势和独特价值主张 主要劣势:揭示当前存在的使用限制或潜在风险 市场定位分析 学术研究:Perplexity AI > Waldo > Google Gemini 开发者工具:Claude > Phind > ChatGPT > Komo 企业服务:Yep > Microsoft Copilot > You.com 隐私保护:Brave > Andi > Arc Search 多模态处理:Google Gemini > ChatGPT > Bing/Copilot 个人挑选出最优的三个工具 Google Gemini 功能: 自然语言搜索: 用户可以使用自然语言提问,Gemini 能够理解复杂的查询意图。 实时网络浏览: 可以实时访问互联网信息,提供最新的搜索结果。 多模态能力: Gemini 是一个多模态大语言模型,能够处理文本、图像、音频、视频等- 多种数据类型。这意味着它可以理解图片、分析视频内容,并生成包含多种媒体的回复。 与 Google 服务集成: 深度集成到 Google 搜索、Gmail、Docs、Slides 等 Google 常用服务中,提供更便捷的 AI 助手功能。 推理和问题解决: 具备一定的逻辑推理和问题解决能力,可以处理更复杂的查询。 “Double-check response”功能: 在 Google 搜索中,Gemini 生成的 AI 概览会提供引用链接,用户可以点击验证信息的来源。 代码生成和解释: 可以生成代码片段,并解释代码的功能。 多语言支持: 支持多种语言。 价格: 免费使用: Gemini 的核心功能通常通过 Google 搜索等产品免费提供。 Google One AI Premium 订阅: 某些高级功能,例如在 Gmail、Docs 等应用中集成 Gemini Advanced (基于更强大的模型),可能需要订阅 Google One AI Premium 计划。该计划包含额外的存储空间和其他权益,价格约为每月 19.99 美元(价格可能因地区而异)。 Perplexity AI 功能: 对话式搜索 多来源分析/实时溯源引用 “Keep Exploring”功能:在给出答案后,会提供一些用户可能感兴趣的相关问题,引导用户进行更深入的探索。 Collections(收藏夹): 用户可以创建和组织自己的查询和答案,方便日后查阅和分享。 多模型集成: 集成了多种大型语言模型,包括 OpenAI 的 GPT、Anthropic 的 Claude、Mistral AI 的模型以及 Perplexity 自研的模型,可以根据不同的查询选择最合适的模型。 网页总结和思维导图: 可以快速总结网页内容,甚至生成思维导图帮助用户理解。 PDF 文献知识库: 用户可以上传 PDF 文献,Perplexity AI 可以帮助建立知识库,并回答与文献内容相关的问题。 图片和视频搜索: 除了文本搜索,还提供相关的图片和视频搜索结果,并支持通过图片进行搜索。 价格: Free Plan(免费版): 提供基本的搜索和对话功能,每天有使用次数限制。 Pro Plan(专业版): 价格: 通常为每月 20 美元或每年 200 美元(价格可能因地区或促销活动而有所不同)。 权益: 无使用次数限制,更快的响应速度,访问更强大的模型,上传文件进行分析,优先支持等。 Enterprise Plan(企业版): 为企业用户提供定制化的解决方案和功能,价格根据具体需求而定。 Bing Search / Microsoft Copilot 功能: 传统网络搜索增强: Bing Search 在传统搜索的基础上,集成了 AI 模型来提高搜索结果的相关性。 Bing Chat (现已整合到 Copilot 中): 提供一个对话式的 AI 聊天界面,用户可以进行自然语言提问,并获得更具互动性的回复。 个性化购物体验: 根据用户的搜索历史、位置等信息,提供更个性化的购物建议。 结构化数据呈现: 能够以文本摘要、列表、电子表格等多种格式呈现结构化数据。 图像生成: 集成了 DALL-E 3 驱动的 Designer,用户可以通过文本描述生成图像。 生成式搜索: 在搜索结果页面提供 AI 生成的见解和摘要。 安全搜索: 提供安全过滤器,帮助用户过滤不适宜的内容。 图像创建器: 用户可以通过文本提示生成图像。 与 Microsoft 365 集成 (Microsoft Copilot): 在 Word、Excel、PowerPoint、Outlook、Teams 等 Microsoft 365 应用中提供 AI 助手功能,帮助用户撰写文档、分析数据、制作演示文稿、管理邮件等。 会议纪要和总结: 可以自动生成会议录音的纪要和关键点总结。 价格: Bing Search: 免费使用。 Microsoft Copilot (个人版): 通常作为 Windows 的一部分免费提供,提供基本的 AI 助手功能。 Microsoft Copilot for Microsoft 365 (企业版/个人订阅): 需要订阅 Microsoft 365 商业版或个人版才能使用在 Word、Excel、PowerPoint 等应用中的完整 Copilot 功能。价格根据订阅计划而有所不同,例如 Microsoft 365 商业标准版约为每月 12.50 美元/用户(按年付费),Microsoft 365 个人版约为每月 69.99 美元/年。 总结对比 Perplexity AI: 专注于提供透明、可验证的答案,特别适合研究和学习场景,免费版功能强大,专业版提供更多高级功能。 Google Gemini: 拥有强大的多模态能力和与 Google 生态系统的深度集成,适合处理复杂和模糊的查询,核心搜索功能免费,高级功能需要订阅 Google One AI Premium。 Bing Search/Microsoft Copilot: 在传统搜索基础上融入 AI,尤其在提高办公效率方面表现出色,与 Microsoft 365 集成紧密,个人版免费,完整版 Copilot 功能需要 Microsoft 365 订阅。 中文区黑马突围战 列出市面上主要的优缺点表格 挑选出最优的三个工具 夸克AI搜索 / 通义 功能: “AI超级框”: 这是夸克AI搜索的核心特色,旨在将传统的搜索框升级为一个集成了AI对话、深度思考、深度搜索、深度研究和深度执行的一站式平台。用户可以在这个框内完成从提问到获取最终答案的整个过程,无需跳转多个页面或进行复杂的关键词调整。 智能交互: 强调与用户的智能对话,能够理解用户的自然语言提问,并进行多轮交互以完成用户需求。 深度搜索: 不仅仅是关键词匹配,而是基于对用户意图的理解进行更深层次的信息检索。 深度研究: 具备一定的分析和整合能力,能够从多个来源提取信息并进行总结。 深度执行: 意味着可能具备一定的行动能力,例如直接提供可执行的建议或引导用户完成特定任务。 通义大模型支持: 背靠阿里巴巴的通义大模型,拥有强大的自然语言处理和生成能力。 优势: 颠覆传统搜索逻辑: “AI超级框”的概念试图改变用户使用搜索引擎的习惯,从“关键词匹配+网页链接”模式升级为“智能交互+一站式答案”,有望提高信息获取的效率和精准度。 强大的AI能力: 依托通义大模型,具备优秀的语义理解、知识问答和内容生成能力。 一站式解决需求: 理论上可以在一个界面内完成多种任务,减少用户的操作步骤。 潜在的生态整合能力: 作为阿里巴巴旗下的产品,未来可能与淘宝、支付宝等阿里生态内的其他服务进行更深入的整合。 潜在劣势/市场定位分析: 用户习惯培养成本: 颠覆性的设计可能需要用户适应新的搜索方式。 “超级框”的复杂性: 功能集成过多可能会导致界面复杂,反而降低易用性。 与阿里生态的绑定: 如果过度依赖阿里生态,可能会限制其在非阿里用户群体中的普及。 实际体验待观察: “深度执行”的具体能力和效果还需要在实际使用中检验。 市场定位: 夸克浏览器本身在用户群体中具有一定的基础,AI搜索的加入可能会吸引更多追求效率和智能化体验的用户。 微信AI搜索 / 腾讯元宝 功能: 微信生态内搜索: 微信拥有庞大的用户基数和丰富的内容生态(包括公众号文章、朋友圈、视频号、小程序等),微信AI搜索的核心优势在于能够便捷地搜索这些内容。 腾讯元宝赋能: 腾讯元宝是腾讯推出的AI助手产品,微信AI搜索的升级很可能得到了腾讯元宝的技术支持,意味着其在自然语言理解、知识问答等方面具备更强的能力。 潜在的跨平台搜索能力: 除了微信生态内的内容,未来也可能拓展到更广阔的互联网信息。 语音搜索和图像搜索: 依托微信强大的语音和图像识别技术,可能提供更便捷的搜索方式。 社交属性的结合: 或许会结合微信的社交属性,例如基于朋友推荐或群组讨论进行搜索结果的优化。 优势: 庞大的用户基础: 微信的月活跃用户数非常庞大,这为微信AI搜索的普及提供了天然的优势。 丰富的内容生态: 微信内拥有海量的优质内容,AI搜索能够帮助用户更高效地发现和获取这些信息。 便捷的使用场景: 用户无需下载额外的App,在日常使用的微信中即可完成搜索。 潜在的社交互动: 结合社交属性的搜索可能带来更个性化和可信赖的结果。 腾讯强大的AI技术支持: 腾讯在AI领域的技术积累为微信AI搜索提供了坚实的基础。 潜在劣势/市场定位分析: 搜索范围的局限性: 目前来看,微信AI搜索可能更侧重于微信生态内的内容,对于需要搜索更广泛互联网信息的用户来说可能不够全面。 隐私担忧: 微信作为社交平台,用户对其数据的使用可能存在一定的隐私担忧。 商业化考量: 微信搜索可能受到商业化因素的影响,例如广告的展示方式等。 市场定位: 微信AI搜索主要服务于微信用户,旨在提升在微信生态内的信息获取效率和用户体验。 豆包AI 功能: 字节跳动旗下产品: 豆包AI 由字节跳动推出,字节跳动在内容推荐和算法方面拥有强大的技术积累。 潜在的短视频和内容推荐优势: 考虑到字节跳动在抖音/TikTok 等短视频平台的成功,豆包AI 很可能在短视频内容的搜索和推荐方面具有独特的优势。 更广泛的AI助手能力: 除了搜索功能,豆包AI 可能还具备更广泛的AI助手能力,例如文本生成、对话聊天、知识问答等。 个性化推荐: 可能会根据用户在字节跳动旗下平台上的行为和偏好,提供更个性化的搜索结果和内容推荐。 优势: 强大的内容推荐算法: 字节跳动在内容推荐方面的技术领先地位是豆包AI 的重要优势。 短视频搜索的潜力: 在短视频内容日益重要的今天,豆包AI 有望成为用户搜索和发现短视频内容的重要工具。 潜在的跨平台整合: 未来可能与抖音、今日头条等字节跳动旗下的其他产品进行整合。 新兴AI力量: 作为字节跳动在AI领域的布局,豆包AI 有望带来一些新的创新功能。 潜在劣势/市场定位分析: 可能侧重于字节跳动生态: 类似于微信,豆包AI 的搜索结果可能更倾向于字节跳动平台上的内容。 通用搜索能力待观察: 在更广泛的互联网信息搜索方面,豆包AI 的能力可能还需要进一步提升。 品牌认知度: 相对于夸克和微信,豆包AI 的品牌认知度可能还需要一定的提升时间。 市场定位: 豆包AI 的目标用户可能更偏向于年轻群体和对短视频内容有较高需求的用户。 总结 这三款中文AI搜索工具都背靠着中国互联网巨头,由于国内免费的用户习惯以及各大APP的信息茧房,其实各自的优缺点更多是针对于各家的搜索独特数据源来的。各自具有独特的优势和侧重点: 夸克AI搜索/通义:背靠阿里巴巴,拥有强大的内容推荐和算法能力,有望在搜索领域带来新的突破。 微信AI搜索/腾讯元宝:依托微信庞大的用户基础和丰富的内容生态,有望在社交搜索领域取得显著优势。 豆包AI:作为字节跳动在AI领域的布局,有望带来一些新的创新功能,尤其是在娱乐领域和字节系如抖音、今日头条等短视频搜索方面。 未来搜索预言 短期内的趋势猜测 场景化搜索: 例如对着汽车说“附近哪里有充电桩”,AR导航直接显示路线。或者是拍冰箱照片→推荐本周菜谱+盒马一键下单 人格化助手:比如用鲁迅文风写邮件催尾款,比如用李白的风格写一首催人泪下的情诗并且邮寄给你精彩问怎么追的女生微信上 防AI幻觉协议:自动标注内容可信度等级,减少AI幻觉,是deepseek推理模式的再度升级化 担忧 隐私问题:AI搜索可能会收集用户的搜索习惯和偏好,这可能会导致隐私泄露。 信息茧房:AI搜索可能会导致用户被锁定在特定的信息流中,这可能会导致信息茧房效应。 安全问题:AI搜索可能会被用于传播错误信息,这可能会导致安全问题。

2025/3/17
articleCard.readMore

激战“后DeepSeek时代”:全球大模型开启“地狱级”内卷模式

2024年,当国产模型DeepSeek以“非常低成本训练和推理,性能比肩o1”的参数和价格横空出世时,AI行业第一次感受到来自中国开源力量的全面冲击。这场由技术突破引发的“地震”,直接加速了大模型从“技术竞赛”到“商业绞杀”的转折——价格腰斩、多模态混战、垂直领域军备竞赛,成为后DeepSeek时代的三大标志。 从价格血战到能力爆炸,LLM竞争如何重构AI行业格局? 各厂商价格策略全景透视 (💰价格单位:美元/百万token | 人民币/百万token,汇率按1:7换算) 🔵 OpenAI:技术分层定价教科书 模型矩阵与价格 模型层级输入单价输出单价市场定位 顶配旗舰 GPT-4.5$75 → ¥525$150 → ¥1050高端科研/金融场景 主流迭代 GPT-4o$2.5 → ¥17.5$10 → ¥70企业级商用 价格屠夫 GPT-4o-mini$0.15 → ¥1.05$0.6 → ¥4.2中小企业收割机 订阅服务 ChatGPT Plus$20/月(含Sora试用配额)个人开发者 ChatGPT Pro$200/月(无限量API接入)企业定制方案 战略解析 ✅ 金字塔定价:用GPT-4.5($75/$150)锚定技术高度,GPT-4o-mini(降价**80%**)抢占市场份额 ✅ 双轨制收费:C端订阅绑定用户,B端按量保证利润率 🟠 阿里Qwen:多模态定价范本 全模型价格清单 模型类型输入单价输出单价技术亮点 文本引擎 qwen-max¥2.4¥9.6通用场景最优解 多模态王者 qwen-omni文本¥0.4 音频¥25 图片视频¥1.5文本¥1.6 多媒体¥4.5跨模态成本控制 视觉专精 qwen-vl-max¥3¥9图文理解TOP1 开发者利器 qwen-coder-plus¥3.5¥7代码生成优化30% 创作工具 qwen-wanx文生图¥0.2/张视频生成¥0.7/秒全球视频生成冠军 生态彩蛋 qwen-audio免费-开发者引流入口 战略解析 ✅ 场景拆解定价:音频(¥25输入)比文本贵62倍,精准匹配行业需求 ✅ 技术换市场:视频生成成本压至0.7元/秒,比国际竞品低40% ✅ 开源生态全球最强:qwen作为超过9万多大模型的基座,全球开发者生态最强 🔴 DeepSeek:开源价格核弹 双模型对比表 模型输入单价输出单价性价比对标(vs GPT-4o-mini) DeepSeek-R1¥4 → $0.57¥16 → $2.29输入贵46%但性能更强 DeepSeek-v3¥2 → $0.29¥8 → $1.14输入价仅为GPT-4o-mini的53% 战略解析 ✅ 中国制造优势:宣称训练成本仅行业1/10,倒逼国际巨头跟降 ✅ 开源+低价组合拳:用国内开发者生态构建技术护城河 🟢 Google Gemini:轻量化多模态闪电战 全模型价格体系 模型类型输入单价输出单价场景定位 免费入口 Gemini Flash 2.0文字/图片/视频:$0.1 音频:$0.7$0.4多模态实验田 轻量商用 Gemini Flash 2.0 Lite$0.075$0.3高频交互场景 订阅服务 Gemini Pro$22.5/月(含100万token额度)中小企业快速接入 视觉专精 Imagen 3-$0.03/张图像生成性价比之王 战略解析 ✅ 闪电战组合拳: 免费版(Flash 2.0)零门槛获客,培养用户多模态使用习惯 Lite版单价压至$0.075/百万token,比GPT-4o-mini低50% ✅ 多模态打包价:文字/图片/视频统一计价($0.1),简化商业化路径 ✅ 图像价格狙击:Imagen 3单张成本$0.03,直接对标Midjourney 🟣 Anthropic Claude:高端能力定价术 全模型价格清单 模型层级输入单价输出单价能力定位性价比对标 复杂任务 Claude 3 Opus$15$75科研/金融决策比GPT-4.5贵20% 开发者专供 Claude 3.7 Sonnet$3$15代码推理优化比GPT-4o贵20% 性价比入口 Claude 3.5 Haiku$0.8$4日常任务处理比Gemini Lite贵6.7% 战略解析 ✅ 能力溢价策略: Opus版本($15/$75)树立技术标杆,锁定高净值客户 Sonnet代码推理能力溢价20%,打造开发者粘性 ✅ 精准卡位战术: Haiku定价$0.8/$4,卡在GPT-4o-mini($0.15)和Gemini Lite($0.075)之间,主打“性能平衡” 🔵 Meta Llama:开源帝国的反击战 现状与价格对比 关键指标Llama3.1竞品对标(DeepSeek/Qwen)差距分析 开源影响力GitHub Star 58kDeepSeek 90k/Qwen 54k社区活跃度落后20% 推理成本$1.2/百万token$0.29/百万token成本高314% 多模态支持仅文本全模态覆盖技术代差1年+ 商业化进度免费API订阅制+按量计费变现路径模糊 战略解析 ✅ 背水一战:4月份即将发布新一代llama ✅ 生态捆绑:通过开源生态框架优势绑定开发者 ⚠️ 风险预警:中国双雄(DeepSeek/Qwen)已构建完整开源商业生态 📊 关键数据对比墙 厂商最低输入单价输出单价降价幅度杀手锏 OpenAI$0.15 (4o-mini)$0.6↓80%技术品牌溢价 Qwen¥0.4 (Omni文本)¥1.6-多模态成本控制 DeepSeek¥2 (v3) → $0.29¥8 → $1.14↓70%开源生态+中国速度 Gemini$0.075 (Lite版)$0.3↓65%轻量化场景覆盖 ⚡️ 行业趋势三大爆点 价格战进入纳米级 → DeepSeek以“中国开源+极致性价比”打破垄断,倒逼国际大厂(如GPT-4o-mini降价80%)跟进价格战。 多模态成新战场 → 文生视频成本进入“秒时代”(Qwen万象0.7元/秒) 垂直领域专业化: → 代码模型(Qwen-coder、Claude Sonnet)、行业大模型(医疗、金融)成新增长点; → 通用模型利润空间被压缩,厂商转向细分场景“深挖护城河”。 开源生态权力转移 → Llama市占率被中国双雄(DeepSeek+Qwen)蚕食,Meta急需技术反击 → 开源模型商业化路径(如广告、定制化服务)成下一阶段焦点。 📈 未来推演 2024-2025:价格战白热化,50%中小厂商出局 **2026+**:形成「3+2+X」格局: 3巨头:OpenAI(技术)、Google(生态)、阿里(多模态) 2黑马:DeepSeek(开源)、Anthropic(垂类) X专精:医疗/金融等场景化模型 中国力量首次掌握定价权:DeepSeek模式或成全球AI基础设施成本基准 AI价格战,谁在赔本赚吆喝? 👉 你认为哪家厂商的定价策略最可能胜出? 👉 开源模型会彻底颠覆闭源商业生态吗?

2025/3/5
articleCard.readMore

改名公告:清竹茶馆的新篇章-清竹志

亲爱的读者们,祝大家1024程序员节快乐! 从今天起,“清竹茶馆”将正式更名为“清竹志”。个人公众号正式更名为“显林叔”。 改名公告:清竹茶馆的新篇章-清竹志 亲爱的读者们, 我很高兴地宣布,我的博客将迎来一个新的篇章——从今天起,“清竹茶馆”将正式更名为“清竹志”。个人公众号正式更名为“显林叔”。 为什么要改名? 这次改名的决定主要是因为视频号的“清竹茶馆”这个名字被别人抢注了。虽然这对我来说是个不小的挑战,但同时也给了我一个重新思考和定义自己的公共平台账号的机会。以下是一些主要原因: 避免混淆:为了避免读者在访问时产生混淆,改名是必要的。 品牌升级:新的名字更具个性化和识别性,有助于增强品牌的独特性。 内容扩展:随着内容的丰富和多样化,我希望新的名字能够更好地涵盖所有主题和领域。 新名字的含义 “清竹志”这个名字包含了深刻的意境和丰富的含义: 清竹: 清:代表清新、纯净,象征着高洁和清雅。 竹:竹子象征着坚韧、挺拔和高风亮节。 合起来希望自己能够做一个低调纯粹的技术人。 志: 志:有志向、记录、志趣的意思,表示对某种目标或兴趣的执着追求。 综合起来,“清竹志”可以理解为: 清新低调的志向:表达了对纯净、低调生活和目标的追求。 记录有趣生活:象征着记录和分享有趣、清新的生活方式或思想。 志趣相投:希望有更多志同道合的朋友一起交流分享。 鞭策自己:希望自己能够坚持自己的目标和兴趣,不断进步。 公众号为何不和博客名一样 因为个人公众号主要在微信流量池,更希望真实些,也希望更具个人品牌性,那么个人名字相关就比较好了。至于网络上,公开化的,还是希望有个代号名字。 所以大家喊习惯的显林叔就比较合适啦 改名后的变化 虽然名字改变了,但我的初衷和目标未变。我仍然会持续为大家提供高质量的内容。以下是一些即将推出的新变化: 博客的域名不会变化:https://blog.vadxq.com 内容更新:将增加更多关于技术、生活和个人成长的文章。 读者互动:推出更多读者互动的活动,如问答环节、读者投稿等。 个人公众号为:显林叔。只是做更名变化 感谢大家的支持 在此,我要感谢每一位读者的支持和陪伴。希望在新的篇章中,我们能继续一起成长,共同进步。 谢谢大家! 1024快乐 值此1024程序员节,祝大家节日快乐! by vadxq 2024.10.24 上海 推荐阅读 献给2023年1024节的礼物 献给2022年1024节的礼物

2024/10/24
articleCard.readMore

【2023年终总结】与逝去的年华道个别

这一年,过的可真快呀!最后缅怀的时间很少,那就与与逝去的年华道个别吧 前言 这一年,过的可真快呀!最后缅怀的时间很少,那就与与逝去的年华道个别吧 与All in Web3的自己道别 围绕着Web3进行的build,在上半年还是很好很有精力,是一个个人认为很Crypto,很积极建设社区的氛围。上半年做了很多的事情。在BuidlerDAO里,做了DAO工具,做了一个完善框架的Web3教育产品,用Flutter做了一个圈子知识付费产品,做了些自己想做的邮箱体系,Docs体系等。在DeBox的开源生态里做了DeBoxLove生态,做了开源的DeBox SDK功能。也参与了一些其他Web3的项目,但是都是涉入不深。这一年,自己似乎做了很多,但是什么都没做好,再也没有以前做Vitae3的成就感和奋斗感。不知从何其,越来越多的活让自己再无心关注业内前沿,再无心去关注开源生态,也无心去与人交流和分享,陷入无限的自我损耗,有心而无力的感觉越来越深。遂于年末,和All in Web3道了别,但是不会远离,只是以兴趣与业余去研究。 与追逐乌托邦技术的自己道别 今年总计提交了1744commit,数量相对前两年是有下降的,但是相对质量和技术积累有所进步的,今年主要是围绕着Nextjs工程化和推进React Server Components而进行工程化适配和改造。同时今年也对Flutter得工程化进行了改造,对最贴近Flutter官方RoadMap的工程化体系进行了深究与项目实践。具体的总结可以见我的GitHub代码,希望能对开源有所贡献。这一年对于Web3原生态技术的追逐越来越弱了,但是对于Rust还是很向往,曾利用Rust写了钱包生成的Demo。曾经的自己是一个有代码洁癖,向往开源与技术改造世界的乌托邦世界的技术人。向往做一个浪漫而简单的技术人,沉迷技术,做好自己。而今创业的经历,以及经济下行行情下办公室的环境氛围,埋头苦干的人并不能得不到好的结局,你无法想象当行情不好的情况下,小利益也会竞争的不可开交。也许是时候和过去纯乌托邦的幻想而道别,在原则性的前提下,做一些改变反而可以让自己可以更好的做技术。 与沉迷独自念旧的自己道别 这一年去了好几个地方,处于上海和江西自不必说。去了苏州,游过拙政园,踏过虎丘,随船穿过十里山塘。去了成都,住于琴台,逛了春熙路太古里万象,穿过了宽窄巷子,吃遍了乐山,排过了玉林的队,批发了菜市场的特产。也去了大理,住于苍山脚下,绕了圈洱海,登顶了玉龙雪山。最后从远程工作又兜兜转转的回到了上海。这一年的自己,和很多人都道别了。苏州,上海,南昌,上饶,大理。每一个地方,也许都是和一些人的最后碰面。站在年终的世界,也许只是自己一个人的念旧与回首。自己一直是一个很仪式感,很念旧的人。其实仪式感也是念旧的一种,可惜,时代变了,世界变了,身边变了,我也变了。这一年,也见证了浪浪的求婚,围观了他们的婚纱照,也见证了子建学长步入婚姻的殿堂。世界也是有美好在传递。但也是记忆中的存在了,和过去的自己道个别吧。 新的一年:变 一切都在变化,个人是无法应对种种因果下的变化的伟力。也许拥抱变化,做最适合当下的舒服且有竞争力的自己。改变衣食住行,做一个积极向上的人。放弃幻想,摒弃固守己见。放弃固守技术,多接触不同职业渠道。也许变,是新的一年的主题。 by vadxq 2023.12.31 上海

2023/12/31
articleCard.readMore

【前端工程化】Nextjs项目工程化最佳实践总结(献给2023-1024的礼物)

时间已经是2023年了,马上也2024年了,自己去Web3世界闯荡已经一年多了。记得两年前在写文章的时候,发现自己对新领域的狂热在减弱,但是经过在新领域的锤炼,仿佛换起来自己的新狂热程度。时过境迁,React十周年了,Nextjs v13发布也一年了,App Dir(App router)模式的出现,对于自己的吸引力非常大,刚出没多久就在研究和完善相关的工程化,而今经过在企业化项目的实践里,已经锤炼出了一套自认为可以使用的Starter和经验积累。正值1024节,特为大家献上一片总结! 可以配合我总结并且近期也更新的一个starter项目进行理解哟:nextjs-web3-starter Nextjs介绍和它自v13版本的技术的改进 Nextjs是一个React的元框架 Nextjs在我的印象里是SSR框架,最初使用v12时,了解其SSG概念。之后深入了解,是一个很不错的全干元框架。2022年v13版本发布,beta版本出来的App Dir,当时是这样叫它的,后来定义为APP Router。这种结合React Server Components范式和React Suspense,让人着迷。当时beta版就开始研究并且引入到正式项目里。后来Turbopack的出现,更让人兴奋,但初时由于Turbopack不够完善,许多包都没法兼容,后来提Issue,改包引用或者自己手撸代码造轮子替代,最后剩下的老大难aws的sdk无法兼容也被手撸代码替代了,最后Nextjsv13+Turbopack,开发体验非常爽!如果遇见不兼容Turbopack的包,建议沉下心手撸替代或者是提交PR。 React Server Components React Server Components(简称RSC)是一种全新范例的名称,我们可以创建专门在服务器上运行的组件。这使我们能够在 React 组件中执行诸如编写数据库查询之类的操作。传统的react组件被叫做client组件,在RSC上,默认情况下所有组件都被假定为服务器组件。当然服务器组件更多需要结合编译工具才能发挥它的特性,所以Nextjs天生适合做这一块东西。可以结合这两篇文章去了解RSC和Nextjs是如何做的:Making Sense of React Server Components / Nextjs Server Components。 在处理RSC的时候,最让人困惑的应该就是它的边界状态问题了,应该不少小伙伴第一次来看这个RSC的时候,都会产生疑惑,Server和Client组件嵌套后的是否会重新渲染问题,状态变量如何保存和变更等等。当然请注意,React有这些规则:客户端组件无法渲染服务器组件,服务器组件永远不会重新渲染。所以有状态的hooks是在client组件里进使用的。在server组件里,无法使用useEffect和useStae等。Server组件传递的props也是不变的。 但是当涉及到这种边界时,父子组件的关系就不那么重要了,可以通过父子组件来进行拆分和处理状态量。我们来看看Nextjs是怎么做的:Nextjs结合Suspense,服务器组件呈现为一种特殊的数据格式,叫做React 服务器组件有效负载(简称RSC Payload),然后结合Client JavaScript指令在服务器组合成Html。 1 2 3 4 5 self.__next['$1024'] = { type: 'p', props: null, children: "Hello world!", }; 然后在客户端:HTML用于初始页面加载,然后RSC Payload协调客户端和服务器组件树,并更新 DOM,JavaScript指令用于水合客户端组件并使应用程序具有交互性。结合Streaming流式传输,性能优化明显,服务器组件不包含在我们的JS包中,这减少了需要下载的JavaScript数量以及需要水合的组件数量,大大提升了性能和加载速度! Nextjs工程化技术栈和架构 了解了Nextjs v13+的新特性,我们来聊聊Nextjs工程化的一些事吧。 Nextjs工程化技术栈 大致可以根据传统的工程化领域划分为如下: 编程语言:TypeScript 5.x 构建工具:Nextjs Turbpack + Webpack 前端框架:Nextjs 路由工具:Nextjs文件路由 状态管理:React Context CSS:Tailwindcss+ Postcss HTTP 工具:Fetch 国际化:Nextjs Middleware + intl-localematcher + negotiator 多环境:ENV Config 数据库ORM:Prisma Git Hook 工具:Husky + Lint-staged 代码规范:EditorConfig + Prettier + ESLint 提交规范:Commitlint Nextjs工程化目录结构 Nextjs 工程化的一些其他细节 关于API请求 这里使用了fetch进行请求,服务端请求和客户端请求保持了一致性。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // server端 import { cookies } from 'next/headers'; export const nextFetchGet = async (api: string) => { const nextCookies = cookies(); const token = nextCookies.get('token') || ''; const role = nextCookies.get('role'); const roleId = nextCookies.get('roleId'); const url = `${process.env.BASE_FETCH_URL}/api/be${api}`; const res = await fetch(url, { headers: token ? { Authorization: 'Bearer ' + token } : {} }); if (!res.ok) { throw new Error('Failed to fetch data'); } return res.json(); }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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 115 116 117 118 119 120 // client端参考 import { getCookie } from './cookie'; import { getApiUrl } from './helpers'; interface RequestOptions extends RequestInit { responseType?: | 'TEXT' | 'JSON' | 'BLOB' | 'ARRAYBUFFER' | 'text' | 'json' | 'blob' | 'arraybuffer'; body?: any; } // 发送数据请求 const request = async (url: string, config?: RequestOptions) => { const finalUrl: string = getApiUrl(url); const inital: RequestOptions = { method: 'GET', body: null, headers: { 'Content-Type': 'application/json', Authorization: getCookie('token') ? 'Bearer ' + getCookie('token') : '' }, credentials: 'include', cache: 'no-cache', mode: 'cors', responseType: 'JSON' }; const configs: RequestOptions = { ...inital, ...config }; if (config && config.headers) configs.headers = { ...inital.headers, Authorization: getCookie('token') ? 'Bearer ' + getCookie('token') : '', ...config.headers }; // 基于fetch请求数据 const finalConfig: RequestInit = { method: configs.method?.toUpperCase(), credentials: configs.credentials, mode: configs.mode, cache: configs.cache, headers: configs.headers, body: configs.body }; return fetch(`${finalUrl}`, finalConfig) .then((response: Response) => { // 走到这边不一定是成功的: // Fetch的特点的是,只要服务器有返回结果,不论状态码是多少,它都认为是成功 const { status } = response; if (status >= 200 && status < 400) { // 真正成功获取数据 let result: any; switch (configs.responseType && configs.responseType.toUpperCase()) { case 'TEXT': result = response.text(); break; case 'JSON': result = response.json(); break; case 'BLOB': result = response.blob(); break; case 'ARRAYBUFFER': result = response.arrayBuffer(); break; default: result = response.json(); } return result; } // 失败的处理 return Promise.reject(response); }) .catch((reason: any) => { // @2:断网 if (typeof window !== 'undefined' && navigator && !navigator.onLine) { console.log('Your network is break!'); } // @1:状态码失败 if (reason && reason.status) { switch (reason.status) { case 400: console.log('Please verify your info!'); break; case 401: console.log('Please Login!'); break; case 403: console.log('You have no access to this'); break; case 500: console.log("Oops, there's something wrong!"); break; case 504: console.log("Oops, there's something wrong!"); break; default: } } else { // @3:处理返回数据格式失败 console.log("Oops, there's something wrong!"); } return Promise.reject(reason); }); }; export default request; 关于代理API问题 这里看自己需要,如果Nextjs的资源不及后端接口,还是不建议直接代理所有接口,只需要编写服务器组件所需要的接口代理就行了。如果需要全局代理后端接口,可以使用rewrites的方式,不建议使用以前proxy包的方式了: 1 2 3 4 5 6 7 8 9 10 11 12 const nextConfig = { rewrites: () => { return [ { // 注意,加了一个be/,为什么?为了区分我们项目写的接口,避免被代理进去了。我们只需要代理外部的接口 // Note that a be/ is added, why? In order to distinguish the interface written by our project, avoid being proxied. We only need to proxy the external interface source: '/api/be/:slug*', destination: `${process.env.BACKEND_URL}/api/:slug*` } ]; } }; Nextjs 国际化 使用了Nextjs中间件模式和intl-localematcher + negotiator进行国际化处理的,由于nextjs版本的更新导致输出的结构变化,所以中间件随着版本更新需要进行维护,如果在生产上版本更新需要注意,最新版中间件如下: 1 pnpm add @formatjs/intl-localematcher negotiator 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 // middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { i18n } from '@/i18n/config'; import { match as matchLocale } from '@formatjs/intl-localematcher'; import Negotiator from 'negotiator'; function getLocale(request: NextRequest): string | undefined { // Negotiator expects plain object so we need to transform headers const negotiatorHeaders: Record<string, string> = {}; request.headers.forEach((value, key) => (negotiatorHeaders[key] = value)); // Use negotiator and intl-localematcher to get best locale const languages = new Negotiator({ headers: negotiatorHeaders }).languages(); const locales: string[] = i18n.locales as unknown as string[]; return matchLocale(languages, locales, i18n.defaultLocale); } export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; // Check if there is any supported locale in the pathname const pathnameIsMissingLocale = i18n.locales.every( (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}` ); // Redirect if there is no locale if (pathnameIsMissingLocale) { const locale = getLocale(request); // e.g. incoming request is /products // The new URL is now /en-US/products return NextResponse.redirect( new URL(`/${locale}${pathname === '' ? '/' : pathname}`, request.url) ); } } export const config = { // 2023-8-28 update matcher: '/((?!api|static|.*\\..*|_next).*)' }; 我们配置获取语言和数据,目前由于Turbopack的一些bug,导致需要使用如下方式引用json: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // getDictionary.ts import 'server-only'; import type { Locale } from './config'; import enJson from '@/i18n/locales/en.json' assert { type: 'json' }; import zhCNJson from '@/i18n/locales/zh-CN.json' assert { type: 'json' }; // We enumerate all dictionaries here for better linting and typescript support // We also get the default import for cleaner types const dictionaries = { en: () => enJson, 'zh-CN': () => zhCNJson // https://github.com/vercel/next.js/issues/47595 // en: () => // import('@/i18n/locales/en.json', { assert: { type: 'json' } }).then( // (module) => module.default // ), // 'zh-CN': () => // import('@/i18n/locales/zh-CN.json', { assert: { type: 'json' } }).then( // (module) => module.default // ) }; export const getDictionary = async (locale: Locale) => dictionaries[locale](); 1 2 3 4 5 6 7 8 // config.ts export const i18n = { defaultLocale: 'en', locales: ['en', 'zh-CN'] } as const; export type Locale = (typeof i18n)['locales'][number]; Nextjs PWA 参考目前市面上的Nextjs PWA插件,目前采取了@ducanh2912/next-pwa,需要修改next.config.js,参考如下,具体细节和定义可以看包文档: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // next.config.js // eslint-disable-next-line @typescript-eslint/no-var-requires const withPWA = require('@ducanh2912/next-pwa').default({ dest: 'public', cacheOnFrontEndNav: true, aggressiveFrontEndNavCaching: true }); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, }; module.exports = withPWA(nextConfig); 请注意,需要在public目录配置mainfest.json 同时layout.tsx记得引入mainfest.json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { Metadata } from 'next'; export async function generateMetadata(): Promise<Metadata> { return { ...DefaultMetadata, title: 'Create Next App', description: 'Generated by create next app', applicationName: 'vadxq', manifest: '/mainfest.json', themeColor: '#FFFFFF', appleWebApp: { capable: true, statusBarStyle: 'default', title: 'vadxq' }, formatDetection: { telephone: false }, icons: { shortcut: '/favicon.ico', apple: [{ url: '/favicon.ico', sizes: '180x180' }] } }; } 对了,会自动生成一堆文件在public目录下,本地开发的话建议将这些添加到gitignore下: 1 2 3 public/sw.js public/swe-worker-* public/workbox-* 关于Nextjs环境区分变量 这里建立使用env环境变量来控制,同时增加本地.env.local配置,进行gitignore忽略,这样大家可以愉快的修改配置文件调试。 关于Nextjs包引入优化 近期Nextjs v13.5+版本更新了,更新了一部分Nextjs配置,在experimental配置项中,增加了optimizePackageImports配置,可以进行包优化!同时更新优化了性能,可以查看此文阅读最新信息Next.js 13.5 关于Nextjs的全局变量 可以使用React的createContext提供,然后通过父子组件嵌套,使其在Client端水合产生作用。这里提供一份代码,用于触发路由进行loading加载进度条的demo,供参考: 1 2 3 4 5 6 7 8 // state.ts 'use client'; import { createContext } from 'react'; const StartRouterChangeContext = createContext<() => void>(() => {}); export default StartRouterChangeContext; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // link.tsx 'use client'; import Link from 'next/link'; import { useContext } from 'react'; import StartRouterChange from './state'; export default function LayoutLink({ href, style, children, className }: React.ComponentProps<'a'>) { const startChange = useContext(StartRouterChange); const useLink = href && href.startsWith('/'); if (useLink) return ( <Link href={href} className={className} onClick={() => { const { pathname, search, hash } = window.location; if (href !== pathname + search + hash) startChange(); }} style={style} > {children} </Link> ); return ( <a href={href} style={style} className={className}> {children} </a> ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 // index.tsx 'use client'; import { useCallback, useEffect, useState } from 'react'; import { usePathname, useSearchParams } from 'next/navigation'; import NProgress from 'nprogress'; import StartRouterChangeContext from './state'; function RouterEventWrapper({ onStart = () => null, onComplete = () => null, children }: React.PropsWithChildren<{ onStart?: () => void; onComplete?: () => void }>) { const [isChanging, setIsChanging] = useState(false); const pathname = usePathname(); const searchParams = useSearchParams(); useEffect(() => setIsChanging(false), [pathname, searchParams]); useEffect(() => { if (isChanging) onStart(); else onComplete(); }, [isChanging]); return ( <StartRouterChangeContext.Provider value={() => setIsChanging(true)}> {children} </StartRouterChangeContext.Provider> ); } export default function RootLayout({ children }: React.PropsWithChildren) { const onStart = useCallback(() => NProgress.start(), []); const onComplete = useCallback(() => NProgress.done(), []); return ( <RouterEventWrapper onStart={onStart} onComplete={onComplete}> {children} </RouterEventWrapper> ); } 关于日志收集 目前有vercel的日志收集和第三方的Sentry。 Sentry直接使用Sentry提供的插件即可。 关于Nextjs操作数据库 我的starter提供了Prisma的调用案例,当然注释了,需要进行以下操作: 安装依赖 1 2 pnpm add @prisma/client pnpm add -D prisma 1 2 3 4 "script": { "prisma:push": "npx prisma db push", "prisma:generate": "npx prisma generate", } next.config.js 1 2 3 experimental: { serverComponentsExternalPackages: ['@prisma/client'] // prisma support }, prisma文件目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // prisma/index.ts import { PrismaClient } from '@prisma/client'; // PrismaClient is attached to the `global` object in development to prevent // exhausting your database connection limit. // // Learn more: // https://pris.ly/d/help/next-js-best-practices const globalForPrisma = global as unknown as { prisma: PrismaClient }; export const prisma = globalForPrisma.prisma || new PrismaClient(); if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma; export default prisma; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // prisma/schema.prisma // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model Post { id String @id @default(cuid()) title String content String? @db.LongText published Boolean @default(false) comments Comment[] @@map(name: "posts") } model Comment { id String @id @default(cuid()) content String? post Post? @relation(fields: [postId], references: [id]) postId String? published Boolean @default(false) } 然后就可以愉快的使用了Prisma orm进行操作数据库啦!其他细节可以查看Prisma文档! 关于 Nextjs持续集成和部署 Nextjs部署最佳方式也许就是Vercel了。但是各大云服务平台也可以,包括AWS在内对于Nextjs进行了优化,访问速度非常不错。 如果是个人项目或者是付费团队项目可以直接使用vercel进行部署。如果是想使用自己的Action或者是自己的部署平台进行部署上传到vercel,以下提供一个action yaml仅供参考: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # VERCEL_ORG_ID就是vercel的组织ID,VERCEL_PROJECT_ID就是vercel的这个项目的ID,VERCEL_TOKEN就是Vercel的api token name: Production Deployment env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} on: push: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install Vercel CLI run: npm install --global vercel@canary - uses: pnpm/action-setup@v2 with: version: latest - uses: actions/setup-node@v3 with: node-version: 18 cache: 'pnpm' - name: Pull Vercel Environment Information run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - name: Build Project Artifacts run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} - name: Deploy Project Artifacts to Vercel run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} 限制pnpm工具 1 2 3 4 // pacakge.json "scripts": { "preinstall": "npx only-allow pnpm", } 关于Husky/lint-staged 配置在提交代码前进行代码检测是非常重要的! 1 2 # 安装husky和lint-staged pnpm add -D husky lint-staged pretty-quick 配置检测,在根目录下的.husky下需要配置命令,可以参考demo 1 2 3 4 5 6 7 // pacakge.json "scripts": { "prepare": "husky install", "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", "lint:pretty": "pretty-quick --staged", "lint": "pnpm lint:lint-staged && pnpm lint:pretty", } .husky目录下文件 lintstagedrc.js 1 2 3 4 5 6 7 8 module.exports = { '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'], '{!(package)*.json}': ['prettier --write--parser json'], 'package.json': ['prettier --write'], '*.vue': ['eslint --fix', 'prettier --write', 'stylelint --fix'], '*.{vue,css,scss,postcss,less}': ['stylelint --fix', 'prettier --write'], '*.md': ['prettier --write'] }; pre-commit 1 2 3 4 5 6 7 8 9 10 11 #!/bin/sh . "$(dirname "$0")/_/husky.sh" . "$(dirname "$0")/common.sh" [ -n "$CI" ] && exit 0 # Format and submit code according to lintstagedrc.js configuration npm run lint:lint-staged npm run lint:pretty common.sh 1 2 3 4 5 6 7 8 9 #!/bin/sh command_exists () { command -v "$1" >/dev/null 2>&1 } # Workaround for Windows 10, Git Bash and Pnpm if command_exists winpty && test -t 1; then exec < /dev/tty fi 关于配置commitlint检测 使用了@commitlint/cli和@commitlint/config-conventional,配置文件为:commitlint.config.js, 1 2 # 添加包以来 pnpm add -D @commitlint/cli @commitlint/config-conventional 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 module.exports = { ignores: [(commit) => commit.includes('init')], extends: ['@commitlint/config-conventional'], rules: { 'body-leading-blank': [2, 'always'], 'footer-leading-blank': [1, 'always'], 'header-max-length': [2, 'always', 108], 'subject-empty': [2, 'never'], 'type-empty': [2, 'never'], 'type-enum': [ 2, 'always', [ 'feat', 'fix', 'perf', 'style', 'docs', 'test', 'refactor', 'build', 'ci', 'chore', 'revert', 'wip', 'workflow', 'types', 'release' ] ] } }; 然后结合husky配置进行检测,配置文件为commit-msg: 1 2 3 4 5 6 #!/bin/sh # shellcheck source=./_/husky.sh . "$(dirname "$0")/_/husky.sh" npx --no-install commitlint --edit "$1" 生成commit log文件 使用了conventional-changelog和@commitlint/cli然后使用生成log文件 1 2 3 4 // pacakge.json "scripts": { "log": "npx conventional-changelog --config ./node_modules/@commitlint/cli -i CHANGELOG.md -s -r 0" } 总结 好了,这次的总结就到此结束了。Nextjs在正式项目上,几乎毫无压力,工程化的沉淀也差不多就这样啦,具体的业务表现就自由发挥啦。 今天是2023年的1024~在此祝所有程序员节日快乐~我们的狂欢🎉!献给2023-1024的礼物! by: vadxq 2023.10.24 shanghai 参考文章: [1] https://www.joshwcomeau.com/react/server-components [2] https://nextjs.org/docs/app/building-your-application/rendering/server-components

2023/10/24
articleCard.readMore

【2022年终总结】此去经年,烟消雾散,晨曦微露

此去经年,烟消雾散,晨曦微露。 让痛与不安停在2022年,未来可期,2023年充满着希望与复苏。 前言 这一年,沉重而无奈的一年,诸多方面的不如意与痛苦覆压而过,却也锤炼着人们的意志。这一年的年末,随着全面放开,随着层层加码的破散,充满人间的希望滋润着你我。就让痛与不安停在2022年,未来可期,2023年充满着希望与复苏!一起奋斗奔向未来! 技术与学习 今年学习的东西不是很多,也未曾实现去年的期待,没有闯入元宇宙的世界。但庆幸的是闯入了元宇宙的一个基础设施Web3的世界。这一年学习了Web3及其一些经济价值知识。针对nextjs/react/tailwindcss和turbo进行了一些工程化的实践。但是却只是浅尝了threejs和WebGL。希望来年能更深入地研究这块领域。今年没有写什么文章,也就一篇对于钱包生成原理和防盗的研究稍微好一些。来年应该多一些文章输出。其他方面研究了midwayjs的hook,对于flutter3.0进行了工程化的适配。为开源贡献的vite插件也达到了周下载500+。这一年有遗憾也有惊喜,来年希望能真正走进元宇宙的世界,学习UE/Webgl/XR的知识。同时持续地学习Rust。 业务与工作 今年一共提交了1894commit。这一年也因为上海三四月的疫情而心中烦闷,从而想逃离上海,也向往远程工作。遂动了离职跳槽的心。人人里人与环境都很好,但甚想出去看看。在这里度过了初来上海的一年半,感谢同事们的携手相助和老板们的看重。此去经年,不知何时再见,愿保重。今年换了个环境,前期的远程工作确实让人很兴奋与激情满满,也每天努力地奋斗干活,同时不忘学习。但是当时针拨动到九月份的时候,一切都开始变化了。一切在往传统公司偏移,职责开始明确,确实是好事,但与自己所向往的一起努力干一些不一样的有意义的事却有所落差。也许业务的变化我没有跟上,也许是业务已经不需要我去努力做事了。当用力之后却打在厚厚的蓬松的棉花上,你无法知道力气轻重的反馈。业务与技术的向往,就好比现实与梦想的平行与相悖。 生活与美好 这一年,因为层层加码等原因,未曾能出去更多地相见与走动。但是每一次的相见,都无比珍惜与回忆。这一年,更多地行走在回忆的路上。走过了曾经走过的路,吃过曾经吃的美食,回忆与美好,相伴在短暂的时光里,现实定格在相机里。每一个场景,都是美好的记忆。这一年,尝遍酸甜苦辣,但坚信感动常伴,相信世间的美好,对未来也依旧充满信心。三年大疫已了,未来充满希望与活力。这一年,见证了梅西拿下大力神杯,封神球王!一切努力终将获得成功!梦幻般的结局,完美结局!希望来年能一起跨年,能一起游览大江南北,相伴于江湖。也希望来年能好好努力,赚更多的钱,能健健康康地活着。 结语 此去经年,烟消雾散,晨曦微露。一切皆美好! by vadxq 2022.12.31 上海

2022/12/31
articleCard.readMore

【1024献礼】生成可掌握钱包和揭秘NFT和token被盗案例以及如何预防被盗

最近圈内钱包被盗的事件频发,有人NFT被盗,有人token被盗了。其实大家都有自己的心理防备,只是有些新花招防不胜防呀!之前的一些被盗需要操作链上交易,这个时候需要交gas费,要交钱的地方每个人都会稍微迟疑一下,新的招式大部分就是签名就盗取了资产,很容易让人上当,导致NFT和Token资产被盗走了,损失了一大笔财产,心疼不已呀!正值1024程序员节,就斗胆为大家献上一份近期案例的分析和一个生成完全掌握且安全的使用hd钱包的方法。 前提概要 区块链:是一种安全共享的去中心化的数据账本。区块链技术支持一组特定的参与方共享数据。具有去中心化,不可纂改的特性。 钱包:存储以比特币为首的密码货币的公钥与私钥、私钥所对应的地址、该地址(群)的货币结算,以及货币交易的支持系统。说白了就是一个地址id,这个id记录了你的交易信息,你的钱,你的资产,只能你自己拥有对他的更改操作权限。 Gas费:是用于测量在以太坊区块链上执行特定操作所需的成本。就相当于付小费别人才会给你记账。 Token:可以粗俗的理解为区块链的货币。 NFT:非同质化通证。NFT的重要特征是独特有唯一标识,两两不可互换,最小单位为1且不可分割。可以粗俗的理解为一张票。 被盗案例 一般黑客会Copy做出一个很像的网页,然后伪装一个公开账户比如微博或者推特,非常像,连域名都很像,很多仅有一个单词不一样,或者某个单词顺序换了一下。有相当一部分用户就被迷惑了。特别是盲签,只有一段加密后的字符串,没有其他信息可以判断。 目前两种资产容易被攻击者盗取:一种是NFT,一种是token。这两种资产被盗的原理也是不一样的: NFT被盗 通常我们购买NFT后,如果价值涨了,会在Opensea上挂出进行出售。当你在Opensea上架的时候,会要求你授权Opensea,让它拥有你的这个项目collection的NFT执行approve方法的权限。作为第三方交易平台,要求这个权限其实也还算正常阶段,毕竟需要帮你托管,也需要你交gas费用。但是这里出现了一个信任危机问题,Opensea的NFT交易协议Seaport是开放的,只要你可以拿到用户的签名信息,它就会基于是你签名的原则,从而导致任何人都可以通过调用这个协议来骗取你的签名,然后将你的NFT挂单0价格出售给了攻击者的账户。以此来实现迷惑用户,将用户NFT资产转走。 怎么预防? 其实这个我们多加一个心眼,就很好预防了。我们可以看下面两个图,分别是官网和钓鱼网站通过seaport协议唤起的签名。我已经画红框圈出来了,我们可以对比这两个有啥区别。最大的区别就是域名。会有提示是哪个网站唤起的签名。第二个域名明显不是Opensea的,完全可以断定是钓鱼的,看到这个你就要小心了,在你没有确认这个网站真实性的时候,千万不要点击sign签名!同时域名一定要看仔细,很多钓鱼网站会利用一字之差,或者换了个顺序来达到迷惑用户,所以一定仔细看! Token被盗 攻击者会利用 盲签(eth_sign) 这种方式,盗取用户的token资产。这种钓鱼方式具有很强的迷惑性,以前碰到的授权类钓鱼会展示出攻击者所要我们签名的数据且需要gas费用。但现在这种只有一段没有特征的字符串,一旦sign签名了,他就会将你的token转走,如下图: 那么它是怎么盗走我们的token的呢?首先攻击者通过小狐狸和第三方查询连上数据服务共同验证地址资产,这是所有钓鱼网站都会做的事情,分析你的所有资产。然后分析出我们拥有的token是攻击者想要的。其中调用了与JSON RPC直接交互的接口eth_call,然后再通过eth_sign盲签拿到了签名,然后就GG了。 那么他是怎么和JSON RPC交互的呢?我们可以回顾一下,交易是怎么进行的:假设我们使用小狐狸等钱包应用进行交易,在广播前,钱包会自动获取和计算出我们的转账对象to,金额value,还有附带数据data,以及 nonce、gasPrice、gasLimit 参数进行 RLP 编码,得到原始交易内容(rawTransaction)。而如果使用合约调用的话,那么转账对象to就是合约地址,data 为调用数据。随后对此内容进行 keccak256 哈希,得到一串 bytes32 的数据signData,这个signData就是我们就是所需要我们签名的数据了。如果我们这时候,对这个唤起的sign签名的话,那么就会发生危险的事情,小狐狸钱包会对这串数据签名后就会得到 r, s, v 值,然后用这三个值再与nonce/gasPrice/gasLimit/to/value/data 进行一次 RLP 编码,就可以得到签名后的原始交易内容了,这时候交易就广播出去了,基本上就是GG了。 如何防盗 其实目前这种接口已经不会有正规网站或者服务会用了。小狐狸官方解释是因为还有一些管理端需要使用到这个盲签接口。所以,我们只要对所有调用盲签接口都不可信即可。基本上不会有这样的服务了。而且小狐狸其实也会在签名的位置有红色提醒文字,看到这个就就可以点拒绝了。 HD钱包 助记词泄漏 还有一种钓鱼网站,会迷惑你交出你的助记词,从而导致整个钱包账户进入风险之中。那么我们对于自己的助记词一定一定不能告诉第三方!当然,我们也可以自己生成一个HD钱包,而且多生成几次,还可以生成一个稍微好看点的钱包地址!我就是一个为了一个稍微好看的钱包地址生成了好多遍的无聊人士。 钱包地址原理 钱包地址的原理,大家可以看参考文章[4]https://huangwenwei.com/blogs/bip32-bip39-and-hd-wallet去详细阅读,这里就只简单介绍一下: 钱包的组成: Address + Private key = Wallet 根据密钥之间是否有关联可把钱包分为两类:nondeterministic wallet 和 deterministic wallet nondeterministic wallet:密钥对之间没有关联 deterministic wallet: 密钥对由一个原始的种子主密钥推导而来。最常见的推导方式是树状层级推导 (hierarchical deterministic) 简称 HD 目前HD钱包是大多是比如imToken钱包应用等采用的。遵循BIP32/39协议, BIP32就是:为了避免管理一堆私钥的麻烦提出的分层推导方案。 根据BIP44协议让地址私钥管理更方便,扩展了对多币种的支持。 BIP0044指定了包含5个预定义树状层级的结构: 1 m / purpose' / coin' / account' / change / address_index m是固定的,。Purpose也是固定的,值为44(或者 0x8000002C)。Coin type这个代表的是币种,0代表比特币,1代表比特币测试链,60代表Ethereum。Account代表这个币的账户索引,从0开始。Change常量0用于外部(收款地址),常量1用于内部(也称为找零地址)。外部用于在钱包外可见的地址(例如,用于接收付款)。内部链用于在钱包外部不可见的地址,用于返回交易变更。(所以一般使用0)。address_index这就是地址索引,从0开始,代表生成第几个地址,官方建议,每个account下的address_index不要超过20。 助记词与HD 这是生成助记词的原理图:粗俗的描述就是通过生成一个128位的随机字符串和4位的随机数,然后得到132位的随机字符串,然后通过进行11位的拆分,得到12个区块,每个区块对应BIP协议词库的一个单词,然后得到一个12个单词的助记词。 然后通过助记词来生成也一个512位的种子seed字符串 再利用种子生成主地址的公私钥 通过bip44协议,可以通过父元素的信息来生成子私钥和子公钥: 实战生成钱包 这里只是简单的罗列一个eth生成的方式,更多token生成方式,比如BTC/SOLANA/中文助记词等可以查看以下代码库: https://github.com/vadxq/generate-hd-wallet 生成之后,我们就只用导入单个地址的密钥即可。即使这个钱包被盗,也不影响其他钱包,真正实现一个助记词管理多个地址。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 const bip39 = require('bip39'); const { hdkey } = require('ethereumjs-wallet'); const util = require('ethereumjs-util'); const args = process.argv.slice(2); console.log(args); // 1.mnemonic生成助记词.第一次使用的时候 // English mnemonic let mnemonic = bip39.generateMnemonic(); // 2.将助记词转成seed const getSeed = async () => { let seed = await bip39.mnemonicToSeed(mnemonic, args[0]); // 如果已经有了助记词,想复原地址,那么用这一行 // let seed = await bip39.mnemonicToSeed(args[1], args[0]); console.log('seed:' + util.bufferToHex(seed)); return seed; }; // 3.提取私钥,公钥,账户 const obtainAccount = async () => { let seed = await getSeed(); // 通过hdkey将seed生成HD Wallet let hdWallet = await hdkey.fromMasterSeed(seed); for (let i = 0; i < 5; i++) { // 生成钱包中在m/44'/60'/0'/0/i路径的keypair ethereum let key = await hdWallet.derivePath("m/44'/60'/0'/0/" + i); // 从keypair中获取私钥 console.log('私钥:' + util.bufferToHex(key._hdkey._privateKey)); // 从keypair中获取公钥 console.log('公钥:' + util.bufferToHex(key._hdkey._publicKey)); // 使用keypair中的公钥生成地址 let address = await util.pubToAddress(key._hdkey._publicKey, true); //编码地址 console.log('account', i + 1, '0x' + address.toString('hex')); console.log('- - - - - - - - - - - - - - - - - - - - - -'); } }; obtainAccount(); 彩蛋 哈喽!Buidler们!最近一直在酝酿一个开放区块链记录平台。想营造一种Buidl精神的氛围社区,可以让我们更好地认识一些有志之士,一起与志同道合者共同去Buidl未来!目前域名已注册,就叫buidl.wiki。希望能以wiki的方式去承载一同buidl世界的人与事。希望来年的1024节的时候,能让大伙体验上! By: vadxq 于2022.10.24 上海 参考文章 [1] https://mp.weixin.qq.com/s/gRwkEYsO5pQ2hY7Pp5CIvA [2] https://mp.weixin.qq.com/s/E-LSN5eYwWhCQOH46-XyNg [3] https://learnblockchain.cn/2018/09/28/hdwallet [4] https://huangwenwei.com/blogs/bip32-bip39-and-hd-wallet

2022/10/24
articleCard.readMore

【2021年终总结】刚好遇见你,留下百年的期许

这一年里 无论你赚了多少钱,经历了什么事,受过多少波折…… 但请记得:如果这一年你很健康!那便是最好的一年! 前言 2021年,相对稳定的一年,也是命运幸运的一年。虽也无特别的战果,但也算经历了一段不平的考验和磨砺 。这一年,刚好重新遇见你,也庆幸再次遇见你。未来的江湖虽路远,也能有人同去同归。 业务与工作 这一年的工作也都在一家公司呆着,没有前两年那么多色彩了,也许是老了点,不想动弹了,激情退散了,折腾的精力也不够了。这一年的业务和代码量也是有史以来最多的,下半年光公司代码平均每个月新增都有2-3万,11/12月涉及的改动都超过了10万行。业务方面覆盖了挺多toC的业务。工作兢兢业业,交代的任务也保质保量的完成。这一年,将vue3项目工程化带入到了公司项目里,有时候为了兼容折腾的头大,有时候为了适配老的测试环境和新的CI/CD而自愿加班加点,踩着坑一路走过来了。做了一些默默无闻的事,给公司的基建也做了一些贡献吧,虽然也许可能是错误的基建,可能也许就是个人觉得方便了一些吧,对于别人也许就是繁琐之事。还是老员工说的对,多做多错,少做少错,不做不错。但是前端又是一个发展如此快速飞猛的行业,不革新就会被淘汰,只是不想出去了,见识不到新世界,也不想一直躺平养老。世界这么大,这么多新鲜事物,总是忍不住好奇,想去瞧瞧。不甘于在这里沉寂下去,所以想着多折腾一下自己,也想给折腾一下公司的项目,稍微做一下额外的工作,也是为了感谢马老师和公司的看重,也感谢那些经常被我问天马行空的问题却不耐烦的同事们。 技术与学习 这一年是一个学习快速的时间,一个沉淀自己的一年。这一年都在为了工程化和构建方向去做了多多少少有一些事情吧,也许对于很多人来说都是微不足道。实践vite和vue3/react的工程化,因为业务需要,实践tarojs+vue3跨端小程序,刚开始官方没有适配快手插件,硬着头皮做了一个插件,后来官方也注意到了,也有计划更新迭代,商量了一下,还是用官方那个demo做后续迭代。后来也开发了一个vite插件,现在用户下载量周才百来个,也算对社区有所微弱贡献。今年稍微稍微入门了一下swift开发,了解了一下ios开发。对于flutter的工程化也做了一套自己的东西,但是目前没有开源出来,在自己玩。对于动画也开始琢磨起来了,研究了一下动画的实现,做了一个还不错的动画。今年一共贡献了2216个commit,第二年保持github全绿色状态,也希望是长期习惯。 生活与社交 这一年忙碌且变化很快,不知不觉,时间就这么过去了。这期间遇到了许多了,也和许多人没有了联系。熙熙攘攘,忙忙碌碌,不知何时许。在上海的这一年,有浪浪小黑等等家园的朋友,也偶尔和来上海的朋友们碰过面,非常感谢朋友们的照顾!这一年,重新遇见了一个人,一个非常重要的人。因为刚好遇见了她,留下的足迹才美丽,因为刚好遇见了她,留下了百年的期许。很庆幸,能再次相遇。与她一起的酸甜苦辣咸,样样味道,都足够留恋与回味。也非常感谢家人们的谅解。疫情期间,去哪儿都不方便,回家也不方便!感谢朋友们的陪伴,感谢家人们的支持与谅解,也感谢宝贝的陪伴与幸福! 未来展望 希望未来能为开源社区做更多的事,希望能尝试闯入元宇宙的世界。希望能花更多的时间去生活和陪家人希望能永远和她在一起,开开心心! by vadxq 2021.12.31 上海

2021/12/31
articleCard.readMore

【Swift】Swift学习计划与资料

2021年了,flutter玩过了,就想看看原生是怎么一回事,看看iOS怎么玩玩。仅仅是个人兴趣爱好,就想玩一玩。学习学习,技多不压身。也想横向扩展一下自己的知识面,对比不同语言的异同。 目前每天更新 学习与实战计划 day1-day7: 基础学习 day7-day14: 模拟出个视频App首页(主要训练首页一个页面,列表,绘制ui,连接接口数据) day14-day21: 详情播放页,我的页面(登录注册) day21-day30: webview打开h5,模仿更多细节和交互,重训练逻辑 上面的模仿不一定是一样功能,一模一样,就是模仿到那个味道就好了。主要是训练自己 学习资料推荐 零基础了解Swift和Xcode:视频,0基础推荐 地址:BBCo - iOS开发零基础教程 Swift Up讲解很详细,适合真0基础了解Swift开发是怎么一回事 讲解的知识点包括:基础知识,数组,字典,函数,类,协议这些。还有xcode怎么用的细节 缺点:后面视频收费了,看看就好,如果你觉得好可以购买,不想花钱就看下面列的资料 Stanford CS193p 2021: 斯坦福2021年SwiftUI课程 Youtube地址: bqu6BquVi2M B站地址:BV1fy4y1u7hX SwiftUI Example练习文字版教程 地址:SwiftUI by Example 学习进度 2021.12.09: 看完BBCo - iOS开发零基础教程 Swift教程 2021.12.10

2021/12/9
articleCard.readMore

【前端工程化】Vite关于Vue3/React项目工程化总结(献给2021-1024的礼物)

时间已经是2021年了,马上也就2022年了,现如今自己的关注点对于新知识的狂热程度也比当年稍减,更多的关注于该新玩意对于现有项目是否有提升的可能性。更多地关注于项目的工程化和稳定性。关于Vite的项目工程化实践也有快一年了,从Vue3刚发布的踩坑实践到现在几乎可以覆盖99.5的机型兼容.是时候在1024节,给大家献上一篇总结啦~ 当然也可以查看我之前在公司内部分享的ppt:vadxq的分享ppt小栈 技术栈 Vue3 工程化项目实践demo 编程语言:TypeScript 4.x 构建工具:Vite 2.x 前端框架:Vue 3.x 路由工具:Vue Router 4.x 状态管理:Vuex 4.x CSS:Sass + Postcss HTTP 工具:Axios MOCK: mockjs + vite plugins 移动端调试插件: vite-plugin-vconsole Git Hook 工具:Husky + Lint-staged 代码规范:EditorConfig + Prettier + ESLint 提交规范:Commitlint + changelog 项目细节介绍 .husky: 关于Git Hook工具的一些命令配置 .vscode: 关于vscode的一些配置,强制开启一些检测插件 build: 关于持续集成,构建和部署相关的node脚本 mocks: mock数据 public: 静态文件夹 src: 主要代码相关 .cz-config.js: 关于commit规范的配置 .env: 环境变量配置 .eslintrc/.eslintignore: eslint配置 .gitignore: 懂得都懂 .prettierignore/.prettierrc: 格式配置 .stylelintrc.json: 样式格式化配置 commitlint.config.js: commitlint配置 index.html: index.html文件 package.json postcss.config.js: postcss配置 tsconfig.json vite.config.ts: vite配置 先谈谈Vue和React项目内部的实践 Vue3 跑Vue3,其实很简单,在src目录配置main.ts和App.vue入口文件即可。可以参考Vue3官方文档,本文不做过多描述。 而在Vite的配置也很简单,只需要引入@vitejs/plugin-vue即可。配置就一句Vue()。是不是很方便?当然,今天讲的更多是工程化方面,这里简单提一句,可以参考demo项目vite-vue-prod-template。 React 跑React,也很简单,在src目录配置main.ts和App.tsx入口文件即可。而在Vite的配置也很简单,只需要引入@vitejs/plugin-react-refresh即可。配置就一句reactRefresh()。非常的方便。可以参考demo项目vite-react-starter。 谈谈两者的工程化 关于结构约定 在前端,其实是很自由化的,模块化的,无处不在。但是对于公司项目来说,这种自由化,会导致项目在人才更替的时候,出现难以维护的情况,所以每一个项目,都需要相互约定一个特定的共同认可的项目结构。通用的几乎不需要修改的配置等提取出来,放入初始化项目里。根据业务类型区分不同的文件夹,约定特定的业务在特定的文件结构里进行编码。比如apis,编写与服务端交互的调用api接口代码。routes则为前端路由相关,styles为公共样式文件,views为页面代码,components为组件等等,这些约定每个团队可能都不一样,这里可以大家自己参考demo项目,与团队成员约定结构,这一步也是很重要的哟~ 关于环境与构建相关 其实前端的部署很简单,将代码build出来即可。对于公司的项目来说,不可能只有两个环境:development和production。往往至少会有测试环境,甚至有多个测试环境和预生产环境。不同环境的打包也是不一样的,打包通过Vite的配置即可处理大部分情况,当然生产环境可能需要上传静态文件到OSS搭配CDN。这里需要根据自己公司的业务去做针对性处理。demo项目是做了在build下对项目上传到七牛CDN下的处理方式。实现细节可以参考build/build.ts 关于环境的处理 环境的处理其实只要设定mode即可 1 2 3 4 5 6 7 8 9 10 11 // 在package.json "scripts": { "dev": "vite --mode mock", "dev:alpha": "vite --mode alpha", "dev:test": "vite --mode test", "dev:test1": "vite --mode test1", "dev:grey": "vite --mode grey", "build:test": "vue-tsc --noEmit && vite build --mode test", "build:test1": "vue-tsc --noEmit && vite build --mode test1", "build": "vue-tsc --noEmit && vite build --mode prod" }, 关于Vite base的处理 这个是配置base,也就是index.html引入的.js/.css/静态文件等的前缀处理。比如index.html打包后引入app.bc8837848788.js,如果正式环境配置的是cdn情况下,或者是将静态文件放置在不同url赏,则可以在这里对不同情况进行处理。 1 2 3 4 5 6 7 8 9 10 base: mode === 'prod' ? `${cdnConfig.host}${projectBasePath}` : mode === 'test' ? baseConfig.publicPath + '/' : mode === 'test1' ? baseConfig.publicPath + '/' : mode === 'grey' ? baseConfig.publicPath + '/' : './', 关于部署与持续集成 这一块根据自家团队的情况自己处理啦。其实也就是几条命令,clone代码,安装node_modules,然后就是build,有上传oss/CDN的可以加一行上传命令,建议写在build下,最后就是将代码copy到服务器或者是serverless。 关于团队规范和git flow 团队规范是我很在意的一点。这一块针对性的做了很多的处理,包括常规js/ts代码规范的检测,还有css代码,以及文件格式的检测和commit规范的检测 关于Husky/lint-staged和commitlint 配置在提交代码前进行代码检测是非常重要的! 1 2 # 安装husky和lint-staged yarn add -D husky lint-staged 1 2 # 安装commitlint yarn add -D @commitlint/cli @commitlint/config-conventional commitizen conventional-changelog-cli cz-customizable 配置检测,在根目录下的.husky下需要配置命令,可以参考demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // pacakge.json "scripts": { "prepare": "husky install", "lint": "npx lint-staged" }, "husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", "pre-commit": "lint-staged" } }, "lint-staged": { "*.md": "prettier . -w", "*.{ts,tsx,js,vue}": "eslint . --fix", "*.{vue,css,scss,less,sass}": "stylelint --fix", "*": "prettier . -w -u" }, 关于配置提交commit格式的检测 1 2 3 4 5 6 7 8 9 // pacakge.json "config": { "commitizen": { "path": "./node_modules/cz-customizable" }, "cz-customizable": { "config": ".cz-config.js" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 // .cz-config.js // 配置可以参考demo的配置,有注释 module.exports = { // type 类型 types: [ { value: 'feat', name: 'feat: 新增产品功能' }, { value: 'fix', name: 'fix: 修复 bug' }, { value: 'upd', name: 'upd: update,更新某功能(不是 feat, 不是 fix' }, { value: 'docs', name: 'docs: 文档的变更' }, { value: 'release', name: 'release: 版本,打tag' }, { value: 'style', name: 'style: 不改变代码功能的变动(如删除空格、格式化、去掉末尾分号等)' }, { value: 'refactor', name: 'refactor: 重构代码。不包括 bug 修复、功能新增' }, { value: 'perf', name: 'perf: 性能优化' }, { value: 'test', name: 'test: 添加、修改测试用例' }, { value: 'build', name: 'build: 构建流程、外部依赖变更,比如升级 npm 包、修改 webpack 配置' }, { value: 'ci', name: 'ci: 修改了 CI 配置、脚本' }, { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改,不影响源文件、测试用例的其他操作' }, { value: 'revert', name: 'revert: 回滚 commit' } ], // scope 类型,针对 React 项目 scopes: [ // 如果选择 custom ,后面会让你再输入一个自定义的 scope , 也可以不设置此项, 把后面的 allowCustomScopes 设置为 true ['custom', '以下都不是?我要自定义,或者回车跳过此项'], ['components', '组件相关'], ['views', '页面相关'], ['utils', '工具函数相关'], ['apis', '接口对接相关'], ['layout', '调整layout和页面布局相关'], ['styles', '样式相关'], ['deps', '项目依赖'], ['other', '其他修改'] ].map(([value, description]) => { return { value, name: `${value.padEnd(30)} (${description})` }; }), // allowTicketNumber: false, // isTicketNumberRequired: false, // ticketNumberPrefix: 'TICKET-', // ticketNumberRegExp: '\\d{1,5}', // 可以设置 scope 的类型跟 type 的类型匹配项,例如: 'fix' /* scopeOverrides: { fix: [ { name: 'merge' }, { name: 'style' }, { name: 'e2eTest' }, { name: 'unitTest' } ] }, */ // 覆写提示的信息 messages: { type: '请确保你的提交遵循了原子提交规范!\n选择你要提交的类型:', scope: '\n选择一个 scope (可选):', // 选择 scope: custom 时会出下面的提示 customScope: '请输入自定义的 scope:', subject: '填写一个简短精炼的描述语句:\n', body: '添加一个更加详细的描述,可以附上新增功能的描述或 bug 链接、截图链接 (可选)。使用 "|" 换行:\n', breaking: '列举非兼容性重大的变更 (可选):\n', footer: '列举出所有变更的 ISSUES CLOSED (可选)。 例如.: #31, #34:\n', confirmCommit: '确认提交?' }, // 是否允许自定义填写 scope ,设置为 true ,会自动添加两个 scope 类型 [{ name: 'empty', value: false },{ name: 'custom', value: 'custom' }] // allowCustomScopes: true, allowBreakingChanges: ['feat', 'fix'], // skip any questions you want // skipQuestions: [], // subject 限制长度 subjectLimit: 100 // breaklineChar: '|', // 支持 body 和 footer // footerPrefix : 'ISSUES CLOSED:' // askForBreakingChangeFirst : true, }; 关于移动端调试Vconsole yarn add -D vconsole vite-plugin-vconsole 1 2 3 4 5 6 7 8 9 10 11 12 // vite配置,可以参考项目vite-plugin-vconsole的文档填写,兼容多环境情况 viteVConsole({ entry: resolve(__dirname, './src/main.ts'), localEnabled: true, enabled: command !== 'serve' && (mode === 'test' || mode === 'test1' || mode === 'alpha'), config: { maxLogNumber: 1000, theme: 'light' } }), 关于兼容性的处理 最难处理的其实就是proxy。 引入@vitejs/plugin-legacy即可,vite的配置如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 legacy({ // 不考虑ie的兼容性和老的vivo/锤子/荣耀等国产机型,则可以使用下面 // targets: ['defaults', 'not IE 11', '> 0.25%, not dead'], // 如果考虑上面说的兼容性问题,使用下面配置 targets: ['ie >= 11'], additionalLegacyPolyfills: ['regenerator-runtime/runtime'], // 下面可以根据情况选用 renderLegacyChunks: true, polyfills: [ 'es.symbol', 'es.array.filter', 'es.promise', 'es.promise.finally', 'es/map', 'es/set', 'es.array.for-each', 'es.object.define-properties', 'es.object.define-property', 'es.object.get-own-property-descriptor', 'es.object.get-own-property-descriptors', 'es.object.keys', 'es.object.to-string', 'web.dom-collections.for-each', 'esnext.global-this', 'esnext.string.match-all' ], modernPolyfills: ['es.promise.finally'] }) 关于mock的处理 引入vite-plugin-mock即可 1 2 3 4 5 6 7 8 9 10 11 viteMockServe({ mockPath: 'mocks', supportTs: true, localEnabled: command === 'serve' && mode === 'mock', prodEnabled: false // 这样可以控制关闭mock的时候不让mock打包到最终代码内 // injectCode: ` // import { setupProdMockServer } from './mockProdServer'; // setupProdMockServer(); // ` }), 关于日志收集 目前有阿里云的arms和自己搭建的sentry。 arms可以使用这个插件,根据不同环境去添加不同的arms。vite-plugin-arms. sentry直接使用sentry提供的插件即可。 总结 好了,这次的总结就到此结束了。Vite在正式项目上,几乎毫无压力,工程化的沉淀也差不多就这样啦,具体的业务表现就自由发挥啦。 今天是2021年的1024~在此祝所有程序员节日快乐~我们的狂欢🎉!献给2020-1024的礼物!

2021/10/24
articleCard.readMore

【图解算法】前端思维学习图解算法数据结构笔记(一)之算法复杂度

书名是《图解算法数据结构》,在力扣看到的,准备开始系统学习一下算法了。会用前端的语言和思维来学习学习~希望能获得一些见解。第一篇是关于算法复杂度的学习笔记记录。 算法复杂度 复杂度概念 书中介绍,算法复杂度旨在计算在输入数据量 N 的情况下,算法的时间使用和空间使用情况;体现算法运行使用的时间和空间随「数据大小N」而增大的速度 时间:假设各操作的运行时间为固定常数,统计算法运行的计算操作的数量,以代表算法运行所需时间 空间:统计在最差情况下,算法运行所需使用的”最大空间” 数据大小N:算法处理的输入数据量;根据不同算法,具有不同定义 排序算法:N 代表需要排序的元素数量 搜索算法:N 代表搜索范围的元素总数 理解:算法复杂度是在一定的输入数据量下,计算所需要的时间操作数量大小(计算操作数量)和空间的大小来体现复杂度量。 时间复杂度 概念:时间复杂度指输入数据大小为 N 时,算法运行所需花费的时间 注意的是,这里的时间是计算操作数量,和运行绝对时间呈正相关关系,并不相等。 时间复杂度具有最差、平均、最佳三种情况,分别使用O, Θ, Ω三种符号表示 1 2 3 4 5 6 7 8 9 10 11 12 13 // demo const find_num = (nums) => { for (const num in nums) { if (num === 7) return false } return false } // 最佳情况Ω(1): nums = [7, a, b, c, ...] ,即当数组首个数字为 77 时,无论 nums 有多少元素,线性查找的循环次数都为 11 次; // 最差情况O(N): nums = [a, b, c, ...] 且 nums 中所有数字都不为 77 ,此时线性查找会遍历整个数组,循环 N 次 // 平均情况Θ: 需要考虑输入数据的分布情况,计算所有数据情况下的平均时间复杂度;例如本题目,需要考虑数组长度、数组元素的取值范围等 常见的算法时间复杂度大小排序:O(1) < O(logN) < O(N) < O(NlogN) < O(N^2) < O(2^N) < O(N!) 常数O(1):不随输入数据大小 N 的变化而变化 线性O(N):循环运行次数与 N 大小呈线性关系 平方O(N^2):两层循环相互独立,都与 N 呈线性关系,因此总体与 N 呈平方关系 指数O(2^N):指数阶常出现于递归 阶乘O(N!):阶乘阶对应数学上常见的 “全排列” 。即给定 NN 个互不重复的元素,求其所有可能的排列方案。阶乘常使用递归实现,算法原理:第一层分裂出 N 个,第二层分裂出 N−1 个,…… ,直至到第 N 层时终止并回溯 对数O(logN):对数阶与指数阶相反,指数阶为 “每轮分裂出两倍的情况” ,而对数阶是 “每轮排除一半的情况” 线性对数O(NlogN):两层循环相互独立,第一层和第二层时间复杂度分别为 O(logN) 和 O(N) 个人理解 常数O(1):就是传入变量不影响计算,相当于一个未使用的变量。eslint下应该是遇不到的 线性O(N):与输入N线性关系,其中只有一次循环是和输入的N有关 平方O(N^2):两层互相独立的线性关系嵌套,两层嵌套循环,比如冒泡排序 指数O(2^N): 树形图,每个节点都有两个操作,节节往下,第一层祖父节点,2^0 ,第二层就是2^1 ,以此类推,最后就是 2^n-1 次方,加起来就是 2^n 阶乘O(N!):全排列,和上面反过来,第一次=层全部循环一遍,然后第二层操作剔除一个特征操作,以此类推 对数O(logN):和指数反过来,就是二分法,分治这样的算法基础,一分为二或者一分为多。 线性对数O(NlogN):两层循环相互独立,第一层和第二层时间复杂度分别为O(logN)和O(N) 空间复杂度 概念:空间复杂度指在输入数据大小为 N 时,算法运行所使用的暂存空间+输出空间的总体大小 输入空间: 存储输入数据所需的空间大小;输入数据 暂存空间: 算法运行过程中,存储所有中间变量和对象等数据所需的空间大小;包括数据空间``栈帧空间``指令空间 数据空间:各项变量使用的空间,包括:声明的常量、变量、动态数组、动态对象等使用的内存空间。 栈帧空间:程序调用函数是基于栈实现的,函数在调用期间,占用常量大小的栈帧空间,直至返回后释放。 指令空间:编译后,程序指令所使用的内存空间。 输出空间: 算法运行返回时,存储输出数据所需的空间大小;输出数据 最差情况:空间复杂度统计算法在 “最差情况” 下使用的空间大小,以体现算法运行所需预留的空间量,使用符号O表示,包含两种情况 最差输入数据:当 N≤10 时,数组 nums 的长度恒定为 10 ,空间复杂度为 O(10) = O(1);当 N > 10时,数组 nums 长度为 N ,空间复杂度为 O(N) ;因此,空间复杂度应为最差输入数据情况下的 O(N) 最差运行点:在执行nums = Array(10).fill(0)时,算法仅使用 O(1) 大小的空间;而当执行 nums = Array(n).fill(0)时,算法使用 O(N) 的空间;因此,空间复杂度应为最差运行点的 O(N) 常见的算法空间复杂度有:O(1) < O(logN) < O(N) < O(N^2) < O(2^N) 常数O(1):普通常量、变量、对象、元素数量与输入数据大小 N 无关的集合,皆使用常数大小的空间 线性O(logN):元素数量与 N 呈线性关系的任意类型集合(常见于一维数组、链表、哈希表等) 平方O(N):元素数量与 N 呈平方关系的任意类型集合(常见于矩阵),皆使用平方大小的空间 指数O(N^2):指数阶常见于二叉树、多叉树 对数O(2^N):对数阶常出现于分治算法的栈帧空间累计、数据类型转换等。如快速排序/数字转化字符串 时空权衡 对于算法的性能,需要从时间和空间的使用情况来综合评价。优良的算法应具备两个特性,即时间和空间复杂度皆较低。而实际上,对于某个算法问题,同时优化时间复杂度和空间复杂度是非常困难的。降低时间复杂度,往往是以提升空间复杂度为代价的,反之亦然。 由于当代计算机的内存充足,通常情况下,算法设计中一般会采取空间换时间的做法,即牺牲部分计算机存储空间,来提升算法的运行速度。 一些DEMO 时间复杂度相关的demo 1 2 3 4 5 6 7 8 9 10 11 const algorithm = (n) => { let count = 0; const a = 100; for (const i in n) { for (const j in a) { count += 1; } } return count; } // 时间复杂度是o(N),一直一层循环是与输入有关 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 冒泡排序 const bubbleSort = (nums) => { const n = nums.length; for (const i in n - 1) { for (const j in n - i - i) { if (nums[j] > nums[j + 1]) { cache = nums[j] nums[j] = nums[j + 1] nums[j + 1] = cache } } } return nums; } // 时间复杂度是O(N^2),第一层复杂的o(N), 第二层平均循环次数为N/2,复杂度也为o(N) 1 2 3 4 5 6 7 8 // 递归 const algorithm = (n) => { if (n <= 0) return 1 const a = algorithm(n - 1) const b = algorithm(n - 1) return a + b; } // 时间复杂度是O(2^N) 1 2 3 4 5 6 7 8 9 10 // 递归 const algorithm = (n) => { if (n <= 0) return 1 let count = 0 for (const i in n) { count = algorithm(n - 1) } return count; } // 时间复杂度是O(N!) 1 2 3 4 5 6 7 8 9 10 11 // 一分为二,二分查找 const algorithm = (n) => { let count = 0 let i = n while (i > 1) { i = i / 2 count += 1 } return count } // 时间复杂度是O(logN) 1 2 3 4 5 6 7 8 9 10 // 线性对数常出现在排序算法中,快速排序、归并排序、堆排序 const algorithm = (n) => { let count = 0 let i = n while (i > 1) { i = i / 2 for (const j in n) count += 1 } } // 时间复杂度是O(NlogN) 空间复杂度的相关demo 1 2 3 4 5 6 const child = () => 0 const parent = (n) => { for (const i in n) child() } // 空间复杂度o(1),因为栈帧空间被释放掉了,每次掉用child()后 1 2 3 4 5 6 // 递归调用 const parent = (n) => { if (n <= 1) return 1 return parent(n - 1) + 1 } // 空间复杂度o(N),因为递归调用,会存在N个未返回的函数,累计使用O(N)大小的栈帧空间 1 2 3 4 5 6 // 最差运行点 const algorithm = (n) => { const num = 5 // O(1) let nums = Array(10).fill(0) // O(1) if (n > 10) nums = Array(n).fill(0) // O(N) } 时空权衡的demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 // 两数之和 // 方法一:暴力枚举 var twoSum = function(nums, target) { const len = nums.length; for (let i = 0; i < len - 1; i++) { for (let j = i + 1; j < len; j++) { if (nums[i] + nums[j] === target) { return [i, j] } } } }; // 时间复杂度 O(N^2),空间复杂度 O(1);属于时间换空间,虽然仅使用常数大小的额外空间,但运行速度过慢。 // 方法二:辅助哈希表 const twoSum = function(nums, target) { const len = nums.length; // 使用哈希表存储当前值 const map = new Map(); for (let i = 0; i < len; i++) { map.set(nums[i], i); } for (let i = 0; i < len; i++) { const needNum = target - nums[i]; if (map.has(needNum) && i !== map.get(needNum)) { return [i, map.get(needNum)] } } } // 时间复杂度 O(N),空间复杂度 O(N);属于「空间换时间」,借助辅助哈希表 dic,通过保存数组元素值与索引的映射来提升算法运行效率,是本题的最佳解法。 // 方法三:哈希优化 const twoSum = function(nums, target) { const len = nums.length; const map = new Map(); for (let i = 0; i < len; i++) { const needNum = target - nums[i]; if (map.has(needNum) && i !== map.get(needNum)) { return [i, map.get(needNum)]; } // 边读边存 map.set(nums[i], i); } }

2021/9/22
articleCard.readMore

【三亚攻略】2021年三亚团建攻略

2021年!三月,团建,三亚。由于自由活动时间受团体活动限制,网上攻略只能取其精华去其糟粕。故形成此特此且定向攻略。欢迎诸君企业微信向我提出想要添加的添加分类和知识。我会逐一更新,此文只是将各种参考攻略合集,然后分析距离和时间的可行性供大家参考,然后诸君可以借此形成适合自己的攻略. 本攻略食用指南,左边目录可以跳转到相关内容。地图上标记了相关景点和住所,缩放可以查看景点距离住所的大概距离。 一、时间点 1.1-共同时间 3.27(周六) 中下午晚上跟团!13.30 团建 18.00 集体吃饭,亚龙湾1号小镇奥特莱斯 3.28(周日) 全天跟团!8.30 集合 9.30 码头 上岛,后自由活动 最后一班轮渡 17.30 15.00 码头集合 18:00酒店集合去亚龙湾吃饭 3.29(周一) 自由活动 1.2-个人时间(文章最后将根据各个航班时间给出推荐游玩路线) !!!注意!如果行李多行走慢的建议再提前点时间! A 航班号 3.26(周五) 10.00 前到机场 11:30-13:55 3.30(周二) 17:30 发车前往机场 20:25-23:00 B 航班号 3.26(周五) 19.40 前到机场 21:20-01:05 3.30(周二) 13:40 发车前往机场 8.05 航班 16:20-19:40 C 航班号 3.26(周五) 11.00 前到机场 12:30-16:05 3.30(周二) 5.20 发车去机场 08:05-11:20 D 航班号 3.26(周五) 16.45 前到机场 18:25-21:50 3.30(周二) 09:50 发车去机场 12:30-15:35 二、住宿地点与活动范围 2.1-住宿金茂三亚希尔顿酒店(地图亚龙湾) function initMap(){ createMap(); setMapEvent(); addMapControl(); addMapOverlay(); } function createMap(){ map = new BMap.Map("map"); map.centerAndZoom(new BMap.Point(109.637379,18.231077),16); } function setMapEvent(){ map.enableScrollWheelZoom(); map.enableKeyboard(); map.enableDragging(); map.enableDoubleClickZoom() } function addClickHandler(target,window){ target.addEventListener("click",function(){ target.openInfoWindow(window); }); } function addMapOverlay(){ var markers = [ {content:"金茂三亚希尔顿酒店",title:"金茂三亚希尔顿酒店",imageOffset: {width:0,height:3},position:{lat:18.231352,lng:109.635188}} ]; for(var index = 0; index --> --> 2.2-上岛在蜈支洲岛,另一个海湾,海棠区 三、景点篇 3.1-亚龙湾景点(距离最近) 包含:亚龙湾热带天堂森林公园 亚龙湾爱立方滨海乐园 亚龙湾国际玫瑰谷 擎天石 红霞石 八仙庙 飞跃雨林 滑索 雨林魔幻迷宫 泰裕仙 亚龙湾假日度假酒店-小星星儿童乐园 雅尚摄影 春光生态椰岛服 秀都魔幻城 小刀专业海钓潜水俱乐部 龙仔湾 三亚亚泽游艇俱乐部 桃花酿 喜麦蒂精酿鲜啤酒吧 三亚海生活 PADI 游艇潜水俱乐部 //创建和初始化地图函数: function initMap(){ createMap();//创建地图 setMapEvent();//设置地图事件 addMapControl();//向地图添加控件 addMapOverlay();//向地图添加覆盖物 } function createMap(){ map = new BMap.Map("maptwo"); map.centerAndZoom(new BMap.Point(109.651911,18.256751),13); } function setMapEvent(){ map.enableScrollWheelZoom(); map.enableKeyboard(); map.enableDragging(); map.enableDoubleClickZoom() } function addClickHandler(target,window){ target.addEventListener("click",function(){ target.openInfoWindow(window); }); } function addMapOverlay(){ var markers = [ {content:"金茂三亚希尔顿酒店",title:"金茂三亚希尔顿酒店",imageOffset: {width:0,height:3},position:{lat:18.231364,lng:109.635008}}, {content:"亚龙湾1号小镇奥特莱斯",title:"亚龙湾1号小镇奥特莱斯",imageOffset: {width:0,height:3},position:{lat:18.247878,lng:109.630657}}, {content:"亚龙湾热带天堂森林公园",title:"亚龙湾热带天堂森林公园",imageOffset: {width:0,height:3},position:{lat:18.259107,lng:109.655777}}, ]; for(var index = 0; index 3.1.1-亚龙湾看日出 距离,最近,住的地方便可看到 3.1.2-亚龙湾热带天堂森林公园 主要景点:兰花谷/过龙江索桥(《非诚勿扰 2》的拍摄地)/山顶 费用:门票+往返游览车票 158 元,买票一定要提前一个小时 时间:预计半天 距离:5.2km,打车 12min/18 元 3.1.3-商业街 推荐 亚龙湾1号小镇奥特莱斯 亚龙湾的新商圈,位于亚龙湾核心地段,各种大品牌折扣店,超市,儿童乐园,电影院,各类特色餐厅等,吃喝玩乐都在一起,重点是这里有个亚龙湾唯一的一个海鲜广场,在亚龙湾能吃到新鲜且性价比高的海鲜,也就只有这里了,大名鼎鼎的林姐香味海鲜在这里也设有分店。 亚龙湾1号小镇奥特莱斯>亚泰商业中心>百花谷 3.1.3-其他景点(如果有遗漏或者想要加上详细描述可以私我) 亚龙湾爱立方滨海乐园 亚龙湾国际玫瑰谷 擎天石 红霞石 八仙庙 飞跃雨林 滑索 雨林魔幻迷宫 泰裕仙 亚龙湾假日度假酒店-小星星儿童乐园 雅尚摄影 春光生态椰岛服 秀都魔幻城 小刀专业海钓潜水俱乐部 龙仔湾 三亚亚泽游艇俱乐部 桃花酿 喜麦蒂精酿鲜啤酒吧 三亚海生活 PADI 游艇潜水俱乐部 3.2-海棠湾景点(距离 32km,驾车 38 分钟,公交一个半小时左右) //创建和初始化地图函数: function initMap(){ createMap();//创建地图 setMapEvent();//设置地图事件 addMapControl();//向地图添加控件 addMapOverlay();//向地图添加覆盖物 } function createMap(){ map = new BMap.Map("mapfour"); map.centerAndZoom(new BMap.Point(109.741605,18.338015),12); } function setMapEvent(){ map.enableScrollWheelZoom(); map.enableKeyboard(); map.enableDragging(); map.enableDoubleClickZoom() } function addClickHandler(target,window){ target.addEventListener("click",function(){ target.openInfoWindow(window); }); } function addMapOverlay(){ var markers = [ {content:"金茂三亚希尔顿酒店",title:"金茂三亚希尔顿酒店",imageOffset: {width:0,height:3},position:{lat:18.231364,lng:109.635008}}, {content:"蜈支洲岛",title:"蜈支洲岛",imageOffset: {width:0,height:3},position:{lat:18.316394,lng:109.77286}}, {content:"三亚海棠湾免税店",title:"三亚海棠湾免税店",imageOffset: {width:0,height:3},position:{lat:18.377083,lng:109.757529}}, ]; for(var index = 0; index 3.2.1-蜈支洲岛景点(公司包车) 蜈支洲岛旅游风景区(6-8 小时) 情人桥(1 小时) 蜈支洲岛海滨浴场(1 小时) 观日岩(1 小时) (适合看日出,根据我们的上岛时间不推荐) 《私人订制》淡水泳池(2 小时) 观光车档位 120 元,环岛一周约 60 分钟,在观日岩、妈祖庙、夏季电瓶车候车区、海洋之星四个景点停靠。 150 元,环岛一周约 60 分钟,在观日岩、情人岛、妈祖庙、夏季电瓶车候车区、海洋之星五个景点停靠。 3.2.3-海南三亚海棠湾免税店—全球最大的免税店 详情见购物免税栏目,推荐时间是我们离开蜈支洲岛的时候去购买比较方便 3.2.4-林旺镇和藤桥镇 比较远啊…性价比不高 3.3-三亚湾景点(距离住的地方 40km 左右,驾车 56 分钟,公交 2 个半小时,打车约 140 元) 以下景点最好选择部分,因为距离较远 //创建和初始化地图函数: function initMap(){ createMap();//创建地图 setMapEvent();//设置地图事件 addMapControl();//向地图添加控件 addMapOverlay();//向地图添加覆盖物 } function createMap(){ map = new BMap.Map("mapdiyishichang"); map.centerAndZoom(new BMap.Point(109.515552,18.246336),12); } function setMapEvent(){ map.enableScrollWheelZoom(); map.enableKeyboard(); map.enableDragging(); map.enableDoubleClickZoom() } function addClickHandler(target,window){ target.addEventListener("click",function(){ target.openInfoWindow(window); }); } function addMapOverlay(){ var markers = [ {content:"金茂三亚希尔顿酒店",title:"金茂三亚希尔顿酒店",imageOffset: {width:0,height:3},position:{lat:18.231364,lng:109.635008}}, {content:"椰梦长廊",title:"椰梦长廊",imageOffset: {width:0,height:3},position:{lat:18.275231,lng:109.491132}}, {content:"鹿回头公园",title:"鹿回头公园",imageOffset: {width:0,height:3},position:{lat:18.231283,lng:109.510248}}, {content:"第一市场",title:"第一市场",imageOffset: {width:0,height:3},position:{lat:18.245203,lng:109.513899}}, {content:"三亚湾",title:"三亚湾",imageOffset: {width:0,height:3},position:{lat:18.293519,lng:109.443598}} ]; for(var index = 0; index 3.3.1-第一市场(夜市/林姐香味海鲜/四川小胡子海鲜店) 距离 19km,打车半小时,公交一个半小时。适合吃好多好多好吃的!!!!适合下午晚上来! 3.3.2-三亚湾沙滩(看日落!!!) 三亚湾椰梦长廊(门票 0/用时 1 小时) 三亚湾中段沙滩 3.3.3-鹿回头公园 登高望海、观看日出日落、俯瞰三亚市全景的佳处,是三亚夜景的最佳观赏点。 3.3.4-临春岭公园(根据咱们的行程,不推荐这) 三亚市内爬山的好去处,重点是没有缆车,全程只能徒步,免费景点。 3.3.5-凤凰岭 都是公园,地理位置较远,看个人选择 3.3.6-天涯海角 就是两块刻了天涯海角的石头… 3.3.7-西岛 距离三亚市区较近的一座小海岛,需要坐船,是三亚海上游玩项目最多,且性价比最高的海岛。 但是吧,时间不允许,蜈支洲岛就够了 3.3.8-南山寺 拥有 108 米世界上最高的三面观音像,景区很大,可游玩性很高,是礼佛的好去处。 3.3.9-千古情 晚上的演出很震撼,对海南许多传说轶事做了非常生动的艺术诠释,除了演出景区还有冰雪世界,动物世界,浪浪水公园 3.4-大东海(距离住的地方 20km 左右,驾车 530 分钟,公交 1 个半小时,打车约 65 元) 3.5-其他 呀诺达雨林文化旅游区 距离:35km,驾车 52 分钟,公交两个小时,不推荐…性价比低 附近包含景点: 梦幻谷 雨林谷 三道谷 HOLD 住爱电影主景地 桐亭 峡谷幽湖 千年夫妻榕 碧玉亭 八榕观景台 一柱亭 千年灵芝洞 鹦鹉表演广场 幸福天道 雨林花架观赏区 雨林登峰区 兰花溪景观区 船型屋景观区 鸟语花香景观区 吉祥平台 神龟石 荷花池 四、美食篇 4.1-海鲜 三亚的海鲜相当有名,来到三亚不试试这里的生猛海鲜那你的旅途将会是不完整的。在三亚若想吃上经济实惠的海鲜,自然是去声名远播的海鲜加工广场,比如第一市场、春园海鲜加工广场等。在这里你可以自己选购海鲜,然后找店加工就可以了。海鲜通常烹调方式有火锅、蒸、炒为主,只是收取相应的加工费。加工前都先询价,一般加工费是 100 左右,不会很贵 推荐店:林姐香味海鲜/梅姐川味海鲜连锁加工店(第一市场总店) 4.2-抱罗粉 抱罗粉是米粉的一种,颜色呈半透明状,软而韧,非常有嚼劲的。好吃的抱罗粉关键在于汤靓,把精心熬煮的猪骨或牛骨汤放入早已烫熟凉却的米粉,然后入炒花生米、炒芝麻仁、炒酸菜等配料。 推荐店:正宗抱罗粉店/祥姐抱罗粉(二十年老字号) 4.3-椰子鸡 因当地盛产椰子,椰子鸡也结合了当地人的智慧结晶。将子鸡切成小块放入精选的老椰子内,再加入适量的料酒、酱油等配料蒸煮,出品汤汁鲜香,鸡肉滑嫩。 推荐店:萌哒哒椰子鸡(情人桥店)/嗲嗲的椰子鸡(明珠广场店) 4.4-文昌鸡 为海南“四大名菜”之一,去当地必吃的菜肴。文昌鸡身材娇小、肉质鲜嫩,隔水清蒸后,待菜品凉后切成小块,配以当地酱料便可食用。 推荐店:萌哒哒椰子鸡(亚龙湾森林公园店)/琼菜王婚宴 4.5-椰子饭 将精选的海南优质糯米放入装着椰肉和椰汁的椰子内蒸煮,蒸熟后的椰肉与米饭相互融合,硬软相间,入口香甜可口,椰香浓郁。 推荐店:阿浪海鲜连锁加工(第一市场店)/7岁家庭餐厅(海棠湾林旺北风情小镇店) 4.6-冬瓜海螺汤 冬瓜海螺汤是当地必尝的特色菜之一。主要原料是冬瓜和海螺,汤清且甜。冬瓜性凉而味甘,加上海螺的鲜香,其营养丰富,能解渴消暑、去除水肿。 推荐店:王梅妹多客来海鲜加工店(友谊海鲜广场B栋11号店),这条推荐存疑,请自行查找 4.7-清补凉 清补凉多以糖水或老火汤的形式出现,具有健脾润肺的作用。清补凉食材丰富,如:花生、红豆、绿豆、通心粉、新鲜椰肉、芋头等,其口感层次丰富,美味至极。 推荐店:张猫猫的店(大东海店)/不仔客海鲜(第一市场总店) 4.8-和乐蟹 和乐蟹是海南四大名菜之一。其膏满肉肥,为其它青蟹罕见,特别脂膏金黄油亮,犹如咸鸭蛋黄,香味扑鼻。常见的制作方法是清蒸,蘸以姜醋配成的调料,原汁原味。 推荐店:阿浪海鲜连锁加工(第一市场店)/梅姐川味海鲜连锁加工店(第一市场总店) 4.9-东山羊 东山羊由于主产于海南万宁的东山岭,所以被当地人简称为“东山羊”。肉质鲜美有弹性,脂肪呈淡黄色,经传统方法烹饪后,肉嫩汤白,无膻味。 推荐店:琼菜王婚宴 4.10-各种热带水果 推荐去第一市场吃! 4.11-其他食物 四角豆/水果炒冰/加积鸭/南山素斋/陵水酸粉 五、特产(可邮寄亲朋好友类) 尽量在市区买,不要在景点 热带水果 珍珠、水晶、贝壳、手工纪念品 椰子食品–椰子糖/椰奶等 海南咖啡 果肉果干 海产干货 六、购物免税方面 6.1-免税政策 每人每年免税额度10万元 每人每次限购化妆品30件,手机电话4件,酒类不超过1500ml,其他不限件数,但必须一次性随身带离岛 取消单件8k元免税限额 据个人对比,apple产品和教育版产品差不多价格。 6.2-推荐去最大的免税店-海棠湾免税店 6.2.1-A区(进门右手边:主营彩妆、护肤和大牌奢侈品) 1-2楼 品牌:香奈儿、兰蔻、后、雪花秀、AHC、馥蕾诗、雅诗兰黛、迪奥、SK2、HR、资生堂、肌肤之钥、MAC、科颜氏等 3楼 名品折扣区、儿童乐园、烟酒、运动品牌、特产 6.2.2-B区(进门左手边,主营珠宝首饰、服饰、儿童玩具、食品) 1-2楼 国际化大腕品牌为主:万国、伯爵、宝格丽、蒂芙尼、TWC、普拉达、欧米伽、浪琴、天梭、阿玛尼等 3楼 餐饮区,快餐等 6.3-扫货建议 6.3.1-开门时间 免税店9点30分开门,建议您在9点前就到,在门口有序排队。上午中午下午人都很多,晚上8店之后人会逐渐变少(一点),22点30分准时关闭 6.3.2-提货地点 购物后提货单保管好,离岛时在机场或火车站凭身份证和提货单提货机场提货(安检后) 海口美兰:15号登机口旁 三亚凤凰:109号登机口对面 6.3.3-优惠 申请海南健康码时,可以领取两张50元代金券 进店先到服务台办好会员卡(免费),食品、日用10元积1分,手表、饰品50元积1分,化妆、衣服30元积1分,1积分=1元,购买商品积分可以直接抵现,结账前告诉店员用积分就好 名品折扣商品:橘标7折、绿标6折、紫标5折、蓝标4折 套盒优先、大额优先,因为积分第二笔有时候就已经够抵扣啦 6.3.4-购物时间/运输时间 购买时间:免税店不全在机场,需保证产品到机场的运输时间 现场线下 三亚凤凰机场登机: 离岛前6小时 其他机场登机: 离岛前24小时 app-线上 三亚凤凰机场登机: 离岛前24小时 其他机场登机: 离岛前40小时 七、必备物品 只是罗列出来,根据个人需要选择,除了团建用品和证件,不都是必须的 证件类:身份证、手机、充电器 团建用品:公司衣服/活动队马甲/电脑,以防改bug 防晒类型:防晒霜、太阳镜、帽子、晒后修复(芦荟胶、面膜、晒后修复霜等) 抗蚊虫:驱蚊水、抗过敏药、风油精 药物:防中暑感冒药、止泻药(以防海鲜吃坏了)、晕车药 沙滩潜水物品:沙滩鞋(花园洞洞鞋)、衣服、防水袋、耳塞、泳装、游泳眼镜 个人用品:纸巾、衣服 八、推荐自由行路线 列出了候选项,可以自己和朋友组合去玩。有时间错误的地方还请联系我更正! 注意事项:3.27中午下午晚上/3.28全天跟团哦!!!迟到发200红包呢!!! 8.1-A航班号 此航班时间比较充裕 3.26 下午/晚上 亚龙湾1号小镇奥特莱 第一市场 吃好吃的 免税店 3.27 上午 亚龙湾热带天堂森林公园 免税店 3.27 下午/晚上 团建和一起吃晚饭 亚龙湾1号小镇奥特莱 3.28 上午 蜈支洲岛相关景点 3.28 下午/晚上 15.00集合离岛 18.00乘车去亚龙湾吃集体晚饭 3.29 上午 亚龙湾热带天堂森林公园 免税店 三亚湾相关景点:椰梦长廊/鹿回头公园等 3.29 下午/晚上 三亚湾相关景点:椰梦长廊/沙滩看日落/鹿回头公园/第一市场 3.30 上午 亚龙湾热带天堂森林公园 亚龙湾1号小镇奥特莱 购买纪念品之类的 3.30 下午 亚龙湾1号小镇奥特莱 等待班车回家 8.2-B航班号 3.27 上午 亚龙湾热带天堂森林公园 免税店 3.27 下午/晚上 团建和一起吃晚饭 亚龙湾1号小镇奥特莱 3.28 上午 蜈支洲岛相关景点 3.28 下午/晚上 15.00集合离岛 18.00乘车去亚龙湾吃集体晚饭 3.29 上午 亚龙湾热带天堂森林公园 免税店 三亚湾相关景点:椰梦长廊/鹿回头公园等 3.29 下午/晚上 三亚湾相关景点:椰梦长廊/沙滩看日落/鹿回头公园/第一市场 3.30 上午 亚龙湾1号小镇奥特莱 亚龙湾热带天堂森林公园 3.30 下午 等班车回家 8.3-C航班号 3.26 晚上 亚龙湾1号小镇奥特莱 第一市场 吃好吃的 免税店 3.27 上午 亚龙湾热带天堂森林公园 免税店 3.27 下午/晚上 团建和一起吃晚饭 3.28 上午 蜈支洲岛相关景点 3.28 下午/晚上 15.00集合离岛 18.00乘车去亚龙湾吃集体晚饭 3.29 上午 亚龙湾热带天堂森林公园 免税店 三亚湾相关景点:椰梦长廊/鹿回头公园等 3.29 下午/晚上 三亚湾相关景点:椰梦长廊/沙滩看日落/鹿回头公园/第一市场 3.30 05:20 等班车回家 8.4-D航班号 3.27 上午 亚龙湾热带天堂森林公园 免税店 3.27 下午/晚上 团建和一起吃晚饭 亚龙湾1号小镇奥特莱 3.28 上午 蜈支洲岛相关景点 3.28 下午/晚上 15.00集合离岛 18.00乘车去亚龙湾吃集体晚饭 3.29 上午 亚龙湾热带天堂森林公园 免税店 三亚湾相关景点:椰梦长廊/鹿回头公园等 3.29 下午/晚上 三亚湾相关景点:椰梦长廊/沙滩看日落/鹿回头公园/第一市场 3.30 上午 亚龙湾1号小镇奥特莱 等班车回家 示例 我是C航班 根据上面的选项,我选择 3.26晚去亚龙湾1号小镇奥特莱吃晚饭,3.27上午去亚龙湾热带天堂森林公园,中午回来团建,吃完晚饭可以去亚龙湾1号小镇奥特莱逛逛,因为晚饭就在那里。3.28,跟团蜈支洲岛相关景点。3.29上午免税店,下午去三亚湾那边溜达一圈,傍晚看完日出去第一市场。差不多就结束了。其中有些可以因为时间太紧去掉。 注意事项:3.27中午下午晚上/3.28全天跟团哦!!!迟到发200红包呢!!! 后记 感谢黄TT大佬/陈Y大佬/大前端朋友们提供的资讯和建议~ 有时间错误的地方还请联系我更正!

2021/3/20
articleCard.readMore

【前端工具】前端工具函数合集

收集一些前端工具函数,整理成集,以便查询使用。随时更新:2021-03-03 正则匹配相关 手机号正则验证 匹配所有手机卡 ^(?:\+?86)?1(?:3\d{3}|5[^4\D]\d{2}|8\d{3}|7(?:[235-8]\d{2}|4(?:0\d|1[0-2]|9\d))|9[0-35-9]\d{2}|66\d{2})\d{6}$ 更多可以查看一组匹配中国大陆手机号码的正则表达式 将手机号中间四位隐藏为**** 1 2 3 4 const phone = 17600000000; const data = phone.toString().replace(/(\d{3})\d{4}(\d{4})/, "$1****$2"); console.log(data); // 176****0000 cookie 操作相关 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 /** * 设置cookie * @param {String} name cookie name * @param {String} val cookie value */ export const setCookie = (name, val) => { const date = new Date(); const value = val; // Set it expire in 7 days date.setTime(date.getTime() + 7 * 24 * 60 * 60 * 1000); // Set it document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; path=/"; }; /** * 获取cookie * @param {String} name cookie name */ export const getCookie = (name) => { const value = "; " + document.cookie; const parts = value.split("; " + name + "="); if (parts.length === 2) { const ppop = parts.pop(); if (ppop) { return ppop.split(";").shift(); } } }; /** * 删除cookie * @param {String} name cookie name */ export const deleteCookie = (name) => { const date = new Date(); // Set it expire in -1 days date.setTime(date.getTime() + -1 * 24 * 60 * 60 * 1000); // Set it document.cookie = name + "=; expires=" + date.toUTCString() + "; path=/"; }; 格式化时间 将时间戳变为个位数带 0 前缀 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * 格式化时间,间隔符为- * @param {Number} date 大小,单位时间戳13位 * @return {String} 格式化后的大小 2021-03-12 20:21:02 */ export const formatDate = (date) => { const da = new Date(date); const year = da.getFullYear(); const month = da.getMonth() + 1; const day = da.getDate(); const hours = da.getHours(); const mins = da.getMinutes(); const secs = da.getSeconds(); return `${year}-${month > 9 ? month : "0" + month}-${ day > 9 ? day : "0" + day } ${hours > 9 ? hours : "0" + hours}:${mins > 9 ? mins : "0" + mins}:${ secs > 9 ? secs : "0" + secs }`; }; 生成 UUID 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 /** * 生成uuid * @param {Number} len 生成的uuid长度 * @param {Number} radix 随机字符的长度选择 * @return {String} uuid */ export const UUID = (len, radix) => { const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split( "" ); const uuid = []; let i; radix = radix || chars.length; if (len) { // Compact form for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)]; } else { // rfc4122, version 4 form let r; // rfc4122 requires these characters uuid[8] = uuid[13] = uuid[18] = uuid[23] = "-"; uuid[14] = "4"; // Fill in random data. At i==19 set the high bits of clock sequence as // per rfc4122, sec. 4.1.5 for (i = 0; i < 36; i++) { if (!uuid[i]) { r = 0 | (Math.random() * 16); uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r]; } } } return uuid.join(""); }; type 类型判断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 export const isString = (e) => { //是否字符串 return Object.prototype.toString.call(e).slice(8, -1) === 'String' } export const isNumber = (e) => { //是否数字 return Object.prototype.toString.call(e).slice(8, -1) === 'Number' } export const isBoolean = (e) => { //是否boolean return Object.prototype.toString.call(e).slice(8, -1) === 'Boolean' } export const isFunction = (e) => { //是否函数 return Object.prototype.toString.call(e).slice(8, -1) === 'Function' } export const isNull = (e) => { //是否为null return Object.prototype.toString.call(e).slice(8, -1) === 'Null' } export const isUndefined = (e) => { //是否undefined return Object.prototype.toString.call(e).slice(8, -1) === 'Undefined' } export const isObj = (e) => { //是否对象 return Object.prototype.toString.call(e).slice(8, -1) === 'Object' } export const isArray = (e) => { //是否数组 return Object.prototype.toString.call(e).slice(8, -1) === 'Array' } export const isDate = (e) => { //是否时间 return Object.prototype.toString.call(e).slice(8, -1) === 'Date' } export const isRegExp = (e) => { //是否正则 return Object.prototype.toString.call(e).slice(8, -1) === 'RegExp' } export const isError = (e) => { //是否错误对象 return Object.prototype.toString.call(e).slice(8, -1) === 'Error' } export const isSymbol = (e) => { //是否Symbol函数 return Object.prototype.toString.call(e).slice(8, -1) === 'Symbol' } export const isPromise = (e) => { //是否Promise对象 return Object.prototype.toString.call(e).slice(8, -1) === 'Promise' } export const isSet = (e) => { //是否Set对象 return Object.prototype.toString.call(e).slice(8, -1) === 'Set' } random随机数 返回一定范围的整数随机数 1 2 // min, 最小的值,max最大的数,包含两者 export const randoms = (min, max) => Math.floor(min + Math.random() * ((max + 1) - min)) 去除空格 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * 去除空格 * @param {String} srt: 传入字符串 * @param {Number} type: 1-所有空格 2-前后空格 3-前空格 4-后空格 * @return {String} */ export const trim = (str, type = 1) => { switch (type) { case 1: return str.replace(/\s+/g, '') case 2: return str.replace(/(^\s*)|(\s*$)/g, '') case 3: return str.replace(/(^\s*)/g, '') case 4: return str.replace(/(\s*$)/g, '') default: return str } } 大小写处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 /** * @param {String} str: * @param {type} type: 1:首字母大写 2:首页母小写 3:大小写转换 4:全部大写 5:全部小写 * @return {String} */ export const changeCase = (str, type = 4) => { switch (type) { case 1: return str.replace(/\b\w+\b/g, (word) => return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase()) case 2: return str.replace(/\b\w+\b/g, (word) => word.substring(0, 1).toLowerCase() + word.substring(1).toUpperCase()) case 3: return str.split('').map((word) => { if (/[a-z]/.test(word)) { return word.toUpperCase() } else { return word.toLowerCase() } }).join('') case 4: return str.toUpperCase() case 5: return str.toLowerCase() default: return str } }

2021/3/3
articleCard.readMore

【2020年终总结】如果这一年你很健康,那便是最好的一年!

这一年里 无论你赚了多少钱,经历了什么事,受过多少波折…… 但请记得:如果这一年你很健康!那便是最好的一年! 前言 2020年,一个短暂而又漫长的一年,一个艰难而又有所成就的一年,一个令人难忘的一年,伤心与感动常在。年初席卷全国的疫情,充满阴霾的开头。庆幸中华民族努力抗疫,充满希望。2020年,疫情还未结束,这场疫情也将载入史册。人类必然战胜这场看不见硝烟的战争。 学习与开源 2020年了,一年多社会的磨练,从稚嫩与冲劲,变得认清现实与洒脱,始沉淀自己。上半年是一个沉淀基础的半年,通过接触国外高校学习的内容与练习,锻炼诸多的基础,结合MDN文档库与实践,半年后的成果个人觉得还是很丰硕!疫情反而让自己飘飘然的心更快沉静下来。下半年,对工程化与原理有更多的认知,2020年,更多的是在沉淀,不仅仅是纵向的沉淀,也有横向的沉淀。以前更多是个人的方向,而今对于团队,对于大型项目的全流程全环节有了更多自己的认知。这一年,对于新技术的关注度没有大学时候那么强烈,许是因为工作忙,许是和以前关注一下就没有以后的态度不一样了,而今会先琢磨一下实用性。比如WebAssembly,目前自己的技术流和瓶颈还很难需要处理高计算的事。今年遗憾之处是对于开源的贡献几乎可以忽略。曾想过很多,都半路夭折了。希望接下来能重新优化,精简一下,大道至简。今年一共贡献了920+个commit,第一年实现github每天都有提交记录!全绿色状态!希望未来继续保持! 工作与社交 2020年的工作历程也是一个很艰难而又很幸运的历程。无论去何处,都遇到了不错的团队。目前经历社会毒打也才一年半,锤炼技术,和身边的人一起进步,许是个很不错的选择。循循渐进,慢慢跳动。原公司的前端团队是很不错,团队氛围很好,都是很不错的人,基建也不错。基建的建设对于团队的效率提升带来的收益远比开发基建的人力消耗高。不管去哪,基建是一个很重要的东西。6月离职后,便边休养边去学校帮忙做了几个项目,顺带赚点伙食费。10月开始投递简历,基本都是投的不加班的公司。不加班是因为自己想多做些开源的事情,多学习提升一下自己,上一年的业务代码已经写的挺累了,想做一些不一样的事儿。来现在公司是个缘分,当时面试官,现在的leader给人的感觉和氛围很好,技术如何都无关系,技术靠自己,工作的环境更最重要的。现在呆了两个月了,也确实是还行。自己也有学习的时间,工作也很自由,把自己的事做好,对得起工作就行。来这里接触面还是很多的,JS,Flutter等都接了些小需求,熟悉了代码和流程。体验很好,重点是不加班! 个人生活 2020年确实比较的难以忘怀却又难以启齿,受到2019年的打击而郁郁到年后慢慢的走出来,而今已经觉得没什么好说的。这年家人和睦,事业波折但总体上还是上升,身体健健康康的,一切都很好。认识了很多新朋友,也走散了很多旧友,熙熙攘攘,江湖路远,能走到最后的又有几人?2月因为疫情在家远程办公,A了快一年的剑三又回去玩了。不想面对旧回忆,玩了个新服新号,遇见了个很好的师父,某红烧肉。三月底方面对过去,许是真的放下了。后来啊,感谢婉言和月总他们的陪伴,带我刷家具玩家园,也非常感谢江上酒馆的小伙伴们,遇见你们真的很好!很庆幸当年选择了这个帮会,也很幸运结识了你们,可惜我后来咸鱼了,现在又不玩了,号都没满级呢。在最后一个赛季前,做出了田螺大橙武,当年不小心承诺过的话,都成了现实。至此,剑三的所有玩法,几乎都体验过了,仿佛就像江湖行走,最后到了该田园生活的时光一样。后来A了很久,11月底才回去玩了。认识了黑鬼系列亲友,也认识了小秃珏和他的亲友们,曾师祖他们考完研也回来了。谢谢你们在剑三江湖的陪伴~ 过年同为老乡的可芹也在赣州过年,本来是打算赣州聚,可惜疫情,同一个市快递都到达不了对方那儿。离职和散伙进度小伙伴们的离别。而今也半年未见,甚是想念!暑假回校园回温了校园生活,也见到了十七子亦清明他们那伙人哈哈哈。也见到了李聪文浩他们未毕业的家园学习学妹们。一切都是那么好!8月,和谢某人日久生情,九月九日本以为能长长久久,而现实却总是令人遗憾,梦一个月的时间便被现实击碎,也许本身便是路人,只是这一段时间因为引入发生偏移,导致短暂的交集吧。所有的生死不离最终都江湖不见了。十月与基佬浪浪于上海再次相见了,整整一年未见!在上海和很多朋友都相见了!有朋友在的城市,待着总是不那么难受。12月冬至,家园传统冬至活动于北京相聚,见到了许多家园小伙伴!待会要去和基佬浪浪他们一起吃跨年饭!2020年,过去的都过去了,未来总不会太差。 未来展望 希望未来自己的能力有更大的提升,希望未来能做一些有意思的开源项目! 希望未来江湖路远,能有人同去同归! 愿岁月无波澜,愿余生无遗憾 by vadxq 2020.12.31 上海

2020/12/31
articleCard.readMore

【Flutter】参与别人flutter项目踩坑填坑记录

参与别人flutter项目踩坑填坑记录,零散报错以及经历的解决方法。初次接触企业级项目,有些配置之类的和自己玩的不太一样,还有些依赖,以及一些配置项的规范化,版本的规定等可能导致自己在运行中出现环境冲突的问题。 对应Android零散报错以及经历的解决方法 出现如下报错信息 1 2 .java使用或覆盖了已过时的 API。 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。 临时解决方案:在工程build.gradle里面找到allprojects加入如下代码: 1 2 3 4 5 gradle.projectsEvaluated { tasks.withType(JavaCompile) { options.compilerArgs << “-Xlint:unchecked” << “- Xlint:deprecation” } } 解决方法: 原因:TODO正在查找 解决方法:TODO正在查找 效果:无 发现时间:2020.11.12 更新时间:2020.11.12 Could not determine the dependencies of task 1 Could not determine the dependencies of task ':app:preDebugBuild 原因: 解决方法:运行flutter clean 效果:本次运行有效,可能有特殊适用性,待以后更多结果反馈 发现时间:2020.11.12 更新时间:2020.11.12 A problem occurred configuring root project ‘connectivity’ 1 2 3 4 5 FAILURE: Build failed with an exception. * What went wrong: A problem occurred configuring root project 'connectivity'. > SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable. 尝试解决方法: 运行flutter clean和flutter pub cache repair:效果,出现下一个问题情况 解决方案: 原因: 解决方法:运行flutter clean 效果:本次运行有效,可能有特殊适用性,待以后更多结果反馈 发现时间:2020.11.12 更新时间:2020.11.12 pub get failed (66 1 2 pub get failed (66; Could not decompress gz stream Connection closed while receiving data, path = '') 原因:运行了flutter clean和flutter pub cache repair,然后翻墙后导致的问题 解决方法:国情问题,你们懂得 效果:本次运行有效,可能有特殊适用性,待以后更多结果反馈 发现时间:2020.11.12 更新时间:2020.11.12 Error: ADB exited with exit code 1 1 2 3 4 5 6 7 Error: ADB exited with exit code 1 Performing Streamed Install adb: failed to install /Users/vadxq/xx/xxx/build/app/outputs/flutter-apk/app.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res=-113] Error launching application on Android SDK built for x86 64. 原因:所安装的SDK版本问题 解决方法:重新下一个对应的就好,Android Studio推荐的就好了 效果:本次运行有效,可能有特殊适用性,待以后更多结果反馈 发现时间:2020.11.12 更新时间:2020.11.12 FlutterLoader( 4962): Flutter initialization failed. couldn’t find “libflutter.so” 1 2 FlutterLoader( 4962): Flutter initialization failed. FlutterLoader( 4962): java.util.concurrent.ExecutionException: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/base.apk"],nativeLibraryDirectories=[/data/app/lib/arm, /data/app/base.apk!/lib/armeabi-v7a, /system/lib]]] couldn't find "libflutter.so" 原因:所安装的虚拟机的系统架构问题,x86架构和arm64架构 解决方法: 下载对应的架构虚拟机,如果没有就只能用真机 build.gradle添加配置 1 android { ... defaultConfig { .... ndk { abiFilters 'armeabi-v7a', 'x86' } } 效果:可行 发现时间:2020.11.12 更新时间:2020.11.12 对应IOS零散报错以及经历的解决方法 前提安装好环境,见【Flutter安装】2020年末flutter安装找坑排坑 Analyzing dependencies pod install 安装问题 1 2 3 4 Analyzing dependencies Inspecting targets to integrate Using `ARCHS` setting to build architectures of target `Pods-Runner`: (``) 1 2 # 开启终端翻墙,在终端窗口添加即可,不同的翻墙方式,端口不太一样,我这是v2ray export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890 原因:网络问题下载失败了 解决方法:开启终端翻墙 效果:可行,下载速度飙升,我的只花了半分钟就好了 发现时间:2020.11.12 更新时间:2020.11.13 Error connecting to the service protocol: failed to connect to 1 2 Error connecting to the service protocol: failed to connect to http://127.0.0.1:61773/xxxxx=/ 原因:前一步开启终端翻墙后,设置了代理 解决方法:将终端的代理关了,或者新开一下窗口 效果:可行 发现时间:2020.11.13 更新时间:2020.11.13 关于groot flutter项目初接触启动总结 首先是看sdk版本和jdk版本已经创建对应的虚拟机是否和项目的对应,下载avd要提前对应相应的sdk 其次查看是否出现项目设定了支持系统的架构问题 然后遇到问题再继续探查

2020/11/12
articleCard.readMore