乐优商城(项目搭建+统一通用异常处理)(一)

技术特点

  • 高并发(分布式、静态化技术、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.2.2webpack四个核心概念

  • 入口
    webpack打包的启点,可以有一个或多个,一般是js文件。
    webpack会从启点文件开始,寻找启点直接或间接依赖的其它所有依赖,包括js、css、图片资源等,作为将来打包的原始数据。

2.3vue-cli

2.3.1介绍和安装

它是一个快速搭建vue项目的脚手架。
能快速的构建一个web工程模板。

npm install -g vue-cli

在这里插入图片描述

2.3.2快速上手

创建项目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

打开终端进入目录

在这里插入图片描述

快速搭建webpack项目

在这里插入图片描述
用键盘上下键选择模式
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在该文件中编写代码。
在这里插入图片描述
build:配置,会帮我们进行打包
config:index.js:配置端口
dist:打包好的目录,部署时候就是拷贝该目录下的文件到tomcat
node_module:所有的依赖
src:源码包
index.html:原始页面
pachage.json:依赖

运行

rpm run dev/start/build
在这里插入图片描述
在这里插入图片描述

2.4Vuetify框架

Vuetify是一个基于vue的UI框架,可以利用预定义的页面组件快速构建页面。有点类似bootstrap框架。
与vue吻合的UI框架也非常多,国内不叫知名的如:

  • element-ui:饿了么出品
  • iview
  • vuetify

2.4后台管理页面

2.4.1导入已有资源

链接:https://pan.baidu.com/s/1GvBwDdTvnbc24NrMwCxA6w
提取码:7v79
复制这段内容后打开百度网盘手机App,操作更方便哦

2.4.2运行一下

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 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 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

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

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 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

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);
    }
}

配置

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

添加:

<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 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中的

<dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-depencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.1.RELEASE</version>
            </plugin>
        </plugins>
    </build>

将ly-item-interface子项目install到本地maven仓库。

3.6.6编写启动类

包: com.leyou
类:LyItemApplication

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

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
复制工具类到该包中。
链接:https://pan.baidu.com/s/1hs1_yhES51DrjLYhYpJ_ng
提取码:fdu1

3.7.2添加依赖

打开cookiesUtils发现缺少依赖
在这里插入图片描述
ly-common的pom.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无非就是两件事情,序列化和反序列化。
序列化:把对象转成字符串。
反序列化:把字符串转成对象。

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

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

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风格的返回。

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,但是返回值没有任何提示。相应体是空的。
修改代码

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的代码,抛出异常
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

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中加入依赖:

 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>

ly-item-service的pom.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和返回消息。

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

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

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();

    }
}
修改异常处理拦截

当跑出异常时,就执行该类中的方法。

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),所以通过获得自定义类的枚举属性对象,获得状态。而相应体就是自定义的相应体类。

测试

在这里插入图片描述


版权声明:本文为weixin_43876557原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>