2022-05-27  2022-05-27    11485 字   23 分钟

第三章

东易买后端微服务框架搭建

优就业.JAVA教研室

学习目标

目标1:了解电商

目标2:了解东易买架构

目标3:了解东易买工程结构

目标4:掌握东易买工程搭建

目标5:掌握商品微服务搭建

目标6:掌握品牌的增删改查

目标7:了解FastDFS工作流程

目标8:搭建文件上传微服务

一、走进电商

1. 电商行业分析

​ 近年来,中国的电子商务快速发展,交易额连创新高,电子商务在各领域的应用不断拓展和深化、相关服务业蓬勃发展、支撑体系不断健全完善、创新的动力和能力 不断增强。电子商务正在与实体经济深度融合,进入规模性发展阶段,对经济社会生活的影响不断增大,正成为我国经济发展的新引擎。

中国电子商务研究中心数据显示,截止到 2019 年底,中国电子商务市场交易规模达 34.81万亿人民币,同比增长6.7%。其中,B2B市场交易规模达到25.94万亿元,同比增长 20%以上。商品,服务类电商交易额33.76万亿元,增长6.6%;合约类电商交易额1.05万亿元,增长10.1%,

双十一交易额

2.电商行业技术特点

  • 技术新
  • 技术范围广
  • 分布式
  • 高并发、集群、负载均衡、高可用
  • 海量数据
  • 业务复杂
  • 系统安全

3.主要电商模式

3.1 B2B–企业对企业

​ B2B ( Business to Business)是指进行电子商务交易的供需双方都是商家(或企业、公司),她(他)们使用了互联网的技术或各种商务网络平台,完成商务交易的过程。电子商务是现代 B2B marketing的一种具体主要的表现形式。 案例:阿里巴巴、慧聪网

3.2 C2C–个人对个人

​ C2C即 Customer(Consumer) to Customer(Consumer),意思就是消费者个人间的电子商务行为。比如一个消费者有一台电脑,通过网络进行交易,把它出售给另外一个消费者,此种交易类型就称为C2C电子商务。

案例:淘宝、易趣、瓜子二手车

3.3 B2C–企业对个人

​ B2C是Business-to-Customer的缩写,而其中文简称为“商对客”。“商对客”是电子商务的一种模式,也就是通常说的直接面向消费者销售产品和服务商业零售模式。这种形式的电子商务一般以网络零售业为主,主要借助于互联网开展在线销售活动。B2C即企业通过互联网为消费者提供一个新型的购物环境——网上商店,消费者通过网络在网上购物、网上支付等消费行为。

案例:唯品会、乐蜂网

3.4 C2B–个人对企业

​ C2B(Consumer to Business,即消费者到企业),是互联网经济时代新的商业模式。这一模式改变了原有生产者(企业和机构)和消费者的关系,是一种消费者贡献价值(Create Value), 企业和机构消费价值(Consume Value)。

​ C2B模式和我们熟知的供需模式(DSM, Demand Supply Model)恰恰相反,真正的C2B 应该先有消费者需求产生而后有企业生产,即先有消费者提出需求,后有生产企业按需求组织生产。通常情况为消费者根据自身需求定制产品和价格,或主动参与产品设计、生产和定价,产品、价格等彰显消费者的个性化需求,生产企业进行定制化生产。

案例:海尔商城、 尚品宅配

3.5 O2O–线上到线下

​ O2O即Online To Offline(在线离线/线上到线下),是指将线下的商务机会与互联网结合,让互联网成为线下交易的平台,这个概念最早来源于美国。O2O的概念非常广泛,既可涉及到线上,又可涉及到线下,可以通称为O2O。主流商业管理课程均对O2O这种新型的商业模式有所介绍及关注。 案例:美团、饿了吗

3.6 F2C–工厂到个人

​ F2C指的是Factory to customer,即从厂商到消费者的电子商务模式。

3.7 B2B2C -企业-企业-个人

