Day 2 正式coding之基础模块的搭建
这里会总结构建项目过程中遇到的问题,以及一些个人思考!!
学习方法:
1 github源码 + 文档 + 官网
2 内容复现 ,实际操作
项目源码同步更新到github 欢迎大家star~ 后期会更新并上传前端项目
创建父工程
新建一个jdk17的maven工程,并删掉src目录
导入依赖文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bootscoder</groupId>
<artifactId>shopping</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 为表示其为父工程,设置打包方式-->
<packaging>pom</packaging>
<!-- 子模块 -->
<modules>
</modules>
<properties>
<java.version>17</java.version>
<dubbo.version>3.2.4</dubbo.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>3.0.2</spring-boot.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
<lombok.version>1.18.28</lombok.version>
<mybatis-plus.version>3.5.4</mybatis-plus.version>
</properties>
<!-- 依赖版本声明 -->
<dependencyManagement>
<dependencies>
<!-- Spring Cloud版本-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba版本 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringBoot版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Dubbo版本 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- lombok版本 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- mybatis-plus版本 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 通用依赖 -->
<dependencies>
<!-- bootstrap启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
</dependencies>
<!-- 插件 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
<!-- 依赖仓库路径 -->
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<!-- 插件仓库路径 -->
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
创建通用模块
使用Nacos时,我们需要使用Dubbo进行服务调用,此时我们需要构建一个通用模块,在通用模块中存放服务接口。除了服务接口,我们还会存放一些实体类、工具类等通用功能,每个模块都会引用通用模块。
Dubbo进行服务调用 有两个身份: 生产者和消费者
通用模块的依赖注入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- MyBatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置父模块
<parent>
<groupId>com.bootscoder</groupId>
<artifactId>shopping</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
配置子模块
<!-- 子模块 -->
<modules>
<!-- 通用模块,包含实体类、服务接口、工具类等 -->
<module>shopping_common</module>
</modules>
流程总结
- pom文件改写
- 创建pojo文件夹,编写实体类。
- 由于通用模块只会被其他模块引用,而不会启动,所以可以删除启动类。
- 创建名为shopping的数据库,将数据库脚本导入mysql中。
- 在通用模块添加
service
包存放服务接口,添加utils
包存放工具类。
创建商品服务模块
<dependencies>
<!-- MyBatisPlus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.bootscoder</groupId>
<artifactId>shopping_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
</dependency>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
# 端口号
server:
port: 9001
# 日志格式
logging:
pattern:
console: '%d{HH:mm:ss.SSS} %clr(%-5level) --- [%-15thread] %cyan(%-50logger{50}):%msg%n'
spring:
application:
name: shopping_goods_service #服务名
cloud:
nacos:
discovery:
server-addr: 192.168.66.100:8848 # 注册中心地址
# 数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///shopping?serverTimezone=UTC
username: 你的name
password: 你的密码
dubbo:
application:
name: shopping_goods_service #服务名
serialize-check-status: DISABLE
check-serializable: false
protocol:
name: dubbo # 通讯协议
port: -1 # 端口号,-1表示自动扫描可用端口。
registry:
address: nacos://192.168.66.100:8848 # 注册中心
# 配置Mybatis-plus
mybatis-plus:
global-config:
db-config:
# 表名前缀
table-prefix: boots_
# 主键生成策略为自增
id-type: auto
configuration:
# 关闭列名自动驼峰命名映射规则
map-underscore-to-camel-case: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启sql日志
spring:
profiles:
active: dev #环境
cloud:
nacos:
config:
server-addr: 192.168.66.100:8848
namespace: 44d5da8b-b356-4aa8-b4be-49a54cb8b8ee
file-extension: yaml
prefix: shopping_goods_service
- 构建springboot
- pom文件依赖+父子模块指定
- 修改yaml
在nacos 中编写配置文件
将配置文件放到nacos中, 在项目中进行读取
配置文件发布
在本地代码中配置 bootstrap.yml 连接nacos 配置中心
woc 这个idea 版本(2019)已经落后了???
更新后 依然起不来
马勒戈壁 说老子没配置database 傻逼
md 配置中心和本地文件命名指向不匹配,操了
更新idea后成功
编写根据id查询品牌功能(放到goods里面)
- 在启动类中添加注解@EnableDiscoveryClient // 向注册中心注册该服务
@EnableDubbo // 开启Dubbo
@RefreshScope // 配置动态刷新 - 编写服务实现类
- 记得加dubbo 的service注解
- 加事务注解
- 编写mapper
- 启动类扫描mapper
提问-通用模块的细节:
- 这里提一个小的问题哈,为什么这里的GoodsService 可以调用common呢?
- 做分布式搭建后,是否仍然可以进行调用?
maven依赖引入;
需要整体打包,或者将common注册到maven的中心仓库
做完整体的流程我想模拟一下分布式调用
创建管理员端API模块
这里使用postman进行测试;没有使用过的……那你可能要Google一下了
商城分为管理员端和用户端,用户端是用户访问的,可以查询商品、购买商品;管理员端是商家访问的,可以维护商品。由于管理员端的访问量有限,我们将管理员端的所有控制器都放入管理员端的API模块。
前端项目访问该模块的控制器,控制器返回json数据给前端。管理员端API模块并不能查询数据,它只是服务的消费者,需要连接服务的生产者才能查询到数据。
-
创建名为
shopping_manager_api
的SpringBoot工程,添加相关依赖。 -
设置该工程的父工程为
shopping
。 -
编写根据id 查询的controller
/**
* 品牌控制器
*
* @author bootsCoder
* @date created on 2024/4/15
*/
@RestController
@RequestMapping(("/brand"))
public class BrandController {
@DubboReference
private BrandService brandService;
@GetMapping("/findById")
public Brand findById(Long id){
return brandService.findById(id);
}
}
API接口设置统一数据返回格式
/**
* 全局统一结果配置
*
* @author bootsCoder
* @date created on 2024/4/15
*/
@Data
@AllArgsConstructor
public class BaseResult<T> {
// 状态码(成功:200 失败:其他)
private Integer code;
// 提示消息
private String message;
// 返回数据
private T data;
// 构建成功结果
public static <T> BaseResult<T> ok() {
return new BaseResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null);
}
// 构建带有数据的成功结果
public static <T> BaseResult<T> ok(T data) {
return new BaseResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
}
/**
* 状态码
*
* @author bootsCoder
* @date created on 2024/4/15
*/
@Getter
@AllArgsConstructor
public enum ResultCode {
// 正常
SUCCESS(200, "OK")
;
private final Integer code;
private final String message;
}
@GetMapping("/findById")
public BaseResult<Brand> findById(Long id){
Brand brand = brandService.findById(id);
return BaseResult.ok(brand);
}
统一异常处理
-
异常信息从服务端传入消费端时,Dubbo会默认封装异常,绕过我们的全局异常处理器,所以在通用模块禁用Dubbo异常封装,
-
由于新版Dubbo默认关闭对象序列化,使得异常信息不能从服务端传入消费端(会发生什么呢?),所以在通用模块开启Dubbo序列化:
# 禁用Dubbo异常封装,使用自定义异常处理
dubbo.provider.filter=-exception
# 开启Dubbo序列化
dubbo.application.serialize-check-status=DISABLE
为了让所有项目都可以加载全局异常处理类,可以在通用模块的resources
目录下建立META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件,添加如下内容:
com.bootscoder.shopping_common.result.GlobalExceptionHandler
在Dubbo这样的分布式服务框架中,如果默认关闭了对象序列化,就会影响异常信息从服务端传到消费端的能力。具体表现为:
- 异常信息丢失:当服务端发生异常时,这些异常信息需要通过网络传输到消费端。对象序列化是这一过程中的关键,它允许复杂的对象数据转换为字节流,以便可以通过网络传输。如果序列化被关闭,异常对象不能被有效地转换和传输,导致消费端无法接收到完整的异常信息。
- 通用异常代替:在没有获取到具体异常信息的情况下,消费端可能只能接收到一些非常通用的异常,例如
java.rmi.RemoteException
。这意味着消费端得到的错误信息不具体,无法准确反映服务端发生的实际问题。- 错误处理困难:缺乏具体的异常信息会使得消费端难以进行针对性的错误处理或调试。这可能会增加问题诊断和解决的时间和复杂性,影响服务的整体可靠性和用户体验。
- 服务降级处理:在一些情况下,如果异常信息不能正确传递,系统可能需要依赖预定义的服务降级策略。这些策略虽然能保持系统运行,但可能不是最优解决方案。
/**
* 自定义异常
*
* @author bootsCoder
* @date created on 2024/4/15
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class MyException extends RuntimeException {
// 状态码
private Integer code;
// 错误消息
private String msg;
public MyException(ResultCode resultCode){
this.code = resultCode.getCode();
this.msg = resultCode.getMessage();
}
}
package com.bootscoder.shopping_common.result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 统一异常处理
*
* @author bootsCoder
* @date created on 2024/4/15
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理业务异常
@ExceptionHandler(MyException.class)
public BaseResult defaultExceptionHandler(MyException e){
BaseResult baseResult = new BaseResult(e.getCode(),e.getMsg(),null);
return baseResult;
}
// 处理系统异常
@ExceptionHandler(Exception.class)
public BaseResult defaultExceptionHandler(HttpServletRequest req, HttpServletResponse resp, Exception e) {
e.printStackTrace();
BaseResult baseResult = new BaseResult(ResultCode.SYSTEM_ERROR.getCode(),ResultCode.SYSTEM_ERROR.getMessage(),null);
return baseResult;
}
}
@GetMapping("/findById")
public BaseResult<Brand> findById(Long id){
/**
* 测试异常处理器
*/
if (id == 0){
long i = 1/id;
} else if (id == -1 ) {
throw new MyException(ResultCode.PARAMETER_ERROR);
}
Brand brand = brandService.findById(id);
return BaseResult.ok(brand);
}
{
"code": 500,
"message": "系统异常",
"data": null
}
{
"code": 601,
"message": "参数异常",
"data": null
}