签入版本
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# 5.10 字典模块
|
||||
|
||||
:::tip 概述
|
||||
DictService 提供字典项的读取、正反向转换与缓存管理,是字典体系的核心服务。采用两级缓存(请求级 + 持久缓存),同一请求内不重复查库,跨请求复用持久缓存。
|
||||
字典体系由三个服务组成:`DictService`(字典 CRUD,`DictController` 注入)、`DictItemService`(字典项 CRUD)、`DictCacheService`(字典项读取、正反向转换与缓存管理,本页主体)。读取侧采用两级缓存(请求级 + 持久缓存),同一请求内不重复查库,跨请求复用持久缓存;字典项变更时 `DictItemObserver` 自动失效对应缓存。
|
||||
:::
|
||||
|
||||
## 字典体系架构
|
||||
@@ -11,7 +11,7 @@ DictService 提供字典项的读取、正反向转换与缓存管理,是字
|
||||
│ 字典体系 │
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ think_dict │ │ think_dict_item │ │
|
||||
│ │ laravel_dict │ │ laravel_dict_item│ │
|
||||
│ │ 字典主表 │ │ 字典项表 │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ id │◄───│ dict_id │ │
|
||||
@@ -22,24 +22,28 @@ DictService 提供字典项的读取、正反向转换与缓存管理,是字
|
||||
│ └──────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ DictService │ │ Logic 层 │ │
|
||||
│ │ 字典服务 │ │ serializeMaps │ │
|
||||
│ │ │ │ 自动翻译 │ │
|
||||
│ │ DictCacheService │ │ Service 层 │ │
|
||||
│ │ 字典服务 │ │ serializeMaps │ │
|
||||
│ │ │ │ 自动翻译 │ │
|
||||
│ │ getText() │ │ │ │
|
||||
│ │ getValue() │◄───│ 'status' => │ │
|
||||
│ │ getOptions() │ │ 'user_status' │ │
|
||||
│ │ clearCache() │ │ │ │
|
||||
│ └──────────────────┘ └──────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
||||
│ │ DictItemObserver(字典项变更 → 自动清除所属字典缓存) │ │
|
||||
│ └──────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 缓存架构
|
||||
|
||||
```text
|
||||
请求级缓存(static::$localCache) ← 同一请求内不重复查库
|
||||
请求级缓存(静态变量) ← 同一请求内不重复查库
|
||||
│ 未命中
|
||||
▼
|
||||
持久缓存(cache() 门面,1小时) ← 跨请求复用(file/redis)
|
||||
持久缓存(cache() 门面,1小时) ← 跨请求复用(Redis)
|
||||
│ 未命中
|
||||
▼
|
||||
数据库查询(dict → dict_item) ← 查询后写入两级缓存
|
||||
@@ -50,9 +54,7 @@ DictService 提供字典项的读取、正反向转换与缓存管理,是字
|
||||
| 缓存类型 | Key 格式 | 存储内容 |
|
||||
|----------|---------|---------|
|
||||
| 请求级 | `$localCache['gender']` | `['1'=>'男', '2'=>'女']` |
|
||||
| 请求级(完整) | `$localCache['full_gender']` | `[{id, name, value, sort, note}, ...]` |
|
||||
| 持久缓存 | `dict_gender` | `['1'=>'男', '2'=>'女']` |
|
||||
| 持久缓存(完整) | `dict_full_gender` | `[{id, name, value, sort, note}, ...]` |
|
||||
| 持久缓存 | `dict_gender`(前缀 `CACHE_PREFIX='dict_'`) | `['1'=>'男', '2'=>'女']` |
|
||||
|
||||
### 缓存生命周期
|
||||
|
||||
@@ -60,8 +62,8 @@ DictService 提供字典项的读取、正反向转换与缓存管理,是字
|
||||
1. 首次查询 → 数据库 → 写入持久缓存 + 请求级缓存
|
||||
2. 同一请求再次命中 → 直接返回请求级缓存(最快)
|
||||
3. 不同请求命中 → 读取持久缓存(不查库)
|
||||
4. 缓存过期(1小时)→ 重新查库 → 更新两级缓存
|
||||
5. 字典数据变更 → 调用 clearCache() 清除 → 下次查询重新加载
|
||||
4. 缓存过期(TTL 见 DictCacheService::CACHE_TTL)→ 重新查库 → 更新两级缓存
|
||||
5. 字典项变更 → DictItemObserver 自动调用 clearCache() → 下次查询重新加载
|
||||
```
|
||||
|
||||
## 核心方法
|
||||
@@ -69,10 +71,10 @@ DictService 提供字典项的读取、正反向转换与缓存管理,是字
|
||||
### getText — 根据值获取名称(最常用)
|
||||
|
||||
```php
|
||||
DictService::getText('gender', 1); // '男'
|
||||
DictService::getText('user_status', 1); // '启用'
|
||||
DictService::getText('gender', ''); // ''(空值返回空字符串)
|
||||
DictService::getText('gender', null); // ''(空值返回空字符串)
|
||||
DictCacheService::getText('gender', 1); // '男'
|
||||
DictCacheService::getText('user_status', 1); // '正常'
|
||||
DictCacheService::getText('gender', ''); // ''(空值返回空字符串)
|
||||
DictCacheService::getText('gender', null); // ''(空值返回空字符串)
|
||||
```
|
||||
|
||||
**用途:** 列表展示时将数据库值转换为可读名称。
|
||||
@@ -80,17 +82,17 @@ DictService::getText('gender', null); // ''(空值返回空字符串)
|
||||
### getValue — 根据名称反向获取值
|
||||
|
||||
```php
|
||||
DictService::getValue('gender', '男'); // '1'
|
||||
DictService::getValue('user_status', '启用'); // '1'
|
||||
DictService::getValue('gender', ''); // ''(空值返回空字符串)
|
||||
DictCacheService::getValue('gender', '男'); // '1'
|
||||
DictCacheService::getValue('user_status', '正常'); // '1'
|
||||
DictCacheService::getValue('gender', ''); // ''(空值返回空字符串)
|
||||
```
|
||||
|
||||
**用途:** Excel 导入时将中文文字转换为数据库存储的数值。
|
||||
**用途:** Excel 导入时将中文文字转换为数据库存储的数值(`ExampleService::import()` 即用此方法反查状态文本)。
|
||||
|
||||
### getOptions — 获取下拉框选项
|
||||
|
||||
```php
|
||||
DictService::getOptions('gender');
|
||||
DictCacheService::getOptions('gender');
|
||||
// [
|
||||
// ['label' => '男', 'value' => '1'],
|
||||
// ['label' => '女', 'value' => '2'],
|
||||
@@ -99,51 +101,58 @@ DictService::getOptions('gender');
|
||||
|
||||
**用途:** 前端下拉框、单选框、复选框组件的数据源。
|
||||
|
||||
### getFullItems — 获取完整字典项
|
||||
### getItems — 获取值→名称映射
|
||||
|
||||
```php
|
||||
DictService::getFullItems('gender');
|
||||
DictCacheService::getItems('gender');
|
||||
// ['1' => '男', '2' => '女']
|
||||
```
|
||||
|
||||
### getBatchTextMaps — 批量获取多个字典的映射
|
||||
|
||||
```php
|
||||
DictCacheService::getBatchTextMaps(['gender', 'user_status']);
|
||||
// [
|
||||
// ['id' => 1, 'name' => '男', 'value' => '1', 'sort' => 1, 'note' => ''],
|
||||
// ['id' => 2, 'name' => '女', 'value' => '2', 'sort' => 2, 'note' => ''],
|
||||
// 'gender' => ['1' => '男', '2' => '女'],
|
||||
// 'user_status' => ['1' => '正常', '2' => '禁用'],
|
||||
// ]
|
||||
```
|
||||
|
||||
**用途:** 需要 id、排序、备注等完整信息的场景。
|
||||
**用途:** 列表翻译时一次取多个字典,避免逐个查询。
|
||||
|
||||
### clearCache — 清除缓存
|
||||
### clearCache / clearAllCache — 缓存管理
|
||||
|
||||
```php
|
||||
DictService::clearCache('gender'); // 清除指定字典的缓存
|
||||
DictService::clearCache(); // 仅清空请求级缓存
|
||||
DictService::clearAllCache(); // 清除所有字典持久缓存
|
||||
DictCacheService::clearCache('gender'); // 清除指定字典的缓存
|
||||
DictCacheService::clearCache(); // 仅清空请求级缓存
|
||||
DictCacheService::clearAllCache(); // 清除所有字典持久缓存
|
||||
```
|
||||
|
||||
## 方法清单
|
||||
|
||||
| 方法 | 说明 | 返回值 |
|
||||
|------|------|--------|
|
||||
| `getText($code, $value)` | 值 → 名称 | `string` |
|
||||
| `getValue($code, $text)` | 名称 → 值 | `string` |
|
||||
| `getItems($code)` | 获取值→名称映射 | `array` `['1'=>'男', '2'=>'女']` |
|
||||
| `getOptions($code)` | 获取下拉框选项 | `array` `[['label'=>'男','value'=>'1'], ...]` |
|
||||
| `getFullItems($code)` | 获取完整字典项 | `array` `[{id, name, value, sort, note}, ...]` |
|
||||
| `clearCache($code?)` | 清除指定/请求级缓存 | `void` |
|
||||
| `getText($dictCode, $value)` | 值 → 名称 | `string` |
|
||||
| `getValue($dictCode, $text)` | 名称 → 值 | `string` |
|
||||
| `getItems($dictCode)` | 获取值→名称映射 | `array` `['1'=>'男', '2'=>'女']` |
|
||||
| `getOptions($dictCode)` | 获取下拉框选项 | `array` `[['label'=>'男','value'=>'1'], ...]` |
|
||||
| `getBatchTextMaps($codes)` | 批量获取多个字典映射 | `array` `[code => map, ...]` |
|
||||
| `clearCache($dictCode?)` | 清除指定/请求级缓存 | `void` |
|
||||
| `clearAllCache()` | 清除所有字典缓存 | `void` |
|
||||
|
||||
## 在 Logic 中使用
|
||||
## 在 Service 中使用
|
||||
|
||||
### 自动翻译(serializeMaps)
|
||||
|
||||
配置 `serializeMaps` 后,查询数据时自动补全 `{字段名}Text` 字段,无需手动调用 DictService:
|
||||
配置 `serializeMaps` 后(需 `use HasSerializeMaps`),查询数据时自动补全 `{字段名}Text` 字段,无需手动调用 DictCacheService:
|
||||
|
||||
```php
|
||||
class UserLogic extends BaseTenantLogic
|
||||
class UserService extends BaseTenantService
|
||||
{
|
||||
/**
|
||||
* 枚举显示名映射
|
||||
* 字典翻译配置:[字段 => 字典编码]
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $serializeMaps = [
|
||||
'gender' => 'gender',
|
||||
@@ -157,38 +166,29 @@ class UserLogic extends BaseTenantLogic
|
||||
```text
|
||||
数据库:{ gender: 1, status: 1 }
|
||||
│
|
||||
▼ processSerializeMaps()
|
||||
│ DictService::getText('gender', 1) → '男'
|
||||
│ DictService::getText('user_status', 1) → '启用'
|
||||
▼ processSerializeMapsList()
|
||||
│ DictCacheService 批量取词(getBatchTextMaps,同一请求只查一次)
|
||||
│ getText('gender', 1) → '男'
|
||||
│ getText('user_status', 1) → '正常'
|
||||
▼
|
||||
前端接收:{ gender: 1, genderText: '男', status: 1, statusText: '启用' }
|
||||
前端接收:{ gender: 1, genderText: '男', status: 1, statusText: '正常' }
|
||||
```
|
||||
|
||||
### 手动调用
|
||||
|
||||
```php
|
||||
// 在 Logic 或 Service 中手动调用
|
||||
$genderText = DictService::getText('gender', $user['gender']);
|
||||
$options = DictService::getOptions('gender');
|
||||
// 在 Service 中手动调用
|
||||
$genderText = DictCacheService::getText('gender', $user['gender']);
|
||||
$options = DictCacheService::getOptions('gender');
|
||||
```
|
||||
|
||||
## 在 Controller 中使用
|
||||
|
||||
### 获取下拉框数据
|
||||
### 获取字典项(前端下拉数据源)
|
||||
|
||||
```php
|
||||
// app/controller/DictController.php
|
||||
|
||||
/**
|
||||
* 获取字典项下拉列表
|
||||
*
|
||||
* GET /dict/getOptions/{code}
|
||||
*/
|
||||
public function getOptions(string $code): Json
|
||||
{
|
||||
$options = DictService::getOptions($code);
|
||||
return $this->success($options);
|
||||
}
|
||||
```bash
|
||||
# routes/api.php(认证组内)
|
||||
GET /api/dict/item/getDictItemList/{code}
|
||||
```
|
||||
|
||||
### 刷新字典缓存
|
||||
@@ -197,11 +197,11 @@ public function getOptions(string $code): Json
|
||||
/**
|
||||
* 刷新字典缓存
|
||||
*
|
||||
* GET /dict/refreshCache
|
||||
* GET /api/dict/refreshCache
|
||||
*/
|
||||
public function refreshCache(): Json
|
||||
public function refreshCache(): JsonResponse
|
||||
{
|
||||
DictService::clearAllCache();
|
||||
DictCacheService::clearAllCache();
|
||||
return $this->success(null, '缓存刷新成功');
|
||||
}
|
||||
```
|
||||
@@ -211,26 +211,22 @@ public function refreshCache(): Json
|
||||
### 导入时:文字转值
|
||||
|
||||
```php
|
||||
// UserLogic::import()
|
||||
// ExampleService::import()(来自项目实际代码)
|
||||
foreach ($data as &$row) {
|
||||
// Excel 中"男" → 数据库中 1
|
||||
if (!empty($row['gender']) && !is_numeric($row['gender'])) {
|
||||
$row['gender'] = DictService::getValue('gender', $row['gender']);
|
||||
}
|
||||
// Excel 中"启用" → 数据库中 1
|
||||
if (!empty($row['status']) && !is_numeric($row['status'])) {
|
||||
$row['status'] = DictService::getValue('user_status', $row['status']);
|
||||
// Excel 中"在用" → 数据库中 1
|
||||
if (! empty($row['status']) && ! is_numeric($row['status'])) {
|
||||
$row['status'] = DictCacheService::getValue('example_status', $row['status']);
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
```
|
||||
|
||||
### 导出时:值转文字
|
||||
|
||||
```php
|
||||
// 导出表头使用 Text 后缀字段
|
||||
// 导出表头使用 Text 后缀字段(由 serializeMaps 自动补全)
|
||||
$headers = [
|
||||
'name' => '姓名',
|
||||
'genderText' => '性别', // 自动翻译后的字段
|
||||
'name' => '名称',
|
||||
'statusText' => '状态', // 自动翻译后的字段
|
||||
];
|
||||
```
|
||||
@@ -241,14 +237,14 @@ $headers = [
|
||||
|
||||
```typescript
|
||||
// API
|
||||
export function getDictOptions(code: string) {
|
||||
return http.request({ url: `/dict/getOptions/${code}`, method: 'GET' });
|
||||
export function getDictItemByCode(code: string) {
|
||||
return http.request({ url: `/dict/item/getDictItemList/${code}`, method: 'GET' });
|
||||
}
|
||||
|
||||
// 使用
|
||||
const genderOptions = ref([]);
|
||||
onMounted(async () => {
|
||||
genderOptions.value = await getDictOptions('gender');
|
||||
genderOptions.value = await getDictItemByCode('gender');
|
||||
});
|
||||
```
|
||||
|
||||
@@ -260,7 +256,7 @@ onMounted(async () => {
|
||||
<el-option
|
||||
v-for="item in genderOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
@@ -269,7 +265,7 @@ onMounted(async () => {
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### think_dict(字典主表)
|
||||
### laravel_dict(字典主表)
|
||||
|
||||
| 字段 | 类型 | 说明 | 示例 |
|
||||
|------|------|------|------|
|
||||
@@ -279,13 +275,13 @@ onMounted(async () => {
|
||||
| `remark` | VARCHAR(500) | 备注 | 性别字典 |
|
||||
| `...` | | 公共字段 | |
|
||||
|
||||
### think_dict_item(字典项表)
|
||||
### laravel_dict_item(字典项表)
|
||||
|
||||
| 字段 | 类型 | 说明 | 示例 |
|
||||
|------|------|------|------|
|
||||
| `id` | INT | 主键 | 1 |
|
||||
| `dict_id` | INT | 字典 ID(外键) | 1 |
|
||||
| `name` | VARCHAR(50) | 项名称 | 男 |
|
||||
| `name` | VARCHAR(50) | 项名称(显示标签) | 男 |
|
||||
| `value` | VARCHAR(50) | 项值 | 1 |
|
||||
| `sort` | INT | 排序 | 1 |
|
||||
| `note` | VARCHAR(200) | 备注 | |
|
||||
@@ -295,12 +291,13 @@ onMounted(async () => {
|
||||
|
||||
| 字典编码 | 名称 | 项值 | 用途 |
|
||||
|----------|------|------|------|
|
||||
| `gender` | 性别 | 0=女, 1=男 | 用户性别 |
|
||||
| `user_status` | 用户状态 | 0=禁用, 1=启用 | 用户账号状态 |
|
||||
| `article_status` | 文章状态 | 0=草稿, 1=已发布 | 文章状态 |
|
||||
| `tenant_status` | 租户状态 | 0=禁用, 1=启用 | 租户状态 |
|
||||
| `example_type` | 案例类型 | 0=类型一, 1=类型二 | 案例分类 |
|
||||
| `example_status` | 案例状态 | 0=禁用, 1=启用 | 案例状态 |
|
||||
| `gender` | 性别 | 1=男, 2=女, 3=保密 | 用户性别 |
|
||||
| `user_status` | 用户状态 | 1=正常, 2=禁用 | 用户账号状态 |
|
||||
| `tenant_status` | 租户状态 | 1=启用, 0=禁用 | 租户状态 |
|
||||
| `example_type` | 案例类型 | 1=类型1, 2=类型2, 3=类型3, 4=类型4 | 案例分类 |
|
||||
| `example_status` | 案例状态 | 1=在用, 2=停用 | 案例状态 |
|
||||
|
||||
(项值以数据库初始化脚本中的实际字典项为准。)
|
||||
|
||||
## 缓存管理
|
||||
|
||||
@@ -308,12 +305,13 @@ onMounted(async () => {
|
||||
|
||||
| 时机 | 操作 | 说明 |
|
||||
|------|------|------|
|
||||
| 字典项新增/修改/删除 | `DictService::clearCache($code)` | 清除该字典的缓存 |
|
||||
| 手动刷新 | `GET /dict/refreshCache` | 清除所有字典缓存 |
|
||||
| 缓存过期 | 自动失效 | 1 小时 TTL |
|
||||
| 字典项新增/修改/删除 | **`DictItemObserver` 自动清除** | 模型事件触发,无需手动 |
|
||||
| 手动刷新 | `GET /api/dict/refreshCache` | 清除所有字典缓存 |
|
||||
| 命令行清理 | `php artisan dict:clear-cache` | 运维兜底 |
|
||||
| 缓存过期 | 自动失效 | TTL 见 `CACHE_TTL` |
|
||||
|
||||
:::warning 字典数据变更后必须刷新缓存
|
||||
修改字典项后,如果不刷新缓存,前端显示的可能仍是旧数据。建议在字典管理的增删改接口中自动调用 `clearCache()`。
|
||||
:::tip 缓存一致性已自动保障
|
||||
字典项(`DictItem`)保存/删除时 `DictItemObserver` 自动调用 `clearCache($code)`——业务代码无需手动刷新;上面的手动接口与命令仅作兜底(如直接改库绕过模型事件)。
|
||||
:::
|
||||
|
||||
## 安全特性
|
||||
@@ -324,3 +322,4 @@ onMounted(async () => {
|
||||
| 空值安全 | `getText`/`getValue` 对 null/空字符串返回空字符串 |
|
||||
| 类型安全 | 值统一转为字符串比较,避免类型松散比较问题 |
|
||||
| 缓存隔离 | 每个字典编码独立缓存,互不影响 |
|
||||
| 自动失效 | Observer 保证数据变更后缓存不脏 |
|
||||
|
||||
Reference in New Issue
Block a user