【数据字典详细步骤】(内附源码)

第01章-nacos和gateway的引入

1、引入nacos

1.1、启动nacos服务

资料:资料>数据字典微服务>nacos-server-1.4.2.zip

将资料中的nacos压缩包解压到非中文目录下,然后执行以下命令,单机启动nacos

startup.cmd -m standalone

访问:http://localhost:8848/nacos

用户名密码:nacos/nacos

1.2、引入依赖

service的pom.xml中配置Nacos客户端依赖

<dependencies>
    <!--服务注册-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

1.3、添加服务配置

配置application-dev.yml,在每个微服务模块中添加注册Nacos服务的配置信息

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

1.4、启动微服务

启动微服务,并查看nacos的服务列表

image-20230209165348351

2、引入微服务网关

2.1、创建微服务网关

image-20230315151724137

2.2、pom.xml引入依赖

在server-gateway中引入依赖

<dependencies>
    <!-- 网关 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- 服务注册 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

2.3、创建启动类

package com.atguigu.syt.gateway;

@SpringBootApplication
public class ServerGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerGatewayApplication.class, args);
    }
}

2.4、配置路由转发

在server-gateway模块中resources目录下创建文件

application.yml

spring:
  application:
    name: server-gateway
  profiles:
    active: dev

application-dev.yml

server:
  port: 8200
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: service-system
          predicates: Path=/*/system/**
          uri: lb://service-system
        - id: service-hosp
          predicates: Path=/*/hosp/**
          uri: lb://service-hosp
        - id: service-cmn
          predicates: Path=/*/cmn/**
          uri: lb://service-cmn
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

3、配置前端环境

3.1、修改代理配置

更改文件:vue.config.js,将target改到网关地址

proxy: {
  '/dev-api': { // 匹配所有以 '/dev-api'开头的请求路径
    target: 'http://localhost:8200',
    changeOrigin: true, // 支持跨域
    pathRewrite: { // 重写路径: 去掉路径中开头的'/dev-api'
      '^/dev-api': ''
    }
  }
}

3.2、重启程序

重启后端程序测试

第02章-数据字典列表

页面预览

image-20230210203119144

1、后端接口

1.1、创建数据库

资料:资料>数据字典微服务>guigu_syt_cmn.sql

1.2、创建service-cmn微服务

image-20230315153520921

1.3、添加依赖

在service-cmn中添加依赖:

<dependencies>
    <!--实体-->
    <dependency>
        <groupId>com.atguigu</groupId>
        <artifactId>model</artifactId>
        <version>1.0</version>
    </dependency>

    <!--服务通用配置-->
    <dependency>
        <groupId>com.atguigu</groupId>
        <artifactId>service-util</artifactId>
        <version>1.0</version>
    </dependency>

    <!--自定义安全模块-->
    <dependency>
        <groupId>com.atguigu</groupId>
        <artifactId>spring-security</artifactId>
        <version>1.0</version>
    </dependency>

    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!-- 单元测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

1.4、使用代码生成器

找到service-util模块中的代码生成器,修改moduleName为cmn,并执行,然后删除entity包,相关类中引入model模块中的类

1.5、创建配置文件

在server-cmn模块中resources目录下创建文件

application.yml

spring:
  application:
    name: service-cmn
  profiles:
    active: dev,redis

application-dev.yml

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:com/atguigu/syt/cmn/mapper/xml/*.xml
server:
  port: 8202
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    password: 123456
    url: jdbc:mysql://localhost:3306/guigu_syt_cmn?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
    username: root
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    
logging:
  level:
    root: info
  file:
    path: cmn

1.6、创建启动类

package com.atguigu.syt.cmn;

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

1.7、Controller

package com.atguigu.syt.cmn.controller.admin;

@Api(tags = "数据字典")
@RestController
@RequestMapping("/admin/cmn/dict")
public class AdminDictController {

    @Resource
    private DictService dictService;

    @ApiOperation(value = "获取全部数据字典")
    @GetMapping(value = "/findAllDictList")
    public Result<List<DictTypeVo>> findAllDictList() {
        List<DictTypeVo> list = dictService.findAllDictList();
        return Result.ok(list);
    }
}

1.8、Service

接口:DictService

/**
     * 获取数据字典嵌套列表
     * @return
     */
