仇康化
line
宠星球 PetSphere🐕 是一个宠物社区社交网络应用,它连接着宠物主人和宠物服务提供者。

目录

  1. 概述
  2. 角色
  3. 1. 一个高度个性化的Web 3.5应用
  4. 2. 响应式设计和极简UI
  5. 3. 作为一名全栈开发者工作
  6. 4. 实现网络分析
时间线 --
3个月(2023年秋季)
角色 --
概念原型设计
UI设计
前端开发
后端开发
为了 --
连接宠物主人与宠物社区/行业
合作 --
导师: Andrew Twigg
团队: Lucy Cao, Dhruvisha Mondhe

概述

Web3.5社交网络应用

PetSphere是一个充满活力的宠物社区社交网络应用,旨在将宠物主人与服务提供者连接起来,为宠物爱好者营造一个充满活力的社区。该平台提供了一个全面的博客,用于分享想法,以及一个提供遛宠和宠物看护服务的平台,满足宠物主人的多样化需求。

角色

UI设计师 & 全栈开发者

作为设计师和全栈开发者,我的角色涉及设计和开发PetSphere的用户体验和界面。我专注于开发响应式和直观的设计,集成了诸如延迟注册、个性化推荐和用户仪表板等功能。

1. 一个高度个性化的Web 3.5应用

创建帖子

用户可以轻松创建和分享他们的宠物故事或建议。这个功能设计得非常直观,即使是最新手的用户也能轻松上手,鼓励大家积极贡献内容。

标签

每个帖子都可以用相关的关键词标记,这样其他人就可以更容易地找到特定主题的内容,比如"狗狗美容"或"猫咪健康"。

点赞 & 通知

这个功能允许用户对他们认为有用或有趣的帖子点赞,帮助社区发现优质内容。当有人通过点赞/踩/标记与用户的帖子互动时,户会收到通知。

搜索 & 收藏

用户可以使用关键词搜索内容/其他用户。标签用于扩展搜索结果。如果用户发现某些帖子有帮助,可以在用户仪表板上收藏这些帖子,方便将来参考。

个性化推荐系统

该系统根据个人用户的偏好和过去的互动记录推荐帖子,增强用户与相关内容的互动。

2. 响应式设计和极简UI

响应式设计

PetSphere平台经过精心设计,在各种设备上都能提供一致且实用的体验——从台式机到智能手机。这种响应式设计方法确保网站能够根据不同设备的屏幕尺寸和分辨率调整其布局、图像和功能。

用户友好的界面

PetSphere的用户界面设计直观,易于导航,减少了新用户的学习曲线,提升了整体用户体验。关键信息呈现清晰,交互元素触手可及,让用户从发现内容到预订服务的过程变得简单而愉悦。

3. 作为一名全栈开发者工作

PetSphere建立在现代web技术的坚实基础之上,确保无缝且安全的用户体验。我们的技术栈包括HTML、CSS、JavaScript、PHP、SQL、Ruby和Node.js,每一项都在我们平台的功能和美学方面发挥着至关重要的作用。

前端开发

在PetSphere的前端开发中,界面经过精心设计,以提供无缝的用户体验。利用HTML、CSS和JavaScript。例如,我使用HTML构建注册表单,确保每个输入字段都有清晰的标签和易于访问。表单的设计使用CSS进行了增强,使其具有视觉吸引力,并与PetSphere的整体美学保持一致。JavaScript用于前端验证,确保用户在提交表单之前正确输入所需信息。

连接后端

