今日练习

简介: 本作业涵盖微服务核心技能:整合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服务列表是否有两个注册服务信息
相关文章
|
2月前
|
JSON Dubbo Java
Feign远程调用
本章介绍如何使用Feign替代RestTemplate实现更优雅的HTTP跨服务调用。通过引入Feign,结合注册中心与注解声明,解决硬编码、可读性差等问题,并支持自定义配置、连接池优化与最佳实践,如客户端抽取为独立模块,提升代码复用性与维护性。
|
2月前
|
监控 算法 Unix
Thread.sleep(0) 到底有什么
Thread.Sleep用于让线程暂停执行一段时间,不参与CPU竞争。Sleep(1000)并不保证精确唤醒时间,因系统调度受优先级和资源影响;而Sleep(0)则触发系统立即重新分配CPU,给予其他线程执行机会,避免界面假死。理解其原理有助于优化多线程程序性能与响应性。
|
2月前
|
JSON 缓存 前端开发
什么是跨域
CORS(跨域资源共享)是W3C标准,允许浏览器向跨源服务器发起XMLHttpRequest请求,突破AJAX同源限制。需浏览器和服务器共同支持,主流浏览器均已兼容。通信过程由浏览器自动完成,开发者无需改变代码。分为简单请求与非简单请求:前者直接发送带Origin头的请求;后者先发起OPTIONS预检,确认后才执行实际请求。服务器通过响应头如Access-Control-Allow-Origin等控制跨域权限。相比仅支持GET的JSONP,CORS更强大灵活,支持所有HTTP方法,且可携带认证信息。
|
2月前
|
关系型数据库 MySQL Java
开发环境搭建
工欲善其事,必先利其器。建议电脑内存16G以上,推荐32G,搭配便携显示器提升开发效率。下载课程资料并按要求配置虚拟机(IP:192.168.101.68)与开发环境。使用VMware导入CentOS 7虚拟机,安装IDEA、Maven、Git等工具,完成JDK11、Maven仓库及SSH远程连接配置,确保开发环境搭建顺利完成。
|
2月前
|
Kubernetes IDE Java
部署篇(工具部署)
本文介绍EDAS在Kubernetes上部署SpringCloud应用的完整工具体系,覆盖开发、运维及DevOps场景。包括IDE插件快速部署、Maven插件配置化发布、Jenkins与云效CI/CD集成、Terraform基础设施编排及CLI命令行控制,全面支持高效安全的应用交付。后续将聚焦“可灰度、可回滚、可监控”的线上变更实践。
|
2月前
|
SQL 安全 关系型数据库
了解SQL注入
SQL注入是一种通过构造恶意SQL语句攻击数据库的高危漏洞,常用于绕过认证、窃取数据或执行系统命令。由于输入验证不足,攻击者可利用拼接字符串篡改SQL逻辑,造成信息泄露、数据损毁等严重后果。防御需结合参数化查询、严格输入验证及IPS防护,尤其应避免错误信息暴露。OWASP将其列为头号Web安全威胁。
|
2月前
|
SQL 安全 网络协议
@PostConstruct与@PreDestroy的作用和原理
恶意软件指具有险恶意图的程序,如病毒、勒索软件、间谍软件等,常通过钓鱼邮件或漏洞入侵系统,窃取数据、破坏功能。网络钓鱼伪装成可信来源骗取敏感信息。中间人攻击通过截获通信窃取数据。DDoS攻击利用大量流量瘫痪系统,近年呈规模化、加密化趋势。SQL注入、零日攻击、DNS隧道等手段亦严重威胁网络安全。
|
2月前
|
JSON 缓存 前端开发
什么是WebFlux
CORS(跨域资源共享)是W3C标准,允许浏览器向跨源服务器发起XMLHttpRequest请求,突破AJAX同源限制。浏览器自动处理CORS通信,开发者无需特殊编码。关键在于服务器需实现CORS接口。请求分为简单和非简单两类,后者会先发送OPTIONS预检请求。相比仅支持GET的JSONP,CORS功能更强大,兼容现代浏览器。
|
2月前
|
存储 Java 关系型数据库
微服务概述
本文对比单体与微服务架构,解析微服务定义、特征及优缺点,涵盖技术选型、部署方案与学习路径,系统阐述如何构建微服务体系,助力应对高并发、易扩展的现代应用需求。
|
2月前
|
SpringCloudAlibaba Java Nacos
SpringCloud概述
Spring Cloud是Spring推出的微服务一站式解决方案,弥补了各技术分散、缺乏统一标准的痛点。它具备“约定优于配置”、组件丰富、开箱即用等特点,完美适配云原生架构。版本以地铁站命名(如Hoxton.SR12),避免与子项目版本冲突。其子项目涵盖服务注册、配置管理、负载均衡等全链路能力。随着Netflix套件停更,Spring Cloud Alibaba凭借Nacos、Sentinel、Seata等成熟组件成为主流选择,获官方认可,功能更完善,经大规模业务验证,成为微服务落地优选方案。