目录
一、搭建Nacos服务
项目需求 需要翻译字段,模块间的调用
1. 启动Nacos
startup.cmd -m standalone
nacos1.4.0 之后的启动方式默认时cluster集群模式,如果没有进行集群配置,直接启动会报错。
可以用命令行startup.cmd -m standalone就能以单体方式启动服务。
如果觉得每次启动都是单体模式,就可以在startup.cmd中的配置信息set MODE="cluster"修改为set MODE=“standalone”,
于是每次启动就默认单体模式。
2. 确认项目依赖
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--服务注册-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3. 添加配置
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
4. 启动类添加注解
@SpringBootApplication
@ComponentScan(basePackages = {"com.atguigu"}) //扫描到其他项目的类
@EnableDiscoveryClient //注册中心
public class ServiceHospApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceHospApplication.class, args);
}
}
5. 测试
二、医院需求分析
- 一、下拉列表
- 实现二级联动下拉选择框
- 实现分页条件医院列表查询 (跨模块到 cmn 翻译字段)
- 更新上线状态
- 二、医院详情
- 根据医院编码查询医院基本信息、预约信息
- 跨模块到cmn翻译字段
三、医院列表功能(接口)
1、医院列表接口初步实现
1. 分析接口
参数:page、limit、HospitalQueryVo
返回值:R(page) (MongoDB用springData,mysql用苞米豆)
2. 实现 controller
@Api(description = "医院接口")
@RestController
@RequestMapping("/admin/hosp/hospital")
@CrossOrigin
public class HospitalController {
@Autowired
private HospitalService hospitalService;
@ApiOperation(value = "获取分页列表")
@GetMapping("{page}/{limit}")
public R index(@PathVariable Integer page,
@PathVariable Integer limit,
HospitalQueryVo hospitalQueryVo) {
Page<Hospital> pageModel = hospitalService.selectPage(page,limit,hospitalQueryVo);
return R.ok().data("pageModel",pageModel);
}
}
3. 实现 Service (暂时略过翻译字段)
//带条件带分页获取医院列表
@Override
public Page<Hospital> selectPage(Integer page, Integer limit, HospitalQueryVo hospitalQueryVo) {
//(1).创建分页对象
//1.1创建排序对象
Sort sort = Sort.by(Sort.Direction.DESC,"createTime");
//1.2创建分页对象
Pageable pageable = PageRequest.of(page-1,limit,sort);
//(2).创建查询条件模板
//2.1封装查询条件
Hospital hospital = new Hospital();
BeanUtils.copyProperties(hospitalQueryVo,hospital);
//2.2创建模板构造器
ExampleMatcher matcher = ExampleMatcher.matching()
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //模糊查询
.withIgnoreCase(true); //不区分大小写
//2.3创建模板
Example<Hospital> example = Example.of(hospital,matcher);
//(3).实现条件分页查询
Page<Hospital> pageModel = hospitalRepository.findAll(example, pageable);
//4. TODO 跨模块cmn 翻译字段(重要)
return pageModel;
}
2、cmn模块 实现翻译字段接口服务
1. 需求分析,需要有两个接口
* 国标数据接口
参数:value
返回值:name
* 自定义数据接口
参数:value、dictCode
返回值:name
2. 实现 controller
@ApiOperation(value = "获取数据字典名称(自定义)")
@GetMapping(value = "/getName/{parentDictCode}/{value}")
public String getName(
@PathVariable("parentDictCode") String parentDictCode,
@PathVariable("value") String value) {
String name = dictService.getName(parentDictCode,value);
return name;
}
@ApiOperation(value = "获取数据字典名称(国标)")
@GetMapping(value = "/getName/{value}")
public String getName(
@PathVariable("value") String value) {
String name = dictService.getName("",value);
return name;
}
3. 实现 service
//获取数据字典名称
@Override
public String getName(String parentDictCode, String value) {
//1.判断parentDictCode是否为空 (国标还是自定义数据)
if(StringUtils.isEmpty(parentDictCode)){
//国标数据查询
Dict dict = baseMapper.selectOne(new QueryWrapper<Dict>()
.eq("value", value));
if(dict!=null){
return dict.getName();
}
}else {
//自定义数据查询
Dict parentDict = this.getDictByDictCode(parentDictCode);
Dict dict = baseMapper.selectOne(new QueryWrapper<Dict>()
.eq("parent_id",parentDict.getId())
.eq("value", value));
if(dict!=null){
return dict.getName();
}
}
return "";
}
//根据字典编码查询父级别数据
private Dict getDictByDictCode(String parentDictCode) {
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("dict_code",parentDictCode);
Dict dict = baseMapper.selectOne(wrapper);
return dict;
}
3、实现跨模块远程调用
1. 搭建service-client 父模块 (提取服务接口),修改pom.xml
<artifactId>service-client</artifactId>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>common_utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided </scope>
</dependency>
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<scope>provided </scope>
</dependency>
</dependencies>
2. 搭建service_cmn_client模块
3. 创建相关服务接口
注意:1.请求路径完整路径 2.远程调用 @PathVariable不能省略
@FeignClient("service-cmn") //指定调用的服务名
public interface DictFeignClient {
//注意完整请求路径
//获取数据字典名称(自定义)
@GetMapping(value = "/admin/cmn/dict/getName/{parentDictCode}/{value}")
public String getName(
@PathVariable("parentDictCode") String parentDictCode,
@PathVariable("value") String value);
//获取数据字典名称(国标)
@GetMapping(value = "/admin/cmn/dict/getName/{value}")
public String getName(
@PathVariable("value") String value);
}
4、在 hosp 实现跨模块调用
1. service模块 确认依赖
<!-- 服务调用feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. hosp模块 导入依赖
<!--服务接口-->
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service_cmn_client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
3. 启动类添加注解 @EnableFeignClients(basePackages="")
@SpringBootApplication
@ComponentScan(basePackages = {"com.atguigu"}) //扫描到其他项目的类
@EnableDiscoveryClient //声明注册中心客户端
@EnableFeignClients(basePackages = "com.atguigu") //开启Feign,扫描其他项目的接口服务
public class ServiceHospApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceHospApplication.class, args);
}
}
4. 改造查询接口
//远程方法调用
@Autowired
private DictFeignClient dictFeignClient;
//带条件带分页获取医院列表
@Override
public Page<Hospital> selectPage(Integer page, Integer limit, HospitalQueryVo hospitalQueryVo) {
//(1).创建分页对象
//1.1创建排序对象
Sort sort = Sort.by(Sort.Direction.DESC,"createTime");
//1.2创建分页对象
Pageable pageable = PageRequest.of(page-1,limit,sort);
//(2).创建查询条件模板
//2.1封装查询条件
Hospital hospital = new Hospital();
BeanUtils.copyProperties(hospitalQueryVo,hospital);
//2.2创建模板构造器
ExampleMatcher matcher = ExampleMatcher.matching()
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //模糊查询
.withIgnoreCase(true); //不区分大小写
//2.3创建模板
Example<Hospital> example = Example.of(hospital,matcher);
//(3).实现条件分页查询
Page<Hospital> pageModel = hospitalRepository.findAll(example, pageable);
//4.TOD 跨模块cmn 翻译字段(重要)
pageModel.getContent().stream().forEach(item ->{
this.packHospital(item);
});
return pageModel;
}
//医院信息字段翻译
private Hospital packHospital(Hospital hospital) {
//DictEnum.HOSTYPE 枚举类
String hostypeString = dictFeignClient.getName(
DictEnum.HOSTYPE.getDictCode(), hospital.getHostype());
String provinceString = dictFeignClient.getName(hospital.getProvinceCode());
String cityString = dictFeignClient.getName(hospital.getCityCode());
String districtString = dictFeignClient.getName(hospital.getDistrictCode());
hospital.getParam().put("hostypeString", hostypeString);
hospital.getParam().put("fullAddress", provinceString + cityString + districtString + hospital.getAddress());
return hospital;
}
测试:
四、实现根据字典编码查询子数据 (下拉列选)
1、后端接口实现
1. controller
//根据数据id查询子数据列表
@ApiOperation(value = "根据dictCode获取下级节点")
@GetMapping("/findByDictCode/{dictCode}")
public R findByDictCode(@PathVariable String dictCode){
List<Dict> list = dictService.findByDictCode(dictCode);
return R.ok().data("list",list);
}
2. service
//根据数据id查询子数据列表
@Override
public List<Dict> findByDictCode(String dictCode) {
Dict dictByDictCode = this.getDictByDictCode(dictCode);
List<Dict> dictList = this.findChildData(dictByDictCode.getId());
return dictList;
}
2、医院列表查询 (前端)
1. 添加路由,创建页面
{
path: 'hospital/list',
name: '医院列表',
component: () =>import('@/views/yygh/hosp/list'),
meta: { title: '医院列表', icon: 'table' }
}
2. 创建API方法
新建hosp.js
import request from '@/utils/request'
//提取请求路径
const api_name = '/admin/hosp/hospital'
export default{
//带条件带分页获取医院列表
getPageList(page,limit,searchObj){
return request({
url: `${api_name}/${page}/${limit}`,//插值表达式
method: 'get',
params: searchObj //普通用params json用data
})
}
}
dict.js 下新增方法
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'
})
},
//查询dictCode查询下级数据字典
findByDictCode(dictCode) {
return request({
url: `${api_name}/findByDictCode/${dictCode}`,
method: 'get'
})
},
}
3. 添加页面元素
<template>
<div class="app-container">
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-select v-model="searchObj.provinceCode" placeholder="请选择省" @change="provinceChanged">
<el-option v-for="item in provinceList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="searchObj.cityCode" placeholder="请选择市">
<el-option v-for="item in cityList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<el-input v-model="searchObj.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" border fit 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">
<template slot-scope="scope">
<img :src="'data:image/jpeg;base64,'+scope.row.logoData" width="80">
</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 === 0 ? '未上线' : '已上线' }}
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" width="230" align="center">
<template slot-scope="scope">
<router-link :to="'/hospSet/hospital/show/'+scope.row.id">
<el-button type="primary" size="mini">查看</el-button>
</router-link>
<router-link :to="'/hospSet/hospital/schedule/'+scope.row.hoscode">
<el-button type="primary" size="mini">排班</el-button>
</router-link>
<el-button v-if="scope.row.status == 1" type="primary" size="mini"
@click="updateStatus(scope.row.id, 0)">下线</el-button>
<el-button v-if="scope.row.status == 0" type="danger" size="mini"
@click="updateStatus(scope.row.id, 1)">上线</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination :current-page="page" :total="total" :page-size="limit" :page-sizes="[5, 10, 20, 30, 40, 50, 100]"
style="padding: 30px 0; text-align: center;" layout="sizes, prev, pager, next, jumper, ->, total, slot"
@current-change="fetchData" @size-change="changeSize" />
</div>
</template>
4. 实现JS
饿了么ui的bug:下拉列选的值要进行初始化
<script>
import hospApi from '@/api/yygh/hosp'
import dictApi from '@/api/yygh/dict'
export default {
data() {
return {
listLoading: true, // 数据是否正在加载
list: null, // 医院列表数据集合
total: 0, // 数据库中的总记录数
page: 1, // 默认页码
limit: 10, // 每页记录数
searchObj: {
provinceCode: '',
cityCode: ''
}, // 查询表单对象 饿了么ui的bug:下拉列选的值要做初始化
provinceList: [], //所有省集合
cityList: [] //所有市集合
}
},
created() {
//初始化表格数据
this.fetchData();
//初始化省信息
this.getProvinceList();
},
methods: {
//表格数据查询
fetchData(page=1){
this.page = page
hospApi.getPageList(this.page,this.limit,this.searchObj)
.then(resopnse=>{
this.list = resopnse.data.pageModel.content
this.total = resopnse.data.pageModel.totalElements
this.listLoading = false
})
},
//查询省信息 下拉列选 传递字典code
getProvinceList(){
dictApi.findByDictCode("Province")
.then(response=>{
this.provinceList = response.data.list
})
},
//二级联动省市信息 下拉列选
provinceChanged(){
//清除历史信息
this.searchObj.cityCode = ""
this.cityList = []
dictApi.findChildData(this.searchObj.provinceCode)
.then(response=>{
this.cityList = response.data.list
})
},
//清空
resetData(){
this.searchObj = {
provinceCode: '',
cityCode: ''
};
this.fetchData();
},
//切换每页记录数
changeSize(size){
this.limit = size;
this.fetchData();
}
}
}
</script>
5. 测试
五、更新医院上线状态
1、后端接口实现
1. 分析接口
参数:id、status
返回值:R.ok()
2. 实现 controller
@ApiOperation(value = "更新上线状态")
@GetMapping("updateStatus/{id}/{status}")
public R updateStatus(
@PathVariable("id") String id,
@PathVariable("status") Integer status){
hospitalService.updateStatus(id,status);
return R.ok();
}
3. 实现 service
//更新上线状态
@Override
public void updateStatus(String id, Integer status) {
if(status.intValue() == 0 || status.intValue() == 1) {
//先查询,后更新
Hospital hospital = hospitalRepository.findById(id).get();
hospital.setStatus(status);
hospital.setUpdateTime(new Date());
hospitalRepository.save(hospital);
}
}
2、前端页面实现
1. 确认入口
2. 新建API方法
//医院状态修改 (上线下限)
updateStatus(id, status) {
return request({
url: `${api_name}/updateStatus/${id}/${status}`,
method: 'get'
})
},
3. JS 实现
updateStatus(id, status){
hospApi.updateStatus(id,status)
.then(response=>{
this.fetchData();
this.$message({
type: "success",
message: "操作成功!",
});
})
}
六、医院详情查询
1、医院详情接口
1. 分析接口
参数:id
返回值:R.(map(医院基本信息、预约规则))
2. 实现 controller
@ApiOperation(value = "获取医院详情")
@GetMapping("show/{id}")
public R show(
@PathVariable String id) {
Map<String,Object> map = hospitalService.getHospitalById(id);
return R.ok().data(map);
}
3. 实现service
//获取医院详情
@Override
public Map<String, Object> getHospitalById(String id) {
//1.根据id查询医院信息,
Hospital hospital = hospitalRepository.findById(id).get();
//2.信息字段翻译
hospital = this.packHospital(hospital);
//3.判断后取出预约规则,封装返回
BookingRule bookingRule = hospital.getBookingRule();
hospital.setBookingRule(null); //节省内存空间
Map<String,Object> map = new HashMap<>();
map.put("hospital",hospital);
map.put("bookingRule",bookingRule);
return map;
}
4. 测试
2、前端页面实现
1. 确认入口
需要创建隐藏路由 (注意修改地址!)
2. 添加隐藏路由,创建页面
{
path: 'hospital/show/:id',
name: '查看医院详情',
component: () => import('@/views/yygh/hosp/show'),
meta: { title: '查看', noCache: true },
hidden: true
}
3. 创建API接口方法
//查看医院详情
getHospById(id) {
return request({
url: `${api_name}/show/${id}`,
method: 'get'
})
}
4. 添加页面元素
<template>
<div class="app-container">
<h4>基本信息</h4>
<table class="table table-striped table-condenseda table-bordered" width="100%">
<tbody>
<tr>
<th width="15%">医院名称</th>
<td width="35%"><b style="font-size: 14px">{{ hospital.hosname }}</b>
| {{ hospital.param.hostypeString }}</td>
<th width="15%">医院logo</th>
<td width="35%">
<img :src="'data:image/jpeg;base64,'+hospital.logoData" width="80">
</td>
</tr>
<tr>
<th>医院编码</th>
<td>{{ hospital.hoscode }}</td>
<th>地址</th>
<td>{{ hospital.param.fullAddress }}</td>
</tr>
<tr>
<th>坐车路线</th>
<td colspan="3">{{ hospital.route }}</td>
</tr>
<tr>
<th>医院简介</th>
<td colspan="3">{{ hospital.intro }}</td>
</tr>
</tbody>
</table>
<h4>预约规则信息</h4>
<table class="table table-striped table-condenseda table-bordered" width="100%">
<tbody>
<tr>
<th width="15%">预约周期</th>
<td width="35%">{{ bookingRule.cycle }}天</td>
<th width="15%">放号时间</th>
<td width="35%">{{ bookingRule.releaseTime }}</td>
</tr>
<tr>
<th>停挂时间</th>
<td>{{ bookingRule.stopTime }}</td>
<th>退号时间</th>
<td>{{ bookingRule.quitDay == -1 ? '就诊前一工作日' : '就诊当日' }}{{ bookingRule.quitTime }} 前取消</td>
</tr>
<tr>
<th>预约规则</th>
<td colspan="3">
<ol>
<li v-for="item in bookingRule.rule" :key="item">{{ item }}</li>
</ol>
</td>
</tr>
<br>
<el-row>
<el-button @click="back">返回</el-button>
</el-row>
</tbody>
</table>
</div>
</template>
5. JS实现
<script>
import hospApi from '@/api/yygh/hosp'
import dictApi from '@/api/yygh/dict'
export default {
data() {
return {
hospital: {},
bookingRule: {}
}
},
created() {
//获取元素用route,页面跳转用router
let id = this.$route.params.id
this.getData(id)
},
methods: {
getData(id) {
hospApi.getHospById(id).then(response => {
this.hospital = response.data.hospital
this.bookingRule = response.data.bookingRule
})
},
//页面跳转
back(){
this.$router.push({path:"/yygh/hospset/hospital/list"})
}
}
}
</script>
6. 添加样式
7. 测试