尚医通-day07【医院管理详细步骤】(内附源码)

发布时间 2023-06-12 19:23:55作者: 自律即自由-

页面预览

列表页

image-20230226170144916

批量导入数据

为了方便测试,我们可以将更多的医院信息数据批量导入到系统中。将资料中的json数据和测试用例复制到项目中,然后执行测试用例即可

资料:资料>批量导入医院数据

image-20230316213027339

第01章-医院列表信息

1、医院列表

1.1、Controller

在service-hosp中创建AdminHospitalController

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

@Api(tags = "医院管理")
@RestController
@RequestMapping("/admin/hosp/hospital")
public class AdminHospitalController {

    //注入service
    @Resource
    private HospitalService hospitalService;

    @ApiOperation(value = "获取分页列表")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "page",value = "页码", required = true),
            @ApiImplicitParam(name = "limit",value = "每页记录数", required = true),
            @ApiImplicitParam(name = "hosname",value = "查询字符串")})
    @GetMapping("/{page}/{limit}")
    public Result<Page<Hospital>> pageList(
            @PathVariable Integer page, //路径
            @PathVariable Integer limit, //路径
            String hosname /*查询字符串*/) {
        //调用方法
        Page<Hospital> pageModel = hospitalService.selectPage(page, limit, hosname);
        return Result.ok(pageModel);
    }
}

1.2、Service

接口:HospitalService

/**
     * 根据医院名称分页查询医院列表
     * @param page
     * @param limit
     * @param hosname
     * @return
     */
Page<Hospital> selectPage(Integer page, Integer limit, String hosname);

实现:HospitalServiceImpl

@Override
public Page<Hospital> selectPage(Integer page, Integer limit, String hosname) {

    //设置排序规则
    Sort sort = Sort.by(Sort.Direction.ASC, "hoscode");
    //设置分页参数
    PageRequest pageRequest = PageRequest.of(page-1, limit, sort);

    //执行查询
    if(StringUtils.isEmpty(hosname)){
        return hospitalRepository.findAll(pageRequest);
    }else{
        return hospitalRepository.findByHosnameLike(hosname, pageRequest);
    }
}

1.3、Repository

/**
     * 根据医院名称查询医院分页列表
     * @param hosname
     * @return
     */
Page<Hospital> findByHosnameLike(String hosname, PageRequest pageRequest);

2、获取数据字典名称

2.1、Controller

在service-cmn中创建InnerDictController

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

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

    @Resource
    private DictService dictService;

    @ApiOperation(value = "获取数据字典名称")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "dictTypeId",value = "字典类型id", required = true),
            @ApiImplicitParam(name = "value",value = "字典值", required = true)})
    @GetMapping(value = "/getName/{dictTypeId}/{value}")
    public String getName(
            @PathVariable("dictTypeId") Long dictTypeId,
            @PathVariable("value") String value) {
        return dictService.getNameByDictTypeIdAndValue(dictTypeId, value);
    }
}

2.2、Service

接口:DictService

/**
     * 根据数据字典类别和字典值获取字典名称
     * @param dictTypeId
     * @param value
     * @return
     */
String getNameByDictTypeIdAndValue(Long dictTypeId, String value);

实现:DictServiceImpl

@Override
public String getNameByDictTypeIdAndValue(Long dictTypeId, String value) {

    LambdaQueryWrapper<Dict> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(Dict::getDictTypeId, dictTypeId);
    queryWrapper.eq(Dict::getValue, value);
    Dict dict = baseMapper.selectOne(queryWrapper);
    if (dict != null) {
        return dict.getName();
    }
    return "";
}

2.3、配置Swagger分组

在service-util的Knife4jConfig类中添加如下@Bean配置:

@Bean
public Docket docketInner() {
    //指定使用Swagger2规范
    Docket docket = new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(new ApiInfoBuilder()
                 .description("尚医通 APIs")
                 .description("本文档描述了尚医通微服务内部调用系统接口")
                 .contact("admin@atguigu.com")
                 .version("1.0")
                 .build())
        //分组名称
        .groupName("微服务内部调用")
        .select()
        .paths(PathSelectors.regex("/inner/.*"))
        .build();
    return docket;
}

