開發者實作輪播圖後台管理:上傳 JPG (1920×481)、列表顯示、刪除、自動重命名排序。發現刪除後重新讀取出錯。本文分析核心 Bug、提供修正代碼與架構優化建議。
protected void BtnDelete_Click(...) {
// ... 刪除檔案 ...
if (file.Exists == false) {
bindReset(); // 重新命名:1.jpg, 2.jpg, 3.jpg...
bind(); // ← 問題:bindReset 內部已隱含讀取目錄,這裡再 bind() 造成狀態不一致
}
}
2.jpgbindReset():讀取目錄 → 獲取 1.jpg, 3.jpg → 重命名為 1.jpg, 2.jpgbind():再次讀取目錄 → 獲取 1.jpg, 2.jpg → 建構 DataTablebindReset 邏輯有瑕疵,導致 GridView 資料錯誤protected void BtnDelete_Click(object sender, EventArgs e) {
int row = ((GridViewRow)((Button)sender).NamingContainer).RowIndex;
HiddenField HFid = (HiddenField)GV.Rows[row].Cells[0].FindControl("HFid");
string Route = SlideFolderPath + HFid.Value + ".jpg";
FileInfo file = new FileInfo(Route);
if (file.Exists) {
file.Attributes = FileAttributes.Normal;
file.Delete();
if (!file.Exists) {
// 僅呼叫 bindReset(),內部已包含重新讀取與綁定
bindReset();
// 移除這行:bind();
ClientScript.RegisterStartupScript(GetType(), "ok", "alert('刪除成功');", true);
} else {
ClientScript.RegisterStartupScript(GetType(), "err", "alert('刪除失敗');", true);
}
}
}
public void bindReset() {
// 1. 讀取現有檔案,按檔名排序 (確保順序確定)
var files = Directory.GetFiles(SlideFolderPath, "*.jpg")
.Select(f => new FileInfo(f))
.OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
// 2. 重新命名:1.jpg, 2.jpg, 3.jpg...
for (int i = 0; i < files.Count; i++) {
string newName = (i + 1) + ".jpg";
string newPath = Path.Combine(SlideFolderPath, newName);
if (files[i].FullName != newPath) {
files[i].MoveTo(newPath); // 覆蓋目標若存在
}
}
// 3. 直接重新綁定 GridView (不再獨立呼叫 bind())
RebindGridView();
}
private void RebindGridView() {
var files = Directory.GetFiles(SlideFolderPath, "*.jpg")
.Select(f => new FileInfo(f))
.OrderBy(f => f.Name)
.ToList();
DataTable dt = new DataTable();
dt.Columns.Add("URL");
dt.Columns.Add("id");
foreach (var f in files) {
string name = Path.GetFileNameWithoutExtension(f.Name);
string url = MyConfig.WebImgUrl + "/kuaigou/huandeng/" + f.Name;
dt.Rows.Add(url, name);
}
LblNumber.Text = dt.Rows.Count.ToString();
GV.DataSource = dt;
GV.DataBind();
divNumber.Style["display"] = dt.Rows.Count > 0 ? "block" : "none";
if (GV.Rows.Count == 1) {
((Button)GV.Rows[0].Cells[1].FindControl("BtnDelete")).Enabled = false;
}
}
public class SlideImageService {
private readonly string _folderPath;
private readonly string _webUrlPrefix;
public SlideImageService(string folderPath, string webUrlPrefix) {
_folderPath = folderPath;
_webUrlPrefix = webUrlPrefix;
}
public List GetAll() { /* 讀取目錄建構 DTO */ }
public void Add(Stream stream, string fileName) { /* 驗證尺寸、儲存 */ }
public void Delete(int id) { /* 刪除檔案 + 重新排序 */ }
public void Reorder() { /* 統一重新命名邏輯 */ }
}
public class SlideImage {
public int Id { get; set; }
public string Url { get; set; }
public string FileName { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
CREATE TABLE slide_images (
id INT PRIMARY KEY AUTO_INCREMENT,
file_name VARCHAR(100) NOT NULL,
display_order INT NOT NULL,
width INT, height INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 刪除時:DELETE FROM ...; UPDATE ... SET display_order = display_order - 1 WHERE display_order > @deletedOrder;
-- 避免檔案系統重命名競態、支援軟刪除、記錄操作日誌
// 檢查 Content-Type 而非僅副檔名
if (FUDtal.PostedFile.ContentType != "image/jpeg") { /* 拒絕 */ }
// 使用 Image.FromStream 驗證真實尺寸
using (var img = Image.FromStream(FUDtal.PostedFile.InputStream)) {
if (img.Width != 1920 || img.Height != 481) { /* 拒絕 */ }
// 重要:FromStream 後需重設 Position 或重新讀取流才能 SaveAs
FUDtal.PostedFile.InputStream.Position = 0;
FUDtal.PostedFile.SaveAs(path);
}
核心 Bug 在於 BtnDelete_Click 中 **刪除成功後同時呼叫 bindReset() 和 bind() 造成雙重綁定衝突**。修正為**僅呼叫 bindReset()**,並將讀取、重命名、綁定邏輯整合為單一 RebindGridView() 方法。長期建議引入服務層封裝檔案操作、資料庫記錄中繼資料、強化上傳驗證,構建可維護、可擴展的輪播圖管理系統。