作业目标
● 作业一:掌握微服务集成常用组件(mysql、mybatis-plus),复习前面知识同时巩固微服务实战的理解,如有精力对数据库三范式设计做进一步学习
● 作业二:掌握微服务跨服务调用,借助RestTemplate实现,并能够分析出此方案的缺点
● 作业三:掌握微服务注册中心Nacos的部署、启动,服务注册与发现,微服务整合
● 整体:考虑到今天课程难度不大,穿插了大量接口、实体类、方法的重构,考察同学对于融入一个新团队后改代码的勇气和能力
工程介绍
工程技术架构图
工程目录结构图
环境准备
● 导入作业中提供的工程:doctor-station()并启动项目,完成下述访问:
应用名称 访问地址 预期结果
inventory-service http://localhost:8081/inventory/update/1 {"id":1,"num":10,"createTime":"2023-02-12T07:46:21.589+00:00"}
doctor-service http://localhost:8088/doctor/create/1 {"id":1,"doctorName":"测试名称","inventory":null}
作业内容
题目一
目标
能够完成MySQL、MyBatis整合微服务,并优化现有代码逻辑。
需求
作为医生站新人,目前工程尚未完成mysql,mybatis-plus的整合工作,请你根据以往工作经验完成上述组件的集成,并依次实现下述需求。
需求项1
完成数据表创建,表结构设计参照下面(注:工作后表结构一般是自己设计,请了解:数据库设计三范式原则),数据库名称:doctor_station :
● 表名:ds_inventory
字段 字段描述 字段类型 字段长度 备注
id 数据id bigint 64 主键、自增
num 库存数量 int 11
name 库存名称 varchar 256
description 库存描述 varchar 256
create_time 创建时间 timestamp 默认取当前记录插入时间
● 请插入至少一条铺底数据(自行插入)
● 表名:ds_doctor
字段 字段描述 字段类型 字段长度 备注
id 数据id bigint 64 主键、自增
inventory_id 库存id bigint 64 非空
doctor_name 医嘱名称 varchar 256
doctor_inventory_num 医嘱库存数量 int 11
create_time 创建时间 timestamp 默认取当前记录插入时间
需求项1-参考答案
-- Table structure for ds_inventory
DROP TABLE IF EXISTS ds_inventory;
CREATE TABLE ds_inventory (
id bigint(64) NOT NULL AUTO_INCREMENT COMMENT '数据ID',
num int(11) DEFAULT NULL COMMENT '库存数量',
name varchar(256) DEFAULT NULL COMMENT '库存名称',
description varchar(256) DEFAULT NULL COMMENT '库存描述',
create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- Records of ds_inventory
INSERT INTO ds_inventory VALUES ('1', '100', '芒果', '芒果库存充足', '2023-02-24 16:05:18');
-- Table structure for ds_doctor
DROP TABLE IF EXISTS ds_doctor;
CREATE TABLE ds_doctor (
id bigint(64) NOT NULL AUTO_INCREMENT COMMENT '数据id',
inventory_id bigint(64) NOT NULL COMMENT '库存id',
doctor_name varchar(256) DEFAULT NULL COMMENT '医嘱名称',
doctor_inventory_num int(11) DEFAULT NULL COMMENT '医嘱库存数量',
create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1629041798847467523 DEFAULT CHARSET=utf8;
需求项2
完成库存中心集成mysql、mybatis-plus。同时针对现有库存http接口做如下优化
● 现有entity属性根据表结构适当调整[模拟需求迭代字段调整]
○ 加分项:作为专业的程序员,每个属性的注释,都不应被随意描述和忽略
● 更新需要能够根据前端传入的id、数量做实际的更新,如:前端传入参数:"id"=1 , "num"=1,则完成对id=1的数据扣减1
○ 注意:此处建议协议仍保留GET,方便后续跨服务调用
○ 加分项:
■ 返回结果封装,属性:updateResult、updateMsg,并考虑参数存放的合理路径(包位置)
■
● 更新成功时:true、更新成功
● 更新失败时:false、库存不足,请稍后重试
■ 针对数据是否存在,剩余库存是否足够扣减做合法性校验,不满足则提前返回
需求项2-参考答案
步骤一:引入mysql、mybatis-plus依赖,并更新配置项,启动类增加扫描注解:@MapperScan("cn.itcast.mapper")
mysql
mysql-connector-java
com.baomidou
mybatis-plus-boot-starter
3.3.0
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///doctor_station?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
username: root
password: root
步骤二:封装对象:Inventory
package cn.itcast.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
- @Description: 耗材信息
@Date: 2023/2/12 15:40
*/
@Data
@TableName("ds_inventory")
public class Inventory {/**
数据ID
*/
private Long id;/**
库存数量
*/
private Long num;/**
库存名称
*/
private String name;/**
库存描述
*/
private String description;/**
- 创建时间
*/
private Date createTime;
}
步骤三:封装返回结果:InventoryUpdateVO,注意包路径
package cn.itcast.web.response;
import lombok.Data;
/**
- @Description: 库存更新Vo
@Date: 2023/2/24 15:45
*/
@Data
public class InventoryUpdateVO {/**
更新结果
*/
public Boolean updateResult;/**
- 更新结果描述
*/
public String updateMsg;
}
步骤四:创建mapper接口,集成mybatis
package cn.itcast.mapper;
import cn.itcast.entity.Inventory;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Component;
/**
- @Description:
- @Date: 2023/2/24 15:39
*/
@Component
public interface InventoryMapper extends BaseMapper {
}
步骤五:修改核心逻辑:
package cn.itcast.service;
import cn.itcast.entity.Inventory;
import cn.itcast.mapper.InventoryMapper;
import cn.itcast.web.response.InventoryUpdateVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
- @Description: 库存核心逻辑层
@Date: 2023/1/30 14:57
*/
@Service
public class InventoryService {@Autowired
private InventoryMapper inventoryMapper;/**
- 查询库存信息
- @param id 库存ID
@return 库存信息
*/
public Inventory queryInventory(Long id) {
return inventoryMapper.selectById(id);
}/**
- 更新库存信息
- @param id 更新参数:库存ID/库存数量
- @param num 更新参数:库存ID/库存数量
@return 更新结果
*/
public InventoryUpdateVO updateInventory( Long id, Long num) {InventoryUpdateVO result = new InventoryUpdateVO();
// 1-查询现有库存
Inventory inventory = queryInventory(id);
if (Objects.isNull(inventory)) {result.setUpdateResult(Boolean.FALSE); result.setUpdateMsg("库存数据不存在,请稍后重试"); return result;}
// 2-数据合法性校验
if (inventory.getNum() < num) {result.setUpdateResult(Boolean.FALSE); result.setUpdateMsg("库存不足,请稍后重试"); return result;}
// 3-更新
Inventory updateEntity = new Inventory();
updateEntity.setId(id);
updateEntity.setNum(inventory.getNum() - num);
int count = inventoryMapper.updateById(updateEntity);// 4-更新结果判断
if (count == 0) {result.setUpdateResult(Boolean.FALSE); result.setUpdateMsg("更新失败,请稍后重试"); return result;}
result.setUpdateResult(Boolean.TRUE);
result.setUpdateMsg("更新成功");
return result;
}
}
步骤六:修改http接口入参、出参结果
package cn.itcast.web;
import cn.itcast.service.InventoryService;
import cn.itcast.web.response.InventoryUpdateVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
- @Description: 库存中心控制层
@Date: 2023/1/30 14:56
*/
@RestController
@RequestMapping("/inventory")
public class InventoryController {@Autowired
private InventoryService inventoryService;/**
- 更新库存信息
*/
@GetMapping("/update/{id}/{num}")
public InventoryUpdateVO updateInventory(@PathVariable("id") Long id, @PathVariable("num") Long num) {
return inventoryService.updateInventory(id, num);
}
}
- 更新库存信息
步骤七:导入postman测试验证:
需求项3
完成医嘱中心集成mysql、mybatis。同时针对现有逻辑做如下优化:
● 前端调用参数调整为:医嘱名称、库存id、医嘱库存数量
○ 加分项:前端参数封装,并考虑参数存放的合理路径(包位置)
● 针对现有表结构设计,对当前工程部分类属性做调整
○ 现有Doctor属性调整为当前表结构对应属性
○ 无用代码及时删除:Inventory
● 当发起创建医嘱时,向数据库保存医嘱信息
○ 加分项:
■ 打破原有返回结果,按照表结构重新设计出参VO[模拟工作中需求迭代出参调整]
● 封装返回结果,属性:createResult、createMsg
○ 新增成功时:true、新增成功
○ 新增失败时:false、新增失败,请稍后重试
需求项3-参考答案
步骤一:引入mysql、mybatis-plus依赖,并更新配置项,启动类增加扫描注解:@MapperScan("cn.itcast.mapper")
mysql
mysql-connector-java
com.baomidou
mybatis-plus-boot-starter
3.3.0
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///doctor_station?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
username: root
password: root
步骤二:完成实体类属性调整,并删除无用的:Inventory
package cn.itcast.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
- @Description: 医生创建医嘱信息
@Date: 2023/2/12 15:42
*/
@Data
@TableName("ds_doctor")
public class Doctor {/**
医嘱ID
*/
private Long id;/**
医嘱名称
*/
private String doctorName;/**
库存ID
*/
private Long inventoryId;/**
医嘱扣减库存数量
*/
private Long doctorInventoryNum;/**
- 创建时间
*/
private Date createTime;
}
步骤三:创建数据库操作的mapper
package cn.itcast.mapper;
import cn.itcast.entity.Doctor;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Component;
/**
- @Description:
- @Date: 2023/2/24 16:39
*/
@Component
public interface DoctorMapper extends BaseMapper {
}
步骤四:封装请求参数:DoctorCreateParam
package cn.itcast.web.request;
import lombok.Data;
/**
- @Description: 医生信息创建模块
@Date: 2023/2/24 16:24
*/
@Data
public class DoctorCreateParam {/**
医嘱名称
*/
public String doctorName;/**
库存ID
*/
public Long inventoryId;/**
- 库存扣减数量
*/
public Long doctorInventoryNum;
}
步骤五:封装响应参数:
package cn.itcast.web.response;
import lombok.Data;
import java.util.Date;
/**
- @Description: 医嘱创建
@Date: 2023/2/24 16:31
*/
@Data
public class DoctorCreateVO {/**
医嘱ID
*/
private Boolean createResult;/**
- 医嘱名称
*/
private String createMsg;
}
步骤六:修改核心业务逻辑处理层
package cn.itcast.service;
import cn.itcast.entity.Doctor;
import cn.itcast.mapper.DoctorMapper;
import cn.itcast.web.request.DoctorCreateParam;
import cn.itcast.web.response.DoctorCreateVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
- @Description: 医生核心逻辑处理层
@Date: 2023/1/30 14:41
*/
@Service
public class DoctorService {@Autowired
private DoctorMapper doctorMapper;/**
创建医嘱信息
*/
public DoctorCreateVO createDoctorOrder(DoctorCreateParam param) {DoctorCreateVO result = new DoctorCreateVO();
Doctor doctor = new Doctor();
doctor.setDoctorName(param.doctorName);
doctor.setInventoryId(param.getInventoryId());
doctor.setDoctorInventoryNum(param.getDoctorInventoryNum());
int count = doctorMapper.insert(doctor);//TODO 扣减库存(调用库存中心)
// 返回
if (count == 0) {result.setCreateResult(Boolean.FALSE); result.setCreateMsg("创建失败,请稍后重试"); return result;}
result.setCreateResult(Boolean.TRUE);
result.setCreateMsg("创建成功");
return result;
}
}
步骤七:调整controller调用方法出入参
package cn.itcast.web;
import cn.itcast.service.DoctorService;
import cn.itcast.web.request.DoctorCreateParam;
import cn.itcast.web.response.DoctorCreateVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Objects;
/**
- @Description: 医生站web应用
@Date: 2023/1/30 14:40
*/
@RestController
@RequestMapping("/doctor")
public class DoctorController {@Autowired
private DoctorService doctorService;@PostMapping("/create")
public DoctorCreateVO createDoctorOrder(DoctorCreateParam param) {// 调用创建医嘱 return doctorService.createDoctorOrder(param);}
}
步骤八:导入postman测试文件验证:
题目二
目标
能够熟练使用RestTemplate发送http请求完成远程调用
需求
对于当前方法调用,你分析出现有问题(你是否意识到了呢):医嘱作为上游业务发起方,并没有对下游库存中心做实际的库存扣减,因此需要发起一个跨服务调用,实现库存的真实扣减。请完成下述需求项:
针对现有调用优化,实现医嘱服务新增时,需扣减库存服务
● 加分项:对于扣减结果做校验(非空校验、更新结果校验)
● 加分项:考虑服务的调用顺序应该在doctor插入数据之前完成,还是之后呢?
● 加分项:分析现有(写死IP+端口)在生产环境是否真实可行的呢?
参考答案
- 启动类DoctorApplication增加RestTemplate注入
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
} - 将inventory-service中的:InventoryUpdateVO,复制到doctor-service中,用作后续调用返回值
- DoctorService引入上述Bean,同时完成服务调用
package cn.itcast.service;
import cn.itcast.entity.Doctor;
import cn.itcast.mapper.DoctorMapper;
import cn.itcast.web.request.DoctorCreateParam;
import cn.itcast.web.response.DoctorCreateVO;
import cn.itcast.web.response.InventoryUpdateVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Objects;
/**
- @Description: 医生核心逻辑处理层
@Date: 2023/1/30 14:41
*/
@Service
public class DoctorService {@Autowired
private DoctorMapper doctorMapper;@Autowired
private RestTemplate restTemplate;/**
创建医嘱信息
*/
public DoctorCreateVO createDoctorOrder(DoctorCreateParam param) {DoctorCreateVO result = new DoctorCreateVO();
// 1-扣减库存(调用库存中心)
String url = "http://localhost:8081/inventory/update/" + param.getInventoryId() + "/" + param.getDoctorInventoryNum();
InventoryUpdateVO inventory = restTemplate.getForObject(url, InventoryUpdateVO.class);// 1.1-库存存在性校验
if (Objects.isNull(inventory)) {result.setCreateResult(Boolean.FALSE); result.setCreateMsg("创建失败,库存不存在"); return result;}
// 1.2-库存更新成功校验
if (Boolean.FALSE.equals(inventory.getUpdateResult())) {result.setCreateResult(Boolean.FALSE); result.setCreateMsg(inventory.getUpdateMsg()); return result;}
// 2-插入医嘱信息
Doctor doctor = new Doctor();
doctor.setDoctorName(param.doctorName);
doctor.setInventoryId(param.getInventoryId());
doctor.setDoctorInventoryNum(param.getDoctorInventoryNum());
int count = doctorMapper.insert(doctor);// 3-返回
if (count == 0) {result.setCreateResult(Boolean.FALSE); result.setCreateMsg("创建失败,请稍后重试"); return result;}
result.setCreateResult(Boolean.TRUE);
result.setCreateMsg("创建成功");
return result;
}
}
- postman发起创建医嘱验证,同时查看库存数据是否 - 1
题目三
目标
能够完成微服务注册到注册中心Nacos,并实现服务调用
需求
让耗材中心的服务注册到Nacos中心,同时医生站能够完成服务调用
参考答案
- 父工程引入pom
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.5.RELEASE
pom
import
- 子工程引入nacos依赖(两个均需引入)
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
子工程增加nacos配置信息(两个均需增加)
cloud:
nacos:
server-addr: localhost:8848修改启动类DoctorApplication的RestTemplate:@LoadBalanced
- 修改DoctorService中请求url为:
String url = "http://inventoryservice/inventory/update/" + param.getInventoryId() + "/" + param.getDoctorInventoryNum();
- 重启服务
- 查看nacos服务列表是否有两个注册服务信息