3、获取地区名称

3.1、Controller

在service-cmn中创建InnerRegionController

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


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

    @Resource
    private RegionService regionService;

    @ApiOperation(value = "根据地区编码获取地区名称")
    @ApiImplicitParam(name = "code",value = "值", required = true)
    @GetMapping(value = "/getName/{code}")
    public String getName(@PathVariable("code") String code) {
        return regionService.getNameByCode(code);
    }
}

3.2、Service

接口:RegionService

/**
     * 根据地区编码获取地区名称
     * @param code
     * @return
     */
String getNameByCode(String code);

实现:RegionServiceImpl

@Override
public String getNameByCode(String code) {

    LambdaQueryWrapper<Region> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(Region::getCode, code);
    Region region = baseMapper.selectOne(queryWrapper);
    if (region != null) {
        return region.getName();
    }
    return "";
}

第02章-微服务远程调用

1、openfeign接口定义

1.1、创建service-client

在父工程guigu-syt-parent下面,选择 maven类型的模块,输入模块名称service-client,完成创建。

删除src目录。

1.2、引入依赖

<dependencies>
    <!-- 服务调用 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>

1.3、创建service-cmn-client模块

在service-client下面,选择 maven类型的模块,输入模块名称service-cmn-client,完成创建。

注意:parent选择service-client,删除Main.java

1.4、添加FeignClient接口

DictFeignClient

package com.atguigu.syt.cmn.client;

@FeignClient(value = "service-cmn")
public interface DictFeignClient {

    /**
     * 获取数据字典名称
     * @param dictTypeId
     * @param value
     * @return
     */
    @GetMapping(value = "/inner/cmn/dict/getName/{dictTypeId}/{value}")
    String getName(
            @PathVariable("dictTypeId") Long dictTypeId,
            @PathVariable("value") String value
    );

}

RegionFeignClient

package com.atguigu.syt.cmn.client;

@FeignClient(value = "service-cmn")
public interface RegionFeignClient {

    /**
     * 根据地区编码获取地区名称
     * @param code
     * @return
     */
    @GetMapping(value = "/inner/cmn/region/getName/{code}")
    String getName(@PathVariable("code") String code);

}

2、openfeign接口调用

2.1、service模块中引入依赖

<!-- 服务调用 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>service-cmn-client</artifactId>
    <version>1.0</version>
</dependency>

2.2、service-hosp启动类添加注解

@EnableFeignClients("com.atguigu.syt")

2.3、修改HospitalServiceImpl

添加FeignClient

@Resource
private DictFeignClient dictFeignClient;

@Resource
private RegionFeignClient regionFeignClient;

封装Hospital数据

/**
     * 封装Hospital数据
     * @param hospital
     * @return
     */
private Hospital packHospital(Hospital hospital) {
    String hostypeString = dictFeignClient.getName(DictTypeEnum.HOSTYPE.getDictTypeId(), hospital.getHostype());
    String provinceString = regionFeignClient.getName(hospital.getProvinceCode());
    String cityString = regionFeignClient.getName(hospital.getCityCode());
    if(provinceString.equals(cityString)) cityString = "";
    String districtString = regionFeignClient.getName(hospital.getDistrictCode());

    hospital.getParam().put("hostypeString", hostypeString);
    hospital.getParam().put("fullAddress", provinceString + cityString + districtString + hospital.getAddress());
    return hospital;
}

完善selectPage方法

@Override
public Page<Hospital> selectPage(Integer page, Integer limit, String hosname) {

    //设置排序规则
    Sort sort = Sort.by(Sort.Direction.ASC, "hoscode");
    //设置分页参数
    PageRequest pageRequest = PageRequest.of(page-1, limit, sort);

    //执行查询
    Page<Hospital> pages = null;
    if(StringUtils.isEmpty(hosname)){
        pages = hospitalRepository.findAll(pageRequest);
    }else{
        pages = hospitalRepository.findByHosnameLike(hosname, pageRequest);
    }

    pages.getContent().forEach(item -> {
        this.packHospital(item);
    });

    return pages;
}

2.4、配置覆盖注册

