Pārlūkot izejas kodu

使用线上会员状态代替本地会员

chendeben 1 gadu atpakaļ
vecāks
revīzija
4e048a843f

+ 90 - 15
entry/src/main/ets/common/util/UserUtil.ets

@@ -5,6 +5,10 @@ import { http } from "@kit.NetworkKit";
 import { bundleManager } from "@kit.AbilityKit";
 import { BusinessError } from '@kit.BasicServicesKit';
 import { hilog } from "@kit.PerformanceAnalysisKit";
+import { FileUtil } from "@pura/harmony-utils";
+import { fileUri, picker } from "@kit.CoreFileKit";
+import { LocalMusic } from "../../view/LocalMusic";
+import { VipPage } from "../../pages/VipPage";
 
 // 用户相关接口定义
 export interface WechatUserInfo {
@@ -48,6 +52,11 @@ export interface WechatUserInfoData {
   system_info?: SystemInfo;
   subscription_info?: SubscriptionInfo;
 }
+export interface ApiResponse {
+  code: number;
+  msg: string;
+  data?: WechatUserInfoData;
+}
 
 export interface WechatUserInfoApiResponse {
   code: number;
@@ -277,11 +286,9 @@ export default class UserUtil {
       const response: http.HttpResponse = await httpRequest.request(url, options);
       if (response.responseCode === 200) {
         let res = response.result as string;
-        LogUtil.debug("getUserInfoByHuawei res =" + res)
         let json: HuaweiUserInfoApiResponse = JSON.parse(res) as HuaweiUserInfoApiResponse;
         if (json.code === 0 && json.data && json.data.user_info) {
           const userId = json.data.system_info?.user_id ? Number(json.data.system_info?.user_id) : 0;
-          hilog.info(0x0000, 'UserUtil', `getUserInfoByHuawei: userId set to ${userId}`);
           const userName = json.data.user_info.nickname || '华为用户';
           const userAvatarUrl = json.data.user_info.headimgurl || '';
           // 处理会员信息
@@ -320,8 +327,9 @@ export default class UserUtil {
   }
 
   // 刷新用户信息
-  static async fetchUserInfo(token: string): Promise<UserInfoApiData | null> {
+  static async fetchUserInfo(): Promise<UserInfoApiData | null> {
     try {
+      let token=PreferencesUtil.getStringSync('userToken','');
       const url = `https://pay.ss5.xyz/user/info?token=${encodeURIComponent(token)}`;
       const httpRequest = http.createHttp();
       const options: http.HttpRequestOptions = {
@@ -335,18 +343,28 @@ export default class UserUtil {
         LogUtil.debug("fetchUserInfo res =" + res);
         let json: UserInfoApiResponse = JSON.parse(res) as UserInfoApiResponse;
         if (json.code === 0 && json.data) {
-          // const userInfo = json.data.user_info;
-          // const systemInfo = json.data.system_info;
-          // const subInfo = json.data.subscription_info;
-          // let hasActiveSubscription = false;
-          // let subscriptionName = '';
-          // let subscriptionEndDate = '';
-          // if (subInfo && subInfo.has_active_subscription && subInfo.subscriptions && subInfo.subscriptions.length > 0) {
-            // hasActiveSubscription = true;
-            // subscriptionName = subInfo.subscriptions[0].plan_name;
-            // subscriptionEndDate = subInfo.subscriptions[0].end_date;
-          // }
-          // ToastUtil.showToast('用户信息已更新');
+          const userId = json.data.system_info?.user_id ? Number(json.data.system_info?.user_id) : 0;
+          const userName = json.data.user_info.nickname || '匿名用户';
+          const userAvatarUrl = json.data.user_info.headimgurl || '';
+          // 处理会员信息
+          const subInfo = json.data.subscription_info;
+          let hasActiveSubscription = false;
+          let subscriptionName = '';
+          let subscriptionEndDate = '';
+          if (subInfo && subInfo.has_active_subscription && subInfo.subscriptions && subInfo.subscriptions.length > 0) {
+            hasActiveSubscription = true;
+            subscriptionName = subInfo.subscriptions[0].plan_name;
+            subscriptionEndDate = subInfo.subscriptions[0].end_date;
+          }
+          // 保存登录状态
+          PreferencesUtil.putSync('isLogin', true);
+          PreferencesUtil.putSync('userId', userId);
+          PreferencesUtil.putSync('userName', userName);
+          PreferencesUtil.putSync('userAvatarUrl', userAvatarUrl);
+          PreferencesUtil.putSync('subscriptionName', subscriptionName);
+          PreferencesUtil.putSync('subscriptionEndDate', subscriptionEndDate);
+          PreferencesUtil.putSync('hasActiveSubscription', hasActiveSubscription);
+          PreferencesUtil.putSync('userToken', json.data.system_info?.token || '');
           return json.data;
         } else {
           ToastUtil.showToast('刷新用户信息失败');
@@ -383,4 +401,61 @@ export default class UserUtil {
       ToastUtil.showToast('登录失败');
     }
   }
+
+  /**
+   * 本地会员同步到服务器
+   * @param expireDate
+   * @returns
+   */
+  static async syncLocalNobleToServer(expireDate: string): Promise<boolean> {
+    try {
+      const token = PreferencesUtil.getStringSync('userToken', '');
+      console.log("Heanup token:"+token+", expireDate:"+expireDate)
+      if (!token || !expireDate) {
+        ToastUtil.showToast('缺少token或到期时间');
+        return false;
+      }
+      const url = `https://pay.ss5.xyz/user/sync_noble?token=${encodeURIComponent(token)}&expireDate=${encodeURIComponent(expireDate)}`;
+      const httpRequest = http.createHttp();
+      const options: http.HttpRequestOptions = {
+        method: http.RequestMethod.GET,
+        readTimeout: 3000,
+        connectTimeout: 3000,
+      };
+      const response: http.HttpResponse = await httpRequest.request(url, options);
+      if (response.responseCode === 200) {
+        let res = response.result as string;
+        let json = JSON.parse(res) as ApiResponse;
+        if (json.code === 0) {
+          ToastUtil.showToast('会员同步成功');
+          PreferencesUtil.putSync('isNoble', false);
+          PreferencesUtil.putSync('nobleExpireDate', '');
+          // 删除本地会员文件
+          try {
+            const documentViewPicker = new picker.DocumentViewPicker()
+            let documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD })
+            let download_path = new fileUri.FileUri(documentSaveResult[0]).path + '/'
+            let filePath = download_path + LocalMusic.STR_LOCK_VIDEO+ '/' + VipPage.VIP_FILEPATH
+            console.log("Heanup filePath="+filePath)
+            if (FileUtil.accessSync(filePath)) {
+              FileUtil.unlink(filePath);
+            }
+          } catch (e) {
+            console.log("Heanup exception="+e)
+            // 可忽略或打印日志
+          }
+          return true;
+        } else {
+          ToastUtil.showToast('会员同步失败:' + (json.msg || '未知错误'));
+          return false;
+        }
+      } else {
+        ToastUtil.showToast('会员同步接口请求失败');
+        return false;
+      }
+    } catch (e) {
+      ToastUtil.showToast('会员同步异常' + (e && e.message ? (': ' + e.message) : ''));
+      return false;
+    }
+  }
 }

+ 65 - 35
entry/src/main/ets/common/util/Utility.ets

@@ -6,26 +6,25 @@ import {
   DateUtil, FileUtil, ImageUtil, LogUtil,
   MD5,
   PreferencesUtil,
-  RandomUtil, StrUtil, ToastUtil } from '@pura/harmony-utils';
+  RandomUtil, StrUtil } from '@pura/harmony-utils';
 import { media } from '@kit.MediaKit';
-import fs from '@ohos.file.fs';
+import fs, { ReadTextOptions } from '@ohos.file.fs';
 import { image } from '@kit.ImageKit';
 import fileIo from '@ohos.file.fs';
 import { VideoItem } from '../../viewmodel/VideoItem';
 import { photoAccessHelper } from '@kit.MediaLibraryKit';
 import { dataSharePredicates, uniformTypeDescriptor } from '@kit.ArkData';
 import { systemShare } from '@kit.ShareKit';
-import { fileUri } from '@kit.CoreFileKit';
+import { fileUri, picker } from '@kit.CoreFileKit';
 import {  common, UIAbility, Want } from '@kit.AbilityKit';
 import { CommonConstants } from '../constants/CommonConstants';
 import { window } from '@kit.ArkUI';
 import { bundleManager } from '@kit.AbilityKit'
-import { VipPage } from '../../pages/VipPage';
-import NetAxiosUtil from './NetAxiosUtil';
 import { pinyin4js } from '@ohos/pinyin4js';
+import { VipData } from '../../viewmodel/VipData';
+import { LocalMusic } from '../../view/LocalMusic';
+import { VipPage } from '../../pages/VipPage';
 
-
-// import userFileManager from '@ohos.filemanagement.userFileManager';
 export class Utility {
 
   private constructor() {}
@@ -39,14 +38,28 @@ export class Utility {
     }
     return false
   }
-  //是否赞助
-  static isNoble():boolean{
-    if(!Utility.isOpenTime())//提交审核的审核,刚刚开始可以全部是赞助用户。
-      return true
-    return PreferencesUtil.getBooleanSync('isNoble',false)
+  static readonly FOREVER_DATE: string = 'forever';
+  /**
+   * 是否会员(只看线上会员状态)
+   */
+  static isNoble(): boolean {
+    // 只判断线上会员状态
+    return PreferencesUtil.getBooleanSync('hasActiveSubscription', false);
+  }
+
+  /**
+   * setNoble方法废弃,保留兼容但不做任何操作
+   */
+  static setNoble(expireDate: string) {
+    // 线上会员体系下不再需要本地设置会员标志
+    // 兼容老代码,什么都不做
   }
 
-  //判断用户安装app是否超过2天,超过2天(2800分钟)才展示广告 显示广告 24*60=1440  1440*2=2800
+  /**
+   * 判断用户安装app是否超过2天,超过2天(2800分钟)才展示广告 显示广告 24*60=1440  1440*2=2800
+   * @param day
+   * @returns
+   */
   static isPassInstallTime(day:number):boolean{
     //获取当前时间
     let currentTime = new Date().getTime();
@@ -97,28 +110,6 @@ export class Utility {
     return false
   }
 
-  static setNoble(expireDate:string){
-    let isNoble = false
-    if(StrUtil.isNotEmpty(expireDate)){
-      if(expireDate===VipPage.FOEVEER_DATE){
-        isNoble = true
-      }else{
-        const currentDate = new Date();
-        const targetDate = DateUtil.getFormatDate(expireDate)
-
-        if (currentDate > targetDate) {
-          isNoble = false
-        }else{
-          isNoble = true
-        }
-      }
-
-
-    }
-    PreferencesUtil.putSync('isNoble',isNoble)
-
-  }
-
   static addDays(date: Date, days: number): Date {
     const result = new Date(date);
     result.setDate(result.getDate() + days);
@@ -1186,6 +1177,45 @@ export class Utility {
     });
   }
 
+  /**
+   * 读取本地会员到期时间(解密.vv文件)
+   * @returns
+   */
+  static async getLocalNobleExpireDate(): Promise<string> {
+    // return "2025-08-08 12:00";
+    try {
+      const documentViewPicker = new picker.DocumentViewPicker()
+      let documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD })
+      let download_path = new fileUri.FileUri(documentSaveResult[0]).path + '/'
+      let filePath = download_path + LocalMusic.STR_LOCK_VIDEO+ '/' + VipPage.VIP_FILEPATH
+      let expireDate =  Utility.readDataFromFile(filePath)
+
+      return expireDate;
+    } catch (e) {
+      return '';
+    }
+  }
+
+  static readDataFromFile(filePath: string) {
+    if(!FileUtil.accessSync(filePath)){
+      return ''
+    }
+    let readTextOptions: ReadTextOptions = {
+      offset: 0,
+      length: 0,
+      encoding: 'utf-8'
+    };
+    let stat = fileIo.statSync(filePath);
+    readTextOptions.length = stat.size;
+    let str = fileIo.readTextSync(filePath, readTextOptions);
+    const parsedData:VipData = JSON.parse(str)  as VipData;
+    // LogUtil.info('onecold parsedData.expireDate = ' + parsedData.expireDate)
+    let result = Base64Util.decodeSync( parsedData.expireDate)
+    // LogUtil.info('onecold 解密的result = ' +StrUtil.unit8ArrayToStr(result))
+    return StrUtil.unit8ArrayToStr(result);
+  }
+
+
 }
 
 

+ 93 - 25
entry/src/main/ets/pages/UserCenter.ets

@@ -19,7 +19,7 @@ import { PayReq } from '@tencent/wechat_open_sdk';
 import { cryptoFramework } from '@kit.CryptoArchitectureKit';
 import json from '@ohos.util.json';
 import UserUtil, { VipFeature, VipPlanApi, VipPlanListApiResponse, WeChatPrepayId } from '../common/util/UserUtil';
-
+import { Utility } from '../common/util/Utility';
 
 
 // 微信支付相关工具方法
@@ -125,7 +125,6 @@ export struct UserCenter {
   @State hasActiveSubscription: boolean = false;
   @State subscriptionName: string = '';
   @State subscriptionEndDate: string = '';
-  @State userToken: string = '';
   private wxApi = WXApi
   private wxEventHandler = WXEventHandler
   @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointTypeEnum.MD;
@@ -171,7 +170,7 @@ export struct UserCenter {
         ToastUtil.showToast('支付成功!');
         // 可在此处刷新会员状态或其他支付成功后的业务逻辑
         // this.fetchVipPlans(); // 刷新会员套餐信息
-        this.fetchUserInfo(this.userToken);
+        this.fetchUserInfo();
       } else {
         // 其他类型的成功回调
         ToastUtil.showToast('操作成功!');
@@ -190,39 +189,66 @@ export struct UserCenter {
 
   // 通过code换取用户信息
   async getUserInfoByCode(code: string) {
-    await UserUtil.getUserInfoByCode(code).then(()=>{
-      this.showUserInfo()
+    await UserUtil.getUserInfoByCode(code).then(async ()=>{
+      // 登录后同步本地会员到线上(仅本地有会员且线上无会员时)
+      Utility.getLocalNobleExpireDate().then((expireDate)=>{
+        if (Utility.isNoble() && !this.hasActiveSubscription && expireDate) {
+          UserUtil.syncLocalNobleToServer(expireDate).then((success)=>{
+            if (success) {
+              ToastUtil.showToast('本地会员已同步到账号');
+              PreferencesUtil.putSync('isNoble', false);
+              this.fetchUserInfo();
+            } else {
+              this.fetchUserInfo();
+            }
+          });
+        } else {
+          this.fetchUserInfo();
+        }
+      });
     });
   }
 
   // 通过华为账号信息换取用户信息和会员状态
   async getUserInfoByHuawei(openID: string, unionID: string, nickname: string, avatarUri: string) {
-    await UserUtil.getUserInfoByHuawei(openID, unionID, nickname, avatarUri).then((data)=>{
-      this.userToken = PreferencesUtil.getStringSync('userToken', '');
-        this.showUserInfo();
+    await UserUtil.getUserInfoByHuawei(openID, unionID, nickname, avatarUri).then(async (data)=>{
+      // 登录后同步本地会员到线上(仅本地有会员且线上无会员时)
+      Utility.getLocalNobleExpireDate().then((expireDate)=>{
+        if (Utility.isNoble() && !this.hasActiveSubscription && expireDate) {
+          UserUtil.syncLocalNobleToServer(expireDate).then((success)=>{
+            if (success) {
+              ToastUtil.showToast('本地会员已同步到账号');
+              // 关键:同步成功后再刷新用户信息
+              this.fetchUserInfo();
+            } else {
+              // 同步失败也刷新一次
+              this.fetchUserInfo();
+            }
+          });
+        } else {
+          // 不需要同步会员,直接刷新
+          this.fetchUserInfo();
+        }
+      });
     })
-
   }
 
+  @Builder
+  syncNobleDialogContentBuilder(): void {
+    Text('检测到您是本地会员,为保障权益请登录账号,会员权益将自动同步到您的账号。')
+      .fontSize(16)
+      .fontColor('#222')
+      .width('100%')
+      .textAlign(TextAlign.Center)
+      .padding(16)
+  }
   aboutToAppear(): void {
     this.topRectHeight = px2vp(AppUtil.getStatusBarHeight());
     let themeColor = PreferencesUtil.getStringSync('THEME_COLOR', CommonConstants.DEFAULT_THEME_COLOR);
     this.isDarkMode = this.currentMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK
     AppStorage.setOrCreate('themeColor', themeColor);
     this.themeColor = themeColor;
-    this.showUserInfo()
-    this.wxEventHandler.registerOnWXRespCallback(this.onWXResp)
-    this.fetchVipPlans();
-    if (this.isLogin && this.userToken) {
-      void this.fetchUserInfo(this.userToken);
-    }
-  }
 
-  aboutToDisappear() {
-    this.wxEventHandler.unregisterOnWXRespCallback(this.onWXResp)
-  }
-  showUserInfo(){
-    // 自动恢复本地登录状态
     this.isLogin = PreferencesUtil.getBooleanSync('isLogin', false);
     this.userId = PreferencesUtil.getNumberSync('userId', 0);
     this.userName = PreferencesUtil.getStringSync('userName', '未登录用户');
@@ -230,7 +256,35 @@ export struct UserCenter {
     this.subscriptionName = PreferencesUtil.getStringSync('subscriptionName', '');
     this.subscriptionEndDate = PreferencesUtil.getStringSync('subscriptionEndDate', '');
     this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false);
-    this.userToken = PreferencesUtil.getStringSync('userToken', '');
+
+    this.wxEventHandler.registerOnWXRespCallback(this.onWXResp)
+    this.fetchVipPlans();
+    if (this.isLogin) {
+      void this.fetchUserInfo();
+    }
+    // 新增:未登录但本地是会员,弹窗提示登录同步会员
+    if (!this.isLogin && Utility.isNoble()) {
+      DialogHelper.showCustomContentDialog({
+        dialogId: 'syncNobleDialog',
+        title: '会员同步提示',
+        contentBuilder: () => {
+          this.syncNobleDialogContentBuilder();
+        },
+        buttons: [
+          { value: '暂不登录' },
+          {
+            value: '去登录',
+            action: () => {
+              this.showLoginDialog();
+            }
+          }
+        ]
+      });
+    }
+  }
+
+  aboutToDisappear() {
+    this.wxEventHandler.unregisterOnWXRespCallback(this.onWXResp)
   }
 
   build() {
@@ -975,7 +1029,7 @@ export struct UserCenter {
       new Pay().pay(orderInfo, true).then(async (result) => {
         if (result.get('resultStatus') === '9000') {
           ToastUtil.showToast('支付成功!');
-          void this.fetchUserInfo(this.userToken);
+          void this.fetchUserInfo();
           // 可在此处刷新会员状态
         } else {
           ToastUtil.showToast('取消支付!');
@@ -987,7 +1041,21 @@ export struct UserCenter {
   }
 
   // 每次进入页面时刷新用户数据
-  async fetchUserInfo(token: string) {
-    await UserUtil.fetchUserInfo(token);
+  async fetchUserInfo() {
+    // console.log('Heanup 刷新用户信息:'+token)
+    await UserUtil.fetchUserInfo().then((user_info)=>{
+      console.log("Heanup UserInfo:"+json.stringify(user_info))
+      this.isLogin = PreferencesUtil.getBooleanSync('isLogin', false);
+      this.userId = PreferencesUtil.getNumberSync('userId', 0);
+      this.userName = PreferencesUtil.getStringSync('userName', '未登录用户');
+      this.userAvatarUrl = PreferencesUtil.getStringSync('userAvatarUrl', '');
+      this.subscriptionName = PreferencesUtil.getStringSync('subscriptionName', '');
+      this.subscriptionEndDate = PreferencesUtil.getStringSync('subscriptionEndDate', '');
+      this.hasActiveSubscription = PreferencesUtil.getBooleanSync('hasActiveSubscription', false);
+      console.log('Heanup hasActiveSubscription:'+this.hasActiveSubscription)
+      // 新增:同步线上会员状态到本地,便于 Utility.isNoble 全局判断
+      PreferencesUtil.putSync('hasActiveSubscription', this.hasActiveSubscription);
+    });
   }
+
 }

+ 7 - 7
entry/src/main/ets/pages/VipPage.ets

@@ -57,7 +57,7 @@ export  struct  VipPage{
   @State isShowDutySheetView: boolean = false;
   @State isAgree: boolean = false;
   static readonly IS_AGREE: string = 'isAgree';
-  static readonly FOEVEER_DATE: string = 'forever';
+  static readonly FOREVER_DATE: string = 'forever';
   @State params: object = router.getParams()
   @State payPrice:number = 29.88 //9是一个  19是一年  19是永久赞助
   static readonly TYPE_MONTH: number = 9.8;
@@ -188,7 +188,7 @@ export  struct  VipPage{
                 .maxLines(1)
                 .fontColor(Color.Gray)
                 .fontWeight(480)
-              Text(this.expireDate===VipPage.FOEVEER_DATE ?'永久赞助':this.expireDate)
+              Text(this.expireDate===VipPage.FOREVER_DATE ?'永久赞助':this.expireDate)
                 .margin({top:15,bottom:8})
                 .fontSize(15)
                 .maxLines(1)
@@ -676,7 +676,7 @@ export  struct  VipPage{
 
     }else if(payPrice === VipPage.TYPE_FOREVER){
 
-      return VipPage.FOEVEER_DATE
+      return VipPage.FOREVER_DATE
     }
     return DateUtil.getFormatDateStr(now,'yyyy-MM-dd HH:mm');
 
@@ -703,12 +703,12 @@ export  struct  VipPage{
 
   async  saveDataToFile(expireDate:string, filePath:string,isHao:boolean) {
     let newExpireDate = expireDate
-    if(expireDate!==VipPage.FOEVEER_DATE){
+    if(expireDate!==VipPage.FOREVER_DATE){
       if(FileUtil.accessSync(filePath)&&!isHao){
         let readDate = this.readDataFromFile(filePath)
         let currentDate = new Date();
-        if(readDate === VipPage.FOEVEER_DATE){
-          newExpireDate = VipPage.FOEVEER_DATE
+        if(readDate === VipPage.FOREVER_DATE){
+          newExpireDate = VipPage.FOREVER_DATE
         }else{
           //如果里面有值,直接把日期相加
           if(DateUtil.getFormatDate(readDate) > currentDate){
@@ -723,7 +723,7 @@ export  struct  VipPage{
 
       }
     }else{
-      newExpireDate = VipPage.FOEVEER_DATE
+      newExpireDate = VipPage.FOREVER_DATE
     }
     this.expireDate = newExpireDate
     Utility.setNoble(this.expireDate)