ASP 实例
四个可直接改造的小例子:留言板、登录、文件列表、分页列表。
1. 留言板:写入与展示
<%@ Language="VBScript" CodePage="65001" %>
<% Option Explicit %>
<% Response.Buffer = True %>
<!-- #include virtual="/includes/conn.asp" -->
<%
Response.Charset = "UTF-8"
Response.ContentType = "text/html"
' 处理提交
If Request.ServerVariables("REQUEST_METHOD") = "POST" Then
Dim name, body, cmd
name = Trim(Request.Form("name"))
body = Trim(Request.Form("body"))
If name <> "" And body <> "" Then
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandText = "INSERT INTO messages (name, body, created) VALUES (?, ?, GETDATE())"
cmd.Parameters.Append cmd.CreateParameter("n", 200, 1, 50, name)
cmd.Parameters.Append cmd.CreateParameter("b", 203, 1, 4000, body)
cmd.Execute
End If
Response.Redirect "board.asp" ' PRG:防刷新重复提交
End If
' 读取列表
Dim rs
Set rs = conn.Execute("SELECT TOP 20 name, body, created FROM messages ORDER BY id DESC")
%>
<!DOCTYPE html>
<html><body>
<h1>留言板</h1>
<form method="post">
昵称:<input name="name" maxlength="50"><br>
内容:<textarea name="body" rows="4" cols="40"></textarea><br>
<button type="submit">提交</button>
</form>
<hr>
<% Do While Not rs.EOF %>
<div>
<strong><%= Server.HTMLEncode(rs("name")) %></strong>
<small><%= rs("created") %></small>
<p><%= Server.HTMLEncode(rs("body")) %></p>
</div>
<% rs.MoveNext
Loop %>
</body></html>留言板
昵称: Ada
内容: 第一条留言
Ada 2026/11/3 14:20:01
第一条留言Redirect after a successful POST (PRG) so F5 does not insert again. Rendering a success page directly means every refresh adds a message.
2. 登录与权限
<%-- login.asp --%>
<%
If Request.ServerVariables("REQUEST_METHOD") = "POST" Then
Dim cmd, rs, pwd
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandText = "SELECT id, name, pwd_hash FROM users WHERE name = ?"
cmd.Parameters.Append cmd.CreateParameter("n", 200, 1, 50, Trim(Request.Form("name")))
Set rs = cmd.Execute
If Not rs.EOF Then
' 实际项目必须用哈希比对,不能存明文
If HashPassword(Request.Form("pwd")) = rs("pwd_hash") Then
Session("uid") = rs("id")
Session("uname") = rs("name")
Response.Redirect "admin.asp"
End If
End If
End If
%>
<form method="post">
用户名:<input name="name">
密码:<input type="password" name="pwd">
<button type="submit">登录</button>
</form><%-- admin.asp:页面顶部做权限校验 --%>
<%
If IsEmpty(Session("uid")) Then
Response.Redirect "login.asp"
End If
%>
<h1>欢迎,<%= Server.HTMLEncode(Session("uname")) %></h1>Put the auth check at the very top, before any output. Placed mid-page, unauthenticated users already see content and Redirect fails because headers are gone.
Response.Redirect may still run (with Buffer=True it ends, but do not rely on it). Call Response.End right after so nothing else executes.3. 文件列表
<%
Dim fso, folder, f
Set fso = Server.CreateObject("Scripting.FileSystemObject")
Set folder = fso.GetFolder(Server.MapPath("/uploads"))
%>
<table>
<tr><th>文件名</th><th>大小</th><th>修改时间</th><th>操作</th></tr>
<% For Each f In folder.Files %>
<tr>
<td><%= Server.HTMLEncode(f.Name) %></td>
<td><%= FormatNumber(f.Size / 1024, 1) %> KB</td>
<td><%= f.DateLastModified %></td>
<td><a href="download.asp?f=<%= Server.URLEncode(f.Name) %>">下载</a></td>
</tr>
<% Next %>
</table>文件名 大小 修改时间 操作
report.pdf 180.0 KB 2026/11/2 17:40:55 下载
notes.txt 1.0 KB 2026/11/3 9:02:11 下载<%-- download.asp --%>
<%
Dim fname, path
fname = Request.QueryString("f")
' 关键:只允许单纯的文件名
If InStr(fname, "..") > 0 Or InStr(fname, "/") > 0 Or InStr(fname, "\") > 0 Then
Response.Status = "400 Bad Request"
Response.End
End If
path = Server.MapPath("/uploads/" & fname)
If Not fso.FileExists(path) Then
Response.Status = "404 Not Found"
Response.End
End If
Response.ContentType = "application/octet-stream"
Response.AddHeader "Content-Disposition", "attachment; filename=" & fname
Dim st
Set st = Server.CreateObject("ADODB.Stream")
st.Type = 1
st.Open
st.LoadFromFile path
Response.BinaryWrite st.Read
st.Close
Response.End
%>Without validation, download.asp?f=../../conn.asp downloads the file holding your database password. Restrict names to alphanumerics and dots and reject every path separator.
4. 分页列表
<%
Dim page, size, total, pages, offset, rs
page = CLng("0" & Request.QueryString("page"))
If page < 1 Then page = 1
size = 10
' 总数
total = conn.Execute("SELECT COUNT(*) AS c FROM products")("c")
pages = Int((total + size - 1) / size)
If page > pages And pages > 0 Then page = pages
offset = (page - 1) * size
Set rs = conn.Execute( _
"SELECT id, name, price FROM products ORDER BY id " & _
"OFFSET " & offset & " ROWS FETCH NEXT " & size & " ROWS ONLY")
%>
<table>
<% Do While Not rs.EOF %>
<tr>
<td><%= rs("id") %></td>
<td><%= Server.HTMLEncode(rs("name")) %></td>
<td>¥<%= FormatNumber(rs("price"), 2) %></td>
</tr>
<% rs.MoveNext
Loop %>
</table>
<p>
第 <%= page %> / <%= pages %> 页,共 <%= total %> 条
<% If page > 1 Then %>
<a href="?page=<%= page - 1 %>">上一页</a>
<% End If %>
<% If page < pages Then %>
<a href="?page=<%= page + 1 %>">下一页</a>
<% End If %>
</p>第 2 / 5 页,共 48 条
上一页 下一页CLng("0" & Request.QueryString("page")) is the classic empty guard: an empty parameter becomes "0" and CLng succeeds. Range-check afterwards so page is never negative or past the end.
OFFSET slows down as it grows: page 1000 makes the database skip twenty thousand rows. For large data switch to keyset paging (continue from the last id of the previous page), which is O(1) per page.把例子改成自己的
| Example | Reuse for |
|---|---|
| 留言板 | 评论、工单、反馈 |
| 登录 | 任何需要权限的后台 |
| 文件列表 | 文档下载中心、素材库 |
| 分页列表 | 商品、文章、用户列表 |
All four share one skeleton: read parameters, validate, query or touch files, escape on output. Changing the business means changing the SQL and fields, not the structure.
1. 留言板:写入与展示
<%@ Language="VBScript" CodePage="65001" %>
<% Option Explicit %>
<% Response.Buffer = True %>
<!-- #include virtual="/includes/conn.asp" -->
<%
Response.Charset = "UTF-8"
Response.ContentType = "text/html"
' 处理提交
If Request.ServerVariables("REQUEST_METHOD") = "POST" Then
Dim name, body, cmd
name = Trim(Request.Form("name"))
body = Trim(Request.Form("body"))
If name <> "" And body <> "" Then
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandText = "INSERT INTO messages (name, body, created) VALUES (?, ?, GETDATE())"
cmd.Parameters.Append cmd.CreateParameter("n", 200, 1, 50, name)
cmd.Parameters.Append cmd.CreateParameter("b", 203, 1, 4000, body)
cmd.Execute
End If
Response.Redirect "board.asp" ' PRG:防刷新重复提交
End If
' 读取列表
Dim rs
Set rs = conn.Execute("SELECT TOP 20 name, body, created FROM messages ORDER BY id DESC")
%>
<!DOCTYPE html>
<html><body>
<h1>留言板</h1>
<form method="post">
昵称:<input name="name" maxlength="50"><br>
内容:<textarea name="body" rows="4" cols="40"></textarea><br>
<button type="submit">提交</button>
</form>
<hr>
<% Do While Not rs.EOF %>
<div>
<strong><%= Server.HTMLEncode(rs("name")) %></strong>
<small><%= rs("created") %></small>
<p><%= Server.HTMLEncode(rs("body")) %></p>
</div>
<% rs.MoveNext
Loop %>
</body></html>留言板
昵称: Ada
内容: 第一条留言
Ada 2026/11/3 14:20:01
第一条留言提交成功后 Redirect 到同一页(PRG 模式),用户按 F5 不会重复插入。直接输出成功页的话,刷新一次就多一条留言。
2. 登录与权限
<%-- login.asp --%>
<%
If Request.ServerVariables("REQUEST_METHOD") = "POST" Then
Dim cmd, rs, pwd
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = conn
cmd.CommandText = "SELECT id, name, pwd_hash FROM users WHERE name = ?"
cmd.Parameters.Append cmd.CreateParameter("n", 200, 1, 50, Trim(Request.Form("name")))
Set rs = cmd.Execute
If Not rs.EOF Then
' 实际项目必须用哈希比对,不能存明文
If HashPassword(Request.Form("pwd")) = rs("pwd_hash") Then
Session("uid") = rs("id")
Session("uname") = rs("name")
Response.Redirect "admin.asp"
End If
End If
End If
%>
<form method="post">
用户名:<input name="name">
密码:<input type="password" name="pwd">
<button type="submit">登录</button>
</form><%-- admin.asp:页面顶部做权限校验 --%>
<%
If IsEmpty(Session("uid")) Then
Response.Redirect "login.asp"
End If
%>
<h1>欢迎,<%= Server.HTMLEncode(Session("uname")) %></h1>权限校验必须放在页面最顶部,在任何输出之前。放到 HTML 中间时,未登录用户已经看到了一部分内容,而且此时 Redirect 会因为响应头已发送而失效。
Response.Redirect 之后代码仍会继续执行(ASP 里它在 Buffer=True 时会 End,但不要依赖这一点)。校验失败后立刻 Response.End,确保后面一行都不跑。3. 文件列表
<%
Dim fso, folder, f
Set fso = Server.CreateObject("Scripting.FileSystemObject")
Set folder = fso.GetFolder(Server.MapPath("/uploads"))
%>
<table>
<tr><th>文件名</th><th>大小</th><th>修改时间</th><th>操作</th></tr>
<% For Each f In folder.Files %>
<tr>
<td><%= Server.HTMLEncode(f.Name) %></td>
<td><%= FormatNumber(f.Size / 1024, 1) %> KB</td>
<td><%= f.DateLastModified %></td>
<td><a href="download.asp?f=<%= Server.URLEncode(f.Name) %>">下载</a></td>
</tr>
<% Next %>
</table>文件名 大小 修改时间 操作
report.pdf 180.0 KB 2026/11/2 17:40:55 下载
notes.txt 1.0 KB 2026/11/3 9:02:11 下载<%-- download.asp --%>
<%
Dim fname, path
fname = Request.QueryString("f")
' 关键:只允许单纯的文件名
If InStr(fname, "..") > 0 Or InStr(fname, "/") > 0 Or InStr(fname, "\") > 0 Then
Response.Status = "400 Bad Request"
Response.End
End If
path = Server.MapPath("/uploads/" & fname)
If Not fso.FileExists(path) Then
Response.Status = "404 Not Found"
Response.End
End If
Response.ContentType = "application/octet-stream"
Response.AddHeader "Content-Disposition", "attachment; filename=" & fname
Dim st
Set st = Server.CreateObject("ADODB.Stream")
st.Type = 1
st.Open
st.LoadFromFile path
Response.BinaryWrite st.Read
st.Close
Response.End
%>download.asp?f=../../conn.asp 这样的请求如果不校验,就能把数据库密码文件下载走。文件名必须只允许字母数字与点,且禁止任何路径分隔符。
4. 分页列表
<%
Dim page, size, total, pages, offset, rs
page = CLng("0" & Request.QueryString("page"))
If page < 1 Then page = 1
size = 10
' 总数
total = conn.Execute("SELECT COUNT(*) AS c FROM products")("c")
pages = Int((total + size - 1) / size)
If page > pages And pages > 0 Then page = pages
offset = (page - 1) * size
Set rs = conn.Execute( _
"SELECT id, name, price FROM products ORDER BY id " & _
"OFFSET " & offset & " ROWS FETCH NEXT " & size & " ROWS ONLY")
%>
<table>
<% Do While Not rs.EOF %>
<tr>
<td><%= rs("id") %></td>
<td><%= Server.HTMLEncode(rs("name")) %></td>
<td>¥<%= FormatNumber(rs("price"), 2) %></td>
</tr>
<% rs.MoveNext
Loop %>
</table>
<p>
第 <%= page %> / <%= pages %> 页,共 <%= total %> 条
<% If page > 1 Then %>
<a href="?page=<%= page - 1 %>">上一页</a>
<% End If %>
<% If page < pages Then %>
<a href="?page=<%= page + 1 %>">下一页</a>
<% End If %>
</p>第 2 / 5 页,共 48 条
上一页 下一页CLng("0" & Request.QueryString("page")) 是经典的防空值技巧:参数为空时变成 "0",CLng 不会报错。之后再做范围校验,page 就不会是负数或超出总页数。
OFFSET 越大越慢——翻到第 1000 页时数据库要先跳过两万行。数据量大时改用「按上一页最后一个 id 继续查」(keyset 分页),性能恒定。把例子改成自己的
| 例子 | 可以改成 |
|---|---|
| 留言板 | 评论、工单、反馈 |
| 登录 | 任何需要权限的后台 |
| 文件列表 | 文档下载中心、素材库 |
| 分页列表 | 商品、文章、用户列表 |
四个例子的骨架是一样的:读参数 → 校验 → 查库或读写文件 → 转义输出。改业务只要换 SQL 和表单字段,结构不用动。