​ B2B2C是一种电子商务类型的网络购物商业模式,B是BUSINESS的简称,C是CUSTOMER的简称,第一个B指的是商品或服务的供应商,第二个B指的是从事电子商务的企业,C则是表示消费者。  第一个BUSINESS,并不仅仅局限于品牌供应商、影视制作公司和图书出版商,任何的商品供应商或服务供应商都能可以成为第一个BUSINESS;第二B是B2B2C模式的电子商务企业,通过统一的经营管理对商品和服务、消费者终端同时进行整合,是广大供应商和消费者之间的桥梁,为供应商和消费者提供优质的服务,是互联网电子商务服务供应商。C表示消费者,在第二个B构建的统一电子商务平台购物的消费者;  B2B2C的来源于目前的B2B、B2C模式的演变和完善,把B2C和C2C完美地结合起来,通过B2B2C模式的电子商务企业构建自己的物流供应链系统,提供统一的服务。

案例:京东商城、天猫商城

二、东易买- 需求分析与系统设计

2.1 东易买简介

东易买网上商城是一个综合性的 B2B2C 平台,类似京东商城、天猫商城。网站采用商家入驻的模式,商家入驻平台提交申请,有平台进行资质审核,审核通过后,商家拥有独立的管理后台录入商品信息。商品经过平台审核后即可发布。用户可以在线购买商品、加入购物车、下单、秒杀商品,可以评论已购买商品

客服可以在后台处理退款操作。

东易买网上商城主要分为网站前台、运营商后台、商家管理后台三个子系统。

希望未来3到5年可以支持千万用户的使用

2.2 网站前台

主要包括网站首页、商家首页、商品详细页、搜索页、会员中心、订单与支付相关页面、秒杀频道等。

2.3 运营商后台

是运营商的运营人员的管理后台。 主要包括商家审核、品牌管理、规格管理、模板管理、商品分类管理、商品审核、广告类型管理、广告管理、订单查询、商家结算等。

2.4 商家管理后台

入驻的商家进行管理的后台,主要功能是对商品的管理以及订单查询统计、资金结算等功能。

2.5 系统设计

2.5.1 前后端分离

网站后台的部分采用前后端分离方式。

以前的JavaWeb项目大多数都是java程序员又当爹又当妈,又搞前端,又搞后端。随着时代的发展,渐渐的许多大中小公司开始把前后端的界限分的越来越明确,前端工程师只管前端的事情,后端工程师只管后端的事情。正所谓术业有专攻,一个人如果什么都会,那么他毕竟什么都不精。

对于后端Java工程师:

把精力放在设计模式,spring+springmvc,linux,mysql事务隔离与锁机制,mongodb,http/tcp,多线程,分布式架构,弹性计算架构,微服务架构,java性能优化,以及相关的项目管理等等。

对于前端工程师:

把精力放在html5,css3,vuejs,webpack,nodejs,Google V8引擎,javascript多线程,模块化,面向切面编程,设计模式,浏览器兼容性,性能优化等等。

我们在本课程中提供与项目课程配套的管理后台的前端代码,但是不讲解前端的内容。这样我们会将更多的精力放在后端代码的开发上!

2.5.2 功能架构

2.5.3 技术架构图

三、 东易买-框架搭建

3.1 数据库表结构

表名称 含义
tb_brand 品牌
tb_specification 规格
tb_specification_option 规格选项
tb_type_template 类型模板:用于关联品牌和规格
tb_item_cat 商品分类
tb_seller 商家
tb_goods 商品
tb_goods_desc 商品详情
tb_item 商品明细
tb_content 内容(广告)
tb_content_category 内容(广告)类型

3.2 项目结构说明

结构说明:

dongyimai-gateway

网关模块,根据网站的规模和需要,可以将综合逻辑相关的服务用网关路由组合到一起。在这里还可以做鉴权和限流相关操作。

dongyimai-service

微服务模块,该模块用于存放所有独立的微服务工程。

dongyimai-service_api

对应工程的JavaBean、Feign、以及Hystrix配置,该工程主要对外提供依赖。

dongyimai-transaction-fescar

分布式事务模块,将分布式事务抽取到该工程中,任何工程如需要使用分布式事务,只需依赖该工程即可。

dongyimai-web

web服务工程,对应功能模块如需要调用多个微服务,可以将他们写入到该模块中,例如网站后台、网站前台等

3.3 公共工程搭建

3.3.1 父工程搭建

创建父工程 dongyimai-parent ,用来管理依赖及其版本,注意是创建project,而不是moudle

填写项目信息:

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.offcn</groupId>
    <artifactId>dongyimai-parent</artifactId>
    <version>1.0</version>
    <name>dongyimai-parent</name>
    <description>Demo project for Spring Boot</description>
    <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>Hoxton.RELEASE</spring-cloud.version>
        <swagger.version>2.9.2</swagger.version>
        <fastjson.version>1.2.51</fastjson.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>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!--swagger-->
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>
    </dependencies>

