537 lines
15 KiB
Markdown
537 lines
15 KiB
Markdown
|
||
|
||
# 数据存储
|
||
|
||
Matchvs 给开发者提供了三种存储接口:用户数据存储、全局数据存储、哈希存储。
|
||
|
||
三种数据存储的特点及对比如下:
|
||
|
||
- 用户数据存储,存储用户数据,只有用户自己有增、删、改、查自己数据的权限
|
||
- 全局数据存储,推荐在 gameServer 里使用,存储游戏全局数据。客户端也可以使用。
|
||
- 哈希存储,数据操作会校验userID,但用户之间可以修改和查看数据。
|
||
|
||
**注意:使用存储接口前,须先调用初始化、注册接口获取userID,token 。**
|
||
|
||
## 存储限制
|
||
|
||
每个游戏通过各种存储接口所存的数据总容量不可以超过5G,如果超过,服务端会返回对应错误。
|
||
|
||
## 域名
|
||
|
||
Matchvs 环境分为测试环境(alpha)和 正式环境(release),所以在使用http接口时,需要通过域名进行区分。使用正式环境需要先在[官网控制台](http://www.matchvs.com/manage/gameContentList)将您的游戏发布上线。
|
||
|
||
**alpha环境域名:alphavsopen.matchvs.com**
|
||
|
||
**release环境域名:vsopen.matchvs.com**
|
||
|
||
|
||
|
||
## 存用户数据
|
||
|
||
存储接口 : **wc5/setUserData.do**
|
||
|
||
开发者可以通过调用该接口将用户自定义的数据存储至服务器。
|
||
|
||
```
|
||
http://alphavsopen.matchvs.com/wc5/setUserData.do?gameID=200660&userID=21023&dataList=[
|
||
{"key":"Johnuser", "value":"Smith"}]&sign=f6c15ebd1957a7616781b20fc150f4aa
|
||
```
|
||
|
||
**注意:** 每个value的长度上限为1M,如果长度超过1M,会返回“长度超过限制”的错误。存储上限为每个游戏5G,如果超过5G,会返回对应错误。
|
||
|
||
可以调用setUserData实现增量存储。为避免特殊字符影响,存储前,建议开发者最好将字符串解码成二进制再用UrlEndcode编码后存储。
|
||
|
||
| 参数名 | 说明 |
|
||
| -------- | -------------------------- |
|
||
| gameID | 游戏ID |
|
||
| userID | 用户ID |
|
||
| dataList | 自定义存储json数组,包括字段的key和value |
|
||
| sign | 见下方sign值获取方法-用户 |
|
||
|
||
返回数据示例如下:
|
||
|
||
```
|
||
{
|
||
"data": "success",
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
|
||
|
||
## 取用户数据
|
||
|
||
获取接口 : **wc5/getUserData.do**
|
||
|
||
开发者可以通过调用该接口获取用户自定义存储的数据。
|
||
|
||
```
|
||
http://alphavsopen.matchvs.com/wc5/getUserData.do?gameID=200660&userID=21023&keyList=[
|
||
{"key":"Johnuser"}]&sign=f6c15ebd1957a7616781b20fc150f4aa
|
||
```
|
||
|
||
**注意:** 存储前,如果将字符串解码成二进制再用UrlEndcode编码后存储,对应的取出时应用UrlDecode进行解码后显示
|
||
|
||
| 参数名 | 说明 |
|
||
| ------- | ------------ |
|
||
| gameID | 游戏ID |
|
||
| userID | 用户ID |
|
||
| keyList | 需要取的数据对应的键列表 |
|
||
| sign | 见下方sign值获取方法-用户 |
|
||
|
||
返回数据示例如下:
|
||
|
||
```
|
||
{
|
||
"data": {
|
||
"dataList": [
|
||
{
|
||
"key": "Johnuser",
|
||
"value": "Smith"
|
||
}
|
||
]
|
||
},
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
## 删用户数据
|
||
|
||
删除接口 : **wc5/delUserData.do**
|
||
|
||
开发者可以通过调用该接口删除用户自定义存储的数据。
|
||
|
||
```
|
||
http://alphavsopen.matchvs.com/wc5/delUserData.do?gameID=200660&userID=21023&keyList=[
|
||
{"key":"Johnuser"}]&sign=f6c15ebd1957a7616781b20fc150f4aa
|
||
```
|
||
|
||
**注意:** 支持一次删除多条数据
|
||
|
||
| 参数名 | 说明 |
|
||
| ------- | ------------- |
|
||
| gameID | 游戏ID |
|
||
| userID | 用户ID |
|
||
| keyList | 需要删除的数据对应的键列表 |
|
||
| sign | 见下方sign值获取方法-用户 |
|
||
|
||
返回数据示例如下:
|
||
|
||
```
|
||
{
|
||
"data": "success",
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
## 存全局数据
|
||
|
||
存储接口 : **wc5/setGameData.do**
|
||
|
||
开发者可以通过调用该接口将全局自定义的数据存储至服务器。
|
||
|
||
```
|
||
http://alphavsopen.matchvs.com/wc5/setGameData.do?gameID=200660&userID=21023&dataList=[
|
||
{"key":"Johnuser", "value":"Smith"}]&sign=0c2c2df5949f498afd307e8783bb1f3c
|
||
```
|
||
|
||
**注意:** 每个value的长度上限为1M,如果长度超过1M,会返回“长度超过限制”的错误。存储上限为每个游戏5G,如果超过5G,会返回对应错误。
|
||
|
||
可以调用setGameData实现增量存储。为避免特殊字符影响,存储前,建议开发者最好将字符串解码成二进制再用UrlEndcode编码后存储。
|
||
|
||
| 参数名 | 说明 |
|
||
| -------- | -------------------------- |
|
||
| gameID | 游戏ID |
|
||
| userID | 用户ID |
|
||
| dataList | 自定义存储json数组,包括字段的key和value |
|
||
| sign | 见下方sign值获取方法-全局 |
|
||
|
||
返回数据示例如下:
|
||
|
||
```
|
||
{
|
||
"data": "success",
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
## 取全局数据
|
||
|
||
获取接口 : **wc5/getGameData.do**
|
||
|
||
开发者可以通过调用该接口获取用户自定义存储的数据。
|
||
|
||
```
|
||
http://alphavsopen.matchvs.com/wc5/getGameData.do?gameID=200660&userID=21023&keyList=[
|
||
{"key":"Johnuser"}]&sign=0c2c2df5949f498afd307e8783bb1f3c
|
||
```
|
||
|
||
**注意:** 存储前,如果将字符串解码成二进制再用UrlEndcode编码后存储,对应的取出时应用UrlDecode进行解码后显示
|
||
|
||
| 参数名 | 说明 |
|
||
| ------- | ------------ |
|
||
| gameID | 游戏ID |
|
||
| userID | 用户ID |
|
||
| keyList | 需要取的数据对应的键列表 |
|
||
| sign | 见下方sign值获取方法-全局 |
|
||
|
||
返回数据示例如下:
|
||
|
||
```
|
||
{
|
||
"data": {
|
||
"dataList": [
|
||
{
|
||
"key": "Johnuser",
|
||
"value": "Smith"
|
||
}
|
||
]
|
||
},
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
## 删全局数据
|
||
|
||
删除接口 : **wc5/delGameData.do**
|
||
|
||
开发者可以通过调用该接口删除全局自定义存储的数据。
|
||
|
||
```
|
||
http://alphavsopen.matchvs.com/wc5/delGameData.do?gameID=200660&userID=21023&keyList=[
|
||
{"key":"Johnuser"}]&sign=0c2c2df5949f498afd307e8783bb1f3c
|
||
```
|
||
|
||
**注意:** 支持一次删除多条数据
|
||
|
||
| 参数名 | 说明 |
|
||
| ------- | ------------- |
|
||
| gameID | 游戏ID |
|
||
| userID | 用户ID |
|
||
| keyList | 需要删除的数据对应的键列表 |
|
||
| sign | 见下方sign值获取方法-全局 |
|
||
|
||
返回数据示例如下:
|
||
|
||
```
|
||
{
|
||
"data": "success",
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
## 存哈希
|
||
|
||
存储接口 : **wc5/hashSet.do**
|
||
|
||
开发者可以通过调用该接口将自定义的数据存储至服务器。
|
||
|
||
```
|
||
http://alphavsopen.matchvs.com/wc5/hashSet.do?gameID=102003&userID=21023&key=1&value=a&sign=68c592733f19f6c5ae7e8b7ae8e5002f
|
||
```
|
||
|
||
**注意:** 每个value的长度上限为1M,如果长度超过1M,会返回“长度超过限制”的错误。存储上限为每个玩家1000条,如果超过1000条,会返回对应错误。
|
||
|
||
可以调用hashSet实现增量存储。为避免特殊字符影响,存储前,建议开发者最好将字符串解码成二进制再用UrlEndcode编码后存储。
|
||
|
||
| 参数名 | 说明 |
|
||
| ------ | ------------ |
|
||
| gameID | 游戏ID |
|
||
| userID | 用户ID |
|
||
| key | 自定义存储字段编号 |
|
||
| value | 自定义存储字段的值 |
|
||
| sign | 见下方sign值获取方法-哈希 |
|
||
|
||
返回数据示例如下:
|
||
|
||
```
|
||
{
|
||
"code": 0,
|
||
"data": "success",
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
## 取哈希
|
||
|
||
取接口:**wc5/hashGet.do**
|
||
|
||
开发者可以通过调用该接口获取存储在服务器的自定义数据。
|
||
|
||
```
|
||
http://vsopen.matchvs.com/wc5/hashGet.do?gameID=102003&userID=21023&key=1&sign=b0244f7ed1d433975512a8f6c2ba4517
|
||
```
|
||
|
||
**注意** 存储前,如果将字符串解码成二进制再用UrlEndcode编码后存储,对应的取出时应用UrlDecode进行解码后显示
|
||
|
||
| 参数名 | 说明 |
|
||
| ------ | ------------ |
|
||
| gameID | 游戏ID |
|
||
| userID | 用户ID |
|
||
| key | 自定义存储字段键值 |
|
||
| sign | 见下方sign值获取方法-哈希 |
|
||
|
||
返回数据示例如下:
|
||
|
||
```
|
||
{
|
||
"code": 0,
|
||
"data": "this is my data",
|
||
"status": 0
|
||
}
|
||
```
|
||
|
||
## sign值获取方法-用户
|
||
|
||
1. 按照如下格式拼接出字符串:
|
||
|
||
```
|
||
appKey&gameID=xxx&userID=xxx&token
|
||
```
|
||
|
||
- `appKey`为您在官网配置游戏所得
|
||
- `token`通过用户注册请求获取
|
||
|
||
2. 计算第一步拼接好的字符串的`MD5`值,即为`sign`的值。
|
||
|
||
## sign值获取方法-全局
|
||
|
||
1. 按照如下格式拼接出字符串:
|
||
|
||
```
|
||
appkey&gameID=xxx&userID=xxx&appSecret
|
||
```
|
||
|
||
- `appKey和appSecret`为您在官网配置游戏所得
|
||
|
||
2. 计算第一步拼接好的字符串的`MD5`值,即为`sign`的值。
|
||
|
||
## sign值获取方法-哈希
|
||
|
||
1. 按照如下格式拼接出字符串:
|
||
|
||
```
|
||
appKey¶m1=value1¶m2=value2¶m3=value3&token
|
||
```
|
||
|
||
- `appKey`为您在官网配置游戏所得
|
||
|
||
- `param1、param2、param3`等所有参数,按照数字`0-9`、英文字母`a~z`的顺序排列
|
||
|
||
例 : 有三个参数`gameID`、`userID`、`key`,则按照`appkey&gameID=xxx&key=xxx&userID=xxx&token` 的顺序拼出字符串。
|
||
|
||
- `token`通过用户注册请求获取
|
||
|
||
2. 计算第一步拼接好的字符串的`MD5`值,即为`sign`的值。
|
||
|
||
|
||
|
||
## 错误码
|
||
|
||
| 错误码 | 含义 |
|
||
| ------ | ---------------- |
|
||
| 413 | 存储长度超过限制 |
|
||
|
||
|
||
|
||
|
||
## 接口代码示例
|
||
|
||
这里以 [demo](https://github.com/matchvs/demo-Egret/blob/master/MatchvsDemo_Egret/src/matchvs/MvsHttpApi.ts) 的代码为例 ,下面这些是公共用代码:
|
||
|
||
```typescript
|
||
class MvsHttpApi {
|
||
//这里定义接口要使用的连接
|
||
public open_host:string = MatchvsData.pPlatform == "release"? "https://vsopen.matchvs.com":"https://alphavsopen.matchvs.com";
|
||
|
||
public get_game_data:string = "/wc5/getGameData.do?";
|
||
public set_game_data:string = "/wc5/setGameData.do?";
|
||
|
||
......
|
||
|
||
private counter:number = Math.floor(Math.random()*1000);
|
||
|
||
public token = GlobalData.myUser.token;
|
||
public gameID = MatchvsData.gameID;
|
||
public userID = GlobalData.myUser.userID;
|
||
public appkey = MatchvsData.appKey;
|
||
public secret = MatchvsData.secret;
|
||
|
||
public constructor() {
|
||
}
|
||
|
||
public getCounter(){
|
||
return ++this.counter;
|
||
}
|
||
|
||
public getTimeStamp():number{
|
||
return Math.floor(Date.now()/1000);
|
||
}
|
||
|
||
/**
|
||
* 把参数中的 key, value 转为 key=value&key1=value2&key3=value3 形式
|
||
* @param {any} args {key:value[, ...]} 形式
|
||
*/
|
||
public static paramsParse(args:any){
|
||
let str = "";
|
||
for(let k in args){
|
||
let val = "";
|
||
|
||
if ( 'object' == (typeof args[k]) ) {
|
||
val = JSON.stringify(args[k]);
|
||
}else{
|
||
val = args[k];
|
||
}
|
||
if(str == ""){
|
||
|
||
str = k + "=" + val;
|
||
}else{
|
||
str = str + "&" + k + "=" + val;
|
||
}
|
||
}
|
||
return str;
|
||
}
|
||
|
||
/**
|
||
* 组合 url 防止出现 host + path 出现两个 // 符号
|
||
* @param {string} host
|
||
* @param {...string} params
|
||
*/
|
||
public static url_Join(host, ...params) {
|
||
let p = "";
|
||
params.forEach(a => {
|
||
if (typeof a == "object") {
|
||
throw 'the parameter can only be string ';
|
||
}
|
||
if (a.substring(0,1) == '/'){
|
||
p = p + a;
|
||
}else{
|
||
p = p + '/' + a;
|
||
}
|
||
});
|
||
if (host.substring(host.length - 1, host.length) == '/') {
|
||
p = host.substring(0, host.length - 1) + p;
|
||
} else {
|
||
p = host + p;
|
||
}
|
||
return p;
|
||
}
|
||
|
||
|
||
/**
|
||
* 指定签名参数签名
|
||
*/
|
||
public SignPoint(args:any, points:Array<string>){
|
||
let tempobj = {}
|
||
points.sort();
|
||
points.forEach((val)=>{
|
||
tempobj[val] = args[val];
|
||
});
|
||
|
||
if(args["seq"]){
|
||
tempobj["seq"] = args["seq"];
|
||
}
|
||
if(args["ts"]){
|
||
tempobj["ts"] = args["ts"];
|
||
}
|
||
|
||
let headKey:string = MatchvsData.appKey;
|
||
let endKey:string = args.mode == 2? MatchvsData.secret: GlobalData.myUser.token;
|
||
|
||
let paramStr = MvsHttpApi.paramsParse(tempobj);
|
||
let md5Encode = new MD5()
|
||
let sign = md5Encode.hex_md5(headKey+"&"+paramStr+"&"+endKey);
|
||
return sign;
|
||
}
|
||
|
||
private dohttp(url:string, method:string, params:any, callback:Function){
|
||
let headtype = (method == "GET" ? "text/plain" : "application/json") ;
|
||
var request = new XMLHttpRequest()
|
||
request.open(method, url)
|
||
request.setRequestHeader("Content-Type",headtype);
|
||
if (method == "GET"){
|
||
request.send();
|
||
}else{
|
||
request.send(JSON.stringify(params));
|
||
}
|
||
request.onerror = (e)=>{
|
||
callback(JSON.parse(request.response), null);
|
||
}
|
||
request.onreadystatechange = ()=>{
|
||
if(request.readyState == 4){
|
||
if( request.status == 200 ){
|
||
callback(JSON.parse(request.responseText), null);
|
||
}else{
|
||
callback(null, " http request error "+request.responseText);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public http_get(url, callback){
|
||
this.dohttp(url, "GET", {}, callback);
|
||
}
|
||
|
||
public http_post(url, params ,callback){
|
||
this.dohttp(url, "POST", params, callback);
|
||
}
|
||
}
|
||
```
|
||
|
||
下面是通过上面的代码实现的接口示例:
|
||
|
||
```typescript
|
||
/**
|
||
* 获取全局接口数据
|
||
*/
|
||
public getGameData(list:Array<any>,callback:Function){
|
||
let keyList = [];
|
||
list.forEach(k=>{
|
||
keyList.push({key:k});
|
||
});
|
||
let data = {
|
||
gameID : "123456",
|
||
userID : GlobalData.myUser.userID || 0,
|
||
keyList : keyList,
|
||
sign : "",
|
||
mode : 2,
|
||
seq: this.getCounter(),
|
||
ts:this.getTimeStamp(),
|
||
}
|
||
data.sign = this.SignPoint(data, ["gameID", "userID"]);
|
||
let param = MvsHttpApi.paramsParse(data);
|
||
this.http_get(MvsHttpApi.url_Join(this.open_host, this.get_game_data)+param, callback);
|
||
}
|
||
/**
|
||
* 保存全局数据
|
||
*/
|
||
public setGameData(userID:number, list:Array<any>, callback:Function){
|
||
let listInfo = [];
|
||
list.forEach(user=>{
|
||
listInfo.push({
|
||
key: user.userID,
|
||
value: ArrayTools.Base64Encode(JSON.stringify({ name: user.name, avatar: user.avatar })),
|
||
});
|
||
});
|
||
let params = {
|
||
gameID : this.gameID,
|
||
userID : userID,
|
||
dataList: listInfo,
|
||
sign : "",
|
||
mode:2,
|
||
seq: this.getCounter(),
|
||
ts:this.getTimeStamp(),
|
||
}
|
||
params.sign = this.SignPoint(params, ["gameID","userID"]);
|
||
this.http_post(MvsHttpApi.url_Join(this.open_host, this.set_game_data), params, callback);
|
||
}
|
||
|
||
|
||
```
|
||
|
||
> 示例代码是Egret斗地主案例,完整代码 [可以看这里](https://github.com/matchvs/demo-Egret/blob/master/MatchvsDemo_Egret/src/matchvs/MvsHttpApi.ts)
|
||
>
|
||
> 注意:** 以上为示例代码为演示接口的调用方法,可能不能直接运行,开发者根据自己需求适当的修改。
|
||
|
||
|