feat: 用户套餐,用户支付功能

This commit is contained in:
xiaojunnuo
2024-12-22 14:00:46 +08:00
parent d70e2b66a3
commit a019956698
69 changed files with 2071 additions and 738 deletions
@@ -99,7 +99,7 @@ export abstract class BaseService<T> {
* 新增|修改
* @param param 数据
*/
async addOrUpdate(param) {
async addOrUpdate(param: any) {
await this.getRepository().save(param);
}
@@ -107,7 +107,7 @@ export abstract class BaseService<T> {
* 新增
* @param param 数据
*/
async add(param) {
async add(param: any) {
const now = new Date();
param.createTime = now;
param.updateTime = now;
@@ -122,7 +122,7 @@ export abstract class BaseService<T> {
* 修改
* @param param 数据
*/
async update(param) {
async update(param: any) {
if (!param.id) throw new ValidateException('id 不能为空');
param.updateTime = new Date();
await this.addOrUpdate(param);
@@ -36,6 +36,10 @@ export const Constants = {
code: 88,
message: '需要VIP',
},
needsuite: {
code: 89,
message: '需要购买或升级套餐',
},
loginError: {
code: 2,
message: '登录失败',
@@ -8,3 +8,9 @@ export class NeedVIPException extends BaseException {
super('NeedVIPException', Constants.res.needvip.code, message ? message : Constants.res.needvip.message);
}
}
export class NeedSuiteException extends BaseException {
constructor(message) {
super('NeedSuiteException', Constants.res.needsuite.code, message ? message : Constants.res.needsuite.message);
}
}
+3 -1
View File
@@ -1,6 +1,8 @@
import { SysSettingsEntity } from './system/index.js';
import { AccessEntity } from './user/access/entity/access.js';
export * from './basic/index.js';
export * from './system/index.js';
export * from './user/index.js';
export { LibServerConfiguration as Configuration } from './configuration.js';
export const libServerEntities = [SysSettingsEntity];
export const libServerEntities = [SysSettingsEntity, AccessEntity];
@@ -136,3 +136,35 @@ export class SysHeaderMenus extends BaseSettings {
menus: MenuItem[];
}
export type PaymentItem = {
enabled: boolean;
accessId?: number;
};
export class SysPaymentSetting extends BaseSettings {
static __title__ = '支付设置';
static __key__ = 'sys.payment';
static __access__ = 'private';
yizhifu?: PaymentItem = { enabled: false };
alipay?: PaymentItem = { enabled: false };
wxpay?: PaymentItem = { enabled: false };
}
export class SysSuiteSetting extends BaseSettings {
static __title__ = '套餐设置';
static __key__ = 'sys.suite';
static __access__ = 'private';
enabled = false;
registerGift?: {
productId: number;
duration: number;
};
intro?: string;
}
@@ -0,0 +1,36 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
/**
* 授权配置
*/
@Entity('cd_access')
export class AccessEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
userId: number;
@Column({ comment: '名称', length: 100 })
name: string;
@Column({ comment: '类型', length: 100 })
type: string;
@Column({ name: 'setting', comment: '设置', length: 10240, nullable: true })
setting: string;
@Column({ name: 'encrypt_setting', comment: '已加密设置', length: 10240, nullable: true })
encryptSetting: string;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
}
@@ -0,0 +1,5 @@
export * from './entity/access.js';
export * from './service/access-service.js';
export * from './service/access-sys-getter.js';
export * from './service/access-getter.js';
export * from './service/encrypt-service.js';
@@ -0,0 +1,18 @@
import { IAccessService } from '@certd/pipeline';
export class AccessGetter implements IAccessService {
userId: number;
getter: <T>(id: any, userId?: number) => Promise<T>;
constructor(userId: number, getter: (id: any, userId: number) => Promise<any>) {
this.userId = userId;
this.getter = getter;
}
async getById<T = any>(id: any) {
return await this.getter<T>(id, this.userId);
}
async getCommonById<T = any>(id: any) {
return await this.getter<T>(id, 0);
}
}
@@ -0,0 +1,176 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService, PageReq, PermissionException, ValidateException } from '../../../index.js';
import { AccessEntity } from '../entity/access.js';
import { AccessDefine, accessRegistry, newAccess } from '@certd/pipeline';
import { EncryptService } from './encrypt-service.js';
/**
* 授权
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class AccessService extends BaseService<AccessEntity> {
@InjectEntityModel(AccessEntity)
repository: Repository<AccessEntity>;
@Inject()
encryptService: EncryptService;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
getRepository() {
return this.repository;
}
async page(pageReq: PageReq<AccessEntity>) {
const res = await super.page(pageReq);
res.records = res.records.map(item => {
delete item.encryptSetting;
return item;
});
return res;
}
async add(param) {
this.encryptSetting(param, null);
return await super.add(param);
}
encryptSetting(param: any, oldSettingEntity?: AccessEntity) {
const accessType = param.type;
const accessDefine: AccessDefine = accessRegistry.getDefine(accessType);
if (!accessDefine) {
throw new ValidateException(`授权类型${accessType}不存在`);
}
const setting = param.setting;
if (!setting) {
return;
}
const json = JSON.parse(setting);
let oldSetting = {};
let encryptSetting = {};
const firstEncrypt = !oldSettingEntity || !oldSettingEntity.encryptSetting || oldSettingEntity.encryptSetting === '{}';
if (oldSettingEntity) {
oldSetting = JSON.parse(oldSettingEntity.setting || '{}');
encryptSetting = JSON.parse(oldSettingEntity.encryptSetting || '{}');
}
for (const key in json) {
//加密
let value = json[key];
if (value && typeof value === 'string') {
//去除前后空格
value = value.trim();
json[key] = value;
}
const accessInputDefine = accessDefine.input[key];
if (!accessInputDefine) {
continue;
}
if (!accessInputDefine.encrypt || !value || typeof value !== 'string') {
//定义无需加密、value为空、不是字符串 这些不需要加密
encryptSetting[key] = {
value: value,
encrypt: false,
};
continue;
}
if (firstEncrypt || oldSetting[key] !== value) {
//星号保护
const length = value.length;
const subIndex = Math.min(2, length);
let starLength = length - subIndex * 2;
starLength = Math.max(2, starLength);
const starString = '*'.repeat(starLength);
json[key] = value.substring(0, subIndex) + starString + value.substring(value.length - subIndex);
encryptSetting[key] = {
value: this.encryptService.encrypt(value),
encrypt: true,
};
}
//未改变情况下,不做修改
}
param.encryptSetting = JSON.stringify(encryptSetting);
param.setting = JSON.stringify(json);
}
/**
* 修改
* @param param 数据
*/
async update(param) {
const oldEntity = await this.info(param.id);
if (oldEntity == null) {
throw new ValidateException('该授权配置不存在,请确认是否已被删除');
}
this.encryptSetting(param, oldEntity);
return await super.update(param);
}
async getSimpleInfo(id: number) {
const entity = await this.info(id);
if (entity == null) {
throw new ValidateException('该授权配置不存在,请确认是否已被删除');
}
return {
id: entity.id,
name: entity.name,
userId: entity.userId,
};
}
async getAccessById(id: any, checkUserId: boolean, userId?: number): Promise<any> {
const entity = await this.info(id);
if (entity == null) {
throw new Error(`该授权配置不存在,请确认是否已被删除:id=${id}`);
}
if (checkUserId) {
if (userId == null) {
throw new ValidateException('userId不能为空');
}
if (userId !== entity.userId) {
throw new PermissionException('您对该Access授权无访问权限');
}
}
// const access = accessRegistry.get(entity.type);
const setting = this.decryptAccessEntity(entity);
const input = {
id: entity.id,
...setting,
};
return newAccess(entity.type, input);
}
async getById(id: any, userId: number): Promise<any> {
return await this.getAccessById(id, true, userId);
}
decryptAccessEntity(entity: AccessEntity): any {
let setting = {};
if (entity.encryptSetting && entity.encryptSetting !== '{}') {
setting = JSON.parse(entity.encryptSetting);
for (const key in setting) {
//解密
const encryptValue = setting[key];
let value = encryptValue.value;
if (encryptValue.encrypt) {
value = this.encryptService.decrypt(value);
}
setting[key] = value;
}
} else if (entity.setting) {
setting = JSON.parse(entity.setting);
}
return setting;
}
getDefineList() {
return accessRegistry.getDefineList();
}
getDefineByType(type: string) {
return accessRegistry.getDefine(type);
}
}
@@ -0,0 +1,17 @@
import { IAccessService } from '@certd/pipeline';
import { AccessService } from './access-service.js';
export class AccessSysGetter implements IAccessService {
accessService: AccessService;
constructor(accessService: AccessService) {
this.accessService = accessService;
}
async getById<T = any>(id: any) {
return await this.accessService.getAccessById(id, false);
}
async getCommonById<T = any>(id: any) {
return await this.accessService.getAccessById(id, false);
}
}
@@ -0,0 +1,43 @@
import { Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import crypto from 'crypto';
import { SysPrivateSettings, SysSettingsService } from '../../../system/index.js';
/**
* 授权
*/
@Provide()
@Scope(ScopeEnum.Singleton)
export class EncryptService {
secretKey: Buffer;
@Inject()
sysSettingService: SysSettingsService;
@Init()
async init() {
const privateInfo: SysPrivateSettings = await this.sysSettingService.getSetting(SysPrivateSettings);
this.secretKey = Buffer.from(privateInfo.encryptSecret, 'base64');
}
// 加密函数
encrypt(text: string) {
const iv = crypto.randomBytes(16); // 初始化向量
// const secretKey = crypto.randomBytes(32);
// const key = Buffer.from(secretKey);
const cipher = crypto.createCipheriv('aes-256-cbc', this.secretKey, iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return iv.toString('hex') + ':' + encrypted.toString('hex');
}
// 解密函数
decrypt(encryptedText: string) {
const textParts = encryptedText.split(':');
const iv = Buffer.from(textParts.shift(), 'hex');
const encrypted = Buffer.from(textParts.join(':'), 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(this.secretKey), iv);
let decrypted = decipher.update(encrypted);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
}
@@ -0,0 +1 @@
export * from './access/index.js';