问题:默认情况下,一个项目中不能同时存在两个相同value值的@FeignClient,否则微服务无法启动

解决方案:

方法一:配置允许覆盖注册

#当遇到同样名字的时候,是否允许覆盖注册
spring.main.allow-bean-definition-overriding=true

方法二:在@FeignClient中添加contextId属性,设置不同的值

DictFeignClient:

@FeignClient(
    value = "service-cmn", 
    contextId = "dictFeignClient"
)
public interface DictFeignClient {

RegionFeignClient:

@FeignClient(
    value = "service-cmn", 
    contextId = "regionFeignClient"
)
public interface RegionFeignClient {

2.5、测试

注意:测试前需要修改spring-security的WebSecurityConfig类,配置授权校验排除inner路径

/**
     * 配置哪些请求不拦截
     * 排除swagger相关请求
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**","/inner/**","/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
    }

3、服务超时和服务降级

3.1、超时配置

如果微服务间的远程调用连接和响应时间过长,导致调用超时,会使整个业务流程选失败,我们可以在服务的消费者端配置如下内容,延长连接和响应的超时时间

feign:
  client:
    config:
      default:
        connect-timeout: 2000 #连接建立的超时时长,单位是ms,默认1s
        read-timeout: 2000 #处理请求的超时时间,单位是ms,默认为1s

3.2、引入服务降级依赖

如果服务生产者长时间没有响应,可以启用服务降级,使下游服务能够正常运行

在service模块中添加依赖

<!-- 熔断和降级 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

3.3、添加配置

在service-hosp模块中添加配置

feign:
  sentinel:
    enabled: true #开启Feign对Sentinel的支持

3.4、降级实现

DictDegradeFeignClient:

package com.atguigu.syt.cmn.client.impl;

@Component
public class DictDegradeFeignClient implements DictFeignClient {

    @Override
    public String getName(Long dictTypeId, String value) {
        return "数据获取失败";
    }
}

RegionDegradeFeignClient:

package com.atguigu.syt.cmn.client.impl;

@Component
public class RegionDegradeFeignClient implements RegionFeignClient {

    @Override
    public String getName(String code) {
        return "数据获取失败";
    }
}

3.5、配置降级实现

DictFeignClient:

@FeignClient(
        value = "service-cmn", 
        contextId = "dictFeignClient", 
        fallback = DictDegradeFeignClient.class
)
public interface DictFeignClient {

RegionFeignClient:

@FeignClient(
        value = "service-cmn", 
        contextId = "regionFeignClient", 
        fallback = RegionDegradeFeignClient.class
)
public interface RegionFeignClient {

第03章-医院状态

1、Controller

在AdminHospitalController添加方法

@ApiOperation(value = "更新上线状态")
@ApiImplicitParams({
    @ApiImplicitParam(name = "hoscode",value = "医院编码", required = true),
    @ApiImplicitParam(name = "status",value = "状态(0:未上线 1:已上线)", required = true)})
@GetMapping("/updateStatus/{hoscode}/{status}")
public Result updateStatus(@PathVariable("hoscode") String hoscode, @PathVariable("status") Integer status){
    hospitalService.updateStatus(hoscode, status);
    return Result.ok();
}

2、Service

接口:HospitalService

/**
     * 根据医院编码修改医院状态
     * @param hoscode
     * @param status
     */
void updateStatus(String hoscode, Integer status);

实现:HospitalServiceImpl

@Override
public void updateStatus(String hoscode, Integer status) {
    if(status.intValue() == 0 || status.intValue() == 1) {
        Hospital hospital = hospitalRepository.findByHoscode(hoscode);
        hospital.setStatus(status);
        hospitalRepository.save(hospital);
        return;
    }

    throw new GuiguException(ResultCodeEnum.PARAM_ERROR);
}

第04章-前端整合

1、路由

静态路由:

src/router/index.js:在“医院管理”下添加子节点

{
    path: 'hosp/list',
    name: 'hospList',
    component: () =>import('@/views/syt/hosp/list'),
    meta: { title: '医院列表', icon: 'el-icon-s-unfold' }
} 

动态路由:

image-20230317102606403

2、api

在src/api/syt目录下创建hosp.js文件

import request from '@/utils/request'

const apiName = '/admin/hosp/hospital'
export default {
  //医院列表
  getPageList(page, limit, hosname) {
    return request ({
      url: `${apiName}/${page}/${limit}`,
      method: 'get',
      params: {hosname}
    })
  },

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

3、组件渲染

创建 src/views/syt/hosp/list.vue

<template>
  <div class="app-container">
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item>
        <el-input v-model="hosname" placeholder="医院名称" />
      </el-form-item>
      <el-button type="primary" icon="el-icon-search" @click="fetchData()">
        查询
      </el-button>
      <el-button type="default" @click="resetData()">清空</el-button>
    </el-form>
    <!-- banner列表 -->
    <el-table
      v-loading="listLoading"
      :data="list"
      element-loading-text="数据加载中"
      border
      highlight-current-row
    >
      <el-table-column label="序号" width="60" align="center">
        <template slot-scope="scope">
          {{ (page - 1) * limit + scope.$index + 1 }}
        </template>
      </el-table-column>
      <el-table-column label="LOGO" width="80">
        <template slot-scope="scope">
          <img
            :src="'data:image/jpeg;base64,' + scope.row.logoData"
            width="50"
          />
        </template>
      </el-table-column>
      <el-table-column prop="hosname" label="医院名称" />
      <el-table-column prop="param.hostypeString" label="等级" width="90" />
      <el-table-column prop="param.fullAddress" label="详情地址" />
      <el-table-column label="状态" width="80">
        <template slot-scope="scope">
          {{ scope.row.status === 1 ? '已上线' : '未上线' }}
        </template>
      </el-table-column>
      <el-table-column label="操作" width="230" align="center">
        <template slot-scope="scope">
          <router-link
            :to="'/syt/hospset/hosp/show/' + scope.row.hoscode"
            style="margin-right: 5px"
          >
            <el-button type="primary" size="mini">查看</el-button>
          </router-link>
          <router-link
            :to="'/syt/hospset/hosp/schedule/' + scope.row.hoscode"
            style="margin-right: 5px"
          >
            <el-button type="success" size="mini">排班</el-button>
          </router-link>
          <el-button
            v-if="scope.row.status == 1"
            type="warning"
            size="mini"
            @click="updateStatus(scope.row.hoscode, 0)"
            >下线</el-button
          >
          <el-button
            v-else
            type="info"
            size="mini"
            @click="updateStatus(scope.row.hoscode, 1)"
            >上线</el-button
          >
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页组件 -->
    <el-pagination
      :current-page="page"
      :total="total"
      :page-size="limit"
      :page-sizes="[2, 5, 10]"
      style="padding: 30px 0; text-align: center"
      layout="total, sizes, prev, pager, next, jumper"
      @size-change="changePageSize"
      @current-change="changeCurrentPage"
    />
  </div>
</template>

<script>
import hosp from '@/api/syt/hosp'
export default {
  data() {
    return {
      listLoading: true, // 数据是否正在加载
      list: null, // 医院列表数据集合
      total: 0, // 数据库中的总记录数
      page: 1, // 默认页码
      limit: 10, // 每页记录数
      hosname: '', //查询表单:医院名称
    }
  },
  created() {
    //调用医院列表
    this.fetchData()
  },
  methods: {
    //医院列表
    fetchData() {
      console.log('加载列表')
      this.listLoading = true
      hosp.getPageList(this.page, this.limit, this.hosname).then((response) => {
        this.list = response.data.content
        this.total = response.data.totalElements
        this.listLoading = false
      })
    },

    // 每页记录数改变,size:回调参数,表示当前选中的“每页条数”
    changePageSize(size) {
      this.limit = size
      this.fetchData()
    },
    // 改变页码,page:回调参数,表示当前选中的“页码”
    changeCurrentPage(page) {
      this.page = page
      this.fetchData()
    },
    //清空查询表单
    resetData() {
      this.hosname = ''
      this.fetchData()
    },

    //修改状态
    updateStatus(hoscode, status) {
      hosp.updateStatus(hoscode, status).then((response ) => {
        this.$message.success(response.message),
        this.fetchData()
      })
    },
  },
}
</script>

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