@mulingai-npm/redis 3.29.4 → 3.29.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -8,4 +8,5 @@ var RedisDatabase;
|
|
|
8
8
|
RedisDatabase[RedisDatabase["MULINGSTREAM_SPEAKER"] = 2] = "MULINGSTREAM_SPEAKER";
|
|
9
9
|
RedisDatabase[RedisDatabase["CONTENT_DATA"] = 3] = "CONTENT_DATA";
|
|
10
10
|
RedisDatabase[RedisDatabase["ACCOUNT_DATA"] = 4] = "ACCOUNT_DATA";
|
|
11
|
+
RedisDatabase[RedisDatabase["USAGE_DATA"] = 5] = "USAGE_DATA";
|
|
11
12
|
})(RedisDatabase = exports.RedisDatabase || (exports.RedisDatabase = {}));
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { RedisClient } from '../redis-client';
|
|
2
|
+
/**
|
|
3
|
+
* Pending usage data for a user
|
|
4
|
+
*/
|
|
5
|
+
export interface PendingUsageData {
|
|
6
|
+
userId: string;
|
|
7
|
+
billingMs: number;
|
|
8
|
+
roomDurations: Record<string, number>;
|
|
9
|
+
lastUpdated: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Usage Data Manager
|
|
13
|
+
*
|
|
14
|
+
* Stores pending streaming usage in Redis for batch DB writes.
|
|
15
|
+
* Pipeline service writes here every 5 seconds during streaming.
|
|
16
|
+
* Job service flushes to DB every 30-60 seconds.
|
|
17
|
+
*
|
|
18
|
+
* Key structure:
|
|
19
|
+
* - usage:pending:{userId} - Hash with billing and room usage
|
|
20
|
+
* - usage:pending:users - Set of all user IDs with pending usage
|
|
21
|
+
*/
|
|
22
|
+
export declare class UsageDataManager {
|
|
23
|
+
private redisClient;
|
|
24
|
+
private readonly EXPIRATION;
|
|
25
|
+
constructor(redisClient: RedisClient);
|
|
26
|
+
/**
|
|
27
|
+
* Key for a user's pending usage data
|
|
28
|
+
*/
|
|
29
|
+
private userKey;
|
|
30
|
+
/**
|
|
31
|
+
* Key for the set of users with pending usage
|
|
32
|
+
*/
|
|
33
|
+
private usersSetKey;
|
|
34
|
+
/**
|
|
35
|
+
* Add streaming usage for a user
|
|
36
|
+
* Called by pipeline-service every 5 seconds during streaming
|
|
37
|
+
*/
|
|
38
|
+
addUsage(userId: string, roomId: string, durationMs: number): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Get all users with pending usage
|
|
41
|
+
*/
|
|
42
|
+
getUsersWithPendingUsage(): Promise<string[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Get pending usage for a specific user
|
|
45
|
+
*/
|
|
46
|
+
getPendingUsage(userId: string): Promise<PendingUsageData | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Get all pending usage data (for batch flush)
|
|
49
|
+
*/
|
|
50
|
+
getAllPendingUsage(): Promise<PendingUsageData[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Clear pending usage for a specific user (after successful DB write)
|
|
53
|
+
*/
|
|
54
|
+
clearUserUsage(userId: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Clear all pending usage (after successful batch flush)
|
|
57
|
+
*/
|
|
58
|
+
clearAllUsage(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Flush a single user's usage to DB immediately
|
|
61
|
+
* Called when session closes to ensure no data is lost
|
|
62
|
+
* Returns the usage data that was flushed (for logging)
|
|
63
|
+
*/
|
|
64
|
+
getAndClearUserUsage(userId: string): Promise<PendingUsageData | null>;
|
|
65
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UsageDataManager = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Usage Data Manager
|
|
6
|
+
*
|
|
7
|
+
* Stores pending streaming usage in Redis for batch DB writes.
|
|
8
|
+
* Pipeline service writes here every 5 seconds during streaming.
|
|
9
|
+
* Job service flushes to DB every 30-60 seconds.
|
|
10
|
+
*
|
|
11
|
+
* Key structure:
|
|
12
|
+
* - usage:pending:{userId} - Hash with billing and room usage
|
|
13
|
+
* - usage:pending:users - Set of all user IDs with pending usage
|
|
14
|
+
*/
|
|
15
|
+
class UsageDataManager {
|
|
16
|
+
constructor(redisClient) {
|
|
17
|
+
this.EXPIRATION = 24 * 60 * 60; // 24 hours TTL
|
|
18
|
+
this.redisClient = redisClient;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Key for a user's pending usage data
|
|
22
|
+
*/
|
|
23
|
+
userKey(userId) {
|
|
24
|
+
return `usage:pending:${userId}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Key for the set of users with pending usage
|
|
28
|
+
*/
|
|
29
|
+
usersSetKey() {
|
|
30
|
+
return 'usage:pending:users';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Add streaming usage for a user
|
|
34
|
+
* Called by pipeline-service every 5 seconds during streaming
|
|
35
|
+
*/
|
|
36
|
+
async addUsage(userId, roomId, durationMs) {
|
|
37
|
+
const key = this.userKey(userId);
|
|
38
|
+
// Increment billing total
|
|
39
|
+
await this.redisClient.hincrby(key, 'billingMs', durationMs);
|
|
40
|
+
// Increment room-specific duration
|
|
41
|
+
await this.redisClient.hincrby(key, `room:${roomId}`, durationMs);
|
|
42
|
+
// Update timestamp
|
|
43
|
+
await this.redisClient.hset(key, { lastUpdated: Date.now().toString() });
|
|
44
|
+
// Set expiration
|
|
45
|
+
await this.redisClient.expire(key, this.EXPIRATION);
|
|
46
|
+
// Add user to the set of users with pending usage
|
|
47
|
+
await this.redisClient.sadd(this.usersSetKey(), userId);
|
|
48
|
+
await this.redisClient.expire(this.usersSetKey(), this.EXPIRATION);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get all users with pending usage
|
|
52
|
+
*/
|
|
53
|
+
async getUsersWithPendingUsage() {
|
|
54
|
+
const members = await this.redisClient.smembers(this.usersSetKey());
|
|
55
|
+
return members || [];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get pending usage for a specific user
|
|
59
|
+
*/
|
|
60
|
+
async getPendingUsage(userId) {
|
|
61
|
+
const key = this.userKey(userId);
|
|
62
|
+
const data = await this.redisClient.hgetall(key);
|
|
63
|
+
if (!data || Object.keys(data).length === 0) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const billingMs = parseInt(data.billingMs || '0', 10);
|
|
67
|
+
const lastUpdated = parseInt(data.lastUpdated || '0', 10);
|
|
68
|
+
// Extract room durations (keys starting with "room:")
|
|
69
|
+
const roomDurations = {};
|
|
70
|
+
for (const [field, value] of Object.entries(data)) {
|
|
71
|
+
if (field.startsWith('room:')) {
|
|
72
|
+
const roomId = field.substring(5); // Remove "room:" prefix
|
|
73
|
+
roomDurations[roomId] = parseInt(value, 10);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
userId,
|
|
78
|
+
billingMs,
|
|
79
|
+
roomDurations,
|
|
80
|
+
lastUpdated
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get all pending usage data (for batch flush)
|
|
85
|
+
*/
|
|
86
|
+
async getAllPendingUsage() {
|
|
87
|
+
const userIds = await this.getUsersWithPendingUsage();
|
|
88
|
+
const results = [];
|
|
89
|
+
for (const userId of userIds) {
|
|
90
|
+
const usage = await this.getPendingUsage(userId);
|
|
91
|
+
if (usage && usage.billingMs > 0) {
|
|
92
|
+
results.push(usage);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Clear pending usage for a specific user (after successful DB write)
|
|
99
|
+
*/
|
|
100
|
+
async clearUserUsage(userId) {
|
|
101
|
+
const key = this.userKey(userId);
|
|
102
|
+
await this.redisClient.del(key);
|
|
103
|
+
await this.redisClient.srem(this.usersSetKey(), userId);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Clear all pending usage (after successful batch flush)
|
|
107
|
+
*/
|
|
108
|
+
async clearAllUsage() {
|
|
109
|
+
const userIds = await this.getUsersWithPendingUsage();
|
|
110
|
+
for (const userId of userIds) {
|
|
111
|
+
await this.redisClient.del(this.userKey(userId));
|
|
112
|
+
}
|
|
113
|
+
await this.redisClient.del(this.usersSetKey());
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Flush a single user's usage to DB immediately
|
|
117
|
+
* Called when session closes to ensure no data is lost
|
|
118
|
+
* Returns the usage data that was flushed (for logging)
|
|
119
|
+
*/
|
|
120
|
+
async getAndClearUserUsage(userId) {
|
|
121
|
+
const usage = await this.getPendingUsage(userId);
|
|
122
|
+
if (usage) {
|
|
123
|
+
await this.clearUserUsage(userId);
|
|
124
|
+
}
|
|
125
|
+
return usage;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.UsageDataManager = UsageDataManager;
|
package/dist/redis-client.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare class RedisClient {
|
|
|
19
19
|
smembers(key: string): Promise<string[]>;
|
|
20
20
|
hset(key: string, data: Record<string, string>): Promise<number>;
|
|
21
21
|
hgetall(key: string): Promise<Record<string, string>>;
|
|
22
|
+
hincrby(key: string, field: string, increment: number): Promise<number>;
|
|
22
23
|
zadd(key: string, score: number, member: string): Promise<number>;
|
|
23
24
|
zrange(key: string, start: number, stop: number): Promise<string[]>;
|
|
24
25
|
zremrangebyrank(key: string, start: number, stop: number): Promise<number>;
|
package/dist/redis-client.js
CHANGED
|
@@ -52,6 +52,9 @@ class RedisClient {
|
|
|
52
52
|
async hgetall(key) {
|
|
53
53
|
return this.client.hgetall(key);
|
|
54
54
|
}
|
|
55
|
+
async hincrby(key, field, increment) {
|
|
56
|
+
return this.client.hincrby(key, field, increment);
|
|
57
|
+
}
|
|
55
58
|
async zadd(key, score, member) {
|
|
56
59
|
return this.client.zadd(key, score, member);
|
|
57
60
|
}
|