PetSphere的后端在支持整个应用程序方面发挥着关键作用,确保高效的数据处理、安全性和与前端的无缝交互。我们采用了PHP、Node.js和SQL等技术的组合,创建了一个强大且可扩展的后端基础设施。例如,注册表单与后端集成,以处理用户注册。使用PHP和SQL,我们开发了一个安全高效的流程来存储用户数据。PHP中的后端脚本处理表单提交。它包括验证,以检查电子邮件是否已经注册,并在将密码存储到数据库之前对其进行加密。


  <!-- Sample .html Code for Signup Interface Structure -->

  <!DOCTYPE html>
  <html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PetSphere | Sign-up</title>
    <link rel="stylesheet" href="./style.css" />
    <style>
              body {
                background-image: url("http://localhost/petsphere2/assets/images/backgrounds/bg_animal.jpeg");
                background-size: cover;
                background-repeat: no-repeat;
                background-position: center;
                background-attachment: fixed;
              }
          </style>
  </head>
  <body>
    <div class="popup-container">
      <div class="signup">
        <div class="logo-container">
          <a href="./index.php">
            <img class="logo" src="http://localhost/petsphere2/assets/images/icons/logo.png" alt=""/>
          </a>
        </div>
        <form action="signup.php" method="post">
          <!-- user input username -->
          <div class="input-container">
            <input type="text" name="username" value="" required />
            <label for="username">Username</label>
            <div class="error-message"></div>
            <div class="error-message"></div>
          </div>

          <div class="input-container">
            <input type="email" name="email" value="" required />
            <label for="email">Email</label>
            <div class="error-message"></div>
            <div class="error-message"></div>
          </div>

          <div class="input-container">
            <input type="password" name="password" required />
            <label for="password">Password</label>
            <div class="error-message"></div>
            <div class="error-message"></div>
          </div>

          <div class="input-container">
            <input type="password" name="confirm_password" required />
            <label for="confirm_password">Confirm Password</label>
            <div class="error-message"></div>
          </div>

          <button class="popup-button" type="submit">Sign up</button>
        </form>
        <p>Have an account? <a href="login.php" class="popup-link">Login</a></p>
      </div>
    </div>
  </body>
  </html>
              

    /* Sample .css Code for Login & Signup Interface */

    .error-message {
      font-size: 12px;
      padding-top: 2px;
      padding-left: 3px;
      margin-bottom: 0;
      padding-bottom: 0;
      color: var(--MAIN);
    }

    .popup-container {
      display: flex;
      justify-content: center;
      align-items: center;
      height: auto;
    }

    .login,
    .signup {
      position: fixed;
      top: 20vh;
      background-co/lor: rgba(255, 255, 255, 0.9);
      padding: 20px;
      border: 1px solid #cccccc68;
      border-radius: 18px;
      width: 300px;
      padding: 50px;
    }

    .popup-container h2 {
      text-align: center;
      padding-bottom: 0;
      margin-bottom: 5px;
    }

    form .input-container {
      position: relative;
      margin-bottom: 20px;
      width: 100%;
    }

    .popup-container input {
      width: 100%;
      padding: 10px;
      padding-left: 10px;
      padding-right: 32px;
      border: 1px solid var(--GRAYLIGHT);
      border-radius: 10px;
      font-size: 1.05rem;
      font-family: "Karla";
    }

    .popup-container .login input {
      width: 85%;
    }

    .popup-container label {
      position: absolute;
      top: 22px;
      left: 10px;
      transform: translateY(-50%);
      pointer-events: none;
      color: var(--GRAY);
      transition: all 0.2s ease-in-out;
    }

    .popup-container input:focus + label {
      top: 0;
      left: 0;
      padding-bottom: 20px;
      font-size: 12px;
      color: var(--GRAY);
    }

    .popup-container input:valid + label {
      top: 0;
      left: 0;
      padding-bottom: 20px;
      font-size: 12px;
      color: var(--GRAY);
    }

    .popup-container p {
      text-align: center;
      padding-top: 20px;
    }

    .popup-link {
      color: var(--MAIN);
      text-decoration: underline;
    }
                

    // Sample .php Code for Handling Signup

    <?php
    $username = "";
    $email = "";
    $password = "";
    $confirm_password = "";
    $signup_date = date("Y-m-d"); 
    $profile_pic = "";
    $error_array = array(); 


    if ($_SERVER["REQUEST_METHOD"] == "POST") {
        $username = $_POST["username"];
        $_SESSION["username"] = $username;

        $email = $_POST["email"];
        $_SESSION["email"] = $email;

        $password = $_POST["password"];
        $confirm_password = $_POST["confirm_password"];

        // verify username length
        if (strlen($username) > 50 || strlen($username) < 2) {
            array_push($error_array, "Username must be between 2 and 50 characters<br>");
        }

        // check if username already exists
        if ($stmt = $con->prepare("SELECT username FROM users WHERE username = ?")) {
            $stmt->bind_param("s", $username);
            $stmt->execute();
            $result = $stmt->get_result();
            $num_rows = $result->num_rows;
            $stmt->close();

            if ($num_rows > 0) {
                array_push($error_array, "Username already exists<br>");
            }
        }



        // verify email address
        if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $email = filter_var($email, FILTER_VALIDATE_EMAIL);

            // prevent SQL injection
            $stmt = $con->prepare("SELECT email FROM users WHERE email = ?");
            $stmt->bind_param("s", $email);
            $stmt->execute();
            $result = $stmt->get_result();
            $num_rows = $result->num_rows;
            $stmt->close();

            if ($num_rows > 0) {
                array_push($error_array, "Email already in use<br>");
            }
        } else {
            array_push($error_array, "Invalid email address<br>");
        }

        // verify password
        $password_valid = true;
        if (preg_match('/[^A-Za-z0-9]/', $password)) {
            array_push($error_array, "Your password can only contain English letters or numbers<br>");
            $password_valid = false;
        }
        
        if (strlen($password) > 30 || strlen($password) < 5) {
            array_push($error_array, "Your password must be between 5 and 30 characters<br>");
            $password_valid = false;
        }

        if ($password !== $confirm_password) {
            array_push($error_array, "Passwords did not match<br>");
            $password_valid = false;
        }

        // register new user if no errors
        if (empty($error_array) && $password_valid) {
            $hashed_password = password_hash($password, PASSWORD_DEFAULT);

            //profile picture assignment
            $rand = rand(1,3);

            if($rand == 1){
              $profile_pic = "http://localhost/petsphere2/assets/images/profile_pics/momo.jpeg";
            } else if ($rand == 2){
              $profile_pic = "http://localhost/petsphere2/assets/images/profile_pics/momo1.jpg";
            } else if ($rand == 3){
              $profile_pic = "http://localhost/petsphere2/assets/images/profile_pics/momo2.jpg";
            };
            
            // write the data into database
            $stmt = $con->prepare("INSERT INTO users (username, email, password, signup_date, profile_pic) VALUES (?, ?, ?, ?, ?)");
            $stmt->bind_param("sssss", $username, $email, $hashed_password, $signup_date, $profile_pic);
            
            if ($stmt->execute()) {
                echo "Welcome to PetSphere!<br>";
                $_SESSION["username"] = $username;
                header("Location: index.php");
                exit();

            } else {
                echo "Woops, Something went wrong: " . $con->error;
            }
            $stmt->close();
        }
    }
    $_SESSION['error_array'] = $error_array;
    ?>

    <script>
        console.log($error_array)
    </script>
                  

