注册
环信即时通讯云

环信即时通讯云

单聊、群聊、聊天室...
环信开发文档

环信开发文档

Demo体验

Demo体验

场景Demo,开箱即用
RTE开发者社区

RTE开发者社区

汇聚音视频领域技术干货,分享行业资讯
技术讨论区

技术讨论区

技术交流、答疑
资源下载

资源下载

收集了海量宝藏开发资源
iOS Library

iOS Library

不需要辛辛苦苦的去找轮子, 这里都有
Android Library

Android Library

不需要辛辛苦苦的去找轮子, 这里都有

程序员就得会偷懒,重写了一个electron小工具,解放美女运营老师!

web
前言 接前一篇美女运营天天找我改配置,给她写了个脚本,终于安静了 之前只是写了一个脚本,本地运行,通过读取文件流获取文件数据,格式化对应数据,运营老师也不会安装node,还是需要我去操作。现在我用electron生成一个桌面应用直接生成后复制json,去配置,...
继续阅读 »

前言


接前一篇美女运营天天找我改配置,给她写了个脚本,终于安静了


之前只是写了一个脚本,本地运行,通过读取文件流获取文件数据,格式化对应数据,运营老师也不会安装node,还是需要我去操作。现在我用electron生成一个桌面应用直接生成后复制json,去配置,全程不需要我参与了。


之前的脚本


const fs = require('fs')
const csv = require('csv-parser');

const csvfilePath = './xxx.csv';
const uidsfilePath = './uids.json';

const results = [];
let newarr = [];
let lineCount = 0;

fs.createReadStream(csvfilePath)
.pipe(csv({ headers: true }))
.on('data', (data) => {
results.push(data);
lineCount++;
})
.on('end',async () => {
console.log(results[0])
await format(results);
fs.writeFile(uidsfilePath, JSON.stringify(newarr), () => {
console.log('done')
})
});
const format = (results) => {
newarr = results.map(item => {
if(item._0 === 'key' || item._1 === 'value') {
return {}
}
return {
label: `${item._1}-${item._0}`,
value: item._1
}
})
}

electron


简介


Electron 是一个用于使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用程序的框架。它由 GitHub 开发,基于 Chromium 和 Node.js。这意味着开发者可以利用他们熟悉的 Web 开发技术来创建桌面应用。


优势



  • 跨平台开发

  • 快速开发迭代

  • 丰富的生态系统


架构与核心概念



  • 主进程和渲染进程:


主进程:主进程是整个 Electron 应用的核心,它负责创建和管理应用程序的窗口。主进程通过BrowserWindow模块来创建浏览器窗口,这个窗口就是用户看到的应用界面的载体。


渲染进程:渲染进程主要负责渲染应用的用户界面。每个BrowserWindow都有自己独立的渲染进程,它使用 Chromium 浏览器内核来解析 HTML 和 CSS 文件,执行 JavaScript 代码。



  • 进程间通信(IPC):
    由于 Electron 应用有主进程和渲染进程之分,进程间通信就显得尤为重要。Electron 提供了ipcMain(用于主进程)和ipcRenderer(用于渲染进程)模块来实现进程间的消息传递。


使用vue3和vite创建vue的项目然后引入electron


安装vite


npm create vite@latest electron-desktop-tool

安装 引入electron&插件


npm install -D electron // electron
npm install -D electron-builder //用于打包可安装exe程序和绿色版免安装exe程序
npm install -D electron-devtools-installer // 调试
npm install -D vite-plugin-electron // vite构建插件

创建主进程


在vue 同级src目录下,创建src-electron 文件夹 新建main.js


// src-electron/main.js
const { app, BrowserWindow } = require('electron')
const { join } = require('path')

// 屏蔽安全警告
// ectron Security Warning (Insecure Content-Security-Policy)
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'

// 创建浏览器窗口时,调用这个函数。
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
title: 'electron-vite',
// icon: join(__dirname, '../public/logo.ico'),
})

// win.loadURL('http://localhost:3000')
// development模式
if(process.env.VITE_DEV_SERVER_URL) {
win.loadURL(process.env.VITE_DEV_SERVER_URL)
// 开启调试台
win.webContents.openDevTools()
}else {
win.loadFile(join(__dirname, '../dist/index.html'))
}
}

// Electron 会在初始化后并准备
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})

配置插件入口


在vite.config.ts中配置vite-plugin-electron 插件入口


import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import electron from 'vite-plugin-electron'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
electron({
// 主进程入口文件
entry: './src-electron/main.js'
})
],
/*开发服务器选项*/
server: {
// 端口
port: 3000,
}
})

配置package.json


在package.json 新增入口文件 "main": "./src-electron/main.js",


原神启动 emmm electron启动


运行 npm run dev 启动项目


请在此添加图片描述


打包配置


首先配置一下打包的命令,在package.json "scripts"里面配置这个打包命令


  "scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"postinstall": "patch-package",
"electron:build": "vite build && electron-builder",
"pack32": "vite build && electron-builder --win --ia32",
"pack64": "vite build && electron-builder --win --x64"
},

同样package.json 需要添加打包配置


  "scripts": {
...
},
"build": {
"productName": "ElectronDeskTopTool",
"appId": "dyy.dongyuanwai",
"copyright": "dyy.dongyuanwai © 2024",
"compression": "maximum",
"asar": true,
"directories": {
"output": "release/"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": true,
"deleteAppDataOnUninstall": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "ElectronDeskTopTool"
},
"win": {
"icon": "./public/logo.ico",
"artifactName": "${productName}-v${version}-${platform}-setup.${ext}",
"target": [
{
"target": "nsis"
}
]
},
"mac": {
"icon": "./public/logo.ico",
"artifactName": "${productName}-v${version}-${platform}-setup.${ext}"
},
"linux": {
"icon": "./public/logo.ico",
"artifactName": "${productName}-v${version}-${platform}-setup.${ext}"
}
},

然后npm run electron:build


请在此添加图片描述


页面效果


请在此添加图片描述


github地址


后续还会继续更新~


作者:一起重学前端
来源:juejin.cn/post/7445289957893259327
收起阅读 »

悲惨!刚入职没几天,无意间把数据库删了,很尴尬,原因很奇葩

1. offer收割机,就职新公司 5年前的就业环境非常好,当时面试了很多家公司,收到了很多 offer。最终我决定入职一家互联网教育公司,新的公司福利非常好,各种零食随便吃,据说还能正点下班,一切都超出我的期望,“可算让我找着神仙公司了”,我的心里一阵窃喜。...
继续阅读 »

1. offer收割机,就职新公司


5年前的就业环境非常好,当时面试了很多家公司,收到了很多 offer。最终我决定入职一家互联网教育公司,新的公司福利非常好,各种零食随便吃,据说还能正点下班,一切都超出我的期望,“可算让我找着神仙公司了”,我的心里一阵窃喜。


在熟悉环境之后,我趁着上厕所的时候,顺便去旁边的零食摊挑了点零食。接下来的一天里,我专注地配置开发环境、阅读新人文档,当然我也不忘兼顾手边的零食。


入职几天后 ,领导给安排了一个小需求,我和同事沟通完技术方案后,就开始开发了。


2. 单元测试有点奇怪


完成开发后,我决定写个单元测试验证下,在研究单元测试代码后,我发现这种单测写法和我之前的写法不太一样。


这家公司的单测好像没有启动整个项目,仅加载了部分类,而且不能访问测试环境数据库~ 于是我决定按照前东家写单测的方式重新写单元测试。


于是我新增了一个单测基类,在单测中启动整个SpringBoot,直接访问测试环境数据库。然而也并不是很顺利,启动阶段总是会遇到各种异常报错,需要一个一个排查…… 所幸项目排期不紧张,还有充足时间。


我做梦也没有想到,此刻,已经铸成大错。


3. 故障现场


我身边的工位旁慢慢地聚集了越来越多的人,本来我还在安安静静的调试单元测试,注意力不自觉的被吸引了过去。


“测试环境为什么这么多异常,访问不通啊。到处都是 500 报错”,不知道谁在说话。


“嗯,我们还在排查,稍等一下”,我旁边的同事一边认真排查日志,一边轻声回复道。


“为什么数据库报的异常是, 查不到数据呢?” ,同事在小声嘀咕,然后打开 命令行,立即登上 MySQL。


我亲眼看着他在操作,奇怪的是数据库表里的数据全部被删掉了,其他的几个表数据也都被删除了。


简直太奇怪了,此刻的我还处于吃瓜心态。


有一个瞬间我在考虑,是否和我执行的单元测试有关系? 但我很快就否决掉了这个想法,因为我只是在调试单元测试,我没有删数据库啊,单测里也不可能删库啊。 我还在笑话自己 胡思乱想……


很快 DBA 就抱着电脑过来,指着电脑说,你们看这些日志,确实有人把这些表删除了。


"有 IP 吗,定位下是谁删除的, 另外线上环境有问题吗?”,旁边的大组长过来和 DBA 说。


“嗯,我找到ip 了,我找运维看下,这个ip是谁的”。DBA 回复道。


4. 庭审现场


当 DBA 找到我的时候,我感到无辜和无助,我懵逼了,我寻思我啥也没干啊,我怎么可能删库呢。 (他们知道我刚入职,我现在怀疑:那一刻他们可能会怀疑 我是友商派过来的卧底、间谍,执行删库的秘密任务)


经过一系列的掰扯和分析,最终定位 确实是我新增的单元测试把数据库删了。


5. 故障原因


需要明确的是,原单元测试执行时不会删除数据库;测试环境启动时也不会删除数据库。


只要在单元测试中连接测试数据库,就会删除掉数据库的所有数据。为什么呢?


5.1 为什么单元测试删除了所有数据?


原单元测试 使用的是 H2 内存数据库,即Java 开发的嵌入式(内存级别)数据库,它本身只是一个类库,也就是只有一个 jar 文件,可以直接嵌入到项目中。H2数据库又被称为内存数据库,因为它支持在内存中创建数据库和表。所以如果我们使用H2数据库的内存模式,那么我们创建的数据库和表都只是保存在内存中,一旦应用重启,那么内存中的数据库和表就不存在了。 所以非常适合用来做单元测试。


H2 数据库在启动阶段,需要执行用户指定的 SQL 脚本,脚本中一般包含表创建语句,用来构建需要使用的表。


但是我司的 SQL 脚本除了创建表语句,还包含了删除表语句。即在创建表之前先删除表。 为什么呢? 据他们说,是因为这个 SQL 脚本可能会重复执行,当重复执行时创建表语句 会报错。所以他们在创建表之前,先尝试删除表。这样确保 SQL 脚本可重复执行。( 其实可以用 Create if not exists )


故障的原因就是:测试数据库执行了这个删表再建表的 SQL 脚本,导致所有数据都被清除了。


5.2 为什么测试数据库会执行这条 SQL 脚本呢?


1) 我新建的单元测试把H2 内存数据库换成了测试数据库。


2) spring.data.initialize=默认值为 true; 默认情况下,会自动执行 sql 脚本。


所以测试数据库 执行了 SQL 脚本。


5.3 为什么在测试环境正常启动时,没有问题,不会删除所有数据呢?


只有单测引入测试数据库才会出问题,在测试环境正常启动项目是没问题的。


当编译项目时,测试目录下的文件、代码和正式代码编译后的结果不会放到一起。因为 SQL脚本被放在了 测试目录下, 所以正式代码在测试环境启动时,不会执行到这个 SQL脚本,自然不会有问题。


image.png


6. 深刻教训


最终数据被修复了,DBA有测试数据库的备份,然而快照并非实时的,不可避免地还是丢失了一部分数据。


所幸的是出问题的是测试环境,并非线上环境。 否则,我会不会被起诉,也未可知。


后续的改进措施包括



  1. 收回了数据库账户的部分权限,只有管理账户才可以修改数据库表结构。代码中执行 DML语句的账户不允许执行 DDL 语句。

  2. DBA 盘点测试数据库的快照能力,确保快照间隔足够短,另外新增一个调研课题:删库后如何快速恢复,参照下其他公司的方案。

  3. 所有的项目 spring.data.initialize 全部声明为 false。不自动执行 SQL 脚本

  4. SQL脚本一律不许出现 删除表的语句。SQL不能重复执行的问题,想其他办法解决。

  5. 另外的一个项目急需人手,把新来的那谁 调到其他项目上


这可能是程序员们在技术上越来越保守的原因……不经意的一个调整可能引发无法承受的滔天巨浪


作者:五阳
来源:juejin.cn/post/7412490391935893541
收起阅读 »

antd 对 ai 下手了!Vue 开发者表示羡慕!

web
前端开发者应该对 Ant Design 不陌生,特别是 React 开发者,antd 应该是组件库的标配了。 近年来随着 AI 的爆火,凡是想要接入 AI 的都想搞一套自己的 AI 交互界面。专注于 AI 场景组件库的开源项目倒不是很多见,近日 antd 宣布...
继续阅读 »


前端开发者应该对 Ant Design 不陌生,特别是 React 开发者,antd 应该是组件库的标配了。


近年来随着 AI 的爆火,凡是想要接入 AI 的都想搞一套自己的 AI 交互界面。专注于 AI 场景组件库的开源项目倒不是很多见,近日 antd 宣布推出 Ant Design X 1.0 🚀 ,这是一个基于 Ant Design 的全新 AGI 组件库,使用 React 构建 AI 驱动的用户交互变得更简单了,它可以无缝集成 AI 聊天组件和 API 服务,简化 AI 界面的开发流程。


该项目已在 Github 开源,拥有 1.6K Star!



看了网友的评论,看来大家还是需要的!当前的 Ant Design X 只支持 React 项目,看来 Vue 开发者要羡慕了...



ant-design-x 特性



  • 🌈 源自企业级 AI 产品的最佳实践:基于 RICH 交互范式,提供卓越的 AI 交互体验

  • 🧩 灵活多样的原子组件:覆盖绝大部分 AI 对话场景,助力快速构建个性化 AI 交互页面

  • ⚡ 开箱即用的模型对接能力:轻松对接符合 OpenAI 标准的模型推理服务

  • 🔄 高效管理对话数据流:提供好用的数据流管理功能,让开发更高效

  • 📦 丰富的样板间支持:提供多种模板,快速启动 LUI 应用开发

  • 🛡 TypeScript 全覆盖:采用 TypeScript 开发,提供完整类型支持,提升开发体验与可靠性

  • 🎨 深度主题定制能力:支持细粒度的样式调整,满足各种场景的个性化需求


支持组件


以下圈中的部分为 ant-design-x 支持的组件。可以看到主要都是基于 AI Chat 场景的组件设计。现在你可以基于这些组件自由组装搭建一个自己的 AI 界面。



ant-design-x 也提供了一个完整 AI Chat 的 Demo 演示,可以查看 Demo 的代码并直接使用。



更多组件详细内容可参考 组件文档


使用


以下命令安装 @ant-design/x 依赖。


注意,ant-design-x 是基于 Ant Design,因此还需要安装依赖 antd


yarn add antd @ant-design/x

import React from 'react';
import {
// 消息气泡
Bubble,
// 发送框
Sender,
} from '@ant-design/x';

const messages = [
{
content: 'Hello, Ant Design X!',
role: 'user',
},
];
const App = () => (
<div>
<Bubble.List items={messages} />
<Sender />
</div>

);

export default App;

Ant Design X 前生 ProChat


不知道有没有小伙伴们使用过 ProChat,这个库后面的维护可能会有些不确定性,其维护者表示 “24 年下半年后就没有更多精力来维护这个项目了,Github 上的 Issue 存留了很多,这边只能尽量把一些恶性 Bug 修复



如上所示,也回答了其和 Ant Design X 的关系:ProChat 是 x 的前生,新用户请直接使用 x,老用户也请尽快迁移到 x


感兴趣的朋友们可以去试试哦!


作者:五月君
来源:juejin.cn/post/7444878635717443595
收起阅读 »

30岁,我开启了人生副业

我有工作,我还在写代码,大家别再重复问了😭😭😭 大家好,我是朽木白,一名非常普通的前端程序员,在前端这条路上淌七年的浑水,毫无建树。在互联网泡沫的末法时代,不得已换了一份工作,自降6k去了一家教育公司,由于个人原因,在今年的三月份我决定辞职。当时辞职后的第一...
继续阅读 »

我有工作,我还在写代码,大家别再重复问了😭😭😭



大家好,我是朽木白,一名非常普通的前端程序员,在前端这条路上淌七年的浑水,毫无建树。在互联网泡沫的末法时代,不得已换了一份工作,自降6k去了一家教育公司,由于个人原因,在今年的三月份我决定辞职。当时辞职后的第一想法是再换个公司继续干前端,正好是金三银四,可能行情好一点。很显然,现实给了我狠狠的一个大逼兜。在我辞职之前,我粉丝群的500个道友们都劝我苟住,不要任性,我没听劝,非要看看怎么个事。准备好了面试题,写好了简历,开始投递,结果一投一个不吱声。要么是未读,要么是已读不回,要么是不合适(岗位是资深、专家、架构师),唯一回复的都是外包,薪资只有十几k,心高气傲的我也是一口回绝。找了半个月,心气彻底凉了。这时候我想着打工已经不是很好的选择了,心里落差很大,于是决定创业。


在之前,我也在创业的路上进行过尝试。主要是面试辅导。在职的时候我就给粉丝修改简历,辅导面试,因此也收获了很多粉丝,大家给面子,都亲切的叫一声白哥。至于现在为什么放弃了呢?当时白天上班,抽空给粉丝看看简历,晚上回家腾讯会议一对一的指导如何修改简历,并且一直都是免费的。后面我又在某音开直播,在直播间连线粉丝,有任何问题都可以问的那种。还有一个收费的就是面试辅导,针对每个人不同的情况进行辅导,给他们指导项目,修改简历,每天晚上讲解面试题,划重点,面试复盘等等。如果不在公司加班(加班一般都是11点才能下班,到家都12点多了),晚上回家的任务就是看简历,辅导学员。因为当时全部都是我自己一个人干,没有任何团队,也没有和任何人合作过,也没有接过任何第三方广告。干了一个月,我遭不住了,身心疲惫。除了收获了一批忠实的粉丝,钱基本没有挣到多少。这时候我已经陷入了一种困境和迷茫的状态。我到底要不要继续坚持下去,我的这种付出到底能给我带来啥,更多的是给我带来的一些焦虑和内耗,尤其是一些粉丝找工作的现状,惨不忍睹,我看着也跟着心急。后来我就索性不干了,在群里活跃气氛,跟大家扯扯皮,聊聊八卦(我摆烂了),创业宣告失败。


接下来就是第二次创业了,在失业的这两月当中,我合伙跟别人开了一家眼镜店,目前已经正常运营。因为我媳妇是内行,所以开起来非常的顺利。我的身份也从一名前端程序员转变成了一名眼镜从业人员。跨度非常大,因为此前我从未接触过这个行业。简单讲讲开店的过程吧。涉及到金额方面的我就不提了。


第一选开店的地址,在北京的这四九城里转悠了一周多,看了好多地方,地段有好有坏,房租有高有低。我选择店铺的依据根据这几个方面来,交通方便,周围上班族、学校多,人流大,周围2KM没有同行,租金一万左右的底商为主。最终我选择了靠近霍营地铁站的一个商铺,距离不过500m。


image.png


第二店铺装修,找熟人,省了不少钱。装修也是要考虑很多,眼镜柜的各种尺寸要测量标准,因为上面要加装玻璃柜,严丝合缝,不能有偏差。还有柜台的设计,各个机器的摆放要合理的腾出位置,因为店铺的面积不是很大,空间要合理利用起来。还有其他的一些柜子,眼镜的摆放等等都是有学问的,没有经验的人来根本干不了一点。
这是装修完完刚收拾的样子


image.png
这是柜台


image.png


第三进货,货主要有三种,镜片,镜框,仪器。这些都是在潘家园眼镜城那边有厂家可以拿货。因为我媳妇之前是开店的,她有认识的人脉资源,进货的时候都是她带着去挑。进货花钱是大头,尤其是那些机器,更先进了,也更贵了。机器会有专门的安装人员上门安装调试。眼镜摆好了,机器调试好了,差不多就可以开业,我的店是05.17正式开业的,在没有开业之前就已经有人来配眼镜了,所以我开业提前了几天。
开业了


image.png


现在开了差不多半个多月了,差不多每天都能开张,时不时也能来个大单,虽然没有我上班每个月挣的多了,但是没有那么累了,因为眼镜店一天人流量没那么大,一天能进店五六个人就够了,所以我有很多时间干自己想干的事情,比如拍拍短视频,写写技术文章,学学自己之前没有来得及学的技术,我依然热爱代码。当然开眼镜店最重要的是为顾客配一副戴着舒适的眼镜,人家才会信任你,帮你带新,回购,因为眼镜这个东西很多人一旦配了一次很舒服的,下次就认准你了,别的店他不信任。


目前生活状态:已经入职新公司,还是继续写代码,不加班,不打卡,没有倒排期,没有PUA,按时发工资。店铺一切经营正常。希望我的这个店能一直开下去,红红火火。


最近好多不在北京的粉丝反馈,让我做个小程序,可以直接查看店里的眼镜,他们可以挑选心仪的镜框,方便线上配镜。目前小程序正在开发中,大家敬请期待!


作者:白哥学前端
来源:juejin.cn/post/7377001684159774760
收起阅读 »

什么黑科技?纯血鸿蒙又可以运行Android应用了!

背景 纯血鸿蒙OS Next系统最近出现了两款热门应用:出境易、卓易通,其功能是:让你在出境后可以方便安装到各种Android应用。 发生了什么? 「出境易」这款应用可以在纯血鸿蒙OS NEXT里直接安装&运行Android应用! 纯血鸿蒙官方声...
继续阅读 »


背景


纯血鸿蒙OS Next系统最近出现了两款热门应用:出境易、卓易通,其功能是:让你在出境后可以方便安装到各种Android应用




发生了什么?


「出境易」这款应用可以在纯血鸿蒙OS NEXT里直接安装&运行Android应用!



纯血鸿蒙官方声称:「不支持运行Android应用」!



于是:



  • 很多正在努力开发、兼容纯血鸿蒙应用的开发者都在议论:是不是可以停鸿蒙开发、继续写回Android了?

  • 也有很多粉丝后台私信Carson,问:目前继续学鸿蒙开发到底还有没意义






本文意图


今天Carson来带大家扒扒「出境易」、「卓易通」到底是怎么能在纯血鸿蒙OS NEXT里运行Android应用的。具体包括:「出境易」、「卓易通」这两款Android应用



  1. 在纯血鸿蒙系统里运行的底层支持是什么?

  2. 在纯血鸿蒙系统里的运行环境是什么?

  3. 「出境易」、「卓易通」本质是什么?

  4. 在「出境易」、「卓易通」上的Android应用性能、体验如何?




问题1:在纯血鸿蒙系统里运行的底层支持是什么?



  • 无论黑科技有多 “黑”,总需要底层给与相关支持才有运行的可能。

  • 在初次安装「出境易」、「卓易通」时需下载一个环境,抓包&解包可得到:



其中最为关键的文件:anco_hmos.img,从字面解释来看:



  • anco:AndroidCompatible = 安卓兼容

  • hmos:HarmonyOS = 鸿蒙OS系统

  • 整体看,即**「鸿蒙OS系统里的安卓兼容」**


实际上,这其实是一个安卓镜像文件,是一个嵌入到鸿蒙OS系统层面的安卓运行环境(类似虚拟机的作用,但实际不是虚拟机)。



其实是类似wsl技术,即Windows Subsystem for Linux = Windows的Linux子系统,能让开发者在Windows操作系统中直接运行Linux环境,而无需任何虚拟机。



所以,要在纯血鸿蒙OS 上安装「出境易」、「卓易通」不仅需要下载很大的安卓镜像资源包,还需要重启系统,因为「出境易」和 「卓易通」是单独的“运行环境”。


值得一提的是:



  • 因为本身鸿蒙OS内核就兼容了Linux ABI(应用程序二进制接口),即鸿蒙OS内核本身就可以运行为Linux设计的应用。

  • 所以,虽然这是一个安卓镜像,但这个属于鸿蒙的安卓镜像并没有包含Linux 内核,只是包含运行时(Runtime)部分。 以下是鸿蒙内核架构图:
    鸿蒙内核示意图




问题2:在纯血鸿蒙系统里的运行环境是什么?


那么,这类Android应用到底是运行在什么环境上的呢?打开「出境易」内的app后,通过执行shell ps -ef会出现以下进程:



  • 即其运行环境是:通过lxc-start命令启动了一个基于iSulad的容器的进程。

  • iSulad 是华为自研的容器引擎,是一个非常通用的容器引擎,具有轻、快、 易、灵的特点。以下是其架构图:



iSulad官网介绍:http://www.openeuler.org/zh/other/pr…






问题3:「出境易」、「卓易通」本质是什么?



二者的功能都是:让你在出境后可以方便安装到各种app。听起来是不是有点类似国内的应用商店



  • 实际上,二者在鸿蒙next商店下载的是一层壳,负责与纯血鸿蒙OS进行权限交互(图片、文件IO等)

  • 本体也是Android应用的apk,即出境易.apk、卓易通.apk。拿出来也是可以在Android手机上安装的。(如下图)


除此以外,「出境易」还含有一个「文件共享.apk」、「卓易通」还有一个「搜应用.apk」、「文件共享.apk」。


这里值得一提的是,两个“应用商店”可搜到的应用原理不同:



  • 出境易:白名单方式,即只有与其合作的Android应用可以安装;

  • 卓易通:黑名单方式,即只有纯血鸿蒙OS上架的应用不可以安装;


下面附上视频:纯血鸿蒙OS 「出境易」、「卓易通」安装Android应用实机演示



http://www.bilibili.com/video/BV1Q9…





性能如何?


既然能跑了,那么用户体验如何呢?网友们已经开始跑分了:



  • 测试环境:麒麟9000s;

  • 结论:单核心正常跑分1000,目前「出境易」是930分左右,效率是93%;




  • 分析:上面提到其底层支持是类似wsl的技术,同时运行环境是采用华为自研的iSulad 容器引擎的方式,并非所谓的虚拟机环境。这种嵌入方式可以使得安卓应用能够在鸿蒙系统上运行,但又不会占用过多的资源或影响系统的稳定性

  • 结合业界常见容器水平93%左右,华为的iSulad容器达到了业界水平,可理解为:GPU性能几乎无损。


但是对于内存使用就不太友好了,容器本身内存占用极大,基本一个容器进程就是8GB,随便开两个应用12GB就没了。

同时结合网上使用的评价:手机容易发烫(功耗高)、应用Bug较多等等,可以总结为:以这种方式在纯血鸿蒙OS上运行的Android应用 「能用」,但是「不好用」,与原生体验还是存在很大差距




结论



  • 技术角度 分析:基于anco_hmos,采用类似wsl的方式同时结合iSulad容器引擎,使得在纯血鸿蒙OS上运行Android应用成为了板上钉钉的现实

  • 性能角度 实践:在CPU性能可认为几乎无损的情况下,内存跟功耗问题短时间内还是无法解决;

  • 用户体验 观察:应用Bug较多,结合性能内存问题,目前暂时仅处于一个**「能用」**的状态。


基于上述分析 & 问题表现,在纯血鸿蒙OS上运行Android应用在国内大范围使用短时间内几乎不可能,更多的是在一些小众、边缘、尝试探索的场景,比如一些使用频率较低的小众app、尝试出海境外的场景(如本文提到的「出境易」等)


最后


如何看待这次在纯血鸿蒙OS上运行Android应用的事件呢?评论区留言你的看法!


参考文章:



作者:Carson带你学Android
来源:juejin.cn/post/7448576110823047202
收起阅读 »

我的 2024 年终总结

2024 年,我离开了待了两年的互联网公司,来到了一家聚焦教育机器人和激光切割机的公司,没错,是一家硬件公司,从未接触过的领域,但这还不是我今年最重要的里程碑事件 5 月份的时候,正式提出了离职,没有骑驴找马,完全裸辞。对于 gap 的这段时间,做了简单的规划...
继续阅读 »

2024 年,我离开了待了两年的互联网公司,来到了一家聚焦教育机器人和激光切割机的公司,没错,是一家硬件公司,从未接触过的领域,但这还不是我今年最重要的里程碑事件


5 月份的时候,正式提出了离职,没有骑驴找马,完全裸辞。对于 gap 的这段时间,做了简单的规划,先去旅游一趟,然后用一个半个月时间备考雅思,九月、十月重新找工作


随后的两个月,公司找到接替我的新同学,站好最好一班岗,跟同事做了告别。7 月 5 号,拿着离职证明,收拾好东西,离开了西丽。隔天就参加 VueConf 大会,见到了尤雨溪和 Anthony Fu




做了这么多年精神股东,终于来支持一回,希望 React 越办越好 👏



VueConf 结束之后,就去流放岭南,在广西桂林,体会到了什么叫做“江作青罗带,山如碧玉簪”



这里的山水真的很美,养得蚊子珠圆玉润,也感受到喀斯特地貌对于经济发展的阻碍,拔地而起的峭壁山峰,割裂了交通,住房和经济


在去到九马画山的偏方小路上,看到一路上种植着柚子,玉米等等,这是属于亚热带季风气候的回馈,光照充足,雨水充沛,水果种植,也成了当地人除旅游业之外的另一条生计


这一次是 p 人之旅,没有特种兵定点打卡,而是更多感受地理,人文。嗦了三天的桂林米粉之后,又回到了深圳,开启了雅思备考


关于考雅思,一个是本身对英语感兴趣,另一个是看过许多老前辈的经验之谈,学好英语对于程序员职业发展来说,是长期利好的事情


在这段时间,还学会做饭,个人以为,做饭和编程一样,都是属于创造的艺术


心血来潮还买了一个小米空气炸锅,第一次用觉得超级神奇的,仅仅依靠空气就能够把鸡米花烤得 tree tree 的



接下来的一个半月的时间,把自己当成高考生,按部就班地学习



8 月 30 号早上踏入考场,上午听力阅读写作,下午口语。成绩出得很快,三天后如期而至,得到了一个非常低的分数,这给了我当头一棒


付出了大量的时间精力,并不意味着有美好的收获,我花了三天时间做了痛苦而又深刻的复盘,是能力基础问题?是自律问题?是备考策略问题?是心态问题?最后结论是出来的是备考策略出现重大失误,罔顾实际情况,按照自己天真烂漫的想法去复习



复盘这几天我似乎有点“龙场悟道”,这个“道”后来我把它总结了「成长型思维」,关于成长型思维具象表现是,对于雅思考试的折戟失败,我没有消沉,在这段经历中,让我学会了第一件事情是——做人、做事需要符合物理、客观的规律,也就是雷军所说的「顺势而为


无业 gap 的时期,并不美好,每天处于没有收入、消耗积蓄的焦虑,所以到了 9 月份,我需要重新开始找工作,自媒体和独立开发被我 pass 掉了,这两者现下并不能带来稳定的收入


又回到了起点,这一次我反而更加自信,充分吸取教训,我需要做好正确的求职策略,搜集各路面试资料,结合自身的实际情况,并且在 AI 的辅助下,写了一份《前端求职大攻略》



把自己求职涉及到的方方面都罗列出来,同时使用 PDCJ 模型,P = Plan(计划),D = Do (执行),C = Check(检查),J = Just(调整),也就是阶段性地计划、施行,再不断地检查、调整,确保自己的面试正确而又高效地进行


就业环境并不会因为你认真做好求职攻略、努力复习就给到丰厚机会,相反,每天投递的数十份简历都石沉大海,一个星期可能都约不到面试机会,这大概就是铜九铁十了吧


求职遇冷,那就好好抓住每一个来之不易面试机会,比如大学舍友的朋友内推了的转转前端岗位,面试前两天,刷完了转转的前端面经,一遍一遍地背前端面试题宝典的八股文,甚至于上厕所,也会拿着小程序刷题



但是,转转还是把我挂了,二面都没进


沮丧是没有用的,迅速复盘转转的整场面试,重听了当时的录音,把自己回答不上的问题找补,犯错的点做具体的纠正,为下一次面试做好准备


不久,意外接到了童心制物 HR 的简历邀请,是一个 Nodejs 的岗位,我拒绝了,如实说目前仅接受前端岗位,HR 却跟我要了简历转发给他们前端部门,没想到还通过了简历筛选,得到了一面的机会


一面发挥的很好,其中问到了一个难题,“如果不用 eval,还有其他方式去执行 js 函数吗”,刚好之前研究过 laf 的源码,知道可以用 vm 模块可以实现。之后又是二面和 HR 面,都顺利通过了,拿到了最终的 offer


童心制物涉及教育方向,这也是我很看重的一个点,大学的时候参加过支教,一直对教育这一块感兴趣,另外,这家公司给的薪资和福利都比较合理,所以……我入职啦



求职告一段落,顺便写了一篇博客作为输出,感兴趣可以查看——《前端开发实用的面试备考分享(内含资料 + 内推)》


这一段求职经历,总体上算是成功的,找到了理想的公司,理想的职位,但人不可能躺在功劳簿上过一辈子,新阶段会有新挑战,需要重新改变,重新努力


入职之后,体验了公司的激光设备,感受到了巨大的创造力:



用 F1 Ultra 雕刻的金属卡片,用 P2S 切割+雕刻的木板:



用 M1 Ultra 和热压机做的工服:



加入了心心念念的篮球社,开启了每周打篮球的固定活动,没想到老板也是篮球迷,现在每周都会一起打,看着他能明显感受身上的年轻、务实,身边的同事也是对他好评,我相信这些称赞不是人情世故


公司没有设立职称体系,转而实行扁平化管理,此举弱化了上下级概念。此外,公司还推行 AMA(Ask Me Anything,即问我任何事)活动,所有员工均可匿名提出任意问题,老板会逐一进行解答。可以看到,公司正在着力打造更为平等、透明、高效的职场环境,这也促使我对自身与企业之间的关系展开深入思考。


思考的结果为:作为员工,其行为与目标应当与公司的发展相契合。基于这一思考,我察觉到自己过往存在一些不良习惯,有时为了维护同事关系,或是顾及上下级的地位差异,对问题视而不见,对待工作打折妥协。


知行合一,慢慢将思考转化为行动,比如与同事协作后,即时给予反馈,对于公司内部平台出现的 bug,立即同步给相应负责人,尽量做到事事有反馈、发挥 owner 精神等等


童心制物很开放,有很多的活动可以参加,比如 Factory Day 工厂日,在周四那天乘着大巴,去到惠州工厂,参观了整条产线,看到了机器从零到一的生产过程



还有 MakeX 机器人挑战赛,这是一个具有重要影响力的国际化机器人赛事和教育平台,今年是第七年了,MakeX 总决赛又回到了深圳举办,立即报名了技术支持,参与了一天的活动现场



场馆设有各个国家的文化摊位,期间还被俄罗斯的大朋友投喂了巧克力零食



看大朋友、小朋友们激烈比赛:



以及让我难过了一天的创客马拉松,这是公司的特色活动,参与的最新的一期比赛,在决赛路演输了,作为队长很自责,忙碌了一周并没有好的结果,促使我进行反思,是带队伍问题?是项目推进问题?是质量问题?还是路演问题?




失败或许不是一件坏事,让我刻骨铭心,反而推着我进步



回头来看,今年最大的里程碑节点是备考雅思失败后的悟“道”,也就是成长性思维,这种认知层面的巨大改变,进而影响了我的工作、生活


我似乎不再害怕去面对我从未遇到的难题,我知道肯定方法、有途径能够去解决它,即使事与愿违,这过程中我也能收获到经验、智慧和勇气


我不再陷入完美主义,先做一个垃圾出来,再慢慢去迭代、优化,不要幻想一下子就能得到 100 分,但先拿到 60 分,再一点点进步,日拱一卒,等待那个增长拐点


我也变得更加正向,“世上只有一种英雄主义,就是在认清生活真相之后依然热爱生活”,坚持开放、乐观的心态,拒绝被悲观、消极所同化


或许我的所谓悟“道”、成长型思维,这些认知只是浅薄的,缺乏更多实践和经历,但是没有关系,做时间的朋友,随着年岁和阅历的增长,这套成长型思维也会随之”成长“


碎碎念的年终总结到此为止,写完之后还是很感慨,今年发生了很多事情,但在当时,只觉得是一个风平浪静的日子,或许此时此刻,也觉得是风平浪静的深夜。弱智吧有一句话,“有人看不到未来,其实是看到了未来”,初看的时候不理解,后来明白了,未来是动态的、不确定的,这就是未来的真面目,以为的看不到,其实恰恰是看到了,所以 2025,未来见


作者:楷鹏Dev
来源:juejin.cn/post/7451924452538548260
收起阅读 »

2024年,30岁前最后一次年度思考

没错!95年,还剩几个月就奔三了。2024年,注定是人生中意义非凡的一年,忐忑、裁员、出书、求职、转正这几个词贯穿了一整年。 忐忑 在上一家公司时,我从面试开始和到入职半年转正后,其实内心对于公司的状况一直保持一种忐忑不安的心情,这种感觉跟我老婆说过几次,我们...
继续阅读 »

没错!95年,还剩几个月就奔三了。2024年,注定是人生中意义非凡的一年,忐忑、裁员、出书、求职、转正这几个词贯穿了一整年。


忐忑


在上一家公司时,我从面试开始和到入职半年转正后,其实内心对于公司的状况一直保持一种忐忑不安的心情,这种感觉跟我老婆说过几次,我们一致认为应当有心理准备。原因在于薪资与公司的组织架构、基础建设、日常工作量安排和人员扩充速度都让人感到迷惑。


公司是在一个包括高层话事人不断更换,高层(副总裁)突然接受停止调查;技术部门仅仅作为辅助,技术氛围低沉,基建缺失,直属leader作用甚微;工作量与人员匹配失常,人多活少,尽管如此年初还在不断扩招中,泡沫感极强,伴随着薪酬发放日漂浮不定,每到月底像是在开盲盒,你永远不知道银彳亍卡何时会有一笔款到账。


裁员


一系列薪酬制度改革和薪酬拖欠不得不怀疑高层战略的正确性,直到四月某一天CTO私聊我,泡沫破裂,裁员尘埃落定。


我被归属于第一批裁员名单中,与CTO交谈中,似乎也流露一丝对高层决策的不满,但没有明说,给我的理由是当前工作任务都很简单,匹配不了我的能力,所以给了我一个名额。


这放在当时听上去有些许意外,但我接受了这种措辞,并不是因为CTO说了几句好听的话,更多是我作为一个技术人的直觉认为这个CTO靠谱。离职过程中对人事提出的补偿计算方式以及分期发放,我都拒绝了,最后经过与人事反复讨论之后拿到了补偿,少不了他的协助,所以内心表示感谢。从现在的视角看来,似乎是他已经意料到公司的发展趋势,以致于后来被裁员的人有很大一部分都没有赔偿。


出书


离职后我在家休息了一个月,期间也为了帮一个粉丝忙,接手了他工作的一部分任务,主要是做游戏业务的动画。期间有被一个后端恶心到,业务不熟悉,接口一直不通就算了,关键还理直气壮说是前端问题;我佩服那个粉丝能够忍气吞声这么久,换做其他人也很难不高血压,为此特意发圈宣泄。
image.png


由于后端提供的接口迟迟不通,需求没有预期上线,为此他们老板还大发雷霆,最后把锅推给了这个前端粉丝,声称把他给炒了。没过一个月,粉丝的这个公司被帽子叔叔查封,业务涉及到了灰产,老板和负责人进去了。员工的工资都没发,但我的报酬是因为签了合约,在deadline之前要求他们打款,对我没有影响,这是苦了这个粉丝。


在此之后我便全职写书,《NestJS全栈开发解析:快速上手与实践》 这本书临近结尾,我一鼓作气完成了并在5.1号劳动节那天交稿;写书的想法也有一部分是来源于CTO的启发,后面图书审阅也是找了CTO帮忙,熬夜帮我看完并给了这个评语,为此我很感谢他。


经过几个月的审批和改稿,图书在9月份正式发布了各大平台,这是一件值得高兴的事情。


image.png
而对于前司的后续,据说后面还搬到一个CBD进行办公,但当时员工已经欠薪几个月,以至于到年底,公司被迫全员原地解散,很遗憾这不是一个好结果。


求职


交稿完成后,花了一个月左右时间求职,拿到了3个offer,最后选择了去深圳的美图,这是凭借NestJS的图书写作获得的一个岗位。之后由于组织架构变化,我在转正前夕面临选择继续从事Node全栈还是Go语言开发,考虑一番后我选择了后者,顺利转到了后端架构组,负责go语言开发,这对我来说又是一个新的尝试和挑战,我选择了这种变化,与框架和语言无关,只不过是践行我的人生哲学:【不断变化】,让自己处于一种长期乐观、短期痛苦、当下快乐的舒适区边缘中。


觉醒


关于成长,过去我一直不喜欢看历史,或许归根于上学时代对于历史学科的厌倦,没看过基本历史文献。2024年底,我看了教员的《毛选》、《实践论》、《矛盾论》、《寻乌调查》,第一种感受是成功绝不是偶然,环环相扣的逻辑能力令人惊叹。我想这些书籍回答了我一直以来的问题:



如何成为一个独立、深度思考的人?



我们人生中做了一个坏的决定,在股市中选择了不争气的股票,最坏的结果无非是让自己从头再来。但革命不同,选择错了就有可能让整个民族处于被毁灭的境地中,每一步都步履蹒跚,这该有怎样的智慧与思维?


第二种感受是遗憾没有早点开悟,在临近30岁时才开始阅读这些书籍,当然也很庆幸没有太晚,一切都来得及!


特别的是,《寻乌调查》报告里面的细节,应该是我人生中读过的一本最详细的一本书籍,里面还记载了寻乌与我老家(兴宁)相关的历史宜了,没有一句多余的,都是干货。第一次感受原来伟人离我这么近。


image.png


教员做了这个调查报告之后,便留下一句千古格言:没有调查,就没有发言权!反观自身,何尝不是应该这样呢?


关于家庭,今年整个过程中家里的大大小小的事基本上都是我老婆操办,为我们的小家默默付出了很多,加上我去了深圳之后,我的衣食住大部分也是她来打理,一个人照顾小孩,现在甜筒一岁半了,如我们所愿健康成长,这隶属她的功劳。


一个家庭要想变好,靠一个人努力不行,需要“拉拢”有能力的人一起,话事人脑子要清醒,能够明辨是非,唯唯诺诺绝对是会出问题的。


一个家族要想变好,靠一两个人不行,得靠一两个家庭真正向好,大家庭才会有希望。


最后,没有Flag,年度总结中对未来进行遐想没有意义,沉浸于自己完成所有Todo List的那种兴奋是虚构的,而实践中那种痛苦、无助才是我们最真实的感受,人不能总活在无限遐想的递归当中


我看过那些在新年Flag列举诸多愿望,买了一堆书籍想要读完的,来年能真正落地完成的少之又少,毕竟我亦如此。


2025年,爱自己,爱家人,步步为营,不负将来!祝所有支持我的粉丝朋友们,一切如意,事业感情双丰收~


作者:元兮
来源:juejin.cn/post/7455282891535302708
收起阅读 »

博弈论(一):身在大厂,平衡工作和家庭太难了

引言 什么是博弈论(Game Theory)?百度百科的介绍如下 博弈论,又称为对策论(Game Theory)、赛局理论等,既是现代数学的一个新分支,也是运筹学的一个重要学科。 博弈论主要研究公式化了的激励结构间的相互作用,是研究具有斗争或竞争性质现象的数...
继续阅读 »

引言


什么是博弈论(Game Theory)?百度百科的介绍如下



博弈论,又称为对策论(Game Theory)、赛局理论等,既是现代数学的一个新分支,也是运筹学的一个重要学科。


博弈论主要研究公式化了的激励结构间的相互作用,是研究具有斗争或竞争性质现象的数学理论和方法。博弈论考虑游戏中的个体的预测行为和实际行为,并研究它们的优化策略。生物学家使用博弈理论来理解和预测进化论的某些结果。


博弈论已经成为经济学的标准分析工具之一。在金融学、证券学、生物学、经济学、国际关系、计算机科学、政治学、军事战略和其他很多学科都有广泛的应用。



博弈论在生活中的影响无处不在,比如许多朋友会面临一种选择选择,两家公司给你发了offer,一家公司薪资高,职位高,但可能需要频繁加班,压力很大。另一家公司薪资比你现在低一些,但是可以保证你准时下班,让你有充足的业余时间。对于单身的人来说这个并不难选择,但是当你结婚生子,这个问题就会瞬间变得复杂。


还有一个现象,为什么当团队内有一个人下班晚,整个团队的下班平均时间就都会往后延。


学习博弈论,只为了一件事情:拥有识别博弈格局的眼光,拥有改变规则的意识。简单来说,先理解长期存在的现象,并能够去改变不好的局面。


看完这篇文章,你或许就会拥有一个全新的视角去看这个世界。


介绍博弈论,我们先从三个基本概念开始,分别是帕累托最优,囚徒困境和纳什均衡。


帕累托最优


每一个成家、有孩子的人,再找工作时都会考虑如何平衡工作、收入、家庭三者之间的关系,说白了就是钱多事少离家近。


你现在有一份不错的工作,有一份稳定的收入,到了下班时间,你回家陪伴家人,辅导孩子写作业,顺便还能做做家务。公司、家人对你很满意,有房有车,收入足以满足日常花销,甚至有点小积蓄。


其实外面有其他公司给你抛来了橄榄枝,有的公司承诺如果你过去,会给你升值加薪。当然也有别的机会,你工作可能更清闲,但你的收入也会下降。


你很难做出抉择,你不是一个个体,你还需要考虑别人的选择。因为你一旦选择更高收入,就意味着要做更多的工作,没法按时下班,家人会对你有意见。新工作压力可能很大,你的状态也会变差,你不但没法照顾家人,可能家人还要额外提供给你情绪价值,家人一定会不满意。


此时,你在工作、收入、家庭取得了平衡,你无法在不影响收入和家人满意度的情况下去换另一份工作,此时的局面就叫做“帕累托最优”。


我们都希望工作和家庭最理想的状态是帕累托最优的,可我们都知道,生活中似乎很难达到这个状态,帕累托最优这个状态很理想化,因为它是不稳定的。


博弈论要求我们需要考虑竞争对手怎么做,虽然我不想把工作称之为竞争对手,但是在这个三角关系中,这里的竞争对手就是你的工作。


工作中公司一定要求你尽职尽责的完成本质工作,你要能够承担一定的责任,最好你要有一点创新,能够给公司带来一定改变,带来额外的价值。


一直以来你自己负责一个项目,这时候你的工作由你自己安排,要做的事情有很多,但你有没有三头六臂,那你只能一件一件来。当天既然没法做完所有工作,那到点下班就好。


可你的项目越来越有前景,公司决定增加人手,公司招进来了另一位同事,我们叫他小齐。现在你俩需要一起负责这个项目,你们既是合作关系,又是竞争关系。


那工作还是这么多,却有两个人来干了。如果你按点下班,可同事还想“承担”,他多做一点,你就少做一点,时间长了对你可不是一个好消息,你不得不选择加班。


compressed_pexels-cottonbro-6944006.jpg


这也就解释了为什么有时候一个团队只要有一个人加班,大家普遍的下边时间都会变晚,也称之为内卷。


这时候你想了一个办法,你请同事吃了一顿饭,你说你还单身,你得丰富一下业余生活。我认识一个单身的女同事,听说你俩爱好挺像,我把她微信推给你,以后你们下班可以一起约着出去玩。


后来你们都不加班了,你们依然是平等竞争,而且你又可以按时回家陪娃了,这就叫“帕累托改进”。


压倒性策略


或许你听过囚徒困境的故事,囚徒困境的故事最适合解释这个概念,但你和小齐的故事还在继续。


(注:下面的例子纯属虚构,如有雷同,纯属巧合)


你和小齐接手了全新的项目,有大量的代码开发工作,你俩的技术水平相当。可之前你给他介绍的单身女同事,后来找了男朋友,可惜那个人不是他。他感觉被套路了,给你说在你没有给他介绍下一个对象之前,他暂时决定和你势不两立。


可工作还得继续,你们都可以选择写出高质量的代码,需要花费更多的精力,但很明显你的口碑会变好。也可以得过且过,写出质量一般的代码,工作量明显变少,但项目质量可能一般。


根据两人的选择,存在几种情况



  • 两人都贡献高质量代码,项目质量得到广泛认可和领导称赞,声誉增加了

  • 如果一人费心费力提供了高质量代码,而另一个人敷衍了事,代码不规范、质量差。那么项目依然会正常上线,写低质量代码的人可以说是被大佬带飞(+7),而高质量代码因为加班、熬夜,所获的收益变少了(+2)

  • 如果两人都敷衍了事,上线之后问题频发,虽然工作完成了,可产品运营会不停的和你领导吐槽,领导对你们的印象大打折扣(+3)


注意:我们这个例子不考虑这个工作对于后续年终奖或职业生涯发展如何。就只针对这个工作,我们来分析一下你和小齐的博弈策略。首先我们把不同的策略和结果画在下面这个矩阵图里。多说一句,这种画法是美国经济学家托马斯·谢林发明的。


小齐贡献高质量代码小齐贡献低质量代码
你贡献高质量代码你:+5,小齐+5你:+2,小齐+7
你贡献低质量代码你:+7,小齐+2你:+3,小齐+3

其实你明显看可以看出,最好的结果就是两个人都贡献高质量的代码,然后项目稳定,还能收获良好的口碑。


但是,博弈论要求我们每次做判断都要考虑对方-----并不是说怎么样对对方好,而是对方会如何选择,然后你根据对方的选择再考虑怎么做。


我们具体看看你的收益分析:



  • 如果小齐开源高质量代码:你选择开源高质量代码,收益为 +5。你选择写低质量代码,收益为 +7。 (低质量代码收益更高)

  • 如果小齐开源低质量代码: 你选择开源高质量代码,收益为 +2。你选择开源低质量代码,收益为 +3。 (低质量代码收益更高)


你发现,无论小齐怎么做,你选择写一些低质量的代码,你的收益都会更高!于是你来说,选择写份低质量的代码,就是一个压倒性策略


可反过来对小齐来说呢,这就是一个被压倒策略!但博弈论的前提是大家都是理性人,小齐也聪明的很,于是他也意识到选择写低质量代码,是一个压倒性策略。


对理性人来说,在博弈中一定要选择压倒性策略,任何情况下都不要选择被压倒性策略。


你俩同时这样选择,这就引出接下来的一个最重要的概念,纳什均衡。


纳什均衡


上面说到你俩同时放松了对自己的要求,写的代码缺乏设计,代码不健壮,上线之后问题频发,领导对你俩很不满。


这个结果可不是帕累托最优,但这个结果是稳定的,不管我们是否喜欢这个局面,但我们认可这个局面了。


线上一直报警,产品运营一直来找我们排查问题,这些都惹人心烦。我们或许都想把项目做好,但在这个组合里,没有一个人愿意单方面改变自己的策略。


因为无论对谁来说,此时选择去写高质量代码,都会让自己的利益受损,谁不想更轻松一点呢?


这种没有任何一方愿意单方面改变策略的局面,就是纳什均衡


纳什均衡的概念由约翰·纳什提出,他是著名数学家、经济学家,还是《美丽心灵》男主角原型,他与另外两位数学家在非合作博弈的均衡分析理论方面做出了开创性的贡献,对博弈论和经济学产生了重大影响,而获得1994年诺贝尔经济学奖。


FE826890-FA8F-4813-98D6-5695D394BFD7.png


文章开头的对于找工作和家庭的例子就是一个纳什均衡,要记住纳什均衡不是最优的局面,如果你观察到身边的人在一家公司干了5年甚至10年以上,他的薪资或许会落后于市场平均水平,但对他来说,这份工作对于工作、生活、收入来说,大概就是一个纳什均衡。当然有的工作也可能不让你准时下班,甚至可能让你没有业余时间,就像一个在大厂996,忙到没有时间顾家和看娃的人来说,这也是一个纳什均衡。


如果你观察到社会上的一个现场长期稳定的存在,它对于所有参与方来说就是一个纳什均衡。纳什均衡告诉我们评价一个事情不能看它是不是对整体最好,它必须要让所有参与者都不愿单方面改变才行。


说在最后


公司领导发现项目问题不断,悄悄从HR那里调出了你和小齐的打卡记录,赫然发现你们天天5:30准时打卡,项目问题这么多,你俩准时下班,难怪运营天天说问题解决不及时,他决定宣布,在这个项目结束之前,项目组实行996工作制。


本来还想平衡好工作和家庭,这下可好了,不但日常加班,还损失了周六的时间。


那你说,这该怎么办呢?欢迎你在评论区说出你的看法,也希望你点赞、评论、收藏,让我知道对你有所收获,这对我来说很重要。也欢迎你加我的wx:Ldhrlhy10,一起交流~


本篇文章是第65篇原创文章,2024目标进度65/100,欢迎有趣的你,关注我。


作者:东东拿铁
来源:juejin.cn/post/7449172888689836047
收起阅读 »

前端ssr项目被打崩后,连夜做限流!

web
token-bucket-limiter-redis 是一个令牌桶算法 + redis 的高效限流器,用于Node服务接口限流。 当然作为一个前端你可能很少接触Node接口开发,用的接口应该都是后端同学提供的,他们有自己的限流策略,但是你一定使用过SSR框架来...
继续阅读 »

token-bucket-limiter-redis 是一个令牌桶算法 + redis 的高效限流器,用于Node服务接口限流。


当然作为一个前端你可能很少接触Node接口开发,用的接口应该都是后端同学提供的,他们有自己的限流策略,但是你一定使用过SSR框架来开发服务端渲染项目,那么此时你的项目就只能靠我们自己来做限流了,否则遇到突发流量时,你的项目可能很容易崩溃。



  • 使用令牌桶算法实现

  • 支持基于内存和基于 redis 存储的两种选择,满足分布式限流需要

  • 高性能,令牌生产的方式为每次请求进来时一次性生产上一次请求到本次请求这一段时间内的令牌,而不是定时器生成令牌

  • 快速,使用 lua 脚本与redis通讯,lua 支持将多个请求通过脚本的形式一次发送到服务器,减少通讯,并且脚本支持缓存,多客户端可以复用

  • 安全,lua 脚本保证redis命令执行的原子性

  • 内存效率高,键过期后自动删除,不占用过多内存

  • 提供多种极端场景下的降级和容错措施



其他限流方法的对比,大家可以自行搜索,这里就不赘述了,令牌桶算法是更适合大部分场景的限流方案。


令牌桶算法:按照一定的速率生产令牌并放入令牌桶中,最大容量为桶的容量,如果桶中令牌已满,则丢弃令牌,请求过来时先到桶中拿令牌,拿到令牌则放行通过,否则拒绝请求。这种算法能够把请求均匀的分配在时间区间内,又能接受服务可承受范围内的突发请求。所以令牌桶算法在业内较为常用。




该项目github地址:token-bucket-limiter-redis



安装


npm i --save token-bucket-limiter-redis

引入


import { RateLimiterTokenBucket, RateLimiterTokenBucketRedis } from 'token-bucket-limiter-redis';

使用


限流方案我们分为无状态限流器和有状态限流器两种:


有状态的限流器(区分key的限流器):这种限流器会根据某种标识(如IP地址、用户ID、url等)来进行区分,并对每个标识进行单独的限流。可以更精细地控制每个用户或者每个IP的访问频率。


无状态的限流器(不区分key的限流器):这种限流器不会区分请求的来源,只是简单地对所有请求进行统一的限制。


基于内存的无状态限流器


const globalRateLimiter = new RateLimiterTokenBucket({
tokenPerSecond: 100,
capacity: 1000,
});

const globalTokens = globalRateLimiter.getToken();

if(globalTokens > 0){
// pass
}


基于内存的有状态限流器,自定义key


const globalRateLimiter = new RateLimiterTokenBucket({
tokenPerSecond: 5,
capacity: 5,
keyPrefix: 'test', // 指定限流器所属项目或模块
});

const key = ip + uid; // 标识用户信息的key

const globalTokens = globalRateLimiter.getToken(key);

if(globalTokens > 0){
// pass
}


这里附上 node 端获取ip的方法



function getClientIp(req) {
// 获取 X-Real-IP 头部字段
const xRealIP = req.headers['x-real-ip'];

// 优先使用 X-Real-IP 头部字段
if (xRealIP) {
return xRealIP;
}

// 获取 X-Forwarded-For 头部字段,通常包含一个或多个IP地址,最左侧的是最初的客户端IP
const xForwardedFor = req.headers['x-forwarded-for'];

// 如果 X-Real-IP 不存在,但 X-Forwarded-For 存在,则使用最左侧的IP地址
if (xForwardedFor) {
const ipList = xForwardedFor.split(',');
return ipList[0].trim();
}

// 获取连接的远程IP地址
const remoteAddress = req.connection?.remoteAddress;
// 如果都不存在,使用连接的远程IP地址
if (remoteAddress) {
return remoteAddress;
}

return '';
}

基于内存的有状态限流器,使用ip作为默认key


const globalRateLimiter = new RateLimiterTokenBucket({
tokenPerSecond: 5,
capacity: 5,
keyPrefix: 'test', // 指定限流器所属项目或模块
});

// 使用 ip 作为key,无需传入,自动获取ip
const globalTokens = globalRateLimiter.getTokenUseIp(req);

// 使用 ip 加上自定义的其他key,如传入则组合在ip后 ip+uid
const globalTokens = globalRateLimiter.getTokenUseIp(req, uid);

if(globalTokens > 0){
// pass
}



注意,单纯使用ip作为限流key可能会有问题,有以下几种可能过个机器的外网ip相同的情况:



  • 使用共享的公共 IP 地址: 在一些特殊的网络环境下,多个设备可能共享同一个公共 IP 地址,如咖啡馆、图书馆等提供 Wi-Fi 服务的地方。在这种情况下,所有连接到同一网络的设备都会共享相同的公共 IP。

  • 使用代理服务器: 如果多个机器通过相同的代理服务器访问互联网,它们可能会在外网上表现为相同的 IP 地址,因为代理服务器向互联网发起请求,而不是直接来自每个终端设备。

  • 使用 NAT(网络地址转换): 在家庭或企业网络中,使用了 NAT 技术的路由器可能会导致多个内部设备共享同一个外网 IP 地址,同一公司下的内网设备公网ip可能是同一个。



综上,如果你需要考虑以上集中情况的话,你需要结合其他可以标识用户身份的key,如uid,浏览器指纹等:


// 使用 ip 加上自定义的其他key,如传入则组合在ip后 ip+uid
const globalTokens = globalRateLimiter.getTokenUseIp(req, uid);

附上浏览器指纹获取方法:


function generateFingerprint() {
try {
// 收集一些浏览器属性
const userAgent = navigator.userAgent || '';
const screenResolution = `${window.screen.width}x${window.screen.height}`;
const language = navigator.language || '';
const platform = navigator.platform || '';

// 将这些属性组合成一个简单的指纹
const fingerprint = userAgent + screenResolution + language + platform;

// 返回指纹
return fingerprint;
} catch (error) {
return '';
}
}

在 express 中使用


const express = require('express');
const app = express();

const globalRateLimiter = new RateLimiterTokenBucket({
tokenPerSecond: 5,
capacity: 5,
keyPrefix: 'test', // 指定限流器所属项目或模块
});

// 全局中间件
app.use((req, res, next) => {
console.log('Express global middleware');
// 使用 ip 作为key,无需传入,自动获取ip
const tokens = globalRateLimiter.getTokenUseIp(req);

if(tokens > 0){
next();
}else {
res.status(429).send({ message: 'Too Many Requests' })
}
});

app.listen(3000, () => {
console.log('Express app listening on port 3000');
});

在 koa 中使用


const Koa = require('koa');
const app = new Koa();

const globalRateLimiter = new RateLimiterTokenBucket({
tokenPerSecond: 5,
capacity: 5,
keyPrefix: 'test',
});

// 全局中间件
app.use(async (ctx, next) => {
console.log('Koa global middleware');
// 使用 ip 作为 key,无需传入,自动获取 ip
const tokens = globalRateLimiter.getTokenUseIp(ctx.req);

if (tokens > 0) {
await next();
} else {
ctx.status = 429;
ctx.body = { message: 'Too Many Requests' };
}
});

app.listen(3000, () => {
console.log('Koa app listening on port 3000');
});


在 fastify 中使用


const fastify = require('fastify')();

const globalRateLimiter = new RateLimiterTokenBucket({
tokenPerSecond: 5,
capacity: 5,
keyPrefix: 'test',
});

// 全局中间件
fastify.addHook('onRequest', (request, reply, done) => {
console.log('Fastify global middleware');
// 使用 ip 作为 key,无需传入,自动获取 ip
const tokens = globalRateLimiter.getTokenUseIp(request);

if (tokens > 0) {
done();
} else {
reply.status(429).send({ message: 'Too Many Requests' });
}
});

fastify.listen(3000, (err) => {
if (err) throw err;
console.log('Fastify app listening on port 3000');
});


基于redis的无状态限流器,传入redis客户端


支持分布式限流,外部传入redis客户端 (由ioredis包创建)


import Redis from 'ioredis';

const redis = new Redis({});

const globalRateLimiter = new RateLimiterTokenBucketRedis({
tokenPerSecond: 100,
capacity: 1000,
keyPrefix: 'test', // 指定限流器所属项目或模块
redisClient: redis,
});

const key = 'myproject'; // 使用全局唯一key (当key省略时,默认为RateLimiterTokenBucketGlobalKey)

const globalTokens = globalRateLimiter.getToken(key);

if(globalTokens > 0){
// pass
}


基于redis的有状态限流器,传入redis客户端


支持分布式限流,外部传入redis客户端 (ioredis)


import Redis from 'ioredis';

const redis = new Redis({});

const globalRateLimiter = new RateLimiterTokenBucketRedis({
tokenPerSecond: 5,
capacity: 5,
keyPrefix: 'test', // 指定限流器所属项目或模块
redisClient: redis,
});

const key = ip + uid; // 标识用户信息的key
const globalTokens = globalRateLimiter.getToken(key);

// 使用 ip 作为key
const globalTokens = globalRateLimiter.getTokenUseIp(req);

// 使用 ip + 自定义key
const globalTokens = globalRateLimiter.getTokenUseIp(req, key);

if(globalTokens > 0){
// pass
}


基于redis的有状态限流器,使用内置redis


外部仅需传入redis配置(ioredis)


const redisOptions = {
port: 6379, // Redis 端口
host: 'localhost', // Redis 主机名
password: 'password' // 如果有的话,你的 Redis 密码
db: 0,
};

const globalRateLimiter = new RateLimiterTokenBucketRedis({
tokenPerSecond: 5,
capacity: 5,
keyPrefix: 'test', // 指定限流器所属项目或模块
redisOptions: redis,
});

const key = ip + uid; // 标识用户信息的key

const globalTokens = globalRateLimiter.getToken(key);

if(globalTokens > 0){
// pass
}


添加内存阻塞策略


内存阻塞策略可以保护redis服务器,抵御DDoS攻击


const redisOptions = {
port: 6379, // Redis 端口
host: 'localhost', // Redis 主机名
password: 'password' // 如果有的话,你的 Redis 密码
db: 0,
};

const globalRateLimiter = new RateLimiterTokenBucketRedis({
tokenPerSecond: 5,
capacity: 5,
keyPrefix: 'test', // 指定限流器所属项目或模块
redisOptions: redis,

// 内存阻塞策略(只计算当前服务器或实例的请求数,非分布式)
inMemoryBlockOnConsumed: 50, // 如果某个key在一分钟内消耗的令牌数量超过 50,将在内存中阻塞该key的请求,不会发起redis,防止DDoS攻击
inMemoryBlockDuration: 10, // 阻塞持续时间s
});

const key = ip + uid; // 标识用户信息的key

const globalTokens = globalRateLimiter.getToken(key);

if(globalTokens > 0){
// pass
}


getToken 方法支持第二个参数,传入判断阻塞的标识键,通常是ip或用户id,因为我们要阻塞的是某个具体的用户或机器,不传的话默认使用第一个参数,即令牌标识键。


当你使用无状态限流器,或是有状态限流器的键无法标识某个具体用户时可能需要填写该参数:


const key = 'myproject'; // 无状态限流器
const key = 'url'; // 有状态限流器,但是只限制某个路由

const blockKey = 'ip'; // 阻塞标识键须使用ip或用户id

const globalTokens = globalRateLimiter.getToken(key, blockKey);

// 使用 ip + 自定义key
const globalTokens = globalRateLimiter.getTokenUseIp(req, key, blockKey);

if(globalTokens > 0){
// pass
}


内存阻塞策略优先于redis限流器以及redis保险策略,即使redis不可用时内存阻塞策略依旧生效。


添加保险策略,配置当redis服务错误时是否自动使用内存限制器


const redisOptions = {
port: 6379, // Redis 端口
host: 'localhost', // Redis 主机名
password: 'password' // 如果有的话,你的 Redis 密码
db: 0,
};

const globalRateLimiter = new RateLimiterTokenBucketRedis({
tokenPerSecond: 5,
capacity: 5,
keyPrefix: 'test', // 指定限流器所属项目或模块
redisOptions: redis,

// 内存阻塞策略
inMemoryBlockOnConsumed: 50, // 如果某个key在一分钟内消耗的令牌数量超过 50,将在内存中阻塞该key的请求,不会发起redis,防止DDoS攻击
inMemoryBlockDuration: 10, // 阻塞持续时间s

// 保险策略,使用内存限流器
insuranceLimiter: true,
insuranceLimiterTokenPerSecond: 3, // 如果未填写将取tokenPerSecond的值
insuranceLimiterCapacity: 3, // 如果未填写将取capacity的值
});

const key = ip + uid; // 标识用户信息的key

const globalTokens = globalRateLimiter.getToken(key);

if(globalTokens > 0){
// pass
}


开启保险策略后,支持传入保险限制器的每秒令牌数和令牌桶容量,如果不传,将取redis限流器的值。


当你的服务是集群部署时,例如使用 pm2 的集群模式时,会用到这些选项,因为使用redis时令牌是共享的,而集群模式下每个服务是一个实例,每个实例有自己的内存空间,所以你要适当地考虑使用内存限流器时每个实例的限流速率。


注意事项



  1. 基于内存的限流器更适用于单机限流的场景,集群或分布式部署时,如果你不能计算出每一个实例的合适限流配置的话推荐使用基于redis的限流器。


FAQ


不使用定时器生成令牌有什么好处?


时间精度:定时器的精度可能会受到系统调度和网络延迟的影响,这可能导致令牌的生成速率无法精确控制。


资源消耗:如果令牌桶的数量非常多,那么需要维护的定时器也会非常多,这可能会消耗大量的系统资源。


时间同步:由于精度问题,如果系统中存在多个令牌桶,且每个令牌桶都使用自己的定时器,那么这些定时器之间可能并不同步。


冷启动问题:如果使用定时器生成令牌,那么在服务刚启动时,令牌桶可能会是空的,这可能导致在服务启动初期无法处理请求。


除了ip还有哪些可以标识具体用户的key



  • 浏览器指纹

  • 用户id

  • 用户名

  • 邮箱

  • 手机号

  • 其他可以标识用户身份的key


// 生成浏览器指纹
export function generateFingerprint() {
try {
// 收集一些浏览器属性
const userAgent = navigator.userAgent || '';
const screenResolution = `${window.screen.width}x${window.screen.height}`;
const language = navigator.language || '';
const platform = navigator.platform || '';

// 将这些属性组合成一个简单的指纹
const fingerprint = userAgent + screenResolution + language + platform;

// 返回指纹
return fingerprint;
} catch (error) {
return '';
}
}

作者:Pursue_LLL
来源:juejin.cn/post/7454095190379888666
收起阅读 »

离职后,前领导突然找你回去帮忙写代码解决问题,该怎么办?

题目中的这个问题,我相信有遇到过这种情况的同学的第一反应是:"诶,是要白嫖我还是说解决完问题给钱呀",且听我接下来慢慢分析。 首先要说的是,这种没头没尾的突发情况,一般大部分人都是很难遇到的。 原因也很简单,老板大部分也都是打过工,当过员工的,也是一路从职场老...
继续阅读 »

题目中的这个问题,我相信有遇到过这种情况的同学的第一反应是:"诶,是要白嫖我还是说解决完问题给钱呀",且听我接下来慢慢分析。


首先要说的是,这种没头没尾的突发情况,一般大部分人都是很难遇到的。


原因也很简单,老板大部分也都是打过工,当过员工的,也是一路从职场老油子混成的老板,很多人情世故,员工的小心思,老板其实都门儿清,甚至比很多员工都更熟。


如果公司里的一些工作是交接时不太能完全搞定的,可能还需要离职的员工继续帮忙的,一般在员工离职前的时候,就各种协商好了。


而像这种“突发情况”,大部分老板在联系离职的员工回去帮忙前,一般也都会把员工会想到的那些事儿,早就想了很多遍了,基本上相关问题都会在联系员工的时候说明白。


比如很多人都提到的报酬问题,这个基本上都是作为老板不可能回避,也不可能不知道的。


如果老板在联系员工的时候什么都提了,就是没聊这个。


那肯定是老板不想给报酬,还在做着让员工回来白干活的美梦。


不可否认,现实中确实有挺多这样的老板


所以,我的经验就是,如果老板在主动联系离职员工回来帮忙的时候,都没提报酬的事儿,那基本上就是不打算给,基本上你问了也是白问。


当然,大部分人都会遇到的情况是,本来跟老板领导关系也不错,老板领导也知道这一点,所以才会跟已经离职的员工开这个口。


这种时候,大部分人看在老板领导人还不错的份儿上,还是愿意回去帮忙的。


至于会和现在的工作造成的一些冲突,比如时间上走不开,现在住得离公司远,这些也都是可以直接明说的事儿,说了后,要么老板可以帮你解决,要么老板心里会知道你回来帮这次忙的成本有多高。


我以前工作过的公司,别说离职走的同事了,有一次是碰到了一个实习生经手的项目,上面很多东西没按照公司规范写,后来看到这些资料的员工整不明白是怎么回事。


但是,部门领导在知道了这件事后,在知道了这个实习生的同学就在本部门工作的情况下,并没有说让这位同学去搞定这个问题。


而是让这位同学联系好那位实习生后,领导亲自开车带着这位同学和要用到这个资料的人,专门在下班时间守在这位实习生的工作单位门口,接着他去一家还不错的餐厅,边吃饭边解决了这个问题。


至于很多人提的,跟老板没啥交情,甚至关系还不怎么好的,那还纠结什么,直接不理或拒绝就行了,但也没必要把话说得太绝。


毕竟,如果老板真的意识到你这边不好搞,同时也只有找你来帮忙是最划算的选择后,一般都会开出更高的加码,如果加码合适,你还是可以考虑一下的。


但是一定要就是论事,划定要解决问题的范围,要不然赖上你了有问题就找你可还行,同时也要注意不要留下太多痕迹。比如你回来帮忙,是不是属于违规行为,再比如请你回来帮忙的时候,装作无意间打听你现在公司的一些事儿,这个事儿很可能属于工作机密,毕竟大家都是同行,这些一定要注意。


综上我觉得,解决这个问题的公式是:上来先拖字诀、加各种不容易各种不行,这种能挡掉99%的需求,毕竟这么大个公司离了我这个小兵还不能转了咋地;实在不行了在谈什么样的条件你才能去帮忙解决问题,而且记住是单次解决问题的条件。


---程序员职场闲聊公众号:网管叨bi叨


作者:kevinyan
来源:juejin.cn/post/7322344486159826996
收起阅读 »

用java做物品识别和姿态识别

前言 之前搞得语音识别突然发现浏览器就有接口可以直接用,而且识别又快又准,参考:使用 JavaScript 的 SpeechRecognition API 实现语音识别_speechrecognition js-CSDN博客 进入正题 这个功能首先要感谢一下作...
继续阅读 »

前言


之前搞得语音识别突然发现浏览器就有接口可以直接用,而且识别又快又准,参考:使用 JavaScript 的 SpeechRecognition API 实现语音识别_speechrecognition js-CSDN博客


进入正题


这个功能首先要感谢一下作者常康,仓库地址(gitee.com/agriculture… 这个项目很早之前就关注了,最近这段时间正好要用才真正实践了一下,只是初步测试了一下,在性能方面还需要进一步测试,本人电脑就很拉识别就很卡。


先看效果


20240912_090041 00_00_00-00_00_30.gif


20240912_091337 00_00_00-00_00_08 00_00_00-00_00_30.gif


改动


主要对姿态识别做了一些小改动,将原图片识别改成视频视频识别,如果要调用摄像头将video.open(0);的代码注释放开即可


package cn.ck;

import ai.onnxruntime.OnnxTensor;
import ai.onnxruntime.OrtEnvironment;
import ai.onnxruntime.OrtException;
import ai.onnxruntime.OrtSession;
import cn.ck.config.PEConfig;
import cn.ck.domain.KeyPoint;
import cn.ck.domain.PEResult;
import cn.ck.utils.Letterbox;
import nu.pattern.OpenCV;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.highgui.HighGui;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.Videoio;

import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/*
* 姿态识别,可以识别动作等等.,比如跳绳技术
*/

public class PoseEstimation {

static {
// 加载opencv动态库
//System.load(ClassLoader.getSystemResource("lib/opencv_java470-无用.dll").getPath());
OpenCV.loadLocally();
}

public static void main(String[] args) throws OrtException {

String model_path = "src\main\resources\model\yolov7-w6-pose-nms.onnx";
// 加载ONNX模型
OrtEnvironment environment = OrtEnvironment.getEnvironment();
OrtSession.SessionOptions sessionOptions = new OrtSession.SessionOptions();
OrtSession session = environment.createSession(model_path, sessionOptions);
// 输出基本信息
session.getInputInfo().keySet().forEach(x -> {
try {
System.out.println("input name = " + x);
System.out.println(session.getInputInfo().get(x).getInfo().toString());
} catch (OrtException e) {
throw new RuntimeException(e);
}
});

VideoCapture video = new VideoCapture();

// video.open(0); //获取电脑上第0个摄像头

//可以把识别后的视频在通过rtmp转发到其他流媒体服务器,就可以远程预览视频后视频,需要使用ffmpeg将连续图片合成flv 等等,很简单。
if (!video.isOpened()) {
System.err.println("打开视频流失败,未检测到监控,请先用vlc软件测试链接是否可以播放!,下面试用默认测试视频进行预览效果!");
video.open("video/test2.mp4");
}
// 跳帧检测,一般设置为3,毫秒内视频画面变化是不大的,快了无意义,反而浪费性能
int detect_skip = 4;

// 跳帧计数
int detect_skip_index = 1;

// 最新一帧也就是上一帧推理结果
float[][] outputData = null;

//当前最新一帧。上一帧也可以暂存一下
Mat img = new Mat();


// 在这里先定义下线的粗细、关键的半径(按比例设置大小粗细比较好一些)
int minDwDh = Math.min((int)video.get(Videoio.CAP_PROP_FRAME_WIDTH), (int)video.get(Videoio.CAP_PROP_FRAME_HEIGHT));
int thickness = minDwDh / PEConfig.lineThicknessRatio;
int radius = minDwDh / PEConfig.dotRadiusRatio;
// 转换颜色空间
Mat image = new Mat();

// 图像预处理
Letterbox letterbox = new Letterbox();
letterbox.setNewShape(new Size(960, 960));
letterbox.setStride(64);


// 使用多线程和GPU可以提升帧率,线上项目必须多线程!!!,一个线程拉流,将图像存到[定长]队列或数组或者集合,一个线程模型推理,中间通过变量或者队列交换数据,代码示例仅仅使用单线程
while (video.read(img)) {
if ((detect_skip_index % detect_skip == 0) || outputData == null) {
Imgproc.cvtColor(img, image, Imgproc.COLOR_BGR2RGB);
image = letterbox.letterbox(image);
int rows = letterbox.getHeight();
int cols = letterbox.getWidth();
int channels = image.channels();
// 将图像转换为模型输入格式
float[] pixels = new float[channels * rows * cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
double[] pixel = image.get(j, i);
for (int k = 0; k < channels; k++) {
pixels[rows * cols * k + j * cols + i] = (float) pixel[k] / 255.0f;
}
}
}
detect_skip_index = 1;
OnnxTensor tensor = OnnxTensor.createTensor(environment, FloatBuffer.wrap(pixels), new long[]{1L, (long) channels, (long) rows, (long) cols});
OrtSession.Result output = session.run(Collections.singletonMap(session.getInputInfo().keySet().iterator().next(), tensor));

// 处理输出结果并绘制
outputData = ((float[][]) output.get(0).getValue());
}else{
detect_skip_index = detect_skip_index + 1;
}
double ratio = letterbox.getRatio();
double dw =letterbox.getDw();
double dh = letterbox.getDh();
List<PEResult> peResults = new ArrayList<>();
for (float[] outputDatum : outputData) {
PEResult result = new PEResult(outputDatum);
if (result.getScore() > PEConfig.personScoreThreshold) {
peResults.add(result);
}
}

// 对结果进行非极大值抑制
peResults = nms(peResults, PEConfig.IoUThreshold);

for (PEResult peResult: peResults) {
System.out.println(peResult);
// 画框
Point topLeft = new Point((peResult.getX0()-dw)/ratio, (peResult.getY0()-dh)/ratio);
Point bottomRight = new Point((peResult.getX1()-dw)/ratio, (peResult.getY1()-dh)/ratio);
// Imgproc.rectangle(img, topLeft, bottomRight, new Scalar(255,0,0), thickness);
List<KeyPoint> keyPoints = peResult.getKeyPointList();
// 画点
keyPoints.forEach(keyPoint->{
if (keyPoint.getScore()>PEConfig.keyPointScoreThreshold) {
Point center = new Point((keyPoint.getX()-dw)/ratio, (keyPoint.getY()-dh)/ratio);
Scalar color = PEConfig.poseKptColor.get(keyPoint.getId());
Imgproc.circle(img, center, radius, color, -1); //-1表示实心
}
});
// 画线
for (int i = 0; i< PEConfig.skeleton.length; i++){
int indexPoint1 = PEConfig.skeleton[i][0]-1;
int indexPoint2 = PEConfig.skeleton[i][1]-1;
if ( keyPoints.get(indexPoint1).getScore()>PEConfig.keyPointScoreThreshold &&
keyPoints.get(indexPoint2).getScore()>PEConfig.keyPointScoreThreshold ) {
Scalar coler = PEConfig.poseLimbColor.get(i);
Point point1 = new Point(
(keyPoints.get(indexPoint1).getX()-dw)/ratio,
(keyPoints.get(indexPoint1).getY()-dh)/ratio
);
Point point2 = new Point(
(keyPoints.get(indexPoint2).getX()-dw)/ratio,
(keyPoints.get(indexPoint2).getY()-dh)/ratio
);
Imgproc.line(img, point1, point2, coler, thickness);
}
}
}
//服务器部署:由于服务器没有桌面,所以无法弹出画面预览,主要注释一下代码
HighGui.imshow("result", img);

// 多次按任意按键关闭弹窗画面,结束程序
if(HighGui.waitKey(1) != -1){
break;
}
}

HighGui.destroyAllWindows();
video.release();
System.exit(0);

}

public static List<PEResult> nms(List<PEResult> boxes, float iouThreshold) {
// 根据score从大到小对List进行排序
boxes.sort((b1, b2) -> Float.compare(b2.getScore(), b1.getScore()));
List<PEResult> resultList = new ArrayList<>();
for (int i = 0; i < boxes.size(); i++) {
PEResult box = boxes.get(i);
boolean keep = true;
// 从i+1开始,遍历之后的所有boxes,移除与box的IOU大于阈值的元素
for (int j = i + 1; j < boxes.size(); j++) {
PEResult otherBox = boxes.get(j);
float iou = getIntersectionOverUnion(box, otherBox);
if (iou > iouThreshold) {
keep = false;
break;
}
}
if (keep) {
resultList.add(box);
}
}
return resultList;
}
private static float getIntersectionOverUnion(PEResult box1, PEResult box2) {
float x1 = Math.max(box1.getX0(), box2.getX0());
float y1 = Math.max(box1.getY0(), box2.getY0());
float x2 = Math.min(box1.getX1(), box2.getX1());
float y2 = Math.min(box1.getY1(), box2.getY1());
float intersectionArea = Math.max(0, x2 - x1) * Math.max(0, y2 - y1);
float box1Area = (box1.getX1() - box1.getX0()) * (box1.getY1() - box1.getY0());
float box2Area = (box2.getX1() - box2.getX0()) * (box2.getY1() - box2.getY0());
float unionArea = box1Area + box2Area - intersectionArea;
return intersectionArea / unionArea;
}
}

姿态识别模型提取链接,
通过网盘分享的文件:yolov7-w6-pose-nms.onnx
链接: pan.baidu.com/s/1UdAUPWr1… 提取码: du6y


后言


就像原作者说的,不是每个同学都会python,不是每个项目都是python语言开发,不是每个岗位都会深度学习。

希望java在AI领域能有更好的发展


作者:北冥有鱼518
来源:juejin.cn/post/7413234304278970404
收起阅读 »

为什么很多程序员会觉得领导没能力

相信很多人在职场里待久了,都会遇到自己觉得比较差劲的领导,这些人可能除了向上管理能力很强外(会舔老板),其他能力在你看来都挺一般,专业能力一般,超级缝合怪--上级给他的任何任务他都能分配给你们,然后他再缝合一遍完事。 那么遇到这种领导我们该怎么办呢?多数人想到...
继续阅读 »

相信很多人在职场里待久了,都会遇到自己觉得比较差劲的领导,这些人可能除了向上管理能力很强外(会舔老板),其他能力在你看来都挺一般,专业能力一般,超级缝合怪--上级给他的任何任务他都能分配给你们,然后他再缝合一遍完事。


那么遇到这种领导我们该怎么办呢?多数人想到的是跳槽,这确实是一个解法,但你跳到下家公司也保不齐会有这样的领导呀,今天咱们讨论的这个话题就先把条件限定成你不能跳槽,这个时候你该采用什么方法让自己的上班体验变好一些。


多元化自己的评估标准


首先,不能用鄙视的眼光去看待你的领导,觉得他只会舔老板(能舔、会舔也是一种很强的能力呀),有的时候你觉得你领导能力不行,很有可能是因为你的能力评估标准太单一了。


他或许在工作的某个方面不如你,但是他必定在某些方面有自己的长处,努力发现他的长处,认可他的长处,学习他的长处,可以更有助于你和他的相处,也有利于你的进步。


社会是一个大熔炉,你需要的不仅仅是业务能力和展现的舞台,也需要与社会中不同个体的和谐共处。包容、接纳,都是立身处世的能力。


学会变通和沟通,满足领导的存在感


领导之所以会在很多工作上提意见、瞎指挥、乱指挥,更多的情况可能是他知道自己对工作不熟悉,但觉得自己是领导,会有自己独特的见解,想刷自己的存在感。这种情况下,要学会满足领导的存在感。


举个例子说,你在工作中,领导过来给你提了个意见,这个意见明显是不合适的,那你就可以说,“领导,这个思路好,我们之前没往这个角度想,可以从这个角度延展一下……。”他走了,还不是我们自己把控,毕竟他只是过来刷个存在感的,只要最后的方案让客户满意,业绩给领导,把一些光环放在他身上,让他觉得他起到了作用,这些方案和他有关,他通常也不会计较了。


摸清领导管理的思想和套路


说到这里,找到领导心中的关键因素,是非常必要的。在一个项目里,员工承担的通常只是局部,而领导看的是整体,由于高度不同,所以你们考虑的关键因素是不同的。


所以你要知道领导心里到底想要的是什么,提前做好这方面的预期和准备,以及针对领导提出的你们没有考虑到的方面要虚心接受(毕竟领导跟高层接触的更多,有些战略方向上的事情他们会更清楚)。 


比如说,你是一个小编,你在意的是按时完成写作任务、及时发表、赚取眼球,而你的领导主编可能更在意的是你文章的各种数据真实性、转化人群、是否会产生舆情、是否zzzq这些。所以,要搞清领导在意的重要维度,工作才能更有效。


这里有三句话分享给大家:



  • 要能够分清你可以改变的事、无法改变的事;

  • 不去抱怨你服务改变的事;

  • 把精力用在你可以改变的地方。


你的上司,是你改变不了的,但你自己,是可以把握的。当然这篇文章也不是教你怎么委屈自己,只是提供一个不同的角度来讨论"领导不行” 这个事情,以及让你在无法立刻更换环境时,该怎样让当前的环境变得不那么恶劣。


想跳槽的同学还是应该按部就班的准备,骑驴找马有更合适的地方该跳就跳,跳过去了说不定今天学到的这些还能用的上……。


作者:kevinyan
来源:juejin.cn/post/7357911195946336293
收起阅读 »

留在家乡还是 奔向一线城市

前言导读 这个专题主要是发表一些生活的感想感悟,言论主观得一塌糊涂哈 各位网友有自己的想法都可以分享发出来。 为什么要写这个话题 1 第一个原因 父母养老问题 父母养老的问题, 相信各位北漂 深漂的游子都比较有感触吧。 自己远在深圳 广州 北京 上...
继续阅读 »

前言导读


这个专题主要是发表一些生活的感想感悟,言论主观得一塌糊涂哈 各位网友有自己的想法都可以分享发出来。




  • 为什么要写这个话题




  • 1 第一个原因 父母养老问题



父母养老的问题, 相信各位北漂 深漂的游子都比较有感触吧。 自己远在深圳 广州 北京 上海 等地工作。一旦有个什么事情,自己也很难第一时间赶回去处理。即使回来了 也是各种奔波。身心巨累,所以这时候很多人,在这个时候犹豫了。是否考虑回去。




  • 2 第二个原因 生活节奏



经历过一线城市高压力快节奏的生活,自己再次回到小县城,之后那种骑着小电驴能到处游玩。 能够干着轻松的工作,能陪伴自己的家人。这种鲜明对比 好似在疫情之后房价暴跌之后愈发明显。 好像去到省城 去到一线城市,不再是我们唯一的选择。似乎对了很对选择。




  • 3 第三个原因 居住环境和工作环境



工作的规划的居住问题, 相对一线贵的让头疼的房价 小县城好像也有他独特的优势。虽然在工作靠的
更多的是人情关系, 人情世故貌似是这样的必修课,当然这些也是一部分年轻人比较反感的, 这个见仁见智, 有些人被迫接受这些, 有人反感这些就远离这些。这都很正常。


具体自己所见 所闻


从一线新家回去一次的经历


image.png


image.png


image.png


image.png


image.png


上图可以看到 回一次家过程非常曲折 新塘站 - 广州白云站 -武昌火车站- 汉口北地铁站 - 红安城乡公交总站。经历了 九九八十一难 我才到达老家。感觉非常折磨人, 此刻我是真的有点动摇了,是否自己几年的前的决定是个错误的决定。 有时候也感慨每年就回来那么两次。忍忍就好了,一切都会过去。


和人交流想法转变


但是这次回来住在表弟家里就在晚上的时候,吃完晚饭 ,去他堂哥家里 休息了,也是交流了很多, 也都是程序员但是他们选择不去武汉留下老家了, 可以避开那些快节奏的生活和高压力。 虽然收入相对可以减少些。 当时就很感触这个不正是我们想要的稳定的生活吗。 可能 20 多岁的时候, 那时候想着有很多很多目标, 几年几年做到某一个高度。 要是实现的自己的某个目标啥的, 但是我们发现,那些真的能成的毕竟是少数, 大多数人还是选择了相对平衡和稳定的生活。


抉择问题


各位好像都有遇到我们,高考要上什么样的学校,选择什么样的专业都不是自己选的,都是父母为自己选择,好像都没有自己为自己的人生真正负责过, 等自己长大了,有一些成就后,想定居哪里以后在哪里发展, 和什么样的人结婚生儿育女,好像都是父母长辈替我们决定的。 这时候内心就会有很多疑问,只要自己决定还是说,尊重父母意见, 以前我很不理解一些选择, 为什么可以留在一线还要回去,现在才知道有部分是权衡利弊的选择,有部分人也只是为了选择而选择 或者是为了父母的选择,
父母都只想子女留在身边(这个想法个人看来有点自私,但是也是目前现状),经历这么多的事情,也是切身感受到,人跟人观念的不同,我们既要找到和我们同频共振的那部人人, 同时也要能接受和尊重那些跟我们不同观念人,因为这个世界允许存在差异,正是因为存在差异才会有不同的观点想法。 也会让我们能多思考怎样能过好自己的生活 ,怎样才能规划好自己的未来。


小县城慢节奏优点


小县城没有快节奏 高压力,相对比较适合那些慢节奏生活的人, 也方便照顾年迈的父母, 如果一旦一个什么事情都可以随时有个照应。这些都是在一线头痛的事情。


image.png


image.png


一线职业发展优点


一线城市有更多的就业机会,和各种线下活动。技术交流,能让自己职业发展更上一层楼。也能节假日去看看各种游玩地区 。


image.png


image.png


image.png


最后总结:


无论是选择留在家乡小县城还是奔向一线城市,我们都要承担对应带来的负面的后果,也能享受对应能带来的福利。这些就看自己选择,如果像我这样的情况父母身体真的很差,建议可以送去敬老院 然后自己再去拼搏 或者留在自己身边 这个看父母自理能力和我们自己是否愿意长期照顾年迈的父母了,以上的观点都是我自己主观分享不代表所有人 各位可以结合自己实际情况做出最佳选择。也预祝各位 2024 国庆节快乐祖国成立 75 周年普天同庆。


作者:坚果派_xq9527
来源:juejin.cn/post/7421185520339124287
收起阅读 »

当我入手一台 MacBookPro 之后

从 13 年实习开始,开发环境从 Ubuntu 转战 MacOS,中间换了好几次电脑,每次都是直接用 Mac 自带的 Time Machine 来迁移数据,仅需一块移动硬盘或者一根 type c 线,经过一个晚上的数据迁移,第二天就可以用新电脑工作了,除了配置...
继续阅读 »


从 13 年实习开始,开发环境从 Ubuntu 转战 MacOS,中间换了好几次电脑,每次都是直接用 Mac 自带的 Time Machine 来迁移数据,仅需一块移动硬盘或者一根 type c 线,经过一个晚上的数据迁移,第二天就可以用新电脑工作了,除了配置升级了,几乎感受不到换电脑的乐趣,并且升级过程中,也积累了不少系统升级的旧疾,这次从Intel芯片到 M3 Max 芯片,我打算从零开始,重新蒸腾一番,顺带更新一下工具库,说干就干,Go!
先介绍下新电脑的配置



  • 太空黑:从经典的银色、到太空灰,这次体验一下太空黑

  • 14 寸:用了大概 3 年的 14 寸,就一直用 15/16寸,因为这台不是用于办公,考虑携带方便,所以入手 14 寸(大屏幕肯定爽,但是在家主要也是外接显示器)




  • M3 Max:想要体验一下本地大模型,直接入手 Max(找个借口🤐)

  • 64G 内存:一直有在 macbook 上装虚拟机(Parallels Desktop)运行 Windows的习惯,升级了一下内存

  • 2TB SSD:以前 512 的时候,由于各种 npm 包、docker 镜像,还是隔一段时间就要重启一下、硬盘清理等方式来释放空间,一步到位





后面还换过几台,从最开始的 touchbar ,蝶式键盘,再到取消 touchbar,这时候更多的是工作工具的更换,连拍照的激情都没有🥱🥱


开发工具


科学上网工具


作为开发,第一件事情是需要一个趁手的科学上网工具,不然类似下载 Google Chrome、安装 Homebrew 等基础的工具都会十分麻烦
我的科学上网工具,支持按照规则配置自动代理,同时也支持终端代理,以下是终端代理,这里不方便推荐具体工具


# 防止终端命令无法确定是否需要科学上网,不建议把这个命令持久化到 bashrc/zshrc,在需要时打开终端输入即可
export https_proxy=http://127.0.0.1:1235 http_proxy=http://127.0.0.1:1235 all_proxy=socks5://127.0.0.1:1234

Xcode


Xcode 命令行工具,许多开发工具和依赖所需的基础,运行一下命令,选择安装,稍等一会即可


xcode-select --install

Homebrew


通过 homebrew 来管理一些开发工具包,如 git、node等等,由于需要下载 github 地址,这里需要借助你的翻墙工具


/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

然后按照提示,把命令加到 PATH


(echo; echo 'eval "$(/opt/homebrew/bin/brew shellenv)"') >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

Git/Tig


brew install git
# brew install git-gui
# brew install tig 个人是 vim 用户,偏向这种终端 gui
# 这里会自动安装 zsh 的自动补全工具,后续安装 zsh 可用
# /opt/homebrew/share/zsh/site-functions

安装 git 和 tig 都会默认新增 zsh 的补全方法,好吧,这是提醒我要立马安装 zsh
tigrc 可以用来自定义 tig 的一些配置和快捷键绑定


A sample of the default configuration has been installed to:
/opt/homebrew/opt/tig/share/tig/examples/tigrc
to override the system-wide default configuration, copy the sample to:
/opt/homebrew/etc/tigrc

zsh completions and functions have been installed to:
/opt/homebrew/share/zsh/site-functions

在任意已经初始化 git 的项目,打入 tig ,然后你就可以使用 vim 的方式来操作了 jk 等等

另外,使用 git 我还会额外安装两个 git 相关的小插件
一个是 tj 大神开发的 git-extras


brew install git-extras

# 添加到 ~/.zshrc
source /opt/homebrew/opt/git-extras/share/git-extras/git-extras-completion.zsh

详细的命令可查看文档,我比较常用了是 git summary、git undo、git setup

然后通过git 的 alias 来实现一个自定义的命令,git up 来实现每次切换到一个仓库时,有意思的更新一下最新代码


git config --global alias.up '!git remote update -p && git pull --rebase && git submodule update --init --recursive'


iTerm


实现通过 command + ecs 键,快速切换显示/隐藏 iTerm



  • 设置默认终端

  • 安装 shell integration




  • 选配色:Solarized

  • 安装 oh-my-zsh


sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"


  • 修改主题,配置插件等等


brew install zsh-syntax-highlighting
brew install zsh-autosuggestions
brew install autojump
brew install fzf

#ZSH_THEME="robbyrussell"
#ZSH_THEME="agnoster"
#ZSH_THEME="miloshadzic"
#ZSH_THEME="sunrise"
# ZSH_THEME="ys"
ZSH_THEME="gnzh"

plugins=(git ruby autojump osx rake rails lighthouse sublime)
plugins=(bgnotify)
plugins=(web-search)
plugins=(node)

source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh
[ -f /opt/homebrew/etc/profile.d/autojump.sh ] && . /opt/homebrew/etc/profile.d/autojump.sh
source <(fzf --zsh)

这就起飞了!看看效果


Docker Desktop


作为开发,docker 技能必不可少,mac 下直接使用 docker desktop,可以省掉很多事情,特别是当你如果需要在本地跑 k8s 环境时,直接勾上 Enable Kubernetes 即可;另外新版本查看镜像,也可以扫描镜像每一层是否有安全漏洞,十分方便


VSCode


vscode 不仅适合前端开发,对于 Go、Rust 等开发者,整体体验都不错。安装后的第一件事,就是把 code 命令加到 Terminal

然后第二件事就是安装 github 的 Copilot 插件,开发、看源码现在是少不了它了

第三件事就是在 vscode 开启 vim 模式(安装 vim 插件)从 vim - sublime - vscode,一直保留 vim 的使用习惯,改不掉了😂
其他的就是各种高亮、开发辅助插件,大家按需安装即可


其他



  • 前端

    • NVM:node 版本管理

    • pnpm:上一台电脑只有 512G,在动不动就几个 G 的前端项目,硬盘一直告警,至此只用 pnpm



  • Go

    • GVM: go 版本管理

    • GoLand:虽然 vscode 可以开发 go,但是整体体验还是比不上收费的 goland




环境搭建可参考 Go + React 指北


效率工具


ChatGPT


说到效率工具,ChatGPT 绝对是提高效率神器,从日常问题到开发、图片生成、画图等等,哦,还可以帮忙挑西瓜🍉


对了,用了苹果芯片,ChatGPT app 直接通过 option + space 即可随时调出,支持实时对话、支持截屏问问题等等,好用程度大幅上升⬆️⬆️⬆️


Bartender($)


吐血推荐,让你状态栏更加一目了然
支持自定义显示哪些 icon,配置哪些 icon 始终不显示,哪些第二状态栏显示

快捷键切换第二状态栏,一下子清爽颜值高


iStat Menus($)


拥有时刻关注着网速、CPU使用率、内存使用率的强迫症,绝对不少


Yoink


当你要把一个文件从一个地方移到另外一个地方时,当你想快速复制一张图片时,剪切板记录、跨设备文件接力等等,这个小小的工具都能帮助你
有时候通过截屏软件截图,可以一次性把需要的截屏操作完,然后在剪贴板直接拖下来使用,十分方便!


BettersnapTool


一款小而美的工具,用来快速调整你的窗口,比如当前窗口在两个显示屏直接切换;全屏,左右分屏等等


iShot


本来我一直使用 Skitch 的,但是这次切换新电脑之后,发现它下架了,之前有朋友推荐了,也使用了一段时间,感觉很不错,除了普通的截图,还有长截图、带壳截图;还有其他的小工具,官方宣传是
截图、长截图、全屏带壳截图、贴图、标注、取色、录屏、录音、OCR、翻译一个顶十个,样样皆优秀!


Draw.io


好吧,这个绝对画图神器,日常写文档几乎离不开他,在线版直接打开即可使用,也可以安装 vscode 插件,不过我还是习惯下载一个 app,这样本地的文件,直接打开即可使用
距离成为架构师,你只差一个 draw.io


Parallels Desktop


如果你有使用 Windows 的诉求,那么我建议你花点钱买个 pd,融合模式一开,原来我的 16 年的机器,玩个魔兽、英雄联盟完全没问题
安装直接点击下载 Windows 11,网速好的话,10 来分钟就安装成功了

融合模式,应用和直接使用 mac 的应用没任何差别

全屏模式,可以看到截图的时候有一部分黑色,应该是没有兼容刘海屏的原因


Markdown 编辑器


Quiver,原来所有的笔记、文档基本都靠它来记录,21 年的时候作者停止维护了,再加上使用纯 markdown 工具,还需要自己找图床,最后都转到语雀、飞书等在线文档
中间还用过其他的、Mweb、Typora 等等,如果自己搭建图床,推荐使用 Typora


image-20240802223626203


image-20240802223739246


image-20240802223841320

Mweb 包含PC 和 移动端,通过 iCloud 同步,也是十分方便!


图床工具


原来写 markdown 的时候,使用的是微博免费的图床,2 年后,然后发现图片都失效了!失效了!


所以,图床还是自己维护比较靠谱!


PicList,免费开源,我自己是购买了阿里云按量付费的 OSS,简单配置一下 aks,即可上传图片,配合 Typora,轻松完成写作


image-20240802224136523


配置好之后,图片拖到 markdown 编辑框,即可实现自动上传


image-20240802224555813


BreakTime 定时提醒工具


为了你的健康,你可以让电脑提醒你,每隔30分钟休息一下,倒杯水,看看风景


DaisyDisk (付费)磁盘空间,文件大小分析工具


作为只能买 256G 的屌丝,每天困扰我的一件事就是磁盘空间不足
现在我是 2T 了,可以不用了
也可以使用 腾讯柠檬用过一段时间,也很好用


微信输入法


搜狗输入法、RIME、百度输入法(作恶多端,还用)
上一次推荐,我还是使用搜狗输入法,有朋友推荐微信输入法,体验了一把,简洁、功能齐全,所以手机、PC 全部改用微信输入法
推荐跨设备复制黏贴,速度比苹果自带的快了许多


思维导图:Xmind, MindNode


脑图应用,一般在项目开发过程中用于 需求分解,Model Design 等等。


其他小应用



  • Caffeine

  • Manico, 在 macOS 强大的触摸板下,一直认为这个软件没什么用, 而且快捷键还有很多冲突

  • tmate, 搞基神器,结对编程,定位问题必备神器


Chrome 插件推荐



  • Vimium, 通过键盘快捷键操作网页,比如打开,关闭,查找书签等等

  • FeHelper(前端助手):JSON自动格式化、手动格式化,支持排序、解码、下载等,更多功能可在配置页按需安装

  • Axure RP Extension for Chrome

  • Grammarly for Chrome,语法检查

  • Octotree,github源码查看神器

  • OneTab,节省高达95%的内存,并减轻标签页混乱现象

  • Postman Interceptor

  • React Developer Tools

  • Redux DevTools

  • Yet Another Drag and Go:超级拖拽.向四个方向拖拽文字即可进行相应的搜索.拖拽链接可在前台/后台,左侧/右侧打开

  • 掘金

  • Sider: ChatGPT 侧边栏 + GPT-4o, Claude 3.5, Gemini 1.5 & AI工具”的产品徽标图片 Sider: ChatGPT 侧边栏 + GPT-4o, Claude 3.5, Gemini 1.5 & AI工具

  • xSwitch:前端开发代理神器,在线 debug 问题,把线上资源代理到本地,方便复现问题


作者:Justin_lu
来源:juejin.cn/post/7398351048777842729
收起阅读 »

BOE(京东方)“向新2025”年终媒体智享会落地深圳 “屏”实力赋能产业创新发展

12月27日,BOE(京东方)“向新 2025”年终媒体智享会的收官之站在创新之都深圳圆满举行,为这场为期两周、横跨三地的年度科技盛会画上了完美句号。活动期间,全面回顾了 BOE(京东方)2024年在多个关键领域取得的卓越成绩,深入剖析其在六大维度构建的“向新...
继续阅读 »

12月27日,BOE(京东方)“向新 2025”年终媒体智享会的收官之站在创新之都深圳圆满举行,为这场为期两周、横跨三地的年度科技盛会画上了完美句号。活动期间,全面回顾了 BOE(京东方)2024年在多个关键领域取得的卓越成绩,深入剖析其在六大维度构建的“向新”发展格局,精彩呈现了以“屏”为核心搭建起的技术引领、伙伴赋能以及绿色发展等平台,全方位赋能全球生态合作伙伴,充分彰显BOE(京东方)作为全球领先的物联网创新企业的引领地位与责任担当。深圳活动现场,BOE(京东方)执行委员会委员、副总裁刘竞以及 BOE(京东方)副总裁、首席品牌官司达亲临现场,发表了主旨演讲。此次系列智享会的成功举办,进一步加深了与会嘉宾对 BOE(京东方)发展理念、技术实力与创新成果的认知和理解,也为BOE(京东方)新一年的发展拉开了充满希望和活力的序幕。

经过三十余年创新发展,秉持着对技术的尊重和对创新的坚持,在“屏之物联”战略指导下,BOE(京东方)从半导体显示领域当之无愧的领军巨擘迅速蝶变,成功转型为全球瞩目的物联网创新企业,并不断引领行业发展风潮。面对下一发展周期,BOE(京东方)将从战略、技术、应用、生态、模式、ESG六大方面全方位“向新”突破,以实现全面跃迁,并为产业高质发展注入强劲动力。

战略向新:自2021年“屏之物联”战略重磅发布以来,BOE(京东方)又于2024年京东方全球创新伙伴大会(BOE IPC·2024)上发布了基于“屏之物联”战略升维的“第N曲线”理论,以半导体显示技术、玻璃基加工、大规模集成智能制造三大核心优势为基础,精准布局玻璃基封装、钙钛矿光伏器件等前沿新兴领域,全力塑造业务增长新赛道。目前,玻璃基封装领域,BOE(京东方)已布局试验线,成立了玻璃基先进封装项目组,实现样机产出;钙钛矿领域,仅用38天就已成功产出行业首片2.4×1.2m中试线样品,标志着钙钛矿产业化迈出了重要一步。

技术向新:2021年,BOE(京东方)发布了中国半导体显示领域首个技术品牌,开创了产业“技术+品牌”双价值驱动的新纪元。以技术品牌为着力点,BOE(京东方)深入赋能超5000家全球顶尖品牌厂商和生态合作伙伴,包括AOC、ROG、创维、华硕、机械师、雷神、联想等,助力行业向高价值增长的路径迈进,也为用户提供了众多行业领先、全球首发的更优选择。BOE(京东方)还将全力深化人工智能与半导体显示技术以及产业发展的深度融合,并在AI+产品、AI+制造、AI+运营三大关键领域持续深耕,并依托半导体显示、物联网创新、传感器件三大技术策源地建设,与产业伙伴和产学研合作伙伴共同创新,为产业高质量可持续发展保驾护航。

应用向新:BOE(京东方)不仅是半导体显示领域的领军企业,也是应用场景创新领域的领跑者,BOE(京东方)秉持“屏之物联”战略,以全面领先的显示技术为基础,通过极致惊艳的显示效果、颠覆性的形态创新,为智慧座舱、电竞、视觉艺术、户外地标等场景注入了新鲜血液,带给用户更加美好智慧的使用体验。以智慧座舱为例,根据市场调研机构Omdia最新数据显示,2024年前三季度BOE(京东方)车载显示出货量及出货面积持续保持全球第一,在此基础上BOE(京东方)还推出“HERO”车载场景创新计划,进一步描绘智能化时代汽车座舱蓝图。

生态向新:BOE(京东方)持续深化与电视、手机、显示器、汽车等众多品牌伙伴的合作,共同打造“Powered by BOE”产业生态集群,赢得众多客户的认可与赞誉。与此同时,BOE(京东方)还持续拓展跨产业生态,通过与上海电影集团、故宫博物院、微博等文化产业领先机构展开跨界合作,以创新技术赋能传统文化艺术与影像艺术。此外,通过战略直投、产业链基金等股权投资方式协同众多生态合作伙伴,通过协同合作、资源聚合共同构筑产业生态发展圈层。

模式向新:为适配公司国际化、市场化、专业化的长远发展,BOE(京东方)持续深化“1+4+N+生态链”的业务发展架构,以及“三横三纵”组织架构和运营机制。在充分市场化和充分授权的机制保障下,形成了以半导体显示核心业务为牵引,传感、物联网创新、MLED业务、智慧医工四大高潜航道全面开花,聚焦包括智慧车联、工业互联、数字艺术、3D光场等规模化应用场景,生态链确保产业上下游合作伙伴协同跃迁的“万马奔腾”的发展图景。此外,BOE(京东方)还鼓励员工创新创业,通过激发人才创新热情,共同为集团发展注入强劲内生动力。

ESG向新:2024年,BOE(京东方)承诺将在2050年实现自身运营碳中和,并通过坚持“Green+”、“Innovation+”、“Community+”可持续发展理念,推动全球显示产业高质永续发展。“Green+”方面,BOE(京东方)依托超过16 家国家级绿色工厂、显示领域唯一1家国家级无废工厂、1 座灯塔工厂及2座零碳工厂,以绿色产品、制造与运营践行低碳路径;“Innovation+”方面,BOE(京东方)凭借全部为自主创新的9万件专利的行业佳绩,以及技术策源地、技术公益池等举措,携手产业上下游伙伴协同创新;“Community+”方面,BOE(京东方)在教育、医疗、环境等公益领域持续投入,积极履行社会责任,例如在“照亮成长路”公益项目中,BOE(京东方)十年间在偏远地区建设的智慧教室已经突破120所。

BOE(京东方):屏即平台赋能创新

在新一轮数智化浪潮中,全球显示行业的龙头企业 BOE(京东方)以屏为核心,充分发挥技术引领作用,积极赋能合作伙伴,并秉持绿色发展理念,全力构建产业高质量、可持续发展的创新生态平台,引领行业在高速发展的科技浪潮中稳步前行,为全球用户缔造更加智能美好的生活体验。作为 BOE(京东方)全球创新生态布局的关键一环,珠三角区域不仅是其创新要素汇聚的高地,更是其全球化发展的重要窗口与强大驱动力,为“屏之物联”战略落地提供了有效支撑。

技术引领方面,BOE(京东方)多年来始终秉持对技术的尊重和对创新的坚持,致力于推动显示技术全面向新发展,以完美画质、AI+显示、无界形态、氧化物(Oxide)关键技术等关键领域,持续挖掘“屏”在物联网领域的无限潜力。

完美画质,BOE(京东方)深入洞察用户真实需求,基于ADS Pro技术优化升级的高端LCD解决方案UB Cell,所呈现的完美画质可以媲美OLED,堪称LCD显示技术发展的重要里程碑。目前,BOE(京东方)已携手合作伙伴推出了一系列搭载UB Cell高端液晶电视旗舰产品,引领液晶显示技术升级风向标;

AI+显示,BOE(京东方)在软硬件层面均已为AI的深度应用构筑完美平台,不仅实现了光感、温感、NFC等传感器件的屏内集成,还开发了屏幕局部刷新、远端功能监测等软件技术,显著增强了用户感知,提升了交互体验;

无界形态,BOE(京东方)作为国内在柔性OLED领域布局早、技术优、市场应用广的领军企业,在材料、工艺等领域具备全面优势,不仅在屏幕轻薄化、超清化方面性能卓越,更能够实现折叠、卷曲等形态变化,同时不断探索屏下摄像、屏下指纹、3D touch等多功能的智慧集成,更是打造出行业首款三折屏等具有行业里程碑意义的产品,带领用户迈入更多变、更智能的未来生活;

氧化物技术,BOE(京东方)在产能、技术以及产品性能上均位居行业领先地位,凭借高刷新率、高分辨率、低功耗等优势,在未来高端IT产品领域展现出广阔的应用前景。

伙伴赋能方面,BOE(京东方)始终以合作共赢为宗旨,高效整合资源,与生态伙伴携手向新发展,共筑高价值发展空间。BOE(京东方)坚持第一时间捕捉行业及市场需求动向,通过内部研发及运营保障机制,完成技术开发应用,组织建设和人才培养,完善流程、数据、组织以及IT能力建设,输出市场化、专业化、国际化的服务能力,并联动上下游及科研机构等生态伙伴,共同探讨“以人为本”的最优解决方案,深度拓展更多高端应用场景;同时,持续进行智能制造实践探索,确保稳定交付,赋能终端伙伴,使其能更好融入更真实、更丰富的消费者使用场景,实现产业高价值增长。

绿色发展方面,BOE(京东方)早已将可持续发展刻入企业基因,融入企业日常经营与管理的全链路,从绿色规划、低碳设计到碳足迹量化认证等各个环节,全力实现极致降碳目标。原材料环节,BOE(京东方)通过打造绿色供应链,积极使用可回收、可降解以及清洁材料,为产品低碳化发展奠定坚实基础;生产制造阶段实现全面绿色低碳;产品流通及回收阶段,BOE(京东方)已完成49个产品的碳足迹认证,凭借可回收、可降解的绿色材料,在产品的全生命周期中均实现了最大化降碳,让“科技创新+绿色发展”成为产业升级的主旋律。

“向新2025”年终媒体智享会,是BOE(京东方)2024创新营销的收官之作和全新实践,系统深化了大众对BOE(京东方)品牌和技术创新实力的认知与理解。近年来,BOE(京东方)通过多种创意独具的品牌破圈推广,包括“你好BOE”系列品牌线下活动、技术科普综艺《BOE解忧实验室》等生动鲜活地传递出BOE(京东方)以创新科技赋能美好生活的理念,为企业业务增长提供了强大动力,也为科技企业品牌推广打造了全新范式。BOE(京东方)“向新2025”主题系列活动已先后于上海、成都、深圳成功举办,为BOE(京东方)2024创新传播划上圆满句号。

面向未来,BOE(京东方)将胸怀“Best on Earth”宏伟愿景,坚持“屏之物联”战略引领,持续推动显示技术和物联网、AI等前沿技术的深度融合。从提升产品视觉体验到优化产业生态协同,从升级智能制造体系到践行社会责任担当,BOE(京东方)将砥砺奋进、创新不辍,为全球用户呈献超凡科技体验,领航全球产业创新发展的新篇章。

收起阅读 »

坚持背单词2000天,能带来哪些变化

近三年前,我写了一篇背单词坚持1000天的小总结,从三个方面整理了这坚持给我带来的变化: 一是背单词最本质用处,它让我认识的英文单词多了许多,我甚至能试着看看英文原版书;二是一种心理暗示,既然我能坚持每天背单词,那看书、摸球或是锻炼,也将是可以坚持的;三是多出...
继续阅读 »

近三年前,我写了一篇背单词坚持1000天的小总结,从三个方面整理了这坚持给我带来的变化:


一是背单词最本质用处,它让我认识的英文单词多了许多,我甚至能试着看看英文原版书;二是一种心理暗示,既然我能坚持每天背单词,那看书、摸球或是锻炼,也将是可以坚持的;三是多出一种对未来的展望,我很期待,背单词到2000天、5000天时我的改变。


图片


打卡1999天


时间过得真快,过完明天,我坚持背单词便满2000天了。


但当下的我,却想对1000天前的我说一句抱歉:“对不起,似乎你未来1000天的坚持,并没有为你带来更多改变,你还是你。你英文的听说读写,似乎和当时水平相差并不太远,你也并没有发展出更多的小坚持……”


当然不只有抱歉,“除了抱歉,你依然还拥有着希望。”


即便当下感受到的改变不多,我依然很期待,第3000天、第4000天、第5000天时我的改变。


从坚持背单词满1400天时起,之后的每一个100天,我都在掘金沸点上面打个卡,我很享受那种一天一天慢慢攒出一个整数值后有东西可以分享的感觉。其中,有一些骄傲,也有一点虚荣。


在第1800、1900天时,我便起了心思到2000天时再写一篇小总结。之后大概每隔两三周,我都会想一想这总结中该写些怎样内容:


“考研词汇对我来说很简单,进入复习阶段,一天背80个,有时候前面一个不错,到最后几个单词,我会很紧张。”


“背单词的时间,一直在变化。大多数是在出门等电梯,独自吃早餐或是吃完早餐后赶地铁、去工位的步行路上。那些长一些的独处时间,我更愿意用来看书,而不是背单词。”


(边走路边看手机,当然是一个不好习惯。)(于此处批评自己让我想到我会在某些时候进入“成人状态”,会对他人提出建议如“少喝些酒”“早点睡”之类,我隐隐感觉这种建议方式似乎正让我的行事方式越来越固化,晚点睡怎么了嘛?)(我简直要变成括号之神。最后这个括号说我思维杂乱,发散太快:我到底要不要删掉这几个括号呢?我决定不删。)


“《百词斩》,一直在进步。除了广告越来越多之外,他们还推出专门背单词的学习机,他们在单词详细界面添加了近义词、反义词、形近词;除了看图选词,他们还推出了汉译英、听音选词等更多帮助强化记忆的方式。”


“好几位朋友问过我,你背单词的目的是什么?出国?和外国人交流?还是怎样?我当时给予的答复都是:‘好像都不是,背单词对我来说只是一种提醒,提醒我别忘记坚持’。”


“背单词对我来说,似乎也变成跟吃饭睡觉一样,是每天都会做的事情,已经可以不用‘坚持’就能持续下去,这使得我已经连续561天不间断。这种稀疏平常的每日行为,似乎没什么总结好写的。”


一周以前,我决定本周公众号的更新主题为“坚持背单词2000天”,翻阅过去想法的汇集,却只有上面寥寥几句。这几句,只是一些零碎想法,当下的我似乎想要表达更多些。我在碎片时间想这个话题,最终冒出一个稍显负面但我却很想知道答案的问句:坚持背单词这么久,是为了什么呢?


这问题甚至被我更扩大些:坚持看书是为了什么?坚持写东西是为了什么?总之,坚持,是为了什么呢?


我将自己过去靠坚持开始到现在还一直在持续的事情一一列举。


坚持最久的一件事,是每天都写日记,它始于15年10月,累积8年半时间。我最初的想法,是“记录生活中的事情,以作总结用”,日记中片段被我用作总结的很少,它更多的用处,是帮我回忆起许多忘记的场景和情绪。


坚持背单词,2000天。


坚持周更公众号3年半。周更公众号,最初是为了获得睡后收入,睡后收入每天只有几分,但它慢慢变成我生活中很重要的一部分,我较远些的目标,是想着写一本完整的延续性强的细节丰富的诉说平凡的书。写公众号为收入,为练习写作,也为总结整理自己。是的,相较日记里的随意,公众号内容是更可以称之为总结的:我想得更多也尽量想得更广更深。


坚持每天看书近3年,《微信阅读》上的记录是“连续阅读581天”。我最初看书是为了让自己有深度,让自己公众号有内容可写,现在则演变成一种爱好,一种习惯;这爱好有些不好之处是它让我很依赖作者的想法,“作者在我脑中跑马”是常发生情况。


坚持每天11点前睡觉,一年又11个月。这习惯的养成,多少和读书有些关系,首先来自于从Why We Sleep中收获的认知:睡眠好了一切都好(我又多出一点自己的理解:睡眠好说明一切都还好);然后是《习惯的力量》,暗示、惯常行为和奖赏围成一个圈,早早洗漱暗示自己想要睡觉,准点睡觉是惯常行为,第二天醒来能量满满便是奖赏。


每天做10个俯卧撑,坚持20天。每天做俯卧撑这件事情,我尝试过很多次,也放弃过很多次,今年过年回家吃饭毫不控制导致肚子又大起来,我想借这坚持作为锻炼的入门提醒。


写日记、背单词、周更公众号、看书和睡觉,是我一直在坚持做着的事情。所以,坚持的意义到底是什么呢?


对书籍已经产生依赖的我想去找一找关于“坚持”主题的图书,简单搜索一番后没找到答案,于是只输出自己的当下理解。


首先,它是一种价值观的体现,我相信这每天的一点点积攒,是肯定会为我带来些提升的,不管是读写能力还是好的身体状态的更长时间延续。


其次,是一种人生不会停止的希望,我坚持做的这些事情,不管四十五十甚至六十七十岁,都可以一直进行下去;我很期待看到五年十年甚至三十年后还依然做着这些事情的自己的样子。


然后,是提醒自己思考的工具,我该为这每一份坚持加上一个目标一个方向?比如背单词是为考研或是雅思?是不是没有方向的坚持,只是在原地转圈呢?


然后,就没有更多想法了,我只告诉自己,且继续将这几件当下自己认为对自己有用事情坚持下去。


至于坚持的意义,等下一个1000天到来时,再想想。


作者:我要改名叫嘟嘟
来源:juejin.cn/post/7352091152584376331
收起阅读 »

三十而立却未立,缺少的是女朋友还是技术能力?

作为一个从事 Web 工作 8 年来的相关人员的一点心路历程,希望我的经历能给大家带来稍许乐趣。 迷茫,特别迷茫 俗话说得好:“岂能尽如人意,但求无愧于心”,工作 8 年来,我经常这样自我安慰。不过这并不影响我也经常感觉无所适从,烦闷与迷茫。尤其是到了一些特殊...
继续阅读 »

作为一个从事 Web 工作 8 年来的相关人员的一点心路历程,希望我的经历能给大家带来稍许乐趣。


迷茫,特别迷茫


俗话说得好:“岂能尽如人意,但求无愧于心”,工作 8 年来,我经常这样自我安慰。不过这并不影响我也经常感觉无所适从,烦闷与迷茫。尤其是到了一些特殊的年月节点,这种焦虑感总是更加强烈。


那到底有什么迷茫的呢?一言以蔽之,有了对比,就有了伤害。正如标题所言,女朋友和技术能力,换一个通俗的话,也可以叫“美女与金钱”,当然更常规的说法,是“家庭与事业”。


如果简单横向对比起来,我迷茫确实看起来不意外:



  • 我好歹也是正儿八经 985 大学软件工程方向本科毕业,也算是科班出身;

  • 工作了 8 年,不仅是被同学、绝大部分同行从业人员从薪资水平、发展前景、人际交往、生活质量等各方向甩在身后,甚至都比不上复读一年考上不知名二本学校、去年才毕业的表弟;

  • 没房没车,没有成婚,还背井离乡,漂泊千里之外;

  • 日子看起来浑浑噩噩,没有什么远大志向,也没什么乐衷的兴趣……


怎么就变成这样了呢,我觉得我有老老实实、脚踏实地地做事情啊。回想自己从业这些年:



  • 从一开始的 JSP + Spring MVC + MySQL 这套原始的 Java Web 开发;

  • 到当时外面还比较时髦的 MEAN(MongoDB、Express.js、Angular 和 Node.js);

  • 后来回归到 Angular + Spring 这套,然后改为现在常用的 Vue + Spring,其中还一度以为 WebFlux 会有大用;

  • 当然前几年除了做些全栈开发,还不得不兼备 K8s 相关一大套的运维技能;

  • TiDB、Redis、ES、Prometheus 什么的都要搞一搞,Flink 什么的也得弄一弄,加上一大堆第三方自动化、监控等工具的使用配置;

  • 现在没事时用 Python 写个脚本处理一些批量任务,自己搞搞 Flutter 练手自己用的 APP。


我都觉得自己还是挺厉害的,因为这些就没一个是学校里教的东西,都是出来挨打自学的。


但实际上的现状呢,我还是呆在一个电子厂里面,拿着千把块,做着鸡毛蒜皮的事情,下班就回到公司的宿舍,龟缩起来。这样 855 毫无意义的日子,居然一呆就是 8 年了。


“可怜之人必有可恨之处?”


那我当然是自以为是的可怜了,毕竟如果真得像我说的那样出色,是金子自然会发光了,也怎么可能愿意继续呆在这种地方,离最近的地铁站、火车站都要30多分钟公交的制造业工厂里面?


确实,扯开嘴巴滋哇乱叫谁不会,有什么因就有什么果了。



  • 大四的时候,跨专业自学准备心理学方向的考研,错过了秋招;没考上之后,当时的技术能力,已经不支撑找个满意的工作了。

  • 做中学,两年后的 18 年正是行业发展高潮,准备出去看看。结果年轻,血气方刚,在领导的 PUA 和自以为是没能干出一点功绩就离开,不满意,然后留下来。

  • 又之后的一年之余,已经发现技术水平和人生阅历和同行差距过大,还是骑驴找马。在得到几个 offer 之后,却不知原因突然想回老家城市,这些深圳广州的机会就莫名其妙放弃了,重庆的眼高手低又没找到满意的。

  • 之后疫情时代,在一些大城市比如 SH、SZ 等出现强烈的排外现象之后,越发想要回家。但重庆的互联网行业,和主流城市差距可太大了。当时当地政府甚至在大力发展实体制造业,老家区县招商建工厂,租 100 亩送 100 亩。

  • 疫情尾期和这两年,什么“前端已死”、行业落寞,找工作难度陡升,试想,什么样的公司会找一个 8 年工作经验的初中级前端?全栈?运维?……


去年我找工作从 5 月份找到 10 月份,沟通了 200 多个岗位,只有 20 多个接收了简历,约到 3 个网上面试,最后一个没过。除了一些需要线下面试的没法去,也有面试的匹配度也不够、岁数不够年轻等其他因素。8 年来最多就管理过不到 10 人的小团队,当然不到一年就结束了,也没有能力发展管理岗。


与自己和解是不是自欺欺人?


会不会有种“咎由自取”的感觉,我偶尔也会想:



  • 如果 18 年我去了深圳而不是听信领导的话留在了东莞这里,我的发展轨迹会不会有所改善?

  • 更有甚,如果大学不是脑袋一热为了自救去考什么心理学专业的研究生,好好学习技能找工作或者考本校,会不会又是另一番风景?

  • 甚至更早,如果当年高考没有发挥失常,或者要是考得更差一点,去个师范,实现我儿时的理想,成为一名教师,情感上是不是更能自洽?


有句网络流行语是这样说的:有人看段子,有人照镜子。曾几何时,我也这样觉得:



  • 反正现在没车没房没女友,离家又远没外债;

  • 物质能力虽不高,但消费欲望不强;

  • 不能为国家做大贡献,但也还没有给社会添乱;

  • 下班回宿舍看看视频、打打游戏、玩玩手机,偶尔出去打打球,散散步……


没有复杂的人际关系,没有太大的家庭工作压力,清闲时间也比较充足,简简单单三餐一宿,我明明很惬意的,也明明已经惬意了 8 年来。


——“你一个月多少工资?” 、“怎么才这点?”

——“你现在什么级别?” 、“怎么才这个级别?”

——“你开什么车?” 、“什么?你连驾-照都没有?”

——“你孩子几岁了?” 、“啊,你还单身?”

——“天啦,你怎么混成这样了?”
……


“人的悲喜并不相通,我只觉得他们吵闹”。“墨镜一带,谁都不爱”,我脑袋摇成螺旋桨,我飞走咯,千里之外~


未立,缺少的是女朋友?


我的看法认为:可能不是。


没有什么是一成不变的,比如年龄。我这个年纪可能不仅和更年轻的同行抢岗位抢不过,也可能在另一个相亲市场也抢不过。


虽然嘴巴上可能有的人觉得单身好,而且现在这个男女关系和社会认同比较复杂的时代。前段时候和老同学聊天聊到近况,他们都一直以为我是一个不婚主义者。当然,这并不影响我们老一辈甚至再老一辈亲戚的期盼,他们偶尔也会认为,结婚之后,一个人才成长了,他们才会放心。


你别说,你还真别说。这半年我没有写博客,也没有太多了解“行业寒冬”的发展情况,有一部分原因还真是因为年初聊见了个相亲对象。这对我是一个完全没有经历过的赛道,难得的是我感觉还不差,虽然发展极为缓慢,但还没有遇到网上那样的“悲惨经历”,当然,也可能是异地的原因。


我要经历这种事,只能是亲戚朋友帮忙,加上微信之后聊了聊,整体氛围很好,就这么聊了一个多月。本来过年的时候约个见面的,但没想到升级了,直接他们父母到我家来坐了坐,然后又邀请我父母去她家吃了饭。这在农村的意思就是老一辈的过场已经走完了,双方家长没有意见,我们能不能成、就全看自己了。


这半年虽然几乎天天都有聊,绝大多数情况下都很愉快,我也变得有些期待每次的聊天;平时也有礼尚往来,偶尔互有一些小惊喜小礼物;五一节我也回去见了面,牵牵小手,后来得知当天她出门之后才发现来例假、身体不适但还是陪我走了将近三万步的路、甚至没让我发现异样……


但问题的关键在于,似乎都没有聊到什么重点和关键的问题,没有实际的发展,感觉温度没有理想上升。仔细想想,把这每天和她相关的一两个小时删除掉,那和我这些年的日子几乎没什么区别,好像一样是挺自在惬意的,她甚至都没有给我一些需要我去翻视频学点“人情世故”才能处理的问题和情景。


本来以为是好事,但我的榆木脑袋才终于不得不承认异地一定是个大问题。所以到现在,我这股子想回家的心情就变成了内因和外因相结合的无懈可击的推力。但是却还没有热切到一拍脑袋裸辞先回家,再看天的程度。


未立,缺少的是技术能力?


我的看法认为:可能也不是。


虽然我个人学的东西有一点点乱,但怎么说呢,并不影响我自娱自乐。偶尔开发一个自用的小玩意儿,还盲目觉得挺有成就感。


而且,从实际情况来讲,现在的“技术能力”真的不是那么的重要,如果是做产品,可能一些经验能力也不可或缺,但会写代码的人,可是一抓一大把。


比如说,现在的 AI 大模型几乎是热到爆的话题,也算是百花齐放,也各自杀红了眼,现在的新东西,不说自己有个 AI,都不好意思大声讲话,新出的 PC 都挂上 AI PC,魅族都不做手机,改名为 AI 终端了。


作为普通用户和普通个人开发者角度来讲,现在使用这些大模型 API 其实非常便宜了。价格战百万 token 才几十块甚至几块钱,文本对话、文生图、图生文,也都有一定的可用性了。


但是呢,但是呢,能拿来做什么呢?有创造性的同行都已经借着东风,扶摇直上九万里了,我还在感慨好便宜啊,除了BAT平台,这两天还去零一万物、深度求索等平台注册了账号,部分也少少充值了些。但是,虽然好便宜啊,可是能用来做点什么呢?我还真的没有创造性。




既然都说到这里,也厚脸皮顺便说一句,最近弄了个比较简陋的,使用Flutter开发,支持诸多AI大模型API调用的,假装类似智能工作生活助手应用。顺带加上之前的极简记账、随机菜品、猫狗写真,放在 Github 上 Sanotsu/swmate ,虽然很简陋也不完善,但感兴趣的朋友可以看看。


智能助手功能展示.jpg


生活不需要别人来定义


可能“三十而立”意思是指人在三十岁前后有所成就。少年老成的例子很多,大器晚成的人物也不少,但到最后,这都是别人来定义的这个“立”的含义。


就如见世面,有的人是“周游列国、追求自由”,有的人是“四体勤、五谷分”,有的人的成就是“成家立业,香车美女环绕”,有的人是“著作等身”,也有的人却是成为“艾尔登之王”……外面的人看到的或许不同,但那份自己内心的快乐,是为了、也是应该能够取悦自己的。


今天是我三十岁生日,大概500天前我列了三十岁前想要完成的 10 件小事,结果当然只完成了小部分:



  • 体重减到正常 BMI 值;

  • 开发一个能自用的 APP/入门一门外语;

  • LOL 上个白金/LOLM 上个宗师;

  • 谈一次恋爱;

  • 出去旅游一次;

  • 换一份工作,换一个城市;

  • 补上自己的网站博客,整理自己的硬盘;

  • 看 10 本名著,并写下每本不多于 5000 字的读后感;

  • 完成一部中篇小说;

  • 完成 50 篇用心写的博文,可包含那 10 篇读后感。


人生是一条连续的时间线,除了起止点,中间这段旅程,并不会因为某一刻的变化而停下来,最多是慢下来;三十岁之前没有完成的事情,三十岁之后依旧可以去做;以前看得太重的东西,以后还可以改变很多;珍惜的事情太多,抱怨的时间太少;人生这段路,就这么些年,就该为自己走走看;路虽然走得不同,但走路的心情,却可以自己来定。


取悦自己真的比迎合他人要轻松和快乐许多。


共勉吧诸君,感谢垂阅。


作者:小流苏生
来源:juejin.cn/post/7385474787698065417
收起阅读 »

哭了,朋友当韭菜被割惨了

最近我的朋友,被某些知识付费坑得很惨。全程毫无干货可言。内容仅仅只适用于初级、或者说部分中级的程序员。为此,我的朋友交了大几千的学费,却收获甚微。 当然,你可能说,是你的朋友问题啊?你朋友烂泥扶不上墙,学习方法不对,别人都有很多成功的案例。什么offer收到...
继续阅读 »

最近我的朋友,被某些知识付费坑得很惨。全程毫无干货可言。内容仅仅只适用于初级、或者说部分中级的程序员。为此,我的朋友交了大几千的学费,却收获甚微。



当然,你可能说,是你的朋友问题啊?你朋友烂泥扶不上墙,学习方法不对,别人都有很多成功的案例。什么offer收到手酸,外包入大厂。




我买这些课就是为了学习,入门一些语言。知识付费很合理呀!!



于是我跟我朋友在微信彻夜长谈,有了如下分析


先说结论



请擦亮你的慧眼,你的一分一毫来之不易。不到迫不得已,才当学费



为什么这么说?


首先,不管你是想就业,还是想学习一些新的技术,网上都有例子,github上也会有前沿的项目提供学习。


类型结论
学习新技术某项技术开源出来,作为技术的布道者,恨不得你免费过去学习,然后你再发一篇文章,越来越多人学习你的技术。
就业简历包装无非就是抄抄抄,抄别人的优秀代码。github开源项目就非常合适

其次,你学费,一定要做到利益最大化。必须要有以下两点



  • 能学到大部分人都学不到的技术亮点。记住,是大部分人,一定要做到差异化

  • 能学到优秀的学习方法,push你前进。


开启慧眼


现在市面的学习机构,鱼龙混杂。,B站大学,某识xin球,某ke时jian 甚至,在某音上,都有那种连麦做模拟面试,然后引导你付费学习。


就业环境不好,买方市场竞争激烈,某些人就抓住你的焦虑心理,坑你一把。回想你的求学生涯,是否也有类似被坑经历?醒醒吧,少年。能救你的,只有你自己


当然,小海也会有潜龙。不可否认,知识付费为我们提供了便利性。



  • 原本散乱无章的知识点,人家给你整理好了,你尽管就是学习,实践

  • 面对焦虑,你觉得很迷茫,需要一个人指点你前进

  • 能认识更多同样诉求的人,为以后学习,就业,甚至做生意提供可能


但是,某些不法分子,就是抓住你的这个心理,疯狂ge你韭菜。什么10块钱知识手册,19.9面试题,100块钱的项目视频。天天一大早,就转发一些公众号到你群上,dddd。


这些内容,不是说没有用。我们讨论适合人群,这类东西不适合中高级程序员



说那么多,你得学会判断这个人是不是大佬




你都可以简历包装,为什么‘大佬’就不会是被包装的



那就稍微整理一下,哪些是真大佬,伪大佬


真伪大佬


某佬博客开源项目学习人群是否顺眼
伪大佬面试题居多,很多基础内容,没有干货无,或者很少。动不动就是商城,博客应届生占比较多可能顺眼
真大佬博客、论坛内容干货。整理分类完善,你能学到东西有,某些大项目的贡献,同时也有优秀开源项目应届生,中高级都有大多数不顺眼,因为实在优秀

就学习人群做一个说明



  • 在就业容易程度上,相对于初中高级别的程序员,应届生无论从考察的内容,招聘的人数。都会容易丢丢。

  • 他说跟着他学,offer赢麻了。但是其中,找到工作的大多数都是应届生


就这些点,我们其实可以能判断个大概了。


记住,你想知识付费。一定要摸清他的底细,不能认为他说得都是对的。人家也是会包装的


你的hello world


或许每个程序员的第一行代码,都是


    print("hello world")

我想说的是,请你记住你的初心。



  • 转行过来当程序员,就是为了狠狠赚他一笔

  • 喜欢写代码,苦中作乐


情况每个人都不太一样,这里不细说。明白你是谁,你还是否有动力能坚持下去。明白这一点,远比你在迷茫的时候病急乱投医更为重要,请勿过度焦虑


为此,后面会说一下如何学习,以及找工作如何不被骗


力量大会


事关钱包的问题,我们都得谨慎谨慎。就业市场那恶劣,朋友找不到工作还被坑了一把。骗子实在可恶。请你先自身强大,先自己找出问题,不花冤枉钱,避免传销式编程


如有雷同,纯属巧合,没有针对任何人,也没有动某些人的饭碗。


作者:Goland猫
来源:juejin.cn/post/7357231056288055336
收起阅读 »

勇敢的人先拿到结果

上周许久未见的大学学长叫我出去喝酒,他这次来贵阳是为开分店的事情而来的,他比我高一个年级,在我毕业的时候,他就自己开始做生意了,短短两三年,到现在他已经开了七八个分店了,还在不断发展,并且加盟的人也不少,平均下来,现在每个月的收入也是很可观的。 对于我们这种末...
继续阅读 »

上周许久未见的大学学长叫我出去喝酒,他这次来贵阳是为开分店的事情而来的,他比我高一个年级,在我毕业的时候,他就自己开始做生意了,短短两三年,到现在他已经开了七八个分店了,还在不断发展,并且加盟的人也不少,平均下来,现在每个月的收入也是很可观的。


对于我们这种末流二本院校毕业的学生,特别还是在贵州这个经济相对比较落后的地区,拿到这个成绩还是挺厉害的,并且这个收入并不是固定的,还是不断增长。


学长是学市场营销的,这也算是个天坑专业,所以那会他就知道自己将来肯定是从事不了这个行业的,所以自己就在宿舍开了一个小卖部,每天下课后就骑着电瓶车去送货,虽然每个月赚不了多少钱,但是对于做生意这一块,他的思维肯定是得到了锻炼。


因为我们是在广西读书,所以螺蛳粉就比较多,在毕业后,他就去柳州考察做螺蛳粉,联系好各种渠道后,回到贵州就直接开干。


因为那会贵州的各个市里面卖螺蛳粉的还很少,并且没有特色和品牌效应,所以自己就先设计名称,logo,最后先开了一个店铺,自己亲自下厨,因为比较有特色,一个月直接干到了全市螺蛳粉餐饮销量的第二名。


随后又开了第二家,第三家......别人在看到他赚了钱后,其它市区的人也纷纷向他学习,他自己就收加盟费用,现在他要做的事情就是玩,还有考察门店,然后扩展。


从他的事迹中,我说两个点。


勇于放弃


对于很多人而言,读书的目的就是为了找一份稳定的工作,最好是体制内。


如果你读完大学后出去做销售,做生意,那么对于你身边的很多人而言,他们会觉得你这个大学白读了,因为在他们眼中,只有坐在办公室里面才是最体面了。


你和他说做生意,创业这些东西,他会给你说:这些不稳,以后没有退休工资。


但是如果你真听他们的,那么后面后悔的一定是你。


就像学长,如果他也和别人一样毕业后回到自己那地方加入考编大军,那么他现在肯定和别人一样,也在背书,焦虑,但是他选择了其它的路。


这时候有些人就会抬杠:考上了就能吃一辈子,而你做生意如果运气不好那么就直接亏光,到时候你就知道编制的香了。


这也是很多人的通病。


我觉得如果一件事情你看不到希望,就别过于去迷恋它,舍不得它,不然会被它束缚,比如学历,经验等等。


敢想敢干


可能你会觉得他家里应该有底子的,不然毕业后怎么就能开店。


但是我们问一下自己,就算你家里有底子,毕业后就给你十万块让你开店,你觉得你行吗?恐怕大部分人都不知道自己该做什么吧。


首先躬身入局本身就是一件很难的事情,我们多数人能够拼命上班,但是如果让你脱离平台去自己干一件事就比登天还难。


因为你在公司有别人给你安排好,你去做就行了,换句话来说,你就是个干苦力的,真让你去谈判,去闯市场,大多数人是没这个能力的。


这也是一种损失厌恶心态,因为你怕自己花时间去做,到后面不仅亏了钱,还把自己弄得很累,而安安稳稳打工不一样,它是“稳赚不赔”的。


但是这个世界上很难有稳赚不赔的东西,就说安安稳稳打工拿工资,但是工资不高,那一定是在亏着走的,除非你觉得自己的时间毫无价值,那么就是赚的。


作者:苏格拉的底牌
来源:juejin.cn/post/7340898858556178432
收起阅读 »

感觉根本等不到35岁AI就把我裁了

感觉等不到35岁AI就把我裁了哥们干前端,刚过34周岁生日,请有缘的老哥兄弟们在评论区里祝哥们生日快乐先。让我焦虑的不是年纪,是这个叫Windsurf Editor的东西之前用过他们家的AI工具,感觉和市面上卷的AI工具相差无几。但自从Cursor出了个编辑器...
继续阅读 »

感觉等不到35岁AI就把我裁了

哥们干前端,刚过34周岁生日,请有缘的老哥兄弟们在评论区里祝哥们生日快乐先。

image.png

让我焦虑的不是年纪,是这个叫Windsurf Editor的东西

之前用过他们家的AI工具,感觉和市面上卷的AI工具相差无几。但自从Cursor出了个编辑器想要取代vs code,这事儿就逐渐不对劲了。

Codeium他们家也出了个编辑器叫Windsurf Editor,今天体验了一下,突然开始焦虑了。体验下来就一个感受:我想过这一天会来,没想到来的这么快

事情是这样的,我让AI帮我创建了一个前端项目,其中要用到xxx技术。然后全程我除了accept它给我生成的指令,我没有敲一行代码。

我自己不喜欢读长篇文字,所以直接看图吧。

image.png

image.png

可以看出它已经具备分析整个项目和整合项目代码的能力。

然后我让它帮我封装了一个RESTFUL式的基于axios的单例请求文件。结果我发现它在request文件中用到了状态管理中的数据(pinia)以及UI组件(ElementPlus),于是让它帮我修改。

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

最后还不忘给个使用示例

image.png

结语:这是一场程序员和技术的自我革新,不仅是我们这个领域,新的时代真的来临,大家一起努力吧,共勉之。


作者:fhf
来源:juejin.cn/post/7441543396979097610

收起阅读 »

马斯克招人策略曝光:9 轮面试,底薪低于同行,只招 “铁杆特斯拉人”

事情是这样的。 Business Insider 最近获得了特斯拉内部薪酬数据库(截至 2021 年 12 月)的访问权限,里面有 10 万名员工的薪酬数据。 然后他们发现了有关特斯拉薪酬的一系列猛料: 面试 9 轮只为招聘特斯拉铁粉; 采用低底薪 + 股票...
继续阅读 »

事情是这样的。


Business Insider 最近获得了特斯拉内部薪酬数据库(截至 2021 年 12 月)的访问权限,里面有 10 万名员工的薪酬数据。


然后他们发现了有关特斯拉薪酬的一系列猛料



  • 面试 9 轮只为招聘特斯拉铁粉;

  • 采用低底薪 + 股票奖励策略,打出 “高风险、高回报” 口号;

  • 特斯拉底薪低于同行,不及苹果、谷歌、英伟达、Meta、福特等科技公司和传统汽车制造商;

  • 工程师更有可能获得股票奖励;

  • 仅有 4% 的员工通过激励股票期权(ISOs) 获得股票,且通常授予高管;

  • ……



更多爆料细节如下——


马斯克招人策略:低底薪 + 股票奖励


透过这份曝光的内部薪酬文件,我们看到特斯拉向员工喊出的是 “高风险、高回报” 这一口号。


why??


一切的一切,还是归于特斯拉想要招聘自身铁粉。据一位了解招聘的内部员工透露:



他们可能在别处得到更好的报酬,但我们想要的是铁杆的特斯拉人。



而为了实现这一目标,特斯拉主要靠 “低底薪 + 股票奖励” 这一策略以及配套的招聘系统


对于前者,一位特斯拉前销售经理将其比喻为 “金手铐”:



股票是主要的钩子…… 我要低下头再等几个月,直到我获得股权。



至于后者,一位特斯拉前招聘人员表示,前司的招聘流程极为严格,通常需要几个月时间来考察面试候选人。


比如面试一位工程师,通常至少包括九次面试,可能需要数月时间



这是一件文化上的事情,一切都是为了排除掉只想 “打卡上下班” 的员工。




那么,特斯拉到底给员工们开了多少薪酬呢?


这里需要补充一个员工人数数据。在这份文件里,我们可以看到 10 万名员工的薪酬情况,而据 CNBC 报道,截至今年 6 月,特斯拉雇佣了大约 12 万名员工(包括正式员工和临时工)。


下面具体来看。


第一,先从公司内部来看。


首先,Business Insider 分析了大约 13,000 名全职、有薪、美国本土员工的平均基本工资(年薪),这些员工分属特斯拉的各个业务部门(如工程、制造或数据管理),而且排除了无法准确计算平均年薪的小时工。



可以看出,这些员工的基本工资中位数(年薪)大多在 10 万美元和 15 万美元之间。


接下来,Business Insider 进一步将数据细分,并查看特斯拉管理岗(全职、美国本土员工、有薪且手下至少有五名员工)的基本工资情况。


结果显示,包括工程总监和在特斯拉服务中心维修车辆的经理在内,这些人的基本工资中位数(年薪)从大约 35,000 美元到 324,000 美元不等。



而且据 9 位现任和前员工透露,自 2021 年 12 月以来,特斯拉的薪酬结构基本保持不变


换句话说,虽然上述数据看起来老旧,但特斯拉目前仍在延续这些薪酬方案。


不过,只看内部情况,我们可能无法直观感受特斯拉的 “低底薪”。


别急,Business Insider 还另外使用了来自证券交易委员会的数据,将特斯拉的基本工资与传统汽车制造商以及市值最大的六家科技公司进行了比较。


可以看出,除了亚马逊,特斯拉均处于落后地位。


而且我们知道,像亚马逊和苹果这样的公司,它们还拥有庞大的仓库劳动力和零售劳动力,这些因素也会影响公司的平均工资。


因此,一个基本情况浮出水面:



特斯拉的基本工资通常低于竞争对手




那么,接下来的问题是:员工为什么愿意接受低底薪呢?


最大原因还是在于股票


9 位现任和前工程师及销售人员表示,特斯拉的股票授予计划使得他们更容易接受较低的底薪。


据悉,过去 5 年,特斯拉的股价飙升超过 1000%;而今年,虽然特斯拉股价经历了显著波动(4 月中旬跌至年初价格的 44%),但在川普成功竞选后,特斯拉收获重大利好,其股价至今累计上涨近 30%。


so,又有多少员工能享受到特斯拉的股票奖励呢?


据内部文件显示,2020 年和 2021 年,有 44 名美国本土员工获得了价值超过 100 万美元的股票。


为了了解哪些员工更有可能获得股票奖励,Business Insider 根据职位类别对股票奖励进行了拆分。


结果显示,大多数工程师收到的股票奖励超过 25,000 美元。(股票的价值基于授予时的股价,但会根据特斯拉的股价变动而变化)



不过需要注意的是,特斯拉将限制性股票单位(RSUs)作为薪酬结构中的主要组成部分,约占薪资发放的 75%。


解释一下,RSUs 指授予时并不立即转化为实际的股票,而是在一定时间锁定期后,以公司股票的形式提供给员工。


换句话说,员工在满足特定条件(如服务年限或公司业绩目标)后才能获得 RSUs 股票。


同时,特斯拉将非合格股票期权(NQSOs) 作为基于业绩的薪酬的一部分,占薪资发放的 21%。


最终,仅有 4% 的员工通过激励股票期权(ISOs) 获得股票,且通常授予高管和其他高级员工。


而对于这一部分,内部文件显示,特斯拉高管中,除一名未列出持股数量的员工外,其余人收到的股票价值在 95 万美元至 2000 万美元之间。



除了股票,另一大原因在于特斯拉的公司形象


按照招聘公司 Stanton Chase 一位总监的说法:



它包含一个以使命为导向的元素…… 这些人正在努力实现地球的脱碳。



更不必说,还有 CEO 马斯克这位顶流的卖力宣传(doge):



我们给每个人股票期权,我们让许多只是在工作一线的人——甚至不知道股票是什么的人——变成了百万富翁。



马斯克 560 亿美元天价薪酬案将于年底见分晓


那么,老马本人在特斯拉的薪酬水平如何呢?


事实上,“马斯克 560 亿美元天价薪酬案” 一直引人关注:


2018 年,特斯拉为马斯克制定了一项为期 10 年的激励计划,方案核心是通过股票期权的方式,将马斯克的个人利益与公司的市值和业绩紧密绑定。


简单说,一旦老马能完成 KPI,他将累计获得特斯拉 12% 的股票期权作为奖励,总价值约为 560 亿美元,这一方案也被外媒认为是美国有史以来规模最大的高管薪酬方案。


当然了,当时来看公司定的 KPI 非常难,结果没想到后来特斯拉一路起飞,市值大涨(目前已来到 1.12 万亿美元)。


这下就有股东跳出来,觉得不公平了。


2022 年,特斯拉的部分股东将马斯克告上法庭,称他将大部分精力花在 SpaceX 等其他公司上,同时利用其对公司及董事会的控制敲定了长期薪酬计划,因此希望废除该方案。


紧接着,特拉华州的法官便以 “对股东不公平” 为由,宣布马斯克的长期薪酬方案无效。


面对这一判决,老马一怒之下宣称将特斯拉的注册地从特拉华州迁至得克萨斯州。(后来确实迁了)


并进行了上诉。


而最终结果,将在今年年底前得到裁决。


就在上周四,负责审理此案的特拉华州衡平法院大法官凯瑟琳・麦考密克(Kathaleen McCormick)表示,她将在 2024 年年底前作出最终决定。


不过,尽管之前的法院判决不利于老马,但特斯拉股东在 2024 年 6 月 13 日的年度股东大会上,已经以较大优势批准了这一薪酬方案。



参考链接:

x.com/BusinessIns…



作者:量子位
来源:juejin.cn/post/7436286873523421238
收起阅读 »

百亿补贴为什么用 H5?H5 未来会如何发展?

web
23 年 11 月末,拼多多市值超过了阿里。我想写一篇《百亿补贴为什么用 H5》,没有动笔;24 年新年,我想写一篇《新的一年,H5 会如何发展》,也没有动笔。 眼看着灵感烂在手里,我决定把两篇文章合为一篇,与你分享。提前说明,我并非百亿补贴的开发人员,本文的...
继续阅读 »

23 年 11 月末,拼多多市值超过了阿里。我想写一篇《百亿补贴为什么用 H5》,没有动笔;24 年新年,我想写一篇《新的一年,H5 会如何发展》,也没有动笔。


眼看着灵感烂在手里,我决定把两篇文章合为一篇,与你分享。提前说明,我并非百亿补贴的开发人员,本文的内容是我的推理。


拳打 H5,脚踢小程序。我是「小霖家的混江龙」,关注我,带你了解更多实用的 H5、小程序武学。


我的证据


在 Android 开发者模式下,开启显示布局边界,你可以看到「百亿补贴」是一个完整大框,这说明「百亿补贴」在 App 内是 H5。拷贝分享链接,在浏览器打开,可以看到资源中有 React,说明「百亿补贴」技术栈是 React。


pdd-stack.png


不只是拼多多,利用同样的方法,你可以发现京东、淘宝的「百亿补贴」技术栈也是 H5。


pdd-jd-taobao.png


那么,为什么电商巨头会选择做「百亿补贴」时会选择 H5 呢?


我的推理逻辑


解答问题前,我先说明下推理逻辑。巨头可能选择 H5 的原因千千万万,但最有说服力的原因,肯定具有排他性


什么是排他性?


举个例子,成功人物为什么成功,如果我回答「成功人士会喝水」,你肯定不满意。如果我回答「成功人士坚持不懈」,你会更满意一些。喝水分明是成功人士成功的原因,不喝水人会渴死,没办法成功。你为什么对这个答案不满意呢?


因为「喝水」不具备排他性,普通人也会喝水;而「坚持不懈」比「喝水」更具排他性,大部分普通人没有这个特质。


按照排他性,我需要说明百亿补贴只有 H5 能干,其他技术栈不能干,这样才有说服力。


百亿补贴为什么用 H5?


现在进入正题。粗略来看,大前端的技术栈分为 Native 和跨平台两大类。前者包括 3 小类,分别是 Android、iOS、纯血鸿蒙;后者也包括 3 小类,分别是基于 Web 的方案、基于系统 UI 框架的方案(比如 React Native)、自己绘制 UI 的方案(比如 Flutter)。


其中,基于 Web 的方案,又可以细分为纯 H5 和 DSL 转 H5(比如 Taro)。


graph TB;
大前端 --> Native;
Native --> Android;
Native --> iOS;
Native --> 纯血鸿蒙;
大前端 --> 跨平台;
跨平台 --> 基于Web的方案;
跨平台 --> 基于系统UI框架的方案;
跨平台 --> 自己绘制UI的方案;
基于Web的方案 --> H5;
基于Web的方案 --> DSL转H5;

我们需要排除 H5 外的其他方案。


原因一:百亿补贴迭代频繁


百亿补贴的业务形式,是一个常住 H5,搭配上多个流动 H5。(「常住」和「流动」是我借鉴「常住人口」和「流动人口」造的词)



  • 常住 H5 链接保持不变,方便用户二次访问。

  • 流动 H5 链接位于常住 H5 的不同位置,方便分发用户流量。


具体到拼多多,它至少有 3 个流量的分发点,可点击的头图、列表上方的活动模块和侧边栏,3 者可以投放不同链接。下图分别投放了 3.8 女神节链接、新人链接和品牌链接:


pdd-activity.png


可以想到,几乎每到一个节日、每换一个品牌,「百亿补贴」就需要更新一次。


这样频繁的迭代,框架必须满足快速开发、快速部署、一次开发多端复用条件。因此可以排除掉 Native 技术栈,留下动态化技术栈。


原因二:百亿补贴需要投放小程序和其他 App


如图所示,你可以在微信上搜索拼多多,可以看到百亿补贴不仅在 App 上投放,还在微信小程序里投放。


pdd-wx.png


此时我们几乎可以排除掉 React Native 和 Flutter 技术栈。因为社区虽然有方案让 React Native、Flutter 适配小程序,但并不成熟,不适合用到生产项目中。


此外,如果你在抖音、B 站和小红书搜索百亿补贴,你可以看到百亿补贴在这些 App 上都有投放广告。


pdd-advertisement.png


这点可以完全排除 React Native 和 Flutter 技术栈。据我所知,目前没有主流 App,会愿意让第三方在自己的 App 里运行 React Native 和 Flutter。


原因三:百亿补贴核心流量在 APP


现在只剩下了基于 Web 的 2 种技术方案,也就是 H5 和 DSL 转出来的 H5(比如 Taro)。


百亿补贴的 HTML 结果,更符合原生 H5 的组织结构,而不是 Taro 这种 DSL 转出来的结构。


我对此的解释是,百亿补贴的核心流量在 App。核心流量在 APP 时。投放小程序是锦上添花,把 H5 嵌入到小程序 Webview 就能满足要求,不需要卷性能。


如果百亿补贴的核心流量在小程序,那么大概率就会使用 DSL 框架,转出来小程序代码和 H5 代码。


综上所述,迭代频繁、需要投放小程序和其他 App,核心流量在 App,是百亿补贴选择 H5 的 3 个主要原因。


H5 未来会如何发展


知道百亿补贴选择 H5 的 3 个原因后,我们可以得到结论,如果 3 个前提不变,未来很长一段时间内,H5 依然是电商活动的主流方案。


不过,主流方案并不意味着一成不变,我认为未来 H5 会有 2 个发展趋势:


趋势一:离线包、SSR 比例增加


H5 有诸多优势的同时,也有着先天缺陷,那就是下载成功率低、容易白屏。


解决这个问题,社区主流的两个方案是离线包和 SSR。


离线包可以将网页的静态资源(如 HTML、CSS、JS、图片等)缓存到本地,用户访问 H5 页面时,可以直接读取本地的离线资源,从而提升页面的加载速度。阿里云腾讯云等云服务商都有自己的离线包方案。


SSR 即服务器端渲染,它可以减少白屏时间,让用户更快看到页面。传统的 CSR(客户端渲染)初始时只渲染空白的 HTML 框架,然后再去获取数据并渲染内容。而在 SSR 中,服务器在接收到客户端请求时,会在服务器端利用数据和模板生成完整的 HTML 页面,再把页面发送给客户端浏览器。


不难想到,业务陷入瓶颈后,企业开始看中性能,大部分前端开发者都会来卷一卷离线包、 SSR,它们的比例会进一步增加。


趋势二:定制化要求苛刻


近年 C 端市场增长缓慢,企业重点从扩张新客,变成留存老客。


这个背景下,定制化要求变得越来越苛刻,目的是让用户区分各种活动。用互联网黑话来说,就是「建立用户心智」。


下面是拼多多、京东、淘宝、12306、中国移动和招商银行的活动 H5,尽管它们结构都差不多,但长得是千奇百怪。


fluid.png


12306-yidong-zhaoshang.png


我估计未来,电商活动 H5 的外观将变得极具个性,各位前端同学可以在卷卷 CSS、动效方向。


总结


本文介绍了我认为「百亿补贴」会选用 H5 的 3 大原因:



  • 百亿补贴迭代频繁

  • 百亿补贴需要投放小程序、其他 App

  • 百亿补贴核心流量是自己的 App


以及我 H5 未来发展趋势的 2 个预测:



  • 离线包、SSR 比例增加

  • 定制化要求苛刻


拳打 H5,脚踢小程序。我是「小霖家的混江龙」,关注我,带你了解更多实用的 H5、小程序武学。


作者:小霖家的混江龙
来源:juejin.cn/post/7344325496983732250
收起阅读 »

2024年总结: 迷茫

12月今年最后一个月了,相逢的人已走散, Q4的OKR已经定型了, 很平淡无味, 闲的无聊 提前写个年终总结吧。00年, 再过一个月就25岁了,一个人来杭州也已经3年多了 每天有时间写一点 周六了 写到凌晨1点了 看直播/打麻将到凌晨5点才睡。 去年也写了一篇...
继续阅读 »

12月今年最后一个月了,相逢的人已走散, Q4的OKR已经定型了, 很平淡无味, 闲的无聊 提前写个年终总结吧。00年, 再过一个月就25岁了,一个人来杭州也已经3年多了 每天有时间写一点 周六了 写到凌晨1点了 看直播/打麻将到凌晨5点才睡。 去年也写了一篇 2023年总结:日渐清醒,得失随意 //TODO DDL 应该是在月中完结吧。



工作



我大概回忆一下 我今年在工作上应该干了这些事情吧




  • 自己申请换项目组,日常维护新品App版本迭代 2周一个版本 多个app同时进行

  • 完成所有App 苹果服务器接口Storekit2 升级上线

  • Google 支付/订阅 SDK 重构原生API调用代码

  • RocketMQ优化多数据中心用户数据同步/webhook/推送

  • RocketMQ-Exporter 搭建 监控相关性能指标

  • SSE+服务器GRPC流式 推送消息

  • us机器 内存优化 切换到jemalloc内存分配器

  • RocketMQ 单机 升级为Dledger 集群模式 Q4 任务



从22年3月毕业到现在 再度过一个季度 在这家公司呆三年了(3年之约),整个过程就是升级打怪,看着人来人往 合作的人 离职一个又来一个, 每个季度干着重复的工作,技术框架还是那一套 SpringBoot+GRPC 经过一年熟悉后 就觉得没有新鲜感了, C端产品App 在用户基数不大情况下 基本的重心在客户端的ui/操作体验上 后端嘛就是一个数据存储的地方 存取能有什么难度 大家都会,每个季度任务就是 基本的版本迭代+一些服务器内部的优化,如果你想要拿到高的绩效 那干这点是远远不够的, 基本规则在无告警的版本迭代下 做一些对团队贡献大 有价值的事情 拿A/季度优秀员工,三年期间升了两次小级别 p5-1->p5-2->p5-3 还是一个初级开发 今年估计也悬了 没有两次A的绩效 跨段位升没机会,没有owner过项目, 和旁边人朋友/同学工作/升职对比下 只能说自己像个废物 躺的太平了 每天965的生活 除了偶尔上线 需要加班留下来 大家都不加班 也没有那么多活需要加班来干的活。




生活



去年立的flag 也是一腔鸡血




  • 软考 系统架构师 +软著 拿到杭州E类人才

  • 健身/减肥

  • 骑车 vlog (杭州景点全部骑完 影石360 ace pro)

  • 摄影佳能rp/视频剪辑学习

  • 日语/英语学习

  • leetcode 上Knight

  • 考D驾-照 骑防赛

  • 日麻线下雀庄体验 参加各种线上日麻比赛

  • ClickHouse hangzhou 线下沙龙

  • 掘金bolg 更新 技术日常

  • 千岛湖

  • 抽烟+喝酒

  • B站 直播 日麻


软考 系统架构师+软著 拿到杭州E类人才



img


骑车 vlog (杭州景点全部骑完 影石360 ace pro)



  • 一个人走走停停 骑过很多地方, 最多的还是钱塘江到彭浦大桥->复兴大桥路线 不知道骑了多少遍,西湖/湘湖/九溪这些地方都去过了,车子是青春款只在线上售卖 后期毛病很多 链条蹭盘/刹车无效 自己不知道维修了多少次,最近这两个月很少骑了,放在地下室发霉 后续准备卖掉了这车 还买了那么多骑行装备,买的insta 360 ace pro 3000多降价到2k左右 当时在大疆和insta中选择了好久 最后还是踩坑了 实体店体验了大疆action 画质比insta360好太多了,有必要考虑再买一台大疆action5了, 一个人的骑行之路也该结束了 开始新的玩具 仿赛摩托车 芜湖起飞。


img
img
img
img
image.png

健身/减肥



  • 怎么说呢 三天打鱼两天晒网的行动 体脂没什么变化,饮食更不会控制 每天外卖外卖外卖, Q1/Q2两个季度挺积极的 基本工作日晚上有时间就去健身房 周末白天也去, 在健身房的时间也能让自身感受到轻松, 这个小区有个百姓健身房在地下室 24小时 刷脸进去 设备齐全 没什么人 每个季度的话300块RMB, 我主要后面可能没有看到短期效果+活着很累 有一段时间没有去了, 偶尔下楼抽烟去逛逛, 最后得到的只是自己的一个心里安慰,没有合理的计划和坚持下去的心 我现在已经懒连手表都不戴了。


img
img
img
img

摄影佳能rp/视频剪辑学习



  • 怎么说呢 周末放假就是宅 已经吃灰了 除了9月1号 拿到免费的门票 杭州植物园专门去了一趟拍彼岸花,其他时间不出手。视频剪辑也是一坨屎 目前就用剪映弄一些雀魂麻将抽角色的视频,后续还是想学一下专业的剪辑工具 这个也看需求吧。


img

考D驾-照 骑防赛



  • 为什么要去骑摩托车,主要是中秋节回家一趟,隔壁邻居已经买了一辆机车, 当时他让我试试 我没试 后面就一直关注摩托车这个事 抖音一直给我推视频。才有了考D驾-照驾-照,周末练半天,工作日考试半天就好了,4科联考 比C1驾-照周期短 速度快,驾-照到手后面周末直接找附近最近的租车平台试试水,萧山那边的之江路/美女坝路险,本田500 手震麻了 1个小时 干了100公里, 最后一个半小时就还车了 跑了150km,整体的体验感是非常好的,无论是去骑车的路上 还是过程中 都能够忘记生活/工作上的烦心事,最尴尬的是红绿灯起步熄火了,后续周末继续出行租车,找个有缘人一起。


img
img
img

日麻线上比赛/线下雀庄体验



  • 每天下班就是点外卖 开始打麻将,水各种日麻群 打友人赛/团队赛,每天晚上达到2 3点 菜就爱多玩 和群友打比赛 对个人的实力也是有了认知 学习别人的打法 从野猪冲击 也慢慢在意铳率了,前一天打完线上比赛, 这个月周日也是马上跟着一个大哥去杭州线下的湖滨牌浪屋体验完线下日麻,今年干的最多的事情就是打麻将,下班除了打麻将还是打麻将。


img
img
img
img

leetcode 上Knight



  • 基本上是原地没动,比赛一场没打,为什么要刷?为了什么?能带给自己什么收益?呆在舒适圈里久了 不想出去,算法也是提不上一点兴趣了 估计只有到时候找工作之前才会接触到了 其实也制定了计划 刷灵神的清单 还是自己懒吧 动不起来,最后一个月 要不开始发力?算了 打麻将吧。


img

其他



  • 今年参加了ClickHouse hangzhou 线下沙龙, 虽然没有使用过clickhouse这款db,去听听别人公司的落地方案,去阿里园区转了转。

  • 掘金bolg 更新 技术日常 主要是参加创作者训练营吧 锻炼一点自己的文本输出能力,总结的过程中也能知道问题的本质是什么,解决的过程/方式以及别人是怎么解决的,收获还是有的。

  • 和同学五一去了千岛湖一趟 结局不是很好 过程体验不错。

  • 在日语/英语学习上面投入的时间 ,无论是日常工作上英语的使用 还是各种文档阅读能力,在逛各种项目/看论文的时候 就能体现出来, 日语兴趣的话 纯粹是打日麻和旁边的日麻群友影响/看番剧而来的 每天用多邻国完成任务,买了4本书《标准日语》+《大家的日语》,在B站上看圆圆姐的视频教程【京大博士带你学日语】新标日初级上册全新课程!必能学会!超详细讲解!轻松搞定日语学习!(课本内容完结!)哔哩哔哩bilibili

  • 抽烟+喝酒已经是家常便饭一样的事情了 上半年是沉迷于喝酒消愁 下半年就抽烟打发时间,每天下班又不知道干什么 找点打发时间的乐趣,天天熬夜看直播 打麻将 2点3点睡觉已经是常态了,每天晚上看陈伯/刘刘江直播 带来的乐趣, 工作日每天基本8点50的闹钟吵醒,拖着尸体去上班,周末基本睡到自然醒中午/下午 除了楼上楼下装修 直接被震醒了。

  • 这样一回想2024年还是干了很多无意义的事。



虽然只有15篇文章 文章的阅读数也有3w 其实数据对我来说也是无所谓的,主要还是方便以后回忆吧,分享出去 可能有人和你遇到相同问题,带给解决思路 明年要不要继续写?还是把时间投入在别的地方?都是未知



img
img
img

个人技术学习



  • AI 知识点拓展学习

  • 部门分享

  • 推荐系统&&RAG

  • 前端

  • 第三方支付订阅

  • 分布式论文学习总结

  • 《计算机网络-自顶向下方法第七版》

  • 《CSAPP 深入理解计算机系统(第3版)》

  • 《设计数据密集型应用》

  • 技术拓展/深挖(RocketMq源码/go-redis源码/Netty源码/Mycat2源码)



看个锤子 没心思学习 下半年天天打麻将




  • AI的话主要是身边环境影响,自己的项目组一直在利用AI做业务,从2023年开始 公司一直对接的是openai 提供的chat 能力,公司内部举行了ai相关的比赛,业务想要搭建自己的知识库和RAG搜索 主要是用AWS上的Redrock封装好的知识库 ,项目组一些APP一直在使用微软的TTS进行语言转音频的操作,部门,组内和项目组 大家一直在内部分享和ai相关的知识点,产品会使用cursor提前将需求写完 自己进部署上线。

  • 跟着项目组业务走,最近在支付方面的功能进行了改动,对于web网页上的购买消耗型商品/续费型商品的购买,主要对接的平台是Stripe信用卡visa支付 和paypal支付。appstore 支付的话 最近负责组内storekit2 服务器接口升级重构代码 用的官方开源库 github.com/apple/app-s…。Alipay 支付宝和Wechat 对于中国环境的用户提供的一种支付方式, 代码很粗糙 很久没有相关需求迭代了。Google 支付 来的两年时间接触的业务还是比较少,整个支付逻辑和appstore是一致的,有时间把代码逻辑和官方文档进行学习总结一下。

  • 现在只会个后端远远不够了,替代性太强了,除非是中大厂那样细分工作岗位/业务内容,如果你有自己的想法 后续做一些自己的产品/独立开发者 一人一公司 全栈只能是无敌路。我这边对前端也是零零散散的学习 没有整个的大项目使用,github.com/lobehub/lob… 前端React开源项目学习 TSX+TS 认知冲击 原来前端已经进化到这个地步了,没有html+css全部被封装了,我们内部的数据平台还是原生html+django搭建的,每次加新功能ai生成的代码 能跑就行。



在下半年 觉得基础知识很重要 技术跟着业务走 没必要太追求新技术 就往计算机基础知识+算法+基础论文投入时间




  • 中间一段也是将《计算机网络-自顶向下方法第七版》 计算机网络-自顶向下方法第七版 · 语雀和《CSAPP 深入理解计算机系统(第3版)》CSAPP 深入理解计算机系统(第3版) · 语雀 细看了一遍 , 书籍买了很多 都是吃灰的 没有去年那个干劲了。

  • 看论文 可以学习技术理论的基础 还有重要一点是学英语, 主要是看一个up在学习这方面的知识点 就跟着看了一段时间,谷歌三大剑客 GFS/MapReduce/BigTable 看论文不看分布式论文 就像四大名著不读红楼梦,唐诗不读李杜,吃泡面不加调料包 Raft/Paxos 那一块真的一看就是几天 深陷进去 每次看硬核课堂的文章 Raft -论文导读 与 ETCD源码解读

  • 参与开源项目任务也没有达成 往里面投入的时间太少了,最后下班/上班有时间 也没深入去学习业务上用到的组件源码, 最近的话 负责RocketMQ集群Dledger搭建/MQ优化业务 ,RocketMQ-exporter+herzbeat+prometheus监控指标, 遇到的异常信息太多 每次都是网上找案例解决,上班利用ai 深入在看看RocketMQ源码吧github.com/apache/rock… 边看边总结RocketMQ 源码 5.2.0 - 生产者 Producer



杂学杂记




  • 中间又去了解了下机器学习/深度学习相关的内容,又看了大数据开发Spark/Flink等等组件,前端看了React+TS相关知识点/demo/Flutter开源项目, 背单词的时候发现墨墨背单词是node+ts写的,有个软考刷题的app是Flutter写的 作者是独立开发者 最近公司客户端也在用flutter开发新品app 代码我这边也是有权限的, 也去了解了一下技术栈,中间又有一段时间去了解了下亚马逊运营的工作,也看了AIGC/agent/图像/音频/向量数据库milvus等相关的方向 RAG 知识库增强式搜索,在推荐系统领域 推荐 广告 搜索 也花了一段时间去了解/学习 因为我们这边没有算法工程师 推荐功能很粗糙 没有用户画像的概念,有一段时间被cloudwego社区的kitex/hertz吸引 当时想去上海站的线下沙龙 可惜正好那周软考考试, 因为有个麻将牌谱工具是rust写的 github.com/Equim-chan/… 所以又去了解了一下rust,我记得有段时间投入在系统设计/业务场景思考方向(IM/Feed流/本地消息表/分布式限流等等), 都是一点毛皮,Q2服务器服务的内存问题 每天上班想下班想 把互联网的文章都翻烂了 从堆内到堆外到Glibc 询问各种技术大佬场景异常 组内成员给不了你任何帮助 全靠自己,所有的东西没有实质性的收获 也没有在项目中使用到 过了一段时间基本全忘了(不做笔记/总结) 。



现在往回看 在技术学习的时长投入的太少了 对技术没有追求 什么都知道一点 什么都不精通 啥也没学会 离开了Java/Spring 我还能干什么,我能干的 找个会使用AI的人来都能干,天天熬夜 不知道熬的是什么 碌碌无为。最近一年 代码写的很少了 基本靠ai生成 微改/设计一下 写的自己也看不懂了。生活/工作迷茫 现在都是活一天是一天, 想回家。



后续规划 待定



  • 英语/日语

  • 独立开发者



打麻将去咯 一起玩雀魂的可以加我



20241212-151216.png

作者:呆呆蛇
来源:juejin.cn/post/7445511025702764555
收起阅读 »

pnpm 的崛起:如何降维打击 npm 和 yarn🫡

web
今天研究了一下 pnpm 的机制,发现它确实很强大,甚至可以说对 yarn 和 npm 形成了降维打击 我们从包管理工具的发展历史,一起看下到底好在哪里? npm2 在 npm 3.0 版本之前,项目的 node_modules 会呈现出嵌套结构,也就是说,我...
继续阅读 »

今天研究了一下 pnpm 的机制,发现它确实很强大,甚至可以说对 yarnnpm 形成了降维打击


我们从包管理工具的发展历史,一起看下到底好在哪里?


npm2


在 npm 3.0 版本之前,项目的 node_modules 会呈现出嵌套结构,也就是说,我安装的依赖、依赖的依赖、依赖的依赖的依赖...,都是递归嵌套的


node_modules
├─ express
│ ├─ index.js
│ ├─ package.json
│ └─ node_modules
│ ├─ accepts
│ │ ├─ index.js
│ │ ├─ package.json
│ │ └─ node_modules
│ │ ├─ mime-types
| | | └─ node_modules
| | | └─ mime-db
| │ └─ negotiator
│ ├─ array-flatten
│ ├─ ...
│ └─ ...
└─ A
├─ index.js
├─ package.json
└─ node_modules
└─ accepts
├─ index.js
├─ package.json
└─ node_modules
├─ mime-types
| └─ node_modules
| └─ mime-db
└─ negotiator

设计缺陷


这种嵌套依赖树的设计确实存在几个严重的问题



  1. 路径过长问题: 由于包的嵌套结构 , node_modules 的目录结构可能会变得非常深,甚至可能会超出系统路径长度上限 ,毕竟 windows 系统的文件路径默认最多支持 256 个字符

  2. 磁盘空间浪费: 多个包之间难免会有公共的依赖,公共依赖会被多次安装在不同的包目录下,导致磁盘空间被大量浪费 。比如上面 express 和 A 都依赖了 accepts,它就被安装了两次

  3. 安装速度慢:由于依赖包之间的嵌套结构,npm 在安装包时需要多次处理和下载相同的包,导致安装速度变慢,尤其是在依赖关系复杂的项目中


当时 npm 还没解决这些问题, 社区便推出了新的解决方案 ,就是 yarn。 它引入了一种新的依赖管理方式——扁平化依赖。


看到 yarn 的成功,npm 在 3.0 版本中也引入了类似的扁平化依赖结构


yarn


yarn 的主要改进之一就是通过扁平化依赖结构来解决嵌套依赖树的问题


具体来说铺平,yarn 尽量将所有依赖包安装在项目的顶层 node_modules 目录下,而不是嵌套在各自的 node_modules 目录中。


这样一来,减少了目录的深度,避免了路径过长的问题 ,也尽可能避免了依赖被多次重复安装的问题


|350


我们可以在 yarn-example 看到整个目录,全部铺平在了顶层 node_modules 目录下,展开下面的包大部分是没有二层 node_modules


然而,有些依赖包还是会在自己的目录下有一个 node_modules 文件夹,出现嵌套的情况,例如 yarn-example 下的http-errors 依赖包就有自己的 node_modules,原因是:


当一个项目的多个依赖包需要同一个库的不同版本时,yarn 只能将一个版本的库提升到顶层 node_modules 目录中。 对于需要这个库其他版本的依赖,yarn 仍然需要在这些依赖包的目录下创建一个嵌套的 node_modules 来存放不同版本的包


比如,包 A 依赖于 lodash@4.0.0,而包 B 依赖于 lodash@3.0.0。由于这两个版本的 lodash 不能合并,yarn 会将 lodash@4.0.0 提升到顶层 node_modules,而 lodash@3.0.0 则被嵌套在包 B 的 node_modules 目录下。


幽灵依赖


虽然 yarn 和 npm 都采用了扁平化的方案来解决依赖嵌套的问题,但这种方案本身也有一些缺陷,其中幽灵依赖是一个主要问题。


幽灵依赖,也就是你明明没有在 package.json 文件中声明的依赖项,但在项目代码里却可以 require 进来
这个也很容易理解,因为依赖的依赖被扁平化安装在顶层 node_modules 中,所以我们能访问到依赖的依赖


但是这样是有隐患的,因为没有显式依赖,未来某个时候这些包可能会因为某些原因消失(例如新版本库不再引用这个包了,然后我们更新了库),就会引发代码运行错误


浪费磁盘空间


而且还有一个问题,就是上面提到的依赖包有多个版本的时候,只会提升一个,那其余版本的包不还是复制了很多次么,依然有浪费磁盘空间的问题


那社区有没有解决这俩问题的思路呢? pnpm 就是其中最成功的一个


pnpm


pnpm 通过全局存储和符号链接机制从根源上解决了依赖重复安装和路径长度问题,同时也避免了扁平化依赖结构带来的幽灵依赖问题
pnpm 的优势概括来说就是“快、准、狠”:



  • 快:安装速度快

  • 准:安装过的依赖会准确复用缓存,甚至包版本升级带来的变化都只 diff,绝不浪费一点空间

  • 狠:直接废掉了幽灵依赖


执行 npm add express,我们可以在 pnpm-example 看到整个目录,由于只安装了 express,那 node_modules 下就只有 express


|400


那么所有的(次级)依赖去哪了呢? binggo,在node_modules/.pnpm/目录下,.pnpm/ 以平铺的形式储存着所有的包


|400


三层寻址



  1. 所有 npm 包都安装在全局目录 ~/.pnpm-store/v3/files 下,同一版本的包仅存储一份内容,甚至不同版本的包也仅存储 diff 内容。

  2. 顶层 node_modules 下有 .pnpm 目录以打平结构管理每个版本包的源码内容,以硬链接方式指向 pnpm-store 中的文件地址。

  3. 每个项目 node_modules 下安装的包以软链接方式将内容指向 node_modules/.pnpm 中的包。
    所以每个包的寻找都要经过三层结构:node_modules/package-a > 软链接 node_modules/.pnpm/package-a@1.0.0/node_modules/package-a > 硬链接 ~/.pnpm-store/v3/files/00/xxxxxx


    这就是 pnpm 的实现原理。官方给了一张原理图,可以搭配食用


    |600


    前面说过,npm 包都被安装在全局 pnpm store ,默认情况下,会创建多个存储(每个驱动器(盘符)一个),并在项目所在盘符的根目录


    所以,同一个盘符下的不同项目,都可以共用同一个全局 pnpm store,绝绝子啊👏,大大节省了磁盘空间,提高了安装速度


    |600



软硬链接


也就是说,所有的依赖都是从全局 store 硬连接到了 node_modules/.pnpm 下,然后之间通过软链接来相互依赖。


那么,这里的软连接、硬链接到底是什么东西?


硬链接是指向磁盘上原始文件所在的同一位置 (直接指向相同的数据块)


软连接可以理解为新建一个文件,它包含一个指向另一个文件或目录的路径 (指向目标路径)



总结


npm2 的嵌套结构: 每个依赖项都会有自己的 node_modules 目录,导致了依赖被重复安装,严重浪费了磁盘空间💣;在依赖层级比较深的项目中,甚至会超出 windows 系统的文件路径长度💣


npm3+ 和 Yarn 的扁平化策略: 尽量将所有依赖包安装在项目的顶层 node_modules 目录下,解决了 npm2 嵌套依赖的问题。但是该方案有一个重大缺陷就是“幽灵依赖”💣;而且依赖包有多个版本时,只会提升一个,那其余版本依然会被重复安装,还是有浪费磁盘空间的问题💣


pnpm全局存储和符号链接机制: 结合软硬链和三层寻址,解决了依赖被重复安装的问题,更加变态的是,同一盘符下的不同项目都可以共用一个全局 pnpm store。节省了磁盘空间,并且根本不存在“幽灵依赖”,安装速度还贼快💪💪💪


作者:柏成
来源:juejin.cn/post/7410923898647461938
收起阅读 »

写点掏心窝子的话

唯有读书和赚钱,才是一个人最好的修行,前者使人不惑,后者使人不屈! 世界上任何一种能力,只要你迫切想学,真心想学,无论身边有没有人教你,你都可以想办法找人,找资源,学会自我学习,自我教育才是最好的教育。 事实上,不管学历怎么样,你一生中绝大部分的东西都是自学的...
继续阅读 »

唯有读书和赚钱,才是一个人最好的修行,前者使人不惑,后者使人不屈!


世界上任何一种能力,只要你迫切想学,真心想学,无论身边有没有人教你,你都可以想办法找人,找资源,学会自我学习,自我教育才是最好的教育。


事实上,不管学历怎么样,你一生中绝大部分的东西都是自学的。


一定要聚焦到自己想做的事情上,并且在头脑里想象自己正在做,而且做得越来越好。


当你的思想,注意力,意识集中在某一个领域,某一件事,某一个行业,某些人身上时,与此相关的大量人,事,物全会被你吸引过来。


对于成年人来说,每个人的时间都是有限的,2025,千万不要再给自己树立太多的目标。


百门通不如一门精,一个人终其一生不如专注一件事,做到极致,朝着一个既定的方向不懈努力的人,几乎都成为社会各界的成功人士。


如果你能只深挖一个点,把事情做到极致,只要肯下功夫,在六个月内你能掌握任何一门学问。


但这不是让你去挑战别人的天赋,深思读书4个月能涨粉10万,只要肯下功夫,你也可以。


因为每门学问所包含的信息量大约是五万个信息块,一个人一分半钟可以记一个信息块。那么五万个大约需要一千小时,以每星期学习40小时计算。


要掌握一门学问,大约需要六个月。


反过来,如果不能专注聚焦于一件事,经不住其他事和人的干扰和诱惑,于是什么都做不精,做不透。


知识是无限的,专业能力有无数种细分领域,行业的经验无穷无尽。


每个都了解一点表面信息,一辈子也不会有成就,每一个专业能力,每一个细分领域赛道,每一个行业都有挖不尽的信息、知识、理论、经验,要用专注和时间去钻研一件事,最容易成为专家有结果的人。


很多人忙碌一生,却不明白真正的优先级是什么!


余认为:


家庭的核心是经济,而不是感情。


职场的准则是价值,而不是努力。


社交的关键是利益互换,而不是单纯的友谊。


教育的目的是培养能力,而不是追求分数。


健康的要素是自律,而不是医疗。


爱情的基础是理解,而不是激情。


创业的要点是市场需求,而不是个人喜好。


投资的原则是风险控制,而不是高回报。


人生的追求是幸福,而不是成功。


养老的保障是提前规划,而不是依赖子女。


成长的动力是反思,而不是经历。


学习的意义是应用,而不是积累知识。


旅行的收获是见识,而不是拍照打卡。


婚姻的支撑是责任,而不是浪漫。


生活的智慧是知足,而不是无尽的欲望。 


与君共勉!愿2025活成理想的自己!


作者:河北小田
来源:juejin.cn/post/7451223580595273778
收起阅读 »

微信公众平台:天下之事合久必分分久必合

web
微信公众平台的故事,还得从 “再小的个体,也有自己的品牌” 的 Slogan 开始说起。 时间线开始 2012年8月17日 这一天,微信公众平台正式向普通用户开放,也就从这一天开始,普通人可以在微信上注册属于自己个人或组织的公众号。 2012年8月23日 这...
继续阅读 »

QQ_1734967828741.png


微信公众平台的故事,还得从 “再小的个体,也有自己的品牌” 的 Slogan 开始说起。


QQ_1734972139162.png


时间线开始


2012年8月17日


这一天,微信公众平台正式向普通用户开放,也就从这一天开始,普通人可以在微信上注册属于自己个人或组织的公众号。


2012年8月23日


这一天,微信公众平台正式上线。各大博主、媒体纷纷注册加入了这个平台,开始在微信公众平台上创作,建立自己的读者圈子,打造自己的IP。


2012年11月29日


从这天起,微信图文群发系统升级发布,图文并茂的文章可以通过微信公众平台发送给关注的粉丝了。


这时候,很多企业嗅到了春天来临的味道,招聘互联网编辑的岗位越来越多。


2013年2月6日


这一天,微信的公众号支持开发者模式了,开发者们的春天(噩梦)开始了。


很多公众号开始提供更多的功能了,比如微信公众平台文档里十年没变的那些什么话费查询、机票航班查询等:



于是公众平台开始对外提供了 “火警请按119,急救请按120” 的鸡肋开发能力——关键词回复、关注消息等。



虽然十多年过去了,我依然碰到了很多人不太理解微信公众号的这玩意的交互流程……


2013年3月19日


2013年3月20日,公众平台灰度了“自定义菜单”,当然,还只是内测。


此时的微信公众号,除了可以推送消息之外,也支持在后台编辑公众号菜单,指定菜单可以回复不同的内容或者打开一个 URL。


2013年8月5日


这天,微信发布了 v5.0 大版本,同时也带来了很多好玩的东西。


为了区分平台内公众号的各种主体,微信公众号在这一天分了家:订阅号 + 服务号。


区别在哪呢?



嗯,内测的自定义菜单给服务号开放了。但是阉割了服务号群发的频率:每月4条。



同时,对可申请的主体也做了限制:



个人只能申请订阅号了。组织类不限制。



然后当年很糟心,但现在很开心的事情发生了:订阅号从消息列表折叠到了 “订阅号” 栏目里。


好,很直接。



不过直至今日,服务号依然还可以在消息列表中直接显示。



2013年10月29日


这天,微信公众平台推出了认证的功能,认证之后有一些特权:



  • 语音识别、客服接口、获取用户地理位置、获取用户基本信息、获取关注列表和用户分组接口的权限

  • 仅认证服务号支持的 OAuth2.0网页授权、生成带参数二维码 等

  • 认证了可以送你一个



此时,微信公众号支持通过 腾讯微博新浪微博 等第三方平台的认证来同步认证服务号。此时不管是个人还是组织的号,都可以认证。但是还没有充值即认证的功能。



我猜是运营开始往赚钱上靠了,毕竟 不充钱的腾讯产品不是好产品。


2013年12月24日


说时迟那是快,这不就来了。


从今天起,你可以花 300 块钱来认证你的号了,前提是,你得是 组织 号,个人的不支持。(当然,部分类型的主体认证是不收费的,比如 政务 媒体 等)


2014年3月


今天,微信公众平台支持接入微信支付了。不过,无论你是订阅号还是服务号,都需要通过企业认证之后,再申请开通微信支付。


这一年,开发者们忙起来了。


创建订单、创建支付请求参数、签名、回调处理、支付结果查询 等等事情接踵而至。


微信开发者的圈子和生态慢慢的繁荣了起来。


2014年9月18日



哎,到哪都逃不掉 ToBCURD 业务。



随着微信开发者生态的繁荣,微信意识到了很多开发者在微信的服务号上做 ToB 的业务,要不要独立一个出来呢?


那就叫 企业号 吧,于是微信公众号的第三个兄弟也来了。


在2014年-2017年这段时间,有一个网站很火,叫 很*微信开发者社区(weixin.com)请记住这个名字,一会要考。


2016年1月11日


2016微信公开课PRO版在广州举行,那个男人(张小龙,微信之父) 首次公开演讲。


这天,张小龙说,微信要做 应用号,要让用户 用完即走


2016年5月


这段时间,上面的社区使用的 weixin.com 最终被南山必胜客拿下。手动狗头:)


2016年9月22日


微信开始内测 小程序。又一次噩梦开始了。


2016年11月3日


微信开始公测 小程序


2017年1月9日


微信小程序 正式上线。



小应用?应用号?



2017年6月29日


随着企业号的发展,微信意识到这与微信的个人社交出现了很多的冲突,于是,微信在2017年6月29日,抽离出了企业微信,牛马们开始使用这个工具来为老板创收了。


2017年12月28日


微信小游戏上线,大家一起来 打飞机


2020年1月22日


微信视频号开始内测,本文讲的微信公众平台系列故事本以为到此会结束了。然而:


2024年11月


这个月,微信把 订阅号 改名为 公众号 了。



服务号:那我呢???



我怎么总觉得有大事要发生?


最近


个人可以注册服务号了,而且注册的服务号依然是在消息列表里,还没有被折叠。



企业:??? 当年费劲巴力注册了服务号,一个月还只有四条,我的特权呢???



当然,目前注册的服务号都是没有认证的,我试了试,目前个人主体的服务号不支持认证。也就是所有的高级开发接口权限一个都没有。


我还是觉得要有大事发生了。



完全没看懂微信公众平台这个骚操作。



总结


今天简单聊了聊微信公众平台的一些小故事,如有错误,欢迎评论区指正和讨论。


只是作为曾经风风火火的微信公众平台开发者,心里感慨颇多。


Bye.


作者:Hamm
来源:juejin.cn/post/7451561994799890483
收起阅读 »

手把手教你做个煎饼小程序,摊子开起来

web
前言 周饼伦在街头摊煎饼,摊后人群熙熙攘攘。他忙得不可开交,既要记住面前小哥要加的培根,又要记住身后奶奶要加的生菜,这时又来了个小妹妹,点的煎饼既要培根又要腊肠。他把鸡蛋打进煎饼后,竟突然忘了前面光头大叔要加的料,是火腿还是鸡柳?一时记不清,好像是火腿,对。然...
继续阅读 »

前言


周饼伦在街头摊煎饼,摊后人群熙熙攘攘。他忙得不可开交,既要记住面前小哥要加的培根,又要记住身后奶奶要加的生菜,这时又来了个小妹妹,点的煎饼既要培根又要腊肠。他把鸡蛋打进煎饼后,竟突然忘了前面光头大叔要加的料,是火腿还是鸡柳?一时记不清,好像是火腿,对。然而当把煎饼交给大叔,大叔却怒了,说要的是鸡柳。😡


这可咋办?周饼伦赶忙道歉,大叔却语重心长地说:“试试用小程序云开发吧!最近的数据模型新功能好用得很!” 周饼伦亮出祖传手艺,边摊煎饼边开发小程序,把新开发的小程序点餐页面二维码贴在摊前。从此再没出过错,终于能安心摊煎饼啦!


设计思路


图片


客户扫摊子上面贴的二维码后,会进入点餐页面,在选好要加的配料之后,点击确定就可以点餐,随后,即可在云后台上看到食客提交的数据


图片


实现过程


周饼伦就把当前摊位的主食、配菜,以及各自相应的价格贴在了摊位上,也要把食客的点餐内容记在脑里或者用笔写在纸上。


点餐页要实现两个功能:1.展示当前摊位有的主食、配菜、口味 2.提交订单到周饼伦的订单页面。


煎饼摊子主食(staple food)目前只有摊饼、青菜饼,主食下面有的配菜(side dish),有鸡柳、生菜、鸡蛋、火腿、腊肠。


同理,数据库里面也需要呈现相应的结构。


数据表的实现


数据模型现在提供了一种便捷的能力来,可以快速创建一套可用的数据表来记录摊煎饼的相关数据。


图片


在云后台中新增了一个基于 MySQL 的数据模型,数据模型相当于一张纸,可以在上面记录任何想要记录的数据,比如周饼伦摊位的提供的菜品


图片


创建了基于云开发MySQL数据库的主食表,主食表中包含主食名称,主食价格


图片


图片


字段的详细设置如下


图片


图片


加了主食、配菜两个表之后,将当前的主食和配菜一起加进数据表中

图片


图片


现在就实现了记录当前摊子的主食和配菜。还需要一个订单表,来记录用户的点餐数据


图片


配菜的类型是一个数组文本,用来记录配菜的类型,结构如下


图片


接着需要分别设置每个数据模型的权限。在使用小程序查看订单时,也是以用户的身份来读取的,所以,需要配置用户权限,通过页面访问来控制用户能够访问到哪些页面


图片


图片


图片


至此,数据表就已经大功告成!现在完全可以使用三个表来记录当前摊子的菜品、营业情况。


但是,别忘了周饼伦的目的不止于此,为了周饼伦实现早日暴富,当上CEO,所以,还要利用小程序实现一个界面,来给”上帝“们点餐,并且提供各位CEO查看订单


小程序实现过程


一. 初始化 SDK


在云后台的数据管理中的右侧中,可以方便的查询到使用的文档


图片


新建一个基于云开发的小程序,删除不必要的页面,并且按照文档的步骤进行初始化👇


1.按照指引在 miniprogram 目录下初始化 npm 环境并安装 npm 包


请注意,这里需要在 miniprogram 目录下初始化 npm ,不然需要编辑 project.config.json 手动指定 npm 包的位置


在 miniprogram 目录下打开终端


图片


2.初始化当前 npm 并且安装 @cloudbase/wx-cloud-client-sdk npm 包


npm init -y & npm install @cloudbase/wx-cloud-client-sdk --save

图片


3.在小程序中构建 npm


图片


4.在小程序 app.js 中初始化环境


// app.js
App({
globalData: {
// 在这里提供全局变量 models 数据模型方法,方便给页面使用
models: null
},
onLaunch: async function () {
const {
init
} = require('@cloudbase/wx-cloud-client-sdk')
// 指定云开发环境 ID
wx.cloud.init({
env: "ju-9g1guvph88886b02",
});
const client = init(wx.cloud);
const models = client.models;
// 可以取消注释查看效果
// const { data } = await models.stapleFood.list({
// filter: {
// where: {}
// },
// pageSize: 10,
// pageNumber: 1,
// getCount: true,
// });
// console.log('当前的主食数据:');
// console.log(data.records);
}
});

二. 下单页面的实现


首先创建一个页面 goods-list 页面作为首页


顾客如果浏览下单页面,那么就需要看到当前可以选择的主食、配菜,还有他们分别的价格。所以首先我们需要把主食、配菜加载进来


// 加载主食
const stapleFood = (await models.stapleFood.list({
filter: {
where: {}
},
pageSize: 100, // 一次性加载完,
pageNumber: 1,
getCount: true,
})).data.records;

// 加载配菜
const sideDish = (await models.sideDish.list({
filter: {
where: {}
},
pageSize: 100, // 一次性加载完,
pageNumber: 1,
getCount: true,
})).data.records;

// pages/goods-list/index.js
Page({
data: {
// 总价格
totalPrize: 0,
// 选中的主食
selectedStapleFoodName: '',
// 选中的配菜
selectedSideDishName: [],
// 所有的主食
stapleFood: [],
// 所有的配菜
sideDish: [],

以下是全部的js代码


// pages/goods-list/index.js
Page({
data: {
// 总价格
totalPrize: 0,
// 选中的主食
selectedStapleFoodName: '',
// 选中的配菜
selectedSideDishName: [],
// 所有的主食
stapleFood: [],
// 所有的配菜
sideDish: [],
},

async onLoad(options) {
const models = getApp().globalData.models;
console.log('models', models)

// 加载主食
const stapleFood = (await models.stapleFood.list({
filter: {
where: {}
},
pageSize: 100, // 一次性加载完,
pageNumber: 1,
getCount: true,
})).data.records;

// 加载配菜
const sideDish = (await models.sideDish.list({
filter: {
where: {}
},
pageSize: 100, // 一次性加载完,
pageNumber: 1,
getCount: true,
})).data.records;

console.log({
stapleFood,
sideDish
});

this.setData({
stapleFood: stapleFood,
sideDish: sideDish
})
},

// 选中主食
onSelectStapleFood(event) {
this.setData({
selectedStapleFoodName: event.currentTarget.dataset.data.name
});

this.computeTotalPrize();
},

// 选中配菜
onSelectedSideDish(event) {
console.log(event);
// 选中配菜名字
const sideDishName = event.currentTarget.dataset.data.name;

// 如果已经选中,则取消选中
if (this.data.selectedSideDishName.includes(sideDishName)) {
this.setData({
selectedSideDishName: this.data.selectedSideDishName.filter((name) => (name !== sideDishName))
});
} else {
// 未选中,则选中
this.setData({
selectedSideDishName: this.data.selectedSideDishName.concat(sideDishName)
});
}

this.computeTotalPrize();
},

// 重新计算价格
computeTotalPrize() {
// 主食价格
let staplePrize = 0;
if (this.data.selectedStapleFoodName) {
staplePrize = this.data.stapleFood.find((staple) => staple.name === this.data.selectedStapleFoodName).prize;
}

// 配菜价格
let sideDish = 0;
this.data.selectedSideDishName.forEach((sideDishName) => {
sideDish += this.data.sideDish.find((sideDishItem) => (
sideDishItem.name === sideDishName
)).prize;
});

// 总价格
this.setData({
totalPrize: staplePrize + sideDish
})
},

// 提交
async onSubmit() {
// 提示正在加载中
wx.showLoading({
title: '正在提交订单',
});

const models = getApp().globalData.models;
const { data } = await models.order.create({
data: {
served: false, // 是否已出餐
sideDish: this.data.selectedSideDishName, // 配菜
stapleFoodName: this.data.selectedStapleFoodName, // 主食名称
prize: this.data.totalPrize, // 订单总价格
}
});

console.log(data);
wx.hideLoading();
}
});

接着来实现页面


<!--pages/goods-list/index.wxml-->
<view>
<view class="title">
<image src='/asset/pancake.png'></image>
<text class="title">请选择主食</text>
</view>

<!-- 主食展示 -->
<view class="staple-food">
<view wx:for="{{stapleFood}}" wx:key="_id">
<view bindtap="onSelectStapleFood" data-data="{{item}}" class="staple-food-item {{selectedStapleFoodName === item.name ? 'selected' : ''}}">
<image src="{{item.imageUrl}}"></image>
<view class="prize">{{item.prize}}¥</view>
</view>
</view>
</view>

<!-- 选择配菜 -->
<view class="title">
<image src='/asset/sideDish.png'></image>
请选择配菜
</view>

<!-- 配菜展示 -->
<view class="side-dish">
<view wx:for="{{sideDish}}" wx:key="_id">
<!-- 使得class动态绑定支持 includes 语法 -->
<wxs module="tool">
var includes = function (array, text) {
return array.indexOf(text) !== -1
}
module.exports.includes = includes;
</wxs>
<view class="side-dish-item {{tool.includes(selectedSideDishName, item.name) ? 'selected' : ''}}" bindtap="onSelectedSideDish" data-data="{{item}}">
<image src="{{item.imageUrl}}"></image>
<view class="prize">{{item.prize}}¥</view>
</view>
</view>
</view>

<!-- 底部菜单 -->
<view class="bottom-content">
<view class='bottom-info'>
<view wx:if="{{!!selectedStapleFoodName}}">主食:{{selectedStapleFoodName}}</view>
<view wx:if="{{selectedSideDishName.length !== 0}}">配菜:{{selectedSideDishName}}</view>
</view>

<view class="bottom-operate">
<view class="total-prize">当前价格<text class="prize">{{totalPrize}}¥</text></view>
<view class="submit-button {{!selectedStapleFoodName ? 'disabled' : ''}}" bind:tap="onSubmit">下单</view>
</view>
</view>
</view>

再添加一点点的样式


/* pages/goods-list/index.wxss */
.title {
display: flex;
align-items: center;
gap: 16rpx;
padding: 0 20rpx;
}

.title image {
height: 46rpx;
width: 46rpx;
}

.staple-food {
display: flex;
margin-bottom: 60rpx;
overflow: auto;
}

.staple-food-item {
margin: 20rpx 10rpx;
display: flex;
flex-direction: column;
border: 1px solid #f3f0ee;
box-shadow: 6rpx 6rpx 6rpx #dfdfdf, -6rpx -6rpx 6rpx #dfdfdf;
border-radius: 6rpx;
padding: 8rpx;
}

.staple-food-item.selected, .side-dish-item.selected {
box-shadow: 6rpx 6rpx 6rpx #58b566, -6rpx -6rpx 6rpx #58b566, 6rpx -6rpx 6rpx #58b566, -6rpx 6rpx 6rpx #58b566;
}

.staple-food-item image {
border-radius: 6rpx;
width: 300rpx;
height: 300rpx;
}

.prize {
padding: 6rpx 6rpx 0;
text-align: right;
color: orangered
}

.side-dish {
padding: 20rpx 12rpx;
display: flex;
gap: 12rpx;
overflow: auto;
}

.side-dish image {
height: 200rpx;
width: 200rpx;
}

.side-dish-item {
border-radius: 8px;
padding: 16rpx;
box-shadow: 6rpx 6rpx 6rpx #dfdfdf, -6rpx -6rpx 6rpx #dfdfdf;
}

.bottom-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
}

.bottom-info {
padding: 30rpx;
display: flex;
flex-direction: column;
color: grey;
font-size: 0.5em;
}

.bottom-content .total-prize {
padding: 0 30rpx;
}

.bottom-operate {
border-top: 1px solid #dfdfdf;
display: flex;
width: 100%;
justify-content: space-between;
align-items: center;
height: 100rpx;
}

.submit-button {
width: 350rpx;
color: white;
background: #22b85c;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}

.submit-button.disabled {
background: grey;
/* 注意,这里设置了当按钮置灰的时候,不可点击 */
pointer-events: none;
}

于是,煎饼摊的小程序就大功告成了!


接着就可以在云后台管理订单了,在将订单完成之后,即可在云后台将订单的状态修改成已完成。


图片


我们还可以做的更多…


是否可以在订单中新增一个点餐号,这样就知道是哪个顾客点的餐?是否可以使用数据模型的关联关系将配菜、主食和订单关联起来?


是否可以在小程序中创建一个管理订单的页面?是否可以添加优惠券数据表,来给客户一些限时优惠?


期待大家的体验反馈!


代码地址:github.com/vancece/qiL…


点击体验:tcb.cloud.tencent.com/cloud-admin…


作者:腾讯云云开发
来源:juejin.cn/post/7413376270518042651
收起阅读 »

这两年,我把28年以来欠的亏都吃完了...

前言 很长一段时间没有总结一下过去几个月的状态了,今天思绪万千,脑海中浮现了很多经历和生活片段,我把它记录下来。顺便今天聊一聊认知突破,分享我在买房这段时间吃过的亏,也希望作为你的前车之鉴。 买房 21年底的时候,那时刚好毕业三年,也正是互联网公司996最流行...
继续阅读 »

前言


很长一段时间没有总结一下过去几个月的状态了,今天思绪万千,脑海中浮现了很多经历和生活片段,我把它记录下来。顺便今天聊一聊认知突破,分享我在买房这段时间吃过的亏,也希望作为你的前车之鉴。


买房


21年底的时候,那时刚好毕业三年,也正是互联网公司996最流行的阶段,由于平时我不怎么花钱,也很少买衣服,上网买东西是个矛盾体,需要花很多时间对比,经常看了一件东西很久,最后又不买。加上比较高强度的工作状态,两点一线,可以说是没时间花钱,再加上自己把钱都拿去理财了,也赚了几万块,最后一共攒了几十万下来。我从小就立志要走出农村,而且认为以后有女朋友结婚也要房子,加上当时花比较多时间在理财上面,那时候其实行情已经不好了,工作上没什么突破,比较迷茫,于是想着干脆就把钱花出去了,自己也就有动力去搞各种路子尝试赚钱。在没有经过任何对比之后就在佛山买了一套房子,房价正是高峰的时候,于是我成功站岗!因为这个契机,躲过了持续了2年多的低迷股市,却没躲过低迷的房地产。


while(true) { 坑++ }


我买的是期房,当时不知道期房会有这么多坑,比如期间不确定开发商会不会破产,我这个开发商(龙光)就差点破产了,房产证无着落,相当于花了200w买了一个无证的房子,这辈子就算是搭进去了。


对于整个购房过程也是很懵逼,对流程完全不熟悉,当时去翻了政府规划文件,看那个地段后续有没有涨价空间,然后跟着亲戚介绍的销售转圈圈,当时说给我免3年物业费,合计也有几万块。在签合同之前销售都有说可以给到,但由于第一次没有录音,导致在签合同的时候销售反口,不承认,我们也没有证据,最后吃了哑巴亏。


开始的时候谈好了一个价格167w,然后销售私下打电话给我洗脑说我给点辛苦费1.5w,他可以向领导申请多几万块优惠。我知道这是他们的销售套路,但是架不住给我优惠5w啊,中间反复拉扯最后说给他8k,采用线下现金交易的方式。这一次我有录音了,因为私底下交易没有任何痕迹,也不合法,所以留了一手,也成为我后面维权时争取话语权的基础。


中介佣金是很乐观的,当时由于我亲戚推荐我去,销售承诺税前有4w,当时看中这个返佣也促使我火急火燎的交了定金。现在3年过去了,这个佣金依旧没有到账,我一度怀疑是中介搞ABC套路把我这个钱💰吃了,其他邻居的推荐佣金都到了账,加上现在地产商没钱了,同时跟那个亲戚有些过节,这个返佣更是遥遥无期。最后通过上面的录音获得了一丝话语权,知道了这个钱还在开发商手上,一直没有拨款下来到中介公司。下面是部分聊天记录:


image.png


不接受微信语音沟通,文字可以留给自己思考的时间,同时也更好收集证据。


image.png


然后去找相关人员把信息拉出来给我看,显示开发商未付款状态,这个状态维持2年了,目前看来只能再等下去。


image.png


签合同的时候,有个律师所说是协助我们签合同、备案、办房产证等各种边缘工作,糊里糊涂交了700元律师费,不交不行,甚至律师所连发票都没有给,而我都没有意识到这个最基本的法律法规问题。现在交房了可以办理房产证了,拿证下来也就80块登记费,居然收我700,其他业主有些是600多,400多,顿时觉得智商受到了侮辱,看了网上铁头各种打假的视频,我觉得自己也应该勇敢发声。现在也在收集商家各种违规证据,提交给相关部门解决。


image.png


image.png


image.png


后面市场监督管理局收到投诉,应该是有协商,意识到没有给我们发票,过来几天之后才把发票补过来,开票日期不是付款时候的2022年,而是2024年,明显属于偷税了。目前跟他要发票的应该只有我,估算2300多户业主都没有开发票的。


当时我首付需要50w,自己手上不够,我爸干建筑一辈子,辛苦供我们两个孩子上了大学,山上建了两层楼,手里没钱。我妈是一辈子没打过工,消极派,说出来没几句好话,家里不和睦的始作俑者,更不可能有钱支持。所以我还有20w是首付贷,也就是跟开发商借的,利率10%,这个利息很高了。销售当时说可以优惠到5%,但是优惠金额是补贴到总房价里面去,其实这也是他们的一种销售套路,这亏我也吃了,2年之后我连本带息还24w。当时认为自己应该一年左右能还完,但是实际远远高估自己的能力,买完房子接着我爸又生病在医院待了几个月,前后花了十几万,人生一下子跌入了谷底。


从头再来


后面2023一年,夫妻出去创业,很多人不赞同,期间遇到了不少小人诋毁我们两夫妻,当时我老婆还在怀孕,但我们最后都熬过来了,还生了一个儿子,6斤多。期间一年赚了十几万,但是开支也大,加上父母要养,我爸还要吃药,房子要供,最后还是选择了先稳定下来,我重新回到了职场,空窗一年后在这个环境下拿了一个还不错的offer,同时也想自己沉淀一下。


自从有了宝宝之后,生活似乎都往好的方面发展,出版社找我出书,为了契合自己的职业发展,我选择了写书《NestJS全栈开发秘籍》,从2023年11月份开始,迄今快半年了,在收尾阶段,希望尽快与各位读者们见面。同时,等了3年的房子也收房了,由于是高层,质量相对其他邻居好,没有出现成片天花掉下来或者漏水的情况。我们经常都说他是天使宝宝,是来报恩的。


由于我们公司技术部门是属于后勤支持性质的,技术变化不大,Vue2+微前端和React管理系统那一套,没有太多的新技术扩展,意味着不确定也大。业务发展不好考虑的是减少这些部门的开支,所以不出意外最近也迎来了降薪。这不是最可怕的,对于我们技术人来讲,最可怕的是我认为在业务中成长停滞了,或者没有业务来锻炼技术,所以在业余时间也选择了参与一些开源项目,如hello-alog开源算法书的代码贡献,并且这也是选择写书的原因。很简单地说,当下一个面试官问到我的时候,我不可能什么都讲不出来,最经典的问题就是:在这个公司期间你做过最有成就感的事情是什么?现在,我有了答案!


哲学


我的人生哲学是不断改变,拥抱不确定性!这么看来,我的确在这些年上了不少当,吃了不少亏,把自己搞的很累,甚至连累到家里人。但,用我老婆经常说的一句话:人生这么长,总是要经历点什么,再说现在也没有很差。的确,不断将自己处于变化之中,当不确定性降临到普罗大众时,我们唯一的优势,就是更加从容


总结


人们还在行走,我们的故事还在继续~


WechatIMG154.jpg


作者:元兮
来源:juejin.cn/post/7349136892333981711
收起阅读 »

运维打工人,周末兼职送外卖的一天

运维打工人,周末兼职送外卖的一天 在那个不经意的周末,我决定尝试一份新的工作——为美团外卖做兼职配送员。这份工作对于一向规律生活的我来说,既是突破也是挑战。 早晨,城市的喧嚣还未完全苏醒,空气中带着几分凉意和宁静。准备好出发时,线上生产环境出现问题,协助处理。...
继续阅读 »

运维打工人,周末兼职送外卖的一天


在那个不经意的周末,我决定尝试一份新的工作——为美团外卖做兼职配送员。这份工作对于一向规律生活的我来说,既是突破也是挑战。


早晨,城市的喧嚣还未完全苏醒,空气中带着几分凉意和宁静。准备好出发时,线上生产环境出现问题,协助处理。


收拾好后,戴上头盔,骑上踏板车,开始了自己的第一次外卖配送之旅。


刚开始,我的心情既紧张又兴奋。手机里的订单提示声是今日的任务号角。第一份订单来自一公里外的一家外卖便利店。我快速地在地图上规划路线,开启高德导航,发动踏板车,朝着目的地出发。


123.jpg


由于便利店在园区里面,转了两圈没找到,这是就慌张了,这找不到店咋办了,没办法赶紧问下旁边的老手骑手,也就顺利找到了,便利店,进门问老板,美团104号好了嘛?老板手一指,在架子上自己看。核对没问题,点击已达到店,然后在点击已取货。


然后在导航去收获目的地,找到C栋,找到107门牌号,紧接敲门,说您好,美团外卖到了,并顺利的送达,然后点击已送达,第一单顺利完成,4.8元顺利到手。


其中的小插曲,送给一个顾客时,手机导航提示目的地,结果一看,周围都拆了。没办法给顾客打电话,加微信确认位置具体在哪里,送达时,还差三分钟,这单就要超时了。


1.jpg


配送过程中,我遇到了第一个难题:找不到店家在哪里,我的内心不禁生出些许焦虑。但很快,我调整心态,不懂不知道的地方,需要多多问人。


紧接着,第二份、第三份订单接踵而至。每一次出发和到达,每一条街道和巷弄,我开始逐渐熟悉。


7.jpg


6.jpg


日落时分,我结束了一天的工作。虽然身体有些疲惫,但内心充满了前所未有的充实感。这份工作让我体验到了不一样的人生角色,感受到了城市节奏背后的种种辛劳与甘甜


周末的兼职跑美团外卖,对我来说不仅是一份简单的工作,更是一段特别的人生经历。它教会了我坚持与责任,让我在忙碌中找到了属于自己的节奏,在逆风中学会了更加珍惜每一次到达。


最后实际周六跑了4个小时,周天跑了7个小时,一共跑了71公里,合计收获了137.80,已提现到账。


5.jpg


2.png


作者:平凡的运维之路
来源:juejin.cn/post/7341669201010425893
收起阅读 »

代码与蓝湖ui颜色值一致!但页面效果出现色差问题?

web
前言 最近在开发新需求,按照蓝湖的ui图进行开发,但是在开发完部署后发现做出来的页面部分元素的颜色和设计图有出入,有色差!经过一步步的排查最终破案,解决。仅以此篇记录自己踩坑、学习的过程,也希望可以帮助到其他同学。 发现问题 事情是这样的,那是一个愉快的周五的...
继续阅读 »

前言


最近在开发新需求,按照蓝湖的ui图进行开发,但是在开发完部署后发现做出来的页面部分元素的颜色和设计图有出入,有色差!经过一步步的排查最终破案,解决。仅以此篇记录自己踩坑、学习的过程,也希望可以帮助到其他同学。


发现问题


事情是这样的,那是一个愉快的周五的下午,和往常一样我开心的提交了代码后进行打包发版,然后通知负责人查看我的工作成果。


但是,过了不久后,负责人找到了我,说我做出来的效果和ui有点出入,有的颜色有点不一样。我一脸懵逼,心想怎么可能呢,我是根据ui图来的,ui的颜色可是手把手从蓝湖复制到代码中的啊。


随后他就把页面和ui的对比效果图发了出来:


image.png


上图中左侧是蓝湖ui图,右侧是页面效果图。我定睛一看,哇趣!!!好像是有点不一样啊。 感觉右侧的比左侧的更亮一些。于是我赶紧本地查看我的页面和ui,果然也是同样问题! 开发时真的没注意,没发现这个问题!!!


排查问题


于是,我迅速开始进行问题排查,看看到底是什么问题,是值写错了?还是那里的问题。


ui、页面、代码对比


下图中:最上面部分是蓝湖ui图、下面左侧是我的页面、右侧是我的页面代码样式


image.png


仔细检查后发现颜色的值没错啊,我的代码中背景颜色、边框颜色的值都和ui的颜色值是一致的! 但这是什么问题呢??? 值都一样为什么渲染到页面会出现色差?


起初,我想到的是屏幕的问题,因为不同分辨率下展示出来的页面效果是会有差距的。但是经过查看发现同事的win10笔记本、我的mac笔记本、外接显示器上都存在颜色有色差这个问题!!!


ui、页面、源文件对比


通过对比ui、页面、颜色值,不同设备展示效果可以初步确认:和显示器关系不大。当我在百思不解的时候,我突然想到了ui设计师!ui提供的ui图是蓝湖上切出来的,那么她的源文件颜色是什么呢?


于是我火急火燎的联系到了公司ui小姐姐,让她发我源文件该元素的颜色值,结果值确实是一样的,但是!!! 源文件展示出来的效果好像和蓝湖上的不太一样!


然后我进行了对比(左侧蓝湖、右上页面、右下源文件):


image.png


可以看到源文件和我页面的效果基本一致!到这一步基本可以确定我的代码是没问题的!


尝试解决


首先去网上找了半天没有找到想要的答案,于是我灵光一现,想到了蓝湖客服!然后就询问了客服,为什么上传后的ui图内容和源文件有色差?


image.png


image.png


沟通了很久,期间我又和ui小姐姐在询问她的软件版本、电脑版本、源文件效果、设置等内容就不贴了,最终得到如下解答:


image.png


解决方式


下载最新版蓝湖插件,由于我们的ui小姐姐用的 sketch 切图工具,然后操作如下:


1.下载安装最新版蓝湖插件: lanhuapp.com/mac?formHea…


2.安装新版插件后--插件重置


3.后台程序退出 sketch,重新启动再次尝试打开蓝湖插件.


4.插件设置打开高清导出上传(重要!)


5.重新切图上传蓝湖


最终效果


左侧ui源文件、右侧蓝湖ui:
image.png


页面效果:


image.png


可以看到我的页面元素的border好像比ui粗一些,感觉设置0.5px就可以了,字体效果的话是因为我还没来得及下载ui对应的字体文件。


但是走到这一步发现整体效果已经和ui图到达了95%以上相似了,不至于和开始有那么明显的色差。


总结


至此,问题已经基本是解决。遇到问题不能怕,多想一想,然后有思路后就一步一步排查、尝试解决问题。当解决完问题后会发现心情舒畅!整个人都好起来了,也会增加自信心!


作者:尖椒土豆sss
来源:juejin.cn/post/7410712345226035200
收起阅读 »

基于Vue.js和高德地图API来实现一个简易的天气预报

web
今天就让我们来使用 Vue.js 和高德地图开放平台提供的 API,实现一个关于天气预报的小demo,实时查询当前城市的天气以及未来三天的天气预测,且实现切换城市查询。实现效果如下; 准备工作 既然要使用真实的数据,那么就需要用到高德地图开放平台提供的天气查...
继续阅读 »

今天就让我们来使用 Vue.js 和高德地图开放平台提供的 API,实现一个关于天气预报的小demo,实时查询当前城市的天气以及未来三天的天气预测,且实现切换城市查询。实现效果如下;


PixPin_2024-12-15_00-13-38.gif


准备工作


既然要使用真实的数据,那么就需要用到高德地图开放平台提供的天气查询 API,先高德地图api注册为开发者。然后点击文档与支持,选择JS API。


image.png


然后登录到控制台创建一个应用并且添加一个key,服务平台为Web端(JS API)。
16b5ba85e6c5f128b699fe8d521bb67.jpg


终端npm create vite@latest使用vite创建项目,npm install下载该项目需要用的包,npm run dev运行项目。


image.png


将天气预报的功能全部开发在weather.vue里面,再将这个组件import weather from "./components/weather.vue"引入到app.vue中。


image.png


js代码概览


image.png


具体代码步骤实现


开始weather.vue里面的代码了。


html 部分


<div>
// 头部
<div class="head">
<div class="city-name">
<i class="iconfont icon-dingwei"></i>
{{ state.city }}
</div>
<div @click="toggle" class="city-change">
<i class="iconfont icon-24gf-city3"></i>
切换城市
</div>
</div>


// 中间部分实时温度
<div class="main">
<div class="weather-info">
<p class="temp">{{ state.weather.temperature }}℃</p>
<div class="info">{{ state.weather.weather }}</div>
<div class="detail">
<div class="item">
<i class="iconfont icon-shuidi"></i>
<span>湿度</span>
<span>{{ state.weather.humidity }}</span>
</div>
<div class="item">
<i class="iconfont icon-feng"></i>
<span>风向</span>
<span>{{ state.weather.windDirection }}</span>
</div>
<div class="item">
<i class="iconfont icon-fengli"></i>
<span>风力</span>
<span>{{ state.weather.windPower }}</span>
</div>
</div>
</div>

// 未来三日的天气预报
<div class="future">
<div class="future-title">三日天气预报</div>
<div class="future-content">
<div v-for="(item,i) in state.future" class="forecast">
<p class="week">周{{ chinese[Number(item.week)-1] }}</p>
<i :class="getWeatherIcon(item.dayWeather)"></i>
<p><span class="left">{{ item.dayTemp }}℃</span> <span class="right"> / {{ item.nightTemp }}℃</span></p>
</div>
</div>
</div>
</div>


// 切换城市input框
<div v-show="state.isVisible" >
<input id="newCity" @keydown.enter="handle" type="text" v-model="state.newCity" placeholder="请输入你要查询的城市">
</div>
</div>


然后使用css样式美化成如下界面


image.png


js部分


接下来就是渲染其中的数据了,首先使用高德 api 来获取定位数据,查看官方文档,JS API结合 Vue 使用,首先安装Loader,如下所示,复制到当前文件终端安装。
image.png


然后复制代码粘贴;
image.png


AMapLoader 是高德地图 js API 的加载器,它可以在前端项目中加载和初始化高德地图的 js API。


import AMapLoader from '@amap/amap-jsapi-loader';
import { onMounted, reactive } from 'vue'

onMounted(() => {   // 在浏览器上出现内容时候触发
// 加载官方提供的方法
window._AMapSecurityConfig = {
securityJsCode: "", // 密钥
};
AMapLoader.load({
key: "", // 申请好的Web端开发者Key,首次调用 load 时必填
version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ["AMap.Scale"], //需要使用的的插件列表,如比例尺'AMap.Scale',支持添加多个如:['...','...']
})

// 加载完上面代码高德提供的服务后,执行then后面的操作
.then((AMap) => {
// 获取定位
getLocalCity(AMap) // 使用一个函数,将获取地址信息放到这个函数中
})
})

获取城市信息


官方文档:
image.png


const getLocalCity = (AMap) => {
AMap.plugin('AMap.CitySearch', function () {
var citySearch = new AMap.CitySearch()
citySearch.getLocalCity(function (status, result) {
if (status === 'complete' && result.info === 'OK') {
// 查询成功,result即为当前所在城市信息
console.log(result.city); // 会打印当前城市
state.city = result.city //将城市改为定位获取到的城市
getWeather(AMap) // 获取天气
}
})
})
}

image.png


利用该地址获取实时天气数据
image.png


const getWeather = (AMap) => {
//加载天气查询插件
AMap.plugin("AMap.Weather", function () {
//创建天气查询实例
var weather = new AMap.Weather();
//执行实时天气信息查询
weather.getLive(state.city, function (err, data) { // 将城市替换成state.city
console.log(err, data); // 获取天气数据,详情见下表
state.weather = data // 将数据赋值给 state.weather
getForecast(AMap) // 后面用来获取未来三天的天气
});
});
}

image.png
将这一整个对象赋值给state.weather然后再state.weather.渲染到页面上。


获取未来三天天气


const getForecast = (AMap) => {
AMap.plugin("AMap.Weather", function () {
//创建天气查询实例
var weather = new AMap.Weather();
//执行实时天气信息查询
weather.getForecast(state.city, function (err, data) {
console.log(err, data);
state.future = data.forecasts // 获取天气预报数据

//err 正确时返回 null
//data 返回天气预报数据,返回数据见下表
});
});
}

image.png


最后就是切换城市中的input框的实现;


<div v-show="state.isVisible" >
<input id="newCity" @keydown.enter="handle" type="text" v-model="state.newCity" placeholder="请输入你要查询的城市">
</div>

添加以一个v-show方法,然后绑定一个键盘敲击事件触发handle,并用v-model获取输入的数据并将其存储到state.newCity


const handle = () => {
state.isVisible =!state.isVisible // 回车键将框不显示
state.city = state.newCity // 城市变为输入的城市
getWeather(AMap) // 重新获取该城市天气以及该城市未来天气
}

const toggle = () => {
state.isVisible =!state.isVisible // 使得点击切换城市框会显示和消失
}

以上就是实现获取定位城市,该城市的实时天气,以及未来三天的天气预测,切换查询其它城市的功能具体代码了。


总结


以上使用了Vue.js 组件化的方式来构建界面,利用高德地图 API 获取定位和天气数据,利用 Vue 的响应式机制来实时更新页面数据,通过使用官方文档中 AMapLoader 加载高德地图的JS API,使得我们能高效处理地图相关功能,希望这个小 demo 能够对你的前端开发有所帮助,同时记得给文章点点赞哦🤗。


image.png


作者:六个点
来源:juejin.cn/post/7448246468471521307
收起阅读 »

一个普通人的27岁

致工作三年即将27岁的自己 这是一篇自己的碎碎念、即回顾自己以前的成长经历、也小小的持有一下对未来的期待。 我是一个双非本科从事于Java开发的一名普普通通的码农、不同于大多数人的27岁、大部分人在这个年龄都已经工作了4/5年、而我也恰恰刚刚满三年而已。 读书...
继续阅读 »

致工作三年即将27岁的自己


这是一篇自己的碎碎念、即回顾自己以前的成长经历、也小小的持有一下对未来的期待。


我是一个双非本科从事于Java开发的一名普普通通的码农、不同于大多数人的27岁、大部分人在这个年龄都已经工作了4/5年、而我也恰恰刚刚满三年而已。


读书


小时候的记忆很模糊、很少关于有父母的记忆、从小的印象就是他们在很远的地方打工、那边还有一个从未谋面的哥哥、小时候的记忆更多是和爷爷奶奶在一起,爷爷在我记事起、他就很忙、很少在家里也或许是我不记事或者缺少了这部分的记忆。


在小时候的记忆里、住在茅草屋里面、那个时候家里还没有完全通电、印象里经常点煤油灯、这个时间段应该是02/03年的时候、记忆里这个时候家里养了一头牛、是一头老黄牛。家里在需要耕田播种的时候、不管风吹日晒、都能看见爷爷在田里一边驾驭着黄牛、嘴边一直在说什么、应该是教导牛牛该怎么走以便使的犁田犁的更好。记不清了、只知道每次遇到下雨的时候、爷爷披着蓑衣带着一个草帽、颇有一些武林大侠的气息。


那个时候家里有一条很凶很凶的狗、幺爷爷家里还是一条白猫、年龄比我那个时候都大。这条很凶的狗已经不记得长啥样了、甚至什么时候去世的都没有印象。


关于这条狗不多的记忆就是、它很凶、但凡看见我们在地坝(四川话:门前小院的意思、通常用于晒一些农作物的地方、或者夜晚乘凉的地方)里面打闹。它都会狂吠不止、这是对它的一个记忆。


还有一个记忆就是,记得是在某一个夏天、在屋后发现了一只野兔、这个时候不记得是不是爸妈在家了、全家都在追这个野兔、追了好久、这条狗也在追、有一个画面就是在我小时候的眼里那个农田的岸边很高、这个直接就从岸边往下面的田里跳下去、连续跳了好几个这样的梯田、那个姿势在我眼里好帅好帅、现在都记得很清楚。最后这个野兔是被抓住了、炸了酥肉、那个味道真的很香、现在都记忆深刻。毕竟小时候家里都是吃猪油、用很小很小的一块、煎出油炒菜。


幺爷爷在的猫是条白猫,印象里是一条抓老鼠的好手、但是不知道它什么死的、只记得大概有十三岁左右。


奶奶有风湿心脏病、那个时候总会吃一些药和一些偏方、记忆里有这么一幕、爷爷把刚出生的小狗狗杀掉、给奶奶治病、嘴馋的我也要吃、结果吃了一口就闷住了。


奶奶在我的记忆里有个画面就是我不想去读书、在上学的路上跑到了一个斜坡上、就如同那个时候黑白电视机上播放的游击战争片一样、以为自己躲在这里他们指定找不到、当然了最后肯定少不了一顿打。印象里只被奶奶打了这一次。


奶奶是在06年走的、不到六十岁。记忆特别深,当时哥哥从东北回老家也快一年了、那是在一个夜晚、哥哥先去睡了、我和其他堂哥堂姐在家里看电视、电视里播放的是洪金宝主演的、是一部战争片、大概就是他们去越南救什么人、有一个人在飞机上跳伞的时候说要倒数十个数、然后打开伞、结果这个嘴吃的人没数到就摔死了。里面有个画面用草杀人、后面还依葫芦画瓢学过这个东西。


奶奶走的时候、爷爷是第一个发现的、我记得爷爷发现之后、我去把哥哥喊醒了、然后我就一直在哭。虽然当时不知道死亡意味着什么、就是在哭、那个时候我上三年级了。奶奶走的那天的天气很好、我还记得我捡了一个螺母回家、后来我把这个螺母扔掉了、当时就想如果不捡这个螺母就好了、奶奶就不会走了。


第一次见哥哥的时候是在一个夏天、爸妈把他从东北送了回来、打算让他家里面读书、当然读书的地方现在已经垮掉了。那个时候家里的公路还是泥巴路、泥巴路大概还是前一年挖机采挖的、挖坏了几个秧田。他们在回来的前一年、写了一封信寄回来、内容是什么记不住了、只记得有一封信、分别向爷爷奶奶以及我都写了点东西。初次见面的时候很陌生、眼前这个和我差不多高有点黑的就是我哥、我的关注并没有在他的身上、更多的是他们提的袋子里面、因为有一袋子糖。


当然了小时候的记忆还有几个画面、就埋藏在心里吧、为什么说上面的狗狗很凶、因为他在我堂姐的脸上留下了印子、现在都能看见。


奶奶走掉之后、我和我哥就去了东北、因为家里没人会做饭了。就去东北读书、东北的记忆说不上多好、校园霸凌是一个很常见的事情。


在东北这三年、父母总是在吵架打架、住在平房里面、附近都是和父母一样的体力劳动者、他们一闹附近的人都会知道。我们的右边住了一个也是一个外出务工者、他们的有个女儿、比我和我哥都大、长得很白。在我们的左边也是一户外地务工者、不过是东三省的、不是四川的、这家的女主人好像很贤惠很好看、长得也很白。


在这期间、四川发生了很大的一件事、汶川地震、当时我记得我和附近的小孩偷偷跑去上网、结果附近的网吧都没网、然后回家就看到电视上到处都在播放新闻、去上学的时候、学校组织了捐款、我捐了五块钱。


小学结束之后、过完了六年级的暑假我就被送回到了老家、走的时候是和爸爸在工地上的工友一路的、正好他要回家。他和我们是一个地方的,记得大概是午饭后、叫了一辆出租车、我就和这个工友上了车、爸爸的这个工友坐在了副驾、我坐在了后排、送行的人有几个、车窗升起、行驶了一段路后、眼泪就落下了、大概知道了以后又不会在爸妈身边了、也不知道为什么没有哭出声、就和电视里面一样。这就是小学的记忆。


大概走了三四天、回到家了、就开始上初中了。


报名的时候见到了很多小学同学、他们很容易就认出我来了、然而我并没有很快的认出他们、他们说我五官没什么变化、很好认。


初中是在一所民办初中读的、我们这边的公立学校很水、很乱、上课打牌抽烟都存在、老师也不会管。而且离我们也很远。民办学校离我家很近、这里的校长和附近的家长都很熟悉、自然而然的就去读了、自然而然的也会听到这样的交代、娃儿不听话不好好读书就整哦。初中的算是目前为止的小高光、因为那个时候自己还算聪明、成绩也还算可以、被当着升学的苗子重点关注。当然最后也还算争气、以A+1的成绩考进县一中、我们这一届也还算争气、有一个去了同济大学、算是历史最好的一届了,当然这个学校现在也垮了。


高中的时候流传出了一个梗、你的数学不会是体育老师教的吧、那个时候会自嘲、我初中的时候、不止是数学是体育老师教的、历史和物理也是体育老师教的、这个老师还没上过高中。


高中是lol很火的时候、那个时候脱离了棍棒教育的我、理所当然的沉迷了进去、高一上学期还好、棍棒教育的习惯还在、期末考试全年级2000多人我考了200名左右、班上第五名好像。


学习态度的变化不止是因为lol、还记得当时班上有个人说我很努力、所以成绩这样、当时不知道是脑子抽了还是咋了就认为别人在说我笨、然后就慢慢放弃了之前的学习方式、再加上联盟的影响、自然而然的成绩一落千丈、后来也就去复读了。


复读这一年没什么特别的记忆、涨了几十分、去了一所双非学校。还是没有做到高一班主任说的那样、你好好读上个一本不成问题。当时学校的升学率是前60名可以上川大的程度、200名左右上一个一本好像确实不是什么问题。但也确实没做到。


上了大学就和大部分人一样、加部门、当班干部、实际上就是混吃等死。不同的是大二那年、由于初中埋下的病因、做了双侧股骨头置换手术、这一下就把家里面掏空了、手术是在北京做的、花了20+、是在18年、三月一号做的左腿三月14号下午14:17做的右腿、刚检测出来的时候很崩溃、出了诊室就哭了、因为知道这么大笔钱家里出不起、当时借住在北京的姐姐家,在十五楼窗口处、恐惧战胜了勇气、没有跳下去。


查出来的时候就告知了父母、父母当时在深圳上班、我一个人去的北京找的姐姐、父亲先赶过来、看见父亲憔悴的面庞、自己也彻底取消了跳下去的想法、太憔悴了、没见过这个样子的父亲、也无法去想象如果跳了父亲会咋样、只知道那个时候父亲的头发白了很多、然后开始秃头了。


做手术的那几天恰逢过年期间、医院的人很多、见识了人生百态、有的人痛苦呻吟着想活下去,有的人沉默不语想离开人世,坐在轮椅上的时候、被推出去透透风、看见了一个和我一般大的人、少了一条腿,那个时候心里想着都是苦命人。不同于大一暑假工被晒的黢黑的我,在学校看到一个老外、老外的黑衬托出我的白,那个时候由于被晒的黢黑心情很糟糕,见到这个交换生之后得到了极大的安慰。


因为这个手术需要人照顾、学校是上下铺、因此休学一年、手术很顺利、在我们眼里是一个天大的事情、在医生眼里如果一个小手术一般、就和普通的感冒差不多。术后也会恢复的很好、有一段时间是长短腿、走路一瘸一拐的、过了两个多月吧就彻底正常了。到目前为止至少没什么问题。唱跳rap不打篮球。


后面的大学时光就很平平无奇、本以为就和之前的师兄师姐一样正常大学然后毕业、后面就遇到了口罩事件、在学校都没有好好学习、在家里怎么可能会好好学习、真的是在混吃等死、大学期间没有什么特别的记忆、唯一的印象就是大一老校区是一群室友、大二搬到新校区、又换了一批室友、寝室从原来和其他专业的混寝、变成了同专业的混寝、但是由于休学一年、复学的时候又被安排到新的寝室、又换了一批室友、读了一年这一批室友毕业了、我大四的时候又换了一批室友。也就是一年一批室友。也算是独一份了。不过后面的都没怎么联系了。


这就是整个读书生涯了。还有很多画面就埋藏在心底吧。


工作


毕业之后、第一年认识了一个老师、养鱼达人、第一次约她出来玩、就问我用什么去接他、给了刚毕业的我一个暴击。于是呼在工作上加把力,从刚毕业的几千块不到一年的时间就破万了。也就是在22年左右吧。这个时候总觉得自己谈恋爱应该有点底气了。可在24年又给了我一个暴击。也就是今年。


在整个22年里面、由于工作还行、有大量的自由时间、在b上学习了尚硅谷的mysql和jvm课程、在慕课网上学习Java高并发课程、还算充实、虽然工作上用到的不多。


在22年、养了一只猫取名壹贰、是只三花、很粘人,也很喜欢、但我把它放在老家了。我的头像就是它很可爱吧。


在23年里、由于之前的学习累积、总觉得要记录一下、避免用的时候又到处找、就开始了写博客这个过程、博客更新的速度很稳定、生活节奏也很稳定、每天下班之后、买菜回家做晚饭和第二天中午的午餐、厨艺和刀工得到了大涨,每天晚上还能学习两小时、从周末开始选题、工作日开始编码、验证写博客、一切都有条不紊的进行着,生活节奏很稳定、窗外的阳光也很温暖。


23年发生了一件事、就是爷爷走了、遗憾的是没有带个孙媳妇回去让他看一眼、爷爷是五月份走的、守灵的那个晚上、睡在爷爷旁边、没有丝毫的害怕、下葬的那一天、没有哭但全是遗憾。至此带我长大的两个人都离开了人世。


在23年11月份的时候、认识了一个菇凉、她的名字很好听、长得也很好看、她的生活多姿多彩、现在都觉得她活得很多姿多彩。就和大家想的那样、慢慢的喜欢上了这个人、好巧不巧的是她对我也有点点意思吧、然后就约着出来玩、一起看电影、一起跨年等等、初期总是美好的、回忆也是。她不会做饭、总是吃外卖、我会让她点菜然后我做好了带给她吃、无论什么时候会送她回家然后自己再回家、每次见面都会给她准备一点零食或者小惊喜、理所当然的我们在一起了、直到过完年之后的某一个周末、我朋友约我们出去玩、在晚上回来的时候、我朋友买了房子(和女朋友一起买的)、刚好又说到这个问题、我就说了一句以后我们也一起买、用公积金带款、然后就因为这个问题讨论了一周、直到最后的分手。


具体的细节问题就不说了。我工作三年攒了一些钱、家里修房子我出了一点钱。一时间我家肯定是拿不出来的、我想让他给我点时间、结果不愿意、她之前有过很长一段时间的恋情被分手、大概是害怕再浪费时间、也能理解。


刚分手那段时间、感觉像是丢了半条命。心态很崩溃、觉得自己很差劲、一眼望到了头、好像也成不了家。掘金的更新速度就能看出来影响,虽然在一起的时间不长、三月份分的手、到现在为止有些时候都会因为这件事emo。


分手之前很喜欢做饭、分手之后再也没做过饭、看着那些为了给她做菜买的调味品以及打包盒、总是别有一番滋味、有时候总觉得自己要是当时做的再好一点就好了。在这期间看了一些心理学相关的书、也学会了一些调整自己的方法。


分手这段时间里、激情消费买了辆车、自驾去了一趟若尔盖大草原、草原很好看、自此身上的积蓄被自己花得差不多了。不止如此、由于工作上没有任何发展、总是干一些和Java无关的事情、甚至打算让我做嵌入式开发和大模型这一类工作,职业发展也看到了头。


整理生活这段时间丢了很多东西、总感觉自己也把自己丢了、好在慢慢的把自己拼好重新捡起来了。


下一个月也就马上27岁了、看着身边同龄的人要么成家、要么即将成家、要么事业有成、自己还是孤家寡人,多多少少也很羡慕。


站在生活这条十字路口、迷茫、彷徨、不安、焦虑每隔一段时间都会出现在自己身边、好在自己的调整能力得到了极大的提升、看书总归是有用的。


古人云:三十而立、至少现在看来、在这有限的时间里很难立起来了、但总要去试试、说不定就成了呢。


未来会是什么样子的呢?不知道,能把自己的生活过好就已经很不错了。感知幸福是一种能力、感知焦虑也是。


对生活的感悟如同总有千言万语、却有一种如鲠在喉的感觉。不知道命运会给我带来什么样的生活?不管怎么样都坦然接受吧。期待吗?期待吧。


写到这里、感受万千、内心细腻的人总是容易伤春悲秋。


回顾过往、就如同这篇文字一样、普普通通平平无奇、都无法用鸡肋来形容。但相信生活不会辜负每一个好好生活的人、始终对未来抱有期待与憧憬。不管最终如何、终将相信我们都会过上自己想要的生活。


最后给自己定一个目标吧:



  • 坚持写博客、写到35岁,我相信自己会一直从事计算机行业的!

  • 健健康康的活到退休。


窗外的天空很蓝、阳光很温暖、最近的心情也很好、希望您也是。


谢谢您能看到这里,祝君心想事成、万事顺遂。


作者:晚_风
来源:juejin.cn/post/7396609176744886310
收起阅读 »

独立开发上班后:我的故事,你的酒,一腔沉默往前走

有时候,我会断言世间奇妙的事情都让我赶上了。后来又觉得是自作多情,因为上天压根就没正眼瞧过我,何谈特意针对,我经历的只是业界常态而已。 我,一名老程序员,失业,本以为能凭借一人可抵一支小团队的技术能力,混口饭吃,实现小富。但是,漂浮半年,收入不及原来工资的二...
继续阅读 »

有时候,我会断言世间奇妙的事情都让我赶上了。后来又觉得是自作多情,因为上天压根就没正眼瞧过我,何谈特意针对,我经历的只是业界常态而已。



我,一名老程序员,失业,本以为能凭借一人可抵一支小团队的技术能力,混口饭吃,实现小富。但是,漂浮半年,收入不及原来工资的二分之一。


最终无奈重新找工作,已上班一个月有余。


找工作时,我特意选择了一家不是很忙的公司。选这类公司,我也是费了一番功夫。首先是凭借自己的判断:一定是传统大型企业,IT人数占比不超过5%。这样的配置说明公司主业务不以IT为主,IT部门仅仅是给传统业务打辅助。其次,从内部的IT员工验证是否加班多。小城市的圈子不大,好打听。我了解到这个公司最近一年内基本不加班。面试时,HR和部门领导也以不加班为谈判优势。


随后,面对一众offer(包含涨薪到120%的巨忙公司)我选择了这家降薪至80%的不忙公司。因为之前的经历,太多无意义的忙碌,会导致生活失去意义。入职后,签了很多协议,包括薪资构成。谈好的固定工资,变成50%基本工资+50%绩效工资。也就是说即便选择降薪,这钱我可能也拿不全。HR说你不用担心,如果你不犯错就能拿全。


好吧。


我开始了我的工作。


和我同时入职的,还有一个空降而来的IT高管。公司历来对IT部门不满意。新来的IT高管对公司进行了诊断,发现一个问题:IT人员的工作不饱和啊,居然不加班!干这行哪有不加班的?!


其实,这个问题,我也发现了。我常环顾四周,发现80%的人60%的时间都在玩手机。


随后,我似乎开始了单休生涯。第一次周六加班,说是要专门验证我AI算法的正确率。然而,我监控了一天,我写的接口一次都没有被调用。晚上,领导问我怎么样?我说,没有收到任何反馈。领导说摇摇头说,唉,正确率真的很差。我又去问测试,测试说模板样例都生成不了,还没到算法检测那一步。


第二次周六加班,是上线后验证整个业务流程。但是,这个线一直没有上去。Java发版一直有问题,提示版本号不对,找不到JDK。而且,生产发版不是运维操作,也不是测试操作,是开发人员自己操作。


我一开始,非常热心。前端写的小程序拍照不清晰,总是出现糊了一片的情况,前端说受限于平台,解决不了。安卓和测试讨论说,一连串音频很难实现依次播放,更无法实现打断一个音频播另一个音频。我则转身就把Demo写好了给他们。后端说不用考虑并发,我说现在不考虑后面肯定会出问题。


表面上看是你帮团队处理卡点问题,实则是你抬高自己贬低别人,对立面包括你的同事和领导。这导致有我不在场的会议,张三说那个新来的算法能力不行,我给他发了一个基础算法问题,他看不懂。而此时,我根本不认识张三。这件事,是李四告诉我的。


先做人再做事,是我们千百年以来的文化精髓。我以为选择了计算机,可以有效地避免这个问题,实际上这并不可能。


其实,我应当学会和环境共存。翻看我以前写的文章,好像也充满了类似的抱怨和愤懑。这说明现状大抵就是这样,是我自己的问题。


如果一个人长期在不变的圈子里混,说明你也不是什么高人。否则,你早就跳出圈外了。既然没有跳到圈外,那么你还有需要突破的东西。就比如清高、严谨或者太过于纯粹。


一个纯粹的人,可能会推崇一种非常耐用的电灯泡,甚至可以亮一百年不会坏。但是,这会导致产品只能卖一次,根本没有替换的需求,最终结局是工厂倒闭。它可能是个精品,甚至是一个真理,但它并不符合市场规则,无法生存。这就好比你写出非常完美,不需要任何修改和维护的项目一样。


很好。你有这种能力,能让一个技术团队,人缩减到原来的50%, 活承担到原来200%,稳定率还能提高到原来的120%。但是,被砍掉的那部分人乐意吗?被削减了势力的总监乐意吗?你凭什么要这样?他们凭什么会那样?


你可能会觉得老板肯定乐意吧。不一定。你觉得,老板是更信任你呢,还是更信任跟随他创业多年的兄弟。他甚至认为你不是来降本增效的,而是来搞破坏的。


这很形象。在你不是权威教育专家的情况下,你有好的教育方式,你要用在你孩子身上,别拿别人的孩子下手啊。


打工和创业,完全不一样。打工,你做好自己分内的事情就好。没有人来找你,或者你对形势不是非常了解,不要乱动。


所以,并不是能力越大,责任越大。而是权利越大,责任越大。


慢慢熬着吧,熬到和同事们关系融洽,熬到老板信任了你,或许那时更容易施展一些策略。


看着眼前的一幕幕,一个个坑,该参与就参与,该跳就跳。别谈故事别谈酒,低头沉默往前走。



本故事纯属虚构,如有雷同,纯属巧合。



作者:TF男孩
来源:juejin.cn/post/7435175934170529830
收起阅读 »

普通人能否彻底告别代码学习?

小明:“嘿,AI,你说我还需要学编程吗?” 机器人:“理论上,当我达到‘终极智能’时,你可能就不需要了。但现在嘛,还是得学一点。” 小程:“那程序员呢?他们会被你取代吗?” 机器人:“哈哈,别担心!虽然我能帮你写代码,但现阶段我更像是个助手,而不是替代者。” ...
继续阅读 »

小明:“嘿,AI,你说我还需要学编程吗?”


机器人:“理论上,当我达到‘终极智能’时,你可能就不需要了。但现在嘛,还是得学一点。”


小程:“那程序员呢?他们会被你取代吗?”


机器人:“哈哈,别担心!虽然我能帮你写代码,但现阶段我更像是个助手,而不是替代者。”


普通人能否彻底告别代码学习,直接使用AI编程?


就这个问题我们先来看看几位大佬们的观点:


百度-李彦宏


2024《对话·开年说》系列中,百度公司创始人、董事长李彦宏在节目中表示“以后不会存在程序员这种职业了”



360-周鸿祎


在《对话》现场,360创始人兼董事长周鸿祎对李彦宏的观点提出反对意见“我不同意这个观点”。



英伟达-黄仁勋


黄仁勋认为,即便是在人工智能(AI)革命刚刚起步的今天,编程已不再是一项关键技能。



可以看到大佬们对AI与编程的影响这个问题的回答,虽然有差异,但我们不难看出AI在影响着编程这个职业或者行业。或许随着技术的发展与成熟,最终AI可以完全的代替人类。当然这个时间可能是一万年或许会更长或者更短。



之前也有读过阮一峰大佬《未来世界的幸存者》, 2018年7月发表的“技术的边界”中有写到:


“人工智能领域有一个概念,叫做“终极智能”。意思是,当机器的智能达到这种程度时,就不需要人类再做发明创造了,因为机器自己就会发明创造。”


我们来看看现阶段AI能给我们编程带来些什么?


一、专业性AI编程插件的能力


自从GPT带动全球AI热潮,AI席卷着各行各业。而在编程界也发生了巨大的变化,最出名的莫过于OpenAI与GitHub联合开发的Github Copilot。Github Copilot带动了一大堆AI编程工具的出现。


当然除了Github Copilot之外还有很多优秀的AI编程插件,我们来具体看一看:



p.s.以上的下载量与评分均只是plugins.jetbrains的marketplace数据,发布的时长也不相同,数据仅供参考。


基本AI编程工具的功能都差不多:



  • 代码补全:根据当前代码上下文自动补全代码。

  • 根据注释生成代码:根据注释描述生成相应的代码。

  • 方法和函数生成:根据方法名或函数名自动生成该方法或函数的代码。

  • 生成测试代码:生成测试代码。

  • ....


这里选择豆包MarsCode来展示AI编程插件的功能:



MarsCode 是豆包旗下的智能编程助手,提供以智能代码补全为代表的核心能力,支持主流编程语言及 IDE,能在编码过程中提供单行或整个函数的建议,同时支持在用户编码过程中提供代码解释、单测生成、问题修复、技术问答等辅助功能,提升编码效率与质量。


安装方式


JetBrains与 Visual Studio Code都可以安装,比如下面就是Visual Studio Code中编程助手的安装,在市场搜索后进行安装。



安装好后就可以看到AI功能界面



主要功能



  • 行级/函数级实时补全、注释生成代码


在编码过程中提供单行或多行的代码推荐,并支持通过注释生成代码片段,提升代码编写速度。



我只写了注释,回车后代码就自动会生成



  • 代码解释


精确解释项目代码,帮助开发人员快速熟悉项目。



生成代码注释




  • 单元测试生成


为选中函数生成单测,提升单测覆盖率,提升代码质量。




  • 智能修复


一键修改代码bug,提升代码修复效率。


当运行程序出现bug后,“AI Fix”图标会自动出现,点击后会可以通过AI生成相应的解决方案,解决方案里也会有相应的按钮半自动化的处理,非常的方便。




  • AI 智能问答


针对研发领域定向优化问答质量,提供更精准的问答结果。



1 通用性AI产品的编程能力


目前市面上能实现编程的AI产品非常多,基本上AI产品都会带编程的能力,比如chatgpt、文心一言、通义千问、豆包等


下面我们用chatgpt4o与kimi的对比,来了解通用性AI产品的编程能力


2 生成手机正则代码



  • KIMI


这里是KIMI生成的代码,



把代码贴到IDE中是可以直接执行的




  • chatgpt4o


和KIMI类似生成相应的正则代码,不过chatgpt默认会生成不同国家的手机号格式的正则



当然这只是比较简单的正则表达式。不过这种情况下就不需要我们去学习复杂的正则表达试的语法了,直接拿过来用就可以了。


再如core表达式也是类似的,比如:每周一晚上10执行一次的core表达式


就需要我们专门去学习core表达式的语法了



3 不同语言代码转换


我们现在让AI把上面的python代码转换成javascript代码



  • KIMI




  • chatgpt



这次两者是完全相同的,在IDE里也是可以执行的



4 生成PDF电子签名


让AI生成PDF电子签名的代码



  • kimi


给chatgpt以下需求:请写出itextpdf5实现pdf电子签名的代码


生成的代码直接放到IDE里还是不能直接使用的



可以看到是缺少import,kimi生成的代码中import并不完整,先把缺少的import先引入



引入后还是有多处错误


1)函数参数类型不正确


2)变量没定义


3)无对象枚举



  • chatgpt


给chatgpt相同的需求



把生成后的代码拷贝到IDE中,可以看到依赖已经下载好了,程序还是会报错



发现是import引用缺失,增加相应的import。


然后还是会发现PdfSignatureAppearance是没有WINCER_SIGNED枚举。一般来说就是引用的版本不对,说明itext生成的代码依赖与代码是不对称的。



虽然chatgpt4o生成的效果好一点,但还是不能直接使用。但大体上还是能知道实现PDF电子签名的技术实现,微调后还是可以使用。还是得完全懂代码的人才能正直使用起来。


总结


AI辅助编程给我们带来了一次变革,但目前或者很长一段时间内它的作用还是辅助的。并没有达到能代替程序员的能力。


像生成代码、代码注释、单元测试、bug自动修复等功能对编程的助力是非常大的。


我是栈江湖,如果你喜欢此文章,不要忘记点赞+关注


作者:栈江湖
来源:juejin.cn/post/7452197545588146214
收起阅读 »

作为一名程序员,你是如何看待外包的

大家好,我是凌览 。 同样是程序员靠手艺吃饭,为啥外包却是过街老鼠人人喊打,这里我精选了几位网友的回答让我们一起来看看。 第一位网友 其实我觉得,国家应当立法禁止外包驻场。应当规定只有在外包公司所在办公场所工作才能算外包,驻场外包一律必须与目标公司签订劳务合...
继续阅读 »

大家好,我是凌览


同样是程序员靠手艺吃饭,为啥外包却是过街老鼠人人喊打,这里我精选了几位网友的回答让我们一起来看看。


第一位网友


其实我觉得,国家应当立法禁止外包驻场。应当规定只有在外包公司所在办公场所工作才能算外包,驻场外包一律必须与目标公司签订劳务合同。否则,驻场外包本质上相当于公司钻劳动法漏洞雇人。


你请外包公司开发软件,给需求给预算给时间给报酬,对方开发了给你验收,这叫外包。没毛病。


你给外包公司钱,人家直接把人派到你办公室,这叫什么玩意的外包?这不就是逃避责任,规避劳动法么?——你是个公司,又不是个人。


当然了,其实确实有些情况,需要不同公司去同一个办公地点合作做项目的,大家觉得说不清楚这与外包的区别。但其实区别还是很明显,区别在于,这些员工遵守谁家的工作制度。


我是A公司人,去客户B那里出差,帮客户B解决问题,这段时间虽然在客户B公司上班,但我不用打他们的卡,不用给他们汇报工作,不用交他们周报,我只对自己公司负责,这是出差,因为我还是A公司员工。


如果我在A公司签订合同然后去B公司工作,由他们(B公司)给分配任务,由他们考核计划完成情况,由他们收我周报,由他们定我KPI,我向他们汇报工作,这性质就完全变了。


这除了劳务关系以外,难道不是实质上B公司员工么?如果允许这样的形态存在,那不就等于是B公司的金蝉脱壳方式规避劳动法么?


所以我的看法是这样:外包可以,B公司写好需求人力时间,签合同,包项目,A公司直接交付最终成果,A公司的员工不受B公司管理,这是外包,这样的外包我觉得很合理。——A公司直接把人派出去给B公司,让B公司管理A公司的人,这不叫外包,这叫买卖人口,这叫A公司帮B公司规避劳动法,这是对外包的侮辱。


第二位网友


我其实一直本着给钱做事的风格,所以外包我并不歧视,直到有一天。


今年我面试了一个外包,行情不好,所以不怎么敢开薪资,比离职前低了一丢丢的样子,喊了12K


甲方面试,问的那一个细,从日常工作到项目数据流,到接口全问了一个遍,还好大差不多,聊了半个多小时。


面试结果是过的。


但是但是面试官和我介绍项目时候就说了,上半个月加班会少一点,可能到8点9点,下半个月可能会到1点2点,偶尔周六还要加班,是一个新项目。


我懂了。


我就问外包公司有加班费吗?他们说没有,只能调休,我算了一下按照面试官介绍,这加班一个月得加班120个小时打底,这没加班费,还只能调休,我直接裂开。


五险一金有,最低的


试用期全薪,这个除了小公司基本上是全的。


剩下啥福利没有,没餐补,没车补,啥也没有。


我直接就拒了。。


第三位网友


外包的活尽量别干,比如培训班入行,或换城市发展着急找工作,或者刚毕业想积累经验,这些情况下可能不得不找外包积累经验,但外包的活尽量别超过2年,干3年都嫌多,原因如下:



  1. 外包员工的工资会被“折上折”,甲方公司会根据自己同等条件员工的薪资打个折给外包公司,而外包公司会在此基础上再打个折,所以外包的薪资一般是甲方同类员工的6折甚至更低。

  2. 技术上得不到提升。甲方公司明着可能不说,但在分配重要活的时候,一定是一个正式员工带若干个外包员工,外包员工顶多就调用下api,打打下手,这样干2,3年,接触不到核心技术,而且在组里干久了业务都熟,可能还自我感觉良好。但此时如果出去找工作,真就很难找了。

  3. 出了问题,会让外包公司顶包。比如在一个项目中,只要外包员工参与的活出了大问题,一般甲方员工顶多就内部批评,外包员工一般就会被“退回原外包公司”。

  4. 工作环境不好,传说中的“不能吃甲方公司提供的零食”,这真不是空悬来风。

  5. 丧失信心。外包干久了,逆来顺受惯了,真会认为自己无法去挑战更高级的职位。

  6. 有一定的风险。比如甲方公司项目组砍预算,优先考虑的是,裁剪外包员工。


总之,甲方人员对外包员工可能真是客客气气的,但在各种工作中,总不免会想,我是甲方,他是外包,也就是说,甲方和外包之间的鸿沟是天然存在的。


作者:程序员凌览
来源:juejin.cn/post/7453817457912938505
收起阅读 »

一个网页打造自己的天气预报

web
概念解释 通过数据接口,简化功能开发。天气数据从哪里来?如果是自己采集,不光要写后端代码,最难的是维护啊,所以必须《天气预报》此类APP特别适合 前后端分离的,以下用一个简单的例子快速打通前后端的调用 前端使用HTML页面,通过JS的Fetch发起请求,向天气...
继续阅读 »

b64dacfad036df7512a0dbcd6a7ceb12.png


概念解释


通过数据接口,简化功能开发。天气数据从哪里来?如果是自己采集,不光要写后端代码,最难的是维护啊,所以必须《天气预报》此类APP特别适合 前后端分离的,以下用一个简单的例子快速打通前后端的调用

前端使用HTML页面,通过JS的Fetch发起请求,向天气API拿到JSON数据回显到页面上。

比较麻烦的是找到免费易用天气API接口。


前后端分离


前端负责用户界面展示和交互体验,

后端负责业务逻辑和数据处理。

这里后端直接使用免费的天气API,所以后端可以视为云服务。图上的左半部分。



  1. 前端:HTML+JS+Fetch请求

  2. 后端:云服务API(天气数据接口网站)

  3. 数据:JSON格式传输


数据接口


简化理解为一个返回JSON数据的网页。


项目《天气预报》


一、后端 云服务API(天气数据接口网站)


1. 注册激活帐号(目标得到APPID和APPSecret即可)


找到免费方便的天气API数据接口网页,这里使用 http://www.yiketianqi.com/
(非广告 只是顺手找到,如果有更方便的欢迎评论区留言),每天有1000次免费调用

注册记下自己 APPIDAPPSecret ,前端请求时要用
图片.png


2. 数据接口文档


一定要注册帐号,才能看到自己的 APPIDAPPSecret
文档中 http://www.yiketianqi.com/index/doc
直接复制下图(1) 就是前端用到的 目标数据接口


图片.png


3.测试天气数据API


以下URL供 前端请求时替换成自己的 APPIDAPPSecret



gfeljm.tianqiapi.com/api?unescap…



使用浏览器打开即可,可以通过浏览器观察,其实前端有这个URL就开业啦
图片.png


二、前端 HTML+JS+Fetch请求


1. 基础fetch请求页面


使用fetch方法发起请求,特别注意每一步返回的数据是否为Promise,需要使用async和await消除回调


const appid = 68621484
const appsecret = `XXXXX`
let URL = `http://gfeljm.tianqiapi.com/api?unescape=1&version=v63&appid=${appid}&appsecret=${appsecret}`

获取(URL)

async function 获取(URL){
let 响应 = await fetch(URL)
let 数据 = await 响应.json()

return 数据
}

注意这个页面通过浏览器 查看网络请求XHR


图片.png


2. 完整静态HTML页面


制作一个简易的HTML页面,显示出关键数据。更多数据需要参考接口文档。


图片.png


使用Fetch发起请求,获得数据后,使用innerHTML属性替换掉元素内容。

同时使用模版字符串,没有使用任何CSS样式。


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>fetch请求</title>
</head>
<body>

<div id="A">
</div>

<script>

const appid = 68621484
const appsecret = `fZnW1ikK`
let URL = `http://gfeljm.tianqiapi.com/api?unescape=1&version=v63&appid=${appid}&appsecret=${appsecret}`


main()

async function main(){
let data = await 获取(URL)


const listItems = data.hours.map(hour => `
<li>
时间:${hour.hours}<br>
天气状况:${hour.wea}<br>
天气图标:<img src="images/weather_icons/${hour.wea_img}.png" alt="${hour.wea}"><br>
温度:${hour.tem}°C<br>
风向:${hour.win}<br>
风速:${hour.win_speed}<br>
能见度:${hour.vis} km<br>
空气质量:${hour.aqi}<br>
</li>
`
).join('');

A.innerHTML = `

<h2>城市:${data.city} (${data.cityEn})</h2>
国家:${data.country} (${data.countryEn})<br>
日期:${data.date} ${data.week}<br>
更新时间:${data.update_time}<br>
天气状况:${data.wea}<br>
天气图标:<img src="images/weather_icons/${data.wea_img}.png" alt="${data.wea}"><br>
当前温度:${data.tem}°C<br>
最高温度:${data.tem1}°C<br>
最低温度:${data.tem2}°C<br>
风向:${data.win}<br>
风速:${data.win_speed} (${data.win_meter})<br>
湿度:${data.humidity}<br>
能见度:${data.visibility}<br>
气压:${data.pressure} hPa<br>
降雨量:${data.rain_pcpn} mm<br>
空气质量指数:${data.air}<br>
PM2.5:${data.air_pm25}<br>
空气质量等级:${data.air_level}<br>
空气质量提示:${data.air_tips}
<ul>
${listItems}
</ul>
`
;


console.log(data)
}

async function 获取(URL){
let 响应 = await fetch(URL)
let 数据 = await 响应.json()

return 数据
}


</script>
</body>
</html>

3. 补充CSS样式


图片.png
ul{
display: flex;
flex-wrap: wrap;
list-style: none;
}

li{
width: 300px;
background-color: palegreen;
margin: 10px;
padding: 15px;
border-radius: 50%;
text-align: center;
}

span{
padding: 15px;
background-color: orange;
cursor: pointer;
}

4. 补充JS多城市查询


天气.gif


4.1 增加对应的HTML代码

图片.png


4.2 增加对应的JS代码

图片.png


三、项目图示总结


使用Fetch和async/await极大的简化了前端代码。后端数据接口就是一个URL地址。
整个后端具备云服务的特征,可以视作云服务数据接口,如图所示


图片.png


四、天气接口


1.易客云天气API 推荐:⭐⭐⭐⭐⭐


对新手比较友好。
tianqiapi.com/


2.高德地图 需要注册开发者(推荐:⭐⭐⭐)


lbs.amap.com/api/webserv…


3.心知天气(推荐:⭐)


免费的API数据只有一行,且文档藏得太深难用
http://www.seniverse.com/


欢迎大家提供更多更好的天气API。


作者:百万蹄蹄向前冲
来源:juejin.cn/post/7441560184010014735
收起阅读 »

“有办法让流程图动起来吗?”“当然有!”:一起用LogicFlow实现动画边

web
引言 在流程图中,边(Edge) 的主要作用是连接两个节点,表示从一个节点到另一个节点的关系或流程。在业务系统中,边通常代表某种逻辑连接,比如状态转移、事件触发、任务流动等。对于复杂的流程图,边不仅仅是两点之间的连接,它还可以传递信息、约束流程的顺序,并通过不...
继续阅读 »

引言


在流程图中,边(Edge) 的主要作用是连接两个节点,表示从一个节点到另一个节点的关系或流程。在业务系统中,边通常代表某种逻辑连接,比如状态转移、事件触发、任务流动等。对于复杂的流程图,边不仅仅是两点之间的连接,它还可以传递信息、约束流程的顺序,并通过不同的样式或标记来表达不同的含义。


不同的场景下,边可能需要具备丰富的样式或交互,比如箭头表示方向、虚线表示条件判断、动画表示动态效果等。因此,灵活定义和实现自定义边对于流程图的可视化设计尤为重要。


LogicFlow的边


为了灵活适配不同场景下的需求,LogicFlow的边模型是由 线条、箭头、文本、调整点五个模块组成。用户可以继承基础边类,对边的线条、箭头、文本和调整点进行自定义。


edge-struct.png
在技术实现上,LogicFlow设计了一个基础边模型BaseEdge,它定义了LogicFlow边的基本属性,如起点、终点、路径、样式等,并提供了操作这些属性的基本方法,提供逻辑处理和渲染的基础,通过继承基础边的数据类BaseEdgeModel和视图类BaseEdge,可以实现自定义边的逻辑和交互。


基础边:BaseEdge


属性方法简介

BaseEdgeModel中定义了一些核心属性,用于描述边的几何结构和样式。


属性释义
sourceNodeId起始节点Id
targetNodeId目标节点Id
startPoint起点信息,默认存储的是起始节点上连接该边锚点的坐标信息
endPoint终点信息,默认存储的是目标节点上连接该边锚点的坐标信息
text边文本信息,存储边上文本的内容和位置
properties自定义属性,用于存储不同业务场景下的定制属性
pointsList路径顶点坐标列表

围绕着这些核心属性,LogicFlow设计了支撑边运转的核心方法


方法用途
initEdgeData初始化边的数据和状态
setAnchors设置边的端点,startPoint和endPoint会在这个被赋值
initPoints设置边路径,pointsList会在这个阶段被赋值
formatText将外部传入的文本格式化成统一的文本对象

还有一些渲染使用的样式方法


方法用途
getEdgeStyle设置边样式
getEdgeAnimationStyle设置边动画
getAdjustPointStyle设置调整点样式
getTextStyle设置文本样式
getArrowStyle设置箭头样式
getOutlineStyle设置边外框样式
getTextPosition设置文本位置

运转过程

边实例化时,数据层Model类内部会先调用initeEdgeData方法,将无需处理的属性直接存储下来,设置为监听属性然后触发setAnchors、initPoints和formatText方法,生成边起终点、路径和文本信息存储并监听。


model-run.png


视图层渲染时,Model中存储的数据会以外部参数的形式传给组件,由不同渲染方法消费。每个渲染方法都是从Model存储的核心数据中获取图形信息、从样式方法中获取图形渲染样式,组装到svg图形上。最终由render函数将不同模块方法返回的内容呈现出来。


view-run.png


内置衍生边


LogicFlow内部基于基础边衍生提供三种边:直线边、折线边和曲线边。


直线边

在基础边的之上做简单的定制:



  1. 支持样式快速设置

  2. 限制文本位置在线段中间

  3. 使用svg的line元素实现线条的绘制


ViewModel
LogicFlow-packages-core-src-view-edge-LineEdge-tsx-at-master-·-didi-LogicFlow-10-29-2024_09_07_PM.pngimage.png

直线边数据层和视图层源码逻辑


折线边

折线边在Model类的实现上针对边路径计算做了比较多的处理,会根据两个节点的位置、重叠情况,使用 A*查找 结合 曼哈顿距离 计算路径,实时自动生成pointsList数据。在View类中则重写了getEdge方法,使用svg polyline元素渲染路径。


录屏2024-10-30 10.52.14.gif


曲线边

曲线边和折线边类似,Model类针对边路径计算做了较多处理,不一样的是,为了调整曲线边的弧度,曲线边额外还提供了两个调整点,边路径也是根据边起终点和两个调整点的位置和距离计算得出,View类里使用svg的path元素渲染路径。


录屏2024-10-30 10.54.48.gif


一起实现一条自定义动画边


自定义边的实现思路和内置边的实现类似:继承基础边 → 重写Model类/View类的方法 → 按需增加自定义方法 → 命名并导出成模块


今天就带大家一起实现一条复杂动画边,话不多说,先看效果:


animate-line-high-quality.gif


要实现这样效果的边,我们核心只需要做一件事:重新定义边的渲染内容。


在实际写代码时,主要需要继承视图类,重写getEdge方法。


实现基础边

那我们先声明自定义边,并向getEdge方法中增加逻辑,让它返回基础的折线边。


为了方便预览效果,我们在画布上增加节点和边数据。


自定义边实现

import { h, PolylineEdge, PolylineEdgeModel } from '@logicflow/core'

class CustomAnimateEdge extends PolylineEdge {
// 重写 getEdge 方法,定义边的渲染
getEdge() {
const { model } = this.props
const { points, arrowConfig } = model
const style = model.getEdgeStyle()
return h('g', {}, [
h('polyline', {
points,
...style,
...arrowConfig,
fill: 'none',
strokeLinecap: 'round',
}),
])
}
}

class CustomAnimateEdgeModel extends PolylineEdgeModel {}

export default {
type: 'customAnimatePolyline',
model: CustomAnimateEdgeModel,
view: CustomAnimateEdge,
}


定义画布渲染内容

lf.render({
nodes: [
{
id: '1',
type: 'rect',
x: 150,
y: 320,
properties: {},
},
{
id: '2',
type: 'rect',
x: 630,
y: 320,
properties: {},
},
],
edges: [
{
id: '1-2-1',
type: 'customPolyline',
sourceNodeId: '1',
targetNodeId: '2',
startPoint: { x: 200, y: 320 },
endPoint: { x: 580, y: 320 },
properties: {
textPosition: 'center',
style: {
strokeWidth: 10,
},
},
text: { x: 390, y: 320, value: '边文本3' },
pointsList: [
{ x: 200, y: 320 },
{ x: 580, y: 320 },
],
},
{
id: '1-2-2',
type: 'customPolyline',
sourceNodeId: '1',
targetNodeId: '2',
startPoint: { x: 150, y: 280 },
endPoint: { x: 630, y: 280 },
properties: {
textPosition: 'center',
style: {
strokeWidth: 10,
},
},
text: { x: 390, y: 197, value: '边文本2' },
pointsList: [
{ x: 150, y: 280 },
{ x: 150, y: 197 },
{ x: 630, y: 197 },
{ x: 630, y: 280 },
],
},
{
id: '1-2-3',
type: 'customPolyline',
sourceNodeId: '2',
targetNodeId: '1',
startPoint: { x: 630, y: 360 },
endPoint: { x: 150, y: 360 },
properties: {
textPosition: 'center',
style: {
strokeWidth: 10,
},
},
text: { x: 390, y: 458, value: '边文本4' },
pointsList: [
{ x: 630, y: 360 },
{ x: 630, y: 458 },
{ x: 150, y: 458 },
{ x: 150, y: 360 },
],
},
{
id: '1-2-4',
type: 'customPolyline',
sourceNodeId: '1',
targetNodeId: '2',
startPoint: { x: 100, y: 320 },
endPoint: { x: 680, y: 320 },
properties: {
textPosition: 'center',
style: {
strokeWidth: 10,
},
},
text: { x: 390, y: 114, value: '边文本1' },
pointsList: [
{ x: 100, y: 320 },
{ x: 70, y: 320 },
{ x: 70, y: 114 },
{ x: 760, y: 114 },
{ x: 760, y: 320 },
{ x: 680, y: 320 },
],
},
],
})

然后我们就能获得一个这样内容的画布:


绚丽动画折线-LogicFlow-Examples-10-30-2024_11_08_AM.png


添加动画

LogicFlow提供的边动画能力其实是svg 属性和css属性的集合,目前主要支持了下述这些属性。


type EdgeAnimation = {
stroke?: Color; // 边颜色, 本质是svg stroke属性
strokeDasharray?: string; // 虚线长度与间隔设置, 本质是svg strokeDasharray属性
strokeDashoffset?: NumberOrPercent; // 虚线偏移量, 本质是svg strokeDashoffset属性
animationName?: string; // 动画名称,能力等同于css animation-name
animationDuration?: `${number}s` | `${number}ms`; // 动画周期时间,能力等同于css animation-duration
animationIterationCount?: 'infinite' | number; // 动画播放次数,能力等同于css animation-iteration-count
animationTimingFunction?: string; // 动画在周期内的执行方式,能力等同于css animation-timing-function
animationDirection?: string; // 动画播放顺序,能力等同于css animation-direction
};

接下来我们就使用这些属性实现虚线滚动效果。


边的动画样式是取的 model.getEdgeAnimationStyle() 方法的返回值,在内部这个方法是取全局主题的edgeAnimation属性的值作为返回的,默认情况下默认的动画是这样的效果:


default-edge-animation.gif


开发者可以通过修改全局样式来设置边动画样式;但如果是只是指定类型边需要设置动画部分,则需要重写getEdgeAnimationStyle方法做自定义,就像下面这样:


class ConveyorBeltEdgeModel extends PolylineEdgeModel {
// 自定义动画
getEdgeAnimationStyle() {
const style = super.getEdgeAnimationStyle()
style.strokeDasharray = '40 160' // 虚线长度和间隔
style.animationDuration = '10s' // 动画时长
style.stroke = 'rgb(130, 179, 102)' // 边颜色
return style
}
}

然后在getEdge方法中加上各个动画属性


// 改写getEdge方法内容
const animationStyle = model.getEdgeAnimationStyle()
const {
stroke,
strokeDasharray,
strokeDashoffset,
animationName,
animationDuration,
animationIterationCount,
animationTimingFunction,
animationDirection,
} = animationStyle

return h('g', {}, [
h('polyline', {
// ...
strokeDasharray,
stroke,
style: {
strokeDashoffset: strokeDashoffset,
animationName,
animationDuration,
animationIterationCount,
animationTimingFunction,
animationDirection,
},
}),
])

我们就得到了定制样式的动画边:


base-edge-animation.gif


添加渐变颜色和阴影

最后来增加样式效果,我们需要给这些边增加渐变颜色和阴影。
SVG提供了元素linearGradient定义线性渐变,我们只需要在getEdge返回的内容里增加linearGradient元素,就能实现边颜色线性变化的效果。
实现阴影则是使用了SVG的滤镜能力实现。


// 继续改写getEdge方法内容
return h('g', {}, [
h('linearGradient', { // svg 线性渐变元素
id: 'linearGradient-1',
x1: '0%',
y1: '0%',
x2: '100%',
y2: '100%',
spreadMethod: 'repeat',
}, [
h('stop', { // 坡度1,0%颜色为#36bbce
offset: '0%',
stopColor: '#36bbce'
}),
h('stop', { // 坡度2,100%颜色为#e6399b
offset: '100%',
stopColor: '#e6399b'
})
]),
h('defs', {}, [
h('filter', { // 定义滤镜
id: 'filter-1',
x: '-0.2',
y: '-0.2',
width: '200%',
height: '200%',
}, [
h('feOffset', { // 定义输入图像和偏移量
result: 'offOut',
in: 'SourceGraphic',
dx: 0,
dy: 10,
}),
h('feGaussianBlur', { // 设置高斯模糊
result: 'blurOut',
in: 'offOut',
stdDeviation: 10,
}),
h('feBlend', { // 设置图像和阴影的混合模式
mode: 'normal',
in: 'SourceGraphic',
in2: 'blurOut',
}),
]),
]),
h('polyline', {
points,
...style,
...arrowConfig,
strokeDasharray,
stroke: 'url(#linearGradient-1)', // 边颜色指向渐变元素
filter: 'url(#filter-1)', // 滤镜指向前面定义的滤镜内容
fill: 'none',
strokeLinecap: 'round',
style: {
strokeDashoffset: strokeDashoffset,
animationName,
animationDuration,
animationIterationCount,
animationTimingFunction,
animationDirection,
},
}),
])

就得到了我们的自定义动画边


录屏2024-10-29 19.57.02.gif


结尾


在流程图中,边不仅仅是节点之间的连接,更是传递信息、表达逻辑关系的重要工具。通过 LogicFlow,开发者可以轻松地创建和自定义边,以满足不同的业务场景需求。从基础的直线边到复杂的曲线边,甚至动画边,LogicFlow 都为开发者提供了高度的灵活性和定制能力。


希望能通过这篇文章抛砖引玉,帮助你了解在 LogicFlow 中创建和定制边的核心技巧,打造出符合你业务需求的流程图效果。


如果这篇文章对你有帮助,请为我们的项目点上star,非常感谢ღ( ´・ᴗ・` )


项目传送门:github.com/didi/LogicF…


作者:LogicFlow
来源:juejin.cn/post/7431379490969010212
收起阅读 »

程序员加班很晚应该怎么锻炼身体?

作为程序员,肯定都深受加班的痛苦。 ❝那每天加班很晚的情况下,该通过怎样的锻炼来保持身体健康呢? 我觉得还是得先把觉睡够,然后才是锻炼。 ❝睡眠不足情况下高强度锻炼,容易猝死。 如果睡觉的时间都不够,建议辞,换个不太卷的地方。 把特别卷的岗位,留给那些更...
继续阅读 »

作为程序员,肯定都深受加班的痛苦。



❝那每天加班很晚的情况下,该通过怎样的锻炼来保持身体健康呢?



我觉得还是得先把觉睡够,然后才是锻炼。



❝睡眠不足情况下高强度锻炼,容易猝死。


如果睡觉的时间都不够,建议辞,换个不太卷的地方。


把特别卷的岗位,留给那些更年轻的,特别需要钱买房结婚的,拼几年,把生存问题解决掉之后,就不要再用命赚钱了。


人生几十年,钱是赚不完的,基本生活需求之外,多赚到的钱,对生活质量提升作用有限。



图片


睡眠的优先级,不但高于锻炼,甚至高于洗脸洗澡。



❝而且睡前三小时不要吃太多东西。


对于经常晚上加班很晚的人来说,戒掉睡觉前玩手机的不良习惯,尽量减少晚上的一切活动,争分夺秒地保证睡眠。



健身,足够的营养和休息,都比身体锻炼本身更重要。



❝所以如果长期生活不规律,饮食习惯不好,休息睡眠不能保证。


如果已经很累了,就不要考虑上高强度的训练了,夸张一点有可能做个俯卧撑都有可能把人送进医院。



有位网友总结得好:



❝去健身,你会得到强壮的身体,过度劳累,你会得到猝死的尸体,过度劳累还去健身,你会得到强壮的尸体。



所以:下班晚,好好休息就是你最好的健身!


程序员在工作空闲之余也可以通过以下方式来锻炼身体:



❝通过走路或骑自行车的方式出门活动,可以锻炼身体的同时享受户外的新鲜空气。


在家里可以做一些简单的,如俯卧撑、仰卧起坐等,这些操作都可以锻炼身体的同时不需要太多的器材。





每日一题


题目描述




给你一个二叉树的根节点 root , 检查它是否轴对称。



解题思路



递归实现


递归结束条件:



  • 都为空指针则返回 true

  • 只有一个为空则返回 false


递归过程:



  • 判断两个指针当前节点值是否相等

  • 判断 A 的右子树与 B 的左子树是否对称

  • 判断 A 的左子树与 B 的右子树是否对称



代码实现


Java代码:


 class Solution {
     public boolean isSymmetric(TreeNode root) {
         if(root == null) {
           return true;
         }
         return dfs(root.left,root.right);
     }
     public boolean dfs(TreeNode p,TreeNode q){
         if (p == null && q == null) {
           return true;
         } else if (p == null||q == null) {
           return false//只有一个为空
         }
         if(p.val != q.val) {
           return false;
         }
         //第一棵子树的左子树和第二棵子树的右子树对称,且第一棵子树的右子树和第二棵子树的左子树对称
         return dfs(p.left,q.right) && dfs(p.right,q.left);
     }
 }

Python代码:


class Solution(object):
 def isSymmetric(self, root):
  """
  :type root: TreeNode
  :rtype: bool
  """

  if not root:
   return True
  def dfs(left,right):
   # 递归的终止条件是两个节点都为空
   # 或者两个节点中有一个为空
   # 或者两个节点的值不相等
   if not (left or right):
    return True
   if not (left and right):
    return False
   if left.val!=right.val:
    return False
   return dfs(left.left,right.right) and dfs(left.right,right.left)
  # 用递归函数,比较左节点,右节点
  return dfs(root.left,root.right)

Go代码:


func isSymmetric(root *TreeNode) bool {
 // 递归-对称二叉树
 var dfs func(leftright *TreeNode) bool
 dfs = func(leftright *TreeNode) bool {
  if left == nil && right == nil {
   return true
  }
  if left ==nil || right == nil || left.Val != right.Val {
   return false
  }
  // 左右子节点都存在且val等,递归其子树
  return dfs(left.Leftright.Right) && dfs(left.Rightright.Left)
 }
 return dfs(root.Left, root.Right)
}

复杂度分析



❝假设树上一共 n 个节点。


时间复杂度:



  • 这里遍历了这棵树,时间复杂度为 O(n)


空间复杂度:



  • 这里的空间复杂度和递归使用的栈空间有关,这里递归层数不超过 n,故空间复杂度为 O(n)



作者:程序员飞鱼
来源:juejin.cn/post/7453489707109531702
收起阅读 »

svg实现地铁线路图

web
简介最近学习了svg,想着使用svg实现地铁线路图 其中黄色是1号线,蓝色是2号线,橙色是3号线实现:react+svg+数据结构-图。考虑范围包括了每站时间,但未包括了换站时间。考虑到换站时间可以加到每2个交换站的路程里功能功能:选择2个地铁站,标...
继续阅读 »

简介

最近学习了svg,想着使用svg实现地铁线路图

insta.gif 其中黄色是1号线,蓝色是2号线,橙色是3号线

实现:react+svg+数据结构-图。

考虑范围包括了每站时间,但未包括了换站时间。考虑到换站时间可以加到每2个交换站的路程里

功能

功能:选择2个地铁站,标出最短路程。

求最少换站路线,暂未做

实现思路

  1. 简化问题,先将所有地铁站分2类,交换站和非交换站。那么交换站可以充当图中的。那么从a=>b, 变成a=>交换站=>交换站=>b的问题,需要写死的是非交换站(a,b)能到达的交换站(下面的adjcent数组), 其中a=>交换站 和b=>交换站 相对静止,但是我这里也考虑到了非交换站到交换站需要的时间(time)

地铁线路图

image.png

image.png

  1. 首先根据每条地铁图数据绘制出地铁线路图,并添加上点击事件,这里要处理好地铁线路图的数据,数据需要相对准确,因为后面需要计算出最短路径。

image.png

image.png

  1. 求最短距离,使用的是Floyd最短路算法(全局/多源最短路)。 其中原理:计算a->b的最短路径,遍历所有,查找是否有最捷径路径 a->x x->b
for(k=1;k<=n;k++) 
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j]) // i->j i->k k->j
e[i][j]=e[i][k]+e[k][j];

然而拿到最短路程后,但是并未拿到路程,拿到的是比如,a点到所有点的最短路程。你们可以思考一下如果获取最短路径。

大概长这样

image.png

  1. 求最短路径 使用一个对象,存储每次找到较短路径。 changeRodePath[${is}to${js}] = [ [is, ks], [ks, js], ]
  function getAllPointShortest(n, e) {
let changeRodePath = {};
for (let k = 0; k < n; k++) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (e[i][j] > e[i][k] + e[k][j]) {
e[i][j] = e[i][k] + e[k][j];
console.log("-------------------------");
const is = changeStation[i];
const ks = changeStation[k];
const js = changeStation[j];
changeRodePath[`${is}to${js}`] = [
[is, ks],
[ks, js],
];
console.log(changeStation[i], changeStation[j]);
console.log(changeStation[i], changeStation[k]);
console.log(changeStation[k], changeStation[j]);
// 2_2 2_5
//2_2 1_2
//1_2 2_5
}
}
}
}
setChangeRodePath(changeRodePath);
return e;
}

当选中2个站时,先取出adjacent,然后求出最短路程,

         let path = {};
adjacent0.forEach((p0,i1) => {
adjacent1.forEach((p1,i2) => {
const index0 = changeStation.indexOf(p0);
const index1 = changeStation.indexOf(p1);
let t=time0[i1]+time1[i2]
if ((rodePath[index0][index1]+t) < minPath) {
minPath = rodePath[index0][index1];
path = { p0, p1};
}
});
});

具体多少不重要,重要的是通过 let pathm = changeRodePath[${path.p0}to${path.p1}],递归查找是否有更短的捷径,因为,2_1 =>3_9 的路径是:2_1 =>1_3=>1_5=>1_8,所以不一定有捷径a->c c—b, 可能是 a->c c->b, 然后发现有更短路径,c->d d->b,那么a-b 路程就变成了a->c->d->b。回到正题,递归之后就能取到最短路径了,然后通过2个交换点取得路径。

没有就更简单了

5.取对应的line,去渲染,这里分2类,交换站之间的路径(最短路径),头和尾。然后分别渲染polyline(使用对应line 的颜色)

function getPl(item, attr, listen) {
return (
<g {...attr} {...listen}>
<polyline //绘制line
{...item}
fill="none"
color={item.colorH}
strokeWidth="5"
strokeLinecap="round"
strokeLinejoin="round"
/>

{item.usePointn.map((point) => { // line 上的站
return (
<use
x={point.x}
onClick={() =>
choosePoint(point)}
y={point.y}
fill={point.color}
href="#point"
>use>
);
})}
g>
);
}

代码准备

// 上图所示,数据随便造,需要合理时间,不然得到的路程奇奇怪怪的

代码部分

html

  
width: "80vw", height: "100vh" }}>
<svg
id="passWay"
viewBox="0 0 800 600"
xmlns="http://www.w3.org/2000/svg"
>

<defs>
<g id="point">
<circle r="4">circle>
<circle r="3" fill="#fff">circle>
g>
defs>
// 所有地铁线路图
{polyline.map((item) => {
return getPl(
item,
{},
{
onMouseEnter: (e) => onMouseEnterShow(e, item),
onMouseOut: () => {
clearTimeout(t1.current);
t1.current = null;
},
}
);
})}
// mask
{ choosePoints.length==2 && (
<rect
x="0"
y="0"
width={"100%"}
height={"100%"}
fillOpacity={0.9}
fill="white"
>
rect>
)}
// 最短路程
{choosePoints && choosePoints.length==2 && showReduLine.map(line=>{
return getPl(line, {}, {})
})
}
svg>

通过line 获取 polyline

  function getLineP(line) {
const usePointn = [];
let path = "";
line.points.forEach((item, index) => {
const { x, y, isStart, isChange, isEnd } = item;

usePointn.push({ ...item, color: line.color });
if (index == 0) {
path = `${x},${y} `;
} else {
path += `${x},${y} `;
}
});
const polylinen = {
usePointn,
stroke: line.color,
...line,
pointStation: line.points,
points: path,
};
return polylinen;
}

选出2站绘制路程

  function comfirPath(point0, point1, p0, p1, pathm) {

let pShow0= getLines(point0,p0)
let pShow1= getLines(point1,p1)
let pathsCenter=[]
if (pathm) {
function recursion(pathm){
pathm.map(([p0,p1])=>{
let pathn = changeRodePath[`${p0}to${p1}`];
if(pathn){
recursion(pathn)
}else{
// 中间的line 不用按顺序
pathsCenter.push(getChangeStationLine(p0,p1))
}
})
}
recursion(pathm)
}else{
pathsCenter=[getChangeStationLine(p0,p1)]
}
const pyAll= [pShow0,pShow1,...pathsCenter].map(line=>{
const py= getLineP({
points:line,
})
py.stroke=line.color
return py
})
setShowReduLine(pyAll); // 绘制
}

参考: 1.# [数据结构拾遗]图的最短路径算法


作者:无名小兵
来源:juejin.cn/post/7445208959151767604

收起阅读 »

极越“猝死“,对于打工人来说可能是好事儿~

大家好,我是日拱一卒的攻城师不浪,致力于技术与艺术的融合,梦想开一家小而美的工作室。这是2024年输出的第49/100篇文章。 人生不止有打工! 前言 今天暂且先不聊技术了,换换口味儿~ 说说最近两天极越汽车暴雷的事情吧,想必大家应该也都有所耳闻了。这件事的...
继续阅读 »

大家好,我是日拱一卒的攻城师不浪,致力于技术与艺术的融合,梦想开一家小而美的工作室。这是2024年输出的第49/100篇文章。



人生不止有打工!


前言


今天暂且先不聊技术了,换换口味儿~


说说最近两天极越汽车暴雷的事情吧,想必大家应该也都有所耳闻了。这件事的发生真是让我越想越气!


其实企业倒闭,在当前这个时间点并不是什么新鲜事儿了,而极越也不是什么大公司,但为什么它暴雷这个事件会闹的这么大呢?



这是极越总部办公室墙上贴的标语,现在回头看看,是不是很讽刺呢?


极越暴雷的很合理


我为什么这么说,并不是我墙倒众人推,而是它倒闭,早就有人先兆预见了。


当时就有之情人士透露,极越公司很快将解散,但是这时作为极越一把手的夏一平先生还仍在努力“辟谣”:公司没有倒闭,只是暂时遇到了困难;


自己刚辟谣完,接着就啪啪打脸,宣布公司所有员工请立即原地解散!要不说谣言从来不会是空穴来风呢。



公司不仅发不出工资,也没钱赔偿,然而更可气的是说,公司无力继续给员工缴纳11月份的五险一金,请员工自行解决。


但凡在一二线工作过的牛马们就能明白,当自己在自己梦想的奋斗了多年的城市,如果断了社保,会带来怎样的一种影响?基本上宣布自己与这个城市无缘了!


这也是这次事件为何如此严重的原因之一!这完全是不给我们这些牛马留活路!


而一个公司能够走到今天这种地步,并且以这种方式结束,无疑不与它的掌门人有着莫大的关系。


公司为何会突然就没钱了?据极越内部的知情人士透露:根源在于百度单方面宣布对极越撤资,终止之后的一切合作


那为什么会突然撤资:因为极越内部高层贪腐严重,百度在财账审计的时候,发现自己一直被蒙在鼓里,原来极越使用的财务供应商是他们“自己人”,并且一直在偷偷的把百度的钱转走!


夏一平经常一手独裁,擅自指定高于十几倍市场价格的供应商,目前极越的财务欠账多达70多亿


甚至还有更炸裂的消息爆出,极越前高管曾经在个人朋友圈爆料:夏一平婚内出轨,嫖娼等,这样的垃圾,在今天选择跑路,一点不奇怪



总结来说,极越的倒闭,原因很明显:高层贪腐,封建官僚,内斗严重,一人掌权,任人唯亲。


其实极越倒闭,对夏老板来说,一点影响都没有,该装进口袋的钱早已装的盆满钵满,事件过去后,该怎么逍遥快活一点都不耽误。


只是苦了极越的底层打工人啊,现在面临的是工资没有,补偿没有,连基本的五险一金都没有(不过最新消息:百度与吉利已经在商讨方案,为极越员工补上欠缺的社保缴纳)。


所以,资本家根本就不值得同情,最苦命的还是底层打工人。


你能说我们不努力吗?现在这个时代,努力真的有用吗?




牛马们该醒醒了


是的,真的该做出改变了,因为时代在改变,你不去适应环境,那就会被环境所淘汰,物竞天择,更古不变的真理。


更可怕的是,这个时代发展之迅速甚至有点让我们招架不住,AI的迅速崛起,已经肉眼可见的在重塑各行各业。


所以,打工人,不要一门心思想着为公司卖力了,当公司不需要你的时候,是一点情面不会跟你讲的!当被淘汰之后,你是否还能够拿出养活自身的技能呢?


当然我不是说不让你去打工,毕竟中国这么多人,也不能每个人都去创业是吗?


我只是想让大家能够转变一下打工的思维,比如:能不能慢慢的变成给自己打工?


几点建议



  1. 选公司就是选领导:领导决定你的职业发展和走势,公司再牛,没有领导提拔重用你,你一样展露不了头角,因此,去找伯乐。

  2. 把公司当成自己的公司:这时候有人会说了:你这不是自相矛盾吗?刚刚还说不要一心只为公司。是的,我的意思其实是:把公司当成自己的创业路上的铺路石,把能在公司里学到的都积极的去学,把公司的能力慢慢的演变成自己的能力,观察下公司日常是怎么运作的,有哪些可取之处与缺点,一步步积累。而不是只把自己当一个工具

  3. 做一个副业:慢慢的挑选到自己的一个副业赛道,最好是跟自己的兴趣相关,不然很难做的下去(经验所谈)。而且经常有人会问:怎么平衡工作与副业?其实坦白来讲:它们两个并不冲突,当你把上班当作自己创业路上的学习平台,那上班的价值意义就体现出来了,你甚至会爱上上班。如果不这么做的话,那你就会感觉很累,就是个纯种牛马



建了一个副业&AI社群,可联系我:brown_7778,我拉你进群,群里会经常分享一些我做副业的经验,认知等,还有一些我掏腰包在其他社群拿到的资料,副业信息等。群里还会讨论与AI相关的信息与知识,欢迎入群。



作者:攻城师不浪
来源:juejin.cn/post/7448806505255813147
收起阅读 »

插件系统为什么在前端开发中如此重要?

web
插件系统是一种软件架构模式,允许开发者通过添加外部模块或组件来扩展和定制软件应用的功能,而无需修改其核心代码。这种方式为软件提供了高度的可扩展性、灵活性和可定制性。 用过构建工具的同学都知道,grunt, webpack, gulp 都支持插件开发。后端框架比...
继续阅读 »

插件系统是一种软件架构模式,允许开发者通过添加外部模块或组件来扩展和定制软件应用的功能,而无需修改其核心代码。这种方式为软件提供了高度的可扩展性、灵活性和可定制性。


用过构建工具的同学都知道,grunt, webpack, gulp 都支持插件开发。后端框架比如 egg koa 都支持插件机制拓展,前端页面也有许多可拓展性的要求。插件化无处不在,所有的框架都希望自身拥有最强大的可拓展能力,可维护性,而且都选择了插件化的方式达到目标。


什么是插件系统


插件系统主要由三个关键部分组成:



  1. 核心系统(Host Application):这是主软件应用,提供了插件可以扩展或修改的基础功能。

  2. 插件接口(Plugin Interface):定义了插件和核心系统之间的交互协议。插件接口规定了插件必须遵循的规则和标准,以便它们能够被核心系统识别和使用。

  3. 插件(Plugins):根据插件接口规范开发的外部模块或组件,用于扩展核心系统的功能。插件可以被添加或移除,而不影响核心系统的运行。


20240316121736


插件的执行流程和实现方式


插件的执行流程是指从插件被加载到执行其功能直至卸载的一系列步骤。



  1. 设计核心系统:首先,我们需要一个核心系统。这个系统负责维护基础功能,并提供插件可以扩展或修改的接口。



    • 核心系统的生命周期:定义核心系统的关键阶段,例如启动、运行中、关闭等。每个阶段可能会触发特定的事件。

    • 暴露的 API:确定哪些内部功能是可以被插件访问的。这包括数据访问、系统服务调用等接口。



  2. 插件的结构设计:插件需要有一个清晰的结构,使其能够容易地集成到核心系统中。一个典型的插件结构可能包含:



    • 初始化代码:插件加载时执行的代码,用于设置插件的运行环境。

    • 处理函数:实现插件功能的核心代码,根据插件的目的可以有多个。

    • 资源清理:插件卸载时需要执行的清理代码,以确保资源被适当释放。



  3. 插件的注册和加载:开发者通过配置文件、命令或图形界面在核心系统中注册插件,系统随后根据注册信息安装并加载插件,这个过程涉及读取插件元数据、执行初始化代码,以及将插件绑定到特定的生命周期事件或 API 上。

  4. 插件的实现:插件的实现依赖于核心系统提供的生命周期钩子和 API。



    • 利用生命周期钩子:插件可以注册函数来响应核心系统的生命周期事件,例如在系统启动完成后执行初始化操作,或在系统关闭前进行资源清理。

    • 调用暴露的 API:插件通过调用核心系统暴露的 API 来实现其功能。这些 API 可以提供系统信息、修改数据、触发事件等功能。



  5. 代码执行流程:插件通过注册自身到核心系统,绑定处理函数至特定事件或 API,以响应系统生命周期变化或 API 调用执行特定任务。在适当时机,如系统关闭或更新时,插件被卸载,其资源得以清理并从系统中移除。


通过这个流程,插件系统提供了一个灵活、可扩展的方式来增强和定制核心系统的功能。插件的开发者可以专注于插件逻辑的实现,而无需修改核心系统的代码。同时,核心系统能够保持稳定性和安全性,因为插件的执行是在明确定义的接口和约束条件下进行的。


插件的几种形式


插件的主要形式主要分为以下几种形式:



  1. 约定式插件

  2. 注入式插件

  3. 事件式插件

  4. 插槽式插件


约定式插件


约定式插件通常在那些采用“约定优于配置”理念的框架或工具中很常见。以 Webpack 为例,它过各种加载器(Loaders)和插件(Plugins)提供强大的扩展性,而这些扩展往往遵循一定的约定,以简化配置的复杂性。


在 Webpack 配置中使用插件时,通常不需要指定插件工作的具体点,只需要将插件加入到配置的 plugins 数组中。Webpack 根据内部的运行机制和生命周期事件,自动调用这些插件,执行相关的任务。


例如,使用 HtmlWebpackPlugin 可以自动生成一个 HTML 文件,并自动将打包后的 JS 文件注入到这个 HTML 文件中。开发者只需要按照约定将 HtmlWebpackPlugin 加入到 plugins 数组中,无需指定具体的注入点或方式,Webpack 就会自动完成这些任务。


const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
// 其他配置...
plugins: [
new HtmlWebpackPlugin({
template: "./src/template.html",
}),
],
};

通过这种约定式的插件机制,Webpack 极大地简化了开发者的配置工作,同时保持了强大的灵活性和扩展性。用户只需遵循简单的约定,如将插件实例添加到 plugins 数组,Webpack 便能自动完成复杂的集成工作,如资源打包、文件处理等,从而提高了开发效率和项目的可维护性。这正体现了约定式插件的主要优势:通过遵循一套预定义的规则,减少配置的需求,同时提供强大的功能扩展能力。


注入式插件


注入式插件通过在应用程序的运行时或编译时将插件的功能注入到应用程序中,从而扩展应用程序的功能。这种方式往往依赖于一种中间件或框架来实现插件的动态加载和执行。一个典型的例子就是 NestJs 世界中广泛使用的依赖注入(DI)功能。


除此之外,尽管 Webpack 更常被人们提及其约定式插件机制,但我们可以从一个角度将 Loaders 视为一种注入式插件,在 Webpack 配置中,Loaders 允许你在模块被添加到依赖图中时,预处理文件。可以看作是在编译过程中“注入”了额外的处理步骤。这些处理步骤可以包括将 TypeScript 转换为 JavaScript、将 SASS 转换为 CSS,或者将图片和字体文件转换为 Webpack 可以处理的格式。


module.exports = {
// ...其他配置
module: {
rules: [
{
test: /\.js$/, // 使用正则表达式匹配文件路径,处理.js文件
exclude: /node_modules/, // 排除node_modules目录
use: {
loader: "babel-loader", // 指定使用babel-loader
options: {
presets: ["@babel/preset-env"], // 使用预设配置转换ES6+代码
},
},
},
],
},
// ...其他配置
};

通过 loader 的配置,Webpack 实现了一种灵活的“注入式”扩展机制,允许开发者根据需要为构建过程注入各种预处理步骤。


事件插件化


事件插件化是一种基于事件驱动编程模式的插件化机制,其中插件通过监听和响应系统中发生的特定事件来工作。这种机制允许插件在不直接修改主程序代码的情况下增加或改变程序的行为。


Node.js 的 EventEmitter 类是实现事件插件化的一个很好的例子。假设我们正在开发一个应用程序,该程序需要在完成某个任务后执行一系列的操作,这些操作由不同的插件来实现。


首先,创建一个基于 EventEmitter 的任务执行器,它在完成任务时会发出一个事件:


const EventEmitter = require("events");

class TaskExecutor extends EventEmitter {
execute(taskFunc) {
console.log("Executing task...");
taskFunc();
this.emit("taskCompleted", "Task execution finished");
}
}

接着,我们可以开发插件来监听 taskCompleted 事件。每个插件都可以注册自己的监听器来响应事件:


// Plugin A
executor.on("taskCompleted", (message) => {
console.log(`Plugin A responding to event: ${message}`);
});

// Plugin B
executor.on("taskCompleted", (message) => {
console.log(`Plugin B responding to event: ${message}`);
});

最后,创建 TaskExecutor 的实例,并执行一个任务,看看插件如何响应:


const executor = new TaskExecutor();

// 注册插件
// ...此处省略插件注册代码...

executor.execute(() => {
console.log("Task is done.");
});

运行上述代码时,TaskExecutor 执行一个任务,并在任务完成后发出 taskCompleted 事件。注册监听该事件的所有插件(在这个例子中是插件 A 和插件 B)都会接到通知,并执行相应的响应操作。这种模式使得开发者可以很容易地通过添加更多的事件监听器来扩展应用程序的功能,而无需修改 TaskExecutor 或其他插件的代码,实现了高度的解耦和可扩展性。


插槽插件化


在 React 中,插槽插件化的概念可以通过组件的 children 属性或使用特定的插槽来实现。这种模式允许开发者定义一个组件框架,其中一些部分可以通过传入的子组件来填充,从而实现自定义内容的注入。这类似于 Vue 中的插槽(slots)功能,但在 React 中,它通过 props.children 或通过特定的 props 来传递组件来实现。


function Card({ children }) {
return <div className="card">{children}</div>;
}

function App() {
return (
<Card>
<h2>标题</h2>
<p>这是一段文本</p>
</Card>

);
}

通过这种方式,React 支持了组件的插槽化,使组件的复用和自定义变得更加容易。这种模式在构建可扩展和可复用的 UI 组件库时尤其有用。


代码实现


接下来我们通过插件来实现一个计算器,可以实现加减乘除


插件核心实现


class Calculator {
constructor(options = {}) {
const { initialValue = 0 } = options;
this.currentValue = initialValue;
}
getCurrentValue() {
return this.currentValue;
}
setValue(value) {
this.currentValue = value;
}
plus(addend) {
this.setValue(this.currentValue + addend);
}
minus(subtrahend) {
this.setValue(this.currentValue - subtrahend);
}
multiply(multiplicand) {
this.setValue(this.currentValue * multiplicand);
}
division(divisor) {
if (divisor === 0) {
console.error("不允许除零。");
return;
}
this.setValue(this.currentValue / divisor);
}
}

// test
const calculator = new Calculator();
calculator.plus(10);
console.log(calculator.getCurrentValue()); // 10
calculator.minus(5);
console.log(calculator.getCurrentValue()); // 5
calculator.multiply(2);
console.log(calculator.getCurrentValue()); // 10
calculator.division(2);
console.log(calculator.getCurrentValue()); // 5

实现 hooks


核心系统想要对外提供生命周期钩子,就需要一个事件机制。


class Hooks {
constructor() {
this.listeners = {};
}
on(eventName, handler) {
let listeners = this.listeners[eventName];
if (!listeners) {
this.listeners[eventName] = listeners = [];
}
listeners.push(handler);
}
off(eventName, handler) {
const listeners = this.listeners[eventName];
if (listeners) {
this.listeners[eventName] = listeners.filter((l) => l !== handler);
}
}
trigger(eventName, ...args) {
const listeners = this.listeners[eventName];
const results = [];
if (listeners) {
for (const listener of listeners) {
const result = listener.call(null, ...args);
results.push(result);
}
}
return results;
}
destroy() {
this.listeners = {};
}
}

这个 Hooks 类是一个事件监听器或事件钩子的简单实现,它允许你在应用程序的不同部分之间传递消息或事件,而不必直接引用那些部分。


暴露生命周期(通过 Hooks)


然后将 hooks 运用在核心系统中 -- JavaScript 计算器。


每个钩子对应的事件:



  • pressedPlus 做加法操作

  • pressedMinus 做减法操作

  • pressedMultiply 做乘法操作

  • pressedDivision 做乘法操作

  • valueWillChanged 即将赋值 currentValue,如果执行此钩子后返回值为 false,则中断赋值。

  • valueChanged 已经赋值 currentValue


class Calculator {
constructor(options = {}) {
this.hooks = new Hooks();
const { initialValue = 0, plugins = [] } = options;
this.currentValue = initialValue;
plugins.forEach((plugin) => plugin.apply(this.hooks));
}

getCurrentValue() {
return this.currentValue;
}

setValue(value) {
const result = this.hooks.trigger("valueWillChanged", value);
if (result.length !== 0 && result.some((_) => !_)) {
} else {
this.currentValue = value;
this.hooks.trigger("valueChanged", this.currentValue);
}
}

plus(addend) {
this.hooks.trigger("pressedPlus", this.currentValue, addend);
this.setValue(this.currentValue + addend);
}

minus(subtrahend) {
this.hooks.trigger("pressedMinus", this.currentValue, subtrahend);
this.setValue(this.currentValue - subtrahend);
}

multiply(factor) {
this.hooks.trigger("pressedMultiply", this.currentValue, factor);
this.setValue(this.currentValue * factor);
}

division(divisor) {
if (divisor === 0) {
console.error("Division by zero is not allowed.");
return;
}
this.hooks.trigger("pressedDivision", this.currentValue, divisor);
this.setValue(this.currentValue / divisor);
}
}

插件实现


插件要实现 apply 方法,在 Calculator 的 constructor 调用时,才能确保插件 apply 执行后会绑定(插件内的)处理函数到生命周期。


apply 的入参是 this.hooks,通过 this.hooks 来监听生命周期并添加处理器。


class LogPlugins {
apply(hooks) {
hooks.on("pressedPlus", (currentVal, addend) =>
console.log(`${currentVal} + ${addend}`)
);
hooks.on("pressedMinus", (currentVal, subtrahend) =>
console.log(`${currentVal} - ${subtrahend}`)
);
hooks.on("pressedMultiply", (currentVal, factor) =>
console.log(`${currentVal} * ${factor}`)
);
hooks.on("pressedDivision", (currentVal, divisor) =>
console.log(`${currentVal} / ${divisor}`)
);
hooks.on("valueChanged", (currentVal) =>
console.log(`结果: ${currentVal}`)
);
}
}

class LimitPlugins {
apply(hooks) {
hooks.on("valueWillChanged", (newVal) => {
if (100 < newVal) {
console.log("result is too large");
return false;
}
return true;
});
}
}

LogPlugins 的目的是记录计算器操作的详细日志。通过监听 Calculator 类中定义的事件(如加、减、乘、除操作和值变化时的事件),这个插件在这些操作执行时打印出相应的操作和结果。


LimitPlugins 的目的是在值变更前进行检查,以确保计算器的结果不会超出预设的限制(在这个例子中是 100)。如果预计的新值超出了限制,这个插件会阻止值的更改并打印一条警告消息。


通过这两个插件,Calculator 类获得了额外的功能,而无需直接在其代码中加入日志记录和值限制检查的逻辑。


完整代码


最后我们应该贴上全部代码:


class Hooks {
constructor() {
this.listener = {};
}

on(eventName, handler) {
if (!this.listener[eventName]) {
this.listener[eventName] = [];
}
this.listener[eventName].push(handler);
}

trigger(eventName, ...args) {
const handlers = this.listener[eventName];
const results = [];
if (handlers) {
for (const handler of handlers) {
const result = handler(...args);
results.push(result);
}
}
return results;
}

off(eventName, handler) {
const handlers = this.listener[eventName];
if (handlers) {
this.listener[eventName] = handlers.filter((cb) => cb !== handler);
}
}

destroy() {
this.listener = {};
}
}

class Calculator {
constructor(options = {}) {
this.hooks = new Hooks();
const { initialValue = 0, plugins = [] } = options;
this.currentValue = initialValue;
plugins.forEach((plugin) => plugin.apply(this.hooks));
}

getCurrentValue() {
return this.currentValue;
}

setValue(value) {
const result = this.hooks.trigger("valueWillChanged", value);
if (result.length !== 0 && result.some((_) => !_)) {
} else {
this.currentValue = value;
this.hooks.trigger("valueChanged", this.currentValue);
}
}

plus(addend) {
this.hooks.trigger("pressedPlus", this.currentValue, addend);
this.setValue(this.currentValue + addend);
}

minus(subtrahend) {
this.hooks.trigger("pressedMinus", this.currentValue, subtrahend);
this.setValue(this.currentValue - subtrahend);
}

multiply(factor) {
this.hooks.trigger("pressedMultiply", this.currentValue, factor);
this.setValue(this.currentValue * factor);
}

division(divisor) {
if (divisor === 0) {
console.error("Division by zero is not allowed.");
return;
}
this.hooks.trigger("pressedDivision", this.currentValue, divisor);
this.setValue(this.currentValue / divisor);
}
}

class LogPlugins {
apply(hooks) {
hooks.on("pressedPlus", (currentVal, addend) =>
console.log(`${currentVal} + ${addend}`)
);
hooks.on("pressedMinus", (currentVal, subtrahend) =>
console.log(`${currentVal} - ${subtrahend}`)
);
hooks.on("pressedMultiply", (currentVal, factor) =>
console.log(`${currentVal} * ${factor}`)
);
hooks.on("pressedDivision", (currentVal, divisor) =>
console.log(`${currentVal} / ${divisor}`)
);
hooks.on("valueChanged", (currentVal) =>
console.log(`结果: ${currentVal}`)
);
}
}

class LimitPlugins {
apply(hooks) {
hooks.on("valueWillChanged", (newVal) => {
if (100 < newVal) {
console.log("result is too large");
return false;
}
return true;
});
}
}

// 运行测试
const calculator = new Calculator({
initialValue: 0,
plugins: [new LogPlugins(), new LimitPlugins()],
});
calculator.plus(10);
calculator.minus(5);
calculator.multiply(2);
calculator.division(5);
calculator.plus(1000); // 尝试加到超过限制的值

最终输出结果如下图所示:


20240316211128


参考资料


-简单实现一个插件系统(不引入任何库),学会插件化思维


-当我们说插件系统的时候,我们在说什么


总结


通过这两个插件的例子,我们可以看到插件化设计模式在软件开发中的强大之处。它允许开发者在不修改原有代码基础上扩展功能、增加新的处理逻辑,使得应用更加模块化和易于维护。这种模式特别适用于那些需要高度可扩展性和可定制性的应用程序。


最后分享两个我的两个开源项目,它们分别是:



这两个项目都会一直维护的,如果你也喜欢,欢迎 star 🚗🚗🚗


如果你对开源项目感兴趣的,可以加我微信 yunmz777


作者:Moment
来源:juejin.cn/post/7347220605609410595
收起阅读 »

没想到,axios下载文件竟然比fetch好用

web
前言 还是和上篇一样,是关于导出excel的问题。好像是生产上导出的excel有问题,具体是啥没和我说,当然和我上篇写的没有什么关系,这是另一个模块历史遗留的问题。反正到我手里的任务就是更改导出的接口让后端去做表格。 原来的写法 原来的写法很粗暴,直接用win...
继续阅读 »

前言


还是和上篇一样,是关于导出excel的问题。好像是生产上导出的excel有问题,具体是啥没和我说,当然和我上篇写的没有什么关系,这是另一个模块历史遗留的问题。反正到我手里的任务就是更改导出的接口让后端去做表格。


原来的写法


原来的写法很粗暴,直接用window.location去跳转下载链接就把excel下载了,后端具体怎么做我的不清楚,前端的逻辑就是有一个固定的地址,然后通过query去传参让后端知道该导出什么样的excel表格。


function exportExcel(params){
const url = 'xxxxx/exportExcel?id=params.id&type=params.type'
   window.location = url
}

content-disposition


基础没学好应该也是会这样的一个疑问,为什么我在浏览器中输入一个地址就会下载文件,是的我也是,所以我去查了一下,主要是由于Content-Disposition 这个响应头字段。它告诉浏览器该文件是作为附件下载,还是在浏览器中直接打开。如果该字段的值为 attachment,则浏览器会将文件下载到本地;如果该字段的值为inline,则浏览器会尝试在浏览器中直接打开文件。


image-20241220101529424.png


语法格式




  • 其基本语法格式为:Content-Disposition: attachment; filename="filename.ext"Content-Disposition: inline; filename="filename.ext"

  • 其中,attachment表示将内容作为附件下载,这是最常见的用于文件下载的设置;而inline则表示在浏览器中内联显示内容,即直接在浏览器窗口中展示,而不是下载。

  • filename参数用于指定下载文件的名称,若不指定,浏览器可能会根据服务器返回的其他信息或自身的默认规则来确定文件名。



标题党?


才不是啊,因为我要对接的接口变成post请求,用原来这种方式肯定是不行的,这个时候我就想到了我之前写过的类似需求,就是用fetch。但是一直请求不成功,后端一直报请求参数异常。


fetch


function exportExcel(data){
fetch(`xxxxxxx/ExportExcel`, {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Authorization': 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
}
}).then(res => {
const readableStream = res.body
if (readableStream) {
return new Response(readableStream).blob()
} else {
console.error('No readable stream available.')
}
}).then(blob => {
// 创建一个下载链接
const downloadLink = document.createElement('a')
downloadLink.href = URL.createObjectURL(blob)
// 设置下载属性,指定文件名
downloadLink.download = '测试.xlsx'
// 模拟点击下载链接
downloadLink.click()
// 释放 URL 对象
URL.revokeObjectURL(downloadLink.href)
})
}

我感觉我写的没有什么毛病啊,fetch第一个then回调转成blob数据类型,第二个then模拟a标签点击下载。但是后端老给报参数类型异常。


image-20241220105642273.png


我本来想让后端给我看看什么原因的,是什么参数没传对,还是什么请求头不对,但是他就老给甩一张swagger的请求成功的截图,根本不会帮你去看日志是因为什么原因。当然,swagger能调成功,说明接口肯定是没问题的,肯定是我没有传对东西,但是就挺烦的,都没有沟通欲望了,想着自己去换种方式去解决,然后我就想着用axios去试一下,没想到成功了


axios


function exportExcel(data) {
 axios({
   method: 'post',
   url: `xxxxx/ExportExcel`,
   data,
   responseType: 'blob'// 这里就是转化为blob文件流
}).then(res => {
   console.log(res, 'res')
     // 创建一个下载链接
   const downloadLink = document.createElement('a')
   downloadLink.href = URL.createObjectURL(res.data)
   // 设置下载属性,指定文件名
   downloadLink.download = '测试.xlsx'
   // 模拟点击下载链接
   downloadLink.click()
   // 释放 URL 对象
   URL.revokeObjectURL(downloadLink.href)
})
}

这里通过responseType设置blob值,就会自动将响应的东西转成blob二进制的格式内容,然后还是通过模拟a标签下载。相比于fetch,我们要在第二个then中对数据进行转换,而axios配置一个参数就行了。


总结


现在大部分的项目中,基本都是使用axios封装的交互方法,所以我们其实用axios是最好的,只需要配置一个参数就可以下载excel,相较于fetch来说,代码是比较简洁一点。虽然我这里fetch是没有成功的,但是放心,肯定是没有问题,是可以这样下载excel的,我估摸着应该是请求头的原因吧,可能是后端做了什么对请求头的处理,我也不知道,但是我之前做这个需求都是用fetch肯定没问题。


作者:落课
来源:juejin.cn/post/7450310230536208418
收起阅读 »

🌿一个vue3指令让el-table自动轮播

web
前言 本文开发的工具,是vue3 element-plus ui库专用的,需要对vue3指令概念有一定的了解 ​ 最近开发的项目中,需要对项目中大量的列表实现轮播效果,经过一番折腾.最终决定不使用第三方插件,手搓一个滚动指令. 效果展示 实现思路 第一步...
继续阅读 »

img


前言



本文开发的工具,是vue3 element-plus ui库专用的,需要对vue3指令概念有一定的了解



​ 最近开发的项目中,需要对项目中大量的列表实现轮播效果,经过一番折腾.最终决定不使用第三方插件,手搓一个滚动指令.


效果展示


列表滚动.webp


实现思路


第一步先确定功能



  • 列表自动滚动

  • 鼠标移入停止滚动

  • 鼠标移出继续滚动

  • 滚轮滚动完成,还可以继续在当前位置滚动

  • 元素少于一定条数时,不滚动


滚动思路


image-20241226223121217.png


image-20241226223310536.png


通过观察el-table的结构可以发现el-scrollbar__view里面放着所有的元素,而el-scrollbar__wrap是一个固定高度的容器,那么只需要获取到el-scrollbar__wrap这个DOM,并且再给一个定时器,不断的改变它的scrollTop值,就可以实现自动滚动的效果,这个值必须要用一个变量来存储,不然会失效


停止和继续滚动思路


设置一个boolean类型变量,每次执行定时器的时候判断一下,true就滚动,否则就不滚动


滚轮事件思路


为了每次鼠标在列表中滚动之后,我们的轮播还可以在当前滚动的位置,继续轮播,只需要在鼠标移出的时候,将当前el-scrollbar__wrapscrollTop赋给前面存储的变量,这样执行定时器的时候,就可以继续在当前位置滚动


不滚动的思路


​ 只需要判断el-scrollbar__view这个容器的高度,是否大于el-scrollbar__wrap的高度,是就可以滚动,不是就不滚动。


大致的思路是这样的,下面上源码


实现代码


文件名:tableAutoScroll.ts


interface ElType extends HTMLElement {
timer: number | null
isScroll: boolean
curTableTopValue: number
}
export default {
created(el: ElType) {
el.timer = null
el.isScroll = true
el.curTableTopValue = 0
},
mounted(el: ElType, binding: { value?: { delay?: number } }) {
const { delay = 15 } = binding.value || {}
const tableDom = el.getElementsByClassName(
'el-scrollbar__wrap'
)[0] as HTMLElement
const viewDom = el.getElementsByClassName(
'el-scrollbar__view'
)[0] as HTMLElement

const onMouseOver = () => (el.isScroll = false)
const onMouseOut = () => {
el.curTableTopValue = tableDom.scrollTop
el.isScroll = true
}

tableDom.addEventListener('mouseover', onMouseOver)
tableDom.addEventListener('mouseout', onMouseOut)

el.timer = window.setInterval(() => {
const viewDomClientHeight = viewDom.scrollHeight
const tableDomClientHeight = el.clientHeight

if (el.isScroll && viewDomClientHeight > tableDomClientHeight) {
const curScrollPosition = tableDom.clientHeight + el.curTableTopValue
el.curTableTopValue =
curScrollPosition === tableDom.scrollHeight
? 0
: el.curTableTopValue + 1
tableDom.scrollTop = el.curTableTopValue
}
}, delay)
},
unmounted(el: ElType) {
if (el.timer !== null) {
clearInterval(el.timer)
}
el.timer = null

const tableDom = el.getElementsByClassName(
'el-scrollbar__wrap'
)[0] as HTMLElement
tableDom.removeEventListener('mouseover', () => (el.isScroll = false))
tableDom.removeEventListener('mouseout', () => {
el.curTableTopValue = tableDom.scrollTop
el.isScroll = true
})
},
}

上面代码中,我在 created中初始化了三个变量,分别用于存储,定时器对象 、是否滚动判断、滚动当前位置。


mounted中我还获取了一个options,主要是为了可以定制滚动速度


用法



  1. 将这段代码放在你的文件夹中

  2. main.ts中注册这个指令


    import tableAutoScroll from './modules/tableAutoScroll.ts'
    const directives: any = {
    tableAutoScroll,
    }
    /**
    * @function 批量注册指令
    * @param app vue 实例对象
    */

    export const install = (app: any) => {
    Object.keys(directives).forEach((key) => {
    app.directive(key, directives[key]) // 将每个directive注册到app中
    })
    }



image-20241226224940418.png
image-20241226225027524.png


我这边是将自己的弄了一个批量注册,正常使用就像官网里面注册指令就可以了


在需要滚动的el-table上使用这个指令就可以


image-20241226225257264.png


<!-- element 列表滚动指令插件 -->
<template>
<div class="container">
<el-table v-tableAutoScroll :data="tableData" height="300">
<el-table-column prop="date" label="时间" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="address" label="Address" />
</el-table>
<!-- delay:多少毫秒滚动一次 -->
<el-table
v-tableAutoScroll="{
delay: 50,
}"
:data="tableData"
height="300"
>
<el-table-column prop="date" label="时间" />
<el-table-column prop="name" label="名称" />
<el-table-column prop="address" label="Address" />
</el-table>
</div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
const tableData = ref<any>([])
onMounted(() => {
tableData.value = Array.from(Array(100), (item, index) => ({
date: '时间' + index,
name: '名称' + index,
address: '地点' + index,
}))
console.log('👉 ~ tableData.value=Array.from ~ tableData:', tableData)
})
</script>

<style lang="scss" scoped>
.container {
height: 100%;
display: flex;
align-items: flex-start;
justify-content: center;
gap: 100px;
.el-table {
width: 500px;
}
}
</style>

上面这个例子,分别演示两种调用方法,带参数和不带参数


最后


做了这个工具之后,突然有很多思路,打算后面再做几个,做成一个开源项目,一个开源的vue3指令集


作者:BAO_OA
来源:juejin.cn/post/7452667228006678540
收起阅读 »

马上2025年了,你还在用组件式弹窗? 来看看这个吧~

web
闲言少叙,直切正题。因为我喜欢命令式弹窗,所以就封装了它做为了业务代码的插件!如今在实际项目中跑了大半年,挺方便也挺灵活的! 如何使用 // vue2 npm install @e-dialog/v2 // main.js 入口文件 import Vue f...
继续阅读 »

闲言少叙,直切正题。因为我喜欢命令式弹窗,所以就封装了它做为了业务代码的插件!如今在实际项目中跑了大半年,挺方便也挺灵活的!


如何使用


// vue2
npm install @e-dialog/v2

// main.js 入口文件
import Vue from 'vue'
import App from './App'

//导包
import eDialog from '@e-dialog/v2'
//注册插件
Vue.use(eDialog, {
width:'50%',//全局配置
top:'15vh',
//...省略
})

new Vue({
el: '#app',
render: h => h(App)
})


// vue3
npm install @e-dialog/v3

// main.js 入口文件
import { createApp } from 'vue'
import App from './App.vue'
//导包
import eDialog from '@e-dialog/v3'

// 创建实例
const setupAll = async () => {
const app = createApp(App)
app.use(eDialog,{
width:'50%',//全局配置
top:'15vh',
//...省略
})
app.mount('#app')
}

setupAll()

插件简介


vue2是基于element ui elDialog组件做的二次封装,vue3则是基于element-plus elDialog组件做的二次封装,属性配置这一块可以全部参考element UI文档!



微信截图_20241215192735.png


扩展的属性配置

参数说明类型默认值
isBtn是否显示底部操作按钮booleantrue
draggable是否开启拖拽,vue3版本element-plus内置了该属性booleantrue
floorBtnSize底部操作按钮的尺寸medium、small、minismall
sureBtnText确定按钮的文案string确定
closeBtnText关闭按钮的文案string关闭
footer底部按钮的插槽,是一个函数返回值必须是JSXfunction-

底部插槽用法

// index.vue
<template>
<!-- vue2示例 -->
<div>
<el-button @click="handleDialog">弹窗</el-button>
</div>

</template>

<script>
//弹窗内容
import Edit form './edit.vue'
export default {
methods: {
handleDialog() {
this.$Dialog(Edit, props,function(vm,next){
//vm可以通过vm.formData拿到数据
},{
isBtn:false //如果定义了插槽,建议关闭底部操作按钮,不然会出现布局问题
footer:function(h,next){
return (
<el-button onClick={()=>{this.handleCheck(next)}}>按钮</el-button>
)
}
})
},

//按钮点击触发
handleCheck(next){
//next是一个手动关闭函数
console.log('业务逻辑')
}
}
}
</script>



页面使用:vue2

// index.vue
<template>
<!-- vue2示例 -->
<div>
<el-button @click="handleDialog">弹窗</el-button>
</div>

</template>

<script>
//弹窗内容
import Edit form './edit.vue'
export default {
methods: {
handleDialog() {
/**
* @function $Dialog是一个全局方法,自动挂载到了vue的原型
* @description 它总共接收4个参数
* @param 组件实例 | html字符串
* @param props要传递到组件的参数对象。
* @param 点击确定按钮的回调,回调里面的第一个参数是弹窗内容的组件实例,第二个参数是关闭弹窗的执行函数
* @param 配置对象,可以覆盖全局的配置
* @return void
*/


this.$Dialog(Edit, props,function(vm,next){
//vm可以通过vm.formData拿到数据
},{
//配置对象
})
}

}
}
</script>


//edit.vue
<template>
<div>弹窗内容!</div>
</template>


<script>
export default {
props:[/*这里可以接收$Dialog第二个参数props的数据*/]
data() {
return {
formData:{
a:'',
b:'',
c:''
}
}
},

}
</script>



页面使用:vue3

// index.vue
<template>
<!-- vue2示例 -->
<div>
<el-button @click="handleDialog">弹窗</el-button>
</div>

</template>

<script setup>
//弹窗内容
import Edit form './edit.vue'
const { proxy } = getCurrentInstance();
const $Dialog = proxy.useDialog()

function handleDialog() {
/**
* @function $Dialog是一个全局方法,自动挂载到了vue的原型
* @description 它总共接收4个参数
* @param 组件实例 | html字符串
* @param props要传递到组件的参数对象。
* @param 点击确定按钮的回调,回调里面的第一个参数是弹窗内容的组件实例,第二个参数是关闭弹窗的执行函数
* @param 配置对象,可以覆盖全局的配置
* @return void
*/


$Dialog(Edit, props,function(vm,next){
//vm可以通过vm.formData拿到数据
},{
//配置对象
})
}
</script>


//edit.vue
<template>
<div>弹窗内容!</div>
</template>

<script setup>
const formData = reactive({
a:'',
b:'',
c:''
})
defineExpose({ formData }) //这里注意一点要把外部要用的抛出去,如果不抛,则$Dialog回调将拿不到任何数据
</script>



函数参数设计理念


  1. 如果你弹窗内容比较复杂,例如涉及一些表单操作。最好建议抽离成一个组件,导入到Dialog第一个入参里面,如果只是简单的静态文本,则直接可以传HTML。

  2. 如果你Dialog导入的是组件,那么你有可能需要给组件传参。所以Dialog第二个入参就是给你开放的入口。

  3. 如果你点击确认按钮可能需要执行一些逻辑,例如调用API接口。所以你可能在Dialog第三个回调函数里面写入逻辑。回调函数会把第一个入参组件的实例给你传递回来,你拿到实例就可以干任何事情咯!

  4. Dialog第四个参数考虑到不同页面的配置不同。可以灵活设置。


vue2源码地址(github.com/zy1992829/e…)


vue3源码地址(github.com/zy1992829/e…)


喜欢的朋友可以去看一看,顺便帮忙点个星星。这个就不贴源码了。。


作者:阳火锅
来源:juejin.cn/post/7448661024440401957
收起阅读 »

Vue3.5正式上线,父传子props用法更丝滑简洁

web
前言 Vue3.5在2024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性说一下props的两个小改动使我们日常使用更加灵活。 一...
继续阅读 »

前言


Vue3.52024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性说一下props两个小改动使我们日常使用更加灵活。


image.png


一、带响应式Props解构赋值


简述: 以前我们对Props直接进行解构赋值是会失去响应式的,需要配合使用toRefs或者toRef解构才会有响应式,那么就多了toRefs或者toRef这工序,而最新Vue3.5版本已经不需要了。



这样直接解构,testCount能直接渲染显示,但会失去响应式,当我们修改testCount时页面不更新。



<template>
<div>
{{ testCount }}
</div>
</template>

<script setup>
import { defineProps } from 'vue';
const props = defineProps({
testCount: {
type: Number,
default: 0,
},
});

const { testCount } = props;
</script>


保留响应式的老写法,使用toRefs或者toRef解构



<template>
<div>
{{ testCount }}
</div>
</template>

<script setup>
import { defineProps, toRef, toRefs } from 'vue';
const props = defineProps({
testCount: {
type: Number,
default: 0,
},
});

const { testCount } = toRefs(props);
// 或者
const testCount = toRef(props, 'testCount');
</script>


最新Vue3.5写法,不借助”外力“直接解构,依然保持响应式



<template>
<div>
{{ testCount }}
</div>
</template>

<script setup>
import { defineProps } from 'vue';
const { testCount } = defineProps({
testCount: {
type: Number,
},
});

</script>

相比以前简洁了真的太多,直接解构使用省去了toRefs或者toRef


二、Props默认值新写法


简述: 以前默认值都是用default: ***去设置,现在不用了,现在只需要解构的时候直接设置默认值,不需要额外处理。



先看看旧的default: ***默认值写法



如下第12就是旧写法,其它以前Vue2也是这样设置默认值


<template>
<div>
{{ props.testCount }}
</div>
</template>

<script setup>
import { defineProps } from 'vue';
const props = defineProps({
testCount: {
type: Number,
default: 1
},
});
</script>

最新优化的写法
如下第9行,解构的时候直接一步到位设置默认值,更接近js语法的写法。


<template>
<div>
{{ testCount }}
</div>
</template>

<script setup>
import { defineProps } from 'vue';
const { testCount=18 } = defineProps({
testCount: {
type: Number,
},
});
</script>

小结


这次更新其实props的本质功能并没有改变,但写法确实变的更加丝滑好用了,props使用非常高频感觉还是有必要跟进这种更简洁的写法。如果那里写的不对或者有更好建议欢迎大佬指点啊。


作者:天天鸭
来源:juejin.cn/post/7410333135118090279
收起阅读 »

自己没有价值之前,少去谈人情世故

昨天和几个网友在群里聊天,一个网友说最近公司辞退了一个人,原因就是太菜了,有一个功能是让从数据库随机查一条数据,他硬是把整个数据表的数据都查出来,然后从里面随机选一条数据。 另外的群友说,这人应该在公司的人情世故做得不咋滴,要是和自己组长,领导搞好关系,不至于...
继续阅读 »

昨天和几个网友在群里聊天,一个网友说最近公司辞退了一个人,原因就是太菜了,有一个功能是让从数据库随机查一条数据,他硬是把整个数据表的数据都查出来,然后从里面随机选一条数据。


另外的群友说,这人应该在公司的人情世故做得不咋滴,要是和自己组长,领导搞好关系,不至于被辞退。


发言人说:相反,这人的人情世故做得很到位,和别人相处得也挺好,说话又好听,大家都觉得他很不错!


但是这有用吗?


和自己的组长关系搞好了,难道他就能给你的愚蠢兜底?


这未免太天真,首先组长也是打工的,你以为和他关系好,他就能包庇你,容忍你不断犯错?


没有人会愿意冒着被举报的风险去帮助一个非亲非故的人,因为自己还要生活,老婆孩子还要等着用钱,包庇你,那么担风险的人就是他自己,他为何要这样做?


我们许多人总是觉得人情世故太重要了,甚至觉得比自己的能力重要,这其实是一个侮误区。


有这种想法的大多是刷垃圾短视频刷多了,没经历过社会的毒打,专门去学酒满敬人,茶满欺人。给领导敬酒杯子不能高过对方,最好直接跪下来……


那么人情世故重要吗?


重要,但是得分阶层,你一个打工的,领导连你名字都叫不出来,你见到他打声招呼,他都是用鼻子答应,你觉得你所谓的人情世故有意义吗?


你以为团建的时候跑上去敬酒,杯子直接低到他脚下,他就会看中你,为他挡酒他就觉得你这人可扶?未免电视看得太多。


人情世故有用的前提一定是建立在你有被利用的价值之上,你能漂漂亮亮做完一件事,问题又少,创造的价值又多,那么别人就会觉得你行,就会记住你,重视你,至于敬酒这些,不过是走个过场而已。


所以在自己没有价值之前,别去谈什么人情世故,安安心心提升自己。


前段时间一个大二的小妹妹叫我帮她运行一个项目,她也是为了课程蒙混过关,后面和她聊了几句,她叫我给她一点建议。


我直接给她说,你真正的去写了几行代码?看了几本书?做了多少笔记?你真正的写了代码,看了书,有啥疑问你再找我,而不是从我这里找简便方法,因为我也没有!


她说最烦学习了,完全不想学,自己还是去学人情世故了。


我瞬间破放了,对她说你才20岁不到,专业知识不好好学,就要去学人情世故了?你能用到人情世故吗?


你是怕以后去进厂自己人情世故不到位别人不要你?还是以后去ktv陪酒或者当营销学不会?这么早就做准备了?


她后面反驳我说:你看那些职场里面的女生不也是很懂人情世故吗,你为啥说没用,这些东西迟早都是要学的,我先做准备啊!


我当时就不想和她聊下去了,我知道又是垃圾短视频看多了,所以才会去想这些!以为自己不好好学习,毕业后只要人情世故做到位,就能像那些女职场秘书一样,陪着领导出去谈生意。


想啥呢!


当然,并不存在歧视别人的想法,因为我没有资格,只不过是觉得该学习的时间别去想一些没啥用的事情!


我们所能看到的那些把人情世故运用得炉火纯青,让人感觉很自然的人,别人肯定已经到了一定的段位,这是TA的职业需要。


而大多数人都是在底层干着街边老太太老大爷都能干的活,领导连你名字都叫不出来,可以用空气人来形容,你说人情世故有什么卵用吗?


这不就等于把自己弄得四不像吗?


当你真的有利用价值,能够给别人提供解决方案的时候,再来谈人情世故,那时候你不学,生活都会逼着你去学。


最后说一句,当你有价值的时候,人情世故是你别人学来用在你身上的,不信你回头去看一下自己的身边的人,哪怕是一个小学教师,都有人提着东西来找他办事,但是如果没有任何利用价值,哪怕TA把酒场上面的套路都运用得炉火纯青,也会成为别人的笑柄!


作者:苏格拉的底牌
来源:juejin.cn/post/7352799449456738319
收起阅读 »

最强开源模型,DeepSeek V3,它来了!

2024年12月26日,DeepSeek正式发布了其最新一代大型语言模型:DeepSeek-V3。 这一模型的发布不仅标志着DeepSeek在 AGI(人工通用智能) 探索道路上的又一里程碑,也再次证明了其在开源AI领域的领先地位。 从V2.5到V3,Deep...
继续阅读 »

2024年12月26日,DeepSeek正式发布了其最新一代大型语言模型:DeepSeek-V3


这一模型的发布不仅标志着DeepSeek在 AGI(人工通用智能) 探索道路上的又一里程碑,也再次证明了其在开源AI领域的领先地位。


从V2.5到V3,DeepSeek仅用了短短几个月的时间,便完成了从通用与代码能力融合到全面性能突破的跨越。


DeepSeek里程碑


DeepSeek的初心:探索AGI的本质


DeepSeek始终秉持"投身于探索AGI的本质,不做中庸的事,带着好奇心,用最长期的眼光去回答最大的问题"的理念。这种长期主义的追求,使得DeepSeek在技术研发上不断突破,从V2.5的通用与代码能力融合,到V3的全面性能提升,每一步都彰显了其对技术创新的执着。


从V2.5到V3:性能的全面飞跃


DeepSeek-V3是一款拥有6710亿参数的专家混合(MoE)模型,激活370亿参数,基于14.8T token的预训练数据。


生成速度方面相比V2.5提升了3倍,从 20TPS 提升至惊人的 60TPS。实测回复速度极快


回复速度


在性能上,DeepSeek-V3在多项基准测试中超越了Qwen2.5-72B和Llama-3.1-405B等开源模型,并与GPT-4和Claude-3.5-Sonnet等顶尖闭源模型不相上下。尤其在数学、代码和中文任务上,V3表现尤为突出,成为当前最强的开源模型。


模型基准测试


技术创新:高效训练与推理


DeepSeek-V3采用了多项创新技术,包括多头潜在注意力(MLA)架构无辅助损失的负载均衡策略以及多token预测(MTP)目标。这些技术不仅提升了模型的推理效率,还大幅降低了训练成本。V3的整个训练过程仅耗费了278.8万H800 GPU小时,总成本约为557.6万美元,远低于其他前沿大模型。


API服务:价格调整与优惠


随着V3的发布,DeepSeek调整了API服务价格。优惠期内(即日起至2025年2月8日),API价格为每百万输入tokens 0.1元(缓存命中)/1元(缓存未命中),每百万输出tokens 2元。优惠期结束后,价格将恢复至每百万输入tokens 0.5元(缓存命中)/2元(缓存未命中),每百万输出tokens 8元。


时期Token类型缓存命中缓存未命中
优惠期内
(至2025年2月8日)
输入tokens(每百万)¥0.1¥1
输出tokens(每百万)¥2¥2
优惠期后输入tokens(每百万)¥0.5¥2
输出tokens(每百万)¥8¥8

开源与社区支持


DeepSeek-V3不仅开源了原生FP8权重,还提供了BF16转换脚本,方便社区适配和应用。SGLang、LMDeploy、TensorRT-LLM等工具已支持V3模型推理,进一步降低了用户的使用门槛。


DeepSeek-V3的实际应用


1. 官方对话平台体验


DeepSeek-V3对话已在官网上线,用户可以通过chat.deepseek.com直接体验。


在线免费使用


2. API能力与开发接入


DeepSeek API 接口,支持以下功能:



  • 多轮对话能力

  • 对话前缀续写(Beta)

  • FIM(Fill In Middle)补全

  • 结构化输出 JSON output

  • 多语言支持


开发者可以通过API文档了解详细的接入方式和示例代码:api-docs.deepseek.com


结语:开源AI的新标杆



DeepSeek-V3的发布不仅是技术的一次飞跃,更是开源精神的体现。


它不仅在性能上与世界顶尖的闭源模型媲美,更以开源的方式推动了人工智能技术的普惠发展,是当之无愧的国产之光!


未来,相信DeepSeek将会继续在AGI探索的道路上砥砺前行,为AI领域带来更多创新与突破。


哦对了,关于使用开源类ChatGPT应用 EsChatPro 接入DeepSeek 大模型的教程,可参考如下文章:


juejin.cn/post/745189…


作者:极客密码
来源:juejin.cn/post/7452914615678713856
收起阅读 »

2024 年了! CSS 终于加入了 light-dark 函数!

web
一. 前言 随着 Web 技术的不断发展,用户体验成为了设计和开发过程中越来越重要的因素之一。为了更好地适应用户的视觉偏好,CSS 在 2024 年正式引入了一项新的功能 —— light-dark() 函数。 这项功能的加入主要在于简化网页对于浅色模式(Li...
继续阅读 »



一. 前言


随着 Web 技术的不断发展,用户体验成为了设计和开发过程中越来越重要的因素之一。为了更好地适应用户的视觉偏好,CSS 在 2024 年正式引入了一项新的功能 —— light-dark() 函数。


这项功能的加入主要在于简化网页对于浅色模式(Light Mode)与深色模式(Dark Mode)的支持,使得我们能够更快更轻松轻松地实现不同的主题切换。


接下来,我们就来详细了解一下我们在开发网页是如何实现主题切换的!


以下 Demo 示例,支持跟随系统模式和自定义切换主题,先一睹为快吧!


juejin6.gif


二. 传统方式


light-dark() 函数出现之前,开发者通常需要通过 JavaScript 或者 CSS 变量配合媒体查询来实现主题切换。例如:


使用 CSS 变量 + 媒体查询


开发者会定义一套 CSS 变量,然后基于用户的偏好设置(如:prefers-color-scheme: darkprefers-color-schema: light)来改变这些变量的值。


/* 默认模式 */
:root {
--background-color: white;
--text-color: black;
}

/* dark模式 */
@media (prefers-color-scheme: dark) {
:root {
--background-color: #333;
--text-color: #fff;
}
}

也可以使用 JavaScript 监听主题切换


JavaScript 可以监听用户更改其操作系统级别的主题设置,并相应地更新网页中的类名或样式表链接。


// 检测是否启用了dark模式
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.classList.add('dark-mode')
} else {
document.body.classList.remove('dark-mode')
}

以上这种方法虽然有效,但增加了代码复杂度,特别是当需要处理多个元素的颜色变化时,我们可能需要更多的代码来支持主题。


接下来我们看一下 light-dark 是如何实现的?


三. 什么是 light-dark?


image.png


light-dark() 是在 2024 年新加入的一种新的 CSS 函数,它允许我们根据用户的系统颜色方案(浅色或深色模式)来自动选择合适的颜色值。这个函数的引入简化了创建响应用户偏好主题的应用程序和网站的过程,而无需使用媒体查询或其他复杂的逻辑。


1. 基本用法


具体的说,light-dark() 函数接受两个参数,分别对应于浅色模式下的颜色值和深色模式下的颜色值。



  • 第一个参数是在浅色模式下使用的颜色。

  • 第二个参数是在深色模式下使用的颜色。


当用户的设备设置为浅色模式时,light-dark() 会返回第一个参数的颜色;当用户的设备设置为深色模式时,则返回第二个参数的颜色。


基本语法如下:


color: light-dark(浅色模式颜色, 深色模式颜色);

因此,light-dark() 提供了一种更简洁的方式来直接在 CSS 中指定两种模式下的颜色,而不需要额外的脚本或复杂的 CSS 结构。例如:


body {
background-color: light-dark(white, #333);
color: light-dark(black, #fff);
}

这里的 light-dark(白色, 深灰色) 表示如果用户处于浅色模式下,则背景色为白色;如果是深色模式,则背景色为深灰色。同样适用于文本颜色等其他属性。


2. 结合其他 CSS 特性


light-dark() 可以很好地与其他 CSS 特性结合使用,如变量、渐变等,以创造更加丰富多样的效果。当结合其他 CSS 特性使用 light-dark() 将更加灵活的创造页面的效果。


(1) 结合 CSS 变量


你可以利用 CSS 变量来存储颜色值,然后在 light-dark() 内引用这些变量,这样就能够在一处更改颜色方案并影响整个站点。


CSS 变量(也称为自定义属性)允许你存储可重复使用的值,这使得在不同的主题之间切换变得非常方便。你可以设置基础颜色变量,然后利用 light-dark() 来决定这些变量的具体值。


:root {
--primary-color: light-dark(#007bff, #6c757d);
--background-color: light-dark(white, #212529);
--text-color: light-dark(black, white);
}

body {
background-color: var(--background-color);
color: var(--text-color);
}

(2) 结合媒体查询


虽然 light-dark() 本身就可以根据系统偏好自动调整颜色,但有时候你可能还需要针对特定的屏幕尺寸或分辨率进行额外的样式调整。这时可以将 light-dark() 与媒体查询结合使用。


@media (max-width: 600px) {
body {
--button-bg: light-dark(#f8f9fa, #343a40); /* 更小的屏幕上按钮背景色 */
--button-text: light-dark(black, white);
}
button {
background-color: var(--button-bg);
color: var(--button-text);
}
}

(3) 结合伪类


light-dark() 也可以与伪类一起工作,比如 :hover, :focus 等,以实现不同状态下的颜色变化。


button {
background-color: light-dark(#007bff, #6c757d);
color: light-dark(white, black);
}

button:hover,
button:focus {
background-color: light-dark(#0056b3, #5a6268);
}

(4) 结合渐变


如果你希望在浅色模式和深色模式下使用不同的渐变效果,同样可以通过 light-dark() 来实现。


.header {
background: linear-gradient(light-dark(#e9ecef, #343a40), light-dark(#dee2e6, #495057));
}

(5) 结合阴影


对于元素的阴影效果,你也可以根据不同主题设置不同的阴影颜色和强度。


.box-shadow {
box-shadow: 0 4px 8px rgba(light-dark(0, 255), light-dark(0, 255), light-dark(0, 255), 0.1);
}

通过上述方法,你可以充分利用 light-dark() 函数的优势,并与其他 CSS 特性结合,创造出既美观又具有高度适应性的网页设计。这样不仅提高了用户体验,还简化了开发过程中的复杂度。


四. 兼容性


在 2024 年初时,light-dark() 函数作为 CSS 的一个新特性被加入到规范中,并且开始得到一些现代浏览器的支持。


image.png


其实,通过上图我们可以看到,light-dark() 在主流浏览器在大部分版本下都是支持了,所以我们可以放心的使用它。


但是同时我们也要注意,在一些较低的浏览器版本上仍然不被支持,比如 IE。因此,为了确保兼容性,在生产环境中使用该功能前需要检查目标浏览器是否支持这一特性。


如果浏览器不支持 light-dark(),可能需要提供回退方案,比如使用传统的媒体查询 @media (prefers-color-scheme: dark) 或者通过 JavaScript 来动态设置颜色。


五. 总结


通过本文,我们了解到,light-dark() 函数是 CSS 中的一个新特性,它允许开发者根据用户的系统偏好(浅色或深色模式)来自动切换颜色。


通过与传统模式开发深浅主题的比较,我们可以总结出 light-dark() 的优势应该包括:



  • 使用简洁:不需要编写额外的媒体查询,简洁高效。

  • 自动响应:能够随着系统的颜色方案改变而自动切换颜色。

  • 易于维护:所有与颜色相关的样式可以在同一处定义。

  • 减少代码量:相比使用多个媒体查询,可以显著减少 CSS 代码量。


light-dark() 函数是 CSS 领域的一项进步,它不仅简化了响应式设计的过程,也体现了对终端用户个性化体验的重视。随着越来越多的现代浏览器开始支持这一特性,我们未来可以在更多的应用场景中使用这一特性!


文档链接


light-dark


码上掘金演示


可以点击按钮切换主题,也可以切换系统的暗黑模式跟随:






🔥 我正在参加2024年度人气创作者评选,每投2票可以抽奖! 点击链接投票



作者:前端梦工厂
来源:juejin.cn/post/7443828372775764006
收起阅读 »

程序员为何会出纰漏?

这两天我们开发团队不知道咋的,跟包饺子下锅似的接连出了不少纰漏,有的大有的小,其实开发能力都可以,不是那种能力差导致的问题,我从外部观察,总结了一些出纰漏的原因和解决方案。 先说一下有啥纰漏。 小程序代码分包的时候,影响到线上正在使用的业务,损失了大概 1 ...
继续阅读 »

这两天我们开发团队不知道咋的,跟包饺子下锅似的接连出了不少纰漏,有的大有的小,其实开发能力都可以,不是那种能力差导致的问题,我从外部观察,总结了一些出纰漏的原因和解决方案。


先说一下有啥纰漏。



  1. 小程序代码分包的时候,影响到线上正在使用的业务,损失了大概 1 晚上的流量。

  2. 上了身-份-证、人脸认证功能,测试回归的时候,测了不需要实名和人脸的场景,没测只需要身-份-证的场景,结果线上跑的时候用这个场景,导致功能也出了问题,用户反馈过来才发现。

  3. 错把代码提交到了 dev 分支。


看起来研发该死,但恐怕不全是研发的锅,当然我不是故意找理由,这些纰漏也是研发扛下来了,我只是尝试分析从更具体的原因分析,而不是简单的说一句能力太差、或者水平不够这样没法定位也没法改进的原因。


这些出问题的场景,无一例外都是很紧急的需求,开发加班加点做出来的,代码写的时候很匆忙,测试也是加班加点测的。


常在河边走,哪有不湿鞋?我觉得快和稳之间,对开发来说很难平衡,有些需求强行要那个时间点,最后只能牺牲稳定性求效率。


那怎么避免这种事情发生?


需求可以 delay,代码不能出问题


如果工作量实在大,那就先花点时间列举工作量大的原因。大部分领导其实讲道理的,你能像他说明工作量的确大,事项的确做不完,领导会额外给时间。


我觉得这是比只闷头写代码更有难度的事,也是一种能力的体现,这需要调研充分、思维清晰、表达有条理、和领导沟通的心理等等各项挑战。


只知道埋头苦干,但干不多不一定就是好。


万一要是真的只知道埋头苦干,那也要掌控好自己的节奏,一定要保证代码的质量,平时加加班,周末也来加班,通过拉长时间线的方式多写点代码,而不是通过偷懒、减少代码逻辑的方式。


加班的时候冒冒泡,留点记录,这样即使需求 delay 了,至少自己的态度表达到位了,一般领导也不会责怪。


需求这东西,delay 两天没那么恐怖,反倒是着急出了纰漏,那才是更恐怖的。


慢慢写


写代码很费脑子,要考虑到所有可能的异常场景,还要从业务上闭环,一着急,就容易漏场景,出纰漏,不要着急,细水长流。


想好再写


尤其是后端,新业务的架构设计,一定是要多花时间思考的,要充分考虑到业务的扩展性、未来的维护性、和其他业务对接的兼容性。


比如我最近写的京东商户订单支付,我们已有一套支付中心的系统,而在对接京东的时候,他们的支付其实是通过京东的订单状态回调来做的,我们一开始准备写在支付中心,后来随着三方接口的对接,对京东业务有更深的理解,我们决定做一套新的商户订单支付系统,和原本的支付中心(支付宝、微信支付)做区分。


如果我们当时匆忙的直接嵌入到支付中心,整个系统架构就会很混乱,订单和支付裹在一起,后续既不好维护,也不好扩展。


这样虽然需求有 delay 风险,但整体技术侧的方案,是绝对没问题的。大不了我周末来加班,加班都干不完,那就得赶紧汇报领导了。


包括最开始的人脸也是的,没有调研清楚,光阿里就两套不同的人脸接口,结果先用的贵的一套,后面发现有便宜的,又强行接入便宜的一套。如果一开始能多想想,先调研清楚,可能最后的工时反而更短一些。


专一写代码不要跳


写代码的时候,最好不要来回跳需求写,看起来很牛逼感觉也很吊,实际上很容易出问题,精力消耗太快了,有些场景思考不深入,就有可能埋雷。


决策和执行分开


如果开发过程中又做决策又做执行,尤其是干需求的时候,有的决策问起来吧很耗时间,需求到期上不了线了,就自己做个决策,没有告知其他人。这种场景的雷我也踩了几个。


开发对业务的理解不如运营产品深入,有时候开发觉得的最优决策不是运营想要的,最好不要为了图省隐蔽这些问题。


甩锅技巧


这部分是语言的艺术,就是当纰漏下来了,判责归自己,怎么表达,才是比较得体的。


一直说自己的责任吧,领导会觉得我很菜,一直推脱责任吧,领导又会觉得我不负责。


最好是那种和自己有点关系,但是关系不是那么直接的描述。


或是用于日常沟通,为了避免别人误把锅扣到自己头上。


我总结了同事们常用的有如下技巧:



  1. 首先主体对象不要说自己,比如分包的问题、锁的问题、分支的问题、没有这样的场景等等,避免说成我打的包有问题、我代码写的有问题、我分支切的不对等等。

  2. 先说一些撇开自己责任的话术,比如这里的代码没动过;之前还是好好的;这里用的外部接口的数据/逻辑。

  3. 接到莫名其妙的锅第一时间弹回去,怎么弹看 2 中的话术。我以前懒得弹,结果头上的锅越来越多。


挺瞧不上这些东西的,也不想花心思想,但有时候职场、工作、社会就是这么贱,人越是老实,就越容易被欺负;越能干活的人,最后会有越来越多的活;希望大家不要重蹈我的覆辙。


每天抽点时间学习和反思,加油,共勉。


作者:小兵张健
来源:juejin.cn/post/7453023172226334739
收起阅读 »

⚡聊天框 - 微信加载历史数据的效果原来这样实现的

web
前言 我记得2021年的时候做过聊天功能,那时业务也只限微信小程序 那时候的心路历程是: 卧槽,让我写一个聊天功能这么高大上?? 嗯?这么简单,不就画画页面来个轮询吗,加个websocket也还行吧 然后,卧槽?这查看历史聊天记录什么鬼,页面闪一下不太好啊,...
继续阅读 »

前言


我记得2021年的时候做过聊天功能,那时业务也只限微信小程序


那时候的心路历程是:



卧槽,让我写一个聊天功能这么高大上??


嗯?这么简单,不就画画页面来个轮询吗,加个websocket也还行吧


然后,卧槽?这查看历史聊天记录什么鬼,页面闪一下不太好啊,真的能做到微信的那种效果吗



然后一堆调研加测试,总算在小程序中查看历史记录没那么鬼畜了,但是总是感觉不是最佳解决方案。



那时打出的子弹,一直等到现在击中了我



最近又回想到了这个痛点,于是网上想看看有没有大佬发解决方案,结果还真被我找到了。


image.png


正文开始


1,效果展示


上才艺~~~


222.gif


2,聊天页面


2.1,查看历史聊天记录的坑


常规写法加载历史记录拼接到聊天主体的顶部后,滚动条会回到顶部、不在原聊天页面


直接上图


111.gif


而我们以往的解决方案也只是各种利用缓存scroll的滚动定位把回到顶部的滚动条重新拉回加载历史记录前的位置,好让我们可以继续在原聊天页面。


但即使我们做了很多优化,也会有安卓和苹果部分机型适配问题,还是不自然,可能会出现页面闪动


其实吧,解决方案只有两行css代码~~~


2.2,解决方案:flex神功


想优雅顺滑的在聊天框里查看历史记录,这两行css代码就是flex的这个翻转属性


dispaly:flex;
flex-direction: column-reverse

灵感来源~~~


333.gif


小伙伴可以看到,在加载更多数据时



滚动条位置没变、加载数据后还是原聊天页面的位置



这不就是我们之前的痛点吗~~~


所以,我们只需要翻转位置,用这个就可以优雅流畅的实现微信的加载历史记录啦


flex-direction: column-reverse


官方的意思:指定Flex容器中子元素的排列方向为列(从上到下),并且将其顺序反转(从底部到顶部)


如果感觉还是抽象,不好理解的话,那就直接上图,不加column-reverse的样子


image.png


加了column-reverse的样子


image.png


至此,我们用column-reverse再搭配data数据的位置处理就完美解决加载历史记录的历史性问题啦


代码放最后啦~~~


2.3,其他问题


2.3.1,数据过少时第一屏展示


因为用了翻转,数据少的时候会出现上图的问题


只需要.mainArea加上height:100%


然后额外写个适配盒子就行


flex-grow: 1; 
flex-shrink: 1;

image.png


2.3.2,用了scroll-view导致的问题


这一part是因为我用了uniappscroll-view组件导致的坑以及解决方案,小伙伴们没用这个组件的可忽略~~~


如下图,.mainArea使用了height:100%后,继承了父级高度后scroll-view滚动条消失了。


image.png


.mainArea去掉height:100%后scroll-view滚动条出现,但是第一屏数据过多时不会滚动到底部展示最新信息


image.png


解决方案:第一屏手动进行滚动条置顶


scrollBottom() {
if (this.firstLoad) return;
// 第一屏后不触发
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query
.select("#mainArea")
.boundingClientRect((data) => {
console.log(data);
if (data.height > +this.chatHeight) {
this.scrollTop = data.height; // 填写个较大的数
this.firstLoad = true;
}
})
.exec();
});
},

3,服务端


使用koa自己搭一个websocket服务端


3.1 服务端项目目录


image.png


package.json


{
"name": "websocketapi",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"koa": "^2.14.2",
"koa-router": "^12.0.1",
"koa-websocket": "^7.0.0"
}
}


koa-tcp.js


const koa = require('koa')
const Router = require('koa-router')
const ws = require('koa-websocket')

const app = ws(new koa())
const router = new Router()

/**
* 服务端给客户端的聊天信息格式
* {
id: lastid,
showTime: 是否展示时间,
time: nowDate,
type: type,
userinfo: {
uid: this.myuid,
username: this.username,
face: this.avatar,
},
content: {
url:'',
text:'',
w:'',
h:''
},
}
消息数据队列的队头为最新消息,以次往下为老消息
客户端展示需要reverse(): 客户端聊天窗口最下面需要为最新消息,所以队列尾部为最新消息,以此往上为老消息
*/



router.all('/websocket/:id', async (ctx) => {
// const query = ctx.query
console.log(JSON.stringify(ctx.params))
ctx.websocket.send('我是小服,告诉你连接成功啦')
ctx.websocket.on('message', (res) => {
console.log(`服务端收到消息, ${res}`)
let data = JSON.parse(res)
if (data.type === 'chat') {
ctx.websocket.send(`我也会说${data.text}`)
}
})
ctx.websocket.on('close', () => {
console.log('服务端关闭')
})
})

// 将路由中间件添加到Koa应用中
app.ws.use(router.routes()).use(router.allowedMethods())

app.listen(9001, () => {
console.log('socket is connect')
})



切到server目录yarn


然后执行nodemon koa-tcp.js


没有nodemon的小伙伴要装一下


image.png


代码区


完整项目Github传送门


聊天页面的核心代码如下(包含data数据的位置处理和与服务端联动)



完结


这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。


到这里,想给小伙伴分享两句话



现在搞不清楚的事,不妨可以先慢下来,不要让自己钻到牛角尖了


一些你现在觉得解决不了的事,可能需要换个角度



欢迎转载,但请注明来源。


最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。


image.png


作者:尘落笔记
来源:juejin.cn/post/7337114587123335180
收起阅读 »