You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firestore中获取当前用户专属Profile UID的技术问题

问题描述

我正在开发React+Firestore项目,数据库中有2000个不同用户的Profile UID。当前代码会获取所有用户的UID,但我需要仅获取当前登录用户创建的特定Profile UID,而非全部用户的UID。


相关代码及截图

Sidebar.js

import React, { useEffect, useState } from "react";
import { Link } from 'react-router-dom';
import {
  Card,
  Typography,
  List,
  ListItem,
  ListItemPrefix,
  ListItemSuffix,
  Chip,
  Input,
} from "@material-tailwind/react";
import {
  UserCircleIcon,
  InboxIcon,
} from "@heroicons/react/solid";
import {
  SearchIcon
} from "@heroicons/react/outline";
import { db } from '../firebase/firebaseConfig';
import { collection, getDocs } from 'firebase/firestore';
import { getAuth } from "firebase/auth";

const Sidebar = () => {
  const [userProfiles, setUserProfiles] = useState([]);
  const auth = getAuth();

  useEffect(() => {
    const fetchUserProfiles = async () => {
      try {
        const profileCollectionRef = collection(db, 'profiles');
        const querySnapshot = await getDocs(profileCollectionRef);

        const profilesData = [];
        querySnapshot.forEach((doc) => {
          profilesData.push({ id: doc.id, ...doc.data() });
        });

        setUserProfiles(profilesData);
      } catch (error) {
        console.error('Error fetching user profiles:', error);
      }
    };

    fetchUserProfiles();
  }, []);

  return (
    <Card className="h-[calc(100vh-2rem)] w-full max-w-[20rem] p-4 shadow-xl shadow-blue-gray-900/5 text-gray-500">
      <div className="mb-2 flex items-center gap-4 p-4 font-bricolage-grotesque">
        <img src="Logo.svg" alt="brand" className="h-8 w-8" />
        <Typography variant="h5" color="blue-gray">
          Sidebar
        </Typography>
      </div>
      <div className="p-2">
        <Input className="rounded-md w-[16.6rem]" icon={<SearchIcon className="h-5 focus:none pt-2 pl-2 w-5" />} />
      </div>
      <List>
        <hr className="my-2 border-blue-gray-50" />
        <Link to="/Offers" className="ml-1 no-underline text-black">
          <ListItem>
            <ListItemPrefix>
              <InboxIcon className="h-5 w-5" />
            </ListItemPrefix>
            <p className="ml-1 text-black font-gowun-dodum text-[1.5rem] ">Offers</p>
            <ListItemSuffix>
              <Chip value="14" size="sm" variant="ghost" color="blue-gray" className="rounded-full ml-1" />
            </ListItemSuffix>
          </ListItem>
        </Link>

        {userProfiles.map((profile) => (
          <Link key={profile.id} to={`/Profile/${profile.id}` || '/CreateProfile'} className="ml-1 mt-4 no-underline text-black">
            <ListItem>
              <ListItemPrefix>
                <UserCircleIcon className="h-5 w-5" />
              </ListItemPrefix>
              <button className="font-gowun-dodum text-[1.5rem] bg-white text-black">{"Your Profile" || "CreateProfile"}</button>
            </ListItem>
          </Link>
        ))}

        <Link to="/Settings" className="ml-1 no-underline text-black">
          <ListItem>
            <ListItemPrefix>
              <img src="cog.svg" className="h-5 w-5" />
            </ListItemPrefix>
            <p className=" font-gowun-dodum text-[1.5rem] ml-1 text-black">Settings</p>
          </ListItem>
        </Link>
      </List>
    </Card>
  );
}

export default Sidebar;

CreateProfile.js

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { db } from '../firebase/firebaseConfig';
import { doc, getDoc, setDoc } from 'firebase/firestore';
import { v4 as uuidv4 } from 'uuid';

const defaultProfileData = {
  name: '',
  about: '',
  previousOccupation: '',
  email: '',
  social: '',
  portfolio_website: '',
  githublink: '',
};

