UserUtil.ets 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. import { cryptoFramework } from "@kit.CryptoArchitectureKit";
  2. import { LogUtil, PreferencesUtil, StrUtil, ToastUtil } from "@pura/harmony-utils";
  3. import { CommonConstants, STR_LOCK_VIDEO, VIP_FILEPATH } from "../constants/CommonConstants";
  4. import { http } from "@kit.NetworkKit";
  5. import { bundleManager } from "@kit.AbilityKit";
  6. import { BusinessError } from '@kit.BasicServicesKit';
  7. import { hilog } from "@kit.PerformanceAnalysisKit";
  8. import { FileUtil } from "@pura/harmony-utils";
  9. import { fileUri, picker } from "@kit.CoreFileKit";
  10. import { VipPage } from "../../pages/VipPage";
  11. // 用户相关接口定义
  12. export interface WechatUserInfo {
  13. openid?: string;
  14. nickname?: string;
  15. sex?: number;
  16. language?: string;
  17. city?: string;
  18. province?: string;
  19. country?: string;
  20. headimgurl?: string;
  21. privilege?: string[];
  22. unionid?: string;
  23. }
  24. export interface Subscription {
  25. plan_id: string;
  26. plan_name: string;
  27. plan_description: string;
  28. price: string;
  29. start_date: string;
  30. end_date: string;
  31. status: string;
  32. auto_renew: string;
  33. features: string[];
  34. }
  35. export interface SubscriptionInfo {
  36. has_active_subscription: boolean;
  37. subscriptions: Subscription[];
  38. }
  39. export interface responseData {
  40. token_info?: object;
  41. user_info: WechatUserInfo;
  42. }
  43. export interface WechatUserInfoData {
  44. token_info?: object;
  45. user_info: WechatUserInfo;
  46. system_info?: SystemInfo;
  47. subscription_info?: SubscriptionInfo;
  48. }
  49. export interface ApiResponse {
  50. code: number;
  51. msg: string;
  52. data?: WechatUserInfoData;
  53. }
  54. export interface WechatUserInfoApiResponse {
  55. code: number;
  56. msg: string;
  57. data: WechatUserInfoData;
  58. }
  59. export interface HuaweiUserInfo {
  60. openid: string;
  61. unionid?: string;
  62. nickname: string;
  63. headimgurl: string;
  64. }
  65. export interface SystemInfo {
  66. user_id: number | string;
  67. token: string;
  68. }
  69. export interface HuaweiUserInfoApiData {
  70. user_info: HuaweiUserInfo;
  71. system_info?: SystemInfo;
  72. subscription_info?: SubscriptionInfo;
  73. }
  74. export interface HuaweiUserInfoApiResponse {
  75. code: number;
  76. msg: string;
  77. data: HuaweiUserInfoApiData;
  78. }
  79. export interface VipFeature {
  80. icon: Resource;
  81. title: string;
  82. description: string;
  83. isVipOnly: boolean;
  84. hasCome: boolean;
  85. }
  86. export interface VipPlanApi {
  87. id: string;
  88. name: string;
  89. description: string;
  90. price: string;
  91. original_price:string;
  92. discount_percent:number;
  93. duration: string;
  94. features: string;
  95. sort_order: string;
  96. status: string;
  97. created_at: string;
  98. updated_at: string;
  99. }
  100. export interface VipPlanListApiData {
  101. plans: VipPlanApi[];
  102. show_wx_login?: boolean;
  103. }
  104. export interface VipPlanListApiResponse {
  105. code: number;
  106. msg: string;
  107. data: VipPlanListApiData;
  108. }
  109. export interface WeChatPrepayId {
  110. prepay_id: string;
  111. }
  112. export interface UserInfoApiData {
  113. user_info: WechatUserInfo;
  114. system_info: SystemInfo;
  115. subscription_info: SubscriptionInfo;
  116. }
  117. export interface UserInfoApiResponse {
  118. code: number;
  119. msg: string;
  120. data: UserInfoApiData;
  121. }
  122. export default class UserUtil {
  123. // 微信是否安装
  124. static async isHasWX(): Promise<boolean> {
  125. try {
  126. let link = 'weixin://';
  127. let data = bundleManager.canOpenLink(link);
  128. if (data) {
  129. return true;
  130. } else {
  131. ToastUtil.showToast('微信未安装');
  132. return false;
  133. }
  134. } catch (err) {
  135. let message = (err as BusinessError).message;
  136. console.error('canOpenLink failed: %{public}s', message);
  137. return false;
  138. }
  139. }
  140. // 创建微信订单
  141. static async createWeChatOrder(app_id: string, mch_id: string, description: string, amount: number, out_trade_no_pre: string, uid: number, plan_id: string): Promise<string> {
  142. let requestUrl = CommonConstants.WX_PAY_API
  143. + '?app_id=' + encodeURIComponent(app_id.trim())
  144. + '&mch_id=' + encodeURIComponent(mch_id.trim())
  145. + '&description=' + encodeURIComponent(description.trim())
  146. + '&amount=' + encodeURIComponent(amount)
  147. + '&uid=' + encodeURIComponent(uid)
  148. + '&plan_id=' + encodeURIComponent(plan_id)
  149. + '&out_trade_no_pre=' + encodeURIComponent(out_trade_no_pre);
  150. const httpRequest: http.HttpRequest = http.createHttp();
  151. const options: http.HttpRequestOptions = {
  152. method: http.RequestMethod.GET,
  153. readTimeout: 6000,
  154. connectTimeout: 6000,
  155. };
  156. try {
  157. const response: http.HttpResponse = await httpRequest.request(requestUrl, options);
  158. if (response.responseCode === 200) {
  159. let responseData: WeChatPrepayId;
  160. if (typeof response.result === 'string') {
  161. responseData = JSON.parse(response.result) as WeChatPrepayId;
  162. } else if (response.result instanceof Object) {
  163. responseData = response.result as WeChatPrepayId;
  164. } else {
  165. throw new Error('Unexpected response format');
  166. }
  167. const prepayId: string = responseData.prepay_id;
  168. return prepayId;
  169. } else {
  170. ToastUtil.showToast('微信下单失败');
  171. return '';
  172. }
  173. } catch (error) {
  174. ToastUtil.showToast('微信下单异常');
  175. return '';
  176. }
  177. }
  178. // 构造签名串
  179. static constructSignatureString(appId: string, timestamp: string, nonceStr: string, prepayId: string): string {
  180. return `${appId}\n${timestamp}\n${nonceStr}\n${prepayId}\n`;
  181. }
  182. // 微信支付签名
  183. static async rsaSign2048( message: string): Promise<Uint8Array> {
  184. try {
  185. let keyGen = cryptoFramework.createAsyKeyGenerator("RSA2048")
  186. let key = keyGen.convertPemKeySync(null, CommonConstants.WX_PAY_RSA_PRIVATE_KEY)
  187. let sign = cryptoFramework.createSign('RSA2048|PKCS1|SHA256');
  188. let msgBlob: cryptoFramework.DataBlob = {data: StrUtil.strToUint8Array(message)}
  189. sign.initSync(key.priKey)
  190. let signature = await sign.sign(msgBlob)
  191. return signature.data
  192. } catch (error) {
  193. console.error(error, `onecold error code: ${error.code}`)
  194. return new Uint8Array()
  195. }
  196. }
  197. // 通过code换取用户信息
  198. static async getUserInfoByCode(code: string): Promise<UserInfoApiData |WechatUserInfoData| null> {
  199. try {
  200. const url = `https://pay.ss5.xyz/wechat/user_info?code=${encodeURIComponent(code)}`;
  201. const httpRequest = http.createHttp();
  202. const options: http.HttpRequestOptions = {
  203. method: http.RequestMethod.GET,
  204. readTimeout: 3000,
  205. connectTimeout: 3000,
  206. };
  207. const response: http.HttpResponse = await httpRequest.request(url, options);
  208. if (response.responseCode === 200) {
  209. let res = response.result as string;
  210. LogUtil.debug("getUserInfoByCode res =" + res)
  211. let json: WechatUserInfoApiResponse = JSON.parse(res) as WechatUserInfoApiResponse;
  212. if (json.code === 0 && json.data && json.data.user_info) {
  213. const userId = json.data.system_info?.user_id ? Number(json.data.system_info?.user_id) : 0;
  214. hilog.info(0x0000, 'UserUtil', `getUserInfoByCode: userId set to ${userId}`);
  215. const userName = json.data.user_info.nickname || '微信用户';
  216. const userAvatarUrl = json.data.user_info.headimgurl && json.data.user_info.headimgurl.length > 0 ? json.data.user_info.headimgurl : '';
  217. // 解析订阅信息
  218. const subInfo = json.data.subscription_info;
  219. let hasActiveSubscription = false;
  220. let subscriptionName = '';
  221. let subscriptionEndDate = '';
  222. if (subInfo && subInfo.has_active_subscription && subInfo.subscriptions && subInfo.subscriptions.length > 0) {
  223. hasActiveSubscription = true;
  224. subscriptionName = subInfo.subscriptions[0].plan_name;
  225. subscriptionEndDate = subInfo.subscriptions[0].end_date;
  226. }
  227. // 保存登录状态和用户信息
  228. PreferencesUtil.putSync('isLogin', true);
  229. PreferencesUtil.putSync('userId', userId);
  230. PreferencesUtil.putSync('userName', userName);
  231. PreferencesUtil.putSync('userAvatarUrl', userAvatarUrl);
  232. PreferencesUtil.putSync('subscriptionName', subscriptionName);
  233. PreferencesUtil.putSync('subscriptionEndDate', subscriptionEndDate);
  234. PreferencesUtil.putSync('hasActiveSubscription', hasActiveSubscription);
  235. PreferencesUtil.putSync('userToken', json.data.system_info?.token || '');
  236. ToastUtil.showToast('微信登录成功');
  237. return json.data;
  238. } else {
  239. // ToastUtil.showToast('用户信息解析失败');
  240. return null;
  241. }
  242. } else {
  243. // ToastUtil.showToast('获取用户信息失败');
  244. return null;
  245. }
  246. } catch (e) {
  247. // ToastUtil.showToast('登录失败' + (e && e.message ? (': ' + e.message) : ''));
  248. return null;
  249. }
  250. }
  251. // 通过华为账号信息换取用户信息和会员状态
  252. static async getUserInfoByHuawei(openID: string, unionID: string, nickname: string, avatarUri: string): Promise<HuaweiUserInfoApiData | null> {
  253. try {
  254. const url = `https://pay.ss5.xyz/user/huawei?openID=${encodeURIComponent(openID)}&unionID=${encodeURIComponent(unionID)}&nickname=${encodeURIComponent(nickname)}&avatarUri=${encodeURIComponent(avatarUri)}`;
  255. const httpRequest = http.createHttp();
  256. const options: http.HttpRequestOptions = {
  257. method: http.RequestMethod.GET,
  258. readTimeout: 3000,
  259. connectTimeout: 3000,
  260. };
  261. const response: http.HttpResponse = await httpRequest.request(url, options);
  262. if (response.responseCode === 200) {
  263. let res = response.result as string;
  264. let json: HuaweiUserInfoApiResponse = JSON.parse(res) as HuaweiUserInfoApiResponse;
  265. if (json.code === 0 && json.data && json.data.user_info) {
  266. const userId = json.data.system_info?.user_id ? Number(json.data.system_info?.user_id) : 0;
  267. const userName = json.data.user_info.nickname || '华为用户';
  268. const userAvatarUrl = json.data.user_info.headimgurl || '';
  269. // 处理会员信息
  270. const subInfo = json.data.subscription_info;
  271. let hasActiveSubscription = false;
  272. let subscriptionName = '';
  273. let subscriptionEndDate = '';
  274. if (subInfo && subInfo.has_active_subscription && subInfo.subscriptions && subInfo.subscriptions.length > 0) {
  275. hasActiveSubscription = true;
  276. subscriptionName = subInfo.subscriptions[0].plan_name;
  277. subscriptionEndDate = subInfo.subscriptions[0].end_date;
  278. }
  279. // 保存登录状态
  280. PreferencesUtil.putSync('isLogin', true);
  281. PreferencesUtil.putSync('userId', userId);
  282. PreferencesUtil.putSync('userName', userName);
  283. PreferencesUtil.putSync('userAvatarUrl', userAvatarUrl);
  284. PreferencesUtil.putSync('subscriptionName', subscriptionName);
  285. PreferencesUtil.putSync('subscriptionEndDate', subscriptionEndDate);
  286. PreferencesUtil.putSync('hasActiveSubscription', hasActiveSubscription);
  287. PreferencesUtil.putSync('userToken', json.data.system_info?.token || '');
  288. ToastUtil.showToast('华为登录成功');
  289. return json.data;
  290. } else {
  291. // ToastUtil.showToast('用户信息解析失败');
  292. return null;
  293. }
  294. } else {
  295. // ToastUtil.showToast('获取用户信息失败');
  296. return null;
  297. }
  298. } catch (e) {
  299. // ToastUtil.showToast('登录失败' + (e && e.message ? (': ' + e.message) : ''));
  300. return null;
  301. }
  302. }
  303. // 刷新用户信息
  304. static async fetchUserInfo(): Promise<UserInfoApiData | null> {
  305. try {
  306. let token=PreferencesUtil.getStringSync('userToken','');
  307. const url = `https://pay.ss5.xyz/user/info?token=${encodeURIComponent(token)}`;
  308. const httpRequest = http.createHttp();
  309. const options: http.HttpRequestOptions = {
  310. method: http.RequestMethod.GET,
  311. readTimeout: 3000,
  312. connectTimeout: 3000,
  313. };
  314. const response: http.HttpResponse = await httpRequest.request(url, options);
  315. if (response.responseCode === 200) {
  316. let res = response.result as string;
  317. let json: UserInfoApiResponse = JSON.parse(res) as UserInfoApiResponse;
  318. if (json.code === 0 && json.data) {
  319. const userId = json.data.system_info?.user_id ? Number(json.data.system_info?.user_id) : 0;
  320. const userName = json.data.user_info.nickname || '匿名用户';
  321. const userAvatarUrl = json.data.user_info.headimgurl || '';
  322. // 处理会员信息
  323. const subInfo = json.data.subscription_info;
  324. let hasActiveSubscription = false;
  325. let subscriptionName = '';
  326. let subscriptionEndDate = '';
  327. let plan_id = '';
  328. if (subInfo && subInfo.has_active_subscription && subInfo.subscriptions && subInfo.subscriptions.length > 0) {
  329. hasActiveSubscription = true;
  330. subscriptionName = subInfo.subscriptions[0].plan_name;
  331. subscriptionEndDate = subInfo.subscriptions[0].end_date;
  332. plan_id = subInfo.subscriptions[0].plan_id
  333. }
  334. // 保存登录状态
  335. PreferencesUtil.putSync('isLogin', true);
  336. PreferencesUtil.putSync('userId', userId);
  337. PreferencesUtil.putSync('userName', userName);
  338. PreferencesUtil.putSync('userAvatarUrl', userAvatarUrl);
  339. PreferencesUtil.putSync('subscriptionName', subscriptionName);
  340. PreferencesUtil.putSync('subscriptionEndDate', subscriptionEndDate);
  341. PreferencesUtil.putSync('hasActiveSubscription', hasActiveSubscription);
  342. PreferencesUtil.putSync('userToken', json.data.system_info?.token || '');
  343. if(plan_id=='1'){
  344. PreferencesUtil.putSync('isForever', true);
  345. }else{
  346. PreferencesUtil.putSync('isForever', false);
  347. }
  348. return json.data;
  349. } else {
  350. return null;
  351. }
  352. } else {
  353. return null;
  354. }
  355. } catch (e) {
  356. return null;
  357. }
  358. }
  359. /**
  360. * 退出登录
  361. * 清除用户登录状态和相关信息
  362. */
  363. static logout(): boolean {
  364. // 清除登录状态
  365. PreferencesUtil.deleteSync('isLogin');
  366. PreferencesUtil.deleteSync('userId');
  367. PreferencesUtil.deleteSync('userName');
  368. PreferencesUtil.deleteSync('userAvatarUrl');
  369. PreferencesUtil.deleteSync('subscriptionName');
  370. PreferencesUtil.deleteSync('subscriptionEndDate');
  371. PreferencesUtil.deleteSync('hasActiveSubscription');
  372. PreferencesUtil.deleteSync('userToken');
  373. PreferencesUtil.deleteSync('isForever');
  374. // 显示退出登录提示
  375. return true;
  376. }
  377. /**
  378. * 本地会员同步到服务器
  379. * @param expireDate
  380. * @returns
  381. */
  382. static async syncLocalNobleToServer(expireDate: string): Promise<boolean> {
  383. try {
  384. const token = PreferencesUtil.getStringSync('userToken', '');
  385. if (!token || !expireDate) {
  386. ToastUtil.showToast('缺少token或到期时间');
  387. return false;
  388. }
  389. const url = `https://pay.ss5.xyz/user/sync_noble?token=${encodeURIComponent(token)}&expireDate=${encodeURIComponent(expireDate)}`;
  390. const httpRequest = http.createHttp();
  391. const options: http.HttpRequestOptions = {
  392. method: http.RequestMethod.GET,
  393. readTimeout: 3000,
  394. connectTimeout: 3000,
  395. };
  396. const response: http.HttpResponse = await httpRequest.request(url, options);
  397. if (response.responseCode === 200) {
  398. let res = response.result as string;
  399. let json = JSON.parse(res) as ApiResponse;
  400. if (json.code === 0) {
  401. ToastUtil.showToast('会员同步成功');
  402. PreferencesUtil.putSync('isNoble', false);
  403. PreferencesUtil.putSync('nobleExpireDate', '');
  404. // 删除本地会员文件
  405. try {
  406. const documentViewPicker = new picker.DocumentViewPicker()
  407. let documentSaveResult = await documentViewPicker.save({ pickerMode: picker.DocumentPickerMode.DOWNLOAD })
  408. let download_path = new fileUri.FileUri(documentSaveResult[0]).path + '/'
  409. let filePath = download_path + STR_LOCK_VIDEO+ '/' + VIP_FILEPATH
  410. if (FileUtil.accessSync(filePath)) {
  411. FileUtil.unlink(filePath);
  412. }
  413. } catch (e) {
  414. console.log("Heanup exception="+e)
  415. // 可忽略或打印日志
  416. }
  417. return true;
  418. } else {
  419. ToastUtil.showToast('会员同步失败:' + (json.msg || '未知错误'));
  420. return false;
  421. }
  422. } else {
  423. ToastUtil.showToast('会员同步接口请求失败');
  424. return false;
  425. }
  426. } catch (e) {
  427. ToastUtil.showToast('会员同步异常' + (e && e.message ? (': ' + e.message) : ''));
  428. return false;
  429. }
  430. }
  431. }