add license check for desktop/mobile
This commit is contained in:
308
src/services/licenseService.ts
Normal file
308
src/services/licenseService.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
import type { LicenseInfo, LicenseCheckResult } from '@/types/license';
|
||||
import { LicenseStorage } from '@/utils/licenseStorage';
|
||||
import { PermissionService } from '@/utils/permissions';
|
||||
import { Notification } from 'kintone-ui-component/lib/notification';
|
||||
import { createApp } from 'vue';
|
||||
import i18n from '@/i18n';
|
||||
|
||||
export class LicenseService {
|
||||
// 常量定义
|
||||
private static readonly WARNING_DAYS_BEFORE_EXPIRY = 7;
|
||||
private static PLUGIN_ID: string = '';
|
||||
// ============ 基础工具函数 ============
|
||||
|
||||
/**
|
||||
* 获取域名
|
||||
*/
|
||||
static getDomain(): string {
|
||||
return window.location.hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件ID
|
||||
*/
|
||||
static getPluginId(): string {
|
||||
return this.PLUGIN_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查许可证是否有效
|
||||
* 已付费/当日获取是 true
|
||||
*/
|
||||
static checkLicenseAvailable(license: LicenseInfo) {
|
||||
const domain = this.getDomain()
|
||||
const pluginId = this.getPluginId()
|
||||
|
||||
// TODO jwt null
|
||||
|
||||
// 检查域名和插件ID是否与当前环境一致
|
||||
if (license.domain !== domain || license.pluginId !== pluginId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否付费
|
||||
if (license.isPaid) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
// 检查存储是否过期(是否同一天)
|
||||
const fetchDate = new Date(license.fetchTime).toDateString();
|
||||
const todayStr = today.toDateString();
|
||||
if (fetchDate !== todayStr) {
|
||||
// 不是同一天,已过期
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查试用是否到期
|
||||
const expiredTime = new Date(license.expiredTime);
|
||||
if (expiredTime < today) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 许可证验证
|
||||
*/
|
||||
static async checkLicense(): Promise<LicenseCheckResult> {
|
||||
const localLicense = this.getLocalLicenseInfo() || undefined
|
||||
if (localLicense) {
|
||||
return {
|
||||
isLicenseValid: true,
|
||||
license: localLicense,
|
||||
};
|
||||
}
|
||||
return await this.checkLicenseRemote()
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程许可证验证(模拟)
|
||||
*/
|
||||
static async checkLicenseRemote(): Promise<LicenseCheckResult> {
|
||||
try {
|
||||
// 这里应该是实际的API调用,暂时模拟创建试用许可证
|
||||
const response = await this.mockRemoteCheck(this.getDomain(), this.getPluginId());
|
||||
|
||||
const license = response.license!;
|
||||
LicenseStorage.saveLicense(license);
|
||||
|
||||
return {
|
||||
isLicenseValid: this.checkLicenseAvailable(license),
|
||||
license,
|
||||
isRemote: true,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Remote license check failed:', error);
|
||||
return {
|
||||
isLicenseValid: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 数据处理函数 ============
|
||||
|
||||
/**
|
||||
* 检查是否快要到期
|
||||
*/
|
||||
static isExpiringSoon(expiryTimestamp: number): boolean {
|
||||
const now = Date.now();
|
||||
const warningTime = expiryTimestamp - (this.WARNING_DAYS_BEFORE_EXPIRY * 24 * 60 * 60 * 1000);
|
||||
|
||||
return now >= warningTime && expiryTimestamp > now;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化到期时间
|
||||
*/
|
||||
static formatExpiryDate(timestamp: number): string {
|
||||
if (timestamp === -1) return '永久';
|
||||
return new Date(timestamp).toLocaleDateString('zh-CN');
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算剩余天数
|
||||
*/
|
||||
static getDaysRemaining(timestamp: number): number {
|
||||
if (timestamp === -1) return Infinity;
|
||||
const now = new Date().getTime();
|
||||
const remainingMs = timestamp - now;
|
||||
return Math.ceil(remainingMs / (24 * 60 * 60 * 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取许可证显示信息
|
||||
*/
|
||||
static getLicenseDisplayInfo(license: any) {
|
||||
if (!license) return null;
|
||||
|
||||
return {
|
||||
domain: this.getDomain(),
|
||||
plugin: this.getPluginId(),
|
||||
expiryDate: this.formatExpiryDate(license.expiredTime)
|
||||
};
|
||||
}
|
||||
|
||||
// ============ UI显示函数 ============
|
||||
|
||||
/**
|
||||
* 显示到期弹框
|
||||
*/
|
||||
static showExpiryModal(licenseInfo: any, options?: {
|
||||
expiryDialogTitle?: string;
|
||||
expiryDialogMessage?: string;
|
||||
}) {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示到期警告通知
|
||||
*/
|
||||
static showExpiryWarning(license: any, options?: {
|
||||
warningDialogTitle?: string;
|
||||
warningDialogMessage?: string;
|
||||
}) {
|
||||
const remainingDays = this.getDaysRemaining(license.expiredTime);
|
||||
const defaultMessage = `您的插件许可证将在 ${remainingDays} 天后到期,请及时续期以避免服务中断。`;
|
||||
|
||||
// 使用自定义消息或默认消息
|
||||
const message = options?.warningDialogMessage || defaultMessage;
|
||||
|
||||
// 检查是否已设置不再提醒
|
||||
const dontShowKey = `license_notification_dont_show_again_${this.getPluginId()}`;
|
||||
const dontShowAgain = localStorage.getItem(dontShowKey) === 'true';
|
||||
if (dontShowAgain) return;
|
||||
|
||||
// 使用KUC Notification
|
||||
const notification = new Notification({
|
||||
text: message,
|
||||
type: 'info',
|
||||
duration: -1 // 不自动关闭
|
||||
});
|
||||
|
||||
// 添加到页面
|
||||
const container = document.body;
|
||||
container.appendChild(notification);
|
||||
|
||||
// 监听关闭事件
|
||||
notification.addEventListener('close', () => {
|
||||
// 这里可以添加不再提醒的逻辑
|
||||
});
|
||||
}
|
||||
|
||||
// ============ 主要入口函数 ============
|
||||
|
||||
/**
|
||||
* 检查插件功能访问权限并加载插件(如果获得授权)
|
||||
*/
|
||||
static async loadPluginIfAuthorized(
|
||||
pluginId: string,
|
||||
callback: () => void | Promise<void>,
|
||||
options?: {
|
||||
expiryDialogTitle?: string;
|
||||
expiryDialogMessage?: string;
|
||||
warningDialogTitle?: string;
|
||||
warningDialogMessage?: string;
|
||||
}
|
||||
) {
|
||||
this.PLUGIN_ID = pluginId;
|
||||
try {
|
||||
// 检查许可证(内部已经包含本地检查无效时自动获取远程的逻辑)
|
||||
const licenseCheck = await this.checkLicense();
|
||||
|
||||
// 检查权限
|
||||
const permissions = await PermissionService.checkPermissions();
|
||||
const isManager = permissions.canManagePlugins;
|
||||
|
||||
// 许可证无效的情况
|
||||
if (!licenseCheck.isLicenseValid) {
|
||||
if (!isManager) {
|
||||
// 普通用户静默输出
|
||||
console.warn('License check failed, plugin functionality disabled');
|
||||
} else {
|
||||
// 管理员可以看到过期弹框
|
||||
alert('许可证无效')
|
||||
// this.showExpiryModal(this.getLicenseDisplayInfo(license.license), {
|
||||
// expiryDialogTitle: options?.expiryDialogTitle,
|
||||
// expiryDialogMessage: options?.expiryDialogMessage
|
||||
// });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 许可证有效,如果快要到期,管理员可以看到警告
|
||||
if (isManager && licenseCheck.license && !licenseCheck.license.isPaid && this.isExpiringSoon(licenseCheck.license.expiredTime)) {
|
||||
// 管理员可以看到过期弹框
|
||||
alert('即将过期')
|
||||
// this.showExpiryWarning(licenseCheck.license, {
|
||||
// warningDialogTitle: options?.warningDialogTitle,
|
||||
// warningDialogMessage: options?.warningDialogMessage
|
||||
// });
|
||||
}
|
||||
|
||||
// 许可证有效,可以加载插件功能
|
||||
await callback();
|
||||
|
||||
} catch (error) {
|
||||
console.error('License check failed, plugin functionality disabled:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 许可证信息管理 ============
|
||||
|
||||
/**
|
||||
* 获取许可证信息(用于显示)
|
||||
*/
|
||||
static getLocalLicenseInfo(): LicenseInfo | null {
|
||||
return LicenseStorage.getLicense(this.getPluginId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制刷新许可证(清除本地缓存重新检查)
|
||||
*/
|
||||
static async forceRefreshLicense(): Promise<LicenseCheckResult> {
|
||||
// 清除本地缓存
|
||||
LicenseStorage.clearLicense(this.getPluginId());
|
||||
return await this.checkLicenseRemote();
|
||||
}
|
||||
|
||||
// ============ 模拟/测试函数 ============
|
||||
|
||||
/**
|
||||
* 创建试用许可证
|
||||
*/
|
||||
private static mockCreateTrialLicense(): LicenseInfo {
|
||||
const expiryDate = new Date();
|
||||
expiryDate.setDate(expiryDate.getDate() + 30);
|
||||
|
||||
return {
|
||||
expiredTime: expiryDate.getTime(),
|
||||
isPaid: false,
|
||||
domain: this.getDomain(),
|
||||
pluginId: this.getPluginId(),
|
||||
fetchTime: new Date().getTime(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟远程验证(生产环境中会被真实API替换)
|
||||
*/
|
||||
private static async mockRemoteCheck(domain: string, pluginId: string): Promise<{ success: boolean; license?: LicenseInfo }> {
|
||||
// 模拟API调用,这里总是返回试用许可证
|
||||
// 生产环境这里会调用后端API: POST /api/license/check
|
||||
|
||||
const license = this.mockCreateTrialLicense();
|
||||
|
||||
// 模拟有时创建失败的情况(1%概率)
|
||||
if (Math.random() < 0.01) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
license,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user