爽易购商城--后台管理

一、项目简介

1、项目背景

1)、电商模式
市面上有5种常见的电商模式:B2B、B2C、C2B、C2C、O2O

  1. B2B模式

    Business to Business,是指商家之间的商业关系,如:阿里巴巴;

  2. B2C模式

    Business to Consumer,就是我们经常看到的供应商直接把商品卖给用户,即“商对客”模式,也就是通常说的商业零售,直接面向消费者销售产品和服务。如:苏宁易购、京东、天猫等;

  3. C2B模式

    Customer to Business,即消费者对企业。先有消费者提出需求,后有企业组织生产;

  4. C2C模式

    Customer to Consumer,客户之间的交易,如:淘宝、咸鱼等;

  5. O2O模式

    Online to Offline,即是将线下商务的机会与互联网结合,让互联网称为线下交易的前台。线上快速支付,线下优质服务。如:饿了么、美团、淘票票、京东到家等;

2、架构图

技术架构图
结构
服务架构图
谷粒商城-服务架构

3、项目技术

  • 前后分离开发,基于vue的后台管理系统
  • SpringCloud全新解决方案
  • 应用监控、限流、网关、熔断降级等分布式方案
  • SpringBoot
  • SpringCloud
  • Nacos、Sentinel、Elasticsearch、git、redis、linux、vue、k8s

二、分布式基础概念

1、微服务

微服务架构风格,就像是把一个单独的应用程序开发为一套小服务,每个小服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API。这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。这些服务可以使用不同的编程语言书写,以及不同数据存储技术,并保持最低限度的集中式管理。
简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个服务独立部署运行。

2、集群&分布式&节点

集群是个物理形态,分布式是个工作方式。
只要是一堆机器,就可以叫集群,他们是不是一起协作工作,这个谁也不知道。
《分布式系统原理与规范》定义:

“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”

分布式系统(distribution system)是建立在网络之上的软件系统。

分布式是指将不同的业务分布在不同的地方。

集群是指将几台服务器集中在一起,实现同一业务。

例如:京东是一个分布式系统,众多业务运行在不同的机器,所有业务构成一个大型业务集群。每一个小的业务,比如用户系统,访问压力大时一个服务器不够用,我们就应该将用户系统部署到多个服务器,也就是每一个业务系统都可以做集群。

分布式中的每一个节点都可以做集群。集群并不一定是分布式。

节点:集群中的一个服务器。

3、远程调用

在分布式系统中,各个服务可能处于不同主机,但服务之间不可避免要互相调用,我们称为远程调用。

SpringCloud中使用HTTP + JSON的方式来完成远程调用

4、负载均衡

分布式系统中,A服务调用B服务,B服务部署集群,A调用任意一个均可完成功能。

为了使每一台服务器都不要太忙或太闲,我们可以负载均衡的调用每一个服务器,提升网站的健壮性。

常见的负载均衡算法:

**轮询:**按照顺序访问每一台服务器。

**最小连接:**优先选择连接数最少,也就是压力最小的服务器。

**散列:**根据请求源IP的hash值来选择要转发的服务器。可以解决session的问题。

5、服务注册/发现&注册中心

A服务调用B服务,但不知道B服务在哪台服务器上,哪些是正常或下线的。解决这个问题可以引入注册中心;


我们可以实时感知到服务的状态,从而避免调用不可用的服务。

6、配置中心

每一个服务最终都有大量配置,并且每个服务都可能部署在多台服务器上。我们经常需要变更配置,因此工作量巨大。我们可以引入配置中心,让每个服务在配置中心获取自己的配置。

**配置中心用来集中管理微服务的配置信息 **

7、服务熔断&服务降级

在微服务架构中,微服务之间通过网络通信,存在相互依赖,当一个服务不可用,有可能出现雪崩效应。要防止这样的情况,必须要有容错机制。

1)、服务熔断

设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启断路保护机制,后来的请求不再去调用这个服务。本地直接返回默认数据。

2)、服务降级

在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业务奖及运行。

降级:某些服务不处理,或者简单处理——抛异常 | 返回Null、调用Mock数据、调用faullback处理逻辑。

