Skip to content

13.4 头像上传、图片展示

头像上传是用户系统中常见的功能,本章节将介绍如何实现头像上传和图片展示功能。

基本原理

头像上传的基本流程:

  1. 创建文件上传表单
  2. 验证上传的文件(类型、大小、是否为图片)
  3. 处理上传的文件(重命名、存储)
  4. 保存文件路径到数据库
  5. 展示上传的头像

实现头像上传

1. 创建上传表单

html
<!DOCTYPE html>
<html>
<head>
    <title>头像上传</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 500px; margin: 0 auto; padding: 20px; }
        h1 { text-align: center; color: #333; }
        .avatar-container { text-align: center; margin: 20px 0; }
        .avatar { width: 150px; height: 150px; border-radius: 50%; object-fit: cover; }
        .form-group { margin: 15px 0; }
        label { display: block; margin-bottom: 5px; font-weight: bold; }
        input[type="file"] { padding: 10px; border: 1px solid #ddd; border-radius: 4px; width: 100%; box-sizing: border-box; }
        input[type="submit"] { padding: 10px 20px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
        .message { padding: 10px; margin: 10px 0; border-radius: 4px; }
        .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
        .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
    </style>
</head>
<body>
    <h1>头像上传</h1>
    
    <div class="avatar-container">
        <img src="<?php echo $avatarUrl; ?>" alt="头像" class="avatar">
    </div>
    
    <?php if (!empty($errors)): ?>
        <div class="message error">
            <ul>
                <?php foreach ($errors as $error): ?>
                    <li><?php echo $error; ?></li>
                <?php endforeach; ?>
            </ul>
        </div>
    <?php endif; ?>
    
    <?php if (isset($success)): ?>
        <div class="message success">
            <?php echo $success; ?>
        </div>
    <?php endif; ?>
    
    <form action="" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label>选择头像: <input type="file" name="avatar" accept="image/*"></label>
        </div>
        <input type="submit" value="上传头像" name="submit">
    </form>
</body>
</html>

2. 服务器端处理

php
<?php
// 配置
$targetDir = "uploads/avatars/";
$maxFileSize = 2 * 1024 * 1024; // 2MB
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$errors = [];
$avatarUrl = 'default-avatar.png'; // 默认头像

// 确保上传目录存在
if (!is_dir($targetDir)) {
    mkdir($targetDir, 0755, true);
}

// 模拟用户ID
$userId = 1;

// 检查是否已有头像
$userAvatar = $targetDir . "avatar_$userId.jpg";
if (file_exists($userAvatar)) {
    $avatarUrl = $userAvatar;
} else {
    // 检查其他格式
    foreach ($allowedExtensions as $ext) {
        $avatarFile = $targetDir . "avatar_$userId.$ext";
        if (file_exists($avatarFile)) {
            $avatarUrl = $avatarFile;
            break;
        }
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
    $file = $_FILES['avatar'];
    
    // 检查是否有错误
    if ($file['error'] !== 0) {
        $errors[] = '上传失败,错误代码: ' . $file['error'];
    } else {
        // 检查文件大小
        if ($file['size'] > $maxFileSize) {
            $errors[] = "文件太大,最大允许 2MB";
        }
        
        // 检查文件扩展名
        $fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!in_array($fileExtension, $allowedExtensions)) {
            $errors[] = "只允许上传图片文件(JPG、PNG、GIF、WebP)";
        }
        
        // 检查是否为真实图片
        if (getimagesize($file['tmp_name']) === false) {
            $errors[] = "请上传真实的图片文件";
        }
        
        // 处理上传
        if (empty($errors)) {
            // 生成新文件名
            $newFileName = "avatar_$userId.$fileExtension";
            $targetFile = $targetDir . $newFileName;
            
            // 删除旧头像
            foreach ($allowedExtensions as $ext) {
                $oldAvatar = $targetDir . "avatar_$userId.$ext";
                if (file_exists($oldAvatar)) {
                    unlink($oldAvatar);
                }
            }
            
            if (move_uploaded_file($file['tmp_name'], $targetFile)) {
                $success = "头像上传成功!";
                $avatarUrl = $targetFile;
            } else {
                $errors[] = "上传失败,请重试";
            }
        }
    }
}
?>

图片展示

1. 基本展示

php
<img src="<?php echo $avatarUrl; ?>" alt="头像" class="avatar">

2. 响应式展示

html
<style>
    .avatar {
        width: 100px;
        height: 100px;
        border-radius: 50%;
        object-fit: cover;
    }
    
    @media (max-width: 768px) {
        .avatar {
            width: 80px;
            height: 80px;
        }
    }
</style>

<img src="<?php echo $avatarUrl; ?>" alt="头像" class="avatar">

3. 带占位符的展示

php
<?php
// 检查头像是否存在
if (file_exists($avatarUrl)) {
    echo "<img src='$avatarUrl' alt='头像' class='avatar'>";
} else {
    echo "<div class='avatar-placeholder'>
            <span>用户</span>
          </div>";
}
?>

<style>
    .avatar-placeholder {
        width: 100px;
        height: 100px;
        border-radius: 50%;
        background-color: #e0e0e0;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 40px;
        color: #9e9e9e;
    }
</style>

完整的头像上传系统

1. 数据库设计

sql
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    avatar VARCHAR(255) DEFAULT 'default-avatar.png',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

2. 上传处理

php
<?php
// upload_avatar.php
require_once 'db.php';
$conn = getDbConnection();

$targetDir = "uploads/avatars/";
$maxFileSize = 2 * 1024 * 1024; // 2MB
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$errors = [];

// 确保上传目录存在
if (!is_dir($targetDir)) {
    mkdir($targetDir, 0755, true);
}

// 假设用户已登录,获取用户ID
$userId = 1; // 实际应用中从会话中获取

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
    $file = $_FILES['avatar'];
    
    // 检查是否有错误
    if ($file['error'] !== 0) {
        $errors[] = '上传失败,错误代码: ' . $file['error'];
    } else {
        // 检查文件大小
        if ($file['size'] > $maxFileSize) {
            $errors[] = "文件太大,最大允许 2MB";
        }
        
        // 检查文件扩展名
        $fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        if (!in_array($fileExtension, $allowedExtensions)) {
            $errors[] = "只允许上传图片文件(JPG、PNG、GIF、WebP)";
        }
        
        // 检查是否为真实图片
        if (getimagesize($file['tmp_name']) === false) {
            $errors[] = "请上传真实的图片文件";
        }
        
        // 处理上传
        if (empty($errors)) {
            // 生成新文件名
            $newFileName = uniqid() . '.' . $fileExtension;
            $targetFile = $targetDir . $newFileName;
            
            if (move_uploaded_file($file['tmp_name'], $targetFile)) {
                // 更新数据库
                $stmt = $conn->prepare("UPDATE users SET avatar = ? WHERE id = ?");
                $stmt->bind_param("si", $newFileName, $userId);
                
                if ($stmt->execute()) {
                    header('Location: profile.php?success=头像上传成功');
                    exit;
                } else {
                    $errors[] = "更新数据库失败";
                }
                
                $stmt->close();
            } else {
                $errors[] = "上传失败,请重试";
            }
        }
    }
}

$conn->close();
?>

3. 个人资料页面

php
<?php
// profile.php
require_once 'db.php';
$conn = getDbConnection();

// 假设用户已登录,获取用户ID
$userId = 1; // 实际应用中从会话中获取

// 获取用户信息
$stmt = $conn->prepare("SELECT username, email, avatar FROM users WHERE id = ?");
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();

if ($result->num_rows > 0) {
    $user = $result->fetch_assoc();
    $avatarUrl = 'uploads/avatars/' . ($user['avatar'] ?: 'default-avatar.png');
    if (!file_exists($avatarUrl)) {
        $avatarUrl = 'default-avatar.png';
    }
} else {
    header('Location: login.php');
    exit;
}

$stmt->close();
$conn->close();
?>

<!DOCTYPE html>
<html>
<head>
    <title>个人资料</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 500px; margin: 0 auto; padding: 20px; }
        h1 { text-align: center; color: #333; }
        .profile { text-align: center; margin: 20px 0; }
        .avatar { width: 150px; height: 150px; border-radius: 50%; object-fit: cover; }
        .user-info { margin: 20px 0; }
        .form-group { margin: 15px 0; }
        label { display: block; margin-bottom: 5px; font-weight: bold; }
        input[type="file"] { padding: 10px; border: 1px solid #ddd; border-radius: 4px; width: 100%; box-sizing: border-box; }
        input[type="submit"] { padding: 10px 20px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
        .message { padding: 10px; margin: 10px 0; border-radius: 4px; }
        .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
        .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
    </style>
</head>
<body>
    <h1>个人资料</h1>
    
    <div class="profile">
        <img src="<?php echo $avatarUrl; ?>" alt="头像" class="avatar">
        <h2><?php echo $user['username']; ?></h2>
        <p><?php echo $user['email']; ?></p>
    </div>
    
    <?php if (isset($_GET['success'])): ?>
        <div class="message success">
            <?php echo $_GET['success']; ?>
        </div>
    <?php endif; ?>
    
    <form action="upload_avatar.php" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label>更换头像: <input type="file" name="avatar" accept="image/*"></label>
        </div>
        <input type="submit" value="上传头像" name="submit">
    </form>
</body>
</html>

图片处理

1. 图片缩放

使用 GD 库对图片进行缩放:

php
<?php
// 缩放图片
function resizeImage($sourcePath, $targetPath, $width, $height) {
    list($sourceWidth, $sourceHeight) = getimagesize($sourcePath);
    
    $sourceImage = null;
    switch (pathinfo($sourcePath, PATHINFO_EXTENSION)) {
        case 'jpg':
        case 'jpeg':
            $sourceImage = imagecreatefromjpeg($sourcePath);
            break;
        case 'png':
            $sourceImage = imagecreatefrompng($sourcePath);
            break;
        case 'gif':
            $sourceImage = imagecreatefromgif($sourcePath);
            break;
        case 'webp':
            $sourceImage = imagecreatefromwebp($sourcePath);
            break;
    }
    
    if (!$sourceImage) {
        return false;
    }
    
    $targetImage = imagecreatetruecolor($width, $height);
    
    // 处理透明背景
    if (pathinfo($sourcePath, PATHINFO_EXTENSION) === 'png') {
        imagealphablending($targetImage, false);
        imagesavealpha($targetImage, true);
        $transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127);
        imagefilledrectangle($targetImage, 0, 0, $width, $height, $transparent);
    }
    
    imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $width, $height, $sourceWidth, $sourceHeight);
    
    $result = false;
    switch (pathinfo($targetPath, PATHINFO_EXTENSION)) {
        case 'jpg':
        case 'jpeg':
            $result = imagejpeg($targetImage, $targetPath, 90);
            break;
        case 'png':
            $result = imagepng($targetImage, $targetPath, 9);
            break;
        case 'gif':
            $result = imagegif($targetImage, $targetPath);
            break;
        case 'webp':
            $result = imagewebp($targetImage, $targetPath, 90);
            break;
    }
    
    imagedestroy($sourceImage);
    imagedestroy($targetImage);
    
    return $result;
}

// 使用示例
$sourcePath = $_FILES['avatar']['tmp_name'];
$targetPath = $targetDir . $newFileName;
$thumbnailPath = $targetDir . 'thumb_' . $newFileName;

// 保存原始图片
move_uploaded_file($sourcePath, $targetPath);

// 创建缩略图
resizeImage($targetPath, $thumbnailPath, 100, 100);
?>

2. 图片裁剪

php
<?php
// 裁剪图片为正方形
function cropImage($sourcePath, $targetPath, $size) {
    list($sourceWidth, $sourceHeight) = getimagesize($sourcePath);
    
    // 计算裁剪区域
    $cropSize = min($sourceWidth, $sourceHeight);
    $cropX = ($sourceWidth - $cropSize) / 2;
    $cropY = ($sourceHeight - $cropSize) / 2;
    
    $sourceImage = null;
    switch (pathinfo($sourcePath, PATHINFO_EXTENSION)) {
        case 'jpg':
        case 'jpeg':
            $sourceImage = imagecreatefromjpeg($sourcePath);
            break;
        case 'png':
            $sourceImage = imagecreatefrompng($sourcePath);
            break;
        case 'gif':
            $sourceImage = imagecreatefromgif($sourcePath);
            break;
        case 'webp':
            $sourceImage = imagecreatefromwebp($sourcePath);
            break;
    }
    
    if (!$sourceImage) {
        return false;
    }
    
    $targetImage = imagecreatetruecolor($size, $size);
    
    // 处理透明背景
    if (pathinfo($sourcePath, PATHINFO_EXTENSION) === 'png') {
        imagealphablending($targetImage, false);
        imagesavealpha($targetImage, true);
        $transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127);
        imagefilledrectangle($targetImage, 0, 0, $size, $size, $transparent);
    }
    
    imagecopyresampled($targetImage, $sourceImage, 0, 0, $cropX, $cropY, $size, $size, $cropSize, $cropSize);
    
    $result = false;
    switch (pathinfo($targetPath, PATHINFO_EXTENSION)) {
        case 'jpg':
        case 'jpeg':
            $result = imagejpeg($targetImage, $targetPath, 90);
            break;
        case 'png':
            $result = imagepng($targetImage, $targetPath, 9);
            break;
        case 'gif':
            $result = imagegif($targetImage, $targetPath);
            break;
        case 'webp':
            $result = imagewebp($targetImage, $targetPath, 90);
            break;
    }
    
    imagedestroy($sourceImage);
    imagedestroy($targetImage);
    
    return $result;
}

// 使用示例
cropImage($targetPath, $targetPath, 300); // 裁剪为 300x300 的正方形
?>

注意事项

  1. 安全性

    • 验证上传文件的类型和大小
    • 重命名文件,避免文件名冲突
    • 限制上传目录的权限
    • 不要将上传的文件直接存储在 web 根目录外
  2. 性能

    • 对上传的图片进行压缩和缩放
    • 生成缩略图,提高加载速度
    • 考虑使用 CDN 存储图片
  3. 用户体验

    • 提供清晰的上传提示
    • 显示上传进度
    • 提供图片预览功能
    • 支持拖拽上传
  4. 兼容性

    • 支持不同浏览器的文件上传
    • 考虑移动设备的上传体验
    • 提供默认头像

练习

  1. 实现一个完整的头像上传系统
  2. 支持图片预览功能
  3. 实现图片缩放和裁剪
  4. 保存头像路径到数据库
  5. 在个人资料页面展示头像

© 2026 编程马·菜鸟教程 版权所有