You need to enable JavaScript to run this app. Angular+Firebase按登录用户ID存储数据的实现及刷新异常问题
优惠活动
大模型
产品
解决方案
定价
更多

Angular+Firebase按登录用户ID存储数据的实现及刷新异常问题

问题根因定位

你遇到的刷新丢数问题本质是异步执行时序竞争:

  • AngularFireAuth提供的authState是异步回调,页面刷新时的执行顺序为:组件构造函数初始化→触发ngOnInit生命周期→同步执行getEmployees()方法→此时authState的回调尚未返回,this.userId仍为初始的undefined
  • 此时实际发起的Firebase请求路径是employees/undefined,完全匹配不到你之前存在employees/{真实用户uid}下的业务数据,因此列表为空
  • 后续authState回调触发、将this.userId赋值为真实用户ID时,之前发起的getEmployees请求已经执行完毕,不会自动重新拉取数据,最终表现为刷新后数据全部消失
  • 首次登录、浏览器返回时能正常显示,是因为此时用户认证状态已经完成本地缓存,userId提前完成了赋值,getEmployees执行时可以拼接出正确的数据库路径
  • 原有实现还存在安全隐患:仅靠前端拼接uid的路径做隔离,没有后端规则校验的话,用户可以通过篡改前端代码读取其他账号的数据,隔离效果仅停留在表面
适配现有代码的修复方案

Service层改造:绑定认证状态与数据请求,解决时序问题

删除原先独立的userId成员变量,把数据请求逻辑和authState流做串联,等用户认证状态确定后再发起数据库请求,彻底避免空值请求的问题:

@Injectable({
providedIn: 'root'
})
export class EmployeeService {
  constructor(
    public firebase: AngularFireDatabase, 
    private datePipe: DatePipe, 
    private afu: AngularFireAuth, 
    public clientService: ClientService, 
    public contractsService: ContractsService, 
    public maintainerService: MaintainerService
  ) {}

  // 原有成员变量、form定义、initializeFormGroup方法完全保留,不做修改

  getEmployees(){
    return this.afu.authState.pipe(
      // 过滤未登录的空状态,等认证结果返回
      filter(user => !!user),
      // 拿到真实uid后再拼接路径发起数据库请求
      switchMap(user => {
        this.employeeList = this.firebase.list(`employees/${user.uid}`);
        return this.employeeList.snapshotChanges();
      })
    );
  }
}
  • 这个逻辑下不管什么时候调用getEmployees(),都会等authState返回明确的用户信息后,再发起对应路径的请求,不会出现空值路径的问题
  • 当用户登录、登出、刷新页面时,只要认证状态变化,流就会自动重新发起正确的请求,不需要额外手动维护赋值逻辑

组件层适配:补全订阅销毁逻辑

原有组件的列表渲染逻辑不需要大改,只需要补充组件销毁时的订阅取消,避免内存泄漏:

import { Subject, takeUntil } from 'rxjs';

export class YourTargetComponent implements OnInit, OnDestroy {
  // 原有组件成员变量(sort、paginator、displayColumns、listData等)完全保留
  private destroy$ = new Subject<void>();

  ngOnInit(): void {
    this.service.getEmployees()
      .pipe(takeUntil(this.destroy$))
      .subscribe(list => {
        // 原有列表映射、MatTableDataSource赋值、过滤器配置逻辑完全不变
        let array = list.map(item => {
          let clientName = this.clientService.getClientName(item.payload.val()['client']);
          let maintainerName = this.maintainerService.getMaintainerName(item.payload.val()['maintainer']);
          return {
            $key: item.key,
            clientName,
            maintainerName,
            ...item.payload.val()
          };
        });
        this.listData = new MatTableDataSource(array);
        this.listData.sort = this.sort;
        this.listData.paginator = this.paginator;
        this.listData.filterPredicate = (data, filter) => {
          return this.displayColumns.some(ele => {
            return ele != 'actions' && data[ele].toLowerCase().indexOf(filter) != -1;
          });
        }
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

数据库层配置:落地真正的用户数据隔离

仅靠前端代码按uid拼路径无法做到真正的隔离,需要在Firebase Realtime Database的规则中补充权限校验,从服务端层面阻止跨账号访问:

按业务实际路径配置规则,核心逻辑是仅允许用户读写自己uid对应的路径下的数据:

{
  "rules": {
    "employees": {
      "$uid": {
        ".read": "auth.uid === $uid",
        ".write": "auth.uid === $uid"
      }
    },
    "clients": {
      "$uid": {
        ".read": "auth.uid === $uid",
        ".write": "auth.uid === $uid"
      }
    },
    "maintainers": {
      "$uid": {
        ".read": "auth.uid === $uid",
        ".write": "auth.uid === $uid"
      }
    },
    "contracts": {
      "$uid": {
        ".read": "auth.uid === $uid",
        ".write": "auth.uid === $uid"
      }
    }
  }
}
  • 配置完成后,即使用户篡改前端代码尝试请求其他用户路径的数据,数据库也会直接拒绝响应,从根源上保障数据隔离的安全性

内容的提问来源于stack exchange,提问作者puzzy.x

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.29 16:54:03