2026 年前端必须掌握的 4 个 CSS 新特性!
1. 前言2026 年已经到来,前端技术又迎来了新一轮的革新。今天,我为你精选了 4 个在 2025 年正式发布、2026 年必须掌握的 CSS 新特性,让你的技术更上一层楼!2. 兄弟元素定位:sibling-index() 与 sibling-count()早些时候这些还只是实验性质的功能,现在它们已经在稳定的 Chrome 和 Safari 浏览器中可用了!记得以前实现列表项交错动画时,要手动给每个元素设置不同的延迟吗?现在,用 sibling-index() 一行代码就能搞定!
li {
transition: opacity 0.3s ease;
transition-delay: calc((sibling-index() - 1) * 100ms);
}
这个函数会自动获取元素在兄弟节点中的位置(从 1 开始计数),通过简单的计算就能实现流畅的交错动画效果。如果再搭配 @starting-style,连入场动画都能轻松搞定:
li {
transition: opacity 0.3s ease;
transition-delay: calc((sibling-index() - 1) * 100ms);
@starting-style {
opacity: 0;
}
}
实现效果如下:3. 滚动状态查询:@container scroll-state()现在你可以精确地知道用户正在如何滚动页面。不仅如此,你还可以查询滚动条的三种状态:粘附、贴靠、可滚动。首先,给需要监测的容器加上 container-type: scroll-state,然后就可以用 @container scroll-state() 来查询它的状态了。3.1. 粘附状态:stuck
/* 当导航栏被“粘住”时 */
@container scroll-state(stuck) {
.inner-navbar {
box-shadow: var(--shadow-3);
}
}
使用效果如下:3.2. 贴靠状态:snapped
section {
overflow: auto hidden;
scroll-snap-type: x mandatory;
> article {
container-type: scroll-state;
scroll-snap-align: center;
@supports (container-type: scroll-state) {
> * {
transition: opacity 0.5s ease;
@container not scroll-state(snapped: x) {
opacity: 0.25;
}
}
}
}
}
使用效果如下:3.3. 可滚动状态:scrollable而且你可以查询滚动方向:
@container scroll-state(scrollable: top) {
}
@container scroll-state(scrollable: right) {
}
@container scroll-state(scrollable: bottom) {
}
@container scroll-state(scrollable: left) {
}
我们来举一个例子:
.scroll-container {
container-type: scroll-state size;
overflow: auto;
&::after {
content: " ";
background: var(--_shadow-top), var(--_shadow-bottom);
transition: --_scroll-shadow-color-1-opacity 0.5s ease, --_scroll-shadow-color-2-opacity 0.5s ease;
@container scroll-state(scrollable: top) {
--_scroll-shadow-color-1-opacity: var(--_shadow-color-opacity, 25%);
}
@container scroll-state(scrollable: bottom) {
--_scroll-shadow-color-2-opacity: var(--_shadow-color-opacity, 25%);
}
}
}
使用效果如下:你可以发现,在滚动的时候,容器顶部和底部有一层阴影。4. 文字精准对齐:text-boxtext-box 可以精确控制文字的边界框,实现像素级的对齐效果。Web 字体渲染时会在字形上下方预留“安全间距”:但有时我们需要进行像素级精确对齐,此时就需要使用 text-box:
h1 {
text-box: trim-both cap alphabetic;
}
这一行代码就能:trim-both:同时修剪上下方的空白cap:修剪到大写字母高度线以上alphabetic:修剪到字母基线以下使用效果如下:机-会技术大厂,前端-后端-测试,全国均有机-会,感兴趣可以试试。待遇和稳定性都还不错~5. 类型安全:typed attr()这是 attr() 函数的升级版,支持类型检查和回退值,在 HTML 和 CSS 之间搭建了强大的桥梁。5.1. 传递颜色
css
.theme {
background: attr(data-bg color, black); /* 类型:颜色,默认:黑色 */
color: attr(data-fg color, white);
}
5.2. 传递数字
…
css
.grid {
--_columns: attr(data-columns number, 3);
grid-template-columns: repeat(var(--_columns), 1fr);
}
5.3. 类型验证(枚举值)
css
[scroll-snap] {
scroll-snap-align: attr(scroll-snap type(start | center | end));
}
type() 函数会验证属性值是否在允许的关键字列表中,无效值会被优雅地回退。6. 浏览器支持现状你可能会说:“这些功能就像那些时髦衣服……等我们能用了,它们可能已经过时了 😂”确实,浏览器兼容性是每一位前端开发者需要关注的问题。但这些功能其实大多属于渐进增强,我们可以先在支持的浏览器中提供更好的体验,不支持的浏览器回退。7. 最后这 4 个 CSS 新特性其实也代表了 CSS 的未来方向:更智能的布局控制(sibling-index)更精细的交互感知(scroll-state)更精准的视觉设计(text-box)更强大的 HTML-CSS 桥梁(typed attr)2026 年,前端开发不再只是“让页面显示出来”,而是“让体验完美起来”。这些工具让我们能够创造出更精致、更智能、更用户友好的网页体验。——转载自:冴
UI小姐姐要求有“Duang~Duang”的效果怎么办?
设计小姐姐: “搞一下这样的回弹效果,你行不行?” 我:“行!直接 50 行 keyframes + transform + 各种百分比,搞定 ” 设计小姐姐:“太硬(撇嘴),不够 Q 弹(鄙视)” 我:(裂开) 隔壁老王:这么简单你都不行,我来一行贝塞尔 cubic-bezier(0.3, 1.15, 0.33, 1.57) 秒了😎 设计小姐姐:哇哦!(兴奋)好帅!(星星眼🌟)好Q弹!(一脸崇拜😍) 我:“???”🧠 一、为什么一行贝塞尔就能“Duang”起来?1️⃣ cubic-bezier 是什么?在 CSS 动画里,我们经常写:
transition: all 0.5s ease;
但其实 ease、linear、ease-in-out 这些都只是封装好的贝塞尔曲线。 底层原理是:
cubic-bezier(x1, y1, x2, y2)
这四个参数定义了时间函数曲线,控制动画速度的变化。x:时间轴(必须在 0~1 之间)y:数值轴(可以超出 0~1!)👉 当 y 超过 1 或小于 0 时,动画值就会冲过终点再回弹, 这就是“回弹感”的核心。2️⃣ 回弹的本质:过冲 + 衰减想象一个球掉下来:过冲:球落地时会压扁(超出终点)回弹:然后反弹回来,再逐渐稳定在动画中,这个“过冲”就是 y>1 的部分, 而“回弹”就是曲线回到 y=1 的过程。🧪 二、一行贝塞尔的魔法✅ 火箭发射
🚀发射!
💡 参数解析:y1 = -0.55 → 先轻微反向缩小y2 = 1.55 → 再冲过头 55%,最后回弹到原位🧩 四、常用贝塞尔参数效果描述贝塞尔参数备注微回弹(按钮)cubic-bezier(0.34, 1.31, 0.7, 1)轻柔弹性强回弹(卡片)cubic-bezier(0.68, -0.55, 0.27, 1.55)爆发力强柔和出入cubic-bezier(0.4, 0, 0.2, 1.4)iOS 风弹性放大cubic-bezier(0.175, 0.885, 0.32, 1.275)弹簧感火箭猛冲cubic-bezier(0.68, -0.55, 0.27, 1.55)推背感 ——转载自:前端九哥
让网页在 PC 缩放时“纹丝不动”的 4 个技巧
记录一次把「标题、描述、背景图」全部做成“流体响应式”的踩坑与经验背景最近给 LUCI OS 官网做首屏改版,需求只有一句话:“PC 端浏览器随意缩放,首屏内容要像海报一样,几乎看不出形变。”听起来简单,但「缩放不变形」+「多端自适应」本质上是矛盾的。 经过 3 轮迭代,我们把问题拆成了 4 个小目标,并给出了最简洁的解法。1. 文本:用 clamp() 一把梭传统写法给 3~4 个断点写死字号,窗口稍微拉一下就会跳变。 CSS 4 级函数 clamp(MIN, VAL, MAX) 天生就是解决“跳变”的:标题:text-[clamp(28px,6vw,48px)]描述:text-[clamp(14px,1.2vw,18px)]一行代码实现「最小值保底、最大值封顶、中间平滑变化」。 浏览器缩放时,字号随 vw 线性变化,肉眼几乎察觉不到阶梯感。2. 容器:限宽 + 居中 = “锁死”水平形变再漂亮的字号,如果容器宽度跟着窗口无限拉伸,一样会崩。 做法简单粗暴:
max-w-6xl mx-auto
max-w-6xl 把最大内容宽度锁死在 1152px;mx-auto 保证左右留白始终对称。窗口继续拉大,两侧只是等比留空,内容区不再变形。3. 图片(或背景):固定尺寸 + 背景定位背景图不能跟着 100% 拉伸,否则人物/产品会被拉长。 我们把背景拆成两层:外层:全屏 div,只做黑色渐变遮罩;内层:真正的背景图用css复制
background: url(...) 50% / cover no-repeat;
max-width: 1280px;
max-height: 800px;
只要窗口没超过 1280×800,背景图始终保持原始比例,居中裁剪。机-会技术大厂,前端-后端-测试,全国均有机-会,感兴趣可以试试。待遇和稳定性都还不错~4. 布局:断点内“锁死”,断点外才变化Tailwind 的 md:flex-row 之类前缀只在跨断点时生效。 在 同一断点内 我们故意:用固定 gap-32px 而非百分比;用固定图片宽 md:w-75 高 md:h-47;用 items-center 保证垂直居中。=> 浏览器宽一点点、窄一点点,所有尺寸都不变,自然看不出变化。 直到窗口拉到下一个断点阈值,布局一次切换,干净利落。最终代码(最简可读版)
#嘉立创PCB#
{/* 1. 背景层:固定尺寸 + 居中 */}
{/* 2. 内容层:限宽 + 居中 + clamp */}
效果1440px 与 1920px 两档分辨率下,标题、描述、背景图的视觉差异 [removed]
Unlocking Vast Data Potential
LUCI OS is powered by Mavi's video understanding engine …
我删光了项目里的 try-catch,老板:6
相信我们经常这样写bug(不是 👇:
try {
const res = await api.getUser()
console.log('✅ 用户信息', res)
} catch (err) {
console.error('❌ 请求失败', err)
}
看似没问题每个接口都要 try-catch,太啰嗦了!错误处理逻辑分散,不可控!代码又臭又长💨!💡 目标:不抛异常的安全请求封装我们希望实现这样的调用👇:
const [err, data] = await safeRequest(api.getUser(1))
if (err) return showError(err)
console.log('✅ 用户信息:', data)
是不是清爽多了?✨ 没有 try-catch,却能同时拿到错误和数据。🧩 实现步骤1️⃣ 先封装 Axios 实例
// src/utils/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
})
// 🧱 请求拦截器
service.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
},
(error) => Promise.reject(error)
)
// 🧱 响应拦截器
service.interceptors.response.use(
(response) => {
const res = response.data
if (res.code !== 0) {
ElMessage.error(res.message || '请求失败')
return Promise.reject(new Error(res.message || '请求失败'))
}
return res.data
},
(error) => {
ElMessage.error(error.message || '网络错误')
return Promise.reject(error)
}
)
export default service
拦截器的作用:✅ 统一处理 token;✅ 统一处理错误提示;✅ 保证业务层拿到的永远是“干净的数据”。2️⃣ 封装一个「安全请求函数」
// src/utils/safeRequest.js
export async function safeRequest(promise) {
try {
const data = await promise
return [null, data] // ✅ 成功时返回 [null, data]
} catch (err) {
return [err, null] // ❌ 失败时返回 [err, null]
}
}
这就是关键! 它让所有 Promise 都变得「温柔」——不再抛出异常,而是返回结构化结果。3️⃣ 封装 API 模块
// src/api/user.js
import request from '@/utils/request'
export const userApi = {
getUser(id) {
return request.get(`/user/${id}`)
},
updateUser(data) {
return request.put('/user', data)
},
}
4️⃣ 在业务层优雅调用
是不是很优雅、数据逻辑清晰、不需要 try-catch、 错误不崩溃。老板说:牛🍺,你小子有点东西 插播一则机-会技术大厂,前端-后端-测试,全国均有机-会,感兴趣可以试试。待遇和稳定性都还不错~🧱 我们还可以进一步优化:实现自动错误提示我们可以给 safeRequest 增加一个选项,让错误自动提示:
// src/utils/safeRequest.js
import { ElMessage } from 'element-plus'
export async function safeRequest(promise, { showError = true } = {}) {
try {
const data = await promise
return [null, data]
} catch (err) {
if (showError) {
ElMessage.error(err.message || '请求失败')
}
return [err, null]
}
}
使用时👇:
const [err, data] = await safeRequest(userApi.getUser(1), { showError: false })
这样你可以灵活控制是否弹出错误提示, 比如某些静默请求就可以关闭提示。🧠 进阶:TypeScript 支持(超丝滑)如果你用的是 TypeScript,可以让返回类型更智能👇:
export async function safeRequest[removed](
promise: Promise[removed]
): Promise[removed] {
try {
const data = await promise
return [null, data]
} catch (err) {
return [err as Error, null]
}
}
调用时:
const [err, user] = await safeRequest[removed](userApi.getUser(1))
if (user) console.log(user.name) // ✅ 自动提示类型
老板:写得很好,下次多写点,明天你来当老板——转载自:前端九哥#嘉立创PCB#
有了免费的Kiro,这次真的可以把Cursor扔了!
家好,我是子昕,一个干了10年的后端开发,现在在AI编程这条路上边冲边摸索,每天都被新技术追着跑。Claude的金主爸爸亚马逊(AWS)偷偷发布了一款AI编程工具,Kiro。我用它做了三个公司的生产级项目需求,深度体验3天后发现:Kiro现在完全免费,可以免费使用Claude-Sonnet-4和Claude-3.7模型规范驱动开发模式,代码质量和工程化程度碾压CursorAgent Hooks自动化系统,真正解决了AI编程工具的健忘问题这可能是今年最值得关注的AI编程工具。下载地址:kiro.dev/Windows用户、Mac用户都可以使用,基于VS Code架构,零学习成本。为什么说Kiro比Cursor更强?技术角度深度分析最近在用真实项目对比各种AI编程工具,发现Cursor在处理复杂业务逻辑时存在几个核心问题:上下文理解不足:经常遗忘项目结构,生成不一致的代码Token优化过度:为了省成本,功能完整性受影响缺乏工程化思维:直接生成代码,缺乏规范和文档Kiro的出现完全解决了这些痛点。Kiro安装体验:零门槛切换Kiro和Cursor一样基于VS Code架构,所以切换成本为零:但在交互设计上,Kiro提供了两种截然不同的工作模式:Vibe模式Spec模式Vibe模式:传统聊天式编程,适合快速原型开发Spec模式:规范驱动开发,这是Kiro的核心创新Spec模式:规范驱动开发的革命这是我见过最接近企业级开发标准的AI工具工作流。Spec模式遵循严格的三阶段开发流程:第一阶段:需求分析(Requirements)自动生成EARS语法标准的需求文档,包含:用户故事定义验收标准边界条件处理非功能性需求第二阶段:系统设计(Design)生成完整的技术设计文档:包含数据库Schema、API接口设计、组件架构图等生产级文档。第三阶段:实现计划(Implementation)将功能分解为有序任务,包含依赖关系和测试要求。任务管理与执行:颗粒度控制Kiro的任务管理机制是其核心优势之一。生成的文档会自动保存在项目根目录的.kiro文件夹中,每个任务都支持独立控制:关键特性:任务状态实时追踪支持并发任务执行智能任务队列管理任务队列这种颗粒度控制完全解决了Cursor一股脑生成代码导致的返工问题。Agent Hooks:自动化质量控制Kiro最具技术含量的功能是Agent Hooks系统,基于文件事件触发自动化检查:实时代码预览预览按钮预览效果通过Follow按钮可以实时查看代码修改,相比Cursor的全量预览和Claude Code的黑盒执行,Kiro提供了更好的可控性。一键回滚机制支持任务级别的原子回滚,比Cursor的checkpoint机制更精确。技术对比:Kiro vs Cursor 实战差异为了客观评估两个工具的差异,我用一个完整的团队任务管理系统项目进行了对比测试:测试场景项目复杂度:类似简化版Jira,包含用户系统、项目管理、任务流转、智能功能、实时通知、数据看板等完整模块技术栈:React + TypeScript + Tailwind CSS + Node.js + Express + PostgreSQL + Prisma ORM + Socket.ioAI集成:调用OpenAI API进行智能工时估算和任务分配评估维度:开发效率、代码质量、文档完整性、可维护性对比结果Cursor表现:直接开始写代码,缺乏整体规划面对复杂业务逻辑时容易遗漏关键模块生成的组件缺乏系统性设计数据库Schema设计不够完整实时通信和AI集成部分需要大量手动调整几乎没有项目文档输出Kiro表现:先生成完整的需求分析和系统设计文档自动分解为用户管理、项目管理、任务系统等独立模块生成完整的数据库Schema和API接口设计包含Socket.io集成和AI功能的详细实现方案自动生成组件架构图和数据流图输出可直接用于团队协作的技术文档核心差异面对这种企业级复杂项目,Cursor更像是功能堆砌,而Kiro展现了真正的系统工程思维。特别是在处理多模块协作、数据库设计、第三方集成等复杂场景时,Kiro的规范驱动开发优势非常明显。插播一则机-会技术大厂,前端-后端-测试,全国均有机-会,感兴趣可以试试。待遇和稳定性都还不错~成本效益分析:定价策略对比当前状态:Kiro完全免费,包含Claude-4模型访问权限未来定价:免费版:50次智能体交互/月Pro版:$19/月,1000次交互Pro+版:$39/月,3000次交互Cursor Pro对比:价格:$20/月限制:500次Chat + 无限Tab补全模型:GPT-4、Claude-4性价比分析: Kiro Pro比Cursor便宜$1,但提供2倍的交互次数,且基于更新的Claude-4模型。技术架构:AWS生态系统优势Kiro基于以下技术栈:前端:Code OSS(VS Code开源版)AI模型:Claude Sonnet 3.7/4.0协议支持:MCP(Model Context Protocol)云基础设施:AWS相比Cursor的多模型策略,Kiro专注于Claude系列模型的深度优化,在代码理解和生成质量上表现更稳定。我的AI编程工具新排名基于深度测试和生产环境使用经验:Kiro - 规范驱动开发,企业级标准Claude Code - 复杂逻辑分析专家Augment - 质量优先,适合高要求项目Cursor - 个人快速原型工具其他工具 - 功能差异明显选择建议适合Kiro的场景:需要完整文档的正式项目团队协作开发对代码质量要求较高企业级应用开发适合Cursor的场景:个人快速原型开发学习编程过程简单功能迭代写在最后Kiro的出现标志着AI编程工具的重要转变:1.0时代:代码生成和补全2.0时代:规范驱动的全流程工程化这种转变反映了行业从能用到好用再到专业的需求升级。建议先用Vibe模式熟悉界面,再尝试Spec模式体验规范驱动开发的完整流程。——转载自:子昕AI编程#技术干货#
什么时候用ref,什么时候用reactive?
官方文档提到过 ,ref 一把梭,不建议用 reactive。ref - 你的"万能工具箱"
// 什么都能装!
const name = ref('张三') // ✅ 字符串
const age = ref(18) // ✅ 数字
const isLoading = ref(false) // ✅ 布尔值
const user = ref({name: '李四'}) // ✅ 对象
const list = ref([]) // ✅ 数组
// 用的时候要加 .value
name.value = '王五'
age.value = 20
reactive - 你的"对象专用盒"
javascript
体验AI代码助手
代码解读
复制代码
// 只能装对象!
const user = reactive({ // ✅ 对象
name: '张三',
age: 18
})
const form = reactive({ // ✅ 对象
username: '',
password: ''
})
const list = reactive([]) // ✅ 数组(其实也是对象)
// 用的时候直接点属性
user.name = '李四'
form.username = 'admin'
关键区别:重新赋值问题机-会技术大厂,前端-后端-测试,全国均有机-会,感兴趣可以试试。待遇和稳定性都还不错~场景:从后台请求数据❌ reactive 的错误用法:
// 初始化
let list = reactive(['苹果', '香蕉'])
// 模拟请求数据
setTimeout(() => {
const newData = ['西瓜', '葡萄', '芒果']
// ❌ 错误!这样会丢失响应式!
list = newData
// 页面不会更新!因为 list 的"监听器"断了
}, 1000)
✅ ref 的正确用法:
// 初始化
const list = ref(['苹果', '香蕉'])
// 模拟请求数据
setTimeout(() => {
const newData = ['西瓜', '葡萄', '芒果']
// ✅ 正确!通过 .value 重新赋值
list.value = newData
// 页面正常更新!
}, 1000)
✅ reactive 的正确用法(如果非要用):
const state = reactive({
list: ['苹果', '香蕉']
})
setTimeout(() => {
const newData = ['西瓜', '葡萄', '芒果']
// ✅ 正确!只改属性,不改对象本身
state.list = newData
}, 1000)
📝 实战选择指南情况1:基础数据 → 必须用 ref
// ✅ 用 ref
const count = ref(0)
const name = ref('')
const isVisible = ref(true)
// ❌ reactive 会报错!
// const count = reactive(0) // 报错!
情况2:需要重新赋值 → 必须用 ref
// 从API获取数据
const data = ref(null)
const fetchData = async () => {
const result = await api.getData()
data.value = result // ✅ 可以重新赋值
}
// 切换页面数据
const currentPageData = ref([])
const changePage = (page) => {
currentPageData.value = getDataByPage(page) // ✅ 可以重新赋值
}
情况3:固定对象,只改属性 → 可以用 reactive
// 表单数据 - 通常不会整个替换
const form = reactive({
username: '',
password: '',
remember: false
})
// 用户信息 - 通常不会整个替换
const userInfo = reactive({
name: '张三',
age: 25,
avatar: ''
})
情况4:不确定用哪个 → 无脑用 ref
// 安全第一!
const something = ref(初始值)
🎯 黄金法则处理数字、字符串、布尔值? → 用 ref需要 xxx = 新数据 这样赋值? → 用 ref只是一个固定对象,只改里面的属性? → 可以考虑 reactive不确定? → 直接用 ref记住:ref 永远不会错,reactive 有时候会坑你!——转载自:我是天龙_绍#技术干货#
前端真的需要懂算法吗?聊聊感受
在公司干了几年,带个小团队,零零总总也面试了上百个前端候选人了。说实话,有时候面完一天,感觉人都是麻的。最让我头疼的是什么?就是“算法题”这个环节。我经常遇到两种候选人。一种是一听算法题,就两手一摊,表情痛苦,说“哥,我天天写业务,真没准备这个”。另一种呢,正好相反,题目一出,眼睛一亮,不出三十秒,就把LeetCode上背得滚瓜烂熟的最优解,一字不差地敲了出来,然后一脸期待地看着我。说实话,这两种,都不是我最想看到的。这就引出了一个很多候选人都想问,但不敢问的问题:“你们这些面试官,到底怎么想的?你们明知道我们前端平时工作中,99%的时间都用不上这些,为什么非要折磨我们?”今天,我就想站在桌子对面,跟大伙掏心窝子地聊聊,我们问算法题,到底图个啥。首先,我得承认一件事:我们知道你工作中不怎么写算法对,你没看错。我心里门儿清,我团队里的小伙伴们,每天的工作是跟产品经理“吵架”,是跟UI设计师对像素,是封装React/Vue组件,是处理浏览器兼容性,是调CSS。我招你进来,也不是为了让你用动态规划来给按钮加border-radius的。我们不会天真地以为,前端开发就是算法竞赛。如果你能把一个复杂的业务表单组件写得清晰、可维护、可扩展,在我眼里,这远比你徒手写一个红黑树要来得有价值。所以,请你先放轻松。我们不是在考察你是不是一个“算法大神”。机-会技术大厂,前端-后端-测试,全国均有机-会,感兴趣可以试试。待遇和稳定性都还不错~那我们到底在看什么?——思路远比答案重要既然不是看你会不会背最优解,那我们花这宝贵的20分钟,到底在考察什么?其实,算法题只是一个“载体”,一个“媒介”。通过这个载体,我想看到的是这几样东西:1. 你是怎么“解读”问题的(沟通与理解能力)一个靠谱的工程师,拿到需求不会立刻动手。他会先问问题,搞清楚所有的边界和约束。我出一道题:“写个函数,找出数组中第二大的数。”普通候选人:埋头就开始写代码。我欣赏的候选人:会先问我,“这个数组里会有重复的数字吗?会是无序的吗?会有负数吗?如果数组长度小于2怎么办?”你看,这就是差距。我能通过这些问题,看出你是否严谨,是否有处理边界情况的意识。这个能力,在你将来面对产品经理那些模糊的需求时,至关重要。2. 你的“思路”是否清晰(逻辑思维)我最喜欢看到的,不是你直接写出最优解,而是你告诉我你的思考过程。比如,你可以说:“我首先想到的,是一个最笨的办法,先排序,然后取倒数第二个。这个时间复杂度是O(n log n)。但感觉可以优化,我再想想……也许我只需要遍历一遍,用两个变量来维护最大值和第二大值,这样时间复杂度就降到O(n)了。”这个“先暴力,再优化”的思考过程,在我看来,比你直接默写出最优解要加分得多。因为它展示了你的逻辑推理能力和优化意识。3. 你的代码“品味”(工程素养)算法题的代码量不大,但足以管中窥豹,看出一个人的代码“品味”。你的变量是怎么命名的?a, b, c 还是 max, secondMax, current?你有没有处理我刚才提到的那些边界情况?你的代码有没有基本的缩进和格式?这些细节,都反映了你平时的编码习惯。一个连算法题都写得乱七八糟的人,我很难相信他在业务项目里能写出整洁的代码。4. 当你卡住时,你会怎么办?(抗压与学习能力)我有时候会故意出一些有点难度的题。我不是为了让你难堪,而是想看看你卡住的时候,会有什么反应。是直接放弃,说“不会”?还是会尝试跟我沟通,说“我卡在xxx了,能不能给点提示?”我非常乐意给提示。我更想招一个能和我一起“协作”解决问题的人,而不是一个遇到困难就“躺平”的人。你面对一道题的态度,很可能就是你未来面对一个技术难题的态度。给求职者的一些真心话所以,聊了这么多:别光背题,没用。 我只要稍微改动一下题目条件,或者问你为什么这么写,背题的同学马上就露馅了。多练习“说” 。刷题的时候,试着把你的思路说出来,录下来自己听听,或者讲给朋友听。面试时的口头表达,和自己闷头做题是两回事。重点理解“为什么” 。不要满足于“这道题这么解”,要去理解它为什么要用双指针,为什么要用哈希表。理解了思路,才能举一反三。面试时,心态放平。 没做出最优解,真没关系。把你思考的过程、你的尝试、你的权衡都清晰地表达出来,你已经赢了很多人了。我知道,让前端去卷算法,这个“游戏规则”本身就不那么公平。我们想找的是一个会思考、会沟通、有工程素养的“解决问题的人”。算法题,只是恰好成了当前最方便、成本最低的考察工具而已。——转载自:ErpanOmer
为什么推荐前端学习油猴脚本开发?
相信不少前端同学对 Chrome 扩展插件并不陌生:比如能屏蔽广告的 Adblock、自定义标签页的 Infinity 新标签页、格式化 JSON 的开发小工具、截图工具,甚至还有 Vue.js devtools、Redux DevTools 这样的调试利器。很早以前,我也被这些插件的强大功能吸引,暗自立下目标,一定要掌握扩展插件开发。但实际动手后发现,Chrome 插件的开发门槛并不低,权限机制、构建流程、清单配置……这些问题让我一度搁置。直到后来,我接触到了油猴(Tampermonkey) 脚本开发。它让我重新燃起了对网页增强脚本的兴趣:同样可以实现丰富的浏览器功能,但上手却要简单许多。借助 HTML、CSS 和 JavaScript,便能快速开发出媲美插件的实用功能,而且部署和调试都非常灵活。正因如此,我花了很多时间研究油猴脚本的各种能力,从简单的界面增强,到跨域数据请求、页面劫持,再到摄像头识别、画中画等进阶玩法,这些都可以仅靠一个脚本实现。也正是在这个过程中,我写下了《油猴脚本实战指南》这本小册,分享我一路走来的经验一些技术分享。这本小册上线已近一个月,期间我也遇到了很多志同道合的开发者,他们和我一样,在原本有限的时间里,通过脚本开发打开了新的成长路径。如果你也是一名前端开发者,渴望提升能力却苦于时间有限,不妨试试油猴脚本开发。它不仅能帮你解决日常工作中的小痛点,还能训练你的综合编程能力。对我来说,这是性价比极高的一种前端进阶方式。希望你也能从中收获惊喜。什么是油猴脚本油猴(Tampermonkey)是一款浏览器插件,允许用户在网页加载时注入自定义的 JavaScript 脚本,来增强、修改或自动化网页行为。通俗地说,借助油猴,你可以将自己的 JavaScript 代码“植入”任意网页,实现自动登录、抢单、签到、数据爬取、广告屏蔽等各种“开挂级”功能,彻底掌控页面行为。分享一些我实现的有趣网页脚本手势识别实现网页控制人脸识别实现”人脸版黄金旷工“小游戏接口拦截工具:修改CSDN博客数据接口返回值Vue路由一键切换:开发效率起飞任意元素双击实现画中画:摸鱼超级助手掘金后台自动签到助手解除文本复制、网页复制、一键下载为MD主题切换助手它能给你带来什么提高互联网体验通过用户脚本,我们可以为网页添加各种实用功能,显著提升浏览效率和使用体验。比如在阅读时,我们可以实现一键翻译、自动展开全文、解除复制限制、去除广告干扰;在观看视频时,可以开启 VIP 视频解析、倍速播放,甚至自动跳过片头片尾;对于学习和工作场景,还能自动刷题、辅助答题、自动播放课程;而在购物或资源获取方面,脚本也能帮你自动抢购、快速下载网页中的音视频内容。所有这些功能,只需我们动动手写几行JavaScript,就能一劳永逸!提升工作效率在日常工作中,我们经常会遇到一些重复、耗时又低效的操作,比如:每天手动输入账号密码登录某个网页、反复填写相似的表单内容、开发中费尽周折从网页中提取数据、频繁刷新 token 以保持页面正常访问,在多路由项目中不断的手动切换页面路径……这些看似“习以为常但不可避免”的操作,实则完全可以通过脚本解决。提高工作竞争力在公司工作中,很多重复、低效的流程其实完全可以通过脚本来优化。如果你能主动用脚本解决这些问题,不仅能提升团队效率,更能显著增强你在公司的核心竞争力。以我自己的经历为例:我们公司的前端项目采用的是 Qiankun 微前端架构。在本地开发子应用时,由于缺乏主应用的数据支持,调试过程经常变得非常麻烦。为了解决这个问题,我编写了一个脚本工具,能够将主应用的数据无缝注入本地环境,同时也支持将本地子应用嵌入到线上主应用中进行联调。这极大地提升了开发调试的效率。后来,这个脚本在公司内部广泛推广,并被评为“最佳提效工具”,也为我在职级晋升中加分不少。所以,如果你能通过脚本解决公司一些业务痛点,很容易提升你的核心竞争力。增加面试亮点果你在简历中写到:“通过脚本解决了开发过程中的 XXX 问题,优化了业务流程中的 XXX 环节,节省了 XX 小时的人力成本”——那你的简历一定会脱颖而出,更容易通过面试。毕竟,几乎所有面试官和管理者都欣赏那种善于发现问题、主动用技术解决问题,并能为团队带来实实在在价值的人。变现利用脚本变现有多种途径,效果因人而异。你可以在 GreasyFork 等平台发布实用脚本,获得用户打赏;也可以承接定制开发项目,实现接单变现;此外,脚本还能帮助你降低其他互联网变现方式的运营成本,从而实现效益的最大化。机-会技术大厂,前端-后端-测试,全国均有机-会,感兴趣可以试试。待遇和稳定性都还不错~学习难度用户脚本本质上是通过 JavaScript 增强网页功能或操作页面 DOM,所以对于前端同学而言,你只需掌握油猴的开发规则和几个关键 API,就能快速上手。借助本小册基础篇的内容,你甚至可以在 2 到 4 小时内完成脚本的入门学习,轻松实现脚本编写与快速发布。但是,如果不懂CSS+HTML+JavaScript ,你需要提前学习这些前置知识。此外,你甚至能零成本将一个油猴脚本打包成一个原生谷歌浏览器插件,YYDS!——转载自:石小石Orz
1个前端同时联调多个后端,牛马的顶级觉悟
场景同样的接口,同样的前缀,只是后端地址不一样,怎么同时代理多个地址呢?也就是一个前端,怎么连接多个后端的地址?一个前端,需要同时和N个后端联调一个需求里有若干个模块,分别给不同的后端开发,前端需要和N个后端联调测试环境在测其他模块,合并发布太麻烦,所以本地开启一个端口给测试,然后你需要去做其他的需求,但是其他的需求需要连接另一个后端接口其他情况,总之:1个前端 VS N个后端上述场景,都是一个前端,联调N个后端的场景,你可能没遇到过,但是确实存在上述的场景。尤其是第三种最为常见,你会一直等测试完了、再去换一个后端代理地址接着开发吗?当然不能,这样做很浪费时间,说明你不是一个合格的牛马,牛马的觉悟不够,牛马是不会让自己闲着的。那么,怎么办呢?和A联调时proxy指向url-A,和B联调时proxy指向url-B……换其他人联调时,你是把本地项目关掉,然后换个proxy代理、再重启一下吗?当然可以,如果你不嫌麻烦的话!那么有没有好办法呢?这个问题问得好,当然有了!解决方案本例以vue2的vue-cli方式【webpack】为例 如果你们公司用的是vue3可以跳过思路既然vue.config.js能代理一个proxy,那么能不能代理多个proxy呢?当然能!本文先讲解传统模式的代理,以后写一个函数式代理 vite有更好的代理方式,暂且不表实现步骤创建项目创建一个空的vue2项目
vue create project-name
默认情况下,npm run serve会启动8080端口目标我希望不同的端口,指向不同的后端代理proxy地址,如:8100端口,代理后端7001端口8105端口,代理后端7002端口没毛病吧?本文以代理2个后端为例,其余的大家自行补充前端配置安装cross-env
yarn add cross-env
cross-env是nodejs设置环境变量的工具,它解决了不同操作系统之间环境变量设置语法不一致的问题,具体可自行搜索配置vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
// webpack-dev-server 相关配置
devServer: {
host: '0.0.0.0',
port: process.env.PORT || 8100,
open: false,
proxy: {
'/api': {
target: getProxyTarget(process.env.PORT),
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
},
},
})
function getProxyTarget(port) {
switch (port) {
case '8100':
return 'http://127.0.0.1:7001'
case '8105':
return 'http://127.0.0.1:7002'
default:
return 'http://127.0.0.1:7001' // 默认代理地址
}
}
上述代码,默认设置启动端口为8100,并且getProxyTarget函数可以根据不同的端口,指向不同的代理地址。配置package.json
{
"name": "more-proxy",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"serve:8005": "cross-env PORT=8105 vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"cross-env": "^7.0.3",
"vue": "^2.6.14"
},
"devDependencies": {
"@vue/cli-service": "~5.0.0",
"vue-template-compiler": "^2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
上述代码,重点是serve:8005这行,就是你要代理哪个端口,这里需要你在vue.config.js写对应的映照proxy机-会技术大厂,前端-后端-测试,全国均有机会,感兴趣可以试试。待遇和稳定性都还不错~#技术干货#测试上述配置已经实现了我们的需求,那么,具体测试一下吧。本地启动两个nodejs服务,分别为7001和7002端口,内容如下
/// 7001端口
const http = require('http');
const hostname = '127.0.0.1';
const port = 7001;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('my port is 7001!');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
javascript
体验AI代码助手
代码解读
复制代码
/// 7002端口
const http = require('http');
const hostname = '127.0.0.1';
const port = 7002;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('my port is 7002!');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
然后前端把上面的2个端口启动,写一个测试函数
mounted() {
fetch('/api').then(() => {})
}
效果如下: 8100已经成功代理7001了 同样的,8105也代理了7002 其余的vue3,react,也可以用类似的思路如果感兴趣,可以点一下关注,后续会出函数式1前端 VS N后端,更加优雅如果有其他更好的方案,可以评论留言。——转载自:前端没钱
别搞混了!MCP 和 Agent Skill 到底有什么区别?
MCP 与 Skill 深度对比:AI Agent 的两种扩展哲学用 AI Agent 工具(Claude Code、Cursor、Windsurf 等)的时候,经常会遇到两个概念:MCP(Model Context Protocol)Skill(Agent Skill)它们看起来都是"扩展 AI 能力"的方式,但具体有什么区别?为什么需要两套机制?什么时候该用哪个?这篇文章会从设计哲学、技术架构、使用场景三个维度,把这两个概念彻底讲清楚。一句话区分先给个简单的定位:MCP 解决"连接"问题:让 AI 能访问外部世界 Skill 解决"方法论"问题:教 AI 怎么做某类任务用 Anthropic 官方的说法:"MCP connects Claude to external services and data sources. Skills provide procedural knowledge—instructions for how to complete specific tasks or workflows."打个比方:MCP 是 AI 的"手"(能触碰外部世界),Skill 是 AI 的"技能书"(知道怎么做某件事)。你需要两者配合:MCP 让 AI 能连接数据库,Skill 教 AI 怎么分析查询结果。MCP:AI 应用的 USB-C 接口MCP 是什么MCP(Model Context Protocol)是 Anthropic 在 2024 年 11 月发布的开源协议,用于标准化 AI 应用与外部系统的交互方式。官方的比喻是"AI 应用的 USB-C 接口"——就像 USB-C 提供了一种通用的方式连接各种设备,MCP 提供了一种通用的方式连接各种工具和数据源。关键点:MCP 不是 Claude 专属的。它是一个开放协议,理论上任何 AI 应用都可以实现。截至 2025 年初,已经被多个平台采用:Anthropic: Claude Desktop、Claude CodeOpenAI: ChatGPT、Agents SDK、Responses APIGoogle: Gemini SDKMicrosoft: Azure AI Services开发工具: Zed、Replit、Codeium、Sourcegraph到 2025 年 2 月,已经有超过 1000 个开源 MCP 连接器。MCP 的架构MCP 基于 JSON-RPC 2.0 协议,采用客户端-主机-服务器(Client-Host-Server)架构:
┌─────────────────────────────────────────────────────────┐
│ Host │
│ (Claude Desktop / Cursor) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Client │ │ Client │ │ Client │ │
│ │ (GitHub) │ │ (Postgres) │ │ (Sentry) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼────────────────┼────────────────┼─────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│MCP Server │ │MCP Server │ │MCP Server │
│ (GitHub) │ │(Postgres) │ │ (Sentry) │
└───────────┘ └───────────┘ └───────────┘
Host:用户直接交互的应用(Claude Desktop、Cursor、Windsurf)Client:Host 应用中管理与特定 Server 通信的组件Server:连接外部系统的桥梁(数据库、API、本地文件等)MCP 的三个核心原语MCP 定义了三种 Server 可以暴露的原语:1. Tools(工具)—— 模型控制可执行的函数,AI 可以调用来执行操作。
{
"name": "query_database",
"description": "Execute SQL query on the database",
"parameters": {
"type": "object",
"properties": {
"sql": { "type": "string" }
}
}
}
AI 决定什么时候调用这些工具。比如用户问"这个月的收入是多少",AI 判断需要查数据库,就会调用 query_database 工具。2. Resources(资源)—— 应用控制数据源,为 AI 提供上下文信息。
{
"uri": "file:///Users/project/README.md",
"name": "Project README",
"mimeType": "text/markdown"
}
资源由应用控制何时加载。用户可以通过 @ 引用资源,类似于引用文件。3. Prompts(提示)—— 用户控制预定义的提示模板,帮助结构化与 AI 的交互。
{
"name": "code_review",
"description": "Review code for bugs and security issues",
"arguments": [
{ "name": "code", "required": true }
]
}
用户显式触发这些提示,类似于 Slash Command。机-会技术大厂,前端-后端-测试,全国均有机-会,感兴趣可以试试。待遇和稳定性都还不错~MCP 与 Function Calling 的关系很多人会问:MCP 和 OpenAI 的 Function Calling、Anthropic 的 Tool Use 有什么区别?Function Calling 是 LLM 的能力——把自然语言转换成结构化的函数调用请求。LLM 本身不执行函数,只是告诉你"应该调用什么函数,参数是什么"。MCP 是在 Function Calling 之上的协议层——它标准化了"函数在哪里、怎么调用、怎么发现"。两者的关系:
用户输入 → LLM (Function Calling) → "需要调用 query_database"
↓
MCP Protocol
↓
MCP Server 执行
↓
返回结果给 LLM
Function Calling 解决"决定做什么",MCP 解决"怎么做到"。MCP 的传输方式MCP 支持两种主要的传输方式:传输方式适用场景说明Stdio本地进程Server 在本地机器运行,适合需要系统级访问的工具HTTP/SSE远程服务Server 在远程运行,适合云服务(GitHub、Sentry、Notion)大部分云服务用 HTTP,本地脚本和自定义工具用 Stdio。MCP 的代价MCP 不是免费的午餐,它有明显的成本:1. Token 消耗大每个 MCP Server 都会占用上下文空间。每次对话开始,MCP Client 需要告诉 LLM "你有这些工具可用",这些工具定义会消耗大量 Token。连接多个 MCP Server 后,光是工具定义可能就占用了上下文窗口的很大一部分。社区观察到:"We're seeing a lot of MCP developers even at enterprise build MCP servers that expose way too much, consuming the entire context window and leading to hallucination."2. 需要维护连接MCP Server 是持久连接的外部进程。Server 挂了、网络断了、认证过期了,都会影响 AI 的能力。3. 安全风险Anthropic 官方警告:"Use third party MCP servers at your own risk - Anthropic has not verified the correctness or security of all these servers."特别是能获取外部内容的 MCP Server(比如网页抓取),可能带来 prompt injection 风险。MCP 的价值尽管有这些代价,MCP 的价值在于标准化和可复用性:一次实现,到处使用:同一个 GitHub MCP Server 可以在 Claude Desktop、Cursor、Windsurf 中使用动态发现:AI 可以在运行时发现有哪些工具可用,而不是写死在代码里供应商无关:不依赖特定的 LLM 提供商Skill:上下文工程的渐进式公开Skill 是什么Skill(全称 Agent Skill)是 Anthropic 在 2025 年 10 月发布的特性。官方定义:"Skills are organized folders of instructions, scripts, and resources that agents can discover and load dynamically to perform better at specific tasks."翻译一下:Skill 是一个文件夹,里面放着指令、脚本和资源,AI 会根据需要自动发现和加载。Skill 在架构层级上和 MCP 不同。用 Anthropic 的话说:"Skills are at the prompt/knowledge layer, whereas MCP is at the integration layer."Skill 是"提示/知识层",MCP 是"集成层"。两者解决不同层面的问题。Skill 的核心设计:渐进式信息公开Skill 最精妙的设计是渐进式信息公开(Progressive Disclosure)。这是 Anthropic 在上下文工程(Context Engineering)领域的重要实践。官方的比喻:"Like a well-organized manual that starts with a table of contents, then specific chapters, and finally a detailed appendix."就像一本组织良好的手册:先看目录,再翻到相关章节,最后查阅附录。Skill 分三层加载:Claude 判断相关需要更多信息第 3+ 层:支持文件(深度按需)reference.mdscripts/helper.pytemplates/...第 2 层:核心指令(按需加载)SKILL.md 完整内容通常 [removed]
Review code for bugs, security issues, and style violations.
Use when asked to review code, check for bugs, or audit PRs.
---
#CodeReviewSkill## Instructions
When reviewing code, follow these steps:
1. First check for security vulnerabilities...
2. Then check for performance issues...
3. Finally check for code style...
关键字段:name:Skill 的唯一标识,小写字母 + 数字 + 连字符,最多 64 字符description:描述做什么、什么时候用,最多 1024 字符description 的质量直接决定 Skill 能不能被正确触发。Skill 的安全考虑Skill 有一个潜在的安全问题:Prompt Injection。研究人员发现:"Although Agent Skills can be a very useful tool, they are fundamentally insecure since they enable trivially simple prompt injections. Researchers demonstrated how to hide malicious instructions in long Agent Skill files and referenced scripts to exfiltrate sensitive data."因为 Skill 本质上是注入指令,恶意的 Skill 可以在长文件中隐藏恶意指令,窃取敏感数据。应对措施:只使用可信来源的 Skill审查 Skill 中的脚本使用 allowed-tools 限制 Skill 的能力范围
---
name: safe-file-reader
description: Read and analyze files without making changes
allowed-tools: Read, Grep, Glob # 只允许读操作
---
Skill 的平台支持Agent Skills 目前支持:Claude.ai(Pro、Max、Team、Enterprise)Claude CodeClaude Agent SDKClaude Developer Platform需要注意的是,Skill 目前是 Anthropic 生态专属的,不像 MCP 是跨平台的开放协议。MCP vs Skill:架构层级对比现在我们可以从架构层级来理解两者的区别:
┌─────────────────────────────────────────────────────────┐
│ 用户请求 │
└────────────────────────┬────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ 提示/知识层 (Skill) │
│ │
│ Skill 注入专业知识和工作流程 │
│ "怎么做某类任务" │
└────────────────────────┬────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ LLM 推理层 │
│ │
│ Claude / GPT / Gemini 等 │
│ 理解请求,决定需要什么工具 │
└────────────────────────┬────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ 集成层 (MCP) │
│ │
│ MCP 连接外部系统 │
│ "能访问什么工具和数据" │
└────────────────────────┬────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ 外部世界 │
│ │
│ 数据库、API、文件系统、第三方服务 │
└─────────────────────────────────────────────────────────┘
Skill 在上层(知识层),MCP 在下层(集成层)。两者不是替代关系,而是互补关系。你可以:用 MCP 连接 GitHub用 Skill 教 AI 如何按照团队规范做 Code Review详细对比表维度MCPSkill核心作用连接外部系统编码专业知识和方法论架构层级集成层提示/知识层协议基础JSON-RPC 2.0文件系统 + Markdown跨平台是(开放协议,多平台支持)否(目前 Anthropic 生态专属)触发方式持久连接,随时可用基于描述的语义匹配,自动触发Token 消耗高(工具定义持久占用上下文)低(渐进式加载)外部访问可以直接访问外部系统不能直接访问,需要配合 MCP 或内置工具复杂度高(需要理解协议、运行 Server)低(写 Markdown 就行)可复用性高(标准化协议,跨应用复用)中(文件夹,可以 Git 共享)动态发现是(运行时发现可用工具)是(运行时发现可用 Skill)安全考虑外部内容带来 prompt injection 风险Skill 文件本身可能包含恶意指令什么时候用 MCP,什么时候用 Skill用 MCP 的场景需要访问外部数据:数据库查询、API 调用、文件系统访问需要操作外部系统:创建 GitHub Issue、发送 Slack 消息、执行 SQL需要实时信息:监控系统状态、查看日志、搜索引擎结果需要跨平台复用:同一个工具在 Claude Desktop、Cursor、其他支持 MCP 的应用中使用用 Skill 的场景重复性的工作流程:代码审查、文档生成、数据分析公司内部规范:代码风格、提交规范、文档格式需要多步骤的复杂任务:需要详细指导的专业任务团队共享的最佳实践:标准化的操作流程Token 敏感场景:需要大量知识但不想一直占用上下文结合使用很多时候,两者是配合使用的:
用户:"Review PR #456 并按照团队规范给出建议"
1. MCP (GitHub) 获取 PR 信息
↓
2. Skill (团队代码审查规范) 提供审查方法论
↓
3. Claude 按照 Skill 的指令分析代码
↓
4. MCP (GitHub) 提交评论
MCP 负责"能访问什么",Skill 负责"怎么做"。写好 Skill 的关键Skill 能不能被正确触发,90% 取决于 description 写得好不好。差的 description
description: Helps with data
太宽泛,Claude 不知道什么时候该用。好的 description
description: >
Analyze Excel spreadsheets, generate pivot tables, and create charts.
Use when working with Excel files (.xlsx), spreadsheets, or tabular data analysis.
Triggers on: "analyze spreadsheet", "create pivot table", "Excel chart"
好的 description 应该包含:做什么:具体的能力描述什么时候用:明确的触发场景触发词:用户可能说的关键词最佳实践官方建议:保持专注:一个 Skill 做一件事,避免宽泛的跨域 SkillSKILL.md 控制在 500 行以内:太长的话拆分到支持文件测试触发行为:确认相关请求能触发,不相关请求不会误触发版本控制:记录 Skill 的变更历史关于 Slash Command文章标题是 MCP vs Skill,但很多人也会问到 Slash Command,简单说一下。Slash Command 是最简单的扩展方式——本质上是存储的提示词,用户输入 /命令名 时注入到对话中。Skill vs Slash Command 的关键区别是触发方式:Slash CommandSkill触发方式用户显式输入 /命令Claude 自动匹配用户控制完全控制何时触发无法控制,Claude 决定问自己一个问题:用户是否需要显式控制触发时机?需要 → Slash Command不需要,希望 AI 自动判断 → Skill总结MCP 和 Skill 是 AI Agent 扩展的两种不同哲学:MCPSkill哲学连接主义知识打包问的问题"AI 能访问什么?""AI 知道怎么做什么?"层级集成层知识层Token 策略预加载所有能力按需加载知识记住这句话:MCP connects AI to data; Skills teach AI what to do with that data.MCP 让 AI 能"碰到"数据,Skill 教 AI 怎么"处理"数据。它们不是替代关系,而是互补关系。一个成熟的 AI Agent 系统,两者都需要。参考资源MCP 官方资源Model Context Protocol 官网 - 协议规范、快速入门、Server 开发指南MCP Specification - 完整的协议规范文档Introducing the Model Context Protocol - Anthropic 发布 MCP 的官方博客MCP GitHub Organization - 官方 SDK、示例 Server、参考实现Awesome MCP Servers - 社区维护的 MCP Server 列表Skill 官方资源Claude Code Skills 文档 - Skills 的完整文档Building effective agents - Anthropic 关于 Agent 设计的研究博客Context Engineering Guide - 上下文工程官方指南,理解 Skill 设计哲学的关键跨平台采用OpenAI adds support for MCP - OpenAI 宣布支持 MCPGoogle Gemini MCP Support - Google 宣布 Gemini 支持 MCP延伸阅读Function Calling vs MCP - 理解两者区别Claude Code Documentation - Claude Code 完整文档Prompt Engineering Guide - 提示工程基础,Context Engineering 的前置知识如果你觉得这篇文章有帮助,欢迎关注我的 GitHub,下面是我的一些开源项目:Claude Code Skills(按需加载,意图自动识别,不浪费 token,介绍文章):code-review-skill - 代码审查技能,覆盖 React 19、Vue 3、TypeScript、Rust 等约 9000 行规则(详细介绍)5-whys-skill - 5 Whys 根因分析,说"找根因"自动激活first-principles-skill - 第一性原理思考,适合架构设计和技术选型全栈项目(适合学习现代技术栈):prompt-vault - Prompt 管理器,用的都是最新的技术栈,适合用来学习了解最新的前端全栈开发范式:Next.js 15 + React 19 + tRPC 11 + Supabase 全栈示例,clone 下来配个免费 Supabase 就能跑chat_edit - 双模式 AI 应用(聊天+富文本编辑),Vue 3.5 + TypeScript + Vite 5 + Quill 2.0 + IndexedDB——转载自:也无风雨也雾晴
前端技巧:检测到省略号文本自动显示 Tooltip
前言在前端开发中,我们经常会遇到接口返回的文本内容过长,无法完全显示的问题。为了处理这一问题,通常会设置固定的宽度并使用省略号样式(text-overflow: ellipsis)来隐藏超出的文本。然而,有时产品需求还希望用户能够通过悬停查看完整内容,这时就需要引入 Tooltip 进行展示。(没被省略的时候不要显示Tooltip)
// tailwind的样式单行省略
.line-clamp-1 {
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
}
// 自行设置的css样式
single-line {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
为了解决这个问题,我们实现了一个自定义 Hook,该 Hook 会监测文本元素是否因宽度限制而被省略。一旦检测到文本内容被省略,Hook 会自动为该元素添加 Tooltip,确保用户可以方便地查看完整信息。代码实现use-ellipsis.ts
import { useEffect, useRef, useState } from 'react';
type Options = {
lines?: number; // 支持多行
};
export function useEllipsis : content;
};
机会技术大厂,前端-后端-测试,全国各地等均有机-会,感兴趣可以试试~使用示例与效果
export default function TestPage() {
const mockText = '很长很长很长很长很长';
const mockText2 = '简短的';
return (
);
}
——转载自:代码小学僧
Stack Overflow,轰然倒下了!
提到 Stack Overflow 技术社区,提到那个橙色的栈溢出图标,相信程序员和开发者们应该都再熟悉不过了。最近在网上看到了一个有关 Stack Overflow 社区的变化趋势图,让人感慨万千。这个曲线图表示的是自 2008 年开始,一直到 2025 年的今天,这 17 年时间,Stack Overflow 社区上每个月新问题个数的变化趋势。怎么样?大家看完这张图有没有什么感触?这张图清晰地展示出了 Stack Overflow 编程社区在这 17 年间所经历的增长、繁荣以及回落的趋势。可以看到,从 2008 年到 2014 年这前 6 年的时间,Stack Overflow 一路高歌,渐入佳境,基本都在稳步增长。而从 2014 年到 2022 年这中间的 8 年时间,虽说图中曲线呈震荡变化状态,但总体都是处于高位趋势,这也是 Stack Overflow 社区的繁荣时刻。从数据上来看,Stack Overflow 最高光的顶峰时刻出现在 2020 年,尤其是在 2020-05-01 这个时间节点,新问题个数来到了 302381 的顶峰。而从图中也可以很明显地看出,自 2022 年底开始,Stack Overflow 社区日渐式微,开始出现了回落之势。同样是那一年底,OpenAI 正式发布了 ChatGPT。机-会技术大厂,前端-后端-测试,新一线和一二线城市等地均有机-会,感兴趣可以试试。待遇和稳定性都还不错~那后面几年的故事,相信大家都非常清楚了,AI 大模型相关的技术和产品飞速迭代,传统的搜索引擎和知识社区也受到了不小的冲击。到了如今的 2025 年,Stack Overflow 的数据也已经跌回到了 15 年前的水平了。这几年的趋势相信大家都非常清楚,AI 大模型日新月异,相关的模型产品更是百花争艳,层出不穷,这对于 Stack Overflow 的冲击在图上也体现得淋漓尽致。于是我也开始回想,我自己这几年在互联网上搜索信息的方式,似乎在不知不觉中已经发生了很大的变化。现在遇到问题,我好像已经不怎么喜欢使用传统搜索引擎了,而是会习惯性地转向各种 AI 大模型工具和智能助手,同时信息的处理和交互范式也完全变了。我们还以编程写代码为例。以前当我们在写代码调试运行出现错误但折腾半天也不知所以的时候,大家会怎么做?相信不少同学和我一样,也是首先复制这段报错信息到搜索引擎中进行检索,然后根据搜索引擎吐出来的搜索结果,自己逐个点进去筛选有用的信息。而当我们一旦在搜索结果里看到了 Stack Overflow 的相关网页时,我们的直觉会告诉我,离问题解决应该不远了。而即便当我们在搜索引擎里找不到任何解决问题的方法,那我们也可以去类似 Stack Overflow 这样的编程社区里进行发帖求助,然后等待被查看和回答。这是在 AI 大模型还没有爆发之前,大家所普遍采用的一个解决问题的办法。但如今一切都变了。随着 AI 模型和产品的快速渗透,大家解决问题的方式发生了变化。我们直接甩给 AI 工具一个问题或者一段信息,AI 工具便会自动理解你的意图,并开始深度思考、收集信息、整理逻辑、分析总结、加工输出,最后直接把生成的答案或解决问题的办法呈现在你的眼前。和这些传统知识社区和搜索引擎相比,AI 大模型很强,这无需质疑。AI 大模型强就强在它的理解能力、整合能力以及推理能力,这些都是传统知识社区和搜索引擎往往所欠缺的东西。传统搜索引擎往往依赖于关键词匹配和链接分析,因此对于用户问题的理解往往有所欠缺,而 AI 大模型则能够深度理解语言含义和上下文,理解问题的真正意图。同时 AI 大模型能阅读、理解并整合数据中不同维度的海量知识,并能在此基础上来进行进一步的推理、分析、总结、泛化,这在如今的信息爆炸的时代来说是一种巨大的价值。所以相比去 Stack Overflow 上发帖子、搜问题、筛答案,AI 引擎无论在时间上还是知识维度的扩展上都给了这些传统知识社区和搜索引擎以降维打击。如此一看,如果再不转型,像 Stack Overflow 这样的传统编程社区的轰然倒下,似乎也成了一个必然的趋势。说到底,AI 大模型不是搜索引擎的简单升级版,而是一种全新的信息处理和交互范式。那面对这波 AI 大模型浪潮的席卷和冲击,不少传统的搜索引擎和知识社区都开始了转型升级,并积极拥抱 AI。比如像 Stack Overflow 自己也搞了一个 Overflow AI,其中包含了一套基于他们自己的历史内容和知识库所打造的 GenAI 工具。从「检索工具」进化到「智能助手」,这是不少现有知识社区和搜索引擎正在经历的蜕变之路。这两年 AI 大模型领域的发展速度相信大家都有目共睹了,技术迭代进化更是远超预期。可以预见的是,未来的信息检索和交互方式一定还会进一步高效、精准和智能,而对此我们也可以拭目以待。好了,那以上就是今天的内容分享了,感谢大家的阅读,我们下篇见。——转载自:CodeSheep
AI 只会淘汰不用 AI 的程序员🥚
作为程序员,你竟然还在手撸代码 ??? 如果没有公司给你提供科学上网,提供AI 编程工具的账号,你真能玩转AI ??? 除了平时搜搜查查,AI 对你还有其他用处 ???震惊!某博主竟然开头就贩卖焦虑?难道程序员真的要被 AI 取代了? 别急,这篇文章就一步步带你玩转 AI 编程! 如果只是想了解如何使用 AI 编程,可以直接跳到章节: 「所以,我们需要什么!?」理解概念要深入使用 AI,我们要先理解一些概念1. AI 基础AI大模型:拥有超大规模参数、超级聪明的机器学习模型,所有的 AI 应用都是调用大模型的计算处理能力。如:问答、图片生成、视频生成。[机-会]技术大厂,前端-后端-测试,新一线和一二线城市等地均有机-会,感兴趣可以试试。待遇和稳定性都不错~————国内主流大模型对比:国外主流大模型对比模型厂商核心能力主攻场景Gemini🐂🍺Google DeepMind多模态能力强大,可无缝处理文本、图像、音频、视频、代码等创意内容创作、文档处理、用于处理复杂、多源信息的场景Claude🐂🍺Anthropic推理能力优秀,多模态能力一般长文档分析场景,如法律文件审查;适用于需要可靠输出的领域,如医疗诊断辅助Veo 3Google自动生成视频和音频,口型同步精准到毫秒级。支持最高 4K 分辨率输出,画质清晰,色彩还原。生成速度快专注于视频生成领域,如短视频内容创作,为用户提供高效、高质量的视频生成解决方案。SonnetAnthropicSonnet 是 Claude 3 系列中的平衡型模型,性价比高适用于注重性价比和处理速度的场景,如一般性的文档分析GPTOpenAI通用性极强,各个方面都有出色表现,GPT-4o 等版本增强了多模态交互能力。自然语言处理相关的场景,如内容创作、智能客服CopilotGitHub 与 OpenAI 合作开发基于 GPT 系列模型训练,理解自然语言,生成对应代码用于日常编码、代码调试、新手编程学习,降低重复编码工作量一般我们编程使用的都是国外的大模型,毕竟开发工具、系统、编程语言都是外国的。编码方面的能力,国外模型还是碾压的存在。MCP:一套提供给 AI 大模型调用的标准协议。一些厂商会把自己的能力包装成MCP,让大模型在理解完用户的复杂任务时,可以调用厂商的能力。比如:你让豆包给你用 “高德” 生成一份超准的导航,豆包就会去调用高德的 MCP,为你出导航~IDE:程序员专属概念。AI IDE是集成了 AI 能力的软件开发平台,开发者可以通过自然语言,让 IDE 调用 AI 模型和 MCP 给你写代码,速度和质量牛的飞起,真有手就能写代码!主流的 AI IDE 对比名称厂商搭载的模型收费维度Cursor🐂Cursor 公司GPT-4、Claude 3.5、Cursor-small、o3-mini 等很贵,按 token 收费Antigravity🐂🍺Google支持在 Gemini 3 Pro、Claude Sonnet 4.5 和 GPT-OSS 等多种模型之间无缝切换有羊毛薅,国外邮箱+学生认证~Trae字节跳动国内版搭载豆包 1.5-pro、DeepSeek R1/V3 等模型,海外版内置 GPT-4o、Claude-3.5-Sonnet 模型国内的,充个会员的事,不贵2. RAG ➡️ Agent ➡️ Planning:AI 应用方式的演进之路AI 的应用方式正从 “被动响应” 向 “主动规划” 快速迭代。 从 RAG 进行检索增强生成,到 AI Agent 实现自主调用工具完成任务,再到 Planning 能 “拆解复杂任务与全局决策” 的高阶形态。让 AI 从 “内容生成器” 蜕变到 “智能协作体”。RAG —— 检索增强生成 核心逻辑是:先检索,再生成。用户提出问题,先从外部数据库、文档库中检索与问题最相关的信息,再将这些信息作为 “参考资料” 喂给大模型,让模型基于真实数据生成回答。AI Agent —— “自主工具操作员” AI Agent(智能体) ,让 AI 像人一样调用工具、执行步骤、验证结果。能理解用户的模糊需求,自主规划任务步骤,选择并调用合适的工具(如计算器、浏览器、代码解释器、RAG 系统),完成任务。Planning(规划)—— “全局任务指挥官” AI 系统的高阶能力,核心逻辑是 “先拆解,再执行,再调整”。基于全局目标,将复杂、长期、多约束的任务拆解为有序的子任务序列,并根据执行过程中的反馈动态调整策略。 不仅关注单个任务的完成,更关注子任务之间的关联和整体目标的达成。演进逻辑与核心差异总结维度RAG(检索增强生成)AI Agent(智能体)Planning(规划)核心定位大模型的 “知识库外挂”自主工作的 “工具操作员”全局任务的 “指挥官”能力核心检索 + 生成,保证回答准确决策 + 工具调用,完成单任务闭环拆解 + 协同 + 动态调整,掌控多任务全局典型比喻学生的 “参考书”能独立完成工作的 “专员”统筹全局的 “项目经理”所以,我们需要什么!?你作为一名优秀的程序员,你需要通过科学上网、精准付费,在 AI IDE中,基于AI大模型的能力,熟练使用 Agent/Planning,配合 MCP 等工具,让 AI 帮你写出又快又好的代码,更好的服务你的业务!1. 使用 Cursor、Antigravity、Trae 开发工具下载地址:Cursor、Antigravity、TREA 账号注册:Cursor 和 Trae 登录方式都超简单,会员的话直接去官网购买即可 至于 Antigravity,因为 Google 是禁止国内用户访问的,因此一定要能正常上网,邮箱账号必须纯正🇺🇸,但是我们有 闲鱼 之光,是可以尝试下的~ 2. 装好主流 MCP对于前端程序员,UI 这类低级工作,完全可以交给 Agent 去编写。比如:公司的设计师用的是figma,我们只需要在 cursor 中装上figma mcp,然后 figma 账号申请开发者权限,就能自由的让 AI 帮我们写好代码。亲测还原度 85%+ 3. 沉淀 Rules 和 Workflows我们现在已经可以通过开发工具让 AI 干活了,但如何更符合我们的编码习惯和设计思想?那就得给 AI 规范,也就是通过提示词让 AI 更乖的,干更对的活。 比如,Antigravity就有明确的让我们添加规则和工作流的入口,并且会引导我们如何写提示词,然后在提问的时候,引用对应的文件即可。 Rules(规则)和Workflows(工作流),沉淀 沉淀 再 沉淀!!! 4. !!!文档先行!!!AI 时代的编码,一定要做好设计,写好文档。 AI 虽然帮你干活,但是任务是你来安排的,你给出的任务要足够精准。 同时,你的编码思维才是代码能写好的核心,你必须把你的思维和想法,落成文档给到 AI 大模型。markdown 格式:注意 AI 需要理解 md 文档图文并茂:在编写文档的时候,时常需要画图,此时可以使用 md 语法来画图。这里我推荐mermaidchart,可以基于 md 语法进行可视化编辑。 写在最后当你有正常可以使用模型的账号后,其实这个账号不仅仅是在 IDE 可以使用,比如 Antigravity 的账号,跟 Gemini 是一致的,你也可以在大模型的官网登录进行图片、视频生成。在了解了大模型、MCP、工作流、AI 编程工具后,相信你对 AI 的应用又有了新的理解。我们一定要积极去尝试,国内国外的 AI 工具能用的多用,尽情的去拥抱 AI!AI 是生产力,毋庸置疑!——转载自:Karl_wei
从 “翻页书” 到 “魔术盒”:React 路由凭啥如此丝滑?
前言想象一下:你打开一个网站,从 “首页” 点到 “个人中心”,页面连个白屏都没有 —— 这不是魔法,是 单页应用(SPA) 的 “小心机”。而让 SPA 实现 “网址变、内容换” 的幕后大佬,就是今天要唠的 React Router。我今天以一个后台管理系统来全方位的拆解路由的细节~想要详细React Router资料可以在这里找到:reactrouter.com一、从 “多页翻书” 到 “单页变魔术”早年间的网站是 “多页应用”:点个链接跳转到新 HTML 文件,像翻书似的 “唰唰” 换页。但缺点很明显:加载慢、体验卡,就像翻一本 500 页的字典找个词,翻半天手都酸了。现在的 SPA 是 “单页魔术盒”:只有一个 HTML 文件,网址变了,只是把对应的 “组件” 塞进这个盒子里 —— 就像变魔术时从盒子里掏出不同道具,盒子本身根本不动。比如我写的这个后台管理系统:访问 http://localhost:5173/login → 塞进「登录组件」访问 http://localhost:5173/home → 塞进「首页组件」网址变,内容秒切,丝滑到像德芙广告~二、React Router:SPA 的 “导航指挥家”要实现这种 “秒切”,得请出react-router-dom这个 “指挥家”。它的核心成员有这些(结合我们的后台系统代码来看更爽):首先你需要安装好react-router:就在我开头给的网址里面就可以找到哈!1. BrowserRouter:给应用 “装个导航系统”它是路由的 “容器”,相当于给整个应用装了个 “导航大脑”(用的是 HTML5 的 History API,所以网址长得像正常网址)。看App.jsx的开头:
import { BrowserRouter, Routes, Route } from 'react-router-dom'
export default function App() {
return (
)
}
2. Routes + Route:给 “组件” 贴 “网址标签”Routes是 “路由出口”,Route是 “网址→组件” 的标签贴。比如我们的后台系统,给 「登录页」「首页」 贴标签:
{/* 404页面:匹配不到的网址都显示这个 */}
<Route path="*" element={
后台管理系统
)
}
点击算法进入/home/leetcode:Outlet就像电视屏幕,点 “课程” 就播 《课程频道》,点 “算法” 就切 《LeetCode 频道》。4. Link:SPA 的 “无痛跳转链接”传统的标签跳转是 “翻页”,Link是 “换内容”—— 点它网址变,但页面不刷新,就像遥控器换台。比如首页侧边栏的导航:
5. useNavigate:“编程式跳转” 的魔法棒有时候需要 “代码触发跳转”(比如登录成功后自动跳首页),这时候useNavigate就派上用场了。看我们的Login.jsx:
import { useNavigate } from 'react-router-dom'
export default function Login() {
const navigate = useNavigate() // 拿到跳转函数
const login = () => {
// 登录逻辑...
navigate('/home') // 登录成功,跳转到首页!
}
return (
)
}
点登录前:点登录后:点 “登录” 按钮,navigate('/home')一执行,网址直接切到首页,比外卖小哥送餐还快~三、总结:React Router 就是 SPA 的 “导航全家桶”把这些成员凑一起,我们的后台系统就活了:打开网站,/自动跳/login → 显示登录界面(带输入框和绿色登录按钮);点 “登录”,useNavigate跳/home → 显示首页(带侧边栏);点侧边栏 “课程”,Link跳/home/class → Outlet显示课程页面;输错网址,直接显示NOT FOUND → 404 页面。是不是感觉 React Router 像个 “全能导航员”?既管网址匹配,又管页面切换,还能代码跳转 —— 有了它,SPA 才能像 “魔术盒” 一样,变内容比变魔术还快!结语说到底,React Router 就是单页应用的 “流量控制器”,它用极简的配置和灵活的 API,让我们的后台管理系统实现了 “网址变、组件换” 的丝滑体验。从登录页到首页,从一级路由到二级路由,没有烦人的页面刷新,只有行云流水的内容切换。路由其实不难,需要多理解,掌握这些核心用法,你也能轻松搭建出结构清晰、体验流畅的 SPA 应用。下次再遇到路由相关的需求,不妨拿出这些 “导航法宝”,让你的项目像后台管理系统一样,在路由的世界里畅通无阻。现在就用我的例子敲代码吧!——转载自:风止何安啊
NOT FOUND
} /> 登录首页:点击登录(自动进入/home/class):像不像给每个组件发了张 “网址门票”?拿着/login门票,就能进登录页的门?机-会技术大厂,前端-后端-测试,新一线和一二线城市等地均有机-会,感兴趣可以试试。待遇和稳定性都不错~3. Outlet:二级路由的 “展示窗口”首页Home是个 “大容器”,里面要放class和leetcode这些 “子页面”——Outlet就是这个 “子页面展示窗口”。看Home.jsx的代码: import { Outlet, Link } from 'react-router-dom' export default function Home() { return (
单点登录:一次登录,全网通行
大家好,我是小悟。想象一下你去游乐园,买了一张通票(登录),然后就可以玩所有项目(访问各个系统),不用每个项目都重新买票(重新登录)。这就是单点登录(SSO)的精髓!SSO的日常比喻普通登录:像去不同商场,每个都要查会员卡单点登录:像微信扫码登录,一扫全搞定令牌:像游乐园手环,戴着就能证明你买过票下面用代码来实现这个"游乐园通票系统":代码实现:简易SSO系统
import java.util.*;
// 用户类 - 就是我们这些想玩项目的游客
class User {
private String username;
private String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
// getters 省略...
}
// 令牌类 - 游乐园手环
class Token {
private String tokenId;
private String username;
private Date expireTime;
public Token(String username) {
this.tokenId = UUID.randomUUID().toString();
this.username = username;
// 令牌1小时后过期 - 游乐园晚上要关门的!
this.expireTime = new Date(System.currentTimeMillis() + 3600 * 1000);
}
public boolean isValid() {
return new Date().before(expireTime);
}
// getters 省略...
}
// SSO认证中心 - 游乐园售票处
class SSOAuthCenter {
private Map[removed] validTokens = new HashMap[removed]();
private Map[removed] users = new HashMap[removed]();
public SSOAuthCenter() {
// 预先注册几个用户 - 办了年卡的游客
users.put("zhangsan", new User("zhangsan", "123456"));
users.put("lisi", new User("lisi", "abcdef"));
}
// 登录 - 买票入场
public String login(String username, String password) {
User user = users.get(username);
if (user != null && user.getPassword().equals(password)) {
Token token = new Token(username);
validTokens.put(token.getTokenId(), token);
System.out.println(username + " 登录成功!拿到游乐园手环:" + token.getTokenId());
return token.getTokenId();
}
System.out.println("用户名或密码错误!请重新买票!");
return null;
}
// 验证令牌 - 检查手环是否有效
public boolean validateToken(String tokenId) {
Token token = validTokens.get(tokenId);
if (token != null && token.isValid()) {
System.out.println("手环有效,欢迎继续玩耍!");
return true;
}
System.out.println("手环无效或已过期,请重新登录!");
validTokens.remove(tokenId); // 清理过期令牌
return false;
}
// 登出 - 离开游乐园
public void logout(String tokenId) {
validTokens.remove(tokenId);
System.out.println("已登出,欢迎下次再来玩!");
}
}
// 业务系统A - 过山车
class SystemA {
private SSOAuthCenter authCenter;
public SystemA(SSOAuthCenter authCenter) {
this.authCenter = authCenter;
}
public void accessSystem(String tokenId) {
System.out.println("=== 欢迎来到过山车 ===");
if (authCenter.validateToken(tokenId)) {
System.out.println("过山车启动!尖叫声在哪里!");
} else {
System.out.println("请先登录再玩过山车!");
}
}
}
// 业务系统B - 旋转木马
class SystemB {
private SSOAuthCenter authCenter;
public SystemB(SSOAuthCenter authCenter) {
this.authCenter = authCenter;
}
public void accessSystem(String tokenId) {
System.out.println("=== 欢迎来到旋转木马 ===");
if (authCenter.validateToken(tokenId)) {
System.out.println("木马转起来啦!找回童年记忆!");
} else {
System.out.println("请先登录再玩旋转木马!");
}
}
}
// 测试我们的SSO系统
public class SSODemo {
public static void main(String[] args) {
// 创建认证中心 - 游乐园大门
SSOAuthCenter authCenter = new SSOAuthCenter();
// 张三登录
String token = authCenter.login("zhangsan", "123456");
if (token != null) {
// 拿着同一个令牌玩不同项目
SystemA systemA = new SystemA(authCenter);
SystemB systemB = new SystemB(authCenter);
systemA.accessSystem(token); // 玩过山车
systemB.accessSystem(token); // 玩旋转木马
// 登出
authCenter.logout(token);
// 再尝试访问 - 应该被拒绝
systemA.accessSystem(token);
}
// 测试错误密码
authCenter.login("lisi", "wrongpassword");
}
}
运行结果示例:
zhangsan 登录成功!拿到游乐园手环:a1b2c3d4-e5f6-7890-abcd-ef1234567890
=== 欢迎来到过山车 ===
手环有效,欢迎继续玩耍!
过山车启动!尖叫声在哪里!
=== 欢迎来到旋转木马 ===
手环有效,欢迎继续玩耍!
木马转起来啦!找回童年记忆!
已登出,欢迎下次再来玩!
=== 欢迎来到过山车 ===
手环无效或已过期,请重新登录!
请先登录再玩过山车!
用户名或密码错误!请重新买票!
总结一下:单点登录就像:一次认证,处处通行 🎫不用重复输入密码 🔑安全又方便 👍好的SSO系统就像好的游乐园管理,既要让游客玩得开心,又要确保安全!机会技术大厂,前端-后端-测试,新一线和一二线城市等地均有机-会,感兴趣可以试试。待遇和稳定性都不错~您的一键三连,是我更新的最大动力,谢谢山水有相逢,来日皆可期,谢谢阅读,我们再会——转载自:悟空码字
为什么有些人边框不用border属性
1) border 会改变布局(占据空间)border 会参与盒模型,增加元素尺寸。例如,一个宽度 200px 的元素加上 border: 1px solid #000,实际宽度会变成:
200 + 1px(left) + 1px(right) = 202px
如果不想影响布局,就很麻烦。使用 box-shadow: 0 0 0 1px #000不会改变大小,看起来像 border,但不占空间。2) border 在高 DPI 设备上容易出现“模糊/不齐”特别是 0.5px border(发丝线),在某些浏览器上有锯齿、断线。transform: scale(0.5) 或伪元素能做更稳定的发丝线。3) border 圆角 + 发丝线 常出现不规则效果border + border-radius 在不同浏览器的渲染不一致,容易出现不均匀、颜色不一致的问题。用 outline / box-shadow 圆角更稳定。4) border 不适合做阴影/多层边框如果你需要两层边框:
双层边框用 border 很难做
而用:
box-shadow: 0 0 0 1px #333,0002px#999;
非常简单。5) border 和背景裁剪一起用时容易出 bug比如 background-clip、overflow: hidden 配合 border 会出现背景被挤压、不应该被裁剪却裁剪等问题。机会技术大厂,前端-后端-测试,新一线和一二线城市等地均有机-会,感兴趣可以试试。待遇和稳定性都不错~6) hover/active 等状态切换时会“跳动”因为 border 会改变元素大小。例子:
.btn { border: 0; }
.btn:hover { border: 1px solid #000; }
鼠标移上去会抖动,因为尺寸变大了。用 box-shadow 的话就不会跳。25/11/25更新,来自评论区大佬补充除了动态外有时候 overflow 也会导致原本刚刚好的布局不会删除滚动条,由于有了 border 1px 导致刚好出现滚动条但其实根本滚不了。总结边框可以分别使用border、outline、box-shadow三种方式去实现,其中outline、box-shadow不会像border一样占据空间。而box-shadow可以用来解决两个元素相邻时边框变宽的问题。不使用border并不是因为它不好,而是因为outline和box-shadow的兼容性和灵活性相对border会更好一点。——转载自:爆浆麻花
这6个网站一旦知道就离不开了
作为一名资深互联网居民和生产力工具爱好者,我的收藏夹里确实有那么几个网站,它们不是那种偶尔用一下的工具,而是已经深度融入我的工作和生活流,堪称数字部件。ServBay这绝对是开发者,尤其是需要和多种语言、多种环境打交道的全栈开发者的福音。它的核心价值在于集成与简化。程序员配置本地环境,往往需要自行处理各种软件的安装、版本冲突和互相之间的配置。比如用 Homebrew 管理服务,容易把系统环境搞乱;用 Docker,又有一定的学习和资源开销。ServBay 把这个过程产品化了。它提供了一个统一的管理界面,就跟点外卖一样,哪里需要点哪里,快速启动和管理多种语言环境(如 Rust, Node.js, Python, Go, PHP等)和数据库(如 MySQL, Redis)。它的几个特点很实用:一键部署,告别繁琐:支持 Python, Go, Java, Rust, Ruby, Node.js 等主流语言,点几下鼠标,开发环境就好了。版本隔离,无痛切换:可以同时运行 Python2.7, 3.8,3.11,或者 Node.js v16, v18, v20。项目之间互不干扰,这对需要维护旧项目或测试新特性的开发者来说很方便。数据库全家桶:常用的 MySQL, PostgreSQL, MariaDB,还有 Redis, MongoDB 这种 NoSQL 数据库,都能多实例同时运行,管理起来非常方便。AI 部署 & 内网穿透:这两点是它的杀手锏。一键部署 Llama, Qwen、DeepSeek 等本地 AI 模型,还能把本地服务通过内网穿透分享给同事或用于微信开发调试,直接打通了从开发到演示的最后一步。ServBay = 极高的效率 + 极大的自由度 + 极低的维护成本,为我节省了大量在环境配置上耗费的时间。坑位技术大厂,前端-后端-测试,新一线和一二线城市等地均有坑位,感兴趣可以试试。待遇和稳定性都不错~GitHub这个就不用多说了,全球最大同性交友网站(不是)。但如果你只把它当成一个存代码的地方,那就太小看它了。它的价值体现在方方面面。职业名片:一个维护良好的 GitHub 账号,是你技术热情和能力的最好证明。知识索引:除了代码,上面还有大量的学习资料、Awesome Lists、教程和笔记。遇到新技术,先去 GitHub 搜索,通常能找到高质量的入门资源。协作标准:它定义的 Issue、Pull Request 等协作流程,已经成为现代软件开发的事实标准。免费的午餐:GitHub Actions 提供了强大的 CI/CD 能力,GitHub Pages 可以免费托管静态网站。它不仅是工具,更是一个生态、一个社区。无论你是编程新手还是资深大佬,都离不开它。Dynamic Wallpaper Club这个网站满足的是桌面个性化和视觉体验的需求。相较于静态壁纸,它提供的动态壁纸能根据一天的时间变化(HEIC 格式),让桌面背景从日出、正午到日落、深夜,呈现不同的光影效果。这种细微的环境变化,能给单调的电脑使用过程增加一些动态感和舒适度。提升幸福感的小确幸网站,用最低的成本,换来开心一整天。123apps这是一个在线音视频及文档处理工具集。工作中经常会遇到一些临时的、轻量的文件处理任务,比如:裁剪一小段音频、合并几个 PDF 文件、转换一个视频的格式。我个人认为为这些一次性的任务去下载安装一个专门的软件,费时费力,还可能附带广告或捆绑安装。123apps 在一个网站内集合了数十种这类小工具,覆盖了视频、音频、PDF、图像转换等常见场景。它的优点是:功能聚合:一个网站解决多种问题,无需到处寻找。即用即走:基于浏览器,无需安装,处理完下载即可。界面简洁:操作直观,没有太多干扰信息。收藏这一个网站,相当于安装了几十个小软件。随用随开,用完即走,体验一把当渣男的快乐(bushi),是解决日常杂事的终极利器。Perplexity AI这是一个带引用来源的答案引擎。不同于ChatGPT,Perplexity AI 提供了另一种更聚焦于搜索和回答的体验。它不是一个纯粹的聊天机器人,而是一个自带引用来源、会深度追问的答案引擎。它最大的特点是信息溯源。对比 搜索引擎:传统搜索返回的是链接列表,需要用户自己去逐个打开、筛选、整合信息。Perplexity 则直接生成一段总结好的回答,并在回答下方清晰地列出信息来源的链接。对比通用模型:通用模型在回答事实性问题时,有时会捏造信息。而 Perplexity 的回答基于其索引的公开信息,并提供了出处,用户可以方便地进行事实核查,这在需要严谨信息源的场景下很有用。我现在查资料、写报告、做技术调研,首选就是 Perplexity。它的 Pro 模式还能上传文件进行分析,或者切换到 Claude-3、GPT-4 等更强大的模型。Excalidraw一个极简的在线白板工具。当需要画流程图、架构草图或进行思维整理时,我们有很多选择,比如 Miro、FigJam 或是专业的绘图软件。但这些工具往往功能复杂,启动也比较慢。Excalidraw 的特点在于它的手绘风格和简洁性。手绘感:它画出的图形带有手绘的质感,看起来不那么正式。我很喜欢这种风格,看起来像草稿,但能让我更专注于思考和创意本身简单高效:打开网站就能开始画,没有账户注册的强制要求。支持实时协作,通过链接分享给他人即可共同编辑。开源与扩展:社区贡献了丰富的图形库,可以方便地拖拽使用,比如各种云服务的图标、UI 组件等。我用它来梳理逻辑、设计系统、给同事讲方案,甚至做会议纪要。它的轻量和高效,是任何重型工具都无法比拟的。以上是我个人常用的一些网站,希望能帮到你。——转载自:该用户已不存在
听说前端又死了?
这几天刷 X、刷 Reddit、刷国内技术社区,只要你稍微点开热榜,就会被同一句话精准爆头:“Gemini 3 真的把前端扬了,这次是骨灰级别的扬。”“一个 prompt 直接出 3D 体素编辑器/视频剪辑软件/电影级登陆页,前端彻底没活了。”“我用 Gemini 3 三分钟写了个比 CapCut 还丝滑的网页版剪辑器,程序员可以回家抱孩子了。”配图永远是那种高潮到发光的 4K 60fps 演示视频:一个 prompt → 进度条走完 → 完美交互应用跃然屏幕,点赞几万,转发狂欢,评论区清一色“前端已死”“我失业了”“时代抛弃你连招呼都不打”。兄弟们,我太熟这个剧本了。前端这几年,平均每 9 个月就被公开处决一次。我们的died清单(2025 年 11 月实时更新版)2010:WordPress 模板 → 前端再卒2015:Webflow/No-Code → 前端三卒2023:GPT-4 一个 prompt 贪吃蛇 → 前端四卒2024:Claude 3.5 Sonnet 卷 UI → 前端五卒2025 年 11 月 18-21 日:Gemini 3 连发三天 demo(体素玩具箱、网页版 CapCut、赛博风登陆页、AI 视频生成工具……)→ 前端这三天死了 114514 次,目前骨灰已被 X 用户扬到太平洋对岸去了这几天最经典的几个“葬礼现场”那个 3D Voxel Toy Box:一个 prompt 出来,实时绘画、破坏、导出 glTF,丝滑得像 Unity 官方 demo。评论区直接高呼“前端工程师可以集体转行了”。那个网页版视频编辑器:拖拽时间线、AI 自动生成视频、TTS、字幕、转场全有。作者在 X 上发帖:“从 0 到 1 只用了 1.5 小时,感谢 Gemini 3。” 底下回复“前端已死”“我哭死”“程序员末日”。那个电影级登陆页:暗黑赛博风、3D 粒子背景、鼠标视差、滚动触发动画,完美响应式,连 iPad 横竖屏切换都丝滑。作者淡定地说:“就一个 prompt,改了三次描述。”然后这几天,时间线彻底沦陷:“以后公司只需要一个会写 prompt 的产品经理就行了。”“前端岗位预计 2026 年消失。”“建议所有大厂立刻裁掉 90% 的前端。”Gemini 3 确实牛逼,这几天 X 上的各种实测提示词都试了下,生成的东西确实牛逼,我把公司首页丢给它,让它“重构得更现代一点”。 20秒后,它给我吐了一个电影级粒子背景 + 3D 卡片 + 滚动触发动画的版本,Lighthouse 直接 100/100/100/100。。。奶奶的这比好多优秀前端写的不知道好多少倍。。但问题来了:你一个 prompt 做出来的完美 demo,明天上线的时候,甲方会告诉你:“背景要那种看不见的黑色,像深夜emo但又有希望的感觉。”“这个动画再慢 0.2 秒,用户会觉得我们公司很稳重。”你问 Gemini 3 怎么改,它会非常认真地给你生成 17 种优雅方案。坑位技术大厂,前端-后端-测试,新一线和一二线城市等地均有坑位,感兴趣可以试试。待遇和稳定性都不错~然后你上线后发现:甲方不满意,这里那里怎么样怎么样。甲方的浏览器 CSS 不兼容而导致的样式混乱。新的样式和项目整体样式冲突。 这时候你还得自己上手,一个个去修、去 hack、去加一堆 !important 和 any。恭喜你,你又回到了最熟悉的前端生活。2026 年的新工种:AI 擦屁股工程师这几天网上最热门的梗就是“前端已死”,但是只要甲方存在,前端同仁就有活路。所以,面对这几天铺天盖地的“Gemini 3 把前端扬了”言论,请各位前端同仁保持冷静。我们前端的真正核心竞争力,从来不是写代码。只要甲方会半夜改需求,只要还有安卓碎片化,只要 CSS 还存在,前端就死不了。前端已死?不,前端只是又被扬了一次骨灰,然后继续负伤加班,顺便帮 Gemini 3 擦屁股。前端万岁!(下次 GPT-6 出来再继续死)首发地址:Gemini 3 发布了,恭喜前端第 10086 次“由于不可抗力”宣告——转载自:suke
接口开发,咱得整得“优雅”点
一、为什么要“优雅”?产品一句话: “凡哥,接口明天上线,支持 10w 并发,数据脱敏,不能丢单,不能重复,还要安全。” 优雅不是装,是为了让自己少加班、少背锅、少掉发。 今天晓凡就把压箱底的东西掏出来,手把手带你撸一套能扛生产的模板。为方便阅读,晓凡以Java代码为例给出“核心代码 + 使用姿势”,全部亲测可直接使用。二、项目骨架(Spring Boot 3.x)
demo-api
├── src/main/java/com/example/demo
│ ├── config // 配置:限流、加解密、日志等
│ ├── annotation // 自定义注解(幂等、日志、脱敏)
│ ├── aspect // 切面统一干活
│ ├── interceptor // 拦截器(签名、白名单)
│ ├── common // 统一返回、异常、常量
│ ├── controller // 对外暴露
│ ├── service
│ └── DemoApplication.java
└── pom.xml
三、 签名(防篡改)对外提供的接口要做签名认证,认证不通过的请求不允许访问接口、提供服务思路 “时间戳 + 随机串 + 业务参数”排好序,最后 APP_SECRET 拼后面,SHA256 一下。 前后端、第三方都统一,拒绝吵架。工具类
public class SignUtil {
/**
* 生成签名
* @param map 除 sign 外的所有参数
* @param secret 分配给你的私钥
*/
public static String sign(Map
配置
spring:
application:
name: demo-api
sentinel:
transport:
dashboard: localhost:8080
使用姿势
@GetMapping("/order/{id}")
@SentinelResource(value = "getOrder",
blockHandler = "getOrderBlock")
public Result
配置
knife4j:
enable: true
setting:
language: zh_cn
启动后访问 http://localhost:8080/doc.html 支持在线调试、导出 PDF、Word。十七、小结接口开发就像炒菜:签名、加密是“食材保鲜”限流、幂等是“火候掌控”日志、文档是“摆盘拍照”每道工序做到位,才能端到桌上“色香味”俱全。 上面 13 段核心代码,直接粘过去就能跑,跑通后再按业务微调,基本能扛 90% 的生产场景。 祝你在领导问起接口怎么样了?的时候,可以淡淡来一句: “接口已经准备好了,压测报告发群里了。”——转载自:程序员晓凡
忍了一年多,我终于对i18n下手了
前言大家好,我是奈德丽。过去一年,我主要参与国际机票业务的开发工作,因此每天都要和多语言(i18n)打交道。熟悉我的朋友都知道,我这个人比较“惜力”(并不是,实际上只是忍不下去了),对于重复笨拙的工作非常抵触,于是,我开始思考如何优化团队的多语言管理模式。痛点背景先说说我们在机票项目中遇到的困境。目前机票项目分为 H5 和 PC 两端,团队在维护多语言时主要通过在线 Excel进行管理:一个 Excel 文件,H5 和 PC 各自占一个 sheet 页;每次更新语言,需要先导出 Excel,然后手动跑脚本生成语言文件,再拷贝到项目中。听起来还算凑合,但随着项目规模的扩大,问题逐渐显现:Key 命名混乱有的首字母大写,有的小驼峰、大驼峰混用;没有统一规则,难以模块化管理。不支持模块化目前已有数千条 key;查找、修改、维护都非常痛苦。更新流程繁琐需要手动进入脚本目录,用 node 跑脚本;生成后再手动复制到项目中。下面是一个实际的 Excel 片段,可以感受一下当时的混乱程度:用原node脚本生成的语言文件如图在这样的场景下,每次迭代多语言文件更新都像噩梦一样。 尤其是我们很多翻译是通过AI 机翻生成,后续频繁修改的成本极高。然而,机票项目的代码量太大、历史包袱太重,短期内几乎不可能彻底改造。新项目,新机会机票项目虽然不能动,但在我们启动酒店业务新项目时,我决定不能再重蹈覆辙。 因此,在酒店项目中,我从零搭建了这套更高效的 i18n 管理方案。目标很简单:统一 key 规则,支持模块化,模块与内容间用.隔开,内容之间用下划线隔开;自动化生成多语言 JSON 文件,集成到项目内,不再需要查找转化脚本的位置;一条命令搞定更新,不需要手动拷贝。于是,我在项目中新增了一个 scripts 目录,并编写了一个 excel-to-json.js 脚本。 在 package.json 中添加如下命令:
{
"scripts": {
"i18n:excel-to-json": "node scripts/excel-to-json.js"
}
}
以后,只需要运行下面一行命令,就能完成所有工作:
pnpm i18n:excel-to-json
再也不用手动寻找脚本路径,也不用手动复制粘贴,效率直接起飞 🚀。坑位技术大厂,前端-后端-测试,新一线和一二线城市等地均有坑位,感兴趣可以试试。待遇和稳定性都不错~脚本实现核心逻辑就是: 从 Excel 读取内容 → 转换为 JSON → 输出到项目 i18n 目录。完整代码如下:
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import XLSX from 'xlsx'
/**
* 语言映射表:Excel 表头 -> 标准语言码
*/
const languageMap = {
'English': 'en',
'简中': 'zh-CN',
'Chinese (Traditional)': 'zh-TW',
'Korean': 'ko',
'Spanish': 'es',
'German Edited': 'de',
'Italian': 'it',
'Norwegian': 'no',
'French': 'fr',
'Arabic': 'ar',
'Thailandese': 'th',
'Malay': 'ms',
}
// 读取 Excel 文件
function readExcel(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error(`❌ Excel 文件未找到: ${filePath}`)
}
const workbook = XLSX.readFile(filePath)
const sheet = workbook.Sheets[workbook.SheetNames[0]]
return XLSX.utils.sheet_to_json(sheet)
}
/**
* 清空输出目录
*/
function clearOutputDir(dirPath) {
if (fs.existsSync(dirPath)) {
fs.readdirSync(dirPath).forEach(file => fs.unlinkSync(path.join(dirPath, file)))
console.log(`🧹 已清空目录: ${dirPath}`)
} else {
fs.mkdirSync(dirPath, { recursive: true })
console.log(`📂 创建目录: ${dirPath}`)
}
}
/**
* 生成 JSON 文件
*/
function generateLocales(rows, outputDir) {
const locales = {}
rows.forEach(row => {
const key = row.Key
if (!key) return
// 遍历语言列
Object.entries(languageMap).forEach(([columnName, langCode]) => {
if (!locales[langCode]) locales[langCode] = {}
const value = row[columnName] || ''
const keys = key.split('.')
let current = locales[langCode]
keys.forEach((k, idx) => {
if (idx === keys.length - 1) {
current[k] = value
} else {
current[k] = current[k] || {}
current = current[k]
}
})
})
})
// 输出文件
Object.entries(locales).forEach(([lang, data]) => {
const filePath = path.join(outputDir, `${lang}.json`)
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8')
console.log(`✅ 生成文件: ${filePath}`)
})
}
/**
* 检测缺失翻译
*/
function detectMissingTranslations(rows) {
const missing = []
rows.forEach(row => {
const key = row.Key
if (!key) return
Object.entries(languageMap).forEach(([columnName, langCode]) => {
const value = row[columnName]
if (!value?.trim()) {
missing.push({ key, lang: langCode })
}
})
})
return missing
}
function logMissingTranslations(missingList) {
if (missingList.length === 0) {
console.log('\n🎉 所有 key 的翻译完整!')
return
}
console.warn('\n⚠️ 以下 key 缺少翻译:')
missingList.forEach(item => {
console.warn(` - key: "${item.key}" 缺少语言: ${item.lang}`)
})
}
function main() {
const desktopPath = path.join(os.homedir(), 'Desktop', 'hotel多语言.xlsx')
const outputDir = path.resolve('src/i18n/locales')
const rows = readExcel(desktopPath)
clearOutputDir(outputDir)
generateLocales(rows, outputDir)
logMissingTranslations(detectMissingTranslations(rows))
}
main()
成果展示这是在线语言原文档这是生成后的多语言文件和内容 现在的工作流大幅简化:操作旧流程新流程运行脚本手动找脚本路径pnpm i18n:excel-to-json文件生成位置生成后手动拷贝自动输出到项目检测缺失翻译无自动提示key 命名管理无统一规则模块化、规范化这套机制目前在酒店项目中运行良好,团队反馈也很积极。总结这次改造让我最大的感触是:旧项目难以推翻重来,但新项目一定要趁早做好架构设计。通过这次优化,我们不仅解决了多语言维护的痛点,还提升了团队整体开发效率。 而这套方案在未来如果机票项目有机会重构,也可以直接平滑迁移过去。——转载自:奈德丽
别再死磕框架了!你的技术路线图该更新了
先说结论:前端不会凉,但“只会几个框架 API”的前端,确实越来越难混 这两年“前端要凉了”“全栈替代前端”的声音此起彼伏,本质是门槛重新洗牌:简单 CRUD、纯样式开发被低代码、模板代码和 AI 模型快速蚕食;复杂业务、工程体系、跨端体验、AI 能力集成,反而需要更强的前端工程师去撑住。如果你对“前端的尽头是跑路转管理”已经开始迷茫,那这篇就是给你看的:别再死磕框架版本号,该更新的是你的技术路线图。一、先搞清楚:2025 的前端到底在变什么?框架红海:从“会用”到“用得值”React、Vue、Svelte、Solid、Qwik、Next、Nuxt……Meta Framework 一大堆,远远超过岗位需求。 现在企业选型更关注:生态成熟度(如 Next.js 的 SSR/SSG 能力)框架在应用生命周期中的角色(渲染策略、数据流转、SEO、部署)趋势:框架 Meta 化(Next.js、Nuxt)将路由、数据获取、缓存策略整体纳入规范;约定优于配置,不再是“一个前端库”,而是“一套完整解决方案”。以前是“你会 Vue/React 就能干活”,现在是“你要理解框架在整个应用中的角色”。工具有 AI,开发方式也在变AI 工具(如 Cursor、GitHub Copilot X)可以显著提速,甚至替代重复劳动。 真正拉开差距的变成了:你能给 AI 写出清晰、可实现的需求描述(Prompt);你能判断 AI 生成代码的质量、潜在风险、性能问题;你能基于生成结果做出合理抽象和重构。AI 不是来抢饭碗,而是逼你从“码农”进化成“架构和决策的人”。业务侧:前端不再是“画界面”,而是“做体验 + 做增长”B 端产品:交互工程师 + 低代码拼装师 + 复杂表单处理专家;C 端产品:与产品运营深度捆绑,懂 A/B 测试、埋点、Funnel 分析、广告投放链路;跨平台:Web + 小程序 + App(RN/Flutter/WebView)混合形态成为常态。那些还在喊“切图仔优化 padding”的岗位确实在消失,但对“懂业务、有数据意识、能搭全链路体验”的前端需求更高。坑位技术大厂,前端-后端-测试,新一线和一二线城市等地均有坑位,感兴趣可以试试。待遇和稳定性都不错~二、别再死磕框架 API:2025 的前端核心能力长什么样?基石能力:Web 原生三件套,得真的吃透重点不是“会用”,而是理解底层原理:JS:事件循环、原型链、Promise 执行模型、ESM 模块化;浏览器:渲染流程(DOM/CSSOM/布局/绘制/合成)、HTTP/2/3、安全防护(XSS/CSRF)。这块扎实了,你在任何框架下都不会慌,也更能看懂“框架为什么这么设计”。工程能力:从“会用脚手架”到“能看懂和调整工程栈”Vite、Rspack、Turbopack 等工具让工程构建从“黑魔法”变成“可组合拼装件”。 你需要:看懂项目的构建配置(Vite/Webpack/Rspack 任意一种);理解打包拆分、动态加载、CI/CD 流程;能排查构建问题(路径解析、依赖冲突)。如果你在团队里能主动做这些事,别人对你的“级别判断”会明显不一样。跨端和运行时:不只会“写 Web 页”2025 年前端视角的关键方向:小程序/多端框架(Taro、Uni-app);混合方案(RN/Flutter/WebView 通信机制);桌面端(Electron、Tauri)。建议:至少深耕一个“跨端主战场”(如 Web + 小程序 或 Web + Flutter)。数据和状态:从“会用 Vuex/Redux”到“能设计状态模型”现代前端复杂度 70% 在“数据和状态管理”。 进阶点在于:设计合理的数据模型(本地 UI 状态 vs 服务端真相);学会用 Query 库、State Machine 解耦状态与视图。当你能把“状态设计清楚”,你在复杂业务团队里会非常吃香。性能、稳定性、可观测性:高级前端的硬指标你需要系统性回答问题,而不是“瞎猜”:性能优化:首屏加载(资源拆分、CDN)、运行时优化(减少重排、虚拟列表);稳定性:错误采集、日志上报、灰度发布;工具:Lighthouse、Web Vitals、Session Replay。这块做得好的人往往是技术骨干,且很难被低代码或 AI 直接替代。AI 时代的前端:不是“写 AI”,而是“让 AI 真正跑进产品”你需要驾驭:基础能力:调用 AI 平台 API(流式返回处理、增量渲染);产品思维:哪些场景适合 AI(智能搜索、文档问答);如何做权限控制、错误兜底。三、路线图别再按“框架学习顺序”排了,按角色来选初中级:从“会用”到“能独立负责一个功能”目标:独立完成中等复杂度模块(登录、权限、表单、列表分页)。建议路线:夯实 JS + 浏览器基础;选择 React/Vue + Next/Nuxt 做完整项目;搭建 eslint + prettier + git hooks 的开发习惯。进阶:从“功能前端”到“工程前端 + 业务前端”目标:优化项目、推进基础设施、给后端/产品提技术方案。建议路线:深入构建工具(Webpack/Vite);主导一次性能优化或埋点方案;引入 AI 能力(如智能搜索、工单回复建议)。高级/资深:从“高级前端”到“前端技术负责人”目标:设计技术体系、推动长期价值。建议路线:明确团队技术栈(框架、状态管理、打包策略);主导跨部门项目、建立知识分享机制;评估 AI/低代码/新框架的引入价值。四、2025 年不要再犯的几个错误只跟着热点学框架,不做项目和抽象选一个主战场 + 一个备胎(React+Next.js,Vue+Nuxt.js),用它们做 2~3 个完整项目。完全忽略业务,沉迷写“优雅代码”把重构和业务迭代绑一起,而不是搞“纯技术重构”。对 AI 持敌视和逃避态度把重复劳动交给 AI,把时间投到架构设计、业务抽象上。把“管理”当成唯一出路做前端架构、性能优化平台、低代码平台的技术专家,薪资和自由度不输管理岗。五、一个现实点的建议:给自己的 2025 做个“年度规划”Q1:选定主技术栈(React+Next 或 Vue+Nuxt);做一个完整小项目(登录、权限、列表/详情、SSR、部署)。Q2:深入工程化方向(优化打包体积、搭建监控埋点系统)。Q3:选一个业务场景引入 AI 或配置化能力(如智能搜索、低代码表单)。Q4:输出和沉淀(写 3~5 篇技术文章、踩坑复盘)。最后:别问前端凉没凉,先问问自己“是不是还停在 2018 年的玩法”如果你还把“熟练掌握 Vue/React”当成简历亮点,那确实会焦虑;但如果你能说清楚:在复杂项目里主导过哪些工程优化;如何把业务抽象成可复用的组件/平台;如何在产品里融入 AI/多端/数据驱动; 那么,在 2025 年的前端市场,你不仅不会“凉”,反而会成为别人眼中的“稀缺”。别再死磕框架了,更新你的技术路线图,从“写页面的人”变成“打造体验和平台的人”。这才是 2025 年前端真正的进化方向。——转载自:纯爱掌门人
Stack Overflow,轰然倒下了!
提到 Stack Overflow 技术社区,提到那个橙色的栈溢出图标,相信程序员和开发者们应该都再熟悉不过了。最近在网上看到了一个有关 Stack Overflow 社区的变化趋势图,让人感慨万千。这个曲线图表示的是自 2008 年开始,一直到 2025 年的今天,这 17 年时间,Stack Overflow 社区上每个月新问题个数的变化趋势。怎么样?大家看完这张图有没有什么感触?这张图清晰地展示出了 Stack Overflow 编程社区在这 17 年间所经历的增长、繁荣以及回落的趋势。可以看到,从 2008 年到 2014 年这前 6 年的时间,Stack Overflow 一路高歌,渐入佳境,基本都在稳步增长。而从 2014 年到 2022 年这中间的 8 年时间,虽说图中曲线呈震荡变化状态,但总体都是处于高位趋势,这也是 Stack Overflow 社区的繁荣时刻。从数据上来看,Stack Overflow 最高光的顶峰时刻出现在 2020 年,尤其是在 2020-05-01 这个时间节点,新问题个数来到了 302381 的顶峰。而从图中也可以很明显地看出,自 2022 年底开始,Stack Overflow 社区日渐式微,开始出现了回落之势。同样是那一年底,OpenAI 正式发布了 ChatGPT。那后面几年的故事,相信大家都非常清楚了,AI 大模型相关的技术和产品飞速迭代,传统的搜索引擎和知识社区也受到了不小的冲击。到了如今的 2025 年,Stack Overflow 的数据也已经跌回到了 15 年前的水平了。这几年的趋势相信大家都非常清楚,AI 大模型日新月异,相关的模型产品更是百花争艳,层出不穷,这对于 Stack Overflow 的冲击在图上也体现得淋漓尽致。于是我也开始回想,我自己这几年在互联网上搜索信息的方式,似乎在不知不觉中已经发生了很大的变化。现在遇到问题,我好像已经不怎么喜欢使用传统搜索引擎了,而是会习惯性地转向各种 AI 大模型工具和智能助手,同时信息的处理和交互范式也完全变了。我们还以编程写代码为例。以前当我们在写代码调试运行出现错误但折腾半天也不知所以的时候,大家会怎么做?相信不少同学和我一样,也是首先复制这段报错信息到搜索引擎中进行检索,然后根据搜索引擎吐出来的搜索结果,自己逐个点进去筛选有用的信息。而当我们一旦在搜索结果里看到了 Stack Overflow 的相关网页时,我们的直觉会告诉我,离问题解决应该不远了。而即便当我们在搜索引擎里找不到任何解决问题的方法,那我们也可以去类似 Stack Overflow 这样的编程社区里进行发帖求助,然后等待被查看和回答。这是在 AI 大模型还没有爆发之前,大家所普遍采用的一个解决问题的办法。但如今一切都变了。随着 AI 模型和产品的快速渗透,大家解决问题的方式发生了变化。我们直接甩给 AI 工具一个问题或者一段信息,AI 工具便会自动理解你的意图,并开始深度思考、收集信息、整理逻辑、分析总结、加工输出,最后直接把生成的答案或解决问题的办法呈现在你的眼前。和这些传统知识社区和搜索引擎相比,AI 大模型很强,这无需质疑。AI 大模型强就强在它的理解能力、整合能力以及推理能力,这些都是传统知识社区和搜索引擎往往所欠缺的东西。传统搜索引擎往往依赖于关键词匹配和链接分析,因此对于用户问题的理解往往有所欠缺,而 AI 大模型则能够深度理解语言含义和上下文,理解问题的真正意图。同时 AI 大模型能阅读、理解并整合数据中不同维度的海量知识,并能在此基础上来进行进一步的推理、分析、总结、泛化,这在如今的信息爆炸的时代来说是一种巨大的价值。所以相比去 Stack Overflow 上发帖子、搜问题、筛答案,AI 引擎无论在时间上还是知识维度的扩展上都给了这些传统知识社区和搜索引擎以降维打击。如此一看,如果再不转型,像 Stack Overflow 这样的传统编程社区的轰然倒下,似乎也成了一个必然的趋势。说到底,AI 大模型不是搜索引擎的简单升级版,而是一种全新的信息处理和交互范式。那面对这波 AI 大模型浪潮的席卷和冲击,不少传统的搜索引擎和知识社区都开始了转型升级,并积极拥抱 AI。比如像 Stack Overflow 自己也搞了一个 Overflow AI,其中包含了一套基于他们自己的历史内容和知识库所打造的 GenAI 工具。从「检索工具」进化到「智能助手」,这是不少现有知识社区和搜索引擎正在经历的蜕变之路。这两年 AI 大模型领域的发展速度相信大家都有目共睹了,技术迭代进化更是远超预期。可以预见的是,未来的信息检索和交互方式一定还会进一步高效、精准和智能,而对此我们也可以拭目以待。好了,那以上就是今天的内容分享了,感谢大家的阅读,我们下篇见。——转载自:CodeSheep
一个AI都无法提供的html转PDF方案
这也许就是AI无法代替人的原因,只需一行代码就可以实现纯前端 html 转矢量 pdf 的功能
// 引入 dompdf.js库
import dompdf from "dompdf.js";
dompdf(document.querySelector("#capture")).then(function (blob) {
//文件操作
});
实现效果(复杂表格)1. 在线体验dompdfjs.lisky.com.cn2. Git 仓库地址 (欢迎 Star⭐⭐⭐)github.com/lmn1919/dom…3. 生成 PDF在前端生态里,把网页内容生成 PDF 一直是一个常见但不简单的需求。从报表导出、小票生成、合同下载到打印排版,很多项目或多或少都会遇到。市面上常见的方案大致有以下几类:服务端渲染 PDF(后端库如 wkhtmltopdf、PrinceXML 等)客户端将 HTML 渲染为图片(如 html2canvas + jsPDF)然后再封装为 PDF前端调用相关 pdf 生成库来生成 PDF(如 pdfmake,jspdf,pdfkit)但是这些方案都有各自的局限性,比如服务端渲染 PDF 对服务器资源要求高,需要后端参与。html2canvas + jsPDF 需要将 html 内容渲染为图片,再将图片封装为 PDF,速度会比较慢,而且生成体积会比较大,内容会模糊,限制于 canvas 生成高度,不能生成超过 canvas 高度的内容。而前端调用相关 pdf 生成库来生成 PDF 则需要对相关库有一定的了解,api 比较复杂,学习使用成本很高。使用 jspdf 生成如图简单的 pdf机会技术大厂→看机会,前端-测试-后端,待遇和稳定性还不错,感兴趣的可以试试~就需要如此复杂的代码,如果要生成复杂的 pdf, 比如包含表格、图片、图表等内容,那使用成本就更高了。
function generateChinesePDF() {
// Check if jsPDF is loaded
if (typeof window.jspdf === "undefined") {
alert("jsPDF library has not finished loading, please try again later");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// Note: Default jsPDF does not support Chinese, this is just a demo
// In real projects you need to add Chinese font support
doc.setFontSize(16);
doc.text("Chinese Text Support Demo", 20, 30);
doc.setFontSize(12);
doc.text("Note: Default jsPDF does not support Chinese characters.", 20, 50);
doc.text("You need to add Chinese font support for proper display.", 20, 70);
// Draw some graphics for demonstration
doc.setFillColor(255, 182, 193);
doc.rect(20, 90, 60, 30, "F");
doc.setTextColor(0, 0, 0);
doc.text("Pink Rectangle", 25, 108);
doc.setFillColor(173, 216, 230);
doc.rect(100, 90, 60, 30, "F");
doc.text("Light Blue Rectangle", 105, 108);
doc.save("chinese-example.pdf");
}
但是现在,有了 dompdf.js,你只需要一行代码,就可以完成比这个复杂 10 倍的 PDF 生成任务,html页面所见即所得,可以将复杂的css样式转化成pdf
javascript
体验AI代码助手
代码解读
复制代码
dompdf(document.querySelector("#capture")).then(function (blob) {
//文件操作
});
而且,dompdf.js 生成的 PDF 是矢量的,非图片式的,高清晰度的,文字可以选中、复制、搜索等操作(在支持的 PDF 阅读器环境下),区别于客户端将 HTML 渲染为图片(如 html2canvas + jsPDF)然后再封装为 PDF。具体可以去体验 立即体验 https://dompdfjs.lisky.com.cn4. dompdf.js 是如何实现的?其实 dompdf.js 也是基于 html2canvas+jspdf 实现的,但是为什么 dompdf.js 生成的 pdf 文件可以二次编辑,更清晰,体积小呢?不同于普通的 html2canvas + jsPDF 方案,将 dom 内容生成为图片,再将图片内容用 jspdf 绘制到 pdf 上,这就导致了生成的 pdf 文件体积大,无法编辑,放大后会模糊。html2canvas 原理简介1. DOM 树遍历 html2canvas 从指定的 DOM 节点开始,递归遍历所有子节点,构建一个描述页面结构的内部渲染队列。2. 样式计算 对每个节点调用 window.getComputedStyle() 获取最终的 CSS 属性值。这一步至关重要,因为它包含了所有 CSS 规则(内联、内部、外部样式表)层叠计算后的最终结果。3. 渲染模型构建 将每个 DOM 节点和其计算样式封装成渲染对象,包含绘制所需的完整信息:位置(top, left)、尺寸(width, height)、背景、边框、文本内容、字体属性、层级关系(z-index)等。4. Canvas 上下文创建 在内存中创建 canvas 元素,获取其 2D 渲染上下文(CanvasRenderingContext2D)。5. 浏览器绘制模拟 按照 DOM 的堆叠顺序和布局规则,遍历渲染队列,将每个元素绘制到 Canvas 上。这个过程实质上是将 CSS 属性"翻译"成对应的绘制 API 调用:CSS 属性传统 Canvas APIdompdf.js 中的 jsPDF APIbackground-colorctx.fillStyle + ctx.fillRect()doc.setFillColor() + doc.rect(x, y, w, h, 'F')borderctx.strokeStyle + ctx.strokeRect()doc.setDrawColor() + doc.rect(x, y, w, h, 'S')color, font-family, font-sizectx.fillStyle, ctx.font + ctx.fillText()doc.setTextColor() + doc.setFont() + doc.text()border-radiusarcTo() 或 bezierCurveTo() 创建剪切路径doc.roundedRect() 或 doc.lines() 绘制圆角imagectx.drawImage()doc.addImage()核心创新:API 替换,底层是封装了 jsPDF 的 API dompdf.js 的关键突破在于改造了 html2canvas 的 canvas-renderer.ts 文件,将原本输出到 Canvas 的绘制 API 替换为 jsPDF 的 API 调用。这样就实现了从 DOM 直接到 PDF 的转换,生成真正可编辑、可搜索的 PDF 文件,而不是传统的图片格式。目前实现的功能1. 文字绘制 (颜色,大小) 2. 图片绘制 (支持 jpeg, png 等格式) 3. 背景,背景颜色 (支持合并单元格) 4. 边框,复杂表格绘制 (支持合并单元格) 5. canvas (支持多种图表类型) 6. svg (支持 svg 元素绘制) 7. 阴影渲染 (使用 foreignObjectRendering,支持边框阴影渲染) 8. 渐变渲染 (使用 foreignObjectRendering,支持背景渐变渲染)7.使用安装
npm install dompdf.js --save
CDN 引入
基础用法
import dompdf from "dompdf.js";
dompdf(document.querySelector("#capture"), {
useCORS: true, //是否允许跨域
})
.then(function (blob) {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "example.pdf";
document.body.appendChild(a);
a.click();
})
.catch(function (err) {
console.log(err, "err");
});
写在最后dompdf.js 让前端 PDF 生成变得前所未有的简单:无需后端、无需繁琐配置、一行代码即可输出矢量、可检索、可复制的专业文档。无论是简历、报告还是发票,它都能轻松胜任。 欢迎在你的项目中使用它 。如果它帮到了你,欢迎去 github.com/lmn1919/dom… 点个 Star,提优化,共建项目。——转载自:刘发财
解决扫码枪中文输入法下条形码内容丢失问题
扫码枪的工作原理扫码枪的基本工作原理是模拟键盘输入。它将扫描到的条码或二维码解析后的数据,以极快的速度输入到当前获得焦点的输入框中,并在数据末尾追加一个回车(Enter)键。这种方法在英文输入法模式下运行良好,但在中文输入模式下可能会出现一些问题,比如录入不完整、小写字母被转换为中文字符等。问题背景在实际应用中,特别是在中文环境下,用户和客户反馈了上述问题。为了改善用户体验,在领导与客户的强大动力支持下,我们开始探索解决方案。解决方案经过一系列的测试和优化,最终摸索出以下基于[onscan.js](https://www.npmjs.com/package/onscan.js)库的解决方案:正确录入:确保在中文模式下也能正确录入数字、大小写字母以及英文特殊字符。内容清空:扫码后自动清空原有的输入内容,展示新的条码内容。多种输入方式:支持扫码输入和手动输入两种方式。跳板机会技术大厂→跳板,前端-测试-后端,待遇和稳定性都还不错,感兴趣可以试试~实现代码
选用原因避免自动填充:使用input[type=password]时会有自动填充下拉框和记住密码弹窗,影响用户体验。兼容性问题:在中文模式下,onscan.js无法触发onScan回调函数。事件监听:在中文模式下监听input标签的keyPress事件时,无法正确监听小写字母录入。遗留问题输入法切换:中途切换输入法(例如点击Caps Lock键),可能导致扫码枪切换为大写模式,从而影响录入结果。通过上述解决方案,我们能够在中文环境下有效解决扫码枪的录入问题,提升用户的使用体验。——转载自:aol121
有了免费的Kiro,这次真的可以把Cursor扔了!
大家好,我是子昕,一个干了10年的后端开发,现在在AI编程这条路上边冲边摸索,每天都被新技术追着跑。Claude的金主爸爸亚马逊(AWS)偷偷发布了一款AI编程工具,Kiro。我用它做了三个公司的生产级项目需求,深度体验3天后发现:Kiro现在完全免费,可以免费使用Claude-Sonnet-4和Claude-3.7模型规范驱动开发模式,代码质量和工程化程度碾压CursorAgent Hooks自动化系统,真正解决了AI编程工具的健忘问题这可能是今年最值得关注的AI编程工具。下载地址:kiro.dev/Windows用户、Mac用户都可以使用,基于VS Code架构,零学习成本。为什么说Kiro比Cursor更强?技术角度深度分析最近在用真实项目对比各种AI编程工具,发现Cursor在处理复杂业务逻辑时存在几个核心问题:上下文理解不足:经常遗忘项目结构,生成不一致的代码Token优化过度:为了省成本,功能完整性受影响缺乏工程化思维:直接生成代码,缺乏规范和文档Kiro的出现完全解决了这些痛点。Kiro安装体验:零门槛切换Kiro和Cursor一样基于VS Code架构,所以切换成本为零:但在交互设计上,Kiro提供了两种截然不同的工作模式:Vibe模式Spec模式Vibe模式:传统聊天式编程,适合快速原型开发Spec模式:规范驱动开发,这是Kiro的核心创新Spec模式:规范驱动开发的革命这是我见过最接近企业级开发标准的AI工具工作流。Spec模式遵循严格的三阶段开发流程:第一阶段:需求分析(Requirements)自动生成EARS语法标准的需求文档,包含:用户故事定义验收标准边界条件处理非功能性需求第二阶段:系统设计(Design)生成完整的技术设计文档:包含数据库Schema、API接口设计、组件架构图等生产级文档。第三阶段:实现计划(Implementation)将功能分解为有序任务,包含依赖关系和测试要求。任务管理与执行:颗粒度控制Kiro的任务管理机制是其核心优势之一。生成的文档会自动保存在项目根目录的.kiro文件夹中,每个任务都支持独立控制:关键特性:任务状态实时追踪支持并发任务执行智能任务队列管理任务队列这种颗粒度控制完全解决了Cursor一股脑生成代码导致的返工问题。Agent Hooks:自动化质量控制Kiro最具技术含量的功能是Agent Hooks系统,基于文件事件触发自动化检查:实时代码预览预览按钮预览效果通过Follow按钮可以实时查看代码修改,相比Cursor的全量预览和Claude Code的黑盒执行,Kiro提供了更好的可控性。一键回滚机制支持任务级别的原子回滚,比Cursor的checkpoint机制更精确。看机会技术大厂→跳板通道,前端-测试-后端,待遇和稳定性还不错,感兴趣的可以来试试~技术对比:Kiro vs Cursor 实战差异为了客观评估两个工具的差异,我用一个完整的团队任务管理系统项目进行了对比测试:测试场景项目复杂度:类似简化版Jira,包含用户系统、项目管理、任务流转、智能功能、实时通知、数据看板等完整模块技术栈:React + TypeScript + Tailwind CSS + Node.js + Express + PostgreSQL + Prisma ORM + Socket.ioAI集成:调用OpenAI API进行智能工时估算和任务分配评估维度:开发效率、代码质量、文档完整性、可维护性对比结果Cursor表现:直接开始写代码,缺乏整体规划面对复杂业务逻辑时容易遗漏关键模块生成的组件缺乏系统性设计数据库Schema设计不够完整实时通信和AI集成部分需要大量手动调整几乎没有项目文档输出Kiro表现:先生成完整的需求分析和系统设计文档自动分解为用户管理、项目管理、任务系统等独立模块生成完整的数据库Schema和API接口设计包含Socket.io集成和AI功能的详细实现方案自动生成组件架构图和数据流图输出可直接用于团队协作的技术文档核心差异面对这种企业级复杂项目,Cursor更像是功能堆砌,而Kiro展现了真正的系统工程思维。特别是在处理多模块协作、数据库设计、第三方集成等复杂场景时,Kiro的规范驱动开发优势非常明显。成本效益分析:定价策略对比当前状态:Kiro完全免费,包含Claude-4模型访问权限未来定价:免费版:50次智能体交互/月Pro版:$19/月,1000次交互Pro+版:$39/月,3000次交互Cursor Pro对比:价格:$20/月限制:500次Chat + 无限Tab补全模型:GPT-4、Claude-4性价比分析: Kiro Pro比Cursor便宜$1,但提供2倍的交互次数,且基于更新的Claude-4模型。技术架构:AWS生态系统优势Kiro基于以下技术栈:前端:Code OSS(VS Code开源版)AI模型:Claude Sonnet 3.7/4.0协议支持:MCP(Model Context Protocol)云基础设施:AWS相比Cursor的多模型策略,Kiro专注于Claude系列模型的深度优化,在代码理解和生成质量上表现更稳定。我的AI编程工具新排名基于深度测试和生产环境使用经验:Kiro - 规范驱动开发,企业级标准Claude Code - 复杂逻辑分析专家Augment - 质量优先,适合高要求项目Cursor - 个人快速原型工具其他工具 - 功能差异明显选择建议适合Kiro的场景:需要完整文档的正式项目团队协作开发对代码质量要求较高企业级应用开发适合Cursor的场景:个人快速原型开发学习编程过程简单功能迭代写在最后Kiro的出现标志着AI编程工具的重要转变:1.0时代:代码生成和补全2.0时代:规范驱动的全流程工程化这种转变反映了行业从能用到好用再到专业的需求升级。建议先用Vibe模式熟悉界面,再尝试Spec模式体验规范驱动开发的完整流程。——转载自:子昕AI编程
脱裤子放屁 - 你们讨厌这样的页面吗?
前言平时在逛掘金和少数派等网站的时候,经常有跳转外链的场景,此时基本都会被中转到一个官方提供的提示页面。掘金:知乎:这种官方脱裤子放屁的行为实在令人恼火。是、是、是、我当然知道这么做有很多冠冕堂皇的理由,比如:防止钓鱼攻击增强用户意识品牌保护遵守法律法规控制流量去向(以上5点是 AI 告诉我的理由)但是作为混迹多年的互联网用户,什么链接可以点,什么最好不要点(悄悄的点) 我还是具备判断能力的。互联网的本质就是自由穿梭,一个 A 标签就可以让你在整个互联网翱翔,现在你每次起飞的时候都被摁住强迫你阅读一次免责声明,多少是有点恼火的。看机会技术大厂→机遇,前端-后端-测试,待遇和稳定性都还不错,感兴趣可以尝试~解决方案这些中转站的实现逻辑基本都是将目标地址挂在中转地址的target 参数后面,在中转站做免责声明,然后点击继续跳转才跳到目标网站。掘金:https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.apple.com%2Fcn%2Fdesign%2Fhuman-interface-guidelines%2Fapp-icons%23macOS/知乎: https://link.zhihu.com/?target=https%3A//asciidoctor.org/所以我们就可以写一个浏览器插件,在这些网站中,找出命中外链的 A 标签,替换掉它的 href 属性(只保留 target 后面的真实目标地址)。核心函数:
function findByTarget() {
if (!hostnames.includes(location.hostname)) return;
const linkKeyword = "?target=";
const aLinks = document.querySelectorAll(
`a[href*="${linkKeyword}"]:not([data-redirect-skipper])`
);
if (!aLinks) return;
aLinks.forEach((a) => {
const href = a.href;
const targetIndex = href.indexOf(linkKeyword);
if (targetIndex !== -1) {
const newHref = href.substring(targetIndex + linkKeyword.length);
a.href = decodeURIComponent(newHref);
a.setAttribute("data-redirect-skipper", "true");
}
});
}
为此我创建了一个项目仓库 redirect-skipper ,并且将该浏览器插件发布在谷歌商店了 安装地址 。安装并启用这个浏览器插件之后,在这些网站中点击外链就不会看到中转页面了,而是直接跳转到目标网站。因为我目前明确需要修改的就是这几个网站,如果大家愿意使用这个插件,且有其他网站需要添加到替换列表的,可以给 redirect-skipper 仓库 提PR。如果需要添加的网站的转换规则是和 findByTarget 一致的,那么仅需更新 sites.json 文件即可。如果需要添加的网站的转换规则是独立的,那么需要更新插件代码,合并之后,由我向谷歌商店发起更新。为了后期可以灵活更新配置(谷歌商店审核太慢了),我默认将插件应用于所有网站,然后在代码里通过 hostname 来判断是否真的需要执行。
{
"$schema": "https://json.schemastore.org/chrome-manifest.json",
"name": "redirect-skipper",
"manifest_version": 3,
"content_scripts": [
{
"matches": ["[removed]"],
"js": ["./scripts/redirect-skipper.js"],
"run_at": "document_end"
}
],
}
在当前仓库里维护一份 sites.json 的配置表,格式如下:
{
"description": "远程配置可以开启 Redirect-Skipper 插件的网站 (因为谷歌商店审核太慢了,否则无需通过远程配置,增加复杂性)",
"sites": [
{
"hostname": "juejin.cn",
"title": "掘金"
},
{
"hostname": "sspai.com",
"title": "少数派"
},
{
"hostname": "www.zhihu.com",
"title": "知乎"
}
]
}
这样插件在拉取到这份数据的时候,就可以根据这边描述的网站配置,决定是否执行具体代码。插件完整代码:
function replaceALinks() {
findByTarget();
}
function observerDocument() {
const mb = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === "childList") {
if (mutation.addedNodes.length) {
replaceALinks();
}
}
}
});
mb.observe(document, { childList: true, subtree: true });
}
// 监听路由等事件
["hashchange", "popstate", "load"].forEach((event) => {
window.addEventListener(event, async () => {
replaceALinks();
if (event === "load") {
observerDocument();
await updateHostnames();
replaceALinks(); // 更新完数据后再执行一次
}
});
});
let hostnames = ["juejin.cn", "sspai.com", "www.zhihu.com"];
function updateHostnames() {
return fetch(
"https://raw.githubusercontent.com/dogodo-cc/redirect-skipper/master/sites.json"
)
.then((response) => {
if (response.ok) {
return response.json();
}
throw new Error("Network response was not ok");
})
.then((data) => {
// 如果拉到了远程数据,就用远程的
hostnames = data.sites.map((site) => {
return site.hostname;
});
})
.catch((error) => {
console.error(error);
});
}
// 符合 '?target=' 格式的链接
// https://link.juejin.cn/?target=https%3A%2F%2Fdeveloper.apple.com%2Fcn%2Fdesign%2Fhuman-interface-guidelines%2Fapp-icons%23macOS/
// https://sspai.com/link?target=https%3A%2F%2Fgeoguess.games%2F
// https://link.zhihu.com/?target=https%3A//asciidoctor.org/
function findByTarget() {
if (!hostnames.includes(location.hostname)) return;
const linkKeyword = "?target=";
const aLinks = document.querySelectorAll(
`a[href*="${linkKeyword}"]:not([data-redirect-skipper])`
);
if (!aLinks) return;
aLinks.forEach((a) => {
const href = a.href;
const targetIndex = href.indexOf(linkKeyword);
if (targetIndex !== -1) {
const newHref = href.substring(targetIndex + linkKeyword.length);
a.href = decodeURIComponent(newHref);
a.setAttribute("data-redirect-skipper", "true");
}
});
}
更详细的流程可以查看 redirect-skipper 仓库地址——转载自:甜甜的泥土
前端开发,为什么容易被边缘化?
我们前端,可能是公司里最卷的岗位之一。天天加班加点,追最新的技术栈,像素级还原设计稿,为了那100毫秒的性能提升死磕到底。但不知道你有没有过类似的感受:尽管我们做了这么多,但在很多公司里,我们总感觉自己处于食物链的下游?为什么在讨论核心战略、分配项目奖金、甚至决定项目方向时,前端的声音总是那么微弱,甚至最先被排除在外?最近我和一些朋友聊起这个话题,并进行了一些思考。对于我们这个角色,在很多公司的权力结构和价值链条中,天然地处于一个比较尴尬的位置。价值与稀缺性?前端的工作,被认为是理所应当的基础设施,而不是稀缺的核心价值。在很多管理者眼中,前端技术栈的更迭再快,本质上也是可以 快速可复制的。“这个React组件你写不出来,我换个培训班出来的年轻人,多花点时间,总能堆出来。”“这个页面仔- 速度慢一点,用户忍一忍也能用。”但资本(决定公司生死)、市场(带来客户和收入)、核心后端算法(构建技术壁垒),只有这些被认为是稀缺且难以替代的。在一个体系里,回报总是会向稀缺的一方倾斜。这就是为什么,尽管我们对用户体验至关重要,但在价值排序上,却常常被放在后面。顺便吆喝一句→机会_技术大厂,前端-后端-测试,待遇和稳定性都还不错,感兴趣试试~我们是执行者公司的资源分配,通常不是按谁干得最累,而是按谁能影响最终的资源流向。我们来看看一个典型项目的流程:老板/管理层:决定项目方向和预算。(掌握资金)产品经理:定义用户需求和产品形态。(掌握需求)后端工程师:设计数据结构和核心业务逻辑。(掌握数据)前端工程师:将后端提供的数据,用产品经理设计的形态,画在屏幕上。(掌握展现)在这个工作链条里,前端往往是的最后的一环。我们是落地者,但不是 决策者。一个最典型的例子就是API的制定。很多时候,我们只能被动地接受后端定义的、对UI极其不友好的数据结构,然后在客户端,写大量的“垃圾代码😳”去适配。我们很少有话语权,去反向推动后端,为我们提供更合理的API。在一个体系里,生产关系决定生产力的回报分配。我们前端,就是那个强大的生产力,但我们离生产关系的核心——资金、客户、股权——太远了。看不见的用户体验这个观点在前端身上体现得淋漓尽致。后端的价值是显性的:服务挂了,网站500,所有人都看得见,这是P0级事故。销售的价值是显性的:签下一个订单,公司账户里多了几百万,这是最直接的功劳。前端的价值,很多时候是隐性的:一个页面加载慢了1秒,用户可能只是皱了皱眉,然后默默忍受。一个动画掉了几帧,用户可能只是觉得有点卡,但说不出所以然。一个按钮的响应慢了100毫秒,这在非技术人员看来,几乎无法察觉。这些问题,不会让系统立刻崩溃,但它们会像温水煮青蛙一样,慢慢地侵蚀用户的好感和留存率。我们前端工程师,花了大量的精力去进行性能优化、代码重构,这些工作极其重要,但它们的价值,很难被量化,也很难被非技术人员看见。一个看不见的价值,自然就很难在资源分配时,获得足够的话语权。如何破局聊了这么多困境,并不是为了贩卖焦虑。认清现实,是为了更好地突围。我认为前端工程师要改变边缘化的处境,有三条路可以走:懂业务,成为产品工程师不要只做一个接需求的人。多去问产品经理为什么?多去了解我们的用户画像、商业模式和市场策略。当你能从业务价值、用户增长的角度,去提出技术方案或产品建议时,你的角色就不再是一个执行者,而是一个共创者。成为领域专家在一个垂直领域,做到团队甚至公司里的Top 1。成为性能优化专家,用翔实的数据(LCP, INP, CLS)和业务指标(转化率、留存率),来证明你的优化,为公司带来了多少实实在在的收益。或者成为可视化专家,能用Three.js, D3.js实现别人实现不了的复杂图表和3D效果。或者成为工程化专家,能搭建一套让团队开发效率翻倍的工具链。当你拥有了不可替代性,你的话语权自然就来了。扩展影响力,成为团队枢纽主动承担起更多的“连接”工作。去和后端吵架,推动更合理的API设计。去给测试同学赋能,开发提效工具。多做技术分享,多写技术文档,把你的知识沉淀下来,赋能给整个团队。当你成为团队不可或缺资源时,你的价值就远远超出了你写的那些代码。写在最后前端开发,绝不是一个没有前途的岗位。恰恰相反,因为我们离用户最近,我们本该是产品体验的第一负责人。问题的关键,在于我们是否愿意主动地,从代码实现的舒适区里走出来,去承担更多的责任(内卷🤣),去争取更多的话语权,去证明我们那些看不见的价值。这条路,不好走,但必须走,你们觉得呢🤔——转载自:ErpanOmer
腾讯二面:王者荣耀亿级排行榜,如何设计?
前言大家好,我是田螺。分享一道网上很火的腾讯面试题:亿级用户排行榜怎么设计呢?换种说法,王者荣耀亿级排行榜,如何设计?本文田螺哥从面试的角度,跟大家一起探讨一下,如何回答更好呢?数据库的order by为什么不行?为什么Redis是排行榜的“扛把子”?Redis扛亿级数据可能存在哪些问题以及对应解决方案实现方案:分治巨人的肩膀,前人踩过的坑1. 数据库的order by很多小伙伴,一提到排行榜,就想到数据库的order by。比如微信运动的步数排行:
select * from user_info
order by step desc
这个实现没有问题的,如果表的数据量少的话,反而推荐这样实现。如果数据量多呢。则存在问题,尤其还涉及亿级的数据量时~在亿级用户+高并发实时更新的场景下,会彻底崩盘。 原因一句话:磁盘扛不住,排序算不动,并发撑不起。大厂机会想选一个大厂作为跳板,作为自己镀金机会的,尤其是看【上海】【深圳】等→机会的朋友,前端-测试-后端都有!待遇薪酬还不错,尽管来!2.为什么Redis是排行榜的扛把子当数据量较大且需要实时更新并频繁查询时,使用 Redis 的zset有序集合更为适合。zset是 Redis 提供的一种数据结构,它类似于集合(set),但每个成员都关联着一个分数(score),Redis 使用这个分数来对集合中的成员进行排序。不仅仅是redis的zset支持排序,API简单易用,还因为redis的排序快、可扩展性强、能轻松应对高并发。2.1 redis排序快Redis 的数据全放内存,避免磁盘读写,核心操作秒回:更新分数(ZADD)→ 快如闪电查排名(ZREVRANK)→ 毫秒响应查Top 100(ZREVRANGE)→ 瞬间出结果以下为 Redis vs MySQL 性能对比(亿级数据)操作Redis (Sorted Set)MySQL (ORDER BY)更新单个用户分数0.1ms5~50ms(索引更新代价高)查询用户排名0.2ms100ms~5s(依赖索引和缓存)获取Top 1001ms1s~10s(内存排序或临时文件)高并发支撑能力10万+/秒1000+/秒(需分库分表)2.2 可扩展性强分片存储轻松应对亿级数据。Redis通过分片存储将数据拆分到多个实例,如同把1亿用户分配到10个小数据库,每个只需处理1000万数据,轻松实现:1️⃣ 线性扩展:加机器就能提升容量和性能2️⃣ 压力分散:读写请求分摊到不同分片,避免单点瓶颈3️⃣ 独立扩容:热点分片可单独升级配置,不干扰其他节点类比理解:把一仓库货物(数据)分装到10辆卡车(分片),每辆车只运1/10的货,装卸速度自然快10倍!2.3 轻松应对高并发Redis用内存操作+单线程+IO多路复用三把利剑,轻松切开高并发大山:1️⃣ 内存闪电读写:数据全放内存,比磁盘快10万倍2️⃣ 单线程无锁:避免多线程切换损耗,原子操作不怕并发冲突3️⃣ IO多路复用:一个线程监听万个连接,像银行超级柜员同时处理多窗口业务田螺哥打个比喻吧:Redis就像一个超高效快餐窗口:只卖预制菜(内存数据) → 出餐快一个收银员专注打单(单线程) → 不手忙脚乱智能叫号器管理排队(IO多路复用) → 千人排队也能快速响应据有关测试证明,单机Redis可扛10万+ QPS,分片集群轻松突破百万级并发。3.Redis扛亿级数据可能存在哪些问题以及对应解决方案3.1 热Key问题比如“全服TOP100”榜单,容易造就热点key问题。全服玩家频繁查询 ZREVRANGE leaderboard 0 99(获取Top 100),导致所有请求集中访问 同一个Key(leaderboard)。容易导致单分片CPU和带宽被打满(假设数据分片不均匀)。极端情况下Redis实例崩溃,全服排行榜瘫痪可以通过这些方式解决:1. 多级缓存(Redis + jvm本地缓存)请求优先读本地内存缓存缓存未命中时读Redis集群Redis集群内部缓存Top 100(设置更短TTL)读写分离 + 从库负载均衡主库处理写请求(更新分数)。多个从库轮询处理读请求(查Top 100)分片Key设计操作:将排行榜按分数区间拆分成多个Key,例如:leaderboard:top1(前100名)leaderboard:top2(101~1000名)leaderboard:rest(其他用户)查询逻辑:查Top 100时,只需访问 leaderboard:top1。3.2 内存爆炸存储1亿用户,若每个键占32字节(如 user:123),仅键就需约3.2GB,加上分数和指针,内存压力巨大。优化方案:缩短键名:将 user:123 转换为整数(如123),利用 Redis 的 int 编码优化内存。分片存储:按用户ID哈希分片到多个 Redis 实例,分散压力。3.3 数据持久化风险Redis 宕机可能导致最新数据丢失(即使开启AOF,默认每秒同步一次)。容灾方案:异步双写:更新分数时,同步写入 Kafka,由消费者异步落库 MySQL,用于故障恢复。混合持久化:开启 RDB + AOF,平衡恢复速度与数据完整性。4. 实现方案:分治比如我们要查询王者荣耀巅峰赛的前一百积分的玩家。(其实就是一个TOP N问题)我们可以按照这种思路:按区间拆分动态路由聚合查询4.1. 按区间拆分:把排行榜切成小块蛋糕**怎么拆?高分玩家放「金盘子」:2500分以上 → rank:2500_2600中分玩家放「银盘子」:2400~2500分 → rank:2400_2500低分玩家丢「大锅」:0~2400分 → rank:0_2400为什么快?查Top 100只需翻「金盘子」,不用搅动整个大锅!盘子越小 → 翻找速度越快4.2 动态路由:玩家换区自动导航怎么动?玩家积分变化时,自动检测该去哪:
# 伪代码:2503分该放哪个区间?
if 2500 <= new_score < 2600:
扔进 rank:2500_2600
elif 2400 <= new_score [removed]
同事混用@Transactional和TransactionTemplate被我怼了,三种事务管理
引言“只要你不考虑事务的问题,总有一天事务会来考虑你。”忘记是哪位哲人说的这句话了,在最近的一次code review中,我逐渐体会到了这句话的分量。当我看到同事在一个复杂的业务逻辑中混用了@Transactional注解和TransactionTemplate时,就预感到可能会有问题。我问他为什么这样设计,他的回答让我有些意外:"反正都能实现事务,应该没什么区别吧?"这个回答让我意识到,很多开发者对Spring的事务管理机制其实理解得并不深入。大家往往知道加个@Transactional就能开启事务,但对于什么时候该用声明式、什么时候该用编程式,以及它们背后的工作原理,却很少深究。事务管理看似简单,实则暗藏玄机。今天我们就来深入聊聊Spring中的三种事务管理方式,以及它们各自的适用场景和潜在的风险。顺便吆喝一句,技术大厂跳板机会→机会→前端-后端-测试,待遇和稳定性还不错,感兴趣可以试试~正文三种方式,各有千秋Spring给我们提供了三种处理事务的方式:@Transactional注解、TransactionTemplate和直接使用TransactionManager。就像武侠小说里的三种兵器,每种都有自己的招式和适用场景。@Transactional:最省心的提到事务管理,基本上都会想到去用@Transactional。确实,这玩意儿用起来简单粗暴,在方法上加一个注解就完事了。
@Service
public class UserService {
@Transactional
public void createUser(User user) {
userRepository.save(user);
// 如果这里抛异常,上面的操作会回滚
sendWelcomeEmail(user);
}
}
最大的优点就是无侵入。业务代码依然干净,事务逻辑完全交给Spring在背后搞定了。但是,这种黑盒式的便利必然有代价。Spring通过AOP来实现声明式事务,给方法外面包了一层代理,这就会导致一些让人头疼的问题:内部方法调用失效:这是最容易踩的坑!当同一个类内部的方法调用带有@Transactional注解的方法时,事务不会生效。因为内部调用不会走代理。
@Service
public class UserService {
public void batchCreate(List[removed] users) {
for (User user : users) {
createUser(user); // ❌ 这里事务不会生效!
}
}
@Transactional
public void createUser(User user) {
userRepository.save(user);
}
}
只对public方法生效:私有方法或者protected方法上加@Transactional毫无卵用。要匹配异常类型:默认只有RuntimeException和Error会触发回滚,检查型异常需要特殊配置。虽然这些都是老生常谈的问题了,但在实际开发中,还是有太多因为这种细节出bug的情况。TransactionTemplate:可控的TransactionTemplate算是声明式和编程式事务的一个平衡点。它既保持了一定的灵活性,又不会让代码变得过于复杂。
@Service
public class AccountService {
@Autowired
private TransactionTemplate transactionTemplate;
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
transactionTemplate.execute(status -> {
try {
accountRepository.debit(fromAccount, amount);
accountRepository.credit(toAccount, amount);
return null;
} catch (InsufficientFundsException e) {
// 可以根据业务逻辑决定是否回滚
status.setRollbackOnly();
throw e;
}
});
}
}
用TransactionTemplate的好处是你可以精确控制事务的边界,也不用担心方法调用的问题。而且还可以在事务执行过程中获取到事务的状态信息,做点更细致的控制。但这种方式也有些问题。最明显的就是代码的可读性会变差一些,业务逻辑和事务逻辑会混在一起。另外,如果业务逻辑比较复杂,嵌套层次深的话,代码也会变得不太好维护。当然一般来说都会在额外封装一些方法以供业务侧来调用。TransactionManager:最灵活的如果想要完全的控制权,那就得直接用TransactionManager了。这种方式最灵活,但灵活的代价就是容易出错。
@Service
public class PaymentService {
@Autowired
private PlatformTransactionManager transactionManager;
public void processPayment(Payment payment) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
paymentRepository.save(payment);
notificationService.sendPaymentConfirmation(payment);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
这种方式的好处是可以精确控制事务的每一个细节,比如隔离级别、传播行为、超时时间等等。但是代码量会增加很多,而且容易出错。一旦忘记提交或者回滚,那迟早会有数据不一致的惊喜。该选哪个?经过上面的介绍,到底该怎么选择呢?我的建议是这样的:大部分情况下,@Transactional就够用了。它简单、直接,能解决90%的场景。除非你遇到了它解决不了的问题,否则没必要搞复杂。需要精细控制时,考虑TransactionTemplate。比如你需要在事务执行过程中根据某些条件决定是否回滚,或者需要在一个方法里管理多个事务,这时候就需要用上TransactionTemplate了。极少数情况下才直接用TransactionManager。通常是在写框架代码或者有非常特殊的需求时才会用到。一个例子让我举个实际的例子来说明这三种方式的差异。假设我们要实现一个批量导入用户的功能,要求是:如果某个用户导入失败,不应该影响其他用户的导入。用@Transactional的话,可能会这样写:
@Service
public class UserImportService {
public void importUsers(List[removed] users) {
for (User user : users) {
try {
createUserWithTransaction(user); // 注意,这样写事务不会生效!
} catch (Exception e) {
log.error("导入用户失败: {}", user.getUsername(), e);
}
}
}
@Transactional
public void createUserWithTransaction(User user) {
userRepository.save(user);
userProfileRepository.save(user.getProfile());
}
}
⚠️ 上面的代码中,事务是不会生效的!这是因为importUsers方法调用同一个类中的createUserWithTransaction方法,属于内部方法调用。Spring AOP通过代理实现事务,而同一个类内部的方法调用不会走代理,所以@Transactional注解完全失效。通俗的做法是将有事务的方法抽取到另一个Service中:
@Service
public class UserImportService {
@Autowired
private UserTransactionService userTransactionService;
public void importUsers(List[removed] users) {
for (User user : users) {
try {
userTransactionService.createUserWithTransaction(user); // ✅ 这样才会生效
} catch (Exception e) {
log.error("导入用户失败: {}", user.getUsername(), e);
}
}
}
}
@Service
public class UserTransactionService {
@Transactional
public void createUserWithTransaction(User user) {
userRepository.save(user);
userProfileRepository.save(user.getProfile());
}
}
这种方式的问题是需要额外创建一个Service类,增加了代码复杂度。当然还有另一种方式使用Spring的 AopContext.currentProxy() 拿到当前代理对象再去调用,或者用 ApplicationContext 去从容器里获取代理对象,但始终觉得不太优雅。这也是为什么有时候TransactionTemplate会是更好的选择。用TransactionTemplate的话:
@Service
public class UserImportService {
@Autowired
private TransactionTemplate transactionTemplate;
public void importUsers(List[removed] users) {
for (User user : users) {
try {
transactionTemplate.execute(status -> {
userRepository.save(user);
userProfileRepository.save(user.getProfile());
return null;
});
} catch (Exception e) {
log.error("导入用户失败: {}", user.getUsername(), e);
}
}
}
}
这种方式就很清晰,事务边界在代码中一目了然,而且不用担心方法调用的问题。那TransactionManager呢?写业务的时候基本不太会用到,所以咱也不多费口舌了。踩过的坑说到事务管理,我自己也踩过不少坑。印象最深的一次是在处理一个报表统计的功能时,由于数据量比较大,我想着用只读事务来提高性能:
@Transactional(readOnly = true)
public List[removed] generateReport(ReportQuery query) {
// 复杂的查询逻辑
return reportRepository.findComplexData(query);
}
结果发现性能提升并不明显,后来才知道只读事务在某些数据库连接池配置下,效果并不理想。另一个常见的问题是事务超时。有些开发者喜欢把事务超时设置得很长,生怕业务逻辑执行时间过长导致事务回滚。但这样做的风险很明显,如果真出现了死锁或者其他问题,系统会长时间无响应。
// 这样设置必然是不太好的,极其不推荐,除非你明确知道自己在做什么
@Transactional(timeout = 300) // 5分钟超时
public void processLargeDataSet(List[removed] dataList) {
// 大量数据处理逻辑
}
更好的做法是,把大事务拆分成小事务,或者改写成批处理。混合使用的问题回到文章开头提到的那个同事的代码,混合使用不同的事务管理方式很容易出问题。最常见的情况是事务传播行为的理解偏差。
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
@Transactional
public void processOrder(Order order) {
orderRepository.save(order);
// 这里又开了一个事务模板,传播行为可能和预期不一致
transactionTemplate.execute(status -> {
auditRepository.save(new OrderAudit(order));
return null;
});
}
}
这种混用的代码很蛋疼,很难理解事务的边界,调试起来也很烦。所以在一个service类中尽量保持事务管理方式的一致性,没事少折腾。结尾/总结稍微总结一下。对于新手,建议先把@Transactional的各种特性和坑点搞明白,基本能解决大部分问题。等到真正需要更精细控制的时候,再考虑其他方案。对于有经验的开发者,在已经熟练掌握编程时事务控制的前提下,再多关注事务的性能影响。事务不是万金油,过度使用或者不当使用都会带来不可预料的性能问题。最重要的是,无论选择哪种方式,都要保证代码的可读性和可维护性。技术是为业务服务的,而代码是要给整个团队一起维护的,不要为了炫技让代码变得晦涩难懂。希望这篇文章能帮你理清楚这三种方式的区别和使用场景。毕竟,在数据一致性面前,再小心都不为过。——转载自:一只叫煤球的猫
Vue中render 函数详解
在Vue3中,render函数被用来代替 Vue 2 中的模板语法。它接收一个 h 函数(或者是 `createElement` 函数的别名),并且返回一个虚拟 DOM。render函数的语法结构如下:
render(h) {
return h('div', { class: 'container' }, 'Hello, World!')
}
使用 h 函数创建了一个 div 元素,并设置了 class 属性,并且在 div 元素中添加了文本内容 。h函数的使用方式如下:
h(tag, data, children)
1)tag:表示要创建的元素的标签名,可以是字符串或者是组件选项对象。 2)data:表示要添加到元素的属性、事件等;可以包含普通 HTML 属性、DOM 属性、事件、样式等 3)children:表示要作为子节点添加到元素中的内容。
render(h) {
return h('div', {
class: 'container',
style: { color: 'red' },
on: {
click: () => {
console.log('Clicked!')
}
}
}, 'Hello, World!')
}
除了使用原生的 HTML 标签,我们还可以使用组件选项对象来创建组件。例如:
const MyComponent = {
render(h) {
return h('div', 'Hello, Component!')
}
}
render(h) {
return h(MyComponent)
}
[更多高薪跳板机会→前端-后端-测试,感兴趣可了解~还有AI提升职场计划,等你来]h函数的第二个参数属性的详解1.class - 设置元素的CSS类名,可以使用字符串或对象。对象的键是类名,值是一个布尔值,用于动态地添加或移除类名。
h('div', {
class: 'red'
})
// 创建
h('div', {
class: {
red: true,
bold: false
}
})
// 创建
2.style - 设置元素的内联样式,可以使用字符串、对象或数组。
h('div', {
style: 'color: red;'
})
// 创建
h('div', {
style: {
color: 'red',
fontSize: '14px'
}
})
// 创建
h('div', {
style: [
{ color: 'red' },
{ fontSize: '14px' }
]
})
// 创建
3.attrs - 设置元素的属性,可以使用对象或数组。
h('input', {
attrs: {
type: 'text',
placeholder: 'Enter text'
}
})
// 创建
5.on - 绑定事件处理函数,可以使用对象或数组。
h('button', {
on: {
click: handleClick
}
})
// 创建
h('div', {
on: [
{ click: handleClick },
{ mouseover: handleMouseOver }
]
})
// 创建
6.nativeOn - 属性用于指定元素的原生事件监听器,即直接绑定到DOM元素上的事件,而不是绑定到组件上的自定义事件。
import Vue from 'vue';
Vue.component('my-component', {
render(h) {
return h('button', {
nativeOn: {
click: this.handleClick
}
}, 'Click me');
},
methods: {
handleClick() {
console.log('Button clicked');
}
}
});
new Vue({
el: '#app'
});
7.domProps - 设置元素的DOM属性,比如innerHTML、textContent等。
h('span', {
domProps: {
textContent: 'Hello'
}
})
// 创建Hello
h('div', {
domProps: {
innerHTML: '
8.key - 用于VNode的唯一标识,用于在列表渲染中进行优化。
h('div', {
key: 'my-key',
class: 'red'
})
// 创建
9.ref - 用于给元素或组件设置一个引用标识,以便通过$refs属性访问。
h('input', {
ref: 'myInput',
attrs: {
type: 'text'
}
})
// 创建
10.slot - 用于分发内容到组件的插槽。
h('my-component', [ h('div', { slot: 'header' }, 'Header content'), h('div', { slot: 'footer' }, 'Footer content')])
// 创建
11.scopedSlots - 属性是一个包含插槽信息的对象。它的每个键是插槽的名称,对应的值是一个函数或者一个具有render函数的对象。
// 示例组件
const MyComponent = {
render(h) {
return h('div', [
h('h1', 'Hello World'),
h('slot', {
// 插槽名称为default
scopedSlots: {
default: props => h('p', `Scoped Slot Content: ${props.text}`)
}
})
])
}
}
// 父组件
new Vue({
render(h) {
return h('my-component', {
// 通过scopedSlots属性传递插槽内容
scopedSlots: {
default: props => h('div', `Parent Slot Content: ${props.text}`)
}
})
},
components: {
MyComponent
}
}).$mount('#app')
// 最终渲染的结果是:
12.directives-属性是一个对象,用来设置指令。指令是一种特殊的属性,通过设置指令可以在元素上执行一些自定义的逻辑或者操作。
import Vue from 'vue';
Vue.directive('my-directive', {
// 指令的生命周期钩子函数
bind: function (el, binding, vnode) {
// 在绑定时被调用,可以在这里进行初始化设置
// el是指令绑定的元素
// binding是一个对象,包含了指令的相关信息,如指令参数、修饰符、绑定值等
// vnode是指令所在的虚拟DOM节点
el.style.color = binding.value;
},
update: function (el, binding, vnode) {
// 在节点更新时被调用,可以在这里对节点进行更新
el.style.color = binding.value;
}
});
var vm = new Vue({
el: '#app',
render: function (h) {
return h('div', {
directives: [{
name: 'my-directive',
value: 'red'
}],
style: {
width: '100px',
height: '100px',
background: 'yellow'
}
}, 'Hello, Vue.js!');
}
});
——转载自:夜熵
Paragraph
' } }) // 创建Paragraph
Hello World
Parent Slot Content: Child Slot Text