3.java项目-尚医通(3)

后端代码:https://github.com/wyj41/yygh_parent.git
前端代码:https://github.com/wyj41/yygh_html.git

数据字典

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

创建表
若在第一节中执行了sql脚本则已创建

#
# Database "yygh_cmn"
#

CREATE DATABASE IF NOT EXISTS `yygh_cmn` CHARACTER SET utf8;
USE `yygh_cmn`;

#
# Structure for table "dict"
#

CREATE TABLE `dict` (
  `id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'id',
  `parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '上级id',
  `name` varchar(100) NOT NULL DEFAULT '' COMMENT '名称',
  `value` bigint(20) DEFAULT NULL COMMENT '值',
  `dict_code` varchar(20) DEFAULT NULL COMMENT '编码',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint(3) NOT NULL DEFAULT '1' COMMENT '删除标记(0:不可用 1:可用)',
  PRIMARY KEY (`id`),
  KEY `idx_dict_code` (`dict_code`),
  KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='组织架构表';

导入数据
执行SQL脚本(链接:https://pan.baidu.com/s/1chuqCbqT7Ov72BHdoJ4xrQ 提取码:ycv7)

数据分析
parent_id:
上级id,通过id与parent_id构建上下级关系,例如:我们要获取所有行业数据,那么只需要查询parent_id=20000的数据
name:名称,例如:填写用户信息,我们要select标签选择民族,“汉族”就是数据字典的名称
value:值,例如:填写用户信息,我们要select标签选择民族,“1”(汉族的标识)就是数据字典的值
dict_code:编码,编码是我们自定义的,全局唯一,例如:我们要获取行业数据,我们可以通过parent_id获取,但是parent_id是不确定的,所以我们可以根据编码来获取行业数据

说明:系统中会使用省市区三级联动数据,该数据我们来自“国家统计局”官方数据,地址:http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2019/index.html

根据页面效果分析数据接口
数据字典是树形展示,由于数据众多,我们使用“树形数据与懒加载”的方式展现数据列表,其他就是对数据的新增、修改与删除操作,因此需要提供的接口如下:

  1. 根据上级id获取下级数据(构造树形数据),参考文档:https://element.eleme.cn/#/zh-CN/component/table,页面搜索:树形数据与懒加载
  2. 导入接口
  3. 导出接口

接下来我们封装服务器端数据接口,接口测试通过后再做页面渲染

1. 数据字典开发

1.1 准备工作

1. 搭建service_cmn模块
在service模块下新建service_cmn模块,具体参考第一节内容

2. 添加改配置文件application.yml

# 服务端口
server:
  port: 8202

# 服务器名
spring:
  application:
    name: service-hosp
  profiles:
    #环境设置:dev、test、prod
    active: dev
  # mysql数据库连接
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.224:3306/yygh_cmn?characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456
  #返回json的全局时间格式
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

3. 创建启动类

扫描二维码关注公众号,回复: 14651284 查看本文章
package com.myproject.yygh.cmn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {
    
    "com.myproject"})
public class ServiceCmnApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(ServiceCmnApplication.class,args);
    }
}

model模块有数据字典实体
在model模块查看实体:com.myproject.yygh.model.cmn.Dict

说明:hasChildren为树形组件所需字典,标识为数据库表不存在该字段

4. 添加数据字典mapper
添加com.myproject.yygh.cmn.mapper.DictMapper

package com.myproject.yygh.cmn.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.myproject.yygh.model.cmn.Dict;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface DictMapper extends BaseMapper<Dict> {
    
    
}

5. 添加config
添加com.myproject.yygh.cmn.config.CmnConfig

package com.myproject.yygh.cmn.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.myproject.yygh.cmn.mapper")
public class CmnConfig {
    
    
    //分页插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
    
    
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

6. 添加数据字典service
添加com.myproject.yygh.cmn.service.DictService

package com.myproject.yygh.cmn.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.myproject.yygh.model.cmn.Dict;

public interface DictService extends IService<Dict> {
    
    
}

添加com.myproject.yygh.cmn.service.impl.DictServiceImpl接口实现

package com.myproject.yygh.cmn.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.myproject.yygh.cmn.mapper.DictMapper;
import com.myproject.yygh.cmn.service.DictService;
import com.myproject.yygh.model.cmn.Dict;
import org.springframework.stereotype.Service;

@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {
    
    

}

7. 添加数据字典controller
添加com.myproject.yygh.cmn.controller.DictController

package com.myproject.yygh.cmn.controller;

import com.myproject.yygh.cmn.service.DictService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/admin/cmn/dict")
public class DictController {
    
    
    @Autowired
    private DictService dictService;
}

1.2 数据字典接口开发

