Day106.尚医通:数据字典列表、EasyExcel、数据字典导入导出、集成Redis缓存

目录

一、数据字典列表

1、后端接口

2、前端搭建

Nginx 反向代理

二、EasyExcel

1. 实现写操作测试实例

2. 实现读操作测试实例

三、数据字典导出

1、数据字典导出接口实现

2、前端页面

四、数据字典导入

1、数据字典导入接口实现

2、前端页面

五、数据字典添加Redis缓存

1、Redis 回顾,启动Redis

2、整合Redis


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. 写操作时,清空相关缓存。再次查询时同步

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值