</project>

可以发现,我们在父工程中引入了SpringCloud等很多以后需要用到的依赖,以后创建的子工程就不需要自己引入了。

然后删除一下文件及目录

3.3.2 其他公共模块搭建

创建dongyimai-gateway、dongyimai-service、dongyimai-service-api、dongyimai-web工程,直接创建Maven模块,工程全部为pom工程,并将所有工程的src文件删除。

pom.xml中打pom包

<packaging>pom</packaging>

项目结构如下:

3.4 Eureka微服务搭建

这个大家应该比较熟悉了。

我们的注册中心,起名为:dongyimai-eureka

直接创建maven项目模块,继承父类的依赖:

选择新建module:

选择maven安装,但是不要选择骨架:

然后填写模块名称为dongyimai-eureka:

3.4.1 pom.xml依赖

创建模块dongyimai-eureka ,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>dongyimai-parent</artifactId>
        <groupId>com.offcn</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dongyimai-eureka</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

</project>

3.4.2 appliation.yml配置

创建配置文件application.yml

server:
  port: 8761
spring:
  application:
    name: eureka
eureka:
  instance:
    hostname: 127.0.0.1
  client:
    register-with-eureka: false   #是否将自己注册到eureka中
    fetch-registry: false         #是否从eureka中获取信息
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
  server:
    enable-self-preservation: false # 关闭自我保护
    eviction-interval-timer-in-ms: 5000 # 每隔5秒进行一次服务列表清理

3.4.3 启动类配置

创建包com.offcn包下创建启动类EurekaApplication,代码如下:

上图代码如下:

@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class,args);
    }
}

测试访问http://localhost:8761/,效果如下:

3.5 公共模块搭建

3.5.1 pom.xml依赖

创建公共子模块dongyimai-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>dongyimai-parent</artifactId>
        <groupId>com.offcn</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dongyimai-common</artifactId>

    <dependencies>
        <!--web起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- redis 使用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.2.1</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-util</artifactId>
            <version>9.3.7.v20160115</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.5</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

公共子模块引入这些依赖后,其他微服务引入dongyimai-common后也自动引入了这些依赖

3.5.2 常用对象

创建com.offcn.entity包 ,在entity包下创建返回状态码实体类

/**
 * 返回码
 */
public class StatusCode {
    public static final int OK = 20000;//成功
    public static final int ERROR = 20001;//失败
    public static final int LOGINERROR = 20002;//用户名或密码错误
    public static final int ACCESSERROR = 20003;//权限不足
    public static final int REMOTEERROR = 20004;//远程调用失败
    public static final int REPERROR = 20005;//重复操作
    public static final int NOTFOUNDERROR = 20006;//没有对应的抢购数据
}

com.offcn.entity包下建立类Result用于微服务返回结果给前端

/**
 * 返回结果实体类
 */
public class Result<T> {
    private boolean flag;//是否成功
    private Integer code;//返回码
    private String message;//返回消息
    private T data;//返回数据

    public Result(boolean flag, Integer code, String message, T data) {
        this.flag = flag;
        this.code = code;
        this.message = message;
        this.data =  data;
    }

    public Result(boolean flag, Integer code, String message) {
        this.flag = flag;
        this.code = code;
        this.message = message;
    }

    public Result() {
        this.flag = true;
        this.code = StatusCode.OK;
        this.message = "操作成功!";
    }

    // getter and setter.....
}

在entity包下建立类用于承载分页的数据结果

/**
 * 分页结果类
 */
public class PageResult<T> {

    private Long total;//总记录数
    private List<T> rows;//记录

    public PageResult(Long total, List<T> rows) {
        this.total = total;
        this.rows = rows;
    }

    public PageResult() {
    }

    //getter and setter ......
}

当然,我们还可以将其他工具类都一起倒入到工程中,以后会用到,将资料\工具类中的所有类直接导入到entity包下。

3.6 数据访问工程搭建

创建公共模块dongyimai-common-db ,pom文件引入依赖

<?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>dongyimai-parent</artifactId>
        <groupId>com.offcn</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dongyimai-common-db</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.offcn</groupId>
            <artifactId>dongyimai-common</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.14</version>
        </dependency>
        <!-- MyBatisPlus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
    </dependencies>

