今日练习

简介: 本作业涵盖微服务核心技能:整合MySQL与MyBatis-Plus实现数据持久化,使用RestTemplate完成跨服务调用并分析其局限性,最终通过Nacos实现服务注册与发现。强调代码重构能力、数据库设计规范及生产环境问题思考,全面提升微服务实战水平。(238字)

作业目标
● 作业一:掌握微服务集成常用组件(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+端口)在生产环境是否真实可行的呢?
参考答案

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

com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.5.RELEASE
pom
import
  1. 子工程引入nacos依赖(两个均需引入)

com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
  1. 子工程增加nacos配置信息(两个均需增加)

    cloud:
    nacos:
    server-addr: localhost:8848

  2. 修改启动类DoctorApplication的RestTemplate:@LoadBalanced

  3. 修改DoctorService中请求url为:

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

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