From ff2a97960a1f60059cf2cb032fd1e4141306d520 Mon Sep 17 00:00:00 2001 From: Xc Date: Fri, 6 May 2022 10:16:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86token/me=E7=AD=89?= =?UTF-8?q?=E5=87=A0=E4=B8=AA=E5=85=AC=E5=85=B1=E9=A1=B5=E9=9D=A2=EF=BC=8C?= =?UTF-8?q?context=E4=B8=AD=E5=A2=9E=E5=8A=A0=E4=BA=86scene?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/RuntimeContext.d.ts | 6 +- lib/RuntimeContext.js | 11 +-- lib/aspects/index.d.ts | 3 +- lib/aspects/index.js | 1 + lib/aspects/token.d.ts | 12 +++ lib/aspects/token.js | 96 +++++++++++++++++++- lib/entities/ExtraFile.d.ts | 3 +- lib/features/token.d.ts | 5 +- lib/features/token.js | 33 ++++++- lib/triggers/user.js | 4 +- lib/utils/extraFile.d.ts | 3 + lib/utils/extraFile.js | 10 +++ package.json | 3 +- scripts/cleanDtsInWechatMp.ts | 22 +++++ src/RuntimeContext.ts | 13 ++- src/aspects/index.ts | 5 +- src/aspects/token.ts | 107 +++++++++++++++++++++- src/checkers/index.ts | 3 +- src/checkers/token.ts | 39 +++++++++ src/data/userRole.ts | 1 + src/entities/ExtraFile.ts | 3 +- src/features/token.ts | 77 +++++++++++++--- src/triggers/user.ts | 5 +- src/utils/extraFile.ts | 20 +++++ wechatMp/pages/address/list/index.ts | 2 +- wechatMp/pages/address/upsert/index.ts | 4 +- wechatMp/pages/mobile/me/index.json | 5 ++ wechatMp/pages/mobile/me/index.less | 34 +++++++ wechatMp/pages/mobile/me/index.ts | 32 +++++++ wechatMp/pages/mobile/me/index.wxml | 24 +++++ wechatMp/pages/pickers/area/index.ts | 2 +- wechatMp/pages/token/login/index.ts | 4 +- wechatMp/pages/token/login/index.wxml | 2 +- wechatMp/pages/token/me/index.json | 5 ++ wechatMp/pages/token/me/index.less | 34 +++++++ wechatMp/pages/token/me/index.ts | 117 +++++++++++++++++++++++++ wechatMp/pages/token/me/index.wxml | 43 +++++++++ wechatMp/pages/user/manage/index.json | 6 ++ wechatMp/pages/user/manage/index.less | 36 ++++++++ wechatMp/pages/user/manage/index.ts | 60 +++++++++++++ wechatMp/pages/user/manage/index.wxml | 32 +++++++ wechatMp/styles/cpn.less | 1 + 42 files changed, 880 insertions(+), 48 deletions(-) create mode 100644 lib/utils/extraFile.d.ts create mode 100644 lib/utils/extraFile.js create mode 100644 scripts/cleanDtsInWechatMp.ts create mode 100644 src/checkers/token.ts create mode 100644 src/utils/extraFile.ts create mode 100644 wechatMp/pages/mobile/me/index.json create mode 100644 wechatMp/pages/mobile/me/index.less create mode 100644 wechatMp/pages/mobile/me/index.ts create mode 100644 wechatMp/pages/mobile/me/index.wxml create mode 100644 wechatMp/pages/token/me/index.json create mode 100644 wechatMp/pages/token/me/index.less create mode 100644 wechatMp/pages/token/me/index.ts create mode 100644 wechatMp/pages/token/me/index.wxml create mode 100644 wechatMp/pages/user/manage/index.json create mode 100644 wechatMp/pages/user/manage/index.less create mode 100644 wechatMp/pages/user/manage/index.ts create mode 100644 wechatMp/pages/user/manage/index.wxml diff --git a/lib/RuntimeContext.d.ts b/lib/RuntimeContext.d.ts index ac4dde2bf..7a8f0296b 100644 --- a/lib/RuntimeContext.d.ts +++ b/lib/RuntimeContext.d.ts @@ -2,9 +2,9 @@ import { UniversalContext } from 'oak-domain/lib/store/UniversalContext'; import { EntityDict } from 'oak-app-domain'; import { RowStore } from 'oak-domain/lib/types'; export declare abstract class GeneralRuntimeContext extends UniversalContext { - applicationId: string; - token?: string; - constructor(store: RowStore>, appId: string, token?: string); + private applicationId; + private getTokenFn; + constructor(store: RowStore>, appId: string, getToken: () => Promise); getApplication(): Promise = TokenAD & CrudAD; diff --git a/lib/aspects/token.d.ts b/lib/aspects/token.d.ts index 6a3eefdb6..fd328f355 100644 --- a/lib/aspects/token.d.ts +++ b/lib/aspects/token.d.ts @@ -12,3 +12,15 @@ export declare function loginWechatMp; +/** + * 同步从wx.getUserProfile拿到的用户信息 + * @param param0 + * @param context + */ +export declare function syncUserInfoWechatMp>({ nickname, avatarUrl, encryptedData, iv, signature }: { + nickname: string; + avatarUrl: string; + encryptedData: string; + iv: string; + signature: string; +}, context: Cxt): Promise; diff --git a/lib/aspects/token.js b/lib/aspects/token.js index 30c154aa8..58bbc5854 100644 --- a/lib/aspects/token.js +++ b/lib/aspects/token.js @@ -3,9 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.loginWechatMp = exports.loginByPassword = exports.loginMp = void 0; -const assert_1 = __importDefault(require("assert")); +exports.syncUserInfoWechatMp = exports.loginWechatMp = exports.loginByPassword = exports.loginMp = void 0; const oak_wechat_sdk_1 = __importDefault(require("oak-wechat-sdk")); +const assert_1 = __importDefault(require("assert")); const lodash_1 = require("lodash"); async function loginMp(params, context) { const { rowStore } = context; @@ -215,6 +215,98 @@ async function loginWechatMp({ code, env }, context) { return id; } exports.loginWechatMp = loginWechatMp; +/** + * 同步从wx.getUserProfile拿到的用户信息 + * @param param0 + * @param context + */ +async function syncUserInfoWechatMp({ nickname, avatarUrl, encryptedData, iv, signature }, context) { + const { rowStore } = context; + const { userId } = (await context.getToken()); + const application = await context.getApplication(); + const { result: [{ sessionKey, user }] } = await rowStore.select('wechatUser', { + data: { + id: 1, + sessionKey: 1, + user: { + id: 1, + nickname: 1, + extraFile$entity: { + $entity: 'extraFile', + data: { + id: 1, + tag1: 1, + origin: 1, + bucket: 1, + objectId: 1, + filename: 1, + }, + filter: { + tag1: 'avatar', + }, + } + } + }, + filter: { + userId: userId, + applicationId: application.id, + } + }, context); + const { type, config } = application; + (0, assert_1.default)(type === 'wechatMp' || config.type === 'wechatMp'); + const config2 = config; + const { appId, appSecret } = config2; + const wechatInstance = oak_wechat_sdk_1.default.getInstance(appId, appSecret, 'wechatMp'); + console.log(avatarUrl); + // const result = wechatInstance.decryptData(sessionKey as string, encryptedData, iv, signature); + // 实测发现解密出来的和userInfo完全一致…… + // console.log(result); + const { id, nickname: originNickname, extraFile$entity } = user; + const updateData = {}; + if (nickname !== originNickname) { + Object.assign(updateData, { + nickname, + }); + } + /* if (extraFile$entity?.length === 0 || composeFileUrl(extraFile$entity![0]) !== avatarUrl) { + // 需要更新新的avatar extra file + const extraFileOperations: ExtraFileOperation['data'][] = [ + { + action: 'create', + data: assign({ + id: await generateNewId(), + tag1: 'avatar', + userId, + }, decomposeFileUrl(avatarUrl)) + } + ]; + if (extraFile$entity!.length > 0) { + extraFileOperations.push( + { + action: 'remove', + data: {}, + filter: { + id: extraFile$entity![0].id, + } + } + ); + } + assign(updateData, { + extraFile$entity: extraFileOperations, + }); + } + + if (keys(updateData).length > 0) { + await rowStore.operate('user', { + action: 'update', + data: updateData, + filter: { + id, + } + }, context); + } */ +} +exports.syncUserInfoWechatMp = syncUserInfoWechatMp; /* export type AspectDict = { loginMp: (params: { code: string }, context: GeneralRuntimeContext) => Promise; loginByPassword: (params: { password: string, mobile: string }, context: GeneralRuntimeContext) => Promise; diff --git a/lib/entities/ExtraFile.d.ts b/lib/entities/ExtraFile.d.ts index 7c6343fea..8943645cc 100644 --- a/lib/entities/ExtraFile.d.ts +++ b/lib/entities/ExtraFile.d.ts @@ -1,7 +1,7 @@ import { String, Text } from 'oak-domain/lib/types/DataType'; import { EntityShape } from 'oak-domain/lib/types/Entity'; export interface Schema extends EntityShape { - origin: 'qiniu'; + origin: 'qiniu' | 'unknown'; type: 'image' | 'pdf' | 'video' | 'audio' | 'file'; bucket: String<16>; objectId: String<64>; @@ -11,4 +11,5 @@ export interface Schema extends EntityShape { md5: Text; entity: String<32>; entityId: String<64>; + extra1?: Text; } diff --git a/lib/features/token.d.ts b/lib/features/token.d.ts index edd432150..4c5779d1f 100644 --- a/lib/features/token.d.ts +++ b/lib/features/token.d.ts @@ -4,8 +4,11 @@ import { Aspect, Context } from 'oak-domain/lib/types'; import { WechatMpEnv } from 'oak-app-domain/Token/Schema'; export declare class Token, AD extends Record>> extends Feature { private token?; + private rwLock; + constructor(); loginByPassword(mobile: string, password: string): Promise; loginWechatMp(code: string, env: WechatMpEnv): Promise; + syncUserInfoWechatMp(): Promise; logout(): Promise; - getToken(): string | undefined; + getToken(): Promise; } diff --git a/lib/features/token.js b/lib/features/token.js index 26b0d7259..3bf7f2bd6 100644 --- a/lib/features/token.js +++ b/lib/features/token.js @@ -8,22 +8,48 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, Object.defineProperty(exports, "__esModule", { value: true }); exports.Token = void 0; const oak_frontend_base_1 = require("oak-frontend-base"); +const concurrent_1 = require("oak-domain/lib/utils/concurrent"); class Token extends oak_frontend_base_1.Feature { token; + rwLock; + constructor() { + super(); + this.rwLock = new concurrent_1.RWLock(); + } async loginByPassword(mobile, password) { + await this.rwLock.acquire('X'); this.token = await this.getAspectProxy().loginByPassword({ password, mobile }); + this.rwLock.release(); } async loginWechatMp(code, env) { + await this.rwLock.acquire('X'); this.token = await this.getAspectProxy().loginWechatMp({ code, env, }); + this.rwLock.release(); + } + async syncUserInfoWechatMp() { + const info = await wx.getUserProfile({ + desc: '同步微信昵称和头像信息', + }); + const { userInfo: { nickName: nickname, avatarUrl }, encryptedData, signature, iv } = info; + await this.getAspectProxy().syncUserInfoWechatMp({ + nickname, + avatarUrl, + encryptedData, + signature, + iv, + }); } async logout() { this.token = undefined; } - getToken() { - return this.token; + async getToken() { + await this.rwLock.acquire('S'); + const token = this.token; + this.rwLock.release(); + return token; } } __decorate([ @@ -32,6 +58,9 @@ __decorate([ __decorate([ oak_frontend_base_1.Action ], Token.prototype, "loginWechatMp", null); +__decorate([ + oak_frontend_base_1.Action +], Token.prototype, "syncUserInfoWechatMp", null); __decorate([ oak_frontend_base_1.Action ], Token.prototype, "logout", null); diff --git a/lib/triggers/user.js b/lib/triggers/user.js index c39a9f69b..7d68231fd 100644 --- a/lib/triggers/user.js +++ b/lib/triggers/user.js @@ -8,7 +8,7 @@ const triggers = [ entity: 'user', action: 'create', when: 'before', - fn: async ({ operation }, context) => { + fn: async ({ operation }, context, params) => { if (NO_ANY_USER) { const { rowStore } = context; const { result } = await rowStore.select('user', { @@ -22,7 +22,7 @@ const triggers = [ }, indexFrom: 0, count: 1, - }, context); + }, context, params); if (result.length === 0) { const { data } = operation; const userData = data instanceof Array ? data[0] : data; diff --git a/lib/utils/extraFile.d.ts b/lib/utils/extraFile.d.ts new file mode 100644 index 000000000..fa5c59e0c --- /dev/null +++ b/lib/utils/extraFile.d.ts @@ -0,0 +1,3 @@ +import { OpSchema as ExtraFile } from '../base-app-domain/ExtraFile/Schema'; +export declare function composeFileUrl(extraFile: ExtraFile): string; +export declare function decomposeFileUrl(url: string): Pick; diff --git a/lib/utils/extraFile.js b/lib/utils/extraFile.js new file mode 100644 index 000000000..3400ffb26 --- /dev/null +++ b/lib/utils/extraFile.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.decomposeFileUrl = exports.composeFileUrl = void 0; +function composeFileUrl(extraFile) { + return ``; +} +exports.composeFileUrl = composeFileUrl; +function decomposeFileUrl(url) { +} +exports.decomposeFileUrl = decomposeFileUrl; diff --git a/package.json b/package.json index 1cea46db9..871b676bd 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "scripts": { "prebuild": "ts-node ./scripts/buildBaseEntityDict && npm link ./src/base-app-domain", "build": "tsc", - "test": "ts-node ./scripts/getAmapArea.ts" + "test": "ts-node ./scripts/getAmapArea.ts", + "clean:mp": "ts-node ./scripts/cleanDtsInWechatMp" }, "main": "src/index" } diff --git a/scripts/cleanDtsInWechatMp.ts b/scripts/cleanDtsInWechatMp.ts new file mode 100644 index 000000000..29f4771eb --- /dev/null +++ b/scripts/cleanDtsInWechatMp.ts @@ -0,0 +1,22 @@ +import { removeSync, readdirSync, statSync } from 'fs-extra'; +import { join } from 'path'; + +function removeDts(path: string) { + const files = readdirSync(path); + + files.forEach( + (file) => { + const stat = statSync(join(path, file)); + if (stat.isDirectory()) { + removeDts(join(path, file)); + } + else if (file.endsWith('.d.ts') || file.endsWith('.js')) { + removeSync(join(path, file)); + console.log(`remove ${join(path, file)}`); + } + } + ); +} + +removeDts(join(process.cwd(), 'wechatMp')); + diff --git a/src/RuntimeContext.ts b/src/RuntimeContext.ts index 6d67fcebc..7925303d4 100644 --- a/src/RuntimeContext.ts +++ b/src/RuntimeContext.ts @@ -6,12 +6,15 @@ import { RowStore } from 'oak-domain/lib/types'; export abstract class GeneralRuntimeContext extends UniversalContext { - applicationId: string; - getTokenFn: () => Promise; - constructor(store: RowStore>, appId: string, getToken: () => Promise) { + private applicationId: string; + private getTokenFn: () => Promise; + private scene: string; + + constructor(store: RowStore>, appId: string, getToken: () => Promise, scene: string) { super(store); this.applicationId = appId; this.getTokenFn = getToken; + this.scene = scene; } async getApplication () { @@ -49,4 +52,8 @@ export abstract class GeneralRuntimeContext extends Unive return token; } } + + getScene() { + return this.scene; + } }; \ No newline at end of file diff --git a/src/aspects/index.ts b/src/aspects/index.ts index a0e671ab2..7e5519dee 100644 --- a/src/aspects/index.ts +++ b/src/aspects/index.ts @@ -1,11 +1,10 @@ -import { EntityDict } from 'oak-domain/lib/types'; -import { EntityDict as BaseEntityDict } from 'oak-app-domain/EntityDict'; -import { loginByPassword, loginMp, loginWechatMp } from './token'; +import { loginByPassword, loginMp, loginWechatMp, syncUserInfoWechatMp } from './token'; export const aspectDict = { loginByPassword, loginMp, loginWechatMp, + syncUserInfoWechatMp, }; // export type AspectDict = TokenAD & CrudAD; \ No newline at end of file diff --git a/src/aspects/token.ts b/src/aspects/token.ts index 036f295c1..702ae09fe 100644 --- a/src/aspects/token.ts +++ b/src/aspects/token.ts @@ -1,13 +1,15 @@ import { GeneralRuntimeContext } from '../RuntimeContext'; import { EntityDict } from 'oak-app-domain'; +import WechatSDK from 'oak-wechat-sdk'; import assert from 'assert'; import { WechatMpConfig } from 'oak-app-domain/Application/Schema'; -import WechatSDK from 'oak-wechat-sdk'; import { CreateOperationData as CreateToken, WechatMpEnv } from 'oak-app-domain/Token/Schema'; import { CreateOperationData as CreateWechatUser } from 'oak-app-domain/WechatUser/Schema'; -import { CreateOperationData as CreateUser } from 'oak-app-domain/User/Schema'; -import { assign, isEqual } from 'lodash'; +import { CreateOperationData as CreateUser, Schema as User } from 'oak-app-domain/User/Schema'; +import { Operation as ExtraFileOperation } from 'oak-app-domain/ExtraFile/Schema'; +import { assign, isEqual, keys } from 'lodash'; import { SelectRowShape } from 'oak-domain/lib/types'; +import { composeFileUrl, decomposeFileUrl } from '../utils/extraFile'; export async function loginMp>(params: { code: string }, context: Cxt): Promise { const { rowStore } = context; @@ -243,6 +245,105 @@ export async function loginWechatMp>({ + nickname, avatarUrl, encryptedData, iv, signature +}: {nickname: string, avatarUrl: string, encryptedData: string, iv: string, signature: string}, context: Cxt) { + const { rowStore } = context; + const { userId } = (await context.getToken())!; + const application = await context.getApplication(); + const { result: [{ sessionKey, user }]} = await rowStore.select('wechatUser', { + data: { + id: 1, + sessionKey: 1, + user: { + id: 1, + nickname: 1, + extraFile$entity: { + $entity: 'extraFile', + data: { + id: 1, + tag1: 1, + origin: 1, + bucket: 1, + objectId: 1, + filename: 1, + extra1: 1, + }, + filter: { + tag1: 'avatar', + }, + } + } + }, + filter: { + userId: userId!, + applicationId: application.id, + } + }, context); + + + // console.log(avatarUrl); + // const { type, config } = application; + + // assert(type === 'wechatMp' || config.type === 'wechatMp'); + // const config2 = config as WechatMpConfig; + // const { appId, appSecret } = config2; + // const wechatInstance = WechatSDK.getInstance(appId, appSecret, 'wechatMp'); + // const result = wechatInstance.decryptData(sessionKey as string, encryptedData, iv, signature); + // 实测发现解密出来的和userInfo完全一致…… + // console.log(result); + const { nickname: originNickname, extraFile$entity } = user as User; + const updateData = {}; + if (nickname !== originNickname) { + Object.assign(updateData, { + nickname, + }); + } + if (extraFile$entity?.length === 0 || composeFileUrl(extraFile$entity![0]) !== avatarUrl) { + // 需要更新新的avatar extra file + const extraFileOperations: ExtraFileOperation['data'][] = [ + { + action: 'create', + data: assign({ + id: await generateNewId(), + tag1: 'avatar', + entity: 'user', + entityId: userId, + }, decomposeFileUrl(avatarUrl)) + } + ]; + if (extraFile$entity!.length > 0) { + extraFileOperations.push( + { + action: 'remove', + data: {}, + filter: { + id: extraFile$entity![0].id, + } + } + ); + } + assign(updateData, { + extraFile$entity: extraFileOperations, + }); + } + + if (keys(updateData).length > 0) { + await rowStore.operate('user', { + action: 'update', + data: updateData, + filter: { + id: userId!, + } + }, context); + } +} + /* export type AspectDict = { loginMp: (params: { code: string }, context: GeneralRuntimeContext) => Promise; loginByPassword: (params: { password: string, mobile: string }, context: GeneralRuntimeContext) => Promise; diff --git a/src/checkers/index.ts b/src/checkers/index.ts index 9ab5a7ee0..beb214853 100644 --- a/src/checkers/index.ts +++ b/src/checkers/index.ts @@ -1,3 +1,4 @@ import addressCheckers from './address'; +import tokenCheckers from './token'; -export default [...addressCheckers]; \ No newline at end of file +export default [...addressCheckers, ...tokenCheckers]; \ No newline at end of file diff --git a/src/checkers/token.ts b/src/checkers/token.ts new file mode 100644 index 000000000..fef53b0b2 --- /dev/null +++ b/src/checkers/token.ts @@ -0,0 +1,39 @@ +import { Checker, OakUserUnpermittedException } from "oak-domain/lib/types"; +import { EntityDict } from 'oak-app-domain'; +import { GeneralRuntimeContext } from '../RuntimeContext'; +import { OakUnloggedInException } from "../types/Exceptions"; +import { assign } from "lodash"; +import { combineFilters } from "oak-domain/lib/store/filter"; + +const checkers: Checker> [] = [ + { + type: 'user', + action: 'select', + entity: 'token', + checker: async ({ operation }, context) => { + const scene = context.getScene(); + const { filter } = operation; + if (scene === 'token:me') { + if (!filter || !filter.id) { + const token = await context.getToken(); + if (!token) { + throw new OakUnloggedInException(); + } + const { id } = token; + assign(operation, { + filter: combineFilters([filter, { id }]), + }); + return 1; + } + return 0; + } + if (['app:onLaunch', 'token:me', 'token:login'].includes(scene)) { + return 0; + } + // 对获取token的权限进行精细化控制,除了root + throw new OakUserUnpermittedException(); + }, + } +]; + +export default checkers; \ No newline at end of file diff --git a/src/data/userRole.ts b/src/data/userRole.ts index 81917b404..7363ea4f3 100644 --- a/src/data/userRole.ts +++ b/src/data/userRole.ts @@ -5,6 +5,7 @@ import { ROOT_ROLE_ID, ROOT_USER_ID } from '../constants'; export const users: Array = [ { password: 'oak@2022', + nickname: 'root', name: 'root', id: ROOT_USER_ID, } diff --git a/src/entities/ExtraFile.ts b/src/entities/ExtraFile.ts index bd7f76784..cc8772846 100644 --- a/src/entities/ExtraFile.ts +++ b/src/entities/ExtraFile.ts @@ -2,7 +2,7 @@ import { String, Int, Text, Image } from 'oak-domain/lib/types/DataType'; import { EntityShape } from 'oak-domain/lib/types/Entity'; export interface Schema extends EntityShape { - origin: 'qiniu'; + origin: 'qiniu' | 'unknown'; type: 'image' | 'pdf' | 'video' | 'audio' | 'file'; bucket: String<16>; objectId: String<64>; @@ -12,4 +12,5 @@ export interface Schema extends EntityShape { md5: Text; entity: String<32>; entityId: String<64>; + extra1?: Text; }; diff --git a/src/features/token.ts b/src/features/token.ts index 1a3fc795c..00d6ee799 100644 --- a/src/features/token.ts +++ b/src/features/token.ts @@ -1,3 +1,4 @@ +import { pick } from 'lodash'; import { EntityDict } from 'oak-app-domain'; import { Action, Feature } from 'oak-frontend-base'; import { Aspect, Context } from 'oak-domain/lib/types'; @@ -14,20 +15,68 @@ export class Token, AD extends Re } @Action - async loginByPassword(mobile: string, password: string) { + async loginByPassword(mobile: string, password: string, scene: string) { await this.rwLock.acquire('X'); - this.token = await this.getAspectProxy().loginByPassword({ password, mobile }); - this.rwLock.release(); + try { + this.token = await this.getAspectProxy().loginByPassword({ password, mobile }, scene); + this.rwLock.release(); + } + catch (err) { + this.rwLock.release(); + throw err; + } } @Action - async loginWechatMp(code: string, env: WechatMpEnv) { + async loginWechatMp(scene: string) { await this.rwLock.acquire('X'); - this.token = await this.getAspectProxy().loginWechatMp({ - code, - env, + try { + + const { code } = await wx.login(); + const env = await wx.getSystemInfo(); + const env2 = pick(env, [ + 'brand', + 'model', + 'pixelRatio', + 'screenWidth', + 'screenHeight', + 'windowWidth', + 'windowHeight', + 'statusBarHeight', + 'language', + 'version', + 'system', + 'platform', + 'fontSizeSetting', + 'SDKVersion' + ]); + this.token = await this.getAspectProxy().loginWechatMp({ + code, + env: Object.assign(env2, { type: 'wechatMp' }) as WechatMpEnv, + }, scene); + this.rwLock.release(); + } + catch(err) { + this.rwLock.release(); + throw err; + } + } + + @Action + async syncUserInfoWechatMp(scene: string) { + const info = await wx.getUserProfile({ + desc: '同步微信昵称和头像信息', }); - this.rwLock.release(); + + const { userInfo: { nickName: nickname, avatarUrl }, encryptedData, signature, iv } = info; + + await this.getAspectProxy().syncUserInfoWechatMp({ + nickname, + avatarUrl, + encryptedData, + signature, + iv, + }, scene); } @Action @@ -37,8 +86,14 @@ export class Token, AD extends Re async getToken() { await this.rwLock.acquire('S'); - const token = this.token; - this.rwLock.release(); - return token; + try { + const token = this.token; + this.rwLock.release(); + return token; + } + catch (err) { + this.rwLock.release(); + throw err; + } } } \ No newline at end of file diff --git a/src/triggers/user.ts b/src/triggers/user.ts index 6e9b1edc0..ef5465ef8 100644 --- a/src/triggers/user.ts +++ b/src/triggers/user.ts @@ -5,6 +5,9 @@ import { CreateOperationData as UserRole } from 'oak-app-domain/UserRole/Schema' import assert from 'assert'; import { ROOT_ROLE_ID, ROOT_USER_ID } from '../constants'; import { DeduceCreateOperationData } from 'oak-domain/lib/types'; +import { OakUnloggedInException } from '../types/Exceptions'; +import { assign } from 'lodash'; +import { combineFilters } from 'oak-domain/lib/store/filter'; let NO_ANY_USER = true; const triggers: Trigger>[] = [ @@ -53,7 +56,7 @@ const triggers: Trigger>[] } return 0; } - } + }, ]; export default triggers; diff --git a/src/utils/extraFile.ts b/src/utils/extraFile.ts new file mode 100644 index 000000000..be9a549e0 --- /dev/null +++ b/src/utils/extraFile.ts @@ -0,0 +1,20 @@ +import { OpSchema as ExtraFile } from '../base-app-domain/ExtraFile/Schema'; + +export function composeFileUrl(extraFile: ExtraFile) { + const { type, bucket, filename, origin, extra1 } = extraFile; + if (origin === 'unknown') { + // 未知第三方源 + return extra1!; + } + throw new Error('not implemented yet'); +} + +export function decomposeFileUrl(url: string): Pick { + return { + origin: 'unknown', + extra1: url, + type: 'file', + filename: '', + bucket: '', + }; +} \ No newline at end of file diff --git a/wechatMp/pages/address/list/index.ts b/wechatMp/pages/address/list/index.ts index e32b818fd..6fe6be343 100644 --- a/wechatMp/pages/address/list/index.ts +++ b/wechatMp/pages/address/list/index.ts @@ -1,7 +1,7 @@ // index.ts OakPage({ - path: 'address-list', + path: 'address:list', entity: 'address', projection: { id: 1, diff --git a/wechatMp/pages/address/upsert/index.ts b/wechatMp/pages/address/upsert/index.ts index c09c760e0..c1aa21b25 100644 --- a/wechatMp/pages/address/upsert/index.ts +++ b/wechatMp/pages/address/upsert/index.ts @@ -1,6 +1,6 @@ OakPage({ - path: 'address-upsert', + path: 'address:upsert', entity: 'address', projection: { id: 1, @@ -34,7 +34,7 @@ OakPage({ }, { methods: { afterUpsert() { - if (this.data.oakFrom === 'address-list') { + if (this.data.oakFrom === 'address:list') { wx.navigateBack(); } } diff --git a/wechatMp/pages/mobile/me/index.json b/wechatMp/pages/mobile/me/index.json new file mode 100644 index 000000000..1a38ae1b3 --- /dev/null +++ b/wechatMp/pages/mobile/me/index.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "手机号", + "usingComponents": { + } +} \ No newline at end of file diff --git a/wechatMp/pages/mobile/me/index.less b/wechatMp/pages/mobile/me/index.less new file mode 100644 index 000000000..406acf8e0 --- /dev/null +++ b/wechatMp/pages/mobile/me/index.less @@ -0,0 +1,34 @@ +/** index.wxss **/ +@import "../../../styles/base.less"; +@import "../../../styles/cpn.less"; + +.page-body { + height: 100vh; + display: flex; + flex: 1; + flex-direction: column; + background-color: @background-color-base; +} + +.userInfo { + padding: 20rpx; + display: flex; + flex-direction: column; + align-items: center; + background-color: @background-color-base; +} + +.avatar { + font-size: 188rpx; + color: @text-color; + height: 200rpx; + width: 200rpx; + border-radius: 20rpx; +} + +.nickname { + font-size: @size-font-large; + color: @text-color; + padding-top: @size-spacing-small; + padding-bottom: @size-spacing-base; +} diff --git a/wechatMp/pages/mobile/me/index.ts b/wechatMp/pages/mobile/me/index.ts new file mode 100644 index 000000000..b3378ce3d --- /dev/null +++ b/wechatMp/pages/mobile/me/index.ts @@ -0,0 +1,32 @@ +import { composeFileUrl } from '../../../../src/utils/extraFile'; + +OakPage({ + path: 'mobile:me', + entity: 'mobile', + isList: true, + projection: { + id: 1, + mobile: 1, + userId: 1, + }, + formData: async (mobiles) => ({ + mobiles, + }), +}, { + methods: { + async onRefreshMobile(e: any) { + this.setData({ + refreshing: true, + }); + try { + console.log(e.detail.code); + } + catch (err) { + console.error(err); + } + this.setData({ + refreshing: false, + }); + } + } +}); \ No newline at end of file diff --git a/wechatMp/pages/mobile/me/index.wxml b/wechatMp/pages/mobile/me/index.wxml new file mode 100644 index 000000000..4e9d1ed88 --- /dev/null +++ b/wechatMp/pages/mobile/me/index.wxml @@ -0,0 +1,24 @@ + + + + + + + + {{mobile.mobile}} + + + + + + + + 尚未授权手机号 + + + + + + \ No newline at end of file diff --git a/wechatMp/pages/pickers/area/index.ts b/wechatMp/pages/pickers/area/index.ts index 7b69499ef..07821d9c6 100644 --- a/wechatMp/pages/pickers/area/index.ts +++ b/wechatMp/pages/pickers/area/index.ts @@ -1,6 +1,6 @@ OakPage({ - path: 'area-picker', + path: 'area:picker', entity: 'area', projection: { id: 1, diff --git a/wechatMp/pages/token/login/index.ts b/wechatMp/pages/token/login/index.ts index 5a6e67f1a..eacaf1054 100644 --- a/wechatMp/pages/token/login/index.ts +++ b/wechatMp/pages/token/login/index.ts @@ -1,7 +1,7 @@ import { WechatMpEnv } from "oak-app-domain/Token/Schema"; OakPage({ - path: 'token-login', + path: 'token:login', entity: 'token', projection: { id: 1, @@ -28,7 +28,7 @@ OakPage({ async onLoginClicked(options: WechatMiniprogram.Touch) { const { code } = await wx.login(); const env = await wx.getSystemInfo(); - await this.features.token.loginWechatMp(code, Object.assign(env, { type: 'wechatMp' }) as WechatMpEnv); + await this.features.token.loginWechatMp('token:login'); }, onReturnClicked() { diff --git a/wechatMp/pages/token/login/index.wxml b/wechatMp/pages/token/login/index.wxml index 69b46e7e0..e5b225c77 100644 --- a/wechatMp/pages/token/login/index.wxml +++ b/wechatMp/pages/token/login/index.wxml @@ -1,6 +1,6 @@ - + diff --git a/wechatMp/pages/token/me/index.json b/wechatMp/pages/token/me/index.json new file mode 100644 index 000000000..141273d68 --- /dev/null +++ b/wechatMp/pages/token/me/index.json @@ -0,0 +1,5 @@ +{ + "navigationBarTitleText": "个人信息", + "usingComponents": { + } +} \ No newline at end of file diff --git a/wechatMp/pages/token/me/index.less b/wechatMp/pages/token/me/index.less new file mode 100644 index 000000000..406acf8e0 --- /dev/null +++ b/wechatMp/pages/token/me/index.less @@ -0,0 +1,34 @@ +/** index.wxss **/ +@import "../../../styles/base.less"; +@import "../../../styles/cpn.less"; + +.page-body { + height: 100vh; + display: flex; + flex: 1; + flex-direction: column; + background-color: @background-color-base; +} + +.userInfo { + padding: 20rpx; + display: flex; + flex-direction: column; + align-items: center; + background-color: @background-color-base; +} + +.avatar { + font-size: 188rpx; + color: @text-color; + height: 200rpx; + width: 200rpx; + border-radius: 20rpx; +} + +.nickname { + font-size: @size-font-large; + color: @text-color; + padding-top: @size-spacing-small; + padding-bottom: @size-spacing-base; +} diff --git a/wechatMp/pages/token/me/index.ts b/wechatMp/pages/token/me/index.ts new file mode 100644 index 000000000..bb8fbf4e8 --- /dev/null +++ b/wechatMp/pages/token/me/index.ts @@ -0,0 +1,117 @@ +import { ROOT_ROLE_ID } from '../../../../src/constants'; +import { composeFileUrl } from '../../../../src/utils/extraFile'; + +OakPage({ + path: 'token:me', + entity: 'token', + isList: true, + projection: { + id: 1, + userId: 1, + playerId: 1, + user: { + id: 1, + nickname: 1, + name: 1, + extraFile$entity: { + $entity: 'extraFile', + data: { + id: 1, + tag1: 1, + origin: 1, + bucket: 1, + objectId: 1, + filename: 1, + extra1: 1, + }, + filter: { + tag1: 'avatar', + }, + indexFrom: 0, + count: 1, + }, + mobile$user: { + $entity: 'mobile', + data: { + id: 1, + mobile: 1, + }, + }, + }, + player: { + id: 1, + userRole$user: { + $entity: 'userRole', + data: { + id: 1, + userId: 1, + roleId: 1, + } + }, + } + }, + formData: async ([token]) => { + const user = token?.user; + const player = token?.player; + const avatarFile = user && user.extraFile$entity && user.extraFile$entity[0]; + const avatar = avatarFile && composeFileUrl(avatarFile); + const nickname = user && user.nickname; + const mobileData = user && user.mobile$user && user.mobile$user[0]; + const { mobile } = mobileData || {}; + const mobileCount = user?.mobile$user?.length || 0; + + const isLoggedIn = !!token; + const isPlayingAnother = token && token.userId !== token.playerId; + const isRoot = player?.userRole$user && player.userRole$user[0].roleId === ROOT_ROLE_ID; + return { + avatar, + nickname, + mobile, + mobileCount, + isLoggedIn, + isPlayingAnother, + isRoot, + }; + }, +}, { + methods: { + async onRefresh() { + this.setData({ + refreshing: true, + }); + try { + await this.features.token.syncUserInfoWechatMp('token:me'); + } + catch (err) { + console.error(err); + } + this.setData({ + refreshing: false, + }); + }, + async doLogin() { + this.setData({ + refreshing: true, + }); + try { + await this.features.token.loginWechatMp('token:me'); + } + catch (err) { + console.error(err); + } + this.setData({ + refreshing: false, + }); + }, + goMyMobile() { + this.navigateTo({ + url: '../../mobile/me/index', + }); + }, + goUserManage() { + this.navigateTo({ + url: '../../user/manage/index', + }); + } + } +}); \ No newline at end of file diff --git a/wechatMp/pages/token/me/index.wxml b/wechatMp/pages/token/me/index.wxml new file mode 100644 index 000000000..a45fdacdb --- /dev/null +++ b/wechatMp/pages/token/me/index.wxml @@ -0,0 +1,43 @@ + + + + + + + + person_outline + + {{ nickname || '未设置'}} + + + + + + + + + + 手机号 + + {{mobileCount}}条手机号 + {{mobile}} + 未设置 + chevron_right + + + + 收货地址 + + + + 用户管理 + + chevron_right + + + + \ No newline at end of file diff --git a/wechatMp/pages/user/manage/index.json b/wechatMp/pages/user/manage/index.json new file mode 100644 index 000000000..d191b300e --- /dev/null +++ b/wechatMp/pages/user/manage/index.json @@ -0,0 +1,6 @@ +{ + "navigationBarTitleText": "用户管理", + "usingComponents": { + + } +} \ No newline at end of file diff --git a/wechatMp/pages/user/manage/index.less b/wechatMp/pages/user/manage/index.less new file mode 100644 index 000000000..12cb25674 --- /dev/null +++ b/wechatMp/pages/user/manage/index.less @@ -0,0 +1,36 @@ +/** index.wxss **/ +@import "../../../styles/base.less"; +@import "../../../styles/cpn.less"; + +.page-body { + height: 100vh; + display: flex; + flex: 1; + flex-direction: column; + background-color: @background-color-base; +} + +.avatar { + font-size: 128rpx; + color: @text-color; + height: 140rpx; + width: 140rpx; + border-radius: 14rpx; +} + +.nickname { + font-size: @size-font-large; + font-weight: bold; +} + +.name { + display: flex; + align-items: center; + font-size: @size-font-base; +} + +.mobile { + display: flex; + align-items: center; + font-size: @size-font-small; +} diff --git a/wechatMp/pages/user/manage/index.ts b/wechatMp/pages/user/manage/index.ts new file mode 100644 index 000000000..c16381de0 --- /dev/null +++ b/wechatMp/pages/user/manage/index.ts @@ -0,0 +1,60 @@ +// index.ts + +import { composeFileUrl } from "../../../../src/utils/extraFile"; + +OakPage({ + path: 'user:manage', + entity: 'user', + projection: { + id: 1, + nickname: 1, + name: 1, + extraFile$entity: { + $entity: 'extraFile', + data: { + id: 1, + tag1: 1, + origin: 1, + bucket: 1, + objectId: 1, + filename: 1, + extra1: 1, + }, + filter: { + tag1: 'avatar', + }, + indexFrom: 0, + count: 1, + }, + mobile$user: { + $entity: 'mobile', + data: { + id: 1, + mobile: 1, + }, + }, + }, + isList: true, + formData: async (users) => { + const userData = users.map( + (user) => { + const { id, nickname, name, mobile$user, extraFile$entity } = user; + const mobile = mobile$user && mobile$user[0]?.mobile; + const avatar = extraFile$entity && extraFile$entity[0] && composeFileUrl(extraFile$entity[0]); + return { + id, + nickname, + name, + mobile, + avatar, + }; + } + ); + return { + userData, + }; + }, +}, { + methods: { + } +}); \ No newline at end of file diff --git a/wechatMp/pages/user/manage/index.wxml b/wechatMp/pages/user/manage/index.wxml new file mode 100644 index 000000000..b7cd0c521 --- /dev/null +++ b/wechatMp/pages/user/manage/index.wxml @@ -0,0 +1,32 @@ + + + + + + + + + + person_outline + + + {{item.nickname || '未设置'}} + + tag_faces + {{item.name || '未设置'}} + + + phone_android + {{item.mobile || '未设置'}} + + + chevron_right + + + + + + + \ No newline at end of file diff --git a/wechatMp/styles/cpn.less b/wechatMp/styles/cpn.less index af7102446..b5346d419 100644 --- a/wechatMp/styles/cpn.less +++ b/wechatMp/styles/cpn.less @@ -147,6 +147,7 @@ flex: 1; overflow: hidden; color: @text-color; + justify-content: flex-end; } } }