</project>

这个公共模块是连接数据库的公共微服务模块,所有需要连接数据库的微服务都继承自此工程。

3.7 商品微服务搭建

商品微服务主要是实现对商品的增删改查相关操作,以及商品相关信息的增删改查。

3.7.1 公共组件工程搭建

创建dongyimai-service-api子模块dongyimai-sellergoods-service-api,并将资料\javabean\dongyimai-service-goods-api中的Pojo导入到工程中。

修改父工程dongyimai-service-api的pom.xml,代码如下:

    <dependencies>
        <dependency>
            <groupId>com.offcn</groupId>
            <artifactId>dongyimai-common-db</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

3.7.2 微服务工程搭建

在dongyimai-service中创建dongyimai-sellergoods-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>dongyimai-service</artifactId>
        <groupId>com.offcn</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dongyimai-sellergoods-service</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.offcn</groupId>
            <artifactId>dongyimai-sellergoods-service-api</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

</project>

在resources下创建配置文件application.yml

server:
  port: 9001
spring:
  application:
    name: dym-sellergoods
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/dongyimaidb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
    lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期
    prefer-ip-address: true
    ip-address: 127.0.0.1
    instance-id: ${spring.application.name}:${server.port}
feign:
  hystrix:
    enabled: true
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true  #开启驼峰式编写规范
  type-aliases-package: com.offcn.sellergoods.pojo
# 配置sql打印日志
logging:
  level:
    com:
      offcn: debug

在包com.offcn.sellergoods 包下创建启动类SellerGoodsApplication,代码如下:

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.offcn.sellergoods.dao")
public class SellerGoodsApplication {
    public static void main(String[] args) {
        SpringApplication.run(SellerGoodsApplication.class);
    }
}

启动dongyimai-sellergoods-service 再访问<http://localhost:8761/>效果如下:

四 商品微服务-品牌增删改查

4.1 需求分析

创建商品微服务,实现对品牌表的增删改查功能。具体包括

(1)查询全部列表数据

(2)根据ID查询实体数据

(3)增加

(4)修改

(5)删除

(6)条件查询

(7)分页查询

(8)分页+条件查询

4.2 表结构分析

品牌表:tb_brand

字段名称 字段含义 字段类型 字段长度 备注
id 品牌id INT
name 品牌名称 VARCHAR
image 品牌图片地址 VARCHAR
first_char 品牌的首字母 CHAR

4.3 代码实现

上面品牌表对应Brand实体类

@TableName(value="tb_brand")
public class Brand implements Serializable{

    @TableId(type = IdType.AUTO)
    @TableField(value = "id")
	private Long id;//

    @TableField(value = "name")
	private String name;//品牌名称

    @TableField(value = "first_char")
	private String firstChar;//品牌首字母

    @TableField(value = "image")
	private String image;//品牌图像
	
	// getter and setter  .....(省略)
}

注解均使用MyBatisPlus的注解

4.3.1 品牌列表

(1)Dao创建

在dongyimai-sellegoods-service微服务下创建com.offcn.goods.dao.BrandMapper接口,代码如下:

public interface BrandMapper extends BaseMapper<Brand> {
}

继承了Mapper接口,就自动实现了增删改查的常用方法。

(2)业务层

创建com.offcn.sellergoods.service.BrandService接口,代码如下:

public interface BrandService {

    /***
     * 查询所有品牌
     * @return
     */
    List<Brand> findAll();
}

创建com.offcn.sellergoods.service.impl.BrandServiceImpl实现类,代码如下:

@Service
public class BrandServiceImpl extends ServiceImpl<BrandMapper, Brand> implements BrandService {

    /**
     * 全部数据
     * @return
     */
    public List<Brand> findAll(){
        return this.list();
    }
}

(3)控制层

控制层 com.offcn.sellergoods包下创建controller包 ,包下创建类

@RestController
@RequestMapping("/brand")
@CrossOrigin
public class BrandController {

    @Autowired
    private BrandService brandService;

    /***
     * 查询全部数据
     * @return
     */
    @GetMapping //不写value值默认调用根路径
    public Result<List<Brand>> findAll(){
        List<Brand> brandList = brandService.findAll();
        return new Result<List<Brand>>(true, StatusCode.OK,"查询成功",brandList) ;
    }
}

