尚医通项目57-65:数据字典的配置与应用

开始时间:2022-05-17
课程链接:【尚医通】

开发数据字典

把常用的诸如省份信息这种数据,开发成一个固定的数据字典

后端开发

先设计表
在这里插入图片描述
再添加数据
在这里插入图片描述
搭建service-cmn模块,和之前搭建service-hosp差不多
因为我们的service-cmn是service的儿子,所以pom文件只需要依赖到service就行,写的内容不多

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>service</artifactId>
        <groupId>com.bupt</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>service_cmn</artifactId>
</project>

配置application.properties文件
改下端口和mysql库

# 服务端口
server.port=8202
# 服务名
spring.application.name=service-cmn

# 环境设置:dev、test、prod
spring.profiles.active=dev

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/yygh_cmn?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=333333

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

#配置mapper xml文件的路径
#mybatis-plus.mapper-locations=classpath:com/atguigu/yygh/mapper/xml/*.xml
mybatis-plus.mapper-locations=classpath:com/bupt/yygh/mapper/xml/*.xml

写一个主启动类

package com.bupt.yygh.cmn;

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

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

分别写controller、service、以及mapper
先写mapper

package com.bupt.yygh.cmn.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bupt.yygh.model.cmn.Dict;

public interface DictMapper extends BaseMapper<Dict> {
}

注意,这里面引入了一个Dict实体类,是我们之前就写好放进去的

package com.bupt.yygh.model.cmn;

import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * Dict
 * </p>
 *
 * @author qy
 */
@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;

}

再写service的接口

package com.bupt.yygh.cmn.service;

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

import java.util.List;


public interface DictService extends IService<Dict> {
    //@Autowired不用写了,已经通过MP帮忙注册了
    List<Dict> findChlidData(Long id);
}

以及实现类,这个service主要实现对id是否有子节点以及设置其是否有子节点属性

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

import ...;

@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict> implements DictService {
    //@Autowired不用写了,已经通过MP帮忙注册了
    @Override
    public List<Dict> findChlidData(Long id) {
        //条件构造器
        QueryWrapper<Dict> wrapper = new QueryWrapper<>();
        wrapper.eq("parent_id", id);

        List<Dict> dictList = baseMapper.selectList(wrapper);
        //向list集合每个dict对象中设置hasChildren
        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);
        Integer count = baseMapper.selectCount(wrapper);
        // 0>0    1>0
        return count > 0;
    }
}

再补充controller

package com.bupt.yygh.cmn.controller;

import ...;

import java.util.List;

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

    //根据数据id查询子数据列表
    @ApiOperation(value = "根据数据id查询子数据列表")
    @GetMapping("findChildData/{id}")
    public Result findChildData(@PathVariable Long id) {
        List<Dict> list = dictService.findChlidData(id);
        return Result.ok(list);
    }
}

我们第一个功能,查询id就完成了,下面测试一下
通过服务地址 /swagger-ui.html 来访问
http://localhost:8202/swagger-ui.html
在这里插入图片描述
比如我们查id=10000
那么返回我们的数据就是所有parent_id=10000的
在这里插入图片描述

前端开发

前端开发也是那几个流程

添加路由

router-index

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

定义api

api dict.js

import request from '@/utils/request'

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

方法调用

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

表格渲染

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

注意一下,我们设置这一个功能,在后端配置的端口是8202
所以我们的config-dev.env.js里面要修改

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"http://localhost:8202"',
})

这样做其实并不科学,因为这样弄了,我的8201又没办法用了,后面会用nginx来解决这个问题

演示效果
在这里插入图片描述

EasyExcel

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
有点用Python读取Excel的感觉了

Excel写

创建一个类用于封装数据

package com.bupt.easyexcel;

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

@Data
public class UserData {
    @ExcelProperty("用户编号")
    private int uid;
    @ExcelProperty("用户名称")
    private String username;
}

调用方法来写入Excel

package com.bupt.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> mylist = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            UserData data = new UserData();
            data.setUid(i);
            data.setUsername("这是第" + i + "个用户");
            mylist.add(data);
        }
        System.out.println(mylist);
        String fileName = "E:\\编程学习\\医疗项目--尚医通\\Excel\\01.xlsx";
//        调用方法实现写操作
        EasyExcel.write(fileName, UserData.class).sheet("用户信息").doWrite(mylist);
    }
}

Excel读

读的话需要再写一个监听器

package com.bupt.easyexcel;

import com.alibaba.excel.EasyExcel;

public class TestRead {
    public static void main(String[] args) {
        String fileName = "E:\\编程学习\\医疗项目--尚医通\\Excel\\01.xlsx";
        EasyExcel.read(fileName, UserData.class, new ExcelListener()).sheet().doRead();
    }
}

写一个Listener用于抓取数据

package com.bupt.easyexcel;

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

import java.util.Map;

public class ExcelListener extends AnalysisEventListener<UserData> {
    //从第二行开始,一行行读取数据
    @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) {

    }
    //    从第二行开始,一行一行读取Excel数据

}

在这里插入图片描述