List<DictTypeVo> findAllDictList();

实现:DictServiceImpl

@Resource
private DictTypeService dictTypeService;

@Override
public List<DictTypeVo> findAllDictList() {

    //获取数据字典类型列表
    List<DictType> dictTypeList = dictTypeService.list();
    //获取数据字典列表
    List<Dict> dictList = this.list();

    List<DictTypeVo> dictTypeVoList = new ArrayList<>();
    //遍历数据字典类型列表
    dictTypeList.forEach(dictType -> {

        DictTypeVo dictTypeVo = new DictTypeVo();
        dictTypeVo.setId("parent-" +dictType.getId());
        dictTypeVo.setName(dictType.getName());

        List<Dict> subDictList = 
            dictList.stream().filter(dict -> dict.getDictTypeId().longValue() == dictType.getId().longValue()).collect(Collectors.toList());

        List<DictVo> children = new ArrayList<>();
        subDictList.forEach(dict -> {
            DictVo dictVo = new DictVo();
            dictVo.setId("children-" + dict.getId());
            dictVo.setName(dict.getName());
            dictVo.setValue(dict.getValue());
            children.add(dictVo);
        });
        dictTypeVo.setChildren(children);
        dictTypeVoList.add(dictTypeVo);
    });

    return dictTypeVoList;
}

2、前端页面

2.1、创建vue组件

在src/views/syt文件夹下创建cmn/dict/list.vue文件

<template>
  <div class="app-container">
    数据字典列表
  </div>
</template>

2.2、添加路由

静态路由:修改router/index.js文件

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

动态路由:

添加菜单:

image-20230315160404935

添加菜单项:

image-20230315160803695

2.3、定义api接口

创建文件 src/api/syt/dict.js

import request from '@/utils/request'
const apiName = '/admin/cmn/dict'

export default {

  findAllDictList() {
    return request({
      url: `${apiName}/findAllDictList`,
      method: 'get'
    })
  }
}

2.4、list.vue页面

cmn/dict/list.vue

<template>
  <div class="app-container">
    <!-- 数据字典列表 -->
    <el-table
      v-loading="listLoading"
      :data="list"
      element-loading-text="数据加载中"
      row-key="id"
      border
    >
      <el-table-column label="名称" prop="name" />
      <el-table-column label="ID" prop="id" />
      <el-table-column label="值" prop="value" />
    </el-table>
  </div>
</template>
<script>
import dictApi from '@/api/syt/dict'

export default {
  // 定义数据
  data() {
    return {
      listLoading: true, // 是否显示loading信息
      list: [],
    }
  },

  // 当页面加载时获取数据
  created() {
    this.fetchData()
  },

  methods: {
    // 调用api层获取数据库中的数据
    fetchData() {
      dictApi.findAllDictList().then((response) => {
        this.list = response.data
        this.listLoading = false
      })
    },
  },
}
</script>

第03章-地区列表

页面预览

image-20230210203214884

1、后端接口

1.1、Controller

package com.atguigu.syt.cmn.controller.admin;

@Api(tags = "地区")
@RestController
@RequestMapping("/admin/cmn/region")
public class AdminRegionController {

    @Resource
    private RegionService regionService;

    @ApiOperation(value = "根据上级code获取子节点数据列表")
    @ApiImplicitParam(name = "parentCode", value = "上级节点code", required = true)
    @GetMapping(value = "/findRegionListByParentCode/{parentCode}")
    public Result<List<Region>> findRegionListByParentCode(
            @PathVariable String parentCode) {
        List<Region> list = regionService.findRegionListByParentCode(parentCode);
        return Result.ok(list);
    }
}

1.2、Service

接口:RegionService