测试:http://localhost:9001/brand

4.3.2 根据ID查询品牌

(1)业务层

修改com.offcn.sellergoods.service.BrandService接口,添加根据ID查询品牌数据方法,代码如下:

/**
 * 根据ID查询
 * @param id
 * @return
 */
Brand findById(Integer id);

修改com.offcn.sellergoods.service.impl.BrandServiceImpl新增方法,代码如下:

    /**
     * 根据ID查询Brand
     * @param id
     * @return
     */
    @Override
    public Brand findById(Long id){
        return  this.getById(id);
    }

(2)控制层

BrandController新增方法

    /***
     * 根据ID查询Brand数据
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Result<Brand> findById(@PathVariable Long id){
        //调用BrandService实现根据主键查询Brand
        Brand brand = brandService.findById(id);
        return new Result<Brand>(true,StatusCode.OK,"查询成功",brand);
    }

测试:<http://localhost:9001/brand/1

4.3.3 新增品牌

(1)业务层

修改com.offcn.sellergoods.service.BrandService,新增方法

/***
 * 新增品牌
 * @param brand
 */
void add(Brand brand);

修改com.offcn.sellergoods.service.impl.BrandServiceImpl,新增增加品牌方法代码如下:

    /**
     * 增加Brand
     * @param brand
     */
    @Override
    public void add(Brand brand){
        this.save(brand);
    }

(2) 控制层

BrandController新增方法

    /***
     * 新增Brand数据
     * @pa am brand
     * @return
     */
    @PostMapping
    public Result add(@RequestBody   Brand brand){
        //调用BrandService实现添加Brand
        brandService.add(brand);
        return new Result(true,StatusCode.OK,"添加成功");
    }

测试:http://localhost:9001/brand

4.3.4 修改品牌

(1)业务层

需改com.offcn.sellergoods.service.BrandService,添加修改品牌方法,代码如下:

/***
 * 修改品牌数据
 * @param brand
 */
void update(Brand brand);

修改com.offcn.sellergoods.service.impl.BrandServiceImpl,添加修改品牌方法,代码如下:

    /**
     * 修改Brand
     * @param brand
     */
    @Override
    public void update(Brand brand){
        this.updateById(brand);
    }

(2)控制层

BrandController新增方法

    /***
     * 修改Brand数据
     * @param brand
     * @param id
     * @return
     */
    @PutMapping(value="/{id}")
    public Result update(@RequestBody  Brand brand,@PathVariable Long id){
        //设置主键值
        brand.setId(id);
        //调用BrandService实现修改Brand
        brandService.update(brand);
        return new Result(true,StatusCode.OK,"修改成功");
    }

测试:http://localhost:9001/brand/35

4.3.5 删除品牌

(1)业务层

修改com.offcn.sellergoods.service.BrandService,添加删除品牌方法,代码如下:

/***
 * 删除品牌
 * @param id
 */
void delete(Long id);

修改com.offcn.sellergoods.service.impl.BrandServiceImpl,新增删除品牌方法,代码如下:

    /**
     * 删除
     * @param id
     */
    @Override
    public void delete(Long id){
        this.removeById(id);
    }

(2)控制层

BrandController新增方法

    /***
     * 根据ID删除品牌数据
     * @param id
     * @return
     */
    @DeleteMapping(value = "/{id}" )
    public Result delete(@PathVariable Long id){
        //调用BrandService实现根据主键删除
        brandService.delete(id);
        return new Result(true,StatusCode.OK,"删除成功");
    }

测试:http://localhost:9001/brand/35

4.3.6 品牌列表条件查询

(1)业务层

修改com.offcn.sellergoods.service.BrandService,增加根据条件搜索品牌方法,代码如下:

/***
 * 多条件搜索品牌方法
 * @param brand
 * @return
 */
List<Brand> findList(Brand brand);