8、API网关

在微服务架构中,API Gateway作为整体架构的重要组件,它抽象了微服务中都需要的功能,同时提供客户端负载均衡,服务自动熔断,灰度发布,统一认证,限流留空、日志统计等丰富功能,帮助我们解决很多API管理难题。

三、环境搭建

  1. 安装Linux虚拟机

    本次项目使用阿里的云服务器:ip:112.124.32.136

  2. 安装docker

  3. docker安装mysql

  4. docker安装redis

5、 开发环境

  1. Maven
  2. Idea插件
    • lombok
    • mybatisx
  3. VS Code 插件
  4. Git

6、创建项目微服务

商品服务、仓储服务、订单服务、优惠券服务、用户服务

共同:

  1. 依赖: web、openfeign
  2. groupId: com.qhit.shuang
  3. 包名: com.qhit.shuang.xxx(product/ware/order/coupon/member)
  4. 模块名: shuang-xxx((product/ware/order/coupon/member)

.gitignore添加以下内容

**/mvnw
**/mvnw.cmd
**/.mvn
**/target/
.idea
**/.gitignore

四、后台管理系统

本项目的后台管理系统采用人人开源提供的后台系统快速进行搭建

接下来使用renren-generator对各个模块进行代码生成

成功生成!

接下来创建公共模块shuang-common

<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.4.15</version>
</dependency>
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>${commons.lang.version}</version>
</dependency>
<!--AOP-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>    

五、技术整合

1、mybatis-plus

1)、引入依赖

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.4.2</version>
</dependency>

2)、配置

  1. 配置数据源

    1)、导入数据库驱动

    <!--mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.21</version>
    </dependency>
    

    2)、在application.yml中配置数据源

    spring:
      # 数据源配置
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://112.124.32.136:3306/shuang_oms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
        password: root
    
  2. 配置mybatis-plus

    1)、启动类添加MapperScan注解

    @MapperScan("com.qhit.shuang.product.dao") //dao层接口全路径
    

    2)、application.yml中配置sql映射文件位置

    mybatis-plus:
    mapper-locations: classpath:/mapper/**/*.xml
    

2、微服务

1)、简介

SpringCloud的几大痛点

  • 部分组件停止维护和更新,给开发带来不便
  • 部分环境搭建复杂,没有完善的可视化界面,我们需要大量的二次开发和定制
  • 配置复杂,难以上手,部分配置差别难以区分和合理应用

SpringCloud Alibaba的优势

  • 性能强悍,设计合理,开源
  • 成套的产品搭配,完善的可视化界面
  • 搭建简单

结合Alibaba最终的技术搭配方案

  1. SpringCloud Alibaba - Nacos:注册中心 (服务发现和注册)
  2. SpringCloud Alibaba - Nacos:配置中心(动态配置管理)
  3. SpringCloud - Ribbon:负载均衡
  4. SpringCloud - Open Feign:远程调用(声明式HTTP客户端)
  5. SpringCloud Alibaba - Sentinel:服务容错(限流、降级、熔断)
  6. SpringCloud - Gateway:API网关(webflux编程模式)
  7. SpringCloud - Sleuth:调用链监控
  8. SpringCloud Alibaba - Seata:原Fescar,即分布式事务解决方案

github地址:https://github.com/alibaba/spring-cloud-alibaba

版本对应关系

2)、远程调用

  1. 引入open-feign依赖

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  2. 编写本地接口作为远程方法映射

    @FeignClient("服务名:application.name")
    public interface 接口名:自定义|示例:feign/UserFeignService.java {
    
      @RequestMapping("远程方法全路径")
      public R list(@RequestParam Map<String, Object> params); // 远程方法签名
    }
    
  3. 开启远程调用功能

    启动类上添加注解

    @EnableFeignClients(basePackages = "映射接口所在包的全路径,以‘.’分隔")
    

3)、API网关

gateway
当请求到达网关,网关会断言请求是否符合某个路由规则,若符合,就按照路由规则将请求路由到指定服务,中间会经过一系列过滤器

断言文档:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gateway-request-predicates-factories

