• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Haixing-Hu / js-common-app / 1a20be3a-40f1-4482-9e36-f4eaf95bb221

16 Nov 2024 08:30PM UTC coverage: 53.084% (-0.09%) from 53.171%
1a20be3a-40f1-4482-9e36-f4eaf95bb221

push

circleci

Haixing-Hu
docs: refine jsdoc and refactor

98 of 187 branches covered (52.41%)

Branch coverage included in aggregate %.

0 of 2 new or added lines in 1 file covered. (0.0%)

2 existing lines in 1 file now uncovered.

229 of 429 relevant lines covered (53.38%)

9.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.55
/src/store/basic-user-store.js
1
////////////////////////////////////////////////////////////////////////////////
2
//
3
//    Copyright (c) 2022 - 2024.
4
//    Haixing Hu, Qubit Co. Ltd.
5
//
6
//    All rights reserved.
7
//
8
////////////////////////////////////////////////////////////////////////////////
9
import Logger from '@haixing_hu/logging';
10
import config from '@haixing_hu/config';
11
import { RawField } from '@haixing_hu/pinia-decorator';
12
import AuthStorage from '../auth-storage';
13
import {
14
  ensureUserExist,
15
  refreshAvatar,
16
  loadTokenFromAuthStorage,
17
} from './impl/basic-user-store-impl';
18

19
const logger = Logger.getLogger('store.user');
3✔
20

21
/**
22
 * 管理用户登录状态的 Pinia Store 类的基类。
23
 *
24
 * @author 胡海星
25
 */