测试成功
在这里插入图片描述
将写和读集成前端部分
在页面添加“导出”按钮

<template>
    <div class="app-container">
        <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" @click="exportData"><i class="fa fa-plus" /> 导出</el-button>

                </a>
            </div>
        </div>

新建方法,以文件形式导出

methods: {
        //导出数据字典的数据
        exportData() {
            //调用导出接口
            window.location.href = 'http://localhost:8202/admin/cmn/dict/exportData'
        },

测试没问题

导入集成前端
添加页面按钮,与导出并排

<el-button type="text" @click="importData"><i class="fa fa-plus" /> 导入</el-button>

添加导入弹出层
界面友好

<el-dialog title="导入" :visible.sync="dialogImportVisible" width="480px">
            <el-form label-position="right" label-width="170px">
                <el-form-item label="文件">
                    <!-- 多文件上传为false,上传成功的响应 -->
                    <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">只能上传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>

添加弹出可见模型

export default {
    data() {
        return {
            list: [], //数据字典列表数组
            dialogImportVisible: false
        }
    },

调用导入方法

//调用导入方法
        importData() {
            this.dialogImportVisible = true
        },

        onUploadSuccess(response, file) {
            //关闭弹框,刷新页面
            this.dialogImportVisible=false
            this.getDictList(1)
        },

看看效果
在这里插入图片描述
正常运行,能添加到数据库中

配置Redis

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

service-util添加配置类

创建com.bupt.yygh.common.config.RedisConfig

package com.bupt.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规则(Redis是key:value形式存值的)
     *
     * @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;
    }
}

service-cmn添加redis配置

这个host是我们linux连接redis的一个ip(好像是我的主机ip)
在这里插入图片描述

#配置redis
spring.redis.host=192.168.113.128
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

改造DictServiceImpl

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

@Override
    @Cacheable(value = "dict", keyGenerator = "keyGenerator")
    public List<Dict> findChlidData(Long id) {
        //条件构造器
        QueryWrapper<Dict> wrapper = new QueryWrapper<>();
        wrapper.eq("parent_id", id);

        List<Dict> dictList = baseMapper.selectList(wrapper);
        //向list集合每个dict对象中设置hasChildren
        for (Dict dict : dictList) {
            Long dictId = dict.getId();
            boolean isChild = this.isChildren(dictId);
            dict.setHasChildren(isChild);
        }
        return dictList;
    }

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

//清空缓存,装最新更新的数据
    @Override
    @CacheEvict(value = "dict", allEntries = true)
    public void importData(MultipartFile file) {
        //导入数据字典
        try {
            EasyExcel.read(file.getInputStream(), DictEeVo.class, new DictListener(baseMapper)).sheet().doRead();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

测试

后台启动 redis-server &
启动服务端

[root@localhost redis-5.0.2]# cd /
[root@localhost /]# ls
1    boot  etc   lib    media  opt   root  sbin  sys  usr
bin  dev   home  lib64  mnt    proc  run   srv   tmp  var
[root@localhost /]# cd /usr
[root@localhost usr]# cd local
[root@localhost local]# cd bin
[root@localhost bin]# ls
redis-benchmark  redis-check-rdb  redis-sentinel
redis-check-aof  redis-cli        redis-server
[root@localhost bin]# redis-server &

启动客户端

[root@localhost /]# cd opt/redis-5.0.2
[root@localhost redis-5.0.2]# redis-cli

注意使用Redis的时候要关闭防火墙,并且将安全模式关掉
关闭安全模式

127.0.0.1:6379> CONFIG SET protected-mode no
OK

关闭防火墙

[root@localhost bin]# systemctl stop firewalld
[root@localhost bin]# systemctl status firewalld

此时刷新我们的前端页面,就可以读取到数据了

127.0.0.1:6379> keys *
1) "dict::com.bupt.yygh.cmn.service.impl.DictServiceImplfindChlidData1"

在这里插入图片描述

配置nginx

在这里插入图片描述

配置nginx
server {
        listen       9001;
        server_name  localhost;

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

这样就可以统一使用两个端口了
那么我们在前端就得略作修改
在config-dev.en.js中
将端口号改为9001

注意,下面的方式是linux里面的启动
启动nginx
在这个目录下

[root@localhost redis-5.0.2]# cd /usr/local/nginx/sbin
[root@localhost sbin]# ./nginx

键入命令

[root@localhost sbin]# ./nginx
[root@localhost sbin]# ps -ef |grep nginx

我们现在用的是windows版本的启动
因为配置文件配置的是Windows版的
在这里插入图片描述
双击nginx.exe
然后通过这个文件夹,进入cmd窗口
可以查看是否正常启动,参考博客
查看Windows任务管理器下Nginx的进程命令:

tasklist /fi "imagename eq nginx.exe"      

在这里插入图片描述
关闭nginx
使用taskkill停止或关闭nginx:

taskkill /f /t /im nginx.exe
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"http://localhost:9001"',
  // BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
})

在idea里面同时启动两个springboot服务
此时两个应用都可以使用了
我的电脑已经快顶不住了
在这里插入图片描述
结束时间:2022-05-20

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值