今日练习

简介: 本课程围绕微服务实战展开,涵盖MySQL、MyBatis-Plus集成,RestTemplate跨服务调用及Nacos注册中心的使用。通过重构与优化代码,提升学生对微服务架构的理解与实战能力,并培养其在团队协作中修改代码的勇气与规范意识。

作业目标

  • 作业一:掌握微服务集成常用组件(mysql、mybatis-plus),复习前面知识同时巩固微服务实战的理解,如有精力对数据库三范式设计做进一步学习
  • 作业二:掌握微服务跨服务调用,借助RestTemplate实现,并能够分析出此方案的缺点
  • 作业三:掌握微服务注册中心Nacos的部署、启动,服务注册与发现,微服务整合
  • 整体:考虑到今天课程难度不大,穿插了大量接口、实体类、方法的重构,考察同学对于融入一个新团队后改代码的勇气和能力

工程介绍

工程技术架构图

工程目录结构图

环境准备

  • 导入作业中提供的工程:doctor-station(📎doctor-station.zip)并启动项目,完成下述访问:

应用名称

访问地址

预期结果

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依赖-->
<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+端口)在生产环境是否真实可行的呢?

参考答案

  1. 启动类DoctorApplication增加RestTemplate注入
@Bean
public RestTemplate getRestTemplate() {
  return new RestTemplate();
}
  1. 将inventory-service中的:InventoryUpdateVO,复制到doctor-service中,用作后续调用返回值
  2. 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;
    }
}


  1. postman发起创建医嘱验证,同时查看库存数据是否 - 1

题目三

目标

能够完成微服务注册到注册中心Nacos,并实现服务调用

需求

让耗材中心的服务注册到Nacos中心,同时医生站能够完成服务调用

参考答案

  1. 父工程引入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>


  1. 子工程引入nacos依赖(两个均需引入)


<!-- nacos客户端依赖包 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>


  1. 子工程增加nacos配置信息(两个均需增加)


cloud:
    nacos:
      server-addr: localhost:8848


  1. 修改启动类DoctorApplication的RestTemplate:@LoadBalanced
  2. 修改DoctorService中请求url为:


String url = "http://inventoryservice/inventory/update/" + param.getInventoryId() + "/" + param.getDoctorInventoryNum();


  1. 重启服务
  2. 查看nacos服务列表是否有两个注册服务信息

  1. postman发起创建医嘱验证,同时查看库存数据是否 - 1
相关文章
|
12天前
|
数据采集 人工智能 安全
|
7天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
344 164
|
6天前
|
机器学习/深度学习 自然语言处理 机器人
阿里云百炼大模型赋能|打造企业级电话智能体与智能呼叫中心完整方案
畅信达基于阿里云百炼大模型推出MVB2000V5智能呼叫中心方案,融合LLM与MRCP+WebSocket技术,实现语音识别率超95%、低延迟交互。通过电话智能体与座席助手协同,自动化处理80%咨询,降本增效显著,适配金融、电商、医疗等多行业场景。
345 155
|
7天前
|
编解码 人工智能 自然语言处理
⚽阿里云百炼通义万相 2.6 视频生成玩法手册
通义万相Wan 2.6是全球首个支持角色扮演的AI视频生成模型,可基于参考视频形象与音色生成多角色合拍、多镜头叙事的15秒长视频,实现声画同步、智能分镜,适用于影视创作、营销展示等场景。
575 4
|
15天前
|
SQL 自然语言处理 调度
Agent Skills 的一次工程实践
**本文采用 Agent Skills 实现整体智能体**,开发框架采用 AgentScope,模型使用 **qwen3-max**。Agent Skills 是 Anthropic 新推出的一种有别于mcp server的一种开发方式,用于为 AI **引入可共享的专业技能**。经验封装到**可发现、可复用的能力单元**中,每个技能以文件夹形式存在,包含特定任务的指导性说明(SKILL.md 文件)、脚本代码和资源等 。大模型可以根据需要动态加载这些技能,从而扩展自身的功能。目前不少国内外的一些框架也开始支持此种的开发方式,详细介绍如下。
1013 7