目录
1、是什么
数据字典就是管理系统常用的分类数据或者一些固定数据,例如:省市区三级联动数据、民族数据、行业数据、学历数据等,由于该系统大量使用这种数据,所以我们要做一个数据管理方便管理系统数据,一般系统基本都会做数据管理。
数据字典就是系统中数据展示文字和编码的映射关系
2、需求分析
数据展示-树形表展示
表结构
数据分析
*国标数据查询
*自定义数据查询
搭建项目模块 -- 8002 service_cmn (省略)
一、数据字典列表
1、后端接口
根据element组件要求,返回列表数据必须包含hasChildren字典,如图:
https://element.eleme.cn/#/zh-CN/component/table
在model模块查看实体:com.atguigu.yygh.model.cmn.Dict,包含自定义字段 hasChildren
@Data
@ApiModel(description = "数据字典")
@TableName("dict")
public class Dict {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
private Long id;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("create_time")
private Date createTime;
@ApiModelProperty(value = "更新时间")
@TableField("update_time")
private Date updateTime;
@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")
@TableLogic
@TableField("is_deleted")
private Integer isDeleted;
@ApiModelProperty(value = "其他参数")
@TableField(exist = false)
private Map<String,Object> param = new HashMap<>();
@ApiModelProperty(value = "上级id")
@TableField("parent_id")
private Long parentId;
@ApiModelProperty(value = "名称")
@TableField("name")
private String name;
@ApiModelProperty(value = "值")
@TableField("value")
private String value;
@ApiModelProperty(value = "编码")
@TableField("dict_code")
private String dictCode;
@ApiModelProperty(value = "是否包含子节点")
@TableField(exist = false)
private boolean hasChildren;
}
1. 添加Config 配置类,mapper扫描
@Configuration
@EnableTransactionManagement
@MapperScan("com.atguigu.yygh.cmn.mapper")
public class CmnConfig {
}
2. controller
@Api(description = "数据字典接口")
@RestController
@RequestMapping("/admin/cmn/dict")
@CrossOrigin
public class DictController {
@Autowired
private DictService dictService;
//根据数据id查询子数据列表
@ApiOperation(value = "根据数据id查询子数据列表")
@GetMapping("findChildData/{id}")
public R findChildData(@PathVariable Long id){
List<Dict> list = dictService.findChildData(id);
return R.ok().data("list",list);
}
}
3. service
public interface DictService extends IService<Dict> {
//根据数据id查询子数据列表
List<Dict> findChildData(Long id);
}
@Service
public class DictServiceImpl
extends ServiceImpl<DictMapper, Dict>
implements DictService {
//根据数据id查询子数据列表
@Override
public List<Dict> findChildData(Long id) {
//1.根据父id查询子级别数据集合
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("parent_id",id);
List<Dict> dictList = baseMapper.selectList(wrapper);
//2.遍历查询是否有子数据
for (Dict dict : dictList) {
boolean hasChildren = this.isChildren(dict.getId());
dict.setHasChildren(hasChildren);
}
return dictList;
}
//查询是否有子数据,方法提取
private boolean isChildren(Long id) {
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("parent_id",id);
Integer count = baseMapper.selectCount(wrapper);
return count > 0;
}
}
4. mapper
public interface DictMapper extends BaseMapper<Dict> {
}
2、前端搭建
1、路由、创建对应页面
{
path: '/cmn',
component: Layout,
redirect: '/cmn/list',
name: '数据管理',
alwaysShow: true, //总是展示一级菜单
meta: { title: '数据管理', icon: 'example' },
children: [
{
path: 'list',
name: '数据字典',
component: () => import('@/views/yygh/dict/list'),
meta: { title: '数据字典', icon: 'table' }
}
]
},
2、api接口
import request from '@/utils/request'
//提取请求路径
const api_name = '/admin/cmn/dict'
export default{
//根据数据id查询子数据列表
findChildData(id){
return request({
url: `${api_name}/findChildData/${id}`,//插值表达式
method: 'get'
})
},
}
3、添加页面元素
lazy 只加载一级节点 ,通过 :load="load" 方法加载子节点数据
<template>
<div class="app-container">
<!-- lazy 只加载一级节点 :load="load" -->
<el-table :data="list" style="width: 100%" row-key="id" border lazy :load="load"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column prop="name" label="名称" width="150">
</el-table-column>
<el-table-column prop="dictCode" label="编码" width="150">
</el-table-column>
<el-table-column prop="value" label="值" width="150">
</el-table-column>
<el-table-column prop="createTime" label="创建时间">
</el-table-column>
</el-table>
</div>
</template>
4、实现js
<script>
import dictApi from "@/api/yygh/dict"
export default{
data() {
return {
list: [] //字典数据集合
}
},
created() {
this.getDate(1)
},
methods: {
//初始化查询方法
getDate(id){
dictApi.findChildData(id).then(response=>{
this.list = response.data.list
})
},
//树形节点加载方法,tree为节点对象,通过resolve()加载数据
load(tree, treeNode, resolve){
dictApi.findChildData(tree.id).then(response=>{
//参考官方文档
resolve(response.data.list)
})
}
}
}
</script>
5、测试、报错
分析1、跨域问题,不排除
分析2、请求地址错误,需要Nginx反向代理
Nginx 反向代理
反向代理、负载均衡、动静分离
1、安装Windows版,解压即可。启动
2、修改配置文件
server {
listen 9001;
server_name localhost;
location ~ /hosp/ {
proxy_pass http://localhost:8201;
}
location ~ /cmn/ {
proxy_pass http://localhost:8202;
}
}
3、重启nginx,访问测试
nginx目录下cmd 热加载,nginx.exe -s reload
http://localhost:9001/admin/cmn/dict/findChildData/1
4、前端修改dev配置文件,重新测试
二、EasyExcel
Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。面对高访问高并发,一定会OOM或者JVM频繁的full gc (重jc)。
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
文档地址:https://alibaba-easyexcel.github.io/index.html
github地址:https://github.com/alibaba/easyexcel
1. 实现写操作测试实例
1. cmd项目,导入依赖
<!--EasyExcel-->
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
2. 实体对象
@Data
public class Stu {
//设置表头名称
@ExcelProperty("学生编号")
private int sno;
//设置表头名称
@ExcelProperty("学生姓名")
private String sname;
}
3. 测试类
public class WriteTest {
public static void main(String[] args) {
String fileName = "E:\\test.xlsx";
EasyExcel.write(fileName,Stu.class)
.sheet("学生信息")
.doWrite(data());
}
//循环设置要添加的数据,最终封装到list集合中
private static List<Stu> data() {
List<Stu> list = new ArrayList<Stu>();
for (int i = 0; i < 10; i++) {
Stu data = new Stu();
data.setSno(i);
data.setSname("张三"+i);
list.add(data);
}
return list;
}
}
2. 实现读操作测试实例
1. 改造实体类,指定对应关系
@Data
public class Stu {
//设置表头名称,指定映射关系(第0行)
@ExcelProperty(value = "学生编号",index = 0)
private int sno;
//设置表头名称,指定映射关系(第1行)
@ExcelProperty(value = "学生姓名",index = 1)
private String sname;
}
2. 创建监听器
public class ExcelListener extends AnalysisEventListener<Stu> {
//一行一行去读取excle内容
@Override
public void invoke(Stu stu, AnalysisContext analysisContext) {
System.out.println("stu = " + stu);
}
//读取excel表头信息
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头信息:"+headMap);
}
//读取完成后执行
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
3. 测试
public class ReadTest {
public static void main(String[] args) {
String fileName = "E:\\test.xlsx";
EasyExcel.read(fileName,Stu.class,new ExcelListener())
.sheet()
.doRead();
}
}
三、数据字典导出
1、数据字典导出接口实现
1. 添加依赖
<!--EasyExcel-->
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
2. 确认vo
@Data
public class DictEeVo {
@ExcelProperty(value = "id" ,index = 0)
private Long id;
@ExcelProperty(value = "上级id" ,index = 1)
private Long parentId;
@ExcelProperty(value = "名称" ,index = 2)
private String name;
@ExcelProperty(value = "值" ,index = 3)
private String value;
@ExcelProperty(value = "编码" ,index = 4)
private String dictCode;
}
3. 分析接口
参数:response (可以获取输出流)
返回值:无
4. 实现controller
//导出字典数据
@Override
public void exportData(HttpServletResponse response) {
try {
//1.设置response的基本数据
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("数据字典", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");
//2.查询所有字典数据
List<Dict> dictList = baseMapper.selectList(null);
//3.转换字典数据类型
List<DictEeVo> dictEeVoList = new ArrayList<>();
for (Dict dict : dictList) {
DictEeVo dictEeVo = new DictEeVo();
//spring提供的工具类,对象拷贝
BeanUtils.copyProperties(dict,dictEeVo);
dictEeVoList.add(dictEeVo);
}
//4,使用工具导出
EasyExcel.write(response.getOutputStream(), DictEeVo.class)
.sheet("数据字典")
.doWrite(dictEeVoList);
} catch (IOException e) {
e.printStackTrace();
}
}
2、前端页面
1. 确认入口
2. 添加页面元素
<div class="el-toolbar">
<div class="el-toolbar-body" style="justify-content: flex-start;">
<el-button type="text" @click="exportData"><i class="fa fa-plus"/> 导出</el-button>
</div>
</div>
3. js实现
//导出数据
exportData(){
//window.open("http://localhost:8202/admin/cmn/dict/exportData")
//照葫芦画瓢。从配置文件读取
window.open(`${process.env.VUE_APP_BASE_API}admin/cmn/dict/exportData`)
}
4. 测试
四、数据字典导入
1、数据字典导入接口实现
1. 分析接口
参数:file (文件)
返回值:R.ok()
2. controller 实现
@ApiOperation(value = "导入字典数据")
@PostMapping("importData")
public R importData(MultipartFile file){
dictService.importData(file);
return R.ok();
}
3. 实现监听器 DictListener
@Component //
public class DictListener extends AnalysisEventListener<DictEeVo> {
@Autowired
private DictMapper dictMapper;
@Override
public void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {
//转换类型
Dict dict = new Dict();
BeanUtils.copyProperties(dictEeVo,dict);
//设置默认值
dict.setIsDeleted(0);
dictMapper.insert(dict);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
4. 实现service
@Autowired
DictListener dictListener;
//导入字典数据
@Override
public void importData(MultipartFile file) {
try {
InputStream inputStream = file.getInputStream();
EasyExcel.read(inputStream,DictEeVo.class,dictListener)
.sheet()
.doRead();
} catch (IOException e) {
e.printStackTrace();
throw new YyghException(20001,"导入数据失败");
}
}
5. 测试
2、前端页面
1. 确认入口
2. 添加页面元素
<div class="el-toolbar">
<div class="el-toolbar-body" style="justify-content: flex-start;">
<el-button type="text" @click="exportData"><i class="fa fa-plus" />
导出</el-button>
<el-button type="text" @click="importData"><i class="fa fa-plus" />
导入</el-button>
</div>
</div>
2. 添加对话框、文件上传组件
ajax无法传递文件,只能通过上传组件,直接提交请求,:action 传入请求地址
<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="BASE_API" class="upload-demo">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传xls文件,且不超过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>
3. js实现
data() {
return {
list: [], //字典数据集合
dialogImportVisible: false, //对话框,默认不显示
BASE_API: `${process.env.VUE_APP_BASE_API}admin/cmn/dict/importData` //请求地址
}
},
created() {
this.getDate(1)
},
methods: {
....
//打开导入对话框
importData(){
this.dialogImportVisible = true
},
//上传成功后的操作
onUploadSuccess(){
//1.关闭对话框
this.dialogImportVisible = false
//2.上传成功提示
this.$message({
type: "success",
message: "上传成功!"
})
//3.刷新表格
this.getDate(1)
}
}
4. 测试
五、数据字典添加Redis缓存
1、Redis 回顾,启动Redis
Redis 与 Memcache区别:
1、Memcache 数据只在缓存中,Redis 可以进行持久化操作 (RDB、AOF)
2、Memcache 只支持字符串类型,Redis多数据类型(string、list、set、hash、zset...)
3、命令机制:多线程加锁。Redis 单线程 + 多路io复用
Redis的特点:
原子性、单线程+多路io复用
启动
(1)虚拟机
(2)安装redis
(3)确认redis配置
(4)关闭虚拟机防火墙服务
(5)启动redis
2、整合Redis
1. common_uilts模块添加依赖
<dependencies>
<!-- 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>
</dependencies>
2. 添加配置类
(建议关闭自动导包?)
@Configuration
@EnableCaching //开启缓存
public class RedisConfig {
/**
* 设置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. service_cmn 配置文件 添加redis配置
#redis
spring.redis.host=192.168.86.86
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
4. redis 缓存原理
5. 添加缓存,service方法 添加@Cacheable 注解即可
注意:redis字符串key 为 value+key
//根据数据id查询子数据列表
//redis k:v k=dict::selectIndexList v=List<Dict>
@Cacheable(value = "dict", key = "'selectIndexList'")
@Override
public List<Dict> findChildData(Long id) {
属性/方法名 | 解释 |
value | 缓存名,必填,它指定了你的缓存存放在哪块命名空间 |
cacheNames | 与 value 差不多,二选一即可 |
key | 可选属性,可以使用 SpEL 标签自定义缓存的key |
第一次:查询数据库,添加缓存
出现问题:一级数据可以展现,二级数据没有展现
原因:查询一级、二级时,查询的是同一个方法,redis key是相同的,但缓存中没有二级数据
解决:key值加入参数 id
同步缓存:
方案1. 加锁、效率低
方案2. 写操作时,清空相关缓存。再次查询时同步