export default function CreateProfile() {
  const navigate = useNavigate();
  const [profileData, setProfileData] = useState(defaultProfileData);
  const [profileExists, setProfileExists] = useState(false);

  useEffect(() => {
    const userName = uuidv4();

    const checkProfile = async () => {
      try {
        const profileRef = doc(db, 'profiles', userName);
        const profileSnapshot = await getDoc(profileRef);
        if (profileSnapshot.exists()) {
          setProfileExists(true);
        }
      } catch (error) {
        console.error('Error checking user profile:', error);
      }
    };

    checkProfile();
  }, []);

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      const userName = uuidv4(); 
      const profileRef = doc(db, 'profiles', userName);
      await setDoc(profileRef, profileData);
      console.log('User profile created successfully.');
      navigate(`/profile/${userName}`); 
    } catch (error) {
      console.error('Error creating user profile:', error);
    }
  };

  if (profileExists) {
    return null;
  }

  return (
    <div className='w-[24rem] mx-auto'>
      <h1 className='text-2xl font-semibold mb-4'>Create Your Profile</h1>
      <form onSubmit={handleSubmit}>
        {/* Name */}
        <div className='mb-4'>
          <label htmlFor='name' className='block text-gray-700 font-medium mb-2'>
            Your Name
          </label>
          <input
            type='text'
            id='name'
            className='w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300'
            value={profileData.name}
            onChange={(e) => setProfileData({ ...profileData, name: e.target.value })}
            maxLength={30}
          />
        </div>

        {/* About Me */}
        <div className='mb-4'>
          <label htmlFor='about' className='block text-gray-700 font-medium mb-2'>
            About Me
          </label>
          <textarea
            id='about'
            className='w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300'
            rows='4'
            value={profileData.about}
            onChange={(e) => setProfileData({ ...profileData, about: e.target.value })}
            maxLength={200}
          />
        </div>

        {/* Previous Occupation */}
        <div className='mb-4'>
          <label htmlFor='previousOccupation' className='block text-gray-700 font-medium mb-2'>
            Previous Occupation
          </label>
          <input
            maxLength={50}
            type='text'
            id='previousOccupation'
            className='w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300'
            value={profileData.previousOccupation}
            onChange={(e) => setProfileData({ ...profileData, previousOccupation: e.target.value })}
          />
        </div>

        {/* Email */}
        <div className='mb-4'>
          <label htmlFor='email' className='block text-gray-700 font-medium mb-2'>
            Email
          </label>
          <input
            maxLength={50}
            type='email'
            id='email'
            className='w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300'
            value={profileData.email}
            onChange={(e) => setProfileData({ ...profileData, email: e.target.value })}
          />
        </div>

        {/* Social Links */}
        <div className='mb-4'>
          <label htmlFor='social' className='block text-gray-700 font-medium mb-2'>
            Social Links
          </label>
          <input
            maxLength={80}
            type='text'
            id='social'
            className='w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300'
            value={profileData.social}
            onChange={(e) => setProfileData({ ...profileData, social: e.target.value })}
          />
        </div>

        <div className='mb-4'>
          <label htmlFor='portfolio_website' className='block text-gray-700 font-medium mb-2'>
            Portfolio Website
          </label>
          <input
            maxLength={80}
            type='text'
            id='portfolio_website'
            className='w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300'
            value={profileData.portfolio_website}
            onChange={(e) => setProfileData({ ...profileData, portfolio_website: e.target.value })}
          />
          </div>
          <div className='mb-4'>
            <label htmlFor='portfolio_website' className='block text-gray-700 font-medium mb-2'>
              Github Link
            </label>
            <input
              maxLength={80}
              type='text'
              id='githublink'
              className='w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring focus:border-blue-300'
              value={profileData.githublink}
              onChange={(e) => setProfileData({ ...profileData, githublink: e.target.value })}
            />
          </div>

          <button
            type='submit'
            className='w-full py-2 bg-blue-500 text-white font-semibold rounded-lg hover:bg-blue-600'
          >
            Create Profile
          </button>
      </form>
    </div>
  );
}

Firebase规则

Firebase规则截图

数据库截图

数据库数据截图

当前结果截图

当前问题截图


解决方案

核心问题是代码未将用户创建的Profile与登录用户UID关联,且查询时无过滤条件。以下是修复步骤:

1. 关联Profile与登录用户UID

修改CreateProfile.js,用当前登录用户的UID作为Profile文档ID(保证每个用户仅能创建一个Profile),并将用户UID存入文档:

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { db } from '../firebase/firebaseConfig';
import { doc, getDoc, setDoc } from 'firebase/firestore';
import { getAuth } from "firebase/auth";

const defaultProfileData = {
  name: '',
  about: '',
  previousOccupation: '',
  email: '',
  social: '',
  portfolio_website: '',
  githublink: '',
};