修改com.offcn.sellergoods.service.impl.BrandServiceImpl,添加根据多条件搜索品牌方法的实现,代码如下:

    /**
     * Brand条件查询
     * @param brand
     * @return
     */
    @Override
    public List<Brand> findList(Brand brand){
        //构建查询条件
        QueryWrapper<Brand> queryWrapper = this.createQueryWrapper(brand);
        //根据构建的条件查询数据
        return this.list(queryWrapper);
    }


    /**
     * Brand构建查询对象
     * @param brand
     * @return
     */
    private QueryWrapper<Brand> createQueryWrapper(Brand brand){
        QueryWrapper<Brand> queryWrapper = new QueryWrapper<>();
        if(brand!=null){
            // 
            if(brand.getId()!=null){
                 queryWrapper.eq("id",brand.getId());
            }
            // 品牌名称
            if(!StringUtils.isEmpty(brand.getName())){
                queryWrapper.like("name",brand.getName());
            }
            // 品牌首字母
            if(!StringUtils.isEmpty(brand.getFirstChar())){
                 queryWrapper.eq("first_char",brand.getFirstChar());
            }
            // 品牌图像
            if(!StringUtils.isEmpty(brand.getImage())){
                 queryWrapper.eq("image",brand.getImage());
            }
        }
        return queryWrapper;
    }

(2) 控制层

BrandController新增方法

/***
 * 多条件搜索品牌数据
 * @param brand
 * @return
 */
@PostMapping(value = "/search" )
public Result<List<Brand>> findList(@RequestBody(required = false) Brand brand){
    List<Brand> list = brandService.findList(brand);
    return new Result<List<Brand>>(true,StatusCode.OK,"查询成功",list);
}

测试:http://localhost:9001/brand/search

4.3.7 品牌列表分页查询

(1)业务层

修改com.offcn.sellergoods.service.BrandService添加分页方法,代码如下:

/***
 * 分页查询
 * @param page
 * @param size
 * @return
 */
PageResult<Brand> findPage(int page, int size);

修改com.offcn.sellergoods.service.impl.BrandServiceImpl添加分页方法实现,代码如下:

    /**
     * Brand分页查询
     * @param page
     * @param size
     * @return
     */
    @Override
    public PageResult<Brand> findPage(int page, int size){
       //分页参数封装对象
        Page<Brand> brandPage = new Page<>(page, size);
        //发出分页查询
        Page<Brand> iPage = this.page(brandPage);
        return new PageResult<Brand>(iPage.getTotal(),iPage.getRecords());
    }

(2)控制层

BrandController新增方法

    /***
     * Brand分页搜索实现
     * @param page:当前页
     * @param size:每页显示多少条
     * @return
     */
    @GetMapping(value = "/search/{page}/{size}" )
    public Result<PageResult<Brand>> findPage(@PathVariable  int page, @PathVariable  int size){
        //调用BrandService实现分页查询Brand
        PageResult<Brand> pageResult = brandService.findPage(page, size);
        return new Result<PageResult<Brand>>(true,StatusCode.OK,"查询成功",pageResult);
    }

(3)在com.offcn.sellergoods.config包下新增MyBatisPlus分页插件,PageConfig.java类

注意:3.4.1版本及以上使用MybatisPlusInterceptor进行配置

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new        PaginationInnerInterceptor();
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        //设置请求的页面大于最大页后操作,true调回到首页,false继续请求 默认false
        paginationInnerInterceptor.setOverflow(true);
        //设置最大单页限制数量,默认 500 条, -1 不受限制
        paginationInnerInterceptor.setMaxLimit(500L);

        interceptor.addInnerInterceptor(paginationInnerInterceptor);

        return interceptor;
    }

测试:http://localhost:9001/brand/search/1/3

4.3.8 品牌列表条件+分页查询

(1)业务层

修改com.offcn.sellergoods.service.BrandService,增加多条件分页查询方法,代码如下:

/***
 * 多条件分页查询
 * @param brand
 * @param page
 * @param size
 * @return
 */
PageResult<Brand> findPage(Brand brand, int page, int size);

修改com.offcn.sellergoods.service.impl.BrandServiceImpl,添加多条件分页查询方法代码如下:

    /**
     * Brand条件+分页查询
     * @param brand 查询条件
     * @param page 页码
     * @param size 页大小
     * @return 分页结果
     */
    @Override
    public PageResult<Brand> findPage(Brand brand, int page, int size){
         Page<Brand> mypage = new Page<>(page, size);
        QueryWrapper<Brand> queryWrapper = this.createQueryWrapper(brand);
        IPage<Brand> iPage = this.page(mypage, queryWrapper);
        return new PageResult<Brand>(iPage.getTotal(),iPage.getRecords());
    }

(2)控制层

BrandController新增方法