26
class BasicUserStore {
27
  /**
28
   * 用于处理用户登录认证的API对象。
29
   *
30
   * 此对象必须有以下接口:
31
   *
32
   * - `loginByUsername(username, password)`: 使用用户名和密码登录;
33
   * - `loginByMobile(mobile, verifyCode)`: 使用手机号码和验证码登录;
34
   * - `loginByOpenId(socialNetwork, appId, openId)`: 使用Open ID登录;
35
   * - `bindOpenId(socialNetwork, appId, openId)`: 绑定Open ID;
36
   * - `logout()`: 登出;
37
   * - `getLoginInfo()`: 获取登录用户的信息;
38
   * - `checkToken(userId, tokenValue)`: 检查用户的Token的值是否合法。
39
   *
40
   * @type {object}
41
   */
42
  @RawField
43
  _userAuthenticateApi = null;
×
44

45
  /**
46
   * 用于处理发送验证码的API对象。
47
   *
48
   * 此对象必须有一个`sendBySms(mobile, scene)`方法,用于发送短信验证码。
49
   *
50
   * @type {object}
51
   */
52
  @RawField
53
  _verifyCodeApi = null;
×
54

55
  /**
56
   * 当前应用程序的代码,用于设置`Cookies`、`LocalStorage`和`SessionStorage`中存储
57
   * 的数据项的键值前缀。
58
   *
59
   * @type {string}
60
   */
61
  @RawField
62
  _appCode = '';
×
63

64
  /**
65
   * 用于存储用户认证信息的存储对象。
66
   *
67
   * @type {AuthStorage}
68
   */
69
  @RawField
70
  _authStorage = null;
×
71

72
  /**
73
   * 当前用户信息。
74
   *
75
   * 用户信息包括:
76
   * - `id`: 用户ID;
77
   * - `username`: 用户名;
78
   * - `nickname`: 用户昵称;
79
   * - `avatar`: 用户头像;
80
   * - `name`: 用户姓名;
81
   * - `gender`: 用户性别;
82
   * - `mobile`: 用户手机号码。
83
   *
84
   * @type {object}
85
   */
86
  user = null;
×
87

88
  /**
89
   * 当前用户的密码。
90
   *
91
   * @type {string}
92
   */
93
  password = '';
×
94

95
  /**
96
   * 当前用户是否需要保存登录信息。
97
   *
98
   * @type {boolean}
99
   */
100
  saveLogin = false;
×
101

102
  /**
103
   * 用户Open ID所属的社交网络。
104
   *
105
   * @type {string}
106
   */
107
  socialNetwork = null;
×
108

109
  /**
110
   * 用户Open ID所属的社交网络上的APP ID。
111
   *
112
   * @type {string}
113
   */
114
  appId = null;
×
115

116
  /**
117
   * 用户Open ID。
118
   *
119
   * @type {string}
120
   */
121
  openId = null;
×
122

123
  /**
124
   * 当前用户的Access Token。
125
   *
126
   * @type {object}
127
   */
128
  token = null;
×
129

130
  /**
131
   * 当前用户的权限。
132
   *
133
   * @type {array<string>}
134
   */
135
  privileges = [];
×
136

137
  /**
138
   * 当前用户的角色。
139
   *
140
   * @type {array<string>}
141
   */
142
  roles = [];
×
143

144
  /**
145
   * 构造一个新的`BasicUserStore`对象。
146
   *
147
   * 该函数需要一个用于处理登录认证的API对象。
148
   * - `sendBySms(mobile, type)`: 发送短信验证码;
149
   *
150
   * @param {object} userAuthenticateApi
151
   *     用于处理用户登录认证的API对象。此API对象需要提供以下接口:
152
   *
153
   *     - `loginByUsername(username, password)`: 使用用户名和密码登录;
154
   *     - `loginByMobile(mobile, verifyCode)`: 使用手机号码和验证码登录;
155
   *     - `loginByOpenId(socialNetwork, appId, openId)`: 使用Open ID登录;
156
   *     - `bindOpenId(socialNetwork, appId, openId)`: 绑定Open ID;
157
   *     - `logout()`: 登出;
158
   *     - `getLoginInfo()`: 获取登录用户的信息;
159
   *     - `checkToken(userId, tokenValue)`: 检查用户的Token的值是否合法。
160
   * @param {object} verifyCodeApi
161
   *     用于处理发送验证码的API对象。
162
   *
163
   *     - `endBySms(mobile, scene)`:发送短信验证码。
164
   * @param {string} appCode
165
   *     当前应用程序的代码,用于设置`Cookies`、`LocalStorage`和`SessionStorage`中存储
166
   *     的数据项的键值前缀。
167
   */
168
  constructor(userAuthenticateApi, verifyCodeApi, appCode) {
169
    if (!userAuthenticateApi) {
×
170
      throw new Error('The API object for authenticating users is required.');
×
171
    }
172
    if (!verifyCodeApi) {
×
173
      throw new Error('The API object for sending verify code is required.');
×
174
    }
175
    if (!appCode) {
×
176
      throw new Error('The app code is required.');
×
177
    }
178
    this._userAuthenticateApi = userAuthenticateApi;
×
179
    this._verifyCodeApi = verifyCodeApi;
×
180
    this._appCode = appCode;
×
181
    this._authStorage = new AuthStorage(appCode);
×
182
    this.user = this._authStorage.loadUserInfo() ?? null;
×
183
    this.password = this._authStorage.loadPassword() ?? '';
×
184
    this.saveLogin = this._authStorage.loadSaveLogin() ?? false;
×
185
    this.socialNetwork = config.get('social_network') ?? null;
×
186
    this.appId = config.get('social_network_app_id') ?? null;
×
187
    this.token = this._authStorage.loadToken() ?? null;
×
188
    this.privileges = this._authStorage.loadPrivileges() ?? [];
×
189
    this.roles = this._authStorage.loadRoles() ?? [];
×
190
  }
191

192
  /**
193
   * 用于存储用户认证信息的存储对象。
194
   *
195
   * @return {AuthStorage}
196
   *     用于存储用户认证信息的存储对象。
197
   */
198
  get authStorage() {
199
    return this._authStorage;
×
200
  }
201

202
  /**
203
   * 当前用户是否已经登录。
204
   *
205
   * @return {boolean}
206
   *     如果当前用户已经登录,则返回`true`;否则返回`false`。
207
   */
208
  get loggedIn() {
209
    return !!this.token?.value;
×
210
  }
211

212
  /**
213
   * 重置状态。
214
   */
215
  resetState() {
216
    this.$reset();
×
217
    logger.debug('state was reset to:', this.$state);
×
218
  }
219

220
  /**
221
   * 设置用户基本信息。
222
   *
223
   * @param {object} user
224
   *     用户基本信息。
225
   */
226
  setUserInfo(user) {
227
    this.user = { ...user };
×
228
    if (this.saveLogin) {
×
229
      this._authStorage.storeUserInfo(this.user);
×
230
    }
231
  }
232

233
  /**
234
   * 设置用户ID。
235
   *
236
   * @param {number|bigint|string} userId
237
   *     用户ID。
238
   */
239
  setUserId(userId) {
240
    const user = ensureUserExist(this);
×
241
    user.id = userId;
×
242
    if (this.saveLogin) {
×
243
      this._authStorage.storeUserId(userId);
×
244
    } else {
245
      this._authStorage.removeUserId();
×
246
    }
247
  }
248

249
  /**
250
   * 设置用户名。
251
   *
252
   * @param {string} username
253
   *     用户名。
254
   */
255
  setUsername(username) {
256
    const user = ensureUserExist(this);
×
257
    user.username = username;
×
258
    if (this.saveLogin) {
×
259
      this._authStorage.storeUsername(username);
×
260
    } else {
261
      this._authStorage.removeUsername();
×
262
    }
263
  }
264

265
  /**
266
   * 设置用户密码。
267
   *
268
   * @param {string} password
269
   *     用户密码。
270
   */
271
  setPassword(password) {
272
    this.password = password;
×
273
    if (this.saveLogin) {
×
274
      this._authStorage.storePassword(password);
×
275
    } else {
276
      this._authStorage.removePassword();
×
277
    }
278
  }
279

280
  /**
281
   * 设置用户手机号码。
282
   *
283
   * @param {string} mobile
284
   *     用户手机号码。
285
   */
286
  setMobile(mobile) {
287
    const user = ensureUserExist(this);
×
288
    user.mobile = mobile;
×
289
    if (this.saveLogin) {
×
290
      this._authStorage.storeMobile(mobile);
×
291
    } else {
292
      this._authStorage.removeMobile();
×
293
    }
294
  }
295

296
  /**
297
   * 设置用户头像。
298
   *
299
   * @param {string} avatar
300
   *     用户头像。
301
   */
302
  setAvatar(avatar) {
303
    const user = ensureUserExist(this);
×
304
    user.avatar = avatar ?? '';
×
305
    this._authStorage.storeAvatar(avatar);
×
306
  }
307

308
  /**
309
   * 设置用户是否保存登录信息。
310
   *
311
   * @param {boolean} saveLogin
312
   *     用户是否保存登录信息。
313
   */
314
  setSaveLogin(saveLogin) {
315
    this.saveLogin = saveLogin;
×
316
    this._authStorage.storeSaveLogin(saveLogin);
×
317
  }
318

319
  /**
320
   * 设置用户的登录令牌。
321
   *
322
   * @param {object} token
323
   *     用户的登录令牌。
324
   */
325
  setToken(token) {
326
    this.token = { ...token };
×
327
    if (this.saveLogin) {
×
328
      this._authStorage.storeToken(token);
×
329
    } else {
330
      this._authStorage.removeToken();
×
331
    }
332
  }
333

334
  /**
335
   * 移除用户的登录令牌。
336
   */
337
  removeToken() {
338
    logger.debug('Remove user access token.');
×
339
    this.token = null;
×
340
    this._authStorage.removeToken();
×
341
  }
342

343
  /**
344
   * 重置用户的Token值。
345
   *
346
   * 此函数将删除用户的Token值,并重置用户的状态。
347
   */
348
  resetToken() {
349
    logger.debug('Reset user access token.');
×
350
    this.removeToken();  // must remove  token  first
×
351
    this.resetState();
×
352
  }
353

354
  /**
355
   * 设置用户的权限列表。
356
   *
357
   * @param {array<string>} privileges
358
   *     用户的权限列表。
359
   */
360
  setPrivileges(privileges) {
361
    this.privileges = privileges ?? [];
×
362
    this._authStorage.storePrivileges(this.privileges);
×
363
  }
364

365
  /**
366
   * 设置用户的角色列表。
367
   *
368
   * @param {array<string>} roles
369
   *     用户的角色列表。
370
   */
371
  setRoles(roles) {
372
    this.roles = roles ?? [];
×
373
    this._authStorage.storeRoles(this.roles);
×
374
  }
375

376
  /**
377
   * 设置用户的Open ID。
378
   *
379
   * @param {string} socialNetwork
380
   *     Open ID 所属社交网络。
381
   * @param {string} appId
382
   *     Open ID 所属社交网络上的APP ID。
383
   * @param {string} openId
384
   *     Open ID。
385
   */
386
  setOpenId(socialNetwork, appId, openId) {
387
    this.socialNetwork = socialNetwork;
×
388
    this.appId = appId;
×
389
    this.openId = openId;
×
390
  }
391

392
  /**
393
   * 设置用户登录后服务器返回的响应数据。
394
   *
395
   * 响应数据包括用户基本信息、Token、权限列表和角色列表。
396
   *
397
   * @param {object} response
398
   *     用户登录后服务器返回的响应数据。
399
   */
400
  setLoginResponse(response) {
401
    logger.debug('Set the login response:', response);
×
402
    this.setUserInfo(response.user);
×
403
    this.setToken(response.token);
×
404
    this.setPrivileges(response.privileges);
×
405
    this.setRoles(response.roles);
×
406
    refreshAvatar(this);
×
407
  }
408

409
  /**
410
   * 尝试获取用户的Token值。
411
   *
412
   * @returns {Promise<object|null>}
413
   *     如果成功获取用户的Token值,则返回该值;否则返回`null`。
414
   */
415
  async loadToken() {
416
    logger.debug('Getting the user token...');
×
417
    if (await loadTokenFromAuthStorage(this)) {
×
418
      logger.debug('The user token value is:', this.token.value);
×
419
      await this.refreshLoginInfo();
×
420
      return this.token;
×
421
    } else {
422
      return null;
×
423
    }
424
  }
425

426
  /**
427
   * 使用用户名和密码登录。
428
   *
429
   * @param {string} username
430
   *     用户名。
431
   * @param {string} password
432
   *     密码。
433
   * @param {boolean} saveLogin
434
   *     是否保存登录信息。
435
   * @return {Promise<LoginResponse|ErrorInfo>}
436
   *     此 HTTP 请求的 Promise,若操作成功,解析成功并返回一个`LoginResponse`对象,包含
437
   *     了指定用户的登录信息;若操作失败,解析失败并返回一个`ErrorInfo`对象。
438
   */
439
  loginByUsername(username, password, saveLogin) {
440
    logger.info('Login: username = %s, saveLogin = %s', username, saveLogin);
×
441
    this.setSaveLogin(saveLogin);
×
442
    this.setUsername(username);
×
443
    this.setPassword(password);
×
444
    this.removeToken();           // 注意调用login API时必须先清除已保存的Access Token
×
445
    return this._userAuthenticateApi.loginByUsername(username, password).then((response) => {
×
446
      logger.debug('Successfully logged in with:', response);
×
447
      this.setLoginResponse(response);
×
448
      return response;
×
449
    });
450
  }
451

452
  /**
453
   * 使用手机号码和验证码登录。
454
   *
455
   * @param {string} mobile
456
   *     手机号码。
457
   * @param {string} verifyCode
458
   *     该手机收到的验证码。
459
   * @param {boolean} saveLogin
460
   *     是否保存登录信息。
461
   * @return {Promise<LoginResponse|ErrorInfo>}
462
   *     此 HTTP 请求的 Promise,若操作成功,解析成功并返回一个`LoginResponse`对象,包含
463
   *     了指定用户的登录信息;若操作失败,解析失败并返回一个`ErrorInfo`对象。
464
   */
465
  loginByMobile(mobile, verifyCode, saveLogin) {
466
    logger.debug('Login: mobile = %s, verifyCode = %s, saveLogin = %s', mobile, verifyCode, saveLogin);
×
467
    this.setSaveLogin(saveLogin);
×
468
    this.setMobile(mobile);
×
469
    this.removeToken();           // 注意调用login API时必须先清除已保存的Access Token
×
470
    return this._userAuthenticateApi.loginByMobile(mobile, verifyCode).then((response) => {
×
471
      logger.debug('Successfully logged in with:', response);
×
472
      this.setLoginResponse(response);
×
473
      return response;
×
474
    });
475
  }
476

477
  /**
478
   * 使用社交网络的Open ID登录。
479
   *
480
   * @param {SocialNetwork} socialNetwork
481
   *     指定的社交网络枚举名称。
482
   * @param {string} appId
483
   *     该社交网络下的APP(公众号)的ID。
484
   * @param {string} openId
485
   *     用户在该社交网络指定的APP(公众号)下的Open ID。
486
   * @return {Promise<LoginResponse|ErrorInfo>}
487
   *     此 HTTP 请求的 Promise,若操作成功,解析成功并返回一个`LoginResponse`对象,包含
488
   *     了指定用户的登录信息;若操作失败,解析失败并返回一个`ErrorInfo`对象。
489
   */
490
  loginByOpenId(socialNetwork, appId, openId) {
491
    logger.debug('Login: socialNetwork = %s, appId = %s, openId = %s', socialNetwork, appId, openId);
×
492
    this.removeToken();           // 注意调用login API时必须先清除已保存的Access Token
×
493
    return this._userAuthenticateApi.loginByOpenId(socialNetwork, appId, openId).then((response) => {
×
494
      logger.debug('Successfully logged in with:', response);
×
495
      this.setOpenId(socialNetwork, appId, openId);
×
496
      this.setLoginResponse(response);
×
497
      return response;
×
498
    });
499
  }
500

501
  /**
502
   * 将当前登录用户的账号绑定到指定的Open ID。
503
   *
504
   * @param {SocialNetwork} socialNetwork
505
   *     指定的社交网络枚举名称。
506
   * @param {string} appId
507
   *     该社交网络下的APP(公众号)的ID。
508
   * @param {string} openId
509
   *     用户在该社交网络指定的APP(公众号)下的Open ID。
510
   * @return
511
   *     此 HTTP 请求的 Promise。若操作成功,解析成功且没有返回值;若操作失败,解析失败并
512
   *     返回一个`ErrorInfo`对象。
513
   */
514
  bindOpenId(socialNetwork, appId, openId) {
UNCOV
515
    logger.debug('Bind the Open ID for the current user: socialNetwork = %s, '
×
516
      + 'appId = %s, openId = %s', socialNetwork, appId, openId);
NEW
517
    return this._userAuthenticateApi.bindOpenId(socialNetwork, appId, openId).then(() => {
×
UNCOV
518
      logger.debug('Successfully bind the Open ID for the current user.');
×
NEW
519
      this.setOpenId(socialNetwork, appId, openId);
×
520
    });
521
  }
522

523
  /**
524
   * 用户注销登录。
525
   *
526
   * @return {Promise<void|ErrorInfo>}
527
   *     此 HTTP 请求的 Promise;若操作成功,解析成功且没有返回值;若操作失败,解析失败并返
528
   *     回一个`ErrorInfo`对象。
529
   */
530
  logout() {
531
    logger.debug('Logout.');
×
532
    return this._userAuthenticateApi.logout().then(() => {
×
533
      this.removeToken(); // must remove  token  first
×
534
      this.resetState();
×
535
    });
536
  }
537

538
  /**
539
   * 刷新当前已登录用户的登录信息。
540
   *
541
   * @return {Promise<LoginResponse|ErrorInfo>}
542
   *     此 HTTP 请求的 Promise,若操作成功,解析成功并返回一个`LoginResponse`对象,包含
543
   *     了指定用户的登录信息;若操作失败,解析失败并返回一个`ErrorInfo`对象。
544
   */
545
  refreshLoginInfo() {
546
    logger.debug('Loading the user information for the current user.');
×
547
    return this._userAuthenticateApi.getLoginInfo().then((response) => {
×
548
      logger.debug('Successfully get the login information of the current user:', response);
×
549
      this.setLoginResponse(response);
×
550
      return response;
×
551
    });
552
  }
553

554
  /**
555
   * 发送登录验证码。
556
   *
557
   * @param {string} mobile
558
   *     登录用户的手机号码。
559
   */
560
  sendLoginVerifyCode(mobile) {
561
    logger.debug('Sending login verify code to %s.', mobile);
×
562
    return this._verifyCodeApi.sendBySms(mobile, 'LOGIN').then(() => {
×
563
      logger.info('Successfully sent the login verify code to:', mobile);
×
564
    });
565
  }
566

567
  /**
568
   * 检查指定的 Token 的值对于指定的用户是否依然合法。
569
   *
570
   * @param {string} userId
571
   *     用户的ID。
572
   * @param {string} tokenValue
573
   *     Token的值。
574
   * @returns {Promise<boolean|ErrorInfo>}
575
   *     此 HTTP 请求的 Promise,若操作成功,解析成功,如果Token的值对于指定的用户依然合法,
576
   *     则返回`true`;否则返回`false`;若操作失败,解析失败并返回一个`ErrorInfo`对象。
577
   */
578
  async isTokenValid(userId, tokenValue) {
579
    try {
×
580
      logger.info('Checking the whether token value for the user %s is valid:', userId, tokenValue);
×
581
      const token = await this._userAuthenticateApi.checkToken(userId, tokenValue);
×
582
      const valid = !!token;
×
583
      logger.info('The validity of the token value is:', valid);
×
584
      return valid;
×
585
    } catch (error) {
586
      logger.info('The token value is invalid.');
×
587
      return false;
×
588
    }
589
  }
590
}
591

592
export default BasicUserStore;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc