4 Commits

Author SHA1 Message Date
1c40c10210 重构了云函数 2025-10-19 20:10:19 +08:00
cfb6819bc7 refactor(common): 重构游戏数据同步与单例模块代码
- 移除 GameDataSyncManager 类及相关依赖,简化数据同步管理逻辑
- 在 SingletonModuleComp 中集成数据管理功能,使用本地数组替代字典结构存储英雄数据
- 优化本地与云端数据同步方法,适配云函数接口改动
- 修改英雄判断逻辑,支持基于数组的查询方式
- 修正金币数据的增减接口,增加异步云调用与本地更新的统一处理
- 删除冗余注释及无用代码,提升代码可读性和维护性
- 调整数据结构定义和类型声明,保障类型安全与代码健壮性
2025-10-19 18:05:45 +08:00
3f6b94af0e refactor(mission): 优化肉鸽关卡及怪物生成逻辑
- 将出战英雄配置由数组改为单个英雄编号,简化相关接口和数据结构
- 统一出战英雄设置和获取方法,移除冗余多英雄管理逻辑
- 增加怪物生成时的强度倍率参数,支持怪物属性随关卡进度递增调整
- 扩展肉鸽模式配置,实现关卡类型区分及怪物数量动态计算
- 新增随机事件系统,支持事件关卡随机触发宝箱、陷阱、增益、减益等事件
- 优化怪物生成流程,整合怪物配置、等级和强度倍率信息,增强游戏体验
2025-10-19 17:18:22 +08:00
928f1dbe16 refactor(config): 精简并优化技能与英雄配置代码
- 删除SkillSet.ts中未使用或多余的枚举和注释
- 移除SkillConfig接口中的无用字段in参数
- 统一调整技能配置字段,删除多余的in参数
- 简化SkillSet技能数据,去除冗余注释和无用代码
- heroSet.ts删除被注释的HeroKind枚举和无用注释
- 精简英雄配置相关注释,提高代码可读性与维护性
2025-10-19 15:52:37 +08:00
1959 changed files with 364276 additions and 6133 deletions

View File

@@ -4048,6 +4048,8 @@
"__id__": 0 "__id__": 0
}, },
"fileId": "d8tk5LEZZGh4E2AdHDAtvh", "fileId": "d8tk5LEZZGh4E2AdHDAtvh",
"instance": null,
"targetOverrides": null,
"nestedPrefabInstanceRoots": null "nestedPrefabInstanceRoots": null
}, },
{ {
@@ -7707,7 +7709,7 @@
"node": { "node": {
"__id__": 321 "__id__": 321
}, },
"_enabled": false, "_enabled": true,
"__prefab": { "__prefab": {
"__id__": 329 "__id__": 329
}, },
@@ -7715,7 +7717,7 @@
"watchPath": "data.data.gold", "watchPath": "data.data.gold",
"labelType": "cc.Label", "labelType": "cc.Label",
"watchPathArr": [ "watchPathArr": [
"data.data.gold" "data.gold"
], ],
"_id": "" "_id": ""
}, },

View File

@@ -1,527 +0,0 @@
import { oops } from "db://oops-framework/core/Oops";
import { WxCloudApi ,UserGameData} from "../wx_clound_client_api/WxCloudApi";
import { smc } from "./SingletonModuleComp";
import { GameData } from "../wx_clound_client_api/WxCloudApi";
/**
* 游戏数据同步管理器
* 负责管理fight_heros、heros、items、tals、equips的远程操作和本地同步
* 只有在远程修改成功后才同步修改本地数据
*/
export class GameDataSyncManager {
private static instance: GameDataSyncManager;
private constructor() {}
public static getInstance(): GameDataSyncManager {
if (!GameDataSyncManager.instance) {
GameDataSyncManager.instance = new GameDataSyncManager();
}
return GameDataSyncManager.instance;
}
/**
* 用远程数据覆盖本地数据(统一方法)
* @param remoteData 远程数据(云端或本地调试)
* @param dataSource 数据源描述
*/
async overrideLocalDataWithRemote(remoteData: UserGameData, dataSource: string) {
try {
// console.log(`[Initialize]: 开始用${dataSource}数据覆盖客户端数据...`);
// 直接覆盖基础游戏数据
if (remoteData.data) {
Object.assign(smc.data, remoteData.data);
// console.log(`[Initialize]: 基础游戏数据已从${dataSource}覆盖`);
}
// 直接覆盖出战英雄配置
if (remoteData.fight_heros) {
Object.assign(smc.fight_heros, remoteData.fight_heros);
// console.log(`[Initialize]: 出战英雄配置已从${dataSource}覆盖`);
}
// 直接覆盖英雄数据
if (remoteData.heros) {
smc.heros = { ...remoteData.heros };
// console.log(`[Initialize]: 英雄数据已从${dataSource}覆盖`);
}
// 直接覆盖道具数据
if (remoteData.items) {
Object.assign(smc.items, remoteData.items);
// console.log(`[Initialize]: 道具数据已从${dataSource}覆盖`);
}
// 直接覆盖天赋数据
if (remoteData.tals) {
Object.assign(smc.tals, remoteData.tals);
// console.log(`[Initialize]: 天赋数据已从${dataSource}覆盖`);
}
// 直接覆盖装备数据
if (remoteData.equips) {
Object.assign(smc.equips, remoteData.equips);
// console.log(`[Initialize]: 装备数据已从${dataSource}覆盖`);
}
// 保存到本地存储(确保数据持久化)
// smc.saveGameData();
// console.log(`[Initialize]: ${dataSource}数据覆盖完成,已保存到本地`);
} catch (error) {
console.error(`[Initialize]: ${dataSource}数据覆盖失败:`, error);
}
}
/**
* 批量更新出战英雄配置
* @param fightHeros 出战英雄配置对象
* @returns 是否成功
*/
async updateFightHeros(fightHeros: any): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 批量更新出战英雄配置:`, fightHeros);
const result = await WxCloudApi.updateFightHeros(fightHeros);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
Object.assign(smc.fight_heros, fightHeros);
// console.log(`[GameDataSyncManager]: 出战英雄配置更新成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 出战英雄配置更新失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 更新出战英雄配置异常:`, error);
smc.error()
return false;
}
}
/**
* 重置出战英雄配置为默认值
* @returns 是否成功
*/
async resetFightHeros(): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 重置出战英雄配置`);
const result = await WxCloudApi.resetFightHeros();
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
smc.fight_heros = result.result.data;
// console.log(`[GameDataSyncManager]: 出战英雄配置重置成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 出战英雄配置重置失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 重置出战英雄配置异常:`, error);
smc.error()
return false;
}
}
// ==================== 英雄管理 ====================
/**
* 添加新英雄到用户库存
* @param heroId 英雄ID
* @param heroData 英雄数据(可选)
* @returns 是否成功
*/
async addHero(heroId: number, heroData?: any): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 添加英雄 ID:${heroId}, 数据:`, heroData);
const result = await WxCloudApi.addHero(heroId, heroData);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
smc.heros[heroId] = result.result.data;
// console.log(`[GameDataSyncManager]: 英雄添加成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 英雄添加失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 添加英雄异常:`, error);
smc.error()
return false;
}
}
/**
* 更新英雄的多个属性
* @param heroId 英雄ID
* @param updateData 要更新的属性
* @returns 是否成功
*/
async updateHero(heroId: number, updateData: any): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 更新英雄 ID:${heroId}, 更新数据:`, updateData);
const result = await WxCloudApi.updateHero(heroId, updateData);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
Object.assign(smc.heros[heroId], result.result.data.new_data);
// console.log(`[GameDataSyncManager]: 英雄更新成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 英雄更新失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 更新英雄异常:`, error);
smc.error()
return false;
}
}
/**
* 设置英雄的单个属性值
* @param heroId 英雄ID
* @param property 属性名
* @param value 属性值
* @returns 是否成功
*/
async setHeroProperty(heroId: number, property: any, value: any): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 设置英雄属性 ID:${heroId}, 属性:${property}, 值:${value}`);
const result = await WxCloudApi.setHeroProperty(heroId, property, value);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
smc.heros[heroId][property] = value;
// console.log(`[GameDataSyncManager]: 英雄属性设置成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 英雄属性设置失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 设置英雄属性异常:`, error);
smc.error()
return false;
}
}
/**
* 英雄升级指定级数
* @param heroId 英雄ID
* @param levels 升级级数默认1级
* @returns 是否成功
*/
async levelUpHero(heroId: number,levels: number = 1,): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 英雄升级 ID:${heroId}, 级数:${levels}`);
const result = await WxCloudApi.levelUpHero(heroId,levels);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
smc.heros[heroId].lv = result.result.data.new_value;
// console.log(`[GameDataSyncManager]: 英雄升级成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 英雄升级失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 英雄升级异常:`, error);
smc.error()
return false;
}
}
// ==================== 便捷方法 ====================
/**
* 增加道具
* @param itemId 道具ID
* @param count 数量
* @returns 是否成功
*/
async addItem(itemId: number, count: number): Promise<boolean> {
smc.items[itemId] = (smc.items[itemId] || 0) + count;
try {
// console.log(`[GameDataSyncManager]: 增加道具 ID:${itemId}, 数量:${count}`);
const result = await WxCloudApi.addInventoryItem('items', itemId, count);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
// console.log(`[GameDataSyncManager]: 道具增加成功`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 道具增加失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 增加道具异常:`, error);
smc.error()
return false;
}
}
/**
* 消耗道具
* @param itemId 道具ID
* @param count 数量
* @returns 是否成功
*/
async consumeItem(itemId: number, count: number): Promise<boolean> {
if(!smc.items[itemId]||smc.items[itemId]<count){
oops.gui.toast("道具数量不足")
return false
}
try {
// console.log(`[GameDataSyncManager]: 消耗道具 ID:${itemId}, 数量:${count}`);
const result = await WxCloudApi.consumeInventoryItem('items', itemId, count);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
smc.items[itemId] = Math.max(0, (smc.items[itemId] || 0) - count);
// console.log(`[GameDataSyncManager]: 道具消耗成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 道具消耗失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 消耗道具异常:`, error);
smc.error()
return false;
}
}
/**
* 增加天赋点
* @param talId 天赋ID
* @param count 数量
* @returns 是否成功
*/
async addTalent(talId: number, count: number): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 增加天赋点 ID:${talId}, 数量:${count}`);
const result = await WxCloudApi.addInventoryItem('tals', talId, count);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
smc.tals[talId] = (smc.tals[talId] || 0) + count;
// console.log(`[GameDataSyncManager]: 天赋点增加成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 天赋点增加失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 增加天赋点异常:`, error);
smc.error()
return false;
}
}
/**
* 消耗天赋点
* @param talId 天赋ID
* @param count 数量
* @returns 是否成功
*/
async consumeTalent(talId: number, count: number): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 消耗天赋点 ID:${talId}, 数量:${count}`);
const result = await WxCloudApi.consumeInventoryItem('tals', talId, count);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
smc.tals[talId] = Math.max(0, (smc.tals[talId] || 0) - count);
// console.log(`[GameDataSyncManager]: 天赋点消耗成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 天赋点消耗失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 消耗天赋点异常:`, error);
smc.error()
return false;
}
}
/**
* 增加装备
* @param equipId 装备ID
* @param count 数量
* @returns 是否成功
*/
async addEquipment(equipId: number, count: number): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 增加装备 ID:${equipId}, 数量:${count}`);
const result = await WxCloudApi.addInventoryItem('equips', equipId, count);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
smc.equips[equipId] = (smc.equips[equipId] || 0) + count;
// console.log(`[GameDataSyncManager]: 装备增加成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 装备增加失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 增加装备异常:`, error);
smc.error()
return false;
}
}
/**
* 消耗装备
* @param equipId 装备ID
* @param count 数量
* @returns 是否成功
*/
async consumeEquipment(equipId: number, count: number): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 消耗装备 ID:${equipId}, 数量:${count}`);
const result = await WxCloudApi.consumeInventoryItem('equips', equipId, count);
if (result.result.code === 200) {
// 远程修改成功,同步本地数据
smc.equips[equipId] = Math.max(0, (smc.equips[equipId] || 0) - count);
// console.log(`[GameDataSyncManager]: 装备消耗成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 装备消耗失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 消耗装备异常:`, error);
smc.error()
return false;
}
}
async addGameProperty(property: string, value: any): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 增加游戏数据 ${property} = ${value}`);
const result = await WxCloudApi.addGameDataField(property, value);
if (result.result.code === 200) {
// console.log(`[GameDataSyncManager]: 游戏数据增加成功`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 游戏数据增加失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 增加游戏数据异常:`, error);
smc.error()
return false;
}
}
async spendGameProperty(property: string|Record<string, number>, value: any = undefined ): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 消耗游戏数据 ${property} = ${value}`);
const result = await WxCloudApi.spendGameDataField(property, value);
if (result.result.code === 200) {
// console.log(`[GameDataSyncManager]: 游戏数据消耗成功`);
return true;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 消耗游戏数据异常:`, error);
smc.error()
return false;
}
}
/**
* 从云端加载所有游戏数据并同步到本地
* @returns 是否成功
*/
async loadAllGameData(): Promise<boolean> {
try {
// console.log(`[GameDataSyncManager]: 从云端加载所有游戏数据`);
const result = await WxCloudApi.getAllGameData();
if (result.result.code === 200) {
// 远程数据获取成功,同步本地数据
const cloudData = result.result.data;
smc.data = cloudData.data;
smc.fight_heros = cloudData.fight_heros;
smc.heros = cloudData.heros;
smc.items = cloudData.items;
smc.tals = cloudData.tals;
smc.equips = cloudData.equips;
// console.log(`[GameDataSyncManager]: 云端数据加载成功,本地数据已同步`);
return true;
} else {
console.warn(`[GameDataSyncManager]: 云端数据加载失败: ${result.result.msg}`);
return false;
}
} catch (error) {
console.error(`[GameDataSyncManager]: 加载云端数据异常:`, error);
smc.error()
return false;
}
}
}
// 导出单例实例
export const gameDataSyncManager = GameDataSyncManager.getInstance();
/*
使用示例:
// 1. 出战英雄管理
await gameDataSyncManager.updateFightHeros({0: 5001, 1: 5005}); // 批量更新
await gameDataSyncManager.resetFightHeros(); // 重置为默认配置
// 2. 英雄管理
await gameDataSyncManager.addHero(5008, {uuid: 5008, lv: 1}); // 添加新英雄
await gameDataSyncManager.levelUpHero(5001, 100, 50, 5); // 英雄升级5级
await gameDataSyncManager.setHeroProperty(5001, "exp", 1000); // 设置英雄经验
// 3. 道具管理 (items)
await gameDataSyncManager.addItem(1001, 10); // 增加道具
await gameDataSyncManager.consumeItem(1001, 2); // 消耗道具
await gameDataSyncManager.setItem(1001, 5); // 设置道具数量
// 4. 天赋点管理 (tals)
await gameDataSyncManager.addTalent(2001, 5); // 增加天赋点
await gameDataSyncManager.consumeTalent(2001, 2); // 消耗天赋点
await gameDataSyncManager.setTalent(2001, 10); // 设置天赋点数量
// 5. 装备管理 (equips)
await gameDataSyncManager.addEquipment(3001, 2); // 增加装备
await gameDataSyncManager.consumeEquipment(3001, 1); // 消耗装备
await gameDataSyncManager.setEquipment(3001, 3); // 设置装备数量
// 6. 数据加载
await gameDataSyncManager.loadAllGameData(); // 从云端加载所有数据
注意:所有方法都返回 Promise<boolean>true表示成功false表示失败
只有在远程修改成功后,本地数据才会被同步修改
*/

View File

@@ -1,9 +0,0 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "89c45a3b-d0bf-4e45-9e27-b7d714ba7e29",
"files": [],
"subMetas": {},
"userData": {}
}

View File

@@ -3,14 +3,22 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { Initialize } from "../initialize/Initialize"; import { Initialize } from "../initialize/Initialize";
import { GameMap } from "../map/GameMap"; import { GameMap } from "../map/GameMap";
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops"; import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
import { GameData, WxCloudApi } from "../wx_clound_client_api/WxCloudApi"; import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
import { gameDataSyncManager } from "./GameDataSyncManager";
import { Test } from "./Test"; import { Test } from "./Test";
import { GameEvent } from "./config/GameEvent"; import { GameEvent } from "./config/GameEvent";
/**
* 用远程数据覆盖本地数据(统一方法)
// import { Role } from "../role/Role"; * @param remoteData 远程数据(云端或本地调试)
// import { data } from "../data/data"; */
interface GameDate{
gold:number,
heros:any,
fight_hero:number
}
interface CloudData {
openid: string;
data: GameDate;
}
/** 游戏模块 */ /** 游戏模块 */
@ecs.register('SingletonModule') @ecs.register('SingletonModule')
export class SingletonModuleComp extends ecs.Comp { export class SingletonModuleComp extends ecs.Comp {
@@ -18,8 +26,7 @@ export class SingletonModuleComp extends ecs.Comp {
initialize: Initialize = null!; initialize: Initialize = null!;
/** 游戏地图 */ /** 游戏地图 */
map: GameMap = null!; map: GameMap = null!;
/** 游戏数据同步管理器 */ openid:string=''
private gameDataSyncManager = gameDataSyncManager;
mission:any={ mission:any={
status:0, //0:未开始 1:进行中 2:胜利 3:失败 status:0, //0:未开始 1:进行中 2:胜利 3:失败
play:false, play:false,
@@ -32,36 +39,14 @@ export class SingletonModuleComp extends ecs.Comp {
data:any={ data:any={
score:0, score:0,
mission:1, mission:1,
gold:100, //升级主要资源
diamond:100, //商店购买 及 双倍奖励资源 diamond:100, //商店购买 及 双倍奖励资源
meat:0, meat:0,
exp:0, exp:0,
task:0, task:0,
} }
shop:any={ fight_hero: number = 5001; // 单个出战英雄
daily:[1001,1004,1002,1005], heros:any= [5001,5002]
weekly:[],
monthly:[],
special:[],
goods_count:[1,1,3,3,10,10,10,10,10,10,10,10],
}
fight_heros:any={ 0:5001, 1:5005, 2:0, 3:0, 4:0, }
heros:any = {
5001:{uuid:5001,lv:1},
5005:{uuid:5005,lv:1},
};
items:any={
}
tals:any={
}
equips:any={
}
monsters:any = []; monsters:any = [];
sk_info:any = []
monsters_dead:any = []
heros_dead:any = []
enhancements:any=[]
vmdata: any = { vmdata: any = {
game_over:false, game_over:false,
game_pause:false, game_pause:false,
@@ -73,7 +58,9 @@ export class SingletonModuleComp extends ecs.Comp {
fight_time:0,//战斗时间 fight_time:0,//战斗时间
level:1,//关卡等级 level:1,//关卡等级
max_mission:4,//最大关卡 max_mission:4,//最大关卡
coin:0,
}, },
gold: 100, // 金币数据MVVM绑定字段
}; };
vmAdd() { vmAdd() {
VM.add(this.vmdata, "data"); VM.add(this.vmdata, "data");
@@ -86,7 +73,7 @@ export class SingletonModuleComp extends ecs.Comp {
// ==================== 数据管理方法 ==================== // ==================== 数据管理方法 ====================
/** /**
* 判断是否为微信客户端 * 判断是否为微信客户端
*/ */
private isWxClient(): boolean { private isWxClient(): boolean {
@@ -96,222 +83,108 @@ export class SingletonModuleComp extends ecs.Comp {
finishGuide(index:number){ finishGuide(index:number){
smc.guides[index]=1 smc.guides[index]=1
this.syncGuide()
}
syncGuide(){
//存储到远程服务器 后续再添加 //存储到远程服务器 后续再添加
} }
//调试用
syncDataFromLocal(){
if(this.isWxClient()) return
const loginResult = new Test().load_data_from_local()
this.gameDataSyncManager.overrideLocalDataWithRemote(loginResult, "本地调试");
}
addHero(hero_uuid:number,autoSave:boolean=true){ updateCloudData(){
if(this.isWxClient()){ let gemeDate=this.getGameDate()
if(this.gameDataSyncManager.addHero(hero_uuid)){ WxCloudApi.save(gemeDate).then((result) => {
this.heros[hero_uuid]={ uuid:hero_uuid, lv:1, } if(result.result.code === 200) {
return true return true
} } else {
console.warn(`[SMC]: 游戏数据增加失败: ${result.result.msg}`);
return false return false
} }
this.heros[hero_uuid]={ uuid:hero_uuid, lv:1, } }).catch((error) => {
console.error(`[SMC]: 增加游戏数据异常:`, error);
return false
});
return true return true
} }
getCloudData(){
setFightHero(position:number,heroId:number,autoSave:boolean=true){ WxCloudApi.get().then(async (result) => {
this.fight_heros[position] = heroId; if(result.result.code === 200) {
if(this.isWxClient()){ let data=result.result.data
this.updateFightHeros() this.overrideLocalDataWithRemote(data)
}
}
updateFightHeros(){
this.gameDataSyncManager.updateFightHeros(this.fight_heros);
}
resetFightHeros(){
this.gameDataSyncManager.resetFightHeros();
}
getHasHeroUUID(){
let heros=this.heros
let heros_uuid=[]
for(let key in heros){
heros_uuid.push(heros[key].uuid)
}
return heros_uuid
}
levelUpHero(heroId:number){
if(this.isWxClient()){
let result=this.gameDataSyncManager.levelUpHero(heroId);
if(result){
this.heros[heroId].lv++;
return true return true
} } else {
console.warn(`[SMC]: 游戏数据增加失败: ${result.result.msg}`);
return false return false
} }
else{ }).catch((error) => {
this.heros[heroId].lv++; console.error(`[SMC]: 获取游戏数据异常:`, error);
});
}
public async overrideLocalDataWithRemote(CloudData) {
try {
// 直接覆盖基础游戏数据
if (CloudData.openid) {
this.openid=CloudData.openid
}
// 直接覆盖出战英雄配置
if (CloudData.data) {
if(CloudData.data.gold) this.vmdata.gold=CloudData.data.gold
if(CloudData.data.heros) this.heros=CloudData.data.heros
if(CloudData.data.fight_hero) this.fight_hero=CloudData.data.fight_hero
}
} catch (error) {
console.error(`[SMC]: 数据覆盖失败:`, error);
}
}
getGameDate(){
return {gold:this.vmdata.gold,heros:this.heros,fight_hero:this.fight_hero}
}
addHero(hero_uuid:number){
if(this.heros.indexOf(hero_uuid)==-1){
this.heros.push(hero_uuid)
}
if(this.isWxClient()){
let res = this.updateCloudData()
if (res){
return true
}else{
// 同步不成功删除uuid
this.heros.splice(this.heros.indexOf(hero_uuid), 1);
oops.gui.toast("数据同步失败,已回滚操作");
return false
}
}
return true return true
} }
// 设置单个出战英雄
updateFihgtHero(heroId: number) {
this.fight_hero = heroId;
if(this.isWxClient()){
let res = this.updateCloudData()
if (res){
return true
}else{
return false
}
}
return true
}
updateGold(gold:number){
this.vmdata.gold += gold;
if(this.isWxClient()){
let res = this.updateCloudData()
if (res){
oops.message.dispatchEvent(GameEvent.GOLD_UPDATE)
return true
}else{
this.vmdata.gold -= gold
return false
}
}
oops.message.dispatchEvent(GameEvent.GOLD_UPDATE)
return true
} }
error(){ error(){
oops.gui.toast("数据处理异常,请重试或重新登录") oops.gui.toast("数据处理异常,请重试或重新登录")
} }
addExp(exp:number,autoSave:boolean=true){
if(this.isWxClient()){
if(this.gameDataSyncManager.addGameProperty("exp",exp)){
this.data.exp+=exp
return true
}
return false
}
this.data.exp+=exp
return true
}
addGold(gold:number,autoSave:boolean=true){
if(this.isWxClient()){
if(this.gameDataSyncManager.addGameProperty("gold",gold)){
this.data.gold+=gold
oops.message.dispatchEvent(GameEvent.GOLD_UPDATE)
return true
}
this.error()
return false
}
this.data.gold+=gold
oops.message.dispatchEvent(GameEvent.GOLD_UPDATE)
return true
}
addDiamond(diamond:number,autoSave:boolean=true){
if(this.isWxClient()){
if(this.gameDataSyncManager.addGameProperty("diamond",diamond)){
this.data.diamond+=diamond
oops.message.dispatchEvent(GameEvent.DIAMOND_UPDATE)
return true
}
return false
}
this.data.diamond+=diamond
oops.message.dispatchEvent(GameEvent.DIAMOND_UPDATE)
return true
}
addMission(mission:number,autoSave:boolean=true){
if(this.isWxClient()){
if(this.gameDataSyncManager.addGameProperty("mission",mission)){
this.data.mission+=mission
oops.message.dispatchEvent(GameEvent.MISSION_UPDATE)
return true
}
return false
}
this.data.mission+=mission
oops.message.dispatchEvent(GameEvent.MISSION_UPDATE)
return true
}
spendExp(exp:number,autoSave:boolean=true){
if(this.isWxClient()){
if(this.gameDataSyncManager.spendGameProperty("exp",exp)){
this.data.exp-=exp
return true
}
return false
}
this.data.exp-=exp
return true
}
spendGold(gold:number,autoSave:boolean=true){
if(this.isWxClient()){
if(this.gameDataSyncManager.spendGameProperty("gold",gold)){
this.data.gold-=gold
oops.message.dispatchEvent(GameEvent.GOLD_UPDATE)
return true
}
return false
}
this.data.gold-=gold
oops.message.dispatchEvent(GameEvent.GOLD_UPDATE)
return true
}
spendDiamond(diamond:number,autoSave:boolean=true){
if(this.isWxClient()){
if(this.gameDataSyncManager.spendGameProperty("diamond",diamond)){
this.data.diamond-=diamond
oops.message.dispatchEvent(GameEvent.DIAMOND_UPDATE)
return true
}
return false
}
this.data.diamond-=diamond
oops.message.dispatchEvent(GameEvent.DIAMOND_UPDATE)
return true
}
/**
* 消耗游戏数据属性(统一接口)
* 处理多个字段spendGameProperty({ gold: 10, exp: 5 })
*/
async spendGameProperty(property: Record<string, number>, autoSave: boolean = true): Promise<boolean> {
if(this.isWxClient()){
if(this.gameDataSyncManager.spendGameProperty(property)){
return true
}
return false
}
// 多字段扣除(原子性:全部满足才扣)
const deductions = property as Record<string, number>;
// 1) 校验是否全部满足
for (const key in deductions) {
if (!Object.prototype.hasOwnProperty.call(deductions, key)) continue;
const need = deductions[key] ?? 0;
const current = this.data[key] || 0;
if (current < need) {
console.warn(`[SMC]: ${key} 不足,当前: ${current}, 需要: ${need}`);
oops.gui.toast(`${key} 不足,当前: ${current}, 需要: ${need}`)
return false;
}
}
// 2) 统一扣减
for (const key in deductions) {
if (!Object.prototype.hasOwnProperty.call(deductions, key)) continue;
const need = deductions[key] ?? 0;
const current = this.data[key] || 0;
const next = current - need;
this.data[key] = next;
// console.log(`[SMC]: 消耗游戏数据 ${key} = ${need}, 当前值: ${next}`);
}
return true;
}
addItem(item_uuid:number,count:number,autoSave:boolean=true){
if(this.isWxClient()){
if(this.gameDataSyncManager.addItem(item_uuid,count)){
this.items[item_uuid] = (this.items[item_uuid] || 0) + count;
return true
}
return false
}
this.items[item_uuid] = (this.items[item_uuid] || 0) + count;
return true
}
spendItem(item_uuid:number,count:number,autoSave:boolean=true){
if(this.isWxClient()){
if(this.gameDataSyncManager.consumeItem(item_uuid,count)){
this.items[item_uuid] = (this.items[item_uuid] || 0) - count;
return true
}
return false
}
this.items[item_uuid] = (this.items[item_uuid] || 0) - count;
return true
}
} }

View File

@@ -1,7 +1,3 @@
import { log } from "cc"
import { QualitySet } from "./BoxSet"
import * as exp from "constants"
export enum TGroup { export enum TGroup {
Self = 0, // 自身 Self = 0, // 自身
Ally = 1, // 所有敌人 Ally = 1, // 所有敌人
@@ -27,19 +23,13 @@ export enum TType {
/** 随机目标 */ /** 随机目标 */
Random =8 // 随机目标 Random =8 // 随机目标
} }
export enum SKILL_CONST {
POWER_UP = 60,
}
export enum DTType { export enum DTType {
single = 0, single = 0,
range = 1, range = 1,
} }
export enum skRun {
runing = 0,
dead = 1,
}
export enum SType { export enum SType {
damage = 0, damage = 0,
heal = 1, heal = 1,
@@ -54,15 +44,7 @@ export enum SType {
zhaohuan = 10, zhaohuan = 10,
buff = 11, buff = 11,
} }
//技能释放cd: 0:技能配置的cd,1:HeroViewComp.cd 值,2:HeroViewComp.pw:0值,当HeroViewComppw==HeroViewComp.pwm值是 释放
export enum CdType {
cd=0,
atk=1,
atked=2,
crit=3,
dod=4,
power=5,
}
export enum AtkedType { export enum AtkedType {
atked = "atked", atked = "atked",
ice = "atked_ice", ice = "atked_ice",
@@ -71,14 +53,6 @@ export enum AtkedType {
crit = "atked_crit", crit = "atked_crit",
} }
//技能释放cd: 0:技能配置的cd,1:HeroViewComp.cd 值,2:HeroViewComp.pw:0值,当HeroViewComppw==HeroViewComp.pwm值是 释放
export enum AType {
linear = 0, // 直线
parabolic = 1, // 抛射物
fixedStart = 2, // 固定在出发点,物理攻击
fixedEnd = 3, // 固定在终点
StartEnd = 4, // 固定在出发点和终点 用于治疗和buff类技能
}
export enum RType { export enum RType {
linear = 0, //直线 linear = 0, //直线
@@ -229,14 +203,6 @@ export const isRatioAttr = (attrType: Attrs): boolean => {
return AttrsType[attrType] === BType.RATIO; return AttrsType[attrType] === BType.RATIO;
}; };
/**
* 获取属性的类型
* @param attrType 属性类型
* @returns BType.VALUE 或 BType.RATIO
*/
export const getAttrType = (attrType: Attrs): BType => {
return AttrsType[attrType];
};
/** /**
* DBuff 与 Attrs 的双向映射关系表 * DBuff 与 Attrs 的双向映射关系表
@@ -285,59 +251,31 @@ export const TransformBuffs = (key: number, isDebuff: boolean): number => {
1. 基础属性: 1. 基础属性:
- uuid: 技能唯一ID - uuid: 技能唯一ID
- name: 技能名称 - name: 技能名称
- for_hero: 是否为英雄专用技能
- sp_name: 特效名称 - sp_name: 特效名称
- AtkedType: 攻击类型 - AtkedType: 攻击类型
- path: 图片资源路径 - path: 图片资源路径
- quality: 技能品质
2. 目标和效果: 2. 目标和效果:
- TType: 目标类型 (最前排、最后排等)
- maxC: 最大命中数量
- TGroup: 目标群体 (敌方、友方等) - TGroup: 目标群体 (敌方、友方等)
- SType: 技能类型 (伤害、治疗、护盾等) - SType: 技能类型 (伤害、治疗、护盾等)
3. 执行参数: 3. 执行参数:
- act: 行为类型 - act: 角色执行的动画
- DTType: 伤害类型 (单体、范围) - DTType: 伤害类型 (单体、范围)
- CdType: 冷却类型
- AType: 动画类型
- EType: 结束条件 - EType: 结束条件
- fname: 特效文件名 - fname: 特效文件名
- flash: 是否闪烁 - with: 暂时无用
- with: 角度参数
4. 数值参数: 4. 数值参数:
- ap: 攻击力百分比 - ap: 攻击力百分比
- cd: 冷却时间 - cd: 冷却时间
- in: 持续时间 - hit_num: 范围攻击 伤害敌人数量
- hit_num: 命中次数 - hit: 穿刺个数
- hit: 伤害倍数 - hitcd: 持续伤害的伤害间隔
- hitcd: 伤害间隔
- speed: 移动速度 - speed: 移动速度
- cost: 消耗值 - cost: 消耗值
5. 效果配置:
- buffs: BuffConf[] - 增益效果数组
- debuffs: DbuffConf[] - 减益效果数组
- info: 技能描述
- hero?: 召唤物英雄ID (可选)
6. BuffConf结构
- buff: BuffAttr - 增益类型
- buV: number - 效果值
- buC: number - 持续次数
- buR: number - 触发概率
7. DbuffConf结构
- debuff: DebuffAttr - 减益类型
- dev: number - 效果值
- deC: number - 持续时间
- deR: number - 触发概率
8. 使用示例:
buffs: [createBuffConf(BuffAttr.SHIELD, 2, 0, 100)]
debuffs: [createDbuffConf(DBuff.STUN, 0, 1, 50)]
*/ */
export const HeroSkillList = [6001,6001,6001,6001,6001,6001] export const HeroSkillList = [6001,6001,6001,6001,6001,6001]
@@ -359,7 +297,7 @@ export interface BuffConf {
// 技能配置接口 - 按照6001格式排列 // 技能配置接口 - 按照6001格式排列
export interface SkillConfig { export interface SkillConfig {
uuid:number,name:string,sp_name:string,AtkedType:AtkedType,path:string,TGroup:TGroup,SType:SType,act:string,DTType:DTType, uuid:number,name:string,sp_name:string,AtkedType:AtkedType,path:string,TGroup:TGroup,SType:SType,act:string,DTType:DTType,
ap:number,cd:number,in:number,t_num:number,hit_num:number,hit:number,hitcd:number,speed:number,cost:number,with:number, ap:number,cd:number,t_num:number,hit_num:number,hit:number,hitcd:number,speed:number,cost:number,with:number,
buffs:BuffConf[],debuffs:DbuffConf[],info:string,hero?:number buffs:BuffConf[],debuffs:DbuffConf[],info:string,hero?:number
} }
@@ -368,71 +306,71 @@ export const SkillSet: Record<number, SkillConfig> = {
// ========== 基础攻击 ========== 6001-6099 // ========== 基础攻击 ========== 6001-6099
6001: { 6001: {
uuid:6001,name:"挥击",sp_name:"atk_s1",AtkedType:AtkedType.atked,path:"3036",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6001,name:"挥击",sp_name:"atk_s1",AtkedType:AtkedType.atked,path:"3036",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:1,in:0.2,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,cost:0,with:0, ap:100,cd:1,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,cost:0,with:0,
buffs:[],debuffs:[],info:"向最前方敌人扔出石斧,造成100%攻击的伤害" buffs:[],debuffs:[],info:"向最前方敌人扔出石斧,造成100%攻击的伤害"
}, },
6002: { 6002: {
uuid:6002,name:"挥击",sp_name:"atk2",AtkedType:AtkedType.atked,path:"3036",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6002,name:"挥击",sp_name:"atk2",AtkedType:AtkedType.atked,path:"3036",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,cost:10,with:0, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,cost:10,with:0,
buffs:[],debuffs:[],info:"向最前方敌人扔出石斧,造成100%攻击的伤害" buffs:[],debuffs:[],info:"向最前方敌人扔出石斧,造成100%攻击的伤害"
}, },
6003: { 6003: {
uuid:6003,name:"射击",sp_name:"arrow",AtkedType:AtkedType.atked,path:"3037",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6003,name:"射击",sp_name:"arrow",AtkedType:AtkedType.atked,path:"3037",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:3,speed:720,cost:10,with:0, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:3,speed:720,cost:10,with:0,
buffs:[],debuffs:[],info:"向最前方敌人释放箭矢,造成100%攻击的伤害" buffs:[],debuffs:[],info:"向最前方敌人释放箭矢,造成100%攻击的伤害"
}, },
6004: { 6004: {
uuid:6004,name:"冰球",sp_name:"am_ice",AtkedType:AtkedType.ice,path:"3034",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6004,name:"冰球",sp_name:"am_ice",AtkedType:AtkedType.ice,path:"3034",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:3,speed:720,cost:10,with:0, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:3,speed:720,cost:10,with:0,
buffs:[],debuffs:[],info:"向最前方敌人释放寒冰弹,造成100%攻击的伤害" buffs:[],debuffs:[],info:"向最前方敌人释放寒冰弹,造成100%攻击的伤害"
}, },
6005: { 6005: {
uuid:6005,name:"火球术",sp_name:"atk_fires",AtkedType:AtkedType.fire,path:"3039",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6005,name:"火球术",sp_name:"atk_fires",AtkedType:AtkedType.fire,path:"3039",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:1,t_num:1,hit_num:1,hit:2,hitcd:0.3,speed:720,cost:20,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:2,hitcd:0.3,speed:720,cost:20,with:90,
buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成300%攻击的伤害,有一定几率施加灼烧" buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成300%攻击的伤害,有一定几率施加灼烧"
}, },
6006: { 6006: {
uuid:6006,name:"能量波",sp_name:"am_blue",AtkedType:AtkedType.ice,path:"3034",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6006,name:"能量波",sp_name:"am_blue",AtkedType:AtkedType.ice,path:"3034",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:3,speed:720,cost:10,with:0, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:3,speed:720,cost:10,with:0,
buffs:[],debuffs:[],info:"向最前方敌人释放寒冰弹,造成100%攻击的伤害" buffs:[],debuffs:[],info:"向最前方敌人释放寒冰弹,造成100%攻击的伤害"
}, },
6007: { 6007: {
uuid:6007,name:"圣光波",sp_name:"am_yellow",AtkedType:AtkedType.fire,path:"3039",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6007,name:"圣光波",sp_name:"am_yellow",AtkedType:AtkedType.fire,path:"3039",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:2,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:2,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成300%攻击的伤害,有一定几率施加灼烧" buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成300%攻击的伤害,有一定几率施加灼烧"
}, },
// ========== 大招 ========== 6100-6199 // ========== 大招 ========== 6100-6199
6101: { 6101: {
uuid:6101,name:"护盾",sp_name:"shield",AtkedType:AtkedType.atked,path:"3045",TGroup:TGroup.Team,SType:SType.shield,act:"max",DTType:DTType.single, uuid:6101,name:"护盾",sp_name:"shield",AtkedType:AtkedType.atked,path:"3045",TGroup:TGroup.Team,SType:SType.shield,act:"max",DTType:DTType.single,
ap:0,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:3,speed:720,cost:10,with:0, ap:0,cd:5,t_num:1,hit_num:1,hit:1,hitcd:3,speed:720,cost:10,with:0,
buffs:[],debuffs:[],info:"为最前排队友召唤一个可以抵御2次攻击的圣盾(最高叠加到6次)" buffs:[],debuffs:[],info:"为最前排队友召唤一个可以抵御2次攻击的圣盾(最高叠加到6次)"
}, },
6102: { 6102: {
uuid:6102,name:"寒冰箭",sp_name:"arrow_blue",AtkedType:AtkedType.ice,path:"3060",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6102,name:"寒冰箭",sp_name:"arrow_blue",AtkedType:AtkedType.ice,path:"3060",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:1,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:1,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成200%攻击的伤害,20%几率冰冻敌人" buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成200%攻击的伤害,20%几率冰冻敌人"
}, },
6103: { 6103: {
uuid:6103,name:"治疗",sp_name:"heath_small",AtkedType:AtkedType.atked,path:"3056",TGroup:TGroup.Team,SType:SType.heal,act:"max",DTType:DTType.single, uuid:6103,name:"治疗",sp_name:"heath_small",AtkedType:AtkedType.atked,path:"3056",TGroup:TGroup.Team,SType:SType.heal,act:"max",DTType:DTType.single,
ap:0,cd:5,in:0,t_num:1,hit_num:1,hit:0,hitcd:0,speed:0,cost:10,with:0, ap:0,cd:5,t_num:1,hit_num:1,hit:0,hitcd:0,speed:0,cost:10,with:0,
buffs:[],debuffs:[],info:"回复最前排队友10%最大生命值的生命" buffs:[],debuffs:[],info:"回复最前排队友10%最大生命值的生命"
}, },
6104: { 6104: {
uuid:6104,name:"烈焰斩击",sp_name:"max_fireatk",AtkedType:AtkedType.fire,path:"3036",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6104,name:"烈焰斩击",sp_name:"max_fireatk",AtkedType:AtkedType.fire,path:"3036",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,cost:10,with:0, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.2,speed:720,cost:10,with:0,
buffs:[],debuffs:[],info:"向最前方敌人扔出石斧,造成100%攻击的伤害" buffs:[],debuffs:[],info:"向最前方敌人扔出石斧,造成100%攻击的伤害"
}, },
6105: { 6105: {
uuid:6105,name:"烈火护盾",sp_name:"max_firedun",AtkedType:AtkedType.atked,path:"3061",TGroup:TGroup.Ally,SType:SType.damage,act:"atk",DTType:DTType.range, uuid:6105,name:"烈火护盾",sp_name:"max_firedun",AtkedType:AtkedType.atked,path:"3061",TGroup:TGroup.Ally,SType:SType.damage,act:"atk",DTType:DTType.range,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:1,speed:80,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:1,speed:80,cost:10,with:90,
buffs:[],debuffs:[],info:"召唤烈焰保护英雄,持续10秒,每秒对范围内的敌人造成100%伤害" buffs:[],debuffs:[],info:"召唤烈焰保护英雄,持续10秒,每秒对范围内的敌人造成100%伤害"
}, },
6106: { 6106: {
uuid:6106,name:"龙卷风",sp_name:"bwind",AtkedType:AtkedType.wind,path:"3065",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6106,name:"龙卷风",sp_name:"bwind",AtkedType:AtkedType.wind,path:"3065",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:1,speed:360,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:1,speed:360,cost:10,with:90,
buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成200%攻击的伤害,50%几率击退敌人" buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成200%攻击的伤害,50%几率击退敌人"
}, },
@@ -440,85 +378,85 @@ export const SkillSet: Record<number, SkillConfig> = {
6107: { 6107: {
uuid:6107,name:"烈焰射击",sp_name:"arrow_yellow",AtkedType:AtkedType.fire,path:"3014",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single, uuid:6107,name:"烈焰射击",sp_name:"arrow_yellow",AtkedType:AtkedType.fire,path:"3014",TGroup:TGroup.Enemy,SType:SType.damage,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成200%攻击的伤害,20%几率眩晕敌人" buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成200%攻击的伤害,20%几率眩晕敌人"
}, },
6108: { 6108: {
uuid:6108,name:"火墙",sp_name:"max_fwall",AtkedType:AtkedType.atked,path:"3040",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range, uuid:6108,name:"火墙",sp_name:"max_fwall",AtkedType:AtkedType.atked,path:"3040",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range,
ap:50,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:1,speed:720,cost:10,with:90, ap:50,cd:5,t_num:1,hit_num:1,hit:1,hitcd:1,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"在最前方敌人位置,召唤一堵火墙,持续10秒,每秒造成50%攻击伤害" buffs:[],debuffs:[],info:"在最前方敌人位置,召唤一堵火墙,持续10秒,每秒造成50%攻击伤害"
}, },
6109: { 6109: {
uuid:6109,name:"冰刺",sp_name:"icez",AtkedType:AtkedType.atked,path:"3049",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range, uuid:6109,name:"冰刺",sp_name:"icez",AtkedType:AtkedType.atked,path:"3049",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range,
ap:300,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:300,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"在最前方敌人位置,召唤冰刺攻击敌人,造成200%攻击的伤害,20%几率冰冻敌人" buffs:[],debuffs:[],info:"在最前方敌人位置,召唤冰刺攻击敌人,造成200%攻击的伤害,20%几率冰冻敌人"
}, },
6110: { 6110: {
uuid:6110,name:"潮汐",sp_name:"watert",AtkedType:AtkedType.atked,path:"3070",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range, uuid:6110,name:"潮汐",sp_name:"watert",AtkedType:AtkedType.atked,path:"3070",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"在最前方敌人位置,召唤水柱攻击敌人,每秒造成100%攻击的伤害,50%几率击退敌人" buffs:[],debuffs:[],info:"在最前方敌人位置,召唤水柱攻击敌人,每秒造成100%攻击的伤害,50%几率击退敌人"
}, },
6111: { 6111: {
uuid:6111,name:"陨石术",sp_name:"max_yunshi",AtkedType:AtkedType.fire,path:"3123",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range, uuid:6111,name:"陨石术",sp_name:"max_yunshi",AtkedType:AtkedType.fire,path:"3123",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range,
ap:500,cd:5,in:0,t_num:1,hit_num:0,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:500,cd:5,t_num:1,hit_num:0,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"在最前方敌人位置,召唤陨石攻击敌人,造成500%攻击的伤害" buffs:[],debuffs:[],info:"在最前方敌人位置,召唤陨石攻击敌人,造成500%攻击的伤害"
}, },
6112: { 6112: {
uuid:6112,name:"冰墙",sp_name:"icet",AtkedType:AtkedType.atked,path:"3050",TGroup:TGroup.Enemy,SType:SType.damage,act:"max",DTType:DTType.range, uuid:6112,name:"冰墙",sp_name:"icet",AtkedType:AtkedType.atked,path:"3050",TGroup:TGroup.Enemy,SType:SType.damage,act:"max",DTType:DTType.range,
ap:400,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:400,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"在最前方敌人位置,召唤冰墙攻击敌人,造成200%攻击的伤害,50%几率击退敌人" buffs:[],debuffs:[],info:"在最前方敌人位置,召唤冰墙攻击敌人,造成200%攻击的伤害,50%几率击退敌人"
}, },
6113: { 6113: {
uuid:6113,name:"剑雨",sp_name:"max_jianyu",AtkedType:AtkedType.fire,path:"3123",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range, uuid:6113,name:"剑雨",sp_name:"max_jianyu",AtkedType:AtkedType.fire,path:"3123",TGroup:TGroup.Ally,SType:SType.damage,act:"max",DTType:DTType.range,
ap:500,cd:5,in:0,t_num:1,hit_num:0,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:500,cd:5,t_num:1,hit_num:0,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"在最前方敌人位置,召唤陨石攻击敌人,造成500%攻击的伤害" buffs:[],debuffs:[],info:"在最前方敌人位置,召唤陨石攻击敌人,造成500%攻击的伤害"
}, },
//召唤取消 //召唤取消
// 6031:{uuid:6031,name:"召唤骷髅",sp_name:"zhaohuan",AtkedType:AtkedType.atked,path:"3018", // 6031:{uuid:6031,name:"召唤骷髅",sp_name:"zhaohuan",AtkedType:AtkedType.atked,path:"3018",
// TGroup:TGroup.Self,SType:SType.zhaohuan,act:"max",DTType:DTType.single,fname:"max_blue",flash:true,with:90, // TGroup:TGroup.Self,SType:SType.zhaohuan,act:"max",DTType:DTType.single,fname:"max_blue",flash:true,with:90,
// debuff:0,deV:0,deC:0,deR:100,in:0.8,ap:70,cd:60,in:0,t_num:1,hit_num:1,hit:1,hitcd:1,speed:720,hero:5221,cost:10,info:"召唤一个骷髅战士为我方而战"}, // debuff:0,deV:0,deC:0,deR:100,in:0.8,ap:70,cd:60,t_num:1,hit_num:1,hit:1,hitcd:1,speed:720,hero:5221,cost:10,info:"召唤一个骷髅战士为我方而战"},
// ========== 超必杀 ========== 6200-6299 // ========== 超必杀 ========== 6200-6299
6201: { 6201: {
uuid:6201,name:"满天火雨",sp_name:"atk_fires",AtkedType:AtkedType.atked,path:"3101",TGroup:TGroup.Ally,SType:SType.damage,act:"atk",DTType:DTType.range, uuid:6201,name:"满天火雨",sp_name:"atk_fires",AtkedType:AtkedType.atked,path:"3101",TGroup:TGroup.Ally,SType:SType.damage,act:"atk",DTType:DTType.range,
ap:100,cd:5,in:2,t_num:1,hit_num:1,hit:5,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:5,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"在最前方敌人位置,召唤陨石攻击敌人,造成500%攻击的伤害" buffs:[],debuffs:[],info:"在最前方敌人位置,召唤陨石攻击敌人,造成500%攻击的伤害"
}, },
6202: { 6202: {
uuid:6202,name:"龙卷风爆",sp_name:"bwind",AtkedType:AtkedType.atked,path:"3069",TGroup:TGroup.Ally,SType:SType.damage,act:"atk",DTType:DTType.range, uuid:6202,name:"龙卷风爆",sp_name:"bwind",AtkedType:AtkedType.atked,path:"3069",TGroup:TGroup.Ally,SType:SType.damage,act:"atk",DTType:DTType.range,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:1,speed:360,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:1,speed:360,cost:10,with:90,
buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成200%攻击的伤害,50%几率击退敌人" buffs:[],debuffs:[],info:"召唤大火球攻击前方所有敌人,造成200%攻击的伤害,50%几率击退敌人"
}, },
6203: { 6203: {
uuid:6203,name:"大潮汐",sp_name:"watert",AtkedType:AtkedType.atked,path:"3070",TGroup:TGroup.Ally,SType:SType.damage,act:"atk",DTType:DTType.range, uuid:6203,name:"大潮汐",sp_name:"watert",AtkedType:AtkedType.atked,path:"3070",TGroup:TGroup.Ally,SType:SType.damage,act:"atk",DTType:DTType.range,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"召唤水柱攻击敌人,每秒造成100%攻击的伤害,50%几率击退敌人" buffs:[],debuffs:[],info:"召唤水柱攻击敌人,每秒造成100%攻击的伤害,50%几率击退敌人"
}, },
// ==========增强型技能,被动技能,========== 6300-6399 // ==========增强型技能,被动技能,========== 6300-6399
6301: { 6301: {
uuid:6301,name:"攻击生命强化Ⅰ",sp_name:"max_ap",AtkedType:AtkedType.atked,path:"3065",TGroup:TGroup.Ally,SType:SType.buff,act:"atk",DTType:DTType.single, uuid:6301,name:"攻击生命强化Ⅰ",sp_name:"max_ap",AtkedType:AtkedType.atked,path:"3065",TGroup:TGroup.Ally,SType:SType.buff,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"增加20%攻击力和生命值" buffs:[],debuffs:[],info:"增加20%攻击力和生命值"
}, },
6302: { 6302: {
uuid:6302,name:"攻击生命强化Ⅱ",sp_name:"max_ap",AtkedType:AtkedType.atked,path:"3093",TGroup:TGroup.Ally,SType:SType.buff,act:"atk",DTType:DTType.single, uuid:6302,name:"攻击生命强化Ⅱ",sp_name:"max_ap",AtkedType:AtkedType.atked,path:"3093",TGroup:TGroup.Ally,SType:SType.buff,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"增加40%攻击力和生命值" buffs:[],debuffs:[],info:"增加40%攻击力和生命值"
}, },
6303: { 6303: {
uuid:6303,name:"攻击生命强化Ⅲ",sp_name:"max_ap",AtkedType:AtkedType.atked,path:"3065",TGroup:TGroup.Ally,SType:SType.buff,act:"atk",DTType:DTType.single, uuid:6303,name:"攻击生命强化Ⅲ",sp_name:"max_ap",AtkedType:AtkedType.atked,path:"3065",TGroup:TGroup.Ally,SType:SType.buff,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"增加60%攻击力和生命值" buffs:[],debuffs:[],info:"增加60%攻击力和生命值"
}, },
6304: { 6304: {
uuid:6304,name:"攻击生命强化Ⅳ",sp_name:"max_ap",AtkedType:AtkedType.atked,path:"3065",TGroup:TGroup.Ally,SType:SType.buff,act:"atk",DTType:DTType.single, uuid:6304,name:"攻击生命强化Ⅳ",sp_name:"max_ap",AtkedType:AtkedType.atked,path:"3065",TGroup:TGroup.Ally,SType:SType.buff,act:"atk",DTType:DTType.single,
ap:100,cd:5,in:0,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90, ap:100,cd:5,t_num:1,hit_num:1,hit:1,hitcd:0.3,speed:720,cost:10,with:90,
buffs:[],debuffs:[],info:"增加80%攻击力和生命值" buffs:[],debuffs:[],info:"增加80%攻击力和生命值"
}, },

View File

@@ -1,30 +1,8 @@
import { v3 } from "cc" import { v3 } from "cc"
import { FacSet, QualitySet } from "./BoxSet" import { FacSet } from "./BoxSet"
import { smc } from "../SingletonModuleComp" import { smc } from "../SingletonModuleComp"
import { BuffConf, DbuffConf } from "./SkillSet" import { BuffConf, DbuffConf } from "./SkillSet"
import { debuff } from "../../skills/debuff" import { debuff } from "../../skills/debuff"
/**
* kind 1:烈焰 2:寒冰 3:自然 4:暗影 5:神圣
**/
// export enum HeroKind {
// /**
// * 词条解释:
// * 烈焰:攻击带击退效果
// * 寒冰:攻击带速度效果
// * 自然:攻击偷取生命
// * 暗影:攻击偷取攻击
// * 神圣:攻击带2倍伤害
// * */
// fire = 1,
// water = 2,
// nature = 3,
// shadow = 4,
// holy = 5,
// }
/**
* 词条解释:
* 0:战士 1:远程 2:法师
* * */
export enum AttrSet { export enum AttrSet {
ATTR_MAX = 85, ATTR_MAX = 85,
@@ -43,13 +21,6 @@ export const HTypeName ={
3:"辅助", 3:"辅助",
4:"刺客", 4:"刺客",
} }
/**
* 解锁英雄所需物品
* 绿色:铜钥匙*100 item:1006 num:100
* 蓝色:银钥匙*200 item:1007 num:200
* 紫色:金钥匙*100 item:1008 num:100
* 橙色:金钥匙*100 item:1009 num:100
*/
//fac:FacSet.HERO //fac:FacSet.HERO
export const getHeroList = (quality:number=0)=>{ export const getHeroList = (quality:number=0)=>{
@@ -59,14 +30,14 @@ export const getHeroList = (quality:number=0)=>{
}); });
// 分离拥有和未拥有的英雄 // 分离拥有和未拥有的英雄
const ownedHeros = filteredHeros.filter(item => smc.heros[item.uuid]); const ownedHeros = filteredHeros.filter(item => smc.heros.some(hero => hero.uuid === item.uuid));
const unownedHeros = filteredHeros.filter(item => !smc.heros[item.uuid]); const unownedHeros = filteredHeros.filter(item => !smc.heros.some(hero => hero.uuid === item.uuid));
// 合并列表:拥有的在前,未拥有的在后 // 合并列表:拥有的在前,未拥有的在后
return [...ownedHeros, ...unownedHeros].map(item => item.uuid); return [...ownedHeros, ...unownedHeros].map(item => item.uuid);
} }
//fac:FacSet.MON //fac:FacSet.MON
export const getMonList = (quality:number=0)=>{ export const getMonList = ()=>{
return Object.values(HeroInfo).filter(item=>{ return Object.values(HeroInfo).filter(item=>{
const facMatch = item.fac === FacSet.MON; const facMatch = item.fac === FacSet.MON;
return facMatch ; return facMatch ;
@@ -100,7 +71,7 @@ export enum HeroUpSet {
} }
export interface heroInfo{ export interface heroInfo{
uuid:number, name:string, path:string,fac:FacSet,kind:QualitySet,type:HType, hp:number,mp:number,map:number, def:number, ap:number,dis:number, cd:number,speed:number, uuid:number, name:string, path:string,fac:FacSet,kind:number,type:HType, hp:number,mp:number,map:number, def:number, ap:number,dis:number, cd:number,speed:number,
lv:number,skills:number[], buff:BuffConf[], debuff:DbuffConf[], info:string lv:number,skills:number[], buff:BuffConf[], debuff:DbuffConf[], info:string
} }
@@ -205,4 +176,3 @@ export const HeroInfo: Record<number, heroInfo> = {
// buff:[],debuff:[],info:"精英怪物-战士型"}, // buff:[],debuff:[],info:"精英怪物-战士型"},
}; };

View File

@@ -30,7 +30,7 @@ export class Monster extends ecs.Entity {
} }
/** 加载角色 */ /** 加载角色 */
load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001,is_boss:boolean=false,is_call:boolean=false) { load(pos: Vec3 = Vec3.ZERO,scale:number = 1,uuid:number=1001,is_boss:boolean=false,is_call:boolean=false, strengthMultiplier: number = 1.0) {
scale=-1 scale=-1
let box_group=BoxSet.MONSTER let box_group=BoxSet.MONSTER
// console.log("mon load",uuid) // console.log("mon load",uuid)
@@ -45,7 +45,7 @@ export class Monster extends ecs.Entity {
const collider = node.getComponent(BoxCollider2D); const collider = node.getComponent(BoxCollider2D);
if (collider) collider.enabled = false; // 先禁用 // 延迟一帧启用碰撞体 if (collider) collider.enabled = false; // 先禁用 // 延迟一帧启用碰撞体
node.setPosition(pos) node.setPosition(pos)
this.hero_init(uuid,node,scale,box_group,is_boss,is_call) this.hero_init(uuid,node,scale,box_group,is_boss,is_call, strengthMultiplier)
oops.message.dispatchEvent("monster_load",this) oops.message.dispatchEvent("monster_load",this)
// 初始化移动参数 // 初始化移动参数
@@ -62,7 +62,7 @@ export class Monster extends ecs.Entity {
node.parent = scene.entityLayer!.node! node.parent = scene.entityLayer!.node!
node.setPosition(pos) node.setPosition(pos)
} }
hero_init(uuid:number=1001,node:Node,scale:number=1,box_group=BoxSet.HERO,is_boss:boolean=false,is_call:boolean=false) { hero_init(uuid:number=1001,node:Node,scale:number=1,box_group=BoxSet.HERO,is_boss:boolean=false,is_call:boolean=false, strengthMultiplier: number = 1.0) {
var hv = node.getComponent(HeroViewComp)!; var hv = node.getComponent(HeroViewComp)!;
@@ -80,9 +80,10 @@ export class Monster extends ecs.Entity {
hv.hero_uuid= uuid; hv.hero_uuid= uuid;
hv.hero_name= hero.name; hv.hero_name= hero.name;
// 初始化基础属性 // 初始化基础属性,并根据强度倍率调整
const baseHp = hero.hp; const baseHp = Math.floor(hero.hp * strengthMultiplier);
const baseAp = hero.ap; const baseAp = Math.floor(hero.ap * strengthMultiplier);
const baseDef = Math.floor(hero.def * strengthMultiplier);
for(let i=0;i<hero.skills.length;i++){ for(let i=0;i<hero.skills.length;i++){
let skill={ uuid:SkillSet[hero.skills[i]].uuid, cd_max:SkillSet[hero.skills[i]].cd,cost:SkillSet[hero.skills[i]].cost,cd:0 } let skill={ uuid:SkillSet[hero.skills[i]].uuid, cd_max:SkillSet[hero.skills[i]].cd,cost:SkillSet[hero.skills[i]].cost,cd:0 }
@@ -90,7 +91,7 @@ export class Monster extends ecs.Entity {
} }
hv.base_ap=baseAp hv.base_ap=baseAp
hv.base_map=hero.mp hv.base_map=hero.mp
hv.base_def=hero.def hv.base_def=baseDef
hv.base_hp=baseHp hv.base_hp=baseHp
hv.base_mp=hero.mp hv.base_mp=hero.mp
hv.hp=hv.base_hp hv.hp=hv.base_hp

View File

@@ -12,8 +12,8 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { UIID } from "../common/config/GameUIConfig"; import { UIID } from "../common/config/GameUIConfig";
import { LoadingViewComp } from "./view/LoadingViewComp"; import { LoadingViewComp } from "./view/LoadingViewComp";
import { smc } from "../common/SingletonModuleComp"; import { smc } from "../common/SingletonModuleComp";
import { WxCloudApi, UserGameData } from "../wx_clound_client_api/WxCloudApi"; import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
import { GameDataSyncManager } from "../common/GameDataSyncManager";
import { Test } from "../common/Test"; import { Test } from "../common/Test";
// import {data} from "../data/data"; // import {data} from "../data/data";
@@ -136,12 +136,26 @@ export class Initialize extends ecs.Entity {
const loginResult = await WxCloudApi.login(); const loginResult = await WxCloudApi.login();
const response = loginResult.result; const response = loginResult.result;
if (response.code === 200) { if (loginResult.result.code === 200) {
console.log("[Initialize]: 云端登录成功"); console.log("[Initialize]: 云端登录成功");
const cloudData = response.data; const cloudData = loginResult.result.data;
try {
// 直接覆盖基础游戏数据
if (cloudData.openid) {
smc.openid=cloudData.openid
}
// 直接覆盖出战英雄配置
if (cloudData.game_data) {
let gameDate=cloudData.game_data
if( gameDate.gold ) smc.vmdata.gold=gameDate.gold
if( gameDate.heros ) smc.heros=gameDate.heros
if( gameDate.fight_hero ) smc.fight_hero=gameDate.fight_hero
}
} catch (error) {
console.error(`[SMC]: 数据覆盖失败:`, error);
}
// 3. 用云端数据覆盖本地数据
GameDataSyncManager.getInstance().overrideLocalDataWithRemote(cloudData, "云端");
} else { } else {
console.warn("[Initialize]: 云端登录失败:", response.msg); console.warn("[Initialize]: 云端登录失败:", response.msg);
// 登录失败时使用本地数据 游戏需要退出 // 登录失败时使用本地数据 游戏需要退出
@@ -160,10 +174,9 @@ export class Initialize extends ecs.Entity {
private async loadFromLocalDebug() { private async loadFromLocalDebug() {
try { try {
// 使用本地调试API模拟云端接口 // 使用本地调试API模拟云端接口
const loginResult = new Test().load_data_from_local()
// 用本地调试数据覆盖客户端数据 // 用本地调试数据覆盖客户端数据
GameDataSyncManager.getInstance().overrideLocalDataWithRemote(loginResult, "本地调试");
} catch (error) { } catch (error) {
console.error("[Initialize]: 本地调试数据加载异常:", error); console.error("[Initialize]: 本地调试数据加载异常:", error);
} }

View File

@@ -18,7 +18,7 @@ export class HInfoComp extends Component {
this.ap_node=this.node.getChildByName("info").getChildByName("base").getChildByName("ap").getChildByName("num") this.ap_node=this.node.getChildByName("info").getChildByName("base").getChildByName("ap").getChildByName("num")
this.hp_node=this.node.getChildByName("info").getChildByName("base").getChildByName("hp").getChildByName("num") this.hp_node=this.node.getChildByName("info").getChildByName("base").getChildByName("hp").getChildByName("num")
this.def_node=this.node.getChildByName("info").getChildByName("base").getChildByName("def").getChildByName("num") this.def_node=this.node.getChildByName("info").getChildByName("base").getChildByName("def").getChildByName("num")
this.h_uuid=smc.fight_heros[0] this.h_uuid=smc.fight_hero
this.update_data(this.h_uuid) this.update_data(this.h_uuid)
} }
update(deltaTime: number) { update(deltaTime: number) {

View File

@@ -42,8 +42,7 @@ export class MissionHeroCompComp extends CCComp {
// this.current_hero_uuid=0 // this.current_hero_uuid=0
smc.vmdata.mission_data.hero_num=0 smc.vmdata.mission_data.hero_num=0
// console.log("[MissionHeroComp]:fight_ready",smc.fight_heros,Object.keys(smc.fight_heros).length) // console.log("[MissionHeroComp]:fight_ready",smc.fight_heros,Object.keys(smc.fight_heros).length)
let heros:any = smc.fight_heros this.addHero(smc.fight_hero,false)
this.addHero(heros[0],false)
// for(let i=0;i<Object.keys(heros).length;i++){ // for(let i=0;i<Object.keys(heros).length;i++){
// if(heros[i]!=0){ // if(heros[i]!=0){
// // console.log("[MissionHeroComp]:fight_ready",heros[i]) // // console.log("[MissionHeroComp]:fight_ready",heros[i])

View File

@@ -9,7 +9,9 @@ import { GameEvent } from "../common/config/GameEvent";
import { import {
getStageMonsterConfigs, getStageMonsterConfigs,
MonsterType, MonsterType,
getStageType getStageType,
EventType,
getRandomEvent
} from "./RogueConfig"; } from "./RogueConfig";
import { MonModelComp } from "../hero/MonModelComp"; import { MonModelComp } from "../hero/MonModelComp";
@@ -24,7 +26,8 @@ export class MissionMonCompComp extends CCComp {
uuid: number, uuid: number,
position: number, position: number,
type: MonsterType, type: MonsterType,
level: number level: number,
strengthMultiplier: number
}> = []; }> = [];
private isSpawning: boolean = false;// 是否正在生成怪物 private isSpawning: boolean = false;// 是否正在生成怪物
private spawnInterval: number = 0.1; // 每个怪物生成间隔时间 private spawnInterval: number = 0.1; // 每个怪物生成间隔时间
@@ -32,6 +35,8 @@ export class MissionMonCompComp extends CCComp {
private spawnCount: number = 0; // 召唤计数器 private spawnCount: number = 0; // 召唤计数器
private pauseInterval: number = 5.0; // 暂停间隔时间5秒 private pauseInterval: number = 5.0; // 暂停间隔时间5秒
private isPausing: boolean = false; // 是否正在暂停 private isPausing: boolean = false; // 是否正在暂停
private currentEvent: EventType | null = null; // 当前关卡的随机事件
private eventProcessed: boolean = false; // 事件是否已处理
onLoad(){ onLoad(){
@@ -54,6 +59,12 @@ export class MissionMonCompComp extends CCComp {
protected update(dt: number): void { protected update(dt: number): void {
if(!smc.mission.play||smc.mission.pause) return if(!smc.mission.play||smc.mission.pause) return
// 处理随机事件
if (this.currentEvent && !this.eventProcessed) {
this.processRandomEvent();
this.eventProcessed = true;
}
// 处理刷怪队列 // 处理刷怪队列
if (this.monsterQueue.length > 0 && !this.isSpawning) { if (this.monsterQueue.length > 0 && !this.isSpawning) {
this.spawnTimer += dt; this.spawnTimer += dt;
@@ -90,16 +101,51 @@ export class MissionMonCompComp extends CCComp {
this.spawnCount = 0; this.spawnCount = 0;
this.isPausing = false; this.isPausing = false;
this.spawnTimer = 0; this.spawnTimer = 0;
this.eventProcessed = false;
const currentStage = smc.data.mission; const currentStage = smc.data.mission;
// 使用新的肉鸽关卡配置 // 使用新的肉鸽关卡配置
let level=smc.vmdata.mission_data.level let level=smc.vmdata.mission_data.level
const stageType = getStageType(currentStage,level); const stageType = getStageType(currentStage,level);
// 检查是否为事件关卡
if (stageType === "event") {
this.currentEvent = getRandomEvent();
} else {
this.currentEvent = null;
}
const monsterConfigs = getStageMonsterConfigs(currentStage,level); const monsterConfigs = getStageMonsterConfigs(currentStage,level);
// console.log(`[MissionMonComp]:第${currentStage}关 - ${stageType}类型,怪物数量: ${monsterConfigs.length}`); // console.log(`[MissionMonComp]:第${currentStage}关 - ${stageType}类型,怪物数量: ${monsterConfigs.length}`);
this.generateMonstersFromStageConfig(monsterConfigs); this.generateMonstersFromStageConfig(monsterConfigs);
} }
// 处理随机事件
private processRandomEvent() {
if (!this.currentEvent) return;
switch (this.currentEvent) {
case EventType.TREASURE:
// 发送获得奖励事件
smc.vmdata.mission_data.gold += 50; // 增加50金币
// 可以触发UI提示
// oops.message.dispatchEvent("event_treasure");
break;
case EventType.TRAP:
// 对玩家造成伤害
// 这里可以实现对玩家英雄造成伤害的逻辑
// oops.message.dispatchEvent("event_trap");
break;
case EventType.BUFF:
// 给玩家增加临时增益效果
// oops.message.dispatchEvent("event_buff");
break;
case EventType.DEBUFF:
// 给玩家增加临时减益效果
// oops.message.dispatchEvent("event_debuff");
break;
}
}
// 根据新的关卡配置生成怪物 // 根据新的关卡配置生成怪物
private generateMonstersFromStageConfig(monsterConfigs: any[]) { private generateMonstersFromStageConfig(monsterConfigs: any[]) {
@@ -114,7 +160,7 @@ export class MissionMonCompComp extends CCComp {
// 为每个怪物配置生成怪物 // 为每个怪物配置生成怪物
monsterConfigs.forEach((monsterConfig: any, index: number) => { monsterConfigs.forEach((monsterConfig: any, index: number) => {
const { uuid, type } = monsterConfig; const { uuid, type, strengthMultiplier } = monsterConfig;
// 位置循环使用 (0-4) // 位置循环使用 (0-4)
const position = index % 5; const position = index % 5;
@@ -123,7 +169,8 @@ export class MissionMonCompComp extends CCComp {
uuid, uuid,
position, position,
type, type,
1 // 默认等级1 1, // 默认等级1
strengthMultiplier // 强度倍率
); );
}); });
@@ -135,13 +182,15 @@ export class MissionMonCompComp extends CCComp {
uuid: number, uuid: number,
position: number, position: number,
type: MonsterType, type: MonsterType,
level: number = 1 level: number = 1,
strengthMultiplier: number = 1.0
) { ) {
this.monsterQueue.push({ this.monsterQueue.push({
uuid: uuid, uuid: uuid,
position: position, position: position,
type: type, type: type,
level: level level: level,
strengthMultiplier: strengthMultiplier
}); });
} }
@@ -158,7 +207,8 @@ export class MissionMonCompComp extends CCComp {
monsterData.position, monsterData.position,
isBoss, isBoss,
false, false,
monsterData.level monsterData.level,
monsterData.strengthMultiplier
); );
// 增加召唤计数 // 增加召唤计数
@@ -172,14 +222,15 @@ export class MissionMonCompComp extends CCComp {
i: number = 0, i: number = 0,
is_boss: boolean = false, is_boss: boolean = false,
is_call: boolean = false, is_call: boolean = false,
lv: number = 1 lv: number = 1,
strengthMultiplier: number = 1.0
) { ) {
let mon = ecs.getEntity<Monster>(Monster); let mon = ecs.getEntity<Monster>(Monster);
let scale = -1; let scale = -1;
let pos: Vec3 = v3(MonSet[i].pos); let pos: Vec3 = v3(MonSet[i].pos);
// 生成怪物 // 生成怪物
mon.load(pos,scale,uuid,is_boss,is_call); mon.load(pos,scale,uuid,is_boss,is_call,strengthMultiplier);
} }
/** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */ /** 视图对象通过 ecs.Entity.remove(ModuleViewComp) 删除组件是触发组件处理自定义释放逻辑 */

View File

@@ -1,16 +1,33 @@
/** /**
* 肉鸽模式配置脚本 - 简化 * 肉鸽模式配置脚本 - 增强
* *
* 功能说明: * 功能说明:
* - 提供基础的刷怪配置:刷什么怪,刷多少怪 * - 提供基础的刷怪配置:刷什么怪,刷多少怪
* - 支持程序化关卡生成逻辑,每一关的怪物组合、数量和强度应随关卡进度递增而变化
* - 支持随机事件系统
* *
* @author 游戏开发团队 * @author 游戏开发团队
* @version 1.0 简化 * @version 2.0 增强
* @date 2025-10-19 * @date 2025-10-19
*/ */
import { QualitySet } from "../common/config/BoxSet"; import { getMonList, HeroInfo } from "../common/config/heroSet";
import { getMonList } from "../common/config/heroSet"; import { Attrs } from "../common/config/SkillSet";
// 精英怪物配置表
export const EliteMonsterList = [
5201, // 兽人战士
5202, // 兽人刺客
5203, // 兽人护卫
// 可以添加更多精英怪物UUID
];
// Boss怪物配置表
export const BossMonsterList = [
5201, // 兽人战士
5202, // 兽人刺客
// 可以添加更多Boss怪物UUID
];
/** /**
* 怪物类型枚举 * 怪物类型枚举
@@ -27,18 +44,29 @@ export enum MonsterType {
export enum StageType { export enum StageType {
NORMAL = "normal", // 普通关卡 NORMAL = "normal", // 普通关卡
ELITE = "elite", // 精英关卡 ELITE = "elite", // 精英关卡
BOSS = "boss" // Boss关卡 BOSS = "boss", // Boss关卡
EVENT = "event" // 事件关卡
} }
/** /**
* 关卡配置规则 - 只保留刷怪类型和数量 * 随机事件类型枚举
*/
export enum EventType {
TREASURE = "treasure", // 额外奖励
TRAP = "trap", // 陷阱伤害
BUFF = "buff", // 临时增益效果
DEBUFF = "debuff" // 临时减益效果
}
/**
* 关卡配置规则 - 增强版,支持怪物数量和强度随关卡递增
*/ */
export const StageConfigRules = { export const StageConfigRules = {
// 普通关卡 // 普通关卡
[StageType.NORMAL]: { [StageType.NORMAL]: {
description: "普通关卡", description: "普通关卡",
monsters: [ monsters: [
{ type: MonsterType.NORMAL, count: 1 } // 5个普通怪物 { type: MonsterType.NORMAL, count: 3, minCount: 2, maxCount: 6 } // 普通怪物数量随关卡递增
] ]
}, },
@@ -46,8 +74,8 @@ export const StageConfigRules = {
[StageType.ELITE]: { [StageType.ELITE]: {
description: "精英关卡", description: "精英关卡",
monsters: [ monsters: [
{ type: MonsterType.ELITE, count: 1 }, // 2个精英怪物 { type: MonsterType.ELITE, count: 2, minCount: 1, maxCount: 4 }, // 精英怪物
{ type: MonsterType.NORMAL, count: 1 } // 3个普通怪物 { type: MonsterType.NORMAL, count: 3, minCount: 2, maxCount: 5 } // 普通怪物
] ]
}, },
@@ -55,10 +83,44 @@ export const StageConfigRules = {
[StageType.BOSS]: { [StageType.BOSS]: {
description: "Boss关卡", description: "Boss关卡",
monsters: [ monsters: [
{ type: MonsterType.BOSS, count: 1 }, // 1个Boss怪物 { type: MonsterType.BOSS, count: 1, minCount: 1, maxCount: 1 }, // 1个Boss怪物
{ type: MonsterType.ELITE, count: 1 }, // 2个精英怪物 { type: MonsterType.ELITE, count: 2, minCount: 1, maxCount: 3 }, // 精英怪物
{ type: MonsterType.NORMAL, count: 1 } // 2个普通怪物 { type: MonsterType.NORMAL, count: 2, minCount: 1, maxCount: 4 } // 普通怪物
] ]
},
// 事件关卡
[StageType.EVENT]: {
description: "事件关卡",
monsters: [
{ type: MonsterType.NORMAL, count: 2, minCount: 1, maxCount: 4 } // 少量普通怪物
]
}
};
/**
* 随机事件配置
*/
export const EventConfig = {
[EventType.TREASURE]: {
description: "宝箱事件",
probability: 0.3, // 30%概率触发
effect: "获得额外奖励"
},
[EventType.TRAP]: {
description: "陷阱事件",
probability: 0.25, // 25%概率触发
effect: "受到一定伤害"
},
[EventType.BUFF]: {
description: "增益事件",
probability: 0.25, // 25%概率触发
effect: "获得临时增益效果"
},
[EventType.DEBUFF]: {
description: "减益事件",
probability: 0.2, // 20%概率触发
effect: "受到临时减益效果"
} }
}; };
@@ -69,6 +131,11 @@ export const StageConfigRules = {
* @returns 关卡类型 * @returns 关卡类型
*/ */
export function getStageType(stageNumber: number, level: number = 1): StageType { export function getStageType(stageNumber: number, level: number = 1): StageType {
// 每隔5关设置特殊事件关卡
if (stageNumber % 5 === 0 && level === 3) {
return StageType.EVENT;
}
// 第10关的特殊规则 // 第10关的特殊规则
if (stageNumber % 10 === 0) { if (stageNumber % 10 === 0) {
if (level === 5) { if (level === 5) {
@@ -89,6 +156,39 @@ export function getStageType(stageNumber: number, level: number = 1): StageType
} }
} }
/**
* 计算怪物数量,随关卡进度递增
* @param stageNumber 关卡号
* @param baseCount 基础数量
* @param minCount 最小数量
* @param maxCount 最大数量
* @returns 实际怪物数量
*/
export function calculateMonsterCount(stageNumber: number, baseCount: number, minCount: number, maxCount: number): number {
// 随关卡递增每5关增加1个怪物最多不超过最大数量
const increment = Math.floor(stageNumber / 5);
let count = baseCount + increment;
// 确保在最小和最大数量之间
count = Math.max(minCount, Math.min(maxCount, count));
return count;
}
/**
* 计算怪物强度倍率,随关卡进度递增
* @param stageNumber 关卡号
* @param level 等级
* @returns 强度倍率
*/
export function calculateMonsterStrengthMultiplier(stageNumber: number, level: number): number {
// 基础倍率基于关卡号和等级
const stageMultiplier = 1 + (stageNumber - 1) * 0.1; // 每关增加10%
const levelMultiplier = 1 + (level - 1) * 0.05; // 每级增加5%
return stageMultiplier * levelMultiplier;
}
/** /**
* 生成关卡配置 * 生成关卡配置
* @param stageNumber 关卡号从1开始 * @param stageNumber 关卡号从1开始
@@ -102,7 +202,15 @@ export function generateStageConfig(stageNumber: number, level: number = 1): Mon
// 根据配置生成怪物类型数组 // 根据配置生成怪物类型数组
rule.monsters.forEach(monsterGroup => { rule.monsters.forEach(monsterGroup => {
for (let i = 0; i < monsterGroup.count; i++) { // 计算实际怪物数量
const actualCount = calculateMonsterCount(
stageNumber,
monsterGroup.count,
monsterGroup.minCount,
monsterGroup.maxCount
);
for (let i = 0; i < actualCount; i++) {
monsterArray.push(monsterGroup.type); monsterArray.push(monsterGroup.type);
} }
}); });
@@ -111,21 +219,21 @@ export function generateStageConfig(stageNumber: number, level: number = 1): Mon
} }
/** /**
* 根据怪物类型获取对应品质的怪物UUID数组 * 根据怪物类型获取对应配置表中的怪物UUID数组
* @param monsterType 怪物类型 * @param monsterType 怪物类型
* @returns 怪物UUID数组 * @returns 怪物UUID数组
*/ */
export function getMonsterUUIDsByType(monsterType: MonsterType): number[] { export function getMonsterUUIDsByType(monsterType: MonsterType): number[] {
switch (monsterType) { switch (monsterType) {
case MonsterType.NORMAL: case MonsterType.NORMAL:
return getMonList(QualitySet.GREEN); // 绿色品质为普通怪物 // 普通怪物使用原有的getMonList方法
return getMonList();
case MonsterType.ELITE: case MonsterType.ELITE:
return getMonList(QualitySet.BLUE); // 蓝色品质为精英怪物 // 精英怪物使用精英配置表
return EliteMonsterList;
case MonsterType.BOSS: case MonsterType.BOSS:
// 紫色及以上品质为Boss怪物 // Boss怪物使用Boss配置表
const purpleMonsters = getMonList(QualitySet.PURPLE); return BossMonsterList;
const orangeMonsters = getMonList(QualitySet.ORANGE);
return [...purpleMonsters, ...orangeMonsters];
default: default:
return []; return [];
} }
@@ -154,7 +262,7 @@ export function getStageMonsterUUIDs(stageNumber: number, level: number = 1): nu
} }
/** /**
* 获取关卡怪物配置(包含UUID * 获取关卡怪物配置包含UUID和强度信息
* @param stageNumber 关卡号 * @param stageNumber 关卡号
* @param level 等级1-5 * @param level 等级1-5
* @returns 怪物配置数组 * @returns 怪物配置数组
@@ -163,6 +271,9 @@ export function getStageMonsterConfigs(stageNumber: number, level: number = 1) {
const monsterTypes = generateStageConfig(stageNumber, level); const monsterTypes = generateStageConfig(stageNumber, level);
const monsterConfigs = []; const monsterConfigs = [];
// 计算强度倍率
const strengthMultiplier = calculateMonsterStrengthMultiplier(stageNumber, level);
monsterTypes.forEach((monsterType, index) => { monsterTypes.forEach((monsterType, index) => {
const availableUUIDs = getMonsterUUIDsByType(monsterType); const availableUUIDs = getMonsterUUIDsByType(monsterType);
if (availableUUIDs.length > 0) { if (availableUUIDs.length > 0) {
@@ -172,10 +283,29 @@ export function getStageMonsterConfigs(stageNumber: number, level: number = 1) {
uuid: randomUUID, uuid: randomUUID,
type: monsterType, type: monsterType,
stageNumber: stageNumber, stageNumber: stageNumber,
level: level level: level,
strengthMultiplier: strengthMultiplier // 强度倍率
}); });
} }
}); });
return monsterConfigs; return monsterConfigs;
} }
/**
* 随机决定是否触发事件
* @returns 事件类型或null
*/
export function getRandomEvent(): EventType | null {
const random = Math.random();
let cumulativeProbability = 0;
for (const eventType in EventConfig) {
cumulativeProbability += EventConfig[eventType].probability;
if (random <= cumulativeProbability) {
return eventType as EventType;
}
}
return null; // 不触发事件
}

View File

@@ -9,8 +9,6 @@ const { ccclass, property } = _decorator;
export class topComp extends Component { export class topComp extends Component {
protected onLoad(): void { protected onLoad(): void {
oops.message.on(GameEvent.GOLD_UPDATE,this.onGoldUpdate,this); oops.message.on(GameEvent.GOLD_UPDATE,this.onGoldUpdate,this);
oops.message.on(GameEvent.DIAMOND_UPDATE,this.onDiamondUpdate,this);
oops.message.on(GameEvent.MEAT_UPDATE,this.onMeatUpdate,this);
this.update_all() this.update_all()
} }
start() { start() {
@@ -23,26 +21,12 @@ export class topComp extends Component {
.to(0.1,{scale:v3(1,1,1)}) .to(0.1,{scale:v3(1,1,1)})
.start() .start()
} }
onDiamondUpdate(event:string,data:any){
}
onMeatUpdate(event:string,data:any){
}
update_gold(gold:number){ update_gold(gold:number){
this.node.getChildByName("bar").getChildByName("gold").getChildByName("num").getComponent(Label).string=NumberFormatter.formatNumber(gold); this.node.getChildByName("bar").getChildByName("gold").getChildByName("num").getComponent(Label).string=NumberFormatter.formatNumber(gold);
} }
update_diamond(diamond:number){
}
update_meat(meat:number){
}
update_all(){ update_all(){
this.update_gold(smc.data.gold) this.update_gold(smc.vmdata.gold)
this.update_diamond(smc.data.diamond)
this.update_meat(smc.data.meat)
} }
update(deltaTime: number) { update(deltaTime: number) {

View File

@@ -2,7 +2,7 @@ import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/O
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS"; import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
import { BoxSet, FacSet } from "../common/config/BoxSet"; import { BoxSet, FacSet } from "../common/config/BoxSet";
import { HType } from "../common/config/heroSet"; import { HType } from "../common/config/heroSet";
import { BuffAttr, SkillSet } from "../common/config/SkillSet"; import {Attrs, SkillSet } from "../common/config/SkillSet";
import { smc } from "../common/SingletonModuleComp"; import { smc } from "../common/SingletonModuleComp";
import { HeroViewComp } from "../hero/HeroViewComp"; import { HeroViewComp } from "../hero/HeroViewComp";
import { SkillCom } from "./SkillCom"; import { SkillCom } from "./SkillCom";
@@ -82,20 +82,7 @@ export class Skill extends ecs.Entity {
group: caster.box_group, group: caster.box_group,
fac: caster.fac, fac: caster.fac,
// 技能数值 // 技能数值
ap: caster.Attrs[BuffAttr.AP],
caster_crit: caster.Attrs[BuffAttr.CRITICAL],
caster_crit_d: caster.Attrs[BuffAttr.CRITICAL_DMG],
puncture: caster.Attrs[BuffAttr.PUNCTURE],
puncture_damage: caster.Attrs[BuffAttr.PUNCTURE_DMG],
burn_count: caster.Attrs[BuffAttr.BURN_COUNT],
burn_value: caster.Attrs[BuffAttr.BURN_VALUE],
stun_time: caster.Attrs[BuffAttr.STUN_TIME],
stun_ratio: caster.Attrs[BuffAttr.STUN_RATIO],
frost_time: caster.Attrs[BuffAttr.FROST_TIME],
frost_ratio: caster.Attrs[BuffAttr.FROST_RATIO],
debuff_up: caster.Attrs[BuffAttr.DEBUFF_UP],
debuff_value: caster.Attrs[BuffAttr.DEBUFF_VALUE],
debuff_count: caster.Attrs[BuffAttr.DEBUFF_COUNT],
}); });
this.add(SComp); this.add(SComp);

View File

@@ -3,7 +3,7 @@ import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ec
import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp"; import { CCComp } from "../../../../extensions/oops-plugin-framework/assets/module/common/CCComp";
import { smc } from "../common/SingletonModuleComp"; import { smc } from "../common/SingletonModuleComp";
import { GameEvent } from "../common/config/GameEvent"; import { GameEvent } from "../common/config/GameEvent";
import { Attrs, AType, DTType, EType, SkillSet, SType, TGroup } from "../common/config/SkillSet"; import { Attrs, DTType, EType, SkillSet, SType, TGroup } from "../common/config/SkillSet";
import { BoxSet, FacSet } from "../common/config/BoxSet"; import { BoxSet, FacSet } from "../common/config/BoxSet";
import { HeroViewComp } from "../hero/HeroViewComp"; import { HeroViewComp } from "../hero/HeroViewComp";
import { BezierMove } from "../BezierMove/BezierMove"; import { BezierMove } from "../BezierMove/BezierMove";
@@ -85,50 +85,50 @@ export class SkillCom extends CCComp {
} }
let bm=this.node.getComponent(BezierMove) let bm=this.node.getComponent(BezierMove)
// //console.log(this.group +"技能 collider ",collider); // //console.log(this.group +"技能 collider ",collider);
switch(this.skillConfig.AType){ // switch(this.skillConfig.AType){
case AType.parabolic: // case AType.parabolic:
this.node.angle +=10 // this.node.angle +=10
// bm.speed=700 // // bm.speed=700
if(this.group==BoxSet.MONSTER) {bm.controlPointSide=-1 } // if(this.group==BoxSet.MONSTER) {bm.controlPointSide=-1 }
bm.rotationSmoothness=0.6 // bm.rotationSmoothness=0.6
bm.moveTo(this.targetPos) // bm.moveTo(this.targetPos)
break; // break;
case AType.linear: // case AType.linear:
let s_x=this.startPos.x // let s_x=this.startPos.x
let s_y=this.startPos.y // let s_y=this.startPos.y
let t_x=this.targetPos.x // let t_x=this.targetPos.x
let t_y=this.targetPos.y // let t_y=this.targetPos.y
// 设定目标x // // 设定目标x
this.targetPos.x = 400; // this.targetPos.x = 400;
if(this.group == BoxSet.MONSTER) { // if(this.group == BoxSet.MONSTER) {
bm.controlPointSide = -1; // bm.controlPointSide = -1;
this.targetPos.x = -400; // this.targetPos.x = -400;
} // }
// 计算斜率 // // 计算斜率
const k = (t_y - s_y) / (t_x - s_x); // const k = (t_y - s_y) / (t_x - s_x);
// 按直线公式计算新的y // // 按直线公式计算新的y
this.targetPos.y = k * (this.targetPos.x - s_x) + s_y; // this.targetPos.y = k * (this.targetPos.x - s_x) + s_y;
bm.controlPointOffset=0 // bm.controlPointOffset=0
bm.rotationSmoothness=0.6 // bm.rotationSmoothness=0.6
bm.moveTo(this.targetPos); // bm.moveTo(this.targetPos);
break; // break;
case AType.StartEnd: // case AType.StartEnd:
// 2段位移先升高然后移到目的地 // // 2段位移先升高然后移到目的地
this.node.setPosition(this.startPos.x > 360?300:this.startPos.x,0,0) // this.node.setPosition(this.startPos.x > 360?300:this.startPos.x,0,0)
this.do_anim() // this.do_anim()
break; // break;
case AType.fixedEnd: // case AType.fixedEnd:
this.node.setPosition(this.targetPos.x > 360?300:this.targetPos.x,0,0) // this.node.setPosition(this.targetPos.x > 360?300:this.targetPos.x,0,0)
this.do_anim() // this.do_anim()
break; // break;
case AType.fixedStart: // // case AType.fixedStart: //
if(this.s_uuid==6001){ // if(this.s_uuid==6001){
console.log("skillcom startPos",this.startPos) // console.log("skillcom startPos",this.startPos)
} // }
this.node.setPosition(this.startPos.x > 360?300:this.startPos.x,0,0) // this.node.setPosition(this.startPos.x > 360?300:this.startPos.x,0,0)
this.do_anim() // this.do_anim()
break; // break;
} // }
} }
@@ -170,12 +170,12 @@ export class SkillCom extends CCComp {
if(this.hit_count > 0 &&!is_range ){ if(this.hit_count > 0 &&!is_range ){
ap=ap*(50+this.puncture_damage)/100 ap=ap*(50+this.puncture_damage)/100
} }
target.do_atked(ap,this.caster_crit,this.caster_crit_d, // target.do_atked(ap,this.caster_crit,this.caster_crit_d,
this.burn_count,this.burn_value, // this.burn_count,this.burn_value,
this.stun_time,this.stun_ratio, // this.stun_time,this.stun_ratio,
this.frost_time,this.frost_ratio, // this.frost_time,this.frost_ratio,
this.skillConfig.AtkedType // this.skillConfig.AtkedType
) // ap 及暴击 属性已经在skill.ts 处理 // ) // ap 及暴击 属性已经在skill.ts 处理
// console.log("[SkillCom]:single_damage t:tp:rtp",this.node.position,this.targetPos,target.node.position) // console.log("[SkillCom]:single_damage t:tp:rtp",this.node.position,this.targetPos,target.node.position)
if(this.skillConfig.debuff>0){ if(this.skillConfig.debuff>0){
let debuff=this.skillConfig let debuff=this.skillConfig

View File

@@ -1,84 +1,11 @@
// 云函数返回类型定义 import { get } from "http";
export type CloudReturnType<T = any> = { export type CloudReturnType<T = any> = {
code: number, // 200成功,其他都是失败 code: number,// 200成功
msg?: string, // 消息信息 msg?:string,
data?: T, // 返回数据 data?:T
timestamp?: number, // 时间戳
version?: string, // 数据版本
execution_time?: number // 执行时间(ms)
} }
// 用户信息类型
export type UserInfo = {
user_id: string,
openid: string,
regist_time: number,
init_time?: number,
data_version?: string,
last_save_time?: number
}
// 完整用户数据类型
export type UserGameData = UserInfo & {
data: GameData,
fight_heros: FightHeros,
heros: Heros,
items: Items,
tals: Tals,
equips: Equips
}
// 基础游戏数据类型
export type GameData = {
score: number,
mission: number,
gold: number,
diamond: number,
meat: number,
exp: number,
}
// 出战英雄类型
export type FightHeros = {
[position: number]: number // 位置 -> 英雄ID
}
// 英雄数据类型
export type HeroData = {
uuid: number,
lv: number,
exp?: number,
star?: number,
power?: number
}
export type Heros = {
[heroId: number]: HeroData
}
// 库存类型
export type Items = {
[itemId: number]: number
}
export type Tals = {
[talId: number]: number
}
export type Equips = {
[equipId: number]: number
}
// 版本兼容性检查结果
export type VersionCompatibility = {
compatible: boolean,
needsUpgrade: boolean,
message: string
}
// 库存类型枚举
export type InventoryType = 'items' | 'tals' | 'equips'
export class WxCloudApi{ export class WxCloudApi{
/** /**
* @en init the cloud * @en init the cloud
@@ -91,14 +18,30 @@ export class WxCloudApi{
}); });
} }
// ==================== 认证相关接口 ====================
/** /**
* @en Login to the cloud * @en Login to the cloud
* @zh 用户登录,获取完整的用户和游戏数据 * @zh 登录云
* @return Promise<CloudCallFunctionResult<CloudReturnType<UserGameData>>> * @return 返回结果
* 参考:
* result.result = {
* code: number, // 200成功其他都是失败
* msg: string, // 如果失败,这里是失败原因等信息
* data: { // 成功才有
* openid: string, // 用户微信平台openid
* regist_time: number, // 时间戳,用户注册时间
* game_data: object, // 开发者自己保存的数据
* }
* }
* 如果这个泛型令你报错一般是因为你删了wx.api.d.ts导致请使用以下签名
* login():Promise<{result: CloudReturnType<{openid: string, regist_time: number, game_data: object}>}>
* 或者你是个“不拘小节”的老哥,可以用以下简洁版签名(参考上方的数据结构例子使用即可)
* login():Promise<any>
*/ */
public static async login(): Promise<CloudCallFunctionResult<CloudReturnType<UserGameData>>> { public static async login(): Promise<CloudCallFunctionResult<CloudReturnType<{
openid:string,
regist_time:number,
game_data:any
}>>>{
return await wx?.cloud.callFunction({ return await wx?.cloud.callFunction({
name: 'cocos_cloud', name: 'cocos_cloud',
data: { data: {
@@ -108,486 +51,29 @@ export class WxCloudApi{
} }
/** /**
* @en Get user basic info * @en Save game data to the cloud
* @zh 获取用户基本信息(不包含游戏数据) * @zh 把客户端数据写入云,此为全覆盖写入,请自行管理完整数据
* @return Promise<CloudCallFunctionResult<CloudReturnType<UserInfo>>> * @return 返回结果
*/ * 参考:
public static async getUserInfo(): Promise<CloudCallFunctionResult<CloudReturnType<UserInfo>>> { * result.result = {
return await wx?.cloud.callFunction({ * code: number, // 200成功其他都是失败
name: 'cocos_cloud', * msg: string, // 如果失败,这里是失败原因等信息
data: { * data: {
cmd: "user_info" * errMsg: "document.update:ok", // 数据库返回结果
} * stats: {
}); * updated: number, // 更新了几条数据正常是1
} * }
* }
/** * 如果这个泛型令你报错一般是因为你删了wx.api.d.ts导致请使用以下签名
* @en Check data version compatibility * save(gameData: any): Promise<{resoult:CloudReturnType}>
* @zh 检查数据版本兼容性 * 或者你是个“不拘小节”的老哥,可以用以下简洁版签名(参考上方的数据结构例子使用即可)
* @return Promise<CloudCallFunctionResult<CloudReturnType<{user_version: string, current_version: string, compatibility: VersionCompatibility}>>> * login():Promise<any>
*/
public static async checkVersion(): Promise<CloudCallFunctionResult<CloudReturnType<{
user_version: string,
current_version: string,
compatibility: VersionCompatibility,
init_time: number,
regist_time: number,
last_save_time: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "version"
}
});
}
/**
* @en Force upgrade user data
* @zh 强制升级用户数据结构
* @return Promise<CloudCallFunctionResult<CloudReturnType<{old_version: string, new_version: string, upgrade_time: number} & UserGameData>>>
*/
public static async upgradeUserData(): Promise<CloudCallFunctionResult<CloudReturnType<{
old_version: string,
new_version: string,
upgrade_time: number
} & UserGameData>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "upgrade"
}
});
}
// ==================== 基础游戏数据接口 ====================
/**
* @en Get game data
* @zh 获取基础游戏数据(金币、钻石、经验等)
* @return Promise<CloudCallFunctionResult<CloudReturnType<GameData>>>
*/
public static async getGameData(): Promise<CloudCallFunctionResult<CloudReturnType<GameData>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "data_get"
}
});
}
/**
* @en Update game data
* @zh 批量更新基础游戏数据
* @param data 要更新的数据
* @param merge 是否合并更新默认true
* @return Promise<CloudCallFunctionResult<CloudReturnType<GameData>>>
*/
public static async updateGameData(data: Partial<GameData>, merge: boolean = true): Promise<CloudCallFunctionResult<CloudReturnType<GameData>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "data_update",
data: data,
merge: merge
}
});
}
/**
* @en Add to game data field
* @zh 增加指定字段的数值
* @param field 字段名
* @param amount 增加的数量
* @return Promise<CloudCallFunctionResult<CloudReturnType<{field: string, old_value: number, new_value: number, change: number}>>>
*/
public static async addGameDataField(field:string, amount: number): Promise<CloudCallFunctionResult<CloudReturnType<{
field: string,
old_value: number,
new_value: number,
change: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "data_add",
field: field,
amount: amount
}
});
}
/**
* @en Spend game data field
* @zh 消耗指定字段的数值(会检查是否足够)
* @param field 字段名
* @param amount 消耗的数量
* @return Promise<CloudCallFunctionResult<CloudReturnType<{field: string, old_value: number, new_value: number, change: number}>>>
*/
public static async spendGameDataField(field:string|Record<string, number>, amount: number): Promise<CloudCallFunctionResult<CloudReturnType<{
field: string|Record<string, number>,
old_value: number,
new_value: number,
change: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "data_spend",
field: field,
amount: amount
}
});
}
/**
* @en Set game data field
* @zh 直接设置某个字段的值
* @param field 字段名
* @param value 新的值
* @return Promise<CloudCallFunctionResult<CloudReturnType<{field: string, old_value: any, new_value: any}>>>
*/
public static async setGameDataField(field: keyof GameData, value: any): Promise<CloudCallFunctionResult<CloudReturnType<{
field: string,
old_value: any,
new_value: any
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "data_set",
field: field,
value: value
}
});
}
/**
* @en Reset game data
* @zh 重置基础游戏数据为默认值
* @return Promise<CloudCallFunctionResult<CloudReturnType<GameData>>>
*/
public static async resetGameData(): Promise<CloudCallFunctionResult<CloudReturnType<GameData>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "data_reset"
}
});
}
// 便捷方法:金币操作
/**
* @en Add gold
* @zh 增加金币
* @param amount 金币数量
*/
public static async addGold(amount: number) {
return await this.addGameDataField('gold', amount);
}
/**
* @en Spend gold
* @zh 消耗金币
* @param amount 金币数量
*/
public static async spendGold(amount: number) {
return await this.spendGameDataField('gold', amount);
}
// 便捷方法:钻石操作
/**
* @en Add diamond
* @zh 增加钻石
* @param amount 钻石数量
*/
public static async addDiamond(amount: number) {
return await this.addGameDataField('diamond', amount);
}
/**
* @en Spend diamond
* @zh 消耗钻石
* @param amount 钻石数量
*/
public static async spendDiamond(amount: number) {
return await this.spendGameDataField('diamond', amount);
}
// ==================== 出战英雄接口 ====================
/**
* @en Get fight heros
* @zh 获取出战英雄配置
* @return Promise<CloudCallFunctionResult<CloudReturnType<FightHeros>>>
*/
public static async getFightHeros(): Promise<CloudCallFunctionResult<CloudReturnType<FightHeros>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "fight_heros_get"
}
});
}
/**
* @en Set fight hero
* @zh 设置指定位置的出战英雄
* @param position 出战位置 (0-4)
* @param heroId 英雄ID0表示移除
* @return Promise<CloudCallFunctionResult<CloudReturnType<{position: number, old_hero_id: number, new_hero_id: number}>>>
*/
public static async setFightHero(position: number, heroId: number): Promise<CloudCallFunctionResult<CloudReturnType<{
position: number,
old_hero_id: number,
new_hero_id: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "fight_hero_set",
position: position,
hero_id: heroId
}
});
}
/**
* @en Update fight heros
* @zh 批量更新出战英雄配置
* @param fightHeros 出战英雄配置对象
* @return Promise<CloudCallFunctionResult<CloudReturnType<FightHeros>>>
*/
public static async updateFightHeros(fightHeros: Partial<FightHeros>): Promise<CloudCallFunctionResult<CloudReturnType<FightHeros>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "fight_heros_update",
fight_heros: fightHeros
}
});
}
/**
* @en Get active fight heros
* @zh 获取当前出战的英雄列表(不包含空位)
* @return Promise<CloudCallFunctionResult<CloudReturnType<{active_heros: Array<{position: number, hero_id: number, hero_data: HeroData}>, total_count: number}>>>
*/
public static async getActiveFightHeros(): Promise<CloudCallFunctionResult<CloudReturnType<{
active_heros: Array<{
position: number,
hero_id: number,
hero_data: HeroData
}>,
total_count: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "fight_heros_active"
}
});
}
/**
* @en Swap fight heros
* @zh 交换两个出战位置的英雄
* @param position1 位置1 (0-4)
* @param position2 位置2 (0-4)
* @return Promise<CloudCallFunctionResult<CloudReturnType<{position1: number, position2: number, hero1_moved_to: number, hero2_moved_to: number}>>>
*/
public static async swapFightHeros(position1: number, position2: number): Promise<CloudCallFunctionResult<CloudReturnType<{
position1: number,
position2: number,
hero1_moved_to: number,
hero2_moved_to: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "fight_heros_swap",
position1: position1,
position2: position2
}
});
}
/**
* @en Reset fight heros
* @zh 重置出战英雄配置为默认值
* @return Promise<CloudCallFunctionResult<CloudReturnType<FightHeros>>>
*/
public static async resetFightHeros(): Promise<CloudCallFunctionResult<CloudReturnType<FightHeros>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "fight_heros_reset"
}
});
}
// ==================== 英雄管理接口 ====================
/**
* @en Get all heros
* @zh 获取用户拥有的所有英雄数据
* @return Promise<CloudCallFunctionResult<CloudReturnType<Heros>>>
*/
public static async getHeros(): Promise<CloudCallFunctionResult<CloudReturnType<Heros>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "heros_get"
}
});
}
/**
* @en Get single hero
* @zh 获取指定英雄的详细数据
* @param heroId 英雄ID
* @return Promise<CloudCallFunctionResult<CloudReturnType<HeroData>>>
*/
public static async getHero(heroId: number): Promise<CloudCallFunctionResult<CloudReturnType<HeroData>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "hero_get",
hero_id: heroId
}
});
}
/**
* @en Add new hero
* @zh 添加新英雄到用户库存
* @param heroId 英雄ID
* @param heroData 英雄数据(可选)
* @return Promise<CloudCallFunctionResult<CloudReturnType<HeroData>>>
*/
public static async addHero(heroId: number, heroData?: Partial<HeroData>): Promise<CloudCallFunctionResult<CloudReturnType<HeroData>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "hero_add",
hero_id: heroId,
hero_data: heroData
}
});
}
/**
* @en Update hero
* @zh 批量更新英雄的多个属性
* @param heroId 英雄ID
* @param updateData 要更新的属性
* @return Promise<CloudCallFunctionResult<CloudReturnType<{old_data: HeroData, new_data: HeroData}>>>
*/
public static async updateHero(heroId: number, updateData: Partial<HeroData>): Promise<CloudCallFunctionResult<CloudReturnType<{
old_data: HeroData,
new_data: HeroData
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "hero_update",
hero_id: heroId,
update_data: updateData
}
});
}
/**
* @en Set hero property
* @zh 设置英雄的单个属性值
* @param heroId 英雄ID
* @param property 属性名
* @param value 属性值
* @return Promise<CloudCallFunctionResult<CloudReturnType<{hero_id: number, property: string, old_value: any, new_value: any}>>>
*/
public static async setHeroProperty(heroId: number, property: keyof HeroData, value: any): Promise<CloudCallFunctionResult<CloudReturnType<{
hero_id: number,
property: string,
old_value: any,
new_value: any
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "hero_property_set",
hero_id: heroId,
property: property,
value: value
}
});
}
/**
* @en Level up hero
* @zh 英雄升级指定级数
* @param heroId 英雄ID
* @param levels 升级级数默认1级
* @return Promise<CloudCallFunctionResult<CloudReturnType<{hero_id: number, property: string, old_value: number, new_value: number}>>>
*/
public static async levelUpHero(heroId: number,levels: number = 1): Promise<CloudCallFunctionResult<CloudReturnType<{
hero_id: number,
property: string,
old_value: number,
new_value: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "hero_levelup",
hero_id: heroId,
levels: levels
}
});
}
/**
* @en Delete hero
* @zh 删除指定英雄(会检查是否在出战阵容中)
* @param heroId 英雄ID
* @return Promise<CloudCallFunctionResult<CloudReturnType<HeroData>>>
*/
public static async deleteHero(heroId: number): Promise<CloudCallFunctionResult<CloudReturnType<HeroData>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "hero_delete",
hero_id: heroId
}
});
}
/**
* @en Get owned hero IDs
* @zh 获取用户拥有的所有英雄ID
* @return Promise<CloudCallFunctionResult<CloudReturnType<{hero_ids: number[], total_count: number}>>>
*/
public static async getOwnedHeroIds(): Promise<CloudCallFunctionResult<CloudReturnType<{
hero_ids: number[],
total_count: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "heros_owned"
}
});
}
// ==================== 兼容旧接口 ====================
/**
* @en Save game data to the cloud (Legacy)
* @zh 把客户端数据写入云,此为全覆盖写入,请自行管理完整数据(兼容旧接口)
* @param gameData 游戏数据
* @return Promise<CloudCallFunctionResult<CloudReturnType<{results: any[]}>>>
*/ */
public static async save(gameData: any): Promise<CloudCallFunctionResult<CloudReturnType<{ public static async save(gameData: any): Promise<CloudCallFunctionResult<CloudReturnType<{
results: any[] errMsg: string,
status:{
updated: number
}
}>>> { }>>> {
return await wx?.cloud.callFunction({ return await wx?.cloud.callFunction({
name: 'cocos_cloud', name: 'cocos_cloud',
@@ -597,284 +83,12 @@ export class WxCloudApi{
} }
}); });
} }
public static async get(): Promise<CloudCallFunctionResult<CloudReturnType<{}>>> {
// ==================== 库存管理接口 (items, tals, equips) ====================
/**
* @en Get inventory
* @zh 获取指定类型的所有库存数据
* @param type 库存类型 ('items', 'tals', 'equips')
* @return Promise<CloudCallFunctionResult<CloudReturnType<Items | Tals | Equips>>>
*/
public static async getInventory(type: InventoryType): Promise<CloudCallFunctionResult<CloudReturnType<Items | Tals | Equips>>> {
return await wx?.cloud.callFunction({ return await wx?.cloud.callFunction({
name: 'cocos_cloud', name: 'cocos_cloud',
data: { data: {
cmd: "inventory_get", cmd: 'get',
type: type
} }
}); });
} }
/**
* @en Get inventory item
* @zh 获取指定物品的数量
* @param type 库存类型
* @param itemId 物品ID
* @return Promise<CloudCallFunctionResult<CloudReturnType<{item_id: number, count: number}>>>
*/
public static async getInventoryItem(type: InventoryType, itemId: number): Promise<CloudCallFunctionResult<CloudReturnType<{
item_id: number,
count: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "inventory_item_get",
type: type,
item_id: itemId
}
});
}
/**
* @en Add inventory item
* @zh 增加指定物品的数量
* @param type 库存类型
* @param itemId 物品ID
* @param count 添加数量
* @return Promise<CloudCallFunctionResult<CloudReturnType<{item_id: number, old_count: number, new_count: number, added: number}>>>
*/
public static async addInventoryItem(type: InventoryType, itemId: number, count: number): Promise<CloudCallFunctionResult<CloudReturnType<{
item_id: number,
old_count: number,
new_count: number,
added: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "inventory_item_add",
type: type,
item_id: itemId,
count: count
}
});
}
/**
* @en Consume inventory item
* @zh 消耗指定数量的物品(会检查是否足够)
* @param type 库存类型
* @param itemId 物品ID
* @param count 消耗数量
* @return Promise<CloudCallFunctionResult<CloudReturnType<{item_id: number, old_count: number, new_count: number, added: number}>>>
*/
public static async consumeInventoryItem(type: InventoryType, itemId: number, count: number): Promise<CloudCallFunctionResult<CloudReturnType<{
item_id: number,
old_count: number,
new_count: number,
added: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "inventory_item_consume",
type: type,
item_id: itemId,
count: count
}
});
}
/**
* @en Set inventory item
* @zh 直接设置物品的数量
* @param type 库存类型
* @param itemId 物品ID
* @param count 新的数量
* @return Promise<CloudCallFunctionResult<CloudReturnType<{item_id: number, old_count: number, new_count: number}>>>
*/
public static async setInventoryItem(type: InventoryType, itemId: number, count: number): Promise<CloudCallFunctionResult<CloudReturnType<{
item_id: number,
old_count: number,
new_count: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "inventory_item_set",
type: type,
item_id: itemId,
count: count
}
});
}
/**
* @en Update inventory
* @zh 批量更新多个物品的数量
* @param type 库存类型
* @param data 更新数据对象
* @param merge 是否合并更新默认true
* @return Promise<CloudCallFunctionResult<CloudReturnType<Items | Tals | Equips>>>
*/
public static async updateInventory(type: InventoryType, data: Partial<Items | Tals | Equips>, merge: boolean = true): Promise<CloudCallFunctionResult<CloudReturnType<Items | Tals | Equips>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "inventory_update",
type: type,
data: data,
merge: merge
}
});
}
/**
* @en Reset inventory
* @zh 重置指定类型的库存为默认值
* @param type 库存类型
* @return Promise<CloudCallFunctionResult<CloudReturnType<Items | Tals | Equips>>>
*/
public static async resetInventory(type: InventoryType): Promise<CloudCallFunctionResult<CloudReturnType<Items | Tals | Equips>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "inventory_reset",
type: type
}
});
}
/**
* @en Get owned items
* @zh 获取数量大于0的物品列表
* @param type 库存类型
* @return Promise<CloudCallFunctionResult<CloudReturnType<{owned_items: Array<{item_id: number, count: number}>, total_types: number}>>>
*/
public static async getOwnedItems(type: InventoryType): Promise<CloudCallFunctionResult<CloudReturnType<{
owned_items: Array<{
item_id: number,
count: number
}>,
total_types: number
}>>> {
return await wx?.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: "inventory_owned",
type: type
}
});
}
// ==================== 便捷方法:道具操作 ====================
/**
* @en Get items
* @zh 获取所有道具数据
*/
public static async getItems() {
return await this.getInventory('items');
}
/**
* @en Add item
* @zh 添加道具
* @param itemId 道具ID
* @param count 数量
*/
public static async addItem(itemId: number, count: number) {
return await this.addInventoryItem('items', itemId, count);
}
/**
* @en Consume item
* @zh 消耗道具
* @param itemId 道具ID
* @param count 数量
*/
public static async consumeItem(itemId: number, count: number) {
return await this.consumeInventoryItem('items', itemId, count);
}
// ==================== 便捷方法:天赋操作 ====================
/**
* @en Get talents
* @zh 获取所有天赋数据
*/
public static async getTalents() {
return await this.getInventory('tals');
}
/**
* @en Add talent
* @zh 添加天赋点
* @param talId 天赋ID
* @param count 数量
*/
public static async addTalent(talId: number, count: number) {
return await this.addInventoryItem('tals', talId, count);
}
/**
* @en Consume talent
* @zh 消耗天赋点
* @param talId 天赋ID
* @param count 数量
*/
public static async consumeTalent(talId: number, count: number) {
return await this.consumeInventoryItem('tals', talId, count);
}
// ==================== 便捷方法:装备操作 ====================
/**
* @en Get equipments
* @zh 获取所有装备数据
*/
public static async getEquipments() {
return await this.getInventory('equips');
}
/**
* @en Add equipment
* @zh 添加装备
* @param equipId 装备ID
* @param count 数量
*/
public static async addEquipment(equipId: number, count: number) {
return await this.addInventoryItem('equips', equipId, count);
}
/**
* @en Consume equipment
* @zh 消耗装备
* @param equipId 装备ID
* @param count 数量
*/
public static async consumeEquipment(equipId: number, count: number) {
return await this.consumeInventoryItem('equips', equipId, count);
}
// ==================== 其他便捷方法 ====================
/**
* @en Load game data (Legacy compatible)
* @zh 加载游戏数据(兼容旧接口)
*/
public static async load() {
return await this.login();
}
/**
* @en Get all game data at once
* @zh 一次性获取所有游戏数据
* @return Promise<CloudCallFunctionResult<CloudReturnType<UserGameData>>>
*/
public static async getAllGameData(): Promise<CloudCallFunctionResult<CloudReturnType<UserGameData>>> {
return await this.login();
}
} }

View File

@@ -1,920 +0,0 @@
# 游戏云函数 API 文档
## 📋 概述
本文档详细说明了游戏云函数的所有可用接口,包括认证、数据管理、英雄系统、库存管理等功能。
## 🏗️ 项目结构
```
cocos_cloud/
├── index.js # 路由入口文件
├── user_init_data.js # 用户初始化数据配置
├── modules/ # 功能模块目录
│ ├── auth.js # 认证模块
│ ├── gameData.js # 基础游戏数据模块
│ ├── fightHeros.js # 出战英雄模块
│ ├── heros.js # 英雄管理模块
│ ├── inventory.js # 库存管理模块
│ └── response.js # 响应处理模块
├── README.md # 项目说明文档
└── API.md # API接口文档
```
## 🚀 通用调用格式
```javascript
wx.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: '命令名',
// 其他参数...
}
}).then(res => {
console.log('调用结果:', res.result);
}).catch(err => {
console.error('调用失败:', err);
});
```
## 📊 通用响应格式
```javascript
{
code: 200, // 状态码 (200=成功, 负数=错误)
msg: "Success", // 响应消息
data: { /* 返回数据 */ }, // 具体数据
timestamp: 1234567890, // 时间戳
version: "1.0.0", // 数据版本
execution_time: 50 // 执行时间(ms)
}
```
## 🔐 认证相关接口
### 1. 用户登录
获取用户完整信息和游戏数据。
```javascript
// 请求
{ cmd: 'login' }
// 响应
{
code: 200,
data: {
user_id: "用户ID",
openid: "微信OpenID",
regist_time: 1234567890,
data: { /* 基础游戏数据 */ },
fight_heros: { /* 出战英雄配置 */ },
heros: { /* 英雄属性数据 */ },
items: { /* 道具数据 */ },
tals: { /* 天赋数据 */ },
equips: { /* 装备数据 */ },
data_version: "1.0.0",
last_save_time: 1234567890
}
}
```
### 2. 获取用户信息
获取用户基本信息(不包含游戏数据)。
```javascript
// 请求
{ cmd: 'user_info' }
// 响应
{
code: 200,
data: {
user_id: "用户ID",
openid: "微信OpenID",
regist_time: 1234567890,
init_time: 1234567890,
data_version: "1.0.0",
last_save_time: 1234567890
}
}
```
### 3. 检查版本信息
检查用户数据版本兼容性。
```javascript
// 请求
{ cmd: 'version' }
// 响应
{
code: 200,
data: {
user_version: "1.0.0",
current_version: "1.1.0",
compatibility: {
compatible: true,
needsUpgrade: true,
message: "Minor version update available"
},
init_time: 1234567890,
regist_time: 1234567890,
last_save_time: 1234567890
}
}
```
### 4. 强制升级数据
手动触发用户数据结构升级。
```javascript
// 请求
{ cmd: 'upgrade' }
// 响应
{
code: 200,
data: {
old_version: "1.0.0",
new_version: "1.1.0",
upgrade_time: 1234567890,
// ... 升级后的完整数据
}
}
```
## 🎮 基础游戏数据接口
### 1. 获取游戏数据
获取用户的基础游戏数据(金币、钻石、经验等)。
```javascript
// 请求
{ cmd: 'data_get' }
// 响应
{
code: 200,
data: {
score: 0,
mission: 1,
gold: 100,
diamond: 100,
meat: 0,
exp: 0,
// ... 更多字段
}
}
```
### 2. 更新游戏数据
批量更新基础游戏数据。
```javascript
// 请求
{
cmd: 'data_update',
data: {
gold: 1000,
diamond: 200,
exp: 500
},
merge: true // 可选默认true合并更新
}
// 响应
{
code: 200,
data: {
// 更新后的完整数据
}
}
```
### 3. 增加指定字段
增加某个字段的数值。
```javascript
// 请求
{
cmd: 'data_add',
field: 'gold',
amount: 100
}
// 响应
{
code: 200,
data: {
field: 'gold',
old_value: 1000,
new_value: 1100,
change: 100
}
}
```
### 4. 消耗指定字段
消耗某个字段的数值(会检查是否足够)。
```javascript
// 请求
{
cmd: 'data_spend',
field: 'gold',
amount: 50
}
// 响应
{
code: 200,
data: {
field: 'gold',
old_value: 1100,
new_value: 1050,
change: -50
}
}
```
### 5. 设置指定字段
直接设置某个字段的值。
```javascript
// 请求
{
cmd: 'data_set',
field: 'mission',
value: 5
}
// 响应
{
code: 200,
data: {
field: 'mission',
old_value: 1,
new_value: 5
}
}
```
### 6. 重置游戏数据
重置基础游戏数据为默认值。
```javascript
// 请求
{ cmd: 'data_reset' }
// 响应
{
code: 200,
data: {
// 重置后的默认数据
}
}
```
## ⚔️ 出战英雄接口
### 1. 获取出战英雄配置
获取当前的出战英雄配置。
```javascript
// 请求
{ cmd: 'fight_heros_get' }
// 响应
{
code: 200,
data: {
0: 5001, // 位置0: 英雄5001
1: 5005, // 位置1: 英雄5005
2: 0, // 位置2: 空
3: 0, // 位置3: 空
4: 0 // 位置4: 空
}
}
```
### 2. 设置单个出战英雄
设置指定位置的出战英雄。
```javascript
// 请求
{
cmd: 'fight_hero_set',
position: 2,
hero_id: 5007
}
// 响应
{
code: 200,
data: {
position: 2,
old_hero_id: 0,
new_hero_id: 5007
}
}
```
### 3. 批量更新出战英雄
批量更新多个位置的出战英雄。
```javascript
// 请求
{
cmd: 'fight_heros_update',
fight_heros: {
0: 5001,
1: 5005,
2: 5007
}
}
// 响应
{
code: 200,
data: {
// 更新后的完整出战配置
}
}
```
### 4. 获取激活的出战英雄
获取当前出战的英雄列表(不包含空位)。
```javascript
// 请求
{ cmd: 'fight_heros_active' }
// 响应
{
code: 200,
data: {
active_heros: [
{
position: 0,
hero_id: 5001,
hero_data: { /* 英雄详细数据 */ }
},
{
position: 1,
hero_id: 5005,
hero_data: { /* 英雄详细数据 */ }
}
],
total_count: 2
}
}
```
### 5. 交换出战英雄位置
交换两个位置的英雄。
```javascript
// 请求
{
cmd: 'fight_heros_swap',
position1: 0,
position2: 2
}
// 响应
{
code: 200,
data: {
position1: 0,
position2: 2,
hero1_moved_to: 5007,
hero2_moved_to: 5001
}
}
```
### 6. 重置出战英雄
重置出战英雄配置为默认值。
```javascript
// 请求
{ cmd: 'fight_heros_reset' }
// 响应
{
code: 200,
data: {
// 重置后的默认配置
}
}
```
## 🦸 英雄管理接口
### 1. 获取所有英雄
获取用户拥有的所有英雄数据。
```javascript
// 请求
{ cmd: 'heros_get' }
// 响应
{
code: 200,
data: {
5001: { uuid: 5001, lv: 10, exp: 500, star: 2, power: 150 },
5005: { uuid: 5005, lv: 8, exp: 300, star: 1, power: 120 },
5007: { uuid: 5007, lv: 1, exp: 0, star: 1, power: 90 }
}
}
```
### 2. 获取单个英雄
获取指定英雄的详细数据。
```javascript
// 请求
{
cmd: 'hero_get',
hero_id: 5001
}
// 响应
{
code: 200,
data: {
uuid: 5001,
lv: 10,
exp: 500,
star: 2,
power: 150
}
}
```
### 3. 添加新英雄
添加新英雄到用户库存。
```javascript
// 请求
{
cmd: 'hero_add',
hero_id: 5008,
hero_data: { // 可选,不提供则使用默认数据
lv: 1,
exp: 0,
star: 1,
power: 110
}
}
// 响应
{
code: 200,
data: {
uuid: 5008,
lv: 1,
exp: 0,
star: 1,
power: 110
}
}
```
### 4. 更新英雄属性
批量更新英雄的多个属性。
```javascript
// 请求
{
cmd: 'hero_update',
hero_id: 5001,
update_data: {
lv: 15,
exp: 800,
star: 3
}
}
// 响应
{
code: 200,
data: {
old_data: { /* 更新前的数据 */ },
new_data: { /* 更新后的数据 */ }
}
}
```
### 5. 设置英雄单个属性
设置英雄的单个属性值。
```javascript
// 请求
{
cmd: 'hero_property_set',
hero_id: 5001,
property: 'lv',
value: 20
}
// 响应
{
code: 200,
data: {
hero_id: 5001,
property: 'lv',
old_value: 15,
new_value: 20
}
}
```
### 6. 英雄升级
英雄升级指定级数。
```javascript
// 请求
{
cmd: 'hero_levelup',
hero_id: 5001,
levels: 3 // 可选默认1级
}
// 响应
{
code: 200,
data: {
hero_id: 5001,
property: 'lv',
old_value: 20,
new_value: 23
}
}
```
### 7. 删除英雄
删除指定英雄(会检查是否在出战阵容中)。
```javascript
// 请求
{
cmd: 'hero_delete',
hero_id: 5008
}
// 响应
{
code: 200,
data: {
// 被删除的英雄数据
}
}
```
### 8. 获取拥有的英雄ID列表
获取用户拥有的所有英雄ID。
```javascript
// 请求
{ cmd: 'heros_owned' }
// 响应
{
code: 200,
data: {
hero_ids: [5001, 5005, 5007],
total_count: 3
}
}
```
## 🎒 库存管理接口
库存管理接口支持三种类型的数据:
- `items`: 道具
- `tals`: 天赋
- `equips`: 装备
### 1. 获取库存数据
获取指定类型的所有库存数据。
```javascript
// 请求
{
cmd: 'inventory_get',
type: 'items' // 'items', 'tals', 'equips'
}
// 响应
{
code: 200,
data: {
1001: 5, // 道具1001: 5个
1002: 3, // 道具1002: 3个
1003: 0, // 道具1003: 0个
// ...
}
}
```
### 2. 获取单个物品
获取指定物品的数量。
```javascript
// 请求
{
cmd: 'inventory_item_get',
type: 'items',
item_id: 1001
}
// 响应
{
code: 200,
data: {
item_id: 1001,
count: 5
}
}
```
### 3. 添加物品
增加指定物品的数量。
```javascript
// 请求
{
cmd: 'inventory_item_add',
type: 'items',
item_id: 1001,
count: 10
}
// 响应
{
code: 200,
data: {
item_id: 1001,
old_count: 5,
new_count: 15,
added: 10
}
}
```
### 4. 消耗物品
消耗指定数量的物品(会检查是否足够)。
```javascript
// 请求
{
cmd: 'inventory_item_consume',
type: 'items',
item_id: 1001,
count: 3
}
// 响应
{
code: 200,
data: {
item_id: 1001,
old_count: 15,
new_count: 12,
added: -3
}
}
```
### 5. 设置物品数量
直接设置物品的数量。
```javascript
// 请求
{
cmd: 'inventory_item_set',
type: 'items',
item_id: 1001,
count: 20
}
// 响应
{
code: 200,
data: {
item_id: 1001,
old_count: 12,
new_count: 20
}
}
```
### 6. 批量更新库存
批量更新多个物品的数量。
```javascript
// 请求
{
cmd: 'inventory_update',
type: 'items',
data: {
1001: 25,
1002: 10,
1003: 5
},
merge: true // 可选默认true合并更新
}
// 响应
{
code: 200,
data: {
// 更新后的完整库存数据
}
}
```
### 7. 重置库存
重置指定类型的库存为默认值。
```javascript
// 请求
{
cmd: 'inventory_reset',
type: 'items'
}
// 响应
{
code: 200,
data: {
// 重置后的默认库存数据
}
}
```
### 8. 获取拥有的物品
获取数量大于0的物品列表。
```javascript
// 请求
{
cmd: 'inventory_owned',
type: 'items'
}
// 响应
{
code: 200,
data: {
owned_items: [
{ item_id: 1001, count: 25 },
{ item_id: 1002, count: 10 },
{ item_id: 1003, count: 5 }
],
total_types: 3
}
}
```
## 🔄 兼容旧接口
为了保持向后兼容,保留了一些旧接口:
### 1. 加载数据 (兼容)
等同于 `login` 命令。
```javascript
// 请求
{ cmd: 'load' }
// 响应与login相同
```
### 2. 保存数据 (兼容)
批量保存多种类型的数据。
```javascript
// 请求
{
cmd: 'save',
data: {
data: { gold: 1000, diamond: 200 },
fight_heros: { 0: 5001, 1: 5005 },
heros: { 5001: { lv: 10, exp: 500 } },
items: { 1001: 10, 1002: 5 },
tals: { 1001: 1 },
equips: { 1001: 2 }
}
}
// 响应
{
code: 200,
data: {
results: [
// 各个模块的保存结果
]
},
msg: "All data saved successfully"
}
```
## ❌ 错误码说明
| 错误码 | 说明 | 示例 |
|-------|------|------|
| 200 | 成功 | 操作成功完成 |
| -1 | 操作失败 | 数据库更新失败 |
| -2 | 未知命令 | 命令不存在 |
| -3 | 参数错误 | 缺少必需参数或参数格式错误 |
| -4 | 用户未找到 | 用户不存在或创建失败 |
| -5 | 系统错误 | 服务器内部错误 |
| -6 | 资源不足 | 金币、道具等资源不够 |
| -7 | 资源已存在 | 英雄已存在等 |
| -8 | 操作被拒绝 | 英雄正在出战中无法删除等 |
## 📝 使用示例
### 完整的游戏流程示例
```javascript
// 1. 用户登录
wx.cloud.callFunction({
name: 'cocos_cloud',
data: { cmd: 'login' }
}).then(res => {
console.log('用户登录成功:', res.result.data);
// 2. 增加金币
return wx.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: 'data_add',
field: 'gold',
amount: 100
}
});
}).then(res => {
console.log('金币增加成功:', res.result.data);
// 3. 添加新英雄
return wx.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: 'hero_add',
hero_id: 5008
}
});
}).then(res => {
console.log('英雄添加成功:', res.result.data);
// 4. 设置出战英雄
return wx.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: 'fight_hero_set',
position: 2,
hero_id: 5008
}
});
}).then(res => {
console.log('出战英雄设置成功:', res.result.data);
}).catch(err => {
console.error('操作失败:', err);
});
```
## 🔧 开发调试
### 1. 启用调试日志
在开发环境中,云函数会输出详细的调试日志:
```javascript
// 请求日志
[data_add] Request from oxxx: { field: 'gold', amount: 100 }
// 响应日志
[data_add] Response (45ms): 200 gold updated successfully
```
### 2. 性能监控
每个响应都包含 `execution_time` 字段,显示接口执行时间。
### 3. 版本管理
使用 `version` 命令检查数据版本兼容性,确保客户端和服务端数据结构一致。
---
**版本**: 1.0.0
**更新时间**: 2024年
**维护者**: 游戏开发团队

View File

@@ -1,270 +0,0 @@
# 微信云函数游戏数据管理系统
## 📁 文件结构
```
cocos_cloud/
├── index.js # 主云函数文件
├── user_init_data.js # 新用户初始化数据配置
└── README.md # 说明文档
```
## 🎮 功能特性
### 1. 数据结构管理
- **平级数据存储**: 所有游戏数据字段与用户基本信息平级存储
- **版本控制**: 支持数据结构版本管理和自动升级
- **数据验证**: 完整的数据结构和类型验证
- **默认值管理**: 集中管理新用户初始化数据
### 2. 支持的命令
| 命令 | 功能 | 说明 |
|------|------|------|
| `login` | 用户登录 | 返回完整的用户和游戏数据 |
| `load` | 加载游戏数据 | 获取用户的所有游戏数据 |
| `save` | 保存游戏数据 | 支持增量更新,自动合并数据 |
| `reset` | 重置游戏数据 | 恢复为默认初始值 |
| `backup` | 创建数据备份 | 在独立集合中创建备份记录 |
| `version` | 查看版本信息 | 检查数据版本兼容性 |
| `upgrade` | 强制升级数据 | 手动触发数据结构升级 |
## 📊 数据结构
### 用户数据库结构 (cocos_users 集合)
```javascript
{
_id: "用户ID",
_openid: "微信OpenID",
regist_time: "注册时间戳",
init_time: "初始化时间戳",
last_save_time: "最后保存时间",
data_version: "数据版本号",
// 游戏数据字段(与基本信息平级)
data: {
score: 0, // 游戏分数
mission: 1, // 当前关卡
gold: 100, // 金币
diamond: 100, // 钻石
meat: 0, // 肉类资源
exp: 0, // 升级经验
ghstone: 0, // 绿色英雄石
bhstone: 0, // 蓝色英雄石
phlestone: 0, // 紫色英雄石
rhstone: 0, // 红色英雄石
herocard: 0, // 英雄卡
ckey: 0, // 铜钥匙
skey: 0, // 银钥匙
gkey: 0 // 金钥匙
},
fight_heros: {
0: 5001, // 出战位置1
1: 5005, // 出战位置2
2: 0, // 出战位置3
3: 0, // 出战位置4
4: 0 // 出战位置5
},
heros: {
5001: { uuid: 5001, lv: 1, exp: 0, star: 1, power: 100 },
5005: { uuid: 5005, lv: 1, exp: 0, star: 1, power: 120 },
5007: { uuid: 5007, lv: 1, exp: 0, star: 1, power: 90 }
},
items: {
1001: 0, // 生命药水
1002: 0, // 魔法药水
1003: 0, // 力量药水
// ... 更多道具
},
tals: {
1001: 0, // 攻击强化
1002: 0, // 防御强化
1003: 0, // 生命强化
// ... 更多天赋
},
equips: {
1001: 0, // 武器
1002: 0, // 头盔
1003: 0, // 胸甲
// ... 更多装备
}
}
```
### 备份数据库结构 (game_backups 集合)
```javascript
{
user_id: "用户ID",
openid: "微信OpenID",
data: { /* 基础游戏数据 */ },
fight_heros: { /* 出战英雄配置 */ },
heros: { /* 英雄属性数据 */ },
items: { /* 道具数据 */ },
tals: { /* 天赋数据 */ },
equips: { /* 装备数据 */ },
backup_time: "备份时间戳",
original_save_time: "原始保存时间"
}
```
## 🚀 使用示例
### 客户端调用示例
#### 1. 用户登录
```javascript
wx.cloud.callFunction({
name: 'cocos_cloud',
data: { cmd: 'login' }
}).then(res => {
console.log('登录成功:', res.result);
// res.result.data 包含完整的用户和游戏数据
});
```
#### 2. 保存游戏数据
```javascript
wx.cloud.callFunction({
name: 'cocos_cloud',
data: {
cmd: 'save',
data: {
data: { gold: 1000, diamond: 50 },
fight_heros: { 0: 5001, 1: 5005 },
heros: { 5001: { lv: 10, exp: 100 } },
items: { 1001: 5 }
}
}
});
```
#### 3. 加载游戏数据
```javascript
wx.cloud.callFunction({
name: 'cocos_cloud',
data: { cmd: 'load' }
}).then(res => {
const gameData = res.result.data;
console.log('游戏数据:', gameData);
});
```
#### 4. 检查版本信息
```javascript
wx.cloud.callFunction({
name: 'cocos_cloud',
data: { cmd: 'version' }
}).then(res => {
console.log('版本信息:', res.result.data);
if (res.result.data.compatibility.needsUpgrade) {
console.log('需要升级数据');
}
});
```
#### 5. 创建数据备份
```javascript
wx.cloud.callFunction({
name: 'cocos_cloud',
data: { cmd: 'backup' }
}).then(res => {
console.log('备份成功:', res.result.data.backup_id);
});
```
## 🔧 配置管理
### 修改默认数据
编辑 `user_init_data.js` 文件中的常量:
```javascript
// 修改默认金币数量
const DEFAULT_GAME_DATA = {
// ...
gold: 200, // 从100改为200
// ...
};
// 添加新英雄
const DEFAULT_HEROS = {
// 现有英雄...
5008: { uuid: 5008, lv: 1, exp: 0, star: 1, power: 110 }
};
```
### 版本升级
当数据结构发生变化时,更新版本号:
```javascript
// 在 user_init_data.js 中
const DATA_VERSION = "1.1.0"; // 从1.0.0升级到1.1.0
```
## 📈 版本控制
### 版本号格式
- **MAJOR.MINOR.PATCH** (如: 1.2.3)
- **MAJOR**: 重大结构变更,不向后兼容
- **MINOR**: 新增字段,向后兼容
- **PATCH**: 数值调整,完全兼容
### 自动升级机制
- 用户登录时自动检查版本兼容性
- 如需升级,自动合并新的默认数据
- 保留用户的现有数据,只补充缺失字段
## 🛡️ 错误处理
### 错误码说明
- `200`: 成功
- `-1`: 操作失败
- `-2`: 未知命令
- `-3`: 数据结构无效
- `-4`: 用户创建/获取失败
- `-5`: 系统错误
### 日志记录
- 新用户创建日志
- 数据版本升级日志
- 错误操作日志
- 性能监控日志
## 🔍 调试和监控
### 查看用户数据版本
```javascript
wx.cloud.callFunction({
name: 'cocos_cloud',
data: { cmd: 'version' }
});
```
### 强制升级用户数据
```javascript
wx.cloud.callFunction({
name: 'cocos_cloud',
data: { cmd: 'upgrade' }
});
```
## 📝 注意事项
1. **数据备份**: 重要操作前建议创建备份
2. **版本兼容**: 升级数据结构时注意向后兼容性
3. **性能优化**: 大量数据操作时注意性能影响
4. **安全验证**: 所有输入数据都经过严格验证
5. **错误处理**: 完善的错误处理和日志记录
## 🚀 部署说明
1. 将整个 `cocos_cloud` 文件夹上传到微信云开发
2. 在微信开发者工具中部署云函数
3. 确保数据库权限配置正确
4. 测试各项功能是否正常工作
---
**版本**: 1.0.0
**更新时间**: 2024年
**维护者**: 游戏开发团队

View File

@@ -0,0 +1,32 @@
# 云函数部署指南
## 问题描述
```
Error: cloud.callFunction:fail Error: errCode: -504002 functions execute fail | errMsg: Error: Cannot find module 'wx-server-sdk'
```
## 解决方案
### 1. 安装依赖
在云函数目录下执行:
```bash
npm install
```
### 2. 验证依赖安装
检查 `node_modules` 目录是否存在且包含 `wx-server-sdk`
```bash
ls node_modules/wx-server-sdk
```
### 3. 重新部署云函数
使用微信开发者工具重新上传并部署云函数。
### 4. 检查云开发环境
确保云开发环境已正确初始化,并且云函数配置正确。
## 注意事项
1. 确保网络连接正常,能够访问 npm 源
2. 如果使用了代理,需要正确配置 npm 代理
3. 确保云开发环境 ID 配置正确
4. 检查云函数名称是否与代码中调用的一致

View File

@@ -1,371 +1,85 @@
// 云函数路由入口文件 // 云函数入口文件
const cloud = require('wx-server-sdk'); const cloud = require('wx-server-sdk');
// 导入各个模块 const user_db_name = "cocos_users";
const authModule = require('./modules/auth');
const gameDataModule = require('./modules/gameData');
const fightHerosModule = require('./modules/fightHeros');
const herosModule = require('./modules/heros');
const inventoryModule = require('./modules/inventory');
const itemsModule = require('./modules/items');
const talsModule = require('./modules/tals');
const equipsModule = require('./modules/equips');
const responseModule = require('./modules/response');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); // 使用当前云环境 cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); // 使用当前云环境
// 云函数入口函数 // 云函数入口函数
exports.main = async (event, context) => { exports.main = async (event, context) => {
const startTime = Date.now(); let cmd = event.cmd;
if (cmd == null) {
try { return {
// 验证命令参数 code: -1,
const validation = responseModule.validateRequiredParams(event, ['cmd']); msg: "no cmd!"
if (validation) { };
return validation;
} }
const { cmd, ...params } = event;
const wxContext = cloud.getWXContext(); const wxContext = cloud.getWXContext();
const db = cloud.database({ const db = cloud.database({
env: cloud.DYNAMIC_CURRENT_ENV, env: cloud.DYNAMIC_CURRENT_ENV,
throwOnNotFound: false throwOnNotFound: false
}); });
const _ = db.command;
console.log(`[${cmd}] Request from ${wxContext.OPENID}:`, params); if (cmd === "login") {
let user = await getOrCreaterUser(db, wxContext.OPENID);
// 路由分发 return {
let result; code: 200,
switch (cmd) { data: user,
// ==================== 认证相关 ==================== };
case 'login': } else if (cmd === "save") {
result = await authModule.login(db, wxContext); let data = event.data;
break; let user = await getOrCreaterUser(db, wxContext.OPENID);
let saveDataRes = await db.collection(user_db_name).doc(user._id).update({
case 'user_info': data: {
result = await authModule.getUserInfo(db, wxContext.OPENID); game_data: _.set(data)
break;
case 'version':
result = await authModule.checkVersion(db, wxContext.OPENID);
break;
case 'upgrade':
result = await authModule.upgradeUserData(db, wxContext.OPENID);
break;
// ==================== 基础游戏数据 ====================
case 'data_get':
result = await gameDataModule.getData(db, wxContext.OPENID);
break;
case 'data_update':
const merge = params.merge !== false; // 默认合并
result = await gameDataModule.updateData(db, wxContext.OPENID, params.data, merge);
break;
case 'data_add':
const fieldValidation = responseModule.validateRequiredParams(params, ['field', 'amount']);
if (fieldValidation) return fieldValidation;
result = await gameDataModule.addDataField(db, wxContext.OPENID, params.field, params.amount);
break;
case 'data_spend':
const spendValidation = responseModule.validateRequiredParams(params, ['field', 'amount']);
if (spendValidation) return spendValidation;
result = await gameDataModule.spendDataField(db, wxContext.OPENID, params.field, params.amount);
break;
case 'data_reset':
result = await gameDataModule.resetData(db, wxContext.OPENID);
break;
// ==================== 出战英雄 ====================
case 'fight_heros_get':
result = await fightHerosModule.getFightHeros(db, wxContext.OPENID);
break;
case 'fight_hero_set':
const heroSetValidation = responseModule.validateRequiredParams(params, ['position', 'hero_id']);
if (heroSetValidation) return heroSetValidation;
result = await fightHerosModule.setFightHero(db, wxContext.OPENID, params.position, params.hero_id);
break;
case 'fight_heros_update':
const herosUpdateValidation = responseModule.validateRequiredParams(params, ['fight_heros']);
if (herosUpdateValidation) return herosUpdateValidation;
result = await fightHerosModule.updateFightHeros(db, wxContext.OPENID, params.fight_heros);
break;
case 'fight_heros_active':
result = await fightHerosModule.getActiveFightHeros(db, wxContext.OPENID);
break;
case 'fight_heros_swap':
const swapValidation = responseModule.validateRequiredParams(params, ['position1', 'position2']);
if (swapValidation) return swapValidation;
result = await fightHerosModule.swapFightHeros(db, wxContext.OPENID, params.position1, params.position2);
break;
case 'fight_heros_reset':
result = await fightHerosModule.resetFightHeros(db, wxContext.OPENID);
break;
// ==================== 英雄管理 ====================
case 'heros_get':
result = await herosModule.getHeros(db, wxContext.OPENID);
break;
case 'hero_get':
const heroGetValidation = responseModule.validateRequiredParams(params, ['hero_id']);
if (heroGetValidation) return heroGetValidation;
result = await herosModule.getHero(db, wxContext.OPENID, params.hero_id);
break;
case 'hero_add':
const heroAddValidation = responseModule.validateRequiredParams(params, ['hero_id']);
if (heroAddValidation) return heroAddValidation;
result = await herosModule.addHero(db, wxContext.OPENID, params.hero_id, params.hero_data);
break;
case 'hero_update':
const heroUpdateValidation = responseModule.validateRequiredParams(params, ['hero_id', 'update_data']);
if (heroUpdateValidation) return heroUpdateValidation;
result = await herosModule.updateHero(db, wxContext.OPENID, params.hero_id, params.update_data);
break;
case 'hero_property_set':
const propValidation = responseModule.validateRequiredParams(params, ['hero_id', 'property', 'value']);
if (propValidation) return propValidation;
result = await herosModule.setHeroProperty(db, wxContext.OPENID, params.hero_id, params.property, params.value);
break;
case 'hero_levelup':
const levelValidation = responseModule.validateRequiredParams(params, ['hero_id']);
if (levelValidation) return levelValidation;
result = await herosModule.levelUpHero(db, wxContext.OPENID, params.hero_id, params.levels);
break;
case 'hero_delete':
const deleteValidation = responseModule.validateRequiredParams(params, ['hero_id']);
if (deleteValidation) return deleteValidation;
result = await herosModule.deleteHero(db, wxContext.OPENID, params.hero_id);
break;
case 'heros_owned':
result = await herosModule.getOwnedHeroIds(db, wxContext.OPENID);
break;
// ==================== 库存管理 (通用接口) ====================
case 'inventory_get':
const invGetValidation = responseModule.validateRequiredParams(params, ['type']);
if (invGetValidation) return invGetValidation;
result = await inventoryModule.getInventory(db, wxContext.OPENID, params.type);
break;
case 'inventory_item_get':
const itemGetValidation = responseModule.validateRequiredParams(params, ['type', 'item_id']);
if (itemGetValidation) return itemGetValidation;
result = await inventoryModule.getInventoryItem(db, wxContext.OPENID, params.type, params.item_id);
break;
case 'inventory_item_add':
const itemAddValidation = responseModule.validateRequiredParams(params, ['type', 'item_id', 'count']);
if (itemAddValidation) return itemAddValidation;
result = await inventoryModule.addInventoryItem(db, wxContext.OPENID, params.type, params.item_id, params.count);
break;
case 'inventory_item_consume':
const itemConsumeValidation = responseModule.validateRequiredParams(params, ['type', 'item_id', 'count']);
if (itemConsumeValidation) return itemConsumeValidation;
result = await inventoryModule.consumeInventoryItem(db, wxContext.OPENID, params.type, params.item_id, params.count);
break;
case 'inventory_item_set':
const itemSetValidation = responseModule.validateRequiredParams(params, ['type', 'item_id', 'count']);
if (itemSetValidation) return itemSetValidation;
result = await inventoryModule.setInventoryItem(db, wxContext.OPENID, params.type, params.item_id, params.count);
break;
case 'inventory_update':
const invUpdateValidation = responseModule.validateRequiredParams(params, ['type', 'data']);
if (invUpdateValidation) return invUpdateValidation;
const invMerge = params.merge !== false;
result = await inventoryModule.updateInventory(db, wxContext.OPENID, params.type, params.data, invMerge);
break;
case 'inventory_reset':
const invResetValidation = responseModule.validateRequiredParams(params, ['type']);
if (invResetValidation) return invResetValidation;
result = await inventoryModule.resetInventory(db, wxContext.OPENID, params.type);
break;
case 'inventory_owned':
const invOwnedValidation = responseModule.validateRequiredParams(params, ['type']);
if (invOwnedValidation) return invOwnedValidation;
result = await inventoryModule.getOwnedItems(db, wxContext.OPENID, params.type);
break;
// ==================== 物品管理 (items) ====================
case 'items_get':
result = await itemsModule.getItems(db, wxContext.OPENID);
break;
case 'item_get':
const itemIdValidation = responseModule.validateRequiredParams(params, ['item_id']);
if (itemIdValidation) return itemIdValidation;
result = await itemsModule.getItem(db, wxContext.OPENID, params.item_id);
break;
case 'item_add':
const addItemValidation = responseModule.validateRequiredParams(params, ['item_id', 'count']);
if (addItemValidation) return addItemValidation;
result = await itemsModule.addItem(db, wxContext.OPENID, params.item_id, params.count);
break;
case 'item_consume':
const consumeItemValidation = responseModule.validateRequiredParams(params, ['item_id', 'count']);
if (consumeItemValidation) return consumeItemValidation;
result = await itemsModule.consumeItem(db, wxContext.OPENID, params.item_id, params.count);
break;
case 'item_set':
const setItemValidation = responseModule.validateRequiredParams(params, ['item_id', 'count']);
if (setItemValidation) return setItemValidation;
result = await itemsModule.setItem(db, wxContext.OPENID, params.item_id, params.count);
break;
case 'items_update':
const updateItemsValidation = responseModule.validateRequiredParams(params, ['data']);
if (updateItemsValidation) return updateItemsValidation;
const itemsMerge = params.merge !== false;
result = await itemsModule.updateItems(db, wxContext.OPENID, params.data, itemsMerge);
break;
case 'items_reset':
result = await itemsModule.resetItems(db, wxContext.OPENID);
break;
case 'items_owned':
result = await itemsModule.getOwnedItems(db, wxContext.OPENID);
break;
// ==================== 天赋管理 (tals) ====================
case 'tals_get':
result = await talsModule.getTals(db, wxContext.OPENID);
break;
case 'tal_get':
const talIdValidation = responseModule.validateRequiredParams(params, ['tal_id']);
if (talIdValidation) return talIdValidation;
result = await talsModule.getTal(db, wxContext.OPENID, params.tal_id);
break;
case 'tal_add':
const addTalValidation = responseModule.validateRequiredParams(params, ['tal_id', 'count']);
if (addTalValidation) return addTalValidation;
result = await talsModule.addTal(db, wxContext.OPENID, params.tal_id, params.count);
break;
case 'tal_consume':
const consumeTalValidation = responseModule.validateRequiredParams(params, ['tal_id', 'count']);
if (consumeTalValidation) return consumeTalValidation;
result = await talsModule.consumeTal(db, wxContext.OPENID, params.tal_id, params.count);
break;
case 'tal_set':
const setTalValidation = responseModule.validateRequiredParams(params, ['tal_id', 'count']);
if (setTalValidation) return setTalValidation;
result = await talsModule.setTal(db, wxContext.OPENID, params.tal_id, params.count);
break;
case 'tals_update':
const updateTalsValidation = responseModule.validateRequiredParams(params, ['data']);
if (updateTalsValidation) return updateTalsValidation;
const talsMerge = params.merge !== false;
result = await talsModule.updateTals(db, wxContext.OPENID, params.data, talsMerge);
break;
case 'tals_reset':
result = await talsModule.resetTals(db, wxContext.OPENID);
break;
case 'tals_owned':
result = await talsModule.getOwnedTals(db, wxContext.OPENID);
break;
// ==================== 装备管理 (equips) ====================
case 'equips_get':
result = await equipsModule.getEquips(db, wxContext.OPENID);
break;
case 'equip_get':
const equipIdValidation = responseModule.validateRequiredParams(params, ['equip_id']);
if (equipIdValidation) return equipIdValidation;
result = await equipsModule.getEquip(db, wxContext.OPENID, params.equip_id);
break;
case 'equip_add':
const addEquipValidation = responseModule.validateRequiredParams(params, ['equip_id', 'count']);
if (addEquipValidation) return addEquipValidation;
result = await equipsModule.addEquip(db, wxContext.OPENID, params.equip_id, params.count);
break;
case 'equip_consume':
const consumeEquipValidation = responseModule.validateRequiredParams(params, ['equip_id', 'count']);
if (consumeEquipValidation) return consumeEquipValidation;
result = await equipsModule.consumeEquip(db, wxContext.OPENID, params.equip_id, params.count);
break;
case 'equip_set':
const setEquipValidation = responseModule.validateRequiredParams(params, ['equip_id', 'count']);
if (setEquipValidation) return setEquipValidation;
result = await equipsModule.setEquip(db, wxContext.OPENID, params.equip_id, params.count);
break;
case 'equips_update':
const updateEquipsValidation = responseModule.validateRequiredParams(params, ['data']);
if (updateEquipsValidation) return updateEquipsValidation;
const equipsMerge = params.merge !== false;
result = await equipsModule.updateEquips(db, wxContext.OPENID, params.data, equipsMerge);
break;
case 'equips_reset':
result = await equipsModule.resetEquips(db, wxContext.OPENID);
break;
case 'equips_owned':
result = await equipsModule.getOwnedEquips(db, wxContext.OPENID);
break;
default:
const availableCommands = [
'login', 'user_info', 'version', 'upgrade',
'data_get', 'data_update', 'data_add', 'data_spend', 'data_set', 'data_reset',
'fight_heros_get', 'fight_hero_set', 'fight_heros_update', 'fight_heros_active', 'fight_heros_swap', 'fight_heros_reset',
'heros_get', 'hero_get', 'hero_add', 'hero_update', 'hero_property_set', 'hero_levelup', 'hero_delete', 'heros_owned',
'inventory_get', 'inventory_item_get', 'inventory_item_add', 'inventory_item_consume', 'inventory_item_set', 'inventory_update', 'inventory_reset', 'inventory_owned',
'items_get', 'item_get', 'item_add', 'item_consume', 'item_set', 'items_update', 'items_reset', 'items_owned',
'tals_get', 'tal_get', 'tal_add', 'tal_consume', 'tal_set', 'tals_update', 'tals_reset', 'tals_owned',
'equips_get', 'equip_get', 'equip_add', 'equip_consume', 'equip_set', 'equips_update', 'equips_reset', 'equips_owned',
'load', 'save'
];
result = responseModule.unknownCommand(cmd, availableCommands);
break;
} }
});
// 添加执行时间 console.log("Save data:", saveDataRes, data);
const executionTime = Date.now() - startTime; if (saveDataRes?.stats?.updated >= 1) {
if (result && typeof result === 'object') { return {
result.execution_time = executionTime; code: 200,
data: saveDataRes
};
} else {
return {
code: -1,
msg: `Save fail, ${JSON.stringify(saveDataRes)}`
};
} }
}else if (event?.cmd === "get") {
console.log(`[${cmd}] Response (${executionTime}ms):`, result.code, result.msg); let user = await getOrCreaterUser(db, wxContext.OPENID);
return result; return {
code: 200,
} catch (error) { data: user,
console.error("Cloud function error:", error); };
return responseModule.systemError("Cloud function execution failed", error);
} }
return {
code: -2,
msg: `Unknow cmd: ${event?.cmd}`,
};
}; };
async function getOrCreaterUser(db, openid) {
try {
let res = await db.collection(user_db_name).where({ _openid: openid }).get();
let userData = null;
if (res == null || res.data == null || res.data.length <= 0) {
userData = {
_openid: openid,
regist_time: Date.now(),
};
let addResult = await db.collection(user_db_name).add({
data: userData
});
userData._id = addResult._id;
} else {
userData = res.data[0];
}
return userData;
} catch (err) {
console.error(`Get or create user err`, err);
return null;
}
}

View File

@@ -1,283 +0,0 @@
// 登录认证模块
const {
getNewUserInitData,
mergeUserDataWithDefaults,
checkDataVersionCompatibility
} = require('../user_init_data');
const user_db_name = "cocos_users";
/**
* 用户登录处理
* @param {Object} db 数据库实例
* @param {Object} wxContext 微信上下文
* @returns {Object} 登录结果
*/
async function login(db, wxContext) {
try {
let user = await getOrCreaterUser(db, wxContext.OPENID);
if (!user) {
return {
code: -4,
msg: "获取或创建用户失败"
};
}
return {
code: 200,
data: {
user_id: user._id,
openid: user._openid,
regist_time: user.regist_time,
data: user.data,
fight_heros: user.fight_heros,
heros: user.heros,
items: user.items,
tals: user.tals,
equips: user.equips,
data_version: user.data_version,
last_save_time: user.last_save_time || null
},
msg: "登录成功"
};
} catch (error) {
console.error("登录错误:", error);
return {
code: -5,
msg: `登录错误: ${error.message}`
};
}
}
/**
* 获取或创建用户
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 用户数据
*/
async function getOrCreaterUser(db, openid) {
try {
let res = await db.collection(user_db_name).where({ _openid: openid }).get();
let userData = null;
if (res == null || res.data == null || res.data.length <= 0) {
// 创建新用户时使用初始化数据配置
let initData = getNewUserInitData();
userData = {
_openid: openid,
regist_time: Date.now(),
data: initData.data,
fight_heros: initData.fight_heros,
heros: initData.heros,
items: initData.items,
tals: initData.tals,
equips: initData.equips,
data_version: initData.data_version,
init_time: initData.init_time
};
let addResult = await db.collection(user_db_name).add({
data: userData
});
userData._id = addResult._id;
// console.log(`新用户已创建: ${openid}, 版本: ${initData.data_version}`);
} else {
userData = res.data[0];
// 检查数据版本兼容性
const versionCheck = checkDataVersionCompatibility(userData.data_version);
// console.log(`用户 ${openid} 数据版本检查:`, versionCheck);
if (versionCheck.needsUpgrade) {
// 使用新的数据管理系统合并和升级数据
const upgradedData = mergeUserDataWithDefaults({
data: userData.data,
fight_heros: userData.fight_heros,
heros: userData.heros,
items: userData.items,
tals: userData.tals,
equips: userData.equips,
data_version: userData.data_version
});
// 更新用户数据
userData.data = upgradedData.data;
userData.fight_heros = upgradedData.fight_heros;
userData.heros = upgradedData.heros;
userData.items = upgradedData.items;
userData.tals = upgradedData.tals;
userData.equips = upgradedData.equips;
userData.data_version = upgradedData.data_version;
// console.log(`用户 ${openid} 数据已升级到版本: ${upgradedData.data_version}`);
}
}
return userData;
} catch (err) {
console.error(`获取或创建用户错误`, err);
return null;
}
}
/**
* 获取用户基本信息
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 用户基本信息
*/
async function getUserInfo(db, openid) {
try {
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
return {
code: 200,
data: {
user_id: user._id,
openid: user._openid,
regist_time: user.regist_time,
init_time: user.init_time,
data_version: user.data_version,
last_save_time: user.last_save_time
},
msg: "用户信息获取成功"
};
} catch (error) {
console.error("获取用户信息错误:", error);
return {
code: -5,
msg: `获取用户信息错误: ${error.message}`
};
}
}
/**
* 检查用户数据版本
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 版本检查结果
*/
async function checkVersion(db, openid) {
try {
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
const versionCheck = checkDataVersionCompatibility(user.data_version);
return {
code: 200,
data: {
user_version: user.data_version || "unknown",
current_version: require('../user_init_data').DATA_VERSION,
compatibility: versionCheck,
init_time: user.init_time,
regist_time: user.regist_time,
last_save_time: user.last_save_time
},
msg: "版本信息获取成功"
};
} catch (error) {
console.error("检查版本错误:", error);
return {
code: -5,
msg: `检查版本错误: ${error.message}`
};
}
}
/**
* 强制升级用户数据
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 升级结果
*/
async function upgradeUserData(db, openid) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "User not found"
};
}
// 强制升级用户数据
const upgradedData = mergeUserDataWithDefaults({
data: user.data,
fight_heros: user.fight_heros,
heros: user.heros,
items: user.items,
tals: user.tals,
equips: user.equips,
data_version: user.data_version
});
let upgradeDataRes = await db.collection(user_db_name).doc(user._id).update({
data: {
data: _.set(upgradedData.data),
fight_heros: _.set(upgradedData.fight_heros),
heros: _.set(upgradedData.heros),
items: _.set(upgradedData.items),
tals: _.set(upgradedData.tals),
equips: _.set(upgradedData.equips),
data_version: _.set(upgradedData.data_version),
last_save_time: _.set(Date.now()),
upgrade_time: _.set(Date.now())
}
});
if (upgradeDataRes?.stats?.updated >= 1) {
return {
code: 200,
data: {
old_version: user.data_version || "unknown",
new_version: upgradedData.data_version,
upgrade_time: Date.now(),
data: upgradedData.data,
fight_heros: upgradedData.fight_heros,
heros: upgradedData.heros,
items: upgradedData.items,
tals: upgradedData.tals,
equips: upgradedData.equips
},
msg: "数据升级成功完成"
};
} else {
return {
code: -1,
msg: `升级失败, ${JSON.stringify(upgradeDataRes)}`
};
}
} catch (error) {
console.error("升级用户数据错误:", error);
return {
code: -5,
msg: `升级错误: ${error.message}`
};
}
}
module.exports = {
login,
getOrCreaterUser,
getUserInfo,
checkVersion,
upgradeUserData
};

View File

@@ -1,370 +0,0 @@
// 出战英雄操作模块
const { getOrCreaterUser } = require('./auth');
const user_db_name = "cocos_users";
/**
* 获取出战英雄配置
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 操作结果
*/
async function getFightHeros(db, openid) {
try {
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
return {
code: 200,
data: user.fight_heros,
msg: "出战英雄获取成功"
};
} catch (error) {
console.error("获取出战英雄错误:", error);
return {
code: -5,
msg: `获取出战英雄错误: ${error.message}`
};
}
}
/**
* 设置出战英雄
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {number} position 出战位置 (0-4)
* @param {number} heroId 英雄ID0表示移除
* @returns {Object} 操作结果
*/
async function setFightHero(db, openid, position, heroId) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
// 验证位置参数
if (position < 0 || position > 4) {
return {
code: -3,
msg: "无效的位置必须是0-4"
};
}
// 验证英雄ID
if (typeof heroId !== 'number' || heroId < 0) {
return {
code: -3,
msg: "无效的英雄ID"
};
}
// 如果不是移除操作,检查英雄是否存在
if (heroId > 0 && !user.heros[heroId]) {
return {
code: -6,
msg: "未拥有该英雄"
};
}
const oldHeroId = user.fight_heros[position];
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`fight_heros.${position}`]: _.set(heroId),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: {
position: position,
old_hero_id: oldHeroId,
new_hero_id: heroId
},
msg: `出战英雄位置 ${position} 更新成功`
};
} else {
return {
code: -1,
msg: `设置出战英雄失败`
};
}
} catch (error) {
console.error("设置出战英雄错误:", error);
return {
code: -5,
msg: `设置出战英雄错误: ${error.message}`
};
}
}
/**
* 批量设置出战英雄
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {Object} fightHeros 出战英雄配置对象
* @returns {Object} 操作结果
*/
async function updateFightHeros(db, openid, fightHeros) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
// 验证数据格式
if (!fightHeros || typeof fightHeros !== 'object') {
return {
code: -3,
msg: "无效的出战英雄数据格式"
};
}
// 验证每个位置和英雄ID
for (const pos in fightHeros) {
const position = parseInt(pos);
const heroId = fightHeros[pos];
if (isNaN(position) || position < 0 || position > 4) {
return {
code: -3,
msg: `无效的位置: ${pos}`
};
}
if (typeof heroId !== 'number' || heroId < 0) {
return {
code: -3,
msg: `位置 ${pos} 的英雄ID无效: ${heroId}`
};
}
// 如果不是移除操作,检查英雄是否存在
if (heroId > 0 && !user.heros[heroId]) {
return {
code: -6,
msg: `位置 ${pos} 未拥有英雄 ${heroId}`
};
}
}
const newFightHeros = { ...user.fight_heros, ...fightHeros };
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
fight_heros: _.set(newFightHeros),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: newFightHeros,
msg: "出战英雄更新成功"
};
} else {
return {
code: -1,
msg: `更新出战英雄失败`
};
}
} catch (error) {
console.error("更新出战英雄错误:", error);
return {
code: -5,
msg: `更新出战英雄错误: ${error.message}`
};
}
}
/**
* 获取当前出战英雄列表(不包含空位)
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 操作结果
*/
async function getActiveFightHeros(db, openid) {
try {
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
const activeHeros = [];
for (let i = 0; i < 5; i++) {
const heroId = user.fight_heros[i];
if (heroId && heroId > 0) {
activeHeros.push({
position: i,
hero_id: heroId,
hero_data: user.heros[heroId] || null
});
}
}
return {
code: 200,
data: {
active_heros: activeHeros,
total_count: activeHeros.length
},
msg: "获取活跃出战英雄成功"
};
} catch (error) {
console.error("获取活跃出战英雄错误:", error);
return {
code: -5,
msg: `获取活跃出战英雄错误: ${error.message}`
};
}
}
/**
* 交换两个出战位置的英雄
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {number} position1 位置1 (0-4)
* @param {number} position2 位置2 (0-4)
* @returns {Object} 操作结果
*/
async function swapFightHeros(db, openid, position1, position2) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
// 验证位置参数
if (position1 < 0 || position1 > 4 || position2 < 0 || position2 > 4) {
return {
code: -3,
msg: "无效的位置必须是0-4"
};
}
if (position1 === position2) {
return {
code: -3,
msg: "不能交换相同位置"
};
}
const hero1 = user.fight_heros[position1];
const hero2 = user.fight_heros[position2];
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`fight_heros.${position1}`]: _.set(hero2),
[`fight_heros.${position2}`]: _.set(hero1),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: {
position1: position1,
position2: position2,
hero1_moved_to: hero1,
hero2_moved_to: hero2
},
msg: `出战英雄交换成功`
};
} else {
return {
code: -1,
msg: `交换出战英雄失败`
};
}
} catch (error) {
console.error("交换出战英雄错误:", error);
return {
code: -5,
msg: `交换出战英雄错误: ${error.message}`
};
}
}
/**
* 重置出战英雄配置
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 操作结果
*/
async function resetFightHeros(db, openid) {
try {
const _ = db.command;
const { getNewUserInitData } = require('../user_init_data');
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
const defaultData = getNewUserInitData();
let resetRes = await db.collection(user_db_name).doc(user._id).update({
data: {
fight_heros: _.set(defaultData.fight_heros),
last_save_time: _.set(Date.now()),
reset_time: _.set(Date.now())
}
});
if (resetRes?.stats?.updated >= 1) {
return {
code: 200,
data: defaultData.fight_heros,
msg: "出战英雄重置成功"
};
} else {
return {
code: -1,
msg: `重置失败, ${JSON.stringify(resetRes)}`
};
}
} catch (error) {
console.error("重置出战英雄错误:", error);
return {
code: -5,
msg: `重置出战英雄错误: ${error.message}`
};
}
}
module.exports = {
getFightHeros,
setFightHero,
updateFightHeros,
getActiveFightHeros,
swapFightHeros,
resetFightHeros
};

View File

@@ -1,386 +0,0 @@
// 基础游戏数据操作模块 (data字段)
const { getOrCreaterUser } = require('./auth');
const { validateDataStructure, mergeUserDataWithDefaults } = require('../user_init_data');
const user_db_name = "cocos_users";
/**
* 获取基础游戏数据
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 操作结果
*/
async function getData(db, openid) {
try {
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "用户未找到"
};
}
return {
code: 200,
data: user.data,
msg: "游戏数据获取成功"
};
} catch (error) {
console.error("获取游戏数据错误:", error);
return {
code: -5,
msg: `获取游戏数据错误: ${error.message}`
};
}
}
/**
* 更新基础游戏数据
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {Object} updateData 要更新的数据
* @param {boolean} merge 是否合并更新默认true
* @returns {Object} 操作结果
*/
async function updateData(db, openid, updateData, merge = true) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "用户未找到"
};
}
// 验证数据格式
if (!updateData || typeof updateData !== 'object') {
return {
code: -3,
msg: "无效的更新数据格式"
};
}
let newData;
if (merge) {
// 合并更新
newData = { ...user.data, ...updateData };
} else {
// 完全替换
newData = updateData;
}
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
data: _.set(newData),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: newData,
msg: "游戏数据更新成功"
};
} else {
return {
code: -1,
msg: `更新失败, ${JSON.stringify(updateRes)}`
};
}
} catch (error) {
console.error("更新游戏数据错误:", error);
return {
code: -5,
msg: `更新游戏数据错误: ${error.message}`
};
}
}
/**
* 增加指定字段的数值
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} field 字段名
* @param {number} amount 增加的数量
* @returns {Object} 操作结果
*/
async function addDataField(db, openid, field, amount) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "用户未找到"
};
}
if (typeof amount !== 'number') {
return {
code: -3,
msg: "数量必须是数字"
};
}
const currentValue = user.data[field] || 0;
const newValue = Math.max(0, currentValue + amount);
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`data.${field}`]: _.set(newValue),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: {
field: field,
old_value: currentValue,
new_value: newValue,
change: amount
},
msg: `${field} 更新成功`
};
} else {
return {
code: -1,
msg: `更新 ${field} 失败`
};
}
} catch (error) {
console.error(`增加 ${field} 错误:`, error);
return {
code: -5,
msg: `增加 ${field} 错误: ${error.message}`
};
}
}
/**
* 消耗指定字段的数值
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string|Object[]} field 字段名或字段对象数组 [{field: 字段名, amount: 数量}]
* @param {number} [amount] 消耗的数量 (当field为字符串时使用)
* @returns {Object} 操作结果
*/
async function spendDataField(db, openid, field, amount) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "用户未找到"
};
}
// 处理单个字段的情况
if (typeof field === 'string') {
if (typeof amount !== 'number' || amount < 0) {
return {
code: -3,
msg: "数量必须是正数"
};
}
const currentValue = user.data[field] || 0;
if (currentValue < amount) {
return {
code: -6,
msg: `${field} 不足, 当前: ${currentValue}, 需要: ${amount}`
};
}
return await addDataField(db, openid, field, -amount);
}
// 处理多个字段的情况
if (Array.isArray(field)) {
const fieldsToSpend = field;
// 验证参数格式
for (const item of fieldsToSpend) {
if (!item.field || typeof item.amount !== 'number' || item.amount < 0) {
return {
code: -3,
msg: "字段参数格式错误,需要 {field, amount} 结构且amount必须是正数"
};
}
}
// 检查所有资源是否足够
for (const item of fieldsToSpend) {
const currentValue = user.data[item.field] || 0;
if (currentValue < item.amount) {
return {
code: -6,
msg: `${item.field} 不足, 当前: ${currentValue}, 需要: ${item.amount}`
};
}
}
// 所有资源都足够,开始扣除
const updateData = {};
const changes = [];
for (const item of fieldsToSpend) {
const currentValue = user.data[item.field] || 0;
const newValue = Math.max(0, currentValue - item.amount);
updateData[`data.${item.field}`] = _.set(newValue);
changes.push({
field: item.field,
old_value: currentValue,
new_value: newValue,
change: -item.amount
});
}
// 更新数据库
updateData['last_save_time'] = _.set(Date.now());
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: updateData
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: changes,
msg: "资源消耗成功"
};
} else {
return {
code: -1,
msg: "资源消耗失败"
};
}
}
return {
code: -3,
msg: "参数格式错误"
};
} catch (error) {
console.error(`消耗资源错误:`, error);
return {
code: -5,
msg: `消耗资源错误: ${error.message}`
};
}
}
/**
* 设置指定字段的数值
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} field 字段名
* @param {any} value 新的值
* @returns {Object} 操作结果
*/
async function setDataField(db, openid, field, value) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "用户未找到"
};
}
const oldValue = user.data[field];
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`data.${field}`]: _.set(value),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: {
field: field,
old_value: oldValue,
new_value: value
},
msg: `${field} 设置成功`
};
} else {
return {
code: -1,
msg: `设置 ${field} 失败`
};
}
} catch (error) {
console.error(`设置 ${field} 错误:`, error);
return {
code: -5,
msg: `设置 ${field} 错误: ${error.message}`
};
}
}
/**
* 重置基础游戏数据
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 操作结果
*/
async function resetData(db, openid) {
try {
const _ = db.command;
const { getNewUserInitData } = require('../user_init_data');
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "用户未找到"
};
}
const defaultData = getNewUserInitData();
let resetRes = await db.collection(user_db_name).doc(user._id).update({
data: {
data: _.set(defaultData.data),
last_save_time: _.set(Date.now()),
reset_time: _.set(Date.now())
}
});
if (resetRes?.stats?.updated >= 1) {
return {
code: 200,
data: defaultData.data,
msg: "游戏数据重置成功"
};
} else {
return {
code: -1,
msg: `重置失败, ${JSON.stringify(resetRes)}`
};
}
} catch (error) {
console.error("重置游戏数据错误:", error);
return {
code: -5,
msg: `重置游戏数据错误: ${error.message}`
};
}
}
module.exports = {
getData,
updateData,
addDataField,
spendDataField,
setDataField,
resetData
};

View File

@@ -1,440 +0,0 @@
// 英雄数据操作模块
const { getOrCreaterUser } = require('./auth');
const user_db_name = "cocos_users";
/**
* 获取所有英雄数据
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 操作结果
*/
async function getHeros(db, openid) {
try {
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
return {
code: 200,
data: user.heros,
msg: "获取英雄列表成功"
};
} catch (error) {
console.error("获取英雄列表错误:", error);
return {
code: -5,
msg: `获取英雄列表错误: ${error.message}`
};
}
}
/**
* 获取单个英雄数据
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {number} heroId 英雄ID
* @returns {Object} 操作结果
*/
async function getHero(db, openid, heroId) {
try {
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
const hero = user.heros[heroId];
if (!hero) {
return {
code: -6,
msg: "未找到英雄"
};
}
return {
code: 200,
data: hero,
msg: "获取英雄成功"
};
} catch (error) {
console.error("获取英雄错误:", error);
return {
code: -5,
msg: `获取英雄错误: ${error.message}`
};
}
}
/**
* 添加新英雄
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {number} heroId 英雄ID
* @param {Object} heroData 英雄数据(可选)
* @returns {Object} 操作结果
*/
async function addHero(db, openid, heroId, heroData = null) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
// 检查英雄是否已存在
if (user.heros[heroId]) {
return {
code: -7,
msg: "英雄已存在"
};
}
// 使用提供的数据或默认数据
const newHero = heroData || {
uuid: heroId,
lv: 1,
exp: 0,
star: 1,
power: 100
};
// 确保uuid字段正确
newHero.uuid = heroId;
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`heros.${heroId}`]: _.set(newHero),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: newHero,
msg: `英雄 ${heroId} 添加成功`
};
} else {
return {
code: -1,
msg: `添加英雄失败`
};
}
} catch (error) {
console.error("添加英雄错误:", error);
return {
code: -5,
msg: `添加英雄错误: ${error.message}`
};
}
}
/**
* 更新英雄属性
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {number} heroId 英雄ID
* @param {Object} updateData 要更新的属性
* @returns {Object} 操作结果
*/
async function updateHero(db, openid, heroId, updateData) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
// 检查英雄是否存在
if (!user.heros[heroId]) {
return {
code: -6,
msg: "未找到英雄"
};
}
// 验证更新数据
if (!updateData || typeof updateData !== 'object') {
return {
code: -3,
msg: "无效的更新数据格式"
};
}
const currentHero = user.heros[heroId];
const newHero = { ...currentHero, ...updateData };
// 确保uuid字段不被修改
newHero.uuid = heroId;
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`heros.${heroId}`]: _.set(newHero),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: {
old_data: currentHero,
new_data: newHero
},
msg: `英雄 ${heroId} 更新成功`
};
} else {
return {
code: -1,
msg: `更新英雄失败`
};
}
} catch (error) {
console.error("更新英雄错误:", error);
return {
code: -5,
msg: `更新英雄错误: ${error.message}`
};
}
}
/**
* 设置英雄属性
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {number} heroId 英雄ID
* @param {string} property 属性名
* @param {any} value 属性值
* @returns {Object} 操作结果
*/
async function setHeroProperty(db, openid, heroId, property, value) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
// 检查英雄是否存在
if (!user.heros[heroId]) {
return {
code: -6,
msg: "未找到英雄"
};
}
// 防止修改uuid
if (property === 'uuid') {
return {
code: -3,
msg: "不可修改英雄UUID"
};
}
const oldValue = user.heros[heroId][property];
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`heros.${heroId}.${property}`]: _.set(value),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: {
hero_id: heroId,
property: property,
old_value: oldValue,
new_value: value
},
msg: `英雄 ${heroId} 属性 ${property} 更新成功`
};
} else {
return {
code: -1,
msg: `设置英雄属性失败`
};
}
} catch (error) {
console.error("设置英雄属性错误:", error);
return {
code: -5,
msg: `设置英雄属性错误: ${error.message}`
};
}
}
/**
* 英雄升级
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {number} heroId 英雄ID
* @param {number} levels 升级级数默认1级
* @returns {Object} 操作结果
*/
async function levelUpHero(db, openid, heroId,levels = 1) {
try {
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
// 检查英雄是否存在
if (!user.heros[heroId]) {
return {
code: -6,
msg: "未找到英雄"
};
}
if (typeof levels !== 'number' || levels < 1) {
return {
code: -3,
msg: "等级必须为正数"
};
}
const currentLevel = user.heros[heroId].lv || 1;
const newLevel = currentLevel + levels;
return await setHeroProperty(db, openid, heroId, 'lv', newLevel);
} catch (error) {
console.error("英雄升级错误:", error);
return {
code: -5,
msg: `英雄升级错误: ${error.message}`
};
}
}
/**
* 删除英雄
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {number} heroId 英雄ID
* @returns {Object} 操作结果
*/
async function deleteHero(db, openid, heroId) {
try {
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
// 检查英雄是否存在
if (!user.heros[heroId]) {
return {
code: -6,
msg: "未找到英雄"
};
}
// 检查英雄是否在出战阵容中
for (let pos = 0; pos < 5; pos++) {
if (user.fight_heros[pos] === heroId) {
return {
code: -8,
msg: `英雄位于出战位置 ${pos},请先从出战阵容移除`
};
}
}
const deletedHero = user.heros[heroId];
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`heros.${heroId}`]: _.remove(),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: deletedHero,
msg: `英雄 ${heroId} 删除成功`
};
} else {
return {
code: -1,
msg: `删除英雄失败`
};
}
} catch (error) {
console.error("删除英雄错误:", error);
return {
code: -5,
msg: `删除英雄错误: ${error.message}`
};
}
}
/**
* 获取已拥有的英雄ID列表
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @returns {Object} 操作结果
*/
async function getOwnedHeroIds(db, openid) {
try {
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
const heroIds = Object.keys(user.heros).map(Number);
return {
code: 200,
data: {
hero_ids: heroIds,
total_count: heroIds.length
},
msg: "获取已拥有英雄ID成功"
};
} catch (error) {
console.error("获取已拥有英雄ID错误:", error);
return {
code: -5,
msg: `获取已拥有英雄ID错误: ${error.message}`
};
}
}
module.exports = {
getHeros,
getHero,
addHero,
updateHero,
setHeroProperty,
levelUpHero,
deleteHero,
getOwnedHeroIds
};

View File

@@ -1,501 +0,0 @@
// 通用库存操作模块 (items, tals, equips)
const { getOrCreaterUser } = require('./auth');
const user_db_name = "cocos_users";
// 支持的数据类型
const SUPPORTED_TYPES = ['items', 'tals', 'equips'];
/**
* 验证数据类型
* @param {string} dataType 数据类型
* @returns {boolean} 是否有效
*/
function validateDataType(dataType) {
return SUPPORTED_TYPES.includes(dataType);
}
/**
* 获取库存数据
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} dataType 数据类型 ('items', 'tals', 'equips')
* @returns {Object} 操作结果
*/
async function getInventory(db, openid, dataType) {
try {
if (!validateDataType(dataType)) {
return {
code: -3,
msg: `无效的数据类型: ${dataType}`
};
}
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
return {
code: 200,
data: user[dataType],
msg: `${dataType}获取成功`
};
} catch (error) {
console.error(`获取${dataType}错误:`, error);
return {
code: -5,
msg: `获取${dataType}错误: ${error.message}`
};
}
}
/**
* 获取单个物品数据
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} dataType 数据类型
* @param {number} itemId 物品ID
* @returns {Object} 操作结果
*/
async function getInventoryItem(db, openid, dataType, itemId) {
try {
if (!validateDataType(dataType)) {
return {
code: -3,
msg: `无效的数据类型: ${dataType}`
};
}
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
const itemCount = user[dataType][itemId];
if (itemCount === undefined) {
return {
code: -6,
msg: `${dataType.slice(0, -1)} ${itemId} 未找到`
};
}
return {
code: 200,
data: {
item_id: itemId,
count: itemCount
},
msg: `${dataType.slice(0, -1)} ${itemId} 获取成功`
};
} catch (error) {
console.error(`获取${dataType}物品错误:`, error);
return {
code: -5,
msg: `获取${dataType}物品错误: ${error.message}`
};
}
}
/**
* 添加物品
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} dataType 数据类型
* @param {number} itemId 物品ID
* @param {number} count 添加数量
* @returns {Object} 操作结果
*/
async function addInventoryItem(db, openid, dataType, itemId, count) {
try {
if (!validateDataType(dataType)) {
return {
code: -3,
msg: `无效的数据类型: ${dataType}`
};
}
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
if (typeof count !== 'number' || count < 0) {
return {
code: -3,
msg: "数量必须是非负数"
};
}
const currentCount = user[dataType][itemId] || 0;
const newCount = currentCount + count;
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`${dataType}.${itemId}`]: _.set(newCount),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: {
item_id: itemId,
old_count: currentCount,
new_count: newCount,
added: count
},
msg: `${dataType.slice(0, -1)} ${itemId} 添加成功`
};
} else {
return {
code: -1,
msg: `添加 ${dataType.slice(0, -1)} 失败`
};
}
} catch (error) {
console.error(`添加${dataType}物品错误:`, error);
return {
code: -5,
msg: `添加${dataType}物品错误: ${error.message}`
};
}
}
/**
* 消耗物品
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} dataType 数据类型
* @param {number} itemId 物品ID
* @param {number} count 消耗数量
* @returns {Object} 操作结果
*/
async function consumeInventoryItem(db, openid, dataType, itemId, count) {
try {
if (!validateDataType(dataType)) {
return {
code: -3,
msg: `无效的数据类型: ${dataType}`
};
}
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
if (typeof count !== 'number' || count < 0) {
return {
code: -3,
msg: "数量必须是非负数"
};
}
const currentCount = user[dataType][itemId] || 0;
if (currentCount < count) {
return {
code: -6,
msg: `${dataType.slice(0, -1)} ${itemId}不足, 当前: ${currentCount}, 需要: ${count}`
};
}
return await addInventoryItem(db, openid, dataType, itemId, -count);
} catch (error) {
console.error(`消耗${dataType}物品错误:`, error);
return {
code: -5,
msg: `消耗${dataType}物品错误: ${error.message}`
};
}
}
/**
* 设置物品数量
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} dataType 数据类型
* @param {number} itemId 物品ID
* @param {number} count 新的数量
* @returns {Object} 操作结果
*/
async function setInventoryItem(db, openid, dataType, itemId, count) {
try {
if (!validateDataType(dataType)) {
return {
code: -3,
msg: `无效的数据类型: ${dataType}`
};
}
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "未找到用户"
};
}
if (typeof count !== 'number' || count < 0) {
return {
code: -3,
msg: "数量必须是非负数"
};
}
const oldCount = user[dataType][itemId] || 0;
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[`${dataType}.${itemId}`]: _.set(count),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: {
item_id: itemId,
old_count: oldCount,
new_count: count
},
msg: `${dataType.slice(0, -1)} ${itemId} 设置成功`
};
} else {
return {
code: -1,
msg: `设置 ${dataType.slice(0, -1)} 失败`
};
}
} catch (error) {
console.error(`设置${dataType}物品错误:`, error);
return {
code: -5,
msg: `设置${dataType}物品错误: ${error.message}`
};
}
}
/**
* 批量更新库存
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} dataType 数据类型
* @param {Object} updateData 更新数据对象
* @param {boolean} merge 是否合并更新默认true
* @returns {Object} 操作结果
*/
async function updateInventory(db, openid, dataType, updateData, merge = true) {
try {
if (!validateDataType(dataType)) {
return {
code: -3,
msg: `Invalid data type: ${dataType}`
};
}
const _ = db.command;
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "User not found"
};
}
// 验证更新数据
if (!updateData || typeof updateData !== 'object') {
return {
code: -3,
msg: "Invalid update data format"
};
}
// 验证所有值都是非负数
for (const itemId in updateData) {
const count = updateData[itemId];
if (typeof count !== 'number' || count < 0) {
return {
code: -3,
msg: `Invalid count for item ${itemId}: ${count}`
};
}
}
let newData;
if (merge) {
// 合并更新
newData = { ...user[dataType], ...updateData };
} else {
// 完全替换
newData = updateData;
}
let updateRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[dataType]: _.set(newData),
last_save_time: _.set(Date.now())
}
});
if (updateRes?.stats?.updated >= 1) {
return {
code: 200,
data: newData,
msg: `${dataType} updated successfully`
};
} else {
return {
code: -1,
msg: `Update ${dataType} fail`
};
}
} catch (error) {
console.error(`Update ${dataType} error:`, error);
return {
code: -5,
msg: `Update ${dataType} error: ${error.message}`
};
}
}
/**
* 重置库存数据
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} dataType 数据类型
* @returns {Object} 操作结果
*/
async function resetInventory(db, openid, dataType) {
try {
if (!validateDataType(dataType)) {
return {
code: -3,
msg: `Invalid data type: ${dataType}`
};
}
const _ = db.command;
const { getNewUserInitData } = require('../user_init_data');
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "User not found"
};
}
const defaultData = getNewUserInitData();
let resetRes = await db.collection(user_db_name).doc(user._id).update({
data: {
[dataType]: _.set(defaultData[dataType]),
last_save_time: _.set(Date.now()),
reset_time: _.set(Date.now())
}
});
if (resetRes?.stats?.updated >= 1) {
return {
code: 200,
data: defaultData[dataType],
msg: `${dataType} reset successfully`
};
} else {
return {
code: -1,
msg: `Reset ${dataType} fail`
};
}
} catch (error) {
console.error(`Reset ${dataType} error:`, error);
return {
code: -5,
msg: `Reset ${dataType} error: ${error.message}`
};
}
}
/**
* 获取拥有的物品列表数量大于0的
* @param {Object} db 数据库实例
* @param {string} openid 用户openid
* @param {string} dataType 数据类型
* @returns {Object} 操作结果
*/
async function getOwnedItems(db, openid, dataType) {
try {
if (!validateDataType(dataType)) {
return {
code: -3,
msg: `Invalid data type: ${dataType}`
};
}
let user = await getOrCreaterUser(db, openid);
if (!user) {
return {
code: -4,
msg: "User not found"
};
}
const ownedItems = [];
for (const itemId in user[dataType]) {
const count = user[dataType][itemId];
if (count > 0) {
ownedItems.push({
item_id: parseInt(itemId),
count: count
});
}
}
return {
code: 200,
data: {
owned_items: ownedItems,
total_types: ownedItems.length
},
msg: `Owned ${dataType} retrieved successfully`
};
} catch (error) {
console.error(`Get owned ${dataType} error:`, error);
return {
code: -5,
msg: `Get owned ${dataType} error: ${error.message}`
};
}
}
module.exports = {
getInventory,
getInventoryItem,
addInventoryItem,
consumeInventoryItem,
setInventoryItem,
updateInventory,
resetInventory,
getOwnedItems,
SUPPORTED_TYPES
};

View File

@@ -1,274 +0,0 @@
// 统一响应处理模块
const { DATA_VERSION } = require('../user_init_data');
/**
* 成功响应
* @param {any} data 返回数据
* @param {string} message 成功消息
* @param {Object} extra 额外信息
* @returns {Object} 响应对象
*/
function success(data = null, message = "成功", extra = {}) {
return {
code: 200,
data: data,
msg: message,
timestamp: Date.now(),
version: DATA_VERSION,
...extra
};
}
/**
* 错误响应
* @param {number} code 错误码
* @param {string} message 错误消息
* @param {any} data 错误相关数据
* @returns {Object} 响应对象
*/
function error(code, message, data = null) {
return {
code: code,
msg: message,
data: data,
timestamp: Date.now(),
version: DATA_VERSION
};
}
/**
* 参数错误响应
* @param {string} message 错误消息
* @param {any} data 错误相关数据
* @returns {Object} 响应对象
*/
function badRequest(message = "请求参数错误", data = null) {
return error(-3, message, data);
}
/**
* 用户未找到响应
* @param {string} message 错误消息
* @returns {Object} 响应对象
*/
function userNotFound(message = "未找到用户") {
return error(-4, message);
}
/**
* 系统错误响应
* @param {string} message 错误消息
* @param {Error} err 错误对象
* @returns {Object} 响应对象
*/
function systemError(message = "系统错误", err = null) {
const errorData = err ? {
error_message: err.message,
error_stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
} : null;
return error(-5, message, errorData);
}
/**
* 资源不足响应
* @param {string} resource 资源名称
* @param {number} current 当前数量
* @param {number} required 需要数量
* @returns {Object} 响应对象
*/
function insufficientResource(resource, current = 0, required = 0) {
return error(-6, `${resource}不足`, {
resource: resource,
current: current,
required: required,
shortage: required - current
});
}
/**
* 资源已存在响应
* @param {string} resource 资源名称
* @param {any} identifier 资源标识
* @returns {Object} 响应对象
*/
function resourceExists(resource, identifier = null) {
return error(-7, `${resource}已存在`, {
resource: resource,
identifier: identifier
});
}
/**
* 操作被拒绝响应
* @param {string} reason 拒绝原因
* @param {any} data 相关数据
* @returns {Object} 响应对象
*/
function operationDenied(reason, data = null) {
return error(-8, `操作被拒绝: ${reason}`, data);
}
/**
* 未知命令响应
* @param {string} command 命令名
* @param {Array} availableCommands 可用命令列表
* @returns {Object} 响应对象
*/
function unknownCommand(command, availableCommands = []) {
return error(-2, `未知命令: ${command}`, {
command: command,
available_commands: availableCommands
});
}
/**
* 包装异步操作,统一错误处理
* @param {Function} operation 异步操作函数
* @param {string} operationName 操作名称
* @returns {Function} 包装后的函数
*/
function wrapAsync(operation, operationName = "Operation") {
return async (...args) => {
try {
const result = await operation(...args);
// 如果操作返回的是错误格式,直接返回
if (result && typeof result === 'object' && result.code !== undefined) {
return result;
}
// 如果是成功结果,包装为成功响应
return success(result, `${operationName}成功完成`);
} catch (error) {
console.error(`${operationName}错误:`, error);
return systemError(`${operationName}失败`, error);
}
};
}
/**
* 验证必需参数
* @param {Object} params 参数对象
* @param {Array} requiredFields 必需字段列表
* @returns {Object|null} 如果验证失败返回错误响应否则返回null
*/
function validateRequiredParams(params, requiredFields) {
if (!params || typeof params !== 'object') {
return badRequest("Invalid parameters");
}
const missingFields = [];
for (const field of requiredFields) {
if (params[field] === undefined || params[field] === null) {
missingFields.push(field);
}
}
if (missingFields.length > 0) {
return badRequest(`Missing required parameters: ${missingFields.join(', ')}`, {
missing_fields: missingFields,
required_fields: requiredFields
});
}
return null;
}
/**
* 验证数值参数
* @param {any} value 要验证的值
* @param {string} fieldName 字段名
* @param {Object} options 验证选项
* @returns {Object|null} 如果验证失败返回错误响应否则返回null
*/
function validateNumber(value, fieldName, options = {}) {
const {
min = Number.NEGATIVE_INFINITY,
max = Number.POSITIVE_INFINITY,
integer = false
} = options;
if (typeof value !== 'number' || isNaN(value)) {
return badRequest(`${fieldName} must be a number`);
}
if (integer && !Number.isInteger(value)) {
return badRequest(`${fieldName} must be an integer`);
}
if (value < min) {
return badRequest(`${fieldName} must be at least ${min}`);
}
if (value > max) {
return badRequest(`${fieldName} must be at most ${max}`);
}
return null;
}
/**
* 创建分页响应
* @param {Array} items 数据项
* @param {number} total 总数
* @param {number} page 当前页
* @param {number} pageSize 页大小
* @param {string} message 成功消息
* @returns {Object} 响应对象
*/
function paginated(items, total, page, pageSize, message = "Data retrieved successfully") {
const totalPages = Math.ceil(total / pageSize);
return success({
items: items,
pagination: {
current_page: page,
page_size: pageSize,
total_items: total,
total_pages: totalPages,
has_next: page < totalPages,
has_prev: page > 1
}
}, message);
}
/**
* 错误码常量
*/
const ERROR_CODES = {
SUCCESS: 200,
OPERATION_FAILED: -1,
UNKNOWN_COMMAND: -2,
BAD_REQUEST: -3,
USER_NOT_FOUND: -4,
SYSTEM_ERROR: -5,
INSUFFICIENT_RESOURCE: -6,
RESOURCE_EXISTS: -7,
OPERATION_DENIED: -8
};
module.exports = {
success,
error,
badRequest,
userNotFound,
systemError,
insufficientResource,
resourceExists,
operationDenied,
unknownCommand,
wrapAsync,
validateRequiredParams,
validateNumber,
paginated,
ERROR_CODES
};

View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../semver/bin/semver" "$@"
else
exec node "$basedir/../semver/bin/semver" "$@"
fi

View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver" $args
} else {
& "$basedir/node$exe" "$basedir/../semver/bin/semver" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../semver/bin/semver" $args
} else {
& "node$exe" "$basedir/../semver/bin/semver" $args
}
$ret=$LASTEXITCODE
}
exit $ret

View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../sshpk/bin/sshpk-conv" "$@"
else
exec node "$basedir/../sshpk/bin/sshpk-conv" "$@"
fi

View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sshpk\bin\sshpk-conv" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-conv" $args
} else {
& "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-conv" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../sshpk/bin/sshpk-conv" $args
} else {
& "node$exe" "$basedir/../sshpk/bin/sshpk-conv" $args
}
$ret=$LASTEXITCODE
}
exit $ret

View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../sshpk/bin/sshpk-sign" "$@"
else
exec node "$basedir/../sshpk/bin/sshpk-sign" "$@"
fi

View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sshpk\bin\sshpk-sign" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-sign" $args
} else {
& "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-sign" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../sshpk/bin/sshpk-sign" $args
} else {
& "node$exe" "$basedir/../sshpk/bin/sshpk-sign" $args
}
$ret=$LASTEXITCODE
}
exit $ret

View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../sshpk/bin/sshpk-verify" "$@"
else
exec node "$basedir/../sshpk/bin/sshpk-verify" "$@"
fi

View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\sshpk\bin\sshpk-verify" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-verify" $args
} else {
& "$basedir/node$exe" "$basedir/../sshpk/bin/sshpk-verify" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../sshpk/bin/sshpk-verify" $args
} else {
& "node$exe" "$basedir/../sshpk/bin/sshpk-verify" $args
}
$ret=$LASTEXITCODE
}
exit $ret

View File

@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../uuid/bin/uuid" "$@"
else
exec node "$basedir/../uuid/bin/uuid" "$@"
fi

View File

@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\uuid\bin\uuid" %*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../uuid/bin/uuid" $args
} else {
& "$basedir/node$exe" "$basedir/../uuid/bin/uuid" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../uuid/bin/uuid" $args
} else {
& "node$exe" "$basedir/../uuid/bin/uuid" $args
}
$ret=$LASTEXITCODE
}
exit $ret

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
root = true
[!{node_modules}/**]
end_of_line = lf
charset = utf-8
[{*.js,*.ts}]
indent_style = space
indent_size = 2

View File

@@ -0,0 +1,43 @@
{
"extends": [
"prettier"
],
"plugins": [
"typescript"
],
"rules": {
"indent": [
"error",
2,
{
"SwitchCase": 1,
"flatTernaryExpressions": true
}
],
"no-unused-vars": "warn",
"typescript/no-unused-vars": "warn",
"semi": [
"error",
"never"
],
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
]
},
"env": {
"es6": true,
"node": true
},
"parser": "typescript-eslint-parser",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"modules": true
}
}
}

View File

@@ -0,0 +1,29 @@
module.exports = {
// 一行最多 100 字符
printWidth: 100,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾无分号
semi: false,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// 末尾不需要逗号
trailingComma: 'none',
// 大括号内的首尾需要空格
bracketSpacing: true,
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 换行符使用 lf
endOfLine: 'lf'
}

View File

@@ -0,0 +1,119 @@
# @cloudbase/database
[![NPM Version](https://img.shields.io/npm/v/@cloudbase/database.svg?style=flat)](https://www.npmjs.com/package/@cloudbase/database)
[![](https://img.shields.io/npm/dt/@cloudbase/database.svg)](https://www.npmjs.com/package/@cloudbase/database)
## 介绍
提供 TCB JS SDK 操作数据库的接口。
## 安装
```
yarn add @cloudbase/database
```
## 使用
使用 `@cloudbase/database` 时,需要提供发送请求的类 `reqClass`
```js
// index.js
const database = require('@cloudbase/database/').Db;
const Request = require('./request');
class Tcb {
...
database(dbConfig) {
database.reqClass = Request;
return new database(dbConfig);
}
}
```
实现 `Request` 类,需要提供异步的 `send` 方法。
```js
// request.js
// 进一步处理,鉴权等...
const requestHandler = require('requestHandler')
class Request {
constructor(config) {
this.config = config
}
async send(action, data) {
const params = Object.assign({}, data, {
action
})
const slowQueryWarning = setTimeout(() => {
console.warn(
'Database operation is longer than 3s. Please check query performance and your network environment.'
)
}, 3000)
try {
return await requestHandler({
timeout: this.config.timeout,
config: this.config.config,
params,
method: 'post',
headers: {
'content-type': 'application/json'
}
})
} finally {
clearTimeout(slowQueryWarning)
}
}
}
module.exports = Request
```
请求数据样例
```json
{
"url": "https://tcb-admin.tencentcloudapi.com/admin?eventId=1554896261428_92044",
"method": "post",
"timeout": 15000,
"headers": { "user-agent": "tcb-admin-sdk/1.4.6", "content-type": "application/json" },
"body": {
"collectionName": "coll-1",
"action": "database.addCollection",
"envName": "base-830cab",
"timestamp": 1554896261428,
"eventId": "1554896261428_92044",
"authorization": "q-sign-algorithm=sha1&q-ak=xxx&q-sign-time=1554896260;1554897160&q-key-time=1554896260;1554897160&q-header-list=content-type;user-agent&q-url-param-list=action;collectionname;envname;eventid;timestamp&q-signature=xxxxx",
"sdk_version": "1.4.6"
},
"json": true
}
```
## 开发指南
### 注意
当前 database 库是通过分支来控制了两个不同的版本,主干版提供给@cloudbase/js-sdk 库使用feature/support_db_2.0 分支提供给@cloudbase/node-sdk 库使用。
两个分支区别:
support_db_2.0 分支进行了数据库接口的升级,将原有的 json 协议转换为 mongo 支持的 bson 协议处理,目的是解决旧接口存在的问题,描述如下
- 日期对象 是走约定协议处理,即转换为 {$date: timestamp},这种方式应摒弃(EJSON 协议可解决)
- 无法支持 null, NaN 等特殊类型处理(EJSON 协议可解决)
- serverDate 这种自定义类型的实现,是继续保留 还是摒弃
- 接口实现混杂,普通 CRUD 接口均走 flexdb而聚合事务又用 runcommand 方式
- 原插入文档接口返回 docId 形式混杂
目前仅针对服务端 sdk @cloudbase/node-sdk 完成了升级,而客户端 sdk @cloudbase/js-sdk 以及 小程序端 SDK 仍保留为旧接口形式。
### 开发及发布事项
1. 数据库接口的改动务必补充测试用例验证
2. 发布时,约定 feature/support_db_2.0 分支上发布正式版for @cloudbase/node-sdk使用master 分支上发布 beta 版 (for @cloudbase/js-sdk)使用。两 sdk 均通过锁版本方式依赖该库,不会受自动更新依赖影响。

View File

@@ -0,0 +1,11 @@
export declare class ObjectId {
id: string;
constructor({ id }?: {
id?: string;
});
readonly _internalType: import("../utils/symbol").InternalSymbol;
parse(): {
$oid: string;
};
}
export declare function ObjectIdConstructor(opt: any): ObjectId;

View File

@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const symbol_1 = require("../helper/symbol");
class ObjectId {
constructor({ id = '' } = {}) {
this.id = id;
}
get _internalType() {
return symbol_1.SYMBOL_OBJECTID;
}
parse() {
return {
$oid: this.id
};
}
}
exports.ObjectId = ObjectId;
function ObjectIdConstructor(opt) {
return new ObjectId(opt);
}
exports.ObjectIdConstructor = ObjectIdConstructor;

View File

@@ -0,0 +1,29 @@
export default class Aggregation {
_db: any;
_request: any;
_stages: any[];
_collectionName: string;
constructor(db?: any, collectionName?: any, rawPipeline?: any);
end(): Promise<any>;
unwrap(): any[];
done(): {
[x: number]: any;
}[];
_pipe(stage: any, param: any, raw?: boolean): this;
addFields(param: any): this;
bucket(param: any): this;
bucketAuto(param: any): this;
count(param: any): this;
geoNear(param: any): this;
group(param: any): this;
limit(param: any): this;
match(param: any): this;
project(param: any): this;
lookup(param: any): this;
replaceRoot(param: any): this;
sample(param: any): this;
skip(param: any): this;
sort(param: any): this;
sortByCount(param: any): this;
unwind(param: any): this;
}

View File

@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("./index");
const bson_1 = require("bson");
const query_1 = require("./serializer/query");
const utils_1 = require("./utils/utils");
const type_1 = require("./utils/type");
const validate_1 = require("./validate");
const point_1 = require("./geo/point");
class Aggregation {
constructor(db, collectionName, rawPipeline) {
this._stages = [];
if (db && collectionName) {
this._db = db;
this._request = new index_1.Db.reqClass(this._db.config);
this._collectionName = collectionName;
if (rawPipeline && rawPipeline.length > 0) {
rawPipeline.forEach((stage) => {
validate_1.Validate.isValidAggregation(stage);
const stageName = Object.keys(stage)[0];
this._pipe(stageName, stage[stageName], true);
});
}
}
}
async end() {
if (!this._collectionName || !this._db) {
throw new Error('Aggregation pipeline cannot send request');
}
const result = await this._request.send('database.aggregateDocuments', {
collectionName: this._collectionName,
stages: this._stages
});
if (result && result.data && result.data.list) {
return {
requestId: result.requestId,
data: result.data.list.map(bson_1.EJSON.parse)
};
}
return result;
}
unwrap() {
return this._stages;
}
done() {
return this._stages.map(({ stageKey, stageValue }) => {
return {
[stageKey]: JSON.parse(stageValue)
};
});
}
_pipe(stage, param, raw = false) {
let transformParam = '';
if (type_1.getType(param) === 'object') {
transformParam = utils_1.stringifyByEJSON(param);
}
else {
transformParam = JSON.stringify(param);
}
this._stages.push({
stageKey: raw ? stage : `$${stage}`,
stageValue: transformParam
});
return this;
}
addFields(param) {
return this._pipe('addFields', param);
}
bucket(param) {
return this._pipe('bucket', param);
}
bucketAuto(param) {
return this._pipe('bucketAuto', param);
}
count(param) {
return this._pipe('count', param);
}
geoNear(param) {
if (param.query) {
param.query = query_1.QuerySerializer.encode(param.query);
}
if (param.distanceMultiplier && typeof (param.distanceMultiplier) === 'number') {
param.distanceMultiplier = param.distanceMultiplier;
}
if (param.near) {
param.near = new point_1.Point(param.near.longitude, param.near.latitude).toJSON();
}
return this._pipe('geoNear', param);
}
group(param) {
return this._pipe('group', param);
}
limit(param) {
return this._pipe('limit', param);
}
match(param) {
return this._pipe('match', query_1.QuerySerializer.encode(param));
}
project(param) {
return this._pipe('project', param);
}
lookup(param) {
return this._pipe('lookup', param);
}
replaceRoot(param) {
return this._pipe('replaceRoot', param);
}
sample(param) {
return this._pipe('sample', param);
}
skip(param) {
return this._pipe('skip', param);
}
sort(param) {
return this._pipe('sort', param);
}
sortByCount(param) {
return this._pipe('sortByCount', param);
}
unwind(param) {
return this._pipe('unwind', param);
}
}
exports.default = Aggregation;

View File

@@ -0,0 +1,17 @@
import { DocumentReference } from './document';
import { Query, QueryOption, UpdateOption } from './query';
import Aggregation from './aggregate';
export declare class CollectionReference extends Query {
protected _transactionId: string;
readonly name: string;
doc(docID: string | number): DocumentReference;
add(data: any): Promise<{
ids?: string[];
id?: string;
inserted?: number;
ok?: number;
requestId: string;
}>;
aggregate(rawPipeline?: object[]): Aggregation;
options(apiOptions: QueryOption | UpdateOption): CollectionReference;
}

View File

@@ -0,0 +1,72 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const document_1 = require("./document");
const query_1 = require("./query");
const aggregate_1 = require("./aggregate");
const datatype_1 = require("./serializer/datatype");
const utils_1 = require("./utils/utils");
const validate_1 = require("./validate");
const type_1 = require("./utils/type");
class CollectionReference extends query_1.Query {
constructor(db, coll, apiOptions, transactionId) {
super(db, coll, '', apiOptions, transactionId);
if (transactionId) {
this._transactionId = transactionId;
}
}
get name() {
return this._coll;
}
doc(docID) {
if (typeof docID !== 'string' && typeof docID !== 'number') {
throw new Error('docId必须为字符串或数字');
}
return new document_1.DocumentReference(this._db, this._coll, this._apiOptions, docID, this._transactionId);
}
async add(data) {
let transformData = data;
if (!type_1.isArray(data)) {
transformData = [data];
}
transformData = transformData.map(item => {
return utils_1.stringifyByEJSON(datatype_1.serialize(item));
});
let params = {
collectionName: this._coll,
data: transformData
};
if (this._transactionId) {
params.transactionId = this._transactionId;
}
const res = await this._request.send('database.insertDocument', params, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
if (!type_1.isArray(data)) {
if (this._transactionId) {
return {
inserted: 1,
ok: 1,
id: res.data.insertedIds[0],
requestId: res.requestId
};
}
return {
id: res.data.insertedIds[0],
requestId: res.requestId
};
}
return {
ids: res.data.insertedIds,
requestId: res.requestId
};
}
aggregate(rawPipeline = []) {
return new aggregate_1.default(this._db, this._coll, (this._apiOptions.raw || false) ? rawPipeline : []);
}
options(apiOptions) {
validate_1.Validate.isValidOptions(apiOptions);
return new CollectionReference(this._db, this._coll, apiOptions, this._transactionId);
}
}
exports.CollectionReference = CollectionReference;

View File

@@ -0,0 +1,175 @@
import { QueryCommand } from './commands/query';
import { LogicCommand } from './commands/logic';
import { UpdateCommand } from './commands/update';
import Aggregation from './aggregate';
export declare type IQueryCondition = Record<string, any> | LogicCommand;
export declare const Command: {
eq(val: any): QueryCommand;
neq(val: any): QueryCommand;
lt(val: any): QueryCommand;
lte(val: any): QueryCommand;
gt(val: any): QueryCommand;
gte(val: any): QueryCommand;
in(val: any): QueryCommand;
nin(val: any): QueryCommand;
all(val: any): QueryCommand;
elemMatch(val: any): QueryCommand;
exists(val: boolean): QueryCommand;
size(val: number): QueryCommand;
mod(): QueryCommand;
geoNear(val: any): QueryCommand;
geoWithin(val: any): QueryCommand;
geoIntersects(val: any): QueryCommand;
and(...__expressions__: import("./serializer/datatype").IQueryCondition[]): LogicCommand;
nor(...__expressions__: import("./serializer/datatype").IQueryCondition[]): LogicCommand;
or(...__expressions__: import("./serializer/datatype").IQueryCondition[]): LogicCommand;
not(...__expressions__: import("./serializer/datatype").IQueryCondition[]): LogicCommand;
set(val: any): UpdateCommand;
remove(): UpdateCommand;
inc(val: number): UpdateCommand;
mul(val: number): UpdateCommand;
push(...args: any[]): UpdateCommand;
pull(values: any): UpdateCommand;
pullAll(values: any): UpdateCommand;
pop(): UpdateCommand;
shift(): UpdateCommand;
unshift(...__values__: any[]): UpdateCommand;
addToSet(values: any): UpdateCommand;
rename(values: any): UpdateCommand;
bit(values: any): UpdateCommand;
max(values: any): UpdateCommand;
min(values: any): UpdateCommand;
expr(values: AggregationOperator): {
$expr: AggregationOperator;
};
jsonSchema(schema: any): {
$jsonSchema: any;
};
text(values: string | {
search: string;
language?: string;
caseSensitive?: boolean;
diacriticSensitive: boolean;
}): {
$search: {
(regexp: string | RegExp): number;
(searcher: {
[Symbol.search](string: string): number;
}): number;
};
$language?: undefined;
$caseSensitive?: undefined;
$diacriticSensitive?: undefined;
} | {
$search: string;
$language: string;
$caseSensitive: boolean;
$diacriticSensitive: boolean;
};
aggregate: {
pipeline(): Aggregation;
abs: (param: any) => AggregationOperator;
add: (param: any) => AggregationOperator;
ceil: (param: any) => AggregationOperator;
divide: (param: any) => AggregationOperator;
exp: (param: any) => AggregationOperator;
floor: (param: any) => AggregationOperator;
ln: (param: any) => AggregationOperator;
log: (param: any) => AggregationOperator;
log10: (param: any) => AggregationOperator;
mod: (param: any) => AggregationOperator;
multiply: (param: any) => AggregationOperator;
pow: (param: any) => AggregationOperator;
sqrt: (param: any) => AggregationOperator;
subtract: (param: any) => AggregationOperator;
trunc: (param: any) => AggregationOperator;
arrayElemAt: (param: any) => AggregationOperator;
arrayToObject: (param: any) => AggregationOperator;
concatArrays: (param: any) => AggregationOperator;
filter: (param: any) => AggregationOperator;
in: (param: any) => AggregationOperator;
indexOfArray: (param: any) => AggregationOperator;
isArray: (param: any) => AggregationOperator;
map: (param: any) => AggregationOperator;
range: (param: any) => AggregationOperator;
reduce: (param: any) => AggregationOperator;
reverseArray: (param: any) => AggregationOperator;
size: (param: any) => AggregationOperator;
slice: (param: any) => AggregationOperator;
zip: (param: any) => AggregationOperator;
and: (param: any) => AggregationOperator;
not: (param: any) => AggregationOperator;
or: (param: any) => AggregationOperator;
cmp: (param: any) => AggregationOperator;
eq: (param: any) => AggregationOperator;
gt: (param: any) => AggregationOperator;
gte: (param: any) => AggregationOperator;
lt: (param: any) => AggregationOperator;
lte: (param: any) => AggregationOperator;
neq: (param: any) => AggregationOperator;
cond: (param: any) => AggregationOperator;
ifNull: (param: any) => AggregationOperator;
switch: (param: any) => AggregationOperator;
dateFromParts: (param: any) => AggregationOperator;
dateFromString: (param: any) => AggregationOperator;
dayOfMonth: (param: any) => AggregationOperator;
dayOfWeek: (param: any) => AggregationOperator;
dayOfYear: (param: any) => AggregationOperator;
isoDayOfWeek: (param: any) => AggregationOperator;
isoWeek: (param: any) => AggregationOperator;
isoWeekYear: (param: any) => AggregationOperator;
millisecond: (param: any) => AggregationOperator;
minute: (param: any) => AggregationOperator;
month: (param: any) => AggregationOperator;
second: (param: any) => AggregationOperator;
hour: (param: any) => AggregationOperator;
week: (param: any) => AggregationOperator;
year: (param: any) => AggregationOperator;
literal: (param: any) => AggregationOperator;
mergeObjects: (param: any) => AggregationOperator;
objectToArray: (param: any) => AggregationOperator;
allElementsTrue: (param: any) => AggregationOperator;
anyElementTrue: (param: any) => AggregationOperator;
setDifference: (param: any) => AggregationOperator;
setEquals: (param: any) => AggregationOperator;
setIntersection: (param: any) => AggregationOperator;
setIsSubset: (param: any) => AggregationOperator;
setUnion: (param: any) => AggregationOperator;
concat: (param: any) => AggregationOperator;
dateToString: (param: any) => AggregationOperator;
indexOfBytes: (param: any) => AggregationOperator;
indexOfCP: (param: any) => AggregationOperator;
split: (param: any) => AggregationOperator;
strLenBytes: (param: any) => AggregationOperator;
strLenCP: (param: any) => AggregationOperator;
strcasecmp: (param: any) => AggregationOperator;
substr: (param: any) => AggregationOperator;
substrBytes: (param: any) => AggregationOperator;
substrCP: (param: any) => AggregationOperator;
toLower: (param: any) => AggregationOperator;
toUpper: (param: any) => AggregationOperator;
meta: (param: any) => AggregationOperator;
addToSet: (param: any) => AggregationOperator;
avg: (param: any) => AggregationOperator;
first: (param: any) => AggregationOperator;
last: (param: any) => AggregationOperator;
max: (param: any) => AggregationOperator;
min: (param: any) => AggregationOperator;
push: (param: any) => AggregationOperator;
stdDevPop: (param: any) => AggregationOperator;
stdDevSamp: (param: any) => AggregationOperator;
sum: (param: any) => AggregationOperator;
let: (param: any) => AggregationOperator;
};
project: {
slice: (param: any) => ProjectionOperator;
elemMatch: (param: any) => ProjectionOperator;
};
};
export declare class AggregationOperator {
constructor(name: any, param: any);
}
export declare class ProjectionOperator {
constructor(name: any, param: any);
}
export default Command;

View File

@@ -0,0 +1,285 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const query_1 = require("./commands/query");
const logic_1 = require("./commands/logic");
const update_1 = require("./commands/update");
const type_1 = require("./utils/type");
const aggregate_1 = require("./aggregate");
exports.Command = {
eq(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.EQ, [val]);
},
neq(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.NEQ, [val]);
},
lt(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.LT, [val]);
},
lte(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.LTE, [val]);
},
gt(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.GT, [val]);
},
gte(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.GTE, [val]);
},
in(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.IN, val);
},
nin(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.NIN, val);
},
all(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.ALL, val);
},
elemMatch(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.ELEM_MATCH, [val]);
},
exists(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.EXISTS, [val]);
},
size(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.SIZE, [val]);
},
mod() {
if (arguments.length == 1)
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.MOD, [arguments[0]]);
if (arguments.length == 2)
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.MOD, [[arguments[0], arguments[1]]]);
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.MOD, arguments);
},
geoNear(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.GEO_NEAR, [val]);
},
geoWithin(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.GEO_WITHIN, [val]);
},
geoIntersects(val) {
return new query_1.QueryCommand(query_1.QUERY_COMMANDS_LITERAL.GEO_INTERSECTS, [val]);
},
and(...__expressions__) {
const expressions = type_1.isArray(arguments[0]) ? arguments[0] : Array.from(arguments);
return new logic_1.LogicCommand(logic_1.LOGIC_COMMANDS_LITERAL.AND, expressions);
},
nor(...__expressions__) {
const expressions = type_1.isArray(arguments[0]) ? arguments[0] : Array.from(arguments);
return new logic_1.LogicCommand(logic_1.LOGIC_COMMANDS_LITERAL.NOR, expressions);
},
or(...__expressions__) {
const expressions = type_1.isArray(arguments[0]) ? arguments[0] : Array.from(arguments);
return new logic_1.LogicCommand(logic_1.LOGIC_COMMANDS_LITERAL.OR, expressions);
},
not(...__expressions__) {
const expressions = type_1.isArray(arguments[0]) ? arguments[0] : Array.from(arguments);
return new logic_1.LogicCommand(logic_1.LOGIC_COMMANDS_LITERAL.NOT, expressions);
},
set(val) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.SET, [val]);
},
remove() {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.REMOVE, []);
},
inc(val) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.INC, [val]);
},
mul(val) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.MUL, [val]);
},
push(...args) {
let values;
if (type_1.isObject(args[0]) && args[0].hasOwnProperty('each')) {
values = {};
const options = args[0];
if (options.each !== undefined) {
values['$each'] = options.each;
}
if (options.position !== undefined) {
values['$position'] = options.position;
}
if (options.sort !== undefined) {
values['$sort'] = options.sort;
}
if (options.slice !== undefined) {
values['$slice'] = options.slice;
}
}
else if (type_1.isArray(args[0])) {
values = args[0];
}
else {
values = Array.from(args);
}
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.PUSH, values);
},
pull(values) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.PULL, values);
},
pullAll(values) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.PULL_ALL, values);
},
pop() {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.POP, []);
},
shift() {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.SHIFT, []);
},
unshift(...__values__) {
const values = type_1.isArray(arguments[0]) ? arguments[0] : Array.from(arguments);
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.UNSHIFT, values);
},
addToSet(values) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.ADD_TO_SET, values);
},
rename(values) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.RENAME, [values]);
},
bit(values) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.BIT, [values]);
},
max(values) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.MAX, [values]);
},
min(values) {
return new update_1.UpdateCommand(update_1.UPDATE_COMMANDS_LITERAL.MIN, [values]);
},
expr(values) {
return {
$expr: values
};
},
jsonSchema(schema) {
return {
$jsonSchema: schema
};
},
text(values) {
if (type_1.isString(values)) {
return {
$search: values.search
};
}
else {
return {
$search: values.search,
$language: values.language,
$caseSensitive: values.caseSensitive,
$diacriticSensitive: values.diacriticSensitive
};
}
},
aggregate: {
pipeline() {
return new aggregate_1.default();
},
abs: param => new AggregationOperator('abs', param),
add: param => new AggregationOperator('add', param),
ceil: param => new AggregationOperator('ceil', param),
divide: param => new AggregationOperator('divide', param),
exp: param => new AggregationOperator('exp', param),
floor: param => new AggregationOperator('floor', param),
ln: param => new AggregationOperator('ln', param),
log: param => new AggregationOperator('log', param),
log10: param => new AggregationOperator('log10', param),
mod: param => new AggregationOperator('mod', param),
multiply: param => new AggregationOperator('multiply', param),
pow: param => new AggregationOperator('pow', param),
sqrt: param => new AggregationOperator('sqrt', param),
subtract: param => new AggregationOperator('subtract', param),
trunc: param => new AggregationOperator('trunc', param),
arrayElemAt: param => new AggregationOperator('arrayElemAt', param),
arrayToObject: param => new AggregationOperator('arrayToObject', param),
concatArrays: param => new AggregationOperator('concatArrays', param),
filter: param => new AggregationOperator('filter', param),
in: param => new AggregationOperator('in', param),
indexOfArray: param => new AggregationOperator('indexOfArray', param),
isArray: param => new AggregationOperator('isArray', param),
map: param => new AggregationOperator('map', param),
range: param => new AggregationOperator('range', param),
reduce: param => new AggregationOperator('reduce', param),
reverseArray: param => new AggregationOperator('reverseArray', param),
size: param => new AggregationOperator('size', param),
slice: param => new AggregationOperator('slice', param),
zip: param => new AggregationOperator('zip', param),
and: param => new AggregationOperator('and', param),
not: param => new AggregationOperator('not', param),
or: param => new AggregationOperator('or', param),
cmp: param => new AggregationOperator('cmp', param),
eq: param => new AggregationOperator('eq', param),
gt: param => new AggregationOperator('gt', param),
gte: param => new AggregationOperator('gte', param),
lt: param => new AggregationOperator('lt', param),
lte: param => new AggregationOperator('lte', param),
neq: param => new AggregationOperator('ne', param),
cond: param => new AggregationOperator('cond', param),
ifNull: param => new AggregationOperator('ifNull', param),
switch: param => new AggregationOperator('switch', param),
dateFromParts: param => new AggregationOperator('dateFromParts', param),
dateFromString: param => new AggregationOperator('dateFromString', param),
dayOfMonth: param => new AggregationOperator('dayOfMonth', param),
dayOfWeek: param => new AggregationOperator('dayOfWeek', param),
dayOfYear: param => new AggregationOperator('dayOfYear', param),
isoDayOfWeek: param => new AggregationOperator('isoDayOfWeek', param),
isoWeek: param => new AggregationOperator('isoWeek', param),
isoWeekYear: param => new AggregationOperator('isoWeekYear', param),
millisecond: param => new AggregationOperator('millisecond', param),
minute: param => new AggregationOperator('minute', param),
month: param => new AggregationOperator('month', param),
second: param => new AggregationOperator('second', param),
hour: param => new AggregationOperator('hour', param),
week: param => new AggregationOperator('week', param),
year: param => new AggregationOperator('year', param),
literal: param => new AggregationOperator('literal', param),
mergeObjects: param => new AggregationOperator('mergeObjects', param),
objectToArray: param => new AggregationOperator('objectToArray', param),
allElementsTrue: param => new AggregationOperator('allElementsTrue', param),
anyElementTrue: param => new AggregationOperator('anyElementTrue', param),
setDifference: param => new AggregationOperator('setDifference', param),
setEquals: param => new AggregationOperator('setEquals', param),
setIntersection: param => new AggregationOperator('setIntersection', param),
setIsSubset: param => new AggregationOperator('setIsSubset', param),
setUnion: param => new AggregationOperator('setUnion', param),
concat: param => new AggregationOperator('concat', param),
dateToString: param => new AggregationOperator('dateToString', param),
indexOfBytes: param => new AggregationOperator('indexOfBytes', param),
indexOfCP: param => new AggregationOperator('indexOfCP', param),
split: param => new AggregationOperator('split', param),
strLenBytes: param => new AggregationOperator('strLenBytes', param),
strLenCP: param => new AggregationOperator('strLenCP', param),
strcasecmp: param => new AggregationOperator('strcasecmp', param),
substr: param => new AggregationOperator('substr', param),
substrBytes: param => new AggregationOperator('substrBytes', param),
substrCP: param => new AggregationOperator('substrCP', param),
toLower: param => new AggregationOperator('toLower', param),
toUpper: param => new AggregationOperator('toUpper', param),
meta: param => new AggregationOperator('meta', param),
addToSet: param => new AggregationOperator('addToSet', param),
avg: param => new AggregationOperator('avg', param),
first: param => new AggregationOperator('first', param),
last: param => new AggregationOperator('last', param),
max: param => new AggregationOperator('max', param),
min: param => new AggregationOperator('min', param),
push: param => new AggregationOperator('push', param),
stdDevPop: param => new AggregationOperator('stdDevPop', param),
stdDevSamp: param => new AggregationOperator('stdDevSamp', param),
sum: param => new AggregationOperator('sum', param),
let: param => new AggregationOperator('let', param)
},
project: {
slice: param => new ProjectionOperator('slice', param),
elemMatch: param => new ProjectionOperator('elemMatch', param)
}
};
class AggregationOperator {
constructor(name, param) {
this['$' + name] = param;
}
}
exports.AggregationOperator = AggregationOperator;
class ProjectionOperator {
constructor(name, param) {
this['$' + name] = param;
}
}
exports.ProjectionOperator = ProjectionOperator;
exports.default = exports.Command;

View File

@@ -0,0 +1,24 @@
import { InternalSymbol } from '../helper/symbol';
export declare const AND = "and";
export declare const OR = "or";
export declare const NOT = "not";
export declare const NOR = "nor";
export declare enum LOGIC_COMMANDS_LITERAL {
AND = "and",
OR = "or",
NOT = "not",
NOR = "nor"
}
export declare class LogicCommand {
fieldName: string | InternalSymbol;
operator: LOGIC_COMMANDS_LITERAL | string;
operands: any[];
_internalType: InternalSymbol;
constructor(operator: LOGIC_COMMANDS_LITERAL | string, operands: any, fieldName?: string | InternalSymbol);
_setFieldName(fieldName: string): LogicCommand;
and(...__expressions__: LogicCommand[]): LogicCommand;
or(...__expressions__: LogicCommand[]): LogicCommand;
}
export declare function isLogicCommand(object: any): object is LogicCommand;
export declare function isKnownLogicCommand(object: any): object is LogicCommand;
export default LogicCommand;

View File

@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const symbol_1 = require("../helper/symbol");
const query_1 = require("./query");
exports.AND = 'and';
exports.OR = 'or';
exports.NOT = 'not';
exports.NOR = 'nor';
var LOGIC_COMMANDS_LITERAL;
(function (LOGIC_COMMANDS_LITERAL) {
LOGIC_COMMANDS_LITERAL["AND"] = "and";
LOGIC_COMMANDS_LITERAL["OR"] = "or";
LOGIC_COMMANDS_LITERAL["NOT"] = "not";
LOGIC_COMMANDS_LITERAL["NOR"] = "nor";
})(LOGIC_COMMANDS_LITERAL = exports.LOGIC_COMMANDS_LITERAL || (exports.LOGIC_COMMANDS_LITERAL = {}));
class LogicCommand {
constructor(operator, operands, fieldName) {
this._internalType = symbol_1.SYMBOL_LOGIC_COMMAND;
Object.defineProperties(this, {
_internalType: {
enumerable: false,
configurable: false,
},
});
this.operator = operator;
this.operands = operands;
this.fieldName = fieldName || symbol_1.SYMBOL_UNSET_FIELD_NAME;
if (this.fieldName !== symbol_1.SYMBOL_UNSET_FIELD_NAME) {
if (Array.isArray(operands)) {
operands = operands.slice();
this.operands = operands;
for (let i = 0, len = operands.length; i < len; i++) {
const query = operands[i];
if (isLogicCommand(query) || query_1.isQueryCommand(query)) {
operands[i] = query._setFieldName(this.fieldName);
}
}
}
else {
const query = operands;
if (isLogicCommand(query) || query_1.isQueryCommand(query)) {
operands = query._setFieldName(this.fieldName);
}
}
}
}
_setFieldName(fieldName) {
const operands = this.operands.map(operand => {
if (operand instanceof LogicCommand) {
return operand._setFieldName(fieldName);
}
else {
return operand;
}
});
const command = new LogicCommand(this.operator, operands, fieldName);
return command;
}
and(...__expressions__) {
const expressions = Array.isArray(arguments[0]) ? arguments[0] : Array.from(arguments);
expressions.unshift(this);
return new LogicCommand(LOGIC_COMMANDS_LITERAL.AND, expressions, this.fieldName);
}
or(...__expressions__) {
const expressions = Array.isArray(arguments[0]) ? arguments[0] : Array.from(arguments);
expressions.unshift(this);
return new LogicCommand(LOGIC_COMMANDS_LITERAL.OR, expressions, this.fieldName);
}
}
exports.LogicCommand = LogicCommand;
function isLogicCommand(object) {
return object && (object instanceof LogicCommand) && (object._internalType === symbol_1.SYMBOL_LOGIC_COMMAND);
}
exports.isLogicCommand = isLogicCommand;
function isKnownLogicCommand(object) {
return isLogicCommand && (object.operator.toUpperCase() in LOGIC_COMMANDS_LITERAL);
}
exports.isKnownLogicCommand = isKnownLogicCommand;
exports.default = LogicCommand;

View File

@@ -0,0 +1,73 @@
import { LogicCommand } from './logic';
import { InternalSymbol } from '../helper/symbol';
import { Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon } from '../geo/index';
import { CenterSphere } from '../typings';
export declare const EQ = "eq";
export declare const NEQ = "neq";
export declare const GT = "gt";
export declare const GTE = "gte";
export declare const LT = "lt";
export declare const LTE = "lte";
export declare const IN = "in";
export declare const NIN = "nin";
export declare const ALL = "all";
export declare const ELEM_MATCH = "elemMatch";
export declare const EXISTS = "exists";
export declare const SIZE = "size";
export declare const MOD = "mod";
export declare enum QUERY_COMMANDS_LITERAL {
EQ = "eq",
NEQ = "neq",
GT = "gt",
GTE = "gte",
LT = "lt",
LTE = "lte",
IN = "in",
NIN = "nin",
ALL = "all",
ELEM_MATCH = "elemMatch",
EXISTS = "exists",
SIZE = "size",
MOD = "mod",
GEO_NEAR = "geoNear",
GEO_WITHIN = "geoWithin",
GEO_INTERSECTS = "geoIntersects"
}
export declare class QueryCommand extends LogicCommand {
operator: QUERY_COMMANDS_LITERAL;
constructor(operator: QUERY_COMMANDS_LITERAL, operands: any, fieldName?: string | InternalSymbol);
toJSON(): {
['$ne']: any;
} | {
[x: string]: any;
$ne?: undefined;
};
_setFieldName(fieldName: string): QueryCommand;
eq(val: any): LogicCommand;
neq(val: any): LogicCommand;
gt(val: any): LogicCommand;
gte(val: any): LogicCommand;
lt(val: any): LogicCommand;
lte(val: any): LogicCommand;
in(list: any[]): LogicCommand;
nin(list: any[]): LogicCommand;
geoNear(val: IGeoNearOptions): LogicCommand;
geoWithin(val: IGeoWithinOptions): LogicCommand;
geoIntersects(val: IGeoIntersectsOptions): LogicCommand;
}
export declare function isQueryCommand(object: any): object is QueryCommand;
export declare function isKnownQueryCommand(object: any): object is QueryCommand;
export declare function isComparisonCommand(object: any): object is QueryCommand;
export default QueryCommand;
export interface IGeoNearOptions {
geometry: Point;
maxDistance?: number;
minDistance?: number;
}
export interface IGeoWithinOptions {
geometry?: Polygon | MultiPolygon;
centerSphere?: CenterSphere;
}
export interface IGeoIntersectsOptions {
geometry: Point | LineString | Polygon | MultiPoint | MultiLineString | MultiPolygon;
}

View File

@@ -0,0 +1,147 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const logic_1 = require("./logic");
const symbol_1 = require("../helper/symbol");
const index_1 = require("../geo/index");
const type_1 = require("../utils/type");
const validate_1 = require("../validate");
exports.EQ = 'eq';
exports.NEQ = 'neq';
exports.GT = 'gt';
exports.GTE = 'gte';
exports.LT = 'lt';
exports.LTE = 'lte';
exports.IN = 'in';
exports.NIN = 'nin';
exports.ALL = 'all';
exports.ELEM_MATCH = 'elemMatch';
exports.EXISTS = 'exists';
exports.SIZE = 'size';
exports.MOD = 'mod';
var QUERY_COMMANDS_LITERAL;
(function (QUERY_COMMANDS_LITERAL) {
QUERY_COMMANDS_LITERAL["EQ"] = "eq";
QUERY_COMMANDS_LITERAL["NEQ"] = "neq";
QUERY_COMMANDS_LITERAL["GT"] = "gt";
QUERY_COMMANDS_LITERAL["GTE"] = "gte";
QUERY_COMMANDS_LITERAL["LT"] = "lt";
QUERY_COMMANDS_LITERAL["LTE"] = "lte";
QUERY_COMMANDS_LITERAL["IN"] = "in";
QUERY_COMMANDS_LITERAL["NIN"] = "nin";
QUERY_COMMANDS_LITERAL["ALL"] = "all";
QUERY_COMMANDS_LITERAL["ELEM_MATCH"] = "elemMatch";
QUERY_COMMANDS_LITERAL["EXISTS"] = "exists";
QUERY_COMMANDS_LITERAL["SIZE"] = "size";
QUERY_COMMANDS_LITERAL["MOD"] = "mod";
QUERY_COMMANDS_LITERAL["GEO_NEAR"] = "geoNear";
QUERY_COMMANDS_LITERAL["GEO_WITHIN"] = "geoWithin";
QUERY_COMMANDS_LITERAL["GEO_INTERSECTS"] = "geoIntersects";
})(QUERY_COMMANDS_LITERAL = exports.QUERY_COMMANDS_LITERAL || (exports.QUERY_COMMANDS_LITERAL = {}));
class QueryCommand extends logic_1.LogicCommand {
constructor(operator, operands, fieldName) {
super(operator, operands, fieldName);
this.operator = operator;
this._internalType = symbol_1.SYMBOL_QUERY_COMMAND;
}
toJSON() {
switch (this.operator) {
case QUERY_COMMANDS_LITERAL.IN:
case QUERY_COMMANDS_LITERAL.NIN:
return {
['$' + this.operator]: this.operands
};
case QUERY_COMMANDS_LITERAL.NEQ:
return {
['$ne']: this.operands[0]
};
default:
return {
['$' + this.operator]: this.operands[0]
};
}
}
_setFieldName(fieldName) {
const command = new QueryCommand(this.operator, this.operands, fieldName);
return command;
}
eq(val) {
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.EQ, [val], this.fieldName);
return this.and(command);
}
neq(val) {
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.NEQ, [val], this.fieldName);
return this.and(command);
}
gt(val) {
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.GT, [val], this.fieldName);
return this.and(command);
}
gte(val) {
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.GTE, [val], this.fieldName);
return this.and(command);
}
lt(val) {
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.LT, [val], this.fieldName);
return this.and(command);
}
lte(val) {
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.LTE, [val], this.fieldName);
return this.and(command);
}
in(list) {
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.IN, list, this.fieldName);
return this.and(command);
}
nin(list) {
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.NIN, list, this.fieldName);
return this.and(command);
}
geoNear(val) {
if (!(val.geometry instanceof index_1.Point)) {
throw new TypeError(`"geometry" must be of type Point. Received type ${typeof val.geometry}`);
}
if (val.maxDistance !== undefined && !type_1.isNumber(val.maxDistance)) {
throw new TypeError(`"maxDistance" must be of type Number. Received type ${typeof val.maxDistance}`);
}
if (val.minDistance !== undefined && !type_1.isNumber(val.minDistance)) {
throw new TypeError(`"minDistance" must be of type Number. Received type ${typeof val.minDistance}`);
}
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.GEO_NEAR, [val], this.fieldName);
return this.and(command);
}
geoWithin(val) {
if (!(val.geometry instanceof index_1.MultiPolygon) &&
!(val.geometry instanceof index_1.Polygon) &&
!validate_1.Validate.isCentersPhere(val.centerSphere)) {
throw new TypeError(`"geometry" must be of type Polygon or MultiPolygon. Received type ${typeof val.geometry}`);
}
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.GEO_WITHIN, [val], this.fieldName);
return this.and(command);
}
geoIntersects(val) {
if (!(val.geometry instanceof index_1.Point) &&
!(val.geometry instanceof index_1.LineString) &&
!(val.geometry instanceof index_1.Polygon) &&
!(val.geometry instanceof index_1.MultiPoint) &&
!(val.geometry instanceof index_1.MultiLineString) &&
!(val.geometry instanceof index_1.MultiPolygon)) {
throw new TypeError(`"geometry" must be of type Point, LineString, Polygon, MultiPoint, MultiLineString or MultiPolygon. Received type ${typeof val.geometry}`);
}
const command = new QueryCommand(QUERY_COMMANDS_LITERAL.GEO_INTERSECTS, [val], this.fieldName);
return this.and(command);
}
}
exports.QueryCommand = QueryCommand;
function isQueryCommand(object) {
return object && object instanceof QueryCommand && object._internalType === symbol_1.SYMBOL_QUERY_COMMAND;
}
exports.isQueryCommand = isQueryCommand;
function isKnownQueryCommand(object) {
return isQueryCommand(object) && object.operator.toUpperCase() in QUERY_COMMANDS_LITERAL;
}
exports.isKnownQueryCommand = isKnownQueryCommand;
function isComparisonCommand(object) {
return isQueryCommand(object);
}
exports.isComparisonCommand = isComparisonCommand;
exports.default = QueryCommand;

View File

@@ -0,0 +1,29 @@
import { InternalSymbol } from '../helper/symbol';
export declare enum UPDATE_COMMANDS_LITERAL {
SET = "set",
REMOVE = "remove",
INC = "inc",
MUL = "mul",
PUSH = "push",
PULL = "pull",
PULL_ALL = "pullAll",
POP = "pop",
SHIFT = "shift",
UNSHIFT = "unshift",
ADD_TO_SET = "addToSet",
BIT = "bit",
RENAME = "rename",
MAX = "max",
MIN = "min"
}
export declare class UpdateCommand {
fieldName: string | InternalSymbol;
operator: UPDATE_COMMANDS_LITERAL;
operands: any;
_internalType: InternalSymbol;
constructor(operator: UPDATE_COMMANDS_LITERAL, operands?: any, fieldName?: string | InternalSymbol);
_setFieldName(fieldName: string): UpdateCommand;
}
export declare function isUpdateCommand(object: any): object is UpdateCommand;
export declare function isKnownUpdateCommand(object: any): object is UpdateCommand;
export default UpdateCommand;

View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const symbol_1 = require("../helper/symbol");
var UPDATE_COMMANDS_LITERAL;
(function (UPDATE_COMMANDS_LITERAL) {
UPDATE_COMMANDS_LITERAL["SET"] = "set";
UPDATE_COMMANDS_LITERAL["REMOVE"] = "remove";
UPDATE_COMMANDS_LITERAL["INC"] = "inc";
UPDATE_COMMANDS_LITERAL["MUL"] = "mul";
UPDATE_COMMANDS_LITERAL["PUSH"] = "push";
UPDATE_COMMANDS_LITERAL["PULL"] = "pull";
UPDATE_COMMANDS_LITERAL["PULL_ALL"] = "pullAll";
UPDATE_COMMANDS_LITERAL["POP"] = "pop";
UPDATE_COMMANDS_LITERAL["SHIFT"] = "shift";
UPDATE_COMMANDS_LITERAL["UNSHIFT"] = "unshift";
UPDATE_COMMANDS_LITERAL["ADD_TO_SET"] = "addToSet";
UPDATE_COMMANDS_LITERAL["BIT"] = "bit";
UPDATE_COMMANDS_LITERAL["RENAME"] = "rename";
UPDATE_COMMANDS_LITERAL["MAX"] = "max";
UPDATE_COMMANDS_LITERAL["MIN"] = "min";
})(UPDATE_COMMANDS_LITERAL = exports.UPDATE_COMMANDS_LITERAL || (exports.UPDATE_COMMANDS_LITERAL = {}));
class UpdateCommand {
constructor(operator, operands, fieldName) {
this._internalType = symbol_1.SYMBOL_UPDATE_COMMAND;
Object.defineProperties(this, {
_internalType: {
enumerable: false,
configurable: false,
},
});
this.operator = operator;
this.operands = operands;
this.fieldName = fieldName || symbol_1.SYMBOL_UNSET_FIELD_NAME;
}
_setFieldName(fieldName) {
const command = new UpdateCommand(this.operator, this.operands, fieldName);
return command;
}
}
exports.UpdateCommand = UpdateCommand;
function isUpdateCommand(object) {
return object && (object instanceof UpdateCommand) && (object._internalType === symbol_1.SYMBOL_UPDATE_COMMAND);
}
exports.isUpdateCommand = isUpdateCommand;
function isKnownUpdateCommand(object) {
return isUpdateCommand(object) && (object.operator.toUpperCase() in UPDATE_COMMANDS_LITERAL);
}
exports.isKnownUpdateCommand = isKnownUpdateCommand;
exports.default = UpdateCommand;

View File

@@ -0,0 +1,6 @@
declare const _default: {
db_doc_size_limit: number;
db_realtime_ping_interval: number;
db_realtime_pong_wait_timeout: number;
};
export default _default;

View File

@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
db_doc_size_limit: 512 * 1024,
db_realtime_ping_interval: 15 * 1000,
db_realtime_pong_wait_timeout: 15 * 1000,
};

View File

@@ -0,0 +1,3 @@
export declare const ERR_CODE: {
[key: string]: string | number;
};

View File

@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERR_CODE = {
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
SDK_DATABASE_REALTIME_LISTENER_INIT_WATCH_FAIL: 'SDK_DATABASE_REALTIME_LISTENER_INIT_WATCH_FAIL',
SDK_DATABASE_REALTIME_LISTENER_RECONNECT_WATCH_FAIL: 'SDK_DATABASE_REALTIME_LISTENER_RECONNECT_WATCH_FAIL',
SDK_DATABASE_REALTIME_LISTENER_REBUILD_WATCH_FAIL: 'SDK_DATABASE_REALTIME_LISTENER_REBUILD_WATCH_FAIL',
SDK_DATABASE_REALTIME_LISTENER_CLOSE_WATCH_FAIL: 'SDK_DATABASE_REALTIME_LISTENER_CLOSE_WATCH_FAIL',
SDK_DATABASE_REALTIME_LISTENER_SERVER_ERROR_MSG: 'SDK_DATABASE_REALTIME_LISTENER_SERVER_ERROR_MSG',
SDK_DATABASE_REALTIME_LISTENER_RECEIVE_INVALID_SERVER_DATA: 'SDK_DATABASE_REALTIME_LISTENER_RECEIVE_INVALID_SERVER_DATA',
SDK_DATABASE_REALTIME_LISTENER_WEBSOCKET_CONNECTION_ERROR: 'SDK_DATABASE_REALTIME_LISTENER_WEBSOCKET_CONNECTION_ERROR',
SDK_DATABASE_REALTIME_LISTENER_WEBSOCKET_CONNECTION_CLOSED: 'SDK_DATABASE_REALTIME_LISTENER_WEBSOCKET_CONNECTION_CLOSED',
SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL: 'SDK_DATABASE_REALTIME_LISTENER_CHECK_LAST_FAIL',
SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR: 'SDK_DATABASE_REALTIME_LISTENER_UNEXPECTED_FATAL_ERROR'
};

View File

@@ -0,0 +1,34 @@
export declare const ERRORS: {
CREATE_WATCH_NET_ERROR: {
code: string;
message: string;
};
CREATE_WACTH_EXCEED_ERROR: {
code: string;
message: string;
};
CREATE_WATCH_SERVER_ERROR: {
code: string;
message: string;
};
CONN_ERROR: {
code: string;
message: string;
};
INVALID_PARAM: {
code: string;
message: string;
};
INSERT_DOC_FAIL: {
code: string;
message: string;
};
DATABASE_TRANSACTION_CONFLICT: {
code: string;
message: string;
};
DATABASE_REQUEST_FAILED: {
code: string;
message: string;
};
};

View File

@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ERRORS = {
CREATE_WATCH_NET_ERROR: {
code: 'CREATE_WATCH_NET_ERROR',
message: 'create watch network error'
},
CREATE_WACTH_EXCEED_ERROR: {
code: 'CREATE_WACTH_EXCEED_ERROR',
message: 'maximum connections exceed'
},
CREATE_WATCH_SERVER_ERROR: {
code: 'CREATE_WATCH_SERVER_ERROR',
message: 'create watch server error'
},
CONN_ERROR: {
code: 'CONN_ERROR',
message: 'connection error'
},
INVALID_PARAM: {
code: 'INVALID_PARAM',
message: 'Invalid request param'
},
INSERT_DOC_FAIL: {
code: 'INSERT_DOC_FAIL',
message: 'insert document failed'
},
DATABASE_TRANSACTION_CONFLICT: {
code: 'DATABASE_TRANSACTION_CONFLICT',
message: 'database transaction conflict'
},
DATABASE_REQUEST_FAILED: {
code: 'DATABASE_REQUEST_FAILED',
message: 'database request failed'
}
};

View File

@@ -0,0 +1,54 @@
declare enum ErrorCode {
DocIDError = "\u6587\u6863ID\u4E0D\u5408\u6CD5",
CollNameError = "\u96C6\u5408\u540D\u79F0\u4E0D\u5408\u6CD5",
OpStrError = "\u64CD\u4F5C\u7B26\u4E0D\u5408\u6CD5",
DirectionError = "\u6392\u5E8F\u5B57\u7B26\u4E0D\u5408\u6CD5",
IntergerError = "must be integer",
BooleanError = "must be boolean",
ArrayError = "must be array",
QueryParamTypeError = "\u67E5\u8BE2\u53C2\u6570\u5FC5\u987B\u4E3A\u5BF9\u8C61",
QueryParamValueError = "\u67E5\u8BE2\u53C2\u6570\u5BF9\u8C61\u503C\u4E0D\u80FD\u5747\u4E3Aundefined",
CentersPhereError = "centersPhere\u7ED3\u6784\u4E0D\u5408\u6CD5"
}
declare const FieldType: {
String: string;
Number: string;
Object: string;
Array: string;
Boolean: string;
Null: string;
GeoPoint: string;
GeoLineString: string;
GeoPolygon: string;
GeoMultiPoint: string;
GeoMultiLineString: string;
GeoMultiPolygon: string;
Date: string;
Command: string;
ServerDate: string;
BsonDate: string;
};
declare type OrderByDirection = 'desc' | 'asc';
declare const OrderDirectionList: string[];
declare type WhereFilterOp = '<' | '<=' | '==' | '>=' | '>';
declare const WhereFilterOpList: string[];
declare enum Opeartor {
lt = "<",
gt = ">",
lte = "<=",
gte = ">=",
eq = "=="
}
declare const OperatorMap: {
[Opeartor.eq]: string;
[Opeartor.lt]: string;
[Opeartor.lte]: string;
[Opeartor.gt]: string;
[Opeartor.gte]: string;
};
declare const UpdateOperatorList: string[];
declare enum QueryType {
WHERE = "WHERE",
DOC = "DOC"
}
export { ErrorCode, FieldType, WhereFilterOp, WhereFilterOpList, Opeartor, OperatorMap, OrderByDirection, OrderDirectionList, UpdateOperatorList, QueryType };

View File

@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var ErrorCode;
(function (ErrorCode) {
ErrorCode["DocIDError"] = "\u6587\u6863ID\u4E0D\u5408\u6CD5";
ErrorCode["CollNameError"] = "\u96C6\u5408\u540D\u79F0\u4E0D\u5408\u6CD5";
ErrorCode["OpStrError"] = "\u64CD\u4F5C\u7B26\u4E0D\u5408\u6CD5";
ErrorCode["DirectionError"] = "\u6392\u5E8F\u5B57\u7B26\u4E0D\u5408\u6CD5";
ErrorCode["IntergerError"] = "must be integer";
ErrorCode["BooleanError"] = "must be boolean";
ErrorCode["ArrayError"] = "must be array";
ErrorCode["QueryParamTypeError"] = "\u67E5\u8BE2\u53C2\u6570\u5FC5\u987B\u4E3A\u5BF9\u8C61";
ErrorCode["QueryParamValueError"] = "\u67E5\u8BE2\u53C2\u6570\u5BF9\u8C61\u503C\u4E0D\u80FD\u5747\u4E3Aundefined";
ErrorCode["CentersPhereError"] = "centersPhere\u7ED3\u6784\u4E0D\u5408\u6CD5";
})(ErrorCode || (ErrorCode = {}));
exports.ErrorCode = ErrorCode;
const FieldType = {
String: 'String',
Number: 'Number',
Object: 'Object',
Array: 'Array',
Boolean: 'Boolean',
Null: 'Null',
GeoPoint: 'GeoPoint',
GeoLineString: 'GeoLineString',
GeoPolygon: 'GeoPolygon',
GeoMultiPoint: 'GeoMultiPoint',
GeoMultiLineString: 'GeoMultiLineString',
GeoMultiPolygon: 'GeoMultiPolygon',
Date: 'Date',
Command: 'Command',
ServerDate: 'ServerDate',
BsonDate: 'BsonDate'
};
exports.FieldType = FieldType;
const OrderDirectionList = ['desc', 'asc'];
exports.OrderDirectionList = OrderDirectionList;
const WhereFilterOpList = ['<', '<=', '==', '>=', '>'];
exports.WhereFilterOpList = WhereFilterOpList;
var Opeartor;
(function (Opeartor) {
Opeartor["lt"] = "<";
Opeartor["gt"] = ">";
Opeartor["lte"] = "<=";
Opeartor["gte"] = ">=";
Opeartor["eq"] = "==";
})(Opeartor || (Opeartor = {}));
exports.Opeartor = Opeartor;
const OperatorMap = {
[Opeartor.eq]: '$eq',
[Opeartor.lt]: '$lt',
[Opeartor.lte]: '$lte',
[Opeartor.gt]: '$gt',
[Opeartor.gte]: '$gte'
};
exports.OperatorMap = OperatorMap;
const UpdateOperatorList = [
'$set',
'$inc',
'$mul',
'$unset',
'$push',
'$pop',
'$unshift',
'$shift',
'$currentDate',
'$each',
'$position'
];
exports.UpdateOperatorList = UpdateOperatorList;
var QueryType;
(function (QueryType) {
QueryType["WHERE"] = "WHERE";
QueryType["DOC"] = "DOC";
})(QueryType || (QueryType = {}));
exports.QueryType = QueryType;

View File

@@ -0,0 +1,14 @@
import { IWatchOptions, DBRealtimeListener } from './typings/index';
export declare class DocumentReference {
readonly id: string | number;
readonly _transactionId: string;
readonly projection: Object;
private _apiOptions;
set(data: Object): Promise<any>;
update(data: Object): Promise<any>;
delete(): Promise<any>;
remove(): Promise<any>;
get(): Promise<any>;
field(projection: Object): DocumentReference;
watch: (options: IWatchOptions) => DBRealtimeListener;
}

View File

@@ -0,0 +1,219 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("./index");
const util_1 = require("./util");
const update_1 = require("./serializer/update");
const datatype_1 = require("./serializer/datatype");
const update_2 = require("./commands/update");
const websocket_client_1 = require("./realtime/websocket-client");
const constant_1 = require("./constant");
const utils_1 = require("./utils/utils");
const code_1 = require("./const/code");
const bson_1 = require("bson");
class DocumentReference {
constructor(db, coll, apiOptions, docID, transactionId) {
this.watch = (options) => {
if (!index_1.Db.ws) {
index_1.Db.ws = new websocket_client_1.RealtimeWebSocketClient({
context: {
appConfig: {
docSizeLimit: 1000,
realtimePingInterval: 10000,
realtimePongWaitTimeout: 5000,
request: this.request
}
}
});
}
return index_1.Db.ws.watch(Object.assign(Object.assign({}, options), { envId: this._db.config.env, collectionName: this._coll, query: JSON.stringify({
_id: this.id
}) }));
};
this._db = db;
this._coll = coll;
this.id = docID;
this._transactionId = transactionId;
this.request = new index_1.Db.reqClass(this._db.config);
this._apiOptions = apiOptions;
}
async create(data) {
if (this.id) {
data['_id'] = this.id;
}
let params = {
collectionName: this._coll,
data: [utils_1.stringifyByEJSON(datatype_1.serialize(data))],
transactionId: this._transactionId
};
const res = await this.request.send('database.insertDocument', params, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
if (this._transactionId) {
return {
inserted: 1,
ok: 1,
id: res.data.insertedIds[0],
requestId: res.requestId
};
}
return {
id: res.data.insertedIds[0],
requestId: res.requestId
};
}
async set(data) {
if (!this.id) {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: 'docId不能为空' }));
}
if (!data || typeof data !== 'object') {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '参数必需是非空对象' }));
}
if (data.hasOwnProperty('_id')) {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '不能更新_id的值' }));
}
let hasOperator = false;
const checkMixed = objs => {
if (typeof objs === 'object') {
for (let key in objs) {
if (objs[key] instanceof update_2.UpdateCommand) {
hasOperator = true;
}
else if (typeof objs[key] === 'object') {
checkMixed(objs[key]);
}
}
}
};
checkMixed(data);
if (hasOperator) {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.DATABASE_REQUEST_FAILED), { message: 'update operator complicit' }));
}
let param = {
collectionName: this._coll,
queryType: constant_1.QueryType.DOC,
data: utils_1.stringifyByEJSON(datatype_1.serialize(data)),
transactionId: this._transactionId,
multi: false,
merge: false,
upsert: true
};
if (this.id) {
param['query'] = utils_1.stringifyByEJSON({ _id: this.id });
}
const res = await this.request.send('database.modifyDocument', param, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
if (this._transactionId) {
return {
updated: res.data.updated,
upserted: [{ _id: res.data.upsert_id }],
requestId: res.requestId
};
}
return {
updated: res.data.updated,
upsertedId: res.data.upsert_id,
requestId: res.requestId
};
}
async update(data) {
if (!data || typeof data !== 'object') {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '参数必需是非空对象' }));
}
if (data.hasOwnProperty('_id')) {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '不能更新_id的值' }));
}
const query = utils_1.stringifyByEJSON({ _id: this.id });
const param = {
collectionName: this._coll,
transactionId: this._transactionId,
data: update_1.UpdateSerializer.encodeEJSON(data, this._apiOptions.raw || false),
query,
queryType: constant_1.QueryType.DOC,
multi: false,
merge: true,
upsert: false
};
const res = await this.request.send('database.modifyDocument', param, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
return {
updated: res.data.updated,
requestId: res.requestId
};
}
async delete() {
return this.remove();
}
async remove() {
const query = utils_1.stringifyByEJSON({ _id: this.id });
const param = {
collectionName: this._coll,
transactionId: this._transactionId,
query: query,
queryType: constant_1.QueryType.DOC,
multi: false
};
const res = await this.request.send('database.removeDocument', param, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
return {
deleted: res.data.deleted,
requestId: res.requestId
};
}
async get() {
const query = utils_1.stringifyByEJSON({ _id: this.id });
const { projection } = this._apiOptions;
const param = {
collectionName: this._coll,
query,
transactionId: this._transactionId,
queryType: constant_1.QueryType.DOC,
multi: false
};
if (projection) {
param.projection = utils_1.stringifyByEJSON(projection);
}
const res = await this.request.send('database.getDocument', param, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
const list = res.data.list.map(item => bson_1.EJSON.parse(item));
const documents = util_1.Util.formatResDocumentData(list);
if (this._transactionId) {
return {
data: documents[0] || null,
requestId: res.requestId
};
}
return {
data: documents,
requestId: res.requestId,
offset: res.data.offset,
limit: res.data.limit
};
}
field(projection) {
let transformProjection = {};
for (let k in projection) {
if (typeof projection[k] === 'boolean') {
transformProjection[k] = projection[k] === true ? 1 : 0;
}
if (typeof projection[k] === 'number') {
transformProjection[k] = projection[k] > 0 ? 1 : 0;
}
if (typeof projection[k] === 'object') {
transformProjection[k] = projection[k];
}
}
let newApiOption = Object.assign({}, this._apiOptions);
newApiOption.projection = transformProjection;
return new DocumentReference(this._db, this._coll, newApiOption, this.id, this._transactionId);
}
}
exports.DocumentReference = DocumentReference;

View File

@@ -0,0 +1,6 @@
export * from './point';
export * from './lineString';
export * from './polygon';
export * from './multiPoint';
export * from './multiLineString';
export * from './multiPolygon';

View File

@@ -0,0 +1,11 @@
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./point"));
__export(require("./lineString"));
__export(require("./polygon"));
__export(require("./multiPoint"));
__export(require("./multiLineString"));
__export(require("./multiPolygon"));

View File

@@ -0,0 +1,24 @@
export interface ISerializedPoint {
type: string;
coordinates: [number, number];
}
export interface ISerializedLineString {
type: string;
coordinates: [number, number][];
}
export interface ISerializedPolygon {
type: string;
coordinates: [number, number][][];
}
export interface ISerializedMultiPoint {
type: string;
coordinates: [number, number][];
}
export interface ISerializedMultiLineString {
type: string;
coordinates: [number, number][][];
}
export interface ISerializedMultiPolygon {
type: string;
coordinates: [number, number][][][];
}

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,19 @@
import { Point } from './point';
import { ISerializedLineString } from './interface';
export declare class LineString {
readonly points: Point[];
constructor(points: Point[]);
parse(key: any): {
[x: number]: {
type: string;
coordinates: number[][];
};
};
toJSON(): {
type: string;
coordinates: number[][];
};
static validate(lineString: ISerializedLineString): boolean;
static isClosed(lineString: LineString): boolean;
readonly _internalType: import("../utils/symbol").InternalSymbol;
}

View File

@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const symbol_1 = require("../helper/symbol");
const point_1 = require("./point");
const type_1 = require("../utils/type");
class LineString {
constructor(points) {
if (!type_1.isArray(points)) {
throw new TypeError(`"points" must be of type Point[]. Received type ${typeof points}`);
}
if (points.length < 2) {
throw new Error('"points" must contain 2 points at least');
}
points.forEach(point => {
if (!(point instanceof point_1.Point)) {
throw new TypeError(`"points" must be of type Point[]. Received type ${typeof point}[]`);
}
});
this.points = points;
}
parse(key) {
return {
[key]: {
type: 'LineString',
coordinates: this.points.map(point => point.toJSON().coordinates)
}
};
}
toJSON() {
return {
type: 'LineString',
coordinates: this.points.map(point => point.toJSON().coordinates)
};
}
static validate(lineString) {
if (lineString.type !== 'LineString' || !type_1.isArray(lineString.coordinates)) {
return false;
}
for (let point of lineString.coordinates) {
if (!type_1.isNumber(point[0]) || !type_1.isNumber(point[1])) {
return false;
}
}
return true;
}
static isClosed(lineString) {
const firstPoint = lineString.points[0];
const lastPoint = lineString.points[lineString.points.length - 1];
if (firstPoint.latitude === lastPoint.latitude && firstPoint.longitude === lastPoint.longitude) {
return true;
}
}
get _internalType() {
return symbol_1.SYMBOL_GEO_LINE_STRING;
}
}
exports.LineString = LineString;

View File

@@ -0,0 +1,18 @@
import { LineString } from './lineString';
import { ISerializedMultiLineString } from './interface';
export declare class MultiLineString {
readonly lines: LineString[];
constructor(lines: LineString[]);
parse(key: any): {
[x: number]: {
type: string;
coordinates: number[][][];
};
};
toJSON(): {
type: string;
coordinates: number[][][];
};
static validate(multiLineString: ISerializedMultiLineString): boolean;
readonly _internalType: import("../utils/symbol").InternalSymbol;
}

View File

@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const symbol_1 = require("../helper/symbol");
const type_1 = require("../utils/type");
const lineString_1 = require("./lineString");
class MultiLineString {
constructor(lines) {
if (!type_1.isArray(lines)) {
throw new TypeError(`"lines" must be of type LineString[]. Received type ${typeof lines}`);
}
if (lines.length === 0) {
throw new Error('Polygon must contain 1 linestring at least');
}
lines.forEach(line => {
if (!(line instanceof lineString_1.LineString)) {
throw new TypeError(`"lines" must be of type LineString[]. Received type ${typeof line}[]`);
}
});
this.lines = lines;
}
parse(key) {
return {
[key]: {
type: 'MultiLineString',
coordinates: this.lines.map(line => {
return line.points.map(point => [point.longitude, point.latitude]);
})
}
};
}
toJSON() {
return {
type: 'MultiLineString',
coordinates: this.lines.map(line => {
return line.points.map(point => [point.longitude, point.latitude]);
})
};
}
static validate(multiLineString) {
if (multiLineString.type !== 'MultiLineString' || !type_1.isArray(multiLineString.coordinates)) {
return false;
}
for (let line of multiLineString.coordinates) {
for (let point of line) {
if (!type_1.isNumber(point[0]) || !type_1.isNumber(point[1])) {
return false;
}
}
}
return true;
}
get _internalType() {
return symbol_1.SYMBOL_GEO_MULTI_LINE_STRING;
}
}
exports.MultiLineString = MultiLineString;

View File

@@ -0,0 +1,18 @@
import { Point } from './point';
import { ISerializedMultiPoint } from './interface';
export declare class MultiPoint {
readonly points: Point[];
constructor(points: Point[]);
parse(key: any): {
[x: number]: {
type: string;
coordinates: number[][];
};
};
toJSON(): {
type: string;
coordinates: number[][];
};
static validate(multiPoint: ISerializedMultiPoint): boolean;
readonly _internalType: import("../utils/symbol").InternalSymbol;
}

View File

@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const symbol_1 = require("../helper/symbol");
const point_1 = require("./point");
const type_1 = require("../utils/type");
class MultiPoint {
constructor(points) {
if (!type_1.isArray(points)) {
throw new TypeError(`"points" must be of type Point[]. Received type ${typeof points}`);
}
if (points.length === 0) {
throw new Error('"points" must contain 1 point at least');
}
points.forEach(point => {
if (!(point instanceof point_1.Point)) {
throw new TypeError(`"points" must be of type Point[]. Received type ${typeof point}[]`);
}
});
this.points = points;
}
parse(key) {
return {
[key]: {
type: 'MultiPoint',
coordinates: this.points.map(point => point.toJSON().coordinates)
}
};
}
toJSON() {
return {
type: 'MultiPoint',
coordinates: this.points.map(point => point.toJSON().coordinates)
};
}
static validate(multiPoint) {
if (multiPoint.type !== 'MultiPoint' || !type_1.isArray(multiPoint.coordinates)) {
return false;
}
for (let point of multiPoint.coordinates) {
if (!type_1.isNumber(point[0]) || !type_1.isNumber(point[1])) {
return false;
}
}
return true;
}
get _internalType() {
return symbol_1.SYMBOL_GEO_MULTI_POINT;
}
}
exports.MultiPoint = MultiPoint;

View File

@@ -0,0 +1,18 @@
import { Polygon } from './polygon';
import { ISerializedMultiPolygon } from './interface';
export declare class MultiPolygon {
readonly polygons: Polygon[];
constructor(polygons: Polygon[]);
parse(key: any): {
[x: number]: {
type: string;
coordinates: number[][][][];
};
};
toJSON(): {
type: string;
coordinates: number[][][][];
};
static validate(multiPolygon: ISerializedMultiPolygon): boolean;
readonly _internalType: import("../utils/symbol").InternalSymbol;
}

View File

@@ -0,0 +1,62 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const symbol_1 = require("../helper/symbol");
const type_1 = require("../utils/type");
const polygon_1 = require("./polygon");
class MultiPolygon {
constructor(polygons) {
if (!type_1.isArray(polygons)) {
throw new TypeError(`"polygons" must be of type Polygon[]. Received type ${typeof polygons}`);
}
if (polygons.length === 0) {
throw new Error('MultiPolygon must contain 1 polygon at least');
}
for (let polygon of polygons) {
if (!(polygon instanceof polygon_1.Polygon)) {
throw new TypeError(`"polygon" must be of type Polygon[]. Received type ${typeof polygon}[]`);
}
}
this.polygons = polygons;
}
parse(key) {
return {
[key]: {
type: 'MultiPolygon',
coordinates: this.polygons.map(polygon => {
return polygon.lines.map(line => {
return line.points.map(point => [point.longitude, point.latitude]);
});
})
}
};
}
toJSON() {
return {
type: 'MultiPolygon',
coordinates: this.polygons.map(polygon => {
return polygon.lines.map(line => {
return line.points.map(point => [point.longitude, point.latitude]);
});
})
};
}
static validate(multiPolygon) {
if (multiPolygon.type !== 'MultiPolygon' || !type_1.isArray(multiPolygon.coordinates)) {
return false;
}
for (let polygon of multiPolygon.coordinates) {
for (let line of polygon) {
for (let point of line) {
if (!type_1.isNumber(point[0]) || !type_1.isNumber(point[1])) {
return false;
}
}
}
}
return true;
}
get _internalType() {
return symbol_1.SYMBOL_GEO_MULTI_POLYGON;
}
}
exports.MultiPolygon = MultiPolygon;

View File

@@ -0,0 +1,19 @@
import { ISerializedPoint } from './interface';
export declare class Point {
readonly latitude: number;
readonly longitude: number;
constructor(longitude: number, latitude: number);
parse(key: any): {
[x: number]: {
type: string;
coordinates: number[];
};
};
toJSON(): {
type: string;
coordinates: number[];
};
toReadableString(): string;
static validate(point: ISerializedPoint): boolean;
readonly _internalType: import("../utils/symbol").InternalSymbol;
}

View File

@@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const validate_1 = require("../validate");
const symbol_1 = require("../helper/symbol");
const type_1 = require("../utils/type");
class Point {
constructor(longitude, latitude) {
validate_1.Validate.isGeopoint('longitude', longitude);
validate_1.Validate.isGeopoint('latitude', latitude);
this.longitude = longitude;
this.latitude = latitude;
}
parse(key) {
return {
[key]: {
type: 'Point',
coordinates: [this.longitude, this.latitude]
}
};
}
toJSON() {
return {
type: 'Point',
coordinates: [
this.longitude,
this.latitude,
],
};
}
toReadableString() {
return `[${this.longitude},${this.latitude}]`;
}
static validate(point) {
return point.type === 'Point' &&
type_1.isArray(point.coordinates) &&
validate_1.Validate.isGeopoint('longitude', point.coordinates[0]) &&
validate_1.Validate.isGeopoint('latitude', point.coordinates[1]);
}
get _internalType() {
return symbol_1.SYMBOL_GEO_POINT;
}
}
exports.Point = Point;

View File

@@ -0,0 +1,19 @@
import { LineString } from './lineString';
import { ISerializedPolygon } from './interface';
export declare class Polygon {
readonly lines: LineString[];
constructor(lines: LineString[]);
parse(key: any): {
[x: number]: {
type: string;
coordinates: number[][][];
};
};
toJSON(): {
type: string;
coordinates: number[][][];
};
static validate(polygon: ISerializedPolygon): boolean;
static isCloseLineString(lineString: any): boolean;
readonly _internalType: import("../utils/symbol").InternalSymbol;
}

View File

@@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const symbol_1 = require("../helper/symbol");
const type_1 = require("../utils/type");
const lineString_1 = require("./lineString");
class Polygon {
constructor(lines) {
if (!type_1.isArray(lines)) {
throw new TypeError(`"lines" must be of type LineString[]. Received type ${typeof lines}`);
}
if (lines.length === 0) {
throw new Error('Polygon must contain 1 linestring at least');
}
lines.forEach(line => {
if (!(line instanceof lineString_1.LineString)) {
throw new TypeError(`"lines" must be of type LineString[]. Received type ${typeof line}[]`);
}
if (!lineString_1.LineString.isClosed(line)) {
throw new Error(`LineString ${line.points.map(p => p.toReadableString())} is not a closed cycle`);
}
});
this.lines = lines;
}
parse(key) {
return {
[key]: {
type: 'Polygon',
coordinates: this.lines.map(line => {
return line.points.map(point => [point.longitude, point.latitude]);
})
}
};
}
toJSON() {
return {
type: 'Polygon',
coordinates: this.lines.map(line => {
return line.points.map(point => [point.longitude, point.latitude]);
})
};
}
static validate(polygon) {
if (polygon.type !== 'Polygon' || !type_1.isArray(polygon.coordinates)) {
return false;
}
for (let line of polygon.coordinates) {
if (!this.isCloseLineString(line)) {
return false;
}
for (let point of line) {
if (!type_1.isNumber(point[0]) || !type_1.isNumber(point[1])) {
return false;
}
}
}
return true;
}
static isCloseLineString(lineString) {
const firstPoint = lineString[0];
const lastPoint = lineString[lineString.length - 1];
if (firstPoint[0] !== lastPoint[0] || firstPoint[1] !== lastPoint[1]) {
return false;
}
return true;
}
get _internalType() {
return symbol_1.SYMBOL_GEO_POLYGON;
}
}
exports.Polygon = Polygon;

View File

@@ -0,0 +1,15 @@
import InternalSymbol from '../utils/symbol';
export * from '../utils/symbol';
export declare const SYMBOL_UNSET_FIELD_NAME: InternalSymbol;
export declare const SYMBOL_UPDATE_COMMAND: InternalSymbol;
export declare const SYMBOL_QUERY_COMMAND: InternalSymbol;
export declare const SYMBOL_LOGIC_COMMAND: InternalSymbol;
export declare const SYMBOL_GEO_POINT: InternalSymbol;
export declare const SYMBOL_GEO_LINE_STRING: InternalSymbol;
export declare const SYMBOL_GEO_POLYGON: InternalSymbol;
export declare const SYMBOL_GEO_MULTI_POINT: InternalSymbol;
export declare const SYMBOL_GEO_MULTI_LINE_STRING: InternalSymbol;
export declare const SYMBOL_GEO_MULTI_POLYGON: InternalSymbol;
export declare const SYMBOL_SERVER_DATE: InternalSymbol;
export declare const SYMBOL_REGEXP: InternalSymbol;
export declare const SYMBOL_OBJECTID: InternalSymbol;

View File

@@ -0,0 +1,20 @@
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
const symbol_1 = require("../utils/symbol");
__export(require("../utils/symbol"));
exports.SYMBOL_UNSET_FIELD_NAME = symbol_1.default.for('UNSET_FIELD_NAME');
exports.SYMBOL_UPDATE_COMMAND = symbol_1.default.for('UPDATE_COMMAND');
exports.SYMBOL_QUERY_COMMAND = symbol_1.default.for('QUERY_COMMAND');
exports.SYMBOL_LOGIC_COMMAND = symbol_1.default.for('LOGIC_COMMAND');
exports.SYMBOL_GEO_POINT = symbol_1.default.for('GEO_POINT');
exports.SYMBOL_GEO_LINE_STRING = symbol_1.default.for('SYMBOL_GEO_LINE_STRING');
exports.SYMBOL_GEO_POLYGON = symbol_1.default.for('SYMBOL_GEO_POLYGON');
exports.SYMBOL_GEO_MULTI_POINT = symbol_1.default.for('SYMBOL_GEO_MULTI_POINT');
exports.SYMBOL_GEO_MULTI_LINE_STRING = symbol_1.default.for('SYMBOL_GEO_MULTI_LINE_STRING');
exports.SYMBOL_GEO_MULTI_POLYGON = symbol_1.default.for('SYMBOL_GEO_MULTI_POLYGON');
exports.SYMBOL_SERVER_DATE = symbol_1.default.for('SERVER_DATE');
exports.SYMBOL_REGEXP = symbol_1.default.for('REGEXP');
exports.SYMBOL_OBJECTID = symbol_1.default.for('OBJECTID');

View File

@@ -0,0 +1,50 @@
import { Point } from './geo/point';
import { CollectionReference } from './collection';
import { Command } from './command';
interface GeoTeyp {
Point: typeof Point;
}
export { Query } from './query';
export { CollectionReference } from './collection';
export { DocumentReference } from './document';
interface ICloudBaseConfig {
timeout?: number;
isHttp?: boolean;
secretId?: string;
secretKey?: string;
envName?: string;
env?: string;
sessionToken?: string;
serviceUrl?: string;
headers?: any;
proxy?: string;
version?: string;
credentials?: ICredentialsInfo;
_useFeature?: boolean;
throwOnCode?: boolean;
}
interface ICredentialsInfo {
private_key_id: string;
private_key: string;
}
export declare class Db {
Geo: GeoTeyp;
command: typeof Command;
RegExp: any;
serverDate: any;
ObjectId: any;
startTransaction: any;
runTransaction: any;
config: ICloudBaseConfig;
static ws: any;
static reqClass: any;
static wsClass: any;
static createSign: Function;
static getAccessToken: Function;
static dataVersion: string;
static runtime: string;
static appSecretInfo: any;
constructor(config?: any);
collection(collName: string): CollectionReference;
createCollection(collName: string): any;
}

View File

@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Geo = require("./geo/index");
const collection_1 = require("./collection");
const command_1 = require("./command");
const index_1 = require("./serverDate/index");
const index_2 = require("./regexp/index");
const index_3 = require("./transaction/index");
const index_4 = require("./ObjectId/index");
var query_1 = require("./query");
exports.Query = query_1.Query;
var collection_2 = require("./collection");
exports.CollectionReference = collection_2.CollectionReference;
var document_1 = require("./document");
exports.DocumentReference = document_1.DocumentReference;
class Db {
constructor(config) {
this.config = config;
this.Geo = Geo;
this.serverDate = index_1.ServerDateConstructor;
this.command = command_1.Command;
this.RegExp = index_2.RegExpConstructor;
this.ObjectId = index_4.ObjectIdConstructor;
this.startTransaction = index_3.startTransaction;
this.runTransaction = index_3.runTransaction;
}
collection(collName) {
if (!collName) {
throw new Error('Collection name is required');
}
return new collection_1.CollectionReference(this, collName);
}
createCollection(collName) {
let request = new Db.reqClass(this.config);
const params = {
collectionName: collName
};
return request.send('database.addCollection', params);
}
}
exports.Db = Db;

View File

@@ -0,0 +1,4 @@
export declare const OperatorMap: {
[key: string]: string;
};
export declare function operatorToString(operator: string): string;

View File

@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const query_1 = require("./commands/query");
const logic_1 = require("./commands/logic");
const update_1 = require("./commands/update");
exports.OperatorMap = {};
for (const key in query_1.QUERY_COMMANDS_LITERAL) {
exports.OperatorMap[key] = '$' + key;
}
for (const key in logic_1.LOGIC_COMMANDS_LITERAL) {
exports.OperatorMap[key] = '$' + key;
}
for (const key in update_1.UPDATE_COMMANDS_LITERAL) {
exports.OperatorMap[key] = '$' + key;
}
exports.OperatorMap[query_1.QUERY_COMMANDS_LITERAL.NEQ] = '$ne';
exports.OperatorMap[update_1.UPDATE_COMMANDS_LITERAL.REMOVE] = '$unset';
exports.OperatorMap[update_1.UPDATE_COMMANDS_LITERAL.SHIFT] = '$pop';
exports.OperatorMap[update_1.UPDATE_COMMANDS_LITERAL.UNSHIFT] = '$push';
function operatorToString(operator) {
return exports.OperatorMap[operator] || '$' + operator;
}
exports.operatorToString = operatorToString;

View File

@@ -0,0 +1,38 @@
import { OrderByDirection } from './constant';
import { IWatchOptions, DBRealtimeListener } from './typings/index';
interface GetRes {
data: any[];
requestId: string;
total: number;
limit: number;
offset: number;
}
interface BaseOption {
timeout?: number;
raw?: boolean;
}
export interface QueryOption extends BaseOption {
limit?: number;
offset?: number;
projection?: Object;
order?: Record<string, any>[];
}
export interface UpdateOption extends BaseOption {
multiple?: boolean;
}
export declare class Query {
protected _transactionId: string;
get(): Promise<GetRes>;
count(): Promise<any>;
where(query: object): Query;
options(apiOptions: QueryOption | UpdateOption): Query;
orderBy(fieldPath: string, directionStr: OrderByDirection): Query;
limit(limit: number): Query;
skip(offset: number): Query;
update(data: Object): Promise<any>;
field(projection: any): Query;
remove(): Promise<any>;
updateAndReturn(data: Object): Promise<any>;
watch: (options: IWatchOptions) => DBRealtimeListener;
}
export {};

View File

@@ -0,0 +1,247 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constant_1 = require("./constant");
const index_1 = require("./index");
const validate_1 = require("./validate");
const util_1 = require("./util");
const query_1 = require("./serializer/query");
const update_1 = require("./serializer/update");
const websocket_client_1 = require("./realtime/websocket-client");
const constant_2 = require("./constant");
const utils_1 = require("./utils/utils");
const code_1 = require("./const/code");
const bson_1 = require("bson");
class Query {
constructor(db, coll, fieldFilters, apiOptions, transactionId) {
this.watch = (options) => {
if (!index_1.Db.ws) {
index_1.Db.ws = new websocket_client_1.RealtimeWebSocketClient({
context: {
appConfig: {
docSizeLimit: 1000,
realtimePingInterval: 10000,
realtimePongWaitTimeout: 5000,
request: this._request
}
}
});
}
const { limit, order } = this._apiOptions;
return index_1.Db.ws.watch(Object.assign(Object.assign({}, options), { envId: this._db.config.env, collectionName: this._coll, query: JSON.stringify(this._fieldFilters), limit, orderBy: order
? order.reduce((acc, cur) => {
acc[cur.field] = cur.direction;
return acc;
}, {})
: undefined }));
};
this._db = db;
this._coll = coll;
this._fieldFilters = fieldFilters;
this._apiOptions = apiOptions || {};
this._request = new index_1.Db.reqClass(this._db.config);
this._transactionId = transactionId;
}
async get() {
const order = this._apiOptions.order;
let param = {
collectionName: this._coll,
queryType: constant_1.QueryType.WHERE,
transactionId: this._transactionId
};
if (this._fieldFilters) {
param.query = this._fieldFilters;
}
if (order) {
param.order = utils_1.stringifyByEJSON(order);
}
const offset = this._apiOptions.offset;
if (offset) {
param.offset = offset;
}
const limit = this._apiOptions.limit;
if (limit) {
param.limit = limit < 1000 ? limit : 1000;
}
else {
param.limit = 100;
}
const projection = this._apiOptions.projection;
if (projection) {
param.projection = utils_1.stringifyByEJSON(projection);
}
const res = await this._request.send('database.getDocument', param, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
const list = res.data.list.map(item => bson_1.EJSON.parse(item));
const documents = util_1.Util.formatResDocumentData(list);
const result = {
data: documents,
requestId: res.requestId
};
if (res.limit)
result.limit = res.limit;
if (res.offset)
result.offset = res.offset;
return result;
}
async count() {
let param = {
collectionName: this._coll,
queryType: constant_1.QueryType.WHERE
};
if (this._fieldFilters) {
param.query = this._fieldFilters;
}
const res = await this._request.send('database.calculateDocument', param, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
return {
requestId: res.requestId,
total: res.data.total
};
}
where(query) {
if (Object.prototype.toString.call(query).slice(8, -1) !== 'Object') {
throw Error(constant_2.ErrorCode.QueryParamTypeError);
}
const keys = Object.keys(query);
const checkFlag = keys.some(item => {
return query[item] !== undefined;
});
if (keys.length && !checkFlag) {
throw Error(constant_2.ErrorCode.QueryParamValueError);
}
return new Query(this._db, this._coll, query_1.QuerySerializer.encodeEJSON(query, this._apiOptions.raw || false), this._apiOptions, this._transactionId);
}
options(apiOptions) {
validate_1.Validate.isValidOptions(apiOptions);
return new Query(this._db, this._coll, this._fieldFilters, apiOptions, this._transactionId);
}
orderBy(fieldPath, directionStr) {
validate_1.Validate.isFieldPath(fieldPath);
validate_1.Validate.isFieldOrder(directionStr);
const newOrder = {
[fieldPath]: directionStr === 'desc' ? -1 : 1
};
const order = this._apiOptions.order || {};
const newApiOption = Object.assign({}, this._apiOptions, {
order: Object.assign({}, order, newOrder)
});
return new Query(this._db, this._coll, this._fieldFilters, newApiOption, this._transactionId);
}
limit(limit) {
validate_1.Validate.isInteger('limit', limit);
let newApiOption = Object.assign({}, this._apiOptions);
newApiOption.limit = limit;
return new Query(this._db, this._coll, this._fieldFilters, newApiOption, this._transactionId);
}
skip(offset) {
validate_1.Validate.isInteger('offset', offset);
let newApiOption = Object.assign({}, this._apiOptions);
newApiOption.offset = offset;
return new Query(this._db, this._coll, this._fieldFilters, newApiOption, this._transactionId);
}
async update(data) {
if (!data || typeof data !== 'object') {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '参数必需是非空对象' }));
}
if (data.hasOwnProperty('_id')) {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '不能更新_id的值' }));
}
let { multiple } = this._apiOptions;
const multi = multiple === undefined ? true : multiple;
let param = {
collectionName: this._coll,
queryType: constant_1.QueryType.WHERE,
multi,
merge: true,
upsert: false,
data: update_1.UpdateSerializer.encodeEJSON(data, this._apiOptions.raw || false)
};
if (this._fieldFilters) {
param.query = this._fieldFilters;
}
const res = await this._request.send('database.modifyDocument', param, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
return {
requestId: res.requestId,
updated: res.data.updated,
upsertId: res.data.upsert_id
};
}
field(projection) {
let transformProjection = {};
for (let k in projection) {
if (typeof projection[k] === 'boolean') {
transformProjection[k] = projection[k] === true ? 1 : 0;
}
if (typeof projection[k] === 'number') {
transformProjection[k] = projection[k] > 0 ? 1 : 0;
}
if (typeof projection[k] === 'object') {
transformProjection[k] = projection[k];
}
}
let newApiOption = Object.assign({}, this._apiOptions);
newApiOption.projection = transformProjection;
return new Query(this._db, this._coll, this._fieldFilters, newApiOption, this._transactionId);
}
async remove() {
const { offset, limit, projection, order } = this._apiOptions;
if (offset !== undefined ||
limit !== undefined ||
projection !== undefined ||
order !== undefined) {
console.warn('`offset`, `limit`, `projection`, `orderBy` are not supported in remove() operation');
}
let { multiple } = this._apiOptions;
const multi = multiple === undefined ? true : multiple;
const param = {
collectionName: this._coll,
query: this._fieldFilters,
queryType: constant_1.QueryType.WHERE,
multi
};
const res = await this._request.send('database.removeDocument', param, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
return {
requestId: res.requestId,
deleted: res.data.deleted
};
}
async updateAndReturn(data) {
if (!data || typeof data !== 'object') {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '参数必需是非空对象' }));
}
if (data.hasOwnProperty('_id')) {
return utils_1.processReturn(this._db.config.throwOnCode, Object.assign(Object.assign({}, code_1.ERRORS.INVALID_PARAM), { message: '不能更新_id的值' }));
}
let param = {
collectionName: this._coll,
queryType: constant_1.QueryType.WHERE,
data: update_1.UpdateSerializer.encodeEJSON(data, false)
};
if (this._transactionId) {
param.transactionId = this._transactionId;
}
if (this._fieldFilters) {
param.query = this._fieldFilters;
}
const res = await this._request.send('database.modifyAndReturnDoc', param, utils_1.getReqOpts(this._apiOptions));
if (res.code) {
return res;
}
return {
requestId: res.requestId,
updated: res.data.updated,
doc: res.data.doc && bson_1.EJSON.parse(res.data.doc)
};
}
}
exports.Query = Query;

View File

@@ -0,0 +1,7 @@
import { IResponseMessageErrorMsg } from '../typings/realtime';
export declare class RealtimeErrorMessageError extends Error {
isRealtimeErrorMessageError: boolean;
payload: IResponseMessageErrorMsg;
constructor(serverErrorMsg: IResponseMessageErrorMsg);
}
export declare const isRealtimeErrorMessageError: (e: any) => e is RealtimeErrorMessageError;

View File

@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class RealtimeErrorMessageError extends Error {
constructor(serverErrorMsg) {
super(`Watch Error ${JSON.stringify(serverErrorMsg.msgData)} (requestid: ${serverErrorMsg.requestId})`);
this.isRealtimeErrorMessageError = true;
this.payload = serverErrorMsg;
}
}
exports.RealtimeErrorMessageError = RealtimeErrorMessageError;
exports.isRealtimeErrorMessageError = (e) => e && e.isRealtimeErrorMessageError;

View File

@@ -0,0 +1,14 @@
import { VirtualWebSocketClient } from './virtual-websocket-client';
import { IRealtimeListenerConstructorOptions, DBRealtimeListener } from '../typings/index';
interface IRealtimeListenerOptions extends IRealtimeListenerConstructorOptions {
close: () => void;
debug?: boolean;
virtualClient?: VirtualWebSocketClient;
}
export declare class RealtimeListener implements DBRealtimeListener {
close: () => void;
onChange: (res: any) => void;
onError: (error: any) => void;
constructor(options: IRealtimeListenerOptions);
}
export {};

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class RealtimeListener {
constructor(options) {
this.close = options.close;
this.onChange = options.onChange;
this.onError = options.onError;
if (options.debug) {
Object.defineProperty(this, 'virtualClient', {
get: () => {
return options.virtualClient;
}
});
}
}
}
exports.RealtimeListener = RealtimeListener;

View File

@@ -0,0 +1,3 @@
import { IResponseMessage, IResponseMessageInitEventMsg } from '../typings/realtime';
export declare function genRequestId(prefix?: string): string;
export declare function isInitEventMessage(msg: IResponseMessage): msg is IResponseMessageInitEventMsg;

Some files were not shown because too many files have changed in this diff Show More