/**
     * 根据上级地区code获取地区列表
     * @param parentCode
     * @return
     */
List<Region> findRegionListByParentCode(String parentCode);

实现:RegionServiceImpl

@Override
public List<Region> findRegionListByParentCode(String parentCode) {

    LambdaQueryWrapper<Region> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(Region::getParentCode, parentCode);

    List<Region> regionList = baseMapper.selectList(queryWrapper);
    regionList.forEach(region -> {
        boolean isHasChildren = region.getLevel().intValue() < 3 ? true : false;
        region.setHasChildren(isHasChildren);
    });
    return regionList;
}

2、前端页面

2.1、创建vue组件

在src/views/syt文件夹下创建cmn/region/list.vue文件

<template>
  <div class="app-container">
    地区列表
  </div>
</template>

2.2、添加路由

静态路由:修改router/index.js文件,在基础数据下添加子节点

{
  path: 'region/list',
  name: '地区管理',
  component: () => import('@/views/syt/cmn/region/list'),
  meta: { title: '地区管理', icon: 'table' }
}

动态路由:

添加菜单项目:

image-20230315163044657

2.3、定义api接口

创建文件 src/api/syt/region.js

import request from '@/utils/request'
const apiName = '/admin/cmn/region'

export default {
  findRegionListByParentCode(parentCode) {
    return request({
      url: `${apiName}/findRegionListByParentCode/${parentCode}`,
      method: 'get'
    })
  }
}

2.4、list.vue页面

cmn/region/list.vue

<template>
  <div class="app-container">

    <el-table
      v-loading="listLoading"
      :data="list"
      element-loading-text="数据加载中"
      :load="getChildren"
      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
      style="width: 100%;margin-top: 10px;"
      row-key="id"
      border
      lazy>

      <el-table-column label="名称" align="left">
        <template slot-scope="scope">
          <span>{{ scope.row.name }}</span>
        </template>
      </el-table-column>
      <el-table-column label="地区编码" >
        <template slot-scope="{row}">
          {{ row.code }}
        </template>
      </el-table-column>
      <el-table-column label="地区级别" align="left">
        <template slot-scope="scope">
          <span>{{ scope.row.level }}</span>
        </template>
      </el-table-column>
    </el-table>

  </div>
</template>
<script>
import regionApi from '@/api/syt/region'

export default {

  // 定义数据
  data() {
    return {
      listLoading: true,  
      list: [],
    }
  },

  // 当页面加载时获取数据
  created() {
    this.fetchData()
  },

  methods: {
    // 调用api层获取数据库中的数据
    fetchData() {
      console.log('加载列表')
      regionApi.findRegionListByParentCode('0').then(response => {
        this.list = response.data
        console.log(this.list)
        this.listLoading = false
      })
    },

    getChildren(tree, treeNode, resolve) {
      debugger
      regionApi.findRegionListByParentCode(tree.code).then(response => {
        resolve(response.data)
      })
    },
  }
}
</script>

第04章-Redis

可以将地区列表的数据放入redis缓存,帮助提高页面的加载速度

1、使用缓存

1.1、RegionServiceImpl

@Resource
private RedisTemplate redisTemplate;

@Override
public List<Region> findRegionListByParentCode(String parentCode) {

    //先查询redis中是否存在dictList
    List<Region> regionList = null;
    try {
        regionList = (List<Region>)redisTemplate.opsForValue().get("regionList:" + parentCode);
        if(regionList != null){
            return regionList;
        }
    } catch (Exception e) {
        log.error("redis服务器异常:get regionList");
    }

    LambdaQueryWrapper<Region> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(Region::getParentCode, parentCode);

    regionList = baseMapper.selectList(queryWrapper);
    regionList.forEach(region -> {
        boolean isHasChildren = region.getLevel().intValue() < 3 ? true : false;
        region.setHasChildren(isHasChildren);
    });

    try {
        //将数据存入redis
        redisTemplate.opsForValue().set("regionList:" + parentCode, regionList, 5, TimeUnit.MINUTES);
    } catch (Exception e) {
        log.error("redis服务异常:set regionList");
    }
    return regionList;
}

