技术特点:
- 高并发(分布式、静态化技术、CDN服务、缓存技术、异步并发、池化、队列)
- 高可用(集群、负载均衡、限流、降级、熔断)
1.乐优商城介绍
1.1项目介绍
- 全品类的电商购物网站(B2C)。
- 用户可以在线购买商品、加入购物车、下单、秒杀商品。
- 可以评论已购买商品。
- 管理员可以在后台管理商品的上下架、促销活动。
- 管理员可以监控商品销售状况。
- 客服可以在后台处理退款操作。1.2系统架构1.2.1架构图
1.2.2系统架构解读
前端技术
npm:项目管理
webpack:项目打包和编译
vue.js:前端的主框架
vuetify:前端渲染,ui框架。
nuxt:前端的服务端渲染。
前端页面
页面从用户角度来看,可以分为两部分:后台管理、前台门户。
后台管理:
- 商品管理,保罗商品分类、品牌、商品规格等信息的管理。
- 销售管理。包括订单统计、订单退款处理,促销活动生成等。
- 用户管理,包括用户控制、冻结、解锁等。
- 权限管理,整个网站的权限控制,采用JWT鉴权方案,对用户及API进行权限控制。
- 统计,各种数据的统计分析展示。
- 后台系统会采用前后端分离开发,而整个后台管理系统会使用vue.js框架搭建出单页应用(SPA)。
前台门户:
- 前台门户面向的是客户,包括与客户交互的一切功能。
- 前台系统会使用nuxt(服务端渲染)结合vue完成页面开发。
后端微服务
无论是前台还是后台系统,都共享相同的微服务集群,包括:
- 商品微服务:商品及商品分类、品牌、库存等服务。
- 搜索微服务:实现搜索功能。
- 订单微服务:实现订单相关。
- 购物车微服务:实现购物车相关功能。
- 用户服务:用户的登录注册、用户信息管理等功能。
- 短信服务:完成各种短信的发送任务。
- 支付服务:对接各大支付平台。
- 授权服务:完成对用户的授权、鉴权等功能。
- Eureka注册中心。
- Zuul网关服务。
Spring Cloud Config
配置中心。- ···
2.商城管理系统前端页面
后端系统采用前后端分离开发,而且整个后台管理系统就会使用vue.js框架搭建出单页应用(SPA)。
2.1什么是SPA
Single Page Application
,即单应用,整个后台管理系统只会出现一个html页面。剩余一切页面的内容都是通过vue组件来实现。
这些vue组件其实就是许多的js文件。帮助搭建前端项目的工具有:webpack
、vue-cli
等。
2.2webpack
2.2.1介绍
webpack是一个前端资源的打包工具,它可以将js、image、css等资源当成一个模块进行打包。
export:导出
import:导入(文件的地址)
为什么要打包?
- 将许多岁碎小文件打包成一个整体,减少单页面内的衍生请求次数,提高网站效率。 一个页面会发出数百数千次请求,碎片文件多,后台压力大,所以把碎片文件打包成整体,这样加载的请求数就少了,提高了网站加载速率,减轻服务器负担。
- 将SE6的高级语法进行转换,变成浏览器可以识别的语言,以兼容老版本的浏览器。
- 将代码打包的同时进行混淆,提高代码的安全性。
2.3vue-cli
2.3.1介绍和安装
代码语言:bash复制npm install -g vue-cli
2.3.2快速上手
创建项目
打开终端进入目录
快速搭建webpack项目
build:配置,会帮我们进行打包
config:index.js:配置端口
dist:打包好的目录,部署时候就是拷贝该目录下的文件到tomcat
node_module:所有的依赖
src:源码包
index.html:原始页面
pachage.json:依赖
运行
代码语言:bash复制npm run dev/start/build
2.4Vuetify框架
Vuetify是一个基于vue的UI框架,可以利用预定义的页面组件快速构建页面。有点类似bootstrap框架。
与vue吻合的UI框架也非常多,国内不叫知名的如:
- element-ui:饿了么出品
- iview
- vuetify
2.4后台管理页面
2.4.1导入已有资源
2.4.2运行
代码语言:bash复制npm run dev
3.搭建基础服务
先准备后台微服务集群的基本架构
3.1技术选型
前端技术:
- 基础的html、css、javascript(基于ES6标准)。
- jquery
- vue.js2.0以及基于Vue的UI框架:Vuetify
- 前端构架工具:WebPack
- 前端安装包工具:NPM
- Vue脚手架:Vue-cli
- Vue路由:vue-router
- ajax框架:axios
- 基于Vue的富文本框架:quill-editor
后端技术:
- 基于的SpringMVC、Spring5.0和Mybatis3
- Spring Boot2.0.4版本
- Spring Cloud最新版Finchley.SR1
- Redis4.0
- RabbitMQ-3.1
- Elasticsearch-5.6.8
- nginx-1.10.2
- FastDFS-5.0.8
- MyCat
- Thymeleaf
- JWT
3.2开发环境
- IDE:idea
- JDK:统一使用JDK1.8.151
- 项目构建:maven3.3x以上版本即可
3.3域名
开发过程中,为了保证以后的生产,测试环境统一,尽量采用域名来访问项目
一级域名:www.leyou.com
二级域名:manage.leyou.com,api.leyou.com
可以通过switchhost工具来修改自己的host对应的地址,只要把这些域名指向127.0.0.1,那么跟用localhost的效果是一样的。
3.4搭建父工程
创建统一的父工程:leyou,用来管理依赖及其版本,注意创建project,而不是moudle。
引入依赖
代码语言:xml复制<?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.leyou.parent</groupId>
<artifactId>leyou</artifactId>
<packaging>pom</packaging>
<version>1.0.0-SNAPSHOT</version>
<name>leyou</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
<mybatis.starter.version>1.3.2</mybatis.starter.version>
<mapper.starter.version>2.0.3</mapper.starter.version>
<druid.starter.version>1.1.9</druid.starter.version>
<mysql.version>5.1.34</mysql.version>
<pageHelper.starter.version>1.2.5</pageHelper.starter.version>
<leyou.latest.version>1.0.0-SNAPSHOT</leyou.latest.version>
<fastDFS.client.version>1.26.1-RELEASE</fastDFS.client.version>
</properties>
<dependencyManagement>
<dependencies>
<!--springCloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--通用mapper启动器-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<!--分页助手启动器-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pageHelper.starter.version}</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--FastDFS客户端-->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>${fastDFS.client.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<modules>
<module>ly-registry</module>
<module>ly-gateway</module>
<module>ly-item</module>
</modules>
</project>
3.5搭建通用工程
new - module
3.5.1通用工程—注册中心
引入依赖
代码语言:xml复制<?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">
<parent>
<artifactId>leyou</artifactId>
<groupId>com.leyou.parent</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.common</groupId>
<artifactId>ly-registry</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--引入注册中心eureka-->
<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
创建启动类
包:com.leyou
类:LyRegistry
代码语言:java复制package com.leyou;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class LyRegistry {
public static void main(String[] args) {
SpringApplication.run(LyRegistry.class);
}
}
创建配置文件application.yml
配置eureka
代码语言:yml复制server:
port: 10086
spring:
application:
name: ly-registry
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
register-with-eureka: false #是否将自己注册到eureka服务中,本身是无需注册
fetch-registry: false #是否从eureka中获取注册信息
3.5.2通用工程—网关
引入依赖
代码语言:xml复制<?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">
<parent>
<artifactId>leyou</artifactId>
<groupId>com.leyou.parent</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.common</groupId>
<artifactId>ly-gateway</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--自身还要注册eureka和拉取服务列表,所以引入eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
编写启动类
包:com.leyou.gateway
类:LyGateway
代码语言:java复制package com.leyou.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class LyGateway {
public static void main(String[] args) {
SpringApplication.run(LyGateway.class);
}
}
配置
代码语言:yml复制server:
port: 10010
spring:
application:
name: api-gateway
zuul:
prefix: /api #添加路由前缀
routes:
item-service: /item/**
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000 #熔断超时时长:5000ms
ribbon:
ConnectTimeout: 1000 #ribbon连接超时时长
ReadTimeout: 3500 #ribbon读取超时时长
MaxAutoRetries: 0 #当前服务重试次数
MaxAutoRetriesNextServer: 0 #切换服务重试次数
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
3.6创建商品微服务
既然是一个全品类的电商购物平台,那么核心自然就是商品,因此我们要搭建的第一个服务,就是商品微服务。其中会包含对于商品相关的一系列内容的管理。包括:
- 商品分类管理
- 品牌管理
- 商品规格参数管理
- 商品管理
- 库存管理
它是一个聚合工程,所有要聚合工程套聚合工程。
3.6.1创建父工程ly-item
因为与商品的品类相关,工程名为:ly-item
ly-item是一个微服务,那么将来肯定会有其他系统需要来调用服务中提供的接口。因此肯定也会使用到接口中关联的实体类。
因此这里我们需要使用聚合工程,将要提供的接口及相关实体类放到独立子工程中。以后别人引用的时候,只需要知道坐标即可。
将回来ly-item中创建两个子工程:
- ly-item-interface:主要是相关实体类
- ly-item-service:所有业务逻辑及内容使用接口
修改pom.xml
添加:
代码语言:xml复制<packaging>pom</packaging>
3.6.2创建子工程ly-item-interface
在ly-item右键上新建module
3.6.3创建子工程ly-item-service
3.6.4整个工程架构
ly-item-service:将来写增删改查业务
ly-item-interface:写实体类
3.6.5添加依赖
ly-item-service需要什么?
- eureka客户端
- web启动器
- 通用mapper启动器
- 分页助手启动器
- 连接池,我们默认的Hykira,引入jdbc启动器
- mysql驱动
- 自己也需要ly-item-interface中的实体类
在ly-item-service工程的pom.xml中引入依赖:
代码语言:xml复制<?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">
<parent>
<artifactId>ly-item</artifactId>
<groupId>com.leyou.service</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.service</groupId>
<artifactId>ly-item-service</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<!--分页助手启动器-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.leyou.service</groupId>
<artifactId>ly-item-interface</artifactId>
<version>${leyou.latest.version}</version>
</dependency>
</dependencies>
</project>
坑(依赖问题)
当引入ly-item-service的依赖时,会报错。
此时注释掉leyou项目中pom.xml中的
代码语言:xml复制<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-depencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
将ly-item-interface子项目install到本地maven仓库。
3.6.6编写启动类
包: com.leyou
类:LyItemApplication
代码语言:java复制package com.leyou;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class LyItemApplication {
public static void main(String[] args) {
SpringApplication.run(LyItemApplication.class);
}
}
3.6.7配置application.yml
代码语言:yml复制server:
port: 8081
spring:
application:
name: item-service
datasource:
url: jdbc:mysql://localhost:3306/yum6
username: root
password: 123456
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
注意事项:
子工程中依赖不能加版本,它会依赖于父工程中的版本,否则会出现问题。
3.6.8启动测试
坑
spring-cloud和spring-boot包版本问题。
解决办法:所以上述父工程配置中修改了spring-cloud和spring-boot的版本。
坑
启动ly-item-service时失败,说没有配置数据源,命名在application.yml中配置了数据源信息,但是却加载失败。
解决办法:修改LyItemApplication中的注解为@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})。
3.7创建通用工程
3.7.1新建工具类
包:com.leyou.common
复制工具类到该包中。
3.7.2添加依赖
ly-common的pom.xml文件:
代码语言:xml复制<?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">
<parent>
<artifactId>leyou</artifactId>
<groupId>com.leyou.parent</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.common</groupId>
<artifactId>ly-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
</project>
坑
若是使用lomok注解不管用,需要idea安装lomok的插件才能使用。
3.7.3json工具类
json无非就是两件事情,序列化和反序列化。
序列化:把对象转成字符串。
反序列化:把字符串转成对象。
代码语言:java复制package com.leyou.common.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.istack.internal.Nullable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* @author: HuYi.Zhang
* @create: 2018-04-24 17:20
**/
public class JsonUtils {
public static final ObjectMapper mapper = new ObjectMapper();
private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);
@Nullable//该注解表示对应的值可以为空
//将对象序列化变成字符串=toString
public static String toString(Object obj) {
if (obj == null) {
return null;
}
if (obj.getClass() == String.class) {
return (String) obj;
}
try {
return mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
logger.error("json序列化出错:" obj, e);
return null;
}
}
@Nullable
public static <T> T toBean(String json, Class<T> tClass) {
try {
return mapper.readValue(json, tClass);
} catch (IOException e) {
logger.error("json解析出错:" json, e);
return null;
}
}
@Nullable
public static <E> List<E> toList(String json, Class<E> eClass) {
try {
return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass));
} catch (IOException e) {
logger.error("json解析出错:" json, e);
return null;
}
}
@Nullable
public static <K, V> Map<K, V> toMap(String json, Class<K> kClass, Class<V> vClass) {
try {
return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));
} catch (IOException e) {
logger.error("json解析出错:" json, e);
return null;
}
}
//对复杂的对象使用,将字符串转换成对象。
@Nullable
public static <T> T nativeRead(String json, TypeReference<T> type) {
try {
return mapper.readValue(json, type);
} catch (IOException e) {
logger.error("json解析出错:" json, e);
return null;
}
}
//使用这个注解,就不用再去手写Getter,Setter,equals,canEqual,hasCode,toString等方法了,注解后在编译时会自动加进去。
@Data
//使用后创建一个无参构造函数
@NoArgsConstructor
//使用后添加一个构造函数,该构造函数含有所有已声明字段属性参数
@AllArgsConstructor
static class User{
String name;
Integer age;
}
/* public static void main(String[] args) {
User user = new User("jack",21);
//序列化
String json = toString(user);
System.out.println(json);
//反序列化
User user1 = toBean(json,User.class);
System.out.println(user1);
//toList
json = "[20,10,5,0]";
toList(json,Integer.class);
System.out.println(json);
//toMap
json = "{"name":"jack","age":21}";
Map<String, Object> stringObjectMap = toMap(json, String.class, Object.class);
System.out.println(json);
json = "[{"name":"jack","age":21},{"name":"rose","age":20}]";
List<Map<String, Object>> maps = nativeRead(json, new TypeReference<List<Map<String, Object>>>() {
});
for (Map<String,Object> map:maps){
System.out.println(map);
}
}*/
}
4.通用异常处理
4.1场景预设
4.1.1场景
我们预设这样一个场景,加入我们新增商品,需要接受下面的参数:
- price:价格
- name:名称
- 然后对数据做简单校验:
- 价格不能为空
- 新增时,自动形成id,然后随商品对象一起返回。
4.1.2代码
这些代码都是为了演示而用,成功后代码要被删掉。
实体类
ly-item-interface
包:com.leyou.item.pojo
代码语言:java复制package com.leyou.item.pojo;
import lombok.Data;
@Data
public class Item {
private Integer id;
private String name;
private Long price;
}
service
ly-item-service
包:com.leyou.item.service
代码语言:java复制package com.leyou.item.service;
import com.leyou.item.pojo.Item;
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class ItemService {
public Item saveItem(Item item){
int id = new Random().nextInt(100);
item.setId(id);
return item;
}
}
Controller
包:com.leyou.item.web
用Rest风格的返回。
代码语言:java复制package com.leyou.item.web;
import com.leyou.item.pojo.Item;
import com.leyou.item.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("item")
public class ItemController {
@Autowired
private ItemService itemService;
@PostMapping
// @ResponseBody把java对象序列化,放到相应体里。
public ResponseEntity<Item> saveItem(Item item){
//校验价格
if(item.getPrice() == null){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
Item items = itemService.saveItem(item);
return ResponseEntity.status(HttpStatus.CREATED).body(item);
}
}
4.1.3统一异常处理
4.1.3.1初步测试
使用insomnia.setup工具测试
4.1.3.2问题分析
当填写正确的item时,返回正常。当参数存在问题时,状态码400,但是返回值没有任何提示。相应体是空的。
修改代码
代码语言:java复制package com.leyou.item.web;
import com.leyou.item.pojo.Item;
import com.leyou.item.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("item")
public class ItemController {
@Autowired
private ItemService itemService;
@PostMapping
// @ResponseBody把java对象序列化,放到相应体里。
public ResponseEntity<Item> saveItem(Item item){
//校验价格
if(item.getPrice() == null){
// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("价格不能为空");
throw new RuntimeException("价格不能为空");
}
Item items = itemService.saveItem(item);
return ResponseEntity.status(HttpStatus.CREATED).body(item);
}
}
再运行,SpringMVC拦截帮我们处理异常,处理成500。
异常不能由SpringMVC帮我们处理,应该我们自己处理。
4.1.3.3统一异常处理
修改Controller的代码,抛出异常
代码语言:java复制package com.leyou.item.web;
import com.leyou.item.pojo.Item;
import com.leyou.item.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("item")
public class ItemController {
@Autowired
private ItemService itemService;
@PostMapping
// @ResponseBody把java对象序列化,放到相应体里。
public ResponseEntity<Item> saveItem(Item item){
//校验价格
if(item.getPrice() == null){
throw new RuntimeException("价格不能为空");
}
Item items = itemService.saveItem(item);
return ResponseEntity.status(HttpStatus.CREATED).body(item);
}
}
建统一异常拦截器
使用SpringMVC提供的统一异常拦截器,因为是统一处理,所以放到ly-common项目中。
包:com.leyou.common.advice
代码语言:java复制package com.leyou.common.advice;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
//通用异常处理
@ControllerAdvice//会自动拦截所有的controller
public class CommonExceptionHandler {
//处理异常,方法返回值就是将来要返回到页面的东西,不同方法些不同的
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleException(RuntimeException r){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(r.getMessage());
}
}
@ControllerAdvice:默认情况下,会拦截所有加了@Controller的类
@ExceptionHandler(RuntimeException.class):作用在方法上,声明要处理的异常类型,可以有多个,这里指定的是RuntimeException。被声明的方法可以看做是一个SpringMVC的handler。
- 参数是要处理的异常,类型必须匹配。
- 返回结果可以使ModleAndView、ResponseEntity等。基本与handler类似。
这里等于重新等一了返回结果,我们可以随意指定想要的返回类型。
引入依赖
ly-common的pom.xml中加入依赖:
代码语言:xml复制 <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
ly-item-service的pom.xml中加入依赖:
代码语言:xml复制<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>ly-common</artifactId>
<version>${leyou.latest.version}</version>
</dependency>
重启测试
4.1.3.4自定义异常的使用
定义枚举
在ly-common项目中编写枚举类
包:com.leyou.common.enums
定义一个枚举,用来统一code和返回消息。
代码语言:java复制package com.leyou.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@AllArgsConstructor //枚举里的构造函数默认是私有的。private static final
@NoArgsConstructor
public enum ExceptionEnum {
PRICE_CONNOT_BE_NULL(400,"价格不能为空!")
;
private int code;
private String msg;
}
新建自定义类
自定义类,在类中使用枚举类作为属性
在ly-common项目中
包:com.leyou.common.exception
代码语言:java复制package com.leyou.common.exception;
import com.leyou.common.enums.ExceptionEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class LyException extends RuntimeException {
private ExceptionEnum exceptionEnum;
}
定义返回格式
觉得返回东西太简单,可以定义一个返回类型,这样返回值就为它。
在ly-common项目中
包:com.leyou.common.vo
代码语言:java复制package com.leyou.common.vo;
import com.leyou.common.enums.ExceptionEnum;
import lombok.Data;
@Data
public class ExceptionResult {
private int status;
private String message;
private Long timestamp;
public ExceptionResult(ExceptionEnum em){
this.status = em.getCode();
this.message = em.getMsg();
this.timestamp = System.currentTimeMillis();
}
}
修改异常处理拦截
当跑出异常时,就执行该类中的方法。
代码语言:java复制package com.leyou.common.advice;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.vo.ExceptionResult;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
//通用异常处理
@ControllerAdvice//会自动拦截所有的controller
public class CommonExceptionHandler {
//处理异常,方法返回值就是将来要返回到页面的东西,不同方法些不同的
@ExceptionHandler(LyException.class)
public ResponseEntity<ExceptionResult> handleException(LyException r){
ExceptionEnum em = r.getExceptionEnum();
return ResponseEntity.status(em.getCode()).body(new ExceptionResult(r.getExceptionEnum()));
}
}
当抛出LyException异常时(因为该异常时自定义的,所以不会重复,抛出该异常,直接经过该类的拦截,进入该方法),执行该方法,返回的相应体是自定义的ExceptionResult,所以泛型是ExceptionResult。由于返回相应体中要写状态,(而枚举类中写的有状态和消息,枚举类又是自定义异常的属性,所以参数为自定义异常类LyException),所以通过获得自定义类的枚举属性对象,获得状态。而相应体就是自定义的相应体类。
测试