当前位置: 首页 > news >正文

Express 项目中选择 EJS 模板引擎的实战指南

1. 为什么在 Express 项目里坚持用 EJS,而不是 Pug 或 Handlebars?

我第一次在生产环境里替换模板引擎,是接手一个上线半年的后台管理系统。原团队用的是 Pug(当时还叫 Jade),代码缩进严格得像军训——少一个空格,整个页面白屏;多一个换行,渲染就报错。更麻烦的是,前端同事改个按钮文案,得先问后端:“这个 div 是嵌套在第几层 .card-body 里?class 名要不要加前缀?”——协作成本高到离谱。

后来我把它全换成 EJS,不是因为 EJS 多先进,而是它最“不折腾”。EJS 的核心逻辑就一条:HTML 是主体,JavaScript 是插件,不是反过来。你写<div class="user-name"><%= user.name %></div>,它就老老实实把user.name插进去;你写<% if (user.isAdmin) { %><button>删除用户</button><% } %>,它就按条件渲染。没有自创语法,没有强制缩进,没有隐式闭合标签。对刚学 Node 的新人来说,打开.ejs文件就像打开.html文件一样自然;对有经验的开发者来说,它不抢控制权,你随时可以切回纯 HTML 写法,或者嵌入复杂逻辑——只要逻辑写得清楚,EJS 绝不干涉。

这背后其实是模板引擎的设计哲学差异。Pug 把 HTML 当作需要被“优化”的冗余物,于是发明了一套新 DSL;Handlebars 追求逻辑剥离,连if都得注册 helper;而 EJS 的选择很务实:不改变开发者的肌肉记忆,只解决变量插入、循环、复用这三个刚需。它不试图教你“怎么写模板”,它只帮你“把数据塞进模板”。