1.2、测试

测试获取数据:

第一次查询从MySQL数据库获取,第二次查询从Redis获取

2、Spring Cache

2.1、简介

1、Spring Cache是Spring提供的一个缓存框架。

2、Spring Cache利用了AOP,实现了基于注解的缓存功能,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能,从而减少对代码的入侵。

3、由于市面上的缓存工具实在太多,SpringCache框架还提供了CacheManager接口,可以降低对各种缓存框架的耦合。

2.2、开启缓存

在service-util模块的RedisConfig类上添加注解

@EnableCaching

2.3、JSON序列化方案

RedisConfig

@Bean
public CacheManager cacheManager(LettuceConnectionFactory connectionFactory) {

    //定义序列化器
    GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();


    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        //过期时间600秒
        .entryTtl(Duration.ofSeconds(600))
        // 配置序列化
        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer));

    RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)
        .cacheDefaults(config)
        .build();
    return cacheManager;
}

3、Spring Cache 常用注解

3.1、@Cacheable

作用:在方法执行前查看是否有缓存对应的数据,如果有直接返回数据,如果没有,则调用方法获取数据返回,并缓存起来。

RegionServiceImpl

    /**
     * @Cacheable:
     * 在方法执行前查看是否有缓存对应的数据,
     * 如果有直接返回数据,如果没有,则调用方法获取数据返回,并缓存起来。
     * 
     * value:缓存的名字
     * key:缓存的key
     * unless:条件符合则不缓存,是对出参进行判断,出参用#result表示
     *
     * @param parentCode
     * @return
     */
@Override
@Cacheable(value = "regionList", key = "#parentCode", unless="#result.size() == 0")
public List<Region> findRegionListByParentCode(String parentCode) {

    //先查询redis中是否存在dictList
    LambdaQueryWrapper<Region> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(Region::getParentCode, parentCode);

    List<Region> regionList = baseMapper.selectList(queryWrapper);
    regionList.forEach(region -> {
        boolean isHasChildren = region.getLevel().intValue() < 3 ? true : false;
        region.setHasChildren(isHasChildren);
    });

    return regionList;
}

3.2、@CachePut

作用:将方法的返回值放到缓存中

接口:RegionService

/**
     * 测试 @CachePut
     * @param region
     * @return
     */
Region saveRegionWithCacheManager(Region region);

实现:RegionServiceImpl

/**
     * 作用:将方法的返回值放到缓存中
     * @param region
     * @return
     */
@Override
@CachePut(value = "regionTest", key = "#region.id")
public Region saveRegionWithCacheManager(Region region) {
    baseMapper.insert(region);
    return region;
}

测试:在service-cmn的test目录中创建测试用例RedisTest

package com.atguigu.syt.cmn.redis;

@SpringBootTest
public class RedisTest {

    @Resource
    private RegionService regionService;

    @Test
    public void testSaveRegionWithCacheManager() {
        Region region = new Region();
        region.setName("test");
        regionService.saveRegionWithCacheManager(region);
    }
}

3.3、@CacheEvict

作用:执行方法体,删除缓存

接口:RegionService

/**
     * 测试 @CacheEvict
     * @param id
     */
void deleteRegionWithCacheManager(Long id);

实现:RegionServiceImpl

/**
     * 作用:执行方法体,删除缓存
     * @param id
     */
@Override
@CacheEvict(value = "regionTest", key = "#id")
public void deleteRegionWithCacheManager(Long id) {
    baseMapper.deleteById(id);
}

测试:RedisTest

@Test
public void testDeleteRegionWithCacheManager() {
    regionService.deleteRegionWithCacheManager(3712L);//刚刚插入的测试数据的id
}

源码:https://gitee.com/dengyaojava/guigu-syt-parent

posted @ 2023-06-12 19:16  自律即自由-  阅读(100)  评论(0编辑  收藏  举报