/***
 * 分页搜索实现
 * @param brand
 * @param page
 * @param size
 * @return
 */
@PostMapping(value = "/search/{page}/{size}" )
public Result<PageResult> findPage(@RequestBody(required = false) Brand brand, @PathVariable  int page, @PathVariable  int size){
    //执行搜索
    PageResult<Brand> pageResult = brandService.findPage(brand, page, size);
    return new Result(true,StatusCode.OK,"查询成功",pageResult);
}

测试:http://localhost:9001/brand/search/1/3

五、FastDFS分布式文件服务器

5.1 FastDFS简介

5.1.1 FastDFS体系结构

FastDFS是用 c 语言编写的一款开源的分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。

FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。

FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过Tracker server 调度最终由 Storage server 完成文件上传和下载。

Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。

Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storageserver 没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将storage称为存储服务器。

5.1.2 上传流程

客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

组名:文件上传后所在的 storage 组名称,在文件上传成功后有storage 服务器返回,需要客户端自行保存。

虚拟磁盘路径:storage 配置的虚拟路径,与磁盘选项store_path*对应。如果配置了

store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。

数据两级目录:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据

文件。

文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储

服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

5.1.3 文件下载流程

5.2 FastDFS搭建

5.2.1 安装FastDFS镜像

我们使用Docker搭建FastDFS的开发环境,虚拟机中已经下载了fastdfs的镜像,可以通过docker images查看,如下图:

拉取镜像(已经下载了该镜像,大家无需下载了)

docker pull morunchang/fastdfs

运行tracker

docker run -d --name tracker --net=host morunchang/fastdfs sh tracker.sh

运行storage

docker run -d --name storage --net=host -e TRACKER_IP=192.168.100.20:22122 -e GROUP_NAME=group1 morunchang/fastdfs sh storage.sh
  • 使用的网络模式是–net=host, 192.168.188.128是宿主机的IP
  • group1是组名,即storage的组
  • 如果想要增加新的storage服务器,再次运行该命令,注意更换 新组名

5.2.2 配置Nginx

Nginx在这里主要提供对FastDFS图片访问的支持,Docker容器中已经集成了Nginx,我们需要修改nginx的配置,进入storage的容器内部,修改nginx.conf

docker exec -it storage  /bin/bash

进入后

vi /etc/nginx/conf/nginx.conf

添加以下内容

location ~ /M00 {
     root /data/fast_data/data;
     ngx_fastdfs_module;
}

设置禁止浏览器缓存:

add_header Cache-Control no-store;

退出容器

exit

重启storage容器

docker restart storage

查看启动容器docker ps -a

开启启动设置,docker启动时

docker update --restart=always tracker
docker update --restart=always storage

5.3 文件存储微服务

创建文件管理微服务dongyimai-file-service,该工程主要用于实现文件上传以及文件删除等功能。

5.3.1 pom.xml依赖

修改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>dongyimai-service</artifactId>
        <groupId>com.offcn</groupId>
        <version>0.0.1</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>dongyimai-file-service</artifactId>

   <dependencies>
       <dependency>
           <groupId>cn.bestwu</groupId>
           <artifactId>fastdfs-client-java</artifactId>
           <version>1.27</version>
       </dependency>
       <dependency>
           <groupId>com.offcn</groupId>
           <artifactId>dongyimai-common</artifactId>
           <version>1.0</version>
       </dependency>
   </dependencies>

</project>

5.3.2 FastDFS配置

在resources文件夹下创建fasfDFS的配置文件fdfs_client.conf

#tracker服务器IP地址和端口号
tracker_server=192.168.188.128:22122

# 连接超时时间,针对socket套接字函数connect,默认为30秒
#connect_timeout=3000

# 网络通讯超时时间,默认是60秒
#network_timeout=60000

tracker_server: tracker服务器IP和端口设置

5.3.3 创建测试类TestStorageClient

代码如下:

package com.offcn;

import org.csource.common.MyException;
import org.csource.fastdfs.*;

import java.io.IOException;