过滤器文档:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

  1. 创建项目shuang-gateway
  2. 添加依赖
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifact>
</dependency>
  1. 使用断言
spring:
  cloud:
    gateway:
      routes:
      - id: after_route	# 路由id
        uri: https://example.org	#路由地址
        predicates:
        #After断言:会匹配指定时间点之后的请求
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
  1. 使用过滤器
spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route # 路由id
        uri: https://example.org #路由地址
        filters:
        # 添加请求头过滤器:将为请求头添加red,blue键值对
        - AddRequestHeader=X-Request-red, blue

前后端技术栈类比
在这里插入图片描述

4)、Nacos

https://blog.csdn.net/weixin_44338156/article/details/122338287

六、前端

前端使用VueElementUI
vue官网文档:https://cn.vuejs.org/v2/guide/
ElementUI官网文档:https://element-plus.gitee.io/zh-CN/
Vue组件模板:https://www.cnblogs.com/songjilong/p/12635448.html

1、分类维护

1)、三级分类

三级分类

  1. 在后台管理系统添加商品系统目录
    商品系统
  2. 添加分类维护菜单
    分类维护
  3. 创建category视图
    在这里插入图片描述
  4. 添加分类
    ElementUI官网的树形控件:https://element.eleme.cn/#/zh-CN/component/tree
    在这里插入图片描述
    在这里插入图片描述
  5. 增加单个增删改功能
    在这里插入图片描述
<el-button
	type="text"
    size="mini"
    @click="append(data)"
    v-if="node.level < 3" <!--三级菜单不允许添加子菜单-->
  >
  Append
</el-button>
<el-button
	type="text"
	size="mini"
	@click="edit(data)"
>
  Edit
</el-button>
<el-button
	type="text"
	size="mini"
	@click="remove(node, data)"
	v-if="node.childNodes.length == 0" <!--当子节点为空时显示删除按钮-->
>
    Delete
</el-button>

在开发中,相较于物理删除,多使用逻辑删除
这里使用mybatis-plus提供的逻辑删除功能:https://mp.baomidou.com/guide/logic-delete.html

2)、拖拽节点

ElementUI的树形控件有一个参数draggable,将其设为true即可拖拽
draggable
但仅仅这样,拖拽可到达任意位置的同时,也会产生超过三级的列表;我们可以设置allow-drop参数来判断当前位置能否被放置
allow-drop
拖拽成功执行的函数
在这里插入图片描述

allowDrop(draggingNode, dropNode, type) { //是否允许拖拽
  //求出被拖拽节点最大深度
  this.countNodeLevel(draggingNode)
  //被拖拽节点作为顶级节点的最大深度
  let deep = Math.abs(this.maxLevel - draggingNode.level) + 1
  this.maxLevel = 1
  if (type == 'inner')  //放置目标节点之中
    return (deep + dropNode.level) <= 3
  else	//目标节点之前或之后
    return (deep + dropNode.data.catLevel - 1) <= 3
},
countNodeLevel(node) { //求出被拖拽节点的最大深度,比如 1-node-3,深度为2
  if (node.childNodes != null && node.childNodes.length > 0) { //是否存在子节点
    for (let i = 0; i < node.childNodes.length; i++) {	//迭代子节点
      let child = node.childNodes[i]
      if (child.level > this.maxLevel)	//若子节点深度大于当前最大深度,则替换
        this.maxLevel = child.level
      this.countNodeLevel(child)	//递归子节点求出最大深度
    }
  }
},
handleDrop(draggingNode, dropNode, dropType, ev) { //拖拽成功触发的函数
  //1. 获取当前节点最新的父节点id
  let pCid = 0
  let siblings = null
  if (dropType == 'inner') {
    pCid = dropNode.data.catId
    siblings = dropNode.childNodes
  }
  else {
    pCid = dropNode.parent.data.catId || 0
    siblings = dropNode.parent.childNodes
  }
  // 2. 被拖拽节点最新数据
  siblings.forEach((node, index) => {
    if (draggingNode.data.catId == node.data.catId) {
      let level = draggingNode.level
      if (node.level != level) {
        level = node.level
        this.updateChildsLevel(node)
      }
      this.updateNodes.push({ catId: node.data.catId, sort: index, parentCid: pCid, catLevel: level })
    } else {
      this.updateNodes.push({ catId: node.data.catId, sort: index })
    }
  })
  this.pCid.push(pCid)
},
updateChildsLevel(node) { //拖拽后更新子节点
  node.childNodes.forEach(ele => {
    this.updateNodes.push({ catId: ele.data.catId, catLevel: ele.level })
    this.updateChildsLevel(ele)
  })
},
batchSave() { //批量拖拽保存
  this.$http({
    url: this.$http.adornUrl('/product/category/update/sort'),
    method: 'post',
    data: this.$http.adornData(this.updateNodes, false)
  }).then(({ data }) => {
    this.$message({
      type: 'success',
      message: '更新成功!'
    })
    this.getMenus()
    this.expandedKeys = this.pCid
    this.updateNodes = []
    this.maxLevel = 1
  })
}

