作业目标
- 作业一:掌握微服务集成常用组件(mysql、mybatis-plus),复习前面知识同时巩固微服务实战的理解,如有精力对数据库三范式设计做进一步学习
- 作业二:掌握微服务跨服务调用,借助RestTemplate实现,并能够分析出此方案的缺点
- 作业三:掌握微服务注册中心Nacos的部署、启动,服务注册与发现,微服务整合
- 整体:考虑到今天课程难度不大,穿插了大量接口、实体类、方法的重构,考察同学对于融入一个新团队后改代码的勇气和能力
工程介绍
工程技术架构图
工程目录结构图
环境准备
- 导入作业中提供的工程:doctor-station(📎doctor-station.zip)并启动项目,完成下述访问:
应用名称 |
访问地址 |
预期结果 |
inventory-service |
{"id":1,"num":10,"createTime":"2023-02-12T07:46:21.589+00:00"} |
|
doctor-service |
{"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依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--引入mybatis依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.0</version> </dependency>
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<Inventory> { }
步骤五:修改核心逻辑:
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测试验证:📎SpringCloud-Day1.json
需求项3
完成医嘱中心集成mysql、mybatis。同时针对现有逻辑做如下优化:
- 前端调用参数调整为:医嘱名称、库存id、医嘱库存数量
- 加分项:前端参数封装,并考虑参数存放的合理路径(包位置)
- 针对现有表结构设计,对当前工程部分类属性做调整
- 现有Doctor属性调整为当前表结构对应属性
- 无用代码及时删除:Inventory
- 当发起创建医嘱时,向数据库保存医嘱信息
- 加分项:
- 打破原有返回结果,按照表结构重新设计出参VO[模拟工作中需求迭代出参调整]
- 封装返回结果,属性:createResult、createMsg
- 新增成功时:true、新增成功
- 新增失败时:false、新增失败,请稍后重试
需求项3-参考答案
步骤一:引入mysql、mybatis-plus依赖,并更新配置项,启动类增加扫描注解:@MapperScan("cn.itcast.mapper")
<!--引入mysql依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--引入mybatis依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.0</version> </dependency>
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<Doctor> { }
步骤四:封装请求参数: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测试文件验证:📎SpringCloud-Day1.json
题目二
目标
能够熟练使用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
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.5.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency>
- 子工程引入nacos依赖(两个均需引入)
<!-- nacos客户端依赖包 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
- 子工程增加nacos配置信息(两个均需增加)
cloud: nacos: server-addr: localhost:8848
- 修改启动类DoctorApplication的RestTemplate:@LoadBalanced
- 修改DoctorService中请求url为:
String url = "http://inventoryservice/inventory/update/" + param.getInventoryId() + "/" + param.getDoctorInventoryNum();
- 重启服务
- 查看nacos服务列表是否有两个注册服务信息
- postman发起创建医嘱验证,同时查看库存数据是否 - 1