MemberAudience.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. <?php
  2. namespace App\Support;
  3. use Illuminate\Support\Facades\DB;
  4. /**
  5. * 会员受众:区分正式数据和模拟数据(users.user_type = 2 的后台模拟会员)。
  6. * 默认 REAL:所有列表和统计都看不到模拟会员;SIMULATED:只看模拟会员,仅 /admin/simulation/* 通过 simulated() 进入。
  7. * 游客(users.from = 2 且不是模拟会员)是否参与由调用方决定,这里只管模拟会员。
  8. */
  9. final class MemberAudience
  10. {
  11. public const REAL = 'real';
  12. public const SIMULATED = 'simulated';
  13. public const TYPE_SIMULATED = 2;
  14. private static string $mode = self::REAL;
  15. private static ?\WeakMap $enabled = null;
  16. private static ?\WeakMap $memo = null;
  17. public static function isSimulated(): bool
  18. {
  19. return self::$mode === self::SIMULATED;
  20. }
  21. /** 在“仅模拟会员”口径下执行 $fn,结束后一定还原。 */
  22. public static function simulated(callable $fn)
  23. {
  24. $previous = self::$mode;
  25. self::$mode = self::SIMULATED;
  26. try {
  27. return $fn();
  28. } finally {
  29. self::$mode = $previous;
  30. }
  31. }
  32. /** users.user_type 存在才区分(迁移执行前不过滤)。按数据库连接缓存。 */
  33. public static function enabled(): bool
  34. {
  35. self::$enabled ??= new \WeakMap();
  36. $connection = DB::connection();
  37. return self::$enabled[$connection] ??= DB::getSchemaBuilder()->hasColumn('users', 'user_type');
  38. }
  39. /** 模拟会员的某一列(member_id / user_id / id)子查询。 */
  40. public static function simulatedMembers(string $column = 'member_id')
  41. {
  42. return DB::table('users')->select($column)->where('user_type', self::TYPE_SIMULATED)->whereNotNull($column);
  43. }
  44. /** 查询里已有 users 表(或别名)时:REAL 排除模拟会员,SIMULATED 只留模拟会员。 */
  45. public static function visibleUsers($query, string $alias = 'users')
  46. {
  47. if (!self::enabled()) return self::isSimulated() ? $query->whereRaw('1 = 0') : $query;
  48. return $query->where($alias . '.user_type', self::isSimulated() ? '=' : '<>', self::TYPE_SIMULATED);
  49. }
  50. /** 查询里没有 users 表时,按会员列过滤。$userColumn 是 $column 对应的 users 列。$column 为空的行在 REAL 口径下保留。 */
  51. public static function visibleMembers($query, string $column = 'member_id', string $userColumn = 'member_id')
  52. {
  53. if (!self::enabled()) return self::isSimulated() ? $query->whereRaw('1 = 0') : $query;
  54. if (self::isSimulated()) return $query->whereIn($column, self::simulatedMembers($userColumn));
  55. return $query->where(fn ($q) => $q->whereNull($column)->orWhereNotIn($column, self::simulatedMembers($userColumn)));
  56. }
  57. /** 原生 SQL 用:users 别名为 $alias 时追加的条件(以 AND 开头)。 */
  58. public static function rawVisibleUsers(string $alias = 'u'): string
  59. {
  60. if (!self::enabled()) return self::isSimulated() ? ' AND 1 = 0' : '';
  61. return " AND {$alias}.user_type " . (self::isSimulated() ? '=' : '<>') . ' ' . self::TYPE_SIMULATED;
  62. }
  63. /** 某个 member_id 是否是模拟会员(同一连接内缓存;用户类型创建后不会变)。 */
  64. public static function isSimulatedMember($memberId): bool
  65. {
  66. if ($memberId === null || $memberId === '' || !self::enabled()) return false;
  67. self::$memo ??= new \WeakMap();
  68. $connection = DB::connection();
  69. $known = self::$memo[$connection] ?? [];
  70. $key = (string) $memberId;
  71. if (!isset($known[$key])) {
  72. $known[$key] = DB::table('users')->where('member_id', $key)->where('user_type', self::TYPE_SIMULATED)->exists();
  73. self::$memo[$connection] = $known;
  74. }
  75. return $known[$key];
  76. }
  77. /** 新建模拟会员后登记,免得同一请求里缓存了“不是模拟会员”。 */
  78. public static function remember($memberId): void
  79. {
  80. self::$memo ??= new \WeakMap();
  81. $connection = DB::connection();
  82. $known = self::$memo[$connection] ?? [];
  83. $known[(string) $memberId] = true;
  84. self::$memo[$connection] = $known;
  85. }
  86. }