3)、批量删除

getCheckedNodes:获取被选中的节点
在这里插入图片描述

// 批量删除
batchDel() {
  let checkedNodes = this.$refs.menuTree.getCheckedNodes() // 获取选中节点
  let catIds = checkedNodes.map(node => node.catId)  // 选中节点的id
  let names = checkedNodes.map(node => node.name)  // 选中节点的name
  let parentCids = checkedNodes.map(node => node.parentCid)  // 删除节点的父id,用来展开
  this.$confirm(`是否确认删除:【${names}`, '提示', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warnning'
  }).then(() => {
    this.$http({
      url: this.$http.adornUrl('/product/category/delete'),
      method: 'post',
      data: this.$http.adornData(catIds, false)
    }).then(({ data }) => {
      this.$message({
        message: '删除成功',
        type: 'success'
      })
      this.getMenus()
      this.expandedKeys = parentCids
    });
  }).catch(() => {
    this.$message({
      message: '已取消',
      type: 'warnning'
    })
  })
}

2、品牌管理

  1. 添加品牌管理菜单
    在这里插入图片描述
  2. 创建brand视图
    将之前逆向工程生成的组件拿来
    在这里插入图片描述
    这里会判断是否拥有新增和删除的权限,将其移除或者return true
    在这里插入图片描述

1)、显示状态

Table表格中有一个自定义模板,我们使用它来控制品牌的显示状态
在这里插入图片描述
Switch开关也拿来
在这里插入图片描述
使用Switch的Event来监听change
switch-change

最终效果:

<el-table-column
  prop="showStatus"
  header-align="center"
  align="center"
  label="显示状态"
>
  <template slot-scope="scope">
    <el-switch
      v-model="scope.row.showStatus"
      active-color="#13ce66"
      inactive-color="#ff4949"
      @change="updateBrandStatus(scope.row)"
      :active-value="1"
      :inactive-value="0"
    >
    </el-switch>
  </template>
</el-table-column>
// 更新显示状态
updateBrandStatus(brand) {
  let { brandId, showStatus } = brand
  this.$http({
    url: this.$http.adornUrl('/product/brand/update'),
    method: 'post',
    data: this.$http.adornData({ brandId, showStatus }, false)
  }).then(({ data }) => {
    this.$message({
      message: '已更新',
      type: 'success'
    })
  })
}

在这里插入图片描述

2)、文件上传

https://element.eleme.cn/#/zh-CN/component/upload
在这里插入图片描述
policy.js

import http from '@/utils/httpRequest.js'
export function policy() {
    return new Promise((resolve, reject) => {
        http({
            url: http.adornUrl('/thirdparty/oss/policy'),
            method: 'get'
        }).then(({ data }) => {
            resolve(data)
        })
    })
}
beforeUpload() {
  let _self = this;
  return policy().then(response => {
    console.log('响应的数据:', response);
    _self.dataObj.policy = response.data.policy
    _self.dataObj.signature = response.data.signature
    _self.dataObj.ossaccessKeyId = response.data.ossaccessKeyId
    _self.dataObj.key = response.data.dir + '/' + getUUID + '_${filename}'
    _self.dataObj.dir = response.data.dir
    _self.dataObj.host = response.data.host
    console.log('响应数据:' + _self.dataObj);
  }).catch(err => {
    reject(false)
  })
}
跨域