public class TestFastDFS {
    public static void main(String[] args) throws IOException, MyException {
        // 1、加载配置文件,配置文件中的内容就是 tracker 服务的地址。
        ClientGlobal.init("C:\\idea_workspace_offcn\\2021shadow\\github\\dongyimai-parent\\dongyimai-service\\dongyimai-file-service\\src\\main\\resources\\fdfs_client.conf");
        // 2、创建一个 TrackerClient 对象。直接 new 一个。
        TrackerClient trackerClient = new TrackerClient();
        // 3、使用 TrackerClient 对象创建连接,获得一个 TrackerServer 对象。
        TrackerServer trackerServer = trackerClient.getConnection();
        // 4、创建一个 StorageServer 的引用,值为 null
        StorageServer storageServer = null;
        // 5、创建一个 StorageClient 对象,需要两个参数 TrackerServer 对象、StorageServer 的引用
        StorageClient storageClient = new StorageClient(trackerServer, storageServer);
        // 6、使用 StorageClient 对象上传图片。
        //扩展名不带“.”
        String[] strings = storageClient.upload_file("C:/1.jpg", "jpg",
                null);
        // 7、返回数组。包含组名和图片的路径。
        for (String string : strings) {
            System.out.println(string);
        }
    }

}

注意:如果出现连接图片服务器超时失败的情况,请检查图片linux服务器是否启动、是否开启了端口22122、23000以及8080的防火墙端口,如果未开启需要开启。

firewall-cmd --add-port=22122/tcp --permanent
firewall-cmd --add-port=23000/tcp --permanent
firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --reload

//停止防火墙
systemctl stop firewalld
//禁止开机启动防火墙
systemctl disable firewalld

控制台输出如下结果:

在浏览器输入:

http://192.168.188.156:8080/group1/M00/00/00/wKi8nGA1w0CAR9LXAAFehLrW1Wg805.jpg

即可看到所上传的图片。

5.3.4 application.yml配置

在resources文件夹下创建application.yml

spring:
  servlet:
    multipart:
      max-file-size: 5MB
      max-request-size: 10MB
  application:
    name: file
server:
  port: 9002
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
  instance:
    prefer-ip-address: true
    
FILE_SERVER_URL: http://192.168.188.128:8080/    

max-file-size是单个文件大小,max-request-size是设置总上传的数据大小

5.3.5 启动类

创建启动类FileApplication

@SpringBootApplication
@EnableEurekaClient
public class FileApplication {

    public static void main(String[] args) {
        SpringApplication.run(FileApplication.class);
    }
}

这里禁止了DataSource的加载创建。

5.3.6 文件上传

5.3.6.1 添加工具类

将“资源/fastDFS/工具类”的FastDFSClient.java 拷贝到com.offcn.util

5.3.6.2 控制层

com.offcn.controller中创建UploadController.java

package com.offcn.controller;

import com.offcn.entity.Result;
import com.offcn.entity.StatusCode;
import com.offcn.util.FastDFSClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;


@RestController
public class UploadController {

    @Value("${FILE_SERVER_URL}")
    private String FILE_SERVER_URL;//文件服务器地址

    @PostMapping("/upload")
    public Result upload(@RequestParam(name = "file") MultipartFile file) {
        //1、取文件的扩展名
        String originalFilename = file.getOriginalFilename();
        String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        try {
            //2、创建一个 FastDFS 的客户端
            FastDFSClient fastDFSClient = new FastDFSClient("classpath:fdfs_client.conf");
            //3、执行上传处理
            String path = fastDFSClient.uploadFile(file.getBytes(), extName);
            //4、拼接返回的 url 和 ip 地址,拼装成完整的 url
            String url = FILE_SERVER_URL + path;
            return new Result(true, StatusCode.OK, url);
        } catch (Exception e) {
            e.printStackTrace();
            return new Result(false, StatusCode.ERROR, "上传失败");
        }
    }

}

5.3.6.3 Postman测试文件上传

步骤:

1、选择post请求方式,输入请求地址 http://localhost:9002/upload

2、填写body

选择form-data 然后选择文件file 点击添加文件,最后发送即可。

3、访问http://192.168.188.156:8080//group1/M00/00/00/wKi8nGA17KaAUgXxAABgnQptHtk851.jpg

注意,这里每次访问的端口是8080端口,访问的端口其实是storage容器的nginx端口,如果想修改该端口可以直接进入到storage容器,然后修改即可。

docker exec -it storage  /bin/bash
vi /etc/nginx/conf/nginx.conf

修改后重启storage即可根据自己修改的端口访问图片了。


avatar
青山
悟已往之不谏 知来者之可追
一言
今日诗词
站点信息
本站访客数 :
本站总访问量 :