5.数据字典

数据字典

一、数据字典介绍

数据字典就是管理系统中常用的分类数据或者一些固定数据,例如:省市区三级联动数据、民族数据、行业数据、学历数据等,由于该系统大量使用这种数据,所以我们要做一个数据管理方便管理系统数据,一般系统基本都会做数据管理。

二、数据字典开发

1.搭建service-cmn模块

  • config
  • controller
  • mapper
  • service
    • impl

2.数据字典接口

列表-后台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@Api(tags = "数据字典的接口")
@CrossOrigin
@RequestMapping("/admin/cmn/dict")
public class DictController {

@Autowired
private DictService dictService;

/**
* 根据数据id查询子数据列表
* @param id
* @return
*/
@ApiOperation(value = "根据数据id查询子数据列表")
@GetMapping("findChildData/{id}")
public Result findChildData(@PathVariable Long id){
List<Dict> dictList = dictService.findChildData(id);
return Result.ok(dictList);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService{

//根据数据id查询子数据列表
@Override
public List<Dict> findChildData(Long id) {
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("parent_id",id);
//ServiceImpl中已经帮忙注入了,所以直接调用就行
List<Dict> dictList = baseMapper.selectList(wrapper);
//向list集合每个dict对象中设置hasChildren
for (Dict dict : dictList) {
Long dictId = dict.getId();
boolean hasChildren = this.hasChildren(dictId);
dict.setHasChildren(hasChildren);
}
return dictList;
}

//判断id下面是否有子节点
private boolean hasChildren(Long id){
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("parent_id",id);
Integer count = baseMapper.selectCount(wrapper);
return count>0;
}
}

列表-前端

1
2
3
4
5
6
7
8
9
10
11
12
13
//数据字典列表
getDictList(id) {
dict.dictList(id)
.then(response => {
this.list = response.data
})
},
//层级显示
getChildren(tree, treeNode, resolve) {
dict.dictList(tree.id).then(response => {
resolve(response.data)
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<el-table
:data="list"
style="width: 100%"
row-key="id"
border
lazy
:load="getChildren"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">

<el-table-column label="名称" width="230" align="left">
<template slot-scope="scope">
<span>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="编码" width="220">
<template slot-scope="{row}">
{{ row.dictCode }}
</template>
</el-table-column>
<el-table-column label="值" width="230" align="left">
<template slot-scope="scope">
<span>{{ scope.row.value }}</span>
</template>
</el-table-column>

<el-table-column label="创建时间" align="center">
<template slot-scope="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
</el-table>

EasyExcel-写操作

  1. 引入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.1</version>
    </dependency>
  2. 创建实体类,在对应的属性上设置注解,注解内容就是表头的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Data
    public class UserData {

    @ExcelProperty("用户编号")
    private int uid;

    @ExcelProperty("用户名称")
    private String username;

    }
  3. 实现写操作的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class TestWrite {
    public static void main(String[] args) {
    //构建数据list集合
    List<UserData> list = new ArrayList();
    for (int i = 0; i < 10; i++) {
    UserData data = new UserData();
    data.setUid(i);
    data.setUsername("Sirius"+(i+1);
    list.add(data);
    }

    //设置excel文件路径和名称
    String fileName = "E:\\StudyFiles\\TestExcel\\write01.xlsx";

    //调用方法实现写操作
    EasyExcel.write(fileName,UserData.class).sheet("用户信息")
    .doWrite(list);
    }
    }

EasyExcel-读操作

  1. 创建实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Data
    public class UserData {

    @ColumnWidth(20) //设置列宽
    @ExcelProperty(value = "用户编号",index = 0)
    private int uid;

    @ColumnWidth(20)
    @ExcelProperty(value = "用户名称",index = 1)
    private String username;

    }
  2. 写一个监听器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class ExcelListener extends AnalysisEventListener<UserData> {

    //一行一行读取excel的内容,从第二行读取,因为第一行是表头
    @Override
    public void invoke(UserData userData, AnalysisContext analysisContext) {
    System.out.println(userData);
    }

    @Override
    public void invokeHeadMap(Map<Integer,String> headMap, AnalysisContext analysisContext){
    System.out.println("表头信息:"+headMap);
    }

    //读取之后会执行
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
    }
  3. 实现读操作的代码

    1
    2
    3
    4
    5
    6
    7
    8
    public class TestRead {
    public static void main(String[] args) {
    //读取文件的路径和名称
    String fileName = "E:\\StudyFiles\\TestExcel\\write01.xlsx";
    //调用方法实现读取操作
    EasyExcel.read(fileName,UserData.class,new ExcelListener()).sheet().doRead();
    }
    }

数据字典-导出

serviceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//导出数据字典接口
@Override
public void exportDictData(HttpServletResponse response) {
//设置类型
response.setContentType("application/vnd.ms-excel");
//设置编码
response.setCharacterEncoding("utf-8");
//这里使用URLEncode.encode可以防止中文乱码,当然和easyexcel没有关系
String fileName = "dict";
response.setHeader("Content-disposition","attachment;filename="+fileName+".xlsx");

//查询数据库
List<Dict> dictList = baseMapper.selectList(null);
//把Dict转换成DictEeVo
List<DictEeVo> dictEeVoList = new ArrayList<>();
for (Dict dict : dictList) {
DictEeVo dictEeVo = new DictEeVo();
BeanUtils.copyProperties(dict,dictEeVo);
dictEeVoList.add(dictEeVo);
}
//调用方法进行写的操作
try {
EasyExcel.write(response.getOutputStream(), DictEeVo.class).sheet("dict")
.doWrite(dictEeVoList);
} catch (IOException e) {
e.printStackTrace();
}
}

controller

1
2
3
4
@GetMapping("exportData")
public void exportDict(HttpServletResponse response){
dictService.exportDictData(response);
}

前端

1
2
3
4
5
//下载数据字典
exportData() {
//调用导出接口
window.location.href="http://localhost:8202/admin/cmn/dict/exportData"
},
1
2
3
<a href="http://localhost:8202/admin/cmn/dict/exportData" target="_blank">
<el-button type="primary"><i class="el-icon--right el-icon-download"></i>&nbsp;&nbsp;下载</el-button>
</a>

数据字典导入

注意:listener、fitter都不是Spring容器管理的,无法在这些类中直接使用Spring注解的方式来注入我们需要的对象。

controller

1
2
3
4
5
@PostMapping("importData")
public Result importDict(MultipartFile file){
dictService.importDictData(file);
return Result.ok();
}

serviceImpl

1
2
3
4
5
6
7
8
9
//导入数据字典实现
@Override
public void importDictData(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(),DictEeVo.class,new DictListener(baseMapper)).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}

listener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DictListener extends AnalysisEventListener<DictEeVo> {

//这里不能用Autowired注入
private DictMapper dictMapper;
//有参构造
public DictListener(DictMapper dictMapper) {
this.dictMapper = dictMapper;
}

//一行一行读取
@Override
public void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {
//调用方法添加数据库
Dict dict = new Dict();
BeanUtils.copyProperties(dictEeVo,dict);
dictMapper.insert(dict);
}

@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {

}
}

前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<el-dialog title="上传" :visible.sync="dialogImportVisible" width="480px">
<el-form label-position="right" label-width="170px">

<el-form-item label="文件">

<el-upload
:multiple="false"
:on-success="onUploadSuccess"
:action="'http://localhost:8202/admin/cmn/dict/importData'"
class="upload-demo">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传excel文件,且不超过500kb</div>
</el-upload>

</el-form-item>

</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogImportVisible = false">
取消
</el-button>
</div>
</el-dialog>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
export default {
data() {
return {
//设置弹框是否弹出 false为不弹出
dialogImportVisible:false,
list:[] //数据字典列表数组
}
},
created() {
this.getDictList(1)
},
methods: {
//上传数据字典
importData() {
this.dialogImportVisible = true
},
//上传成功调用
onUploadSuccess() {
//关闭弹框
this.dialogImportVisible = false
//刷新页面
this.getDictList(1)
},
//下载数据字典
exportData() {
//调用导出接口
window.location.href="http://localhost:8202/admin/cmn/dict/exportData"
},
//数据字典列表
getDictList(id) {
dict.dictList(id)
.then(response => {
this.list = response.data
})
},
//层级显示
getChildren(tree, treeNode, resolve) {
dict.dictList(tree.id).then(response => {
resolve(response.data)
})
}
}
}

数据字典-添加缓存

缓存:提高查询速度

不经常修改的数据固定的数据经常查询的数据适合做缓存

项目集成SpringCache + Redis

  1. 在service_util中添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- redis -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- spring2.X集成redis所需common-pool2-->
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.0</version>
    </dependency>
  2. 在service_util中的config包下添加redis的配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    @Configuration
    //开启缓存处理
    @EnableCaching
    public class RedisConfig {

    /**
    * 自定义key规则
    * @return
    */
    @Bean
    public KeyGenerator keyGenerator(){
    return new KeyGenerator() {
    @Override
    public Object generate(Object target, Method method, Object... params) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(target.getClass().getName());
    stringBuilder.append(method.getName());
    for (Object obj : params) {
    stringBuilder.append(obj.toString());
    }
    return stringBuilder.toString();
    }
    };
    }

    /**
    * 设置RedisTemplate规则
    * @param redisConnectionFactory
    * @return
    */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory);
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

    //解决查询缓存转换异常的问题
    ObjectMapper om = new ObjectMapper();
    // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);

    //序列号key value
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

    redisTemplate.afterPropertiesSet();
    return redisTemplate;
    }

    /**
    * 设置CacheManager缓存规则
    * @param factory
    * @return
    */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

    //解决查询缓存转换异常的问题
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);

    // 配置序列化(解决乱码的问题),过期时间600秒
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofSeconds(600))
    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
    .disableCachingNullValues();

    RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
    .cacheDefaults(config)
    .build();
    return cacheManager;
    }
    }
  3. 在application.properties中添加redis配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #redis服务器地址
    spring.redis.host=192.168.134.134
    #redis服务器连接端口
    spring.redis.port=6379
    #redis数据库索引(默认为0)
    spring.redis.database=0
    #连接超时时间(毫秒)
    spring.redis.timeout=1800000
    #连接池最大连接数(使用负值表示没有限制)
    spring.redis.lettuce.pool.max-active=20
    #最大阻塞等待时间(使用负值表示没有限制)
    spring.redis.lettuce.pool.max-wait=-1
    #连接池中最大空闲连接
    spring.redis.lettcue.pool.max-idle=5
    #连接池中最小空闲连接
    spring.redis.lettuce.pool.min-idle=0

3. SpringCache常用缓存标签

  1. 缓存@Cacheable

    根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。

    查看源码,其属性值如下:

    value:缓存名,必填,它指定了你的缓存放在哪块命名空间

    cacheNames:和value差不多,二选一即可

    key:可选属性,可以使用SpEl标签自定义缓存的key

  2. 缓存@CachePut

    使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。

    查看源码,属性值如下:

    value:缓存名,必填,它指定了你的缓存放在哪块命名空间

    cacheNames:和value差不多,二选一即可

    key:可选属性,可以使用SpEl标签自定义缓存的key