export default function CreateProfile() {
  const navigate = useNavigate();
  const [profileData, setProfileData] = useState(defaultProfileData);
  const [profileExists, setProfileExists] = useState(false);
  const auth = getAuth();
  const currentUser = auth.currentUser;

  useEffect(() => {
    const checkProfile = async () => {
      if (!currentUser) return;
      try {
        const profileRef = doc(db, 'profiles', currentUser.uid);
        const profileSnapshot = await getDoc(profileRef);
        if (profileSnapshot.exists()) {
          setProfileExists(true);
          navigate(`/profile/${currentUser.uid}`);
        }
      } catch (error) {
        console.error('Error checking user profile:', error);
      }
    };

    checkProfile();
  }, [currentUser, navigate]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!currentUser) return;

    try {
      const profileRef = doc(db, 'profiles', currentUser.uid);
      await setDoc(profileRef, {
        ...profileData,
        createdBy: currentUser.uid
      });
      console.log('User profile created successfully.');
      navigate(`/profile/${currentUser.uid}`); 
    } catch (error) {
      console.error('Error creating user profile:', error);
    }
  };

  if (profileExists || !currentUser) {
    return null;
  }

  return (
    <div className='w-[24rem] mx-auto'>
      <h1 className='text-2xl font-semibold mb-4'>Create Your Profile</h1>
      <form onSubmit={handleSubmit}>
        {/* 原表单字段保持不变 */}
      </form>
    </div>
  );
}

2. 修改Sidebar查询逻辑,仅获取当前用户的Profile

修改Sidebar.js,监听用户登录状态,仅查询当前用户的Profile:

import React, { useEffect, useState } from "react";
import { Link } from 'react-router-dom';
import {
  Card,
  Typography,
  List,
  ListItem,
  ListItemPrefix,
  ListItemSuffix,
  Chip,
  Input,
} from "@material-tailwind/react";
import {
  UserCircleIcon,
  InboxIcon,
} from "@heroicons/react/solid";
import {
  SearchIcon
} from "@heroicons/react/outline";
import { db } from '../firebase/firebaseConfig';
import { collection, query, where, getDocs, doc, getDoc } from 'firebase/firestore';
import { getAuth, onAuthStateChanged } from "firebase/auth";

const Sidebar = () => {
  const [userProfiles, setUserProfiles] = useState([]);
  const auth = getAuth();
  const [currentUser, setCurrentUser] = useState(null);

  useEffect(() => {
    const unsubscribe = onAuthStateChanged(auth, (user) => {
      setCurrentUser(user);
      if (user) {
        fetchUserProfile(user);
      } else {
        setUserProfiles([]);
      }
    });

    return () => unsubscribe();
  }, [auth]);

  const fetchUserProfile = async (user) => {
    try {
      // 方式一:用用户UID直接获取单个文档
      const profileRef = doc(db, 'profiles', user.uid);
      const profileSnapshot = await getDoc(profileRef);
      if (profileSnapshot.exists()) {
        setUserProfiles([{ id: profileSnapshot.id, ...profileSnapshot.data() }]);
      } else {
        setUserProfiles([]);
      }

      // 方式二:如果用createdBy字段过滤(备用)
      // const profileQuery = query(
      //   collection(db, 'profiles'),
      //   where('createdBy', '==', user.uid)
      // );
      // const querySnapshot = await getDocs(profileQuery);
      // const profilesData = [];
      // querySnapshot.forEach((doc) => profilesData.push({ id: doc.id, ...doc.data() }));
      // setUserProfiles(profilesData);
    } catch (error) {
      console.error('Error fetching user profile:', error);
    }
  };

  return (
    <Card className="h-[calc(100vh-2rem)] w-full max-w-[20rem] p-4 shadow-xl shadow-blue-gray-900/5 text-gray-500">
      <div className="mb-2 flex items-center gap-4 p-4 font-bricolage-grotesque">
        <img src="Logo.svg" alt="brand" className="h-8 w-8" />
        <Typography variant="h5" color="blue-gray">
          Sidebar
        </Typography>
      </div>
      <div className="p-2">
        <Input className="rounded-md w-[16.6rem]" icon={<SearchIcon className="h-5 focus:none pt-2 pl-2 w-5" />} />
      </div>
      <List>
        <hr className="my-2 border-blue-gray-50" />
        <Link to="/Offers" className="ml-1 no-underline text-black">
          <ListItem>
            <ListItemPrefix>
              <InboxIcon className="h-5 w-5" />
            </ListItemPrefix>
            <p className="ml-1 text-black font-gowun-dodum text-[
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:02:05