---
title: 键值数据存储 (KV)
source: https://vjmap.com/guide/service/api/08-data-storage.html
---

# 键值数据存储 (KV)

> 分类代号：C.8  本章共 **5 个接口**

vjmap 内置了一个按工作区隔离的 KV 数据表（SQLite），用来存用户配置、缩略图、缓存数据等零碎东西。"零碎"并不代表"不好用"——二开里非常常用，比如存每个用户的图层可见性、自定义地图标注等。

**存储位置**：后端 `data/customData.db`。  
**自动前缀**：所有用户可见 key 都加了隐形 `data_` 前缀，本 SDK 已屏蔽此细节。  
**TTL**：可选过期时间（秒），到期自动清理。  
**批量 key 分隔符**：路径里 `;;`（双分号）分隔多个 key。

---

## 快速索引

| 序号 | SDK 方法 | URL |
|----:|---------|-----|
| 53 | `get_custom_data` / `getCustomData` | `GET /api/v1/service/data/{key}` |
| 54 | `save_custom_data` / `saveCustomData` | `POST /api/v1/service/data/{key}` |
| 55 | `delete_custom_data` / `deleteCustomData` | `DELETE /api/v1/service/data/{key}` |
| 56 | `save_multiple_custom_data` / `saveMultipleCustomData` | `POST /api/v1/service/datas` |
| 57 | `get_custom_data_keys` / `getCustomDataKeys` | `GET /api/v1/service/datakeys?prefix=` |


---

## 53 · 获取值 `data/{key}` GET

### 查询参数

| 字段 | 取值 | 说明 |
|------|------|------|
| `retDataType` | `""` / `"value"` / `"prop"` | 默认返回 `{data, prop}`；`value` 只要 `data`；`prop` 只要 `prop` |
| `contentType` | `""` / `"image"` | `image` 时后端把 base64 dataURL 解出，直接返回 PNG 二进制（不是 JSON）|

批量：`key` 路径段用 `;;` 拼接多 key，SDK 传字符串列表即可。

### 响应

- 存在：`{ "status": "ok", "data": <value>, "prop": <prop> }`
- 不存在：`{ "status": "keyNoExist" }`（**不是 404**）
- 批量：`{ "values": [...], "props": [...], "status": [...] }`

### 实采

```json
// get_custom_data.json
{
  "data": "hello-vjmap",
  "prop": "text/plain",
  "status": "ok"
}
```

```json
// get_missing.json —— 一定要判断 status
{ "status": "keyNoExist" }
```

### Python

```python
d = svc.get_custom_data("myproj/theme")
if d["status"] == "keyNoExist":
    ...
else:
    theme = d["data"]
```

### Node.js

```javascript
const d = await svc.getCustomData('myproj/theme');
if (d.status === 'keyNoExist') { /* ... */ }
```

### 批量读

```python
svc.get_custom_data(["a", "b"])
# { "values": ["va","vb"], "props":["pa","pb"], "status":["ok","ok"] }
```

---

## 54 · 保存值 `data/{key}` POST

### 查询参数

| 字段 | 说明 |
|------|------|
| `ttl` | 秒；到点清除。省略或 `0` = 长期 |

### Body 两种写法

1. **JSON**：`{"value": "...", "prop": "..."}`；`prop` 可存元数据（MIME、扩展字段）
2. **Raw**：直接把文件二进制 POST 上去，作为 value 字段存（SDK 里叫 `raw_body` / `rawBody`）

### 响应（实采）

```json
{
  "data": "nb_kv_a_1776832185",
  "status": "ok"
}
```

### Python

```python
svc.save_custom_data("myproj/theme",
                    value='{"mode":"dark"}',
                    prop="application/json")

# 带 TTL
svc.save_custom_data("tmp_token",
                    value="eyJ...", ttl=3600)

# Raw bytes
svc.save_custom_data("user_avatar_42",
                    raw_body=open("avatar.png","rb").read())
```

### Node.js

```javascript
await svc.saveCustomData('myproj/theme', {
  value: JSON.stringify({ mode: 'dark' }),
  prop: 'application/json',
});

await svc.saveCustomData('tmp_token', { value: tokenStr, ttl: 3600 });
```

---

## 55 · 删除值 `data/{key}` DELETE

### 查询参数

| 字段 | 说明 |
|------|------|
| `isPrefix` | `"true"` 时把 `key` 当前缀，批量清除；否则只删单条 |

### 响应

- 单条（实采）：`{"data":"nb_kv_a_...","status":"ok"}`
- 前缀（实采）：`{"data":"nb_prefix_...","isPrefix":true,"status":"ok"}`

### Python

```python
svc.delete_custom_data("myproj/theme")

# 前缀批量
svc.delete_custom_data("myproj/user42/", is_prefix=True)

# 列表批量（走 ;; 分隔）
svc.delete_custom_data(["a", "b", "c"])
```

---

## 56 · 批量存值 `datas` POST

**URL**：`POST /api/v1/service/datas`

### Body

`[{"key":..,"value":..,"prop":..,"ttl":"60"}]`，注意 `ttl` 是**字符串**秒数。

### 响应（实采）

```json
{ "status": ["ok"] }
```

### Python

```python
svc.save_multiple_custom_data([
    {"key": "cfg/a", "value": "1"},
    {"key": "cfg/b", "value": "2", "ttl": "3600"},
])
```

### Node.js

```javascript
await svc.saveMultipleCustomData([
  { key: 'cfg/a', value: '1' },
  { key: 'cfg/b', value: '2', ttl: '3600' },
]);
```

---

## 57 · 按前缀列键 `datakeys` GET

### 查询参数

| 字段 | 说明 |
|------|------|
| `prefix` | 前缀（自动加 `data_`） |
| `retDataType` | `value` / `prop` / `valueprop` —— 同时取出值，省一次往返 |
| `isSys` | `true` 时返回带 `data_` 内部前缀的系统键（仅 root 场景） |

### 响应

- 仅 keys（实采）：

```json
{ "keys": ["nb_kv_a_1776832185", "nb_kv_b_1776832185"] }
```

- 带值（`retDataType=valueprop`，实采）：

```json
{
  "keys":   ["nb_kv_a_1776832185", "nb_kv_b_1776832185"],
  "props":  ["text/plain",          "bp-1"],
  "values": ["hello-vjmap",         "batch-1"],
  "status": ["ok",                  "ok"]
}
```

### Python

```python
keys = svc.get_custom_data_keys(prefix="myproj/")["keys"]

# 同时取值
bundle = svc.get_custom_data_keys(prefix="myproj/", ret_data_type="valueprop")
for k, v, p in zip(bundle["keys"], bundle["values"], bundle["props"]):
    print(k, v, p)
```

---

## 最佳实践

1. **前缀分层**：用 `/`/`:` 建立命名空间，如 `myproj/user42/tool_state`。这样前缀批量删除、列出都方便。
2. **大文件不要存 KV**：value 单条一般 < 1 MB；大图/DWG 用 `/map/uploadmap` 或独立对象存储。
3. **TTL 清理**：用 `ttl` 让后端帮忙过期；别自己写 crontab。
4. **`keyNoExist` ≠ 错误**：200 正常响应里 `status == "keyNoExist"` 表示没值，而不是失败。

---
