在维护 ColdFusion 老项目的时候,经常会看到一种结构:
xxx.cfm
↓
JavaScript
↓
Ajax
↓
xxx.cfc
↓
Database
第一次看到的时候,很容易产生一个疑问:
.cfm和.cfc明明都是 ColdFusion 文件,它们为什么还需要 Ajax?
原因在于:
CFM 页面已经返回给浏览器之后,
如果用户不刷新页面,又想向服务器请求新的数据,
就需要 JavaScript 再发送 HTTP 请求。
这个过程就是 Ajax。
这一篇详细整理 ColdFusion 项目里的:
CFM
+
JavaScript
+
CFC
+
Ajax
到底是如何连接起来的。
1. 先理解普通 CFM 请求
例如:
user.cfm?id=100
浏览器:
GET /user.cfm?id=100
ColdFusion:
执行 user.cfm
↓
查询数据库
↓
生成 HTML
↓
返回浏览器
如果用户点击:
<button>
查看用户
</button>
传统做法可能是:
刷新整个页面
但是 Ajax 可以做到:
不刷新页面
↓
JavaScript 请求服务器
↓
服务器返回 JSON
↓
JavaScript 更新页面
2. Ajax 的基本结构
整个流程:
CFM 页面
│
│ JavaScript
▼
HTTP Request
│
▼
CFC remote function
│
▼
Database
│
▼
JSON
│
▼
JavaScript
│
▼
更新 HTML
例如:
student.cfm
↓
StudentApi.cfc
↓
getStudent()
↓
JSON
3. 为什么 CFC 函数需要 remote?
例如:
<cffunction
name="getStudent"
access="remote"
returntype="struct"
returnformat="json"
>
关键就是:
access="remote"
remote 表示:
这个方法允许通过 HTTP 从 ColdFusion 外部调用。
如果只是:
access="public"
那么其他 CFML 可以调用:
<cfset service.getStudent()>
但是 JavaScript 不能直接通过 HTTP 调用。
4. 最基础的 Remote CFC
例如:
<cfcomponent>
<cffunction
name="getStudent"
access="remote"
returntype="struct"
returnformat="json"
>
<cfargument
name="studentId"
type="numeric"
required="true"
>
<cfset var result = {}>
<cfset result.success = true>
<cfset result.studentId = arguments.studentId>
<cfset result.name = "Yamada">
<cfreturn result>
</cffunction>
</cfcomponent>
保存为:
StudentApi.cfc
那么理论上可以通过:
StudentApi.cfc?method=getStudent&studentId=100
调用。
ColdFusion 会:
method=getStudent
识别要调用:
getStudent()
然后:
studentId=100
传给:
arguments.studentId
5. CFC 返回 JSON
例如:
<cfset result = {}>
<cfset result.success = true>
<cfset result.message = "OK">
<cfreturn result>
配合:
returnformat="json"
浏览器最终可能得到:
{
"SUCCESS": true,
"MESSAGE": "OK"
}
ColdFusion 版本以及序列化设置不同的时候,JSON Key 大小写有可能和预期不同。
所以实际前端处理时需要确认真实响应内容。
6. 用 jQuery Ajax 调用 CFC
老 ColdFusion 项目里非常常见:
$.ajax({
url: "StudentApi.cfc",
type: "POST",
dataType: "json",
data: {
method: "getStudent",
studentId: 100
},
success: function(response) {
console.log(response);
},
error: function(xhr) {
console.error(xhr.responseText);
}
});
请求实际上类似:
POST /StudentApi.cfc
method=getStudent
studentId=100
ColdFusion 根据:
method
调用:
getStudent()
7. 为什么通常建议 POST?
如果请求只是查询数据:
GET
从 REST 设计角度完全正常。
例如:
GET /StudentApi.cfc?method=getStudent&studentId=100
但是一些老系统出于:
安全策略
WAF
框架设计
参数暴露
既有规范
可能统一要求:
Ajax → POST
例如:
$.ajax({
type: "POST"
});
这样参数放在 Request Body。
不过要注意:
POST 本身并不会自动让接口变安全。
攻击者仍然可以构造 POST 请求。
真正的安全必须靠:
权限检查
Session
CSRF
参数验证
SQL 参数化
访问控制
8. CFC 中接收参数
例如:
<cffunction
name="searchStudent"
access="remote"
returntype="struct"
returnformat="json"
>
<cfargument
name="keyword"
type="string"
required="false"
default=""
>
</cffunction>
JavaScript:
data: {
method: "searchStudent",
keyword: "yamada"
}
ColdFusion:
arguments.keyword
得到:
yamada
9. Query 参数一定要参数化
不要这样:
<cfquery name="qStudent" datasource="mydb">
SELECT *
FROM student_master
WHERE student_name = '#arguments.keyword#'
</cfquery>
这种写法存在 SQL Injection 风险。
应该:
<cfquery name="qStudent" datasource="mydb">
SELECT
student_id,
student_name
FROM
student_master
WHERE
student_name =
<cfqueryparam
value="#arguments.keyword#"
cfsqltype="cf_sql_varchar"
>
</cfquery>
或者 Script 风格:
local.qStudent = queryExecute(
"
SELECT
student_id,
student_name
FROM
student_master
WHERE
student_name = :studentName
",
{
studentName = {
value = arguments.keyword,
cfsqltype = "cf_sql_varchar"
}
},
{
datasource = "mydb"
}
);
10. 一个完整的搜索 API
例如:
component {
remote struct function searchStudent(
string keyword = ""
)
returnformat="json" {
local.result = {
success = false,
message = "",
data = []
};
try {
local.qStudent = queryExecute(
"
SELECT
student_id,
student_name,
grade,
class_no
FROM
student_master
WHERE
student_name LIKE :keyword
ORDER BY
grade,
class_no,
student_id
",
{
keyword = {
value = "%" & arguments.keyword & "%",
cfsqltype = "cf_sql_varchar"
}
},
{
datasource = "mydb"
}
);
local.result.success = true;
for (
local.row in local.qStudent
) {
arrayAppend(
local.result.data,
{
studentId = local.row.student_id,
studentName = local.row.student_name,
grade = local.row.grade,
classNo = local.row.class_no
}
);
}
}
catch (any e) {
local.result.message =
"検索処理でエラーが発生しました。";
}
return local.result;
}
}
11. 前端接收 JSON
例如:
$.ajax({
url: "StudentApi.cfc",
type: "POST",
dataType: "json",
data: {
method: "searchStudent",
keyword: $("#keyword").val()
},
success: function(response) {
if (!response.success) {
alert(response.message);
return;
}
console.log(response.data);
}
});
这样就形成:
CFM
↓
JavaScript
↓
POST
↓
CFC
↓
SQL
↓
JSON
↓
JavaScript
12. 使用 Fetch API
现在如果不是受到旧项目限制,我个人会更喜欢原生:
fetch("StudentApi.cfc", {
method: "POST",
headers: {
"Content-Type":
"application/x-www-form-urlencoded"
},
body: new URLSearchParams({
method: "searchStudent",
keyword:
document.querySelector("#keyword").value
})
})
.then(response => {
if (!response.ok) {
throw new Error(
"HTTP Error: " + response.status
);
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
这样不依赖 jQuery。
13. application/json 的情况
如果前端发送:
fetch("StudentApi.cfc?method=saveStudent", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
studentId: 100,
studentName: "Yamada"
})
});
这时候数据并不会自动成为:
form.studentId
通常需要读取:
getHttpRequestData()
例如:
local.requestData = getHttpRequestData();
local.body =
deserializeJSON(
toString(
local.requestData.content
)
);
之后:
local.body.studentId
local.body.studentName
14. cfajaxproxy 是什么?
老 ColdFusion 项目中经常看到:
<cfajaxproxy
cfc="components.StudentApi"
jsclassname="StudentApi"
>
ColdFusion 会自动生成一个 JavaScript Proxy。
之后 JavaScript 可以:
const api = new StudentApi();
api.getStudent(...);
看起来就像直接调用 JavaScript 对象。
实际上背后还是:
JavaScript
↓
HTTP
↓
CFC remote method
并不是 JavaScript 真的直接调用服务器函数。
15. cfajaxproxy 的基本例子
CFM:
<cfajaxproxy
cfc="components.StudentApi"
jsclassname="StudentService"
>
CFC:
<cfcomponent>
<cffunction
name="getStudent"
access="remote"
returntype="struct"
returnformat="json"
>
<cfargument
name="studentId"
required="true"
>
<cfreturn {
"studentId" = arguments.studentId,
"name" = "Yamada"
}>
</cffunction>
</cfcomponent>
JavaScript:
const service =
new StudentService();
service.setCallbackHandler(
function(result) {
console.log(result);
}
);
service.setErrorHandler(
function(statusCode, statusMsg) {
console.error(
statusCode,
statusMsg
);
}
);
service.getStudent(100);
16. cfajaxproxy 和普通 Ajax 的本质区别
cfajaxproxy:
ColdFusion 帮你自动生成 JavaScript Proxy
普通 Ajax:
自己构造 HTTP Request
本质都是:
Browser
↓
HTTP
↓
CFC
17. 为什么 access=”remote” 是安全重点?
假设有:
<cffunction
name="deleteUser"
access="remote"
>
很多人会误以为:
页面上没有按钮
=
别人调用不了
实际上并不是。
攻击者完全可以直接构造:
POST /UserApi.cfc
method=deleteUser
userId=100
所以 CFC 内部本身必须检查权限。
例如:
if (
!structKeyExists(
session,
"userId"
)
) {
throw(
type = "Unauthorized",
message = "Unauthorized"
);
}
进一步:
if (
session.role != "ADMIN"
) {
throw(
type = "Forbidden",
message = "Forbidden"
);
}
18. 不要相信 JavaScript 验证
例如前端:
if (!user.isAdmin) {
return;
}
这只是 UI 控制。
真正安全检查必须在服务器:
if (session.role != "ADMIN") {
return {
success = false,
message = "権限がありません。"
};
}
原因很简单:
JavaScript 在用户电脑运行
用户可以:
DevTools 修改
直接调用 API
使用 curl
使用 Postman
19. Ajax 出错时怎么排查?
我现在通常会按照:
① Browser Network
② HTTP Status
③ Request Parameters
④ Response
⑤ CFC method
⑥ Application.cfc
⑦ Server Log
来排查。
20. Network 面板
Chrome / Firefox:
F12
↓
Network
点击 Ajax 请求。
重点看:
Request URL
Request Method
Status Code
Request Payload
Response
例如:
POST
/components/StudentApi.cfc
参数:
method=searchStudent
keyword=yamada
响应:
{
"success": true
}
21. HTTP 200 不一定代表业务成功
比如:
HTTP 200
但是 JSON:
{
"success": false,
"message": "権限がありません"
}
这代表:
HTTP 请求成功
但是业务失败
所以需要区分:
HTTP Level
和:
Business Level
22. HTTP 500
如果:
500 Internal Server Error
通常代表:
CFC
SQL
CFML
Application.cfc
出现服务器端异常。
这时候看:
Response
ColdFusion Log
Exception
Stack Trace
非常重要。
23. 最终结构
我现在理解 ColdFusion Ajax 的核心关系其实很简单:
CFM
=
页面
JavaScript
=
发请求
CFC remote
=
HTTP API
Query
=
访问数据库
最后组合:
Browser
│
▼
CFM
│
JavaScript
│
▼
Ajax POST
│
▼
CFC remote
│
▼
DB
│
▼
Struct / Array
│
▼
JSON
│
▼
JavaScript
│
▼
HTML 更新
只要把这张图真正理解,很多 ColdFusion 老项目中的 Ajax 代码就会突然变得容易看懂。