  1. 在DictService接口中提供相应抽象方法
package com.myproject.yygh.cmn.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.myproject.yygh.model.cmn.Dict;

import java.util.List;

public interface DictService extends IService<Dict> {
    
    
    //根据数据id查询子数据列表
    List<Dict> findChildData(Long id);
}
  1. 在DictServiceImpl中实现方法
 //根据数据id查询子数据列表
@Override
public List<Dict> findChildData(Long id) {
    
    
    QueryWrapper<Dict> wrapper = new QueryWrapper<>();
    wrapper.eq("parent_id",id);
    List<Dict> dictList = baseMapper.selectList(wrapper);
    //向list集合每个dict对象中设置hashChildren
    for (Dict dict : dictList) {
    
    
        Long dictId = dict.getId();
        boolean isChild = this.isChildren(dictId);
        dict.setHasChildren(isChild);
    }
    return dictList;
}

//判断id下面是否有子节点
private boolean isChildren(Long id){
    
    
    QueryWrapper<Dict> wrapper = new QueryWrapper<>();
    wrapper.eq("parent_id",id);
    Long count = baseMapper.selectCount(wrapper);
    return count > 0;
}
  1. 在controller中提供访问接口方法
package com.myproject.yygh.cmn.controller;

import com.myproject.yygh.cmn.service.DictService;
import com.myproject.yygh.common.result.Result;
import com.myproject.yygh.model.cmn.Dict;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Api(value = "数据字典接口")
@RestController
@RequestMapping("/admin/cmn/dict")
public class DictController {
    
    
    @Autowired
    private DictService dictService;

    //根据数据id查询子数据列表
    @ApiOperation("根据数据id查询子数据列表")
    @GetMapping("findChildData/{id}")
    public Result findChildData(@PathVariable Long id){
    
    
        List<Dict> list = dictService.findChildData(id);
        return Result.ok(list);
    }
}
  1. 在http://localhost:8202/swagger-ui.html中测试方法

1.3 数据字典前端

1.3.1 添加路由

在src/router/index.js 文件添加路由

{
    
    
    path: '/cmn',
    component: Layout,
    redirect: '/cmn/list',
    name: '数据管理',
    alwaysShow: true,
    meta: {
    
     title: '数据管理', icon: 'el-icon-s-help' },
    children: [
      {
    
    
        path: 'list',
        name: '数据字典',
        component: () => import('@/views/dict/list'),
        meta: {
    
     title: '数据字典', icon: 'table' }
      }
    ]
},

1.3.2 定义api

创建src/api/dict.js

import request from '@/utils/request'

export default {
    
    
  //数据字典列表
  dictList(id){
    
    
    return request({
    
    
      url:`/admin/cmn/dict/findChildData/${
      
      id}`,
      method:'get'
    })
  }
}

1.3.3 方法调用

创建src/views/dict/list.vue

<script>
import dict from '@/api/dict'

export default {
  data(){
    return {
      list:[] //数据字典列表数组
    }
  },
  created() {
    this.getDictList(1)
  },
  methods:{
    getDictList(id){
      dict.dictList(id)
        .then(response => {
          this.list = response.data
        })
    },
    getChildrens(tree, treeNode, resolve){//查询下面层级内容
      dict.dictList(tree.id)
        .then(response => {
          resolve(response.data)
        })
    }
  }
}
</script>

1.3.4 表格渲染

<template>
  <div class="app-container">
    <el-table
      :data="list"
      style="width: 100%"
      row-key="id"
      border
      lazy
      :load="getChildrens"
      :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>
  </div>
</template>

1.3.5 测试效果

  1. 修改config/dev.env.js文件中BASE_API(后面会解决调用不同服务器的问题)
BASE_API: '"http://localhost:8202"'
  1. 在DictController中添加 @CrossOrigin 注解解决跨域问题
  2. 安装更新的element-ui
npm install [email protected]
  1. 启动,然后在浏览器中查看效果
    http://localhost:9528/#/cmn/list

1.4 补充知识——EasyExcle

EasyExcle简介
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便。
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
文档地址:https://alibaba-easyexcel.github.io/index.html
github地址:https://github.com/alibaba/easyexcel

导出示例
示例链接:https://alibaba-easyexcel.github.io/quickstart/write.html

导入示例
示例链接:https://alibaba-easyexcel.github.io/quickstart/read.html

EasyExcel集成

  1. 添加依赖
    在service_cmn模块的pom中添加
<dependencies>
    <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.1.1</version>
    </dependency>
</dependencies>
  1. 在service_cmn模块中创建com.myproject.easyexcel包做测试
  2. 创建UserData实体类
package com.myproject.easyexcel;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class UserData {
    
    
    @ExcelProperty(value = "用户编号",index = 0)
    private int uid;

    @ExcelProperty(value = "用户名称",index = 1)
    private String username;
}
  1. 测试写操作
    创建TestWrite类
package com.myproject.easyexcel;

import com.alibaba.excel.EasyExcel;

import java.util.ArrayList;
import java.util.List;

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("lucy"+i);
            list.add(data);
        }

        //设置excel文件路径和文件名称
        String fileName = "D:\\tmp\\easyecxel_test\\01.xlsx";

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

    }
}
  1. 测试读操作
    创建ExcelListener监听器
package com.myproject.easyexcel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;

import java.util.Map;

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 context) {
    
    
        System.out.println("表头信息:"+headMap);
    }

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

    }
}

创建TestRead类并测试读取操作

package com.myproject.easyexcel;

import com.alibaba.excel.EasyExcel;

public class TestRead {
    
    
    public static void main(String[] args) {
    
    
        //读取文件路径
        String fileName = "D:\\tmp\\easyecxel_test\\01.xlsx";

        //调用方法实现读取操作
        EasyExcel.read(fileName,UserData.class,new ExcelListener()).sheet().doRead();
    }
}

1.5 数据字典导出功能

1.5.1 导出接口封装

在model模块添加有导出实体
在model模块查看实体:com.myproject.yygh.vo.cmn.DictEeVo

1. 在service-cmn模块添加service方法
在DictService类添加接口

//导出数据字典接口
void exportDictData(HttpServletResponse response);

在DictServiceImpl类添加接口实现类

//导出数据字典接口
@Override
public void exportDictData(HttpServletResponse response) {
    
    
    try {
    
    
        //设置下载信息
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName = "dict";
        response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");
        //查询数据库
        List<Dict> dictList = baseMapper.selectList(null);
        //Dict --> DictEevo
        List<DictEeVo> dictVoList = new ArrayList<>();
        for (Dict dict : dictList) {
    
    
            DictEeVo dictEeVo = new DictEeVo();
            BeanUtils.copyProperties(dict,dictEeVo);
            dictVoList.add(dictEeVo);
        }

        //调用方法进行写操作
        EasyExcel.write(response.getOutputStream(), DictEeVo.class).sheet("dict")
                .doWrite(dictVoList);
    } catch (IOException e) {
    
    
        throw new RuntimeException(e);
    }
}

2. 在service-cmn模块添加controller方法

/导出数据字典接口
@GetMapping("exportData")
public void exportDict(HttpServletResponse response){
    
    
    dictService.exportDictData(response);
}

1.5.2 导出前端实现

yygh_page/src/views/dict/list.vue的table组件上方添加

<div class="el-toolbar">
  <div class="el-toolbar-body" style="justify-content: flex-start;">
    <a href="http://localhost:8202/admin/cmn/dict/exportData" target="_blank">
      <el-button type="text"><i class="fa fa-plus"/> 导出</el-button>
    </a>
  </div>
</div>

启动项目,查看效果

1.6 数据字典导入功能

1.6.1 导入接口封装

1. 创建回调监听器
com.myproject.yygh.cmn.listener.DictListener

package com.myproject.yygh.cmn.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.myproject.yygh.cmn.mapper.DictMapper;
import com.myproject.yygh.model.cmn.Dict;
import com.myproject.yygh.vo.cmn.DictEeVo;
import org.springframework.beans.BeanUtils;

public class DictListener extends AnalysisEventListener<DictEeVo> {
    
    

    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) {
    
    

    }
}

2. 在service-cmn模块添加service方法
DictService中添加

//导入数据字典
void importDictData(MultipartFile file);

DictServiceImpl中添加

//导入数据字典
@Override
public void importDictData(MultipartFile file) {
    
    
    try {
    
    
        EasyExcel.read(file.getInputStream(),DictEeVo.class,new DictListener(baseMapper)).sheet().doRead();
    } catch (IOException e) {
    
    
        throw new RuntimeException(e);
    }
}

3. 在service-cmn模块添加controller方法

//导入数据字典接口
@PostMapping("importData")
public void importDict(MultipartFile file){
    
    
    dictService.importDictData(file);
}

1.6.2 导入前端实现

yygh_page/src/views/dict/list.vue
1. 列表页面添加导入按钮

<div class="el-toolbar">
  <div class="el-toolbar-body" style="justify-content: flex-start;">
    <a href="http://localhost:8202/admin/cmn/dict/exportData" target="_blank">
      <el-button type="text"><i class="fa fa-plus"/> 导出</el-button>
    </a>
    <el-button type="text" @click="importData"><i class="fa fa-plus"/> 导入</el-button>
  </div>
</div>

说明:按钮位置与导出并列

2. 添加导入弹出层

<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>

3. 添加弹出可见参数

data(){
    
    
    return {
    
    
      list:[], //数据字典列表数组
      dialogImportVisible:false //设置弹框是否显示
    }
},

4. 添加方法

//导入数据字典
importData(){
    
    
  this.dialogImportVisible = true
},
//上传成功调用
onUploadSuccess(){
    
    
  //关闭弹框
  this.dialogImportVisible = false
  //刷新页面
  this.getDictList(1)
},

测试效果
1.手动创建需要上传的文件,如图所示
请添加图片描述

2.由于建表时设置“is_deleted”逻辑删除列01定义相反,默认值为1,因此手动修改默认值为0,并修改注释为“删除标记(0:可用 1:不可用)”

3.上传excel文件查看效果

1.8 Spring Cache + Redis 缓存数据

Spring Cache 是一个非常优秀的缓存组件。自Spring 3.1起,提供了类似于@Transactional注解事务的注解Cache支持,且提供了Cache抽象,方便切换各种底层Cache(如:redis)
使用Spring Cache的好处:

1,提供基本的Cache抽象,方便切换各种底层Cache;
2,通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;
3,提供事务回滚时也自动回滚缓存;
4,支持比较复杂的缓存逻辑;

debian10安装redis参考:https://blog.csdn.net/hutc_Alan/article/details/125861887

1.8.1 项目集成Spring Cache + Redis

因为缓存也是公共使用,所有的service模块都有可能使用缓存,所以我们把依赖与部分配置加在service-util模块,这样其他service模块都可以使用了

1. service-util添加依赖
在service-util模块的pom.xml添加依赖(已添加)

<!-- 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添加配置类
创建com.myproject.yygh.common.config.RedisConfig

package com.myproject.yygh.common.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;

@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 sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
    
    
                    sb.append(obj.toString());
                }
                return sb.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;
    }
}

说明:
@EnableCaching:标记注解 @EnableCaching,开启缓存,并配置Redis缓存管理器。@EnableCaching 注释触发后置处理器, 检查每一个Spring bean 的 public 方法是否存在缓存注解。如果找到这样的一个注释, 自动创建一个代理拦截方法调用和处理相应的缓存行为。

3. service-cmn添加redis配置
在application.yml中添加配置

spring:
    redis:
        host: 192.168.1.224
        port: 6379
        database: 0
        timeout: 1800000
        lettuce:
          pool:
            max-active: 20
            #最大阻塞等待时间(负数表示没限制)
            max-wait: -1
            max-idle: 5
            min-idle: 0

1.8.2 使用Spring Cache

常用缓存标签

  1. 缓存@Cacheable
    根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
    查看源码,属性值如下:

value:缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames:与 value 差不多,二选一即可
key:可选属性,可以使用 SpEL 标签自定义缓存的key

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

value:缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames:与 value 差不多,二选一即可
key:可选属性,可以使用 SpEL 标签自定义缓存的key

  1. 缓存@CacheEvict
    使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
    查看源码,属性值如下:

value:缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames:与 value 差不多,二选一即可
key:可选属性,可以使用 SpEL 标签自定义缓存的key
allEntries:是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存
beforeInvocation:是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存

数据字典应用
改造com.myproject.yygh.cmn.service.impl.DictServiceImpl类方法

//根据数据id查询子数据列表
@Cacheable(value = "dict",keyGenerator = "keyGenerator")
@Override
public List<Dict> findChildData(Long id) {
    
    
...
}

//导入数据字典
@CacheEvict(value = "dict",allEntries = true)
@Override
public void importDictData(MultipartFile file) {
    
    
...
}

浏览器访问:http://localhost:9528/#/cmn/list
然后在服务器中查看

root@TestNode4:~# redis-cli
127.0.0.1:6379> keys *
1) "dict::com.myproject.yygh.cmn.service.impl.DictServiceImplfindChildData1"

报redisconnectionexception的错误,解决方法:
方法一:禁用保护模式
服务器上修改redis配置并重启redis

vim /etc/redis/redis.conf

#找到 protected-mode yes
#将其改为
protected-mode no

#重启redis服务
systemctl restart redis.service

方法二:设置绑定地址或认证密码(未操作)

2. 配置nginx

由于我们后端有很多服务模块,每个模块都有对应的访问路径与端口,为了提供统一的api接口,所以使用nginx作为反向代理服务器;
反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址

1.下载安装nginx(window版)

2.配置nginx
打开conf/nginx.conf
将以下内容添加到http{}内

server {
        listen       9001;
        server_name  localhost;

	location ~ /hosp/ {           
	    proxy_pass http://localhost:8201;
	}
	location ~ /cmn/ {           
	    proxy_pass http://localhost:8202;
	}
}

3.在cmd中启动nginx
到nginx目录下

D:\tools\nginx>nginx.exe
  1. 调整/config/dev.env.js中的BASE_API
    BASE_API: ‘“http://localhost:9001”’

5.同时启动医院设置和数据字典两个服务,然后在页面上分别测试是否能够使用

说明:后续我们会使用Spring Cloud Gateway网关,将替代nginx网关

猜你喜欢

转载自blog.csdn.net/hutc_Alan/article/details/125881856