本文还有配套的精品资源,点击获取
简介:eWebEditor V7.3是一款基于ASP技术的富文本在线网页编辑器,支持IE、Firefox、Chrome等主流浏览器,具备良好的跨平台兼容性与稳定性。该编辑器提供文本、图像、表格、超链接等内容的可视化编辑,支持HTML源码修改、实时预览、图片上传管理及安全过滤机制,并兼容简体中文GB2312编码,同时可通过API扩展功能。本项目涵盖eWebEditor的安装配置、页面集成、自定义设置与二次开发要点,适用于各类内容管理系统中的文本编辑需求。
eWebEditor V7.3 技术深度解析:从架构设计到安全扩展的全链路实战
在企业级内容管理系统(CMS)刚刚兴起的年代,富文本编辑器还是一个“奢侈品”。那时没有 React、Vue 这样的现代前端框架,也没有 Webpack 构建工具,开发者们面对的是浏览器碎片化严重、DOM API 接口不统一、服务端脚本语言性能有限等一系列挑战。而就在那个时代, eWebEditor V7.3 像一颗流星划过早期 Web 开发的夜空——它虽不是最早的所见即所得(WYSIWYG)编辑器,但却是为数不多真正落地于 ASP 项目并广泛使用的成熟产品。
今天回看,它的技术栈早已“过时”:VBScript 写后端、IE-only 的兼容性、基于 exec***mand 的命令驱动模式……可正是这些“老古董”,构成了我们理解现代编辑器演进路径的重要一环。✨
更关键的是,很多仍在运行的企业系统里,你依然会遇到它。维护?迁移?审计?重构?无论哪种场景,深入理解其底层机制都至关重要。
那我们就别绕弯子了——来吧,一起钻进这个经典编辑器的“心脏”,看看它是如何用一套看似简单的三层结构,撑起无数企业的内容创作需求的。🚀
核心架构:iframe + *** + exec***mand 的三重奏 🎵
想象一下你要做一个“所见即所得”的编辑器,用户点个加粗按钮,文字就变粗了,还能实时保存成 HTML。这事儿听起来简单,但在十几年前可不是件容易的事儿。
eWebEditor 的解决方案很“微软范儿”: 前端靠浏览器原生能力,后端靠 *** 组件加持,中间层用 JavaScript 桥接一切。
整个系统采用典型的三层架构:
- 表现层 :一个
<iframe>作为编辑区域,承载contenteditable或designMode = "on"的文档; - 控制层 :JavaScript 调用
document.exec***mand()执行格式化命令,并与工具栏交互; - 服务层 :ASP 页面通过
Server.CreateObject()实例化注册在系统中的 *** 组件,完成初始化配置、HTML 过滤和持久化存储。
这种设计的好处在于—— 解耦清晰、职责分明 。前端负责展示和交互,后端负责逻辑和安全,中间层只是个“传话筒”。
不过有趣的是,这个“传话筒”其实并不被动。比如当你访问一个包含 eWebEditor 的 .asp 页面时,服务器并不会直接返回静态 HTML,而是先执行 VBScript 脚本,动态生成一段带有隐藏 textarea、iframe 和 JS 初始化代码的完整页面结构。
这就引出了一个问题:为什么不能像现在的编辑器那样,把所有逻辑放在客户端?
答案是: 为了权限控制和个性化定制 。
试想,不同角色的用户应该看到不同的工具栏按钮。管理员可以插入表格、上传文件,普通员工只能加粗斜体。如果把这些逻辑放前端,稍懂点 F12 就能绕过限制。而 eWebEditor 在服务端生成 HTML 时就已经决定了谁能看到什么功能,从根本上杜绝了越权操作的可能性。
所以你看,这不是技术落后,而是一种 以安全性优先的设计哲学 。💡
ASP 集成:当 VBScript 遇上 *** 组件 💥
说到 ASP(Active Server Pages),现在年轻人可能只在面试题里听过这个名字。但它曾是微软主推的 Web 开发动态技术,核心原理就是在 HTML 中嵌入 VBScript 或 JScript 代码,由 IIS 调用 asp.dll 解析执行。
当这样一个“古老”的服务端环境遇上 eWebEditor,会发生什么化学反应?
请求生命周期:一次 .asp 页面加载的背后
我们不妨从一次最普通的页面请求说起:
用户输入
http://example.***/edit.asp→ 浏览器发送 HTTP 请求 → IIS 接收请求 → 判断扩展名为.asp→ 交给asp.dll处理 → 执行 VBScript 代码 → 输出最终 HTML → 返回给浏览器
整个过程可以用下面这张 Mermaid 图清晰呈现:
graph TD
A[客户端发起 .asp 请求] --> B{IIS 判断扩展名}
B -->|是 .asp| C[交由 asp.dll 处理]
C --> D[解析 <% %> 脚本块]
D --> E[执行 VBScript/JScript]
E --> F[调用 *** 组件或数据库]
F --> G[生成 HTML 输出流]
G --> H[通过 Response 发送回客户端]
H --> I[浏览器渲染页面]
注意第 F 步:“调用 *** 组件或数据库”。这就是 eWebEditor 的灵魂所在。
*** 组件登场:DLL 如何掌控编辑器命运?
***(***ponent Object Model)是 Windows 平台下的组件复用模型。你可以把它理解为一种跨语言的对象接口标准——C++ 写的 DLL 可以被 VBScript 调用,Java 写的 ActiveX 控件也能嵌入网页。
eWebEditor 正是利用这一点,将核心功能封装成多个 *** 组件,例如:
| 组件名称 | ProgID | 主要功能 |
|---|---|---|
| eWebEditor.Editor | eWebEditor.Editor | 编辑器主控件,生成 HTML 输出 |
| eWebEditor.Filter | eWebEditor.Filter | HTML 标签过滤与脚本剥离 |
| eWebEditor.Uploader | eWebEditor.Uploader | 文件上传处理与类型校验 |
| Scripting.FileSystemObject | Scripting.FileSystemObject | 文件读写、目录管理 |
| ADODB.Connection | ADODB.Connection | 数据库连接与 SQL 执行 |
这些组件都需要通过 regsvr32.exe 注册到系统注册表中才能使用。一旦注册成功,ASP 就可以用一行代码实例化它们:
Set editor = Server.CreateObject("eWebEditor.Editor")
然后就可以调用各种方法了:
editor.SetBasePath "/editor/"
editor.AllowImageUpload = True
editor.UploadDirectory = "uploads/" & Session("UserID")
htmlOutput = editor.GetEditorHTML("txtContent")
看到没?连上传目录都可以动态设置!这意味着每个用户的文件都会存到独立子目录下,极大提升了安全性。👏
而且最关键的是,这些敏感逻辑都在服务端执行,前端根本看不到。哪怕你篡改了表单字段名,只要服务端绑定的是 txtContent ,你就必须按规矩来。
初始化流程:服务端如何“绘制”编辑器?
很多人以为 eWebEditor 是纯前端控件,其实不然。它的初始化是一个多阶段的过程:
- ASP 页面加载 → 2. 读取配置文件或数据库设置 → 3. 创建 *** 对象 → 4. 生成 HTML 片段 → 5. 注入 JS 初始化脚本 → 6. 浏览器端激活编辑器
举个例子:
<!--#include file="config.asp"-->
<%
Set ewe = Server.CreateObject("eWebEditor.Editor")
ewe.SetBasePath "/editor/"
ewe.AllowImageUpload = True
ewe.UploadDirectory = "uploads/" & Session("UserID")
ewe.LoadConfigFromDB()
Dim htmlOutput
htmlOutput = ewe.GetEditorHTML("txtContent") ' 返回完整的编辑器 HTML
%>
<!DOCTYPE html>
<html>
<head><title>编辑页面</title></head>
<body>
<form action="save.asp" method="post">
<%=htmlOutput%>
<input type="submit" value="保存">
</form>
</body>
</html>
这段代码干了三件事:
- 创建编辑器对象;
- 设置参数;
- 获取 HTML 输出并插入页面。
最后浏览器收到的是一段已经组装好的 DOM 结构,包括 iframe、工具栏按钮、隐藏 textarea 和 JS 初始化代码。
这才是真正的“服务端驱动前端渲染”啊!🤯
数据流转:从点击提交到数据入库 📤
好了,用户终于编辑完内容,点了“提交”按钮。接下来发生了什么?
表单提交:同步策略 vs AJAX?
eWebEditor 使用传统表单 POST 提交方式。虽然不够酷炫,但在那个年代却是最稳妥的选择。
它的核心技巧在于: 利用 JavaScript 把 iframe 中的内容同步回隐藏的 <textarea> 。
结构大概是这样:
<form id="frm" action="save.asp" method="post">
<textarea name="editor_content" id="editor_content" style="display:none;"></textarea>
<div id="eWebEditorControl"></div>
<button type="submit">保存</button>
</form>
<script>
var editor = new eWebEditor('editor_content');
editor.EditorPath = '/editor/';
editor.init();
// 离开页面前确保内容已同步
window.onbeforeunload = function() {
editor.sync();
};
</script>
其中 editor.sync() 是关键方法:
this.sync = function() {
var textarea = document.getElementById(this.textareaId);
var iframeDoc = document.frames[this.editorId].document;
textarea.value = iframeDoc.body.innerHTML;
}
这样一来,当用户提交表单时, Request.Form("editor_content") 就能拿到完整的 HTML 内容啦!
后端接收:小心 Request.Form 的坑 ⚠️
你以为 Request.Form("content") 拿到的就是干净的 HTML?错!这里有几个常见陷阱:
-
编码问题 :中文乱码怎么办?
- 确保页面<meta charset="UTF-8">;
- ASP 文件开头加上<%@ Codepage=65001 %>;
- 使用Server.URLEncode/Decode处理特殊字符。 -
大小限制 :默认最大请求体只有 200KB 左右。
- 修改 IIS 配置:AspMaxRequestEntityAllowed(单位字节),建议设为 5MB 左右。 -
XSS 风险 :万一有人提交
<script>alert(1)</script>怎么办?
- 先别急着回答“过滤标签”,我们后面专门讲防护机制。
数据持久化:文件 or 数据库?
拿到原始 HTML 后,下一步就是存储。两种主流方式:
✅ 文件存储(适合中小系统)
Dim filePath
filePath = Server.MapPath("user_data/" & Session("UserID") & "_" & FormatDateTime(Now(), 2) & ".html")
Dim fso, ts
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.CreateTextFile(filePath, True, True) ' 第三个参数表示支持 UTF-8
ts.Write rawHtml
ts.Close
优点:简单高效;缺点:难以检索、备份麻烦。
✅ 数据库存储(推荐用于大型 CMS)
Dim conn, rs
Set conn = Server.CreateObject("ADODB.Connection")
Set rs = Server.CreateObject("ADODB.Recordset")
conn.Open "Provider=SQLOLEDB;Data Source=localhost;Initial Catalog=Articles;"
rs.Open "Articles", conn, 1, 3 ' adOpenKeyset, adLockOptimistic
rs.AddNew
rs("Title") = Request.Form("title")
rs("Content") = rawHtml
rs("AuthorID") = Session("UserID")
rs("CreateTime") = Now()
rs.Update
优点:结构化好、易扩展;缺点:需要数据库支持。
选择哪种方案取决于你的业务规模。不过建议尽早接入数据库,毕竟未来迟早要搞搜索、版本管理等功能。
多浏览器兼容性:一场与差异的持久战 🛡️
如果说 ASP 和 *** 是 eWebEditor 的骨架,那么 JavaScript 层的兼容性处理就是它的神经系统。
要知道,在 IE6~IE11 统治天下的年代,Firefox、Chrome 也开始崛起,Safari 也在 Mac 上悄悄扩张。各家对 exec***mand 和 contentEditable 的实现千差万别,简直是一场噩梦。
exec***mand 的“薛定谔行为”
同样是执行 document.exec***mand('bold', false, null) ,不同浏览器的表现可能是这样的:
| 命令 | IE (≥8) | Firefox | Chrome | Safari | Edge (Legacy) |
|---|---|---|---|---|---|
| bold | ✅ | ✅ | ✅ | ✅ | ✅ |
| italic | ✅ | ✅ | ✅ | ✅ | ✅ |
| underline | ✅ | ✅ | ⚠️部分 | ❌ | ⚠️不稳定 |
| insertUnorderedList | ✅ | ✅ | ✅ | ⚠️嵌套异常 | ✅ |
| createLink | ✅ | ✅ | ✅ | ✅ | ✅ |
| formatBlock | ✅ | ✅ | ✅ | ⚠️p/h1转换异常 | ✅ |
注:⚠️ 表示存在边缘情况下的行为偏差;❌ 表示完全不可靠或已被弃用。
比如 Safari 插入列表后经常留下孤立的 <li> 标签,Chrome 对 insertHTML 的脚本过滤过于激进,Firefox 默认用 <br> 而非 <p> 换行……
这些问题怎么解决?
答案是: 抽象层 + 特征检测 + 降级补丁 。
命令抽象层:让混乱归于秩序 🧩
eWebEditor 构建了一个名为 ***mand Abstraction Layer (CAL) 的中间层,用来统一封装所有命令调用:
var ***mandManager = {
***mands: {
'bold': {
exec: function() { this._wrapWith('strong'); },
queryState: function() { return this._hasAncestor('strong'); }
},
'italic': {
exec: function() { this._wrapWith('em'); },
queryState: function() { return this._hasAncestor('em'); }
},
'insertImage': {
exec: function(src) {
var img = document.createElement('img');
img.src = src;
this.insertElement(img);
}
}
},
execute: function(cmd, value) {
if (this.***mands[cmd]) {
this.***mands[cmd].exec.call(this, value);
this._recordForUndo(); // 撤销记录
}
},
query***mandState: function(cmd) {
return this.***mands[cmd]?.queryState?.call(this) || false;
}
};
这么做有什么好处?
- 语义统一 :强制使用
<strong>而不是<b>,提升 HTML 语义质量; - 可控性强 :可在执行前后插入验证、日志、样式修正等钩子;
- 可测试性高 :命令逻辑独立于宿主环境,便于单元测试。
更重要的是,它可以轻松实现“降级兜底”策略。比如在 Edge 上 foreColor 不稳定,那就手动创建 <span style="color:red"> 来替代。
浏览器特征检测:别再相信 UserAgent 了!
过去很多人喜欢用 UA 字符串判断浏览器类型,但这种方式极易出错。eWebEditor 改用运行时特征探测:
var BrowserFeatures = {
HAS_EXEC_***MAND_UNLINK: (function() {
try {
document.exec***mand('unlink', false, null);
return true;
} catch (e) {
return false;
}
})(),
SUPPORTS_INSERT_HTML: 'insertHTML' in document.query***mandEnabled ?
document.query***mandEnabled('insertHTML') : false,
NEEDS_BR_FIX: navigator.userAgent.indexOf('Firefox') > -1
};
然后根据特征动态加载补丁模块:
if (BrowserFeatures.NEEDS_BR_FIX) {
require('./patches/firefox-br-fix.js');
}
if (!BrowserFeatures.SUPPORTS_INSERT_HTML) {
patchInsertHTML();
}
这种“按需加载”的策略显著减少了非必要代码的执行负担,也让兼容性修复更加灵活。
功能体系:不只是加粗斜体那么简单 ✨
很多人以为富文本编辑器就是个“高级 textarea”,其实它的功能复杂度远超想象。
文本格式化:不仅仅是 exec***mand
字体、字号、颜色控制看似简单,实则暗藏玄机。
比如 fontSize 命令接受的是 1~7 的数字等级,而不是像素值。eWebEditor 内部有一张映射表:
const fontSizeMap = {
'1': '10px', '2': '12px', '3': '14px',
'4': '16px', '5': '18px', '6': '24px', '7': '36px'
};
这样一来,就能屏蔽浏览器之间的差异,保证输出一致性。
还有段落对齐,有些浏览器用 <div align="center"> ,有些用 style="text-align:center" 。eWebEditor 在保存前会对整个文档做一次语义化审查,统一转换为 CSS 方式。
图片上传:预览 ≠ 永久存储
本地图片上传是个高频需求。为了实现即时预览,前端可以用 FileReader 把图片转成 Base64 数据 URI:
reader.onload = function(evt) {
const base64Data = evt.target.result;
insertImageIntoEditor(base64Data);
};
function insertImageIntoEditor(src) {
const img = `<img src="${src}" alt="embedded image" style="max-width:100%;" />`;
document.exec***mand('insertHTML', false, img);
}
但这只是临时方案!Base64 会让 HTML 体积暴增,影响性能和存储成本。
正确做法是:客户端预览 → 提交表单 → 服务端解码并保存为物理文件 → 替换 URL。
流程如下:
sequenceDiagram
participant User
participant Frontend
participant Server
User->>Frontend: 选择本地图片
Frontend->>Frontend: FileReader 转为 Base64
Frontend->>Frontend: 插入 Data URL 图片
User->>Frontend: 编辑完成并提交
Frontend->>Server: 发送表单含 Base64 图像
Server->>Server: 解码并保存为物理文件
Server->>Frontend: 返回真实 URL
Frontend->>Frontend: 替换所有 Data URL 为真实路径
完美闭环!🎉
表格与链接:结构化内容的关键
表格创建很容易,难的是合并拆分算法:
function mergeCells(startTd, endTd) {
const rowSpan = Math.abs(endTd.parentNode.rowIndex - startTd.parentNode.rowIndex) + 1;
const colSpan = ...;
startTd.setAttribute('rowspan', rowSpan);
startTd.setAttribute('colspan', colSpan);
// 删除其余单元格
for (...) {
cell.remove();
}
}
超链接插入更要小心:
function isValidUrl(string) {
try {
new URL(string);
return true;
} catch (_) {
return false;
}
}
if (!isValidUrl(href)) {
alert('请输入有效网址(需包含 http:// 或 https://)');
return;
}
还要禁止 javascript: 协议,防止 XSS:
dangerousAttrs = "on\w+|javascript:|vbscript:|data:text/html"
安全防线:XSS、上传漏洞、属性净化 🔐
富文本 = 安全雷区。eWebEditor 的防护策略堪称教科书级别。
输入验证:白名单过滤才是王道
不允许黑名单!因为总有漏网之鱼。
正确的做法是定义白名单标签和属性:
allowedTags = "p|br|strong|em|u|ol|ul|li|h1|h2|h3|img|a|table|tr|td|th"
re.Pattern = "<(?!/?" & allowedTags & "\b)[^>]*>"
正则负向前瞻断言精准剔除非白名单标签,比字符串替换靠谱得多。
属性净化:onclick、href=”javascript:” 必须删!
即使标签合法,属性也可能带毒:
dangerousAttrs = "on\w+|javascript:|vbscript:"
reAttr.Pattern = "(" & dangerousAttrs & ")=[^ ]*"
SanitizeAttributes = reAttr.Replace(html, "")
甚至还可以用 MSXML DOM 遍历所有属性节点,主动移除危险项。
输出编码:上下文感知才够安全
- HTML 文本:
Server.HTMLEncode() - JS 字符串:转义双引号
- URL 参数:
Server.URLEncode()
三位一体,缺一不可!
扩展开发:插件、主题、API 全开放 🧱
eWebEditor 不只是一个编辑器,更是一个平台。
自定义按钮:JSON 配置驱动 UI
{
"toolbar": [["bold", "italic"], ["custom_alert"]],
"buttons": {
"custom_alert": {
"label": "提醒",
"icon": "bell",
"click": "function() { alert('自定义功能'); }"
}
}
}
动态注入,零侵入。
主题切换:CSS 动态加载
eweb.setTheme = function(themeName) {
let link = document.getElementById('editor-skin');
if (!link) {
link = document.createElement('link');
link.id = 'editor-skin';
link.rel = 'stylesheet';
document.head.appendChild(link);
}
link.href = `themes/${themeName}/style.css`;
};
轻松实现夜间模式、企业蓝等皮肤。
API 接口:供第三方调用
editor.getContent() // 获取 HTML
editor.setContent(html) // 设置内容
editor.exec***mand('bold') // 执行命令
editor.on('change', cb) // 监听事件
标准化接口,方便集成。
总结:经典为何值得回味?🤔
eWebEditor V7.3 的技术栈或许已经“过时”,但它所体现的设计思想至今仍不过时:
- 安全第一 :服务端控制优于前端隐藏;
- 兼容为王 :抽象层隔离浏览器差异;
- 可扩展性 :插件化架构支持持续演进;
- 用户体验 :双模式编辑兼顾新手与专家。
如果你正在维护一个老旧系统,希望这篇文章能帮你理清脉络;
如果你打算重构或迁移,也希望你能从中汲取经验,少走弯路。💪
毕竟,每一个“过时”的系统背后,都曾有过一群认真做事的人。致敬他们,也致敬这个时代的技术变迁。🌍✨
本文还有配套的精品资源,点击获取
简介:eWebEditor V7.3是一款基于ASP技术的富文本在线网页编辑器,支持IE、Firefox、Chrome等主流浏览器,具备良好的跨平台兼容性与稳定性。该编辑器提供文本、图像、表格、超链接等内容的可视化编辑,支持HTML源码修改、实时预览、图片上传管理及安全过滤机制,并兼容简体中文GB2312编码,同时可通过API扩展功能。本项目涵盖eWebEditor的安装配置、页面集成、自定义设置与二次开发要点,适用于各类内容管理系统中的文本编辑需求。
本文还有配套的精品资源,点击获取