所以当我在团队内部做技术选型评审时,没讲什么抽象架构图,就放了三段等效代码:

  • Pug 版本(带缩进、管道符、隐式标签):

    ul.users each user in users li(class=user.active ? 'active' : '') a(href='/user/' + user.id)= user.name
  • Handlebars 版本(需预编译、注册 helper、双大括号):

    <ul class="users"> {{#each users}} <li class="{{#if active}}active{{/if}}"> <a href="/user/{{id}}">{{name}}</a> </li> {{/each}} </ul>
  • EJS 版本(就是 HTML + 嵌入 JS):

    <ul class="users"> <% users.forEach(function(user) { %> <li class="<%= user.active ? 'active' : '' %>"> <a href="/user/<%= user.id %>"><%= user.name %></a> </li> <% }); %> </ul>

结果所有前端和后端同学都指着第三段说:“这个我一眼就懂。”——技术选型从来不是比谁更炫,而是比谁让团队少踩坑、少解释、少返工。EJS 就是那个“不用解释”的答案。

提示:EJS 不是万能的,它不适合超大型组件化前端项目(比如需要 Vue/React 式响应式更新的场景),但对绝大多数 Express 后台管理页、内容展示页、表单提交页、邮件模板生成等场景,它的开发效率、可读性、调试便利性,至今没被真正超越。

2. 从零配置一个支持 EJS 的 Express 项目:避开 npm install 后的五个典型失败

很多人卡在第一步:npm install ejs express之后,浏览器打开http://localhost:3000显示 “Cannot GET /” 或者直接报错Error: Failed to lookup view。这不是你代码写错了,而是 Express 默认根本不认识.ejs文件——它连模板引擎都没注册。下面是我实测过、踩过坑、验证过的完整初始化流程,每一步都带原理说明。

2.1 初始化项目与基础依赖安装

先确保你本地有 Node 环境(别纠结版本,Node 18+ 和 20+ 都稳)。执行:

mkdir my-express-app && cd my-express-app npm init -y npm install express ejs

注意:不要装ejs-mateexpress-ejs-layouts这类封装库。新手一上来就装这些,等于还没学会走路就想跑马拉松。它们会掩盖底层机制,一旦出问题,你连日志都看不懂。我们先用原生 API 把路走通。

2.2 创建标准目录结构(关键!)

Express 对目录结构没强制要求,但 EJS 有默认约定。如果你不按它预期的路径放文件,res.render()就会找不到视图。必须建立如下结构:

my-express-app/ ├── app.js # 入口文件 ├── package.json ├── views/ # ← 必须叫这个名字!Express 默认只在这里找 .ejs │ ├── index.ejs # 主页模板 │ └── partials/ # ← 子模板推荐放这里(非强制,但强烈建议) │ └── header.ejs └── public/ # 静态资源(CSS/JS/图片) └── style.css

注意:views文件夹名不能改成templatessrc/views。Express 的app.set('views', ...)可以指定路径,但新手请先用默认值,避免引入额外变量。等你跑通第一个页面再改。

2.3 编写最小可行 app.js(含三处易错点)

这是最容易出错的代码段,我标出了三个新手必踩的坑:

const express = require('express'); const app = express(); const PORT = 3000; // ✅ 坑1:必须显式设置视图引擎,且顺序不能错 app.set('view engine', 'ejs'); // ← 告诉 Express:我用 ejs 当模板引擎 app.set('views', './views'); // ← 告诉 Express:模板文件在 ./views 目录 app.set('view engine', 'ejs'); // ← 这行必须在 set('views') 之后! // ✅ 坑2:必须启用静态资源服务,否则 CSS/JS 加载 404 app.use(express.static('public')); // ✅ 坑3:路由必须在所有中间件之后定义,且路径要匹配 app.get('/', (req, res) => { // 渲染 views/index.ejs,传入数据对象 res.render('index', { title: '我的首页', message: 'Hello from EJS!' }); }); app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });

常见错误还原:

  • 错误1app.set('views', ...)写在app.set('view engine', ...)之后 → Express 找不到模板路径,报Failed to lookup view
  • 错误2:忘了app.use(express.static('public'))→ 浏览器控制台疯狂报GET http://localhost:3000/style.css net::ERR_ABORTED 404,页面光秃秃没样式。
  • 错误3res.render('index')写成res.render('./views/index')→ Express 会拼成./views/./views/index.ejs,路径爆炸。

2.4 编写 index.ejs(验证基础渲染)

views/index.ejs中写:

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title><%= title %></title> <link rel="stylesheet" href="/style.css"> <!-- 注意:/ 开头,走 static 中间件 --> </head> <body> <h1><%= message %></h1> <p>当前时间:<%= new Date().toLocaleString() %></p> </body> </html>

同时在public/style.css中写一行测试样式:

body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto; } h1 { color: #2c3e50; }

启动服务:

node app.js

打开http://localhost:3000,如果看到带样式的标题和时间,恭喜——你的 EJS 环境已通。如果失败,请回头检查上面三个“坑”,90% 的问题都在这里。

实操心得:我见过太多人花两小时查“EJS 不渲染”,最后发现只是views文件夹名拼错了,或者app.set('views', ...)漏写了。建议把console.log(app.get('views'))加在app.listen()前,确认路径输出是否正确。这是最朴实也最有效的调试手段。

3. EJS 核心语法实战:从变量插入到布局复用,拒绝“只会 <%= %>”

很多教程只教<%= %><% %>,导致开发者遇到复杂页面就懵:导航栏要复用怎么办?不同页面要共享头部 footer 怎么办?数据要分页怎么写?其实 EJS 提供了四类核心能力,覆盖 95% 的模板需求。下面用真实场景代码演示,每段都附带“为什么这么写”的原理。

3.1 变量输出:<%= %>vs<%- %>的生死之别

<% const rawHtml = '<strong>危险内容</strong>'; %> <% const safeText = '普通文本'; %> <!-- ✅ 安全输出:自动转义 HTML 实体 --> <p><%= safeText %></p> <!-- 输出:普通文本 --> <p><%= rawHtml %></p> <!-- 输出:&lt;strong&gt;危险内容&lt;/strong&gt; --> <!-- ⚠️ 非安全输出:直接插入 HTML(仅用于可信内容) --> <p><%- rawHtml %></p> <!-- 输出:<strong>危险内容</strong>(加粗显示) -->

原理<%= %>调用的是escapeHTML()函数,把<变成&lt;>变成&gt;,防止 XSS 攻击。这是 Express+EJS 的默认安全策略。只有当你明确知道rawHtml是后端生成的、绝对可信的 HTML(比如富文本编辑器保存的内容),才用<%- %>。日常开发中,99% 的场景用<%= %>就够了。

踩坑记录:曾有个项目,用户昵称字段用了<%- %>渲染,结果有人昵称设为<script>alert(1)</script>,所有访问他主页的人都弹窗。修复方案就是统一换成<%= %>,前端再用DOMPurify做二次过滤。

3.2 条件与循环:用 JavaScript 原生语法,不造轮子

EJS 不提供@if{{#each}}这种语法糖,它直接让你写 JS。好处是——你写的 JS,就是 JS:

<!-- 条件判断 --> <% if (user && user.role === 'admin') { %> <button class="btn-danger">删除用户</button> <% } else if (user) { %> <button class="btn-secondary">编辑资料</button> <% } else { %> <a href="/login" class="btn-primary">登录</a> <% } %> <!-- 数组遍历(推荐 forEach,语义清晰) --> <ul class="product-list"> <% products.forEach(function(product) { %> <li class="product-item"> <h3><%= product.name %></h3> <p>¥<%= product.price.toFixed(2) %></p> <button onclick="addToCart(<%= product.id %>)">加入购物车</button> </li> <% }); %> </ul> <!-- for 循环(兼容性更好,适合老项目) --> <% for (let i = 0; i < comments.length; i++) { %> <div class="comment"> <strong><%= comments[i].author %>:</strong> <p><%= comments[i].content %></p> </div> <% } %>

关键细节

  • forEach回调函数里,product是局部变量,不会污染全局作用域;
  • for循环中,i是循环变量,注意别在循环外误用;
  • 所有 JS 代码块<% ... %>内不能有return(会中断渲染),但可以break/continue

3.3 局部模板(Partials):把重复代码抽成“零件”

这是 EJS 最被低估的能力。所谓partials,就是把公共模块(如 header、footer、侧边栏)单独抽成.ejs文件,然后在主模板里“引用”。它不是黑魔法,就是一次include

  1. views/partials/header.ejs中写:

    <header class="site-header"> <nav> <a href="/">首页</a> <a href="/products">商品</a> <a href="/about">关于</a> </nav> </header>
  2. views/index.ejs中引用:

    <!DOCTYPE html> <html> <head><title><%= title %></title></head> <body> <!-- ✅ 正确:使用相对路径,从 views 目录开始算 --> <%- include('partials/header') %> <main> <h1><%= message %></h1> </main> <!-- ✅ footer 同理 --> <%- include('partials/footer') %> </body> </html>

为什么路径是'partials/header'而不是'./partials/header'
因为include()是 EJS 的内置函数,它的工作目录就是app.set('views', ...)指定的根目录(即./views)。你写'partials/header',EJS 自动拼成./views/partials/header.ejs。加./反而会报错。

实操技巧:include()支持传参,让局部模板更灵活。比如header.ejs需要动态标题:

<%- include('partials/header', { pageTitle: '用户列表页' }) %>

然后在header.ejs里用<%= pageTitle || '默认标题' %>接收。

3.4 布局模板(Layouts):一套壳,千张脸

partials解决“复用零件”,layouts解决“统一外壳”。想象一个网站:所有页面都有相同的<html><head><body>结构,只有<main>内容不同。这时,你应该用layout.ejs作为母版:

  1. 创建views/layouts/main.ejs

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title><%= title || '我的网站' %></title> <link rel="stylesheet" href="/style.css"> </head> <body> <%- include('partials/header') %> <main class="container"> <!-- ✅ 关键:用 <%- body %> 占位,子页面内容将注入此处 --> <%- body %> </main> <%- include('partials/footer') %> </body> </html>
  2. 创建子页面views/dashboard.ejs(不写完整 HTML,只写<main>里的内容):

    <h2>仪表盘</h2> <div class="stats-grid"> <div class="stat-card">用户数:<%= stats.users %></div> <div class="stat-card">订单数:<%= stats.orders %></div> </div>
  3. 在路由中指定 layout:

    app.get('/dashboard', (req, res) => { res.render('dashboard', { title: '管理后台 - 仪表盘', stats: { users: 1247, orders: 89 } }); });

原理:EJS 本身不内置 layout 功能,但res.render()会把子模板内容作为字符串传给 layout 的body变量。你只需在 layout 里用<%- body %>接收即可。这是最轻量、最可控的布局方案。

注意:如果你用express-ejs-layouts库,它会自动帮你注入body,但会增加一层抽象。我建议新手先手写 layout,理解透原理后再用库。

4. 生产环境避坑指南:EJS 在真实项目中的六个血泪教训

EJS 上手快,但真正在高流量、多团队协作的生产环境里,一堆隐藏雷等着你。下面这些不是理论推演,而是我在线上系统里亲手踩过、修过、监控过的实战教训,每一条都配了可落地的解决方案。

4.1 错误1:模板文件编码为 GBK,导致中文乱码成 “某些文本”

现象:本地开发一切正常,部署到 Linux 服务器后,.ejs文件里的中文全变成乱码,页面显示一堆方块或问号。

根因:Windows 记事本默认保存为 GBK 编码,而 Linux 服务器(及 Node.js)默认按 UTF-8 读取文件。EJS 读取模板时,把 GBK 字节流当 UTF-8 解码,必然错乱。

解决方案

  • 开发阶段:强制所有.ejs文件用 UTF-8 无 BOM 编码保存。VS Code 用户:右下角点击编码名称 → 选择 “Save with Encoding” → “UTF-8”。
  • CI/CD 阶段:在构建脚本中加入校验(Linux 下):
    # 检查 views 目录下所有 .ejs 文件是否为 UTF-8 find ./views -name "*.ejs" -exec file -i {} \; | grep -v "charset=utf-8"
  • 终极保险:在app.js中显式指定模板编码(Node.js 16+):
    const ejs = require('ejs'); ejs.options = { ...ejs.options, encoding: 'utf8' // 强制以 UTF-8 读取模板文件 };

个人经验:这个坑我踩过三次。第一次花了 4 小时查 nginx 配置,第二次怀疑是数据库连接问题,第三次才意识到是文件编码。现在我的 VS Code 设置里,"files.encoding": "utf8"是第一行。

4.2 错误2:res.render()传入 undefined 数据,页面崩溃白屏

现象:某个路由偶尔返回空白页,日志里没有错误,res.render()调用后直接结束。

根因:EJS 渲染时遇到undefinednull,比如<%= user.name %>userundefined,EJS 默认抛出TypeError: Cannot read property 'name' of undefined,但 Express 默认错误处理中间件没捕获,导致请求静默失败。

解决方案

  • 防御性编程:永远假设数据可能为空:
    <!-- ❌ 危险 --> <h2><%= user.name %></h2> <!-- ✅ 安全(推荐) --> <h2><%= user?.name || '未知用户' %></h2> <!-- ✅ 安全(兼容老 Node) --> <h2><%= (user && user.name) || '未知用户' %></h2>
  • 全局错误兜底:在 Express 中添加错误处理中间件:
    // 放在所有路由之后 app.use((err, req, res, next) => { console.error('Template render error:', err); res.status(500).render('error', { message: '页面加载失败,请稍后重试', error: process.env.NODE_ENV === 'development' ? err.message : {} }); });

4.3 错误3:include()路径错误,报Error: Could not find include file

现象:<%- include('partials/header') %>报错,提示找不到文件,但文件明明存在。

排查链路(必须按顺序):

  1. 检查app.set('views', ...)输出的路径是否正确:console.log(app.get('views'))
  2. 检查include()路径是否相对于views目录(不能有./../);
  3. 检查文件扩展名:include('partials/header')会自动尝试header.ejsheader.html,但如果你文件叫header.ejs.bak,它找不到;
  4. 检查大小写:Linux 文件系统区分大小写,Header.ejsheader.ejs是两个文件。

快速验证法:在app.js中临时加一段代码,手动读取文件:

const fs = require('fs'); console.log(fs.readFileSync('./views/partials/header.ejs', 'utf8'));

如果这行报错,说明路径或权限有问题;如果不报错,那一定是include()调用方式不对。

4.4 错误4:大量include()导致渲染变慢,首屏 TTFB 超过 2s

现象:页面包含 10+ 个include(),用户明显感觉到卡顿,Chrome DevTools 显示TTFB(Time to First Byte)飙升。

根因:每个include()都是一次文件 I/O 操作。Node.js 是单线程,同步读取多个小文件会阻塞事件循环。

优化方案

  • 合并局部模板:把高频一起出现的partials合并成一个文件,比如nav-and-search.ejs
  • 启用 EJS 缓存(开发关,生产开):
    if (process.env.NODE_ENV === 'production') { app.set('view cache', true); // 启用模板缓存 }
    开启后,EJS 第一次读取并编译模板,后续直接执行编译后的函数,I/O 归零;
  • 预编译模板(高级):用ejs.compile()提前编译,存入内存:
    const compiledIndex = ejs.compile( fs.readFileSync('./views/index.ejs', 'utf8') ); // 使用时:compiledIndex({ title: 'xxx' })

4.5 错误5:<%- body %>在 layout 中被多次渲染,页面结构错乱

现象:一个页面里出现了两次 header,或者 footer 被渲染了三遍。

根因:在子模板中误写了<%- include('layouts/main') %>,而不是靠res.render()的机制自动注入bodyinclude()是简单文本替换,它会把 layout 内容原样插入,如果 layout 里又有<%- body %>,而你又在子模板里手动调用了include,就会形成递归嵌套。

正确姿势

  • 子模板(如dashboard.ejs只写内容,不写 HTML 结构
  • Layout(如main.ejs只写结构,用<%- body %>占位
  • res.render('dashboard', {...})会自动把dashboard.ejs的内容作为body传给 layout。

一句话口诀include()是“复制粘贴”,res.render()+layout是“内容注入”。二者不可混用。

4.6 错误6:EJS 模板被搜索引擎抓取为纯文本,SEO 效果差

现象:Google 搜索你的网站关键词,结果页显示的是<%= title %>这样的原始代码,而不是真实标题。

根因:EJS 是服务端渲染(SSR),但如果你的页面是通过 AJAX 加载的,或者 Nginx 配置错误返回了.ejs源文件,搜索引擎爬虫就看不到渲染后的内容。

验证方法

  • 在 Chrome 里按Ctrl+U查看网页源代码,确认看到的是<h1>Hello from EJS!</h1>还是<h1><%= message %></h1>
  • 用 curl 模拟爬虫:curl -I http://your-domain.com,检查Content-Type是否为text/html

解决方案

  • 确保 Express 路由返回的是text/html,不是text/plain
  • Nginx 配置中,禁止直接暴露.ejs文件:
    location ~ \.ejs$ { deny all; }
  • app.js中添加 SEO 友好头:
    app.use((req, res, next) => { res.set('X-Content-Type-Options', 'nosniff'); res.set('X-Frame-Options', 'DENY'); next(); });

最后提醒:EJS 本身不影响 SEO,影响 SEO 的是你的部署方式和 HTTP 响应头。只要curl http://localhost:3000返回的是渲染后的 HTML,搜索引擎就能正常索引。

5. EJS 进阶实战:用真实业务场景串联所有知识点

前面讲的都是“零件”,现在我们组装一台“整车”——一个真实的用户管理后台页面。它会用到:路由传参、partials 复用、layout 统一、条件判断、循环列表、表单提交、错误提示。代码全部可运行,你复制粘贴就能看到效果。

5.1 需求描述与最终效果

我们要实现一个/users页面,功能包括:

  • 展示用户列表(从模拟数据读取);
  • 每个用户显示姓名、邮箱、状态(激活/禁用);
  • 状态旁有“启用/禁用”按钮(点击发送 AJAX 请求);
  • 页面顶部有搜索框,可按姓名过滤;
  • 全局 header 和 footer 复用;
  • 无用户时显示友好提示。

最终效果:一个干净、可交互、符合生产标准的后台列表页。

5.2 完整代码实现(含注释)

1.app.js—— 路由与数据准备

const express = require('express'); const app = express(); // 设置视图 app.set('view engine', 'ejs'); app.set('views', './views'); // 静态资源 app.use(express.static('public')); app.use(express.urlencoded({ extended: true })); // 解析表单 app.use(express.json()); // 解析 JSON // 模拟用户数据(实际项目中从数据库读取) const users = [ { id: 1, name: '张三', email: 'zhangsan@example.com', active: true }, { id: 2, name: '李四', email: 'lisi@example.com', active: false }, { id: 3, name: '王五', email: 'wangwu@example.com', active: true } ]; // GET /users:渲染用户列表页 app.get('/users', (req, res) => { const { q = '' } = req.query; // 获取搜索关键词 let filteredUsers = users; if (q) { filteredUsers = users.filter(user => user.name.includes(q) || user.email.includes(q) ); } res.render('users', { title: '用户管理', users: filteredUsers, searchQuery: q }); }); // POST /users/toggle/:id:切换用户状态(AJAX 接口) app.post('/users/toggle/:id', (req, res) => { const userId = parseInt(req.params.id); const user = users.find(u => u.id === userId); if (user) { user.active = !user.active; return res.json({ success: true, active: user.active }); } res.status(404).json({ success: false, error: '用户不存在' }); }); app.listen(3000, () => { console.log('Server running on http://localhost:3000'); });

2.views/layouts/main.ejs—— 全局布局

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><%= title %></title> <link rel="stylesheet" href="/style.css"> </head> <body> <%- include('partials/header') %> <main class="main-content"> <div class="container"> <%- body %> </div> </main> <%- include('partials/footer') %> <!-- 公共 JS --> <script src="/script.js"></script> </body> </html>

3.views/partials/header.ejs—— 复用头部

<header class="navbar"> <div class="container"> <h1 class="logo">Admin Panel</h1> <nav class="nav-links"> <a href="/users">用户管理</a> <a href="/settings">系统设置</a> </nav> </div> </header>

4.views/partials/footer.ejs—— 复用底部

<footer class="site-footer"> <div class="container"> <p>&copy; <%= new Date().getFullYear() %> Admin System. All rights reserved.</p> </div> </footer>

5.views/users.ejs—— 核心页面(只写内容)

<h1 class="page-title"><%= title %></h1> <!-- 搜索表单 --> <form method="GET" class="search-form"> <input type="text" name="q" placeholder="搜索姓名或邮箱..." value="<%= searchQuery %>" > <button type="submit">搜索</button> <% if (searchQuery) { %> <a href="/users" class="clear-search">清除</a> <% } %> </form> <!-- 用户列表 --> <% if (users.length === 0) { %> <div class="empty-state"> <p>暂无用户。请检查搜索关键词,或<a href="/users">查看全部用户</a>。</p> </div> <% } else { %> <table class="user-table"> <thead> <tr> <th>姓名</th> <th>邮箱</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> <% users.forEach(function(user) { %> <tr> <td><%= user.name %></td> <td><%= user.email %></td> <td> <span class="status-badge <%= user.active ? 'active' : 'inactive' %>"> <%= user.active ? '已激活' : '已禁用' %> </span> </td> <td> <button class="toggle-btn" >* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: "Helvetica Neue", Arial, sans-serif; line-height: 1.6; } .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; } .navbar { background: #3498db; color: white; padding: 1rem 0; } .logo { display: inline-block; font-size: 1.5rem; margin-right: 2rem; } .search-form { margin: 1.5rem 0; } .search-form input { padding: 0.5rem; width: 300px; margin-right: 0.5rem; } .user-table { width: 100%; border-collapse: collapse; margin-top: 1rem; } .user-table th, .user-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #eee; } .status-badge { padding: 0.25rem 0.5rem; border-radius: 3px; font-size: 0.85rem; } .status-badge.active { background: #2ecc71; color: white; } .status-badge.inactive { background: #e74c3c; color: white; } .toggle-btn { background: #3498db; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 3px; cursor: pointer; } .toggle-btn:hover { background: #2980b9; }

5.3 运行与验证步骤

  1. 创建上述所有文件,确保目录结构正确;
  2. 在项目根目录执行node app.js
  3. 浏览器打开http://localhost:3000/users
http://www.jsqmd.com/news/1068393/

相关文章:

  • 网址收藏8325
  • 深度解析:JPMML-LightGBM 企业级模型部署技术方案
  • CentOS MySQL服务部署实操:从安装到生产就绪全链路解析
  • CSDN勋章体系全景解析与获取指南
  • windows脚本
  • CrossRef API资源组件全解析:works、funders与members的终极指南
  • MCU低功耗模式下ADC配置与精度优化实战指南
  • Android+PHP+MySQL登录系统实战:从环境搭建到安全加固
  • FrogBase核心功能详解:下载、转录、嵌入、搜索全流程解析
  • Preact SSR实战:Unistore状态同步与Router同构路由详解
  • Ubuntu 18.04 部署 Eclipse Theia 云原生 IDE 实战指南
  • [LeetCode] 104、二叉树的最大深度
  • 为什么这个DevOps工具集合能入选GitHub Trending?awesome-devops背后的完整故事
  • QtBitcoinTrader安全机制详解:AES-256加密与RSA保护如何保障你的资产安全 [特殊字符]
  • python 零碎知识 super用法
  • Rcpp包开发全流程:从C++代码到CRAN发布的完整指南
  • Burp Suite高级功能使用指南:会话管理与自动化测试全攻略
  • 基于ddddocr与Captcha-Killer构建高精度验证码自动化识别工具链
  • FastStream核心功能详解:6倍加速下载、智能字幕、音视频调节全解析
  • python web自动化selenium【元素定位与操作】及弹窗(alert/confirm/prompt)操作及上传附件7
  • 通俗易懂理解RANSAC算法
  • AI编程提示词工程:从324条实战样本看工作流逆向设计
  • 如何用AMD Ryzen AI软件构建本地智能助手:一个完整的零配置开发指南
  • HACG数据管理终极指南:本地缓存与网络同步的最佳实践
  • k8s环镜搭建(续2)
  • synp与yarn import对比:哪款工具更适合你的项目需求
  • docker安装svn
  • Modbus协议报文深度解析:从字节结构到实战调试
  • DPF外部UI开发:跨进程插件界面实现原理与实战指南
  • Coblocks入门教程:零基础打造响应式WordPress网站的7个步骤