跨域错误
在这里插入图片描述
在这里开启跨域访问
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

七、第三方服务模块

此模块用来提供第三方服务

1. 创建

在这里插入图片描述
在这里插入图片描述
引入common模块

<dependency>
  <groupId>com.qhit.shuang</groupId>
  <artifactId>shuang-common</artifactId>
  <version>1.0.0</version>
</dependency>

引入依赖管理

<dependencyManagement>
   <dependencies>
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-dependencies</artifactId>
           <version>${spring-cloud.version}</version>
           <type>pom</type>
           <scope>import</scope>
       </dependency>
       <dependency>
           <groupId>com.alibaba.cloud</groupId>
           <artifactId>spring-cloud-alibaba-dependencies</artifactId>
           <version>2.1.2.RELEASE</version>
           <type>pom</type>
           <scope>import</scope>
       </dependency>
   </dependencies>
</dependencyManagement>

2. 文件上传

在这里插入图片描述

SpringCloud Alibaba-OSS

1)、介绍

上传文件的方式:

  • 用户上传到应用服务器,由应用服务器携带凭证发给OSS
  • 用户直接携带凭证上传到OSS
  • 用户向应用服务器请求上传Policy,应用服务器返回上传Policy,用户上传文件到OSS
    第一种方式会给服务器带来很大的压力,第二种方式存在安全隐患,因此我们选择第三种

服务端签名后直传:https://help.aliyun.com/document_detail/91868.htm?spm=a2c4g.11186623.0.0.16075d3f6zYO9b#concept-ahk-rfz-2fb

2)、使用
  1. 引入gav坐标
    <!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alicloud-oss -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
        <version>2.1.0.RELEASE</version>
    </dependency>
    
  2. 配置AccessKey
    spring:
      cloud:
        alicloud:
          access-key: your-ak
          secret-key: your-sk
          oss:
            endpoint: oss-cn-hangzhou.aliyuncs.com #地域节点,此为示例
    

3. 新增品牌

1)、前端校验

ElementUI表单校验:https://element.eleme.cn/#/zh-CN/component/form
Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。校验规则参见 async-validator
在这里插入图片描述
还可以自定义校验规则:在这里插入图片描述

2)、后端校验

前端校验方便用户的同时,也减轻了服务器压力
但当恶意访问者绕过浏览器,通过Postman等类似工具进行访问时,前端校验未必有用,此时我们需要添加后端校验

我们此次使用JSR303

错误码

自定义错误码以便快速精确定位错误
放在公共模块以便所有模块使用
BizCodeEnume

4. 属性分组

后端统一异常处理

ExceptionControllerAdvice.java

bug

找不到python问题

**问题描述:**在github上clone下的项目执行npm install时报python环境错误

确保windows已安装node.js/vue/vue-cli

一、cmd下运行npm install -globabl -production windows-build-tools一键安装

  1. python(v2.7 ,3.x不支持);
  2. visual C++ Build Tools,或者 (vs2015以上(包含15))
  3. .net framework 4.5.1

二、在控制台输入:npm install -g node-gyp安装node-gyp
三、安装后检查:node-gyp list
最后到项目下执行npm install…成功。。。
后记:
如果还是提示“python找不到或者环境不对”

npm config set python C:\Users\Administrator\.windows-build-tools\python27\python.exe

跨域问题

跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域。
跨域问题
解决方案一:使用Nginx部署为同一域
在这里插入图片描述
解决方案二:配置当前请求允许跨域

  1. 添加响应头
    • Access-Control-Allow-Origin:支持哪些来源的请求跨域
    • Accss-Control-Allow-Methods:支持哪些方法跨域
    • Access-Control-Allow-Credentials:跨域请求默认不包含cookie,设置true包含
    • Access-Control-Expose-Headers:跨域请求暴漏的字段
      • CORS请求时,XMLHttpRequest对下国内的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果想要拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
    • Access-Control-Max-Age:表迷宫该响应的有效时间为多少秒。有效时间内,浏览器无需为同一请求再次发起预见请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将失效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值