3. 缓存@CacheEvict

​ 使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上

​ 查看源码,属性值如下:

value:缓存名,必填,它指定了你的缓存放在哪块命名空间

cacheNames:和value差不多,二选一即可

key:可选属性,可以使用SpEl标签自定义缓存的key

allEntries:是否清空所有缓存,默认为false

beforeInvocation:是否在方法执行前就清空,默认为false

4. 数据字典的应用

在DictServiceImpl中加入注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//根据数据id查询子数据列表
@Override
@Cacheable(value = "dict",keyGenerator = "keyGenerator")
public List<Dict> findChildData(Long id) {
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("parent_id",id);
//ServiceImpl中已经帮忙注入了,所以直接调用就行
List<Dict> dictList = baseMapper.selectList(wrapper);
//向list集合每个dict对象中设置hasChildren
for (Dict dict : dictList) {
Long dictId = dict.getId();
boolean hasChildren = this.hasChildren(dictId);
dict.setHasChildren(hasChildren);
}
return dictList;
}

启动项目查询后,进入redis就可以看到查询的所有数据已经被存入redis中了。

5. 配置nginx

  1. 下载nginx(windows版本,因为后面会用网关代替nginx)

  2. 配置nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    server {
    listen 9001;
    server_name localhost;
    location ~ /hosp/ {
    proxy_pass http://localhost:8201;
    }
    location ~ /cmn/ {
    proxy_pass http://localhost:8202;
    }
    }
  3. 去dev.env.js中修改BASE_API

  • 后续添加service模块自行添加nginx配置,不做说明

  • 后续我们将了Spring Cloud Gateway网关,将替代nginx网关


本站由 Cccccpg 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。