wechatQrCode的逻辑实现(部分)

This commit is contained in:
Xu Chang 2022-05-30 18:35:13 +08:00
parent b0ff174e44
commit 2f580ad8eb
34 changed files with 435 additions and 78 deletions

View File

@ -6,12 +6,18 @@ export declare abstract class GeneralRuntimeContext<ED extends EntityDict> exten
private getTokenFn; private getTokenFn;
private scene; private scene;
constructor(store: RowStore<ED, GeneralRuntimeContext<ED>>, appId: string, getToken: () => Promise<string | undefined>, scene: string); constructor(store: RowStore<ED, GeneralRuntimeContext<ED>>, appId: string, getToken: () => Promise<string | undefined>, scene: string);
getApplicationId(): string;
getApplication(): Promise<import("oak-domain/lib/types").SelectRowShape<import("oak-app-domain/Application/Schema").Schema, { getApplication(): Promise<import("oak-domain/lib/types").SelectRowShape<import("oak-app-domain/Application/Schema").Schema, {
id: 1; id: 1;
name: 1; name: 1;
config: 1; config: 1;
type: 1; type: 1;
systemId: 1; systemId: 1;
system: {
id: 1;
name: 1;
config: 1;
};
}>>; }>>;
getToken(): Promise<import("oak-domain/lib/types").SelectRowShape<ED["token"]["Schema"], { getToken(): Promise<import("oak-domain/lib/types").SelectRowShape<ED["token"]["Schema"], {
id: 1; id: 1;

View File

@ -12,6 +12,9 @@ class GeneralRuntimeContext extends UniversalContext_1.UniversalContext {
this.getTokenFn = getToken; this.getTokenFn = getToken;
this.scene = scene; this.scene = scene;
} }
getApplicationId() {
return this.applicationId;
}
async getApplication() { async getApplication() {
const { result: [application] } = await this.rowStore.select('application', { const { result: [application] } = await this.rowStore.select('application', {
data: { data: {
@ -20,6 +23,11 @@ class GeneralRuntimeContext extends UniversalContext_1.UniversalContext {
config: 1, config: 1,
type: 1, type: 1,
systemId: 1, systemId: 1,
system: {
id: 1,
name: 1,
config: 1,
}
}, },
filter: { filter: {
id: this.applicationId, id: this.applicationId,

View File

@ -24,7 +24,7 @@ async function getUploadInfo(params, context) {
}, context); }, context);
try { try {
const { config: systemConfig } = system; const { config: systemConfig } = system;
const originConfig = systemConfig?.Cos[origin]; const originConfig = systemConfig.Cos[origin];
const instance = new ExternalUploadClazz[origin](originConfig); const instance = new ExternalUploadClazz[origin](originConfig);
const uploadInfo = await instance.getUploadInfo(fileName); const uploadInfo = await instance.getUploadInfo(fileName);
return uploadInfo; return uploadInfo;

21
lib/aspects/wechatQrCode.d.ts vendored Normal file
View File

@ -0,0 +1,21 @@
import { EntityDict } from "oak-app-domain";
import { WechatQrCodeProps } from 'oak-app-domain/WechatQrCode/Schema';
import { GeneralRuntimeContext } from "../RuntimeContext";
export declare function createWechatQrCode<ED extends EntityDict, T extends keyof ED, Cxt extends GeneralRuntimeContext<ED>>(options: {
entity: T;
entityId: string;
applicationId: string;
tag?: string;
lifetimeLength?: number;
permanent?: boolean;
props: WechatQrCodeProps;
}, context: Cxt): Promise<Omit<Omit<import("oak-app-domain/WechatQrCode/Schema").OpSchema, "applicationId" | "entity" | "entityId">, import("oak-domain/lib/types").InstinctiveAttributes> & {
id: string;
} & {
applicationId: string;
application?: import("oak-app-domain/Application/Schema").UpdateOperation | undefined;
} & {
[K: string]: any;
} & {
[k: string]: any;
}>;

View File

@ -0,0 +1,61 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWechatQrCode = void 0;
const assert_1 = __importDefault(require("assert"));
async function createWechatQrCode(options, context) {
const { entity, entityId, applicationId, tag, lifetimeLength, permanent, props } = options;
const { type: appType, config } = await context.getApplication();
if (appType === 'wechatMp') {
const { qrCodePrefix } = config;
const id = await generateNewId();
if (qrCodePrefix) {
// 设置了域名跳转,优先使用域名 + id来生成对应的ur
const data = {
id,
type: 'wechatMpDomainUrl',
tag,
entity,
entityId,
applicationId,
permanent: true,
url: `${qrCodePrefix}/id`,
expired: false,
props,
};
await context.rowStore.operate('wechatQrCode', {
action: 'create',
data,
}, context);
return data;
}
else {
// 没有域名跳转,使用小程序码
// todo这里如果有同组的公众号应该优先使用公众号的关注链接
const data = {
id,
type: 'wechatMpWxaCode',
tag,
entity,
entityId,
applicationId,
permanent: false,
expired: false,
props,
};
await context.rowStore.operate('wechatQrCode', {
action: 'create',
data,
}, context);
return data;
}
}
else {
(0, assert_1.default)(appType === 'wechatPublic');
// 还未实现,记得
throw new Error('method not implemented yet');
}
}
exports.createWechatQrCode = createWechatQrCode;

5
lib/constants.d.ts vendored
View File

@ -1,2 +1,7 @@
export declare const ROOT_ROLE_ID = "oak-root-role"; export declare const ROOT_ROLE_ID = "oak-root-role";
export declare const ROOT_USER_ID = "oak-root-user"; export declare const ROOT_USER_ID = "oak-root-user";
export declare const DefaultConfig: {
userEntityGrant: {
lifetimeLength: number;
};
};

View File

@ -1,5 +1,10 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.ROOT_USER_ID = exports.ROOT_ROLE_ID = void 0; exports.DefaultConfig = exports.ROOT_USER_ID = exports.ROOT_ROLE_ID = void 0;
exports.ROOT_ROLE_ID = 'oak-root-role'; exports.ROOT_ROLE_ID = 'oak-root-role';
exports.ROOT_USER_ID = 'oak-root-user'; exports.ROOT_USER_ID = 'oak-root-user';
exports.DefaultConfig = {
userEntityGrant: {
lifetimeLength: 3600 * 1000,
},
};

View File

@ -6,6 +6,7 @@ export declare type WechatMpConfig = {
type: 'wechatMp'; type: 'wechatMp';
appId: string; appId: string;
appSecret: string; appSecret: string;
qrCodePrefix?: string;
}; };
export declare type WebConfig = { export declare type WebConfig = {
type: 'web'; type: 'web';

View File

@ -1,8 +1,8 @@
import { String, Text } from 'oak-domain/lib/types/DataType'; import { String, Text } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity'; import { EntityShape } from 'oak-domain/lib/types/Entity';
export declare type SystemConfig = { export declare type SystemConfig = {
Cos: { Cos?: {
qiniu: { qiniu?: {
accessKey: string; accessKey: string;
secretKey: string; secretKey: string;
uploadHost: string; uploadHost: string;
@ -10,11 +10,14 @@ export declare type SystemConfig = {
domain: string; domain: string;
}; };
}; };
Map: { Map?: {
amap: { amap?: {
webApiKey: string; webApiKey: string;
}; };
}; };
UserEntityGrant?: {
lifetimeLength: number;
};
}; };
export interface Schema extends EntityShape { export interface Schema extends EntityShape {
name: String<32>; name: String<32>;

View File

@ -1,4 +1,4 @@
import { String, Text } from 'oak-domain/lib/types/DataType'; import { String, Text, Datetime } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity'; import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Schema as User } from './User'; import { Schema as User } from './User';
import { Schema as WechatQrCode } from './WechatQrCode'; import { Schema as WechatQrCode } from './WechatQrCode';
@ -8,8 +8,9 @@ export interface Schema extends EntityShape {
relation: String<32>; relation: String<32>;
action: String<32>; action: String<32>;
remark?: Text; remark?: Text;
uuid: String<32>;
granter: User; granter: User;
grantee?: User; grantee?: User;
files: Array<WechatQrCode>; files: Array<WechatQrCode>;
expiresAt?: Datetime;
expired?: Boolean;
} }

View File

@ -1,11 +1,5 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const IActionDef = {
stm: {
confirm: ['init', 'expire'],
},
is: 'init'
};
const indexes = [ const indexes = [
{ {
name: 'index_entity_entityId', name: 'index_entity_entityId',
@ -22,8 +16,11 @@ const indexes = [
name: 'index_uuid', name: 'index_uuid',
attributes: [ attributes: [
{ {
name: 'uuid', name: 'expired',
}, },
{
name: 'expiresAt',
}
], ],
}, },
]; ];

View File

@ -1,18 +1,22 @@
import { String, Text, Datetime, Boolean } from 'oak-domain/lib/types/DataType'; import { String, Text, Datetime, Boolean } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity'; import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Schema as Application } from './Application'; import { Schema as Application } from './Application';
export declare type WechatQrCodeProps = {
pathname: string;
props?: Record<string, any>;
state?: Record<string, any>;
};
export interface Schema extends EntityShape { export interface Schema extends EntityShape {
entity: String<32>; entity: String<32>;
entityId: String<64>; entityId: String<64>;
type?: String<32>; type: 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp';
expiresAt: Datetime; tag?: String<32>;
expired: Boolean; expiresAt?: Datetime;
autoExtend: Boolean; expired?: Boolean;
sceneStr?: Text;
ticket?: Text; ticket?: Text;
url?: String<64>; url?: String<64>;
isPermanent: Boolean; permanent: Boolean;
buffer?: Text; buffer?: Text;
application: Application; application: Application;
props?: Object; props: WechatQrCodeProps;
} }

View File

@ -2,7 +2,7 @@
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const indexes = [ const indexes = [
{ {
name: 'index_entity_entityId', name: 'index_entity_entityId_tag',
attributes: [ attributes: [
{ {
name: 'entity', name: 'entity',
@ -10,6 +10,9 @@ const indexes = [
{ {
name: 'entityId', name: 'entityId',
}, },
{
name: 'tag',
}
], ],
}, },
{ {

View File

@ -3,7 +3,7 @@ import { Feature } from 'oak-frontend-base';
import { Aspect, Context, DeduceCreateOperationData } from 'oak-domain/lib/types'; import { Aspect, Context, DeduceCreateOperationData } from 'oak-domain/lib/types';
export declare class ExtraFile<ED extends EntityDict, Cxt extends Context<ED>, AD extends Record<string, Aspect<ED, Cxt>>> extends Feature<ED, Cxt, AD> { export declare class ExtraFile<ED extends EntityDict, Cxt extends Context<ED>, AD extends Record<string, Aspect<ED, Cxt>>> extends Feature<ED, Cxt, AD> {
constructor(); constructor();
upload(extraFile: DeduceCreateOperationData<ED['extraFile']['Schema']>, scene: string): Promise<{ upload(extraFile: DeduceCreateOperationData<EntityDict['extraFile']['OpSchema']>, scene: string): Promise<{
url: string; url: string;
bucket: string; bucket: string;
}>; }>;

View File

@ -1,4 +1,4 @@
import { EntityDict as BaseEntityDict } from 'oak-app-domain/EntityDict'; import { EntityDict as BaseEntityDict } from 'oak-app-domain/EntityDict';
import { Trigger } from 'oak-domain/lib/types'; import { Trigger } from 'oak-domain/lib/types';
declare const _default: (Trigger<BaseEntityDict, "address", import("..").GeneralRuntimeContext<BaseEntityDict>> | Trigger<BaseEntityDict, "user", import("..").GeneralRuntimeContext<BaseEntityDict>> | Trigger<BaseEntityDict, "userEntityGrant", import("..").GeneralRuntimeContext<BaseEntityDict>>)[]; declare const _default: (Trigger<BaseEntityDict, "address", import("..").GeneralRuntimeContext<BaseEntityDict>> | Trigger<BaseEntityDict, "user", import("..").GeneralRuntimeContext<BaseEntityDict>> | Trigger<BaseEntityDict, "userEntityGrant", import("..").GeneralRuntimeContext<BaseEntityDict>> | Trigger<BaseEntityDict, "wechatQrCode", import("..").GeneralRuntimeContext<BaseEntityDict>>)[];
export default _default; export default _default;

View File

@ -6,4 +6,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
const address_1 = __importDefault(require("./address")); const address_1 = __importDefault(require("./address"));
const user_1 = __importDefault(require("./user")); const user_1 = __importDefault(require("./user"));
const userEntityGrant_1 = __importDefault(require("./userEntityGrant")); const userEntityGrant_1 = __importDefault(require("./userEntityGrant"));
exports.default = [...address_1.default, ...user_1.default, ...userEntityGrant_1.default]; const wechatQrCode_1 = __importDefault(require("./wechatQrCode"));
exports.default = [...address_1.default, ...user_1.default, ...userEntityGrant_1.default, ...wechatQrCode_1.default];

View File

@ -6,9 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash"); const lodash_1 = require("lodash");
const types_1 = require("oak-domain/lib/types"); const types_1 = require("oak-domain/lib/types");
const assert_1 = __importDefault(require("assert")); const assert_1 = __importDefault(require("assert"));
const constants_1 = require("../constants");
const wechatQrCode_1 = require("../aspects/wechatQrCode");
const triggers = [ const triggers = [
{ {
name: '当创建userEntityGrant时,查询是否有未过期的实体', name: '当创建userEntityGrant时,查询是否有未过期可重用的对象',
entity: 'userEntityGrant', entity: 'userEntityGrant',
action: 'create', action: 'create',
when: 'before', when: 'before',
@ -16,8 +18,9 @@ const triggers = [
const { data, filter } = operation; const { data, filter } = operation;
const fn = async (userEntityGrantData) => { const fn = async (userEntityGrantData) => {
const { userId } = (await context.getToken()); const { userId } = (await context.getToken());
const { id: applicationId, config: appConfig, system: { config: SystemConfig } } = (await context.getApplication());
(0, assert_1.default)(userId); (0, assert_1.default)(userId);
const { action, entity, entityId, relation } = userEntityGrantData; const { action, entity, entityId, relation, id } = userEntityGrantData;
const { result } = await context.rowStore.select('userEntityGrant', { const { result } = await context.rowStore.select('userEntityGrant', {
data: { data: {
id: 1, id: 1,
@ -25,11 +28,14 @@ const triggers = [
entity: 1, entity: 1,
entityId: 1, entityId: 1,
relation: 1, relation: 1,
iState: 1, expired: 1,
granterId: 1, granterId: 1,
}, },
filter: { filter: {
iState: 'init', expired: false,
expiresAt: {
$gt: Date.now() - 600 * 1000,
},
action, action,
entity, entity,
entityId, entityId,
@ -42,9 +48,26 @@ const triggers = [
if (result.length) { if (result.length) {
throw new types_1.OakCongruentRowExists(result[0], '有可重用的userEntityGrant'); throw new types_1.OakCongruentRowExists(result[0], '有可重用的userEntityGrant');
} }
const expiresAt = Date.now() + (SystemConfig.UserEntityGrant?.lifetimeLength || constants_1.DefaultConfig.userEntityGrant.lifetimeLength);
(0, lodash_1.assign)(userEntityGrantData, { (0, lodash_1.assign)(userEntityGrantData, {
granterId: userId, granterId: userId,
expiresAt,
expired: false,
}); });
// 如果是微信体系的应用为之创建一个默认的weChatQrCode
if (['wechatPublic', 'wechatMp'].includes(appConfig.type)) {
await (0, wechatQrCode_1.createWechatQrCode)({
entity: 'userEntityGrant',
entityId: id,
applicationId,
props: {
pathname: 'pages/userEntityGrant/confirm',
props: {
oakId: id,
},
}
}, context);
}
}; };
if (data instanceof Array) { if (data instanceof Array) {
(0, assert_1.default)('授权不存在一对多的情况'); (0, assert_1.default)('授权不存在一对多的情况');

5
lib/triggers/wechatQrCode.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { EntityDict } from 'oak-app-domain/EntityDict';
import { Trigger } from 'oak-domain/lib/types/Trigger';
import { GeneralRuntimeContext } from '../RuntimeContext';
declare const triggers: Trigger<EntityDict, 'wechatQrCode', GeneralRuntimeContext<EntityDict>>[];
export default triggers;

View File

@ -0,0 +1,47 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const assert_1 = __importDefault(require("assert"));
const oak_wechat_sdk_1 = require("oak-wechat-sdk");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const lodash_1 = require("lodash");
const triggers = [
{
name: '选择userEntityGrant时动态生成需要的数据',
entity: 'wechatQrCode',
action: 'select',
when: 'after',
fn: async ({ result }, context, params) => {
let count = 0;
const application = await context.getApplication();
const { type, config } = application;
(0, assert_1.default)(type === 'wechatMp' || config.type === 'wechatMp');
const config2 = config;
const { appId, appSecret } = config2;
for (const code of result) {
const { type, expired, url, id } = code;
if (type === 'wechatMpWxaCode') {
// 小程序码去实时获取(暂时不考虑缓存)
const wechatInstance = oak_wechat_sdk_1.WechatSDK.getInstance(appId, appSecret, 'wechatMp');
const buffer = await wechatInstance.getMpUnlimitWxaCode({
scene: (0, uuid_1.shrinkUuidTo32Bytes)(id),
page: 'pages/index/index', // todo这里用其它的页面微信服务器拒绝因为没发布。应该是 pages/wechatQrCode/scan/index
});
// 把arrayBuffer转成字符串返回
const str = String.fromCharCode(...new Uint8Array(buffer));
(0, lodash_1.assign)(code, {
buffer: str,
});
}
else if (expired) {
// 如果过期了,在这里生成新的临时码并修改值(公众号)
throw new Error('not implemented yet');
}
}
return count;
}
},
];
exports.default = triggers;

View File

@ -17,6 +17,10 @@ export abstract class GeneralRuntimeContext<ED extends EntityDict> extends Unive
this.scene = scene; this.scene = scene;
} }
getApplicationId() {
return this.applicationId;
}
async getApplication () { async getApplication () {
const { result: [application] } = await this.rowStore.select('application', { const { result: [application] } = await this.rowStore.select('application', {
data: { data: {
@ -25,11 +29,27 @@ export abstract class GeneralRuntimeContext<ED extends EntityDict> extends Unive
config: 1, config: 1,
type: 1, type: 1,
systemId: 1, systemId: 1,
system: {
id: 1,
name: 1,
config: 1,
}
}, },
filter: { filter: {
id: this.applicationId, id: this.applicationId,
} }
}, this) as SelectionResult<EntityDict['application']['Schema'], {id: 1, name: 1, config: 1, type: 1, systemId: 1}>; }, this) as SelectionResult<EntityDict['application']['Schema'], {
id: 1,
name: 1,
config: 1,
type: 1,
systemId: 1,
system: {
id: 1,
name: 1,
config: 1,
},
}>;
return application; return application;
} }

View File

@ -25,7 +25,7 @@ export async function getUploadInfo<ED extends EntityDict, Cxt extends GeneralRu
}, context); }, context);
try { try {
const { config: systemConfig } = system; const { config: systemConfig } = system;
const originConfig = (systemConfig as SystemConfig)?.Cos[ const originConfig = (systemConfig as SystemConfig).Cos![
origin as keyof typeof systemConfig origin as keyof typeof systemConfig
]; ];

View File

@ -0,0 +1,70 @@
import assert from "assert";
import { EntityDict } from "oak-app-domain";
import { WechatMpConfig } from "oak-app-domain/Application/Schema";
import { CreateOperationData as CreateWechatQrcodeData, WechatQrCodeProps } from 'oak-app-domain/WechatQrCode/Schema';
import { GeneralRuntimeContext } from "../RuntimeContext";
export async function createWechatQrCode<ED extends EntityDict, T extends keyof ED, Cxt extends GeneralRuntimeContext<ED>>(options: {
entity: T;
entityId: string;
applicationId: string;
tag?: string;
lifetimeLength?: number;
permanent?: boolean;
props: WechatQrCodeProps;
}, context: Cxt) {
const { entity, entityId, applicationId, tag, lifetimeLength, permanent, props } = options;
const { type: appType, config } = await context.getApplication();
if (appType === 'wechatMp') {
const { qrCodePrefix } = (<WechatMpConfig>config);
const id = await generateNewId();
if (qrCodePrefix) {
// 设置了域名跳转,优先使用域名 + id来生成对应的ur
const data: CreateWechatQrcodeData = {
id,
type: 'wechatMpDomainUrl',
tag,
entity,
entityId,
applicationId,
permanent: true,
url: `${qrCodePrefix}/id`,
expired: false,
props,
};
await context.rowStore.operate('wechatQrCode', {
action: 'create',
data,
}, context);
return data;
}
else {
// 没有域名跳转,使用小程序码
// todo这里如果有同组的公众号应该优先使用公众号的关注链接
const data: CreateWechatQrcodeData = {
id,
type: 'wechatMpWxaCode',
tag,
entity,
entityId,
applicationId,
permanent: false,
expired: false,
props,
};
await context.rowStore.operate('wechatQrCode', {
action: 'create',
data,
}, context);
return data;
}
}
else {
assert(appType === 'wechatPublic');
// 还未实现,记得
throw new Error('method not implemented yet');
}
}

View File

@ -1,2 +1,8 @@
export const ROOT_ROLE_ID = 'oak-root-role'; export const ROOT_ROLE_ID = 'oak-root-role';
export const ROOT_USER_ID = 'oak-root-user'; export const ROOT_USER_ID = 'oak-root-user';
export const DefaultConfig = {
userEntityGrant: {
lifetimeLength: 3600 * 1000,
},
};

View File

@ -7,6 +7,7 @@ export type WechatMpConfig = {
type: 'wechatMp'; type: 'wechatMp';
appId: string; appId: string;
appSecret: string; appSecret: string;
qrCodePrefix?: string; // 扫描二维码跳转的前缀(在小程序后台配置必须统一跳转到weCharQrCode/scan/index)
}; };
export type WebConfig = { export type WebConfig = {

View File

@ -2,8 +2,8 @@ import { String, Int, Datetime, Image, Boolean, Text } from 'oak-domain/lib/type
import { EntityShape } from 'oak-domain/lib/types/Entity'; import { EntityShape } from 'oak-domain/lib/types/Entity';
export type SystemConfig = { export type SystemConfig = {
Cos: { Cos?: {
qiniu: { qiniu?: {
accessKey: string; accessKey: string;
secretKey: string; secretKey: string;
uploadHost: string; //七牛上传域名 uploadHost: string; //七牛上传域名
@ -11,11 +11,14 @@ export type SystemConfig = {
domain: string; domain: string;
}; };
}; };
Map: { Map?: {
amap: { amap?: {
webApiKey: string; // 高德访问rest服务接口的key webApiKey: string; // 高德访问rest服务接口的key
}; };
}; };
UserEntityGrant?: {
lifetimeLength: number; // 授权的过期时间ms
};
}; };
export interface Schema extends EntityShape { export interface Schema extends EntityShape {

View File

@ -1,9 +1,8 @@
import { String, Text } from 'oak-domain/lib/types/DataType'; import { String, Text, Datetime } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity'; import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Index } from 'oak-domain/lib/types/Storage'; import { Index } from 'oak-domain/lib/types/Storage';
import { Schema as User } from './User'; import { Schema as User } from './User';
import { Schema as WechatQrCode } from './WechatQrCode'; import { Schema as WechatQrCode } from './WechatQrCode';
import { ActionDef } from 'oak-domain/lib/types/Action';
export interface Schema extends EntityShape { export interface Schema extends EntityShape {
entity: String<32>; entity: String<32>;
@ -11,24 +10,13 @@ export interface Schema extends EntityShape {
relation: String<32>; relation: String<32>;
action: String<32>; action: String<32>;
remark?: Text; remark?: Text;
uuid: String<32>;
granter: User; granter: User;
grantee?: User; grantee?: User;
files: Array<WechatQrCode>; files: Array<WechatQrCode>;
expiresAt?: Datetime;
expired?: Boolean;
} }
type IAction = 'confirm';
type IState = | 'init'| 'expire';
const IActionDef: ActionDef<IAction, IState> = {
stm: {
confirm: ['init', 'expire'],
},
is: 'init'
};
type Action = IAction;
const indexes: Index<Schema>[] = [ const indexes: Index<Schema>[] = [
{ {
name: 'index_entity_entityId', name: 'index_entity_entityId',
@ -45,8 +33,11 @@ const indexes: Index<Schema>[] = [
name: 'index_uuid', name: 'index_uuid',
attributes: [ attributes: [
{ {
name: 'uuid', name: 'expired',
}, },
{
name: 'expiresAt',
}
], ],
}, },
]; ];

View File

@ -3,25 +3,30 @@ import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Index } from 'oak-domain/lib/types/Storage'; import { Index } from 'oak-domain/lib/types/Storage';
import { Schema as Application } from './Application'; import { Schema as Application } from './Application';
export type WechatQrCodeProps = {
pathname: string;
props?: Record<string, any>;
state?: Record<string, any>;
};
export interface Schema extends EntityShape { export interface Schema extends EntityShape {
entity: String<32>; entity: String<32>;
entityId: String<64>; entityId: String<64>;
type?: String<32>; //类型 type: 'wechatMpDomainUrl' | 'wechatMpWxaCode' | 'wechatPublic' | 'wechatPublicForMp',
expiresAt: Datetime; // 过期时间 tag?: String<32>; // 调用者加的tag
expired: Boolean; //是否过期 expiresAt?: Datetime; // 过期时间
autoExtend: Boolean; expired?: Boolean; //是否过期
sceneStr?: Text;
ticket?: Text; ticket?: Text;
url?: String<64>; url?: String<64>;
isPermanent: Boolean; //是否永久码 permanent: Boolean; //是否永久码
buffer?: Text; // 若没有url使用buffer存储生成的小程序码数据base64) buffer?: Text; // 若没有url使用buffer存储生成的小程序码数据base64)
application: Application; application: Application;
props?: Object; props: WechatQrCodeProps;
} }
const indexes: Index<Schema>[] = [ const indexes: Index<Schema>[] = [
{ {
name: 'index_entity_entityId', name: 'index_entity_entityId_tag',
attributes: [ attributes: [
{ {
name: 'entity', name: 'entity',
@ -29,6 +34,9 @@ const indexes: Index<Schema>[] = [
{ {
name: 'entityId', name: 'entityId',
}, },
{
name: 'tag',
}
], ],
}, },
{ {

View File

@ -13,7 +13,7 @@ export class ExtraFile<
super(); super();
} }
@Action @Action
async upload(extraFile: DeduceCreateOperationData<ED['extraFile']['Schema']>, scene: string) { async upload(extraFile: DeduceCreateOperationData<EntityDict['extraFile']['OpSchema']>, scene: string) {
try { try {
const { origin, extra1: filePath, filename: fileName } = extraFile; const { origin, extra1: filePath, filename: fileName } = extraFile;
const uploadInfo = const uploadInfo =

View File

@ -4,5 +4,6 @@ import { Trigger } from 'oak-domain/lib/types';
import addressTriggers from './address'; import addressTriggers from './address';
import userTriggers from './user'; import userTriggers from './user';
import userEntityGrantTriggers from './userEntityGrant'; import userEntityGrantTriggers from './userEntityGrant';
import wechatQrCodeTriggers from './wechatQrCode';
export default [...addressTriggers, ...userTriggers, ...userEntityGrantTriggers]; export default [...addressTriggers, ...userTriggers, ...userEntityGrantTriggers, ...wechatQrCodeTriggers];

View File

@ -6,10 +6,12 @@ import { CreateOperationData as CreateUserEntityGrantData } from 'oak-app-domain
import { assign, keys } from 'lodash'; import { assign, keys } from 'lodash';
import { OakCongruentRowExists } from 'oak-domain/lib/types'; import { OakCongruentRowExists } from 'oak-domain/lib/types';
import assert from 'assert'; import assert from 'assert';
import { DefaultConfig } from '../constants';
import { createWechatQrCode } from '../aspects/wechatQrCode';
const triggers: Trigger<EntityDict, 'userEntityGrant', GeneralRuntimeContext<EntityDict>>[] = [ const triggers: Trigger<EntityDict, 'userEntityGrant', GeneralRuntimeContext<EntityDict>>[] = [
{ {
name: '当创建userEntityGrant时,查询是否有未过期的实体', name: '当创建userEntityGrant时,查询是否有未过期可重用的对象',
entity: 'userEntityGrant', entity: 'userEntityGrant',
action: 'create', action: 'create',
when: 'before', when: 'before',
@ -17,8 +19,9 @@ const triggers: Trigger<EntityDict, 'userEntityGrant', GeneralRuntimeContext<Ent
const { data, filter } = operation; const { data, filter } = operation;
const fn = async (userEntityGrantData: CreateUserEntityGrantData) => { const fn = async (userEntityGrantData: CreateUserEntityGrantData) => {
const { userId } = (await context.getToken())!; const { userId } = (await context.getToken())!;
const { id: applicationId, config: appConfig, system: { config: SystemConfig }} = (await context.getApplication());
assert(userId); assert(userId);
const { action, entity, entityId, relation} = userEntityGrantData; const { action, entity, entityId, relation, id } = userEntityGrantData;
const { result } = await context.rowStore.select('userEntityGrant', { const { result } = await context.rowStore.select('userEntityGrant', {
data: { data: {
id: 1, id: 1,
@ -26,11 +29,14 @@ const triggers: Trigger<EntityDict, 'userEntityGrant', GeneralRuntimeContext<Ent
entity: 1, entity: 1,
entityId: 1, entityId: 1,
relation: 1, relation: 1,
iState: 1, expired: 1,
granterId: 1, granterId: 1,
}, },
filter: { filter: {
iState: 'init', expired: false,
expiresAt: {
$gt: Date.now() - 600 * 1000,
}, // 至少有10分钟有效期的
action, action,
entity, entity,
entityId, entityId,
@ -44,9 +50,27 @@ const triggers: Trigger<EntityDict, 'userEntityGrant', GeneralRuntimeContext<Ent
throw new OakCongruentRowExists(result[0] as any, '有可重用的userEntityGrant'); throw new OakCongruentRowExists(result[0] as any, '有可重用的userEntityGrant');
} }
const expiresAt = Date.now() + (SystemConfig.UserEntityGrant?.lifetimeLength || DefaultConfig.userEntityGrant.lifetimeLength);
assign(userEntityGrantData, { assign(userEntityGrantData, {
granterId: userId, granterId: userId,
expiresAt,
expired: false,
}); });
// 如果是微信体系的应用为之创建一个默认的weChatQrCode
if (['wechatPublic', 'wechatMp'].includes(appConfig.type)) {
await createWechatQrCode({
entity: 'userEntityGrant',
entityId: id,
applicationId,
props: {
pathname: 'pages/userEntityGrant/confirm',
props: {
oakId: id,
},
}
}, context);
}
} }
if (data instanceof Array) { if (data instanceof Array) {
assert('授权不存在一对多的情况') assert('授权不存在一对多的情况')

View File

@ -0,0 +1,49 @@
import { EntityDict } from 'oak-app-domain/EntityDict';
import { SelectTriggerAfter, Trigger } from 'oak-domain/lib/types/Trigger';
import { GeneralRuntimeContext } from '../RuntimeContext';
import assert from 'assert';
import { WechatSDK } from 'oak-wechat-sdk';
import { WechatMpConfig } from 'oak-app-domain/Application/Schema';
import { shrinkUuidTo32Bytes } from 'oak-domain/lib/utils/uuid';
import { assign } from 'lodash';
const triggers: Trigger<EntityDict, 'wechatQrCode', GeneralRuntimeContext<EntityDict>>[] = [
{
name: '选择userEntityGrant时动态生成需要的数据',
entity: 'wechatQrCode',
action: 'select',
when: 'after',
fn: async ({ result }, context, params) => {
let count = 0;
const application = await context.getApplication();
const { type, config } = application;
assert(type === 'wechatMp' || config.type === 'wechatMp');
const config2 = config as WechatMpConfig;
const { appId, appSecret } = config2;
for (const code of result) {
const { type, expired, url, id } = code;
if (type === 'wechatMpWxaCode') {
// 小程序码去实时获取(暂时不考虑缓存)
const wechatInstance = WechatSDK.getInstance(appId, appSecret, 'wechatMp');
const buffer = await wechatInstance.getMpUnlimitWxaCode({
scene: shrinkUuidTo32Bytes(id),
page: 'pages/index/index', // todo这里用其它的页面微信服务器拒绝因为没发布。应该是 pages/wechatQrCode/scan/index
});
// 把arrayBuffer转成字符串返回
const str = String.fromCharCode(...new Uint8Array(buffer));
assign(code, {
buffer: str,
});
}
else if (expired) {
// 如果过期了,在这里生成新的临时码并修改值(公众号)
throw new Error('not implemented yet');
}
}
return count;
}
} as SelectTriggerAfter<EntityDict, 'wechatQrCode', GeneralRuntimeContext<EntityDict>>,
];
export default triggers;

View File

@ -131,7 +131,8 @@ OakComponent({
filename: filename, filename: filename,
}, },
beforeExecute: async (updateData) => { beforeExecute: async (updateData) => {
const { url, bucket } = await this.features.extraFile.upload(updateData as DeduceCreateOperationData<EntityDict['extraFile']['Schema']>, "extraFile:gallery:upload"); const { url, bucket } = await this.features.extraFile.upload(
updateData as DeduceCreateOperationData<EntityDict['extraFile']['Schema']>, "extraFile:gallery:upload");
Object.assign(updateData, { Object.assign(updateData, {
bucket, bucket,
extra1: url, extra1: url,

View File

@ -9,7 +9,6 @@ OakPage({
relation: 1, relation: 1,
action: 1, action: 1,
remark: 1, remark: 1,
uuid: 1,
granterId: 1, granterId: 1,
granteeId: 1, granteeId: 1,
wechatQrCode$entity: { wechatQrCode$entity: {
@ -21,15 +20,9 @@ OakPage({
type: 1,//类型 type: 1,//类型
expiresAt: 1,// 过期时间 expiresAt: 1,// 过期时间
expired: 1, //是否过期 expired: 1, //是否过期
autoExtend: 1,
sceneStr: 1,
ticket: 1, ticket: 1,
url: 1, url: 1,
isPermanent: 1, //是否永久码 buffer: 1,
},
filter: {
entity: 'userEntityGrant',
expired: false,
}, },
indexFrom: 0, indexFrom: 0,
count: 1, count: 1,

View File

@ -12,7 +12,6 @@ OakPage({
relation: 1, relation: 1,
action: 1, action: 1,
remark: 1, remark: 1,
uuid: 1,
granterId: 1, granterId: 1,
granteeId: 1, granteeId: 1,
}, },