4. 数据分析

用户行为分析

通过分析用户在平台上的行为,比如点赞、收藏、标签使用,我们可以了解到哪些内容更受大家的喜爱。这些数据将用于改进内容策略,为用户提供更多精彩实用的养宠知识和资讯,让每一个用户都能成为最棒的铲屎官。

个性化推荐算法

我使用了外部程序来分析用户行为并创建用户向量。这个算法会考虑用户点赞、收藏的帖子等因素,对未阅读的帖子进行排序,并按相关性推荐给用户。


        # Sample .rb Code for Calculating Keywords
        
        require 'json'
        
        # parses the JSON data passed to the script
        input_json = ARGV[0]
        posts_data = JSON.parse(input_json)
        
        # initializes a hash table for keyword frequencies
        keyword_frequencies = Hash.new(0)
        
        # defines a method for basic text cleaning
        def clean_text(text)
          # basic cleaning: converts to lowercase and removes non-alphanumeric characters
          text.downcase.gsub(/[^a-z0-9\s]/i, '')
        end
        
        # Extended list of stopwords
        stopwords = %w[
          the and to of a in that is was it for on with as by he she they at be this have from or one had 
          by word but not what all were we when your can said there use an each which she do how their if 
          will up other about out many then them these so some her would make like him into time has look 
          two more write go see number no way could people my than first water been call who oil its now 
          find long down day did get come made may part over new sound take only little work know place 
          year live me back give most very after thing our just name good sentence man think say great 
          where help through much before line right too mean old any same tell boy follow came want show 
          also around form three small set put end does another well large must big even such because turn 
          here why ask went men read need land different home us move try kind hand picture again change 
          off play spell air away animal house point page letter mother answer found study still learn 
          should america world high every near add food between own below country plant last school father 
          keep tree never start city earth eye light thought head under story saw left few while 
          along might close something seem next hard open example begin life always those both paper 
          together got group often run important until children side feet car mile night walk white sea 
          began grow took river four carry state once book hear stop without second late miss idea enough 
          eat face watch far indian real almost let above girl sometimes mountains cut young talk soon list 
          song being leave family
        ]
        
        TITLE_TAG_WEIGHT = 100
        
        # iterates over post data, extracting and processing text
        posts_data.each do |post|
          # combines title, body, and tags
          combined_text = post['title'] + ' ' + post['body'] + ' ' + post['tags']
          cleaned_text = clean_text(combined_text)
        
          # splits the text into words and counts frequencies, skipping stopwords
          cleaned_text.split.each do |word|
            next if stopwords.include?(word) 
            keyword_frequencies[word] += 1
          end
        
          title_and_tags = post['title'] + ' ' + post['tags']
          cleaned_title_and_tags = clean_text(title_and_tags)
          cleaned_title_and_tags.split.each do |word|
            next if stopwords.include?(word)
            keyword_frequencies[word] += TITLE_TAG_WEIGHT
          end
        end
        
        # outputs the keyword frequencies in JSON format
        puts keyword_frequencies.to_json
            

        // Sample .php Code for Creating User Vector and Getting Recommendation

        <?php
    
        function fetchPostsLikedByUser($userId, $con) {
            $likedPostsData = [];
            $likedPostsQuery = mysqli_query($con, "SELECT post_id FROM likes WHERE username = '" . mysqli_real_escape_string($con, $userId) . "'");
            if (!$likedPostsQuery) {
                die('Query failed: ' . mysqli_error($con));
            }
            while ($row = mysqli_fetch_assoc($likedPostsQuery)) {
                $postId = $row['post_id'];
                $postQuery = mysqli_query($con, "SELECT title, body, tags FROM posts WHERE id = '$postId'");
                if ($postRow = mysqli_fetch_assoc($postQuery)) {
                    $likedPostsData[] = $postRow;
                }
            }
            return $likedPostsData;
        }
    
        function getKeywordFrequencies($postsData) {
            $rubyExecutable = '/usr/bin/ruby';
            $scriptPath = __DIR__ . '/../assets/ruby/process.rb';
            $command = $rubyExecutable . ' ' . escapeshellarg($scriptPath) . ' ' . escapeshellarg(json_encode($postsData)) . ' 2>&1';
            $output = shell_exec($command);
    
            if ($output === null) {
                die("Error: Ruby script did not return any output");
            }
    
            $decodedOutput = json_decode($output, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                die("Error processing posts data: " . $output . "\nJSON Error: " . json_last_error_msg());
            }
    
            return $decodedOutput;
        }
    
        function getRecommendations($userId, $con) {
        
            $likedPostsData = fetchPostsLikedByUser($userId, $con);
            
            if (empty($likedPostsData)) {
                $allPostsQuery = mysqli_query($con, "SELECT id FROM posts WHERE deleted='no' ORDER BY date_added DESC");
                $allPostsData = mysqli_fetch_all($allPostsQuery, MYSQLI_ASSOC);
                return array_column($allPostsData, 'id');
            }
        
            $keywordFrequencies = getKeywordFrequencies($likedPostsData);
            arsort($keywordFrequencies);
        
            $allPostsQuery = mysqli_query($con, "SELECT id, title, body, tags FROM posts WHERE deleted='no'");
            $allPostsData = mysqli_fetch_all($allPostsQuery, MYSQLI_ASSOC);
        
            usort($allPostsData, function ($post1, $post2) use ($keywordFrequencies, $userId, $con) {
                $score1 = calculatePostScore($post1, $keywordFrequencies);
                $score2 = calculatePostScore($post2, $keywordFrequencies);
        
                $liked1 = isLikedByUser($post1['id'], $userId, $con);
                $liked2 = isLikedByUser($post2['id'], $userId, $con);
        
                if ($liked1 == $liked2) {
                    return $score2 <=> $score1;
                }
        
                return $liked1 ? 1 : -1;
            });
        
            return array_column($allPostsData, 'id');
        }
    
        function isLikedByUser($postId, $userId, $con) {
            $likeQuery = mysqli_query($con, "SELECT * FROM likes WHERE post_id='$postId' AND username='" . mysqli_real_escape_string($con, $userId) . "'");
            return mysqli_num_rows($likeQuery) > 0;
        }
        
    
        function calculatePostScore($post, $keywordFrequencies) {
            $combinedText = $post['title'] . ' ' . $post['body'] . ' ' . $post['tags'];
            $words = explode(' ', $combinedText);
            $score = 0;
    
            foreach ($words as $word) {
                if (isset($keywordFrequencies[$word])) {
                    $score += $keywordFrequencies[$word];
                }
            }
    
            return $score;
        }
    
        if (isset($userLoggedIn)) {
            // get list of posts based on recommendation algorithm
            $recommendations = getRecommendations($userLoggedIn, $con);
            $postIds = implode(',', $recommendations);
            $postQuery = "SELECT * FROM posts WHERE id IN ($postIds) ORDER BY FIELD(id, $postIds)"; // 使用 FIELD 函数确保按推荐顺序排序
        } else {
            $postQuery = "SELECT * FROM posts ORDER BY likes DESC, date_added DESC";
        }
    
        $postResult = mysqli_query($con, $postQuery);
    
        if(mysqli_num_rows($postResult) > 0) {
    
            while($post = mysqli_fetch_assoc($postResult)) {
            $userQuery = "SELECT profile_pic FROM users WHERE username = '" . mysqli_real_escape_string($con, $post['added_by']) . "'";
            $userResult = mysqli_query($con, $userQuery);
            $postUser = mysqli_fetch_assoc($userResult);
    
            date_default_timezone_set('America/New_York');
            $dateAdded = new DateTime($post['date_added']);
            $now = new DateTime("now");
            $interval = $dateAdded->diff($now);
    
            // format date based on the difference
            if ($interval->h < 1) {
                // less than 1 hour
                if ($interval->i == 0) {
                    $timeString = 'Just now';
                } else {
                    $timeString = $interval->i . 'min ago';
                }
            } elseif ($interval->d < 1) {
                // less than 24 hours but more than 1 hour
                $timeString = $interval->h . 'h ago';
            } elseif ($interval->d < 7) {
                // less than 7 days
                $timeString = $interval->d . 'd ago';
            } else {
                // 7 days or more
                $timeString = $dateAdded->format('M d, Y');
            }
    
            // check if the vote, flag, like has been clicked
            $flag_query = mysqli_query($con, "SELECT * FROM flags WHERE post_id='{$post['id']}' AND username='{$userLoggedIn}'");
            $flag_icon_class = (mysqli_num_rows($flag_query) > 0) ? "fa" : "far";
    
            $like_query = mysqli_query($con, "SELECT * FROM likes WHERE post_id='{$post['id']}' AND username='{$userLoggedIn}'");
            $like_icon_class = (mysqli_num_rows($like_query) > 0) ? "fa" : "far";
    
            $vote_query = mysqli_query($con, "SELECT * FROM votes WHERE post_id='{$post['id']}' AND username='{$userLoggedIn}'");
            $vote_icon_class = (mysqli_num_rows($vote_query) > 0) ? "fa" : "far";
    
                echo '
                    <div class="card-container" id="post-' . htmlspecialchars($post['id']) . '">
                        <div class="card-header">
                            <h2>' . htmlspecialchars($post['title']) . '</h2>';
    
                // display tags
                if(!empty($post['tags'])) {
                    $tags = explode(',', $post['tags']);
                    foreach($tags as $tag) {
                        echo '<span class="post-tag">#' . htmlspecialchars(trim($tag)) . '</span> ';
                    }
                }
    
                echo '
                            <a href="#" class="card-userinfo">
                                <div class="user-icon">
                                    <img src="' . htmlspecialchars($postUser['profile_pic']) . '" alt="" />
                                </div>
                                <span>' . htmlspecialchars($post['added_by']) . '</span>
                                <span> &nbsp;&nbsp;&nbsp;</span>
                                <span class="post-date" style="color:#b1a290">' . htmlspecialchars($timeString) . '</span>
                                <span> &nbsp;&nbsp;&nbsp;</span>                        
                                <span class="critter-type">' . htmlspecialchars($post['category']) . '</span>
                            </a>
                        </div>
                        
                        <div class="card-content">
                            <img src="' . htmlspecialchars($post['imageName']) . '" alt="" class="card-image" />
                            <div>' . $post['body'] . '</div>
                        </div>
                        
                        <div class="card-footer">
                            <div class="interaction-icons">   
                            <i class="' . $vote_icon_class . ' fa-thumbs-up" id="vote-icon-' . $post['id'] . '" onclick="toggleVote(' . $post['id'] . ', \'' . $userLoggedIn . '\')"></i>
                            <span>' . htmlspecialchars($post['votes']) . ' votes</span>
                        
                            <i class="' . $flag_icon_class . ' fa-thumbs-down" id="flag-icon-' . $post['id'] . '" onclick="toggleFlag(' . $post['id'] . ', \'' . $userLoggedIn . '\')"></i>
                            <span>' . htmlspecialchars($post['flags']) . ' flags</span>
                        
                            <i class="' . $like_icon_class . ' fa-heart" id="like-icon-' . $post['id'] . '" onclick="toggleLike(' . $post['id'] . ', \'' . $userLoggedIn . '\')"></i>
                            <span>' . htmlspecialchars($post['likes']) . '</span>
                        </div>';
                            
                            
                if (isset($userLoggedIn) && $userLoggedIn != $post['added_by']) {
                    echo '
                    <div class="tag-add-form">
                        <form action="/petsphere2/includes/form_handlers/tag_adder.php" method="POST">
                            <span>#</span>
                            <input type="hidden" name="post_id" value="' . htmlspecialchars($post['id']) . '">
                            <input type="text" name="tag" placeholder="Add a tag">
                            <button type="submit" name="add_tag">+ Tag</button>
                        </form>
                    </div>
                    ';
                }
                        
                
                echo '
                    
                    </div>
                    </div>
                ';
            }
        } else {
            echo 'No posts found.';
        }
        ?>
              

更多作品

Bumble遇见

课程项目 - 产品设计

Bumble 遇见(Vibely)引入交互式地图,促进基于实时地理位置的时效性互动,提升用户在线交友体验并增加转化率。

Bumble Vibely项目图片