|
|
@@ -0,0 +1,99 @@
|
|
|
+import { RtcTokenBuilder, RtmTokenBuilder, RtcRole, RtmRole } from 'agora-token'
|
|
|
+
|
|
|
+export class AgoraTokenService {
|
|
|
+ private appId: string
|
|
|
+ private appSecret: string
|
|
|
+ private tokenExpiry: number
|
|
|
+
|
|
|
+ constructor() {
|
|
|
+ this.appId = process.env.AGORA_APP_ID || ''
|
|
|
+ this.appSecret = process.env.AGORA_APP_SECRET || ''
|
|
|
+ this.tokenExpiry = parseInt(process.env.AGORA_TOKEN_EXPIRY || '3600')
|
|
|
+
|
|
|
+ if (!this.appId || !this.appSecret) {
|
|
|
+ throw new Error('Agora配置缺失:请设置AGORA_APP_ID和AGORA_APP_SECRET环境变量')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成RTC Token(用于语音转文字功能)
|
|
|
+ */
|
|
|
+ generateRtcToken(channelName: string, userId: string | number = 0): string {
|
|
|
+ try {
|
|
|
+ const currentTimestamp = Math.floor(Date.now() / 1000)
|
|
|
+ const privilegeExpiredTs = currentTimestamp + this.tokenExpiry
|
|
|
+
|
|
|
+ const token = RtcTokenBuilder.buildTokenWithUid(
|
|
|
+ this.appId,
|
|
|
+ this.appSecret,
|
|
|
+ channelName,
|
|
|
+ typeof userId === 'number' ? userId : parseInt(userId),
|
|
|
+ RtcRole.PUBLISHER,
|
|
|
+ privilegeExpiredTs
|
|
|
+ )
|
|
|
+
|
|
|
+ return token
|
|
|
+ } catch (error) {
|
|
|
+ throw new Error(`RTC Token生成失败: ${error instanceof Error ? error.message : '未知错误'}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成RTM Token(用于实时消息功能)
|
|
|
+ */
|
|
|
+ generateRtmToken(userId: string): string {
|
|
|
+ try {
|
|
|
+ const currentTimestamp = Math.floor(Date.now() / 1000)
|
|
|
+ const privilegeExpiredTs = currentTimestamp + this.tokenExpiry
|
|
|
+
|
|
|
+ const token = RtmTokenBuilder.buildToken(
|
|
|
+ this.appId,
|
|
|
+ this.appSecret,
|
|
|
+ userId,
|
|
|
+ RtmRole.Rtm_User,
|
|
|
+ privilegeExpiredTs
|
|
|
+ )
|
|
|
+
|
|
|
+ return token
|
|
|
+ } catch (error) {
|
|
|
+ throw new Error(`RTM Token生成失败: ${error instanceof Error ? error.message : '未知错误'}`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证Token参数
|
|
|
+ */
|
|
|
+ validateTokenParams(type: 'rtc' | 'rtm', channel?: string, userId?: string): void {
|
|
|
+ if (type === 'rtc' && !channel) {
|
|
|
+ throw new Error('RTC Token需要提供channel参数')
|
|
|
+ }
|
|
|
+
|
|
|
+ if (type === 'rtm' && !userId) {
|
|
|
+ throw new Error('RTM Token需要提供userId参数')
|
|
|
+ }
|
|
|
+
|
|
|
+ if (channel && channel.length > 64) {
|
|
|
+ throw new Error('频道名称长度不能超过64个字符')
|
|
|
+ }
|
|
|
+
|
|
|
+ if (userId && userId.length > 64) {
|
|
|
+ throw new Error('用户ID长度不能超过64个字符')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取Token信息(包含过期时间)
|
|
|
+ */
|
|
|
+ getTokenInfo(token: string, type: 'rtc' | 'rtm') {
|
|
|
+ const currentTimestamp = Math.floor(Date.now() / 1000)
|
|
|
+ const expiryTimestamp = currentTimestamp + this.tokenExpiry
|
|
|
+
|
|
|
+ return {
|
|
|
+ token,
|
|
|
+ type,
|
|
|
+ expiresAt: expiryTimestamp,
|
|
|
+ expiresIn: this.tokenExpiry,
|
|
|
+ generatedAt: currentTimestamp
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|